STM32CubeMX+freeRTOS系统+串口DMA通信IDLE中断

        接上文,我们在LED灯的基础上加入串口中断,首先在CubeMX中使能DMA和USART1,串口保持默认配置即可,打开串口中断:

我们更新代码后打开keil软件,在工程中新建一个uart1recv.c文件以及该文件的.h文件:

在uart1recv..h文件中添加以下代码:

#ifndef __UART1RECV_H
#define __UART1RECV_H

void Uart1Recv_Task(void const * argument);

#endif /* __UART1RECV_H */

uart1recv.c文件中添加以下代码:

#include "uart1recv.h"
#include "cmsis_os.h"
#include "usart.h"

uint8_t UART1_RxBuf[256];
uint8_t UART1_TxBuf[256];

volatile uint16_t UART1_RecvLength=0;
volatile uint16_t UART1_SendLength=0;

void Uart1Recv_Task(void const * argument)
{
		osEvent event;
		osStatus status;
		for(;;)
		{
			event=osSignalWait(0x01,osWaitForever);//接收任务通知
			if(event.status==osEventSignal)
			{
					if(event.value.signals==0x01) 
					{
								UART1_SendLength=UART1_RecvLength;
								HAL_UART_Transmit_DMA(&huart1,UART1_TxBuf,UART1_SendLength);
								UART1_TxBuf[0]+=1;
						
					}
			}

//			HAL_UART_Receive_DMA(&huart1,UART1_RxBuf,UART1_RxBuf_SIZE );
//			osDelay(100);
  }
}

函数解析:

这里有两个新的知识点:任务通知和任务接收函数,在串口中断的回调函数中,我们发出了任务通知,在这里我们收到任务通知后,可以对收到的数据根据自己的需求进行处理,我们这里为了演示方便,返回的数据为输入缓冲区第0个字节每次加1,返回的数据长度等于收到的数据长度,文章末尾我们给出了示例图片。

同样的我们需要在freertos.c文件中添加头文件,定义任务句柄ID,创建线程:

/* USER CODE BEGIN Includes */
#include "led.h"
#include "uart1recv.h"
/* USER CODE END Includes */

    /* USER CODE BEGIN Variables */
osThreadId Uart1Recv_Handle;
osThreadId LED_Task_Handle;
/* USER CODE END Variables */

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
	osThreadDef(UART1_RECV, Uart1Recv_Task, osPriorityHigh, 0, 128);
  Uart1Recv_Handle = osThreadCreate(osThread(UART1_RECV), NULL);
	
	osThreadDef(LED, LED_Task, osPriorityNormal, 0, 128);
  LED_Task_Handle = osThreadCreate(osThread(LED), NULL);
  /* USER CODE END RTOS_THREADS */

在串口的初始化函数MX_USART1_UART_Init()中,配置接收方式为:

  /* USER CODE BEGIN USART1_Init 2 */
	__HAL_UART_CLEAR_FLAG(&huart1, UART_CLEAR_IDLEF);
	__HAL_UART_CLEAR_OREFLAG(&huart1);
	HAL_UARTEx_ReceiveToIdle_DMA(&huart1, UART1_RxBuf, UART1_RxBuf_SIZE);
  /* USER CODE END USART1_Init 2 */

在main.c文件中重写IDLE中断事件函数:

//事件回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
		uint16_t temp;
    if (huart->Instance == USART1)
    {
				temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
				UART1_RecvLength = UART1_RxBuf_SIZE - temp;
				osSignalSet(Uart1Recv_Handle,0x01);//发送任务通知(中断中的任务通知)
    }


}

最后在stm32l4xx_it.c文件中还需要重新设置DMA接收方式:

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
	//重新开启串口空闲中断和DMA接收,一定要放在这里
	HAL_UARTEx_ReceiveToIdle_DMA(&huart1,UART1_RxBuf, UART1_RxBuf_SIZE);
  /* USER CODE END USART1_IRQn 1 */
}

        新版的HAL库更新后,在中断处理函数中增加了接收模式检查,如果采用了DMA+空闲中断的方式(这是我们开始在串口初始化函数中配置的),我们便可以通过重写HAL_UARTEx_RxEventCallback(huart, nb_rx_data)该函数,来实现不定长数据的接收,比以往我们修改底层驱动程序的方法(详见STM32 HAL库串口+DMA接收不定长数据_stm32h743串口不定长dma收发-优快云博客)更简单,更便捷。

### STM32CubeIDE FreeRTOS 串口 DMA 不定长 数据 收发 实现 方法 #### 使用双缓存机制提高数据传输稳定性 为了实现高效的DMA串口不定长数据收发,在FreeRTOS环境中可以利用双缓冲区来增强系统的稳定性和响应速度。每当一个缓冲区满载时,另一个缓冲区继续接收新来的数据流,这样能够有效防止数据丢失并减少CPU负担[^1]。 ```c // 定义两个独立的接收缓冲区 uint8_t bufferA[BUFFER_SIZE]; uint8_t bufferB[BUFFER_SIZE]; // 切换标志位用于指示当前使用的缓冲区 volatile uint8_t currentBuffer = BUFFER_A; ``` #### 配置DMA循环接收模式 对于接收操作而言,推荐使用DMA循环接收方式。这种方式下,一旦初始化完成后,无论何时接收到新的字符都会立即触发DMA控制器将其存储到指定位置直到达到预设数量为止;之后DMA指针会自动重定位回起始地址准备新一轮的数据采集工作[^3]。 ```c void StartDmaReception(UART_HandleTypeDef *huart){ if (currentBuffer == BUFFER_A) { HAL_UART_Receive_DMA(huart, bufferA, BUFFER_SIZE); } else { HAL_UART_Receive_DMA(huart, bufferB, BUFFER_SIZE); } } ``` #### 结合空闲中断处理未知长度的消息帧 考虑到实际应用场景中的消息可能具有不同的大小,因此还需要借助于UART IDLE线状态变化产生的硬件事件——即所谓的“空闲中断”。当检测到该信号后意味着前一包完整的字节序列已经结束,则停止当前正在进行中的DMA事务,并计算已成功转移了多少个有效的八位组。随后可依据此数值提取出完整的信息片段供后续解析或转发之用[^4]。 ```c void UART_IDLE_IRQHandler(void){ /* 如果确实发生了IDLE事件 */ if (__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)){ __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 停止DMA接收过程以便读取计数器值 HAL_UART_DMAStop(&huart1); // 获取实际接收到的有效字节数量 size_t receivedBytesCount = BUFFER_SIZE - __HAL_DMA_GET_COUNTER((&hdma_usart1_rx)); // 复制接收到的数据至临时变量中 memcpy(receivedData[currentBuffer], GetActiveBuffer(), receivedBytesCount); // 更新全局变量记录最新一次接收的结果 lastReceivedLength = receivedBytesCount; // 更改正在使用的缓冲区索引 ToggleCurrentBuffer(); // 继续监听下一个潜在到来的数据包 StartDmaReception(&huart1); } } static inline void* GetActiveBuffer(){ return (currentBuffer==BUFFER_A)?bufferA:bufferB; } static inline void ToggleCurrentBuffer(){ currentBuffer ^= BUFFER_A ^ BUFFER_B; // XOR trick to toggle between A and B } ``` #### 发送部分采用一次性DMA传输策略 不同于较为复杂的接收流程设计,发送端相对简单得多:只需要准备好待发出的内容数组以及其确切尺寸参数即可启动相应的DMA通道完成整个动作链路。由于每次都是针对特定的任务实例执行固定规模的操作所以无需额外考虑动态调整的问题。 ```c void SendDataViaUart(const char* dataToSend,size_t length){ HAL_UART_Transmit_DMA(&huart1,(uint8_t*)dataToSend,length); } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值