第三章 系统架构设计:硬件选型与软件分层

好,咱们进入第三章。这一章我打算聊聊系统架构设计。说白了,就是决定「用什么芯片」和「软件怎么写」。

很多新手工程师一上来就急着画原理图、写代码。我个人习惯是——先停下来,想清楚架构。你想想看,架构没定好,后面改起来可是要命的。

3.1 硬件选型:MCU、MPU、SoC 怎么选?

先说说硬件选型。我见过不少项目,芯片选错了,后面整个团队都在填坑。

MCU(微控制器):适合控制类任务。比如家电、传感器节点、电机控制。特点是集成度高,片内就有 Flash 和 RAM,功耗低,成本也低。我做过一个智能插座项目,用的就是 MCU,一颗几块钱,搞定。

MPU(微处理器):适合跑复杂操作系统,比如 Linux。需要外接 DDR、Flash。典型场景是路由器、工业平板、HMI。嗯,这里要注意——MPU 的启动流程比 MCU 复杂得多,Bootloader、设备树、驱动,一个都不能少。

SoC(片上系统):把 CPU、GPU、DSP、NPU 等集成在一起。适合音视频处理、AI 边缘计算。比如智能摄像头、人脸识别终端。我去年参与的一个边缘计算盒子,用的就是 SoC,性能确实强,但散热和功耗也是头疼事。

类型 典型芯片 适用场景 我踩过的坑
MCU STM32、ESP32 传感器、家电、电机控制 曾经选了一颗 Flash 只有 64KB 的 MCU,结果固件塞不下,硬着头皮砍功能
MPU i.MX6、RK3568 工业平板、HMI、网关 DDR 布线没做好,系统跑起来随机死机,查了三天
SoC 全志 V3s、海思 Hi3516 摄像头、AI 盒子、无人机 散热没算好,夏天户外直接过热重启
避坑指南:我曾经选型时只看主频和 RAM,忽略了外设接口数量。结果项目做到一半发现 UART 不够用,只能加一颗扩展芯片,成本、面积都上去了。所以选型时一定要列清楚外设需求清单。

3.2 软件架构分层:HAL / OS / APP

硬件定下来,接下来就是软件架构。我习惯分成三层:HAL、OS、APP。为什么这么分?说白了就是为了「解耦」。你想想看,如果所有代码都揉在一起,换一颗芯片就得重写一遍,那得多痛苦。

3.2.1 HAL(硬件抽象层)

HAL 层负责屏蔽硬件差异。比如 GPIO 操作,不同芯片的寄存器地址不一样,但 HAL 层提供统一的接口:gpio_write(pin, level)。这样上层代码就不用关心底层是 STM32 还是 GD32。

我个人习惯在 HAL 层里做两件事:

  • 封装寄存器操作,提供函数接口
  • 做基本的错误检查,比如参数合法性
// HAL 层示例:GPIO 操作
// 不同芯片只需要改这个文件
void hal_gpio_write(uint8_t pin, uint8_t level) {
    if (pin > MAX_GPIO_PIN) return;  // 参数检查
    // 芯片相关的寄存器操作
    GPIO->ODR = (GPIO->ODR & ~(1 << pin)) | (level << pin);
}
我的经验:HAL 层不要做得太厚。我见过有人把 HAL 层写成了一坨「万能驱动」,结果性能差、维护难。保持简单,只做硬件抽象,不做业务逻辑。

3.2.2 OS(操作系统层)

OS 层负责任务调度、资源管理。简单项目可以用裸机+状态机,复杂项目得上 RTOS 甚至 Linux。

我建议这样选:

  • 任务数 < 5 个,实时性要求不高 → 裸机
  • 任务数 5~20 个,有实时要求 → FreeRTOS / RT-Thread
  • 需要文件系统、网络协议栈、多进程 → Linux

我记得有个项目,客户要求同时处理 4 路传感器数据、1 路显示、1 路通信。裸机根本忙不过来,上了 FreeRTOS 后,每个任务各司其职,清爽多了。

3.2.3 APP(应用层)

APP 层就是业务逻辑。比如读取温度、控制电机、上报数据。这一层应该尽量与硬件无关,只调用 HAL 和 OS 提供的接口。

我见过最糟糕的代码——APP 层直接操作寄存器。换芯片时,整个 APP 层都得重写。所以,请务必遵守分层原则。

3.3 接口定义:把「契约」写清楚

接口定义是架构设计中最容易被忽视的一环。很多人觉得「反正代码是我写的,接口随便改」。但项目一大了,团队协作时,接口就是大家的「契约」。

我习惯在项目启动前,先定义好以下接口:

  1. 模块间接口:比如传感器模块给主控发数据,用什么协议?I2C?SPI?UART?数据格式是什么?
  2. 软件层间接口:HAL 层给 OS 层提供哪些函数?OS 层给 APP 层提供哪些 API?
  3. 人机接口:按键怎么定义?显示菜单怎么跳转?
核心原则:接口一旦确定,不要轻易修改。如果非要改,必须通知所有相关方,并更新文档。我曾经因为偷偷改了一个接口参数,导致同事的模块跑飞了三天...嗯,从那以后我再也不敢了。

3.4 一个实际案例:智能温控器

最后,我拿一个实际项目来串一下。智能温控器,功能很简单:读取温度、控制加热器、显示数据、支持手机 App 控制。

硬件选型

  • MCU:STM32F103(性价比高,外设够用)
  • 传感器:DS18B20(一线总线,简单)
  • 显示:OLED 128x64(I2C 接口)
  • 通信:ESP8266(WiFi 模块,UART 通信)

软件分层

  • HAL 层:封装 GPIO、I2C、UART、定时器操作
  • OS 层:FreeRTOS,任务包括温度采集、显示刷新、通信处理、按键扫描
  • APP 层:PID 控制算法、温度阈值判断、数据上报逻辑

接口定义

  • HAL→OS:hal_i2c_write()hal_uart_send()hal_timer_start()
  • OS→APP:os_task_create()os_queue_send()os_delay_ms()
  • 模块间:传感器数据通过队列传递,通信数据通过消息队列处理

这个项目从设计到量产,只用了 3 个月。架构清晰,后期维护也很轻松。说白了,前期多花点时间在架构上,后面能省下几倍的时间。

最后一句:架构设计不是一次性的工作。随着需求变化,架构也需要演进。但核心的分层思想和接口契约,一定要守住。