freeRTOS消息队列信号量的串口中断收发数据

1.概念

消息队列:

  1. 什么是消息队列?

    • 一种 异步通信机制,允许任务/中断服务程序(ISR)之间传递数据块

    • 数据结构:先进先出(FIFO) 的缓冲区(可自定义队列长度和元素大小)

    • 典型应用场景:传感器数据处理、事件通知、任务间数据共享

  2. 关键特性

    • 多元素存储:可同时存放多个数据项(队列深度由用户定义)

    • 阻塞机制:队列空时读取阻塞、队列满时写入阻塞(可设置超时)

    • 线程安全:自带互斥保护,无需额外同步

    • 支持中断:提供专用 FromISR API 保证中断安全

  3. 数据传递方式

    • 值传递:数据会被完整复制到队列(非指针传递)

    • 固定大小元素:队列中每个元素的大小在创建时确定

二、关键API函数详解
1. 队列创建
QueueHandle_t xQueueCreate(
    UBaseType_t uxQueueLength,  // 队列长度(最大元素数量)
    UBaseType_t uxItemSize      // 单个元素大小(字节)
);

返回值

  • 成功:队列句柄(QueueHandle_t 类型)

  • 失败:NULL(内存不足时)

示例

// 创建可存储10个int的队列
QueueHandle_t xIntQueue = xQueueCreate(10, sizeof(int));
 

2. 数据发送
BaseType_t xQueueSend(
    QueueHandle_t xQueue,    // 队列句柄
    const void *pvItemToQueue, // 发送数据指针
    TickType_t xTicksToWait    // 阻塞时间(portMAX_DELAY表示永久等待)
);

// 变种API:
xQueueSendToBack();    // 等效xQueueSend()
xQueueSendToFront();   // 插队到队列头部
xQueueSendFromISR();   // 中断专用版本

返回值

  • pdPASS:发送成功

  • errQUEUE_FULL:队列满(仅在非阻塞模式下返回)

3. 数据接收
BaseType_t xQueueReceive(
    QueueHandle_t xQueue,    // 队列句柄
    void *pvBuffer,          // 接收缓冲区指针
    TickType_t xTicksToWait  // 阻塞时间
);

// 中断专用版本:
xQueueReceiveFromISR();

返回值

  • pdPASS:接收成功

  • errQUEUE_EMPTY:队列空(非阻塞模式下)

二进制信号量:

  1. 什么是二进制信号量?

    • 一种 任务同步机制,用于协调任务与任务、任务与中断之间的操作时序

    • 状态特性:只有0和1两种状态(类似布尔值),不可累加

    • 典型应用场景:

      • 事件通知(如按键触发、数据就绪)

      • 资源互斥访问(轻量级替代互斥锁)

      • 中断服务程序(ISR)与任务通信

  2. 关键特性

    • 原子操作:信号量的获取(Take)和释放(Give)是线程安全的

    • 阻塞机制:任务可设置超时等待信号量

    • 中断安全:提供专用的 FromISR API 版本

    • 无所有者:与互斥锁不同,任何任务均可释放信号量

  3. 与计数信号量的区别

    特性二进制信号量计数信号量
    最大值固定为1用户自定义
    典型用途事件通知/互斥资源池管理
    释放次数限制满时释放无效可累加到最大值
二、关键API函数详解

1. 信号量创建
SemaphoreHandle_t xSemaphoreCreateBinary(void);

特性

  • 创建时初始状态为 0(不可用)

  • 需手动调用 xSemaphoreGive 激活

示例

SemaphoreHandle_t xButtonSemaphore = xSemaphoreCreateBinary();

2. 信号量释放
BaseType_t xSemaphoreGive(
    SemaphoreHandle_t xSemaphore  // 信号量句柄
);

// 中断专用版本:
BaseType_t xSemaphoreGiveFromISR(
    SemaphoreHandle_t xSemaphore,
    BaseType_t *pxHigherPriorityTaskWoken
);

行为

  • 若信号量为0 → 置1并唤醒等待任务

  • 若信号量已为1 → 操作无效(返回errQUEUE_FULL

返回值

  • pdPASS:释放成功

  • errQUEUE_FULL:信号量已满(二进制信号量特有)

3. 信号量获取
BaseType_t xSemaphoreTake(
    SemaphoreHandle_t xSemaphore,  // 信号量句柄
    TickType_t xTicksToWait        // 阻塞时间
);

// 中断专用版本(仅用于计数信号量,二进制信号量不推荐):
xSemaphoreTakeFromISR();

行为

  • 若信号量为1 → 置0并继续执行

  • 若信号量为0 → 根据超时设置阻塞等待

返回值

  • pdTRUE:获取成功

  • pdFALSE:超时或信号量无效

这里有一个注意点,就是二进制信号量的释放和获取都有个中断版本的,因为一般的二进制信号量释放后可能触发 ​调度器立即唤醒等待该信号量的线程,而中断处理程序必须满足 ​无阻塞、无睡眠、快速执行 的要求,所以中断中必须使用中断版本的信号量释放获取,这样在中断释放信号量时,​不会直接触发上下文切换,而是通过标记一个待处理的调度请求(例如设置 pendSV 标志),在中断退出后由调度器统一处理,这里需要注意,笔者曾经吃过亏!

2.代码

思路:

创建一个处理串口接收数据消息队列的任务,并获取二进制信号量阻塞该任务,通过配置串口1的中断,当串口接收到数据时,会产生中断接收数据,在中断处理程序中将接收到的数据发送到队列中,并且这里我使用了;作为一个数据帧的结束符,当接收到结束符时,释放二进制信号量,处理队列接收数据任务获取到信号量,弹出消息队列中的数据进行处理。

这里我们用STM32F407的串口1作为例子:

usart.c

#include "usart.h"

/*
串口1
PA9  ---- USART1_TX(发送端)
PA10 ---- USART1_RX(接收端)

串口接受数据到消息队列中,当接收到;结束符时,
释放二进制信号量,串口发送数据任务得到信号量
发送消息队列中的数据到串口中
*/

// 消息队列句柄和信号量句柄
SemaphoreHandle_t xBinarySemaphore_usart;
QueueHandle_t xUsartRxQueue_usart;


#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
int _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数   printf 是一个宏
int fputc(int ch, FILE *f)
{ 	
	USART_SendData(USART1,ch);  //通过串口发送数据
	//等待数据发送完毕
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);      
	return ch;
}

/************************************
引脚说明:

PA9  ---- USART1_TX(发送端)
PA10 ---- USART1_RX(接收端)

*************************************/
void Usart1_Init(int myBaudRate)
{
		//结构体变量
	GPIO_InitTypeDef	GPIO_InitStructure;
	USART_InitTypeDef	USART_InitStruct;
	NVIC_InitTypeDef   NVIC_InitStructure;	
	
	//GPIOA 时钟使能
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);	
	//串口时钟使能,
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	//设置引脚复用器映射:调用 GPIO_PinAFConfig 函数。
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);	
    
	GPIO_InitStructure.GPIO_Pin 	= USART1_TX_PIN|USART1_RX_PIN;  		//引脚
	GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_AF;		//复用功能
	GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_25MHz; //速度
	GPIO_InitStructure.GPIO_OType 	= GPIO_OType_PP;	//推挽
	GPIO_InitStructure.GPIO_PuPd 	= GPIO_PuPd_UP ;	//上拉
	//GPIO 初始化设置:要设置模式为复用功能。
	GPIO_Init(GPIOA, &GPIO_InitStructure); 	
	
	USART_InitStruct.USART_BaudRate		= myBaudRate;   			//波特率
	USART_InitStruct.USART_Mode			= USART_Mode_Rx|USART_Mode_Tx;  //双全工
	USART_InitStruct.USART_Parity		= USART_Parity_No;  		//无奇偶校验位
	USART_InitStruct.USART_StopBits		= USART_StopBits_1; 		//1位停止位
	USART_InitStruct.USART_WordLength	= USART_WordLength_8b;		//8位数据
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件控制流
	//5、串口参数初始化:设置波特率,字长,奇偶校验等参数。
	USART_Init(USART1, &USART_InitStruct);
	
	//配置NVIC
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;    		//中断通道,只能在stm32f4xx.h 查阅
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 7;  	//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 			//响应优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//通道使能
	NVIC_Init(&NVIC_InitStructure);	

	//配置为接收中断(表示有数据过来,CPU要中断进行接收)
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);    
    USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    
    xBinarySemaphore_usart = xSemaphoreCreateBinary();
	xUsartRxQueue_usart = xQueueCreate(USART_RX_QUEUE_LENGTH, USART_RX_BUFFER_SIZE);
    if ((xUsartRxQueue_usart == NULL) || (xBinarySemaphore_usart == NULL))
    {
        return ;
    }
	//使能串口。
	USART_Cmd(USART1, ENABLE);

}

void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
    {
        uint8_t data = USART_ReceiveData(USART1);
        //将数据放入消息队列
		xQueueSendFromISR(xUsartRxQueue_usart,&data,NULL);
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        if(data == ';')
        {
            xSemaphoreGiveFromISR(xBinarySemaphore_usart,NULL);
        }
    }

}

这里需要注意的就是串口中断配置的优先级需要从5开始,一般用户的自定任务是从5开始,0-4的任务优先级是系统任务的

USART1_IRQHandler串口中断函数中,当接收到数据既串口中断标志位USART_IT_RXNE置1时,data存储接收的数据,并且发送到消息队列中,清除中断的标志位,判断接收到的数据是否为结束符,如果是的话释放信号量。

main.c

#include "stm32f4xx.h"
#include "FreeRTOS.h"
#include "sys.h"
#include "semphr.h"
#include "event_groups.h"
#include "queue.h"
#include "task.h"
#include "delay.h"
#include "typedef.h"
#include "usart.h"



//串口任务
static TaskHandle_t app_usart1_task_handle = NULL;
void usart1_task(void* pvParameters);  


int main(void)
{
	//设置NVIC分组(一个项目只能配置一次)
	//抢占优先级取值范围:0~15
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	
	delay_init(168);
    Usart1_Init(USART1_BAUDRATE);


	/* 创建app_task1任务 */
	xTaskCreate((TaskFunction_t )usart1_task,  		/* 任务入口函数 */
			    (const char*    )"usart1task",			/* 任务名字 */
			    (uint16_t       )512,  				/* 任务堆栈大小 */
			    (void*          )NULL,				/* 任务入口函数参数 */
			    (UBaseType_t    )7, 					/* 任务的优先级 */
			    (TaskHandle_t*  )&app_usart1_task_handle);	/* 任务控制块指针 */ 
	

                
	/* 开启任务调度 */
	vTaskStartScheduler(); 

}

//串口1任务
void usart1_task(void* pvParameters)
{
    uint8_t rx_buffer[128] = {0};
    uint8_t count = 0;
	for(;;) 
	{
        if(uxSemaphoreGetCount(xBinarySemaphore_usart) == 1) 
        {
            xSemaphoreTake(xBinarySemaphore_usart, portMAX_DELAY);
            while (xQueueReceive(xUsartRxQueue_usart, &rx_buffer[count++], 10) == pdTRUE);
            printf("%s\n",rx_buffer);
            count = 0;
            memset(rx_buffer,'\0',sizeof(rx_buffer));
        }    
		vTaskDelay(1000);
	}
}   

创建处理串口消息队列的任务后,通过xSemaphoreTake(xBinarySemaphore_usart, portMAX_DELAY);获取信号量无限期阻塞等待中断释放信号量,当获取到信号量后,将消息队列中的数据弹出到rx_buffer中,这里我做的处理就是把接收到的数据再次发送到串口中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值