嵌入式软件开发的三个层次,到达第三层,老板都要敬你三分

原创 无际单片机编程 2025-03-12 07:50

关注公众号,回复“入门资料”获取单片机入门到高级开挂教程

 开发板带你入门,我们带你飞

文 | 无际(微信:2777492857)

全文约7078字,阅读大约需要 15 分钟

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

          

 

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

          

 

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

          

 

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

          

 

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

          

 

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

          

 

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

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

          

 

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

          

 

1.1 这个阶段有哪些症状?

1.1.1 代码风格?那是啥?

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

          

 

1.1.2 功能实现糙得像毛坯房

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

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

          

 

1.1.3 调试全靠玄学

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

          

 

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

          

 

1.1.4 复制粘贴就完事儿

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

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

          

 

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

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

#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.2.1 代码分析

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

特点: 

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

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

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

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

          

 

1.2.2 为什么会这样?

          

 

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

          

 

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

 

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

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

          

 

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

          

 

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

          

 

2.1 进阶玩家的标志

          

 

2.1.1 代码开始像人话了

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

          

 

2.1.2 功能实现有了讲究

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

          

 

2.1.3 调试不再抓瞎

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

          

 

2.1.4 代码能独立写出来了

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

          

 

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

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

#include "stm32f10x.h"typedef enum{    STATE_DISARMED,   // 撤防    STATE_ARMED,      // 布防    STATE_ALARM,      // 报警    STATE_PASSWORD    // 密码输入} SystemState;#define PASSWORD_LEN 4unsigned char password[PASSWORD_LEN] = {1234}; // 示例密码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(); // 在主循环中更新状态        }    }}

          

 

2.2.1 这代码比新手村强在哪?

          

 

模块化初现:传感器检测、报警处理、按键检测都分成了函数,可读性提高。想改传感器逻辑?直接找check_sensors(),简单明了。

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

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

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

          

 

2.2.2 但还有啥不足?

          

 

耦合度高:功能模块之间仍有关联,比如alarm_triggered是全局变量,改起来不够灵活。

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

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

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

          

 

2.2.3 进阶区的瓶颈

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

          

 

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

          

 

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

          

 

2.2.4 怎么提升?

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

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

          

 

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

          

 

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

          

 

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

          

 

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

          

 

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

          

 

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

          

 

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

          

 

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

          

 

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

          

 

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

3.1 大师代码特点

3.1.1 优雅又实用

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

          

 

3.1.2 功能实现极致

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

          

 

3.1.3 调试机会都不多

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

          

 

          

 

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

3.2.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 4unsigned char password[PASSWORD_LEN] = {1234}; // 示例密码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);            }        }    }}       

有点看傻眼了吧?

          

 

这只是一个伪代码模型,实际上无际单片机特训营项目6的功能,远比这个复杂,可以说不掌握这些架构,这个项目根本做不稳定。

          

 

好了,不吹牛了,哈哈,继续聊上面的代码。

          

 

3.2.2 这代码牛在哪?

清晰性:状态机将系统行为分解为独立的状态,逻辑一目了然。

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

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

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

          

 

3.2.3 与前两层的区别

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

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

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

          

 

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

          

 

4. 最后总结下

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

          

 

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


end



下面是更多无际原创个人成长经历、行业经验、技术干货

1.电子工程师是怎样的成长之路?10年5000字总结

2.如何快速看懂别人的代码和思维

3.单片机开发项目全局变量太多怎么管理?

4.C语言开发单片机为什么大多数都采用全局变量的形式

5.单片机怎么实现模块化编程?实用程度让人发指!

6.c语言回调函数的使用及实际作用详解

7.手把手教你c语言队列实现代码,通俗易懂超详细!

8.c语言指针用法详解,通俗易懂超详细!

无际单片机编程 单片机编程、全栈孵化。
评论 (0)
  • 多功能电锅长什么样子,主视图如下图所示。侧视图如下图所示。型号JZ-18A,额定功率600W,额定电压220V,产自潮州市潮安区彩塘镇精致电子配件厂,铭牌如下图所示。有两颗螺丝固定底盖,找到合适的工具,拆开底盖如下图所示。可见和大部分市场的加热锅一样的工作原理,手绘原理图,根据原理图进一步理解和分析。F1为保险,250V/10A,185℃,CPGXLD 250V10A TF185℃ RY 是一款温度保险丝,额定电压是250V,额定电流是10A,动作温度是185℃。CPGXLD是温度保险丝电器元件
    liweicheng 2025-05-05 18:36 187浏览
  • 你是不是也有在公共场合被偷看手机或笔电的经验呢?科技时代下,不少现代人的各式机密数据都在手机、平板或是笔电等可携式的3C产品上处理,若是经常性地需要在公共场合使用,不管是工作上的机密文件,或是重要的个人信息等,民众都有防窃防盗意识,为了避免他人窥探内容,都会选择使用「防窥保护贴片」,以防止数据外泄。现今市面上「防窥保护贴」、「防窥片」、「屏幕防窥膜」等产品就是这种目的下产物 (以下简称防窥片)!防窥片功能与常见问题解析首先,防窥片最主要的功能就是用来防止他人窥视屏幕上的隐私信息,它是利用百叶窗的
    百佳泰测试实验室 2025-04-30 13:28 615浏览
  • UNISOC Miracle Gaming奇迹手游引擎亮点:• 高帧稳帧:支持《王者荣耀》等主流手游90帧高画质模式,连续丢帧率最高降低85%;• 丝滑操控:游戏冷启动速度提升50%,《和平精英》开镜开枪操作延迟降低80%;• 极速网络:专属游戏网络引擎,使《王者荣耀》平均延迟降低80%;• 智感语音:与腾讯GVoice联合,弱网环境仍能保持清晰通话;• 超高画质:游戏画质增强、超级HDR画质、游戏超分技术,优化游戏视效。全球手游市场规模日益壮大,游戏玩家对极致体验的追求愈发苛刻。紫光展锐全新U
    紫光展锐 2025-05-07 17:07 51浏览
  • ‌一、高斯计的正确选择‌1、‌明确测量需求‌‌磁场类型‌:区分直流或交流磁场,选择对应仪器(如交流高斯计需支持交变磁场测量)。‌量程范围‌:根据被测磁场强度选择覆盖范围,例如地球磁场(0.3–0.5 G)或工业磁体(数百至数千高斯)。‌精度与分辨率‌:高精度场景(如科研)需选择误差低于1%的仪器,分辨率需匹配微小磁场变化检测需求。2、‌仪器类型选择‌‌手持式‌:便携性强,适合现场快速检测;‌台式‌:精度更高,适用于实验室或工业环境。‌探头类型‌:‌横向/轴向探头‌:根据磁场方向选择,轴向探头适合
    锦正茂科技 2025-05-06 11:36 284浏览
  •  一、‌核心降温原理‌1、‌液氮媒介作用‌液氮恒温器以液氮(沸点约77K/-196℃)为降温媒介,通过液氮蒸发吸收热量的特性实现快速降温。液氮在内部腔体蒸发时形成气-液界面,利用毛细管路将冷媒导入蒸发器,强化热交换效率。2、‌稳态气泡控温‌采用‌稳态气泡原理‌:调节锥形气塞与冷指间隙,控制气-液界面成核沸腾条件,使漏热稳定在设定值。通过控温仪调整加热功率,补偿漏热并维持温度平衡,实现80K-600K范围的快速变温。二、‌温度控制机制‌1、‌动态平衡调节‌控温仪内置模糊控制系统,通过温度
    锦正茂科技 2025-04-30 11:31 76浏览
  • 二位半 5线数码管的驱动方法这个2位半的7段数码管只用5个管脚驱动。如果用常规的7段+共阳/阴则需要用10个管脚。如果把每个段看成独立的灯。5个管脚来点亮,任选其中一个作为COM端时,另外4条线可以单独各控制一个灯。所以实际上最多能驱动5*4 = 20个段。但是这里会有一个小问题。如果想点亮B1,可以让第3条线(P3)置高,P4 置低,其它阳极连P3的灯对应阴极P2 P1都应置高,此时会发现C1也会点亮。实际操作时,可以把COM端线P3设置为PP输出,其它线为OD输出。就可以单独控制了。实际的驱
    southcreek 2025-05-07 15:06 49浏览
  • 浪潮之上:智能时代的觉醒    近日参加了一场课题的答辩,这是医疗人工智能揭榜挂帅的国家项目的地区考场,参与者众多,围绕着医疗健康的主题,八仙过海各显神通,百花齐放。   中国大地正在发生着激动人心的场景:深圳前海深港人工智能算力中心高速运转的液冷服务器,武汉马路上自动驾驶出租车穿行的智慧道路,机器人参与北京的马拉松竞赛。从中央到地方,人工智能相关政策和消息如雨后春笋般不断出台,数字中国的建设图景正在智能浪潮中徐徐展开,战略布局如同围棋
    广州铁金刚 2025-04-30 15:24 334浏览
  • 这款无线入耳式蓝牙耳机是长这个样子的,如下图。侧面特写,如下图。充电接口来个特写,用的是卡座卡在PCB板子上的,上下夹紧PCB的正负极,如下图。撬开耳机喇叭盖子,如下图。精致的喇叭(HY),如下图。喇叭是由电学产生声学的,具体结构如下图。电池包(AFS 451012  21 12),用黄色耐高温胶带进行包裹(安规需求),加强隔离绝缘的,如下图。451012是电池包的型号,聚合物锂电池+3.7V 35mAh,详细如下图。电路板是怎么拿出来的呢,剪断喇叭和电池包的连接线,底部抽出PCB板子
    liweicheng 2025-05-06 22:58 172浏览
  • 想不到短短几年时间,华为就从“技术封锁”的持久战中突围,成功将“被卡脖子”困境扭转为科技主权的主动争夺战。众所周知,前几年技术霸权国家突然对华为发难,导致芯片供应链被强行掐断,海外市场阵地接连失守,恶意舆论如汹涌潮水,让其瞬间陷入了前所未有的困境。而最近财报显示,华为已经渡过危险期,甚至开始反击。2024年财报数据显示,华为实现全球销售收入8621亿元人民币,净利润626亿元人民币;经营活动现金流为884.17亿元,同比增长26.7%。对比来看,2024年营收同比增长22.42%,2023年为7
    用户1742991715177 2025-05-02 18:40 174浏览
  • 随着智能驾驶时代到来,汽车正转变为移动计算平台。车载AI技术对存储器提出新挑战:既要高性能,又需低功耗和车规级可靠性。贞光科技代理的紫光国芯车规级LPDDR4存储器,以其卓越性能成为国产芯片产业链中的关键一环,为智能汽车提供坚实的"记忆力"支持。作为官方授权代理商,贞光科技通过专业技术团队和完善供应链,让这款国产存储器更好地服务国内汽车厂商。本文将探讨车载AI算力需求现状及贞光科技如何通过紫光国芯LPDDR4产品满足市场需求。 车载AI算力需求激增的背景与挑战智能驾驶推动算力需求爆发式
    贞光科技 2025-05-07 16:54 52浏览
  • 5小时自学修好BIOS卡住问题  更换硬盘故障现象:f2、f12均失效,只有ESC和开关机键可用。错误页面:经过AI的故障截图询问,确定是机体内灰尘太多,和硬盘损坏造成,开机卡在BIOS。经过亲手拆螺丝和壳体、排线,跟换了新的2.5寸硬盘,故障排除。理论依据:以下是针对“5小时自学修好BIOS卡住问题+更换硬盘”的综合性解决方案,结合硬件操作和BIOS设置调整,分步骤说明:一、判断BIOS卡住的原因1. 初步排查     拔掉多余硬件:断开所有外接设备(如
    丙丁先生 2025-05-04 09:14 72浏览
  • 某国产固态电解的2次和3次谐波失真相当好,值得一试。(仅供参考)现在国产固态电解的性能跟上来了,值得一试。当然不是随便搞低端的那种。电容器对音质的影响_电子基础-面包板社区  https://mbb.eet-china.com/forum/topic/150182_1_1.html (右键复制链接打开)电容器对音质的影响相当大。电容器在音频系统中的角色不可忽视,它们能够调整系统增益、提供合适的偏置、抑制电源噪声并隔离直流成分。然而,在便携式设备中,由于空间、成本的限
    bruce小肥羊 2025-05-04 18:14 130浏览
  • 一、gao效冷却与控温机制‌1、‌冷媒流动设计‌采用低压液氮(或液氦)通过毛细管路导入蒸发器,蒸汽喷射至样品腔实现快速冷却,冷却效率高(室温至80K约20分钟,至4.2K约30分钟)。通过控温仪动态调节蒸发器加热功率,结合温度传感器(如PT100铂电阻或Cernox磁场不敏感传感器),实现±0.01K的高精度温度稳定性。2、‌宽温区覆盖与扩展性‌标准温区为80K-325K,通过降压选件可将下限延伸至65K(液氮模式)或4K(液氦模式)。可选配475K高温模块,满足材料在ji端温度下的性能测试需求
    锦正茂科技 2025-04-30 13:08 505浏览
  • 文/Leon编辑/cc孙聪颖‍2023年,厨电行业在相对平稳的市场环境中迎来温和复苏,看似为行业增长积蓄势能。带着对市场向好的预期,2024 年初,老板电器副董事长兼总经理任富佳为企业定下双位数增长目标。然而现实与预期相悖,过去一年,这家老牌厨电企业不仅未能达成业绩目标,曾提出的“三年再造一个老板电器”愿景,也因市场下行压力面临落空风险。作为“企二代”管理者,任富佳在掌舵企业穿越市场周期的过程中,正面临着前所未有的挑战。4月29日,老板电器(002508.SZ)发布了2024年年度报告及2025
    华尔街科技眼 2025-04-30 12:40 328浏览
  • 在全球制造业加速向数字化、智能化转型的浪潮中,健达智能作为固态照明市场的引领者和智能电子以及声学产品的创新先锋,健达智能敏锐捕捉到行业发展的新机遇与新挑战,传统制造模式已难以满足客户对品质追溯、定制化生产和全球化布局的需求。在此背景下, 健达智能科技股份有限公司(以下简称:健达智能)与盘古信息达成合作,正式启动IMS数字化智能制造工厂项目,标志着健达智能数字化转型升级迈入新阶段。此次项目旨在通过部署盘古信息IMS系统,助力健达实现生产全流程的智能化管控,打造照明行业数字化标杆。行业趋势与企业挑战
    盘古信息IMS 2025-04-30 10:13 89浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦