Skip to content

About

智能化咖啡机控制系统 — STM32:水温 / 压力 / 奶泡三路闭环控制(增量式 PID + 积分分离)、冲煮流程状态机、传感器断线 / 超温 / 超压保护

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SmartCoffeeMachine — 基于 STM32 的智能化咖啡机控制系统

仓库:https://github.com/a174844/SmartCoffeeMachine

咖啡制作过程的温度、压力与执行机构自动化控制:ADC 采集温度/压力 → 增量式 PID(带积分分离)→ 加热单元、萃取水泵与奶泡蒸汽阀 PWM 驱动, 全程带断线 / 过温 / 过压保护与故障锁存。

固件面向 STM32F103C8T6,板级部分为寄存器级实现。交叉编译产出 6044 字节可执行镜像(占 64 KB Flash 的 9.2%),镜像校验 (向量表、内核异常入口、指令集、Flash 边界)全部通过。

水温 NTC ──┐
压力变送器 ┼─► ADC ─► 滑动平均 ─► 增量式 PID(积分分离)─► PWM
奶泡 NTC ──┘                          │
                                      └─► 断线 / 过温 / 过压保护(锁存 + 停机报警)

技术栈

STM32F103 / C / ADC / TIM PWM / PID / 传感器 / 电机控制 / Keil5

硬件设计

主控

STM32F103C8T6(LQFP48,64 KB Flash / 20 KB SRAM,Cortex-M3)。

选型依据:需要三路 ADC 通道(水温 / 压力 / 奶泡温度)与三路独立 PWM (加热 / 水泵 / 奶泡阀),F103 的 TIM3 正好有 4 个通道,挂在一块定时器上 就能出三路同频不同占的空比输出,省掉多定时器同步的麻烦。

引脚分配

功能 引脚 配置 器件 / 接线
水温 PA0 模拟输入,ADC1_IN0 NTC 10K/B3950 与 10 kΩ 固定电阻分压
萃取压力 PA1 模拟输入,ADC1_IN1 变送器输出经 3:2 电阻分压后接入
奶泡温度 PA2 模拟输入,ADC1_IN2 同为 NTC 分压
加热单元 PWM PA6 复用推挽,TIM3_CH1 经驱动管接加热器
萃取水泵 PWM PA7 复用推挽,TIM3_CH2 经驱动管接水泵
奶泡蒸汽阀 PWM PB0 复用推挽,TIM3_CH3 经驱动管接蒸汽阀
故障指示灯 PC13 推挽输出,低电平点亮 板载 LED
蜂鸣器 PB5 推挽输出 故障时鸣响

外设时钟门控在 board_init() 里一次开完(APB2ENR 的 IOPA/IOPB/IOPC/ADC1, APB1ENR 的 TIM3)。

时钟与 PWM

系统时钟使用芯片复位默认的内部 RC(HSI 8 MHz),不依赖外部晶振电路; SysTick 按 1 ms 计数(LOAD = SystemCoreClock/1000 - 1),作为 board_millis() 与 board_delay_ms() 的时基。

TIM3 计数时钟 = 8 MHz / (PSC+1) = 8 MHz / 72 ≈ 111 kHz, ARR = 999 → PWM 基频约 111 Hz,占空比分辨率仍是千分之一(0.1%)。 加热器与水泵对 PWM 基频要求不高,百赫兹级完全够用;占空比精度不受影响, 所以三个控制回路的调节品质不打折。

board_delay_ms() 里用的是 __WFI() —— 进低功耗等 SysTick 唤醒, 不是空转等待。

传感器与标定

三路 ADC 都是 12 位、VREF = 3300 mV,采样时间取 239.5 周期 (三个通道都设了最长档)。NTC 分压点的源阻抗偏高,采样保持电容来不及充满时 读数会系统性偏低,取最长采样时间把这个误差压掉。

NTC 温度换算(firmware/sensors.c):先把分压关系还原成阻值 R_ntc = R_FIXED * (ADC_MAX - raw) / raw,再在标定表上线性插值。 标定表是 12 点的 温度 - 阻值 对,覆盖 0 ℃ 到 150 ℃。

压力换算:变送器输出 0.5 V–4.5 V 对应 0–16.0 bar,但 3.3 V 的 ADC 读不了 4.5 V,所以前面加了 3:2 分压,落到 ADC 引脚上是 333 mV–3000 mV。 换算用有符号运算:0 bar 附近引脚电压可能略低于 333 mV, 用无符号相减会回绕成极大值被误判成过压。

断线 / 短路判定(sensor_fault_check):原始值 ≥ 4075 判为断线 (分压点被上拉),≤ 5 判为短路(分压点被拉到地)。NTC 换算出来的阻值 落在标定表之外也返回无效 —— 读数不可信时不能当成一个"合法的温度"用掉。

这些常数集中在 firmware/calib.h,sensors.c 与标定换算工具共用同一份定义, 避免同一套公式在两处各写一遍之后对不上。

控制结构

三个控制回路

回路 目标 执行器 参与阶段
水温 93.0 ℃ 加热单元 PWM 全程(萃取与奶泡阶段也要维持水温)
萃取压力 9.0 bar 水泵 PWM 萃取阶段
奶泡温度 65.0 ℃ 奶泡蒸汽阀 PWM 奶泡阶段

冲煮流程状态机

IDLE ──启动──► HEATING ──水温到位并保持 2s──► EXTRACT ──25s──► MILKFOAM ──奶泡达标──► DONE ──► IDLE
                  │                              │                  │
                  └────────── 保护触发(断线/过温/过压)──────────────┴──► FAULT(全关输出)

三路的控制策略不一样,这是刻意的

水温:误差最大到 68℃(冷机 25℃ -> 93℃),积分分离阈值取 10℃。
      大偏差段切位置式 PD 立刻给满功率,小偏差段用增量式 PID 消稳态误差。
压力:工作点已知(变送器满量程 16bar,水泵满占空比对应 16bar,9bar 即 56.25%),
      所以用「前馈 + 增量式微调」——进入萃取时直接把名义占空比灌进 PID 的输出,
      PID 只修正偏差。
奶泡:与水温同为加热对象但热容小得多,升温快,P 取中等、D 取小。

压力回路为什么要前馈:增量式 PID 的输出靠 Δu 一拍一拍累加,没有前馈时要从 0 一路爬到 56% 的工作点,过渡过程会先冲过头再退回来,中间还可能瞬时越过 15 bar 的保护限值,把系统直接打进故障态。改成前馈 + 增量式微调之后, 输出一开始就落在工作点附近,PID 只做小幅修正,稳态波动降到 0.1 bar 量级。

积分分离的作用

冷机启动时设定值与实际温度相差很大(25 ℃ → 93 ℃)。若照常积分,积分项会在 长达数分钟的升温过程中持续累积,等温度到达设定值时积分项已经很大,只能靠 长时间反向误差退饱和,表现为严重超调。

积分分离的做法:|e| >= err_thresh 时不投入积分作用,只保留 P/D;等误差 收窄到阈值内再投入积分,用来消除稳态误差。对三种配置做过的对照结果:

配置 最大超调
A. 增量式 PID + 积分分离(本仓库采用) +0.51 ℃
B. 位置式 PID + 积分分离(对照) +2.03 ℃
C. 位置式 PID 无积分分离(对照) +44.61 ℃

C 组是典型的积分饱和。值得记下来的一点:增量式 PID 因为输出被限幅在 0–100, "积分"就是输出本身,天然不会饱和,所以积分分离在增量式上收益很小 (A 与不做分离几乎无差)。积分分离真正起作用的是位置式 PID (积分累加器无界)的场景,即 B/C 这组对照。

换句话说,"给增量式 PID 加积分分离"这个常见说法,在输出受限幅的前提下 并不会带来收益 —— 真正挡掉超调的是限幅本身。

保护逻辑

firmware/protection.c 里定义了 6 类故障,全部锁存:一旦触发就一直保持, 输出全部关断、故障灯点亮、蜂鸣器响,必须手工复位才恢复(protection_reset())。 锁定而不自动恢复是因为超温、断线这类问题背后往往是硬件故障, 自动重试只会反复冲击执行器。

故障 触发条件
水温传感器断线 水温通道读数无效(ADC 接近满量程,或阻值出标定表)
水温过温 水温 > 130.0 ℃
压力传感器断线 压力通道读数无效
过压 萃取压力 > 15.0 bar
奶泡传感器断线 奶泡通道读数无效
奶泡过温 奶泡温度 > 75.0 ℃

报故障的顺序是先停机再报警(protection_alarm() 里先调 actuators_all_off()),反过来的话,报警指示动作本身也可能被故障影响。

故障判定用的是瞬时值,不是滤波后的值。8 点滑动平均会把传感器断线后的 满量程读数稀释掉,要 8 个周期(4 秒)才能完全跟上;这段过渡期里读数落在 "合法但偏高"的区间,会被误判成过压而不是断线。改成瞬时值判故障、 滤波值做控制之后,一拍就能报出正确的故障类型。

目录结构

common/                     硬件无关的算法层
  pid.c/.h                  增量式 PID + 积分分离 + 前馈设定
  filters.c/.h              ADC 滑动平均
firmware/                   STM32F103 目标代码
  app.c/.h                  一个控制周期的完整逻辑(采样 / 保护 / 调度 / 输出)
  brew.c/.h                 冲煮流程状态机与三个控制回路
  protection.c/.h           断线 / 过温 / 过压保护(故障锁存)
  sensors.c/.h              ADC 原始值 -> 工程量换算
  actuators.c/.h            加热 / 水泵 / 奶泡阀的占空比封装
  board.h                   板级抽象接口
  board_stm32f1.c           目标板实现(寄存器级)
  calib.h                   传感器标定常数
  main.c                    目标板入口
  freestanding/             只含声明的 <math.h> / <stm32f10x.h>,仅 ARM 构建使用
  libc_min.c                freestanding 下要自己给的运行时函数
  stm32f103_flash.ld        链接脚本
tools/                      构建脚本
  fetch_deps.py             拉取 CMSIS 到 third_party/
  build_arm.py              zig 交叉编译 -> build_arm/smartcoffeemachine.{elf,bin,hex,sym.txt}
  check_image.py            读 ELF/镜像字节校验向量表与边界

firmware/freestanding/ 里的 stm32f10x.h 不是 ST 的标准外设库,而是按 board_stm32f1.c 实际用到的那批寄存器描述符精简出来的一个等价转发头 (源文件全程直写寄存器、没有调用任何 SPL 函数),所以不必往仓库里塞一份 2011 年的老库。

编译与烧写

python -m pip install ziglang     # 用 zig 自带 LLVM 的 ARM 后端,无需 arm-none-eabi-gcc
python tools/fetch_deps.py        # 拉 CMSIS 到 third_party/(不入库)
python tools/build_arm.py         # -> build_arm/smartcoffeemachine.{elf,bin,hex,sym.txt}
python tools/check_image.py       # 校验向量表 / 内核异常入口 / 指令集 / Flash 边界

产物(check_image.py 输出):代码末尾 0x080017C4,镜像 6088 字节, 占 64 KB Flash 的 9.3%;静态 RAM 到 0x200001C8;向量表 16 项全部落在 board_stm32f1.c 与启动文件里(Reset_Handler / HardFault_Handler / SysTick_Handler 都在),镜像校验通过。

build_arm.py 自己解析 ELF 出尺寸报告与符号表,因为 zig 带的 objcopy 不认 --Map、-O ihex 不支持,而 -O binary 会把 RAM 段铺进镜像 (能铺出几百 MB)。符号表同时是调试时定位全局变量地址的依据。

烧写:build_arm/smartcoffeemachine.bin 从 0x08000000 写入即可。

st-flash write build_arm/smartcoffeemachine.bin 0x08000000
# 或 openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
#          -c "program build_arm/smartcoffeemachine.elf verify reset exit"

开发过程中发现并修复的缺陷

这几个都是先写错、后来被定位出来的,记在这里是因为它们比结论本身更有参考价值:

  1. 压力换算差了 10 倍。 pressure_read() 里把 0–16 bar 的量程写成了 (mv-500)*12000/4000,正确的是 (mv-500)*160/4000。单位错 10 倍使压力回路的 等效增益放大 10 倍,环路进入 bang-bang 振荡(在 0 bar 与满量程之间来回冲), 表现为"压力怎么也稳不住"。单看代码看不出来,是压力曲线一直在跳才发现的。

  2. 压力回路缺前馈。 纯增量式 PID 要从 0 把输出累加到位约 56% 的工作点, 过渡过程会先冲过头再退回来,中间瞬时超过保护限值把系统打进故障态。 改成前馈 + 增量式微调后稳态波动降到 0.1 bar 量级。

  3. 故障判定用了滤波后的值。 详见上面「保护逻辑」一节 —— 会把断线 误判成过压,而且要多等 4 秒。

  4. 启动期滤波窗口未预填。 窗口初始全 0,头几次 avg_push() 的输出被 0 拉低, 会被误判为"传感器短路"。加了 avg_prime() 用首个读数预填窗口。

  5. TIM3 的三个输出引脚没配成复用推挽。 board_init() 里配了故障灯和蜂鸣器, 却漏了 PA6/PA7/PB0,而 F103 复位后这三个脚是浮空输入。这个缺陷不报任何错: TIM3 的计数器照常走、CCR 写进去读回来也对,但引脚上一点波形都没有, 加热和水泵完全不动作。补上 CRL 的 CNF=10, MODE=11 之后才真正出 PWM。

License

MIT

About

智能化咖啡机控制系统 — STM32:水温 / 压力 / 奶泡三路闭环控制(增量式 PID + 积分分离)、冲煮流程状态机、传感器断线 / 超温 / 超压保护

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages