FreeRTOS之串口通信

FreeRTOS环境下串口接收完整数据帧的实用技术

FreeRTOS的核心是调度器,调度器的主要工作是管理任务和任务之间、任务和中断之间的上下文切换。
理解调度机制,熟悉几个API函数,就能使用FreeRTOS操作系统了。
在实际应用过程中要注意几个问题:
1,任务的优先级安排;
2,中断的优先级安排;
3,采用互斥技术做好资源管理;
(注意:其实最好的互斥方法是通过精心设计应用程序,尽量不要共享资源,或者每个资源都通过单任务、中断访问)
FreeRTOS环境下串口接收完整的数据帧,方法有三种:
1,任务+串口接收中断;
2,消息队列+串口接收中断和空闲中断;
3,消息队列+DMA+串口空闲中断;
关键字及概念:
串口空闲中断:一个字节的时间内没有再接收到数据时发生。
接收完成中断:接收到一个完整的字节数据后发生。

一) *使用任务接收串口完整的数据帧

///UART0 中断服务函数
void LpUart0_IRQHandler(void)
{
     BaseType_t xHigherPriorityTaskWoken = pdFALSE;        
     uint8_t res=0;       
    if(LPUart_GetStatus(M0P_LPUART0, LPUartRC))       ///接收数据中断
    {
        LPUart_ClrStatus(M0P_LPUART0, LPUartRC);      ///<清接收中断请求                 
          res =  LPUart_ReceiveData(M0P_LPUART0);
          //参数“xHigherPriorityTaskWoken”是函数调用后传递的一个flag,意思就是唤醒高优先级任务。
         //如果API函数将此值设置为pdTRUE,则在中断退出前应当进行一次上下文切换,这样才能保证中断直接
         //返回到就绪状态任务中优先级最高的任务中。这里没有判断pdTRUE,写队列后立即进行上下了上下文切换。
        xQueueSendFromISR(usart_Queue,(void *) &res,&xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);  
        // 这里也可以这么写:
        //被队列读操作解除阻塞的任务,判断其优先级是否高于中断打断的任务,如果是,则进行上下文切换。
        // if( xHigherPriorityTaskWoken == pdTRUE )
        //{
        //	portYIELD_FROM_ISR(xHigherPriorityTaskWoken); 
        //}      
    }
}
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "stm32fxxx_hal.h"
 
#define RX_BUFFER_SIZE 128
 
UART_HandleTypeDef huart;
QueueHandle_t xRxQueue;
 
void SystemClock_Config(void);
void MX_GPIO_Init(void);
void MX_USART1_UART_Init(void);
 
void vUARTInterruptHandler(void);
void vUARTTask(void *pvParameters);
 
int main(void)
{
    // 系统时钟配置
    SystemClock_Config();
    // GPIO初始化
    MX_GPIO_Init();
    // 串口初始化
    MX_USART1_UART_INIT();
    // 创建串口接收任务
    xTaskCreate(vUARTTask, "UART", 128, NULL, 1, NULL);
    // 开启任务调度
    vTaskStartScheduler(); 
    while (1);
} 
void vUARTTask(void *pvParameters)
{
    uint8_t byte;
            uint8_t  temp_bytes = 0; /*队列中字节长度new*/
        uint8_t cnt;
        static uint8_t  buff[QueueSIZE] = {0}; /*暂存接收协议,从0x68开始,用于crc计算*/
        static TickType_t StartTick = 0;
        static uint8_t ShadowBytes = 0;  /*old*/
    for(;;)
    {
        temp_bytes = uxQueueMessagesWaiting(usart_Queue);//检查消息数
        if(temp_bytes == 0)//检查队列的长度
        {
           ShadowBytes = 0;
        }
        else
        {
                if(ShadowBytes != temp_bytes)//有新的数据
                {
                        ShadowBytes = temp_bytes;
                        StartTick = xTaskGetTickCount();
                }
                else
                {
                        //100ms绝对延时,确保一帧数据接收完成
                        if(xTaskGetTickCount() - StartTick > 100)
                        {
                                for(cnt = 0; cnt<temp_bytes; cnt++)
                                {
                                        xQueueReceive(xRxQueue,(void*)&buff[cnt%QueueSIZE],(TickType_t)100);//接收数据
                                }                        
                                protocol_parse(buff,temp_bytes);               
                               // 处理接收到的数据
                               HAL_UART_Transmit(&huart, &byte, 1, portMAX_DELAY);
                        }
                }
          }
    }
}
 
void vUARTInterruptHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    uint8_t byte; 
    // 确认中断标志
    if(__HAL_UART_GET_FLAG(&huart, UART_FLAG_RXNE) != RESET)
    {
        // 读取接收到的数据
        byte = (uint8_t)huart.Instance->DR; 
        // 清除中断标志
        __HAL_UART_CLEAR_FLAG(&huart, UART_FLAG_RXNE); 
        // 向队列发送数据
                  //参数“xHigherPriorityTaskWoken”是函数调用后传递的一个flag,意思就是唤醒高优先级任务。
         //如果API函数将此值设置为pdTRUE,则在中断退出前应当进行一次上下文切换,这样才能保证中断直接
         //返回到就绪状态任务中优先级最高的任务中。这里没有判断pdTRUE,写队列后立即进行上下了上下文切换。
        xQueueSendFromISR(usart_Queue,(void *) &byte,&xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);  
        // 这里也可以这么写:
        //被队列读操作解除阻塞的任务,判断其优先级是否高于中断打断的任务,如果是,则进行上下文切换。
        // if( xHigherPriorityTaskWoken == pdTRUE )
        //{
        //	portYIELD_FROM_ISR(xHigherPriorityTaskWoken); 
        //} 
    }
    // 如果需要,释放调度
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
} 
// 初始化串口中断和队列
void MX_USART1_UART_Init(void)
{
    __HAL_UART_ENABLE_IT(&huart, UART_IT_RXNE); // 开启接收中断
    xRxQueue = xQueueCreate(RX_BUFFER_SIZE, sizeof(uint8_t)); // 创建接收队列 
    // 注册中断服务程序
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
} 
void USART1_IRQHandler(void)
{
    vUARTInterruptHandler();
}```

任务中接收信号,这里并不是每一条消息都接收吗,因为没有空闲中断,而是做了100ms绝对延时,确保一帧数据接收完成。

```c
/**
***********************************************************************
** \brief   2400波特率:'100ms = 24bytes'
**
**
** \param 1 :void
** \retval   void                                 
***********************************************************************/        
void APP_LocalCOM_ReadData(void)
{
        uint8_t  temp_bytes = 0; /*队列中字节长度new*/
        uint8_t cnt;
        static uint8_t  buff[QueueSIZE] = {0}; /*暂存接收协议,从0x68开始,用于crc计算*/
        static TickType_t StartTick = 0;
        static uint8_t ShadowBytes = 0;  /*old*/
        temp_bytes = uxQueueMessagesWaiting(usart_Queue);//检查消息数
        if(temp_bytes == 0)//检查队列的长度
        {
           ShadowBytes = 0;
        }
        else
        {
                if(ShadowBytes != temp_bytes)//有新的数据
                {
                        ShadowBytes = temp_bytes;
                        StartTick = xTaskGetTickCount();
                }
                else
                {
                        //100ms绝对延时,确保一帧数据接收完成
                        if(xTaskGetTickCount() - StartTick > 100)
                        {
                                for(cnt = 0; cnt<temp_bytes; cnt++)
                                {
                                        xQueueReceive(usart_Queue,(void*)&buff[cnt%QueueSIZE],(TickType_t)100);//接收数据
                                }                        
                                protocol_parse(buff,temp_bytes);               
                                //BSP_UARTx_SendBytes(M0P_UART0,temp_bytes, buff); //test
                        }
                }
   }
} 

二)FreerRTOS串口中断+队列实现串口接收完整的数据帧

实现逻辑:
①开启串口的接收中断(UART_IT_RXNE)和空闲中断(UART_IT_IDLE)
接收中断是每接收一个字符时 产生一次中断
空闲中断是每完成一次接收后产生的中断
②每当接收中断时(即接收一个字符),将获取的字符送入消息队列
③当完成一个消息队列传输时(即本次传输完成),释放信号(通知任务传输完成)
④任务获取到信号量后,将消息队列中的数据尽数取出,直到’\n’,(这个\r是我自己定义的)
⑤将取出的消息放提前准备好的缓冲区中,使用strcmp函数进行对比
⑥清空缓冲区,等待下一次的’\n’
1)串口中断

//开启中断,创建完任务后;
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);//开启接收中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//开启空闲中断
__HAL_UART_CLEAR_IDLEFLAG(&huart1);	
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 1 */
	uint8_t res = 0;
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) != RESET)//接收中断
	{
		//UART句柄,接收数据的缓存区指针,接收数据字节数,接收超时时间(ms)
		//返回值:成功接收数据的字节数,如果错误返回负值。
		HAL_UART_Receive(&huart1,&res,1,1000);
		//将数据放入消息队列
		xQueueSendFromISR(uartQueue,&res,NULL);
		__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);//清除中断标记
	}
	//空闲中断
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET)
	{
		//一帧数据接收完成		
		//释放信号量
		xSemaphoreGiveFromISR(uartSemaphore,NULL);
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);
	}
  /* USER CODE END USART1_IRQn 1 */
}

2)解析任务

void StartCMDAnalysisTask(void const * argument)
{
	char Rx_Buffer[128];
	int index=0;
	memset(Rx_Buffer,'\0',128);
  for(;;)
  {
		if(uxSemaphoreGetCount(uartSemaphore) == 1) //串口完成了一次接收
		{
			xSemaphoreTake(uartSemaphore, ( TickType_t ) 10);//获取信号量
           /* 获取消息队列中的数据到Buffer中直至获取到\r */		
			do{
				if(xQueueReceive(uartQueue,&Rx_Buffer[index],( TickType_t ) 10) == pdTRUE)
				{
					index++;
				}				
				else
					osDelay(10);
			}while(Rx_Buffer[index-1] != '\r');
			Rx_Buffer[index-1] = '\0';
			index = 0;	
            /* 命令解析 命令 存于Rx_Buffer中,解析完一次则清空一次*/
			if(strcmp(Rx_Buffer,"AT") == 0)
			{
				printf("ok\r\n");
			}
			/* 放置你需要解析的命令
			if(strcmp(Rx_Buffer,"your CMd") == 0)
			{
				//定义处理的方式
			}
			*/
			memset(Rx_Buffer,'\0',128);			
		}
    osDelay(2);
  }
}
  1. 串口配置
    在usart.c文件中添加以下代码以重定义fputc以支持printf
    //加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)  
//解决HAL库使用时,某些情况可能报错的bug
int _ttywrch(int ch)    
{
    ch=ch;
	return ch;
}
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
	/* Whatever you require here. If the only file you are using is */ 
	/* standard output using printf() for debugging, no file handling */ 
	/* is required. */ 
}; 
/* FILE is typedef’ d in stdio.h. */ 
FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->ISR&0X40)==0);//循环发送,直到发送完毕   
	USART1->TDR = (unsigned char) ch;      
	return ch;
	
//	HAL_UART_Transmit_DMA(&huart1, (uint8_t *)&ch, 1); 
//  return (ch);
	
}
#endif 

4)创建指令解析的任务和消息队列和信号量
其中信号量是用于串口接收完一次完整消息后通知任务开始获取消息队列的消息。
这部分代码主要:
创建命令解析任务、创建命令解析所用的消息队列、创建命令解析所用的信号量

void StartCMDAnalysisTask(void const * argument);//声明命令解析函数

osThreadId cmdAnalysisTaskHandle;//定义命令解析任务的句柄
SemaphoreHandle_t  uartSemaphore;//定义串口接收一次完成的信号量
QueueHandle_t  uartQueue;//定义串口消息队列的句柄

/* 创建指令解析任务 */
osThreadDef(cmdAnalysisTask, StartCMDAnalysisTask, osPriorityBelowNormal, 0, 512);
cmdAnalysisTaskHandle = osThreadCreate(osThread(cmdAnalysisTask), NULL);

/* 创建串口的消息队列 */
uartQueue = xQueueCreate((UBaseType_t ) 128,/* 消息队列的长度 */
                            (UBaseType_t ) sizeof( char));/* 消息的大小 */
if(NULL == uartQueue)
    printf("uartQueue created failed\r\n");
	
/* 创建串口接收完成的信号量 */
uartSemaphore = xSemaphoreCreateBinary();
if(NULL == uartSemaphore)
   printf("uartSemaphore created failed\r\n");

三)使用freemodbus自带串口 freertos串口接收数据

逻辑:
1.移植FreeRTOS;
2.初始化串口(配置中断的优先级,使能TX和RX引脚的时钟,使能USART0的时钟,配置波特率,数据位长度,停止位,校验位等,使能输出,使能输入,使能串口,使能接收中断和空闲中断);
3.创建开始任务,开始任务的工作是创建二值信号量,创建串口任务;
4.串口中断服务函数中判断是接收缓冲区非空中断还是空闲中断,为什么要开启这两个中断呢?因为没接收到一个字节的数据就会触发前者,而当一连串字符接收完成后则会触发后者,开启后者就可以接收不定长的数据。当触发的中断是接收缓冲区非空的中断时,读取数据,而当触发的中断是空闲中断时,说明结束完一帧数据,此时就可以释放信号量;
5、串口任务的工作是获取信号量,当获取到信号量成功后,就可以把接收到的数据原封不动地发送出去。
代码:main.c

#include "include.h"
#include "gd32e507z_eval.h"


void Start_Task(void * parameter);

#define START_STK_SIZE		120	
#define START_TASK_PRIO		1
TaskHandle_t START_TASK_Handle;

#define LED_STK_SIZE		120	
#define LED_TASK_PRIO		2
TaskHandle_t LED_TASK_Handle;

#define USART_STK_SIZE		500	
#define USART_TASK_PRIO		3
TaskHandle_t USART_TASK_Handle;

SemaphoreHandle_t BinarySemaphore;

/*!
    \brief      main function
    \param[in]  none
    \param[out] none
    \retval     none
*/
int main(void)
{
  /* configure 4 bits pre-emption priority */
    nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);

    systick_config();
	 
    /*	initialize LED 	*/
    LED_Init();
  /*	initialize USART0 	*/
  /* configure NVIC and systick */
    nvic_irq_enable(USART0_IRQn, 0, 0);//中断优先级一定要配,不然中断就没反应
  USART_Config(usart0,115200,USART_WL_8BIT,USART_STB_1BIT,USART_PM_NONE);
  usart_interrupt_enable(USART0,USART_INT_RBNE);
  usart_interrupt_enable(USART0,USART_INT_IDLE);
		
    xTaskCreate(Start_Task,"Start_Task",START_STK_SIZE,NULL,START_TASK_PRIO,&START_TASK_Handle); 
    vTaskStartScheduler();
    while(1)
    {
    }
}
void Start_Task(void * parameter)
{
	BinarySemaphore = xSemaphoreCreateBinary();
	xTaskCreate(USART_Task,"USART_Task",USART_STK_SIZE,NULL,USART_TASK_PRIO,&USART_TASK_Handle);
	vTaskDelete(NULL);
}

usart.c

#include "usart.h"
#include "string.h"
#include "semphr.h"

static rcu_periph_enum USART_CLK[USARTn] = {RCU_USART0,RCU_USART1,RCU_USART2,RCU_UART3,RCU_UART4,RCU_USART5};
static rcu_periph_enum USART_GPIO_CLK[USARTn] = {USART0_GPIO_CLK,USART1_GPIO_CLK,USART2_GPIO_CLK,UART3_GPIO_CLK,UART4_GPIO_CLK,USART5_GPIO_CLK};

static uint32_t USARTx_TX_PORT[USARTn] = {GPIOA,GPIOA,GPIOB,GPIOC,GPIOC,GPIOC};
static uint32_t USARTx_TX_PIN[USARTn] = {GPIO_PIN_9,GPIO_PIN_2,GPIO_PIN_10,GPIO_PIN_10,GPIO_PIN_12,GPIO_PIN_6};	

static uint32_t USARTx_RX_PORT[USARTn] = {GPIOA,GPIOA,GPIOB,GPIOC,GPIOD,GPIOC};
static uint32_t USARTx_RX_PIN[USARTn] = {GPIO_PIN_10,GPIO_PIN_3,GPIO_PIN_11,GPIO_PIN_11,GPIO_PIN_2,GPIO_PIN_7};	

static uint32_t USARTx[USARTn] = {USART0,USART1,USART2,UART3,UART4,USART5};

void USART_Config(uint8_t com,uint32_t Baudrate,uint32_t WL,uint32_t STB,uint32_t Parity)
{
	/* enable GPIO clock */
    rcu_periph_clock_enable(USART_GPIO_CLK[com]);

    /* enable USART clock */
    rcu_periph_clock_enable(USART_CLK[com]);
	
		/* connect port to USARTx_Tx */
		gpio_init(USARTx_TX_PORT[com], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, USARTx_TX_PIN[com]);
		/* connect port to USARTx_Rx */
		gpio_init(USARTx_RX_PORT[com], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, USARTx_RX_PIN[com]);

    /* USART configure */
    usart_deinit(USARTx[com]);
    usart_word_length_set(USARTx[com], WL);
    usart_stop_bit_set(USARTx[com], STB);
    usart_parity_config(USARTx[com], Parity);
    usart_baudrate_set(USARTx[com], Baudrate);
    usart_receive_config(USARTx[com], USART_RECEIVE_ENABLE);
    usart_transmit_config(USARTx[com], USART_TRANSMIT_ENABLE);
    usart_enable(USARTx[com]);
}

void USART_Transmit_String(uint32_t usart_periph,uint8_t * str)
{
		uint32_t len = strlen((char *)str);
		for(int i = 0;i < len;i++)
		{
			usart_data_transmit(usart_periph,*str);
			while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
			str++;
		}
} 

void USART_Task(void * parameter)
{		
		BaseType_t err = pdPASS;
		uint8_t data[100] = "Welcom to Shenzhen\r\n";
		
		while(1)
		{
			if(BinarySemaphore != NULL)
			{
				err = xSemaphoreTake(BinarySemaphore,portMAX_DELAY);
				if(err == pdPASS)
				{
					memcpy(data,REV_DATA,REV_LEN);
        		USART_Transmit_String(USART0,data);
          memset(data,0,sizeof(data));
          memset(REV_DATA,0,sizeof(REV_DATA));
          REV_LEN = 0;
        }	
			}
			else
			{
				vTaskDelay(10);
			}

		}
		
}

usart.h

#ifndef _USART_H_
#define _USART_H_

#include "include.h"

#define USARTn 		6
#define USART0_GPIO_CLK						RCU_GPIOA
#define USART1_GPIO_CLK						RCU_GPIOA
#define USART2_GPIO_CLK						RCU_GPIOB
#define UART3_GPIO_CLK						RCU_GPIOC
#define UART4_GPIO_CLK						(RCU_GPIOC|RCU_GPIOD)
#define USART5_GPIO_CLK						RCU_GPIOC

typedef enum
{
	usart0 = 0,
	usart1,
	usart2,
	uart3,
	uart4,
	usart5,
}COMx;


void USART_Config(uint8_t com,uint32_t Baudrate,uint32_t WL,uint32_t STB,uint32_t Parity);
void USART_Transmit_String(uint32_t usart_periph,uint8_t * str);
void USART_Task(void * parameter);

#endif

include.h

#ifndef _INCLUDE_H_
#define _INCLUDE_H_

#include "gd32e50x.h"
#include "systick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

extern SemaphoreHandle_t BinarySemaphore;
extern uint8_t REV_DATA[1000];
extern uint32_t REV_LEN;

#endif

STM32F10x_it.c

void USART0_IRQHandler(void)
{
	uint8_t data;
	if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE))
	{
		data = usart_data_receive(USART0);
		REV_DATA[REV_LEN++]= data;
//		usart_interrupt_flag_clear(USART0,USART_INT_FLAG_RBNE);
	}
	if(usart_flag_get(USART0,USART_FLAG_IDLE)!=RESET)
	{
		xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken);
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
          //以下两行代码作用是清除空闲标志,先读取状态寄存器,在读取数据寄存器
		GET_BITS(USART_STAT0(USART0), 0U, 8U);
		GET_BITS(USART_DATA(USART0), 0U, 8U);
	}
}

知识点:
二值信号量跟互斥信号量非常相似,区别是互斥信号量拥有优先级继承机制,而二值信号量没有。因此二值信号量更适用于同步(任务与任务或任务与中断的同步)。
二值信号量其实就是只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,正好就是二值的。任务和中断使用这个特殊的队列时不用在乎队列中存了什么消息,只需要知道这个队列是满的还是空的即可。可以利用这个机制来完成任务与中断之间的同步。

二值信号量的一种使用方法:

1、创建二值信号量(新版的创建二值信号量的API函数,创建后二值信号量默认是空的);

2、中断服务函数中释放二值信号量(如果没有释放,任务中获取二值信号量超时,获取失败);

3、任务中获取二值信号量(在阻塞时间等待二值信号量释放,进入阻塞状态,把CPU让给其他任务);

函数

/*
1,创建二值信号量:
SemaphoreHandle_t	 xSemaphoreCreateBinary(void)
参数:无
返回值:NULL——二值信号量创建失败,其他值:创建成功的二值信号量的句柄
2.  释放二值信号量  xSemaphoreGive()

BaseType_t  xSemaphoreGive(xSemaphore)
参数:xSemaphore:要释放的信号量句柄
返回值:pdPASS:释放信号量成功;errQUEUE_FULL:释放信号量失败
3.  释放二值信号量  xSemaphoreGiveFromISR()

BaseType_t   xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t * pxHigherPriorityTaskWoken)

参数:xSemaphore:要释放的信号量句柄
 pxHigherPriorityTaskWoken:标记退出此函数以后是否进行任务切换,当此值为pdTRUE的时候,则在退出中断服务函数之前一定要进行一次任务切换。

返回值:pdPASS:释放信号量成功
errQUEUE_FULL:释放信号量失败
4. 获取二值信号量   BaseType_t  xSemaphoreTake(SemaphoreHandle_t  xSemaphore,TickType_t  xBlockTime)

参数:xSemaphore:要获取的信号量句柄;xBlockTime:阻塞时间
返回值:pdTRUE:获取信号量成功;pdFALSE:超时,获取信号量失败。

5. 获取二值信号量   BaseType_t  xSemaphoreTakeFromISR(SemaphoreHandle_t  xSemaphore,BaseType_t  pxHigherPriorityTaskWoken)

参数:xSemaphore:要获取的信号量句柄;
        pxHigherPriorityTaskWoken: 标记退出此函数后是否进行任务切换,当此值为pdTRUE的时候,则在退出中断服务函数前一定要进行一次任务切换;
*/

四)消息队列+DMA+串口空闲中断

逻辑:使用消息队列传输串口接收到的不定长数据
关键字:传指针、串口中断、DMA
实现:创建一个消息队列、创建串口数据缓冲区结构体数组;该结构体至少包含数据缓冲区和数据大小。串口空闲中断处理接收数据,DMA接收的数据放在结构体里的数据缓冲区里面,然后用指针指向这整个结构体,最后通过消息队列发送出去。传指针的方法可以快速传输数据,哪怕有几千的字节,只要定义好保存数据的缓冲区,最后也不过是往消息队列里面发一个几字节大小的指针。
1)main.c 串口空闲中断接收消息

/* 先定义好存放数据的空间,队列大小是5,这里就定义5个。 */
UART_RX_TypeDef uart_rx_data_t[UART_BUFFER_QUANTITY];
/* 定义一个变量用作分配上面定义的数据 */
uint8_t uart_buff_ctrl = 0;
/* 引用串口DMA handle  */
extern DMA_HandleTypeDef hdma_usart1_rx;
/* 队列 handle。去freertos.c文件里面找CubeMX给你创建好的。 */
extern osMessageQId uart_queueHandle;
 
/* Redirect printf function */
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
    return (ch);
}
 
void USART1_DMAHandler(void)
{
    if (RESET != __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) // 判断是否是空闲中断
    {
        UART_RX_TypeDef *pUartData; /* 定义指向创建串口数据的指针 */
 
        __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除空闲中断标志
        HAL_UART_DMAStop(&huart1);          // 停止本次DMA传输
 
        /* 计算接收到的数据长度,放进串口数据结构体中 */
        uart_rx_data_t[uart_buff_ctrl].size = UART_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        /* 将这个串口数据的结构体地址给指针 */
        pUartData = &uart_rx_data_t[uart_buff_ctrl];
        /* 把指向串口接收数据的指针放入消息队列。注意,这里传的是指针的地址,也就是指针串口数据结构体的指针的地址不能传pUartData本身 */
        xQueueSendFromISR(uart_queueHandle, &pUartData, NULL);
        /* 这里进行加一,使其下一次DMA传输时将接收的数据放到下一个串口缓冲区中 */
        uart_buff_ctrl++;
        /* 取余操作防止越界 */
        uart_buff_ctrl %= UART_BUFFER_QUANTITY;
        /* 重启开始DMA传输 */
        HAL_UART_Receive_DMA(&huart1, uart_rx_data_t[uart_buff_ctrl].buffer, UART_BUFFER_SIZE);
    }
}

开启空闲中断

__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1, uart_rx_data_t[uart_buff_ctrl].buffer, UART_BUFFER_SIZE);

main.h
这里的缓冲区大小和缓冲区数量你们自己按照实际的项目情况来定义。例如在你的项目中是单次少量数据,但是短时间会有很多次,那么这里可以把UART_BUFFER_SIZE改小点,节省内存,然后UART_BUFFER_QUANTITY改大些,最后别忘了改一下队列的长度!要和这个UART_BUFFER_QUANTITY同样。

/* 一、引用标准输入输出库 */
#include <stdio.h>
 
/* 二、定义串口数据缓冲区配置 */
#define UART_BUFFER_SIZE        (256)
#define UART_BUFFER_QUANTITY    (5)
 
/* 三、定义串口数据结构体,DMA传输的数据将保存在这里 */
typedef struct
{
    uint8_t buffer[UART_BUFFER_SIZE];   /* 存放数据的空间 */
    uint16_t size;                      /* 已存放数据的大小 */
}UART_RX_TypeDef;

2)中断函数:stm32f4xx_it.c文件

extern void USART1_DMAHandler(void);
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  USART1_DMAHandler();
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
 
  /* USER CODE END USART1_IRQn 1 */
}

3)freertos.c

void Uart_Thread(void const *argument)
{
    /* USER CODE BEGIN Uart_Thread */
    UART_RX_TypeDef *pRecvUartData; /* 定义指向串口数据的指针 */
 
    printf("Uart Thread init. \r\n");
    /* Infinite loop */
    for (;;)
    {
        /* 一直等待直到有数据 */
        /* 参数1:队列handle */
        /* 参数2:放入接收消息的指针,也就是让它指向串口中断中已接收串口数据完成的结构体 */
        /* 参数3:等待时间,这里是等待永久,直到有数据 */
        if (xQueueReceive(uart_queueHandle, &pRecvUartData, portMAX_DELAY) == pdTRUE)
        {
            /* 打印或处理接收到的数据,这里为了演示做个串口回显 */
            HAL_UART_Transmit(&huart1, pRecvUartData->buffer, pRecvUartData->size, 1000);
        }
    }
    /* USER CODE END Uart_Thread */
}
FreeRTOS串口接收主要涉及任务的创建、串口初始化和中断处理三个方面。首先,需要创建一个任务来处理串口接收的数据。可以使用FreeRTOS提供的任务创建函数vTaskCreate()来创建一个任务,并在任务函数中编写串口接收的逻辑处理代码。其次,需要进行串口的初始化设定,可以使用FreeRTOS提供的串口初始化函数来配置串口的参数,如波特率、数据位、校验位和停止位等。在初始化之后,串口就可以开始接收数据了。最后,需要编写中断处理函数来处理串口接收数据FreeRTOS中提供了中断服务函数的API函数,可以使用这些函数来注册串口接收的中断处理函数,并在中断处理函数中实现对接收数据的处理和存储。 在串口接收的过程中,需要注意接收数据的缓冲区大小,及时处理接收到的数据,避免数据溢出。同时,在任务中要考虑到串口接收的数据处理和其他任务之间的调度关系,合理安排任务的优先级和时间片,确保串口接收任务能够及时处理接收到的数据。另外,为了保证数据的完整性和正确性,可以考虑加入一定的数据校验和重发机制,确保接收到的数据是正确的。 总的来说,FreeRTOS串口接收涉及任务的创建、串口初始化和中断处理三个方面,需要充分考虑数据的处理和调度关系,保证数据的正确性和完整性。通过合理的设计和编码,可以实现稳定可靠的串口接收功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值