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:

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:

  1. Open sample project file.

  2. To build the target, follow the steps listed on the Generating App Image in Quick Start.

  3. After a successful compilation, the app bin app_MP_xxx.bin will be generated in the directory mdk\bin or gcc\bin.

  4. To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.

  5. Press reset button on EVB board and it will start running.

Experimental Verification

  1. Read the external flash JEDEC_ID and print the JEDEC_ID information.

    [io_spi] spi_demo: flash_id = EF 40 18
    
  2. 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 ...
    
  3. 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:

  1. Source Code Directory.

  2. Peripheral initialization will be introduced in chapter Initialization.

  3. 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:

  1. In board_init, execute board_spi_init, which is responsible for PAD/PINMUX settings and includes the following process:

    1. Configure PAD: Set pins, PINMUX mode, PowerOn, internal pull-up, output high.

    2. Configure PINMUX: Assign pins to SPI0_CLK_MASTER, SPI0_MO_MASTER, SPI0_MI_MASTER, SPI0_SS_N_0_MASTER functions respectively.

  1. After executing os_sched_start() to start task scheduling, in the app_main_task main task, execute driver_init to initialize and configure the peripheral drivers.

  2. In driver_init, execute driver_spi_init, which is the initialization function for the SPI peripheral, including the following process:

    1. Enable the RCC clock.

    2. Configure the SPI transmission mode to full-duplex mode.

    3. Configure the data width to 8 bits.

    4. Configure the serial clock to idle high, and data capture on the second clock edge.

    5. Set the clock division factor to 20.

    6. Set the receive data length threshold and data transmission format.

    7. 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

  1. Execute os_sched_start() to start task scheduling. When the stack is ready, execute app_handle_dev_state_evt and execute spi_demo.

    1. Define the data content to be written to the specified address.

    2. 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));
    }
    
  2. Execute spi_flash_read_id to read the JEDEC_ID information.

    1. 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.

    2. Execute SPI_SendBuffer() to send four bytes of data.

    3. 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
    }
    
  3. Execute spi_flash_sector_erase to erase data at the specified address.

    1. 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.

    2. Before writing data, execute spi_flash_write_enable to enable the write operation and send the write enable command to the external flash.

    3. Execute SPI_SendBuffer() to send data, and repeatedly execute SPI_ReceiveData() to clear the data replied by the slave in the RX FIFO.

    4. 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();
    }
    
  4. Execute spi_flash_read to read data of specified length at the specified address in the flash.

    1. 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.

    2. After sending the instruction and address data, repeatedly execute SPI_GetRxFIFOLen() to read dummy data.

    3. 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);
        }
    
    }
    
  5. Execute the spi_flash_page_write function to write the specified data to the designated address in the flash memory.

    1. Define the data to be sent, with the first byte as the write instruction and the second, third, and fourth bytes as the address.

    2. Before writing the data, execute spi_flash_write_enable to enable the write operation, sending the write enable instruction to the external flash.

    3. Execute SPI_SendBuffer() to send the data: first sending the instruction and address data, followed by the data content to be written.

    4. 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();
    }