STM32项目开源:智能医疗输液点滴系统(代码+原理图+仿真)
搞嵌入式这几年,我一直想找一个既能练手、又真正有实际意义的完整项目。实验室里那些流水灯、温湿度计做得再多,面试时也拿不出手。直到去年在医院陪床,看着护士一趟趟跑过来盯着输液瓶,我突然意识到:输液监测这件事,简直太适合用STM32做一套完整方案了。
这个项目我从零开始画原理图、写驱动、调PID、跑仿真,前前后后花了一个多月。现在把整套东西开源出来,包含完整代码、可生产的原理图、以及Proteus仿真工程,希望能帮到正在找项目的朋友。
这套智能医疗输液点滴系统解决的是真实痛点:传统输液完全靠人盯,病人睡着了没人换药,家属紧张得不敢合眼,护士工作强度也大。系统通过红外对管检测滴壶内的液滴下落,实时计算滴速,当滴速异常或药液即将输完时,能本地声光报警,同时通过串口把数据推送到上位机大屏。整套系统成本极低,一个STM32F103C8T6核心板加几个传感器就能跑起来。
适合拿来练手的朋友有三类:准备求职的嵌入式学生,需要一个能写在简历上的完整项目;想往医疗电子方向转的工程师,可以看看医疗设备的基本设计逻辑;还有对红外检测、定时器输入捕获、PWM控制这些STM32核心外设不熟的人,这项目把这些知识点全串起来了。
1. 系统整体设计与思路拆解
1.1 需求分析:医院场景下输液监控到底要解决什么
做项目之前,我把输液场景里的真实需求列了个清单。不是拍脑袋想功能,而是从医护人员的实际工作流程倒推。
护士站最关心的三个问题:第一,每个床位的药液还剩多少,第二,当前滴速是否在医嘱范围内,第三,药液快完的时候能不能提前预警。病人和家属关心的则更简单:别出意外,别一直盯着。
从这个清单倒推系统功能,核心就三块:滴速检测、滴速控制(或异常报警)、液位检测。滴速检测用红外对管方案,液位检测用滴壶下方的对射式红外传感器,控制执行器用步进电机或直流电机带动挤压阀,夹住输液管调节流速。这套架构和市面上几千块的医用输液泵逻辑几乎一致,只是我们在精度和可靠性上做了取舍,让它更适合学习和二次开发。
1.2 方案选型:为什么用STM32F103C8T6而不是其他芯片
主控选型我纠结过一阵子。当时手上有ESP32、STM32F407、还有这块F103C8T6。最后选了F103C8T6,核心原因有三个。
一是资料和生态足够成熟。不管是在Keil里点灯,还是用CubeMX配外设,网上教程一抓一大把,遇到问题能快速搜到解决方案。二是外设资源刚好够用。这个项目要用到定时器输入捕获(测滴速)、PWM输出(控制电机)、ADC采集(备用模拟量监测)、USART通信(上位机交互)、GPIO中断(按键和报警),F103C8T6的TIM1、TIM2、TIM3、TIM4全都派上了用场。三是价格确实便宜,核心板十几块钱,烧了不心疼。
如果你手头只有F407或G431,移植也不难。项目代码里我把硬件相关的初始化都放在单独的HAL层,换芯片只需要改CubeMX配置和少量引脚映射。
1.3 系统架构:从信号采集到上位机显示的完整链路
整个系统的数据流是这样的:滴壶里的液滴滴落瞬间,穿过红外对管的检测区域,红外接收管接收到一个遮挡脉冲,经比较器整形后送入STM32的TIM2通道1做输入捕获;单片机根据两次捕获的时间间隔计算滴速,通过一阶低通滤波平滑数据,然后同时做三件事:把当前滴速和设定值比较,偏差超过阈值就触发声光报警并启动电机调节;把滴速和液位状态打包成帧,通过USART1发送给上位机;把液位传感器信号送给EXTI中断,一旦检测到液面低于警戒线,立即进入紧急报警状态。
有人可能会问,为什么液滴检测不用摄像头或激光,而用红外对管?我实测下来,红外对管在成本、响应速度、抗干扰三方面最均衡。摄像头方案识别准确但处理复杂,激光方案精度高但对安装位置太敏感,红外对管只要把比较器阈值调好,滴速范围20-80滴/分钟都能稳定捕获。
2. 核心硬件方案解析
2.1 原理图设计:红外检测电路的几个关键细节
原理图我用的立创EDA画的,开源仓库里导出了PDF和Gerber文件。整板分四块:主控最小系统、液滴检测电路、液位检测电路、电机驱动与报警电路。这里重点讲液滴检测电路,因为这是整套系统精度好坏的关键。
红外发射管选用940nm波长的IR333,接收管用与它光谱匹配的PT334。这两个管子配对使用时,发射管串一个100Ω限流电阻,接收管c极接3.3V,e极通过10kΩ下拉电阻接地,信号从e极引出。静态时接收管导通,e极电压接近高电平;液滴经过时遮挡红外线,接收管截止,e极被拉低,产生一个负脉冲。
但这个负脉冲不能直接进STM32,因为波形边沿不够陡,而且会有抖动。我在后面加了一级LM393比较器,基准电压用两个电阻分压设定在2.0V。实测效果立竿见影,波形从缓慢变化的模拟信号变成标准的方波脉冲,STM32的输入捕获不会误触发。这是我从做光电编码器测速的帖子里学到的经验,比直接接GPIO稳定太多。
液位检测的原理类似,只是红外对管安装在滴壶下方两侧,正常有液时接收管被液体折射影响,输出电平跳变。需要注意滴壶本身是透明软管,安装时要做遮光处理,否则环境光会干扰检测结果。我用了热缩管加黑色电工胶布,两层遮光后实测稳定性大幅提升。
2.2 电机驱动与执行机构:用挤压阀实现滴速控制
滴速控制执行器我选了28BYJ-48步进电机,驱动芯片ULN2003。为什么不用直流电机?因为滴速控制需要精确调节,步进电机可以按固定角度步进,方便实现定量挤压。ULN2003内部是达林顿管阵列,可以直接驱动5V步进电机,STM32的GPIO输出3.3V电平就能驱动它。
挤压阀的机械结构我用3D打印做了一个滚轮夹具,步进电机转动时带动滚轮下压输液管,改变管道截面积,从而调节滴速。这个方案在开源硬件社区很常见,缺点是响应稍慢,但从学习角度完全够用。
调节策略我用的是增量式PID。目标滴速由按键设定,实际滴速由红外检测得到,PID输出控制步进电机的步数和方向。参数我做了两组:一组偏快速响应(Kp=35,Ki=8,Kd=15),用于从静止状态快速到达目标滴速;一组偏稳定(Kp=20,Ki=5,Kd=20),用于稳态时的小幅修正。实际调参过程后面详细说。
2.3 电源与外围电路:从USB取电到系统供电的稳定性考虑
供电部分我用了USB 5V输入,经过AMS1117-3.3稳压给STM32供电,电机和红外发射管直接吃5V。这里有个容易被忽略的坑:步进电机启动瞬间电流尖峰能达到300mA以上,如果和单片机共用LDO输出,会导致单片机复位。所以我的PCB上3.3V和5V电源之间加了磁珠和100uF大电容,电机驱动电源单独走线。
报警电路很简单,一个无源蜂鸣器加一个NPN三极管驱动,GPIO输出高电平导通。我做了一个渐强报警音,通过定时器PWM输出不同频率的脉冲,比单纯的滴滴声更容易引起注意。指示灯用了红绿双色LED,绿色表示运行正常,红色闪烁表示报警。
3. 软件代码设计与核心实现
3.1 代码结构:HAL库工程如何分层
软件部分我用的是STM32CubeMX生成工程框架,加上Keil MDK编写业务代码。工程目录清晰分了四个部分:Core里放main.c、stm32f1xx_it.c等系统文件;Drivers放HAL库;BSP放板级驱动,比如infrared.c、motor.c、buzzer.c、uart.c;App放应用层逻辑,包括speed_detect.c、pid.c、protocol.c。
分层的意义在于:BSP层只负责寄存器底层的读写,不关心业务逻辑;App层只调用BSP提供的接口,不直接操作寄存器。这样改硬件平台时,只需要改BSP层,App层完全不用动。这种模块化思维在正式工程项目里是基本功,面试官很看重这个。
main.c里的主逻辑是一个超级循环加多个中断服务函数。滴速检测在TIM2输入捕获中断里做,液位报警在EXTI中断里做,PID控制在TIM3定时器中断里以100Hz频率执行,主循环只处理按键扫描、OLED刷新和串口数据发送。这样分时处理的好处是每个任务的实时性都能得到保证,不会因为某个任务卡死影响整个系统。
3.2 滴速检测:输入捕获模式的配置与数据处理
滴速检测是整个系统的灵魂。我用了TIM2的通道1做输入捕获,上升沿触发。每个上升沿进入中断时,读取当前计数器的值,减去上一次的捕获值,就能得到两次液滴之间的时间间隔,单位是定时器时钟周期数。换算成滴速的公式是:
滴速(滴/分钟) = 60000000 / (时间间隔 × 定时器分频系数)
我这里定时器时钟是72MHz,分频系数72,所以计数器频率是1MHz。如果测得两次滴落间隔为60000个计数,那滴速就是60000000/60000=100滴/分钟。
原始测量值抖动很大,因为液滴下落并不是绝对匀速的。我在App层加了一阶低通滤波,滤波系数0.3:filtered_value = 0.7 * old_value + 0.3 * new_value。这个系数是我反复试出来的,太小响应慢,太大滤不掉抖动。体温计式的趋势平滑,用在这里正合适。
还有个细节是输入捕获中断里不要做太多事情,否则会影响下一次捕获。我实测过,中断服务函数里只做计数读取和标志位设置,滤波计算放在主循环里处理,这样可以最大限度避免中断丢失。
3.3 PID控制:步进电机的增量式控制算法
PID控制这块我直接用的位置式改增量式。公式不复杂:
增量 = Kp * (e(k) - e(k-1)) + Ki * e(k) + Kd * (e(k) - 2*e(k-1) + e(k-2))
其中e(k)是当前误差,即目标滴速减去实际滴速。得到增量后,判断正负决定步进电机正转还是反转,绝对值大小决定电机走的步数。
实际调试时发现,直接让电机根据PID输出持续转动,电机容易过冲。因为步进电机每一步都会真实改变输液管截面,机械系统的响应滞后于电气信号。我的解决办法是给PID输出加了死区:误差绝对值小于3滴/分钟时,PID输出归零,电机不动作;误差超过20滴/分钟时,输出饱和限幅,防止电机一步走太多把管子完全压死。
调参顺序我建议先调Kp,再调Ki,最后调Kd。先让系统稳定在目标值附近不振荡,然后加Ki消除稳态误差,最后加Kd抑制超调。这套方法在自控原理里叫临界比例度法,用在嵌入式PID调参里同样适用。
3.4 通信协议:串口如何与上位机对接
串口部分我用的是USART1,波特率115200,8N1。通信协议自己定义了一个简单但完备的帧格式:帧头0xAA 0x55,数据长度,数据区,校验和。数据区固定12字节,包含当前滴速、目标滴速、PID输出值、液位状态、报警标志、系统运行时间。
为什么不直接用文本printf输出?因为上位机解析文本不方便,而且文本传输容易出错。二进制帧协议虽然写起来多几行代码,但解析稳定、扩展方便。配套的上位机我用Python写了个简易界面,读取串口数据后实时绘制滴速曲线,同时显示报警状态。Python做桌面工具比C快太多了,而且用pyserial库操作串口非常简单。
校准这块我要特别提醒:不同滴壶的液滴体积不同,同样滴速下实际输液量可能差别很大。所以我做了一个校准模式,用户先让输液泵以设定滴速运行一分钟,实际称量输液量,然后把校正系数写进Flash保存。这样系统的计量误差可以控制在±5%以内。
4. 仿真验证与Proteus联调
4.1 仿真模型的搭建:Proteus里的传感器模拟
Proteus仿真是我前期调代码时最重要的工具。真机调试需要焊接PCB、安装传感器、准备输液架,成本高迭代慢,而仿真环境里改电路、调参数瞬间完成。我把原理图导入Proteus,用虚拟红外对管模拟液滴下落,用虚拟示波器观测波形,省了大量的硬件调试时间。
传感器在Proteus里怎么模拟?我采用的方法是:用两个数字开关模拟红外对管的通断,通过定时脉冲发生器按设定频率产生脉冲信号,直接接入STM32的输入捕获引脚。脉冲频率可调,对应不同的滴速,这样就能验证代码在不同滴速下的响应是否正确。OLED显示模块我用Proteus自带的虚拟终端代替,把状态信息通过串口输出,在虚拟终端里查看。
仿真的价值在于,它能帮你把软件逻辑先验证到八九成,再上真机时就只需要关注硬件层面的问题。比如PID参数在仿真环境和真机上表现很接近,因为控制对象本质都是一个惯性系统。
4.2 仿真中遇到的典型问题:时序与协议验证
仿真过程中我踩过最大的坑是Proteus里的系统时钟。Proteus默认的STM32模型时钟频率和真实芯片有差异,导致我用延时函数写的时序全部乱套。解决办法是工程里所有依赖时间的逻辑都用定时器,不用软件延时。这也促使我代码写得更规范,算是因祸得福。
另一个坑是UART虚拟终端乱码。排查了很久发现是波特率设置问题。Proteus里的虚拟终端有自己的时钟源,和STM32外设时钟不同步,需要手动在虚拟终端属性里设置从外部时钟输入。这个在真实硬件上不存在,但很多初学者玩仿真时经常被搞晕。
碳膜电阻这块我提一句:仿真里的电阻电容参数必须设置真实值,比如10kΩ不能写成10k,否则仿真结果和实际电路差异很大。我见过很多人在Proteus里电容阻值不填,仿真通过,一焊板子就废了。
4.3 仿真到实物的迁移:从代码到硬件调试
仿真通过后,我开始焊电路板。焊接顺序有讲究:先焊电源部分,用万用表确认3.3V输出正常,再焊主控最小系统,用ST-Link下载点灯程序验证GPIO,最后才焊传感器和电机电路。每焊一部分就测试一部分,不要一次性全焊完再上电,否则出了问题极难排查。
实物调试时我遇到的问题,很多在仿真里根本模拟不出来。最典型的是红外对管的信号抖动。真实环境里有环境光干扰、滴壶反光、甚至人手晃过都会产生误触发电平。我在软件里加了一个简单的防抖处理:连续两次捕获间隔小于5ms的信号直接丢弃,判断为干扰而非真实液滴。同时把比较器的基准电压微调高了一点,实测误触发率从每分钟十几次降到几乎为零。
电机控制从仿真迁移到实物也遇到机械公差问题。3D打印的挤压滚轮和不同品牌输液管的配合松紧度不同,每次换管子都要重新标定电机步数和滴速的对应关系。我在代码里做了一个自动标定函数:先让电机从完全松开状态逐渐下压,同时实时检测滴速变化,当滴速首次降到目标值以下时记录步数。这个函数在现场换管后一键执行,非常实用。
5. 常见问题与调试经验总结
5.1 滴速检测不准,数值跳动大
这个问题十个人里有八个会碰到。排查思路按顺序走:第一步用示波器看红外检测电路的输出波形,如果波形边沿不陡或幅值不够,检查比较器基准电压和接收管的偏置电阻;第二步检查输入捕获中断是否频繁丢失,可以在中断里加一个计数器,对比一秒内进入中断的次数和OLED显示值;第三步检查滤波算法是否合理,滤波系数太小时输出滞后明显,太大时跳动依旧。
我自己的经验是,波形整形比软件滤波更重要。硬件波形做好了,软件只要做轻微滤波就能得到平滑的滴速值。如果硬件波形一塌糊涂,靠软件强行滤波,系统响应会变得迟钝,PID控制也容易振荡。
5.2 串口通信数据错乱或丢失
典型原因是帧同步没做好。我的协议在软件里做了超时重同步处理:接收状态机检测到帧头后,如果4ms内没收到完整帧,就复位重新等待帧头。这样哪怕中间丢一两个字节,下一条帧也能恢复同步,不会出现连续乱码。
另外注意USB转TTL模块的3.3V和5V电平问题。STM32的USART引脚是3.3V电平,如果USB转TTL模块是5V供电,RX引脚可能输出5V高电平,长期使用会损伤STM32引脚。最好用带电平转换的模块,或者给RX引脚串一个1kΩ限流电阻。
5.3 电机调节时滴速反而失控
这个现象多半是机械结构问题。挤压阀下压太紧时,输液管被完全压死,滴速直接降到0;电机再反转松开一点,滴速又冲上去。PID在这种情况下会反复震荡。解决办法是给电机的步进范围做软件限位,把最大步数和最小步数提前标定好,PID输出在这两个极限之间运动,不允许越界。
还有个小细节:步进电机运转时会发热,长时间堵转(电机关节顶到限位块但PID还在输出)会损坏驱动芯片。我加了一个超时保护,电机持续运转3秒以上且滴速没有变化时,强制停止电机并报警。
5.4 编译下载时"no stm32 target found"的排查
很多朋友拿到代码后烧录时碰到这个报错。这个提示的大意是调试器没有找到STM32芯片,常见原因有四个:SWDIO和SWCLK接线接反了、目标板没有供电、芯片进入了低功耗模式或读保护状态、Keil里Debug设置没选对烧录器型号。逐个排查即可,绝大多数情况是前两个原因。
如果芯片之前烧录过程序并且开启了读保护,需要用ST-Link Utility先解除保护。新手最容易忽略的是给板子单独供电,ST-Link的3.3V输出电流有限,带不动整板负载时就会导致连接不稳定。
6. 开源资料的获取与进一步扩展建议
整个项目的代码、原理图、仿真工程、上位机源码都放在开源仓库里,目录结构如下:/Doc放设计文档和BOM清单,/Hardware放立创EDA原理图和Gerber文件,/Firmware放Keil工程源码,/Simulation放Proteus仿真文件,/Tool放Python上位机源码。每个部分都有README说明,照着操作就行。
使用前建议先看一遍设计文档,了解整体结构后再打开工程。代码里的关键函数都有注释,模块化程度比较高,不管是直接烧录学习还是改成自己的项目,都不会太痛苦。
如果想让这个项目更进一步,我建议往这几个方向扩展:一是加WiFi模块(比如ESP8266),把滴速数据上传到云平台,做成真正的物联网医疗监护系统;二是加触控屏替代按键和OLED,人机交互会舒服很多;三是把电源改成电池供电,加入低功耗管理,提升便携性。根据我个人经验,每拓展一个方向,你对嵌入式系统的理解都会上一个台阶。
坦白说,这个项目做到量产标准还有很多路要走,比如双冗余检测、系统自检、EMC设计,这些都是医疗电子入门的必修课。但作为学习项目,它已经把嵌入式开发的核心环节完全串联起来了。希望这份开源资料能帮到正在这条路上探索的朋友,期待看到你们的二次创作。