Fixed Length Transmit and Receive - DMA
该示例使用 UART 和 DMA 与 PC 终端进行数据通信。
SoC 使用 DMA 将数据发送到 PC 终端程序(如 PUTTY 或 UartAssist),DMA 将数据从 Memory 传输到 UART TX FIFO 外设。
SoC 使用 DMA 接收 PC 终端输入的数据,DMA 将数据从 UART RX FIFO 外设传输到 Memory。
一旦 DMA 传输完成,SoC 将缓冲数据发送回 PC 终端。
在该示例中,PC 终端程序必须发送大于等于 UART_RX_GDMA_BUFFER_SIZE
个字节的数据,才会触发 DMA 中断。
环境需求
该示例支持以下开发套件:
Hardware Platforms |
Board Name |
---|---|
RTL8752H HDK |
RTL8752H EVB |
更多信息请参考 快速入门。
硬件连线
连接 P3_0(UART TX)和 FT232 的 RX,P3_1(UART RX)和 FT232 的 TX。
编译和下载
该示例的工程路径如下:
Project file: board\evb\io_sample\UART\GDMA\mdk
Project file: board\evb\io_sample\UART\GDMA\gcc
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译 APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下 reset 按键,开始运行。
测试验证
准备阶段
启动 PuTTY 或 UartAssist 等 PC 终端,连接到使用的 COM 端口,并进行以下 UART 设置:
波特率: 115200
8 数据位
1 停止位
无校验
无硬件流控
测试阶段
EVB 复位后,SoC 首先发送 100 个字节的数据至 PC 端,其中后 10 个字节为 FF。随后 SoC 通过 DMA 发送 4095 个字节的数据(该示例中 RX Buffer Size
UART_RX_GDMA_BUFFER_SIZE
设置为 4095,数据从 0x00-0xFF 循环),观察 PC 终端上出现的数据信息,在 Debug Analyzer 内打印 DMA TX 中断信息。TX DMA HANDLER
在 PC 中断上输入超过 4095 个字节的数据(该示例中 RX Buffer Size
UART_RX_GDMA_BUFFER_SIZE
设置为 4095),会触发 DMA 接收中断,SoC 会将相同数据发回至 PC 端,观察 PC 终端上出现的数据信息,在 Debug Analyzer 内打印 DMA RX 中断信息。RX DMA HANDLER
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
sdk\board\evb\io_sample\UART\GDMA
源码路径:
sdk\src\sample\io_sample\UART\GDMA
该工程的工程文件代码结构如下:
└── Project: gdma
└── 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
├── profile
└── app includes the ble_peripheral user application implementation
├── main.c
├── ancs.c
├── app.c
├── app_task.c
└── io_uart.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_uart_init
,该函数为 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 时钟。
配置 UART 的波特率为 115200。
使能 UART TX/RX DMA 传输,配置对应的 waterLevel。
void driver_uart_init(void) { UART_DeInit(UART0); RCC_PeriphClockCmd(APBPeriph_UART0, APBPeriph_UART0_CLOCK, ENABLE); /* uart init */ UART_InitTypeDef UART_InitStruct; UART_StructInit(&UART_InitStruct); UART_InitStruct.UART_Div = BaudRate_Table[BAUD_RATE_115200].div; UART_InitStruct.UART_Ovsr = BaudRate_Table[BAUD_RATE_115200].ovsr; UART_InitStruct.UART_OvsrAdj = BaudRate_Table[BAUD_RATE_115200].ovsr_adj; UART_InitStruct.UART_RxThdLevel = 16; //1~29 UART_InitStruct.UART_IdleTime = UART_RX_IDLE_2BYTE; //idle interrupt wait time UART_InitStruct.UART_DmaEn = UART_DMA_ENABLE; UART_InitStruct.UART_TxWaterLevel = 15; //Better to equal TX_FIFO_SIZE(16)- GDMA_MSize UART_InitStruct.UART_RxWaterLevel = 1; //Better to equal GDMA_MSize #if (UART_GDMA_TX_ENABLE == EVB_ENABLE) UART_InitStruct.UART_TxDmaEn = ENABLE; #endif #if (UART_GDMA_RX_ENABLE == EVB_ENABLE) UART_InitStruct.UART_RxDmaEn = ENABLE; #endif UART_Init(UART0, &UART_InitStruct); }
在主函数中执行
os_sched_start()
,开启任务调度。当 stack 准备好时,执行app_handle_dev_state_evt
,执行io_uart_demo
。在
io_uart_demo
内,执行UART_SendDataByGDMA
和UART_ReceiveDataByGDMA
对 DMA TX/RX 外设进行初始化。在
UART_SendDataByGDMA
内对 DMA TX 外设进行初始化,执行以下步骤:初始化 DMA 发送数组。
设置数据传输方向为内存至外设。
设置源端地址自增,目的端地址固定。
设置数据宽度为 8 位,每次 burst 传输一个数据。
设置源端地址为
GDMA_SendData_Buffer
,目的端地址为(&(UART0->RB_THR))
。配置 DMA 总传输完成中断
GDMA_INT_Transfer
。使能 DMA TX,开始发送数据。
void UART_SendDataByGDMA(void) { /*--------------initialize test buffer which for sending data to UART---------------------*/ for (uint32_t i = 0; i < UART_TX_GDMA_BUFFER_SIZE; i++) { GDMA_SendData_Buffer[i] = 0x10 + i; } /*--------------GDMA init-----------------------------*/ GDMA_InitTypeDef GDMA_InitStruct; GDMA_StructInit(&GDMA_InitStruct); GDMA_InitStruct.GDMA_ChannelNum = UART_TX_GDMA_CHANNEL_NUM; GDMA_InitStruct.GDMA_DIR = GDMA_DIR_MemoryToPeripheral; GDMA_InitStruct.GDMA_BufferSize = UART_TX_GDMA_BUFFER_SIZE;//determine total transfer size 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_SendData_Buffer; GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)(&(UART0->RB_THR)); GDMA_InitStruct.GDMA_DestHandshake = GDMA_Handshake_UART0_TX; GDMA_InitStruct.GDMA_ChannelPriority = 2;//channel prority between 0 to 5 GDMA_Init(UART_TX_GDMA_CHANNEL, &GDMA_InitStruct); GDMA_INTConfig(UART_TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE); /*-----------------GDMA IRQ init-------------------*/ NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = UART_TX_GDMA_CHANNEL_IRQN; NVIC_InitStruct.NVIC_IRQChannelPriority = 3; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); /*-----------------start to send data-----------*/ GDMA_Cmd(UART_TX_GDMA_CHANNEL_NUM, ENABLE); }
在
UART_ReceiveDataByGDMA
内对 DMA RX 外设进行初始化,执行以下步骤:初始化 DMA 接收数组。
设置数据传输方向为外设至内存。
设置源端地址固定,目的端地址自增。
设置数据宽度为 8 位,每次 burst 传输一个数据。
设置源端地址为
(&(UART0->RB_THR))
,目的端地址为GDMA_ReceiveData_Buffer
。配置 DMA 总传输完成中断
GDMA_INT_Transfer
。使能 DMA RX,开始接收数据。
void UART_ReceiveDataByGDMA(void) { /*--------------initialize test buffer which for sending data to UART---------------------*/ for (uint32_t i = 0; i < UART_RX_GDMA_BUFFER_SIZE; i++) { GDMA_ReceiveData_Buffer[i] = 0; } /*--------------GDMA init-----------------------------*/ GDMA_InitTypeDef GDMA_InitStruct; GDMA_StructInit(&GDMA_InitStruct); GDMA_InitStruct.GDMA_ChannelNum = UART_RX_GDMA_CHANNEL_NUM; GDMA_InitStruct.GDMA_DIR = GDMA_DIR_PeripheralToMemory; GDMA_InitStruct.GDMA_BufferSize = UART_RX_GDMA_BUFFER_SIZE;//determine total 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)(&(UART0->RB_THR)); GDMA_InitStruct.GDMA_SourceHandshake = GDMA_Handshake_UART0_RX; GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)GDMA_ReceiveData_Buffer; GDMA_InitStruct.GDMA_ChannelPriority = 2;//channel prority between 0 to 5 GDMA_Init(UART_RX_GDMA_CHANNEL, &GDMA_InitStruct); GDMA_INTConfig(UART_RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE); /*-----------------GDMA IRQ init-------------------*/ NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = UART_RX_GDMA_CHANNEL_IRQN; NVIC_InitStruct.NVIC_IRQChannelPriority = 3; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); /*-----------------start to send data-----------*/ GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, ENABLE); }
功能实现
在
io_uart_demo
中,首先执行UART_SendData_Continuous
,发送 100 个字节的数据至 PC 端,在 PC 端串口助手可以看到发送数据的信息。执行
UART_SendDataByGDMA
,DMA TX 通道将预设的 4095 个字节的数据发送至 PC 端,在 PC 端串口助手可以看到发送数据的信息。执行
UART_ReceiveDataByGDMA
,开启 DMA RX 通道进行数据接收。当 PC 端发送的数据大于等于 4095 个字节时,触发 DMA 中断,进入中断处理函数UART_RX_GDMA_Handler
。清除中断标志位。
定义消息类型为
IO_MSG_TYPE_GDMA
,消息子类型为 1,发送消息至 app task。主 task 检测到消息后,执行io_handle_gdma_msg
,对消息进行处理。若检测到的子消息类型为 1,代表是 DMA RX 中断的消息处理,执行
UART_SendData_Continuous
,将 DMA 接收到的数据发回至 PC 端。
void UART_RX_GDMA_Handler(void) { GDMA_ClearINTPendingBit(UART_RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer); DBG_DIRECT("RX DMA HANDLER"); T_IO_MSG int_uart_msg; int_uart_msg.type = IO_MSG_TYPE_GDMA; int_uart_msg.subtype = 1; int_uart_msg.u.buf = (void *)(GDMA_SendData_Buffer); if (false == app_send_msg_to_apptask(&int_uart_msg)) { APP_PRINT_ERROR0("[io_adc]UART_GDMA_Channel_Handler: Send int_uart_msg failed!"); //Add user code here! GDMA_ClearINTPendingBit(UART_TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer); return; } } void io_handle_gdma_msg(T_IO_MSG *io_gdma_msg) { uint16_t subtype = io_gdma_msg->subtype; if (subtype == 0) ... else if (subtype == 1) { APP_PRINT_INFO0("io_handle_gdma_msg: uart rx done "); UART_SendData_Continuous(UART0, GDMA_ReceiveData_Buffer, UART_RX_GDMA_BUFFER_SIZE); } }