基于STM32 SPI通信的驱动代码

简介: 基于STM32 SPI通信的驱动代码 ,包含DMA传输、中断处理、数据读写、错误处理,可以直接用于W25Qxx、SD卡、OLED屏、传感器等SPI设备

基于STM32 SPI通信的驱动代码 ,包含DMA传输、中断处理、数据读写、错误处理,可以直接用于W25Qxx、SD卡、OLED屏、传感器等SPI设备。


一、STM32 SPI核心特性

模式 CPOL CPHA 数据采样 适用设备
模式0 0 0 第一个边沿 大部分SPI设备
模式1 0 1 第二个边沿 特殊设备
模式2 1 0 第二个边沿 特殊设备
模式3 1 1 第一个边沿 特殊设备

推荐配置

  • 模式:Mode 0 (CPOL=0, CPHA=0)
  • 数据位:8位
  • 传输顺序:MSB First
  • 时钟极性:Low
  • 数据采样:1 Edge

二、硬件连接

1、SPI引脚定义

SPI1 (APB2总线,最高36MHz):
PA4 → NSS   (片选)
PA5 → SCK   (时钟)
PA6 → MISO  (主入从出)
PA7 → MOSI  (主出从入)

2、设备片选控制

设备1_CS → PB0
设备2_CS → PB1
设备3_CS → PB2

注意:NSS硬件管理通常关闭,用GPIO软件控制CS


三、CubeMX配置

1、SPI配置参数

Mode: Full-Duplex Master
Data Size: 8 bits
First Bit: MSB First
Baud Rate: 9MHz (APB2/4)
CPOL: Low
CPHA: 1 Edge
NSS: Soft
CRC: Disable

2、DMA配置

SPI_TX: DMA1 Channel3
SPI_RX: DMA1 Channel2
Mode: Normal
Increment: Memory
Data Width: Byte

四、SPI驱动头文件

// spi_driver.h
#ifndef __SPI_DRIVER_H
#define __SPI_DRIVER_H

#include "stm32f1xx_hal.h"

// SPI设备枚举
typedef enum {
   
    SPI_DEVICE_FLASH = 0,   // W25Q64
    SPI_DEVICE_SDCARD,      // SD卡
    SPI_DEVICE_OLED,        // OLED屏幕
    SPI_DEVICE_MAX
} SPI_Device_t;

// SPI传输状态
typedef enum {
   
    SPI_STATE_READY = 0,
    SPI_STATE_BUSY,
    SPI_STATE_ERROR
} SPI_State_t;

// SPI传输结构
typedef struct {
   
    uint8_t *tx_buffer;
    uint8_t *rx_buffer;
    uint16_t tx_size;
    uint16_t rx_size;
    uint16_t tx_index;
    uint16_t rx_index;
    SPI_State_t state;
    uint8_t dma_enabled;
} SPI_Transfer_t;

// SPI设备配置
typedef struct {
   
    GPIO_TypeDef *cs_port;
    uint16_t cs_pin;
    uint32_t timeout;
    uint8_t dummy_byte;
} SPI_Device_Config_t;

// 函数声明
void SPI_Init(void);
void SPI_Select_Device(SPI_Device_t device);
void SPI_Deselect_Device(SPI_Device_t device);
uint8_t SPI_Transmit_Receive(uint8_t *tx_data, uint8_t *rx_data, uint16_t size);
uint8_t SPI_Transmit(uint8_t *data, uint16_t size);
uint8_t SPI_Receive(uint8_t *data, uint16_t size);
void SPI_Transmit_IT(uint8_t *data, uint16_t size);
void SPI_Receive_IT(uint8_t *data, uint16_t size);
void SPI_Transmit_Receive_DMA(uint8_t *tx_data, uint8_t *rx_data, uint16_t size);
uint8_t SPI_Is_Busy(void);
void SPI_Wait_Ready(void);

// 设备特定函数
uint8_t W25Q64_Read_ID(void);
void W25Q64_Read_Data(uint32_t addr, uint8_t *data, uint32_t size);
void W25Q64_Write_Page(uint32_t addr, uint8_t *data, uint16_t size);
void W25Q64_Erase_Sector(uint32_t addr);

#endif

五、SPI驱动实现

1、SPI初始化

// spi_driver.c
#include "spi_driver.h"
#include <string.h>

// SPI句柄
SPI_HandleTypeDef hspi1;
DMA_HandleTypeDef hdma_spi1_tx;
DMA_HandleTypeDef hdma_spi1_rx;

// 传输控制结构
static SPI_Transfer_t spi_transfer = {
   0};

// 设备配置
static SPI_Device_Config_t spi_devices[SPI_DEVICE_MAX] = {
   
    // W25Q64 Flash
    {
   GPIOB, GPIO_PIN_0, 100, 0xFF},
    // SD卡
    {
   GPIOB, GPIO_PIN_1, 1000, 0xFF},
    // OLED
    {
   GPIOB, GPIO_PIN_2, 10, 0x00}
};

// SPI初始化
void SPI_Init(void)
{
   
    GPIO_InitTypeDef GPIO_InitStruct = {
   0};

    // 1. 使能时钟
    __HAL_RCC_SPI1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_DMA1_CLK_ENABLE();

    // 2. 配置SPI引脚
    // PA5: SCK, PA6: MISO, PA7: MOSI
    GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 3. 配置CS引脚
    for (int i = 0; i < SPI_DEVICE_MAX; i++) {
   
        GPIO_InitStruct.Pin = spi_devices[i].cs_pin;
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(spi_devices[i].cs_port, &GPIO_InitStruct);

        // 默认取消选中
        HAL_GPIO_WritePin(spi_devices[i].cs_port, 
                         spi_devices[i].cs_pin, GPIO_PIN_SET);
    }

    // 4. 初始化SPI
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;  // 9MHz
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLED;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
    hspi1.Init.CRCPolynomial = 10;

    HAL_SPI_Init(&hspi1);

    // 5. 初始化DMA
    // TX DMA (SPI1 -> 外设)
    hdma_spi1_tx.Instance = DMA1_Channel3;
    hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi1_tx.Init.Mode = DMA_NORMAL;
    hdma_spi1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
    HAL_DMA_Init(&hdma_spi1_tx);

    // RX DMA (外设 -> SPI1)
    hdma_spi1_rx.Instance = DMA1_Channel2;
    hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi1_rx.Init.Mode = DMA_NORMAL;
    hdma_spi1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
    HAL_DMA_Init(&hdma_spi1_rx);

    // 关联DMA到SPI
    __HAL_LINKDMA(&hspi1, hdmatx, hdma_spi1_tx);
    __HAL_LINKDMA(&hspi1, hdmarx, hdma_spi1_rx);

    // 6. 启用DMA中断
    HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);
    HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
    HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);

    // 7. 启用SPI
    __HAL_SPI_ENABLE(&hspi1);

    spi_transfer.state = SPI_STATE_READY;
}

2、设备选择函数

// 选择设备
void SPI_Select_Device(SPI_Device_t device)
{
   
    if (device >= SPI_DEVICE_MAX) return;

    HAL_GPIO_WritePin(spi_devices[device].cs_port, 
                     spi_devices[device].cs_pin, GPIO_PIN_RESET);

    // 短暂延时,确保设备准备好
    __NOP(); __NOP(); __NOP(); __NOP();
}

// 取消选择设备
void SPI_Deselect_Device(SPI_Device_t device)
{
   
    if (device >= SPI_DEVICE_MAX) return;

    // 等待传输完成
    SPI_Wait_Ready();

    HAL_GPIO_WritePin(spi_devices[device].cs_port, 
                     spi_devices[device].cs_pin, GPIO_PIN_SET);

    // 短暂延时
    __NOP(); __NOP(); __NOP(); __NOP();
}

3、阻塞式传输(最简单)

// 阻塞式发送接收
uint8_t SPI_Transmit_Receive(uint8_t *tx_data, uint8_t *rx_data, uint16_t size)
{
   
    HAL_StatusTypeDef status;

    if (spi_transfer.state != SPI_STATE_READY) {
   
        return 0;
    }

    spi_transfer.state = SPI_STATE_BUSY;

    // 使用HAL库的阻塞传输
    status = HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, size, 1000);

    spi_transfer.state = SPI_STATE_READY;

    return (status == HAL_OK);
}

// 阻塞式发送
uint8_t SPI_Transmit(uint8_t *data, uint16_t size)
{
   
    return SPI_Transmit_Receive(data, NULL, size);
}

// 阻塞式接收
uint8_t SPI_Receive(uint8_t *data, uint16_t size)
{
   
    // 接收需要发送虚拟数据
    uint8_t dummy;
    uint8_t *dummy_ptr = (data != NULL) ? data : &dummy;

    return SPI_Transmit_Receive(NULL, dummy_ptr, size);
}

4、中断式传输(不阻塞)

// 中断发送
void SPI_Transmit_IT(uint8_t *data, uint16_t size)
{
   
    if (spi_transfer.state != SPI_STATE_READY) {
   
        return;
    }

    spi_transfer.state = SPI_STATE_BUSY;
    spi_transfer.tx_buffer = data;
    spi_transfer.tx_size = size;
    spi_transfer.dma_enabled = 0;

    HAL_SPI_Transmit_IT(&hspi1, data, size);
}

// 中断接收
void SPI_Receive_IT(uint8_t *data, uint16_t size)
{
   
    if (spi_transfer.state != SPI_STATE_READY) {
   
        return;
    }

    spi_transfer.state = SPI_STATE_BUSY;
    spi_transfer.rx_buffer = data;
    spi_transfer.rx_size = size;
    spi_transfer.dma_enabled = 0;

    HAL_SPI_Receive_IT(&hspi1, data, size);
}

5、DMA传输(最快)

// DMA发送接收
void SPI_Transmit_Receive_DMA(uint8_t *tx_data, uint8_t *rx_data, uint16_t size)
{
   
    if (spi_transfer.state != SPI_STATE_READY) {
   
        return;
    }

    spi_transfer.state = SPI_STATE_BUSY;
    spi_transfer.tx_buffer = tx_data;
    spi_transfer.rx_buffer = rx_data;
    spi_transfer.tx_size = size;
    spi_transfer.rx_size = size;
    spi_transfer.dma_enabled = 1;

    // 启动DMA传输
    HAL_SPI_TransmitReceive_DMA(&hspi1, tx_data, rx_data, size);
}

// 检查SPI是否繁忙
uint8_t SPI_Is_Busy(void)
{
   
    return (spi_transfer.state == SPI_STATE_BUSY);
}

// 等待SPI就绪
void SPI_Wait_Ready(void)
{
   
    while (SPI_Is_Busy()) {
   
        __NOP();
    }
}

6、SPI中断处理

// SPI中断回调
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
   
    if (hspi->Instance == SPI1) {
   
        spi_transfer.state = SPI_STATE_READY;

        // 可以在这里添加传输完成回调
        if (spi_transfer.dma_enabled) {
   
            // DMA传输完成处理
        } else {
   
            // 中断传输完成处理
        }
    }
}

// SPI错误回调
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
   
    if (hspi->Instance == SPI1) {
   
        spi_transfer.state = SPI_STATE_ERROR;

        // 错误处理
        uint32_t error = HAL_SPI_GetError(hspi);

        if (error & HAL_SPI_ERROR_MODF) {
   
            // 模式错误
        }
        if (error & HAL_SPI_ERROR_CRC) {
   
            // CRC错误
        }
        if (error & HAL_SPI_ERROR_OVR) {
   
            // 溢出错误
        }
        if (error & HAL_SPI_ERROR_FRE) {
   
            // 帧格式错误
        }
        if (error & HAL_SPI_ERROR_DMA) {
   
            // DMA错误
        }

        // 重置SPI
        __HAL_SPI_DISABLE(hspi);
        __HAL_SPI_ENABLE(hspi);
    }
}

7、DMA中断处理

// DMA中断服务函数
void DMA1_Channel2_IRQHandler(void)
{
   
    HAL_DMA_IRQHandler(&hdma_spi1_rx);
}

void DMA1_Channel3_IRQHandler(void)
{
   
    HAL_DMA_IRQHandler(&hdma_spi1_tx);
}

// DMA传输完成回调
void HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi) 
{
   
    // 半传输完成
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
   
    // 接收完成
}

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
   
    // 发送完成
}

六、W25Q64 Flash驱动示例

// w25q64.c
#include "spi_driver.h"
#include <string.h>

// W25Q64命令定义
#define W25Q64_CMD_WRITE_ENABLE     0x06
#define W25Q64_CMD_WRITE_DISABLE    0x04
#define W25Q64_CMD_READ_STATUS_REG1 0x05
#define W25Q64_CMD_READ_STATUS_REG2 0x35
#define W25Q64_CMD_WRITE_STATUS_REG 0x01
#define W25Q64_CMD_PAGE_PROGRAM     0x02
#define W25Q64_CMD_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_CMD_BLOCK_ERASE      0xD8
#define W25Q64_CMD_SECTOR_ERASE     0x20
#define W25Q64_CMD_CHIP_ERASE       0xC7
#define W25Q64_CMD_ERASE_SUSPEND    0x75
#define W25Q64_CMD_ERASE_RESUME     0x7A
#define W25Q64_CMD_POWER_DOWN       0xB9
#define W25Q64_CMD_HIGH_PERFORMANCE 0xA3
#define W25Q64_CMD_READ_DATA        0x03
#define W25Q64_CMD_FAST_READ        0x0B
#define W25Q64_CMD_READ_JEDEC_ID    0x9F
#define W25Q64_CMD_RELEASE_POWER_DOWN 0xAB

// 状态寄存器位
#define W25Q64_STATUS_BUSY          0x01
#define W25Q64_STATUS_WRITE_ENABLE  0x02

// 读取设备ID
uint8_t W25Q64_Read_ID(void)
{
   
    uint8_t tx_buffer[4] = {
   0};
    uint8_t rx_buffer[4] = {
   0};

    tx_buffer[0] = W25Q64_CMD_READ_JEDEC_ID;

    SPI_Select_Device(SPI_DEVICE_FLASH);
    SPI_Transmit_Receive(tx_buffer, rx_buffer, 4);
    SPI_Deselect_Device(SPI_DEVICE_FLASH);

    // rx_buffer[1] = Manufacturer ID
    // rx_buffer[2] = Memory Type
    // rx_buffer[3] = Capacity
    return rx_buffer[3];
}

// 读取状态寄存器
static uint8_t W25Q64_Read_Status_Reg1(void)
{
   
    uint8_t tx_buffer[2] = {
   W25Q64_CMD_READ_STATUS_REG1, 0x00};
    uint8_t rx_buffer[2] = {
   0};

    SPI_Select_Device(SPI_DEVICE_FLASH);
    SPI_Transmit_Receive(tx_buffer, rx_buffer, 2);
    SPI_Deselect_Device(SPI_DEVICE_FLASH);

    return rx_buffer[1];
}

// 等待Flash就绪
static void W25Q64_Wait_Busy(void)
{
   
    while (W25Q64_Read_Status_Reg1() & W25Q64_STATUS_BUSY) {
   
        // 等待
    }
}

// 写使能
static void W25Q64_Write_Enable(void)
{
   
    uint8_t cmd = W25Q64_CMD_WRITE_ENABLE;

    SPI_Select_Device(SPI_DEVICE_FLASH);
    SPI_Transmit(&cmd, 1);
    SPI_Deselect_Device(SPI_DEVICE_FLASH);
}

// 读取数据
void W25Q64_Read_Data(uint32_t addr, uint8_t *data, uint32_t size)
{
   
    uint8_t tx_buffer[5];

    // 构建读取命令
    tx_buffer[0] = W25Q64_CMD_READ_DATA;
    tx_buffer[1] = (addr >> 16) & 0xFF;  // 地址高字节
    tx_buffer[2] = (addr >> 8) & 0xFF;   // 地址中字节
    tx_buffer[3] = addr & 0xFF;          // 地址低字节

    SPI_Select_Device(SPI_DEVICE_FLASH);

    // 发送命令和地址
    SPI_Transmit(tx_buffer, 4);

    // 接收数据
    if (data != NULL) {
   
        SPI_Receive(data, size);
    } else {
   
        // 如果data为NULL,只接收不存储
        uint8_t dummy;
        for (uint32_t i = 0; i < size; i++) {
   
            SPI_Receive(&dummy, 1);
        }
    }

    SPI_Deselect_Device(SPI_DEVICE_FLASH);
}

// 写入一页数据(最大256字节)
void W25Q64_Write_Page(uint32_t addr, uint8_t *data, uint16_t size)
{
   
    uint8_t tx_buffer[4];

    if (size > 256) size = 256;  // 限制页大小

    // 等待Flash就绪
    W25Q64_Wait_Busy();

    // 写使能
    W25Q64_Write_Enable();

    // 构建页编程命令
    tx_buffer[0] = W25Q64_CMD_PAGE_PROGRAM;
    tx_buffer[1] = (addr >> 16) & 0xFF;
    tx_buffer[2] = (addr >> 8) & 0xFF;
    tx_buffer[3] = addr & 0xFF;

    SPI_Select_Device(SPI_DEVICE_FLASH);

    // 发送命令和地址
    SPI_Transmit(tx_buffer, 4);

    // 发送数据
    SPI_Transmit(data, size);

    SPI_Deselect_Device(SPI_DEVICE_FLASH);

    // 等待写入完成
    W25Q64_Wait_Busy();
}

// 擦除扇区(4KB)
void W25Q64_Erase_Sector(uint32_t addr)
{
   
    uint8_t tx_buffer[4];

    // 等待Flash就绪
    W25Q64_Wait_Busy();

    // 写使能
    W25Q64_Write_Enable();

    // 构建扇区擦除命令
    tx_buffer[0] = W25Q64_CMD_SECTOR_ERASE;
    tx_buffer[1] = (addr >> 16) & 0xFF;
    tx_buffer[2] = (addr >> 8) & 0xFF;
    tx_buffer[3] = addr & 0xFF;

    SPI_Select_Device(SPI_DEVICE_FLASH);
    SPI_Transmit(tx_buffer, 4);
    SPI_Deselect_Device(SPI_DEVICE_FLASH);

    // 等待擦除完成
    W25Q64_Wait_Busy();
}

七、主程序示例

// main.c
#include "main.h"
#include "spi_driver.h"
#include <string.h>

int main(void)
{
   
    HAL_Init();
    SystemClock_Config();

    // 初始化SPI
    SPI_Init();

    // 1. 测试Flash ID
    uint8_t flash_id = W25Q64_Read_ID();
    printf("Flash ID: 0x%02X\r\n", flash_id);

    // 2. 测试数据读写
    uint8_t write_buffer[256];
    uint8_t read_buffer[256];

    // 填充测试数据
    for (int i = 0; i < 256; i++) {
   
        write_buffer[i] = i;
    }

    // 擦除扇区
    printf("Erasing sector...\r\n");
    W25Q64_Erase_Sector(0x000000);

    // 写入数据
    printf("Writing data...\r\n");
    W25Q64_Write_Page(0x000000, write_buffer, 256);

    // 读取数据
    printf("Reading data...\r\n");
    W25Q64_Read_Data(0x000000, read_buffer, 256);

    // 验证数据
    if (memcmp(write_buffer, read_buffer, 256) == 0) {
   
        printf("SPI Flash test PASS!\r\n");
    } else {
   
        printf("SPI Flash test FAIL!\r\n");
    }

    // 3. 测试DMA传输
    uint8_t dma_tx_buffer[1024];
    uint8_t dma_rx_buffer[1024];

    for (int i = 0; i < 1024; i++) {
   
        dma_tx_buffer[i] = 0xAA;
    }

    // 使用DMA传输
    printf("Starting DMA transfer...\r\n");

    SPI_Select_Device(SPI_DEVICE_FLASH);
    SPI_Transmit_Receive_DMA(dma_tx_buffer, dma_rx_buffer, 1024);

    // 等待DMA传输完成
    while (SPI_Is_Busy()) {
   
        // 可以在这里做其他事情
    }

    SPI_Deselect_Device(SPI_DEVICE_FLASH);
    printf("DMA transfer complete!\r\n");

    while (1) {
   
        // 主循环
        HAL_Delay(1000);
    }
}

参考代码 STM32的SPI外设与之通信,进行数据的读写 www.youwenfan.com/contentali/70085.html

八、性能优化

1. 提高SPI时钟

// 根据APB2时钟设置
// 72MHz系统时钟,APB2=72MHz
// 分频设置:
// SPI_BAUDRATEPRESCALER_2  = 36MHz
// SPI_BAUDRATEPRESCALER_4  = 18MHz
// SPI_BAUDRATEPRESCALER_8  = 9MHz

2. 使用快速读取

// W25Q64支持快速读取
void W25Q64_Fast_Read(uint32_t addr, uint8_t *data, uint32_t size)
{
   
    uint8_t tx_buffer[5] = {
   W25Q64_CMD_FAST_READ, 
                           (addr >> 16) & 0xFF,
                           (addr >> 8) & 0xFF,
                           addr & 0xFF,
                           0xFF};  // 虚拟字节

    SPI_Select_Device(SPI_DEVICE_FLASH);
    SPI_Transmit_Receive(tx_buffer, data, size + 5);
    SPI_Deselect_Device(SPI_DEVICE_FLASH);
}

3. 批量传输优化

// 批量写入
void W25Q64_Write_Multi_Page(uint32_t addr, uint8_t *data, uint32_t size)
{
   
    uint32_t offset = 0;

    while (size > 0) {
   
        uint16_t write_size = (size > 256) ? 256 : size;

        W25Q64_Write_Page(addr + offset, data + offset, write_size);

        offset += write_size;
        size -= write_size;

        // 延时避免Flash过热
        if (offset % 4096 == 0) {
   
            HAL_Delay(1);
        }
    }
}

九、调试技巧

1、逻辑分析仪调试

// 添加调试GPIO
#define DEBUG_PIN_SET()   HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET)
#define DEBUG_PIN_CLR()   HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET)

// 在关键位置添加
DEBUG_PIN_SET();
SPI_Transmit(data, size);
DEBUG_PIN_CLR();

2、错误检测

// 检查SPI错误
void SPI_Check_Error(void)
{
   
    if (hspi1.ErrorCode != HAL_SPI_ERROR_NONE) {
   
        printf("SPI Error: 0x%08lX\r\n", hspi1.ErrorCode);

        // 重置SPI
        __HAL_SPI_DISABLE(&hspi1);
        HAL_SPI_DeInit(&hspi1);
        HAL_SPI_Init(&hspi1);
    }
}

十、常见问题

问题 原因 解决
无响应 CS引脚错误 检查CS引脚电平和时序
数据错误 时钟相位不对 调整CPOL/CPHA
DMA卡死 DMA未正确初始化 检查DMA配置和中断
速度慢 SPI时钟太低 提高时钟频率
干扰大 未加滤波电容 靠近芯片加0.1uF电容
相关文章
|
4月前
|
传感器 数据采集 数据可视化
基于STM32的智能家居控制系统设计方案
基于STM32的智能家居控制系统设计方案
|
1月前
|
存储 弹性计算 数据库
阿里云服务器ECS免费试用攻略:0成本试用体验与申请与使用注意事项
阿里云ECS免费试用活动为新手用户提供零成本上云体验。完成实名认证且从未购买过ECS的用户,可申请3个月免费试用:个人用户享300元额度(0.833元/小时),企业用户享660元额度(1.833元/小时),每月另赠20GB国内+200GB海外公网流量,支持华北2、杭州、广州等7大免费地域。试用期内可灵活调整实例配置,适用于网站托管、开发测试、数据库部署等多种场景。超出额度按量计费,到期未释放将自动转为按量付费。
|
1月前
|
人工智能 Rust 监控
这 3 个开源小工具,帮你让 Coding Agent 少吃点 Token
今天我们就来分享 3 个有用的开源项目,专门帮你的 Coding Agent 整理“上下文”:让它少翻无关代码,少吞冗长日志,把 token 留给更关键的信息。
282 0
这 3 个开源小工具,帮你让 Coding Agent 少吃点 Token
|
1月前
|
人工智能 前端开发 开发者
2026年阿里云618活动云服务器价格表及选购指南
2026阿里云618“AI加速季·智惠生产力”大促(6.1–6.30)来袭!轻量服务器低至38元/年,e实例99元/年、u1实例199元/年;u2i/c9i/g9i/r9i等高配机型享3–6.4折。新人领券最高减800元,每日限时秒杀,AI开发者优选组合购——上云省钱正当时!
290 1
|
NoSQL Java 关系型数据库
【AgentScope Java新手村系列】(5)记忆与会话管理
记忆与会话管理 — AgentState 管理上下文窗口,AgentStateStore 持久化,RuntimeContext.sessionId 隔离多用户会话。
296 0
|
26天前
|
人工智能 安全 Serverless
MCP Server 开发与部署实战:让 AI 智能体拥有"双手"
MCP(Model Context Protocol)协议是 2026 年最热的 AI 技术趋势之一,它让大模型从"只会说"进化为"能做事"。阿里云百炼平台上线了业界首个全生命周期 MCP 服务,预置 20+ 云端服务和 50+ 本地服务。本文从 MCP 协议原理讲起,实战演示自定义 MCP Server 开发、函数计算 FC 部署、百炼 MCP 服务广场集成,以及与 Qwen3.7 智能体的联动,帮你构建真正能调用工具的 AI 应用。
|
29天前
|
算法
基于MATLAB的非线性有限元梁扭矩分析实现
基于MATLAB的非线性有限元梁扭矩分析实现
|
28天前
|
缓存 前端开发 JavaScript
阿里云 OSS 实战:从零搭建静态网站托管 + CDN 加速完整指南
本文分享阿里云OSS+CDN托管静态站点的实战方案:省去服务器运维,提升国内访问速度至50ms内;涵盖Bucket配置、SPA路由适配、自动化部署(CLI/GitHub Actions)、HTTPS与CDN缓存策略,并附成本估算(约¥18/月)和常见坑点解析,适合前端开发者快速落地。