这里选用STM32H563Nucleo开发板,使用USART3外设演示DMA链表模式的实现过程。大致过程是这样的:
STM32H563芯片上的USART3与板载STLINK的VCP相连,通过PC端串口助手软件先后发送3串字符给MCU,MCU的USART3通过DMA以链表模式依次接收并存到相应内存,同时在相应的调试界面的观察窗口加以显示。接收过程对应3个DMA接收节点,他们构成1个DMA接收队列。当MCU接收到3串字符后,也以DMA 链表模式依次发送3串字符到PC端的串口助手并显示出来。同样,USART3的DMA 发送也用到3个节点以组成1个DMA发送队列。
GPDMA1的2个通道CH0,CH1,配置在链表模式。其中CH0用于USART3 RX DMA队列,CH1用于USART3 TX DMA队列。这个通道安排可以在用户代码里调整。
(相关资料图)
下面使用STM32CbueMx图形化工具进行基本配置后创建测试工程。【注:手机模式下文中图片可放大查看】
3个USART3 DMA RX节点的配置如下【3个配置类似,只是源地址、目标地址有差异】:
3个USART3 DMA TX节点的配置如下【3个配置类似,只是源地址、目标地址有差异】:
相关的用户参考代码如下:
#define RXBUFFERSIZE (8)#define TXBUFFERSIZE (22)volatileuint8_tRxComplete=0;//DMA接收完成标志ALIGN_32BYTES (uint8_t aTxBuffer0[])="STM32H5_DMA_LIST_0";ALIGN_32BYTES (uint8_t aTxBuffer1[])="STM32H5_DMA_LIST_1";ALIGN_32BYTES (uint8_t aTxBuffer2[])="STM32H5_DMA_LIST_2";ALIGN_32BYTES (uint8_t aRxBuffer0[RXBUFFERSIZE]);ALIGN_32BYTES (uint8_t aRxBuffer1[RXBUFFERSIZE]);ALIGN_32BYTES (uint8_t aRxBuffer2[RXBUFFERSIZE]);DMA_HandleTypeDef handle_GPDMA1_Channel1;DMA_HandleTypeDef handle_GPDMA1_Channel0;UART_HandleTypeDef huart3;int main(void){/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */ MX_GPIO_Init(); MX_GPDMA1_Init();//MX_ADC1_Init(); MX_ICACHE_Init(); MX_USART3_UART_Init();/* USER CODE BEGIN 2 */ MX_Uart_tx_queue_Config(); MX_Uart_rx_queue_Config();/* Link UART queue to DMA channel */ HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel0, &Uart_tx_queue);/* Associate the initialized GPDMA handle to the UART handle */ __HAL_LINKDMA(&huart3, hdmatx, handle_GPDMA1_Channel0);/* Link UART queue to DMA channel */ HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel1, &Uart_rx_queue);/* Associate the initialized GPDMA handle to the UART handle */ __HAL_LINKDMA(&huart3, hdmarx, handle_GPDMA1_Channel1);HAL_UART_Receive_DMA(&huart3, (uint8_t *)aRxBuffer0, RXBUFFERSIZE);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1) {if(RxComplete)//checkuartRxDMAtransfercomplete ornot { RxComplete=0; huart3.gState = HAL_UART_STATE_READY; HAL_UART_Transmit_DMA(&huart3, (uint8_t*)aTxBuffer0, TXBUFFERSIZE); } }}/*UART3DMA接收完成中断回调函数*/void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){RxComplete++;//DMAtransfercompleted}
下面开始运行程序以验证结果。
PC串口终端先发送三串字符被MCU USART3通过DMA接收,然后MCU以DMA链表的形式发送三串字符到串口终端。下图中右边观察串口的字符为USART3通过DMA以链表形式接收的来自串口助手的数据。下图中绿色方框内三串字符为STM32H563 USART3以DMA链表形式3次发送出来的。
在上面测试中,关于DMA传输完成事件的配置,我选择的针对整个DMA传输链完成传输后才产生完成事件。以上面的USART3 RX DMA接收为例,当3个节点都完成DMA接收时才触发DMA传输完成事件。当然,我们也可以基于每个DMA传输节点来产生DMA传输完成事件,比如按下面的配置修改。
如果这样修改之后,其它地方不动,这时USART3 通过DMA每收到一串数据都会触发传输完成事件,并在相应中断里设置完成事件标志【RxComplete】,进而启动USART3发送的DMA 链式传输。见下图结果【褐紫色字符是USART3通过DMA接收到的数据】:
其实,在前面验证过程中,发生了点小波折也顺便分享出来。
当我做完基本的配置,创建工程、添加用户代码。编译、除错后,开始运行程序时并不顺利。
我先使用串口助手发送字符串给MCU,可是在USART3的DMA接收队列里,只有第一个节点有反应。通过开发环境的观察窗口看到的接收数据跟我发出去的字符大相径庭,其它2个DMA接收节点不论我发送多少数据就根本没反应。因为我设计的就是只有当接收队列即3个接收节点都正常接收后才会启动USART3的DMA发送队列。所以此时USART3也就没有任何数据发送出来。检查配置和用户代码,但没有很快发现问题原因。
后来在检查USART3的基本通信配置时,无意中发现了一点异样。就是有关接收数据是否需要翻转的配置项不知何时变成Enable了。
显然,这个地方不能翻转,不然发送的数据跟接收的数据肯定不一样了。我清楚地记得这个地方我是没动的,不知是不是某个时候不小心碰着而改动了。立即将其Disable掉后进行测试验证。
测试结果发现第一个DMA接收节点的数据正常了。但奇怪的是第2个、第3个接收节点仍然接收不到数据。又是一番配置和代码核对,无果。
后来,先干脆不管接收队列,而试着直接启动USART3的DMA发送队列。庆幸的是,发送很正常。看来,USART3 TX DMA发送队列的配置是靠谱的。其实USART3 RX DMA接收队列的配置跟发送队列配置很相似,只是请求源、传输端地址的配置差异。再次逐个核对DMA接收节点2和3的配置。功夫不负有心人,果真发现乌龙了。在DMA接收节点2的DMA请求配置那里,我竟然不小心选成USART1的了,一字之差,谬以千里。节点3的配置没有问题。看来问题就出在接收节点2的那个误选项。立马验证,结果一切正常了。
为什么只是节点2配置有问题,节点3也不能接收数据呢?因为这3个节点构成了一条关于DMA传输的时序、事件配合链,节点2没有正常完成,节点3也没法正常启动。
另外,还解释一个变量。我在代码里设置了RxComplete变量,是用来标记DMA接收完成事件的,在主程序和中断里都用到了。记得定义它时加上volatile,不然当优化等级调至较高时,可能出现程序运行异常情况。
OK,今天的DMA 链表功能的应用演示就到分享这里,下次再聊。
审核编辑:汤梓红