单片机裸机编程:状态机与其他高效编程框架

news/2025/2/26 8:38:16

单片机裸机编程中,状态机是一种非常强大的工具,能够有效管理复杂的逻辑和任务切换。除了状态机,还有其他几种编程模式可以在不使用 RTOS 的情况下实现高效的程序设计。以下是一些常见的方法:

1. 状态机编程

状态机通过定义系统的不同状态和状态之间的转换规则,将复杂的逻辑分解为简单的状态和事件处理。它适用于事件驱动的系统,能够有效管理任务切换和逻辑复杂性。

实现思路:
  • 定义状态枚举类型。

  • 使用状态变量记录当前状态。

  • 在主循环中根据当前状态执行对应的任务。

  • 根据事件或条件触发状态转换。

示例代码:

typedef enum {
    STATE_IDLE,
    STATE_PROCESS_SENSOR,
    STATE_HANDLE_BUTTON,
    STATE_UPDATE_DISPLAY
} StateTypeDef;

StateTypeDef currentState = STATE_IDLE;

void process_sensor_data(void) {
    // 处理传感器数据
}

void handle_button_press(void) {
    // 处理按钮事件
}

void update_display(void) {
    // 更新显示
}

void main(void) {
    while (1) {
        switch (currentState) {
            case STATE_IDLE:
                if (sensor_data_ready) {
                    currentState = STATE_PROCESS_SENSOR;
                } else if (button_pressed) {
                    currentState = STATE_HANDLE_BUTTON;
                }
                break;

            case STATE_PROCESS_SENSOR:
                process_sensor_data();
                currentState = STATE_IDLE;
                break;

            case STATE_HANDLE_BUTTON:
                handle_button_press();
                currentState = STATE_IDLE;
                break;

            case STATE_UPDATE_DISPLAY:
                update_display();
                currentState = STATE_IDLE;
                break;
        }
    }
}

2. 时间片轮询(Super Loop + 定时器)

时间片轮询是一种模拟多任务调度的方法,通过定时器中断实现时间片的管理。每个任务被分配一个固定的时间片,在主循环中依次执行各个任务的一部分。当时间片用完时,切换到下一个任务。

实现思路:
  • 设置一个定时器中断,用于记录时间片的结束。

  • 在主循环中,根据时间片的计数器决定当前任务是否继续执行。

示例代码:

#define TASK_COUNT 3
#define TIME_QUANTUM 10  // 时间片大小,单位为毫秒

typedef struct {
    void (*taskFunc)(void);  // 任务函数指针
    int remainingTime;       // 剩余时间片
} TaskTypeDef;

TaskTypeDef tasks[TASK_COUNT] = {
    {task1, TIME_QUANTUM},
    {task2, TIME_QUANTUM},
    {task3, TIME_QUANTUM}
};

void task1(void) {
    // 执行任务1
}

void task2(void) {
    // 执行任务2
}

void task3(void) {
    // 执行任务3
}

void main(void) {
    int currentTask = 0;

    while (1) {
        if (tasks[currentTask].remainingTime > 0) {
            tasks[currentTask].taskFunc();  // 执行当前任务
            tasks[currentTask].remainingTime--;
        }
        currentTask = (currentTask + 1) % TASK_COUNT;  // 轮询下一个任务
    }
}

3. 中断驱动编程

中断驱动是一种利用单片机中断机制来处理事件的方法。通过配置中断源(如 GPIO、定时器、串口等),可以在事件发生时直接跳转到中断服务例程(ISR),从而实现快速响应。

实现思路:
  • 配置中断源,设置中断优先级。

  • 在中断服务例程中处理事件,避免在 ISR 中执行耗时操作。

示例代码:

void EXTI0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        // 处理按键中断事件
        EXTI_ClearITPendingBit(EXTI_Line0);
        handle_button_press();
    }
}

void handle_button_press(void) {
    // 按键处理逻辑
}

void main(void) {
    // 初始化中断
    NVIC_EnableIRQ(EXTI0_IRQn);

    while (1) {
        // 主循环可以执行其他任务
    }
}

4. 非阻塞式编程

非阻塞式编程通过轮询或定时器检测事件状态,而不是在事件未发生时阻塞程序。这种方式可以提高程序的响应速度,避免因等待某个事件而导致程序卡顿。

实现思路:
  • 使用定时器或计数器检测事件状态。

  • 在主循环中不断检查事件是否发生,并根据状态执行相应操作。

示例代码:

#include "bsp_dwt.h"  // 假设使用硬件定时器库

#define TIMEOUT 100000  // 超时时间,单位为微秒

void handle_button_press(void) {
    // 按键处理逻辑
}

void handle_timeout(void) {
    // 超时处理逻辑
}

void main(void) {
    uint32_t start_time = DWT_GetTimeline_us();  // 获取当前时间

    while (1) {
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == SET) {
            // 按键按下,执行任务
            handle_button_press();
            start_time = DWT_GetTimeline_us();  // 重置计时器
        }
        if (DWT_GetTimeline_us() - start_time > TIMEOUT) {
            // 超时处理
            handle_timeout();
            start_time = DWT_GetTimeline_us();  // 重置计时器
        }
    }
}

5. 超级循环(Super Loop)

超级循环是一种简单的多任务实现方式,通过在一个大循环中轮流执行不同的任务。每个任务函数执行一个任务的一部分,然后将控制权交给下一个任务。

实现思路:
  • 在主循环中按顺序调用各个任务函数。

  • 可以通过条件语句或计数器控制任务的执行频率。

示例代码:

void task1(void) {
    // 执行任务1
}

void task2(void) {
    // 执行任务2
}

void main(void) {
    while (1) {
        task1();
        task2();
    }
}

总结

单片机裸机编程中,状态机、时间片轮询、中断驱动、非阻塞式编程和超级循环都是常见的编程模式。它们各有优缺点,适用于不同的场景:

  • 状态机:适用于复杂逻辑和事件驱动的系统,能够有效管理任务切换和逻辑复杂性。

  • 时间片轮询:适合多任务并发但对实时性要求不高的场景。

  • 中断驱动:适合对实时性要求较高的系统。

  • 非阻塞式编程:适合需要快速响应多个事件的系统。

  • 超级循环:适合简单的多任务场景。


http://www.niftyadmin.cn/n/5868401.html

相关文章

C语言 —— 此去经年 应是良辰好景虚设 - 函数

目录 1. 函数的概念 1.1 库函数 1.2 自定义函数 2. 形参和实参 3. return 语句 4. 数组做函数参数 5. 嵌套调用和链式访问 5.1 嵌套调用 5.2 链式访问 6. 函数的声明和定义 6.1 单个文件 6.2 多个文件 7. static 和 extern 7.1 static 修饰局部变量 7.2 static 修…

Java GC 基础知识快速回顾

目录 一、Java 垃圾回收(GC)基本概念和重要性分析 (一) Java 垃圾回收(GC)基本概念回顾 1.GC 三种常见语义 2.Mutator:应用程序的内存管理角色 3.TLAB(线程本地分配缓存&#x…

数据结构与算法面试专题——桶排序

引入 桶排序,顾名思义,会用到“桶”,核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。 桶排序…

关于网关和ip地址怎么理解?

互联网各领域资料分享专区(不定期更新): Sheet 正文 网关和IP地址是计算机网络中的两个核心概念,它们共同协作实现设备之间的通信。以下是通俗易懂的解释: 1. IP地址(Internet Protocol Address) 作用: IP地址是网络中设备的“唯一标识符”,类似于现实中的门牌号。它…

[高等数学] 有理函数的积分

一、知识点 两个多项式的商 P ( x ) Q ( x ) \frac{P(x)}{Q(x)} Q(x)P(x)​ 称为有理函数,又称有理分式。 当分子多项式 P ( x ) P(x) P(x) 的次数小于分母多项式 Q ( x ) Q(x) Q(x) 的次数时,称这有理函数为真分式,否则称为假分式。 对…

transformer架构嵌入层位置编码之动态NTK-aware位置编码

前文,我们已经构建了一个小型的字符级语言模型,是在transformer架构基础上实现的最基本的模型,我们肯定是希望对该模型进行改进和完善的。所以我们的另外一篇文章也从数据预处理、模型架构、训练策略、评估方法、代码结构、错误处理、性能优化等多个方面提出具体的改进点,但…

简单介绍 SSL 证书类型: DV、OV、EV 的区别

SSL证书类型DV、OV、EV 区别: DV(域名验证型)SSL证书 OV(组织验证型)SSL证书 EV(扩展验证型)SSL证书

【云原生实战】DevOps基础与实战项目

🔎这里是【云原生实战】,关注我学习云原生不迷路 👍如果对你有帮助,给博主一个免费的点赞以示鼓励 欢迎各位🔎点赞👍评论收藏⭐️ 👀专栏介绍 【云原生实战】 目前主要更新微服务,…