STM32LL库编程系列番外——DMA常用编程

前言

前面与很多工程都有使用到DMA,DMA在传输大量数据时有非常好的效果,但是DMA本人也是边接触,边学习,不断使用不断总结,这里给出本人认为一般情况下非常好用DMA发送与接收LL库配置代码。


本人目前只使用了DMA在串口外设和内存之间的传输,所以基于该任务编写代码,但是我相信在其他的引用上也殊途同归。
首先在cubeMX中配置好DMA的参数与中断,不熟悉得可以去看往期文章。

在dma.c文件编写DMA初始化函数如下:
这里说明一下,创建接收和发送数组,一般我喜欢接收数组比接收的数据位多一位,用来存储该数据格式是否正确。设置的接受数据长度也比传输的多一位。发送数组和数据长度按照要发送的数据一样,可以对需要发送的数组把帧头和帧尾直接初始化掉,避免其他程序每次的写入,更加精炼。如下所示:我需要接收数据的字节长度为14个字节,发送数据的字节长度为6字节。大家使用时按照这个模板套就好了。

//定义变量
uint8_t RS422_R_data[15];
uint8_t RS422_R_date_size = 15;
uint8_t RS422_T_data[6]={0xff,0xfe,0,0,0,0x0f};
uint8_t RS422_T_date_size = 6;
void USART2_DMA_init(void)
{
	//DMA接收配置
	LL_DMA_SetPeriphAddress(DMA1, LL_DMA_STREAM_5, (uint32_t)&(USART2->DR));//设置外设地址
	LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_5, (uint32_t)RS422_R_data);//设置内存地址
	LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_5, RS422_R_date_size);//设置内存的数据长度

	LL_DMA_ClearFlag_TC5(DMA1);
	
	LL_USART_EnableDMAReq_RX(USART2);//启用串口DMA接收模式
	LL_DMA_EnableStream(DMA1,LL_DMA_STREAM_5);//启动USART2_RX的DMA接收
	//DMA发送配置
	LL_DMA_SetPeriphAddress(DMA1, LL_DMA_STREAM_6, (uint32_t)&(USART2->DR));//设置外设地址
	LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_6, (uint32_t)RS422_T_data);//设置内存地址
	LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_6, RS422_T_date_size);//设置内存的数据长度

		//清除中断标志位
	LL_USART_ClearFlag_TC(USART2);//按手册要求清除此标志位
	LL_DMA_ClearFlag_TC6(DMA1);
	LL_DMA_EnableIT_TC(DMA1,LL_DMA_STREAM_6);
	LL_USART_EnableDMAReq_TX(USART2);//启用串口DMA发送模式
	//LL_DMA_EnableStream(DMA1,LL_DMA_STREAM_6);//启动USART2_RX的DMA发送
}

接收和发送配置按照上述配置一般情况下完全足够。注意在mian.c里添加上面函数。
在usart.c文件配置串口的空闲中断如下:

  /* USER CODE BEGIN USART2_Init 2 */
	LL_USART_ClearFlag_IDLE(USART2);
	LL_USART_EnableIT_IDLE(USART2);

  /* USER CODE END USART2_Init 2 */

接着就可以编写中断处理函数了
stm32f4xx_it.c中USART2的串口处理函数如下:
首先先判断是否是空闲中断,然后清除中断标志位,接着关闭DMA传输,接着的if-else判断是用来判断接收数据的帧头和帧尾是否正确,把标志位存储到数组最后一位,这也是为什么我前面说把接收数组比接收的数据位多一位的用处。然后清除DMA完全中断标志位,接着重新设置内存地址,再开启DMA传输。这样可以不断把数据存入刷新该数据。大家按这个模板套就行。

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
	if(LL_USART_IsActiveFlag_IDLE(USART2))//RS422数据接收空闲中断
	{
		LL_USART_ClearFlag_IDLE(USART2);
		LL_DMA_DisableStream(DMA1,LL_DMA_STREAM_5);
		if((RS422_R_data[0]==0XFF)&&(RS422_R_data[1]==0xFE)&&(RS422_R_data[13]==0x0F))
		{
			RS422_R_data[14]=0xe0;//数据帧正确
		}
		else
		{
			RS422_R_data[14]=0x0e;//数据帧错误
		}
		LL_DMA_ClearFlag_TC5(DMA1);
		LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_5, (uint32_t)RS422_R_data);//设置内存地址
		LL_DMA_EnableStream(DMA1,LL_DMA_STREAM_5);//启动UART5_RX的DMA传输
	}

  /* USER CODE END USART2_IRQn 0 */
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
}

这里我解释一下重要操作的意义:
1.明明DMA接收配置没有打开TC中断为什么还要清除TC中断标志位
如下图所示,每次传输完,DMA的TC标志都会硬件置1,无论你是否开启TC中断,都需要软件清0才可以开启下一次DMA传输(这也就是为什么清除TC中断标志位)。如果开启了TC中断,那么此时还会跳入到DMA中断服务函数。
在这里插入图片描述
在这里插入图片描述
2.为什么要重新设置内存地址
因为这里是USART的空闲中断,如图,空闲中断就是在空闲帧处触发,如果我传输的数据错乱了,比应该传输的字节少,那么重新设置内存地址可以保证下次传输的首地址正确,从而这次错误不影响下一次的数据传输,如果不这么干,下次传输的首地址错了,就会一直错下去。如果比应该传输的字节多,那么传输一轮后,下一轮就是比应该传输的字节少了,继续改正。
在这里插入图片描述
这个是DMA发送配置传输完成的TC中断服务函数,将发送的数据发送完后就会进入该中断,在中断清除标志位就可以等待下一次发送传输了。

void DMA1_Stream6_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Stream6_IRQn 0 */
	if(LL_DMA_IsActiveFlag_TC6(DMA1))//RS422控制板数据发送完成
	{
		LL_DMA_ClearFlag_TC6(DMA1);
	}

  /* USER CODE END DMA1_Stream6_IRQn 0 */

  /* USER CODE BEGIN DMA1_Stream6_IRQn 1 */

  /* USER CODE END DMA1_Stream6_IRQn 1 */
}

可以看到在DMA配置函数了,发送配置的DMA传输使能我是注释掉了,在把需要发送的数据处理好了,保存在发送数组里再开启发送使能。如下图每次传输完成时会自动关闭DMA,所以中断服务函数里不用再关闭了,但是需要清除中断标志位,不然下次没法发送也没法退出中断。
在这里插入图片描述

总结

到这里DMA在USART外设和内存之间的传输就完成了,大家使用时可以直接套用上述模板即可。该模板的优点是具有数据接收格式判断和数据错误纠正,在高速,大量的数据传输方面很实用。

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值