在我自己的实际项目中,需要用到定时器进行精确计时,但是出于高实时性的考虑,不能让
单片机频繁进入中断,哪怕只是几百毫秒的长间隔中断也不好,最好是一直在主循环中轮询定时器状态寄存器来查询溢出事件,这种用法常用于一些跟传感器有密切交互的场合,比如GPS模块,心跳传感器,这些场合下不单单只是接收传感器数据,还需要在接收到特定指令或数据后延时规定长度,比如100毫秒,500毫秒,1秒,所以使用非中断事件是最好的选择,但是我现在还没有往探索者V3板接入实际的传感器,所以重置定时器事件我用按键实现。
手动或外部事件重置定时器机制很简单,只需要用到CR1寄存器的CEN位即可:
当定时器计数值溢出后,SR状态寄存器的末位会有反应,也就是会变成1:
知道了这两点,想要实现按键手动重置就非常简单了,首先是初始化定时器,计数模式向上向下都可,没有区别,重要的是分频系数写8400-1,因为定时器6 7 12的满配时钟都是84MHz,分频系数写8400-1的话,也就是一秒钟计数10K次,即一万次,那么重装载值写10000就是1秒触发一次更新事件,5000就是0.5秒触发一次更新事件,以此类推,我这里分别设置定时器6 100毫秒触发一次更新事件,定时器7 500毫秒,定时器12 1秒:
- void tiM6_NO_INT_Init(uint16_t arr, uint16_t psc)
- {
- __HAL_RCC_TIM6_CLK_ENABLE();
-
- htim6.Instance = TIM6;
- htim6.Init.Period = arr;
- htim6.Init.Prescaler = psc;
- htim6.Init.CounterMode = TIM_COUNTERMODE_DOWN;
- htim6.Init.ClockDivision = 1;
- HAL_TIM_Base_Init(&htim6);
-
- TIM6->CNT = 65535;
- TIM6->CR1 &= (~TIM_CR1_CEN);
- //TIM6->CR1 |= TIM_CR1_CEN;
- TIM6->SR &= ~TIM_FLAG_UPDATE;
- }
- void TIM7_NO_INT_Init(uint16_t arr, uint16_t psc)
- {
- __HAL_RCC_TIM7_CLK_ENABLE();
-
- htim7.Instance = TIM7;
- htim7.Init.Period = arr;
- htim7.Init.Prescaler = psc;
- htim7.Init.CounterMode = TIM_COUNTERMODE_DOWN;
- htim7.Init.ClockDivision = 1;
- HAL_TIM_Base_Init(&htim7);
-
- TIM7->CNT = 65535;
- TIM7->CR1 &= (~TIM_CR1_CEN);
- //TIM7->CR1 |= TIM_CR1_CEN;
- TIM7->SR &= ~TIM_FLAG_UPDATE;
- }
- void TIM12_NO_INT_Init(uint16_t arr, uint16_t psc)
- {
- __HAL_RCC_TIM12_CLK_ENABLE();
-
- htim12.Instance = TIM12;
- htim12.Init.Period = arr;
- htim12.Init.Prescaler = psc;
- htim12.Init.CounterMode = TIM_COUNTERMODE_DOWN;
- htim12.Init.ClockDivision = 1;
- HAL_TIM_Base_Init(&htim12);
-
- TIM12->CNT = 65535;
- TIM12->CR1 &= (~TIM_CR1_CEN);
- //TIM12->CR1 |= TIM_CR1_CEN;
- TIM12->SR &= ~TIM_FLAG_UPDATE;
- }
- TIM6_NO_INT_Init(999 , 8399 - 1);
- TIM7_NO_INT_Init(4999 , 8399 - 1);
- TIM12_NO_INT_Init(9999 , 8399 - 1);
然后加入按键例程代码,设计为按键重置定时器计数:
- while(1)
- {
- ret = USER_KEY_Scan();
- if (ret)
- {
- switch (ret)
- {
- case KEY0_PRES:
- printf(\\\\\\\"KEY0_PRES\\\\\\\\n\\\\\\\");
- TIM6->CR1 |= TIM_CR1_CEN;
- break;
- case KEY1_PRES:
- printf(\\\\\\\"KEY1_PRES\\\\\\\\n\\\\\\\");
- TIM7->CR1 |= TIM_CR1_CEN;
- break;
- case KEY2_PRES:
- printf(\\\\\\\"KEY2_PRES\\\\\\\\n\\\\\\\");
- TIM12->CR1 |= TIM_CR1_CEN;
- break;
- default : break;
- }
- }
- if(TIM6->SR & TIM_FLAG_UPDATE)
- {
- TIM6->SR &= ~TIM_FLAG_UPDATE;
- TIM6->CR1 &= (~TIM_CR1_CEN);
- printf(\\\\\\\"TIM6 TIM_FLAG_UPDATE TIM6->CNT = %d\\\\\\\\n\\\\\\\" , TIM6->CNT);
- }
- if(TIM7->SR & TIM_FLAG_UPDATE)
- {
- TIM7->SR &= ~TIM_FLAG_UPDATE;
- TIM7->CR1 &= (~TIM_CR1_CEN);
- printf(\\\\\\\"TIM7 TIM_FLAG_UPDATE TIM7->CNT = %d\\\\\\\\n\\\\\\\" , TIM7->CNT);
- }
- if(TIM12->SR & TIM_FLAG_UPDATE)
- {
- TIM12->SR &= ~TIM_FLAG_UPDATE;
- TIM12->CR1 &= (~TIM_CR1_CEN);
- printf(\\\\\\\"TIM12 TIM_FLAG_UPDATE TIM12->CNT = %d\\\\\\\\n\\\\\\\" , TIM12->CNT);
- }
- }
运行效果,当按键按下之后,三个定时器对应三个不同按键分别工作,在延时规定时间(100毫秒 500毫秒 1秒)后,触发更新事件:
成功使用了通用定时器进行轮询外部事件计时后,现在使用同样的原理实现延时,唯一区别就是这个延时是阻塞的,而计时是非阻塞的,用途不同,可以用在需要阻塞延时的场合,使用通用定时器13实现。
先看看代码:
- void TIM13_NO_INT_Init(uint16_t arr, uint16_t psc)
- {
- __HAL_RCC_TIM13_CLK_ENABLE();
-
- htim13.Instance = TIM13;
- htim13.Init.Period = arr;
- htim13.Init.Prescaler = psc;
- htim13.Init.CounterMode = TIM_COUNTERMODE_UP;
- htim13.Init.ClockDivision = 1;
- HAL_TIM_Base_Init(&htim13);
-
- //TIM13->CR1 &= (~TIM_CR1_CEN);
- TIM13->CR1 |= TIM_CR1_CEN;
- TIM13->SR &= ~TIM_FLAG_UPDATE;
- }
- void TIM13_Delay_us(unsigned int delay_us)
- {
- TIM13->CNT = 0;
- while (TIM13->CNT < delay_us);
- }
- void TIM13_Delay_ms(uint32_t delay_ms)
- {
- while (delay_ms--)
- {
- TIM13_Delay_us(1000);
- }
- }
原理非常简单,也是使用通用定时器的计数寄存器CNT,当CNT计数未达到规定数时,使用死循环使CPU一直阻塞,systick也是类似的原理。