定时器
1,定时器的三要素:超时时间,函数,单次触发 or 周期触发
创建函数 1,定时器名称 2,定时周期 3,模式选择:单次 or 连续 4,定时器id 5,回调函数,软件定时器将一直执行这个回调函数 | TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction ); |
/* 启动定时器 * xTimer: 哪个定时器 * xTicksToWait: 超时时间 * 返回值: pdFAIL 表示"启动命令"在 xTicksToWait 个 Tick 内无法写入队列 * pdPASS 表示成功 */ | BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait ); |
在tick中断中判断是否超时,如果超时就执行回调函数 不在tick中断中执行定时器函数,因为很耗时,会影响tick中断 在RTOS Damemon Task,RTOS守护任务 中执行 守护任务管理着定时器的回调函数,并在适当的时机调用它们,以实现定时任务的执行。 | void pxCallbackFunction() 回调函数 |
示例:简单使用定时器回调函数
在main创建定时器,100ms自动加载,在task1中开启定时器,在回调函数中执行打印。
回调函数的优先级高于task1,
在回调函数中加标志监控是否是100ms进一次
void Task1Function(void *parm)
{
xTimerStart(xMyTimerHandle, 0);
while(1)
{
printf("Task1Function ....\r\n");
}
}
void MyTimerCallbackFunction(TimerHandle_t xtimer)
{
static int cnt = 0;
flag_mytimer = ~flag_mytimer;
printf("mytimercallbackfunction cnt = %d \r\n",cnt++);
}
int main( void )
{
prvSetupHardware();
printf("Hello, world!\r\n");
xMyTimerHandle = xTimerCreate("Mytimer",100,pdTRUE,NULL,MyTimerCallbackFunction);
xTaskCreate(Task1Function,"task1",100,NULL,1,&Task1Handle);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
总结:
1,定时器回调函数受守护任务管理,在freertos文件中,要使能定时器,指定守护任务的优先级、长度、深度。
2,守护任务的优先级需要高于其他任务,
3,回调函数有配置,单次触发,还是持续触发
2、示例:简单使用定时器实现按键驱动程序
1,EXTI0_IRQHandler
{
xTimerReset();//按键按下复位定时器,复位相当于启动
}
2,定时器回调函数 //定时器复位后2000ms之后去处理任务,
{
//处理按键任务
}
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
KeyInit();
KeyIntInit();
xMyTimerHandle = xTimerCreate("mytimer", 2000, pdFALSE, NULL, MyTimerCallbackFunction); //1,定义定时器,定时器start后,2000ms进入回调函数,并且是单次触发
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
//xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
void EXTI0_IRQHandler(void)
{
static int cnt = 0;
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
printf("EXTI0_IRQHandler cnt = %d\r\n", cnt++);
/* 使用定时器消除抖动 */
xTimerReset(xMyTimerHandle, 0); /* Tcur + 2000 */ //按键触发后,复位定时器,如果有多次抖动,会多次复位,直至最后一次进入按键中断后,开始记录2000ms,这里的2000ms为了调试方便,实际运用20ms即可
EXTI_ClearITPendingBit(EXTI_Line0); //清除中断
}
}
疑问:
- 如果有多个按键,定时器回调函数如何被操作
/*1. 按键数量较少(如1-2个)
对于少量的按键,可以直接为每个按键创建一个定时器,并编写各自的定时器回调函数来处理按键事件。这种方式简单直接,但会占用较多的定时器资源。
*/
// 假设有两个按键KEY1和KEY2
TimerHandle_t xTimerHandleKey1;
TimerHandle_t xTimerHandleKey2;
// 创建定时器并设置回调函数
xTimerHandleKey1 = xTimerCreate("Key1Timer", pdMS_TO_TICKS(20), pdFALSE, (void*)1, Key1TimerCallback);
xTimerHandleKey2 = xTimerCreate("Key2Timer", pdMS_TO_TICKS(20), pdFALSE, (void*)2, Key2TimerCallback);
// 在按键中断服务程序中
void EXTIX_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_LineX) != RESET) {
// 假设EXTI_LineX对应KEY1
xTimerReset(xTimerHandleKey1, 0);
EXTI_ClearITPendingBit(EXTI_LineX);
}
}
// 定时器回调函数
void Key1TimerCallback(TimerHandle_t xTimer) {
if ((int)xTimer.pvTimerID == 1) {
// 处理KEY1按键事件
}
}
void Key2TimerCallback(TimerHandle_t xTimer) {
if ((int)xTimer.pvTimerID == 2) {
// 处理KEY2按键事件
}
}
/*2. 按键数量较多(如10个或更多)
对于大量的按键,使用单个定时器并结合软件逻辑来管理去抖是一个更经济的选择*/
// 定义一个数组来存储按键的去抖时间
static uint32_t debounceTime[MAX_KEYS];
static uint8_t keyStates[MAX_KEYS]; // 存储按键状态的数组
// 创建一个1ms定时器
TimerHandle_t x1msTimerHandle;
x1msTimerHandle = xTimerCreate("1msTimer", pdMS_TO_TICKS(1), pdTRUE, NULL, OneMSTimerCallback);
// 在按键中断服务程序中
void EXTIX_IRQHandler(void) {
uint8_t keyIndex = GetKeyIndexFromInterrupt(EXTI_LineX); // 假设这个函数能根据中断线返回按键索引
if (EXTI_GetITStatus(EXTI_LineX) != RESET) {
debounceTime[keyIndex] = xTaskGetTickCount(); // 记录当前tick时间作为去抖开始时间
EXTI_ClearITPendingBit(EXTI_LineX);
}
}
// 1ms定时器回调函数
void OneMSTimerCallback(TimerHandle_t xTimer) {
for (uint8_t i = 0; i < MAX_KEYS; i++) {
if (debounceTime[i] != 0) {
if ((xTaskGetTickCount() - debounceTime[i]) > pdMS_TO_TICKS(20)) {
// 如果时间差大于去抖时间,则处理按键事件
HandleKeyEvent(i);
debounceTime[i] = 0; // 重置去抖时间
}
}
}
}
void HandleKeyEvent(uint8_t keyIndex) {
// 处理按键事件
}
1,如果按键少,比如1,2个可以使用多新建定时器回调函数,各自处理按键
2,如果按键比较多,比如10个,可以参考下面的裸机的操作方式。
-
- 定义全局变量time[N]数组,分别对应不同的按键
-
- 当按键中断触发时,更新time[n] = 当前tick()+20
-
- 新建一个1ms定时器,每隔1ms进入一次。每隔1ms进入回调函数
-
- 在回调函数中分别判断time[n] 与当前tick()如果相等就执行按键处理操作
- 对比之前的裸机代码定时器如何处理多按键
//按键.c中。
volatile uint32_t time[4] = {0}
uint32_t gettimes(int index)
{
return time[index];
}
//中断函数
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(KEY2_Pin);
HAL_GPIO_EXTI_IRQHandler(KEY3_Pin);
}
//回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
case KEY1_Pin:
{
time[0] = HAL_GetTick()+20;
break;
}
case KEY2_Pin:
{
time[1] = HAL_GetTick()+20;
break;
}
case KEY3_Pin:
{
time[2] = HAL_GetTick()+20;
break;
}
case KEY4_Pin:
{
time[3] = HAL_GetTick()+20;
break;
}
default:break;
}
}
//在tick 中断中
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
if(HAL_GetTick() == gettimes(0))
{
if(0 == KEY1)
{
HAL_GPIO_TogglePin(LED_R_GPIO_Port,LED_R_Pin);
setledonoff(LED_GREEN,1);
setledonoff(LED_BLUE,1);
}
}
if(HAL_GetTick() == gettimes(1))
{
if(0 == KEY2)
{
setledonoff(LED_RED,1);
HAL_GPIO_TogglePin(LED_G_GPIO_Port,LED_G_Pin);
setledonoff(LED_BLUE,1);
}
}
if(HAL_GetTick() == gettimes(2))
{
if(0 == KEY3)
{
setledonoff(LED_RED,1);
setledonoff(LED_GREEN,1);
HAL_GPIO_TogglePin(LED_B_GPIO_Port,LED_B_Pin);
}
}
if(HAL_GetTick() == gettimes(3))
{
if(0 == KEY4)
{
HAL_GPIO_TogglePin(LED_R_GPIO_Port,LED_R_Pin);
HAL_GPIO_TogglePin(LED_G_GPIO_Port,LED_G_Pin);
HAL_GPIO_TogglePin(LED_B_GPIO_Port,LED_B_Pin);
}
}
1,新建全局变量数组time[N]
2,当按键按下后,进入回调回调函数判断发生的GPIO,将对应的time[n] = 当前tick+20,即20ms之后的tick值算出来。
3,在tick中断中,读取当前tick 是否等于 time[n] 如果等于触发一次按键动作的操作
- FreeRtos按键中断优先级与定时器中断优先级如何?
无论任务的优先级如何设置,中断的优先级总是高于任何任务的优先级。这意味着,当一个中断发生时,即使当前正在执行的任务优先级非常高,CPU也会立即停止当前任务的执行,转而处理中断服务程序。中断服务程序执行完毕后,CPU才会回到原来的任务继续执行。
因此,对于按键中断和定时器中断,它们的优先级都是相对于其他中断而言的,而不是相对于任务的。你可以在硬件配置中设定按键中断和定时器中断的优先级,以决定它们在发生时的处理顺序。如果按键中断和定时器中断具有不同的抢占式优先级,那么抢占式优先级高的中断将优先得到处理。如果它们具有相同的抢占式优先级,那么子优先级将决定它们的处理顺序。
4)记录发生reset中断到进入回调函数的时间
void MyTimerCallbackFunction( TimerHandle_t xTimer )
{
static int cnt = 0;
key_end = xTaskGetTickCount() ;
printf("key_end = %d\r\n",key_end);
flagTimer = !flagTimer;
printf("Get GPIO Key cnt = %d\r\n", cnt++);
}
void EXTI0_IRQHandler(void)
{
static int cnt = 0;
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
printf("EXTI0_IRQHandler cnt = %d\r\n", cnt++);
/* 使用定时器消除抖动 */
xTimerReset(xMyTimerHandle, 0); /* Tcur + 2000 */ //按键触发后,复位定时器,如果有多次抖动,会多次复位,直至最后一次进入按键中断后,开始记录2000ms,这里的2000ms为了调试方便,实际运用20ms即可
key_start = xTaskGetTickCount() ;
printf("key_start = %d\r\n",key_start);
EXTI_ClearITPendingBit(EXTI_Line0); //清除中断
}
}
5)如果有PA0,PB0同时作为按键中断,都进入按键中断处理函数,如何区分?
在STM32中,如果PA0和PB0都配置为外部中断,并且它们共用一个中断线(例如EXTI0),那么它们将触发同一个中断服务函数(ISR)。STM32的外部中断控制器设计允许多个GPIO引脚共享同一个中断线,因此它们的中断事件将被合并,并导向相同的ISR进行处理。
在ISR内部,你需要编写代码来检测是哪个GPIO引脚触发了中断。这通常通过读取外部中断状态寄存器来实现,该寄存器会指示哪个中断线被激活。一旦确定了触发中断的GPIO引脚,你就可以执行相应的操作或调用不同的回调函数来处理不同引脚的中断事件。
需要注意的是,虽然多个GPIO引脚可以共享同一个中断线,但它们的中断触发条件(如上升沿、下降沿或任意边沿)可以单独配置。因此,即使它们共用一个中断线,你也可以根据需要对每个GPIO引脚的中断触发方式进行不同的设置。
#include "stm32f10x.h" // 根据你的STM32系列和型号选择合适的头文件
// 假设我们已经配置好了PA0和PB0作为外部中断源,并且它们共用一个中断线EXTI0
// 中断服务函数
void EXTI0_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line0) != RESET) { // 检查EXTI_Line0是否有中断发生
// 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0);
// 检测是哪个GPIO引脚触发了中断
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) { // PA0被按下(假设为低电平触发)
// 处理PA0中断的逻辑
// ...
// 可以调用一个处理PA0中断的回调函数或执行相应操作
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) { // PB0被按下(假设为低电平触发)
// 处理PB0中断的逻辑
// ...
// 可以调用一个处理PB0中断的回调函数或执行相应操作
}
// 注意:如果两个引脚同时触发中断,上面的代码会分别处理两个中断事件
}
}
// 其他初始化代码和中断配置代码...
在这个例子中,EXTI_GetITStatus(EXTI_Line0)
用于检查EXTI_Line0上是否有中断发生。EXTI_ClearITPendingBit(EXTI_Line0)
用于清除中断标志位,以避免在下次中断发生时重复处理同一个中断事件。
接下来,使用GPIO_ReadInputDataBit
函数来读取GPIOA和GPIOB上对应引脚的输入数据。根据引脚的实际配置(高电平触发还是低电平触发),你可以检查引脚的状态。在这个例子中,我们假设中断是低电平触发的,因此检查引脚是否为0(低电平)。