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.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 运行结果
以上就是本期的所有内容,创造不易,点个关注再走呗。
相关文章:

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的详解和使用 提示:建议一定要看后面的RequestBody的核心逻辑源码以及六个重要结论!本文前半部分的内容都是一些基- 本知识常识,可选择性跳过。 声明:本文是基于SpringBoot,进行的演示说明。 基础知识介…...
VMware介绍及常见使用方法
VMware 是一家全球知名的虚拟化和云计算软件提供商。以下是关于 VMware 的详细介绍: 一、主要产品和功能 VMware vSphere 服务器虚拟化平台,允许将物理服务器虚拟化为多个虚拟机(VM)。提供高可用性、资源管理、动态迁移等功能,确保业务的连续性和高效性。通过集中管理控制…...

Deepinteraction 深度交互:通过模态交互的3D对象检测
一.前提 为什么要采用跨模态的信息融合? 点云在低分辨率下提供必要的定位和几何信息,而图像在高分辨率下提供丰富的外观信息。 -->因此必须采用跨模态的信息融合 提出的原因? 传统的融合办法可能会由于信息融合到统一表示中的不太完美而丢失很大一部分特定…...
开展物业满意度调查的策略与注意事项
(专业物业满意度调查公司)在物业管理领域,满意度调查是一项重要的工作,可以帮助物业公司了解居民的需求和期望,及时发现并解决问题,提升服务质量。民安智库作为专业调查咨询机构,拥有丰富的实战…...
如何使用 Maven 不同环境使用不同资源文件 提升项目安全性
需求: 之前的文章介绍过,不同环境,配置文件可以灵活构建,参考Maven 不同环境灵活构建。 进一步的,打包时时可以进一步优化,即开发环境,构建时只将测试资源文件打包到应用中,进一步提…...
QT 如何置顶窗口并激活
基本上,客户端软件都会有置顶某个窗口的需求。置顶窗口激活窗口,两者不是同一个问题。有时候窗口置顶了,并不代表该窗口属于激活状态。本文将尝试把这两个问题一起解决了,请看下文: 一、置顶窗口 通过函数setWindowF…...
嵌入式面试刷题(day19)
Makefile和Cmake的区别 Makefile 和 CMake 都是用于构建和管理软件项目的工具,但它们有不同的设计理念和使用方式。以下是二者的主要区别: 1. 概念和工作原理 Makefile: Makefile 是 make 工具的配置文件,定义了如何编译和链接程序。它基于文件的时间戳,使用规则(规则指…...

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

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

pytorch的标签平滑介绍
什么是标签平滑(Label Smoothing)? 标签平滑(Label Smoothing)是一种正则化技术,旨在防止模型过度自信(即输出的概率分布过于“尖锐”)。在分类任务中,标准的目标标签是one-hot编码,也就是正确类别的概率为 1,其他类别的概率为 0。而标签平滑通过将正确类别的概率从…...
CTE 与存储过程:SQL 查询简化与复杂业务逻辑处理的最佳选择
CTE(Common Table Expression,公共表表达式)和存储过程是两种不同的SQL工具,分别用于解决不同的问题。它们各有优缺点,适用于不同的场景。让我们从以下几个方面来比较它们: 1. 定义与作用 CTE:…...
mysql delete命令操作后,数据库文件大小并未变化,该怎么解决?
在 MySQL 中,使用 DELETE 命令删除数据后,数据表的大小可能不会立即减小,因为 MySQL 并不总是立即回收已删除数据所占用的空间。这是因为 MySQL 的存储引擎(如 InnoDB)可能会保留这些空间以备将来插入新数据时使用&…...
GitLab 发布安全补丁版本 17.3.2, 17.2.5, 17.1.7
本分分享极狐GitLab 补丁版本 17.4.2, 17.3.5, 17.2.9 的详细内容。这几个版本包含重要的缺陷和安全修复代码,我们强烈建议所有私有化部署用户应该立即升级到上述的某一个版本。对于极狐GitLab SaaS,技术团队已经进行了升级,无需用户采取任何…...
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创新方法培训值得去吗?
面对日益复杂的市场环境和竞争态势,掌握有效的创新方法不仅关乎企业的生死存亡,也直接关系到个人的职业成长与竞争力。TRIZ作为一种系统化的创新方法论,其培训课程正逐渐受到企业和个人的广泛关注。那么,TRIZ创新方法培训究竟值得…...

STM32之基本定时器TIM6和TIM7
1.定时器概念和作用 在编程任务中,定时器是非常常用的一个问题。当需要定时发送数据,定时起某个任务,定时做某个操作等等,这些都离不开定时器。本文基于以STM32F4xx系列开发板,介绍一下基本定时器。 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:生成发票 1.1 题目: 表: Products ------------------- | Column Name | Type | ------------------- | product_id | int | | price | int | ------------------- product_id 包含唯一值。 该表中的每一行显示了一个产品的 ID …...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...