0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

单片机软件定时器的实现方法

CHANBAEK 来源:固件工人 作者:固件工人 2023-01-17 15:14 次阅读

1.1 背景

目前市面上的单片机基本都带有硬件定时器功能,单片机应用程序开发中也经常会用到定时器进行一些和时间相关的开发,比如延时或者周期性地执行一些操作。单片机的硬件定时器个数一般都是固定的,而且一些低端单片机的定时器个数一般都比较少,在一些有多个周期性操作的应用场合就无法满足要求。这时,就可以基于硬件定时器派生出软件定时器,来满足这种多种周期性或多个单次延时操作的需求。软件定时器的优点就是个数可以根据实际需求进行灵活配置,而且可以实现多种不同的定时周期。

1.2 测试平台

这里使用的开发环境和相关硬件如下。

  • 操作系统:Ubuntu 20.04.2 LTS x86_64(使用uname -a命令查看)
  • 集成开发环境(IDE):Eclipse IDE for Embedded C/C++ Developers,Version: 2021-06 (4.20.0)
  • 硬件开发板:STM32F429I-DISCO
  • 本文对应的例程代码链接如下。

https://download.csdn.net/download/goodrenze/85106391

1.3 软件定时器实现方法

这里就结合开发板STM32F429I-DISCO上的STM32F429ZI的单片机来演示软件定时器的实现方法。

一般定时器的计数方式有2种:一种是单次定时,即定时时间到了之后,自动停止定时;另一种是周期定时,定时时间到了之后,自动按照之前的定时周期重新定时。对于周期定时,可以手动进行定时器的启动、关闭和删除。

下面讲解软件定时器的实现步骤。

1)由于软件定时器是基于硬件定时器的,所以需要先初始化一个硬件定时器,并启动硬件定时器。这里使用STM32F429ZI的硬件定时器7,定时器的定时周期为10ms,即每10ms产生一次定时器中断。初始化代码如下。

TIM_HandleTypeDef    Tim7Handle;
uint8_t InitTim7(uint32_t period_ms)
{
  uint16_t uwPrescalerValue;


  if(0 == period_ms)
  {
    return 1;
  }


  __HAL_RCC_TIM7_CLK_ENABLE();


  HAL_NVIC_SetPriority(TIM7_IRQn, 2, 0);


  HAL_NVIC_EnableIRQ(TIM7_IRQn);


  /* Compute the prescaler value to have TIM7 counter clock equal to 10 KHz */
  uwPrescalerValue = (uint32_t) ((SystemCoreClock /2) / 10000) - 1;


  /* Set TIM7 instance */
  Tim7Handle.Instance = TIM7;
  Tim7Handle.Init.Period = period_ms * 10 - 1;
  Tim7Handle.Init.Prescaler = uwPrescalerValue;
  Tim7Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  Tim7Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
  if(HAL_TIM_Base_Init(&Tim7Handle) != HAL_OK)
  {
    return 1;
  }


  /* Start the TIM Base generation in interrupt mode */
  if(HAL_TIM_Base_Start_IT(&Tim7Handle) != HAL_OK)
  {
    return 1;
  }


  return 0;
}

2)硬件定时器定时时间到了之后,会产生中断,所以需要实现定时器中断处理函数。这里基于STM32的HAL进行开发,所以在定时器的中断入口函数中直接调用HAL_TIM_IRQHandler()函数,然后实现实际的中断处理回调函数HAL_TIM_PeriodElapsedCallback()。对应的代码如下。其中调用的软件定时器更新函数会在后面介绍。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim == &Tim7Handle)
  {
    SwTimerUpdateCount();
  }
}

3)设计软件定时器对应的结构体。按照软件定时器的实际使用特点,必须要包含定时器计数值和周期定时器的重装载值,另外还要有定时器时间到之后需要执行的回调函数。对应的软件定时器结构体如下所示。当结构体中的TimerCount和TimerReload都为0时,说明软件定时器处于空闲状态,可以分配使用;如果TimerCount非0,而TimerReload为0,说明软件定时器是单次定时器;如果TimerCount和TimerReload都非0,说明软件定时器是周期定时器。

typedef void (*TimerCallbackFunc)(void);


typedef struct _SwTimer_t
{
  uint32_t TimerCount;
  uint32_t TimerReload;
  TimerCallbackFunc TimerCallback;
}SwTimer_t;

4)这里将软件定时器设置成10个,可以通过宏定义来设置软件定时器个数。使用软件定时器结构体定义一个具有10个定时器的数组。如下代码所示。

#define SW_TIMER_NUM    10
SwTimer_t SwTimer[SW_TIMER_NUM];

5)软件定时器复位函数,用于实现所有软件定时器的重置操作,重置后,所有软件定时器都处于空闲状态,可供分配使用。函数代码如下。

void SwTimerReset(void)
{
  uint8_t i;


  for(i = 0; i < SW_TIMER_NUM; i++)
  {
    SwTimer[i].TimerCount = 0;
    SwTimer[i].TimerReload = 0;
    SwTimer[i].TimerCallback = 0;
  }
}

6)启动单次定时器函数,用于实现单次定时,定时时间到了之后,执行对应回调函数,并停止定时和释放定时器资源。函数代码如下。如果启动成功,函数返回定时器的索引号(该值小于定时器个数值);启动失败,返回的定时器索引号等于定时器的个数。

uint8_t SwTimerStartSingleTimer(uint32_t single_ms, TimerCallbackFunc TimerCallback)
{
  uint8_t i;


  single_ms /= MINI_PERIOD_MS;
  if(0 == single_ms)
  {
    single_ms = 1;
  }
  for(i = 0; i < SW_TIMER_NUM; i++)
  {
    if((SwTimer[i].TimerCount == 0) && (SwTimer[i].TimerReload == 0))
    {
      SwTimer[i].TimerCount = single_ms;
      SwTimer[i].TimerCallback = TimerCallback;
      break;
    }
  }


  return i;
}

7)添加周期定时器函数,用于添加一个新的周期定时器但不启动定时,需要手动启动定时器。函数代码如下。如果添加成功,函数返回定时器的索引号(该值小于定时器个数值);添加失败,返回的定时器索引号等于定时器的个数。

uint8_t SwTimerAddPeriodTimer(uint32_t period_ms, TimerCallbackFunc TimerCallback)
{
  uint8_t i;


  period_ms /= MINI_PERIOD_MS;
  if(0 == period_ms)
  {
    period_ms = 1;
  }
  for(i = 0; i < SW_TIMER_NUM; i++)
  {
    if((SwTimer[i].TimerCount == 0) && (SwTimer[i].TimerReload == 0))
    {
      SwTimer[i].TimerReload = period_ms;
      SwTimer[i].TimerCallback = TimerCallback;
      break;
    }
  }


  return i;
}

8)启动周期定时器函数,用于启动指定定时器索引号的周期定时器开始定时。函数代码如下。如果启动成功,函数返回定时器的索引号(该值小于定时器个数值);启动失败,返回的定时器索引号等于定时器的个数。

uint8_t SwTimerStartPeroidTimer(uint8_t timer_no)
{
  if(SW_TIMER_NUM <= timer_no)
  {
    return SW_TIMER_NUM;
  }
  else if((SwTimer[timer_no].TimerCount == 0) && (SwTimer[timer_no].TimerReload == 0))
  {
    return SW_TIMER_NUM;
  }
  else
  {
    SwTimer[timer_no].TimerCount = SwTimer[timer_no].TimerReload;
    return timer_no;
  }
}

9)停止定时器函数,用于结束指定定时器索引号的定时器的定时,可用于停止单次或周期定时器。函数代码如下。如果停止成功,函数返回定时器的索引号(该值小于定时器个数值);停止失败,返回的定时器索引号等于定时器的个数。

uint8_t SwTimerStopTimer(uint8_t timer_no)
{
  if(SW_TIMER_NUM <= timer_no)
  {
    return SW_TIMER_NUM;
  }
  else
  {
    SwTimer[timer_no].TimerCount = 0;
    return timer_no;
  }
}

10)删除周期定时器函数,用于结束指定定时器索引号的周期定时器的定时,并释放定时器资源。函数代码如下。如果删除成功,函数返回定时器的索引号(该值小于定时器个数值);删除失败,返回的定时器索引号等于定时器的个数。

uint8_t SwTimerDeletePeroidTimer(uint8_t timer_no)
{
  if(SW_TIMER_NUM <= timer_no)
  {
    return SW_TIMER_NUM;
  }
  else
  {
    SwTimer[timer_no].TimerCount = 0;
    SwTimer[timer_no].TimerReload = 0;
    return timer_no;
  }
}

11)软件定时器计数值更新函数,用于更新每个已经启动定时的软件定时器的计数值,该函数必须在硬件定时器的中断处理函数中调用。函数的实现思路是:遍历所有的软件定时器,如果遍历到的定时器的计数值非0,则进行减1操作。如果减1后计数值为0,如果定时器的重装载值非0,说明是周期定时器,需要将计数值更新成对应的重装载值以便重新定时,同时执行对应的回调函数;如果定时器的重装载值是0,说明是单次定时器,执行完回调函数后自动停止定时并释放定时器资源。如果减1后计数值不为0,继续遍历更新后续的定时器,直到所有定时器都遍历完毕。函数流程图和对应代码如下。

图1 软件定时器计数值更新函数

void SwTimerUpdateCount(void)
{
  uint8_t i;


  for(i = 0; i < SW_TIMER_NUM; i++)
  {
    if(SwTimer[i].TimerCount != 0)
    {
      SwTimer[i].TimerCount -= 1;
      if(SwTimer[i].TimerCount == 0)
      {
        if(SwTimer[i].TimerReload != 0)
        {
          SwTimer[i].TimerCount = SwTimer[i].TimerReload;
        }
        if(SwTimer[i].TimerCallback != 0)
        {
          SwTimer[i].TimerCallback();
        }
      }
    }
  }
}

12)软件定时器的实际使用示例。代码如下。

int main(void)
{
  uint8_t no;


  HAL_Init();


  /* Configure the system clock to 168 MHz */
  SystemClock_Config();


  BSP_LED_Init(LED3);
  BSP_LED_Init(LED4);
  InitTim7(MINI_PERIOD_MS);
  SwTimerReset();
  no = SwTimerAddPeriodTimer(500, ToggleLed3);
  if(no < SW_TIMER_NUM)
  {
    SwTimerStartPeroidTimer(no);
  }
#if 1
  no = SwTimerAddPeriodTimer(1000, ToggleLed4);
  if(no < SW_TIMER_NUM)
  {
    SwTimerStartPeroidTimer(no);
  }
#else
  no = SwTimerStartSingleTimer(5000, ToggleLed4);
#endif


  while (1)
  {
  }
}
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    5998

    文章

    43963

    浏览量

    620694
  • STM32
    +关注

    关注

    2239

    文章

    10669

    浏览量

    348687
  • 软件定时器
    +关注

    关注

    0

    文章

    17

    浏览量

    6703
  • 定时器
    +关注

    关注

    23

    文章

    3146

    浏览量

    112021
  • 函数
    +关注

    关注

    3

    文章

    3863

    浏览量

    61303
收藏 人收藏

    评论

    相关推荐

    单片机定时器软件

    单片机定时器软件
    发表于 08-03 17:10

    51单片机定时器计算小软件

    51单片机定时器计算小软件,用起特别方便。
    发表于 12-23 23:12

    单片机定时器”的应用如何实现

    谁能以瑞萨电子的单片机--RX63N的电路板“GR-SAKURA”为例进行说明一下单片机定时器”的应用?
    发表于 03-05 08:36

    单片机定时器的工作原理是什么

    单片机定时器的工作原理是什么,在刚开始学习单片机的时候,那时候由于简单的51单片机资源比较少所以一直就觉得单片机
    发表于 07-14 07:14

    单片机T2定时器实现1秒精确定时

    单片机T2定时器实现1秒精确定时程序单片机T2定时器实现
    发表于 07-16 06:18

    单片机的系统定时器

    1.单片机的系统定时器也称为滴答定时器,能够实现精准定时。2.stm32f1XX和stm32f4XX的系统
    发表于 08-19 06:46

    基于51单片机定时器查询方式

    设计思路。这样自己拿到任何型号的51单片机,只要有原理图,都可以自主设计。博主刚接触单片机,才疏学浅,可能会出现设计不足和错误,欢迎大家评论区交流。^ _ ^/********************************************************
    发表于 11-10 08:49

    单片机只用定时器实现秒表的方法

    目前常用的单片机中往往都配备了定时器/计数。在AT89S52芯片内包含有三个16位的定时器/计数:T0、T1和T2,其核心是加1计数
    发表于 12-02 06:03

    关于定时器/计数实现定时功能的几种方法

    :(1)软件定时:不占用硬件资源,但占用了CPU时间,降低了CPU利用率(2)定时器…3.关于52系列单片机和15系列单片机
    发表于 01-17 06:47

    单片机视频教程06:使用定时器方法

    《手把手教你学单片机单片机视频教程06:使用定时器方法 单片机视频教程06:使用定时器
    发表于 08-21 09:33 1.6w次阅读
    <b class='flag-5'>单片机</b>视频教程06:使用<b class='flag-5'>定时器</b>的<b class='flag-5'>方法</b>

    基于51单片机定时器2的操作与实现

    基于51单片机定时器2的操作与实现,51单片机定时器2的使用!
    发表于 02-22 17:53 12次下载

    52单片机有几个定时器?52单片机定时器1和52单片机定时器2程序对比

    52单片机有几个定时器?STC89C52RC其实是有三个定时器单片机,STC89C52RC共有3个定时器,分别是T0、T1、T2。而51
    发表于 11-10 14:30 3.4w次阅读

    基于单片机定时器的设计方法

    单片机实现一个定时器只要对单片机里的特殊寄存器进行设置就可以实现了,下面我与朋友们说说这个0到9.9秒
    的头像 发表于 11-02 16:58 9868次阅读
    基于<b class='flag-5'>单片机</b>的<b class='flag-5'>定时器</b>的设计<b class='flag-5'>方法</b>

    51单片机定时器中断

    定时器介绍:51单片机定时器属于单片机的内部资源,其电路连接和运转均在单片机的内部完成定时器
    发表于 11-11 19:36 7次下载
    51<b class='flag-5'>单片机</b><b class='flag-5'>定时器</b>中断

    51单片机定时器中断

    定时器介绍:51单片机定时器属于单片机的内部资源,其电路连接和运转均在单片机的内部完成定时器
    发表于 11-20 20:36 15次下载
    51<b class='flag-5'>单片机</b><b class='flag-5'>定时器</b>中断