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

STM32F1+HAL库+FreeTOTS学习19——软件定时器

STM32F1+HAL库+FreeTOTS学习19——软件定时器

  • 1 软件定时器
    • 1.1 FreeRTOS软件定时器简介
    • 1.2 FreeRTOS软件定时器服务任务
    • 1.3 FreeRTOS软件定时器服命令队列。
    • 1.4 软件定时器的状态
    • 1.5 复位定时器
    • 1.6 软件定时器结构体
  • 2 软件定时器配置
  • 3 软件定时器API函数
    • 3.1 xTimerCreate()和xTimerCreateStatic()
    • 3.2 xTimerStart()和xTimerStartFromISR()
    • 3.3 xTimerStop()和xTimerStopFromISR()
    • 3.4 xTimerReset()和xTimerResetFromISR()
    • 3.5 xTimerChangePeriod()和xTimerChangePeriodFromISR()
    • 3.6 xTimerDelete()
    • 3.7 xTimerIsTimerActive()
    • 3.8 pvTimerGetTimerID()
    • 3.9 vTimerSetReloadMode()
    • 3.10 vTimerSetTimerID()
    • 3.11 xTimerPendFunctionCall()和xTimerPendFunctionCallFromISR()
    • 3.12 pcTimerGetName()
    • 3.13 xTimerGetPeriod()
    • 3.14 xTimerGetExpiryTime()
    • 3.15 xTimerGetReloadMode() 和uxTimerGetReloadMode()
  • 4 软件定时器操作实验
    • 4.1. 实验内容
    • 4.2 代码实现
    • 4.3 运行结果

上期内容中,我们学习了任务通知的相关内容,本期我们开始介绍FreeRTOS中的软件定时器。

1 软件定时器

我们在学习32的时候,或多或少都接触过定时器,在我们学习32的时候,我们从最简单基本定时器,到带有输入捕获,比较输出的通用定时器、再到互补输出、死区控制的高级定时;从看门狗到RTC;还有sysTick定时器,了解了各种各样的定时器,学习了各种各样的配置,但这些定时器都有一个共同的特点,都是单片机芯片的片上外设,是在硬件层面的硬件定时器。

而我们今天要学习的是FreeRTOS中的软件定时器,是基于FreeRTOS的系统节拍实现的,属于软件层面,它不像硬件定时器那样,可以实现精确的定时、也没有丰富的功能,只能适用于一些对定时器任务精度不高的场景。

同时软件定时还具有使用简单(不需要考虑中断与任务之间的交互处理),成本低等优点,让我们在茫茫人海中一眼看中了它。下面我们来简单的介绍一些软件定时器:

1.1 FreeRTOS软件定时器简介

在FreeRTOS中提供了用户创建软件定时器的机会,在创建的时候,可以设置对应的超时时间,在软件定时器创建成功并启动之后,开始计时,达到超时时间之后就会调用相应的回调函数,进行任务的处理。

  • 用户还可以根据需要,选择定时器是周期的还是单次的(类似于硬件定时器里面的是否重装载),以满足不同的使用场景
  • 软件定时器的数量是可以任意的(前提是你的堆栈空间足够大)
  • 在FreeRTOS中,软件定时器不是必选项,是支持裁剪的,需要使用的话,需要在FreeRTOSConfig.h 中将 configUSE_TIMERS配置为1,这个在后面会介绍。

1.2 FreeRTOS软件定时器服务任务

我们在前面学习:STM32F1+HAL库+FreeTOTS学习8——第一个任务,启动!的时候有介绍到,在调用vTaskStartScheduler()函数开启任务调度的时候,分别创建了空闲任务和软件定时器服务任务,也就是我们这里提到的软件定时器服务任务。

#endif /* ( ( INCLUDE_xTaskResumeFromISR == 1 ) && ( INCLUDE_vTaskSuspend == 1 ) ) */
/*-----------------------------------------------------------*/void vTaskStartScheduler( void )
{/*、、上面代码省略、、*//* 判断是否使用软件定时器 */#if ( configUSE_TIMERS == 1 ){if( xReturn == pdPASS ){/* 调用这个函数,里面会创建软件定时器服务任务 */xReturn = xTimerCreateTimerTask();}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_TIMERS *//*、、下面代码省略、、*/}

实际上,不管我们创建了多少了软件定时器,最后都是跑到这个软件定时器服务任务中进行逻辑判断,调用对应的超时回调函数;通知这个软件定时器服务任务还需要负责处理软件定时器命令队列的消息。

1.3 FreeRTOS软件定时器服命令队列。

在FreeRTOS中提供了很多对应的API函数,这些API函数大部分都是往定时器命令队列中写入消息(发送命令),这个队列是FreeRTOS源码中提供个软件定时器使用的。不支持用户直接访问,只能通过这些API函数来写入消息(发送命令)。操作过程如下:
在这里插入图片描述

1.4 软件定时器的状态

  1. 休眠态:刚刚创建的软件定时器处于休眠态,可以通过其句柄被引用,但是其超时回调函数不会被执行,简单来说,就是存在,但是没有开始计时。
  2. 运行态:处于运行态的定时器,可以通过其句柄进行引用,同时其超时回调函数也会被执行,简单来说,就是存在,且开始计时了。

下面是软件定时器状态的转换方式:

  • 单次定时器:只会执行一次超时回调函数,(不会重装载)
    在这里插入图片描述
  • 周期定时器:每到一次超时时间,就会执行一次超时回调函数,(会重装载)
    在这里插入图片描述

可以看到在定时器状态转换上,单次定时器执行回调函数后,会从运行态转换为休眠态;而周期定时器则会保持在运行态,除此之外,欸有任何区别。

1.5 复位定时器

如下图所示,在超时时间到来之前,重复的执行复位定时器操作,就可以导致超时回调函数一直不发生(不管单次定时器还是周期定时器都一样),这样的操作会导致定时器的周期不可控,应该避免这种操作,当然如果你是故意这样做的(喂狗),当我没说。

在这里插入图片描述

以上就是关于软件定时器的所有简介部分,下面我们来看一些,使用软件定时器,需要做的一些配置。

1.6 软件定时器结构体

下面是软件定时器的结构体成员变量:

typedef    struct{const char * 					pcTimerName				/* 软件定时器名字 */ListItem_t 					xTimerListItem				/* 软件定时器列表项 */TickType_t 					xTimerPeriodInTicks;        /* 软件定时器的周期 */     void * 						pvTimerID					/* 软件定时器的ID */TimerCallbackFunction_t	 		pxCallbackFunction; 	/* 软件定时器的回调函数 */#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t 					uxTimerNumber			/*  软件定时器的编号,调试用  */#endifuint8_t 						ucStatus;               /*  软件定时器的状态  */} xTIMER;

2 软件定时器配置

前面我们提到,软件定时器在FreeRTOS中是可以裁剪的,他并不是FreeRTOS的必需品,所以我们在使用软件定时器的时候,就需要做以下配置。
1. configUSE_TIMERS

次宏用来开启软件定时器,需要将此宏定义为1,才会在开启任务调度的过程中创建软件定时器服务任务。

2. configTIMER_TASK_PRIORITY

此宏用于配置软件定时器服务任务的优先级,当使能了软件定时器功能之后,会根据此宏来配置软件定时器服务任务的优先级,取值区间为:0 ~ (configMAX_PRIORITY-1)

3. configTIMER_QUEUE_LENGTH

此宏用于配置软件定时器命令队列的长度,此宏的长度必须大于0,不然无法正常使用软件定时器功能。

4. configTIMER_TASK_STACK_DEPTH

此宏用于配置软件定时器服务任务的栈空间大小,由于软件定时器的所有超时回调函数都是由软件定时器服务任务调用,因此在创建的定时器的时候,尤其要考虑栈溢出的风险,需要适当增大软件定时器服务任务的栈空间大小。

3 软件定时器API函数

FreeRTOS中提供了一些软件定时器的操作函数,在源码中的timers.c / timers.h 文件中有定义。如下表

表1:包含软件定时器的一些基本操作,创建、开启、停止、复位、删除

函数描述
xTimerCreate()动态方式创建软件定时器
xTimerCreateStatic()静态方式创建软件定时器
xTimerStart()开启软件定时器定时
xTimerStartFromISR()在中断中开启软件定时器定时
xTimerStop()停止软件定时器定时
xTimerStopFromISR()在中断中停止软件定时器定时
xTimerReset()复位软件定时器定时
xTimerResetFromISR()在中断中复位软件定时器定时
xTimerChangePeriod()更改软件定时器的定时超时时间
xTimerChangePeriodFromISR()在中断中更改软件定时器的定时超时时间
xTimerDelete()删除软件定时器

表2:包含软件定时器的一些查询操作,查看一些状态、以及两个特殊使用场景的请求调用函数。

函数描述
xTimerIsTimerActive()查询软件定时器是否处于活动或休眠状态
pvTimerGetTimerID()返回分配给软件定时器的 ID
vTimerSetReloadMode()将软件定时器的“模式”更新为自动重新加载定时器或一次性 定时器
vTimerSetTimerID()更改定时器ID
xTimerPendFunctionCall()用于请求一个函数,这个函数叫做xTimerPendFunctionCall(),会在软件定时器服务任务中执行
xTimerPendFunctionCallFromISR()在中断中请求一个函数,这个函数叫做xTimerPendFunctionCall(),会在软件定时器服务任务中执行
pcTimerGetName()获取软件定时器的名字
xTimerGetPeriod()返回软件计时器的周期。周期以滴答为单位
xTimerGetExpiryTime()获取软件定时器下一次的超时时间
xTimerGetReloadMode ()查询软件定时器的模式
uxTimerGetReloadMode()查询软件定时器的模式

下面我们来简单的了解一些这些个函数:

3.1 xTimerCreate()和xTimerCreateStatic()

此函数用于创建软件定时器,并返回软件定时器的句柄。其中创建方式分为动态和静态的方式,动态方式由FreeRTOS动态分配内存,而静态方式则是自己分配内存。其他部分上使用没有什么区别,所以我们这里重要介绍动态的方式。下面是函数的原型:

/*** @brief       xTimerCreate:动态方式创建软件定时器* @param       pcTimerName: 软件定时器的名字* @param       xTimerPeriod: 软件定时器的周期,默认系统的滴答值* @param       uxAutoReload: 是否为周期定时器,为pdTRUE是周期定时器,为pdFLASE是单次定时器* @param       pvTimerID: 定时器的ID值,由于判断软件定时器服务任务具体调用哪一个回调函数* @param       pxCallbackFunction : 定时器超时时间到达的回调函数* @retval      返回值为NULL,表示创建失败。否则表示创建完成之后的句柄*/TimerHandle_t xTimerCreate( const char * const pcTimerName,const TickType_t xTimerPeriod,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction );/*** @brief       xTimerCreateStatic:动态方式创建软件定时器* @param       pcTimerName: 软件定时器的名字* @param       xTimerPeriod: 软件定时器的周期,默认系统的滴答值* @param       uxAutoReload: 是否为周期定时器,为pdTRUE是周期定时器,为pdFLASE是单次定时器* @param       pvTimerID: 定时器的ID值,由于判断软件定时器服务任务具体调用哪一个回调函数* @param       pxCallbackFunction : 定时器超时时间到达的回调函数* @param       pxTimerBuffer : 用户自己创建的堆栈,用来存放软件定时器相关的结构体成员* @retval      返回值为NULL,表示创建失败。否则表示创建完成之后的句柄*/
TimerHandle_t xTimerCreateStatic( const char * const pcTimerName,const TickType_t xTimerPeriod,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunctionStaticTimer_t *pxTimerBuffer );

【注】:软件定时器超时回调函数的原型如下:

void vCallbackFunction( TimerHandle_t xTimer );

3.2 xTimerStart()和xTimerStartFromISR()

此函数用于开启软件定时器,前面我们说过,刚刚创建的软件定时器是处于休眠态的,所以在使用之前需先开启定时,使其从休眠态转换为运行态,下面是函数原型:

/*** @brief       xTimerStart:开启软件定时器* @param       xTimer: 需要开启的软件定时器句柄* @param       xBlockTime : 阻塞超时时间* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示超时,发送失败*/BaseType_t xTimerStart( TimerHandle_t xTimer,TickType_t xBlockTime );
/*** @brief       xTimerStartFromISR:在中断中开启定时器* @param       xTimer: 需要开启的软件定时器句柄* @param       pxHigherPriorityTaskWoken: 是否需要进行任务交换,如果为pdTRUE,表示开启软件定时器之后,软件定时器服务任务被唤醒,* 										   且优先级大于当前正在执行的任务,需要进行任务切换,需要在中断退出之前进行任务切换,否则不需要* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示发送失败*/BaseType_t xTimerStartFromISR(TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken);

3.3 xTimerStop()和xTimerStopFromISR()

此函数用于停止定时器,使得软件定时器由运行态转换为休眠态,下面是函数原型:


/*** @brief       xTimerStop:停止软件定时器* @param       xTimer: 需要停止的软件定时器句柄* @param       xBlockTime : 阻塞超时时间* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示超时,发送失败*/BaseType_t xTimerStop( TimerHandle_t xTimer,TickType_t xBlockTime );
/*** @brief       xTimerStopFromISR:在中断中停止定时器* @param       xTimer: 需要停止的软件定时器句柄* @param       pxHigherPriorityTaskWoken: 是否需要进行任务交换,如果为pdTRUE,表示开启软件定时器之后,软件定时器服务任务被唤醒,* 										   且优先级大于当前正在执行的任务,需要进行任务切换,需要在中断退出之前进行任务切换,否则不需要* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示发送失败*/BaseType_t xTimerStopFromISR(TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken);

3.4 xTimerReset()和xTimerResetFromISR()

此函数用于复位软件定时器,将软件定时器的值清零;但是对于处于休眠态的软件定时器,作用和xTimerStart()相同,下面是函数原型:

/*** @brief       xTimerReset:复位软件定时器* @param       xTimer: 需要复位的软件定时器句柄* @param       xBlockTime : 阻塞超时时间* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示超时,发送失败*/BaseType_t xTimerReset( TimerHandle_t xTimer,TickType_t xBlockTime );
/*** @brief       xTimerResetFromISR:在中断中复位定时器* @param       xTimer: 需要复位的软件定时器句柄* @param       pxHigherPriorityTaskWoken: 是否需要进行任务交换,如果为pdTRUE,表示开启软件定时器之后,软件定时器服务任务被唤醒,* 										   且优先级大于当前正在执行的任务,需要进行任务切换,需要在中断退出之前进行任务切换,否则不需要* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示发送失败*/BaseType_t xTimerResetFromISR(TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken);

3.5 xTimerChangePeriod()和xTimerChangePeriodFromISR()

此函数用于改变软件定时器的周期,不管定时器处于什么状态都可以改变定时器周期,并且更改后会进入运行态,下面是函数原型:

/*** @brief       xTimerChangePeriod:改变定时器的周期* @param       xTimer: 需要改变周期的软件定时器句柄* @param       xNewPeriod: 需要改变的周期,单位为滴答周期* @param       xBlockTime : 阻塞超时时间* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示超时,发送失败*/BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,TickType_t xNewPeriod,TickType_t xBlockTime );
/*** @brief       xTimerChangePeriod:改变定时器的周期* @param       xTimer: 需要改变周期的软件定时器句柄* @param       xNewPeriod: 需要改变的周期,单位为滴答周期* @param       xBlockTime : 阻塞超时时间* @param       pxHigherPriorityTaskWoken: 是否需要进行任务交换,如果为pdTRUE,表示开启软件定时器之后,软件定时器服务任务被唤醒,* 										   且优先级大于当前正在执行的任务,需要进行任务切换,需要在中断退出之前进行任务切换,否则不需要* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示超时,发送失败*/BaseType_t xTimerChangePeriodFromISR(TimerHandle_t xTimer,TickType_t xNewPeriod,BaseType_t *pxHigherPriorityTaskWoken);

3.6 xTimerDelete()

此函数用于删除软件定时器,下面是函数原型:

/*** @brief       xTimerDelete:删除软件定时器* @param       xTimer: 需要改变周期的软件定时器句柄* @param       xBlockTime : 阻塞超时时间* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示超时,发送失败*/BaseType_t xTimerDelete( TimerHandle_t xTimer,TickType_t xBlockTime );

3.7 xTimerIsTimerActive()

此函数用于获取定时器的状态,是否处于休眠态或者运行态。下面是函数原型:

/*** @brief       xTimerDelete:删除软件定时器* @param       xTimer: 需要改变周期的软件定时器句柄* @retval      返回pdPASS表示成功发送命令到软件定时器服务命令队列,返回值为pdFALL,表示超时,发送失败*/BaseType_t xTimerIsTimerActive( TimerHandle_t xTimer );

3.8 pvTimerGetTimerID()

此函数用于获取软件定时器的ID,下面是函数原型:

 /*** @brief       pvTimerGetTimerID:获取软件定时器ID* @param       xTimer: 需要获取ID的软件定时器句柄* @retval      分配给被查询的定时器的 ID*/void *pvTimerGetTimerID( TimerHandle_t xTimer );

3.9 vTimerSetReloadMode()

此函数用于修改软件定时器的模式,下面是函数原型:

 /*** @brief       vTimerSetReloadMode:设置软件定时器的模式* @param       xTimer: 需要修改模式的软件定时器句柄* @param       uxAutoReload : 传入参数为pdTRUE表示设置周期定时器,为odFALSE表示设置为单次定时器。* @retval      void*/void vTimerSetReloadMode( TimerHandle_t xTimer,const UBaseType_t uxAutoReload );

3.10 vTimerSetTimerID()

此函数用来设置定时器的ID值,函数原型如下:

 /*** @brief       vTimerSetTimerID:设置软件定时器的ID* @param       xTimer: 需要设置ID的软件定时器句柄* @param       pvNewID : 新的ID* @retval      void*/void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

3.11 xTimerPendFunctionCall()和xTimerPendFunctionCallFromISR()

此函数用于请求执行一个另一个函数,不过被请求的函数调用不是当下立即完成,而是被挂起(延时)到软件定时器服务任务(也叫RTOS守护进程任务)中执行,下面是函数原型:

/*** @brief       xTimerPendFunctionCall:请求执行一个函数xFunctionToPend()* @param       xFunctionToPend: 请求被执行的函数,必须为PendedFunction_t 类型* @param       pvParameter1: 被请求的函数的第一个参数* @param       ulParameter2: 被请求的函数的第二个参数* @param       xTicksToWait : 阻塞超时时间* @retval      返回值为pdPASS,表示请求成功,为pdFALSE表示请示失败*/BaseType_t xTimerPendFunctionCall(PendedFunction_t xFunctionToPend,void *pvParameter1,uint32_t ulParameter2,TickType_t xTicksToWait );/*** @brief       xTimerPendFunctionCallFromISR:在中断中请求执行一个函数xFunctionToPend()* @param       xFunctionToPend: 请求被执行的函数,必须为PendedFunction_t 类型* @param       pvParameter1: 被请求的函数的第一个参数* @param       ulParameter2: 被请求的函数的第二个参数* @param       pxHigherPriorityTaskWoken : 是否需要进行任务交换,如果为pdTRUE,表示开启软件定时器之后,软件定时器服务任务被唤醒,* 										   且优先级大于当前正在执行的任务,需要进行任务切换,需要在中断退出之前进行任务切换,否则不需要* @retval      返回值为pdPASS,表示请求成功,为pdFALSE表示请示失败*/BaseType_t xTimerPendFunctionCallFromISR(PendedFunction_t xFunctionToPend,void *pvParameter1,uint32_t ulParameter2,BaseType_t *pxHigherPriorityTaskWoken );

下面是请求被执行的函数原型,必须满足PendedFunction_t 类型,否则无法成功使用:

 void vPendableFunction( void * pvParameter1, uint32_t ulParameter2 );

【注】:请求执行另一个函数成功,会往软件定时器服务任务命令队列中写入消息,使得软件定时器服务任务处于就绪态,且默认配置下软件定时服务任务的优先级为最大,所以会立即执行上下文切换。因此xTimerPendFunctionCall()函数在实际上没有很大的意义(拙见),但是在中断中,为了满足快进快出的要求,可以使用xTimerPendFunctionCallFromISR()函数将一些比较耗时、非确定性的操作推迟到软件定时器服务任务(RTOS守护进程程序)中执行,以增加中断响应的灵活性。

最后是一个简单的使用示例:


/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 */
#define TASK1_PRIO      1                  /* 任务优先级 */
#define TASK1_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task1Task_Handler;  /* 任务句柄 */
void task1(void *pvParameters);					/*任务函数*/void myCallbackFunction(void * pvParameters,uint32_t ulParameter2);	/* 被请求的函数声明 */void freertos_demo(void)
{taskENTER_CRITICAL();           /* 进入临界区,关闭中断,此时停止任务调度*/								/* 创建任务1 */xTaskCreate((TaskFunction_t )task1,(const char*    )"task1",(uint16_t       )TASK1_STK_SIZE,(void*          )NULL,(UBaseType_t    )TASK1_PRIO,(TaskHandle_t*  )&Task1Task_Handler);taskEXIT_CRITICAL();            /* 退出临界区,重新开启中断,开启任务调度 */vTaskStartScheduler();		//开启任务调度
}
/* 任务1 */
void task1(void *pvParameters)
{while(1){/* 请求执行myCallbackFunction函数 */xTimerPendFunctionCall(myCallbackFunction, (void *) 123, 0, pdTRUE);/* xTimerPendFunctionCall()执行完之后会发生上下文切换,到软件定时器服务任务,然后回调myCallbackFunction()函数 */HAL_Delay(1000);}
}	
/* 被请求执行的函数 */
void myCallbackFunction(void * pvParameters,uint32_t ulParameter2) {// 可以在这里处理传递的参数uint32_t param = (uint32_t) pvParameters;// 例如:打印参数printf("Callback executed with parameter: %d\n", param);
}/* 最后的现象就是串口每1打印一次“Callback executed with parameter: 123” */

3.12 pcTimerGetName()

此函数用于获取软件定时器的名字,函数原型如下:

 /*** @brief       pcTimerGetName:获取软件定时器的名字* @param       xTimer: 需要获取名字的软件定时器句柄* @retval      软件定时器的名字*/const char * pcTimerGetName( TimerHandle_t xTimer );

3.13 xTimerGetPeriod()

此函数用于获取软件定时的周期,函数原型如下:

 /*** @brief       xTimerGetPeriod:获取软件定时器的周期* @param       xTimer: 需要获取周期的软件定时器句柄* @retval      软件定时器的周期*/TickType_t xTimerGetPeriod( TimerHandle_t xTimer );

3.14 xTimerGetExpiryTime()

此函数用于获取下一次超时的时间,函数原型如下:

 /*** @brief       xTimerGetExpiryTime:获取下一次超时时间* @param       xTimer: 需要获取下一次超时时间的软件定时器句柄* @retval      下一次的超时时间,也可以说是滴答数。*/TickType_t xTimerGetExpiryTime( TimerHandle_t xTimer );

下面是一个使用示例:

static void prvAFunction( TimerHandle_t xTimer )
{
TickType_t xRemainingTime;/* Calculate the time that remains before the timer referenced by xTimerexpires. TickType_t is an unsigned type, so the subtraction will result inthe correct answer even if the timer will not expire until after the tickcount has overflowed. */xRemainingTime = xTimerGetExpiryTime( xTimer ) - xTaskGetTickCount();
}

3.15 xTimerGetReloadMode() 和uxTimerGetReloadMode()

此函数用于获取软件定时器的模式,下面是函数原型:

 /*** @brief       xTimerGetReloadMode:获取定时器的模式* @param       xTimer: 需要获取下一次超时时间的软件定时器句柄* @retval      返回值为pdTRUE,表示为周期定时器,为pdFALSE表示为单次定时器。*/
BaseType_t  xTimerGetReloadMode( TimerHandle_t xTimer );
/* uxTimerGetReloadMode()函数和上面一样,但属于老版本中存在,保证向后兼容使用的,新的应用程序中应该避免使用此函数  */
UBaseType_t uxTimerGetReloadMode( TimerHandle_t xTimer );

以上就是所有FreeRTOS提供的软件定时器相关的API函数,最后我们来完成一个简单的操作实验,结束今天的学习!!!

4 软件定时器操作实验

4.1. 实验内容

在STM32F103RCT6上运行FreeRTOS,通过按键控制,完成对应的队列集操作,具体要求如下:

  • 创建两个软件定时器,一个单次定时器、一个周期定时器
  • 定义任务1:用于LED闪烁,执行系统正常运行
  • 定义任务2:用于按键扫描,按键按下控制定时器的启动、停止和复位
  • 每完成一个步骤,通过串口打印相关信息。

4.2 代码实现

  • 由于本期内容涉及到按键扫描,会用到: STM32框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
  1. freertos_demo.c
#include "freertos_demo.h"
#include "gpio.h"
#include "queue.h" 		//需要包含队列和任务相关的头文件
#include "key.h"		//包含按键相关头文件
#include "timers.h"
/*FreeRTOS*********************************************************************************************//******************************************************************************************************/
/*FreeRTOS配置*//* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 */
#define TASK1_PRIO      1                  /* 任务优先级 */
#define TASK1_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task1Task_Handler;  /* 任务句柄 */
void task1(void *pvParameters);					/*任务函数*//* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 */
#define TASK2_PRIO      2                  /* 任务优先级 */
#define TASK2_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task2Task_Handler;  /* 任务句柄 */
void task2(void *pvParameters);					/*任务函数*/#define Timer1ID 0x01
#define Timer2ID 0x02TimerHandle_t Timer1Handle;
TimerHandle_t Timer2Handle;void Timer1CallbackFunction( TimerHandle_t xTimer );
void Timer2CallbackFunction( TimerHandle_t xTimer );
/******************************************************************************************************//*** @brief       FreeRTOS例程入口函数* @param       无* @retval      无*/
void freertos_demo(void)
{taskENTER_CRITICAL();           /* 进入临界区,关闭中断,此时停止任务调度*//* 创建单次定时器 */Timer1Handle = xTimerCreate( "timer1",1000,pdFALSE,(void *)Timer1ID,Timer1CallbackFunction );/* 创建周期定时器 */Timer2Handle = xTimerCreate( "timer2",1000,pdTRUE,(void *)Timer2ID,Timer2CallbackFunction );/* 创建任务1 */xTaskCreate((TaskFunction_t )task1,(const char*    )"task1",(uint16_t       )TASK1_STK_SIZE,(void*          )NULL,(UBaseType_t    )TASK1_PRIO,(TaskHandle_t*  )&Task1Task_Handler);/* 创建任务2 */xTaskCreate((TaskFunction_t )task2,(const char*    )"task2",(uint16_t       )TASK2_STK_SIZE,(void*          )NULL,(UBaseType_t    )TASK2_PRIO,(TaskHandle_t*  )&Task2Task_Handler);taskEXIT_CRITICAL();            /* 退出临界区,重新开启中断,开启任务调度 */vTaskStartScheduler();		//开启任务调度
}/**
* @brief       task1:LED0状态翻转,指示系统正常运行。* @param       pvParameters : 传入参数(未用到)* @retval      无*/
void task1(void *pvParameters)
{while(1){HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);vTaskDelay(1000);}
}	
/**
* @brief       task2:按键扫描:按键0开启定时器,按键1停止定时器,按键2复位定时器* @param       pvParameters : 传入参数(未用到)* @retval      无*/
void task2(void *pvParameters)	
{	while(1){	Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task);Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task);Key_One_Scan(Key_Name_Key2,Key2_Up_Task,Key2_Down_Task);vTaskDelay(10);}}void Timer1CallbackFunction( TimerHandle_t xTimer )
{static uint16_t Timer1_Count = 0;Timer1_Count++;printf("软件定时器1任务执行次数: %d\r\n",Timer1_Count);
}void Timer2CallbackFunction( TimerHandle_t xTimer )
{static uint16_t Timer2_Count = 0;Timer2_Count++;printf("软件定时器2任务执行次数: %d\r\n",Timer2_Count);
}
  1. key.c
/* USER CODE BEGIN 2 */#include "key.h"
#include "freertos_demo.h"
#include "usart.h"
#include "event_groups.h"	//包含事件标志组头文件
#include "gpio.h"
#include "task.h"
#include "timers.h"void Key0_Down_Task(void)
{printf("\r\n按键0按下\r\n\r\n");printf("启动定时器\r\n");/* 启动定时器1和定时器2 */xTimerStart(Timer1Handle,portMAX_DELAY);xTimerStart(Timer2Handle,portMAX_DELAY);}
void Key0_Up_Task(void)
{}
void Key1_Down_Task(void)
{printf("\r\n按键1按下\r\n\r\n");printf("停止定时器\r\n");/* 停止定时器1和定时器2 */xTimerStop(Timer1Handle,portMAX_DELAY);xTimerStop(Timer2Handle,portMAX_DELAY);}
void Key1_Up_Task(void)
{}
void Key2_Down_Task(void)
{printf("\r\n按键2按下\r\n\r\n");printf("复位定时器\r\n");/* 定时器1和定时器2 */xTimerReset(Timer1Handle,portMAX_DELAY);xTimerReset(Timer2Handle,portMAX_DELAY);
}
void Key2_Up_Task(void)
{}
void WKUP_Down_Task(void)
{}
void WWKUP_Up_Task(void)
{}void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{static uint8_t Key_Val[Key_Name_Max];    //按键值的存放位置static uint8_t Key_Flag[Key_Name_Max];   //KEY0~2为0时表示按下,为1表示松开,WKUP反之Key_Val[KeyName] = Key_Val[KeyName] <<1;  //每次扫描完,将上一次扫描的结果左移保存switch(KeyName){case Key_Name_Key0:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin));    //读取Key0按键值break;case Key_Name_Key1:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin));   //读取Key1按键值break;case Key_Name_Key2:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin));   //读取Key2按键值break;
//        case Key_Name_WKUP:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin));   //读取WKUP按键值
//            break; default:break;}
//    if(KeyName == Key_Name_WKUP)     //WKUP的电路图与其他按键不同,所以需要特殊处理
//    {
//        //WKUP特殊情况
//        //当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xff
//        if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1)
//        {
//            (*OnKeyOneDown)();
//           Key_Flag[KeyName] = 0;
//        }
//        //当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00
//        if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0)
//        {
//            (*OnKeyOneUp)();
//           Key_Flag[KeyName] = 1;
//        } 
//    }
//    else                               //Key0~2按键逻辑判断
//    {//Key0~2常规判断//当按键标志为1(松开)是,判断是否按下if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1){(*OnKeyOneDown)();Key_Flag[KeyName] = 0;}//当按键标志位为0(按下),判断按键是否松开if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0){(*OnKeyOneUp)();Key_Flag[KeyName] = 1;}  }//}
/* USER CODE END 2 */

4.3 运行结果

在这里插入图片描述

以上就是本期的所有内容,创造不易,点个关注再走呗。

在这里插入图片描述

相关文章:

STM32F1+HAL库+FreeTOTS学习19——软件定时器

STM32F1HAL库FreeTOTS学习19——软件定时器 1 软件定时器1.1 FreeRTOS软件定时器简介1.2 FreeRTOS软件定时器服务任务1.3 FreeRTOS软件定时器服命令队列。1.4 软件定时器的状态1.5 复位定时器1.6 软件定时器结构体 2 软件定时器配置3 软件定时器API函数3.1 xTimerCreate()和xTi…...

@RequestBody的详解和使用

RequestBody的详解和使用 提示&#xff1a;建议一定要看后面的RequestBody的核心逻辑源码以及六个重要结论&#xff01;本文前半部分的内容都是一些基- 本知识常识&#xff0c;可选择性跳过。 声明&#xff1a;本文是基于SpringBoot&#xff0c;进行的演示说明。 基础知识介…...

VMware介绍及常见使用方法

VMware 是一家全球知名的虚拟化和云计算软件提供商。以下是关于 VMware 的详细介绍: 一、主要产品和功能 VMware vSphere 服务器虚拟化平台,允许将物理服务器虚拟化为多个虚拟机(VM)。提供高可用性、资源管理、动态迁移等功能,确保业务的连续性和高效性。通过集中管理控制…...

Deepinteraction 深度交互:通过模态交互的3D对象检测

一.前提 为什么要采用跨模态的信息融合? 点云在低分辨率下提供必要的定位和几何信息&#xff0c;而图像在高分辨率下提供丰富的外观信息。 -->因此必须采用跨模态的信息融合 提出的原因? 传统的融合办法可能会由于信息融合到统一表示中的不太完美而丢失很大一部分特定…...

开展物业满意度调查的策略与注意事项

&#xff08;专业物业满意度调查公司&#xff09;在物业管理领域&#xff0c;满意度调查是一项重要的工作&#xff0c;可以帮助物业公司了解居民的需求和期望&#xff0c;及时发现并解决问题&#xff0c;提升服务质量。民安智库作为专业调查咨询机构&#xff0c;拥有丰富的实战…...

如何使用 Maven 不同环境使用不同资源文件 提升项目安全性

需求&#xff1a; 之前的文章介绍过&#xff0c;不同环境&#xff0c;配置文件可以灵活构建&#xff0c;参考Maven 不同环境灵活构建。 进一步的&#xff0c;打包时时可以进一步优化&#xff0c;即开发环境&#xff0c;构建时只将测试资源文件打包到应用中&#xff0c;进一步提…...

QT 如何置顶窗口并激活

基本上&#xff0c;客户端软件都会有置顶某个窗口的需求。置顶窗口激活窗口&#xff0c;两者不是同一个问题。有时候窗口置顶了&#xff0c;并不代表该窗口属于激活状态。本文将尝试把这两个问题一起解决了&#xff0c;请看下文&#xff1a; 一、置顶窗口 通过函数setWindowF…...

嵌入式面试刷题(day19)

Makefile和Cmake的区别 Makefile 和 CMake 都是用于构建和管理软件项目的工具,但它们有不同的设计理念和使用方式。以下是二者的主要区别: 1. 概念和工作原理 Makefile: Makefile 是 make 工具的配置文件,定义了如何编译和链接程序。它基于文件的时间戳,使用规则(规则指…...

Robot Framework命令和Tag运用

前面的文章中我们为大家介绍了市面上常见自动化测试框架的解读以及Robot Framework的环境搭建&#xff0c;本文我们继续为大家介绍Robot Framework命令和Tag的运用&#xff0c;首先我们先一起看一下Robot Framework有哪些命令。 Robot Framework命令 先来看这一条&#xff1a…...

软件分享丨PDF Shaper

【资源名】PDF Shaper 【地址】https://www.pdfshaper.com/ 【资源介绍】 PDF Shaper Professional是一款功能强大的PDF文档编辑与转换工具&#xff0c;使用它可以对PDF文件进行各种转换、提取、合并、旋转、加密、解密等编辑操作&#xff0c;主要功能有分割和合并PDF文件&…...

pytorch的标签平滑介绍

什么是标签平滑(Label Smoothing)? 标签平滑(Label Smoothing)是一种正则化技术,旨在防止模型过度自信(即输出的概率分布过于“尖锐”)。在分类任务中,标准的目标标签是one-hot编码,也就是正确类别的概率为 1,其他类别的概率为 0。而标签平滑通过将正确类别的概率从…...

CTE 与存储过程:SQL 查询简化与复杂业务逻辑处理的最佳选择

CTE&#xff08;Common Table Expression&#xff0c;公共表表达式&#xff09;和存储过程是两种不同的SQL工具&#xff0c;分别用于解决不同的问题。它们各有优缺点&#xff0c;适用于不同的场景。让我们从以下几个方面来比较它们&#xff1a; 1. 定义与作用 CTE&#xff1a;…...

mysql delete命令操作后,数据库文件大小并未变化,该怎么解决?

在 MySQL 中&#xff0c;使用 DELETE 命令删除数据后&#xff0c;数据表的大小可能不会立即减小&#xff0c;因为 MySQL 并不总是立即回收已删除数据所占用的空间。这是因为 MySQL 的存储引擎&#xff08;如 InnoDB&#xff09;可能会保留这些空间以备将来插入新数据时使用&…...

GitLab 发布安全补丁版本 17.3.2, 17.2.5, 17.1.7

本分分享极狐GitLab 补丁版本 17.4.2, 17.3.5, 17.2.9 的详细内容。这几个版本包含重要的缺陷和安全修复代码&#xff0c;我们强烈建议所有私有化部署用户应该立即升级到上述的某一个版本。对于极狐GitLab SaaS&#xff0c;技术团队已经进行了升级&#xff0c;无需用户采取任何…...

data_table_2 与 flutter 官方 DataTable 的区别

data_table_2 与 flutter 官方 DataTable 的区别 https://blog.csdn.net/ken2232/article/details/143181634 flutter 数据表增强库 data_table_2 错误问题 记录 (****) https://blog.csdn.net/ken2232/article/details/143180018 data_table_2 与 flutter 官方 DataT…...

TRIZ创新方法培训值得去吗?

面对日益复杂的市场环境和竞争态势&#xff0c;掌握有效的创新方法不仅关乎企业的生死存亡&#xff0c;也直接关系到个人的职业成长与竞争力。TRIZ作为一种系统化的创新方法论&#xff0c;其培训课程正逐渐受到企业和个人的广泛关注。那么&#xff0c;TRIZ创新方法培训究竟值得…...

STM32之基本定时器TIM6和TIM7

1.定时器概念和作用 在编程任务中&#xff0c;定时器是非常常用的一个问题。当需要定时发送数据&#xff0c;定时起某个任务&#xff0c;定时做某个操作等等&#xff0c;这些都离不开定时器。本文基于以STM32F4xx系列开发板&#xff0c;介绍一下基本定时器。 2.基本定时器TIM…...

嵌入式※~MCU~LWIP~TCPS/HTTPS等

单片机MCU中的加密通道, 使用各种的加密通道, http / tcp / mqtt 等 可能不在重复发了 ~~ 请看链接吧~~~ 我自己的原文哦~ https://blog.51cto.com/whaosoft/11803802...

神经网络model训练时loss=nan【原因总结】

一、Loss functions 中含 F.log_softmax()函数 原因: 由于在计算log_softmax(x)时, 出现log(0)的情况。 解决方法: 给log_softmax的参数x添加一个很小的数: out=F.log_softmax(x+1e-10).二、loss_function(x)函数参数中出现nan 原因: 网络的生成features x 中含有nan. 解…...

【力扣 | SQL题 | 每日5题】力扣2362, 2356, 2394, 2480, 2388

1. 力扣2362&#xff1a;生成发票 1.1 题目&#xff1a; 表: Products ------------------- | Column Name | Type | ------------------- | product_id | int | | price | int | ------------------- product_id 包含唯一值。 该表中的每一行显示了一个产品的 ID …...

直播间“饕餮盛宴”的背后,是“他经济”正在冒头

最近&#xff0c;一个有意思的现象逐渐露出了苗头。 今年“双11”第一轮尾款开启支付的当晚&#xff0c;罗永浩的直播间上演了一出别样的“饕餮盛宴”。直播开场后&#xff0c;iphone16系列、可口可乐&#xff08;含糖、无糖300ml&#xff09;10秒售罄&#xff0c;索尼PS5、沃…...

盘点5款常用的环境部署工具

在现代软件开发中&#xff0c;环境部署工具扮演着重要角色&#xff0c;特别是在 PHP 开发和微服务架构中。本文将深入探讨几种主流的环境部署工具&#xff0c;包括 XAMPP、Servbay、MAMP、Laravel Herd 和 Docker&#xff0c;详细分析它们的特点、优缺点、适用场景及最佳实践&a…...

条码检测系统——基于MATLAB的一维条码识别

条码检测系统——基于MATLAB的一维条码识别 摘 要&#xff1a;条码技术是如今应用最广泛的识别和输入技术之一&#xff0c;由于其包含的信息量大&#xff0c;识别错误率低而在各个方面得到很大的重视。它发展迅速并被广泛应用于于工业、商业、图书出版、医疗卫生等各行各业。由…...

【Word原件测试资料合集】软件系统功能测试方案,软件测试方案(整体方案),软件测试文档-测试计划模版(功能与性能),软件测试流程

一、 前言 &#xff08;一&#xff09; 背景 &#xff08;二&#xff09; 目的 &#xff08;三&#xff09; 测试目标 &#xff08;四&#xff09; 适用范围与读者对象 &#xff08;五&#xff09; 术语与缩写 二、 软件测试实施流程 &#xff08;一&#xff09; 测试工作总体流…...

1024 程序员节 快乐

大家好&#xff0c;今天是2024-10-24 程序员节~~~~~~~~~祝你节日快乐&#xff0c;程序员&#xff01;哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦 喂~俺个炊饼~ 马飞~ 好了&#xff0c;该说…...

Java枚举类

1.枚举 1.1概述【理解】 为了间接的表示一些固定的值&#xff0c;Java就给我们提供了枚举 是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内 1.2定义格式【应用】 格式 public enum s { 枚举项1,枚举项2,枚举项3; } 注意: 定义枚举类要用关键字enum 示例代…...

kubenetes/kubesphere搭建报错

一、prometheus问题 1.kubesphere 安装 prometheus-k8s 报以下错误&#xff1a; MountVolume.SetUp failed for volume "secret-kube-etcd-client-certs" : secret "kube-etcd-client-certs" not found 原因是&#xff1a;kube-etcd-client-certs 这个证…...

分享几个办公类常用的AI工具

办公类 WPS AI讯飞智文iSlideProcessOn亿图脑图ChatPPT WPS AI 金山办公推出的协同办公 AI 应用&#xff0c;具有文本生成、多轮对话、润色改写等多种功能&#xff0c;可以辅助用户进行文档编辑、表格处理、演示文稿制作等办公操作。 https://ai.wps.cn/ 讯飞智文 科大讯飞推…...

Vue入门示例

今天滴学习目标&#xff01;&#xff01;&#xff01; 示例简介HTML内容主体区域输入框列表区域统计和清空 JS引入Vue.js库定义Vue实例el选项data选项methods选项 示例简介 HTML内容 本次实例讲解的是v-for、v-on、v-model来写这小小的实例&#xff0c;下面是实例的效果图&am…...

鸿蒙开发:实现全局异常捕获和异常查看

前言 开发中的异常信息&#xff0c;我们很容易排查&#xff0c;直接可以在控制台中就可以查看&#xff0c;但是&#xff0c;提交给测试同学或者上线后的异常信息&#xff0c;我们如何获取呢&#xff1f;这里我们很容易想起&#xff0c;三方sdk&#xff0c;比如常见的腾讯Bugly…...

免费网站推广/百度快速收录权限域名

Python的多线程只能运行在单核上&#xff0c;各个线程以并发的方法异步运行。而多进程可以利用CPU的多核&#xff0c;进程数取决于计算机CPU的处理器个数&#xff0c;由于运行在不同的核上&#xff0c;各个进程的运行是并行的。在python中&#xff0c;如果使用多进程&#xff0…...

做全景效果图的网站/网络推广属于什么专业

题目连接&#xff1a;点击打开链接 网上啥啥都特别乱&#xff0c;自己看了点&#xff0c;直接自己写了个被称为原地逆序。 void ABBA(char a[]) { int left0,rightstrlen(a)-1; while(left<right) { swap(a[left],a[right]); left; r…...

做传媒网站公司/帮人推广注册app的平台

文章目录 前言 网站访问流程&#xff08;故事版&#xff09; 如何排查问题 如果客户说访问web网站慢 如果你访问网站发现的确访问速度很慢&#xff0c;这时候你需要排查网站慢的原因 1&#xff09; 检查网站服务器是否异常 2&#xff09; 检查与网站相关联的服务是否异常 3&…...

武汉网站建设武汉/百度搜索下载app

下面是小凰凰的简介&#xff0c;看下吧&#xff01; &#x1f497;人生态度&#xff1a;珍惜时间&#xff0c;渴望学习&#xff0c;热爱音乐&#xff0c;把握命运&#xff0c;享受生活 &#x1f497;学习技能&#xff1a;网络 -> 云计算运维 -> python全栈( 当前正在学习…...

律师网站建设/福建键seo排名

&#xff08;《软件工艺》一书即将由人民邮电出版社出版&#xff0c;详情参见http://www.china-pub.com/computers/subject/huodong/ry12.23/tyzt.htm。本文是作者Pete McBreen专门为中译本所作的序言。&#xff09;中译本序 看见“软件工艺”的思想在全世界得到日益广泛的认同…...

域名建议网站/电话投放小网站

由于MySQL的社区版是不支持审计系统的&#xff0c;因此通过第三方的插件实现审计功能。此次采用MariaDB的server_audit插件来实现MySQL的审计功能。 安装server_audit插件 1、下载server_audit插件 下载地址&#xff1a;http://mirrors.neusoft.edu.cn/mariadb//mariadb-5.5.62…...