目录

概述

1 STM32 SPI驱动实现

1.1 STM32Cube配置参数

1.2 实现和驱动相关的接口函数

2 STM32库SPI相关的函数介绍

2.1 HAL_SPI_TransmitReceive 函数

2.2 HAL_SPI_Transmit函数

2.3  HAL_SPI_Receive函数

2.4 函数功能总结

3 ZD25WQ16C 芯片

3.1 硬件结构

3.2 芯片相关的参数

3.3 封装和IO接口

4 驱动实现

4.1 代码实现

4.2 验证


概述

本文主要介绍STM32 HAL 库 SPI接口驱动的相关接口函数,并使用STM32 Cube配置SPI接口生成代码,同时介绍了HAL库中几个核心的接口函数的功能,还介绍ZD25WQ16C Flash芯片的相关内容。并基于ST的HAL库函数实现其驱动。

1 STM32 SPI驱动实现

1.1 STM32Cube配置参数

1) 使能SPI接口

2) 配置相关的参数

3) 确认IO接口相关配置

4) 完成上述配置后,生成项目代码,其框架结构如下:

1.2 实现和驱动相关的接口函数

STM32 的HAL库中已经实现和MCU芯片相关的接口函数,驱动具体的芯片其使用的接口函数需要根据芯片的特性编写。这里主要实现两个函数:

1) SPI_NSS_Set, 实现片选功能

2) spi_TransferByte 读写数据功能

具体源代码如下:


// SPI发送并接收一个字节
uint8_t spi_TransferByte(uint8_t data)
{
    uint8_t RxData;
    HAL_SPI_TransmitReceive(&hspi1, &data, &RxData, 1, 1000);

    return RxData;
}

// 设置NSS引脚状态
void SPI_NSS_Set(uint8_t state)
{
    if (state)
       HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
    else
       HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
}

2 STM32库SPI相关的函数介绍

2.1 HAL_SPI_TransmitReceive 函数

HAL_SPI_TransmitReceive 是 STM32 的 HAL 库中一个非常重要的 SPI 通信函数。它的核心功能是 在一次 SPI 通信过程中,同时完成数据的发送和接收。这是一种 全双工 的通信模式,在主机向从机发送数据的同时,也从从机读取数据。

1) 函数原型:

HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi,
                                          uint8_t *pTxData,
                                          uint8_t *pRxData,
                                          uint16_t Size,
                                          uint32_t Timeout);

2) 参数详解

  1. *hspi

    • 类型SPI_HandleTypeDef 指针

    • 含义: 指向一个 SPI 实例的句柄,这个句柄包含了该 SPI 外设的所有配置信息(如模式、波特率、数据大小等)。你需要在初始化时配置好这个句柄。

  2. *pTxData

    • 类型uint8_t 指针(指向字节数组)

    • 含义: 指向发送数据缓冲区的指针。函数会从这个缓冲区读取数据,并通过 MOSI 线发送出去。

    • 注意: 如果只想接收数据而不发送,这个参数不能为 NULL,你需要提供一个有效的缓冲区(里面的数据内容通常会被忽略,但某些从机需要时钟,所以必须发送数据来产生时钟信号)。

  3. *pRxData

    • 类型uint8_t 指针(指向字节数组)

    • 含义: 指向接收数据缓冲区的指针。函数会将从 MISO 线接收到的数据存入这个缓冲区。

    • 注意: 缓冲区必须足够大以容纳 Size 个字节的数据。

  4. Size

    • 类型uint16_t

    • 含义: 期望发送和接收的数据量(以字节为单位)。发送和接收的字节数是相同的。

  5. Timeout

    • 类型uint32_t

    • 含义: 超时时间(以毫秒为单位)。函数会等待通信完成,但如果超过这个时间仍未完成,函数会退出并返回 HAL_TIMEOUT 错误。可以使用 HAL_MAX_DELAY 来一直等待。

3) 返回值

返回值

  • HAL_OK: 操作成功。

  • HAL_ERROR: 参数错误。

  • HAL_BUSY: SPI 总线正忙。

  • HAL_TIMEOUT: 操作超时。

2.2 HAL_SPI_Transmit函数

HAL_SPI_Transmit 是 STM32 HAL 库中用于 同步发送数据 的 SPI 函数。它的核心功能是 将指定数据缓冲区中的数据通过 SPI 总线发送出去,而不关心接收到的数据。这是一种主发送的通信模式,主要用于向从设备写入数据、发送命令或配置寄存器。

1)函数原型

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi,
                                   uint8_t *pData,
                                   uint16_t Size,
                                   uint32_t Timeout);

2) 参数介绍

  1. *hspi

    • 类型SPI_HandleTypeDef 指针

    • 含义: 指向 SPI 实例的句柄,包含该 SPI 外设的所有配置信息。

  2. *pData

    • 类型uint8_t 指针(指向字节数组)

    • 含义: 指向发送数据缓冲区的指针。函数会从这个缓冲区读取数据并通过 MOSI 线发送出去。

  3. Size

    • 类型uint16_t

    • 含义: 要发送的数据量(以字节为单位)。

  4. Timeout

    • 类型uint32_t

    • 含义: 超时时间(以毫秒为单位)。如果通信超过此时间未完成,函数返回 HAL_TIMEOUT

3) 返回值

  • HAL_OK: 发送成功

  • HAL_ERROR: 参数错误

  • HAL_BUSY: SPI 总线正忙

  • HAL_TIMEOUT: 操作超时

2.3  HAL_SPI_Receive函数

HAL_SPI_Receive 是 STM32 HAL 库中用于 同步接收数据 的 SPI 函数。它的核心功能是 从 SPI 从设备读取指定数量的数据到接收缓冲区。虽然名为"接收"函数,但由于 SPI 是全双工协议,实际上它通过发送虚拟数据来产生时钟信号,从而接收从设备的返回数据

1) 函数原型

HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi,
                                  uint8_t *pData,
                                  uint16_t Size,
                                  uint32_t Timeout);

2) 参数介绍

  1. *hspi

    • 类型SPI_HandleTypeDef 指针

    • 含义: 指向 SPI 实例的句柄,包含该 SPI 外设的所有配置信息。

  2. *pData

    • 类型uint8_t 指针(指向字节数组)

    • 含义: 指向接收数据缓冲区的指针。函数会将从 MISO 线接收到的数据存入这个缓冲区。

  3. Size

    • 类型uint16_t

    • 含义: 要接收的数据量(以字节为单位)。

  4. Timeout

    • 类型uint32_t

    • 含义: 超时时间(以毫秒为单位)。

3) 返回值

  • HAL_OK: 接收成功

  • HAL_ERROR: 参数错误

  • HAL_BUSY: SPI 总线正忙

  • HAL_TIMEOUT: 操作超时

2.4 函数功能总结

函数 功能 内部机制 适用场景
HAL_SPI_Receive 只接收数据 发送虚拟数据产生时钟 读取传感器数据、存储器内容
HAL_SPI_Transmit 只发送数据 忽略接收到的数据 配置寄存器、发送命令
HAL_SPI_TransmitReceive 同时发送和接收 真正的全双工交换 发送命令并接收响应的复杂协议
HAL_SPI_Receive_IT 中断方式接收 非阻塞、异步 需要同时处理其他任务
HAL_SPI_Receive_DMA DMA方式接收 高效、不占用CPU 大数据量接收

3 ZD25WQ16C 芯片

ZD25WQ16C 是一款 16M-bit(即 2MB)的串行 Flash 存储器,支持标准的 SPI 接口,最高时钟频率可达 104MHz。它支持双线/四线 SPI 模式,具有高速读取、低功耗等特点。

关键规格

  • 容量: 16M-bit = 2M-Byte = 2048KB

  • 接口: 标准 SPI / Dual SPI / Quad SPI

  • 电压: 2.7V - 3.6V

  • 扇区结构: 统一 4KB 扇区

  • 页大小: 256 字节

  • 封装: SOIC-8, WSON-8 等

3.1 硬件结构

1) 框架结构

2) memory 相关参数

3) 主要操作指令

3.2 芯片相关的参数

其设备ID如下:

工作参数:

3.3 封装和IO接口

D25Q16E(16M 位)串行闪存支持标准的串行外设接口(SPI),以及双/四SPI:串行时钟、芯片选择、串行数据输入/输出 0(SI)、输入/输出 1(SO)、输入/输出 2(WP#)、输入/输出 3(HOLD#)。双输入数据的传输速度为 266Mbit/s,四输入数据的传输速度为 532Mbit/s。

引脚定义如下:

4 驱动实现

4.1 代码实现

编写驱动程序,其代码如下:

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * File Name        :  spi_flash.c
 * Description      :  flash driver library 
 ******************************************************************************
 * @attention
 *
 *  COPYRIGHT:    Copyright (c) 2025  mingfei_tang
 *  DATE:         MAR 28th, 2025
 *
 *
 ******************************************************************************
 */
/* USER CODE END Header */
#include "spi_flash.h" 
#include "spi.h"

#if 0
    #define  zCMDxx_log  printf
#else
    #define zCMDxx_log(...)
#endif


Flash_Info flash_Info_Obj;

static bool flash_check_address( u32 addr);

static void spi_init( void )
{

}

u8 spi_transfer_byte( u8 data )
{
    return spi_TransferByte(data);
}

/**
 *
 * @brief  select the flash chip 
 * 
 * @details  en = true, select the chip
 *           en = false, release the chip
 * 
 * @ingroup spi flash driver 
 */
static void spi_cs_ctrl( u8 en )
{
    SPI_NSS_Set(en);
}

/**
 *
 * @brief  read the chip id
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
static u16  flash_read_id(void)
{
    u32 ID = 0;
    u8 Temp0 = 0, Temp1 = 0, Temp2;
    
    spi_cs_ctrl(0);
    
    spi_transfer_byte( CMD_READ_JEDEC_ID );
    
    spi_transfer_byte(0x00);
    spi_transfer_byte(0x00);
    spi_transfer_byte(0x00);

    Temp0 = spi_transfer_byte(0xff);
    Temp1 = spi_transfer_byte(0xff);
    Temp2 = spi_transfer_byte(0xff);
    
    spi_cs_ctrl(1);
    
    ID = (Temp1 << 8) | Temp2;
    
    return ID;
}

/**
 *
 * @brief  read data from flash 
 * 
 * @details  addr:start address 
 *           buff: data buff
 *           len: data length that will be read     
 * 
 * @ingroup spi flash driver 
 */
static void spi_flash_read_bytes( u32 addr, u8* buff, u16 len )
{
     if( !flash_check_address( addr + len ))
         return;

    spi_cs_ctrl(0);
    
    spi_transfer_byte( CMD_READ_DATA );

    spi_transfer_byte( (addr & 0xFF0000) >>16 );
    spi_transfer_byte( (addr & 0xFF00) >>8 );
    spi_transfer_byte( addr & 0xff );
    
    while (len--)
    {
      *buff++ = spi_transfer_byte(0xff);
    } 
    
    spi_cs_ctrl(1);
}



/**
 *
 * @brief  enable to write the flash 
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
static void flash_wr_enable(void)
{
    spi_cs_ctrl(0);

    spi_transfer_byte( CMD_WRITE_ENABLE );
    
    spi_cs_ctrl(1);
} 


/**
 *
 * @brief  wait for writing data ready 
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
static void flash_wait_for_ready( void )
{
    spi_cs_ctrl(0);

    spi_transfer_byte( CMD_READ_STATUS1 );

    while(( spi_transfer_byte(0xff) & 0x01 ) == 0x01);

    spi_cs_ctrl(1); 
}


/**
 *
 * @brief write one page 
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
void flash_write_bytes_page(u32 addr, u8* buff, u16 len )
{
    u16 i;
    
    if( !flash_check_address( addr + len ))
         return;

    flash_wr_enable();

    spi_cs_ctrl(0);
    
    spi_transfer_byte( CMD_PAGE_PROGRAM );

    spi_transfer_byte((u8)((addr)>>16));
    spi_transfer_byte((u8)((addr)>>8));
    spi_transfer_byte((u8)addr);
    
    for( i=0; i < len; i++ )
    {
        spi_transfer_byte(buff[i]);
    }
    
    spi_cs_ctrl(1);
    
    flash_wait_for_ready();
} 


/**
 *
 * @brief erase the sector
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
void ext_flash_erase_page(u32 pagenum)
{ 
    u32 addr;

    addr = SPI_FLASH_PAGE_SIZE*pagenum;

    if( !flash_check_address( addr ))
        return;

    flash_wr_enable();
    flash_wait_for_ready();
    
    spi_cs_ctrl(1);

    spi_transfer_byte( CMD_PAGE_ERASE );

    // write the address 
    spi_transfer_byte( (u8)((addr)>>16) );
    spi_transfer_byte( (u8)((addr)>>8) );
    spi_transfer_byte( (u8)addr );
    spi_cs_ctrl(0);
    
    flash_wait_for_ready();
}


/**
 *
 * @brief erase the sector
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
void flash_erase_sector(u32 sectornum)
{ 
    u32 addr;

    addr = SPI_FLASH_SECTOR_SIZE*sectornum;

    if( !flash_check_address( addr ))
        return;

    flash_wr_enable();
    flash_wait_for_ready();
    
    spi_cs_ctrl(1);

    spi_transfer_byte( CMD_SECTOR_ERASE );

    // write the address 
    spi_transfer_byte( (u8)((addr)>>16) );
    spi_transfer_byte( (u8)((addr)>>8) );
    spi_transfer_byte( (u8)addr );
    spi_cs_ctrl(0);
    
    flash_wait_for_ready();
}



/**
 *
 * @brief erase the chip 
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
void flash_erase_chip(void)
{
    flash_wr_enable();
    flash_wait_for_ready();
    
    spi_cs_ctrl(1);
    spi_transfer_byte( CMD_CHIP_ERASE );
    
    spi_cs_ctrl(0);
    
    flash_wait_for_ready();
}


/**
 *
 * @brief write block data 
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
void ext_flash_wr_no_check(u32 addr, u8* buff, u16 len)   
{
    u16 pageremain;

    pageremain = SPI_FLASH_PAGE_SIZE - addr%SPI_FLASH_PAGE_SIZE;
        
    if(len <= pageremain )
          pageremain=len;

    while(1)
    {
        flash_write_bytes_page(addr,buff, pageremain);
        if( len == pageremain )
            break;
        else 
        {
            buff += pageremain;
            addr += pageremain;

            len -= pageremain;
            if(len > SPI_FLASH_PAGE_SIZE)
               pageremain = SPI_FLASH_PAGE_SIZE;
            else 
               pageremain = len; 
        }
    }
}


/**
 *
 * @brief write block data 
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
u8 tempbuff[SPI_FLASH_PAGE_SIZE];
void ext_spi_flash_write_block( u32 addr, u8 *buff, u16 len)   
{ 
    u32 secpos;
    u16 secoff;
    u16 secremain;
    u16 i;
    u8 * pbuff;

    if( !flash_check_address( addr + len ))
        return;

    pbuff = tempbuff;

    secpos = addr/SPI_FLASH_PAGE_SIZE;
    secoff = addr%SPI_FLASH_PAGE_SIZE;
    secremain = SPI_FLASH_PAGE_SIZE-secoff;

    if(len <= secremain)
        secremain = len;
    
    while(1) 
    {
        spi_flash_read_bytes(secpos*SPI_FLASH_PAGE_SIZE, pbuff, SPI_FLASH_PAGE_SIZE); 
        for(i=0;i<secremain;i++)
        {
            if(pbuff[secoff+i] != 0XFF)
                break;  
        }

        if(i<secremain)
        {
            ext_flash_erase_page(secpos);
            for(i=0;i<secremain;i++)
            {
                pbuff[i+secoff] = buff[i];
            }
            ext_flash_wr_no_check(secpos*SPI_FLASH_PAGE_SIZE, pbuff, SPI_FLASH_PAGE_SIZE);
        }
        else
        {
            ext_flash_wr_no_check(addr, buff, secremain);
        }    

        if( len == secremain)
            break;
        else
        {
            secpos++;
            secoff=0;

            buff += secremain; 
            addr += secremain;
            len -= secremain;

            if( len > SPI_FLASH_PAGE_SIZE )
                secremain = SPI_FLASH_PAGE_SIZE;
            else
                secremain = len;
        }
    }
}


/**
 *
 * @brief init flash driver 
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */
void flash_Init( void )
{
    u16 ID;

    spi_init();
 
    // read flash ID
    ID = flash_read_id();

    flash_Info_Obj.ID = ID;
    flash_Info_Obj.enable = false;
    if( ID == 0X6015 || ID == 0X4015 )   //W25Q16  
    {
        flash_Info_Obj.pageSize = SPI_FLASH_PAGE_SIZE;
        flash_Info_Obj.pageTotal = 8192;
        flash_Info_Obj.memSize = SPI_FLASH_PAGE_SIZE * 8192;
        flash_Info_Obj.enable = true;
        printf("Flash ID: 0x%04x, size: %d\r\n", ID, flash_Info_Obj.memSize);
    }
    else
    {
        flash_Info_Obj.pageSize = 0;
        flash_Info_Obj.pageTotal = 0;
        flash_Info_Obj.memSize = 0;
    }
}


static bool flash_check_address( u32 addr)
{
    if( !flash_Info_Obj.enable )
    {
        zCMDxx_log("zCMDxx: initial fail \r\n");
        return false;
    }

    if( addr > flash_Info_Obj.memSize )
    {
        zCMDxx_log("zCMDxx: address out of range, addr = %x, memsize = %x \r\n", addr, flash_Info_Obj.memSize);
        return false;
    }

    return true;
}

Flash_Info flash_chip_Infor( void )
{
    return flash_Info_Obj;
}


/**
 *
 * @brief control the spi flash power 
 *         en = 1: enable the spi
 *         en  = 0:  disable the spi and flash 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */


/**
 *
 * @brief  used to test the driver 
 * 
 * @details  
 *         
 * 
 * @ingroup spi flash driver 
 */

 #if 1
 #define TEST_LEN 1024
u8 rd_buff[TEST_LEN];
u8 wd_buff[TEST_LEN];

bool spi_flash_test( void )
{
    u32 addr;
    
    spi_init();
    flash_read_id();

    for ( int i =0; i< TEST_LEN; i++ )
    {
        wd_buff[i] = 0x50;
    }

    addr = 6*256;
    ext_spi_flash_write_block( addr, wd_buff, TEST_LEN);
    spi_flash_read_bytes( addr, rd_buff, TEST_LEN);

    for ( int i =0; i< TEST_LEN; i++ )
    {
        if(wd_buff[i]!=rd_buff[i])
        {
            zCMDxx_log("Flash: match data fail \r\n");
            return false;
        }
    }

    zCMDxx_log("Flash: match data pass \r\n");

    return true;
}
#else
bool  spi_flash_test( void )
{
    //flash_erase_chip();
    return true;
}
#endif 


/******************************************************************************
 *              SPI Flash CS PIN 
 ******************************************************************************/


/** End of this file  */


/* End of this file */

4.2 验证

读取Flash的ID, 确认其和芯片定义一致

使用J-Link 调试确认:

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐