2. 系统架构设计:硬件架构与软件架构
好,咱们进入第二章。系统架构设计,说白了就是给整个项目搭骨架。骨架搭歪了,后面填肉填得再漂亮也白搭。我个人习惯,做任何嵌入式项目,第一步不是写代码,而是画框图——硬件一张,软件一张。
这一章,咱们就聊聊窗帘电机这个项目,硬件上怎么选型、怎么搭,软件上怎么分层、怎么调度。嗯,这里要注意,窗帘电机看着简单,但量产之后坑不少。我当年第一个量产项目就是窗帘电机,踩过的坑,今天一并倒给你们。
2.1 硬件架构:四块板子撑起一个家
窗帘电机的硬件,说白了就四大模块:主控、驱动、电源、通信。缺一个,这电机就转不起来。
2.1.1 主控芯片选型
主控是大脑。我建议选ARM Cortex-M系列,比如STM32F103或者GD32F103。为什么?
- 性价比高:几块钱一片,功能够用
- 生态成熟:库函数、例程、调试工具,网上随便找
- 功耗可控:待机时能跑到微安级
我在项目中遇到过用国产芯片替代的情况。说实话,有些国产芯片的ADC精度确实差点意思,但做窗帘电机这种低速应用,完全够用。别盲目追求高端,够用就好。
2.1.2 驱动模块
驱动模块负责让电机转起来。窗帘电机一般用直流有刷电机或者步进电机。我推荐用直流有刷电机加H桥驱动,比如DRV8837或者L9110S。
驱动电路的核心就三点:
- 电流检测:加一个采样电阻,实时监测电机堵转
- PWM调速:用定时器输出PWM,控制电机转速
- 防反接保护:加一个二极管,防止电源接反烧芯片
我曾经在驱动电路上省了一个续流二极管,结果电机一停,反向电动势直接把MOS管击穿了。嗯,从那以后,我再也不敢省这几个毛钱的器件了。
2.1.3 电源模块
电源是命脉。窗帘电机一般用24V直流供电,但主控和通信模块需要3.3V或5V。所以需要一级降压。
我常用的方案:
- 24V转5V:用MP2359或者LM2596,开关电源效率高
- 5V转3.3V:用AMS1117-3.3,LDO纹波小
这里有个坑:电机启动瞬间电流很大,会导致电源电压跌落。我建议在电源输入端加一个大电容,至少470μF。否则主控会频繁复位,你想想看,窗帘开到一半突然复位,多尴尬。
2.1.4 通信模块
窗帘电机需要跟遥控器或者智能家居网关通信。常用的方案有:
- 433MHz射频:成本低,穿墙能力强,但只能单向通信
- Wi-Fi(ESP8266/ESP32):可以接入互联网,但功耗高
- 蓝牙BLE:手机直连,功耗低,但距离短
我个人习惯,量产产品用433MHz加Wi-Fi双模。平时用433遥控器,需要远程控制时走Wi-Fi。这样既保证了可靠性,又兼顾了智能化。
2.2 软件架构:分层、状态机、任务调度
软件架构,说白了就是让代码有条理。我见过太多人,把所有代码都塞进main函数里,最后改一个bug引出三个新bug。嗯,咱们不干这种事。
2.2.1 分层设计
我习惯把软件分成三层:
- 硬件抽象层(HAL):直接操作寄存器,封装成API,比如GPIO_Write、UART_Send
- 中间层:处理协议、算法、状态机,比如电机控制算法、通信协议解析
- 应用层:业务逻辑,比如“收到开窗指令→调用电机控制→反馈状态”
为什么要分层?说白了就是为了可移植性。如果哪天要换主控芯片,只需要重写HAL层,中间层和应用层基本不用动。我在项目中换过一次主控,从STM32F103换到GD32F103,只改了几十行HAL代码,上层完全没动。
2.2.2 状态机设计
窗帘电机的工作状态其实很简单:
- 空闲:等待指令
- 开启:电机正转,窗帘打开
- 关闭:电机反转,窗帘闭合
- 停止:电机停转,保持当前位置
- 堵转保护:检测到电流过大,立即停止
我建议用枚举类型定义状态,用switch-case实现状态切换。别用if-else嵌套,否则代码会变成一团乱麻。
typedef enum {
STATE_IDLE,
STATE_OPENING,
STATE_CLOSING,
STATE_STOPPED,
STATE_STALL_PROTECT
} MotorState_t;
MotorState_t currentState = STATE_IDLE;
void MotorStateMachine(void) {
switch(currentState) {
case STATE_IDLE:
// 等待指令
if(GetCommand() == CMD_OPEN) {
MotorStartForward();
currentState = STATE_OPENING;
}
break;
case STATE_OPENING:
// 检测是否到达限位或堵转
if(IsLimitReached() || IsStallDetected()) {
MotorStop();
currentState = STATE_STOPPED;
}
break;
// 其他状态类似...
}
}
我曾经在状态机里忘了处理“堵转保护”状态,结果电机卡住后一直嗡嗡响,最后烧了。从那以后,我每个状态都加了一个超时保护,超过5秒没切换就强制停止。
2.2.3 任务调度
窗帘电机不需要跑RTOS,一个简单的前后台系统就够了。前台是中断,后台是主循环。
我常用的调度方式:
- 定时器中断:每1ms触发一次,处理时间敏感的任务,比如PWM更新、电流采样
- 主循环:处理非实时任务,比如状态机切换、通信解析、按键扫描
主循环里,我习惯用一个简单的轮询调度器:
void main(void) {
SystemInit();
while(1) {
MotorStateMachine(); // 状态机
CommProcess(); // 通信处理
KeyScan(); // 按键扫描
LedUpdate(); // 指示灯更新
// 其他非实时任务...
}
}
这里有个技巧:每个任务函数里不要做耗时操作,比如延时等待。如果某个任务需要等待,用状态机拆分成多个小步骤。否则一个任务卡住了,整个系统就瘫痪了。
2.3 硬件与软件的接口设计
硬件和软件之间,需要一个清晰的接口文档。我建议用结构体把硬件资源抽象出来:
typedef struct {
GPIO_TypeDef* motorPwmPort;
uint16_t motorPwmPin;
GPIO_TypeDef* motorDirPort;
uint16_t motorDirPin;
ADC_HandleTypeDef* currentSenseAdc;
UART_HandleTypeDef* commUart;
} HardwareConfig_t;
// 初始化时传入硬件配置
void Motor_Init(HardwareConfig_t* hwConfig);
这样做的好处是,换硬件平台时,只需要改HardwareConfig_t里的配置,驱动代码完全不用动。我在项目中用这个方式,从STM32F103移植到GD32F103,只花了半天时间。
2.4 避坑指南
最后,分享几个我踩过的坑:
- 电源纹波:电机启动时,电源纹波会干扰通信模块。我后来在电源和通信模块之间加了一个LC滤波,问题解决。
- GPIO电平不匹配:主控是3.3V,驱动模块是5V,直接连会烧IO。加一个电平转换芯片,比如TXS0108E。
- 状态机死循环:如果状态机进入了一个未定义的状态,系统会卡死。我习惯在每个case后面加一个default,强制回到空闲状态。
好了,第二章就到这里。系统架构设计,说白了就是“先想清楚再动手”。硬件上选型留余量,软件上分层解耦合,状态机考虑所有边界情况。做到这三点,项目就成功了一半。
下一章,咱们聊聊具体的硬件电路设计,从原理图到PCB布局,一步步来。