
1. 为什么做“升级版”初版系统暴露的三个硬伤1.1 初版只能监测不能调控护士照样来回跑先说这个项目最初的定位。医院里输液监护本质上是一个“低频但高代价”的重复劳动护士需要定时巡视观察滴速是否正常、剩余药液还有多少、输液管有没有气泡、针头有没有回血。初版我做的那套系统说白了就是一个会报警的计时器——红外对管检测滴速OLED屏显示实时数据和报警信息能做的极限是“滴速异常了响一声”。真拿去跟临床场景比对就会发现只监不控的价值大打折扣。护士听到报警跑过来发现滴速偏快还得手动去拧滚轮调节器滴速偏慢又得跑一趟。更尴尬的是很多报警是瞬时干扰触发的等护士走到病床前滴速自己又恢复正常了白跑一趟。跟几个护士聊完我最深的感受是她们要的不是一个“告诉你出问题”的设备而是一个“尽量别出问题、出了小问题自己先处理掉”的设备。这就是升级版立项的根本动机——把闭环控制加进去让系统在设定滴速范围内自动调速只剩真正需要人介入的异常才触发人工处理。1.2 滴速检测误报频繁数据一抖全盘皆输初版滴速检测用的是红外对管加一颗比较器理论模型很简单水滴从滴壶落下时遮挡红外光路接收管电平翻转MCU捕捉边沿就能算出滴速。实际用起来问题一堆。最典型的是环境光干扰白天病房靠窗位置的阳光、走廊射灯、隔壁床的移动设备屏幕都会让接收管信号叠加噪声比较器输出抖动MCU在1秒内数出十几个虚假边沿实际滴速5滴/分钟显示出来却飙到60滴/分钟。另外水滴的遮挡时间非常短大概10到20毫秒传感器响应如果不够快边沿会被漏掉。初版用的是直接读GPIO电平轮询主循环里还要刷OLED、读温度、处理按键轮询周期稍长就会丢滴。升级版在硬件上加了LM393比较器整形和灵敏度调节电位器软件上改用外部中断加定时器捕获信号从“模拟毛刺”变成“干净方波”这才是能谈闭环控制的基础。1.3 控制逻辑和界面搅在一起改一处崩全局初版代码整体是一个while(1)大循环OLED刷新、按键扫描、滴速统计、报警判断全部串在一个流程里。表面看结构简单实际维护起来极其痛苦想加一个“断电续输”的功能发现所有状态变量都暴露在主循环里改一个标志位可能会影响显示刷新频率想调整报警阈值还得翻到OLED绘制函数里找一个写死的数字。代码跑起来之后最难受的是实时性不可控——一次OLED全屏刷新要十几毫秒这期间中断如果没处理好滴速统计就会偶尔丢包。升级版我痛下决心把代码分成三层驱动层管传感器和外设业务层管控制逻辑界面层只负责显示层与层之间通过结构体或者队列传递数据。这样每一层都能独立测试后期加Wi-Fi模块、加多床位组网都不用动核心控制逻辑。后面第七章我会给开源的目录结构大家拿到代码的时候可以对照着看。2. 系统总体架构与核心器件选型思路2.1 整机框架传感采集、执行机构、人机交互三层升级版整机可以拆成三个层面来理解第一层是感知层负责数据的采集包括滴速检测单元、非接触式液位传感器、DS18B20温度传感器以及预留的血压血氧扩展接口第二层是决策层也就是主控STM32F103C8T6负责信号处理、PID调速计算、状态机管理和报警判定第三层是执行与交互层包括蠕动泵电机驱动、OLED显示、按键输入、蜂鸣器和LED报警以及预留的串口/Wi-Fi通信接口。三层之间有一个很关键的设计原则——执行机构绝不能阻塞感知采集。具体来说蠕动泵的PWM调速由定时器硬件自动输出不占用CPU滴速捕捉走外部中断以及定时器捕获通道主循环只做状态机流转和界面刷新。这样即使OLED刷新再慢、按键扫描再频繁滴速信号也不会丢。这一点是初版用轮询方式吃过大亏之后总结出来的。2.2 核心器件选型表与替代方案我整理了一张选型表都是我自己实测过、确定能跑通的方案同时也标注了替代选项。模块型号/方案关键参数替代方案选型理由主控STM32F103C8T672MHz, 64KB FlashSTM32F103RCT6 / GD32F303库函数资料多、Proteus模型成熟、够用不浪费滴速检测红外对管 E3F-5L LM393比较器遮挡检测、TTL输出光电遮断器模块、工业光电传感器成本低、响应快、可调灵敏度液位检测非接触式电容液位传感器数字输出高低电平红外式液位开关、浮球开关不接触药液卫生安全温度检测DS18B20单总线±0.5℃NTC热敏电阻免校准、直接读数字量电机驱动L298N双H桥峰值2ADRV8825 / 分立MOS管H桥驱动能力强、防反接、便宜电机大扭矩直流减速电机蠕动泵泵头6-12V1:100减速比步进电机泵头直流电机PWM调速简单蠕动泵是线性输出的结构显示OLED SSD1306 0.96寸 I2C128x64LCD1602 / TFT彩屏I2C只占两个IO显示内容丰富报警有源蜂鸣器 RGB LED5V驱动无源蜂鸣器需要PWM驱动有源蜂鸣器电平触发逻辑简单可靠这里特别说下蠕动泵和电机驱动。输液调速最忌讳用夹板挤压方式那种结构会改变药液流动的物理特性还可能产生回血风险。蠕动泵通过滚轮顺序挤压软管液体只在管内单向流动泵头本身不接触药液换病人时换根输液管就行。直流减速电机驱动蠕动泵转速和流量近似线性配合PID调速非常自然。2.3 通信与扩展接口设计升级版预留了两个口一个是USART1的TTL串口走调试打印另一个是I2C总线扩展用来挂Wi-Fi模块或者蓝牙模块。这样后续做中央监护系统时每台输液泵可以当做一个节点把滴速、液位、温度、报警状态周期上报给护士站。接口设计的时候我给每个传感器模块都留了独立的电源引脚可以单独断电重启排查故障时不用拔线。3. 硬件原理图设计关键电路拆解3.1 滴速检测电路从传感器到MCU中断引脚滴速检测是本项目最核心的信号链路。完整链路是红外发射管连续发射红外光 → 水滴经过滴壶遮挡光路 → 红外接收管电平变化 → LM393比较器整形 → STM32的PA0外部中断引脚。红外发射管需要串联限流电阻典型值是330欧姆到470欧姆工作电流控制在10到20毫安。太大会加快发射管老化太小则信号幅度不够。接收管用光电三极管集电极接上拉电阻到3.3V典型上拉是10千欧。当红外光被水滴遮挡时接收管等效电阻变大集电极电压被拉高。LM393比较器这里有一个非常值得注意的细节——必须加迟滞电阻。如果不加迟滞参考电压附近微小的噪声会让比较器输出反复翻转一个水滴可能产生三四个脉冲。我用的迟滞方案是比较器同相输入端接传感器信号反相输入端接参考电压由10K电位器分压调节输出端通过100K电阻反馈回同相输入端形成正反馈。实测下来只要水滴压过参考电压输出会很干脆地翻一次不再抖动。PA0在STM32上配置为外部中断下降沿触发也可以根据光路设计配置为上升沿。每捕捉到一个边沿就在中断服务函数里读取定时器TIM2的CNT寄存器算出相邻两滴的时间间隔。滴速就是60秒钟除以间隔秒数公式很简单滴/分钟 60 / 间隔秒数。3.2 蠕动泵驱动电路PWM调速与逻辑隔离电机驱动我选了L298N五线制12V电机电源、GND、5V逻辑电源、IN1/IN2控制方向以及ENA使能口。关键点在于怎么把MCU的3.3V PWM信号和L298N的5V逻辑电平对接。L298N数据手册上逻辑高电平最低是2.3VSTM32的3.3V输出是能直接识别的但为了稳定我在PWM输出引脚和L298N的ENA之间加了一级三极管电平转换把3.3V PWM抬高到5V。别小看这一步直接连接虽然可能工作但在电机启停瞬间12V电机电流的冲击会在地线上产生噪声可能会把3.3V信号拉出毛刺导致误触发。PWM频率选10kHz。这个频率下电机线圈的感抗可以保证电流纹波小而且高于人耳听觉阈值的敏感频段不会有尖锐噪音。如果选1kHz电机会发出明显啸叫病房里根本没法用。电机正反转控制也要设计好。输液场景里绝大多数情况是正转泵液反转只用于排空气或停泵。我设置IN1高IN2低为正转IN1低IN2高为反转全部拉低为刹车。特别注意不要用PWM同时控制IN1和IN2桥臂会直通烧管子。3.3 液位检测与报警电路液位检测用的是非接触式电容传感器贴在滴壶下端的管壁上。它的原理是利用水和空气介电常数差异管内液面在传感器附近时电容值变化传感器内部比较电路输出高低电平。硬件连接上数字信号输出接STM32的PB1配置为上拉输入。传感器供电用3.3V或者5V都能工作我实测5V供电更稳定。液位低报警的阈值不需要软件反复采样可以在传感器安装位置上做物理调整——传感器贴得越高越早检测到低液位留给人反应的时间越多。报警电路分两级一级是声光报警有源蜂鸣器接PB5高电平触发RGB LED接PB6/PB7/P8红色表示滴速异常、黄色表示液位低、绿色表示运行正常。二级是联动制动当液位传感器输出低电平信号持续3秒后系统进入报警状态电机PWM输出强制为0同时夹紧阀动作。夹紧阀我用的是一个5V电磁铁驱动的弹性夹管结构电磁铁失电时夹子靠弹簧力夹紧管路得电时释放。选失电夹紧而不是得电夹紧是为了断电安全意外断电时管子自动夹紧防止空气输入。3.4 电源设计与功耗估算整机是外部12V DC输入电流能力要求2A以上。电源拓扑分成三路12V直接给L298N电机驱动供电电机峰值电流按1.5A算12V经过LM2596降压到5V给OLED、蜂鸣器、液位传感器、LM393比较器以及L298N的逻辑部分供电最大负载约300mA5V再经过AMS1117-3.3给STM32和DS18B20供电负载约80mA。有一个功耗计算的细节要提醒L298N在12V供电、输出PWM驱动电机时饱和压降大约1.6V到2V加在电机上的有效电压比12V低。如果泵头卡阻电流会飙到两三倍L298N的功耗会明显上升。所以在电源输入端我加了一颗自恢复保险丝额定2A电机堵转时自动切断温度降下来后自动恢复。原理图上务必画上这个器件实物调试时它能救你很多次。4. 软件核心状态机PID调速的实现细节4.1 滴速捕捉外部中断与定时器的配合逻辑滴速检测不能靠主循环轮询必须靠中断。我用PA0外部中断下降沿触发搭配TIM2作为时间基准。TIM2配置为72MHz、预分频72、计数周期0xFFFF也就是每微秒计数一次65535微秒后溢出中断一次。因为滴落的间隔通常在500毫秒以上一个16位计数器够用溢出时做一个软件扩展计数。外部中断服务函数里做三件事读取当前时间戳和上一次时间戳相减得到间隔把间隔写入一个长度为5的环形缓冲区更新最近有效滴速。这里注意不能在中断里做PID计算或者OLED刷新那些重量级操作全放到主循环中断里只负责记录和缓冲。计算滴速时我用滑动平均值公式是当前平均间隔 (最近5次间隔之和) / 5滴速 60秒 / 平均间隔。滑动窗口不能太长太长了调节响应慢也不宜太短太短了单个抖动数据会直接反映到输出上。实测5次是很好的折中大约对应2到4秒的滞后PID调节能闭环得住。4.2 增量式PID在STM32上的实现PID控制的目标是让实际滴速稳定在设定滴速附近。我选增量式PID输出的是PWM占空比的增量而不是绝对占空比优点是输出不会突变积分饱和处理相对简单即使PID输出限幅系统也不会出现大的超调。核心代码如下typedef struct { float Kp; float Ki; float Kd; float target; // 目标滴速 float actual; // 当前实际滴速 float err_prev; // 上一次误差 float err_prev2; // 上上次误差 float delta_out; // 输出增量 int pwm_out; // 最终PWM占空比 } PID_TypeDef; void PID_Calc(PID_TypeDef *pid) { float err pid-target - pid-actual; pid-delta_out pid-Kp * (err - pid-err_prev) pid-Ki * err pid-Kd * (err - 2.0f * pid-err_prev pid-err_prev2); pid-pwm_out (int)pid-delta_out; if (pid-pwm_out 100) pid-pwm_out 100; if (pid-pwm_out 0) pid-pwm_out 0; pid-err_prev2 pid-err_prev; pid-err_prev err; }有人会问为什么用“滴/分钟”作为被控量而不是直接控制流量。原因是输液场景里的临床指标本身就是以滴/分钟来要求的医生开医嘱写的是“XX滴/分”护士执行也是按滴数来数直接把控制目标做成滴速人机交互最直观。但要注意滴的大小受输液器型号影响每个输液器20滴大约等于1毫升这个换算系数在软件里做成常量日后可以按实际输液器标定。PID采样周期我定为500毫秒也就是每半秒做一次计算和一次PWM输出更新。采样周期太短比如100毫秒滴速数据本身波动就很大微分项会被正常波动干扰放大采样周期太长比如2秒系统调节滞后明显滴速过冲会超标。4.3 系统状态机与报警逻辑整机软件是一个典型的状态机INIT状态上电自检读取温度和液位状态OLED显示开机画面如果液位正常则进入STANDBY否则停在INIT并报警。STANDBY状态等待用户设定目标滴速按键调整数值确认后进入RUNNING。此时电机不转。RUNNING状态电机按PID计算结果运转实时显示滴速、温度、液位和累计时间。液位低持续3秒或者滴速偏差超过设定值±20%持续10秒进入ALARM状态。ALARM状态蜂鸣器鸣叫红色指示灯闪烁OLED显示报警类型。如果是滴速偏差电机继续转但标记人工干预如果是液位低电机停止并开启夹紧阀。按确认键可退出报警回到STANDBY重新设定。报警逻辑最忌讳一个毛刺就触发。我在软件里做了一个连续计数机制只有连续3个采样周期都满足报警条件才算真正报警。这样能滤掉瞬时干扰又不会延迟太久。5. Proteus仿真搭建与联调过程5.1 仿真电路搭建要点Proteus仿真里要跑STM32需要先确认安装了STM32的元件模型库。在元件模式搜索“STM32F103C8”添加到原理图后双击可以设置启动文件。晶振我直接在仿真模型里用默认8MHz配置但要注意Proteus对STM32的时钟树仿真并不完全准确所以仿真里我尽量不依赖复杂外设时序主要验证逻辑。仿真电路比实物简单滴速检测用一个“虚拟脉冲信号源”代替内部就是一个方波发生器频率对应滴速。比如要模拟30滴/分钟把方波频率设成0.5Hz2秒一个脉冲。信号源输出接STM32的PA0这样MCU的滴速捕捉逻辑就能在仿真里跑起来。电机用Proteus自带的DC Motor元件L298N驱动模型也直接搜索得到连接好后可以看到电机转速变化。5.2 虚拟仪器与数据观察Proteus里最有用的是虚拟终端Virtual Terminal我把它接到STM32的USART1每秒输出一行调试信息set:30, act:31, pwm:45, temp:24.5, level:OK。这样PID调节过程完全可视化哪里振荡、哪里发散一眼就能看出来。改目标滴速的方式有两种一是在仿真里按按钮模拟按键输入二是直接在USART虚拟终端发指令。我推荐先在代码里写死一个目标值先验证PID能不能稳定稳定了再上按键交互。仿真阶段不要急着把所有功能都堆上去那样出了问题不好定位。为了模拟输液管阻力变化我给DC Motor负载侧加了一个电压控制的电阻元件电压值可以在仿真运行时手动调整等效于滴速变化带来的负载波动。这能让PID在“负载扰动”下表现得更直观。5.3 仿真和实物之间的差异仿真通过不等于实物能转这个差异我给大家提个醒时钟精度仿真的定时器计数是理想化的实物的HSE晶振和PLL配置如果没对滴速计算偏差会很大。我刚开始在实物上发现实际滴速和显示差了很多排查半天发现是时钟树配置成了内部HSI系统主频只有8MHz而不是72MHz。模拟电路特性仿真里信号源是完美方波实测中比较器输出的沿有几十微秒的抖动但这相对于2秒的滴落间隔微不足道所以对滴速计算影响不大。电机模型仿真的DC Motor是理想摩擦模型实物的蠕动泵有机械脉动和死区PID参数必须重新整定。仿真只能验证算法结构不能替代实物调参。6. 实测中踩过的坑与排查链路6.1 ST-Link报错no stm32 target found这个报错我要单独拎出来说因为太常见了而且新版固件库的报错提示容易让人误解。完整报错是error: no stm32 target found! if your product embeds debug authentication, please ...很多人看到“debug authentication”以为是加密或者调试认证问题其实绝大多数情况根本不是。我这边当时的排查链路是先确认ST-Link是否被电脑识别。在设备管理器里看是否有未知设备如果驱动异常换一个USB口或者重装ST-Link驱动。确认目标板3.3V供电。用万用表直接量STM32的VDD引脚对地电压我遇到过一次AMS1117虚焊板子上电后电压只有1.8VMCU完全没法进调试模式。确认SWDIO和SWCLK两个引脚没有被复用。如果程序里初始化了这两个引脚为普通GPIO连接调试器时可能锁死SWD口。解决方式是按住复位键点击下载后瞬间松开复位让MCU从复位状态启动时立即被调试器接管。确认BOOT0引脚电平。BOOT0拉高的器件会从ISP启动不执行Flash里的程序SWD下载本身不受影响但如果你之前的程序里设置了SWD禁用配合BOOT0拉高就会出现一直找不到目标的情况。我的板子把BOOT0经10K电阻下拉到GND需要ISP烧录时才短接到3.3V。如果还是不行用示波器看SWCLK是否有调试时钟输出。调试器正常工作时SWCLK引脚上应该有方波没有就换个ST-Link试试。最终我的问题出在主板上3.3V去耦电容位置太远SWD通信时电平波动导致调试器无法稳定握手上。后来在STM32的VDD引脚旁边加了两个100nF和一个10uF电容问题彻底消失。6.2 液位传感器误动作气泡和管壁水珠非接触式电容液位传感器有一个隐蔽的坑当输液管里有连续气泡经过时局部介电常数变化传感器可能会短暂输出低电平。我用的是“持续3秒”软件滤波但气泡一长串超过了延时时间系统就会误报低液位。这里的排查过程比较曲折。一开始我怀疑是传感器安装位置太靠近滴壶出口气泡还没融合就直接经过传感器。后来把传感器往下移了5厘米让气泡经过时大部分已经浮升上去误报频率大幅下降。这属于典型的机械安装位置问题比调软件阈值更根本。我的建议是液位传感器不要紧贴滴壶出口留出一段缓冲管段同时软件保留连续计数滤波两者缺一不可。6.3 PID整定的血泪过程PID调参是我在这个项目里耗时最长的环节。我一开始用试凑法Kp给0.5Ki给0.01Kd给0结果系统持续低频振荡实际滴速在目标值±5滴/分之间来回晃OLED上的数字看着就像抽风一样。后来改成系统化的整定步骤先把Ki和Kd清零只留Kp从0.1开始逐步加大。观察滴速阶跃响应直到出现等幅振荡的临界值记录这个Kp临界值。临界值大约是0.8。取Kp为临界值的一半也就是0.4作为基本比例增益。加上Ki从0.005开始观察静差能否消除。当目标滴速从30改为40时如果实际值能稳定收敛到接近40而不大的超调说明Ki合适。最后加Kd从0.05开始处理蠕动泵的机械脉动。Kd主要体现在参考值变化瞬间能够压制过冲但如果太大系统会放大滴速信号本身的噪声产生高频抖动。最终参数锁定在Kp0.35Ki0.01Kd0.08采样周期500ms。整定过程中最深的体会是别把PID当黑盒去瞎凑每一步调节都要看响应曲线的形态。我后来干脆在调试模式里用串口把每500ms的设定值、实际值、PWM占空比实打实记录下来导入Excel画曲线一眼就能看出振荡还是静差还是发散。6.4 蠕动泵机械脉动引起的滴速波动直流减速电机驱动蠕动泵泵头滚轮在连续旋转时对软管的挤压是周期性的这导致每转一圈都有一次瞬时流量波动。这种机械脉动会直接反映在滴速信号上造成PID输入端的周期性扰动。我刚开始以为PID参数没调好反复调Ki和Kd都没用。后来用示波器观察滴速信号的波形发现有一个固定的频率分量和泵头滚轮数量乘以电机转速的频率正好对应。这才明白问题出在机械结构本身而不是控制算法。对策有两层底层是把滴速计算的时间窗口加长滑动平均窗口从3次加到5次把周期脉动平滑掉上层是在PID算法里对微分项做一阶低通滤波消除高频分量。效果上实际滴速波动从±4滴/分缩小到±1滴/分。这也提醒我控制算法和机械结构是耦合的不能完全靠软件解决所有问题。7. 开源仓库结构说明与二次开发建议7.1 代码、原理图、仿真文件怎么配合使用开源仓库我按功能分层排布拿到压缩包后建议按下面顺序来研究STM32_Infusion_Monitor/ ├── Doc/ │ ├── System_Design.md // 系统设计文档 │ ├── Pin_Mapping.md // 引脚分配表 │ └── PID_Tuning_Guide.md // PID参数整定指南 ├── Hardware/ │ ├── Schematic/ // 原理图工程文件 │ ├── PCB/ // PCB工程文件 │ └── BOM.csv // 物料清单 ├── Firmware/ │ ├── Core/ // 启动文件、系统时钟配置 │ ├── Drivers/ // 标准外设库/HAL库驱动 │ ├── bsp/ // 板级外设驱动OLED、DS18B20、滴速传感器 │ ├── App/ │ │ ├── pid_control.c // PID控制核心 │ │ ├── state_machine.c // 系统状态机 │ │ ├── alarm.c // 报警逻辑 │ │ └── ui.c // OLED界面处理 │ └── MDK-ARM/ // Keil工程文件 └── Simulation/ ├── Infusion_Monitor.pdsprj // Proteus仿真工程 └── firmware_for_sim.hex // 仿真用的固件拿到代码后建议先看Hardware/Schematic里的原理图对着Doc/Pin_Mapping.md把每个引脚对应关系过一遍然后打开Firmware工程在main.c里看系统初始化流程。仿真工程里的固件和实物固件是同一套代码编译出来的Behavior有差异但不涉及逻辑改动。7.2 进阶功能扩展方向这个系统的架构给自己留了足够的扩展空间我列几个值得研究的方向多床位组网每个输液泵作为RS485节点用Modbus协议和护士站主机通信。硬件上只需加一颗MAX485收发器软件上用现成Modbus协议栈就行。Wi-Fi/蓝牙监护在I2C总线上挂一块ESP8266或者ESP32模块把滴速和液位数据上传到手机小程序。要注意I2C总线上多设备地址冲突SSD1306默认地址是0x3CESP8266的AT固件走UART不占I2C扩展时优先考虑UART透传方案。输液完成自动停机现在的逻辑是液位低报警后人工确认停机进阶版本可以做成两级液位传感器低位提前报警极低位强制停机夹管。断电续输用STM32的内部Flash保存运行状态掉电重上电后恢复之前的设定滴速和累计药量。这个功能在临床场景里很有用断电不至于从头再来。我个人的建议是不要急着同时加太多功能先把当前的PID控制理解透彻把滴速检测的可靠性做到位。这个系统的灵魂在于闭环控制传感器数据都不准再花哨的上层功能都是空中楼阁。最后再分享一个调试技巧PID调参阶段把OLED刷新注释掉所有数据走串口输出到上位机。串口带宽足够每秒打一次调试日志画成曲线观察比盯着几十毫秒刷一次的屏幕判断响应要靠谱得多。这个习惯帮我节省了大量调参时间也推荐给后面想复刻这个项目的人。