当前位置: 首页 > backend >正文

RT-Thread学习笔记(三)

RT-Thread学习笔记

  • 时钟管理
    • 时钟节拍
    • 获取系统节拍
    • 定时器
    • 系统定时器初始化
    • 定时器工作机制
    • 动态创建定时器
    • 静态创建定时器
    • 定时器控制
    • 高精度延时函数

时钟管理

操作系统需要通过时间来规范其任务

时钟节拍

任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等。

RT-Thread 中,时钟节拍的长度可以根据 RT_TICK_PER_SECOND 的定义来调整。 RT_TICK_PER_SECOND 称为 系统节拍频率(System Tick Frequency),也叫:每秒节拍数 / 每秒系统滴答数

rtconfig.h 是 RT-Thread 系统配置头文件,用于控制内核功能、组件、驱动和中间件是否启用的开关配置。它相当于整个RT-Thread 工程的“功能开关总控台”。
✅ 主要作用
在这里插入图片描述

系统的节拍是1000Hz,即 1/100=1ms 一个节拍

在这里插入图片描述
时钟节拍数的原理是用了一个硬件的滴答定时器(SysTick),调用系统滴答定时器中断处理函数(每1ms触发一次systick定时器中断),用来进行计数。

SysTick(系统定时器)是 ARM Cortex-M 内核内置的一个 简单、专用的 24 位倒计时定时器,用于提供系统时基(如 1ms),通常用来驱动 RTOS 的节拍中断(tick)。

每1ms触发一次systick定时器中断后就会执行SysTick_Handler()函数,即回调函数,进行相应操作

在这里插入图片描述

SysTick_Handler()回调函数里调用了rt_tick_increase()函数

在这里插入图片描述

rt_tick_increase()函数每一次都对 rt_tick 加1

rt_tick 是一个静态全局变量,在初始化的时候是为0

在这里插入图片描述

在这里插入图片描述

获取系统节拍

使用 rt_tick_t rt_tick_get(void) 函数,函数的返回值是计数值的值

在这里插入图片描述

系统默认1ms,tick加1,延时500ms,也确实每次打印都是加500

在这里插入图片描述

将系统节拍频率改为1000,即10000Hz,tick每 100us 加1

在这里插入图片描述

编译会有警告,但是只是看一下现象

在这里插入图片描述

确实是大约每次加1000

在这里插入图片描述

改为100Hz

在这里插入图片描述

在这里插入图片描述
但是一般情况下是不去修改这个系统节拍频率的,只是为了看现象,因为系统各个模块都需要以这个为基准,所以很重要,不轻易修改

定时器

定时器,是指从指定的时刻开始,经过一定的指定时间后触发一个事件,定时器有硬件定时器软件定时器之分。

硬件定时器: 芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。硬件定时器就是在使用裸机开发时使用到的基本定时器、通用定时器和高级定时器。

软件定时器: 由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。RT-Thread操作系统提供软件实现的定时器,以时钟节拍(OS Tick)的时间长度为单位,即定时数值必须是OS Tick的整数倍。

RT-Thread的定时器提供两类定时器机制:单次触发周期触发

✅ 区别对比:

在这里插入图片描述

根据定时器超时函数执行时所处的上下文环境,RT-Thread的定时器可以分为HARD_TIMER模式和SOFT_TIMER模式。

在这里插入图片描述
🌩️ 硬定时器(Hard Timer)
定时器超时后,直接在中断中调用回调函数
响应速度快、精度高
不能使用可能引起调度的操作(例如 rt_sem_take()、rt_thread_delay())
✅ 适用场景:时间精度要求高、执行逻辑简单的任务
⚠️ 注意事项:不能调用阻塞函数或复杂逻辑
例如在中断上下文中执行的超时函数它不应该试图去申请动态内存、释放动态内存等

🧵 软定时器(Soft Timer)
定时器回调是由一个后台线程(timer thread)在线程上下文中执行
可以使用 RTOS 的全部功能,如信号量、消息队列、动态内存等
✅ 适用场景:回调函数里需要调度、操作 RTOS 对象的场合
⚠️ 注意事项:精度略低,取决于 RT_TIMER_THREAD_PRIO 线程调度
该模式被启用后,系统会在初始化时创建一个 timer 线程,然后SOFT_TIMER 模式的定时器超时函数在都会在timer线程的上下文环境中执行

系统定时器初始化

系统定时器初始化函数在启动函数中

启动函数有两个 rt_thread_startup()rtthread_startup()

在这里插入图片描述
✅ rt_thread_startup()
作用:启动一个单独线程

✅ rtthread_startup()
作用:启动整个 RT-Thread 内核系统
一般在 main() 或 boot 初始化里调用一次
含义类似 RT-Thread 的“入口函数”

在这里插入图片描述

在这里插入图片描述
rt_list_init() 是 RT-Thread 中用于初始化一个内核链表节点的函数。这个链表叫做“内核双向链表(rt_list_t)”,是 RT-Thread 里非常核心的数据结构,用于管理各种系统资源,比如线程、定时器、等待队列等。

rt_list_t 是什么?

struct rt_list_node
{struct rt_list_node *next; // 指向下一个节点struct rt_list_node *prev; // 指向上一个节点
};

这是一个 双向循环链表节点结构,RT-Thread 用它来组成各种对象的“队列”或“链表”。

rt_list_init() 干什么用?

void rt_list_init(struct rt_list_node *l)
{l->next = l;l->prev = l;
}

💡 初始化后,该节点既是头,也是尾。可以开始往里插入元素。

📦 链表(List)在 RT-Thread 中有什么用?
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

由上面可知,在系统初始化时就把硬件定时器和软件定时器都初始化好了,也给用户应用层提供了相应的操作接口去操作定时器,上面的作为了解

定时器工作机制

在 RT-Thread 定时器模块中维护着两个重要的全局变量:

  1. 当前系统经过的 tick 时间 rt_tick(当硬件定时器中断来临时,它将加 1)
  2. 定时器链表 rt_timer_list。系统新创建并激活的定时器都会按照以超时时间排序的方式插入到rt_timer_list 链表中。

如下图所示,系统当前tick值为20,在当前系统中已经创建并启动了三个定时器,分别是定时时间为50个tick的Timer1、100个tick的Timer2和500个tick的Timer3,这三个定时器分别加上系统当前时间 rt_tick=20,从小到大排序链接在 rt_timer_list 链表中,形成如图所示的定时器链表结构。

在这里插入图片描述
而 rt_tick 随着硬件定时器的触发一直在增长(每一次硬件定时器中断来临,rt_tick 变量会加 1) ,50个tick以后,rt_tick从20增长到70,与Timer1的timeout值相等,这时会触发与Timer1定时器相关联的超时函数,同时将Timer1从rt_timer_list链表上删除。同理,100个tick和500个tick过去后,与Timer2 和 Timer3 定时器相关联的超时函数会被触发,接着将 Time2 和 Timer3 定时器从 rt_timer_list链表中删除。
如果系统当前定时器状态在 10 个 tick 以后(rt_tick=30)有一个任务新创建了一个 tick 值为 300 的Timer4定时器,由于Timer4定时器的timeout=rt_tick+300=330,因此它将被插入到Timer2和Timer3定时器中间,形成如下图所示链表结构:
在这里插入图片描述

因为上面说到定时器分为两种,一种是单次触发,另一种是周期触发的

如果是周期触发的定时器,假设此时tick为20,timeout设置为10,那么当tick为30时会触发该定时器,并把它移除出链表中,然后timeout+10,再次添加进链表中,以此周期循环

动态创建定时器

动态创建定时器函数

rt_timer_t rt_timer_create(const char *name,void (*timeout)(void *parameter),void       *parameter,rt_tick_t   time,rt_uint8_t  flag)

第一个参数是定时器的名字
第二个参数是一个函数指针,即超时函数,定时时间到了要去处理的超时事件,通过回调这个超时函数
第三个参数是传递给超时函数的参数
第四个参数是超时的时间
第五个参数是flag

#define RT_TIMER_FLAG_DEACTIVATED       0x0             /**< timer is deactive */
#define RT_TIMER_FLAG_ACTIVATED         0x1             /**< timer is active */
#define RT_TIMER_FLAG_ONE_SHOT          0x0             /**< one shot timer */
#define RT_TIMER_FLAG_PERIODIC          0x2             /**< periodic timer */#define RT_TIMER_FLAG_HARD_TIMER        0x0             /**< hard timer,the timer's callback function will be called in tick isr. */

返回值是 rt_timer_t

/*** timer structure*/
struct rt_timer
{struct rt_object parent;                            /**< inherit from rt_object */rt_list_t        row[RT_TIMER_SKIP_LIST_LEVEL];void (*timeout_func)(void *parameter);              /**< timeout function */void            *parameter;                         /**< timeout function's parameter */rt_tick_t        init_tick;                         /**< timer timeout tick */rt_tick_t        timeout_tick;                      /**< timeout tick */
};
typedef struct rt_timer *rt_timer_t;

定时器删除函数

rt_err_t rt_timer_delete(rt_timer_t timer)

传入参数就是动态创建的定时器,返回值是错误码

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>rt_timer_t tm1;//定义一个参数用于接收创建定时器后的返回值void time1out_callback(void *parameter)//创建一个回调函数
{}int main(void)
{tm1 = rt_timer_create("tm1_demo", time1out_callback, NULL, 3000, \RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER );//创建一个软件定时器,周期触发模式if(tm1 == RT_NULL)//如果创建失败{LOG_E("rt_timer_create failed...\n");return -RT_ENOMEM;}LOG_D("rt_timer_create successed...\n");//如果创建成功return RT_EOK;
}

在终端用list_timer查看定时器线程,可以看到成功创建了一个软件定时器,但是还没有启动,超时时间为0

在这里插入图片描述

使用函数rt_err_t rt_timer_start(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_timer_t类型的结构体变量,然后传入,第二个参数是定时器的名字,第三个参数是超时的回调函数,第四个参数是传给回调函数的参数,第五个参数是超时时间,第六个参数是flag,与动态创建不同的是是否需要我们自己手动申请内存

对应的脱离函数

rt_err_t rt_timer_detach(rt_timer_t timer)

即将静态初始化的定时器从链表中移除

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>rt_timer_t tm1;//定义一个参数用于接收创建定时器后的返回值struct rt_timer tm2;//定义一个结构体,用于静态创建定时器void time1out_callback(void *parameter)//创建一个回调函数
{rt_kprintf("tm1 running...\n");//3s打印一次
}void time2out_callback(void *parameter)//静态定时器回调函数
{rt_kprintf("tm2 running...\n");//3s打印一次
}int main(void)
{//动态创建tm1 = rt_timer_create("tm1_demo", time1out_callback, NULL, 3000, \RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER );//创建一个软件定时器,周期触发模式if(tm1 == RT_NULL)//如果创建失败{LOG_E("rt_timer_create failed...\n");return -RT_ENOMEM;}LOG_D("rt_timer_create successed...\n");//如果创建成功rt_timer_start(tm1);//启动定时器//静态创建rt_timer_init(&tm2, "tm2_demo", time2out_callback, NULL, 3000, \RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);rt_timer_start(&tm2);//启动定时器,传入的是地址return RT_EOK;
}

在这里插入图片描述

定时器控制

让定时器停止,与启动函数一般是成对出现

rt_err_t rt_timer_stop(rt_timer_t timer)

控制定时器

rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg)//cmd
#define RT_TIMER_CTRL_SET_TIME          0x0             /**< set timer control command */
#define RT_TIMER_CTRL_GET_TIME          0x1             /**< get timer control command */
#define RT_TIMER_CTRL_SET_ONESHOT       0x2             /**< change timer to one shot */
#define RT_TIMER_CTRL_SET_PERIODIC      0x3             /**< change timer to periodic */
#define RT_TIMER_CTRL_GET_STATE         0x4             /**< get timer run state active or deactive*/
#define RT_TIMER_CTRL_GET_REMAIN_TIME   0x5             /**< get the remaining hang time */

在定时器2的回调函数中修改定时器2的超时时间,从3s变为1s,并打印系统tick时间,方便观察

在这里插入图片描述

可以看到第一次打印时间是3s后,后面的都为1s打印一次

在这里插入图片描述

让定时器2触发5次后,改为单次触发

在这里插入图片描述

在循环触发5次后,定时器2再触发

请添加图片描述

触发模式也确实变成了单次触发

在这里插入图片描述

高精度延时函数

注意:这个函数只支持低于1个OS Tick的延时, 否则SysTick会出现溢出而不能够获得指定的延时时间

即该函数只适用于延时低于一个系统tick,即低于1ms时使用,此函数是us级别的

void rt_hw_us_delay(rt_uint32_t us)
http://www.xdnf.cn/news/1284.html

相关文章:

  • 从零开始学java--二叉树和哈希表
  • 工作中sql总结
  • 无需复杂操作即可锁定键鼠的工具
  • [大模型]什么是function calling?
  • Linux操作系统--进程程序替换and做一个简单的shell
  • 3.6/Q1,Charls数据库经典文章解读
  • 【第九章 Python学习之函数Ⅱ】
  • 监控页面卡顿PerformanceObserver
  • idea快捷键 Project tool window
  • MySQL 性能监控工具的多维度对比分析
  • 出现了锁等待或死锁现象怎么办?乐观锁?分布式锁了解一下?
  • C语言教程(十三):C 语言中 enum(枚举)的详细介绍
  • C++ 学习指南
  • 一款强大的实时协作Markdown工具 | CodiMD 9.6K ⭐
  • 携程-酒旅-数据研发面经【附答案】
  • 【Spring】单例作用域下多次访问同一个接口
  • Discuz!+DeepSeek:传统论坛的智能化蜕变之路
  • 【C++】新手入门指南(下)
  • 《Linux TCP通信深度解析:实现高可靠工业数据传输客户端》
  • 使用Python设置excel单元格的字体(font值)
  • 笔记本电脑研发笔记:BIOS,Driver,Preloader详记
  • Win10一体机(MES电脑设置上电自动开机)
  • 《Android系统应用部署暗礁:OAT文件缺失引发的连锁崩溃与防御体系构建》
  • Mediatek Android13 设置Launcher
  • 基于ssm的疫情防控志愿者管理系统(源码+文档)
  • SpringBoot_为何需要SpringBoot?
  • AlmaLinux 9.5 调整home和根分区大小
  • 机器学习基础 - 分类模型之决策树
  • 深度学习--卷积神经网络数据增强
  • TP(张量并行)和EP(专家并行)的区别