资源汇总
RT-Thread官方文档: 官网地址
STM32CubeMX支持包地址: 官网地址
STM32CubeMX工程设置
进入工程后设置参数
- Select Component中勾选Kernel和Shell
- 在Software Packs进行设置 打开所需要的功能 推荐打开信号量和消息队列
- 在NVIC的Code generation关闭Hard fault interrupt
- (或在工程中的it.c文件删除HardFault_Handle)
- 每次生成工程后取消rtconfig.h的145行的注释#include “finsh_config.h”
- main.c用户代码中#include
- finsh_config.h中将FINSH_THREAD_PRIORITY改为10(避免不调度finsh)
- 若是L4、H7系列的芯片,或者调试串口不是USART2,还需修改下列部分
- board.c中的UartHandle参数初始化时改成自己需要的串口
- rt_hw_console_getchar函数中从寄存器中取值的寄存器DR改为RDR
- 在board.c开头加入#include “main.h”
常用设置和代码
FinSh控制台代码
version 显示操作系统的版本
ps 显示系统中的线程
list_thread 显示系统中的线程
list_sem 显示系统中的信号量
list_event 显示系统中的事件
list_mutex 显示系统中的互斥量
list_mailbox 显示系统中的邮箱
list_msgqueue 显示系统中的消息队列
list_timer 显示系统中的定时器
list_device 显示系统中的设备
时延函数,us级和ms级
void rt_hw_us_delay(int us)
{
rt_uint32_t delta;
/* 获得延时经过的tick数 */
us = us * (SysTick->LOAD/(1000000/RT_TICK_PER_SECOND));
/* 获得当前时间 */
delta = SysTick->VAL;
/* 循环获得当前时间,直到达到指定的时间后退出循环 */
while (delta - SysTick->VAL< us);
}
void rt_hw_ms_delay(int ms)
{
int i=0,j=0;
for(j=0;j<ms;j++)
for (i=0;i<2;i++)
rt_hw_us_delay(500);
}
线程
定义全局变量
static struct rt_thread sensor_thread; // 创建线程的结构体
static char sensor_thread_stack[512]; // 创建线程的栈
static void sensor_thread_entry(void* parameter) // 线程的入口函数
{
while(1)
{...}
}
main函数中启动线程
rt_err_t rst; // 起线程的句柄
rst = rt_thread_init(&sensor_thread, "sensor", sensor_thread_entry, RT_NULL, &led_thread_stack[0], sizeof(led_thread_stack), RT_THREAD_PRIORITY, 20); //线程结构体初始化
if(rst == RT_EOK)
rt_thread_startup(&sensor_thread); // 启动线程
&sensor_thread -线程的结构体
“sensor” -线程的名称
sensor_thread_entry -线程的入口函数
RT_NULL -入口函数参数
&led_thread_stack[0] -线程栈起始地址
sizeof(led_thread_stack) -线程栈大小
RT_THREAD_PRIORITY -线程优先级 越小优先级越高
main进程的优先级为最大值32/3 = 10 finsh的优先级在finsh_config.h里设置为了21
20 -线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍
当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度
这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行
rt_thread_detach(&sensor_thread) // 结束进程
信号量
简介:
二值信号量:
在嵌入式操作系统中二值信号量是线程间、线程与中断间同步的重要手段。为什么叫二值信号量呢?因为信号量资源被获取了,信号量值就是 0,信号量资源被释放,信号量值就是 1,把这种只有 0和 1 两种情况的信号量称之为二值信号量。
比如某个线程需要等待一个标记,那么线程可以在轮询中查询这个标记有没有被置位,这样子做,就会很消耗 CPU 资源,其实根本不需要在轮询中查询这个标记,只需要使用二值信号量即可,当二值信号量没有的时候,线程进入阻塞态等待二值信号量到来即可,当得到了这个信号量(标记)之后,在进行线程的处理即可,这样子么就不会消耗太多资源了,而且实时响应也是最快的。
再比如某个线程使用信号量在等中断的标记的发生,在这之前线程已经进入了阻塞态,在等待着中断的发生,当在中断发生之后,释放一个信号量,也就是我们常说的标记,当它退出中断之后,操作系统进行线程的调度,如果这个线程能够运行,系统就会把等待这个线程运行起来,这样子就大大提高了我们的效率。
举例说明来理解二值信号量:
1、线程与线程之间同步
有两个线程任务T1、T2。T1为温度传感器1S采集一次外界温度。T2为LCD显示采集到的温度,理论上也是1S变化一次温度值。如果LCD的屏幕刷新率为10ms,那么此时的温湿度的数据还没更新,液晶屏根本无需刷新,只需要在 1s 后温湿度数据更新的时候刷新即可。那么在裸机中,经常会用到轮询方式,即一直在刷新LCD屏幕,即使没有到下一个温度值到来,再不需要更新数值时,也要刷新。这样CPU资源会被LCD刷新占有,影响资源的合理使用。
如果液晶屏刷新的周期是 10s 更新一次,那么温湿度的数据都变化了 10 次,液晶屏才来更新数据,那拿这个产品有啥用,根本就是不准确的,所以,还是需要同步协调工作,在温湿度采集完毕之后,进行液晶屏数据的刷新,这样子,才是最准确的,并且不会浪费 CPU的资源。
2、线程与中断之间同步
二值信号量在线程与中断同步的应用场景:我们在串口接收中,我们不知道啥时候有数据发送过来,有一个线程是做接收这些数据处理,总不能在线程中每时每刻都在线程查询有没有数据到来,那样会浪费 CPU 资源,所以在这种情况下使用二值信号量是很好的办法,当没有数据到来的时候,线程就进入阻塞态,不参与线程的调度,等到数据到来了,释放一个二值信号量,线程就立即从阻塞态中解除,进入就绪态,然后运行的时候处理数据,这样子系统的资源就会很好的被利用起来。
struct rt_semaphore sem_pressdown; // 信号量的结构体句柄
rt_sem_init(&sem_pressdown, "0", RT_IPC_FLAG_PRIO) // 初始化信号量 0为信号量初值
rt_sem_take(&sem_pressdown) // 获取信号量的值,信号量的值减1,如果信号量当前的值为0,则线程进入阻塞态,等待有信号量释放
rt_sem_release(&sem_pressdown) // s释放信号量的值,信号量的值加1
消息队列
简介:
消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。
如下图所示,线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程也可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则 (FIFO)。
/* 消息队列控制块 */
static struct rt_messagequeue mq;
/* 消息队列中用到的放置消息的内存池 */
static rt_uint8_t msg_pool[2048];
/* 内存池指向 msg_pool
每个消息的大小是 512 字节
内存池的大小是 msg_pool 的大小
如果有多个线程等待,优先级大小的方法分配消息 */
result = rt_mq_init(&mq, "mqt", &msg_pool[0], 512, sizeof(msg_pool), RT_IPC_FLAG_PRIO);
/*mq 消息队列对象的句柄
buffer 消息内容
size 消息大小*/
rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size);
/*mq 消息队列对象的句柄
buffer 消息内容
size 消息大小
timeout 指定的超时时间*/
rt_err_t rt_mq_recv (rt_mq_t mq, void* buffer, rt_size_t size, rt_int32_t timeout);