你是不是也遇到过这种情况?

刚开始学 STM32,点灯、串口打印、按键检测,全都写在 main.c 里。程序能跑,效果也正常,感觉没什么问题。

可项目一复杂,问题就来了。加个 OLED,塞一段代码;加个按键菜单,又塞一段;再来个串口协议、ADC 采样、PWM 控制,while(1) 里面越来越长。过几天自己回头看,都不知道哪一段是干什么的。

最可怕的是,项目出问题时,你连调试入口都找不到。

为什么这个问题很常见

因为初学阶段大家最关心的是“功能能不能跑”。

点灯成功了,串口能收发了,电机能转了,就觉得代码写对了。但工程开发不是实验课,功能跑起来只是第一步。

很多人一开始没有模块意识,看到外设初始化就写进 main.c,看到业务逻辑也写进 main.c。结果 main.c 既管硬件,又管协议,还管状态判断,最后变成一个“大杂烩”。

小项目看不出问题,大项目一定会爆。

核心原因拆解

从底层看,单片机项目通常有几层逻辑。

第一层是驱动层,比如 GPIO、UART、I2C、SPI、ADC、PWM。这些代码应该负责“怎么操作硬件”。

第二层是应用层,比如按键扫描、LED 状态、传感器读取、电机控制。这些代码负责“这个硬件用来干什么”。

第三层是协议层,比如串口命令解析、Modbus、自定义数据帧。它们不应该和 GPIO 翻转混在一起。

第四层是状态机,比如设备待机、运行、报警、升级、低功耗。项目一复杂,没有状态机,代码就会变成一堆 if else

如果这些全部塞进 main.c,后果就是:变量到处飞,函数互相调用,改一个地方影响一大片。

错误写法或错误理解

很多初学者有几个常见误区。

第一个误区:main.c 写得多,说明项目完整。错。main.c 越大,越说明职责没拆开。

第二个误区:先写一起,后面再整理。现实是,后面根本没时间整理。项目赶进度时,只会继续往里面补代码。

第三个误区:函数封装一下就算模块化。其实不一定。如果函数虽然拆出去了,但变量还全局乱用,逻辑还互相缠绕,本质还是乱。

第四个误区:驱动和业务不分。比如在串口接收中断里直接控制电机,在 ADC 采样函数里直接判断报警,这种写法后期非常难维护。

正确理解方式

main.c 不应该是所有代码的仓库。

它更像一个“项目入口”。它只负责初始化系统、启动模块、周期调度任务。

比如:

bsp_uart.c 管串口底层收发。
app_key.c 管按键逻辑。
app_motor.c 管电机控制。
protocol.c 管数据解析。
state_machine.c 管设备状态切换。
config.h 放项目配置参数。

这样拆开后,你再查问题就很清楚。

串口不通,去看 UART。
按键没反应,去看 Key。
状态跳错,去看 State。
不会所有问题都挤在 main.c 里打架。

项目中应该怎么做

建议你从学习阶段就养成这个习惯:一个模块解决一类问题。

驱动文件只做硬件动作,不写业务判断。
应用文件只做功能逻辑,不直接乱操作寄存器。
协议文件只负责解析命令,不直接控制所有外设。
状态机负责决定当前系统处于什么状态。
main.c 只做初始化和调度。

调试时也要配合模块化。

比如每个模块提供 Init()Task()Test() 这类接口。串口日志也按模块打印:

[KEY] press short
[MOTOR] start
[STATE] idle -> run

这样项目出问题,不是满屏乱找,而是顺着模块定位。

如果用 FreeRTOS,也一样。不要把所有任务都写在 main.c,任务函数可以放到对应模块里,main.c 只负责创建任务。

一段可参考代码思路

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    BSP_GPIO_Init();
    BSP_UART_Init();
    BSP_ADC_Init();

    App_Key_Init();
    App_Motor_Init();
    Protocol_Init();
    StateMachine_Init();

    while (1)
    {
        App_Key_Task();
        Protocol_Task();
        App_Motor_Task();
        StateMachine_Task();

        Delay_1ms();
    }
}

这段代码并不复杂,但思路很重要。

main.c 里面看不到一大堆业务细节,只能看到项目有哪些模块,以及它们如何被调度。

真正的按键消抖,放在 app_key.c
真正的电机启停,放在 app_motor.c
真正的串口帧解析,放在 protocol.c

这样项目越写越大,结构反而越清楚。

最后

  1. main.c 越乱,项目后期越难维护。
  2. 初学阶段就要建立模块化思维,不要等项目崩了再重构。
  3. 驱动、应用、协议、状态机要分开,各管各的事。
  4. main.c 最好只负责初始化和任务调度。
  5. 能跑只是开始,能改、能查、能扩展,才是真正的工程代码。

很多单片机项目不是败在功能不会写,而是败在代码结构一开始就没搭好。

如果你也曾经把 main.c 写成“代码垃圾桶”,建议收藏这篇文章,下次新建工程前先看一遍,也欢迎留言说说你项目里最乱的一次经历。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐