Freertos串口中断接收处理
https://blog.csdn.net/weixin_69902486/article/details/146391387
提示:文章
前言
前期疑问:
本文目标:
一、背景
最近
二、
2.1
在有操作系统freeRTOS的框架下使用串口中断。
参考文章:接收中断
2.2
尝试在自己工程中实现freeRTOS串口中断接收。
写了第一版代码:SHA-1: 579c3e76615f3f34ff91905507f7f97092353946
上述代码报错:
将这个问题查资料,Error:…\FreeRTOS\portable\RVDS\ARM_CM3\port.c,244,查到上述文章。查看代码发现问题
static void KeyTask1(void* pvParam)
{
if(GetKey1Value() == KEY_ON)
{
printf("按键1被按下\r\n");
}
}
static void KeyTask2(void* pvParam)
{
if(GetKey2Value() == KEY_ON)
{
printf("按键2被按下\r\n");
}
}
// 任务函数定义不是死循环
实现现象
只有按键2按下按键2任务可以执行。按下按键1没有打印。led灯不闪烁。
分析原因,因为按键2优先级最高
| 序号 | 任务 | 优先级 |
|---|---|---|
| 1、 | led1 | 2 |
| 2、 | led2 | 3 |
| 3、 | key1 | 4 |
| 4、 | key2 | 5 |
所以按键2抢占任务,导致只有key2任务在一直占用CPU。如果在key任务加上延时,是的任务可以进行切换呢?效果会有不同吗,按键1可以得到执行吗?
实验现象如下:
这边按下按键2有可能不会打印按键2的信息。按下按键1有可能会打印。按键1打印的时候,优先级2和3的led任务会立即执行。
(我想了下这边好像出现了优先级翻转)。
下面对代码进行修改
即使用消息通知方法,并且消息通知在中断中产生。
先实现按键外部中断。
代码提交信息为:56b2ca303054e9786d0139789a3302aa617f292b。
这过程中发现一个问题,就是如果配置了中断但是没有实现中断服务函数,并且触发了中断条件。程序会死机。
在基于按键外部中断可用的情况下,创建rtos任务,但是程序会死机。程序debug调试运行到
vTaskStartScheduler(); /* 启动任务,开启调度 */
会死机。
问了copilot,提到中断任务优先级应该大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY,试着搞了下也没有成功。
该版代码如下:SHA-1: 3693c52528518d109df40f62a0cce016af1e532e。
索性按照copilot代码,只创建一个按键任务。修改代码如下,按下离屏幕最近的按键,能够正常进EXTI0_IRQHandler中断服务函数,但是程序会卡死在Error:freeRTOS\src\tasks.c,4579,卡在configASSERT( xTaskToNotify )。查了资料,解释大概如下:【xTaskToNotify: 这是一个任务句柄,用于标识要被通知的任务。在 FreeRTOS 中,使用 xTaskNotify() 或 xTaskNotifyGive() 函数可以向特定任务发送通知。】,意识到可能是通知的任务句柄不对。检查代码确实如此。
如图:
通知任务句柄为Key1_Task_Handle,但是Key1_Task_Handle函数被我注释了。
修改后发现还是有问题,configASSERT( xTaskToNotify )这行代码意思xTaskToNotify为空,实际应该不为空的。
仔细对比了代码,竟然发现了个大BUG。即任务创建时,这边应该写成取地址。
将这边改成取地址后,程序打印还是有点问题,就是按键2不会打印“按键2被按下”字符串。
上述代码提交到:SHA-1: 8a9161d9a97a8311026daed5acac717b09c6dd10可以实现按键中断,但是按键1正常,按键2不会打印预期文字,
百思不得其解,还以为跟任务优先级有关。试了好多次,仔细看了代码,发现是因为按键2是下降沿触发。
改成上升沿触发就正常了。
放开打印任务,程序也正常运行。
SHA-1: 20a3f06543345afe55a70c21dff59be074ea8e1a程序正常运行
以做记录。
尝试将按键外部中断EXTI中断优先级改成1,按下按键后报错,
对应代码configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );,调试看到ucMaxSysCallPriority ucMaxSysCallPriority 值是5,所以EXTI中断优先级应该>=5才不会有问题。
三、
下一步就是配置串口中断,并且在串口中断中发送信号给任务,任务可以打印接收的数据。
2025年7月30日23:05:42
这边中断优先级应该改成大于5。
实现freeRTOS中断接收数据
0d291559fcd4b87e8f82a4b161277811ca659741
3.1 关于freeRTOS中断配置
freeRTOS代码中这行代码什么意思?
这段 FreeRTOS 的代码是在做运行时的断言检查,来确保系统运行的安全性和一致性。来,咱们逐步拆解它:
代码解析:configASSERT( ucCurrentPriority >= ucMaxSysCallPriority )
这行代码的意思是:当前任务的优先级必须高于或等于允许调用系统函数的最低优先级。如果条件不满足,系统会触发断言(assert),通常是在调试时中断程序或者报告错误。
关键字段解释:
-
configASSERT(...)
FreeRTOS 的宏定义,用于在调试时进行条件检查。条件为假时,触发断言(类似标准的assert()),帮助开发者发现潜在错误。 -
ucCurrentPriority
当前运行任务的优先级(通常是一个uint8_t类型的值)。 -
ucMaxSysCallPriority
定义了能安全调用 FreeRTOS 系统调用(比如vTaskDelay()或者队列操作)的最大优先级阈值。优先级数字越小,优先级越高(这是 ARM Cortex-M 的优先级排序方式)。
⚠️ 为什么需要这个检查?
某些 FreeRTOS API 不支持在高优先级(特别是中断上下文)中调用,比如不能在某些中断里调用阻塞函数。这个断言是为了确保你的代码不会违反这些规则:
- 高优先级任务或中断服务程序如果调用不允许的 API,可能导致系统不稳定或死锁。
- 所以,系统在运行时做断言检查,提前防止这种危险情况发生。
为什么需要做这个检查,解释更详细些
当然可以,我来更深入地给你解析一下这个断言的必要性,甚至可以说它是系统稳定运行的“防火墙”。🔥
背后动机:为什么需要这个 configASSERT 检查?
FreeRTOS 是一个实时操作系统,它需要严格的优先级管理。为了防止“优先级混乱”引发系统崩溃或者行为异常,这个断言起到了“守门员”的作用。
防止错误调用系统 API
某些 FreeRTOS 的 API(比如队列发送/接收、任务调度)不能在中断优先级过高的情况下被调用。如果你在高优先级中断(比如不可屏蔽中断)里调用这些函数:
- 系统可能无法正常关中断(因为中断优先级太高,不可被屏蔽)
- 会造成调度器失效,引发 Hard Fault 或系统死锁
所以这里的 configASSERT( ucCurrentPriority >= ucMaxSysCallPriority ) 检查就是用来确保:
“当前优先级足够低,允许调用系统 API”
Cortex-M 的中断优先级机制补充
在 ARM Cortex-M 中断模型里:
- 优先级值越小,优先级越高(比如
0是最高优先级) - 高优先级中断不能被屏蔽(也就不能调用一些需要关闭中断的系统服务)
FreeRTOS 使用 configMAX_SYSCALL_INTERRUPT_PRIORITY 来标记“能安全调用系统 API 的最高优先级”,这个值通常设置为一个比较大的数字,比如 5 << (8 - __NVIC_PRIO_BITS)。
举个例子更清楚:
假设你有一个中断服务函数(ISR),其优先级为 3(比 ucMaxSysCallPriority=5 高):
void MyISR(void)
{
// 错误做法!在高优先级中断中调用系统API
xQueueSendFromISR(...);
}
这个调用就是不安全的,因为它可能违反 FreeRTOS 的中断管理规则。
一旦你开启调试断言,就会触发:
configASSERT(3 >= 5); // 条件不满足,断言失败
这能立即提醒你:这里的 ISR 优先级设置不合理!
总结一下为什么有必要:
- ⛔ 防止高优先级中断非法调用系统函数
- 🧠 保护调度器运行和中断管理机制
- 🧰 帮助开发者尽早发现配置错误,避免“潜在炸弹”
- 📊 符合 ARM 中断模型和 FreeRTOS 的设计约束
。如果你在高优先级中断(比如不可屏蔽中断)里调用这些函数:
- 系统可能无法正常关中断(因为中断优先级太高,不可被屏蔽)
这是什么意思
这句话的核心意思是:中断优先级太高,导致系统无法暂时屏蔽它,从而无法安全地执行某些关键操作。我来给你深入拆解一下这个机制背后的逻辑👇
什么叫“关中断”?
在嵌入式系统中,为了保护共享资源或保证某些关键代码的原子性,FreeRTOS 在某些 API 中需要 临时关闭中断(也叫屏蔽中断),避免中断打断这些操作。
比如在访问一个队列或调度任务时:
taskENTER_CRITICAL();
// 关键代码区域
taskEXIT_CRITICAL();
这段代码会禁止中断进入,从而避免数据竞争或死锁。
高优先级中断的问题
在 Cortex-M 架构中:
- 优先级数字越小,优先级越高
- 某些高优先级中断,不能被屏蔽,无论你怎么关中断
所以当你在这种“无法屏蔽”的中断里调用像 xQueueSendFromISR() 这样的函数:
- FreeRTOS 会试图关闭中断来保护资源
- 但高优先级中断无法关闭,就等于保护措施失效
- 这可能造成资源破坏、调度器失控,甚至系统崩溃或 HardFault
举个具体例子:
假如你设定一个中断的优先级为 1(非常高),然后在里面调用:
xQueueSendFromISR(xQueue, &data, NULL);
此时 FreeRTOS 内部会尝试使用 portSET_INTERRUPT_MASK_FROM_ISR() 来屏蔽中断,但你的中断优先级太高,该调用无效。结果:
- 调度器状态可能被破坏
- 多任务访问共享资源时发生竞态条件
- 轻则功能异常,重则系统崩溃
所以这就解释了为什么需要断言检查:
configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
这是在提醒你:你正在一个优先级太高的中断里调用了不安全的 API!请调整中断优先级。
下面是一个简洁清晰的FreeRTOS 与 Cortex-M 中断优先级配置图
| 优先级值(越小越高) | 是否允许调用 FreeRTOS API | 描述 |
|---|---|---|
| 0 | 不允许 | 极高优先级,通常为紧急中断,如故障处理 |
| 1 | 不允许 | 高优先级,不能被屏蔽,禁止调用系统函数 |
| 2 | 不允许 | 仍不可屏蔽,禁止调用调度器相关 API |
| 3 | 不允许 | 超过最大系统调用优先级限制 |
| 4 | 不允许 | 接近阈值,系统断言会触发 |
| 5 | 允许 | 推荐:设置为configMAX_SYSCALL_INTERRUPT_PRIORITY |
| 6 | 允许 | 可调用 xQueueSendFromISR() 等安全 API |
| 7 | 允许 | 优先级较低,可用于一般外设中断 |
| … | 允许 | 越大越安全(越低优先级) |
| 255 | 最安全 | 最低优先级(最大值具体取决于 NVIC 配置位数) |
四
今天看串口通讯的代码,看到这行代码
void task_rs485(void *pvParameters)
{
while (1)
{
vTaskSuspend(NULL);
if (slave_rx_finish(&dat_rs485, FIRE_EVENT_SET_485) == FALSE)
{
bsp_rs485_send(NULL, 0);
}
else
{
dat_rs485.rxokcnt++;
bsp_rs485_send(dat_rs485.ptxbuf, dat_rs485.txlen);
}
}
}
上述是一个串口任务,其中对于vTaskSuspend(NULL);这行代码不理解
查了下百度,
vTaskSuspend(NULL)用于挂起当前任务自身,即停止当前任务的执行,直到被唤醒。以下是具体说明:行为描述
调用
vTaskSuspend(NULL)时,当前运行的任务会被立即挂起,调度器会切换到其他就绪任务。挂起后,任务将不再参与调度,直到被vTaskResume()恢复。
就是任务挂起,但是任务什么时候被启动呢?程序中应该是调用了vTaskResume();函数来唤醒的啊,搜索程序发现这行代码
void hook_rs485(void)
{
BaseType_t xHigherPriorityTaskWoken;
if(eTaskGetState( app_task_t[ HANDLE_INDEX_TASK_RS485 ].handle ) == eSuspended)
{
xHigherPriorityTaskWoken = xTaskResumeFromISR(app_task_t[ HANDLE_INDEX_TASK_RS485 ].handle);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);/* 如果需要的话进行一次任务切换 */
}else{
usart_receive_config(USART1, USART_RECEIVE_ENABLE);
}
}
所以使用的不是vTaskResume(),而是中断的唤醒函数xTaskResumeFromISR;
hook_rs485函数是在这里调用的,
/* 中断处理 */
void USART1_IRQHandler(void)
{
/* 超时 */
if(SET == usart_interrupt_flag_get(RS485_1_USART_NAME, USART_INT_FLAG_RT))
{
usart_receive_config(RS485_1_USART_NAME, USART_RECEIVE_DISABLE);
usart_interrupt_flag_clear(RS485_1_USART_NAME, USART_INT_FLAG_RT);
hook_rs485();
}
/* 接收中断处理 */
if(SET == usart_interrupt_flag_get(RS485_1_USART_NAME, USART_INT_FLAG_RBNE))
{
if(dat_rs485.rxlen + 1 < UART_MSG_SIZE)
{
buf_rs485_rx[dat_rs485.rxlen++] = usart_data_receive(RS485_1_USART_NAME);
}
}
/* 发送中断处?? */
if(SET == usart_interrupt_flag_get(RS485_1_USART_NAME, USART_INT_FLAG_TC))
{
usart_interrupt_flag_clear(RS485_1_USART_NAME, USART_INT_FLAG_TC);
if(dat_rs485.txlen == 0U)
{
dat_rs485.txcnt++;
dat_rs485.rxlen = 0;
usart_transmit_config(RS485_1_USART_NAME, USART_TRANSMIT_DISABLE);
usart_receive_config(RS485_1_USART_NAME, USART_RECEIVE_ENABLE);
dat_rs485.ptxbuf = buf_rs485_tx;
}
else
{
usart_data_transmit(RS485_1_USART_NAME, *dat_rs485.ptxbuf++);
dat_rs485.txlen--;
}
}
}
至于为什么要在超时函数中调用就不知道了,但是除此其他的是理解了。另外上述是GD32的驱动函数。
总结
未完待续
更多推荐

所有评论(0)