rtthread

官方文档在新窗口打开开源免费:Apache 2.0协议

版本

版本最小资源占用需求
nanoFLASH 3KB、RAM 1.2KB

概念

相关概念描述
IPC进程间通信

线程

线程状态

创建线程和销毁

// 创建动态线程
rt_thread_t rt_thread_create(const char* name,
                            void (*entry)(void* parameter),
                            void* parameter,
                            rt_uint32_t stack_size,
                            rt_uint8_t priority,
                            rt_uint32_t tick);
// 线程删除
rt_err_t rt_thread_delete(rt_thread_t thread);


// 创建静态线程
rt_err_t rt_thread_init(struct rt_thread* thread,
                        const char* name,
                        void (*entry)(void* parameter), void* parameter,
                        void* stack_start, rt_uint32_t stack_size,
                        rt_uint8_t priority, rt_uint32_t tick);
// 线程脱离
rt_err_t rt_thread_detach (rt_thread_t thread);

线程启动

将线程从初始状态,进入就绪状态。

rt_err_t rt_thread_startup(rt_thread_t thread);

获取当前线程

同一段代码可能被多个线程调用,若需要获取当前线程,则使用该函数。

rt_thread_t rt_thread_self(void);

线程让出处理器资源

执行该函数后,线程依然在就绪状态。只是挂在了就绪队列尾部,等待调度。

rt_err_t rt_thread_yield(void);

线程睡眠

rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);

线程挂起和恢复

// 线程挂起
rt_err_t rt_thread_suspend (rt_thread_t thread);
// 线程恢复
rt_err_t rt_thread_resume (rt_thread_t thread);

线程控制

cmd说明
RT_THREAD_CTRL_CHANGE_PRIORITY动态更改线程优先级
RT_THREAD_CTRL_STARTUP开始运行一个线程,等同于rt_thread_startup
RT_THREAD_CTRL_CLOSE关闭一个线程,等同于rt_thread_delete或rt_thread_detach
rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void* arg);

钩子函数

空闲钩子

系统空闲期间,执行其他操作。比如指示灯

rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));

调度器钩子

想知道线程切换时,发生了怎样的线程切换。可以设置调度器钩子函数。

void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to));

应用案例

USB-SCSI 线程创建

// 创建线程1
rt_thread_init(&thread_usb_scsi, "u-scsi", usb_scsi_thread, NULL, thread_usb_scsi_stack, T_USB_SCSI_MAX, 5, 1000);
rt_thread_startup(thread_usb_scsi);

定时器

系统启动前,会先调用系统定时器初始化函数。

void rt_system_timer_init(void);

// 若使用软定时器,则除了上述接口外,还需要调用该函数
void rt_system_timer_thread_init(void);

定时器创建和销毁

flag说明
RT_TIMER_FLAG_ONE_SHOT定时器是一次性定时器,在超时后自动销毁
RT_TIMER_FLAG_PERIODIC周期性定时器,超时后自动重新启动
RT_TIMER_FLAG_HARD_TIMER硬定时器,超时后不会自动销毁
RT_TIMER_FLAG_SOFT_TIMER软定时器,超时后自动销毁
// 动态创建定时器
rt_timer_t rt_timer_create(const char* name,
                           void (*timeout)(void* parameter),
                           void* parameter,
                           rt_tick_t time,
                           rt_uint8_t flag);
// 动态删除定时器
rt_err_t rt_timer_delete(rt_timer_t timer);


// 静态创建定时器
void rt_timer_init(rt_timer_t timer,
                   const char* name,
                   void (*timeout)(void* parameter),
                   void* parameter,
                   rt_tick_t time, rt_uint8_t flag);
// 脱离定时器
rt_err_t rt_timer_detach(rt_timer_t timer);

启动和停止定时器

// 启动定时器
rt_err_t rt_timer_start(rt_timer_t timer);
// 停止定时器
rt_err_t rt_timer_stop(rt_timer_t timer);

控制定时器

cmd说明
RT_TIMER_CTRL_SET_TIME设置定时器超时时间
RT_TIMER_CTRL_GET_TIME获取定时器超时时间
RT_TIMER_CTRL_SET_ONESHOT设置定时器为一次性定时器
RT_TIMER_CTRL_SET_PERIODIC设置定时器为周期性定时器
rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg);

应用示例

// 2021-04-09
#include "demo.h"

static struct rt_timer timer1;
static struct rt_timer timer2;
static uint32_t cnt = 0;

void timeout1(void *param)
{
    cnt ++;

    rt_kprintf("periodic timer is timeout %d\n", cnt);
    if(cnt >= 10)
    {
        rt_timer_stop(&timer1);
    }
}

void timeout2(void *param)
{
     rt_kprintf("one shot timer is timeout\n");
}

void demo_02(void)
{
    static uint8_t first_init = 0;
    if(first_init == 0)
    {  
        first_init = 1;

        rt_timer_init(&timer1,"timer1",timeout1,NULL,100,RT_TIMER_FLAG_PERIODIC);
        rt_timer_init(&timer2,"timer2",timeout2,NULL,500,RT_TIMER_FLAG_ONE_SHOT);
        
        rt_timer_start(&timer1);
        rt_timer_start(&timer2);

    }
    else
    {
        rt_kprintf("demo_02 is already started\n");
    }
}

信号量

释放一次,信号量加1。获取一次,信号量减1。若信号量为0,则获取信号量的线程将挂起,直到有线程释放信号量,使得信号量大于0。

信号量的创建和销毁

flag功能说明
RT_IPC_FLAG_PRIO优先级信号量确保实时性
RT_IPC_FLAG_FIFO先进先出信号量
// 创建信号量
rt_sem_t rt_sem_create(const char *name,
                        rt_uint32_t value,
                        rt_uint8_t flag);
// 删除信号量
rt_err_t rt_sem_delete(rt_sem_t sem);


// 创建静态信号量
rt_err_t rt_sem_init(rt_sem_t       sem,
                    const char     *name,
                    rt_uint32_t    value,
                    rt_uint8_t     flag)
// 脱离信号量
rt_err_t rt_sem_detach(rt_sem_t sem);

信号量的获取和释放

// 获取信号量
rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);
// 释放信号量
rt_err_t rt_sem_release (rt_sem_t sem);

// 无等待获取信号量
rt_err_t rt_sem_trytake(rt_sem_t sem);

信号量控制

cmd说明
RT_IPC_CMD_RESET复位信号量
rt_err_t rt_sem_control(rt_sem_t sem, int cmd, void *arg);

应用示例

生产者消费者模型

这个模型可以做个比喻: 假设有一家小商店,店员负责上货,顾客负责进店消费。店员上了货才会离开,顾客进店消费才会离开。 但是该店每次只能容纳一个人,所以店员和顾客是互斥的。

因此,写代码的时候需要注意死锁。 店员需要先判断货架是否满,才考虑是否等待没人时进入。而顾客需要先判断货架是否空,才考虑是否等待没人时进入。

否则,店员和顾客会一直等待,然后导致死锁。例如以下两种情况:

  1. 店员先判断到没人,然后进入店里。结果发现货架满了,此时店员会一直等待上货才离开。但是因为店员一直在店里,导致顾客无法进入来消费取货,因此顾客也在等待。
  2. 顾客先判断到没人,然后进入店里。结果发现货架空了,此时顾客会一直等待有货,消费了才离开。但是因为顾客一直在店里,导致店员无法进来上货,因此店员也在等待。
// 2021-04-09
#include "demo.h"

static struct rt_thread thread1;
static struct rt_thread thread2;

static char thread1_stack[256];
static char thread2_stack[256];

#define SEM_MAX             5
static uint32_t array[SEM_MAX] = {0}; 

/*
    2021-04-09 这里有问题问题先记录一下
    理论上来说,static变量不是直接初始化到静态区了么?
    那么如果将这两个参数分别放到不同的线程中,那么结果会我期待的不一致?
    请问这是为什么

    现在first_init标志放在函数内部都不行了?放在全局就好了
    是因为内存占用太大了?不对呀,这个static又不占用栈空间?
    不太理解
*/
static uint32_t set_index = 0;
static uint32_t get_index = 0;

static struct rt_semaphore sem_lock;
static struct rt_semaphore sem_empty,sem_full;


void producer_thread(void *param)
{
    uint32_t cnt = 0;

    while(cnt < 10)
    {
        // 等待空位,有存放的位置才生产。
        rt_sem_take(&sem_empty,RT_WAITING_FOREVER);

        rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
        array[set_index] = cnt;
        rt_kprintf("the producer generates a number: %d\n", array[set_index]);
        if(set_index < SEM_MAX)
        {
            set_index ++;
        }
        else
        {
            set_index = 0;
        }
        rt_sem_release(&sem_lock);

        // 释放一个满位,表示有货。表示可以来取了
        rt_sem_release(&sem_full);

        cnt ++;
        rt_thread_mdelay(200);
    }

    rt_kprintf("the producer exit!\n");
}

void consumer_thread(void *param)
{
    while(1)
    {
        // 等待满位,有货才取。(没货取个寂寞?)
        rt_sem_take(&sem_full,RT_WAITING_FOREVER);

        rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
        rt_kprintf("the consumer[%d] get a number: %d\n", (get_index), array[get_index]);
        if(get_index < SEM_MAX)
        {
            get_index ++;
        }
        else
        {
            get_index = 0;
        }
        rt_sem_release(&sem_lock);

        // 释放一个空位,表示有空仓。可以继续放货了
        rt_sem_release(&sem_empty);

        rt_thread_mdelay(500);
    }
}

void demo_03(void)
{
    static uint8_t first_init = 0;

    if(first_init == 0)
    {
        first_init = 1;

        rt_sem_init(&sem_lock,"sem_lock",1 ,RT_IPC_FLAG_FIFO);
        rt_sem_init(&sem_empty,"sem_empty",SEM_MAX,RT_IPC_FLAG_FIFO);
        rt_sem_init(&sem_full,"sem_full",0,RT_IPC_FLAG_FIFO);

        rt_thread_init(&thread1,
                    "produce",
                    producer_thread,
                    NULL,
                    thread1_stack,
                    256,
                    1,
                    10);

        rt_thread_init(&thread2,
                    "consum",
                    consumer_thread,
                    NULL,
                    thread2_stack,
                    256,
                    3,
                    10);
        
        rt_thread_startup(&thread1);
        rt_thread_startup(&thread2);
    }
    else
    {
        rt_kprintf("demo_03 is already started\n");
    }
}

MSH_CMD_EXPORT(demo_03, semaphore description)

USB接收处理

初值配置为0,当USB端点接收中断触发后。信号量+1。 当线程处理完之后,信号量-1。

// 创建信号量
if(RT_EOK != rt_sem_init(&sem_usb_recv, "u-scsi-r", 0, RT_IPC_FLAG_FIFO))
{
    rt_kprintf("# rt_sem_init failed!\n");
}

// 信号量获取,value-1
if(RT_EOK != rt_sem_take(&sem_usb_recv, RT_WAITING_FOREVER))
{
    rt_kprintf("# rt_sem_take failed!\n");
}

// 信号量释放,value+1
if(RT_EOK != rt_sem_release(&sem_usb_recv))
{
    rt_kprintf("# rt_sem_release failed!\n");
}

互斥量

互斥量只能由持有的线程释放,而信号量可以由任何线程释放。

注意

使用信号量做互斥时,可能存在优先级反转问题。例如A>B>C,当A获取信号量时,若C还未释放的同时,被B打断了。那么相当于B比A要优先执行。

使用互斥量,解决优先级反转问题。当A获取共享资源被暂时挂起时,会暂时把低优先级的线程C提高到和A一样。防止C被B抢占(也即A间接的被B抢占)。

警告

不可在中断中使用互斥量

互斥量的创建和销毁

// 创建互斥量(flag标志无效,均按照RT_IPC_FLAG_PRIO来处理)
rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag);
// 删除互斥量
rt_err_t rt_mutex_delete (rt_mutex_t mutex);


// 静态创建互斥量
rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag);
// 脱离互斥量
rt_err_t rt_mutex_detach (rt_mutex_t mutex);

互斥量的获取和释放

// 获取互斥量
rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);
// 释放互斥量
rt_err_t rt_mutex_release(rt_mutex_t mutex);

// 无等待获取互斥量
rt_err_t rt_mutex_trytake(rt_mutex_t mutex);

事件集

事件集本质上是一个32位的变量,每一位代表一个事件。可以用来做中断处理,或者线程间通信。

  • 特定事件唤醒线程(单个bit)
  • 任意单个事件唤醒线程(多bit或操作)
  • 多个事件同时发生来唤醒线程(多bit与操作)

事件集的创建和销毁

rt_event_t rt_event_create(const char* name, rt_uint8_t flag);
rt_err_t rt_event_delete(rt_event_t event);

rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag);
rt_err_t rt_event_detach(rt_event_t event);

发送事件和接收事件

option说明
RT_EVENT_FLAG_OR任意一个事件发生,则唤醒线程
RT_EVENT_FLAG_AND多个事件同时发生,则唤醒线程
RT_EVENT_FLAG_CLEAR事件发生后,自动清除事件
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);

rt_err_t rt_event_recv(rt_event_t event,
                           rt_uint32_t set,
                           rt_uint8_t option,
                           rt_int32_t timeout,
                           rt_uint32_t* recved);

邮箱

每一封邮箱的大小为4字节(也即指针大小),可以用于中断。当线程间需要传递比较大的消息时,可以把指向缓存的指针发送到邮箱中。

  • 优点 开销低,效率高

邮箱的创建和销毁

// 动态创建邮箱
rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag);
// 删除邮箱
rt_err_t rt_mb_delete (rt_mailbox_t mb);

// 静态创建邮箱
rt_err_t rt_mb_init(rt_mailbox_t mb,
                    const char* name,
                    void* msgpool,
                    rt_size_t size,
                    rt_uint8_t flag);
// 脱离邮箱
rt_err_t rt_mb_detach(rt_mailbox_t mb);

邮箱的发送和接收

// 发送邮件
rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value);

// 等待方式发送邮件
// 发送邮件,若邮箱已满,则等待timeout毫秒。超出时间,则返回超时错误码
rt_err_t rt_mb_send_wait (rt_mailbox_t mb,
                      rt_uint32_t value,
                      rt_int32_t timeout);

// 发送紧急邮件
// 邮件被插入到队首,优先级最高
rt_err_t rt_mb_urgent (rt_mailbox_t mb, rt_ubase_t value);

// 接收邮件
rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);

示例

按键检测

线程1检测按键状态并发送,线程2接收按键状态并处理。

动态内存

A线程动态分配的内存,通过邮箱发送给B线程。B线程处理完成之后,再释放掉。

消息队列

接收线程或中断处理服务例程中不固定长度的消息。

消息队列的创建和销毁

// 动态创建消息队列
rt_mq_t rt_mq_create(const char* name, rt_size_t msg_size,
            rt_size_t max_msgs, rt_uint8_t flag);
// 删除消息队列
rt_err_t rt_mq_delete(rt_mq_t mq);

// 静态创建消息队列
rt_err_t rt_mq_init(rt_mq_t mq, const char* name,
                        void *msgpool, rt_size_t msg_size,
                        rt_size_t pool_size, rt_uint8_t flag);
// 脱离消息队列
rt_err_t rt_mq_detach(rt_mq_t mq);

消息队列的发送和接收

// 发送消息
rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size);
// 等待方式发送消息
rt_err_t rt_mq_send_wait(rt_mq_t     mq,
                         const void *buffer,
                         rt_size_t   size,
                         rt_int32_t  timeout);
// 发送紧急消息
rt_err_t rt_mq_urgent(rt_mq_t mq, void* buffer, rt_size_t size);

// 接收消息
rt_ssize_t rt_mq_recv (rt_mq_t mq, void* buffer,
                    rt_size_t size, rt_int32_t timeout);

应用示例

线程间消息交换

串口接收不定长数据

信号

又称“软中断信号”,在软件层面上对中断进行模拟。

安装信号

rt_sighandler_t rt_signal_install(int signo, rt_sighandler_t[] handler);

阻塞和解除阻塞信号

void rt_signal_mask(int signo);
void rt_signal_unmask(int signo);

发送和等待信号

int rt_thread_kill(rt_thread_t tid, int sig);
// 等待信号
int rt_signal_wait(const rt_sigset_t *set,
                        rt_siginfo_t[] *si, rt_int32_t timeout);

内存池

命令行

// 导出软复位指令
// reset 
MSH_CMD_EXPORT(reset, chip soft reset);