FreeRTOS(软件定时器)

资料来源于硬件家园:资料汇总 - FreeRTOS实时操作系统课程(多任务管理)

目录

一、软件定时器的概念

1、软件定时器的概念

2、软件定时器支持功能

3、单次模式与周期模式

4、定时器守护任务

二、软件定时器的应用

1、应用场景

2、软件定时器的精度

3、回调函数

4、使用时注意事项

 三、软件定时器的API函数‍

 1、使用软件定时器的典型流程与API

2、软件定时器的创建与删除

3、启动软件定时器

4、停止软件定时器

5、获取软件定时器ID

 四、软件定时器的应用编程

 1、Cube初始化

2、创建并启动软件定时器

2.1 定义ID号与句柄

2.2 创建定时器

2.3 使用按键关闭与打开定时器

3、软件定时器回调函数


一、软件定时器的概念

1、软件定时器的概念

FreeRTOS 软件定时器的时基是基于系统时钟节拍实现的,之所以叫软件定时器是因为它的实现不需要额外使用硬件定时器,而且可以创建很多个,综合这些因素,这个功能就被称之为软件定时器组。既然是定时器,那么它实现的功能与硬件定时器也是类似的。在硬件定时器中,我们是在定时器中断中实现需要的功能,而使用软件定时器时,我们是在创建软件定时器时指定软件定时器的回调函数,在回调函数中实现相应的功能。

2、软件定时器支持功能

① 裁剪,可通过宏关闭软件定时器功能 ② 软件定时器创建 ③ 软件定时器启动 ④ 软件定时器停止 ⑤ 软件定时器复位 ⑥ 软件定时器删除 软件定时器的使用相当于扩展了定时器的数量,允许创建更多的定时任务。

3、单次模式与周期模式

FreeRTOS 提供的软件定时器支持单次模式和周期性模式,单次模式就是用户创建了定时器并启动了定时器后,定时时间到将不再重新执行,这就是单次模式软件定时器的含义。周期模式就是此定时器会按照设置的时间周期重复去执行,这就是周期模式软件定时器的含义。另外就是单次模式或者周期模式的定时时间到后会调用定时器的回调函数,用户可以回调函数中加入需要执行的工程代码。

4、定时器守护任务

FreeRTOS通过一个prvTimerTask任务(也叫作守护任务(Daemon))管理软件定时器,它是在启动调度器时自动创建的,以满足用户定时需求。pryTimerTask任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。只有设置 FreeRTOSConfig.h中的宏定义configUSE_TIMERS 为1,将相关代码编译进来,才能正常使用软件定时器相关功能。 FreeRTOS 定时器组的大部分 API 函数都是通过消息队列给定时器任务发消息,在定时器任务里面执行实际的操作。

左侧图是用户应用程序,右侧是定时器任务。在用户应用程序里面调用了定时器组API函数xTimerReset,这个函数会通过消息队列给定时器任务发消息,在定时器任务里面执行实际操作。消息队列在此处的作用有一个专门的名字:Timer command queue,即专门发送定时器组命令的队列。

二、软件定时器的应用

1、应用场景

在很多应用中,我们需要用到一些定时器任务,硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,无法提供更多的定时器,那么可以采用软件定时器来完成,由软件定时器任务代替硬件定时器任务。但需要注意的是,软件定时器的精度是无法和硬件定时器相比的,因为在软件定时器的定时过程中极有可能被其他中断所打断,这是由于软件定时器的执行上下文环境是任务(prvTimerTask任务)。所以,软件定时器更适用于对时间精度要求不高的任务,或一些辅助型的任务。

2、软件定时器的精度

在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍是系统的心跳节拍,表示系统时钟的频率,类似人的心跳 1s 能跳动多少下。系统节拍配置为 configTICK_RATE HZ,该宏在FreeRTOSConfig.h 中有定义,默认是1000。那么系统的时钟节拍周期就为1ms(1s 跳动 1000 下,每一下时长就为 1ms)。软件定时器的所定时数值必须是这个节拍周期的整数倍,例如节拍周期是10ms,那么上层软件定时器定时数值只能是 10ms、20ms、100ms 等,而不能取值为 15ms。由于节拍定义了系统中定时器能够分辨的精确度,系统可以根据实际CPU的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为这代表在1s 中系统进入时钟中断的次数也就越多。

3、回调函数

在prvTimerTask任务中检测软件定时器,一旦定时时间到,将执行回调函数(被作为参数传递的函数,间接调用),以完成任务。 

4、使用时注意事项

① prvTimerTask任务的优先级设置高些,以便及时处理软件定时器的相关指令;

② 定时器回调函数是在定时器任务中执行的,实际应用中不可在定时器回调函数中调用任何将定时器任务挂起的函数,比如vTaskDelay(), vTaskDelayUntil()以及非零延迟的消息队列和信号量相关的函数。将定时器任务挂起,会导致定时器任务负责的相关功能都不能正确执行了。

 三、软件定时器的API函数‍

 1、使用软件定时器的典型流程与API

> 创建软件定时器  xTimerCreate()

> 启动软件定时器  xTimerStart()

> 停止软件定时器  xTimerStop()

> 删除软件定时器  xTimerDelete()

> 获取软件定时器ID  pvTimerGetTimerID()

2、软件定时器的创建与删除

>软件定时器控制块(句柄)

结构体成员变量说明:

① 软件定时器的名字,一般用于调试,因为控制定时器是通过句柄

② 软件定时器的列表项,用于插入定时器链表

③ 软件定时器的周期,单位为系统节拍(tick)

④ 软件定时器是否自动重置,pdFALSE->单次模式;pdTRUE->周期模式

⑤ 软件定时器的数字ID,典型用法是多个定时器共用一个回调函数时,通过ID辨别

⑥  软件定时器的回调函数,当定时时间到就会调用这个函数

>创建软件定时器

函数原型:TimerHandle_t xTimerCreate

         ( const char * const pcTimerName, /* 定时器名字 */

         const TickType_t xTimerPeriod, /* 定时器周期,单位系统时钟节拍 */

         const UBaseType_t uxAutoReload, /* 选择单次模式或者周期模式 */

         void * const pvTimerID,                /* 定时器 ID */

         TimerCallbackFunction_t pxCallbackFunction ); /* 定时器回调函数 */

函数描述:函数 xTimerCreate 用于创建软件定时器。

 第 1 个参数是定时器名字,用于调试目的,方便识别不同的定时器。

 第 2 个参数是定时器周期,单位系统时钟节拍。

 第 3 个参数是选择周期模式还是单次模式,pdFALSE->单次模式;pdTRUE->周期模式

 第 4 个参数是定时器 ID,当不同的定时器使用相同的回调函数时,在回调函数中通过不同的ID 号来区分不同的定时器。

 第 5 个参数是定时器回调函数。

 返回值,创建成功返回定时器的句柄,由于 FreeRTOSCongfig.h 文件中 heap 空间不足,或者定时器周期设置为 0,会返回 NULL。

使用这个函数要注意以下问题:

1. 在 FreeRTOSConfig.h 文件中使能宏定义:#define configUSE_TIMERS     1

应用举例:

>删除软件定时器

函数原型:TimerHandle_t xTimerDelede        

                    (TimerHandle_t xTimer,  /* 定时器句柄 */

                    TickType_t xBlockTime ); /* 定时器队列消息发送超时时间 */

函数描述:函数 xTimerCreate 用于创建软件定时器。

 第 1 个参数是定时器句柄

 第 2 个参数定时器队列消息发送超时间,定时器组的大部分 API函数不是直接运行的,而是通过消息队列给定时器任务发消息来实现的,此参数设置的等待时间就是当消息队列已经满的情况下,等待消息队列有空间时的最大等待时间。

 返回值,返回 pdFAIL 表示此函数向消息队列发送消息失败,返回 pdPASS 表示此函数向消息队列发送消息成功。定时器任务实际执行消息队列发来的命令依赖于定时器任务的优先级,如果定时器任务是高优先级会及时得到执行,如果是低优先级,就要等待其余高优先级任务释放 CPU 权才可以得到执行。

应用举例:

xTimerDelede(MyTimer01Handle, 100);

3、启动软件定时器

函数原型:BaseType_t xTimerStart

            ( TimerHandle_t xTimer, /* 定时器句柄 */

             TickType_t xBlockTime ); /* 定时器队列消息发送超时时间 */

函数描述:函数 xTimerStart 用于启动软件定时器。

 第 1 个参数是定时器句柄。

 第 2 个参数定时器队列消息发送超时间,定时器组的大部分 API函数不是直接运行的,而是通过消息队列给定时器任务发消息来实现的,此参数设置的等待时间就是当消息队列已经满的情况下,等待消息队列有空间时的最大等待时间。

 返回值,返回 pdFAIL 表示此函数向消息队列发送消息失败,返回 pdPASS 表示此函数向消息队列发送消息成功。定时器任务实际执行消息队列发来的命令依赖于定时器任务的优先级,如果定时器任务是高优先级会及时得到执行,如果是低优先级,就要等待其余高优先级任务释放 CPU 权才可以得到执行。

使用这个函数要注意以下问题:

1. 使用前一定要保证定时器组已经通过函数 xTimerCreate 创建了。

2、对于已经被激活的定时器,即调用过函数 xTimerStart 进行启动,再次调用此函数相当于调用了函数xTimerReset 对定时器时间进行了复位。

3. 如果在启动 FreeRTOS 调度器前调用了此函数,定时器是不会立即执行的,需要等到启动了 FreeRTOS调度器才会得到执行,即从此刻开始计时,达到 xTimerCreate 中设置的单次或者周期性延迟时间才会执行相应的回调函数。

应用举例:

4、停止软件定时器

函数原型:BaseType_t xTimerStop

            ( TimerHandle_t xTimer, /* 定时器句柄 */

             TickType_t xBlockTime ); /* 定时器队列消息发送超时时间 */

函数描述:函数 xTimerStart 用于停止软件定时器。

 第 1 个参数是定时器句柄。

 第 2 个参数定时器队列消息发送超时间

 返回值,返回 pdFAIL 表示此函数向消息队列发送消息失败,返回 pdPASS 表示此函数向消息队列发送消息成功。才可以得到执行。

应用举例:

5、获取软件定时器ID

函数原型:void *pvTimerGetTimerID( TimerHandle_t xTimer ); /* 定时器句柄 */

函数描述:函数 pvTimerGetTimerID 用于获取软件定时器ID。

 第 1 个参数是定时器句柄。

 返回值,返回定时器 ID。

使用这个函数要注意以下问题:

1. 使用前一定要保证定时器组已经通过函数 xTimerCreate 创建了。

2. 创建不同的定时器时,可以对定时器使用相同的回调函数,在回调函数中通过此函数获取是哪个定时器的时间到了,这个功能就是此函数的主要作用。

应用举例:

 

 四、软件定时器的应用编程

 1、Cube初始化

需要使用软件定时器,必须使能定时器组

2、创建并启动软件定时器

2.1 定义ID号与句柄

//定义ID号
uint8_t Timer01_ID =1;
uint8_t Timer02_ID =2;

//定义软件定时器句柄
static TimerHandle_t MyTimer01Handle = NULL;
static TimerHandle_t MyTimer02Handle = NULL;

2.2 创建定时器

  /* USER CODE BEGIN Init */
	MyTimer01Handle = xTimerCreate(
			"Timer01",   //名字
			100,         //定时周期 100ms
			pdTRUE,      //周期模式
			(void *)&Timer01_ID, //ID
			vMyTimerCallback //回调函数
	);
	if(MyTimer01Handle != NULL)
	{
		sprintf(buff,"%s \r\n","创建软件定时器1成功");
		HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);

		if(xTimerStart(MyTimer01Handle,0) == pdPASS)
		{
			sprintf(buff,"%s \r\n","启动软件定时器1成功");
			HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
		}
	}

	MyTimer02Handle = xTimerCreate(
			"Timer02",
			500,
			pdTRUE,
			(void *)&Timer02_ID,
			vMyTimerCallback
	);
	if(MyTimer02Handle != NULL)
	{
		sprintf(buff,"%s \r\n","创建软件定时器2成功");
		HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);

		if(xTimerStart(MyTimer02Handle,0) == pdPASS)
		{
			sprintf(buff,"%s \r\n","启动软件定时器2成功");
			HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
		}
	}
  /* USER CODE END Init */

2.3 使用按键关闭与打开定时器

	  //KEY0 xTimerStop
	  if(KeyCode==KEY0)
	  {
			if(xTimerStop(MyTimer01Handle,100) == pdPASS)
			{
				sprintf(buff,"%s \r\n","关闭定时器1成功");
				HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
			}
			else
			{
				sprintf(buff,"%s \r\n","关闭定时器1失败");
				HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
			}

			if(xTimerStop(MyTimer02Handle,100) == pdPASS)
			{
				sprintf(buff,"%s \r\n","关闭定时器2成功");
				HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
			}
			else
			{
				sprintf(buff,"%s \r\n","关闭定时器2失败");
				HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
			}
	  }
	  //KEY1
	  if(KeyCode==KEY1)
	  {
			if(xTimerStart(MyTimer01Handle,100) == pdPASS)
			{
				sprintf(buff,"%s \r\n","重新打开定时器1成功");
				HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
			}
			else
			{
				sprintf(buff,"%s \r\n","重新打开定时器1失败");
				HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
			}

			if(xTimerStart(MyTimer02Handle,100) == pdPASS)
			{
				sprintf(buff,"%s \r\n","重新打开定时器2成功");
				HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
			}
			else
			{
				sprintf(buff,"%s \r\n","重新打开定时器2失败");
				HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
			}
	  }

3、软件定时器回调函数

/* USER CODE BEGIN Application */
static void vMyTimerCallback(xTimerHandle pxTimer)
{
	uint8_t Timer_ID = 0;//软件定时器ID
	static uint16_t Timer01CallBackCnt = 0;//回调次数
	static uint16_t Timer02CallBackCnt = 0;
	//校验形参
	configASSERT(pxTimer);
	//获取ID
	Timer_ID = *((uint8_t*)pvTimerGetTimerID(pxTimer));

	if(Timer_ID == Timer01_ID)
	{
		//软件定时器1任务...
		sprintf(buff,"软件定时器1回调次数= %u\r\n",++Timer01CallBackCnt);
		HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
	}

	if(Timer_ID == Timer02_ID)
	{
		//软件定时器2任务...
		sprintf(buff,"软件定时器2回调次数= %u\r\n",++Timer02CallBackCnt);
		HAL_UART_Transmit(&huart2, (uint8_t*)buff,strlen(buff), HAL_MAX_DELAY);
	}
}

/* USER CODE END Application */

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

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

相关文章

OptaPlanner笔记6 N皇后

N 个皇后 问题描述 将n个皇后放在n大小的棋盘上,没有两个皇后可以互相攻击。 最常见的 n 个皇后谜题是八个皇后谜题,n 8: 约束: 使用 n 列和 n 行的棋盘。在棋盘上放置n个皇后。没有两个女王可以互相攻击。女王可以攻击同一水…

YOLO v8目标跟踪详细解读(二)

上一篇,结合代码,我们详细的介绍了YOLOV8目标跟踪的Pipeline。大家应该对跟踪的流程有了大致的了解,下面我们将对跟踪中出现的卡尔曼滤波进行解读。 1.卡尔曼滤波器介绍 卡尔曼滤波(kalman Filtering)是一种利用线性…

Java多款线程池,总有一款适合你。

线程池的选择 一:故事背景二:线程池原理2.1 ThreadPoolExecutor的构造方法的七个参数2.1.1 必须参数2.1.2 可选参数 2.2 ThreadPoolExecutor的策略2.3 线程池主要任务处理流程2.4 ThreadPoolExecutor 如何做到线程复用 三:四种常见线程池3.1 …

Jenkins+Docker+SpringCloud微服务持续集成项目优化和微服务集群

JenkinsDockerSpringCloud微服务持续集成项目优化和微服务集群 JenkinsDockerSpringCloud部署方案优化JenkinsDockerSpringCloud集群部署流程说明修改所有微服务配置 设计Jenkins集群项目的构建参数编写多选项遍历脚本多项目提交进行代码审查多个项目打包及构建上传镜像把Eurek…

Vue 引入 Element-UI 组件库

Element-UI 官网地址:https://element.eleme.cn/#/zh-CN 完整引入:会将全部组件打包到项目中,导致项目过大,首次加载时间过长。 下载 Element-UI 一、打开项目,安装 Element-UI 组件库。 使用命令: npm …

时序预测 | MATLAB实现基于LSTM长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于LSTM长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于LSTM长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍 Matlab实现LSTM长短期记忆神经…

[保研/考研机试] KY87 鸡兔同笼 北京大学复试上机题 C++实现

描述 一个笼子里面关了鸡和兔子(鸡有2只脚,兔子有4只脚,没有例外)。已经知道了笼子里面脚的总数a,问笼子里面至少有多少只动物,至多有多少只动物。 输入描述: 每组测试数据占1行,…

二次封装element-plus上传组件,提供校验、回显等功能

二次封装element-plus上传组件 0 相关介绍1 效果展示2 组件主体3 视频组件4 Demo 0 相关介绍 基于element-plus框架,视频播放器使用西瓜视频播放器组件 相关能力 提供图片、音频、视频的预览功能提供是否为空、文件类型、文件大小、文件数量、图片宽高校验提供图片…

盛元广通食品药品检验检测实验室LIMS系统

随着食品与制药行业法规标准的日益提高和国家两化融合的不断推进,为保障检验工作的客观、公正及科学性,确保制药企业对于生产、实验室、物流、管理的信息化和智能化需求越来越明确,为确保新品可及时得到科学准确的检测检验结果,盛…

H5 和小程序的区别

什么是小程序? 从“微信之父” 张小龙的定义里,我们可以了解到,小程序其实就是内嵌在微信,不需要安装和卸载的一种新应用形态。它具备的两个强属性:提高效率,用完即走!因此小程序的设计以轻便、…

微服务02-docker

1、Docker架构 1.1 镜像和容器 Docker中有几个重要的概念: 镜像(Image):Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像。Docker镜像是用于创建 Docker 容器的模板 。就像面向对象编程中的类。 容器(Container):镜像中的应用程序运…

02:STM32--EXTI外部中断

目录 一:中断 1:简历 2:AFIO 3:EXTI ​编辑 4:NVIC基本结构 5:使用步骤 二:中断的应用 A:对外式红外传感计数器 1:连接图​编辑 2:函数介绍 3:硬件介绍 4:计数代码 B;旋转编码计数器 1:连接图 2:硬件介绍 3:旋转编码器代码: 一:中断 1:简历 中断:在主程…

OpenCV基本操作——图像的基础操作

目录 图像的IO操作读取图像显示图像保存图像 绘制几何图形绘制直线绘制圆形绘制矩形向图像中添加文字效果展示 获取并修改图像中的像素点获取图像的属性图像通道的拆分与合并色彩空间的改变 图像的IO操作 读取图像 cv2.imread()import numpy as np import cv2 imgcv2.imread(…

【Java从0到1学习】08 String类

1. 概述 字符串是由多个字符组成的一串数据(字符序列),字符串可以看成是字符数组。 在实际开发中,字符串的操作是最常见的操作,没有之一。而Java没有内置的字符串类型,所以,就在Java类库中提供了一个类String 供我们…

Python爬虫:单线程、多线程、多进程

前言 在使用爬虫爬取数据的时候,当需要爬取的数据量比较大,且急需很快获取到数据的时候,可以考虑将单线程的爬虫写成多线程的爬虫。下面来学习一些它的基础知识和代码编写方法。 一、进程和线程 进程可以理解为是正在运行的程序的实例。进…

jvs-rules API数据源配置说明(含配置APIdemo视频)

在JVS中,多数据源支持多种形态的数据接入,其中API是企业生产过程中常见的数据形态。使用数据源的集成配置,以统一的方式管理和集成多个API的数据。这些平台通常提供各种数据转换和处理功能,使得从不同数据源获取和处理数据变得更加…

搭建一个能与大家分享的旅游相册网站——“cpolar内网穿透”

如何用piwigo与cpolar结合共同搭建一个能分享的旅行相册网站 文章目录 如何用piwigo与cpolar结合共同搭建一个能分享的旅行相册网站前言1. 使用piwigo这款开源的图片管理软件2. 需要将piwigi网页复制到phpstudy3. “开始安装”进入自动安装程序4. 创建新相册5. 创建一条空白数据…

Spring Gateway+Security+OAuth2+RBAC 实现SSO统一认证平台

背景:新项目准备用SSO来整合之前多个项目的登录和权限,同时引入网关来做后续的服务限流之类的操作,所以搭建了下面这个系统雏形。 关键词:Spring Gateway, Spring Security, JWT, OAuth2, Nacos, Redis, Danymic datasource, Jav…

ansible剧本之role角色模块

role角色 一:Roles 模块1.roles 的目录结构:2.roles 内各目录含义解释3.在一个 playbook 中使用 roles 的步骤:(1)创建以 roles 命名的目录(2)创建全局变量目录(可选)&am…

Java进阶-Oracle(二十一)(2)

🌻🌻 目录 一、Oracle 数据库的操作(DDL DML DQL DCL TPL)1.1 标识符、关键字、函数等1.1.1 数值类型:1.1.2 字符串类型:1.1.3 日期类型1.1.4 大的数据类型--适合保存更多的数据 1.2 运算符1.3 函数---预定义函数、自定义函数&…
最新文章