RT-Thread 的常用函数可以系统地分为几个核心模块。下面是每个模块的详细功能说明和常用 C-API。
📋 RT-Thread 核心模块与常用 C-API
| 模块 | 核心 C-API | 作用与解析 | 关键参数/选项 |
|---|---|---|---|
| 1. 线程管理 | rt_thread_create() | 动态创建线程 | name: 线程名称entry: 线程入口函数parameter: 传入参数stack_size: 栈大小(字节)priority: 优先级(数值越小越高)tick: 时间片大小 |
rt_thread_init() | 静态初始化线程 | 静态方式需用户预先分配好线程控制块和栈空间 | |
rt_thread_startup() | 启动线程 | 使线程进入就绪状态,参与系统调度 | |
rt_thread_mdelay() | 线程毫秒级延时 | ms: 延时时间(毫秒),调用该函数会使当前线程让出CPU | |
rt_thread_self() | 获取当前线程句柄 | 常用于在线程内部获取自身信息 | |
| 2. 线程间同步 | rt_sem_create() / rt_sem_take() / rt_sem_release() | 信号量机制 | 用于同步与资源计数,take获取,release释放 |
rt_mutex_create() / rt_mutex_take() / rt_mutex_release() | 互斥量机制 | 用于互斥,解决优先级反转问题,保护临界区 | |
rt_event_init() / rt_event_send() / rt_event_recv() | 事件集机制 | 用于等待多个“与/或”关系的事件 | |
| 3. 线程间通信 | rt_mb_create() / rt_mb_send() / rt_mb_recv() | 邮箱机制 | 传递4字节的简短消息,效率高 |
rt_mq_create() / rt_mq_send() / rt_mq_recv() | 消息队列机制 | 传递可变长度的数据,是邮箱的扩展 | |
| 4. 系统与设备管理 | rt_kprintf() | 格式化打印 | 类似printf,输出到系统控制台,用于调试 |
rt_device_find() / rt_device_open() / rt_device_read() / rt_device_write() / rt_device_control() | 统一设备访问接口 | 通过设备名查找设备句柄,然后进行打开、读写、控制等操作 | |
rt_malloc() / rt_calloc() / rt_realloc() / rt_free() | 动态内存管理 | 从系统堆中分配/释放内存,使用后要及时释放避免内存泄漏 |
下面,我们来看看其中一些复杂函数在实际开发中是如何使用的。
💡 核心场景与使用思路
1. 线程管理:创建与启动线程
这是RT-Thread应用开发最基础的步骤。
【方式一】动态创建 (rt_thread_create)
推荐使用,内存从动态堆中分配,更灵活。
/* 定义线程入口函数 */
static void led_thread_entry(void *parameter) {
while (1) {
rt_kprintf("LED Thread is Running...\n");
rt_thread_mdelay(1000);
}
}
/* 在主函数或其他地方动态创建和启动线程 */
void main(void) {
rt_thread_t led_thread = RT_NULL;
/* 动态创建线程 */
led_thread = rt_thread_create("led",
led_thread_entry,
RT_NULL,
1024, /* 线程栈大小,单位字节 */
25, /* 线程优先级 */
10); /* 时间片大小(tick) */
/* 如果创建成功,则启动线程 */
if (led_thread != RT_NULL) {
rt_thread_startup(led_thread);
}
}【方式二】静态初始化 (rt_thread_init)
这种方式适合生命周期固定的线程,内存空间在编译时就分配好了。
/* 1. 静态定义线程栈和控制块 */
static char thread1_stack[1024];
static struct rt_thread thread1;
/* 2. 线程入口函数 */
static void thread1_entry(void *param) {
while (1) {
/* 线程执行体 */
rt_thread_mdelay(500);
}
}
/* 3. 在线程初始化函数中静态初始化并启动 */
void static_thread_sample(void) {
rt_thread_init(&thread1, /* 线程控制块 */
"thread1", /* 线程名称 */
thread1_entry, /* 线程入口函数 */
RT_NULL, /* 线程入口参数 */
&thread1_stack[0], /* 线程栈起始地址 */
sizeof(thread1_stack), /* 线程栈大小 */
10, /* 线程优先级 */
5); /* 时间片 */
rt_thread_startup(&thread1);
}2. 线程间同步:信号量
信号量时最常用的同步机制。
【场景】中断与线程同步
这个示例展示了如何通过信号量,在中断发生时唤醒一个线程去处理任务。
/* 定义信号量结构体 */
static struct rt_semaphore rx_sem;
/* 中断服务程序 */
void USARTx_IRQHandler(void) {
/* 进入中断 */
rt_interrupt_enter();
if (/* 判断是否接收到数据 */) {
/* 释放信号量,唤醒等待的线程 */
rt_sem_release(&rx_sem);
}
/* 退出中断 */
rt_interrupt_leave();
}
/* 数据处理线程 */
void data_process_thread_entry(void *parameter) {
while (1) {
/* 永久等待信号量,直到被中断释放 */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
/* 收到信号,开始处理数据 */
/* ... */
}
}
/* 初始化 */
void sync_sample_with_irq(void) {
/* 初始化信号量,初始计数值为0 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_PRIO);
}3. 线程间通信:消息队列
当需要在线程间传递大量或变长的数据时,消息队列是首选。
这个示例演示了如何创建消息队列,并在线程间发送和接收结构化的消息数据。
/* 定义消息队列控制块 */
static struct rt_messagequeue mq;
/* 定义消息队列中用到的消息结构体 */
typedef struct {
rt_uint8_t type;
rt_uint16_t len;
rt_uint8_t *data;
} message_t;
/* 用于存储消息的内存池 */
static char msg_pool[2048];
/* 发送消息的线程 */
static void sender_thread_entry(void *parameter) {
message_t msg;
while (1) {
msg.type = 0x01;
msg.len = 16;
/* 发送消息 */
rt_mq_send(&mq, &msg, sizeof(msg));
rt_thread_mdelay(1000);
}
}
/* 接收消息的线程 */
static void receiver_thread_entry(void *parameter) {
message_t msg;
while (1) {
/* 接收消息,并永久等待 */
if (rt_mq_recv(&mq, &msg, sizeof(msg), RT_WAITING_FOREVER) == RT_EOK) {
/* 成功接收到消息,进行处理 */
}
}
}
/* 初始化示例 */
void mq_sample(void) {
/* 初始化消息队列 */
rt_mq_init(&mq,
"test_mq", /* 名称 */
&msg_pool[0], /* 消息内存池 */
sizeof(message_t), /* 单个消息大小 */
sizeof(msg_pool), /* 消息内存池大小 */
RT_IPC_FLAG_PRIO); /* 挂起线程的等待方式 */
/* 创建发送和接收线程... */
}4. 系统管理:统一的设备访问模型
RT-Thread提供了一套统一的I/O设备模型,让你可以用同样的API操作不同类型的硬件,让硬件访问标准化。
下面的示例展示了如何查找、打开一个串口设备,并通过轮询方式发送和接收数据。
#include <rtthread.h>
#include <rtdevice.h>
#define SAMPLE_UART_NAME "uart1" /* 串口设备名称 */
/* 查找系统中的串口设备 */
static rt_device_t serial = RT_NULL;
void uart_sample(void) {
serial = rt_device_find(SAMPLE_UART_NAME);
if (serial == RT_NULL) {
rt_kprintf("find %s failed!\n", SAMPLE_UART_NAME);
return;
}
/* 以读写及中断接收方式打开串口设备 */
rt_device_open(serial, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
/* 发送数据 */
char send_str[] = "Hello, RT-Thread!\n";
rt_device_write(serial, 0, send_str, sizeof(send_str));
/* 接收数据,轮询方式 */
char ch;
while (1) {
if (rt_device_read(serial, 0, &ch, 1) == 1) {
rt_kprintf("RX: %c\n", ch);
}
rt_thread_mdelay(10);
}
}5. 内存管理:动态分配与使用的注意事项
动态内存管理是把双刃剑,用得不好容易造成内存泄漏。使用时要格外小心。
下面的示例展示了一个反模式,即申请了内存却忘记释放,造成了内存泄漏。
/* 错误示例:只分配,不释放,导致内存泄漏 */
void bad_memory_usage(void) {
while (1) {
/* 动态分配128字节的内存 */
void *ptr = rt_malloc(128);
if (ptr != RT_NULL) {
rt_kprintf("Memory allocated at %p\n", ptr);
/* 未调用 rt_free(ptr); 内存泄漏! */
}
rt_thread_mdelay(100);
}
}
/* 正确示例:分配内存,使用后立即释放 */
void good_memory_usage(void) {
void *ptr = rt_malloc(128);
if (ptr != RT_NULL) {
/* 使用内存 */
memset(ptr, 0, 128);
/* 释放内存 */
rt_free(ptr);
}
}注意:
- 从信号量/互斥量等回调函数或中断里,不要使用
rt_malloc这类可能阻塞的函数。- 为了避免内存碎片化和提高分配效率,对于频繁分配和释放固定大小内存块的场景,应该优先考虑使用内存池(
rt_mp_create等)。- 上面的例子主要是串口操作,RT-Thread的大部分常用功能,如创建内存池、使用互斥量、在中断中正确同步等,更详细的代码示例可以在官方API参考手册的内核例程模块中找到。
提示:以上介绍的函数是RT-Thread内核中最常用的一部分。要深入学习,可以直接阅读RT-Thread源码中的详细注释或在官网查阅完整的
API参考手册。
评论
暂无评论