2. 系统架构设计:硬件架构与软件架构

好,咱们进入第二章。系统架构设计,说白了就是给整个项目搭骨架。骨架搭歪了,后面填肉填得再漂亮也白搭。我个人习惯,做任何嵌入式项目,第一步不是写代码,而是画框图——硬件一张,软件一张。

这一章,咱们就聊聊窗帘电机这个项目,硬件上怎么选型、怎么搭,软件上怎么分层、怎么调度。嗯,这里要注意,窗帘电机看着简单,但量产之后坑不少。我当年第一个量产项目就是窗帘电机,踩过的坑,今天一并倒给你们。

2.1 硬件架构:四块板子撑起一个家

窗帘电机的硬件,说白了就四大模块:主控、驱动、电源、通信。缺一个,这电机就转不起来。

2.1.1 主控芯片选型

主控是大脑。我建议选ARM Cortex-M系列,比如STM32F103或者GD32F103。为什么?

  • 性价比高:几块钱一片,功能够用
  • 生态成熟:库函数、例程、调试工具,网上随便找
  • 功耗可控:待机时能跑到微安级

我在项目中遇到过用国产芯片替代的情况。说实话,有些国产芯片的ADC精度确实差点意思,但做窗帘电机这种低速应用,完全够用。别盲目追求高端,够用就好。

小提示:选型时留出20%的GPIO余量。我吃过这个亏——板子画好了,发现少一个IO控制指示灯,最后只能飞线,丑得要命。

2.1.2 驱动模块

驱动模块负责让电机转起来。窗帘电机一般用直流有刷电机或者步进电机。我推荐用直流有刷电机加H桥驱动,比如DRV8837或者L9110S。

驱动电路的核心就三点:

  1. 电流检测:加一个采样电阻,实时监测电机堵转
  2. PWM调速:用定时器输出PWM,控制电机转速
  3. 防反接保护:加一个二极管,防止电源接反烧芯片

我曾经在驱动电路上省了一个续流二极管,结果电机一停,反向电动势直接把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();           // 指示灯更新
        // 其他非实时任务...
    }
}

这里有个技巧:每个任务函数里不要做耗时操作,比如延时等待。如果某个任务需要等待,用状态机拆分成多个小步骤。否则一个任务卡住了,整个系统就瘫痪了。

经验之谈:主循环的执行时间要控制在10ms以内。如果超过这个时间,说明任务太多了,需要优化或者考虑上RTOS。

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布局,一步步来。