External Flash
This example demonstrates the method of SPI communication with external flash.
Using polling mode, perform operations such as reading data, erasing data, and writing data to the external flash.
In Debug Analyzer, print Flash ID and the information of reading and writing internal flash data.
Requirements
The sample supports the following development kits:
Hardware Platforms |
Board Name |
---|---|
RTL8752H HDK |
RTL8752H EVB |
For more requirements, please refer to Quick Start.
Wiring
Connect the EVB to the W25Q128 module using DuPont wires: P4_0 (SCK) to CLK, P4_1 (MISO) to DO, P4_2 (MOSI) to DI, and P4_3 (CS) to CS#.
Hardware Introduction
The W25Q128 is an SPI interface NOR Flash chip that supports standard Serial Peripheral Interface (SPI), Dual/Quad SPI, and 2-clock instruction cycle Quad Peripheral Interface (QPI).
Additionally, the device supports JEDEC standard manufacturer and device ID, SFDP registers, a 64-bit unique serial number, and three 256-byte security registers.
Further details can be found in the device’s application guide. In the example, the device is used as a slave for SPI communication testing.
Building and Downloading
This sample can be found in the SDK folder:
Project file: board\evb\io_sample\SPI\Flash\mdk
Project file: board\evb\io_sample\SPI\Flash\gcc
Please follow these steps to build and run the example:
Open sample project file.
To build the target, follow the steps listed on the Generating App Image in Quick Start.
After a successful compilation, the app bin
app_MP_xxx.bin
will be generated in the directorymdk\bin
orgcc\bin
.To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.
Press reset button on EVB board and it will start running.
Experimental Verification
Read the external flash JEDEC_ID and print the JEDEC_ID information.
[io_spi] spi_demo: flash_id = EF 40 18
Erase the flash data at the specified address and read the data at that address after erasure (all FF).
[io_spi] spi_demo: spi_flash_sector_erase done [io_spi] spi_demo: read_data = FF FF FF ...
Write data to the specified address in the flash, and then read the data.
[io_spi] spi_demo: read_data = 00 01 02 ...
Code Overview
This chapter will be introduced according to the following several parts:
Peripheral initialization will be introduced in chapter Initialization.
Functional implementation after initialization will be introduced in chapter Function Implementation.
Source Code Directory
Project directory:
sdk\board\evb\io_sample\SPI\Flash
Source code directory:
sdk\src\sample\io_sample\SPI\Flash
Source files are currently categorized into several groups as below.
└── 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
Initialization
When the EVB reset is initiated, the main()
function is called, and the following process will be executed:
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;
}
Note
le_gap_init()
, gap_lib_init()
, app_le_gap_init
, and app_le_profile_init
are related to the initialization of the privacy management module. Refer to the initialization process description in LE Peripheral Privacy.
The specific initialization process related to peripherals is as follows:
In
board_init
, executeboard_spi_init
, which is responsible for PAD/PINMUX settings and includes the following process:Configure PAD: Set pins, PINMUX mode, PowerOn, internal pull-up, output high.
Configure PINMUX: Assign pins to SPI0_CLK_MASTER, SPI0_MO_MASTER, SPI0_MI_MASTER, SPI0_SS_N_0_MASTER functions respectively.
After executing
os_sched_start()
to start task scheduling, in theapp_main_task
main task, executedriver_init
to initialize and configure the peripheral drivers.In
driver_init
, executedriver_spi_init
, which is the initialization function for the SPI peripheral, including the following process:Enable the RCC clock.
Configure the SPI transmission mode to full-duplex mode.
Configure the data width to 8 bits.
Configure the serial clock to idle high, and data capture on the second clock edge.
Set the clock division factor to 20.
Set the receive data length threshold and data transmission format.
Enable the SPI peripheral.
Note
The minimum clock division factor for SPI is 2, and it must be an even number.
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); }
Functional Implementation
Execute
os_sched_start()
to start task scheduling. When the stack is ready, executeapp_handle_dev_state_evt
and executespi_demo
.Define the data content to be written to the specified address.
Execute the following process in order: read ID -> erase data at specified address -> read data at specified address -> write data to specified address -> read data at specified address.
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)); }
Execute
spi_flash_read_id
to read the JEDEC_ID information.Define the first byte of the sent data as the read JEDEC_ID instruction. According to the spec, define the remaining three bytes of the sent data to receive dummy data.
Execute
SPI_SendBuffer()
to send four bytes of data.Execute
SPI_ReceiveData()
to read the data returned by the slave device.
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 }
Execute
spi_flash_sector_erase
to erase data at the specified address.Define the first byte of the sent data as the sector erase command, and the 2nd to 4th bytes as the address information of the data to be erased.
Before writing data, execute
spi_flash_write_enable
to enable the write operation and send the write enable command to the external flash.Execute
SPI_SendBuffer()
to send data, and repeatedly executeSPI_ReceiveData()
to clear the data replied by the slave in the RX FIFO.After sending, execute
spi_flash_busy_check
to check the status of the external 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(); }
Execute
spi_flash_read
to read data of specified length at the specified address in the flash.Edit the data to be sent. The first byte is the read instruction, and the second, third, and fourth bytes are the specified address. Execute
SPI_SendBuffer()
to send the instruction and address.After sending the instruction and address data, repeatedly execute
SPI_GetRxFIFOLen()
to read dummy data.Due to the characteristics of full-duplex communication, before each data reception, a piece of dummy data needs to be sent to the flash, and then flash send back a piece of data.
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); } }
Execute the
spi_flash_page_write
function to write the specified data to the designated address in the flash memory.Define the data to be sent, with the first byte as the write instruction and the second, third, and fourth bytes as the address.
Before writing the data, execute
spi_flash_write_enable
to enable the write operation, sending the write enable instruction to the external flash.Execute
SPI_SendBuffer()
to send the data: first sending the instruction and address data, followed by the data content to be written.Continuously receive dummy data sent back by the flash. After sending is complete, execute
spi_flash_busy_check
to check the status of the external 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(); }