RT-Thread 和 FreeRTOS 有什么不同

FreeRTOS 是一个纯内核:给你任务调度、队列、信号量,剩下的文件系统、网络协议栈、设备模型都要自己拼。RT-Thread 在实时内核之上,还带了一套完整的组件生态:设备框架、文件系统、网络协议栈、Shell 命令行、自动初始化机制,开箱就能跑一个带命令行的物联网设备。

FreeRTOS 像一块裸机芯,RT-Thread 像一台装好常用软件的小电脑。你写产品代码时更多是“接组件”而不是“造轮子”。
RT-Thread 配图:中央内核与向外辐射的任务节点和消息管道
RT-Thread 在内核之上提供设备模型与组件生态,应用开发像搭积木

RT-Thread 分标准版和 Nano 版:Nano 版只有几 KB,和 FreeRTOS 定位类似;标准版带了完整的组件,适合资源稍宽裕的联网设备。它由国内团队主导开发,中文文档和社区资料非常全,遇到问题搜得到答案,这对国内嵌入式工程师是实打实的便利。

线程管理:创建、优先级与时间片

RT-Thread 里的“任务”叫线程。创建线程用 rt_thread_create,可以静态或动态方式。它同样是抢占式调度,相同优先级线程按时间片轮转。一个值得注意的区别是:RT-Thread 的线程栈单位直接是字节,不像 FreeRTOS 那样以字为单位。

#include <rtthread.h>

static rt_thread_t tid_sensor, tid_upload;

static void sensor_entry(void *param)
{
    rt_tick_t last = rt_tick_get();
    while (1) {
        read_sensor();
        /* 严格周期延时 */
        rt_thread_mdelay(10);
    }
}

static void upload_entry(void *param)
{
    while (1) {
        /* 等队列里有数据再发 */
        rt_mb_recv(sample_mb, &sample, RT_WAITING_FOREVER);
        do_upload(&sample);
    }
}

int main(void)
{
    /* 动态创建:名称、入口、栈大小、优先级、时间片 */
    tid_sensor = rt_thread_create("sensor", sensor_entry, RT_NULL,
                                  1024, 10, 20);
    rt_thread_startup(tid_sensor);

    tid_upload = rt_thread_create("upload", upload_entry, RT_NULL,
                                  2048, 15, 20);
    rt_thread_startup(tid_upload);
    return 0;
}

线程创建后不会立刻运行,必须调用 rt_thread_startup 把它挂到就绪队列。RT-Thread 的 main 线程本身也是一个普通线程,main 返回后它会被自动清理,这一点和裸机 while(1) 结构不同,写的时候要留意。

线程间通信:消息队列与信号量

RT-Thread 提供了一套和 FreeRTOS 思路类似但命名不同的通信原语:消息队列(rt_mq)传数据,信号量(rt_sem)传事件,互斥量(rt_mutex)保护资源,事件集合(rt_event)等多个条件。

#include <rtdef.h>

static rt_mq_t    sample_mb;     /* 消息队列 */
static rt_sem_t   dma_sem;       /* 信号量 */
static rt_mutex_t bus_mutex;     /* 互斥量 */

struct sample { rt_uint32_t ts; float temp; float hum; };

static void producer(void *arg)
{
    struct sample s;
    while (1) {
        s.ts = rt_tick_get();
        s.temp = read_temp();
        s.hum  = read_hum();
        /* 队列满了等 10 个 tick,写不进就丢 */
        if (rt_mq_send(sample_mb, &s, sizeof(s)) != RT_EOK) {
            g_drop++;
        }
        rt_thread_mdelay(100);
    }
}

static void bus_write(const rt_uint8_t *buf, rt_size_t len)
{
    rt_mutex_take(bus_mutex, RT_WAITING_FOREVER);
    spi_transfer(buf, len);
    rt_mutex_release(bus_mutex);
}
通信原语选择速查
  • 传数据用消息队列自带拷贝语义,收发双方解耦,是最常用的线程间通道。
  • 传事件用信号量中断里用 rt_sem_release 通知线程处理,避免在中断里干重活。
  • 保护资源用互斥量带优先级继承,比信号量更适合锁总线、锁外设。

设备驱动框架:I/O 设备模型

RT-Thread 最有特色的是它的 I/O 设备模型。你写的应用代码只需要 rt_device_find、rt_device_read、rt_device_write 这几个统一接口,不管底层是 UART、SPI 还是 I2C,上层用法都一样。驱动开发者把硬件注册成一个 rt_device,应用层完全感知不到底层差异。

#include <rtdevice.h>

static rt_device_t uart;

int console_init(void)
{
    /* 按名字找设备:注册时叫 "uart1" */
    uart = rt_device_find("uart1");
    if (!uart) return -RT_ERROR;

    rt_device_open(uart, RT_DEVICE_OFLAG_RDWR);
    /* 设置接收回调:收到数据就调这个函数 */
    rt_device_set_rx_indicate(uart, uart_rx_cb);
    return RT_EOK;
}

static rt_err_t uart_rx_cb(rt_device_t dev, rt_size_t size)
{
    /* 中断上下文里只发信号量,不读数据 */
    rt_sem_release(uart_rx_sem);
    return RT_EOK;
}

/* 在单独线程里真正读数据 */
static void uart_thread(void *arg)
{
    char buf[64];
    while (1) {
        rt_sem_take(uart_rx_sem, RT_WAITING_FOREVER);
        rt_size_t n = rt_device_read(uart, 0, buf, sizeof(buf));
        process_line(buf, n);
    }
}

这种模型的好处是:换一块芯片、换一种外设,只要驱动层把设备注册好,应用层一行都不用改。这也是 RT-Thread 被称为“物联网操作系统”而不仅仅是“内核”的原因。