做嵌入式这几年我陆陆续续肝过不少STM32项目但要说哪个项目“做完之后愿意公开全部资料、还觉得不丢人”这套智能输液监护调控系统算是其中之一。它不算那种特别炫技的玩意没有RTOS没有复杂的机器学习算法但它的完整度非常高从传感器采集、信号调理到PID闭环控制再到人机交互、异常报警甚至电机执行机构全部跑在一个中低成本的STM32平台上。换句话说这是一个“五脏俱全”的医疗电子小系统特别适合想做综合项目、准备电赛/毕设或者想了解嵌入式闭环控制完整链路的朋友参考。这篇文章我打算把整个系统的设计思路、硬件模块选型、软件核心逻辑、Proteus仿真搭建以及我在调试过程中踩过的坑全部摊开讲。代码、原理图和仿真工程文件我都已经整理成开源包了文章末尾会说明获取方式评论区置顶或者私信回复关键词都行。不管你手里有实物板子还是暂时只有仿真环境都能照着走一遍。1. 系统整体架构与设计思路1.1 这个系统到底解决什么问题静脉输液是临床最基础的治疗手段之一但传统输液完全靠护士人工巡查和患者家属盯瓶。滴速快了不行心脏负担大滴速慢了不行药效跟不上瓶空了更危险搞不好空气栓塞。我做的这套“智能输液监护调控系统-升级版”核心就干三件事实时监测通过红外对管传感器检测墨菲斯滴管内的液滴精确计算出当前滴速滴/分钟。自动调控把计算出的实际滴速和医护人员设定的目标滴速做比较通过步进电机挤压输液管自动调节滴速。异常报警输液完成、滴速异常过快/过慢、管路中有气泡等异常情况立即声光报警并通过串口将状态上报给上位机。“升级版”对比网上流传的早期版本主要的改进我放在了三个方面一是滴速控制从简单的“开关式”改成了增量式PID闭环控制过程更平滑不容易出现电机频繁启停的问题二是增加了OLED实时曲线显示能直观看到滴速的波动和收敛过程三是加强了仿真工程的完整度Proteus里可以直接跑通整个逻辑转实物之前可以先在电脑上把算法和状态机摸透。1.2 系统模块划分与数据流整个系统的数据流是单方向的非常清晰这也是我做嵌入式项目时习惯的思考方式传感器感知物理量 → 信号调理 → MCU采集与算法处理 → 执行机构动作/人机交互输出。我画一下逻辑框图文字版大家脑补一下连线液滴红外传感器 - 信号整形电路 - STM32定时器输入捕获通道 | v 滴速计算 PID控制器 | ----------------------------------------------- | | | v v v 步进电机(调速) OLED显示/按键设置 蜂鸣器LED报警 | | | ----------------------------------------------- | 串口打印 (可接上位机/ESP8266上云)从框图上可以看到这个系统覆盖了一个典型的闭环反馈控制系统的所有环节。很多初学者做项目容易止步于“采集-显示”也就是只做了框图的上半部分没有形成闭环。这个项目的价值就在于它逼你把下半部分也做出来不仅要“感知”还要“决策”更要“执行”。1.3 为什么选用STM32F103C8T6作为主控选型的时候其实纠结过一阵子也考虑过GD32、APM32这些国产替代还有ESP32。最终定在STM32F103C8T6理由很实在成本低蓝板子十几块钱一片翻新片嘉立创打板贴片整套下来成本可控在50元以内学生党完全能接受。外设够用这个项目需要的高级定时器输入捕获测滴速、PWM输出控步进电机、I2C驱动OLED、ADC可选做传感器校准、USART调试打印F103C8T6全部内置不需要外扩任何芯片。资料多到溢出Keil环境成熟标准外设库/HAL库都有遇到问题搜索引擎能解决99%的bug。这点对新手极其重要卡住了不至于孤立无援。性能有余量72MHz的主频跑这种级别的控制逻辑绰绰有余剩下的性能还能顺便带个简单的菜单系统和数据滤波。不是吹STM32有多神但在这个量级的项目里它就是最稳妥、最“够用”的选择。如果你非要用ESP32想顺便带WiFi上云也行但在做这种实时性要求较高的闭环控制时ESP32的ADC和定时器反而不如F103用着顺手而且功耗还高。2. 硬件设计详解原理图与模块选型硬件是整个系统的基础。我做项目有个习惯先画原理图再写代码。因为原理图阶段能把引脚分配、电气连接、电源规划都定下来写代码的时候照着原理图查引脚就行不用翻着数据手册来回对。2.1 主控最小系统与电源设计主控部分就是经典的STM32F103C8T6最小系统电路8MHz晶振作为HSE时钟源两个20pF负载电容复位电路用10kΩ上拉电阻100nF电容NRST引脚低电平复位BOOT0引脚通过10kΩ下拉电阻接地确保从Flash启动VDD引脚并联10410uF去耦电容模拟电源VDDA通过磁珠或10Ω电阻隔离后单独供电。电源这块我用了两种方案使用USB 5V供电时通过AMS1117-3.3稳压到3.3V给MCU供电。如果手头没有USB口也可以用3.7V锂电池ME6211低压差LDO方案效果差不多但要特别注意电机驱动那一路要和MCU电源严格分开。这里要提醒一个很多人容易犯的错误步进电机的电源不能和单片机共用同一个LDO。电机启动瞬间电流可能到几百毫安甚至更高会把3.3V拉垮轻则OLED闪烁重则MCU直接复位重启。我的做法是5V输入后一路经AMS1117给MCU另一路直接给ULN2003驱动板和电机供电两路共地GND一定是连在一起的但电源轨物理分离。2.2 液滴检测模块与信号整形液滴检测是整个系统的“眼睛”也是最容易出问题的地方。我用了比较成熟的红外对管方案一对红外发射管接收管LTH1550-01或者普通的ITR9608替代分列在透明滴管的两侧。原理上很简单当一滴液体穿过红外对管之间的空隙时由于液滴对红外光的折射和吸收接收管收到的光强会发生一个明显变化。把这个变化转换成电信号就能判断“掉了一滴”。但是红外接收管的输出信号非常微弱而且是模拟量直接接单片机引脚肯定不行。所以信号调理部分我做了两级处理一级放大/滤波利用LM393比较器或者用运放构建滞回比较器把接收管的微弱变化信号整形成标准的数字方波。关键在滞回比较器回差电压大约0.1V能有效防止信号在临界区域抖动导致计数毛刺。二级整形比较器输出后加一个74HC14施密特触发器相当于又做一次整形保证送给单片机定时器输入捕获引脚的波形边沿干净利落。实际做下来这一步对测速精度影响很大省了这个整形电路的话滴速显示基本都是跳来跳去的。信号整形的电路我贴一下关键原理图节点简化版红外接收管集电极接3.3V串联10kΩ电阻发射极接地发射极信号经过10kΩ100nF低通滤波后接到LM393同相输入端LM393反相输入端接电位器分压作为阈值LM393输出通过10kΩ上拉到3.3V然后接74HC14再次整形后输出到STM32的PA0引脚TIM2_CH1。注意红外发射管限流电阻要留足余量我实测用220Ω限流在5V供电时发射管压降约1.2V电流约17mA接收效果较好。电流太大容易衰减发射管寿命太小则信号太弱。2.3 电机执行机构与驱动电路滴速控制的“手”是步进电机。为什么不选直流减速电机因为步进电机能精确控制转角而且自带保持转矩断电后电机轴仍然锁定可以防止输液管在非工作状态下被意外挤压变形。我选用的是28BYJ-48步进电机ULN2003驱动板这个组合在某宝上十块钱一套灰度便宜做项目完全够了。28BYJ-48是一个四相五线电机步距角5.625°/64即一个脉冲理论上转5.625/64≈0.0879°配减速比1/64输出轴转一圈需要4096个步进脉冲。驱动方式上我用了经典的四相八拍模式相序为A→AB→B→BC→C→CD→D→DA每个脉冲走半步。这样电机运行更平稳噪声也小一些适合输液这种需要安静环境的场景。电机转角和挤液量的映射关系需要实测标定。我用的输液管是普通PVC输液管外径约4.5mm测试方法是先把管子充满水用电机从初始位置开始转动记录转动N步对应的液滴数或者液流量然后用游标卡尺量管子变形量。最后我标定得到电机每转动约220步对应挤压输液管造成的流量变化约为1滴以20滴/mL的标准输液管计。这个数值每个管子和安装方式都不一样建议拿到实物后自己再标一次写入代码的配置区。2.4 人机交互与显示模块显示部分用的是0.96寸I2C接口OLEDSSD1306驱动程序128×64分辨率。虽然不大但显示核心信息完全够用。我的界面规划方案是主界面显示目标滴速、当前实测滴速、累计液滴数、运行状态。曲线界面实时绘制最近60秒的滴速变化折线方便观察PID控制的效果。设置界面通过两个按键目标、目标-调整目标滴速短按步进1滴/分钟长按连续步进。按键我设计了两个S1、S2再加上一个复位键三个按键够了。代码里用GPIO外部中断消抖处理消抖用了10ms定时器扫描法比我以前用Delay延时消抖科学得多不阻塞主循环。2.5 声光报警电路报警电路也简单粗暴一个有源蜂鸣器NPN三极管8050驱动接PB5 一个红色LED指示灯接PB4。只要有源蜂鸣器直接GPIO输出高电平就响不需要PWM驱动省代码。异常条件触发时蜂鸣器响0.5秒、停0.5秒循环三次同时LED常亮直到人工按RESET或状态恢复正常。还有一路“液位监测”可选功能需要加一根金属探针到输液瓶口利用液体导电检测液位低。考虑到开源包里的仿真模型很难模拟这个物理过程我在代码里预留了这个接口但默认关闭注释也写得很清楚需要的人自己打开宏定义就行。3. 软件核心逻辑与算法实现3.1 滴速测量输入捕获与周期计算滴速测量的准确性直接决定整个控制系统的上限。我的实现方式不是数“每秒钟内有多少个脉冲”那样太粗略了秒级分辨率根本不够做PID反馈。我用的方法是测量两个连续下降沿之间的时间间隔T单位毫秒那么单滴滴速就是单滴滴速v 60000 / T滴/分钟举个例子如果红外信号相邻两个下降沿间隔T 500ms那么滴速v 60000 / 500 120滴/分钟。如果间隔1秒则v 60滴/分钟。在STM32上做这件事我配置了TIM2的输入捕获通道1PA0上升沿和下降沿都捕获记录两次捕获的计数器差值再根据定时器的分频系数换算成实际时间。考虑到红外信号的边沿抖动我在软件里加了一个“消抖窗口”只有当前后两次捕获的时间差大于80ms时才认为这是一滴有效液滴。不然信号毛刺会把滴速算得飞起。为了平滑显示和PID控制的稳定性我又加了一级滑动平均滤波把最近5滴的时间间隔存进一个环形缓冲区取平均值算滴速。这样即使偶尔有一滴没检测到滴速也不会瞬间突变到离谱。3.2 增量式PID控制器PID是这系统的灵魂。我用的是离散增量式PID输出的是“电机步数的增量”。先解释一下为什么要用增量式而不是位置式位置式PID会输出一个绝对值“当前转多少步”如果传感器瞬间丢了一滴数据计算出的位置输出会突变一大块电机跟着猛跳一下反而会干扰输液管。增量式PID输出的是“在这个控制周期内相对上一步应该再增加/减少多少步”每次只输出一个很小的修正量动作更平滑。公式如下Delta_Output Kp * (e[n] - e[n-1]) Ki * e[n] Kd * (e[n] - 2*e[n-1] e[n-2])其中e[n] 目标滴速 - 当前实测滴速。每50ms执行一次PID计算控制周期50ms计算结果的整数部分就是本周期内电机需要正转/反转的步数增量。参数整定过程我大概分享一下经验值。先置Kd0Ki0只调Kp从1慢慢往上加。观察OLED上滴速曲线如果滴速在目标值附近小幅震荡说明Kp太大如果响应太慢比如要一分多钟才逼近目标则Kp太小。我在这个系统里实测比较好的初始值是Kp18Ki0.8Kd6。Ki不能给太大因为环境本身响应有延迟积分太强容易产生超调甚至形成等幅震荡Kd主要起到阻尼作用能抑制蠕动泵周期扰动的超调。注意PID参数是跟执行机构速度、输液管弹性、电路温漂都相关的如果你复制代码后直接上电觉得反应不对先别急着改代码优先检查滴速测量值是不是稳定。测量不准PID调再好都白搭。3.3 主状态机设计程序的主框架我写成了一个简单状态机比“裸奔大循环一堆标志位”更容易维护。五个状态STATE_IDLE待机状态屏幕上显示“Press SET to Start”。此时电机不转传感器仍在检测但只更新液滴计数不做报警。STATE_RUNNING运行状态。PID闭环控制开启滴速稳定OLED实时刷新曲线。每10ms刷新一次显示每50ms执行一次PID控制。STATE_AUTO_ALARM异常状态。滴速超过目标±15%持续10秒以上或检测到连续3秒无新液滴疑似管路堵塞/瓶空进入报警蜂鸣器鸣叫同时在OLED上显示具体异常原因。STATE_PAUSE暂停状态。护士需要调整管路或者换药瓶时使用电机停止动作但传感器继续监测停止报警音。STATE_DONE输液完成状态。累计液滴数达到设定目标默认可以设总滴数OLED显示“INFUSION COMPLETE”自动进入待机。状态机的好处是每个状态下的操作边界清晰不容易出现“我在暂停状态还在PID出步进脉冲”这种低级错误。代码里我用了typedef enum定义状态然后一个switch-case处理状态跳转逻辑。3.4 电机控制与细分实现28BYJ-48电机属于四相五线步进电机驱动顺序我用查表法const uint8_t step_phase[8][4] { {1, 0, 0, 0}, // A {1, 1, 0, 0}, // AB {0, 1, 0, 0}, // B {0, 1, 1, 0}, // BC {0, 0, 1, 0}, // C {0, 0, 1, 1}, // CD {0, 0, 0, 1}, // D {1, 0, 0, 1} // DA };对应ULN2003的IN1~IN4引脚依次把表中对应项写到GPIO的ODR寄存器并延时2ms即500Hz的步进脉冲频率。实测这个速度下电机噪声小、转矩够用转速再快容易丢步再慢则响应太迟钝。注意在PID输出“需要调整X步”之后我并不是一把头把这X步连续走完而是每控制周期最多只走10步。也就是说即使PID算出需要调整200步这个周期也只执行10步剩下的累积到下一个周期继续修正。这样做的好处是避免电机猛转一大圈导致滴速瞬间过调代价是响应会稍慢一些但配合增量式PID收敛过程非常平滑肉眼几乎看不到液滴速度的突变。3.5 滤波与抗干扰细节说个细节红外传感器波形在经过比较器和施密特触发器之后理论上是干净的方波。但实际使用时如果周围有强阳光或者节能灯频闪信号边沿仍可能出现低频抖动。我的处理是三重保险硬件上光敏管套了一个黑色热缩管遮光筒只留一个1mm左右的窄缝让红外光只能从目标路径进来大幅抑制环境光干扰。软件上输入捕获中断里对时间差做判断小于80ms的“滴”直接丢弃人工限制最大滴速750滴/分钟远大于生理极限大于10秒的间隔认为“无滴”。数据链路上滴速计算后的滑动平均窗口长度为5既保证平滑度又不至于太滞后。这三点叠加下来实际调试中基本没有出现过快滴速误跳的问题至少是很偶发属于可以接受的工程误差。4. Proteus仿真搭建与联调验证4.1 仿真工程的搭建步骤这套系统的Proteus仿真文件我也一并在开源包里给出了。如果你手里没有实物或者想在动烙铁之前先验证代码逻辑用Proteus 8.15以上版本打开工程就能跑。仿真工程的关键部件配置我逐个说一下MCUSTM32F103C8T6Proteus内置了该模型芯片型号可直接在Pick Devices里搜到。需要正确连接到电源、晶振、复位。液滴传感器模拟Proteus里没有红外对管滴传感器模型我用了一个“信号发生器SIGNAL GENERATOR”来模拟传感器脉冲设置为方波、频率可调。调试PID时改信号发生器的频率就相当于改变“真实滴速”。电机及驱动Proteus里有步进电机模型可以选用MOTOR-STEPPER接上ULN2003芯片实现驱动。OLED显示Proteus的OLED模型我只在部分版本里见过不通用。所以我做仿真的时候是接了一个LCD1602来替代OLED显示并预留了I2C接口。如果你非要在仿真里看OLED可以外挂一颗AT24C02 I2C从机来模拟I2C总线时序工程量偏大或者干脆仿LCD1602版本。开源包里这两个版本的代码都有宏定义切换即可。4.2 仿真调试的要点与注意事项仿真最大的优势是不怕烧板子PLC逻辑随便折腾。但要注意仿真和实物的差异时间实时性Proteus的仿真速度受计算机性能影响有时候跑起来比实际慢不少。STM32本身带HSE时钟Proteus会尽量模拟实时性但如果你发现滴速显示翻倍了先别急着调PID看看仿真左下角的“Simulation Time”和真实时间比率很可能不是1:1。I2C时序仿真环境下I2C上拉电阻没接的话OLED/LCD显示屏直接白屏。这个坑我踩过Proteus默认不会像真实单片机那样自动启动内部上拉所以务必在SDA/SCL线上各接一个4.7kΩ上拉电阻。发电机模拟步进信号发生器频率设多少就对应多少滴/分钟。比如你设5Hz等于模拟300滴/分钟太高了步进电机还没跟上就报警了。仿真调试建议从1Hz起步即60滴/分钟比较接近真实生理滴速。4.3 虚拟实物联调的过程记录我在仿真里做了一轮完整的“从目标值突变到收敛”的实验过程如下开机复位OLED/LCD显示“IDLE”状态。按下SET按键进入RUNNING状态。初始信号发生器设为1Hz60滴/分钟此时电机静止因为在PID看来误差为零。突然把信号发生器频率从1Hz改为2Hz模拟真实滴速变成120滴/分钟PID检测到误差60输出电机正转调整步数软化输液管增大液滴速率。观察曲线实际滴速曲线先快速上升到100以上然后出现约20%超调再经过约20秒时间回落并稳定在120±5滴/分钟的范围。这个超调在可接受范围内。如果想压缩超调可以把Kd值增大到8~10但代价是系统反应会变迟钝对于输液这种非高危场景我宁愿要稳定不要敏捷。5. 常见问题与调试排查避坑手册做这个项目过程中我最常被问到的几个问题也是我自己调试时候差点头秃的坑统一整理成速查表大家直接抄作业。现象可能原因排查定位方法解决方案Keil下载程序时报error: no stm32 target found烧录器驱动没装好 / BOOT0跳线不对 / SWD引脚被占用点魔术棒-Debug里看下拉框是否能识别到设备SW Device重新安装ST-Link驱动确认BOOT0接地检查PA13/PA14有没有被复用成其他功能滴速显示乱跳忽大忽小红外传感器信号没整形干净 / 阈值设置不对用示波器或逻辑分析仪看信号整形后的波形确认是否为干净的方波调整LM393的阈值电位器加强施密特整形若环境光干扰严重给光敏管加遮光筒电机转几下就卡死或“嗡嗡”响不转步进相序不对 / 驱动频率太高电机丢步检查step_phase查表顺序是否和ULN2003接线一致对照电机说明书确认相序A(红)、B(橙)、C(黄)、D(粉)、公共端(蓝)降低步进脉冲频率至1kHz以下上电后OLED不亮 / 白屏I2C地址不对 / 电源不稳 / SDA SCL被占用扫描I2C设备地址0x3C还是0x3D检查OLED模块上的地址电阻0x3C通常为7位地址确认SDA/SCL对应STM32的PA9/PA10等引脚没被串口复用串口打印乱码板载晶振不是8MHz / 波特率误差用示波器测MCO引脚时钟频率如果板子是12MHz晶振在代码里修改HSE_VALUE为12000000PID始终无法稳定出现等幅振荡Ki太大或Kp太大调出滴速的历史波形观察振幅和周期先减小Ki到0.5以下如果依然振荡把Kp降到8~10再慢慢往上加电机不转但OLED显示正常电源共地问题 / ULN2003供电没接用万用表量驱动板VCC和GND是否有5V确保电机电源和MCU电源共地单独给电机供电不可共用3.3V输液时滴速一直偏低电机转很多步也没用管子没卡好 / 电机压杆位置不对手动转动电机查看管子变形量调整电机固定支架让步进电机的凸轮准确压在输液管的弹性段上标定转速-滴速映射表5.1 这个项目还能怎么扩展动手能力强的朋友完成这个基础版之后肯定不满足。我列几个我自己觉得很不错的扩展方向都是基于现有硬件小幅改动就能实现的场景1IoT远程监护现有代码串口打印的数据格式我定义成JSON了形如{target: 60, actual: 57, status: running, cnt: 1024}所以你只需要在UART2上接一个ESP8266模块把串口数据透传到WiFi上位机那边写一个简单的WebSocket服务端就能看到实时曲线。或者更省事接个TTL转USB模块上位机用Python pyserial读取打印内容画个matplotlib动态曲线。场景2多通道扩展一台STM32F103C8T6理论上有4个通用定时器可用于输入捕获还能再扩展一两个通道同时监护2~3路输液每路独立PID显示用页面切换。不过要注意F103C8的引脚资源有限三路以上建议换F103ZET6或者直接上F407。场景3掉电数据保存STM32F103C8T6内置的Flash有64KB程序编译完大约占掉25KB还剩30多KB。这些空间足够做一个简易的EEPROM模拟写入到Flash末尾扇区保存目标滴速、累计液滴数等信息掉电不丢失。代码里我已经预留了FLASH_SAVE_ENABLE的宏定义开关默认关闭需要的人打开就好。5.2 关于开源的说明这套项目的资料包我按照“就是给一个朋友直接能上手做”的标准整理的。里面包含以下内容源码Keil MDK工程基于标准外设库不是HAL库个人觉得F103用标准库更简单直接包含完整注释、可编译。原理图PDF工程源文件使用嘉立创EDA绘制的原理图你打开后在PCB模式下直接布局走线就能打样。Proteus仿真文件可直接运行的仿真工程含LCD1602替代OLED版本的完整代码。BOM表所有元器件的选型、型号、数量和参考购买价格照着买就行。说明文档包含每个模块的接线说明、管脚映射表、PID参数整定记录表格。在真正动手前还有几个重要的提醒这套系统定位是教学演示与功能验证不是医疗器械。请不要直接用于真实临床治疗或患者监护。医疗设备有严格的法规和准入要求DIY项目踏踏实实做学习与算法验证就好。电机挤压输液管的方式虽然能调速但长时间连续挤压容易导致管子疲劳变形建议做实验验证时用注水测试同时定期换新输液管。如果做毕设需要查重强烈建议在理解代码逻辑的基础上按自己的风格重写一遍主循环和PID部分既加深理解也能避免学术问题。6. 最后再分享一个实在的经验项目做完之后我把原理图和代码发给几个做医工结合方向的朋友看大家反馈最多的一点是这个项目把“传感器-算法-执行器”三者串成了一个完整的闭环而这个闭环思维恰恰是做嵌入式项目的核心。如果你准备照着做我给一个顺序建议先仿真再画板最后焊接调试。仿真阶段把所有逻辑跑通特别是PID参数先在仿真里调到基本稳定画板阶段认真核对BOM和封装焊接阶段从电源开始逐模块烧录测试不要一口气全部焊完再上电否则出了问题极难排查。真机调试的时候记得准备一个逻辑分析仪或示波器可以省去你大量瞎猜的时间。我自己在调试滴速测量的时候被一个“偶尔跳变”的波形折腾了一晚上后来一上示波器才发现是红外对管的阈值设定太临界环境光一变就触发毛刺。后来把阈值调低并加上遮光筒问题立刻消失。先让测量值“看得准”再谈控制“控得稳”——这个顺序不能反。目前这套系统我已经稳定跑了两周每天用蠕动泵循环测试电机没有丢步滴速误差基本控制在±3滴/分钟以内。对这个精度我已经比较满意了。后续如果有时间我打算给这个系统加一个简单的触摸屏控制界面把OLED和按键交互彻底升级掉到时候再单独写一篇分享。有什么问题评论区见。开源资料获取方式评论区或者私信发“智能输液”即可收到链接。