Interrupt

该示例演示 SPI 与外部 flash 通信的方法。

使用中断的方式对外部 flash 进行数据的读取。分别读取外部 flash 的 DEVICE_ID,MF_DEVICE_ID 和 JEDEC_ID。

在 Debug Analyzer 内,打印 Flash ID 信息。

环境需求

该示例支持以下开发套件:

开发套件

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 的模块介绍参考 W25Q128 硬件介绍

编译和下载

该示例的工程路径如下:

Project file: board\evb\io_sample\SPI\Interrupt\mdk

Project file: board\evb\io_sample\SPI\Interrupt\gcc

请按照以下步骤操作构建并运行该示例:

  1. 打开工程文件。

  2. 按照 快速入门编译 APP Image 给出的步骤构建目标文件。

  3. 编译成功后,在路径 mdk\bingcc\bin 下会生成 app bin app_MP_xxx.bin 文件。

  4. 按照 快速入门MP Tool 给出的步骤将 app bin 烧录至 EVB 内。

  5. 按下 reset 按键,开始运行。

测试验证

  1. EVB 启动复位后,打印如下信息:

    [io_spi] spi_demo: Read flash id.
    
  2. 主机向外部 flash 分别发送读取 ID 指令信息。主机收到信息后进入中断,打印收到数据的信息:

    [app] app_handle_io_msg: spi msg.
    [io_spi] io_spi_handle_msg: data_length = 4, data = FF FF FF 17
    [app] app_handle_io_msg: spi msg.
    [io_spi] io_spi_handle_msg: data_length = 5, data = FF FF FF EF 17
    [app] app_handle_io_msg: spi msg.
    [io_spi] io_spi_handle_msg: data_length = 3, data = EF 40 18
    

代码介绍

该章节分为以下几个部分:

  1. 源码路径

  2. 初始化函数将在 初始化 章节介绍。

  3. 初始化后的功能实现将在 功能实现 章节介绍。

源码路径

  • 工程路径: sdk\board\evb\io_sample\SPI\Interrupt

  • 源码路径: sdk\src\sample\io_sample\SPI\Interrupt

该工程的工程文件代码结构如下:

└── Project: interrupt
    └── 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
        ├── 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_initapp_le_profile_init 等为 privacy 管理模块相关的初始化,参考 LE Peripheral Privacy 中的初始化流程介绍。

与外设相关的初始化流程具体如下:

  1. board_init 中,执行 board_spi_init ,该函数为 PAD/PINMUX 设置,包含如下流程:

    1. 配置 PAD:设置引脚,PINMUX 模式,PowerOn,内部上拉,输出高。

    2. 配置 PINMUX:分配引脚分别为 SPI0_CLK_MASTER、SPI0_MO_MASTER、SPI0_MI_MASTER、SPI0_SS_N_0_MASTER 功能。

  2. 在执行 os_sched_start() 开启任务调度后,在 app_main_task 主任务内,执行 driver_init 对外设驱动进行初始化配置。

  3. driver_init 中执行 driver_spi_init ,该函数为 SPI 外设的初始化,包含如下流程:

    1. 使能 RCC 时钟。

    2. 配置 SPI 的传输模式为 EEPROM 模式。

    3. 配置数据宽度为 8 位。

    4. 配置串行时钟的稳态悬空高,数据捕获于第二个时钟沿。

    5. 设置时钟分频系数为 50。

    6. 设置接收数据长度阈值,在 EEPROM 模式下需要设置 NDF 值。

    7. 使能 SPI 外设。

    8. 配置 SPI 接收数据达到阈值中断 SPI_INT_RXF

    备注

    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_EEPROM;
        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  = 50;
        /* SPI_Direction_EEPROM mode read data lenth. */
        SPI_InitStruct.SPI_RxThresholdLevel  = 1 - 1;/* Flash id lenth = 3*/
        SPI_InitStruct.SPI_NDF               = 1 - 1;/* Flash id lenth = 3*/
        /* cause SPI_INT_RXF interrupt if data length in receive FIFO  >= SPI_RxThresholdLevel + 1*/
        SPI_InitStruct.SPI_FrameFormat = SPI_Frame_Motorola;
    
        SPI_Init(SPI0, &SPI_InitStruct);
        SPI_Cmd(SPI0, ENABLE);
    
        /* detect receive data */
        SPI_INTConfig(SPI0, SPI_INT_RXF, ENABLE);
        /* Config SPI interrupt */
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = SPI0_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
    }
    

功能实现

  1. 在主函数中执行 os_sched_start() ,开启任务调度。当 stack 准备好时,执行 app_handle_dev_state_evt ,执行 spi_demo

    1. 执行 spi_flash_read_id 读取 DEVICE_ID 信息。

    2. spi_flash_read_id 内,定义主机需要发送的指令和数据长度。

    3. 执行 spi_flash_write_read ,设定需要读取的数据长度,并将设定好的数据发送至 flash。

    void spi_demo(void)
    {
        uint8_t id[10];
        APP_PRINT_INFO0("[io_spi] spi_demo: Read flash id.");
        spi_flash_read_id(DEVICE_ID, id);
        flash_id_type = 0;
    }
    
    void spi_flash_read_id(Flash_ID_Type vIdType, uint8_t *pId)
    {
        uint8_t write_data = 0;
        uint16_t write_length = 1;
    
        switch (vIdType)
        {
        case DEVICE_ID:
            write_data = SPI_FLASH_DEVICE_ID;
            Flash_ID_Length = 4;
            break;
        case MF_DEVICE_ID:
            write_data = SPI_FLASH_MANU_ID;
            Flash_ID_Length = 5;
            break;
        case JEDEC_ID:
            write_data = SPI_FLASH_JEDEC_ID;
            Flash_ID_Length = 3;
            break;
        default:
            return;
        }
        spi_flash_write_read(&write_data, write_length, &pId[1], Flash_ID_Length);
        pId[0] = Flash_ID_Length;
    }
    
    void spi_flash_write_read(uint8_t *pWriteBuf, uint16_t vWriteLen, uint8_t *pReadBuf,
                          uint16_t vReadLen)
    {
        SPI_SetReadLen(FLASH_SPI, vReadLen);
        SPI_SendBuffer(FLASH_SPI, pWriteBuf, vWriteLen);
    }
    
  2. 当 SPI 接收到 1 字节的数据时,触发 SPI_INT_RXF 中断,进入中断处理函数 SPI0_Handler

    1. 清除中断标志位。

    2. 判断直到满足 FIFO 中数据长度等于 Flash_ID 长度,即等待 flash 将所有的数据发送至主机。

    3. 获取 FIFO 内的数据并打印。

    4. 继续执行 spi_flash_read_id ,读取下一个 ID 类型,重复此过程。

    void SPI0_Handler(void)
    {
        volatile uint8_t data_len = 0;
        volatile uint8_t SPI_ReadINTBuf[70] = {0};
    
        if (SPI_GetINTStatus(SPI0, SPI_INT_RXF) == SET)
        {
            SPI_ClearINTPendingBit(SPI0, SPI_INT_RXF);
    
            while (SPI_GetRxFIFOLen(SPI0) < Flash_ID_Length);
            data_len = SPI_GetRxFIFOLen(SPI0);
            Flash_Data[0] = data_len;
    
            for (uint8_t i = 0; i < data_len; i++)
            {
                /* Must read all data in receive FIFO , otherwise cause SPI_INT_RXF interrupt again. */
                Flash_Data[1 + i] = SPI_ReceiveData(SPI0);
            }
    
            T_IO_MSG int_spi_msg;
            int_spi_msg.type = IO_MSG_TYPE_SPI;
            int_spi_msg.subtype = 0;
            int_spi_msg.u.buf = (void *)(Flash_Data);
            if (false == app_send_msg_to_apptask(&int_spi_msg))
            {
                APP_PRINT_ERROR0("[io_spi] SPI0_Handler: Send int_spi_msg failed!");
                SPI_ClearINTPendingBit(SPI0, SPI_INT_RXF);
                //Add user code here!
                return;
            }
        }
    }
    
    void io_spi_handle_msg(T_IO_MSG *io_spi_msg)
    {
        uint8_t *p_buf = io_spi_msg->u.buf;
        uint8_t data_lenth = p_buf[0];
        APP_PRINT_INFO2("[io_spi] io_spi_handle_msg: data_lenth = %d, data = %b ", data_lenth,
                        TRACE_BINARY(data_lenth, &p_buf[1]));
        uint8_t id[1];
        flash_id_type++;
        if (flash_id_type < 3)
        {
            spi_flash_read_id((Flash_ID_Type)flash_id_type, id);
        }
    }