定时器分为硬件定时器和软件定时器,几乎所有的微控制器上都配备了数量有限的硬件定时器,即控制器本身有专门实现定时的模块。几乎所有的硬件定时器的工作原理都是一样的:定时器在外部时钟提供的周期脉冲下进行计数工作,当计数到用户指定的次数时,就产生一次中断。这个过程完全由微控制器内部的定时器硬件电路实现,不需要CPU干预。
相比之下,软件定时器则需要 CPU 的介入来实现了。实现软件定时器一般有两种方法:一种是纯粹依赖 CPU 指令的堆积来实现;另一种是以硬件定时器产生的时间片为基准单位,CPU 基于这个基准单位进行累积来实现。
显然,硬件定时器的精度取决于驱动的时钟脉冲,一般情况下可以达到很高的精度(纳秒级),而软件定时器的实现由于引入了非硬件因素,精度必然有所下降。
在嵌入式应用中,经常使用定时器进行定时,当定时时间到达之后执行预定的操作。一个具体的嵌入式系统可能有几个甚至数十个定时应用,而这些应用对定时器的精度、最大周期等要求往往都是不同的。比如使用定时器产生一个准确频率的方波是对定时精度比较高的应用,而使用定时器定时翻转一个用户指示灯以表示当前设备的工作状态,则对定时器精度的要求大为下降,这时如果启用硬件定时器无疑是一种资源的浪费。所以,在一个具体的嵌入式系统中,硬件定时器和软件定时器配合使用,是提高性能和降低成本的有效方法。
那么,在 STM32 中如何利用一个硬件定时器构建多个软件定时器呢?
我们来分析软件定时器的基本需求:在嵌入式应用中,对定时器的使用模式一般是“定时时间到达后执行预定的操作”。所以需要解决如下一些问题:
- 定时多久?——定时时间必须是可配置修改的;
- 定时时间到达后做什么?——需要指定执行函数;
- 定时器如何工作?——可以选择单次执行或循环执行。
我们看看程序中怎么实现
采用滴答定时器1ms中断作为心跳或者时基
1)初始化软件定时器
/* 软件定时器初始化,清零定时器队列(8个结构体数组对应8个定时器) */
#ifdef ZL_USING_SOFT_TIMER
bsp_soft_timer_init();
#endif
下面是软件定时器的成员
typedef struct
{
uint32_t wTimer; /* 定时时间计数器 */
uint16_t hwCtrl; /* 定时器开关控制 */
uint32_t wRunCnt; /* 运行计数器 */
zlfn_st_t fn_call_back; /* 回调函数 */
} zl_SoftTimer_t, *ptSoftTimer;
/* Z L _ S O F T _ T I M E R _ I N I T */
/*-------------------------------------------------------------------------
* 功能:初始化软件定时器模块。
* 参数:无
* 返回:无
* 备注:OK.
-------------------------------------------------------------------------*/
void bsp_soft_timer_init(void)
{
s_wSoftTimerInitOK = 0;
zl_memory_clear((uint8_t*)s_tSoftTimer, sizeof(s_tSoftTimer)); //s_tSoftTimer为软件定时器队列并清0操作
s_wSoftTimerInitOK = 1;
}
2)如何启动软件定时器
static void _mcu_run_led_init (void)
{
st_gpio_out_pp_init(_MCU_RUNLED_PORT, _MCU_RUNLED_PIN);
/* 启动定时器编号5任务函数*/
zl_soft_timer_set(SFTIMER_ID_runled, 1 * 1000, &_mcu_run_led_cb);
}
形参有定时器编号,定时时间,回调函数(就是执行的任务函数)
3)滴答定时器中断 对每个定时器时间减一操作/*-------------------------------------------------------------------------
*功能:APP 时标心跳服务,由心跳中断调用。
*参数:无
*返回:无
*备注:ok.
-------------------------------------------------------------------------*/
void app_tick(void)
{
/*每隔1ms进来一次(仅用于滴答延时)*/
st_systick_delay_tick();
/*软件定时器任务对每个定时器的定时时间减1操作*/
#ifdef ZL_USING_SOFT_TIMER
zl_soft_timer_tick();
#endif
/*一个延时模块的驱动任务*/
#ifdef ZL_USING_POLL_DELAY
zl_poll_delay_tick();
#endif
/* 调用所有用户相关的时标 */
app_user_tick();
}
* 功能:软件定时器任务,在心跳中断里运行。
* 参数:无
* 返回:无
* 备注:OK.
该函数的运行周期决定定时器的时基。
-------------------------------------------------------------------------*/
void zl_soft_timer_tick(void)
{
uint16_t i;
if (!s_wSoftTimerInitOK)
{
return;
}
for (i = 0; i < ZL_SOFT_TIMER_MAX_NUM; i++)
{
if (s_tSoftTimer.hwCtrl)/*判断定时开关*/
{
if (0 == s_tSoftTimer.wTimer)/*判断定时时间到没*/
{
s_tSoftTimer.hwCtrl = STIMER_STOP; /* 定时时间到就停掉定时器 */
s_tSoftTimer.wRunCnt++; /* 更新运行计数器,定时时间到以后加1操作 */
}
else
{
s_tSoftTimer.wTimer--;
}
}
}
}
4)超级循环执行多个定时器任务函数
/*-------------------------------------------------------------------------
* 功能:软件定时器服务,在超级循环里运行。
* 参数:无
* 返回:无
* 备注:OK.
-------------------------------------------------------------------------*/
void zl_soft_timer_service(void)
{
uint16_t i;
if (!s_wSoftTimerInitOK)
{
return;
}
/*对软件定时器个数加1操作*/
for (i = 0; i < ZL_SOFT_TIMER_MAX_NUM; i++)
{
if (s_tSoftTimer.wRunCnt)
{
s_tSoftTimer.fn_call_back(); /* 执行回调函数 */
s_tSoftTimer.wRunCnt--; /* 对每个定时器中的运行计数器减1操作*/
}
}
}
备注说明:如需源码,请在评论区回复邮箱获取,谢谢!