External Flash
该示例演示 SPI 与外部 flash 通信的方法。
使用轮询的方式,对外部 flash 进行数据的读取,数据的擦除,写入等操作。
在 Debug Analyzer 内,打印 Flash ID 以及 FLASH 内部数据信息的读取和写入信息。
环境需求
该示例支持以下开发套件:
Hardware Platforms |
Board Name |
---|---|
RTL8752H HDK |
RTL8752H EVB |
更多信息请参考 快速入门。
硬件连线
EVB 外接 W25Q128 模块,使用杜邦线连接 P4_0(SCK)和 CLK,P4_1(MISO)和 DO,P4_2(MOSI)和 DI,P4_3(CS)和 CS#。
硬件介绍
W25Q128 是一款 SPI 接口的 NOR Flash 芯片,支持标准串行外设接口(SPI)、双线/四线 SPI 以及 2-时钟指令周期四外设接口(QPI)。
此外,该器件还支持 JEDEC 标准的制造商和器件 ID 和 SFDP 寄存器、64 位唯一序列号和三个 256 字节安全寄存器。
其他细节说明可查阅该器件的应用指南。在示例中使用该器件作为 slave 进行 SPI 的通信测试。
编译和下载
该示例的工程路径如下:
Project file: board\evb\io_sample\SPI\Flash\mdk
Project file: board\evb\io_sample\SPI\Flash\gcc
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译 APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下 reset 按键,开始运行。
测试验证
读取外部 flash JEDEC_ID,打印 JEDEC_ID 信息。
[io_spi] spi_demo: flash_id = EF 40 18
对指定地址下的 flash 数据进行擦除,读取擦除后该地址下的数据(全 FF)。
[io_spi] spi_demo: spi_flash_sector_erase done [io_spi] spi_demo: read_data = FF FF FF ...
在 flash 内的指定地址下进行数据写入操作,后再进行数据读取。
[io_spi] spi_demo: read_data = 00 01 02 ...
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
sdk\board\evb\io_sample\SPI\Flash
源码路径:
sdk\src\sample\io_sample\SPI\Flash
该工程的工程文件代码结构如下:
└── Project: flash
└── secure_only_app
└── include
├── app_define.h
└── rom_uuid.h
├── cmsis includes CMSIS header files and startup files
├── overlay_mgr.c
├── system_rtl876x.c
└── startup_rtl876x.s
├── lib includes all binary symbol files that user application is built on
├── rtl8752h_sdk.lib
├── gap_utils.lib
├── ROM.lib
└── adc.lib
├── peripheral includes all peripheral drivers and module code used by the application
├── rtl876x_rcc.c
├── rtl876x_pinmux.c
├── rtl876x_nvic.c
└── rtl876x_spi.c
├── profile
└── app includes the ble_peripheral user application implementation
├── main.c
├── ancs.c
├── app.c
├── app_task.c
├── spi_flash.c
└── io_spi.c
初始化
当 EVB 复位启动时,调用 main()
函数,将执行以下流程:
int main(void)
{
extern uint32_t random_seed_value;
srand(random_seed_value);
global_data_init();
board_init();
le_gap_init(APP_MAX_LINKS);
gap_lib_init();
app_le_gap_init();
app_le_profile_init();
pwr_mgr_init();
task_init();
os_sched_start();
return 0;
}
备注
le_gap_init()
,gap_lib_init()
,app_le_gap_init
,app_le_profile_init
等为 privacy 管理模块相关的初始化,参考 LE Peripheral Privacy 中的初始化流程介绍。
与外设相关的初始化流程具体如下:
在
board_init
中,执行board_spi_init
,该函数为 PAD/PINMUX 设置,包含如下流程:配置 PAD:设置引脚,PINMUX 模式,PowerOn,内部上拉,输出高。
配置 PINMUX:分配引脚分别为 SPI0_CLK_MASTER、SPI0_MO_MASTER、SPI0_MI_MASTER、SPI0_SS_N_0_MASTER 功能。
在执行
os_sched_start()
开启任务调度后,在app_main_task
主任务内,执行driver_init
对外设驱动进行初始化配置。在
driver_init
中执行driver_spi_init
,该函数为 SPI 外设的初始化,包含如下流程:使能 RCC 时钟。
配置 SPI 的传输模式为全双工模式。
配置数据宽度为 8 位。
配置串行时钟的稳态悬空高,数据捕获于第二个时钟沿。
设置时钟分频系数为 20。
设置接收数据长度阈值和数据传输格式。
使能 SPI 外设。
备注
SPI 的时钟分频系数最小为 2,且只能为偶数。
void driver_spi_init(void) { RCC_PeriphClockCmd(APBPeriph_SPI0, APBPeriph_SPI0_CLOCK, ENABLE); SPI_InitTypeDef SPI_InitStruct; SPI_StructInit(&SPI_InitStruct); SPI_InitStruct.SPI_Direction = SPI_Direction_FullDuplex; SPI_InitStruct.SPI_Mode = SPI_Mode_Master; SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; SPI_InitStruct.SPI_CPOL = SPI_CPOL_High; SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStruct.SPI_BaudRatePrescaler = 20; /* SPI_Direction_EEPROM mode read data lenth. */ SPI_InitStruct.SPI_RxThresholdLevel = 1;/* Flash id lenth = 3*/ SPI_InitStruct.SPI_NDF = 0;/* Flash id lenth = 3*/ SPI_InitStruct.SPI_FrameFormat = SPI_Frame_Motorola; SPI_Init(SPI0, &SPI_InitStruct); SPI_Cmd(SPI0, ENABLE); }
功能实现
在主函数中执行
os_sched_start()
,开启任务调度。当 stack 准备好时,执行app_handle_dev_state_evt
,执行spi_demo
。定义需要写入指定地址的数据内容。
按顺序执行以下流程:读取 ID -> 擦除指定地址数据 -> 读取指定地址数据 -> 对指定地址写入数据 -> 读取指定地址数据。
void spi_demo(void) { uint8_t flash_id[10] = {0}; uint8_t write_data[100]; uint8_t read_data[105] = {0}; for (uint16_t i = 0; i < 100; i++) { write_data[i] = i & 0xFF; } spi_flash_read_id(flash_id); APP_PRINT_INFO1("[io_spi] spi_demo: flash_id = %b ", TRACE_BINARY(3, flash_id)); spi_flash_sector_erase(0x001000); APP_PRINT_INFO0("[io_spi] spi_demo: spi_flash_sector_erase done"); spi_flash_read(SPI_FLASH_FAST_READ, 0x001000, read_data, 100); // APP_PRINT_INFO1("[io_spi] spi_demo: read_data = %b,", TRACE_BINARY(100, &read_data[5])); APP_PRINT_INFO1("[io_spi] spi_demo: read_data = %b,", TRACE_BINARY(100, read_data)); spi_flash_page_write(0x001000, write_data, 100); spi_flash_read(SPI_FLASH_FAST_READ, 0x001000, read_data, 100); APP_PRINT_INFO1("[io_spi] spi_demo: read_data = %b,", TRACE_BINARY(100, read_data)); }
执行
spi_flash_read_id
,读取 JEDEC_ID 信息。定义发送数据的第一个字节为读 JEDEC_ID 指令。根据 spec 说明,定义剩余三个字节的发送数据,用于接收 dummy 数据。
执行
SPI_SendBuffer()
,发送四个字节的数据。执行
SPI_ReceiveData()
,读取 slave 端回复的数据信息。
void spi_flash_read_id(uint8_t *pFlashId) { uint8_t send_buf[4] = {SPI_FLASH_JEDEC_ID, 0, 0, 0}; uint8_t recv_buf[10] = {0}; uint8_t recv_len = 3; #if (SPI_MODE_EEPROM == SPI_MODE) SPI_SetReadLen(FLASH_SPI, 3); SPI_SendBuffer(FLASH_SPI, send_buf, 1); #elif (SPI_MODE_FULLDUPLEX == SPI_MODE) SPI_SendBuffer(FLASH_SPI, send_buf, 4); recv_len += 1; #endif uint8_t idx = 0; while (recv_len--) { while (RESET == SPI_GetFlagState(FLASH_SPI, SPI_FLAG_RFNE)); recv_buf[idx++] = SPI_ReceiveData(FLASH_SPI); } #if (SPI_MODE_EEPROM == SPI_MODE) memcpy(pFlashId, recv_buf, 3); #elif (SPI_MODE_FULLDUPLEX == SPI_MODE) memcpy(pFlashId, &recv_buf[1], 3); #endif }
执行
spi_flash_sector_erase
,对指定地址下的数据进行擦除。定义发送数据的第一个字节为 sector 擦除指令,第 2-4 个字节为需要擦除数据的地址信息。
在写入数据之前,执行
spi_flash_write_enable
使能写操作,发送写使能指令至外部 flash。执行
SPI_SendBuffer()
发送数据,并循环执行SPI_ReceiveData()
清空 RX FIFO 内 slave 端回复的数据。发送完毕后,执行
spi_flash_busy_check
,检查外部 flash 的状态。
void spi_flash_sector_erase(uint32_t vAddress) { uint8_t send_buf[4] = {SPI_FLASH_SECTOR_ERASE, 0, 0, 0}; uint8_t recv_len = 0; send_buf[1] = (vAddress >> 16) & 0xff; send_buf[2] = (vAddress >> 8) & 0xff; send_buf[3] = vAddress & 0xff; /* enable write */ spi_flash_write_enable(); /* erase data */ SPI_SendBuffer(FLASH_SPI, send_buf, 4); while (SPI_GetFlagState(FLASH_SPI, SPI_FLAG_BUSY) == SET); recv_len = SPI_GetRxFIFOLen(FLASH_SPI); while (recv_len--) { SPI_ReceiveData(FLASH_SPI); } spi_flash_busy_check(); }
执行
spi_flash_read
,读取 flash 指定地址下,指定长度的数据。编辑需要发送的数据。第一个字节为读指令,第二三四字节为指定地址。执行
SPI_SendBuffer()
发送指令和地址。在发送完指令+地址数据后,循环执行
SPI_GetRxFIFOLen()
,读取 dummy 数据。由于全双工的通信特点,在每次接收数据之前,需要发送一笔 dummy 数据至 flash,随后才能接收 flash 发回的一笔数据。
void spi_flash_read(uint8_t vReadCmd, uint32_t vReadAddr, uint8_t *pBuffer, uint16_t vLength) { uint8_t send_buf[10] = {0}; uint8_t send_len = 0; uint16_t recv_len = 0; if (SPI_FLASH_READ_DATA == vReadCmd) { send_len = 4; } else if (SPI_FLASH_FAST_READ == vReadCmd) { send_len = 5; } send_buf[0] = vReadCmd; send_buf[1] = (vReadAddr >> 16) & 0xFF; send_buf[2] = (vReadAddr >> 8) & 0xFF; send_buf[3] = (vReadAddr) & 0xFF; SPI_SendBuffer(FLASH_SPI, send_buf, send_len); while (SPI_GetFlagState(FLASH_SPI, SPI_FLAG_RFNE) == RESET); for (uint8_t i = 0; i < send_len; i++) { SPI_ReceiveData(FLASH_SPI);//dummy read data } recv_len = vLength; while (recv_len--) { SPI_SendBuffer(FLASH_SPI, &send_buf[9], 1); while (SPI_GetFlagState(FLASH_SPI, SPI_FLAG_RFNE) == RESET); *pBuffer++ = SPI_ReceiveData(FLASH_SPI); } }
执行
spi_flash_page_write
函数,将指定数据写入 flash 的指定地址下。定义发送数据,第一个字节为写指令,第二三四字节为地址。
在写入数据之前,执行
spi_flash_write_enable
使能写操作,发送写使能指令至外部 flash。执行
SPI_SendBuffer()
发送数据,首先发送指令数据和地址数据,其次发送需要写入的数据内容。循环接收 flash 发回的 dummy 数据。发送完毕后,执行
spi_flash_busy_check
,检查外部 flash 的状态。
void spi_flash_page_write(uint32_t vWriteAddr, uint8_t *pBuffer, uint16_t vLength) { uint16_t send_data_len = vLength; uint8_t recv_data_len = 0; uint8_t send_buf[4] = {SPI_FLASH_PAGE_PROGRAM, 0, 0, 0}; send_buf[1] = (vWriteAddr >> 16) & 0xff; send_buf[2] = (vWriteAddr >> 8) & 0xff; send_buf[3] = vWriteAddr & 0xff; if (vLength > SPI_FLASH_PAGE_SIZE) { send_data_len = SPI_FLASH_PAGE_SIZE; } /* Enable write */ spi_flash_write_enable(); SPI_SendBuffer(FLASH_SPI, send_buf, 4); SPI_SendBuffer(FLASH_SPI, pBuffer, send_data_len); while (SPI_GetFlagState(FLASH_SPI, SPI_FLAG_BUSY) == SET); recv_data_len = SPI_GetRxFIFOLen(FLASH_SPI); while (recv_data_len--) { SPI_ReceiveData(FLASH_SPI); } spi_flash_busy_check(); }