基于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电容
相关文章
|
18天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
6706 30
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
3天前
|
数据采集 人工智能 前端开发
让 Coding Agent 从黑盒到透明:阿里云 Agent 观测审计数据采集实践
AI Agent 规模化落地带来执行黑盒、行为难追溯、成本难度量三大难题。阿里云基于 OTel 标准,面向 Coding Agent、个人通用助理和框架型 Agent,推出 LoongSuite Pilot、插件及探针等无侵入采集方案,让 Agent 实现可看见、可分析、可审计、可治理。
602 138
|
3天前
|
人工智能 弹性计算 运维
阿里云发布堡垒机智能运维Agent,运维交互进入自然语言新时代
支持自然语言运维,提升效率与安全双保障。
1142 0
|
10天前
|
人工智能 安全 定位技术
CodeGraph深度解析 让Claude Code工具调用直降七成的核心原理与实操教程
如今以Claude Code为代表的AI编程智能体已经成为开发者日常编码、项目重构、漏洞修复的必备工具。但在长期使用过程中,几乎所有开发者都会遇到同一个明显痛点:AI虽然具备强大的代码生成与分析能力,却常常陷入盲目探索的循环中。
1154 1
|
13天前
|
存储 定位技术 数据库
CodeGraph 如何让 Claude Code减少 7 成工具调用?
CodeGraph 为 Coding Agent 提供本地代码知识图谱,把函数、类、调用链和框架路由提前整理成“项目地图”,减少盲目搜索和文件读取。它不是新 Agent,而是上下文基础设施,让 Agent 更快找到正确代码路径,平均减少 7 成工具调用。
1268 3
|
10天前
|
人工智能 弹性计算 安全
阿里云618活动时间、活动入口、优惠活动详细解读
2026年阿里云618创新加速季已全面开启,作为年度力度最大的云产品促销活动,本次大促覆盖轻量应用服务器、ECS云服务器、GPU云服务器、数据库、AI算力、安全服务、CDN等全品类产品,推出5亿元算力补贴、新用户限时秒杀、普惠满减、企业专享、免费试用、云大使返佣等多重福利,个人开发者、中小企业、AI团队均可享受专属低价。本文将系统梳理2026年阿里云618活动的完整时间节点、官方参与入口、各类优惠细则、使用规则、热门产品推荐及实操代码,帮助用户精准参与、高效省钱,以最低成本完成上云部署。
943 5
|
9天前
|
人工智能 自然语言处理 安全
Vibe Coding 实战:别盲目跟风,先分清 vibe coding 适合什么场景
本文系统总结vibe coding实战经验:明确其适用场景(原型、小工具、标准化模块),剖析5步落地流程(场景判定→结构化提示词→目录初始化→分模块生成→自动化校验),指出四大常见误区,并推荐适配工具Trae。强调“场景匹配+规则前置”是提效关键,避免盲目套用。
784 1