精妙的单片机非阻塞延时程序设计

本文介绍了在单片机项目中实现非阻塞定时延时的方法,通过使用systick定时中断和自定义数据结构,提高了程序效率并简化了任务定时逻辑。展示了如何在主轮询中调用定时处理函数,实现多个不同时间间隔的任务处理,同时保持代码清晰易读。

http://blog.chinaunix.net/uid-29673749-id-4425603.html

  对于每个单片机爱好者及工程开发设计人员,在刚接触单片机的那最初的青葱岁月里,都有过点亮跑马灯的经历。从看到那一排排小灯按着我们的想法在跳动时激动心情。到随着经验越多,越来又会感觉到这个小灯是个好东西,尤其是在调试资源有限的环境中,有时会帮上大忙。


但对于绝大多数人,我们在最最初让灯闪烁起来时大约都会用到阻塞延时实现,会像如下代码的样子:
  1. while(1)
  2. {
  3.     LED =OFF;
  4.     Delay_ms(500);
  5.     LED = ON;
  6.     Delay_ms(500);
  7. }
然后,在我们接触到定时器,我们会发现,原来用定时中断来处理会更好。比如我们可以500ms中断一次,让灯亮或灭,其余的时间系统还可以做非常之多的事情,效率一下提升了很多。

这时我们就会慢慢意识到,第一种(阻塞延时)方法效率很低,让芯片在那儿空运行几百毫米,什么也不做,真是莫大的浪费,尤其在芯片频率较高,任务又很多时,这样做就像在平坦宽阔的高速公路上挖了一大坑,出现事故可想而知。

但一个单片机中的定时器毕竟有限,如果我需要几十个或者更多不同时间的定时中断,每一个时间到都完成不同的处理动作,如何去做呢。一般我们会想到在一个定时中断函数中再定义static 变量继续定时,到了所需时间,做不同的动作。而这样又会导致在一个中断里做了很多不同的事情,会抢占主轮询更多时间,有时甚至喧宾夺主,并也不是很如的思维逻辑。

那么有没有更好的方法来实现呢,答案是肯定的。下面介绍我在一个项目中偶遇,一个精妙设计的非阻塞定时延时软件的设计(此设计主要针对于无操作系统的裸机程序)。

在上篇文章中有对systick的介绍,比如我要设置其10ms中断一次,如何实现呢?
也很简单,只需调用 core_cm3.h文件中 SysTick_Config 函数 ,当系统时钟为72MHZ,则设置成如下即可 SysTick_Config(720000 ); (递减计数720000次后中断一次) 。此时SysTick_Handler中断函数就会10ms进入一次;

任务定时用软件是如何设计的呢 ? 
且先看其数据结构,这也是精妙所在之处,在此作自顶向下的介绍:

其定义结构体类型如:
  1. typedef struct
  2. {
  3.     uint8_t Tick10Msec;
  4.     Char_Field Status;
  5. } Timer_Struct;
其中Char_Field 为一联合体,设计如下:
  1. typedef union
  2. {
  3.     unsigned char byte;
  4.     Timer_Bit field;
  5. } Char_Field
而它内部的Timer_Bit是一个可按位访问的结构体:
  1. typedef struct
  2. {
  3.     unsigned char bit0: 1;
  4.     unsigned char bit1: 1;
  5.     unsigned char bit2: 1;
  6.     unsigned char bit3: 1;
  7.     unsigned char bit4: 1;
  8.     unsigned char bit5: 1;
  9.     unsigned char bit6: 1;
  10.     unsigned char bit7: 1;
  11. } Timer_Bit
此联合体的这样设计的目的将在后面的代码中体现出来。
如此结构体的设计就完成了。

然后我们定义的一全局变量,Timer_Struct  gTimer;

并在头文件中宏定义如下:
  1. #define bSystem10Msec        gTimer.Status.field.bit0
  2. #define bSystem50Msec        gTimer.Status.field.bit1
  3. #define bSystem100Msec       gTimer.Status.field.bit2
  4. #define bSystem1Sec          gTimer.Status.field.bit3
  5. #define bTemp10Msec          gTimer.Status.field.bit4
  6. #define bTemp50Msec          gTimer.Status.field.bit5
  7. #define bTemp100Msec         gTimer.Status.field.bit6
  8. #define bTemp1Sec            gTimer.Status.field.bit
另外为了后面程序清晰,再定义一状态指示:
  1. typedef enum
  2. {
  3.     TIMER_RESET = 0,
  4.     TIMER_SET = 1,
  5. } TimerStatus;
至此,准备工作就完成了。下面我们就开始大显神通了!

首先,10ms定时中断处理函数如,可以看出,每到达10ms 将把bTemp10Msec置1,每50ms 将把bTemp50Msec 置1,每100ms 将把bTemp100Msec 置1,每1s 将把bTemp1Sec 置1,
  1. void SysTick_Handler(void)
  2. {
  3.          
  4.         bTemp10Msec = TIMER_SET;
  5.         
  6.         ++gTimer.Tick10Msec;
  7.         if (== (gTimer.Tick10Msec % 5))
  8.         {
  9.             bTemp50Msec = TIMER_SET;
  10.         }
  11.         
  12.         if (== (gTimer.Tick10Msec % 10))
  13.         {
  14.             bTemp100Msec = TIMER_SET;
  15.         }
  16.         
  17.         if (100 == gTimer.Tick10Msec)
  18.         {
  19.             gTimer.Tick10Msec = 0;
  20.             bTemp1Sec = TIMER_SET;
  21.         }
  22. }
而这又有什么用呢 ?

这时,我们需在主轮询while(1)内最开始调用一个定时处理函数如下:
  1. void SysTimer _Process(void)
  2. {
  3.     gTimer.Status.byte &= 0xF0;
  4.     
  5.     if (bTemp10Msec)
  6.     {
  7.         bSystem10Msec = TIMER_SET;
  8.     }
  9.     
  10.     if (bTemp50Msec)
  11.     {
  12.         bSystem50Msec = TIMER_SET;
  13.     }
  14.     
  15.     if (bTemp100Msec)
  16.     {
  17.         bSystem100Msec = TIMER_SET;
  18.     }
  19.     
  20.     if (bTemp1Sec)
  21.     {
  22.         bSystem1Sec = TIMER_SET;
  23.     }
  24.     
  25.     gTimer.Status.byte &= 0x0F;
  26. }
此函数开头与结尾两句
  1. gTimer.Status.byte &= 0xF0;
  2. gTimer.Status.byte &= 0x0F
就分别巧妙的实现了bSystemXXX (低4位) 和 bTempXXX(高4位)的清零工作,不用再等定时到达后还需手动把计数值清零。此处清零工作用到了联合体中的变量共用一个起始存储空间的特性。

但要保证while(1)轮询时间要远小于10ms,否则将导致定时延时不准确。这样,在每轮询一次,就先把bSystemXXX ,再根据bTempXXX判断是否时间到达,并把对应的bSystemXXX 置1,而后面所有的任务就都可以通过bSystemXXX 来进行定时延时,在最后函数退出时,又会把bTempXXX清零,为下一次时间到达后查询判断作好了准备。

说了这么多,举例说明一下如何应用:
  1. void Task_A_Processing(void)
  2. {
  3.     if(TIMER_SET == bSystem50Msec){
  4.         //do something
  5.     }
  6. }

  7. void Task_B_Processing(void)
  8. {
  9.     if(TIMER_SET == bSystem100Msec){
  10.         //do something
  11.     }
  12. }

  13. void Task_C_Processing(void)
  14. {
  15.     static uint8_t ticks = 0;
  16.     if(TIMER_SET == bSystem100Msec){
  17.        ticks ++ ;
  18.     }

  19.     if(== ticks){
  20.         ticks = 0;
  21.          //do something
  22.     }

  23. }

  24. void Task_D_Processing(void)
  25. {
  26.     if(TIMER_SET == bSystem1Sec){
  27.         //do something
  28.     }
  29. }
以上示例四个任务进程,

在主轮询里可进行如下处理:
  1. int main(void)
  2. {
  3.     while(1)
  4.     {
  5.         SysTimer _Process();

  6.         Task_A_Processing();
  7.         Task_B_Processing();
  8.         Task_C_Processing();
  9.         Task_D_Processing();

  10.     }
  11. }
这样,就可以轻松且清晰实现了多个任务,不同时间内处理不同事件。(但注意,每个任务处理中不要有阻塞延时,也不要处理过多的事情,以致处理时间较长。可设计成状态机来处理不同任务。)
<think>好的,我现在需要帮助用户找到关于STM32实现非阻塞延时函数的方法和代码示例。用户之前提供的引用资料主要涉及阻塞式延时函数,比如使用循环或SysTick定时器。但用户现在明确要求非阻塞的方法,所以我得回忆一下相关知识,并结合引用内容思考如何整合信息。 首先,非阻塞延时的核心是不占用CPU资源,允许程序在等待期间执行其他任务。通常,这可以通过状态机、定时器中断或系统滴答定时器(SysTick)来实现。在用户提供的引用中,引用[1]提到了SysTick的初始化和中断,这可能与非阻塞延时有关。引用[3]和[4]中的代码都是阻塞式的,比如循环延时,所以需要排除这些方法。 接下来,我需要考虑如何利用SysTick或硬件定时器来记录时间流逝,从而判断延时是否完成。例如,使用全局变量来存储时间戳,然后在主循环中检查当前时间是否超过预设的延时时间。这种方法需要定时器提供精确的时间基准,比如每毫秒递增一个计数器。 用户提供的引用[1]中的Delay_Init函数初始化了SysTick,可能用于产生定时中断。这里可以设想,在中断服务例程中更新一个全局时间变量,比如uwTick,类似于HAL库中的做法。然后,非阻塞延时函数可以通过比较当前uwTick和开始时间来判断是否完成延时。 另外,引用[3]和[4]中提到了循环延时,但这些都是阻塞的,不适合非阻塞需求。所以,正确的方向应该是基于定时器中断的方法。需要确保代码示例中使用的是状态检查而非等待循环。 可能还需要考虑多任务情况下的时间管理,例如多个延时任务同时进行。因此,每个延时实例需要独立的开始时间和延时长度,可能通过结构体或数组来管理。 最后,我需要确保代码示例正确使用了SysTick,并且有初始化和更新时间戳的部分。同时,需要说明如何调用这些函数,比如在main循环中检查时间是否到达,从而执行相应的操作而不阻塞其他代码。</think>### STM32非阻塞延时函数实现方法 非阻塞延时的核心思想是通过**时间戳比对**实现延时判断,无需占用CPU资源。以下是三种常用实现方式及代码示例: --- ### 方法一:SysTick滴答定时器方案 #### 实现原理 利用SysTick定时器每1ms产生中断更新全局时间戳,通过比对时间差实现非阻塞判断[^1][^3] ```c // delay.h volatile uint32_t uwTick = 0; // 全局时间戳 void Delay_Init(void) { SysTick_Config(SystemCoreClock / 1000); // 1ms中断 } uint32_t HAL_GetTick(void) { return uwTick; } // SysTick中断服务函数 void SysTick_Handler(void) { uwTick++; } // 非阻塞延时判断函数 uint8_t Delay_Elapsed(uint32_t *prev_tick, uint32_t delay) { if(HAL_GetTick() - *prev_tick >= delay) { *prev_tick = HAL_GetTick(); return 1; } return 0; } ``` #### 使用示例 ```c uint32_t led_timestamp = 0; while(1) { if(Delay_Elapsed(&led_timestamp, 500)) { // 500ms间隔 LED_Toggle(); // 非阻塞执行LED翻转 } // 此处可执行其他任务 } ``` --- ### 方法二:硬件定时器方案 #### 实现原理 使用通用定时器(如TIM2)实现高精度计时,通过捕获比较寄存器自动更新时间戳[^1] ```c // 启用TIM2定时器(1MHz时钟) void TIM2_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitTypeDef timer; timer.TIM_Prescaler = 72-1; // 72MHz/72=1MHz timer.TIM_CounterMode = TIM_CounterMode_Up; timer.TIM_Period = 0xFFFFFFFF; TIM_TimeBaseInit(TIM2, &timer); TIM_Cmd(TIM2, ENABLE); } // 获取当前计时值 uint32_t Get_Micros(void) { return TIM2->CNT; } // 微秒级非阻塞判断 uint8_t Delay_NonBlocking(uint32_t *prev_time, uint32_t us) { if(Get_Micros() - *prev_time >= us) { *prev_time = Get_Micros(); return 1; } return 0; } ``` --- ### 方法三:状态机方案 #### 实现原理 通过结构体封装延时状态,支持多任务并行处理[^2] ```c typedef struct { uint32_t start_time; uint32_t duration; uint8_t is_running; } NonBlockDelay_t; void Delay_Start(NonBlockDelay_t *delay, uint32_t ms) { delay->start_time = HAL_GetTick(); delay->duration = ms; delay->is_running = 1; } uint8_t Delay_Check(NonBlockDelay_t *delay) { if(delay->is_running && (HAL_GetTick() - delay->start_time >= delay->duration)) { delay->is_running = 0; return 1; } return 0; } ``` #### 使用示例 ```c NonBlockDelay_t led_delay, sensor_delay; Delay_Start(&led_delay, 500); // LED延时500ms Delay_Start(&sensor_delay, 2000); // 传感器延时2s while(1) { if(Delay_Check(&led_delay)) { LED_Toggle(); Delay_Start(&led_delay, 500); // 重启延时 } if(Delay_Check(&sensor_delay)) { Read_Sensor(); Delay_Start(&sensor_delay, 2000); } } ``` --- ### 关键点对比 | 方法 | 精度 | 资源占用 | 适用场景 | |-------------|-------|----------|------------------------| | SysTick | 1ms | 低 | 常规任务调度 | | 硬件定时器 | 1μs | 中 | 高精度控制 | | 状态机 | 1ms | 低 | 多任务并行管理 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值