基于STM32的智能输液监护系统:滴速检测与PID自动调控实现
每次去医院输液总有家属盯着点滴瓶看着液面一点点往下走生怕空气进管子又怕滴太快心脏受不了。我家里老人长期打营养液陪护的那几天让我彻底受够了这种“人肉监护”于是动手做了这套基于 STM32 的智能输液监护调控系统。这次开源的版本是我迭代过的第二版相对初版增加了自动调速、阻塞检测和离线数据记录硬件、代码、仿真全部打包拿来就能跑改一改也可以直接当毕业设计或者产品原型。先交代清楚这套系统能干什么它通过光电传感器实时检测输液滴速当滴速偏离设定值时系统会自动调节蠕动泵把速度拉回目标范围液位过低、管路堵塞、意外断电都会触发声光报警同时 OLED 屏幕上显示当前滴速、累积液量和工作状态。你既可以把整套代码烧进蓝丸STM32F103C8T6配合实物验证也能在没有硬件的情况下用 Proteus 仿真完整跑通逻辑。这篇博文我就按“为什么这么做、原理图怎么画、代码怎么写、仿真怎么调、坑在哪里”的顺序讲透适合正在做嵌入式课设、准备相关毕设或者纯粹想研究上位机底层控制的工程师。1. 项目整体设计与方案选型1.1 输液监护的核心需求拆解医疗场景下的输液监护本质上是一个“检测-决策-执行”的闭环过程。我先把这个闭环拆成五个具体需求后面所有设计都围着它们转滴速检测输液器滴壶里的液滴下落速度通常以滴/分钟drops per minute, dpm为单位成人常用 20~60 dpm儿童或特殊药物可能低至 5 dpm。滴速调控当实际滴速与设定值偏差过大时通过改变蠕动泵转速调节滴速这是整个系统的核心执行功能。异常报警液位低于阈值、管路堵塞、滴速异常、断电等故障需要及时提醒避免医疗事故。人机交互医护人员需要设定目标滴速、查看实时数据、手动干预所以必须有显示和按键输入。数据记录与通信升级版增加了运行数据记录方便事后分析。如果只是做一个“滴滴滴报警器”那方案太简单了一个比较器加一个蜂鸣器就够。但要做成“监护调控记录”的完整系统就必须有 MCU 做闭环运算、有执行机构做输出、有显示做反馈。这也是我选择用单片机而不是纯模拟电路做核心控制器的原因。1.2 为什么主控选 STM32F103C8T6市面上有 51、Arduino、ESP32、GD32 等各种选择我最后锁定 STM32F103C8T6理由很直接资源够用不浪费内置 64KB Flash、20KB RAM72MHz 主频有 3 个定时器、2 个 SPI、2 个 I2C、3 个 USART、10 路 12 位 ADC对这套系统来说绰绰有余。生态极其成熟Keil MDK STM32CubeMX HAL 库的组合网上资料多到看不完遇到问题随便一搜就有解决方案。成本低、封装友好LQFP48 封装手工焊接无压力芯片单价在国产替代后已经很便宜。仿真支持好Proteus 里有完整的 STM32F103C8T6 模型不用硬件也能先跑逻辑这对开源项目来说非常重要别人下载代码后没有板子也能复现。相比之下51 单片机虽然入门简单但外设资源太弱做 PID 调速和 ADC 采集会比较吃力ESP32 虽然性能更强还带 WiFi但有医疗设备场景下的稳定性和功耗考虑反而不如 STM32F103 这种经典的工业级 MCU 稳妥。1.3 基础版到升级版到底升级了什么我第一版只做了“检测报警”滴速调控完全靠人工去夹滚轮说实话那只能算半自动。这次升级版做了三个关键改进执行机构从“手动夹持”改成“蠕动泵PWM调速”实现了闭环自动控制。增加压力传感器检测管路阻塞不只是靠滴速异常间接判断可靠性提高不少。增加离线运行日志把每次运行的设定值、实际滴速、报警次数存在 Flash 里方便后期用串口读出分析。这些升级让工程复杂度和代码量大概翻了一倍但系统的完整度也从“课设水平”提升到了“接近真实产品原型”的层级。后面我会详细讲每一部分的设计取舍。2. 硬件设计原理图分析与关键电路原理图是整套系统的骨架。很多初学者喜欢直接抄网上的原理图但抄完不知道怎么改、不知道为什么要加这些电阻电容。这里我把每个关键模块的电路设计逻辑拆开讲你理解了以后再去看我开源的原理图会发现每颗电阻都有它存在的意义。2.1 系统供电与电源设计整个系统涉及多路电压MCU 需要 3.3V传感器和运放需要 3.3V 或 5V蠕动泵电机需要 6V 或 12V驱动芯片的工作电压又有自己的要求。我的方案是外部输入 12V 适配器然后分级降压12V 输入经过 LM2596 或 MP1584 降压模块得到 5V给蜂鸣器、传感器、电机驱动逻辑供电。5V 再经过 AMS1117-3.3 得到 3.3V给 STM32、OLED、存储芯片供电。这里有个很重要的经验电机和 MCU 的电源必须在靠近负载端加滤波电容并且地线要分区处理否则电机启停瞬间的电流冲击会把 ADC 采样值打得乱七八糟。我最初的原理图没有对电机驱动地和 MCU 地做单点连接结果实测发现滴速检测信号上叠加了大量毛刺后来只能在 PCB 上割线飞线。开源的原理图里我在 12V 入口加了反接保护二极管 SS34同时在 5V 和 3.3V 输出端分别放了 10uF 100nF 的滤波组合这三个电容最好不要省。电源指示灯、按键复位电路这些基础电路我保留在原理图里不再赘述但要注意复位电路 RST 引脚对地电容不能太大否则上电复位时间过长可能导致 ST-Link 连接不正常。2.2 滴速检测电路红外对管与信号调理滴速检测是整个系统最关键的信号采集环节。最常用的方案是红外对管红外发射管和接收管分别放在输液滴壶的两侧液滴下落时会遮挡红外光接收管上的光电流随之变化通过电路转换成脉冲信号。我的电路设计思路如下红外发射管串一个限流电阻典型值 10mA 左右我用 100Ω 配 3.3V实测电流约 20mA红外管光强足够。接收管用光敏三极管上拉电阻到 3.3V。无液滴时接收管导通输出约为 0V液滴遮挡时接收管截止输出被上拉到 3.3V。但光敏三极管的输出波形边沿很缓直接进 MCU 容易误触发所以我加了一级 LM393 比较器整形基准电压用可调电阻分压设置在 1.2V 左右把模拟信号整形成边沿陡峭的方波。比较器输出加了一个 10K 上拉到 3.3V再接一个 100nF 电容滤波。这里有一个我踩过的坑红外对管非常容易受到环境光的干扰尤其是阳光直射和日光灯的 100Hz 频闪。在机械结构上应该把对管做成一个卡在滴壶上的凹槽结构尽量屏蔽环境光电路上比较器还可以采用正反馈做迟滞窗口避免液滴半遮挡时产生多次翻转。我在原理图里加了大约 200mV 的迟滞实测误触发率下降非常明显。信号最终送入 STM32 的某个定时器输入捕获引脚或者外部中断引脚用于测量脉冲间隔。在这个设计里脉冲间隔就是相邻两滴液滴的时间差有了这个时间差就能算出实时滴速。2.3 电机驱动与调速执行机构执行机构我选的是小型蠕动泵。蠕动泵的好处是液体只接触泵管不接触泵体卫生性符合医疗场景而且它能提供稳定的流量控制只要电机转速稳定输液速度就能稳定。驱动方案上小电流蠕动泵额定电流小于 1A我用 DRV8833 双路驱动芯片一路备用一路输出。DRV8833 内部集成 H 桥逻辑电压可以直接接 3.3V电机电源接 5VPWM 频率设置在 20kHz 左右既听不到噪声又能保证调速线性度。PWM 调速的原理很简单通过改变 PWM 占空比改变电机的平均电压从而改变转速。但电机不是线性元件PWM 占空比和滴速之间并不是严格的正比关系所以后面才需要用 PID 闭环去补偿。还有一个值得注意的细节蠕动泵的泵管弹性会影响流量泵管用久了会变形同样的转速下流量会变低。这是闭环控制系统必须存在的重要原因也是我坚持在软件里用 PID 而不是开环控制的原因——开环控制无法补偿这种机械磨损带来的偏差。2.4 液位检测、阻塞检测与报警电路液位检测我用了电容式接近传感器贴在输液瓶外壁的液面位置。这种传感器不需要直接接触药液卫生安全输出是开关量信号接一个 GPIO 即可。当液面低于传感器安装位置时输出电平会翻转MCU 检测到下降沿后延迟 500ms 确认防抖再触发报警。阻塞检测用的是薄膜压力传感器贴在蠕动泵出口的输液管路上。压力传感器的输出是模拟量经过运放 AD620 放大后接入 STM32 的 ADC。当管路堵塞时管内压力升高ADC 值超过阈值且持续 2 秒以上判定为阻塞。为什么要持续 2 秒因为输液过程中病人稍微动一下也可能造成瞬时压力波动时间判定能有效避免误报警。报警电路最简单有源蜂鸣器加一个 NPN 三极管 S8050 驱动MCU 引脚输出高电平使三极管导通。蜂鸣器电源一定要接续流二极管否则断电瞬间的反向电动势容易把三极管击穿。这个二极管我在原理图里特意标注了好多初学者抄板时会漏掉这一颗。2.5 人机交互界面OLED 与按键显示部分我用 0.96 寸 I2C 接口的 OLEDSSD1306四根线就能搞定占用资源少。屏幕实时显示四行信息设定滴速、实际滴速、运行状态、累计量。OLED 的可视角度和对比度都很好在病房里光线复杂的场景下也能看清。按键我设计了三颗设定键、加键、减键。为了减少 I/O 占用我没有用矩阵键盘而是三颗独立的按键分别接 PA0、PA1、PA2其中 PA0 用作外部中断唤醒方便低功耗场景下随时响应。有兴趣的话你也可以把这套显示方案改成更大尺寸的 LCD 屏只需要调整驱动库应用层逻辑不用变。这就是把显示逻辑和业务逻辑分离的好处我在代码里做了对应的抽象接口。3. 软件实现代码架构与核心算法硬件只是骨架软件才是灵魂。这套系统的软件架构我按以下层次划分HAL 驱动层CubeMX 生成、外设中间层OLED、按键、传感器驱动、应用层滴速计算、PID控制、状态机、报警处理。分层的好处是每一层独立可测调试起来思路清晰。3.1 项目工程搭建与引脚分配我用 STM32CubeMX 生成的工程型号选择 STM32F103C8Tx。关键引脚分配如下功能引脚配置滴速检测输入PA8定时器1通道1输入捕获上拉电机 PWMPA6定时器3通道1 PWM输出压力传感器 ADCPA4ADC1 IN4液位传感器PB0GPIO输入上升沿中断蜂鸣器PB1GPIO推挽输出OLED SCLPB6I2C1 SCLOLED SDAPB7I2C1 SDA按键设定PA0GPIO输入下降沿中断按键加PA1GPIO输入轮询按键减PA2GPIO输入轮询串口日志PA9/PA10USART1 115200 8N1CubeMX 里重点配置三个东西系统时钟设置为 72MHz外部 8MHz 晶振PLL 9 倍频滴速检测引脚的定时器开输入捕获模式同时打开溢出中断电机的定时器配置 PWM 频率为 20kHz初始占空比为 0。外围传感器和按键的初始化代码我放在 main 函数开头依次执行顺序是 OLED → 液位传感器 → 压力传感器 → 定时器 → 电机 → 串口。3.2 滴速检测与滤波算法实现滴速检测用输入捕获定时器记录两次下降沿之间的计数值也就是一个滴间隔。假设定时器时钟频率为 72MHz预分频 72得到 1MHz 的计数频率那么一滴间隔 T单位毫秒就等于捕获值除以 1000。实时滴速的换算公式很简单滴速 dpm 60000 / T。但直接拿相邻两滴的间隔算滴速会非常毛糙因为液滴滴落本身就有一定的随机性偶尔还会受气泡影响跳变。我采用了一阶低通滤波speed_filtered alpha * speed_raw (1 - alpha) * speed_filtered_prevalpha 取 0.3 左右这样既保留了响应的灵敏度又不会让显示值来回跳。实测在 40dpm 环境下滤波后的波动在正负 2dpm 以内。如果你希望更平滑还可以加一个滑动窗口求中位数但那是拿内存换平滑度对 20KB RAM 的 F103 来说一个 8 点窗口完全够用。开源的代码版本里我特意把滤波函数写成了一个独立模块你打开filter.c就能看到低通和中位数的两种实现用宏切换即可。3.3 增量式 PID 电机调速算法滴速闭环控制我用的是增量式 PID。标准位置式 PID 的输出是绝对控制量一旦积分项累积过大电机容易满转或停转而且切换手动/自动时有冲击增量式 PID 只输出控制量的增量天然带有“无扰动切换”的优点非常适合对接 PWM 占空比这种执行器。增量式 PID 公式 Δu Kp * (e(k) - e(k-1)) Ki * e(k) Kd * (e(k) - 2*e(k-1) e(k-2)) u(k) u(k-1) Δu在我的实现里e(k) 是设定滴速与当前滤波后滴速的差值。PWM 占空比上限限制在 80%下限 0%每次 PID 计算输出的增量加到当前占空比上。为了防止积分饱和我做了输出限幅同时把积分项累计值也做了限幅范围在 -50 到 50 之间。PID 参数整定我是在仿真里先粗调再上实物细调的。整定顺序是先只加 Kp观察响应曲线直到出现等幅振荡再把 Kp 减小到振荡幅度的 60% 左右加 Ki 消除稳态误差最后加 Kd 减小超调。我最终使用的整定结果是 Kp1.8、Ki0.15、Kd0.05这个参数在仿真和实物上表现都比较稳但不同电机、不同泵管参数会有不小差异强烈建议你拿到代码后自己重新整定一遍。3.4 状态机系统的运行逻辑骨架整个系统的工作逻辑我用一个简单的状态机管理状态包括INIT上电初始化滴速检测校准STANDBY待机状态显示信息等待启动RUNNING正常运行PID 控制电机调节滴速ALARM_LOW液位低报警ALARM_BLOCK阻塞报警MANUAL手动模式通过按键直接调节电机转速状态切换的代码我放在了main_loop里的 10ms 定时器中断标志位中。周期性地扫描传感器、读取滴速、执行 PID、刷新 OLED。为什么用 10ms 作为控制周期因为滴速的变化频率不会超过 5Hz10ms 周期的响应能力完全够用同时还能留出大量 CPU 时间去处理通信和显示。这个时间基准用 SysTick 中断完成CubeMX 默认已经配好。如果把所有逻辑都塞进一个超级循环代码会非常难维护。开源代码里我用 switch-case 实现状态机每个状态有独立的处理子函数结构很清晰你拿到手可以直接照着状态图去加新功能。3.5 升级版新增的数据记录与通信升级版一个比较大的变化是增加了运行数据记录功能。系统每分钟将当前滴速、设定值、报警状态写入片内 Flash 的一个 4KB 区域采用先擦后写的循环覆盖方式。F103 内部 Flash 写寿命大约 1 万次每分钟写一次大概能连续记录 6 天左右对临时监护场景足够用了。Flash 写入需要注意写之前必须确保目标地址是空全 0xFF的否则要先执行扇区擦除另外写入过程中不能发生中断所以在写 Flash 时我先关了滴速捕获中断写完再恢复。F103 的 Flash 是以页为单位操作最小擦除单位 1KB部分型号是 2KB我使用的是最后一个扇区地址 0x0800F800大小 2KB通过这种设计不会覆盖到代码区。串口方面我用 USART1 输出调试日志波特率 115200。数据帧格式是TIME, SET, ACT, STATE用逗号分隔、换行结尾这样可以直接用串口助手查看也可以喂给第三方上位机解析。如果后续要接 WiFi 或蓝牙模块把串口数据转发到无线模块就行逻辑层完全不需要改动。4. 仿真与硬件调试从零跑通整个系统4.1 Proteus 仿真环境搭建步骤很多初学者在开始时会遇到 Proteus 仿真不运行的情况其实 90% 的问题出在元件版本和虚拟设备配置上。我用 Proteus 8.15 版本演示工程文件已经在开源包里直接打开simulation/IV_System.pdsprj。仿真工程里用到的几个关键虚拟设备STM32F103C8T6 芯片模型直接放在原理图左上方虚拟串口 VIRTUAL TERMINAL用于查看调试日志虚拟示波器挂在滴速检测引脚上观察脉冲波形滴速模拟信号用两个按钮替代一个按下代表一滴液滴落下产生脉冲另一种是用脉冲发生器自动生成固定频率脉冲模拟恒定滴速。仿真里我默认给的是 40dpm 对应的 1.5Hz 脉冲方便直接观察系统在闭环下是否自己调节 PWM。打开工程后先点 Debug 菜单里的 Run Simulation如果一切正常OLED 虚拟屏上会显示设定滴速 40dpm实际滴速 40dpm电机 PWM 占空比会自动在一个稳定值附近浮动。如果想模拟滴速突变直接改脉冲发生器的频率看系统是不是能在几秒内追回设定值。仿真有个天然限制Proteus 里的蠕动泵模型并不存在我没有办法把 PWM 占空比真正转化为滴速变化。所以仿真里的电机 PWM 只是展示控制输出滴速反馈仍然由信号发生器模拟。这也就是说仿真验证重点是代码逻辑和控制算法机械环节还是必须上实物验证。4.2 硬件烧录与调试常见错误实录拿到实物板子后第一步就是烧录程序。我用 ST-Link V2 加 SWD 四线SWDIO、SWCLK、GND、3.3V连接Keil 里配置好 Debug 工具。这里有一个高概率问题就是 Keil 烧录时报error: no stm32 target found! if your product embeds debug authentication, please ...这个错误我在很多群和论坛看到过身边的人也经常踩。这个报错的本质是调试器找不到目标芯片排查顺序我建议是先量 MCU 的 3.3V 供电是否正常再看复位引脚是不是被拉低然后看 SWD 线有没有接反最后检查有没有在代码里把 PB3/PB4SWD 复用引脚当作普通 GPIO 使用。如果你在 CubeMX 里把 SWD 引脚重新映射了就会导致调试口被关闭芯片变砖一样没法连接。解决办法是按住复位键不松手在 Keil 设置里选择 Connect under Reset然后点烧录等出现连接成功提示的瞬间再松开复位。还有一个常见的坑是 Win10/Win11 下 ST-Link 驱动安装后仍然显示感叹号。这个多半是权限或驱动签名问题去驱动管理器卸载设备重启后再安装驱动通常能解决。如果还不行就换一根短一点的 USB 线长线劣质 USB 线引起的供电不稳定会让 ST-Link 随机掉线。4.3 实测数据与实际效果分析实物搭建完成后我做了几组测试场景设定滴速 (dpm)稳定滴速 (dpm)调节时间 (s)是否报警正常输液4039~414.2否滴速突变40 → 2020~225.8否液位过低400/是管路阻塞400/是从数据看PID 闭环能把滴速稳在正负 2dpm 的范围内这个精度对静脉输液来说已经足够。调节时间在 5 秒左右不会因为调节过快导致患者不适。报警响应时间从故障发生到蜂鸣器响起大约在 2 到 3 秒这个延迟主要来自防抖和持续判定逻辑目的是减少误报。当然实测也暴露了一些仿真发现不了的问题比如电机堵转时 PWM 占空比会迅速加到最大值产生尖锐的电机噪声还有蠕动泵管和泵头的摩擦会导致泵管温度轻微上升。这些都属于机械层面的优化空间在控制代码层面能做的保护是检测电机电流超过阈值立即停机这个功能可以在下一版继续加。5. 常见问题与排查技巧速查表这个项目从零到一的过程中我积累了不少经验下面这些是我遇到最多的问题和解决办法整理成表格方便你直接对照现象可能原因排查方法解决办法烧录报 no stm32 target found供电异常/线序错误/SWD被禁用量电压、检查连线、按住复位重试检查供电、使用 Connect under Reset、恢复默认引脚映射滴速检测数值乱跳环境光干扰/信号没整形/滤波系数不合适观察比较器输出波形加遮光结构、调整迟滞电压、修改滤波系数电机不转PWM 占空比为 0/驱动芯片供电异常测量 PWM 波形、检查驱动芯片输入输出调整 PID 参数、检查电源通路蜂鸣器巨响但停不下来状态机卡在报警状态打印当前状态码增加超时自动复位逻辑OLED 无显示I2C 地址错误/接线松/频率过高扫描设备地址、降 I2C 频率修改 SSD1306 地址宏、将 I2C 时钟降为 400kHz压力值跳动很大ADC 采样未滤波/地线共阻抗串口打印 ADC 原始值做均值滤波、优化地线布局数据写到 Flash 后代码跑飞擦除时触发中断/写入地址越界检查 Flash 操作函数关中断再擦写、最后使用独立扇区排查这类嵌入式问题建议养成一个习惯先把问题拆成“电源层、信号层、逻辑层”三部分。电源层问题最隐蔽也最致命信号层问题用示波器和万用表就能定位逻辑层问题通过串口日志打印状态变量来排查。不要一上来就怀疑代码逻辑先确认硬件物理状态很多时候问题根本不在软件。6. 开源项目结构说明与二次开发指南6.1 仓库文件目录速览开源包我按功能模块做了目录整理下载后一眼就能找到需要的内容/ ├── hardware/ # 原理图与PCB文件 │ ├── IV_System.SchDoc │ └── IV_System.PcbDoc ├── firmware/ # STM32工程源码 │ ├── Core/ # 主逻辑、状态机、控制算法 │ ├── Drivers/ # HAL库 │ └── Middlewares/ # OLED、传感器驱动 ├── simulation/ # Proteus 仿真工程 │ └── IV_System.pdsprj ├── docs/ # 使用说明与调试笔记 └── README.md拿到压缩包后的正确步骤是先看 README了解版本和硬件配置然后用 CubeMX 重新生成一遍代码避免 HAL 库版本不一致导致的错误再打开仿真工程跑一遍逻辑最后再动手焊硬件。这套顺序能帮你节省至少一半的排错时间。6.2 从零复现项目需要准备的物料清单如果你想按着硬件重新焊接做实物物料清单我列在文档里核心件大概这些物料型号/规格数量备注主控板STM32F103C8T6 最小系统板1蓝丸即可红外对管5mm 红外发射/接收管2或是集成式光电传感器比较器LM3931信号整形电机驱动DRV88331也可用 L298N蠕动泵小型蠕动泵 5V~12V1注意流量范围压力传感器薄膜压力传感器1配合运放放大液位传感器电容式接近开关1非接触检测OLED0.96寸 I2C1SSD1306蜂鸣器有源蜂鸣器15V 供电电源模块12V→5V 降压模块1或直接接 USB 5V稳压芯片AMS1117-3.313.3V 供电如果你手头没有压力传感器和液位传感器可以先在 Proteus 仿真里用开关代替逻辑照样能跑通。等后续有预算再补硬件不影响前期学习。6.3 从这套系统还能扩展出去的方向做完成品之后你可以考虑从这几个方向继续深挖一是加无线模块。用 ESP8266/ESP32 的串口透传把实时数据推送到云平台再做一个 Web 端或小程序端的监控页面就变成一个物联网输液监控系统这也是目前医疗物联网很热的方向。二是做多通道扩展。一套主机带四五个输液监测模块每个模块独立检测并通过 RS485 总线回传数据应用场景会从单个病床扩展到整个病房。三是升级屏幕和交互。换上触摸屏加曲线显示和图像化设置界面把操作门槛进一步降低。四是完善医疗安全设计。增加软件看门狗、双 MCU 冗余、声光双路报警自检向医疗级标准靠拢。我个人建议先吃透 PID 调试和状态机设计这两块底层能力因为不管外接什么模块、换什么屏控制核心和系统思维是不变的。这套项目的价值不在于板子本身而在于你通过它真正理解了从一个传感器信号到一个可靠闭环动作的完整链路。最后再分享一个小技巧调试 PID 的时候可以把串口日志接上一个串口绘图上位机比如 VOFA把设定值和实际值画成两条实时曲线。这样你看一眼曲线就能知道系统是超调、震荡还是响应太慢比盯着调试助手的数字干猜效率高太多。我当时调参只用了不到半小时就找到合适的参数完全归功于曲线可视化。这套系统的源码和仿真文件我已经整理好需要复现的朋友直接下载跑有问题欢迎在评论区交流。