
我做的这个STM32智能输液监护调控系统从第一版迭代到现在前前后后改了差不多快两个月了。这版升级版重点解决了之前非常头疼的几个问题输液速度控制不够线性、滴速检测容易受干扰、还有报警误报率偏高。这次干脆把代码、原理图和Proteus仿真工程全部整理开源出来给正在做类似项目的朋友一个完整参考尤其是那种课程设计、毕业设计卡在“原理图能画但仿真跑不通”阶段的这份资料应该能帮你省下不少折腾时间。1. 升级版整体架构与设计思路1.1 从第一版到升级版到底改了哪些东西先说说为什么叫“升级版”。第一版功能其实比较简单就是一个红外对管检测莫菲氏滴管里的液滴然后用数码管显示滴速超限了就蜂鸣器报警整个系统属于“能跑但不好用”的状态。升级版的关键变化在于加入了闭环控制。也就是说系统不再只是“检测-报警”而是真正做到了“检测-决策-执行”通过红外对管实时采集滴速STM32把实测滴速和目标滴速做PID运算然后输出PWM信号控制蠕动泵的转速从而动态调节输液速度。简单说原来的系统是个只会喊“快超速了”的仪表现在变成了一个会自己动手调速的“助手”。另外几个重要的升级点通信能力增强加入RS485总线接口可以实现多床位联网监护护士站那边能看到所有病床的输液状态。人机交互升级矩阵键盘可以设定目标滴速和报警阈值相比原来拨码开关的方式友好太多。数据记录能力增加EEPROM存储掉电后参数不丢失这个在真实医疗场景里是刚需。安全机制完善新增气泡检测通路和电机堵转保护逻辑虽然仿真里不好体现电机堵转但代码层面已经做了防呆处理。1.2 为什么选STM32F103C8T6而不是其他主控主控选型上我坚持用了STM32F103C8T6这是颗非常经典的Cortex-M3内核芯片。市面上那些“兼容版”芯片比如GD32、APM32也试过但最终还是换回ST原厂。原因主要是这几个第一资料生态太成熟了。不管是标准外设库还是HAL库教程一搜一大把遇到问题基本都能查到解决方案。对于学生党或者刚入门的朋友来说这点非常关键——调试遇到坑的时候能查到的资料数量直接决定你能不能走出来。第二外设资源完全够用且不浪费。这个系统需要的外设包括定时器输入捕获滴速检测、定时器PWM输出电机控制、ADC采样压力传感器、USART调试串口和RS485、I2CEEPROM、GPIO矩阵键盘和显示接口。STM32F103C8T6把这些全部包圆了还有富余。第三价格和供货。在目前的市场行情下这颗芯片的国产替代型号甚至能压到几块钱整板打样成本非常低。如果你的预算更紧张板子自己画的话核心板加外围器件全套原件成本控制在50块以内问题不大。1.3 系统整体框图与工作流程整个系统的工作流程是这样的红外对管传感器安装在莫菲氏滴管两侧液滴落下时遮挡红外光线产生一个脉冲信号。STM32的定时器工作在输入捕获模式记录脉冲间隔时间软件计算出实时滴速滴/分钟。与按键设定的目标滴速比较经过PID控制器计算出PWM占空比调整量。PWM信号驱动蠕动泵电机改变输液管道的挤压频率从而调节实际流速。压力传感器实时监测输液管道压力超过阈值立即停止电机并声光报警。所有运行参数通过LCD显示同时通过RS485上传到上位机。这套闭环逻辑参考的就是工业控制里特别经典的**“检测-控制-执行”**模型。跟实验室里常见的开环控制相比好处在于系统能自动对抗扰动——比如病人翻身压到输液管了管道阻力增大滴速下降这时候系统会主动加快电机把滴速拉回设定值附近。2. 硬件电路设计与原理图解读2.1 电源电路设计多路电压怎么分配硬件设计摆在第一位的就是电源。整个系统需要的电压有几种STM32和传感器需要3.3V蠕动泵电机和继电器需要5V或12V运放电路需要正负供电我用的是单电源轨到轨运放避免了负压。电源方案我采用的是外部5V适配器输入 板上LDO降压到3.3V。很多朋友喜欢直接上AMS1117-3.3这个芯片便宜也好用但有一点得提醒AMS1117的最大压差其实不太适合输入电压过高的场景输入5V时输出3.3V没问题但如果你的适配器是12V的建议先一级降压到5V再进AMS1117否则芯片发热会比较严重长时间运行稳定性也差。原理图上电源部分我还加了几个关键细节输入端的防反接二极管SS14接反了不会烧板子。每个电源引脚附近都放了0.1uF高频退耦电容和10uF钽电容这个习惯我从做第一版就养成了对ADC采样稳定性帮助很大。模拟地和数字地单点连接用0欧电阻或者磁珠隔开减少数字开关噪声对模拟采样的干扰。2.2 滴速检测电路红外对管比较器整形滴速检测是本项目的灵魂模块这块电路设计得好不好直接决定系统能不能正常计数。原理图上的设计是这样的红外发射管一般是940nm波长串联一个限流电阻接到3.3V红外接收管光敏三极管接成共射极电路。当液滴从莫菲氏滴管落下经过红外光束时光线被遮挡接收管导通状态改变集电极电压产生一个从高到低的跳变。但这个跳变信号直接进STM32的GPIO是不行的因为信号边缘不够陡峭且存在抖动。所以我在接收管输出端加了一级比较器整形用的是LM393比较器参考电压通过电位器分压设定。信号经过比较器后变成标准的方波再送入STM32的PA0口TIM2_CH1做输入捕获。这里有个仿真时容易踩的坑Proteus里那套红外对管模型光强和遮挡关系的模拟非常理想化你直接照搬真实电路参数可能仿真正常、真实硬件反而失灵。所以我在原理图里特意用了一个电位器来调节比较器阈值实际调试时要配合示波器观察波形把阈值调到信号波形比较干净的那种状态。2.3 电机驱动与PWM控制电路蠕动泵电机我选的是12V直流减速电机额定电流大概300mA左右。驱动方案用的是经典的ULN2003达林顿管阵列虽然L298N或者TB6612更“高级”但对于单个直流电机来说ULN2003完全够用电路还简单价格也便宜。不过ULN2003有一个特点必须注意它是集电极开路输出内部没有续流二极管到电源正极。驱动感性负载电机时必须外部加上续流二极管否则电机断电瞬间产生的反向电动势会把达林顿管击穿。原理图上我在电机两端反向并联了1N4007二极管这是驱动感性负载的标准做法别省。PWM信号从STM32的PA1TIM2_CH2输出频率设定为10kHz。为什么选10kHz因为音频范围大约在20Hz到20kHz10kHz稍微低了一点其实还行主要考虑到电机本身有机械惯性和线圈电感太高的PWM频率会导致电流纹波小但开关损耗变大太低又会听到明显的电机啸叫。10kHz是一个比较折中的选择实测电机运行也比较平稳。2.4 显示与按键电路显示设备用的是1602LCD液晶屏这是最经典的做法。1602需要8根数据线加3根控制线占用的引脚比较多。为了省引脚我用了I2C接口的LCD转接板PCF8574芯片这样只需要两根线SCL、SDA就能驱动屏幕。对STM32F103C8T6这种48脚芯片来说引脚资源还是比较宝贵的。按键部分用了4x4矩阵键盘连接到GPIOB的低8位。矩阵键盘的扫描原理就是行列分时扫描代码里用定时器中断做20ms消抖扫描不会阻塞主循环。3. 软件框架与核心代码实现3.1 工程如何组织模块化编程实战这次开源的代码是按照模块化思路组织的每个外设一个对应的文件和头文件主函数只负责初始化各模块和调度主循环。整个工程结构可以列为Core/ ├── main.c // 主函数系统初始化主循环 ├── stm32f10x_it.c // 中断服务函数 Hardware/ ├── bsp_dripsensor.c // 滴速传感器驱动 ├── bsp_motor.c // 电机PWM控制 ├── bsp_key.c // 矩阵键盘扫描 ├── bsp_lcd1602.c // LCD显示驱动 ├── bsp_eeprom.c // AT24C02存储 └── bsp_rs485.c // RS485通信 Algorithm/ ├── pid.c // PID控制器 └── filter.c // 数字滤波 System/ ├── delay.c // 延时函数 ├── usart.c // 串口调试这样组织的好处是每个模块的耦合度很低。比如你不想用1602LCD想换OLED只需要新写一个bsp_oled.c把相关调用接口保持一致其他模块不用大改。3.2 滴速检测定时器输入捕获软件滤波滴速检测的代码实现核心是配置TIM2_CH1为输入捕获模式。一个液滴落下产生一个脉冲定时器捕获到上升沿时记录当前的计数器值两次上升沿之间的时间差就是液滴间隔。void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); uint16_t capture TIM_GetCapture1(TIM2); uint16_t interval capture - last_capture; // 计算边沿间隔 last_capture capture; // 丢弃明显无效的测量值50ms看做干扰3000ms看做停滴 if (interval 50 interval 3000) { // 存到环形缓冲区用于软件滤波 interval_buffer[filter_index] interval; filter_index (filter_index 1) % FILTER_SIZE; } } }这里有个非常关键的细节测量值必须做滤波。因为实际环境中红外对管很容易受到环境光干扰或者液滴因为表面张力产生不规则抖动直接拿单次测量值计算滴速会跳得很厉害。我采用的是中位值平均滤波把最近10次的间隔值排序去掉最大和最小剩余8个求平均。这种滤波方式对抗脉冲干扰非常有效比单纯滑动平均稳健得多。滴速的计算公式很简单speed_dpm 60000 / average_interval_ms;如果平均间隔是600ms那么滴速就是100滴/分钟。3.3 PID闭环算法怎么让滴速稳定到设定值这版升级版的核心卖点就是这个PID控制器。PID控制说简单也简单说复杂也复杂。关键点是参数整定参数没调好系统要么响应太慢要么超调震荡。我用的是增量式PID。为什么不用位置式因为增量式输出的是控制量的增量即使PID输出错误也只是当前基础上的一个微小变化不容易造成系统大幅跳变安全性更好。公式是// 增量式PID计算 float PID_Calculate(PID_TypeDef *pid, float setpoint, float measured) { float error setpoint - measured; pid-integral error; // 积分限幅防止积分饱和 if (pid-integral pid-integral_limit) pid-integral pid-integral_limit; if (pid-integral -pid-integral_limit) pid-integral -pid-integral_limit; float derivative error - pid-last_error; float output pid-Kp * error pid-Ki * pid-integral pid-Kd * derivative; pid-last_error error; return output; }参数整定上我用的经验主义手工整定法先把Ki和Kd设为0只留Kp从小到大慢慢加观察滴速响应曲线找到系统开始震荡的Kp值取一半左右作为初始Kp。然后加入Ki用来消除稳态误差。注意Ki不能太大否则会产生低频震荡。最后加Kd用来抑制超调。Kd对噪声非常敏感如果滴速值滤波没做好Kd反而会放大抖动所以Kd一般给很小的值就可以了。我调出来的结果大概在Kp8.0Ki0.5Kd0.2这个水平。不过每个人的机械结构不一样参数会有差异尤其是油泵的管径、电机的响应速度都会影响所以这批代码里我把PID参数定义成了宏在pid.h里可以直接改。需要注意的是PID输出值即PWM占空比需要做输出限幅。我把PWM占空比限制在0到1000对应0到100%防止PID计算出界值导致电机突然全速运转那在输液场景下是很危险的。3.4 按键扫描与LCD显示更新逻辑按键扫描放在1ms的定时器中断里逻辑很简单按键状态改变后开始计时超过20ms且状态稳定才判定为有效按键。LCD的更新频率不需要太高我设定主循环每200ms刷新一次屏幕。因为1602LCD写数据本身比较耗时如果频率太高反而浪费CPU时间。屏幕第一行显示“Set:xxx/min”,第二行显示“Now:xxx/min State:Normal/Alarm”。4. Proteus仿真工程从零搭建与常见坑4.1 仿真工程包含哪些元件Proteus仿真工程是我这次开源的重点之一。很多初学者卡在“代码写好了但仿真跑不通”这一步多半是原理图连接或者元件参数设置有问题。我这个仿真工程包含的元件清单如下STM32F103C8T6Proteus库里叫STM32F103C8LM393比较器红外发射管和光敏三极管模型用LDR光敏电阻模拟液滴遮挡ULN2003驱动芯片DC电机模型1602LCD屏幕LM016LLED指示灯和蜂鸣器报警按键矩阵虚拟终端Virtual Terminal用于串口调试关注仿真的朋友会问液滴检测在仿真里怎么模拟这个我用的办法是用一个按钮开关并联在光敏电阻两端每按一次模拟一个液滴落下。也可以用一个方波信号发生器设置频率等于滴速接在比较器输入端这样更加自动化演示效果也更好。仿真工程文件里我已经把比较器阈值调整好了虚拟终端也能正常输出串口调试信息。打开Proteus工程加载编译好的HEX文件工程里附带了一个已经编译好的hex点运行就能看到动态效果屏幕显示当前滴速调节按键设定目标值电机会跟着转动。4.2 仿真跑不通排查思路分享这几类问题是仿真新手最容易遇到的第一程序下载不进去。Proteus里双击STM32芯片在Program File属性里选择HEX文件。很多人下载不了是因为没有设置Crystal Frequency属性默认是4MHz和代码里的系统时钟配置不匹配程序运行就会卡死。我建议设置成8MHz或者72MHz代码里用的是外部8MHz晶振倍频到72MHz。第二LCD显示乱码。多半是I2C地址不对。PCF8574的I2C地址默认是0x27如果原理图上A0-A2引脚接了上拉电阻地址会变需要在代码里对应修改。第三虚拟终端没有输出。虚拟终端的TX/RX和USART引脚的接法容易接反。虚拟终端的RXD接STM32的TX引脚PA9TXD接STM32的RX引脚PA10而且两个设备的地必须连接好。第四电机总是转个不停。检查PWM引脚是否正确配置。如果电机控制引脚悬空了ULN2003输入端的下拉状态可能会导致输出端误导通。4.3 仿真和实物的差异提醒我必须强调一个事实Proteus仿真通过不等于硬件就能正常工作。仿真环境的理想化和真实世界的差距主要体现在这几个地方仿真里没有接触电阻、没有导线压降、没有电磁干扰所以信号质量永远是完美的。仿真里的LM393、ULN2003都是理想模型不涉及真实的传输延迟和饱和压降。仿真里没法很好地模拟电机堵转、电源纹波这些现实问题。所以如果你是课程设计需要做实物的千万不要因为仿真跑通了就觉得万事大吉硬件调试时该踩的坑一个都不会少。反过来如果你只是为了交仿真作业那这批代码和工程文件足够让你拿一个不错的分数。5. 通信功能与上位机联动5.1 RS485通信协议设计RS485通信在医疗监护场景里非常重要因为医院病房的传输距离可能超过50米普通TTL串口的电平标准撑不住这么长距离的信号传输。RS485用差分信号传输抗共模干扰能力强布线也简单所以仍然是工业现场和医疗设备里的主流选择。协议方面我定义了一个简单但健壮的数据帧格式帧头(2字节)地址(1字节)功能码(1字节)数据长度(1字节)数据(N字节)校验(1字节)0xAA 0x550x01~0x100x01读参数 / 0x02写参数 / 0x03报警Len具体数据异或校验帧头0xAA 0x55可以帮助接收端快速同步即使前一个数据帧传输中途丢失接收方也能重新对帧。地址字段用于区分不同床位的输液终端比如1号床地址是0x012号床是0x02护士站电脑作为主机轮询各从机。校验用异或比较简单虽然强度比不上CRC16但在这个应用场景下配合帧头同步已经足够实用。5.2 串口调试输出的经验开发调试时我习惯把关键状态信息通过USART1输出到电脑串口助手。波特率设定为115200打印格式大概是[Drip] SPeed: 82 DPM, Set: 80 DPM, PWM: 43.2% [Alarm] Over speed! Current 85 DPM Limit 80 DPM这些打印信息帮助我快速定位了很多问题。比如PID参数整定的时候光看电机的转动速度很难判断控制效果但通过串口实时观察滴速变化曲线就能很直观地看到超调量和稳态误差。我习惯配合SerialPlot这类工具把数据画成曲线效果比看数字直观得多。5.3 上位机部分这次开源的是下位机源码和仿真上位机软件我只提供了一个简单的Python演示脚本主要是为了验证RS485通信协议的正确性。完整的上位机界面程序还在整理后续会补发请大家留意更新。演示脚本的逻辑很简单打开串口、发送读参数指令、解析返回帧并打印滴速信息。用pyserial库实现大概几十行代码就能跑起来。6. 关键功能调试与异常处理6.1 报警系统别等危险发生了才响输液监护系统的报警机制和普通电子制作里的“响了就行”完全不是一个级别。我把报警分了三个等级一级报警提示滴速偏差在±15%以内蜂鸣器每2秒短鸣一声LED慢闪。二级报警警告滴速偏差超过±15%但低于±30%蜂鸣器每500ms鸣叫一声LED快闪。三级报警严重滴速偏差超过±30%或者检测到输液完成长时间无滴速蜂鸣器连续鸣叫LED常亮同时自动停止电机并锁定执行机构必须人工复位。这个分级逻辑在代码里通过一个简单的状态机实现。每一级报警除了声光提示外还会通过RS485上传报警帧到护士站方便远程处理。6.2 输液完成检测怎么判断“滴完了”输液完成检测是实际使用中一个很关键的功能。当输液瓶空了不再有液滴落下滴速降为0。但这里有个陷阱单纯检测滴速为0不够因为电机停转时滴速也会变成0。我采用的双重判定逻辑是滴速为0持续超过30秒。同时压力传感器检测到的管道压力低于某个阈值说明不是堵管导致的停液。两个条件同时满足才判定为“输液完成”进入三级报警并锁定系统。这样能避免把“堵管”误判成“输完”的情况提高报警准确性。// 输液完成判定伪代码 if (speed_dpm 0) { if (no_drip_time 30s pressure PRESSURE_THRESHOLD) { alarm_level LEVEL3; motor_stop(); system_locked 1; } }6.3 看门狗技术程序卡死也能自救嵌入式系统最怕的就是程序死循环或者跑飞了。尤其是医疗设备如果设备在监护过程中死机了后果不堪设想。我的代码里启用了独立看门狗IWDG在初始化时设置超时时间为1秒然后主循环每200ms喂狗一次。如果程序卡死超过1秒看门狗会强制复位系统重新运行。这个功能在仿真里不容易体现但实机上非常重要。我在代码的main函数里加了IWDG初始化注释也写得比较详细建议大家移植到自己的工程时一定保留这个功能。7. 常见问题排查与优化记录7.1 滴速检测不准波动大怎么排查按照我的调试经验滴速检测波动大通常从以下三个方向排查光电传感器阈值设置不当。如果比较器阈值设置得太接近信号电平微小干扰就能触发翻转。需要根据示波器观察到的信号幅值把阈值设定在信号摆幅的50%左右。环境光干扰。红外接收管对阳光和室内照明中的红外分量有响应。解决办法是给传感器加上遮光罩或者在软件上做窗口判决——只接受脉宽在一定范围内的信号。滤波算法参数不合适。如果滤波窗口太小干扰信号没有被有效抑制如果窗口太大系统响应变慢。我最终用的10次中位值平均是在动态响应和抗干扰之间的平衡。7.2 PID参数整定经验记录PID参数整定我走了不少弯路。一开始参考的是位置式PID的经典整定流程结果发现增量式PID的表现和位置式还是有些区别的。位置式对积分项的依赖更强而增量式由于自带惯性Kp可以给得比较激进一些。实际调参过程中最有用的工具就是串口曲线可视化。我把设定值和实测滴速通过串口上传用SerialPlot画成两条曲线直观看到Kp太小实测值到达设定值的时间很长曲线平缓上升误差长时间存在。Kp太大实测值在设定值附近大幅震荡系统不稳定。Ki太大曲线出现低频爬坡式震荡电机转速时快时慢听起来就是“嗡嗡”一阵一阵的。Kd合适超调量明显减小系统平稳接近设定值。最后定的Kp8.0、Ki0.5、Kd0.2在仿真里和实物上都表现不错不过仅供参考。7.3 蜂鸣器误报警问题升级版的报警逻辑加了延时确认机制不太容易出现误报警了。但在第一版的时候蜂鸣器会因为滴速的瞬时波动而频繁报警非常烦人。解决办法是加了一个报警持续时间门槛。检测到滴速超限后不是立即触发报警而是连续超限超过3秒才确认报警。如果只是单次干扰造成的瞬时超限就会被过滤掉。这个思路在工业现场叫做“延迟确认”实质上是利用时间维度做了一次滤波效果立竿见影。8. 开源资料清单与使用建议8.1 这次开源了哪些文件这次打包开源的资料包括以下内容源代码完整工程基于MDK5Keil uVision5开发环境使用标准外设库注释覆盖率大概在60%左右关键函数都有详细说明。Proteus仿真工程版本为Proteus 8.11以上包含完整的原理图连接和元件参数设置加载固件后可直接运行演示。原理图PDF版本AD绘制的原理图导出方便快速查阅和打印。PCB设计文件这次一并开源了PCB工程两层板设计元件封装都已经调好可以直接送去打样。使用说明书包含硬件连接、烧录步骤、按键操作说明以及PID参数调整指南。BOM物料清单所有元件的型号、数量、封装、参考价格照着采购就能焊一套。8.2 给初学者的上手路线建议经常有人问我拿到这些资料不知道从哪看起。我的建议是从仿真开始然后看代码最后做实物。第一步先打开Proteus仿真工程跑起来看看效果对系统有一个整体认识。第二步打开代码工程对照原理图先看main.c里的初始化流程再看各个外设的驱动代码。重点是定时器输入捕获和PID部分那两块是系统核心。第三步如果条件允许再自己动手搭一套实物。根据自己的实际情况调整参数。8.3 后续可以扩展的方向这个项目的扩展空间其实很大这里列几个我最近在思考的方向LoRa无线传输替代RS485实现无线组网解决病房布线困难的问题。触摸屏界面用串口屏替代1602LCD人机交互体验会好非常多。RTOS移植把裸机程序改成FreeRTOS用任务管理来处理不同优先级的功能系统的实时性会更好维护。边缘AI用简单的机器学习算法比如异常流量检测来预测输液反应风险这个方向比较前沿适合想挑战自己的朋友。8.4 最后的体验分享做完这套系统我最深的感触是嵌入式项目最花时间的地方往往不是写代码而是调试和验证。尤其是在硬件和软件交界的地方——信号调理、电机驱动、传感器校准这些地方出了问题光看代码是找不出原因的必须结合原理图和波形去分析。另外有个小经验想分享给做毕设的朋友做实物的时候尽量买两个滴速传感器模块。红外对管这类器件比较脆弱焊接时高温容易损坏静电也可能打坏。我调试过程中就烧了两个传感器幸好备份充足才没有耽误进度。如果你也想做医疗相关方向这套系统是一个非常合适的起点不仅能学到完整的嵌入式开发流程还能深入理解闭环控制在实际应用中的价值。有问题欢迎在评论区交流我会尽量抽空回复大家。项目文件已经上传下载后如果仿真跑不起来或者代码有编译问题先对照说明文档排查一遍还不行就留言说吧。