rtthread
官方文档开源免费:Apache 2.0协议
版本
版本 | 最小资源占用需求 |
---|---|
nano | FLASH 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);
应用示例
生产者消费者模型
这个模型可以做个比喻: 假设有一家小商店,店员负责上货,顾客负责进店消费。店员上了货才会离开,顾客进店消费才会离开。 但是该店每次只能容纳一个人,所以店员和顾客是互斥的。
因此,写代码的时候需要注意死锁。 店员需要先判断货架是否满,才考虑是否等待没人时进入。而顾客需要先判断货架是否空,才考虑是否等待没人时进入。
否则,店员和顾客会一直等待,然后导致死锁。例如以下两种情况:
- 店员先判断到没人,然后进入店里。结果发现货架满了,此时店员会一直等待上货才离开。但是因为店员一直在店里,导致顾客无法进入来消费取货,因此顾客也在等待。
- 顾客先判断到没人,然后进入店里。结果发现货架空了,此时顾客会一直等待有货,消费了才离开。但是因为顾客一直在店里,导致店员无法进来上货,因此店员也在等待。
// 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);