系统级架构设计:万用表功能模块划分
好,咱们进入正题。上一章聊了需求分析,说白了就是搞清楚「我们要做什么」。这一章,我们来拆解「怎么做」——也就是系统级架构设计。
我个人习惯,拿到一个项目,先画大框框。万用表这种嵌入式设备,功能再多,也能归到四个核心模块里:测量、显示、通信、电源。你想想看,是不是这么回事?
一、四大功能模块的划分
1. 测量模块——万用表的心脏
测量模块负责把物理信号变成数字信号。我做过一个项目,客户要求测到10μV级别的直流电压,结果前端模拟电路噪声就吃掉了一半精度。嗯,这里要注意,测量模块的难点不在ADC选型,而在前端调理电路。
测量模块内部又分几个子模块:
- 输入保护电路:防烧表,这是底线。我曾经因为省了一个PTC电阻,烧了三块样板,血的教训。
- 信号调理:包括衰减器、放大器、滤波器。说白了就是把信号调到ADC喜欢的范围。
- ADC转换:常用的是Σ-Δ型ADC,精度高,但速度慢。测电阻、电容还需要额外的激励源。
- 量程切换:自动量程还是手动量程?我建议新手先做手动,自动量程的逻辑坑很多。
关键数据流:模拟信号 → 保护电路 → 调理电路 → ADC → 数字信号 → MCU
2. 显示模块——用户的眼睛
显示模块,大家最熟悉。但我要说,显示模块的功耗往往被低估。一个128×64的OLED屏,全亮时能吃掉20mA,比MCU本身还多。
显示模块的架构要点:
- 显示驱动:要么用MCU自带LCD驱动,要么外挂驱动IC。我建议用外挂,比如SSD1306,MCU负担小很多。
- 显示内容管理:数值、单位、量程、电池电量、功能符号。别小看这个,UI逻辑写不好,用户会骂娘的。
- 刷新策略:静态显示还是动态刷新?低功耗模式下,我习惯用「变化才刷新」的策略,能省不少电。
我的小技巧:显示模块的缓冲区放在MCU内部RAM里,别用外部存储器。每次刷新只更新变化的部分,别全屏重绘。
3. 通信模块——万用表的触角
通信模块,现在基本是标配。蓝牙、USB、甚至Wi-Fi。但你要想清楚,通信模块是功耗大户。
常见的通信方案对比:
| 通信方式 | 峰值功耗 | 传输距离 | 适用场景 |
|---|---|---|---|
| 蓝牙BLE | ~10mA | 10m | 手机连接、数据记录 |
| USB | ~100mA | 有线 | 固件升级、高速传输 |
| Wi-Fi | ~200mA | 100m | 物联网、远程监控 |
我建议,低功耗万用表首选蓝牙BLE。为什么?因为它的待机功耗可以做到μA级,而且手机端生态成熟。我曾经做过一个USB通信的方案,用户反馈插拔USB时经常死机,后来发现是热插拔保护没做好。
4. 电源模块——系统的命脉
电源模块,我把它放在最后讲,因为它最重要。没有好的电源,前面三个模块都是白搭。
电源模块的设计要点:
- 电池选择:干电池还是锂电池?干电池简单,但电压会下降。锂电池需要充电管理。我个人偏爱锂电池+升压方案。
- 电压转换:万用表内部需要多个电压轨。模拟电路需要±5V或±3.3V,数字电路需要3.3V或1.8V。升压、降压、LDO,怎么组合?
- 功耗管理:自动关机、待机模式、深度睡眠。我习惯用PMOS管做负载开关,彻底切断不用的模块。
注意:电源模块的纹波噪声会直接影响测量精度。尤其是ADC的参考电压,必须用低噪声LDO单独供电。我曾经因为共用了一个LDO,导致测量结果跳了3个字,查了两天才找到原因。
二、系统框图与数据流
好了,四个模块讲完了。现在把它们拼起来,看看数据是怎么流动的。
系统框图,我习惯用「自顶向下」的方式画。最顶层是MCU,它负责调度一切。下面是四个模块,各自通过总线与MCU通信。
+------------------+
| MCU |
| (STM32/GD32) |
+--------+---------+
|
+-------+--------+--------+--------+
| | | | |
测量模块 显示模块 通信模块 电源模块
| | | |
ADC数据 显示缓存 蓝牙协议 电池管理
| | | |
+-------+--------+--------+--------+
|
用户交互
数据流是这样的:
- 测量数据流:模拟信号 → 测量模块 → ADC → SPI/I2C → MCU → 数据处理 → 显示缓存
- 显示数据流:MCU → 显示缓冲区 → SPI/I2C → 显示驱动 → 屏幕
- 通信数据流:MCU → UART/SPI → 蓝牙模块 → 手机APP
- 电源数据流:电池 → 升压/降压 → LDO → 各模块供电
你可能会问,这些数据流是并行的还是串行的?答案是:分时复用。MCU在一个时间片里只能做一件事。测量时,显示和通信可以暂停。显示刷新时,测量可以继续。这就是嵌入式系统的精髓——时间片调度。
核心思路:测量模块是最高优先级,因为它对实时性要求最高。显示和通信可以适当延迟。电源模块是后台任务,持续监控电池电压。
三、模块间的接口定义
模块划分好了,接口定义是关键。我见过太多项目,模块内部做得很好,但接口没定义清楚,最后联调时一团糟。
接口定义的原则:
- 物理接口:SPI、I2C、UART、GPIO。选哪种?SPI速度快,但线多。I2C线少,但速度慢。UART简单,但需要波特率匹配。
- 逻辑接口:数据格式、命令集、状态码。我建议用结构体封装,别用散乱的全局变量。
- 时序接口:采样率、刷新率、通信速率。这些要提前约定好,不然后期改起来很痛苦。
举个例子,测量模块和MCU的接口:
// 测量模块数据结构
typedef struct {
uint16_t adc_raw; // ADC原始值
float voltage; // 转换后的电压值
uint8_t range; // 当前量程
uint8_t status; // 状态:0=正常,1=超量程,2=欠量程
} MeasureData_t;
// MCU读取测量数据
MeasureData_t get_measurement(void) {
MeasureData_t data;
data.adc_raw = read_adc();
data.voltage = adc_to_voltage(data.adc_raw);
data.range = get_current_range();
data.status = check_range(data.voltage);
return data;
}
你看,接口定义清楚了,MCU只管调用get_measurement(),不用关心测量模块内部怎么实现的。这就是模块化的好处。
四、避坑指南
最后,分享几个我踩过的坑:
- 电源模块的纹波:我曾经用示波器量过,一个便宜的升压芯片,纹波有50mV,直接导致ADC测量结果波动。后来换了低纹波的LDO,问题解决。
- 显示模块的刷新率:别设太高,30Hz就够了。我试过60Hz,功耗翻倍,人眼还看不出区别。
- 通信模块的协议栈:蓝牙协议栈很复杂,建议用现成的模组,别自己写。我见过一个团队自己写蓝牙协议,半年没搞定。
- 测量模块的输入保护:一定要加!哪怕只是两个二极管和一个PTC。我烧过三块样板,都是因为用户误接了高压。
好了,这一章就到这里。系统级架构设计,说白了就是「分而治之」。把大问题拆成小问题,每个小问题用专门的模块去解决。下一章,我们会深入测量模块,聊聊ADC选型和前端电路设计。