单片机软件开发的三层天梯详解:成为大师所需的步骤与策略(深度解析文章,约5000字)

雷猴啊~我是你们那个有点毒舌但靠谱的技术朋友。

最近内容越来越多,头发越来越少,又爆肝2天2夜,写了一篇5000字的文章,先点赞收藏起来,以后这样写代码,经理看到都敬你三分。

今天咱们来聊聊单片机软件的三个层次,看看你到底是刚摸到门的新手,还是有点追求的进阶玩家,亦或是已经站在山顶俯视众生的大佬。

别紧张,这不是考试,也不是要给你打分,就是随便聊聊,顺便让你照照镜子,看看自己站哪块地。

单片机开发这玩意儿,说白了跟做菜差不多,有人刚学会炒蛋,有人能做满汉全席,还有人能把炒蛋玩出花来。

为了让大家更直观地理解这三个层次的区别,咱们以无际单片机的防盗报警主机项目部分功能为例,声明下,只是举例简化版的伪代码,并非项目实际代码。

  1. 第一层萌新:能跑就行,别管啥样

刚开始玩单片机的时候,谁不是从新手村摸爬滚打出来的?我当年也是,写个代码跟小学生写作文似的,能凑合看懂就谢天谢地了。这层次的开发者,最大的特点就是“实用主义至上”——代码能跑,功能凑合实现,至于好不好看、效率高不高,那是以后的事儿。

就像你刚学会做菜,炒个蛋能熟不糊锅就觉得自己是天才,至于盐多盐少、盘子漂不漂亮,谁管呢?

  1. 这个阶段有哪些症状?

  1. 代码风格?那是啥?

变量命名完全看心情,今天用abc,明天用xyz,后天心情不好直接上中文,比如灯亮灯灭。缩进?不存在的,代码就像被风吹乱的头发,东倒西歪。别人看你的代码,得先猜你在干嘛。更有甚者,代码里全是拼音,比如deng_shan(灯闪),看着像技术代码,其实是“中式英语”的变种。

  1. 功能实现糙得像毛坯房

想让程序等一秒?简单,扔个for循环,i从0数到10000,CPU就老老实实干等着。效率是个啥?没听说过。

LED要闪烁?那就P1 = 0xFF点亮,再P1 = 0x00熄灭,循环搞定,至于CPU累不累,跟我啥关系?简直是把单片机的命不当命。

  1. 调试全靠玄学

程序出问题了怎么办?串口疯狂printf,打印一堆乱七八糟的东西,靠肉眼在屏幕上找线索。

更“高级”点的,可能用LED灯当信号灯,亮一下是“这里没问题”,灭一下是“救命我崩了”。

  1. 复制粘贴就完事儿

代码全靠复制粘贴,抄过来改改数字,能跑就行。至于为啥这么配,完全不懂,反正动了就崩,不动就用呗。

datasheet?那是天书,看不懂也不想看。更搞笑的是,有人抄代码抄错了,把人家调试用的printf也抄进来,结果单片机没串口,程序直接崩,愣是找了一天都没发现问题。

  1. 假如萌新去开发防盗报警功能

咱们来看看新手村的开发者会怎么写这个防盗报警主机的代码:

#include "stm32f10x.h"

void delay_ms(uint32_t ms)
{
    uint32_t i;
    for (i = 0; i < ms; i++)
    {
        uint32_t j;
        for (j = 0; j < 7200; j++); // 粗略延时
    }
}

int main(void)
{
    // 使能GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);

    // 配置GPIO
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct); // 传感器输入

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct); // 蜂鸣器和LED

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOC, &GPIO_InitStruct); // 按键输入,上拉

    // 配置USART1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct); // TX引脚

    USART_InitTypeDef USART_InitStruct;
    USART_InitStruct.USART_BaudRate = 9600;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStruct);
    USART_Cmd(USART1, ENABLE);

    unsigned char armed = 0; // 0:撤防, 1:布防

    while (1)
    {
        // 检测按键切换布防状态
        if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == 0)
        {
            armed = !armed;
            delay_ms(20); // 简单防抖
            while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == 0); // 等待松开
        }

        if (armed)
        {
            if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1 || GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 1)
            {
                GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1); // 触发报警
                while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
                USART_SendData(USART1, 'A'); // 发送报警信号
                delay_ms(100);
            }
            else
            {
                GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
            }
        }
        else
        {
            GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
        }
    }
}

功能说明:通过轮询检测按键和传感器状态,布防时触发蜂鸣器和LED报警,并通过串口发送字符'A'。

这代码乍一看没毛病,功能也能实现:按键切换布防撤防,布防状态下传感器触发就报警。但你仔细想想,这代码问题多多:

  1. 代码分析
  • 功能:通过忙等待检测按键和传感器状态,布防时触发蜂鸣器和LED报警,并通过串口发送字符'A'。

  • 特点

  • 简单直接:所有逻辑写在main函数中,易于理解。

  • 效率低:使用delay_ms忙等待,浪费CPU资源。

  • 无安全性:任何人都能按键切换状态,无保护。

  • 扩展性差:代码耦合严重,难以修改或增加功能。

    1. 为什么会这样?

    新手村的开发者,通常是刚接触单片机不久,可能连C语言都没学扎实。他们的目标很简单:让硬件动起来,看到LED亮了、蜂鸣器响了,就觉得自己成功了。这种“能跑就行”的心态,其实挺正常的,毕竟谁不是从零开始的呢?

    但这种代码的问题在于,它只适合“玩具项目”。一旦项目复杂起来,比如防盗报警主机要加显示屏、联网功能,或者多几个传感器,这代码就彻底崩了。改不动、调不通,最后只能推倒重来。所以,新手村的阶段虽然美好,但不能待太久,得赶紧往上爬。

    1. 第二层,有点东西但不多

    爬出新手村,你就到了进阶区。这时候的你,已经不是那个只求“能跑”的愣头青了,开始有点追求,想让代码不仅能跑,还要跑得好看。

    就像做菜,你学会了刀工、调味,开始琢磨怎么把盘子摆得像样点,味道也得让人夸一句“不错”。

    进阶区的开发者,已经有点“职业选手”的味道了,但离顶级大神还有段距离。

    1. 进阶玩家的标志

    1. 代码开始像人话了

    变量命名有了规律,比如armedalarm_triggered,一看就知道干啥用的。缩进整齐,注释到位,别人看你的代码,不用猜半天,至少能顺着逻辑走一遍。甚至有些人会用匈牙利命名法,比如bArmed表示布尔型的布防状态,看着就专业。

    1. 功能实现有了讲究

    不再用delay()忙等待,改用定时器中断,CPU终于能喘口气干点别的。代码也开始模块化,一个函数干一件事儿,不再是啥都塞main里的大杂烩。比如传感器检测单独写个函数,报警处理再写一个,改起来方便多了。

    1. 调试不再抓瞎

    你开始用调试工具了,比如Keil的仿真器,或者接个逻辑分析仪,看看信号波形。出问题不再是大海捞针,而是有点章法地找线索。我见过一个进阶玩家,调试时直接用示波器测中断信号,问题两分钟就定位了,比新手村的“LED灯闪烁法”高明多了。

    1. 代码能独立写出来了

    写代码不只是会复制粘贴了,一出问题靠猜的阶段了,能利用好"轮子",并在此基础上改代码,甚至自己独立完成项目功能,只是架构设计上不太好。

    1. 进阶版的防盗报警主机代码

    来看看进阶区的开发者会怎么改进这个代码:

    #include "stm32f10x.h"
    
    typedef enum
    {
        STATE_DISARMED,   // 撤防
        STATE_ARMED,      // 布防
        STATE_ALARM,      // 报警
        STATE_PASSWORD    // 密码输入
    } SystemState;
    
    #define PASSWORD_LEN 4
    unsigned char password[PASSWORD_LEN] = {1, 2, 3, 4}; // 示例密码
    unsigned char input[PASSWORD_LEN];
    unsigned char input_index = 0;
    SystemState current_state = STATE_DISARMED;
    volatile unsigned char update_flag = 0; // 状态更新标志
    
    // 传感器读取
    unsigned char read_sensors(void)
    {
        return (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) | (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) << 1));
    }
    
    // 密码验证
    unsigned char check_password(void)
    {
        for (unsigned char i = 0; i < PASSWORD_LEN; i++)
        {
            if (input[i] != password[i]) return 0;
        }
        return 1;
    }
    
    // 状态机更新
    void update_state(void)
    {
        switch (current_state)
        {
            case STATE_DISARMED:
                GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
                if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == 0)
                {
                    current_state = STATE_PASSWORD;
                    input_index = 0;
                    GPIO_SetBits(GPIOB, GPIO_Pin_1); // LED提示输入
                    while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == 0); // 防抖
                }
                break;
    
            case STATE_ARMED:
                if (read_sensors() != 0)
                {
                    current_state = STATE_ALARM;
                }
                if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == 0)
                {
                    current_state = STATE_PASSWORD;
                    input_index = 0;
                    GPIO_SetBits(GPIOB, GPIO_Pin_1); // LED提示输入
                    while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == 0); // 防抖
                }
                break;
    
            case STATE_ALARM:
                GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
                while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
                USART_SendData(USART1, 'A');
                if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) == 0)
                {
                    current_state = STATE_ARMED;
                    GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
                    while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) == 0); // 防抖
                }
                break;
    
            case STATE_PASSWORD:
                if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) == 0)
                {
                    input[input_index++] = 1; // 简化为按键输入1
                    GPIO_ToggleBits(GPIOB, GPIO_Pin_1); // LED闪烁提示
                    while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) == 0); // 防抖
                    if (input_index == PASSWORD_LEN)
                    {
                        if (check_password())
                        {
                            current_state = (current_state == STATE_ARMED) ? STATE_DISARMED : STATE_ARMED;
                        }
                        else
                        {
                            current_state = STATE_DISARMED;
                            GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
                            for (volatile uint32_t i = 0; i < 50000; i++); // 短促报警
                            GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
                        }
                    }
                }
                break;
        }
    }
    
    // 定时器中断服务
    void TIM2_IRQHandler(void)
    {
        if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
        {
            TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
            update_flag = 1; // 仅设置标志位
        }
    }
    
    int main(void)
    {
        // 时钟配置
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_USART1, ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
        // GPIO配置
        GPIO_InitTypeDef GPIO_InitStruct;
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIOA, &GPIO_InitStruct); // 传感器
    
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB, &GPIO_InitStruct); // 蜂鸣器和LED
    
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
        GPIO_Init(GPIOC, &GPIO_InitStruct); // 按键
    
        // USART1配置
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_Init(GPIOA, &GPIO_InitStruct); // TX
    
        USART_InitTypeDef USART_InitStruct;
        USART_InitStruct.USART_BaudRate = 9600;
        USART_InitStruct.USART_WordLength = USART_WordLength_8b;
        USART_InitStruct.USART_StopBits = USART_StopBits_1;
        USART_InitStruct.USART_Parity = USART_Parity_No;
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStruct.USART_Mode = USART_Mode_Tx;
        USART_Init(USART1, &USART_InitStruct);
        USART_Cmd(USART1, ENABLE);
    
        // TIM2配置(10ms中断)
        TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
        TIM_TimeBaseStruct.TIM_Period = 10000 - 1; // 10ms
        TIM_TimeBaseStruct.TIM_Prescaler = 7200 - 1; // 72MHz / 7200 = 10kHz
        TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
        TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
        TIM_Cmd(TIM2, ENABLE);
    
        // NVIC配置
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
    
        while (1)
        {
            if (update_flag)
            {
                update_flag = 0;
                update_state(); // 在主循环中更新状态
            }
        }
    }

    1. 这代码比新手村强在哪?
  • 模块化初现:传感器检测、报警处理、按键检测都分成了函数,可读性提高。想改传感器逻辑?直接找check_sensors(),简单明了。

  • 中断驱动:用定时器中断检测传感器,CPU可以干别的事,比如检测按键,不用傻乎乎地忙等待了。

  • 规范性提升:变量命名有意义,逻辑分块,别人看代码至少能猜出个大概。注释虽然不多,但关键地方有了提示。

  • 硬件利用:开始配置定时器和串口寄存器,不完全靠抄了。比如定时器的计算,10ms的溢出时间是自己算出来的,看着就专业。

    1. 但还有啥不足?
  • 耦合度:功能模块之间仍有关联,比如alarm_triggered是全局变量,改起来不够灵活。

  • 安全性不足:仍无密码保护,小偷按一下按键照样撤防。

  • 报警简单:触发后持续报警,无复位机制,用户体验差。

  • 功耗没考虑:CPU一直在跑while(1),电量哗哗流,像个不关水的水龙头。

    1. 进阶区的瓶颈

    进阶区的开发者,已经能写出“能用”的代码了,但离“好用”还有距离。他们的代码像个半成品房子,外观不错,功能齐全,但细节不够讲究。

    比如知道模块化好,但不知道怎么设计低耦合的模块。这种状态,就像厨师学会了摆盘,但菜的味道还没到极致,吃着不错,但不惊艳。

    我在这个阶段徘徊了好几年,受限于公司项目,接触不到好的代码,代码虽然分了模块,用了中断,但每次加新功能都得改老代码,改到最后自己都烦了。这代码咋越写越乱呢?其实不是代码乱,是我还没学会更高层次的架构设计。

    1. 怎么提升?

    想爬到大师殿堂,我给几条路子:

    学架构:研究状态机、事件驱动这些设计方法,别让代码逻辑乱成一团麻。状态机听起来高大上,其实很简单,就是把系统分成几个状态,状态之间按规则切换,后面大师级的代码会给你示范。

    优化细节:想想怎么省电、怎么提速,比如用位操作代替复杂逻辑,用静态变量减少内存开销。

    多看牛人代码:去GitHub上找找单片机开源项目,看看高手咋写的,模仿着来。别怕看不懂,看多了自然有感觉。

    多找复杂项目做:接点复杂点的项目,像联网设备、网关,逼自己解决大问题,水平自然就上去了,比如无际单片机的项目6,我们光研发出来都差不多花了1年时间。

    半山腰的风光虽好,但山顶的景色更美,别停下脚步,继续爬吧!

    1. 第三层:架构、算法、功耗我全都要!

    到了大师殿堂,你就不是普通开发者了,你是单片机界的“米其林三星厨师”。

    代码不仅要跑得好,还要美得像诗,高效得像机器,优雅得让人想鼓掌。

    这层次的开发者,已经把技术和艺术玩到了一起,写代码跟画画似的,既实用又有灵魂。他们的代码,不仅能解决问题,还能让人看了拍案叫绝。

    下面,我将引入之前文章提到的状态机表驱动的设计模式,重构一下代码,让代码结构更清晰、灵活,易于扩展和维护。

    在此之前,我先提一下,这种级别代码的特点。

    1. 大师代码特点

    1. 优雅又实用

    结构优雅,每一行都像是精心设计过的艺术品。别人看你的代码,不仅能学技术,还能感受到一种美感。比如变量名起得像散文,函数分工像交响乐,注释少但字字珠玑。

    1. 功能实现极致

    性能优化到极致,比如用位操作代替复杂逻辑,用内联汇编榨干CPU的每一滴性能。资源利用率高得吓人,连一字节RAM都不浪费。甚至能根据硬件特性调整算法,让软硬件配合得天衣无缝。

    1. 调试机会都不多

    出问题?大师一出手,立马定位,连日志都不用多打。甚至写代码时就把坑都填了,bug压根没机会冒头,大概7,8年前,我就领教过一个大佬的代码,至今回味无穷。

    1. 大师级的防盗报警主机代码

    1. 设计思路
  • 状态机:将系统划分为明确的状态,每个状态代表系统的一种工作模式(如撤防、布防、报警、密码输入)。状态之间通过事件触发进行转换。

  • 表驱动:使用状态转换表来定义状态之间的跳转规则,避免大量嵌套的if-else或switch-case,使逻辑更简洁且易于修改。

  • 模块化:将状态转换逻辑与具体动作分离,降低耦合度,提升代码复用性。

  • 直接上代码:

    #include "stm32f10x.h"
    
    // 系统状态枚举
    typedef enum
    {
        STATE_DISARMED,   // 撤防
        STATE_ARMED,      // 布防
        STATE_ALARM,      // 报警
        STATE_PASSWORD    // 密码输入
    } SystemState;
    
    // 事件枚举
    typedef enum
    {
        EVENT_BUTTON1_PRESS,   // 按键1按下(布防/撤防)
        EVENT_BUTTON2_PRESS,   // 按键2按下(密码输入)
        EVENT_SENSOR_TRIGGER,  // 传感器触发
        EVENT_PASSWORD_OK,     // 密码正确
        EVENT_PASSWORD_FAIL,   // 密码错误
        EVENT_RESET_ALARM      // 复位报警
    } EventType;
    
    #define PASSWORD_LEN 4
    unsigned char password[PASSWORD_LEN] = {1, 2, 3, 4}; // 示例密码
    unsigned char input[PASSWORD_LEN];
    unsigned char input_index = 0;
    SystemState current_state = STATE_DISARMED;
    volatile unsigned char event_flag = 0; // 事件标志
    
    // 传感器读取
    unsigned char read_sensors(void)
    {
        return (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) | (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) << 1));
    }
    
    // 密码验证
    unsigned char check_password(void)
    {
        for (unsigned char i = 0; i < PASSWORD_LEN; i++)
        {
            if (input[i] != password[i]) return 0;
        }
        return 1;
    }
    
    // 动作函数
    void start_password_input(void)
    {
        input_index = 0;
        GPIO_SetBits(GPIOB, GPIO_Pin_1); // LED提示输入
    }
    
    void trigger_alarm(void)
    {
        GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
        USART_SendData(USART1, 'A');
    }
    
    void reset_alarm(void)
    {
        GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
    }
    
    void switch_state(void)
    {
        current_state = (current_state == STATE_ARMED) ? STATE_DISARMED : STATE_ARMED;
    }
    
    void password_fail(void)
    {
        GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
        for (volatile uint32_t i = 0; i < 50000; i++); // 短促报警
        GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
    }
    
    // 状态转换表
    typedef struct
    {
        SystemState current_state;
        EventType event;
        SystemState next_state;
        void (*action)(void);
    } StateTransition;
    
    StateTransition transition_table[] =
    {
        {STATE_DISARMED, EVENT_BUTTON1_PRESS, STATE_PASSWORD, start_password_input},
        {STATE_ARMED, EVENT_SENSOR_TRIGGER, STATE_ALARM, trigger_alarm},
        {STATE_ARMED, EVENT_BUTTON1_PRESS, STATE_PASSWORD, start_password_input},
        {STATE_ALARM, EVENT_RESET_ALARM, STATE_ARMED, reset_alarm},
        {STATE_PASSWORD, EVENT_PASSWORD_OK, STATE_ARMED, switch_state},
        {STATE_PASSWORD, EVENT_PASSWORD_FAIL, STATE_DISARMED, password_fail}
    };
    
    // 状态机处理
    void process_event(EventType event)
    {
        for (int i = 0; i < sizeof(transition_table) / sizeof(StateTransition); i++)
        {
            if (transition_table[i].current_state == current_state && transition_table[i].event == event)
            {
                current_state = transition_table[i].next_state;
                if (transition_table[i].action != NULL)
                {
                    transition_table[i].action();
                }
                break;
            }
        }
    }
    
    // 定时器中断服务
    void TIM2_IRQHandler(void)
    {
        if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
        {
            TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
            event_flag = 1; // 仅设置标志位
        }
    }
    
    int main(void)
    {
        // 时钟配置
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_USART1, ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
        // GPIO配置
        GPIO_InitTypeDef GPIO_InitStruct;
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIOA, &GPIO_InitStruct); // 传感器
    
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB, &GPIO_InitStruct); // 蜂鸣器和LED
    
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
        GPIO_Init(GPIOC, &GPIO_InitStruct); // 按键
    
        // USART1配置
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_Init(GPIOA, &GPIO_InitStruct); // TX
    
        USART_InitTypeDef USART_InitStruct;
        USART_InitStruct.USART_BaudRate = 9600;
        USART_InitStruct.USART_WordLength = USART_WordLength_8b;
        USART_InitStruct.USART_StopBits = USART_StopBits_1;
        USART_InitStruct.USART_Parity = USART_Parity_No;
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStruct.USART_Mode = USART_Mode_Tx;
        USART_Init(USART1, &USART_InitStruct);
        USART_Cmd(USART1, ENABLE);
    
        // TIM2配置(10ms中断)
        TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
        TIM_TimeBaseStruct.TIM_Period = 10000 - 1; // 10ms
        TIM_TimeBaseStruct.TIM_Prescaler = 7200 - 1; // 72MHz / 7200 = 10kHz
        TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
        TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
        TIM_Cmd(TIM2, ENABLE);
    
        // NVIC配置
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
    
        while (1)
        {
            if (event_flag)
            {
                event_flag = 0;
                // 检测事件并处理
                if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == 0)
                {
                    process_event(EVENT_BUTTON1_PRESS);
                    while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == 0); // 防抖
                }
                if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) == 0)
                {
                    input[input_index++] = 1; // 简化为按键输入1
                    GPIO_ToggleBits(GPIOB, GPIO_Pin_1); // LED闪烁
                    while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) == 0); // 防抖
                    if (input_index == PASSWORD_LEN)
                    {
                        if (check_password())
                        {
                            process_event(EVENT_PASSWORD_OK);
                        }
                        else
                        {
                            process_event(EVENT_PASSWORD_FAIL);
                        }
                    }
                }
                if (read_sensors() != 0 && current_state == STATE_ARMED)
                {
                    process_event(EVENT_SENSOR_TRIGGER);
                }
                if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1) == 0 && current_state == STATE_ALARM)
                {
                    process_event(EVENT_RESET_ALARM);
                }
            }
        }
    }

    有点看傻眼了吧?

     

    1. 这代码牛在哪?
  • 清晰性:状态机将系统行为分解为独立的状态,逻辑一目了然。

  • 可扩展性:只需修改transition_table,即可添加新状态或事件,无需更改核心代码。

  • 可维护性:动作函数与状态转换分离,调试和修改更方便。

  • 耦合:状态逻辑与硬件操作解耦,增强代码复用性。

    1. 与前两层的区别

    第一层:可能是简单的顺序逻辑或条件分支,代码紧耦合,难以扩展。

    第二层:可能引入函数封装或简单状态管理,但仍依赖硬编码逻辑。

    第三层:通过状态机和表驱动实现高级架构,强调设计上的优雅与灵活性,而非功能的增加。

    这种设计不仅满足防盗报警主机的功能需求,还体现了大师级的架构思维。

    1. 最后总结下

    单片机软件开发,说简单也简单,说难也真挺难。它考验的不光是你的编程功底,还有你对硬件的理解、对工程的把控。代码写得好,不仅能解决问题,还能大大提高开发效率,这种成就感是别的行业给不了的。

    技术是门手艺活,熟能生巧。别怕出错,错了就改,改了再错,错着错着你就对了,有点啰嗦,但绝对真理。


    最近很多粉丝问我单片机怎么学,我根据自己从业十年经验,累积耗时一个月,精心整理一份「单

    片机最佳学习路径+单片机入门到高级教程+工具包」全部无偿分享给铁粉!!!

    除此以外,再含泪分享我压箱底的22个热门开源项目,包含源码+原理图+PCB+说明文档,让你迅速进阶成高手

    教程资料包和详细的学习路径可以看我下面这篇文章的开头

    《单片机入门到高级开挂学习路径(附教程+工具)》

    《单片机入门到高级开挂学习路径(附教程+工具)》

    《单片机入门到高级开挂学习路径(附教程+工具)》

    作者:无际单片机编程

    物联沃分享整理
    物联沃-IOTWORD物联网 » 单片机软件开发的三层天梯详解:成为大师所需的步骤与策略(深度解析文章,约5000字)

    发表回复