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.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框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
- 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);
}
- 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 运行结果
以上就是本期的所有内容,创造不易,点个关注再走呗。