在裸机编程中,我们常常需要定时执行一些任务,比如让LED以固定频率闪烁、周期性检测按键输入等。这类需求看似简单,但处理方式不当(例如使用阻塞延时)就会导致系统“卡顿”或响应迟钝。
本文将介绍一种推荐的裸机延时结构:任务分离计数器法(非阻塞延时),并通过LED闪烁与按键响应为例,展示其实际应用效果。
一、阻塞延时与非阻塞延时的区别
阻塞延时
led_on();
delay_ms(500); // 阻塞CPU
led_off();
delay_ms(500);
缺点:
在
delay_ms
期间CPU无法做任何其他事情无法响应其他任务,比如按键、串口等
系统实时性差,功能耦合
非阻塞延时
非阻塞延时通过计时判断是否到了任务的执行时间,而不会让CPU等待。这样可以在主循环中“轮询执行”多个任务,各任务之间互不干扰。
两种实现方式:
方法 | 描述 |
---|---|
对比全局时间差 | 比如 if(sys_tick - last_tick >= 500) |
分离计数器法(推荐) | 每个任务维护独立计数变量,如 count_led++ ,周期到了就触发 |
本文使用任务分离计数器法,逻辑更清晰,易于扩展和维护。
二、设计思路概览
使用SysTick定时器或硬件定时器生成1ms节拍;
为每个任务分配独立周期变量和计数器变量;
在中断中对各自的计数器进行自增;
在主循环中判断是否达到设定周期。
三、变量与定时器配置
全局变量定义:
#include <stdint.h>
volatile uint16_t count_led = 0; // LED计数器
volatile uint16_t count_key = 0; // 按键计数器
uint16_t time_led = 500; // LED周期(ms)
uint16_t time_key = 20; // 按键扫描周期(ms)
SysTick中断配置(以STM32为例):
void SysTick_Handler(void)
{
count_led++;
count_key++;
}
void systick_init(void)
{
SysTick_Config(SystemCoreClock / 1000); // 配置1ms节拍
}
如果使用的是其他平台,可替换为普通定时器中断,每1ms中断一次即可。
四、LED任务(非阻塞控制)
void led_task(void)
{
if (count_led >= time_led)
{
count_led = 0;
led_toggle(); // 用户自定义函数:翻转LED状态
}
}
五、按键任务(非阻塞扫描 + 消抖)
void key_task(void)
{
if (count_key >= time_key)
{
count_key = 0;
static uint8_t last_key = 1;
uint8_t key_now = read_key(); // 用户自定义函数,读取按键引脚
if (last_key == 1 && key_now == 0)
{
// 按键按下动作
led_toggle(); // 示例:按键触发LED翻转
}
last_key = key_now;
}
}
六、主函数结构
int main(void)
{
system_init(); // 初始化系统时钟、IO、LED、按键等
systick_init(); // 配置SysTick为1ms节拍
while (1)
{
led_task(); // LED非阻塞控制
key_task(); // 按键非阻塞扫描
// 可继续添加其他任务...
}
}
七、方法优势总结
特性 | 表现 |
---|---|
多任务并行 | 各任务独立运行 |
CPU效率 | 无阻塞等待 |
可扩展性 | 添加任务只需新增变量和判断 |
逻辑清晰 | 每个任务结构简单直观 |
每个任务都有自己的计数器和周期变量 在SysTick中断(定时器中断)中统一更新 在主循环中轮询判断执行 高效、灵活、适合各种裸机场景
可扩展应用
增加多个任务(如蜂鸣器、OLED刷新、传感器采集);
周期变量可动态调整,实现可调频率;
可封装成任务结构体,实现裸机“轻量任务调度器”。
总结一句话:
用非阻塞延时 + 任务分离计数器,就能在裸机系统中实现“并行任务”的控制结构,轻松应对多个定时控制需求。