1. 为什么要自己搭一套智能输液监护系统:从医院场景倒推出来的需求
做嵌入式这几年,我陆陆续续接触过不少医疗电子相关的项目,但真正让我下定决心把整套"智能输液监护调控系统"开源出来的,其实是一次去医院的陪护经历。当时隔壁床一位老人输液,药液快滴完时家属没注意,护工又忙不过来,最后是靠邻床提醒才叫来护士换药。那一刻我就在想,输液监护这件事,表面上是个"盯着吊瓶"的小事,实际上牵扯到患者安全、护士工作量、家属焦虑感一堆问题,而市面上多数输液泵的价格又让很多基层医疗机构和家庭场景望而却步。
正好那段时间我在做STM32相关的项目,手头有F103C8T6的板子,又刚研究了步进电机驱动和多种传感器融合的方案,于是就有了这套"智能输液监护调控系统升级版"。所谓的升级版,是相对于我上一版只做"监测报警"来说的——上一版只能检测滴速异常和液位过低然后报警,这一版加上了自动调控的能力,也就是说系统不仅能"发现"问题,还能在安全范围内做初步处理,比如暂停输液、调整滴速,甚至可以和上位机联动,由医护人员远程确认后执行操作。
整个项目全部开源,包含三样东西:完整可编译的STM32工程代码、Altium Designer绘制的原理图、Proteus仿真工程。也就是说,你有三种方式来使用这套项目:
- 手上只有开发板想快速验证逻辑,可以直接看代码和仿真工程;
- 想做一块正经的PCB原型机,可以直接参考原理图甚至改版;
- 想把它做成毕业设计、竞赛作品或者产品原型,这套项目提供了完整的基线版本。
项目的主要功能和性能指标,我先放在这儿,方便你判断这套东西是否值得深入看:
| 功能模块 | 具体能力 | 实现方式 |
|---|---|---|
| 滴速检测 | 红外对管遮挡计数,实时计算滴速(滴/分钟) | 外部中断 + 定时器窗口统计 |
| 液位检测 | 输液管末段液位传感器,低于阈值报警 | 数字IO轮询 + 消抖处理 |
| 自动调控 | 步进电机驱动蠕动泵,PID闭环调节滴速 | 增量式PID + PWM细分驱动 |
| 人机交互 | OLED显示实时参数,按键设置目标滴速 | I2C驱动SSD1306 + GPIO按键扫描 |
| 报警系统 | 声光报警,异常状态分级提示 | 蜂鸣器 + LED + 串口上报 |
| 上位机通信 | 串口协议上报数据和接收指令 | USART1中断收发 + 自定义帧协议 |
这套系统最能打的一点,是它把"监测"和"调控"这两件事同时给做了。很多开源项目只做监测,数据报了就不管了;也有很多项目只做调控,但不检测实际效果。这套系统是先把滴速的实际值测准,然后用PID去逼近目标值,形成了一个完整的闭环控制链路。
2. 系统硬件设计:原理图里的每一路信号都是有讲究的
2.1 主控选型:STM32F103C8T6为什么是"刚刚好"的选择
主控我用了STM32F103C8T6,这颗芯片在嵌入式圈子里几乎人手一块,某宝上几块钱就能买到,Flash虽然只有64KB,但跑这套逻辑绰绰有余。选它有几个具体考虑:
第一,外设够用且不浪费。系统需要的核心外设是外部中断、定时器、I2C、USART、GPIO、ADC,F103C8T6全都具备,而且数量正好匹配:滴速检测用PA0的外部中断,液位传感器用PB0~PB3的数字输入,步进电机用TIM2输出PWM驱动,OLED走PB6/PB7的I2C1,串口用PA9/PA10。资源表排得满满当当但又不紧张,这种"刚刚好"的感觉,实际开发中非常舒服。
第二,3.3V供电体系统一。整块板子使用3.3V作为主逻辑电压,传感器模块、OLED模块都能直接由板载LDO供电,不需要额外电平转换电路。这大大简化了原理图的设计,也降低了新手焊接时的出错概率。
第三,生态成熟,调试工具链完备。无论是用标准外设库还是HAL库,网上都有海量参考资料。我自己用标准外设库写的这套代码,底层寄存器操作比较直观,也方便你移植到寄存器版或者HAL版。
2.2 电源设计与隔离:模拟和数字部分为什么必须分开
先说一个很多人会忽略的问题:这套系统里既有传感器的小信号(红外对管的微弱电流变化),又有步进电机的驱动电流(瞬时电流可能到几百毫安甚至1A),如果电源不处理好,会出现一个非常典型的现象——电机一转,滴速检测值就开始乱跳。原因很简单:电机启动时拉低电源电压,导致红外对管的发射管光强闪烁,接收端的信号就出现了毛刺。
解决方案是在原理图里做了模拟电源和数字电源的分区处理:
- 输入电源先经过一个SS34肖特基二极管做反接保护,然后进入AMS1117-3.3稳压,输出3.3V主电源;
- 模拟部分(红外对管接收端、液位传感器的比较器供电)通过一个磁珠L1从3.3V分出VCC_ANA;
- 数字部分(MCU、OLED、蜂鸣器、电机驱动逻辑侧)直接接VCC_3V3;
- 模拟地AGND和数字地GND单点连接,在电路板的电源入口处汇合。
这么做的好处是,电机和蜂鸣器工作时的电流波动主要影响数字电源,而模拟电源被磁珠的高频阻抗挡在外面,红外对管接收端的信号稳定性显著提升。实测下来,电机工作时滴速检测值的波动从原来的±5滴/分钟降到了±1滴/分钟以内,这个改进对PID控制至关重要——你给PID的反馈数据如果本身就不准,后面调得再辛苦也是白搭。
2.3 红外对管滴速检测电路:一个下拉电阻的坑,我踩了整整一下午
滴速检测的原理不复杂:输液滴壶两侧分别放红外发射管和接收管,液滴落下时会短暂遮挡红外光,接收管的状态就会从"导通"变到"截止",用一个上/下拉电阻把这个变化转成电平跳变,MCU就能通过外部中断捕捉到每一次滴落。
但这套方案里有个很隐蔽的坑,就是接收管的偏置电阻怎么选。我给的是10K下拉电阻,信号接到比较器的同相输入端。一开始为了省事,我没用比较器,直接把接收管的分压信号送进MCU的GPIO,结果发现信号边缘特别"软",滴速快了之后经常丢中断。后来查了逻辑分析仪才知道,红外管的光电响应不是瞬间完成的,分压信号上升沿有几十毫秒的斜坡,MCU的施密特触发虽然能处理一定的边沿抖动,但高速滴落时连续两个脉冲间隔太短,信号还没恢复到阈值就又掉下去了,就会漏检。
最终的电路改成了LM393比较器整形:接收管信号经过10K/10K分压后进比较器的同相端,反相端接一个可调电位器提供参考电压。调整电位器可以设置触发阈值,保证滴落瞬间比较器快速翻转,输出方波直接进MCU的外部中断引脚。这个方案的抗干扰能力比直接接GPIO强很多,测试中在每分钟20滴到200滴的范围内都能稳定计数。
2.4 步进电机与蠕动泵驱动:为什么我不建议你用直流电机
自动调控的核心执行机构是蠕动泵——一根软管被滚轮反复挤压,推动液体往前流动。蠕动泵的流量和转速近似线性,所以用步进电机驱动比直流电机有天然优势:
- 精确控制:步进电机按固定角度走,可以精确控制挤压频率,从而实现精确的滴速控制;
- 保持力矩:步进电机在停止状态下也有保持力矩,可以防止蠕动泵在停止时由于液体压力产生倒流;
- 无需编码器:开环就能达到流量控制精度,降低系统复杂度和成本。
电机驱动我选了ULN2003,这是最经典的达林顿管阵列芯片,5线四相步进电机(28BYJ-48)的标准搭配。原理图里每一相都串了限流电阻,并且在电机电源端加了100uF电解电容和104陶瓷电容做去耦。
关于电机驱动,我的建议是:如果你要做一个真正的产品级原型,建议把ULN2003换成A4988或者DRV8825驱动板。前者电流能力弱,噪音大,力矩也一般,但用在Demo和毕设场景完全够用且极便宜。后者支持细分驱动,噪音小很多,但需要额外的逻辑电源和电机电源分离设计。这也是为什么我在原理图里保留了电机供电接口的独立性,方便你后续替换驱动方案。
2.5 完整BOM和成本估算:整套硬件做下来大约多少钱
我把原理图里所有器件整理一下,给你做个成本参考(按某宝零售价估算,不含PCB打样费):
| 器件 | 型号/规格 | 数量 | 参考单价 | 小计 |
|---|---|---|---|---|
| 主控 | STM32F103C8T6 | 1 | 6元 | 6元 |
| 红外对管 | 红外发射/接收一体或分体 | 2组 | 1元 | 2元 |
| 比较器 | LM393 | 1 | 0.5元 | 0.5元 |
| 稳压 | AMS1117-3.3 | 1 | 0.3元 | 0.3元 |
| OLED屏 | SSD1306 0.96寸 I2C | 1 | 15元 | 15元 |
| 步进电机 | 28BYJ-48 + ULN2003 | 1 | 8元 | 8元 |
| 蜂鸣器 | 有源蜂鸣器 | 1 | 1元 | 1元 |
| 按键 | 轻触开关 | 4 | 0.2元 | 0.8元 |
| 电阻电容 | 各种规格 | 若干 | - | 约3元 |
| PCB打样 | 嘉立创5片包邮 | 1次 | 约20元 | 20元 |
| 传感器管子 | 输液管液位检测附件 | 1套 | 约10元 | 10元 |
大约66.6元。这个成本做出来的东西,功能上对标几千块的输液泵的监测和基础调控能力,性价比是碾压级的。当然,电机力矩、控制精度、安全认证这些和医疗级设备肯定是没法比的,所以这个项目的定位很明确:教学演示、原理验证、原型开发、个人学习,而不是临床使用。
3. 核心代码与软件逻辑:从滴速计算到PID闭环控制
3.1 工程代码的整体架构和文件划分
代码我基于标准外设库写的,工程结构严格按照模块化思想划分。你下载下来用Keil5打开就能直接编译,不需要额外配置路径。主目录如下:
SmartInfusion/ ├── User/ │ ├── main.c // 主逻辑循环 │ ├── stm32f10x_it.c // 中断服务函数 │ └── system_stm32f10x.c // 系统时钟配置 ├── App/ │ ├── droplet_detect.c/h // 滴速检测模块 │ ├── liquid_level.c/h // 液位检测模块 │ ├── motor_control.c/h // 步进电机控制模块 │ ├── pid.c/h // PID算法模块 │ ├── oled_display.c/h // OLED屏显示模块 │ ├── key_scan.c/h // 按键扫描模块 │ └── alarm.c/h // 报警模块 ├── BSP/ │ ├── usart.c/h // 串口初始化与收发 │ ├── i2c.c/h // I2C软件模拟 │ └── timer.c/h // 定时器初始化 └── Hardware/ ├── stm32f10x_conf.h └── stm32f10x.h这种分层的好处是:App层的每个模块只做自己的事,通过接口函数互相调用,不直接操作寄存器。比如滴速检测模块对外暴露一个DropletDect_GetSpeed()函数,主循环只用调这个函数拿结果,至于内部是外部中断计数还是定时器计算,完全不用关心。你要改成HAL库,只需要把BSP层重写一遍,App层代码几乎不用动。
3.2 滴速检测的算法细节:怎么把两次滴落间隔换算成"滴/分钟"
滴速检测的核心是外部中断+定时器窗口统计。我在EXTI0_IRQHandler中断服务函数里做两件事:
第一,读取当前系统滴落计数器的值g_dropletCount,加1; 第二,判断如果当前处于"统计窗口"内(定时器每5秒触发一次窗口更新),就记录两次滴落的时间戳,计算出当前滴速。
// App/droplet_detect.c 核心片段 volatile uint32_t g_dropletCount = 0; volatile uint32_t g_lastTick = 0; volatile uint16_t g_speedDropletsPerMin = 0; static uint32_t s_windowStartTick = 0; static uint32_t s_windowDropletCount = 0; void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { uint32_t now = TIM2->CNT; // 如果两次滴落间隔太短,判定为抖动,忽略 if (now - g_lastTick > 10) { g_dropletCount++; g_lastTick = now; } EXTI_ClearITPendingBit(EXTI_Line0); } } void DropletDect_UpdateSpeed(void) { uint32_t now = TIM2->CNT; uint32_t elapsed = now - s_windowStartTick; if (elapsed >= 5000) // 5秒统计窗口 { uint32_t dropletsInWindow = g_dropletCount - s_windowDropletCount; // 换算为每分钟滴速 g_speedDropletsPerMin = (uint16_t)((float)dropletsInWindow * 12.0f); s_windowStartTick = now; s_windowDropletCount = g_dropletCount; } }这里有个容易被新手忽略的细节:为什么统计窗口是5秒而不是1秒或者10秒?
如果窗口太短,比如1秒,那么滴速慢时(比如每分钟20滴),一次滴落间隔是3秒,一个1秒窗口内可能一次滴落都没有,测出来的速度就是0滴/分钟,这显然不对;窗口太长,比如10秒,系统对滴速变化的实时性就差。5秒是一个折中——对于每分钟12滴以上的输液速度(临床上常见范围是20~60滴/分钟),5秒窗口至少能捕捉到1~5个脉冲,换算成"滴/分钟"误差可控。实际测试中这个窗口下,稳态误差在±2滴以内,对PID控制来说是够用的。
另外注意代码里有个抖动过滤:两次中断间隔小于10ms就忽略。因为红外对管在液滴落下的瞬间可能出现回弹(液滴表面张力导致光线多次遮挡),不处理就会一次滴落记成多次。
3.3 PID调参实战记录:从发散到稳定的完整过程
PID是这套系统里最"硬核"的部分,也是很多人在毕设或竞赛里最容易翻车的地方。我把实际调参过程记录下来,因为这比单纯贴代码有用得多。
首先明确被控对象和控制量:
- 被控对象:蠕动泵的滴速(单位:滴/分钟)
- 执行器:步进电机的转速(单位:拍/秒,对应相序脉冲频率)
- 反馈量:红外对管测得的实际滴速
我最初写的PID参数是理论值:P=1.0,I=0.1,D=0.0,想着反正系统惯量不大,先跑起来看看。结果一跑,电机疯狂振荡,滴速在目标值上下大幅摆动,有时还冲出安全范围触发报警。用串口把数据导出来画图,就是那种经典的"等幅振荡"曲线。
后来我系统地做了调整:
第一步,把I和D先设0,只留P。从P=0.1开始往上涨,每次跑30秒看响应曲线。发现P=0.3时系统已经能接近目标值但有稳态误差,P=0.5时就开始出现轻微过冲,P=0.8时明显振荡。于是把P定在0.35。
第二步,加I消除稳态误差。I=0.05,观察系统能不能缓慢逼近目标值。发现响应偏慢,调到0.08后稳态误差基本消除,但出现了一点低频波动。又调回0.06,波动消失。
第三步,加D抑制过冲。目标值从30滴突变为60滴时,纯PI会产生约15%的过冲。D=0.2时过冲减小到5%以内,但响应速度略有下降。最后D取0.15,过冲约3%,调节时间约10秒,各项指标都满足需求。
最终参数:
// App/pid.c 最终调参结果 PID_TypeDef g_pid = { .targetValue = 40.0f, // 目标滴速 .kp = 0.35f, // 比例系数 .ki = 0.06f, // 积分系数 .kd = 0.15f, // 微分系数 .outputLimit = 1000.0f, // 输出限幅(步进电机最高脉冲频率) };关于PID有个非常重要但经常被忽略的点:输出限幅。步进电机的脉冲频率不是可以无限大的,超出电机响应能力后,脉冲再多也是白搭,反而会让电机丢步。我把输出限幅设为1000PPS,对应蠕动泵最高流速约每分钟120滴,这个值在医疗输液场景下已经非常高了。
还有一个工程技巧:积分分离。当实际滴速和目标值偏差超过设定范围时(我设为20滴),把积分项清零,防止积分饱和导致系统过冲。这个逻辑用代码写很简单,但对控制效果的提升非常明显。
float PID_Calculate(PID_TypeDef *pid, float currentValue) { float error = pid->targetValue - currentValue; float proportional = pid->kp * error; float integral = pid->integral; if (error < 20.0f) // 积分分离:偏差过大时关闭积分 { integral += pid->ki * error; pid->integral = integral; } else { pid->integral = 0.0f; } float derivative = pid->kd * (error - pid->lastError); pid->lastError = error; float output = proportional + integral + derivative; // 输出限幅 if (output > pid->outputLimit) output = pid->outputLimit; if (output < -pid->outputLimit) output = -pid->outputLimit; return output; }3.4 串口协议设计:自定义帧格式 + 握手应答机制
上位机通信的串口协议我设计成一个简单的自定义帧:帧头 + 设备地址 + 命令字 + 数据长度 + 数据域 + 校验字节。校验采用异或和,够简单也够用。
// 协议帧格式 // | 0xAA | 0x5A | ADDR | CMD | LEN | DATA... | XOR_CHECK |命令分为三类:
- 0x01 查询命令:上位机请求设备状态,设备回复当前滴速、液位状态、报警状态;
- 0x02 控制命令:上位机设置目标滴速、启动/暂停电机;
- 0x03 主动上报:设备在滴速异常、液位过低时主动上报报警事件。
握手流程是:设备上电后每2秒发送一次心跳帧,上位机收到后如果设备地址匹配就回复ACK,之后进入正常工作模式。这个握手机制解决了一个很实际的痛点:如果上位机软件没打开或者串口被占用,设备会一直尝试连接但不会误动作。
有人可能会问,为什么不用Modbus这样现成的协议?因为项目定位是轻量级教学/原型,Modbus虽然规范,但帧结构较复杂,在小资源MCU上跑起来有点大材小用。自己定义协议帧的好处是代码量小、逻辑直观,而且上位机这边我用Python的pyserial写了个简单的调试工具,几行代码就能解析帧并实时显示数据。
4. 仿真工程与实物联调:Proteus仿真能验证什么,不能验证什么
4.1 仿真工程的搭建细节
Proteus仿真工程里我把硬件设计等比例映射进去:STM32F103C8T6模型、LM393比较器模型、红外对管可以用开关+信号发生器模拟、步进电机用仿真模型、OLED用I2C显示模型。你在Proteus里打开工程就能直接跑,程序烧到虚拟MCU里,OLED会显示仿真滴速和状态。
仿真工程的价值主要在逻辑验证阶段。比如我想测试"目标滴速从40变成80时PID如何响应",在实物上需要手动调整吊瓶高度或者更换泵管,在仿真里只需要改一个参数就行。这种"改参秒级验证"的效率优势,在开发初期非常明显。
4.2 仿真和实物最大的差异:信号完整性
但必须坦白讲,仿真不能替代实物验证,最大的原因在于信号完整性。Proteus里的红外对管不会受到电机电源波动的影响,LM393的输出也是理想的数字电平,但在实物上,电机的EMI干扰、电源纹波、导线电感这些东西全都存在。我在实物调试时就遇到过一个非常诡异的现象:电机转动后,滴速检测值从每分钟60掉到了每分钟8,一开始还以为是传感器坏了,后来用示波器一看,原来是电机在地线上产生的纹波干扰了红外接收管的参考电压,导致比较器误触发。
这类问题在仿真里根本复现不出来,所以我的建议是:用仿真验证算法逻辑,用实物验证信号链路。两者配合,才能保证系统的可靠性。
4.3 上位机联调:Python开个可视化的数据监控小工具
串口协议既然定好了,上位机端我也顺手写了个Python小工具,用来实时显示滴速曲线和系统状态。代码很简单,核心逻辑就是串口接收和解析——读取到帧头0xAA 0x5A后按帧格式解析命令,把滴速数据画成折线图。
import serial import matplotlib.pyplot as plt import matplotlib.animation as animation ser = serial.Serial('COM3', 115200, timeout=1) speed_history = [] def parse_frame(data): # 完整帧解析逻辑 # 如果数据帧的校验通过,返回滴速值 return speed def update_plot(frame): raw_data = ser.read(64) if raw_data: speed = parse_frame(raw_data) if speed is not None: speed_history.append(speed) if len(speed_history) > 100: speed_history.pop(0) plt.cla() plt.plot(speed_history) plt.xlabel('Time') plt.ylabel('Droplets/min') plt.title('Infusion Speed Monitor') ani = animation.FuncAnimation(plt.gcf(), update_plot, interval=500) plt.show()这个工具在调PID时作用巨大。你能直观看到系统的响应曲线,比如是否存在过冲、稳态误差有多大、调节时间有多长。如果没有可视化工具,只靠OLED屏上的数字,这类调试基本没法做。
4.4 三套使用路径的配合关系
至此你应该能看出整个项目代码、原理图、仿真三者之间的关系了:
| 路径 | 需要的资源 | 适合场景 | 能验证什么 |
|---|---|---|---|
| 纯仿真 | 仿真工程 + 代码 | 逻辑入门、PID调参、演示讲解 | 算法正确性、逻辑分支、控制效果 |
| 实物原型 | 原理图 + 代码 | 毕设实物、竞赛作品、产品验证 | 电路设计、信号质量、实际控制精度 |
| 二次开发 | 全部代码 + 原理图 | 扩展功能、改成无线方案、升级屏幕等 | 架构扩展性、代码可维护性 |
我之前遇到过不少问"能不能只做仿真不做实物"的同学——能,但你要清楚仿真做出来的东西只能证明"逻辑对了",不能证明"电路对了"。反过来,如果你直接开打PCB却不先跑仿真,一旦PID逻辑有bug,改板子的成本比改代码高得多。所以最稳的路径永远是:仿真先行,实物跟上,两者互相印证。
5. 从初版到升级版的演进:换代过程中踩过的最记忆深刻的几个坑
5.1 初版报警误报率为什么居高不下:来自滴壶挂壁水珠的干扰
上一版的系统主要做监测报警,但初版做完后我拿到真实输液场景测试,发现误报率高到没法用。最典型的一个场景:输液滴壶上方的管壁挂了一颗水珠,它不落下,只是挂在管壁上,红外对管长时间处于遮挡状态,系统就判定为"滴速为0"并报警。但实际上输液是正常的,只是那滴水珠刚好挡住了红外线。
解决思路是区分"长时间遮挡"和"持续滴落"的波形特征。真正的滴落是一个短暂的脉冲——遮挡时间通常在50~200ms左右,然后迅速恢复;挂壁水珠则是持续的遮挡。于是代码里加了一个逻辑:如果遮光持续时间超过500ms,就不算一次滴落,而是标记为"疑似挂壁",进入异常检测分支。
这个改进没有增加任何硬件成本,纯靠软件算法就解决了。机电系统里的很多问题都是这样,先别急着换硬件,想想能不能通过软件逻辑来"看穿"物理世界的噪声。
5.2 升级版新加入的电机控制给电源带来的连锁反应
升级版加了电机之后,新的问题又来了:电机一启动,OLED屏幕亮度就闪烁,串口偶尔还会出现乱码。排查之后发现是电源的问题——电机启动瞬间电流尖峰导致3.3V电压跌落,MCU和OLED虽然不是同一个电源网络,但地线是共用的,地线上的压差直接影响了信号质量。
这个问题的处理我在前面原理图部分已经提到:模拟/数字分离、电机电源独立、加去耦电容。但更重要的是要有排查思路:先用示波器看电源纹波,再用逻辑分析仪看串口信号,逐步缩小范围,最后锁定到地线公共阻抗噪声。分享这个例子的目的是想说,设计时预留好测试点非常重要。我在原理图的电源、地、关键信号上都加了测试焊盘,调试时直接夹示波器探头就行,不用拆线,省了很多时间。
5.3 两个"防呆"设计:堵转保护与报警延迟消抖
升级版另外加了两个"防呆"设计,我觉得对做同类项目的你有参考价值:
第一个是堵转保护。蠕动泵在输液过程中如果输液管被折叠或者患者移动扯到了管路,泵的负载会突然增大,步进电机可能丢步堵转。如果不处理,电机会持续发热,严重时可能损坏电机或管路。我在电机控制模块里加了一个逻辑:如果连续10次PID运算中输出值都超过上限的90%,但实际滴速几乎为0,就判定为堵转,立即停止电机并报警。
// motor_control.c void Motor_CheckStall(void) { static uint8_t highOutputCount = 0; if (g_pid.output > g_pid.outputLimit * 0.9f && g_speedDropletsPerMin < 5) { highOutputCount++; if (highOutputCount > 10) { Motor_StopEmergency(); Alarm_Raise(ALARM_STALL); highOutputCount = 0; } } else { highOutputCount = 0; } }第二个是报警延迟消抖。液位传感器在输液过程中可能因为气泡经过产生短暂的误触发信号,如果一触发就报警会很烦人。我加了一个软件消抖逻辑:液位低信号必须持续超过3秒才触发报警。这个延迟时间既不影响安全性(输液管末段的液位低到报警点后,到完全滴空还有至少几十秒的缓冲),又有效避免了误报。
5.4 从这一版还能往哪些方向继续升级
这套系统现在的开源版本,其实还有很大的扩展空间。如果你想在这个基础上继续做,我建议按下面的优先级考虑:
第一,加无线通信模块。把串口协议改成通过ESP8266/ESP32的WiFi模块或者HC-05蓝牙模块传输,就能实现护士站大屏监控。硬件只需要在USART1上挂一个透传模块,协议几乎不用改。
第二,加多通道支持。当前版本是单通道输液监护,如果要做多通道,主控建议换成STM32F103ZET6(144pin的型号),它有更多的外部中断引脚和定时器通道,靠软件框架的模块化,复制一份App层的逻辑就能扩展一路。
第三,加断电续调。用EEPROM(I2C接口的AT24C02)保存当前的PID参数和运行状态,掉电后重新上电可以自动恢复中断前的输液任务,不用护士重新设置。
第四,加GSM/4G远程报警。对于家庭场景,用SIM800L模块发送短信报警,这是目前很多商用产品不具备但实际需求很强的功能。
这套项目做到这版,已经是"从传感器采集到闭环控制,从本地显示到上位机联动"的全链路闭环了。结构上足够覆盖一个典型的嵌入式系统包含的几乎所有技术要素:中断、定时器、PWM、PID、串口协议、模块化编程。如果你正在做类似的项目,可以直接拿这套代码和原理图当基线,把精力花在你特有的创新点上,比从零开始要高效得多。
最后分享一个我做这类项目多年下来的体会:做嵌入式系统,功能实现只是第一步,把"稳定可靠"四个字刻进代码里才是真正拉开差距的地方。比如滴速检测的抖动过滤、报警的延时消抖、电机的堵转保护,这些初看都是"小事",但正是这些细节决定了别人愿意不愿意把你的作品当成"产品"来用。希望这套开源项目,能帮你少走一些我走过的弯路。