Polling
该示例通过使用 SPI 轮询模式进行与外部 flash 的通信。
示例中使用轮询的方式进行通信,分别采用全双工模式和 EEPROM 模式,对外部 flash 进行数据的读取,数据的擦除,写入等操作。
示例采用 W25Q128 作为外部 flash 与 SPI 通信,关于该硬件的介绍详见 硬件介绍。
用户可以通过宏配置来选择 SPI 的通信模式,是否使用软件控制 CS 线通信,修改引脚设置等参数,具体宏配置详见 配置选项。
备注
使用软件控制 CS 线的目的,是为了保证 SPI 在数据传输过程中不会断流。如果 SPI 在传输过程中出现 Tx underflow 的情况,可能会使得 CS 线被拉高,导致通信出现问题。使用软件控制 CS 线,可以保证在本次通信传输完毕前,CS 线不会拉高,保证通信的完整性。
环境需求
该示例的环境需求,可参考 环境需求。
硬件连线
EVB 外接 W25Q128 模块,使用杜邦线连接 P4_0(SCK)和 CLK,P4_1(MISO)和 DI,P4_2(MOSI)和 DO,P4_3(CS)和 CS#。
备注
W25Q128 的模块介绍参考 W25Q128硬件介绍 。
配置选项
可配置如下宏修改是否使用软件控制 CS 线。
#define SPI_CONFIG_GPIO_SIM_CS 1 /*< Set this macro to 1 to enable software control CS. */
可配置如下宏修改 SPI 的通信模式为全双工或 EEPROM 模式。
#define SPI_MODE_FULLDUPLEX 0 #define SPI_MODE_EEPROM 3 #define SPI_MODE SPI_MODE_FULLDUPLEX /*< Set this macro to change the SPI transfer mode. */
可配置如下宏修改引脚定义。
#define SPI_SCK_PIN P4_0 #define SPI_MISO_PIN P4_1 #define SPI_MOSI_PIN P4_2 #define SPI_CS_PIN P4_3
编译和下载
该示例的编译和下载流程,可参考 编译和下载。
测试验证
EVB 启动后,在 Debug Analyzer 工具内观察 log。
Start spi polling test!
SPI 通信模式配置:
若配置宏
SPI_MODE
为SPI_MODE_FULLDUPLEX
,则会打印如下 log。SPI is set to FullDulpex Mode!
若配置宏
SPI_MODE
为SPI_MODE_EEPROM
,则会打印如下 log。SPI is set to EEPROM Mode!
读取外部 flash id:
读取 DEVICE_ID,前三个字节为 dummy read,最后一个字节为 DEVICE_ID。
flash id = 000000ff flash id = 000000ff flash id = 000000ff flash id = 00000017
读取 MF_DEVICE_ID,前三个字节为 dummy read,后两个字节为 DEVICE_ID。
flash id = 000000ff flash id = 000000ff flash id = 000000ff flash id = 000000ef flash id = 00000017
读取 JEDEC_ID
flash id = 000000ef flash id = 00000040 flash id = 00000018
将 Flash 指定地址下的数据进行擦除操作,读取擦除之后的数据(全 FF)。
spi_demo: spi_flash_sector_erase done spi_demo: after erase read_data[0] = 0x000000ff ... spi_demo: after erase read_data[99] = 0x000000ff
在 Flash 内的指定地址下进行数据写入操作,后再进行数据读取:
在全双工模式下,由于发送了四个字节的数据,接收数据的前四个字节为 FF(无效数据),第五个字节开始为有效数据。
spi_demo: after write read_data[0] = 0x000000ff spi_demo: after write read_data[1] = 0x000000ff spi_demo: after write read_data[2] = 0x000000ff spi_demo: after write read_data[3] = 0x000000ff spi_demo: after write read_data[4] = 0x0000000a ... spi_demo: after write read_data[99] = 0x00000069
在 EEPROM 模式下,在发送数据时不进行接收,所以接收的所有数据都为有效信息。
spi_demo: after write read_data[0] = 0x0000000a spi_demo: after write read_data[1] = 0x0000000b ... spi_demo: after write read_data[99] = 0x0000006d
代码介绍
该章节主要介绍示例中的初始化和相应功能实现的代码和流程说明。
源码路径
工程文件和源码路径如下:
工程路径:
sdk\samples\peripheral\spi\polling\proj
源码路径:
sdk\samples\peripheral\spi\polling\src
初始化
外设的初始化流程可参考 General Introduction 中的 初始化流程 部分。
调用
Pad_Config()
与Pinmux_Config()
,配置对应引脚的 PAD 和 PINMUX。如果配置SPI_CONFIG_GPIO_SIM_CS
为1
,则需要将 CS 引脚 PINMUX 为 GPIO 功能。void board_spi_init(void) { Pad_Config(SPI_SCK_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH); Pad_Config(SPI_MOSI_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH); Pad_Config(SPI_MISO_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH); Pad_Config(SPI_CS_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH); Pinmux_Deinit(SPI_SCK_PIN); Pinmux_Deinit(SPI_MOSI_PIN); Pinmux_Deinit(SPI_MISO_PIN); Pinmux_Deinit(SPI_CS_PIN); Pinmux_Config(SPI_SCK_PIN, SPI_CLK_MASTER); Pinmux_Config(SPI_MOSI_PIN, SPI_MO_MASTER); Pinmux_Config(SPI_MISO_PIN, SPI_MI_MASTER); #if (SPI_CONFIG_GPIO_SIM_CS == 1) Pinmux_Config(SPI_CS_PIN, DWGPIO); #else Pinmux_Config(SPI_CS_PIN, SPI_CSN_0_MASTER); #endif }
调用
RCC_PeriphClockCmd()
,开启 SPI 时钟。对 SPI 外设进行初始化:
定义
SPI_InitTypeDef
类型SPI_InitStruct
,调用SPI_StructInit()
将SPI_InitStruct
预填默认值。根据需求修改
SPI_InitStruct
参数,SPI 的初始化参数配置如下表。调用
SPI_Init()
,初始化 SPI 外设。
SPI Hardware Parameters |
Setting in the |
SPI |
---|---|---|
SPI Transfer Mode |
|
|
SPI Data Size |
||
Clock Mode - CPOL |
||
Clock Mode - CPHA |
||
Clock Prescaler |
20 |
|
Frame Format |
调用
SPI_Cmd()
,使能 SPI 外设。
功能实现
SPI 在轮询模式下通信的流程如图所示:

SPI 轮询模式通信流程
根据 W25Q128 的硬件信息和通信协议,在执行不同操作时 SPI 需要发送的数据长度和内容是不同的。同时在 SPI 不同的通信模式下,读取和发送的逻辑也有区别。
以读取外部 flash ID 信息为例,程序内需要分别读取外部 flash DEVICE_ID / MF_DEVICE_ID / JEDEC_ID 信息。
准备需要发送的数据内容。根据需要读取的不同 ID 类型,修改发送数据的第一个字节对应的不同指令与需要接收的数据长度。
若配置宏
SPI_MODE
为SPI_MODE_EEPROM
时:在发送数据前需要调用
SPI_SetReadLen()
设置需要读取的数据长度。由于 EEPROM 通信模式发送和接收逻辑是分开进行的,因此设置好接收长度后,只需要调用
SPI_SendBuffer()
发送一个字节的指令数据,后续再循环调用SPI_ReceiveData()
函数进行数据接收。
若配置宏
SPI_MODE
为SPI_MODE_FULLDUPLEX
时:调用
SPI_SendBuffer()
发送数据。由于全双工模式通信为收发同时进行,所以在发送数据时,除了指令数据外,仍需要发送接收逻辑所需要的字节数数据以维持 SCK 信号持续打出。在发送数据之后,调用
SPI_ReceiveData()
,读取从设备端回复的数据。在全双工模式下,读取的第一个字节为发送的指令字节对应回复的无效数据,因此可以不存储到数组内。
若配置宏
SPI_CONFIG_GPIO_SIM_CS
为1
,则需要在每次通信开始前和结束后调用GPIO_ResetBits()
和GPIO_SetBits()
,分别拉低和拉高 CS 线。
void spi_flash_read_id(Flash_ID_Type vFlashIdType, uint8_t *pFlashId) { #if (SPI_CONFIG_GPIO_SIM_CS == 1) GPIO_ResetBits(GPIO_GetPort(SPI_CS_PIN), GPIO_GetPin(SPI_CS_PIN)); #endif uint8_t send_buf[4] = {SPI_FLASH_JEDEC_ID, 0, 0, 0}; uint8_t recv_len = 3; switch (vFlashIdType) { ... } #if (SPI_MODE_EEPROM == SPI_MODE) SPI_SetReadLen(FLASH_SPI, recv_len); SPI_SendBuffer(FLASH_SPI, send_buf, 1); #elif (SPI_MODE_FULLDUPLEX == SPI_MODE) SPI_SendBuffer(FLASH_SPI, send_buf, recv_len + 1); while (SPI_GetFlagState(FLASH_SPI, SPI_FLAG_RFNE) == RESET){} SPI_ReceiveData(FLASH_SPI);//dummy read data #endif *pFlashId++ = recv_len; uint8_t idx = 1; while (recv_len--) { while (RESET == SPI_GetFlagState(FLASH_SPI, SPI_FLAG_RFNE)){} *pFlashId++ = SPI_ReceiveData(FLASH_SPI); } #if (SPI_CONFIG_GPIO_SIM_CS == 1) GPIO_SetBits(GPIO_GetPort(SPI_CS_PIN), GPIO_GetPin(SPI_CS_PIN)); #endif }
在执行写操作时,在此通信逻辑下只需要发送数据不需要接收数据,此时 EEPROM 的通信模式无法支持该场景的使用,所以需要调用
SPI_ChangeDirection()
将通信模式切换至 Tx Only 模式进行发送数据。在发送数据完毕后再次调用SPI_ChangeDirection()
将通信模式切换回 EEPROM 模式。SPI_ChangeDirection(FLASH_SPI, SPI_Direction_TxOnly); SPI_SendBuffer(FLASH_SPI, send_buf, 4); while (SPI_GetFlagState(FLASH_SPI, SPI_FLAG_BUSY) == SET){} SPI_ChangeDirection(FLASH_SPI, SPI_Direction_EEPROM); ...
在主函数中,调用
spi_flash_read_id
读取外部 flash 的 ID 信息。调用spi_flash_sector_erase
对指定地址下的数据进行擦除,擦除后调用spi_flash_read
读取对应地址下的数据,检查是否擦除成功。调用spi_flash_page_write
对指定地址下进行数据写入,写入后再次读取该地址下的数据检查是否写入成功。void spi_demo(void) { ... while (flash_id_type < 3) { spi_flash_read_id((Flash_ID_Type)flash_id_type, flash_id); ... flash_id_type++; memset(flash_id, 0, sizeof(flash_id)); } spi_flash_sector_erase(0x001000); spi_flash_read(SPI_FLASH_FAST_READ, 0x001000, read_data, 105); ... spi_flash_page_write(0x001000, write_data, 100); spi_flash_read(SPI_FLASH_FAST_READ, 0x001000, read_data, 105); ... }