FreeRTOS软件定时器

说明本文章基于百问网RTOS教程文档

1.硬件定时器

什么是硬件定时器,由硬件电路构成的定时器。在学习STM32时我们都会学到定时器,这个就是硬件定时器。硬件定时器不单单可以定时,它还可以进行PWM输出等等。硬件定时器每隔一段固定的时间会进入一次中断,我们叫Tick中断。

 我们生活中有重复闹钟,有一次性闹钟。闹钟有三种状态:运行,暂停和超时。

闹钟到点需要做什么事情?回调函数,为了方便区分做哪些事我们还会涉入函数参数。

2.FreeRTOS软件定时器

创建定时器一般都是创建一个结构体变量。这个结构体里会有定时器的各种信息,模式,状态,延时,函数,函数参数,还有一个链表。

比如我们有三个定时器定时器A,B,C,A定的时间为2个Tick,B定的时间为5个Tick,C定的时间为10个Tick。

 怎么去处理链表中的定时器呢?

·首先在Tick中断里去判断链表的第一项是否超时。如果超时有两种执行选择,下面会讲。第一项没超时那就不管。

怎么判断是否超时?在启动软件定时器时会计算出超时时间,根据当前Count和超时间比较就可知道定时器是否超时。

·如果第一项超时会有两种执行选择。

第一种:在中断中执行

实时操作系统不会再中断中处理复杂任务,因此不会选择中断里处理任务

第二种:在任务中执行(守护任务)

在中断中通知一个定时器任务(守护函数),在定时器任务中调用定时器里的函数执行。

怎么通知?

答:写一个队列,定时器任务会读这个队列处理队列的数据来判断是哪个定时器超时,再去调用定时器里的函数执行。

3.守护任务

3.1守护任务创建

当FreeRTOS的配置项configUSE_TIMERS被设置为1时,在启动调度器时,会自动创建RTOS Damemon Task。

3.2守护任务机制

守护任务中只执行两件事:读队列,处理队列数据

我们用户所有关于定时器的函数里都会去写某个队列,守护任务执行时会去处理队列的数据对定时器执行相关操作:Runing,Stop,Rest,执行超时定时器的任务等等。

因此守护任务的优先级一定要高,否则就无法处理定时任务。因此在没有处理完队列数据前它会一直运行。

 

 3.3回调函数

定时器的回调函数的原型如下:

void ATimerCallback( TimerHandle_t xTimer );

定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。

所以,定时器的回调函数不要影响其他人:

  • 回调函数要尽快实行,不能进入阻塞状态
  • 不要调用会导致阻塞的API函数,比如 vTaskDelay()
  • 可以调用 xQueueReceive() 之类的函数,但是超时时间要设为0:即刻返回,不可阻塞

4.定时器函数

根据定时器的状态转换图,就可以知道所涉及的函数:

4.1 创建

要使用定时器,需要先创建它,得到它的句柄。

有两种方法创建定时器:动态分配内存、静态分配内存。函数原型如下:

/* 使用动态分配内存的方法创建定时器
 * pcTimerName:定时器名字, 用处不大, 尽在调试时用到
 * xTimerPeriodInTicks: 周期, 以Tick为单位
 * uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
 * pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
 * pxCallbackFunction: 回调函数
 * 返回值: 成功则返回TimerHandle_t, 否则返回NULL
 */
TimerHandle_t xTimerCreate( const char * const pcTimerName, 
							const TickType_t xTimerPeriodInTicks,
							const UBaseType_t uxAutoReload,
							void * const pvTimerID,
							TimerCallbackFunction_t pxCallbackFunction );

/* 使用静态分配内存的方法创建定时器
 * pcTimerName:定时器名字, 用处不大, 尽在调试时用到
 * xTimerPeriodInTicks: 周期, 以Tick为单位
 * uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
 * pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
 * pxCallbackFunction: 回调函数
 * pxTimerBuffer: 传入一个StaticTimer_t结构体, 将在上面构造定时器
 * 返回值: 成功则返回TimerHandle_t, 否则返回NULL
 */
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
                                 TickType_t xTimerPeriodInTicks,
                                 UBaseType_t uxAutoReload,
                                 void * pvTimerID,
                                 TimerCallbackFunction_t pxCallbackFunction,
                                 StaticTimer_t *pxTimerBuffer );

回调函数的类型是:

void ATimerCallback( TimerHandle_t xTimer );

typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );

4.2 删除

动态分配的定时器,不再需要时可以删除掉以回收内存。删除函数原型如下:

/* 删除定时器
 * xTimer: 要删除哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: pdFAIL表示"删除命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
*/
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );

定时器的很多API函数,都是通过发送"命令"到命令队列,由守护任务来实现。

如果队列满了,"命令"就无法即刻写入队列。我们可以指定一个超时时间 xTicksToWait ,等待一会。

4.3 启动/停止

启动定时器就是设置它的状态为运行态(Running、Active)。

停止定时器就是设置它的状态为冬眠(Dormant),让它不能运行。

涉及的函数原型如下:

/* 启动定时器
 * xTimer: 哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: pdFAIL表示"启动命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 启动定时器(ISR版本)
 * xTimer: 哪个定时器
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
 *                            如果守护任务的优先级比当前任务的高,
 *                            则"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要进行任务调度
 * 返回值: pdFAIL表示"启动命令"无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerStartFromISR(   TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );

/* 停止定时器
 * xTimer: 哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: pdFAIL表示"停止命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 停止定时器(ISR版本)
 * xTimer: 哪个定时器
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
 *                            如果守护任务的优先级比当前任务的高,
 *                            则"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要进行任务调度
 * 返回值: pdFAIL表示"停止命令"无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerStopFromISR(    TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );

注意,这些函数的 xTicksToWait 表示的是,把命令写入命令队列的超时时间。命令队列可能已经满了,无法马上把命令写入队列里,可以等待一会。

xTicksToWait 不是定时器本身的超时时间,不是定时器本身的"周期"。

创建定时器时,设置了它的周期(period)。xTimerStart() 函数是用来启动定时器。假设调用 xTimerStart() 的时刻是tX,定时器的周期是n,那么在tX+n时刻定时器的回调函数被调用。

如果定时器已经被启动,但是它的函数尚未被执行,再次执行 xTimerStart() 函数相当于执行 xTimerReset() ,重新设定它的启动时间。

4.4 复位

从定时器的状态转换图可以知道,使用 xTimerReset() 函数可以让定时器的状态从冬眠态转换为运行态,相当于使用 xTimerStart() 函数。

如果定时器已经处于运行态,使用 xTimerReset() 函数就相当于重新确定超时时间。假设调用 xTimerReset() 的时刻是tX,定时器的周期是n,那么tX+n就是重新确定的超时时间。

复位函数的原型如下:

/* 复位定时器
 * xTimer: 哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: pdFAIL表示"复位命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 复位定时器(ISR版本)
 * xTimer: 哪个定时器
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
 *                            如果守护任务的优先级比当前任务的高,
 *                            则"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要进行任务调度
 * 返回值: pdFAIL表示"停止命令"无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerResetFromISR(   TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );

4.5 修改周期

从定时器的状态转换图可以知道,使用 xTimerChangePeriod() 函数,处理能修改它的周期外,还可以让定时器的状态从冬眠态转换为运行态。

修改定时器的周期时,会使用新的周期重新计算它的超时时间。假设调用 xTimerChangePeriod() 函数的时间tX,新的周期是n,则tX+n就是新的超时时间。

相关函数的原型如下:

/* 修改定时器的周期
 * xTimer: 哪个定时器
 * xNewPeriod: 新周期
 * xTicksToWait: 超时时间, 命令写入队列的超时时间 
 * 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerChangePeriod(   TimerHandle_t xTimer,
                                 TickType_t xNewPeriod,
                                 TickType_t xTicksToWait );

/* 修改定时器的周期
 * xTimer: 哪个定时器
 * xNewPeriod: 新周期
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
 *                            如果守护任务的优先级比当前任务的高,
 *                            则"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要进行任务调度
 * 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,
                                      TickType_t xNewPeriod,
                                      BaseType_t *pxHigherPriorityTaskWoken );

4.6 定时器ID

定时器的结构体如下,里面有一项 pvTimerID ,它就是定时器ID:

怎么使用定时器ID,完全由程序来决定:

  • 可以用来标记定时器,表示自己是什么定时器
  • 可以用来保存参数,给回调函数使用

它的初始值在创建定时器时由 xTimerCreate() 这类函数传入,后续可以使用这些函数来操作:

  • 更新ID:使用 vTimerSetTimerID() 函数
  • 查询ID:查询 pvTimerGetTimerID() 函数

这两个函数不涉及命令队列,它们是直接操作定时器结构体。

函数原型如下:

/* 获得定时器的ID
 * xTimer: 哪个定时器
 * 返回值: 定时器的ID
 */
void *pvTimerGetTimerID( TimerHandle_t xTimer );

/* 设置定时器的ID
 * xTimer: 哪个定时器
 * pvNewID: 新ID
 * 返回值: 无
 */
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

 5.代码例程

本节代码为:28_timer_game_sound,主要看nwatch\beep.c。

对于无源蜂鸣器,只要设置PWM输出方波,它就会发出声音。在game1游戏中,什么时候发出声音?球与挡球板、转块碰撞时发出声音。什么时候停止声音?发出声音后,过一阵子就应该停止声音。这使用软件定时器来实现。

在初始化蜂鸣器时,创建定时器,代码如下:

25 static TimerHandle_t g_TimerSound;

/* 省略 */

52 void buzzer_init(void)

53 {

54   /* 初始化蜂鸣器 */

55   PassiveBuzzer_Init();

56

57   /* 创建定时器 */

58   g_TimerSound = xTimerCreate( "GameSound",

59                           200,

60                           pdFALSE,

61                           NULL,

62                           GameSoundTimer_Func);

63 }

想发出声音时,调用buzzer_buzz函数,代码如下:

78 void buzzer_buzz(int freq, int time_ms)

79 {

80   PassiveBuzzer_Set_Freq_Duty(freq, 50);

81

82   /* 启动定时器 */

83   xTimerChangePeriod(g_TimerSound, time_ms, 0);

84 }

第80行:设置PWM频率。

第83行:启动定时器。

当定时器超时后,GameSoundTimer_Func函数被调用,它会停止蜂鸣器,代码如下:

37 static void GameSoundTimer_Func( TimerHandle_t xTimer )

38 {

39   PassiveBuzzer_Control(0);

40 }

 回调函数指针,在创建定时器时传入函数地址。

game1里如何使用音效?先初始化,代码如下:

297 void game1_task(void *params)

298 {		  

299	g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);

300	draw_init();

301	draw_end();

302	

303	buzzer_init();

第303行:初始化蜂鸣器。

game1里使用buzzer_buzz函数发出声音,比如碰到砖块时:

412        buzzer_buzz(2000, 100);

第412行会发出2000Hz的声音,维持100ms。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/584178.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

近几年视频取证、视频篡改检测技术发展现状及挑战

前言 本文主要搜集了视频取证各个子领域近几年的高影响因子/引用数的文章及其主要思想和做法,旨在分析目前视频篡改检测的发展现状与热点领域,文章中也融合了自己的一点看法和展望,欢迎感兴趣的同学和我多多沟通。 本文无论是文献搜集还是方…

基于Spring Boot的外卖点餐系统设计与实现

基于Spring Boot的外卖点餐系统设计与实现 开发语言:Java框架:springbootJDK版本:JDK1.8数据库工具:Navicat11开发软件:eclipse/myeclipse/idea 系统部分展示 网站首页界面图,通过进入网站可以查看首页、…

并查集应用-连通块中点的数量and食物链

文章目录 连通块中点的数量思路代码javaC 代码 食物链带扩展域的并查集代码带边权的并查集数组d的真正含义以及find()函数调用过程核心代码注意事项,即明白 d[i] 的含义 代码C Java 连通块中点的数量 给定一个包含 n 个点(编号为 1∼n )的无向…

Leetcode—860. 柠檬水找零【简单】

2024每日刷题&#xff08;122&#xff09; Leetcode—860. 柠檬水找零 实现代码 class Solution { public:bool lemonadeChange(vector<int>& bills) {int count5 0;int count10 0;for(int i 0; i < bills.size(); i) {if(bills[i] 5) {count5;} else if(bi…

【自然语言处理】Word2VecTranE的实现

作业一 Word2Vec&TranE的实现 1 任务目标 1.1 案例简介 Word2Vec是词嵌入的经典模型&#xff0c;它通过词之间的上下文信息来建模词的相似度。TransE是知识表示学习领域的经典模型&#xff0c;它借鉴了Word2Vec的思路&#xff0c;用“头实体关系尾实体”这一简单的训练目…

【matplot】【matlab】绘制简洁美观二维坐标系的一个例子

觉得下图不错美观大方&#xff0c;现仿制下图&#xff1a; import numpy as np import matplotlib.pyplot as pltdef sigmoid(x):return 1 / (1 np.exp(-x))def sigmoid_derivative(x):return sigmoid(x) * (1 - sigmoid(x))# 设置中文字体 plt.rcParams[font.family] [Tim…

如何使用Go语言的标准库和第三方库?

文章目录 一、如何使用Go语言的标准库示例&#xff1a;使用标准库中的fmt包打印输出 二、如何使用Go语言的第三方库示例&#xff1a;使用第三方库github.com/gin-gonic/gin创建Web服务器 总结 在Go语言中&#xff0c;标准库和第三方库的使用是日常编程中不可或缺的一部分。标准…

Facebook的声音:听见社交媒体的心跳

社交媒体如今已经成为人们日常生活中不可或缺的一部分&#xff0c;而Facebook作为其中的佼佼者&#xff0c;承载着数以亿计的用户的交流、分享和连接。在这个信息爆炸的时代&#xff0c;Facebook的声音就像是社交媒体的心跳&#xff0c;传递着无数个体的情感、思想和生活。本文…

NASA数据集——NASA 标准三级(L3)每月深蓝气溶胶产品提供了全球陆地和海洋上空气溶胶光学厚度(AOT)

VIIRS/NOAA20 Deep Blue Level 3 monthly aerosol data, 1x1 degree grid 简介 联合极地卫星系统&#xff08;JPSS&#xff09;系列 NOAA-20 仪器中的可见红外成像辐射计套件&#xff08;VIIRS&#xff09;NASA 标准三级&#xff08;L3&#xff09;每月深蓝气溶胶产品提供了全…

分布式与一致性协议之Raft算法(三)

Raft算法 如何复制日志 你可以把Raft算法的日志复制理解成一个优化后的二阶段提交(将二阶段优化成了一阶段)。优化后减少了一半的往返消息&#xff0c;也就是降低了一半的消息延迟&#xff0c;那日志复制的具体过程又是什么呢&#xff1f; 首先&#xff0c;领导者进入第一阶段…

LT2611UX四端口 LVDS转 HDMI2.0,带音频

描述LT2611UX 是一款面向机顶盒、DVD 应用的高性能 LVDS 至 HDMI2.0 转换器。LVDS输入可配置为单端口、双端口或四端口&#xff0c;具有1个高速时钟通道和3~4个高速数据通道&#xff0c;工作速率最高为1.2Gbps/通道&#xff0c;可支持高达19.2Gbps的总带宽。LT2611UX 支持灵活的…

C语言案例04 -流程控制-逻辑符的正确使用

****一.C语言逻辑运算符详解&#xff1a;逻辑与&&与逻辑或||的运用及其短路特性 你知道吗&#xff1f;逻辑运算符&&和||可是C语言世界的“流量担当”&#xff0c;它们不仅实力强大&#xff0c;还自带神秘光环——短路效应 &#x1f4dd;短路法则揭秘&#xf…

CVE-2022-2602:unix_gc 错误释放 io_uring 注册的文件从而导致的 file UAF

前言 复现该漏洞只是为了学习相关知识&#xff0c;在这里仅仅做简单记录下 exp&#xff0c;关于漏洞的详细内容请参考其他文章&#xff0c;最后在 v5.18.19 内核版本上复现成功&#xff0c;v6.0.2 复现失败 漏洞利用 diff --git a/include/linux/skbuff.h b/include/linux/s…

vscode 配置与插件记录

vscode插件 python PythonPython DebuggerruffisortPylanceJupyterJupyter KeymapJupyter Slide ShowJupyter Cell TagsautoDocstring - Python Docstring Generator ruff isort pylance autodocsting 在setting.json里这么配置&#xff0c;这样你保存时就会自动format…

WordPress缓存插件有哪些?好用的缓存插件分享

目前WordPress缓存插件有&#xff1a;WP Rocket、WP Super Cache、W3 Total Cache、Sucuri、NitroPack、SiteGround Optimizer、LiteSpeed Cache、WP-Optimize、Hummingbird、Cache Enabler、Comet Cache。 在当今的数字世界中&#xff0c;拥有一个高效的网站对于吸引和留住用…

Hadoop之路---伪分布式环境搭建

hadoop更适合在liunx环境下运行&#xff0c;会节省后期很多麻烦&#xff0c;而用虚拟器就太占主机内存了&#xff0c;因此后面我们将把hadoop安装到wsl后进行学习,后续学习的环境是Ubuntu-16.04 &#xff08;windows上如何安装wsl&#xff09; 千万强调&#xff0c;创建完hado…

数据结构(八)----树

目录 一.树的逻辑结构 1.双亲表示法(顺序存储) 2.孩子表示法&#xff08;顺序链式存储&#xff09; 3.孩子兄弟表示法&#xff08;链式存储&#xff09; 二.树的遍历 1.先根遍历 2.后根遍历 3.层次遍历 三.森林的遍历 1.森林的先序遍历 2.森林的中序遍历 四.哈夫曼…

基于FPGA的数字信号处理(6)--如何确定Verilog表达式的符号

前言 尽管signed语法的使用能带来很多便利&#xff0c;但同时也给表达式的符号确定带来了更多的不确定性。比如一个有符号数和一个无符号数的加法/乘法结果是有符号数还是无符号数&#xff1f;一个有符号数和一个无符号数的比较结果是有符号数还是无符号数&#xff1f;等等。接…

数学建模--图论最短路径基础

1.图的定义 学过数据结构或者离散数学的小伙伴们应该知道图的概念&#xff0c;我在这里简单的介绍一下&#xff1a; 图的概念和我们理解的是很不一样的&#xff0c;这里的图并不是我们的生活里面的图片&#xff0c;而是一种表示不同的数据之间的关系&#xff0c;例如这里的5个…

CMMI认证--基础知识总览

最近公司研发部搞CMMI L5认证&#xff0c;顺便记录下培训内容。 文章目录 一、什么是CMMI二、CMMI作用三、CMMI的成熟度等级四、过程域五、此次CMMI DEV 2.0或3.0特点六、CMMI 评估1、评估方法2、客观证据3、每个过程域如何给出评分等级 七、CMMI规程文件八、CMMI L5将度量统计…
最新文章