DMA Single Block
该示例通过使用 SPI 与外部 flash 进行通信,并使用 DMA 搬运数据。
SPI 通过 DMA 与外部 flash 通信,读取外部 flash 的 JEDEC_ID 信息,并通过 UART 将读到的数据发送至 PC 端串口助手。
示例中 SPI 使用 EEPROM 模式进行通信。
环境需求
该示例支持以下开发套件:
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#。
EVB 外接 FT232 模块,连接 P3_0(UART TX)和 FT232 的 RX,P3_1(UART RX)和 FT232 的 TX。
备注
W25Q128 的模块介绍参考 W25Q128硬件介绍 。
编译和下载
该示例的工程路径如下:
Project file: board\evb\io_sample\SPI\GDMA_SingleBlock\mdk
Project file: board\evb\io_sample\SPI\GDMA_SingleBlock\gcc
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译 APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下 reset 按键,开始运行。
测试验证
准备阶段
启动 PuTTY 或 UartAssist 等 PC 终端,连接到使用的 COM 端口,并进行以下 UART 设置:
波特率: 115200
8 数据位
1 停止位
无校验
无硬件流控
测试阶段
SPI 通过 DMA TX 通道发送读 JEDEC_ID 指令信息。发送完毕后,在 DMA 中断函数内打印发送完成信息。
[io_gdma]GDMA_TX_Channel_Handler: TX completed!
在 PC 端串口助手上可观察到 JEDEC_ID 信息。
EF 40 18
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
sdk\board\evb\io_sample\SPI\GDMA_SingleBlock
源码路径:
sdk\src\sample\io_sample\SPI\GDMA_SingleBlock
该工程的工程文件代码结构如下:
└── Project: gdma_singleblock
└── 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_gdma.c
├── rtl876x_uart.c
└── rtl876x_spi.c
├── profile
└── app includes the ble_peripheral user application implementation
├── main.c
├── ancs.c
├── app.c
├── app_task.c
├── io_spi.c
├── io_uart.c
└── io_gdma.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
,该函数为 SPI 相关引脚的 PAD/PINMUX 设置,包含如下流程:配置 PAD:设置引脚,PINMUX 模式,PowerOn,内部上拉,输出高。
配置 PINMUX:分配引脚分别为 SPI0_CLK_MASTER、SPI0_MO_MASTER、SPI0_MI_MASTER、SPI0_SS_N_0_MASTER 功能。
在
board_init
中,执行board_uart_init
,该函数为 UART 相关引脚的 PAD/PINMUX 设置,包含如下流程:配置 PAD:设置引脚,PINMUX 模式,PowerOn,内部上拉,输出失能。
配置 PINMUX:分配引脚分别为 UART0_TX 和 UART0_RX 功能。
在执行
os_sched_start()
开启任务调度后,在app_main_task
主任务内,执行driver_init
对外设驱动进行初始化配置。在
driver_init
中执行driver_uart_init
,该函数为 UART 外设的初始化,包含如下流程:使能 RCC 时钟。
默认设置波特率为 115200,数据位 8 位,停止位 1 位。
设置 RX FIFO 接收阈值为 16。
设置空闲时间为 2 Byte,即当前波特率下接收 2 个字节的时间。
void driver_uart_init(void) { RCC_PeriphClockCmd(APBPeriph_UART0, APBPeriph_UART0_CLOCK, ENABLE); /* uart init */ UART_InitTypeDef UART_InitStruct; UART_StructInit(&UART_InitStruct); UART_InitStruct.UART_Parity = UART_PARITY_NO_PARTY; UART_InitStruct.UART_StopBits = UART_STOP_BITS_1; UART_InitStruct.UART_WordLen = UART_WORD_LENGTH_8BIT; UART_InitStruct.UART_RxThdLevel = 16; //1~29 UART_InitStruct.UART_IdleTime = UART_RX_IDLE_2BYTE; //idle interrupt wait time UART_Init(UART0, &UART_InitStruct); }
在主函数中执行
os_sched_start()
,开启任务调度。当 stack 准备好时,执行app_handle_dev_state_evt
,在spi_demo
中执行driver_spi_gdma_init
对 SPI 和 DMA 外设进行初始化。初始化 SPI 外设:
使能 RCC 时钟。
设置 SPI 的传输模式为 EEPROM 模式。
设置数据宽度为 8 位。
设置时钟分频系数为 100。
在 EEPROM 模式需要设置 NDF 为
GDMA_READ_SIZE_MAX - 1
。使能 DMA TX 和 RX 功能,设置 waterLevel。
RCC_PeriphClockCmd(APBPeriph_SPI0, APBPeriph_SPI0_CLOCK, ENABLE); /*----------------------SPI init---------------------------------*/ SPI_StructInit(&SPI_InitStructure); SPI_InitStructure.SPI_Direction = SPI_Direction_EEPROM;//SPI_Direction_RxOnly; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_BaudRatePrescaler = 100; SPI_InitStructure.SPI_FrameFormat = SPI_Frame_Motorola; SPI_InitStructure.SPI_NDF = GDMA_READ_SIZE_MAX - 1; SPI_InitStructure.SPI_RxDmaEn = ENABLE; SPI_InitStructure.SPI_TxDmaEn = ENABLE; SPI_InitStructure.SPI_RxWaterlevel = 1; SPI_InitStructure.SPI_TxWaterlevel = 1; SPI_Init(FLASH_SPI, &SPI_InitStructure);
初始化 DMA TX 外设:
设置 DMA 传输方向为内存到外设。
设置源端地址自增,目的端地址固定。
设置数据宽度为 8 位,每次 burst 传输一个字节的数据。
设置源端地址为
GDMA_WriteCmdBuffer
,目的端地址为FLASH_SPI->DR
。配置 DMA 传输完成中断
GDMA_INT_Transfer
。
/*---------------------TX GDMA initial------------------------------*/ GDMA_StructInit(&GDMA_InitStruct); GDMA_InitStruct.GDMA_ChannelNum = GDMA_TX_CHANNEL_NUM; GDMA_InitStruct.GDMA_DIR = GDMA_DIR_MemoryToPeripheral; GDMA_InitStruct.GDMA_BufferSize = 0; GDMA_InitStruct.GDMA_SourceInc = DMA_SourceInc_Inc; GDMA_InitStruct.GDMA_DestinationInc = DMA_DestinationInc_Fix; GDMA_InitStruct.GDMA_SourceDataSize = GDMA_DataSize_Byte; GDMA_InitStruct.GDMA_DestinationDataSize = GDMA_DataSize_Byte; GDMA_InitStruct.GDMA_SourceMsize = GDMA_Msize_1; GDMA_InitStruct.GDMA_DestinationMsize = GDMA_Msize_1; GDMA_InitStruct.GDMA_SourceAddr = (uint32_t)GDMA_WriteCmdBuffer; GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)FLASH_SPI->DR; GDMA_InitStruct.GDMA_DestHandshake = GDMA_Handshake_SPI0_TX; GDMA_InitStruct.GDMA_ChannelPriority = 1; GDMA_InitStruct.GDMA_Multi_Block_En = 0; GDMA_Init(GDMA_TX_Channel, &GDMA_InitStruct); /*-----------------GDMA IRQ-----------------------------*/ NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = GDMA_TX_Channel_IRQn; NVIC_InitStruct.NVIC_IRQChannelPriority = 3; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); /* Enable transfer interrupt */ GDMA_INTConfig(GDMA_TX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
初始化 DMA RX 外设:
设置 DMA 的传输方向为外设到内存。
设置源端地址固定,目的端地址自增。
设置数据宽度为 8 位,每次 burst 传输一个字节的数据。
设置源端地址为
FLASH_SPI->DR
,目的端地址为GDMA_Recv_Buffer
。配置 DMA 传输完成中断
GDMA_INT_Transfer
。
GDMA_StructInit(&GDMA_InitStruct); GDMA_InitStruct.GDMA_ChannelNum = GDMA_RX_CHANNEL_NUM; GDMA_InitStruct.GDMA_DIR = GDMA_DIR_PeripheralToMemory; GDMA_InitStruct.GDMA_BufferSize = GDMA_TRANSFER_SIZE; GDMA_InitStruct.GDMA_SourceInc = DMA_SourceInc_Fix; GDMA_InitStruct.GDMA_DestinationInc = DMA_DestinationInc_Inc; GDMA_InitStruct.GDMA_SourceDataSize = GDMA_DataSize_Byte; GDMA_InitStruct.GDMA_DestinationDataSize = GDMA_DataSize_Byte; GDMA_InitStruct.GDMA_SourceMsize = GDMA_Msize_1; GDMA_InitStruct.GDMA_DestinationMsize = GDMA_Msize_1; GDMA_InitStruct.GDMA_SourceAddr = (uint32_t)FLASH_SPI->DR; GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)GDMA_Recv_Buffer; GDMA_InitStruct.GDMA_SourceHandshake = GDMA_Handshake_SPI0_RX; GDMA_InitStruct.GDMA_ChannelPriority = 1; GDMA_InitStruct.GDMA_Multi_Block_En = 0; GDMA_Init(GDMA_RX_Channel, &GDMA_InitStruct); /*-----------------GDMA IRQ-----------------------------*/ NVIC_InitStruct.NVIC_IRQChannel = GDMA_RX_Channel_IRQn; NVIC_InitStruct.NVIC_IRQChannelPriority = 3; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); /* Enable transfer interrupt */ GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
使能 DMA RX 通道,使能 SPI。
设置 DMA TX 通道的总传输数据量为 1,使能 DMA TX 通道。
GDMA_Cmd(GDMA_RX_CHANNEL_NUM, ENABLE); SPI_Cmd(FLASH_SPI, ENABLE); /* Send read data command */ GDMA_SetBufferSize(GDMA_TX_Channel, 1); GDMA_SetSourceAddress(GDMA_TX_Channel, (uint32_t)GDMA_WriteCmdBuffer); GDMA_Cmd(GDMA_TX_CHANNEL_NUM, ENABLE);
功能实现
SPI 通过 DMA TX 通道发送读 JEDEC_ID 指令给到外部 flash。DMA 传输完毕后,触发中断,进入中断处理函数
GDMA_TX_Channel_Handler
,打印相关信息,清除中断标志位。void GDMA_TX_Channel_Handler(void) { GDMA_INTConfig(GDMA_TX_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE); DBG_DIRECT("[io_gdma]GDMA_TX_Channel_Handler: TX completed!"); GDMA_ClearINTPendingBit(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer); GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE); }
flash 向主机回复 JEDEC_ID 信息,主机通过 DMA RX 通道接收数据。数据搬运完成时,触发中断,进入中断处理函数
GDMA_RX_Channel_Handler
。失能 DMA 中断。
执行
uart_senddata_continuous
,将收到的数据内容发送至 PC 端串口助手。清除中断标志位。
void GDMA_RX_Channel_Handler(void) { GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, DISABLE); T_IO_MSG int_gdma_msg; int_gdma_msg.type = IO_MSG_TYPE_GDMA; int_gdma_msg.subtype = 0; int_gdma_msg.u.buf = (void *)GDMA_Recv_Buffer; if (false == app_send_msg_to_apptask(&int_gdma_msg)) { APP_PRINT_ERROR0("[io_gdma]GDMA_Channel_Handler: Send int_gdma_msg failed!"); //Add user code here! GDMA_ClearINTPendingBit(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer); return; } GDMA_ClearINTPendingBit(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer); GDMA_INTConfig(GDMA_RX_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE); } void io_handle_gdma_msg(T_IO_MSG *io_gdma_msg) { uint8_t *p_buf = io_gdma_msg->u.buf; uint16_t data_len = (GDMA_TRANSFER_SIZE); APP_PRINT_INFO1("[io_gdma] io_handle_gdma_msg: read data complete,data_len = %d", data_len); uart_senddata_continuous(UART0, p_buf, data_len); }