STM32基于LL库的USART+DMA使用

时隔两年半再次更新LL库,本次带来USART + DMA 实现接收不定长。

1、开发思路

使用USART + DMA接收不定长的功能的思路是:借助USART的空闲中断、DMA发送完成中断

打开F103的手册可得知,USART的空闲中断触发条件是在接收完成后触发,如下图:

2、新建工程

  1. 配置工程属性
  2. 选择外部时钟源
  3. 时钟倍频
  4. 使能USART

这里选择的是USART1、模式为异步、打开中断,相关参数配置选择默认


开启TX DMA、RX DMA,二者都设置成单次模式

5. 生成代码

点击 GENERATE CODE 会在设定的路径成功生成代码,选择打开工程

代码编写

在编写代码之前需要先知道以下几个函数:

__STATIC_INLINE void LL_USART_ClearFlag_IDLE(USART_TypeDef *USARTx); //清除USART空闲中断标志
__STATIC_INLINE void LL_USART_EnableIT_IDLE(USART_TypeDef *USARTx);  //使能USART空闲中断

__STATIC_INLINE void LL_USART_EnableDMAReq_TX(USART_TypeDef *USARTx);  //使能USART DMA发送
__STATIC_INLINE void LL_USART_EnableDMAReq_RX(USART_TypeDef *USARTx);  //使能USART DMA接收

__STATIC_INLINE void LL_DMA_ClearFlag_TC4(DMA_TypeDef *DMAx);  //清除DMA 通道4 传输完成标志
__STATIC_INLINE void LL_DMA_ClearFlag_TC5(DMA_TypeDef *DMAx);  //清除DMA 通道5 传输完成标志
__STATIC_INLINE uint32_t LL_DMA_IsActiveFlag_TC4(DMA_TypeDef *DMAx); //判断是否是通道4传输完成标志
__STATIC_INLINE uint32_t LL_DMA_IsActiveFlag_TC5(DMA_TypeDef *DMAx); //判断是否是通道5传输完成标志

__STATIC_INLINE void LL_DMA_EnableIT_TC(DMA_TypeDef *DMAx, uint32_t Channel); //使能指定DMA 通道传输完成中断

__STATIC_INLINE uint32_t LL_USART_IsActiveFlag_IDLE(const USART_TypeDef *USARTx); //判断是否是空闲中断标志or空闲标志

__STATIC_INLINE uint32_t LL_USART_DMA_GetRegAddr(const USART_TypeDef *USARTx); //得到指定的USART DR寄存器地址

//设置DMA 通道的外设数据寄存器地址,对于例程来说也即是USART1->DR的地址
__STATIC_INLINE void LL_DMA_SetPeriphAddress(DMA_TypeDef *DMAx, uint32_t Channel, uint32_t PeriphAddress);
//设置DMA 通道的MEMORY地址
__STATIC_INLINE void LL_DMA_SetMemoryAddress(DMA_TypeDef *DMAx, uint32_t Channel, uint32_t MemoryAddress);
//设置DMA传输数据大小
__STATIC_INLINE void LL_DMA_SetDataLength(DMA_TypeDef *DMAx, uint32_t Channel, uint32_t NbData);
//启动DMA传输
__STATIC_INLINE void LL_DMA_EnableChannel(DMA_TypeDef *DMAx, uint32_t Channel);
//停止DMA传输
__STATIC_INLINE void LL_DMA_DisableChannel(DMA_TypeDef *DMAx, uint32_t Channel);
//得到DMA传输数据个数
__STATIC_INLINE uint32_t LL_DMA_GetDataLength(DMA_TypeDef *DMAx, uint32_t Channel);

CubeMx生成的代码有些寄存器没有使能,我们不能直接使用,需要我们自己使能相关寄存器

  1. 使能相关中断
    打开usart.c源文件,我们在函数MX_USART1_UART_Init() 中,开启DMA传输完成中断:

    紧接着使能USART空闲中断、使能USART采用DMA传输数据:
  2. 编写USART DMA RX、USART DMA TX函数
    下面的函数在main.c中。
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t receive[2048];

void uart_rx_dma_config()
{
	//rx
	LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_5, (uint32_t)LL_USART_DMA_GetRegAddr(USART1));
	LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_5, (uint32_t)receive);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, 2048);
	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5);
}

void uart_tx_dma_config(uint8_t *buf, uint32_t len)
{
	LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_4, (uint32_t)LL_USART_DMA_GetRegAddr(USART1));
	LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_4, (uint32_t)buf);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_4, len);

	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_4);
}
/* USER CODE END 0 */
  1. 编写中断函数
    DMA中断中,只需判断是否是 传输完成中断,如果是清除标志即可。
/**
  * @brief This function handles DMA1 channel4 global interrupt.
  */
void DMA1_Channel4_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel4_IRQn 0 */
	if (LL_DMA_IsActiveFlag_TC4(DMA1)) {
		LL_DMA_ClearFlag_TC4(DMA1);
	}
  /* USER CODE END DMA1_Channel4_IRQn 0 */

  /* USER CODE BEGIN DMA1_Channel4_IRQn 1 */

  /* USER CODE END DMA1_Channel4_IRQn 1 */
}
/**
  * @brief This function handles DMA1 channel5 global interrupt.
  */
void DMA1_Channel5_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel5_IRQn 0 */
	if (LL_DMA_IsActiveFlag_TC5(DMA1)) {
		
		LL_DMA_ClearFlag_TC5(DMA1);
	}
  /* USER CODE END DMA1_Channel5_IRQn 0 */

  /* USER CODE BEGIN DMA1_Channel5_IRQn 1 */

  /* USER CODE END DMA1_Channel5_IRQn 1 */
}

在USART中断中,判断是否是空闲中断。在前面说过USART空闲中断的触发的条件是在USART接收完成后触发。
因此,检测到USART空闲中断,就意为着USART接收完成,此时得到DMA接收通道接收到的数据长度,然后通过DMA发送通道发送出去即可。代码如下:

/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
	/* USER CODE BEGIN USART1_IRQn 0 */
	uint32_t len;
	extern uint8_t receive[2048];
	extern void uart_rx_dma_config();
	extern void uart_tx_dma_config(uint8_t *buf, uint32_t len);
	
	/* 判断USART1是否空闲 */
	if (SET == LL_USART_IsActiveFlag_IDLE(USART1)) {
		LL_USART_ClearFlag_IDLE(USART1);
		
		LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5);
		LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_4);
		
		len = 2048 - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_5);
		uart_tx_dma_config(receive, len);
		
		uart_rx_dma_config();
	}
	/* USER CODE END USART1_IRQn 0 */
	/* USER CODE BEGIN USART1_IRQn 1 */
	
	/* USER CODE END USART1_IRQn 1 */
}
  1. 开始编写main函数

    紧接着在while(1) 之前将字符串str通过DMA发送出去,并开启DMA接收通道。

  2. 下载程序,并打开串口助手进行测试
    测试结果如下:

作者:点灯大师~

物联沃分享整理
物联沃-IOTWORD物联网 » STM32基于LL库的USART+DMA使用

发表回复