当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]在RT-Thread(RTT)嵌入式实时系统中,多线程间的同步与通信是应用稳定性的基石。信号量(Semaphore)、队列(Message Queue)与事件集(Event)分别对应“通知”“传数”“多条件等待”三种典型场景。本文将通过STM32上的实战代码,帮你快速掌握三者的选用原则与避坑要点。



RT-Thread(RTT)嵌入式实时系统中,多线程间的同步与通信是应用稳定性的基石。信号量(Semaphore)、队列(Message Queue)与事件集(Event)分别对应“通知”“传数”“多条件等待”三种典型场景。本文将通过STM32上的实战代码,帮你快速掌握三者的选用原则与避坑要点。


一、信号量:资源计数与二值同步


信号量适合“我干完了,你可以开始”的单向同步,或“N个同类资源可用吗”的资源计数。


1. 二值信号量(Binary Semaphore)—— 中断→线程通知


最经典用法:按键/定时器ISR告知处理线程“有新事件”。

#include <rtthread.h>


#define THREAD_STACK 512

#define THREAD_PRIO  20


static rt_sem_t sem_btn;


/* 消费线程 */

static void btn_thread_entry(void *param)

{

   while (1)

   {

       /* 阻塞等待信号量(中断中释放) */

       rt_sem_take(sem_btn, RT_WAITING_FOREVER);

       rt_kprintf("Button pressed, do action!\n");

   }

}


/* 模拟ISR(通常在外部中断回调中调用) */

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

{

   if (GPIO_Pin == GPIO_PIN_0)

   {

       rt_sem_release(sem_btn);   // 不能在ISR中做I/O,仅释放信号量

   }

}


int btn_sample_init(void)

{

   sem_btn = rt_sem_create("btn_sem", 0, RT_IPC_FLAG_FIFO);

   if (!sem_btn) return -1;


   rt_thread_t tid = rt_thread_create("btn", btn_thread_entry,

                                       RT_NULL, THREAD_STACK,

                                       THREAD_PRIO, 20);

   if (tid) rt_thread_startup(tid);

   return 0;

}

INIT_APP_EXPORT(btn_sample_init);



要点:

• 初值0=二值(等待方先阻塞);


• RT_IPC_FLAG_FIFO/PRIO决定等待线程唤醒顺序;


• ISR中严禁rt_sem_take(),只能rt_sem_release()。


2. 计数信号量——管理有限资源池


rt_sem_t sem_pool = rt_sem_create("pool", 3, RT_IPC_FLAG_FIFO); // 3个资源

/* 线程取资源 */

rt_sem_take(sem_pool, RT_WAITING_FOREVER);

/* 用完归还 */

rt_sem_release(sem_pool);



二、队列(Message Queue):带数据的线程通信


当线程间需传递结构化数据(而非简单“告知”)时,队列是唯一正确选择。

#define MQ_MAX_MSGS 10

#define MQ_MSG_SIZE sizeof(struct sensor_data)


struct sensor_data

{

   uint32_t timestamp;

   int16_t  temp;

   int16_t  humi;

};


static rt_mq_t mq_sensor;


/* 采集线程 → 发送数据 */

static void sensor_collect_entry(void *param)

{

   struct sensor_data pkt;

   pkt.timestamp = rt_tick_get();

   pkt.temp = 256;

   pkt.humi = 600;


   /* 若队列满可择性丢弃或等待 */

   rt_mq_send(mq_sensor, &pkt, MQ_MSG_SIZE);

}


/* 处理线程 → 接收数据 */

static void sensor_proc_entry(void *param)

{

   struct sensor_data rcv;

   while (1)

   {

       if (rt_mq_recv(mq_sensor, &rcv, MQ_MSG_SIZE,

                      rt_tick_from_millisecond(500)) == RT_EOK)

       {

           rt_kprintf("T=%d, H=%d\n", rcv.temp/10, rcv.humi/10);

       }

   }

}


int mq_sample_init(void)

{

   mq_sensor = rt_mq_create("sen_mq", MQ_MSG_SIZE,

                             MQ_MAX_MSGS, RT_IPC_FLAG_FIFO);

   if (!mq_sensor) return -1;


   /* 创建采集/处理线程(略,同信号量例) */

   return 0;

}

INIT_APP_EXPORT(mq_sample_init);



对比信号量优势:队列自带缓冲区,发送方不会被接收方节奏阻塞(只要不满),且数据有序、线程安全。


三、事件集(Event):多标志位“与/或”等待


事件集适合“多个条件任意一个/全部满足才继续”的场景,如初始化流程需等“Flash就绪 AND 网络链路 UP”。

#define EV_FLASH_RDY  (1 << 0)

#define EV_NET_LINK   (1 << 1)


static rt_event_t ev_sys;


/* 初始化线程 */

int event_sample_init(void)

{

   ev_sys = rt_event_create("sys_ev", RT_IPC_FLAG_FIFO);

   return ev_sys ? 0 : -1;

}

INIT_APP_EXPORT(event_sample_init);


/* Flash驱动初始化完成 → 置位 */

void flash_ready_cb(void)

{

   rt_event_send(ev_sys, EV_FLASH_RDY);

}


/* Net链路UP → 置位 */

void net_link_up_cb(void)

{

   rt_event_send(ev_sys, EV_NET_LINK);

}


/* 主任务:等两者都就绪(AND逻辑) */

static void main_task_entry(void *param)

{

   rt_uint32_t recv_set = 0;

   rt_event_recv(ev_sys,

                 EV_FLASH_RDY | EV_NET_LINK,

                 RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,

                 RT_WAITING_FOREVER,

                 &recv_set);

   rt_kprintf("System fully initialized!\n");

}



• RT_EVENT_FLAG_AND → 所有位均置位才唤醒


• RT_EVENT_FLAG_OR  → 任一位置位即唤醒


• 建议配合RT_EVENT_FLAG_CLEAR避免重复触发


⚠️ 事件不携带数据,仅传递位图,不能替代队列传结构体。


四、三者选型速查表


需求 推荐IPC 原因


中断通知线程“有事做” 二值信号量 快、无数据拷贝,ISR安全


线程A → 线程B 传结构体 队列 带缓冲、保序、类型安全


等“条件A 且/或 条件B” 事件集 支持多标志位组合判断


管理N个相同外设(如DMA缓冲) 计数信号量 直观反映资源余量


需记录历史数据供慢线程处理 队列 信号量无缓存能力


五、常见坑与调试技巧


1. 在ISR中调错API:ISR只能用rt_sem_release()、rt_mq_urgent()、rt_event_send(),禁止rt_xxx_take(),否则触发scheduler in ISR断言失败。

2. 队列满丢数:高吞吐建议适当加大MQ_MAX_MSGS,或在发送前rt_mq_get_msg_num()判断,满时做标记丢弃而非死等。

3. 事件位不清除导致死循环:若使用循环rt_event_recv(..., OR, CLEAR),确保消费逻辑正确处理所有置位条件,否则可能反复唤醒。

4. 优先级反转:默认二值信号量无优先级继承,若高优先级线程等待低优先级持有的信号量,建议配合互斥锁(rt_mutex_t)保护临界资源,信号量仅用于同步。


六、结语


RT-Thread的多线程通信三件套各司其职:信号量同步、队列传数、事件集等多条件等待。在STM32等资源受限平台,按上表选型并严格遵守ISR API限制,即可构建出既高效又易维护的实时系统。


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

在国产嵌入式实时操作系统领域,RT-Thread无疑是当下最活跃、应用最广泛的开源项目。从早期的个人项目发展到如今支持从8位微控制器到64位多核处理器,覆盖物联网、工业控制、消费电子等多个领域,RT-Thread的成功离...

关键字: RT-Thread 对象容器

树莓派3B凭借低成本、高性能、丰富的外设资源,一直是嵌入式爱好者和开发者学习RTOS的热门平台,而RT-Thread作为国内生态最完善的开源实时操作系统,对树莓派3B有着完善的原生支持。但很多刚接触的开发者,往往卡在环境...

关键字: RT-Thread Linux

作为国内应用最广泛的开源物联网实时操作系统,RT-Thread凭借体积小、实时性强、资源占用低的优势,已经成为中小微嵌入式设备开发的首选RTOS。而线程作为RT-Thread中最基础的调度单元,其调度与管理机制直接决定了...

关键字: RT-Thread CPU

当嵌入式工程师在FreeRTOS、RT-Thread、Zephyr和μC/OS之间做选择时,他们面对的不仅是技术参数的对比,更是四种截然不同的设计哲学。这四款RTOS分别代表了“极简主义的胜利”、“商业可靠的典范”、“国...

关键字: FreeRTOS RT-Thread

作为国内自主研发的实时操作系统(RTOS),RT-Thread凭借其轻量、高效、可裁剪的特性,在嵌入式领域占据了重要地位。内存管理作为操作系统的核心组件之一,直接影响着系统的性能、稳定性和资源利用率。RT-Thread针...

关键字: 实时操作系统 RT-Thread

在工业控制、汽车电子等硬实时场景中,任务调度的确定性直接决定系统稳定性。RT-Thread作为国产实时操作系统,通过优先级继承机制和死锁预防策略,为嵌入式开发者提供了可靠的调度优化方案。

关键字: RT-Thread 工业控制 任务调度

在工业机器人、CNC机床等高精度运动控制场景中,传统RTOS常面临实时性不足、多轴同步困难等问题。RT-Thread凭借其微内核架构、丰富的组件生态和硬实时特性,为运动控制系统提供了全新的解决方案。

关键字: RT-Thread 工业机器人

我们希望在HMI板上建立一些高度互动的东西,超越静态显示。我们的核心理念很简单:模拟抛硬币。这个名为《Lucky 3》的项目就是从这个概念发展而来的,它展示了HMI板如何为一款简单而普遍的机会游戏提供引人入胜的实时反馈。

关键字: RT-Thread HMI 嵌入式

随着物联网和嵌入式系统的发展,实时操作系统(RTOS)的安全性和性能需求日益提高。传统基于C语言的RTOS在内存安全和并发控制方面存在局限,容易导致缓冲区溢出、数据竞争等问题。本项目以RT-Thread为基础,使用Rus...

关键字: Rust RTOS RT-Thread 内存安全 并发安全 多级反馈队列调度 嵌入式系统

在嵌入式系统领域,实时性是一个至关重要的指标,它直接关系到系统对外部事件的响应速度和处理效率。在众多实时操作系统(RTOS)中,RT-Thread、RT-Linux和Zephyr因其各自的特点和优势,成为业界广泛关注的焦...

关键字: RT-Thread RT-Linux Zephyr 物联网
关闭