Unfixed length DMA
该示例使用 UART 和 DMA 与PC终端进行任意长度数据通信。
MCU使用DMA接收PC终端输入的数据,同时在 UART_FLAG_RX_IDLE
中断中获取任意长度数据长度、处理数据、置位received_flag。
一旦received_flag被设置,MCU使用DMA将收到的数据发送回PC终端。
环境需求
该示例支持以下开发套件:
Hardware Platforms |
Board Name |
---|---|
RTL87x2G HDK |
RTL87x2G EVB |
更多信息请参考 快速入门
硬件连线
连接P3_0和RX,P3_1和TX。
编译和下载
该示例的工程路径如下:
Project file: samples\peripheral\uart\unfixedlen_gdma\proj\rtl87x2g\mdk
Project file: samples\peripheral\uart\unfixedlen_gdma\proj\rtl87x2g\gcc
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下复位按键,开始运行。
测试验证
准备阶段
启动 PuTTY 或 UartAssist 等PC终端,连接到使用的COM端口,并进行以下UART设置:
波特率: 115200
8 数据位
1 停止位
无校验
无硬件流控
测试阶段
当EVB启动后,在DebugAnalyzer工具内观察如下LOG。
Start uart tx rx unfixedlen by gdma test!
在PC终端上输入字符串,并观察PC终端上是否出现相同的字符串。同时在DebugAnalyzer工具上会显示接收到的数据和中断信息。假设PC terminal输入数据长度为number,当number处于不同case时打印中断LOG情况如下:
GDMA0_Channel4_Handler /* time = (number / block_size) > 0 触发time次中断并打印LOG */ UART_FLAG_RX_IDLE /* time = (number % block_size) > 0 且满足 UART_FLAG_RX_IDLE 中断条件触发该中断并打印LOG */ value is 0x.. value is 0x.. value is 0x.. ... UART_TX_GDMA_Handler /* 一旦received_flag被设置,MCU使用GDMA将number长度的数据发送回PC终端,发送完成触发该中断并打印LOG */
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
sdk\samples\peripheral\uart\unfixedlen_gdma\proj
源码路径:
sdk\samples\peripheral\uart\unfixedlen_gdma\src
该工程的工程文件代码结构如下:
└── Project: output_toggle
└── secure_only_app
└── Device includes startup code
├── startup_rtl.c
└── system_rtl.c
├── CMSIS includes CMSIS header files
├── CMSE Library Non-secure callable lib
├── lib includes all binary symbol files that user application is built on
└── rtl87x2g_io.lib
├── peripheral includes all peripheral drivers and module code used by the application
├── rtl_gdma.c
├── rtl_nvic.c
├── rtl_pinmux.c
├── rtl_rcc.c
└── rtl_uart.c
└── APP includes the ble_peripheral user application implementation
├── io_uart.c
└── main_ns.c
初始化
初始化流程包括了 board_uart_init
, driver_uart_init
,driver_gdma3_init
和 driver_gdma4_init
。
board_uart_init
中包含了PAD与PINMUX设置:
配置PAD:设置引脚、PINMUX模式、PowerOn、内部上拉。
配置PINMUX:分配引脚分别为UART3_TX、UART3_RX功能。
driver_uart_init
包含了对uart外设的初始化:
使能PCC时钟源。
设置波特率为115200,校验位为无奇偶校验,停止位为1位,数据长度为8bits。
使能UART DMA传输。
设置UART_TxWaterLevel为1,推荐设置为
GDMA_MSize
。设置UART_RxWaterLevel为1,推荐设置为
GDMA_MSize
。使能UART TX DMA, RX DMA。
使能UART 接收 IDLE 中断
UART_INT_RX_IDLE
。
RCC_PeriphClockCmd(APBPeriph_UART3, APBPeriph_UART3_CLOCK, ENABLE);
...
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_IdleTime = UART_RX_IDLE_2BYTE; //idle interrupt wait time
UART_InitStruct.UART_TxWaterLevel = 1; /* Better to equal: GDMA_MSize */
UART_InitStruct.UART_RxWaterLevel = 1; /* Better to equal: GDMA_MSize */
UART_InitStruct.TxDmaEn = ENABLE;
UART_InitStruct.RxDmaEn = ENABLE;
UART_InitStruct.dmaEn = UART_DMA_ENABLE;
UART_Init(UART_DEMO, &UART_InitStruct);
UART_INTConfig(UART_DEMO, UART_INT_RX_IDLE | UART_INT_LINE_STS, ENABLE);
...
driver_gdma3_init
包含了对GDMA TX的初始化。
使用GDMA通道3。
GDMA传输方向为内存到外设传输。
源端地址为
GDMA_Tx_Buf
,目的端地址为(&(UART_DEMO->UART_RBR_THR)
。使能GDMA通道3总传输完成中断
GDMA_INT_Transfer
。
GDMA_InitStruct.GDMA_ChannelNum = UART_TX_GDMA_CHANNEL_NUM;
GDMA_InitStruct.GDMA_DIR = GDMA_DIR_MemoryToPeripheral;
...
GDMA_InitStruct.GDMA_SourceAddr = (uint32_t)GDMA_Tx_Buf;
GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)(&(UART_DEMO->UART_RBR_THR));
GDMA_InitStruct.GDMA_DestHandshake = GDMA_Handshake_UART3_TX;
GDMA_Init(UART_TX_GDMA_CHANNEL, &GDMA_InitStruct);
/* Enable transfer finish interrupt */
GDMA_INTConfig(UART_TX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
...
driver_gdma4_init
包含了对GDMA RX的初始化。
使用GDMA通道4。
GDMA传输方向为 外设 到 内存 传输。
源端地址为
(&(UART_DEMO->UART_RBR_THR)
,目的端地址为GDMA_Rx_Buf
。使能GDMA通道4总传输完成中断
GDMA_INT_Transfer
。使能GDMA通道4传输。
GDMA_InitStruct.GDMA_ChannelNum = UART_RX_GDMA_CHANNEL_NUM;
GDMA_InitStruct.GDMA_DIR = GDMA_DIR_PeripheralToMemory;
GDMA_InitStruct.GDMA_BufferSize = GDMA_BLOCK_SIZE;
...
GDMA_InitStruct.GDMA_SourceAddr = (uint32_t)(&(UART_DEMO->UART_RBR_THR));
GDMA_InitStruct.GDMA_DestinationAddr = (uint32_t)GDMA_Rx_Buf;
GDMA_InitStruct.GDMA_SourceHandshake = GDMA_Handshake_UART3_RX;
GDMA_Init(UART_RX_GDMA_CHANNEL, &GDMA_InitStruct);
/* Enable transfer finish interrupt */
GDMA_INTConfig(UART_RX_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
...
GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, ENABLE);
功能实现
在 PC 终端上输入字符会启动从 UART RX FIFO 到内存的 GDMA 传输。
假设 PC 终端上的输入数据长度为 number, 对于不同情况的中断如下:
如果 number / block_size > 0,则触发 n_time 次
GDMA_INT_Transfer
中断。如果 number % block_size > 0 且满足
UART_FLAG_RX_IDLE
中断条件(若在读完 RX FIFO 中的所有数据后的 RX 空闲超时时间内没有数据放入 RX FIFO),则触发 n_time 次UART_INT_RX_IDLE
中断。
当触发
UART_FLAG_RX_IDLE
中断并进入UART中断处理函数UART3_Handler
时:暂停UART_RX_GDMA_CHANNEL通道传输。
关闭
UART_INT_RX_IDLE
中断。获取GDMA传输长度(即GDMA接收到的数据长度)并将接收到的数据保存到GDMA_Tx_Buf中。
置位接收标志receive_offset以指示接收完成。
在恢复和禁用GDMA传输后重新初始化GDMA以接收下一个数据。
在清空UART FIFO后再次使能
UART_INT_RX_IDLE
中断。
if (UART_GetFlagState(UART3, UART_FLAG_RX_IDLE) == SET)
{
DBG_DIRECT("UART_FLAG_RX_IDLE");
/* Suspend GDMA_Channel2 */
GDMA_SuspendCmd(UART_RX_GDMA_CHANNEL, ENABLE);
UART_INTConfig(UART3, UART_INT_RX_IDLE, DISABLE);
data_len = GDMA_GetTransferLen(UART_RX_GDMA_CHANNEL);
if (data_len)
{
receive_offset += data_len;
memcpy(GDMA_Tx_Buf + GDMA_BLOCK_SIZE * count, GDMA_Rx_Buf, data_len);
...
GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, DISABLE);
GDMA_SuspendCmd(UART_RX_GDMA_CHANNEL, DISABLE);
driver_gdma4_init();
/* GDMA TX flag */
receiveflg = true;
}
......
UART_ClearRxFIFO(UART3);
UART_INTConfig(UART3, UART_INT_RX_IDLE, ENABLE);
}
...
}
当触发
GDMA_INT_Transfer
中断并进入GDMA中断处理函数GDMA0_Channel4_Handler
时:关闭GDMA RX通道并清除GDMA通道3中断标志位。
将接收到的数据保存到GDMA_Tx_Buf中。
重新设置目的端地址并使能GDMA RX通道传输。
GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, DISABLE);
GDMA_ClearAllTypeINT(UART_RX_GDMA_CHANNEL_NUM);
...
memcpy(GDMA_Tx_Buf + GDMA_BLOCK_SIZE * (count - 1), GDMA_Rx_Buf, GDMA_BLOCK_SIZE);
...
GDMA_SetDestinationAddress(UART_RX_GDMA_CHANNEL, (uint32_t)GDMA_Rx_Buf);
GDMA_Cmd(UART_RX_GDMA_CHANNEL_NUM, ENABLE);
一旦置位received_flag, 执行
GDMA_SetBufferSize()
更新GDMA TX Block Size, 执行GDMA_Cmd()
即开始GDMA从Memory向UART TX FIFO传输数据。设GDMA TX Block size为 PC终端输入字符数 number 。
重新使能GDMA TX通道。
while (1)
{
if (receiveflg)
{
GDMA_SetBufferSize(UART_TX_GDMA_CHANNEL, receive_offset);
GDMA_Cmd(UART_TX_GDMA_CHANNEL_NUM, ENABLE);
...
}
}
当GDMA传输完成时,会触发
GDMA_INT_Transfer
中断,进入中断处理函数GDMA0_Channel3_Handler
。打印GDMA搬运UART接收数据信息。
关闭GDMA TX通道传输并清除GDMA TX通道中断标志位。
void GDMA0_Channel3_Handler(void)
{
DBG_DIRECT("UART_TX_GDMA_Handler");
GDMA_Cmd(UART_TX_GDMA_CHANNEL_NUM, DISABLE);
GDMA_ClearAllTypeINT(UART_TX_GDMA_CHANNEL_NUM);
}