
做嵌入式这几年我见过不少带“智能”二字的开源项目但很多停在“检测”这一步读个传感器、显示个数字就结束了。真正能把检测、闭环控制、报警、上位机通讯串成一条完整链路的其实不多。这套智能输液监护调控系统升级版是我自己从零开始搭的一套完整方案主控用STM32F103C8T6源代码、原理图和仿真工程全部开源。它解决的是输液场景里最实际的问题滴速怎么测准、怎么自动调稳、异常怎么报警、数据怎么发出去。如果你正在做课程设计、毕业设计或者想从“会读传感器”进阶到“会做闭环控制”这个项目应该能帮你省不少时间。先声明一点这套系统的定位是学习与实验验证项目不是医疗产品不能直接当临床设备用。但作为电子设计、嵌入式开发和自动控制的学习载体它的完整度非常高——有光电检测、有步进电机执行机构、有PID闭环、有液晶显示、有串口协议几乎把一个控制类项目该有的环节都覆盖了。1. 项目整体设计与升级点拆解1.1 系统到底在解决什么问题传统输液依靠护士手动转动滚轮调整流速滴速漂了不容易发现患者活动、液体挂高挂低都会让滴速悄悄变化。这套系统的目标就是把这件“靠人盯”的事自动化。系统运行流程是这样的红外对管安装在滴壶两侧液滴落下时短暂遮挡红外光传感器产生一个脉冲STM32通过外部中断统计脉冲并计算实时滴速用户用按键设定目标滴速后PID控制器把“目标滴速”和“实测滴速”做比较输出控制量调速步进电机驱动的蠕动泵同时液位传感器监测输液瓶剩余量温度传感器读取液体温度OLED屏幕显示全部运行参数一旦滴速偏差过大、液位过低或者出现堵塞蜂鸣器、LED和上位机同时报警。这里最关键的还不是“检测”而是“调控”。很多人做类似项目只做到测量和显示但升级版加入了步进电机和PID闭环让滴速真正能自动拉回目标值。这是整个项目技术含量最高、也最适合用来讲清楚控制原理的部分。1.2 升级版相对基础版改了什么如果你手上有旧的基础版代码对比一下就很明显了。功能维度基础版升级版滴速检测红外对管计数红外对管 去抖 滑动平均滤波滴速控制无只报警步进电机蠕动泵 增量式PID闭环液位检测无非接触液位传感器低位报警温度检测无支持DHT11/DS18B20人机交互LED指示OLED显示 按键设置目标滴速通讯无串口自定义协议可扩展WiFi模块软件结构顺序执行状态机 模块化文件预留RTOS接口可靠性基本没有看门狗、按键消抖、数据滤波、异常恢复升级版最大的变化是“从只测不控变成了测控一体”。基础版检测到滴速不对只能叫护士升级版会自己先把滴速调回来实在调不回来再报警。这个逻辑上的转变恰好把自动控制里“反馈”的概念体现得很直观。1.3 主控选型为什么还是STM32F103C8T6有人问2025年了为什么不用H7、不用国产更高端的芯片我的回答很简单这套项目的需求F103C8T6完全够用而且资料多到你踩坑都有人垫着。F103C8T6有64KB Flash、20KB RAM、3个USART、多个定时器、2个ADCI2C和SPI也齐全。滴速检测用外部中断电机控制用定时器PWM或者GPIO脉冲显示用I2C通信用USART所有外设正好被利用起来。而且这颗芯片的购买成本很低开发板、核心板、最小系统板遍地都是教学视频和示例代码多对刚接触STM32的人非常友好。如果以后想升级彩色大屏、跑LVGL或者更复杂的GUI再考虑STM32F407或者F429不迟。选型要跟着需求走不是越贵越好。2. 硬件设计与原理图要点2.1 主控最小系统与电源电路原理图是整个项目的地基。我见过太多照着网上模板抄原理图最后板子跑不起来的情况问题往往出在最基础的最小系统上。STM32F103C8T6最小系统包含几部分8MHz无源晶振 两个负载电容。电容具体取多大用公式算一下心里就有底了外接电容C 2 × 晶振负载电容CL - PCB杂散电容Cstray。比如某颗晶振的CL是12pFPCB杂散电容按3~5pF估算C 2×12 - 4 20pF取两个20pF就对了。如果CL是18pF算下来要30~33pF。网上很多人直接抄20pF只能说在多数情况下能用但严格按公式来更稳妥。VCAP1和VCAP2引脚必须各接一个2.2uF电容到地。这个电容漏掉的话芯片上电复位和唤醒都会出问题。很多STM32F103板子“下载正常但程序不跑”原因就在这。NRST复位电路10kΩ电阻上拉到3.3V再并联一个100nF电容到地可选一个复位按键。BOOT0下拉到GNDBOOT1随意处理。这样默认从Flash启动。SWD下载接口引出SWDIO、SWCLK、GND、3.3V、NRST五根线就够了。下载调试非常方便。电源部分USB 5V进AMS1117-3.3降到3.3V输入输出各加10uF和100nF电容。每个VDD引脚旁边放一个0.1uF去耦电容VDDA用1uF0.1uFVREF接3.3V。这些元件不是可有可无的装饰。去耦电容靠近芯片引脚放置是为了给高速开关的数字电路提供瞬态电流回路晶振靠近MCU、封装走线对称是为了减少寄生电容和干扰。原理图上省几颗电容后面调试能多花几天。2.2 滴速检测、电机驱动与传感器接口电路滴速检测是整个系统的“眼睛”电路上必须可靠。红外对管由发射管和接收管组成发射管串一个限流电阻接收管输出信号。液滴经过检测区域时遮挡红外光接收管上的电压发生变化。问题是液滴下落很快信号脉宽短、边沿不够陡直接接GPIO容易误触发。升级版的硬件方案是接收管输出信号先经过LM393比较器整形整成干净的方波再送进STM32的外部中断引脚。LM393是开集输出记得加上拉到3.3V的电阻一般4.7kΩ到10kΩ。如果不想用比较器至少也要加一个RC滤波电路否则滴速计数会跳得怀疑人生。电机驱动我选的是28BYJ-48步进电机加ULN2003驱动板。这个组合成本低、驱动逻辑简单四个IO口输出脉冲序列就能转。最重要的是步进电机在低速下有稳定的扭矩蠕动泵夹住输液管后转速和滴速近似线性非常适合PID控制。如果你以后项目功率更大可以换成42步进电机加DRV8825原理一样。电机和MCU要特别注意供电关系。电机启动瞬间电流很大会拉低电源电压导致MCU复位。升级版的推荐做法是电机单独用5V或12V适配器供电驱动板的电源和MCU电源分开控制信号在两者之间加光耦隔离或者至少保证可靠共地并在电机电源两端加大容量电容。别小看这个问题“一开电机MCU就重启”是这类项目排名靠前的经典故障。液位传感器用非接触电容式液位传感器贴在输液瓶外壁检测低液位输出高电平或低电平。温度部分可以选DHT11或者DS18B20DHT11电路简单但时序比较敏感数据线要接4.7kΩ上拉电阻而且读取间隔必须大于1秒。DS18B20抗干扰更好就是驱动代码稍长一点。OLED显示屏用0.96寸I2C接口的SSD1306SCL、SDA各接一个4.7kΩ上拉电阻地址一般是0x3C。2.3 原理图绘制与PCB布线心得原理图可以用嘉立创EDA、Altium Designer、KiCad都行开源资料里最好同时放出原理图PDF和可编辑源文件方便别人查看和修改。绘制时强烈建议分模块画电源模块、STM32最小系统、传感器接口、电机驱动接口、显示和按键、扩展排针。网络标号要见名知义比如DROP_SENSOR、PUMP_STEP、MOTOR_DIR、LIQUID_LEVEL不要用N1、N2这种谁都看不懂的标号。PCB布线时晶振尽量靠近MCU的OSC_IN/OSC_OUT引脚走线短且对称晶振下方不要走其他信号线。模拟信号线比如滴速传感器输入要短最好有包地保护。电机驱动部分和传感器检测部分分区布局大电流回路和小信号回路不要交叉。所有芯片电源引脚都要放0.1uF去耦电容不是一个“总电容”替代得了的。3. 软件设计与核心代码逻辑3.1 模块化分层与状态机设计这套代码我没有一上来就上RTOS而是用裸机前后台加状态机的方式写因为逻辑并不复杂裸机更容易看懂。main函数里初始化外设后进入一个主循环中断里做时间敏感的信号采集主循环做PID计算、显示刷新和通讯处理。所有耗时操作都不要用delay死等用标志位加非阻塞延时。系统状态划分成几个基本状态typedef enum { ST_IDLE, // 待机 ST_SETTING, // 设置目标滴速 ST_RUNNING, // 正常运行 ST_ALARM, // 报警 ST_PAUSE // 暂停 } SysState;待机状态下系统只做初始化检查设置状态下通过按键调整目标滴速运行状态下执行检测、PID和控制报警状态下蜂鸣器和LED动作并上报上位机。状态之间切换要确保资源释放干净比如从运行切到暂停时电机一定要停下来不然蠕动泵继续转就是安全隐患。模块化方面我把代码分成drop_sensor.c、pid.c、motor.c、display.c、key.c、uart_protocol.c这几个独立文件。这样做的好处是你想换一个电机驱动芯片只需要改motor.c想换一个显示屏只需要改display.c其他模块不用动。3.2 滴速检测从计数到滑动平均滴速检测在中断里做。用STM32的外部中断信号线接在PA0上滴落一次触发一次中断。关键代码如下volatile uint32_t last_tick 0; volatile float instant_rate 0.0f; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin DROP_SENSOR_PIN) { uint32_t now HAL_GetTick(); uint32_t interval now - last_tick; last_tick now; if (interval 30) // 滤掉抖动造成的毛刺 { // 按当前滴间间隔换算瞬时滴速滴/分钟 instant_rate 60000.0f / interval; } } }瞬时滴速能不能直接用不能。液滴之间的时间间隔并不是恒定的重力、管路弹性和药液表面张力都会导致间隔抖动。如果直接把瞬时值送进PID控制量会跟着一起抖系统很难稳定。升级版的处理方法是加滑动平均滤波。维护一个长度为N的缓存数组每测到一滴就更新一次平均值#define RATE_BUF_SIZE 8 float rate_buffer[RATE_BUF_SIZE]; uint8_t rate_index 0; float rate_sum 0.0f; float filtered_rate 0.0f; void update_filtered_rate(float new_rate) { rate_sum - rate_buffer[rate_index]; rate_buffer[rate_index] new_rate; rate_sum new_rate; rate_index (rate_index 1) % RATE_BUF_SIZE; filtered_rate rate_sum / RATE_BUF_SIZE; }N取多少也有讲究。太小了滤波效果不明显太大了测量滞后PID控制器反应不过来。我实测下来8是比较合适的值既能把抖动压住又不至于让滴速响应慢半拍。3.3 滴速闭环控制增量式PID落地PID部分是整个软件的核心。控制目标是让实测滴速贴近目标滴速执行机构是步进电机驱动的蠕动泵。控制方式是每隔1秒计算一次当前滴速误差用增量式PID算出新的控制量输出映射到步进电机的转速。增量式PID公式Δu(k) Kp×[e(k) - e(k-1)] Ki×e(k) Kd×[e(k) - 2×e(k-1) e(k-2)]这样算出来的Δu是上一次控制量基础上的增量不会把累计误差一次性全压上去对电机这种执行机构来说更平稳。代码实现我习惯用结构体typedef struct { float Kp; float Ki; float Kd; float target; float actual; float err_prev; float err_prev2; float delta_out; float out_min; float out_max; } PidController; float pid_increment(PidController *pid, float actual) { float err pid-target - actual; float delta pid-Kp * (err - pid-err_prev) pid-Ki * err pid-Kd * (err - 2.0f * pid-err_prev pid-err_prev2); pid-err_prev2 pid-err_prev; pid-err_prev err; if (delta pid-out_max) delta pid-out_max; if (delta pid-out_min) delta pid-out_min; return delta; }调参顺序是我踩过坑之后总结出来的先把Ki和Kd设为0只调Kp观察滴速是否朝目标方向走出现周期振荡就把Kp适当减小然后加Ki消除目标值和实际值之间的静差Ki不要太大否则会低频振荡最后根据情况决定要不要加Kd。Kd对噪声很敏感而滴速测量已经经过了滑动平均所以这项通常给得很小甚至为0也可以。还有一个容易被忽略的细节如果长时间检测不到新液滴要判定“停滴”或“堵塞”进入报警状态。所以PID循环里也要做一个超时判断比如连续3秒没有触发滴速更新就置报警标志。3.4 串口协议与上位机对接升级版加入了自定义串口协议目的是把系统状态实时上报给上位机也为后续接WiFi模块、MQTT上云留好了接口。协议帧格式是这样的帧头1帧头2命令字数据长度数据校验和0xAA0x55CMDLENDATASUM命令字0x01表示上报状态0x02表示设置参数。状态上报的数据包括目标滴速、实测滴速、液位状态、温度、报警标志按固定顺序排列。校验和取前面所有字节累加的低8位接收端必须校验通过才执行命令否则直接丢弃避免粘包和错帧。接线就是把STM32的USART1 TX、RX接到USB转TTL模块波特率115200打开串口助手能看到周期性的状态帧。如果你手上正好有ESP8266模块把USART2让它透传连到家里的WiFi再把MQTT客户端跑起来手机端就能看到实时滴速了这部分在仓库里给了引导文档。4. 仿真环境搭建与整机验证4.1 Proteus仿真流程仿真工程不能用来看热闹它最大的价值是让没有硬件的人也能把逻辑跑起来。我这套仿真用的Proteus 8流程非常简单。第一步新建Proteus工程从元件库找到STM32F103C8T6。第二步搭最小电路电源、复位、晶振这些在仿真里可以简化但为了和原理图对应建议都放上去。第三步添加外设LED、蜂鸣器、按键、虚拟串口终端。第四步模拟滴速传感器——这一点比较关键Proteus里没有现成的红外对管模型我用一个信号发生器DCLOCK产生周期性脉冲接到STM32的外部中断引脚上用脉冲频率模拟滴速。这样你想测试“滴速突然变化时PID能不能拉回来”只需要调节信号发生器的频率参数非常直观。第五步双击芯片加载编译好的hex文件。在Keil MDK里编译工程输出文件在MDK-ARM目录下的xxx.hex。第六步点运行OLED显示屏、按键、报警、串口输出就都能在仿真里看到了。仿真时有一个问题要注意Proteus里DHT11模型经常卡住读不到数据。这个不用纠结仿真工程里我默认用一个可调电阻分压接到ADC引脚来模拟温度值代码里读ADC换算成温度。等你拿到真实硬件再把温度传感器驱动打开。4.2 在线仿真Wokwi等平台除了Proteus现在还有Wokwi这类在线仿真平台浏览器打开就能用非常适合及时验证一段逻辑。Wokwi的元件库里可以搜索STM32F103C8T6能找到的话直接拖出来面试用。它支持在浏览器里连线、写代码、跑仿真体验和Proteus互补。我实际用下来的感受是Wokwi更适合快速验证传感器逻辑、按键扫描、状态机切换这类纯软件的流程Proteus更适合做整机电路级别的仿真演示比如按键、电机、显示、串口一起跑起来的效果。两类工程我在仓库里都放了方便不同习惯的人选择。4.3 仿真和实物的差异仿真毕竟是仿真它验证的是逻辑不是物理世界。仿真里的脉冲信号非常干净真实红外对管的信号却带着毛刺和抖动仿真里的步进电机只是一个转速模型不会产生电流冲击真实电机一启动就把电源电压拉低仿真里也不存在线缆寄生电容、地线阻抗、静电干扰这些问题。所以仿真跑通只是第一步。烧录到真实板子之前一定要分级测试先确认电源输出正确再测最小系统能否烧录和运行然后分别验证传感器、电机、显示、通讯每个模块最后再整合整个系统。这个顺序能帮你快速定位问题出在哪一层而不是一上来就东改西改。5. 常见问题与排查技巧实录5.1 下载报错no stm32 target found这是一个出现频率很高的报错。遇到它不要慌按顺序排查。首先确认供电3.3V和GND是否正常芯片是否发烫然后检查SWD接线SWDIO、SWCLK、GND三条线有没有接反目标板是否需要额外供电再降低下载速度有些线长了或者干扰大高速下载不稳把速度降到1MHz甚至更慢试试还可以尝试“按住复位点击下载再松开复位”的方式让芯片在上电复位瞬间进入调试模式。如果是新出厂的芯片报“debug authentication”这类提示可能是调试口被保护了用STM32CubeProgrammer连接后在Option Bytes里解除读保护。新芯片如果上电后程序卡在某种异常状态也可以先用串口ISP方式全片擦除再重新下载。5.2 晶振不起振与时钟配置异常晶振不起振的典型现象是下载正常但程序不跑或者一运行就卡死在等待时钟就绪的循环里。检查思路是先看晶振两个引脚有没有焊好附近有没有虚焊、连锡再看负载电容值是否合理公式在前面已经算过了用示波器探头测晶振引脚波形正常应该能看到振荡就算幅度不大也没关系常见的是完全没波形。最快速的定位方法是先把代码里的时钟源切换到内部HSI把系统时钟改成8MHz。如果改成HSI之后程序能跑那问题基本就锁定在外部晶振及其电路上了。这个方法我很常用比换电容还快。5.3 ADC读数漂移和电机干扰复位ADC读数漂移多半是电源纹波和地线噪声带来的。软件上可以做多重采样取平均值或者中值滤波硬件上在ADC引脚附近加100nF10uF滤波电容必要时串一个小电阻形成RC低通。如果ADC在电机运行时漂得厉害优先检查电机和MCU是否真的共地电机驱动器电源是否干净控制信号线上有没有串电阻限制瞬态电流。“一开电机MCU就复位”这个问题我在2.2节说过核心原因是电机启动电流把电源电压拉到了单片机最低工作电压以下。解决办法按优先级排电机用独立电源控制信号光耦隔离电机电源两端加大容量电容470uF到1000uF主控电源用电感或磁珠和电机电源隔开。这四条能解决绝大多数这类问题。5.4 串口和驱动识别问题电脑上设备管理器里出现“STM32 Virtual COM Port”带黄色感叹号通常是USB CDC驱动没装好重新安装STM32官方驱动或者更新到最新版本往往能解决。如果用的是CH340USB转串口模块装上驱动还识别不了优先怀疑USB线是不是只有充电没有数据功能——这种线特别迷惑人换一根数据线试一下。串口乱码的话检查三点波特率和代码里是否一致、TX和RX有没有接反、两个设备之间GND有没有共地。现象可能原因排查 / 解决办法下载失败 no target found接线错误、供电不足、芯片锁死、速度过高检查3.3V与GND、SWD连线降低下载速度按住复位再点下载CubeProgrammer解锁程序下载后不运行晶振不起振、VCAP电容缺失、BOOT设置错改用HSI验证检查VCAP电容确认BOOT0接地滴速显示跳动很大传感器信号抖动、未滤波加比较器整形软件做滑动平均检查红外对管安装位置一开电机MCU就复位电机启动电流拉低电源电压电机独立供电加大电容光耦隔离控制信号虚拟串口感叹号CDC驱动缺失或冲突重新安装官方驱动禁用驱动签名换USB线/口串口乱码波特率不一致、TX/RX接反、未共地统一波特率交换TX/RX连接GNDDHT11一直读不到数据上拉电阻缺失、读时序不对、读取间隔太短加4.7k上拉每次读取间隔大于1秒检查引脚配置6. 开源资料组织与二次开发建议6.1 仓库目录与快速上手开源不只是把文件丢到网上资料组织得好别人才能快速跑起来。仓库目录大概是这样Smart-Infusion-Monitor/ ├── hardware/ │ ├── schematics_pdf/ // 原理图PDF方便直接看 │ ├── pcb/ // 可编辑的PCB源工程 │ └── datasheets/ // 用到的芯片和数据手册 ├── firmware/ │ ├── Core/ // HAL库生成的代码 │ ├── Drivers/ // ST官方驱动 │ ├── MDK-ARM/ // Keil工程编译输出hex │ └── README.md // 编译和烧录说明 ├── simulation/ │ ├── proteus/ // Proteus仿真工程 │ └── wokwi/ // Wokwi在线仿真工程 └── docs/ ├── 硬件调试记录.md └── 参数整定记录.md拿到代码后用Keil MDK打开MDK-ARM目录下的工程文件先确认电脑上装了STM32F1系列的器件支持包编译通过后用ST-Link下载打开串口助手就能看到状态数据。烧录时务必确认芯片型号是STM32F103C8T6F1系列选错型号一样能烧但跑起来可能出奇怪问题。6.2 怎么改成自己的课程设计或毕业设计这套代码直接交上去肯定不行但你可以基于它做合理的二次开发。最容易出成果的几个方向把温度传感器从DHT11换成DS18B20并完善驱动把步进电机驱动换成DRV8825功率更大控制更精细给系统加上WiFi模块做一个手机App或者网页可视化界面把单通道改成双通道一个STM32管理两路输液重点考验你的资源分配和任务调度能力。代码本身的模块化结构就是为了方便替换而设计的。换传感器就改对应模块换通讯协议就改uart_protocol.c主循环基本不用动。不要再出现把全部功能堆在main.c里的写法那会把自己也绕晕。6.3 后续可以怎么扩展如果只是想继续深入学习这个系统可扩展的点非常多。想搞实时系统就把它移植到FreeRTOS上检测任务、控制任务、显示任务、通讯任务分开跑想搞图形界面换一块彩色TFT屏幕跑LVGL把滴速曲线画出来想搞物联网加一个ESP8266或者4G模块把数据推到云平台想搞低功耗在待机状态进入STOP模式用RTC唤醒定时巡检用按键或传感器事件唤醒处理任务。还有一个容易被忽略但有意义的改进增加历史数据掉电存储。因为滴速、温度、报警记录这种数据掉电后不想丢可以用STM32内部的Flash模拟EEPROM来保存。这也是一个很好的学习点。最后再分享一点个人的实际体会。这套系统我从基础版一路改到升级版最大的教训是先把检测做稳再谈控制。一开始我急着调PID结果滴速本身就没测准PID算出来的控制量全是噪声电机一抖一抖的根本稳不住。后来老老实实把红外对管信号整形做好把滑动平均滤波加上再回来调PID感觉完全不一样。很多同学卡在“PID调不通”上其实问题不出在PID而出在被控变量的测量上。希望这份开源的代码、原理图和仿真能帮你少走几步弯路也期待你在滴速滤波、参数整定或者上位机可视化上有更好的改进。