1. 项目概述:为什么我在2024年还要开源一个输液监控系统
先把这个项目说清楚:这是一个基于STM32的智能医疗输液点滴系统,硬件上以STM32F103C8T6为核心,配合红外对管传感器检测滴速、OLED显示屏做实时反馈,稍加扩展还能接上步进电机做自动截流,配套文件包含完整的Keil工程源码、Altium Designer原理图以及Proteus仿真文件。适合正在做课程设计、毕业设计,或者想动手做一个医工交叉小项目的电子爱好者参考。
说实话,输液监控这个方向在嵌入式竞赛和课设里已经不算新鲜了,网上随便一搜能有几十个版本。但大多数开源项目只丢一个“能跑”的代码,原理图缺胳膊少腿,仿真文件干脆没有。我整理这套项目时给自己定了三条规矩:代码注释完整、原理图与实际接线严格对应、仿真文件能直接打开跑出效果。目的就是让拿到资料的人不用猜,照着搭就能复现。
这个系统的核心功能可以拆成三层来理解。感知层负责采集输液过程中的关键物理量,包括滴壶内的液滴速率和液面是否过低;控制层以STM32为主控,对采集信号进行滤波、计算和逻辑判断,实时调整报警状态或执行截流动作;交互层通过OLED显示当前滴速和输液进度,配合蜂鸣器和LED实现声光报警。整套系统的响应时间可以做到百毫秒级别,完全满足病房监护场景的需要。
从适用人群来说,如果你是大三、大四的电子/自动化/生物医学工程专业学生,这套系统覆盖了传感器信号处理、定时器中断、PWM控制、串口通信、低功耗设计这些经典知识点,配合原理图和仿真可以很直观地把课本内容和实物对应起来。如果你是想做医疗电子入门开发的工程师,这套系统的软件架构同样具备参考价值,尤其是滴速检测的防抖逻辑和闭环控制思路,在工业仪表中也是常见套路。
我自己的实际感受是,这类项目的难点从来不在STM32本身,而在于传感器信号怎么处理得干净、电机动作怎么做得平滑、系统在异常情况下怎么表现得更“聪明”。本文就按照从硬件到软件、从静态设计到动态调试的顺序,把这套系统的每个环节都拆开讲清楚。
2. 系统整体设计与方案选型
2.1 为什么要用STM32F103C8T6做主控
选主控是整个项目的第一步。STM32F103C8T6这颗芯片在开源社区里几乎快被“玩烂”了,但这恰恰是它最大的优势。我见过太多人一上来就奔着STM32F407、H743去,结果做输液监控这种小系统,资源浪费一大半,调试难度倒是翻倍。
F103C8T6的核心参数放到这个项目里挨个看:72MHz主频处理滴速计算和滤波算法绰绰有余,20KB RAM跑常规嵌入式逻辑完全不紧张,64KB Flash放完整工程固件加字库还剩不少余量。外设资源上,2个SPI、2个I2C、3个USART、4个16位定时器,刚好覆盖传感器读取、显示驱动、串口调试和电机PWM控制这些需求。更重要的是,这颗芯片最小系统板在国内电商平台十块钱左右就能拿下,资料多到看不完,真要出了诡异问题,搜索引擎基本都能给你答案。
对比一下其他备选方案:51单片机虽然简单,但片上外设太少,做滴速检测需要频繁用中断,主频12MHz跑起来有点勉强,而且没有硬件I2C,驱动OLED全靠软件模拟,代码复杂度和稳定性都不如STM32。ESP32/ESP8266这类WiFi芯片性能确实强,但用在输液监控上有两个问题,一是启动时间偏长,掉电重启到正常工作要一两秒,这种延迟在医疗场景中是不可接受的;二是ADC精度和稳定性不如STM32的12位ADC,模拟量采集容易飘。综合算下来,STM32F103C8T6是最务实的选择。
同系列还有一个经典型号是STM32F103RCT6,Flash更大、引脚更多,但那个封装体积也大了一圈,做小型化设备不如C8T6来得紧凑。如果你后续要加WiFi模块做物联网上报,C8T6的引脚可能有点紧张,那时候再考虑换RCT6或者加一颗ESP8266做协处理器都来得及。
2.2 传感器选型:滴速检测用红外对管,液位检测用电容接近
滴速检测是整个系统里最核心的传感环节,方案上主要有红外对射、红外反射和摄像头视觉识别三条路线。
红外对射是最经典的做法:在滴壶两侧分别放置红外发射管和接收管,液滴下落经过光束时,接收管收到的光强会瞬间减弱,输出一个脉冲信号。我在这个项目里用的就是这种方式,具体型号是ST188反射式红外传感器,其实它本来是反射式的,但用在滴壶场景下对射安装效果一样好,而且价格便宜、采购方便。
红外反射则是把发射管和接收管放在同一侧,利用液滴表面的反射来检测,好处是安装空间小,但容易受环境光干扰和环境背景反射影响,稳定性不如对射式。视觉识别方案精度最高,可以同时检测滴速、液滴大小和气泡,但需要摄像头模组加图像处理,成本和研发周期都不是这个项目能承受的。
实际选型还考虑过一个更“高级”的方案——电容式液滴检测。原理是液滴经过电极时改变介电常数,从而改变电容值。这个方案的优点是完全非接触、无光路污染,但缺点是信号变化量非常小(皮法级别),需要专用的电容检测芯片(如FDC2214)或高精度ADC才能分辨,成本直接翻好几倍。对于教学演示级项目来说,得不偿失。
液位检测我用了电容式接近传感器,具体是LJ12A3-4-Z/BX这类NPN常开型。当液面低于传感器安装位置时,输出高电平,表示需要报警;液面正常时输出低电平。选它而不是浮球开关的原因很简单:浮球开关是机械结构,存在卡滞失效的风险,而且需要接触液体,有污染隐患,电容式方案完全避免了这两个问题。
2.3 执行机构与显示方案:蠕动泵控制滴速,OLED做信息窗口
输液速度的控制有两种思路,一种是靠夹紧装置手动调节软管流量,另一种是用电机驱动泵头主动推进。前者结构简单但只能单向调节,无法应对瓶内液面下降导致的流速变化;后者可以实现闭环控制,但机械结构复杂度上升。我选的是后者——步进电机驱动蠕动泵。
蠕动泵的工作原理是:电机带动多个滚轮依次碾压硅胶软管,通过挤压推动液体前进。这种结构有一个天然的优势——液体只在软管内流动,不接触泵体任何金属部件,完全避免了交叉污染问题,这在医疗场景中是底线要求。步进电机选用的是28BYJ-48加上ULN2003驱动板,这几乎是开源社区最常见的组合,虽然是五线四相减速电机,精度对于蠕动泵这个应用场景来说绰绰有余。控制滴速时,通过调节步进电机的脉冲频率改变泵速,从而控制单位时间内输送的液量。
显示方面选了0.96寸I2C接口OLED屏(SSD1306驱动)。选它的理由有两个:一是I2C只需要两根线(SDA、SCL),接线非常省事,在空间受限的设备里是个巨大优势;二是OLED自发光,对比度高,在病房光线环境下依然能清晰读数。如果你手上只有SPI接口的OLED,代码里加一个简单的软件SPI适配层也能用,不影响整体逻辑。
另外我还在系统里预留了串口通信接口,方便通过USB转TTL模块连接PC上位机,实时查看滴速数据和报警记录。调试阶段这个接口帮了大忙,数据曲线直接拉出来看,比对着屏幕猜爽多了。
3. 硬件设计详解:原理图思路与PCB注意事项
3.1 原理图设计架构拆解
整套硬件的原理图按功能可以切分为六个模块:最小系统、电源电路、滴速检测电路、液位检测电路、电机驱动电路、交互显示电路。下面逐个说明设计思路。
最小系统部分,STM32F103C8T6需要8MHz晶振作为HSE时钟源,两个20pF负载电容接地,NRST引脚接10K上拉电阻加0.1uF去耦电容到地。BOOT0和BOOT1引脚分别通过10K电阻下拉到地,确保从主Flash启动。VDDA引脚需要单独接一个1uF钽电容和一个0.1uF陶瓷电容做模拟电源滤波,这点很多初学者容易忽略——VDDA不干净会直接影响ADC采样精度,后面做传感器电压读取时会遇到数据跳动的问题。
电源电路上,系统输入用5V直流,LDO稳压芯片选AMS1117-3.3,纹波控制在1%以内。输入输出各接一个10uF电解电容加0.1uF陶瓷电容组成滤波网络。AMS1117的最大输出电流是1A,整个系统实测峰值功耗在500mA左右,留了充足余量。如果你计划外接的OLED模块尺寸较大或者蜂鸣器功率较高,建议换成ME6211这类低 dropout 的 LDO,压差只有100mV左右,更好用。
滴速检测电路是硬件设计的重点。红外发射管的阳极通过220欧限流电阻接5V电源,阴极接地,限流电阻的取值决定了发射管电流,我实测220欧在5V供电下电流约为十几毫安,属于安全且灵敏的工作区间。接收管部分用了一个NE555芯片构成施密特触发器整形电路,把光信号变化转化为干净的数字脉冲。实际调试中发现,如果直接接单片机IO口,液滴经过光路时信号会有几十毫秒的渐变过程,直接计数会出现误触发;加了施密特整形之后,一个液滴对应一个陡峭的下降沿,可靠性大幅提升。
液位检测电路中,LJ12A3的输出是NPN三极管开漏,信号线上需要接一个10K上拉电阻到3.3V,这样输出高电平就是稳定的3.3V,不会出现悬空状态。信号进STM32之前串了一个1K电阻,起限流保护作用,防止传感器故障时高压直接冲击主控引脚。
电机驱动部分用的是ULN2003达林顿管阵列芯片。这个芯片内部集成了7路达林顿管,每路最大能承受500mA电流,直接驱动小功率步进电机没问题。需要注意的是,ULN2003属于集电极开路输出,线圈另一端接5V电源,输入侧接STM32引脚时要加上拉电阻避免干扰。我在每个输出通道上并联了一个1N4007续流二极管,吸收电机线圈断电瞬间产生的反向电动势,防止高压尖峰打坏驱动芯片和单片机。这个细节在仿真文件里看不出来,但实际硬件上非常关键。
3.2 STM32原理图引脚分配与注意事项
下面是完整的引脚分配表,照着这个接线基本不会错:
| 功能模块 | STM32引脚 | 外设配置 | 说明 |
|---|---|---|---|
| 滴速检测输入 | PA0 | EXTI0外部中断 | 滴速脉冲下降沿触发 |
| 液位检测输入 | PA1 | GPIO输入上拉 | 有液位时低电平报警 |
| OLED SDA | PB7 | I2C1数据线 | 软件I2C或硬件I2C均可 |
| OLED SCL | PB6 | I2C1时钟线 | 注意上拉电阻4.7K |
| 蜂鸣器控制 | PA8 | GPIO推挽输出 | 低电平触发有源蜂鸣器 |
| 报警LED | PA9 | GPIO推挽输出 | 正常时熄灭报警时点亮 |
| 步进电机IN1~IN4 | PB0-PB3 | GPIO推挽输出 | 接ULN2003输入侧 |
| 串口TX | PA9 | USART1_TX | 调试输出 |
| 串口RX | PA10 | USART1_RX | 上位机命令输入 |
| 滴速校准按键 | PB12 | GPIO输入上拉 | 短按进入校准模式 |
| 启动模式 | BOOT0 | 10K下拉 | 从Flash启动 |
| 复位 | NRST | 10K上拉+0.1uF电容 | RC复位电路 |
画原理图时有几个容易踩的坑必须提醒一下。第一,STM32F103的PB3、PB4、PA13、PA14、PA15这几个引脚默认复用为JTAG调试口,直接当普通GPIO用必须先关闭JTAG功能,否则输出状态会异常。我建议是除非引脚真不够用了,否则避免在这几个脚上接关键外设,调试不方便。第二,I2C的SDA和SCL一定要接上拉电阻,4.7K或10K都可以,不接的话通信不稳定,经常偶尔通偶尔不通。第三,如果用的是有源蜂鸣器,驱动电流一般20mA左右,STM32引脚推挽输出能力足够直接驱动,但如果换成无源蜂鸣器需要PWM频率驱动,引脚要选带定时器输出复用的通道。
3.3 PCB布局与布线心得
硬件布局上花了不少心思。整套系统我用的是两层板,尺寸控制在10cm×10cm以内,成本上两层板在嘉立创打样只要几十块钱,纯学习用途完全够了。
布局的时候遵循了一个重要原则:模拟信号、数字信号、电源三区分开,按功能模块分区布局。滴速检测电路和液位检测电路属于模拟信号区域,远离电机驱动的PWM走线区,避免电平翻转时产生的高频噪声耦合到模拟信号上。电源部分集中在板子一角,LDO的散热焊盘下方要打过孔阵列,帮助热量传导到背面地层。地和电源的走线宽度不低于30mil,主干电源线甚至走到50mil,降低走线电阻和压降。
滴速检测的红外对管安装位置是个设计重点。它不能离主控板太远,否则信号线过长容易引入干扰;也不能离滴壶太近,机械上不好固定。我的做法是在主板上留一个2pin的排针接口,通过短线连接外置的红外对管模块,模块上自带安装孔,用扎带固定在滴壶两侧。这样既保证了信号线长度在15cm以内,又兼顾了机械安装的灵活性。
地线处理上,整个系统采用单点接地策略,模拟地和数字地在主电源入口处用0欧电阻连接。这样能有效避免数字电路的高频噪声通过地线回流污染模拟部分。实际测试时,没做单点接地时滴速检测会偶尔多计脉冲,做了之后数据稳定了不少。
4. 软件设计与核心代码实现
4.1 软件架构:前后台系统+状态机
软件部分没有上RTOS,而是用了一个非常经典的前后台架构:主循环处理按键扫描、显示刷新、状态判断等任务,定时器中断负责滴速计数和时间基准。对这样一个功能明确、逻辑不复杂的系统来说,RTOS反而是过剩的,反而引入了任务调度开销和调试复杂度。
系统整体分为四个状态:正常输液状态、滴速异常报警状态、液位低报警状态、校准模式。状态切换的逻辑用switch-case实现,主循环每次执行时根据传感器输入和当前状态决定是否跳转。这种状态机架构的好处是逻辑清晰,新增状态也容易扩展。
滴速检测模块的工作流程是:液滴经过红外光路→接收管信号变化→施密特整形输出下降沿→STM32外部中断触发→中断服务函数中记下当前时间戳→主循环中根据时间差计算滴速。为了消除抖动,我加了一个软件消抖策略:连续两次下降沿的时间间隔小于150ms的视为无效信号,直接丢弃。这是从实际调试中总结出来的经验——输液管轻微晃动时,液滴边缘经过光路会产生毛刺,不消抖的话计数会莫名多一截。
4.2 滴速检测代码:外部中断+定时器时间戳
滴速检测的代码是整个系统的核心,放一段关键实现:
// 滴速检测相关变量 volatile uint32_t g_last_drop_time = 0; // 上一次滴落时间戳(ms) volatile uint32_t g_drop_interval = 0; // 当前滴落间隔(ms) // 外部中断服务函数 - 滴速检测 void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { uint32_t current_time = HAL_GetTick(); uint32_t delta = current_time - g_last_drop_time; // 软件消抖:间隔小于150ms视为抖动信号 if (delta > 150) { g_drop_interval = delta; g_drop_count++; } g_last_drop_time = current_time; EXTI_ClearITPendingBit(EXTI_Line0); } } // 计算滴速(滴/分钟) uint16_t CalculateDropRate(void) { if (g_drop_interval == 0) { return 0; // 没有检测到滴落 } uint16_t rate = 60000 / g_drop_interval; // 对极端值做限幅处理 if (rate > 150) rate = 150; return rate; }HAL_GetTick()是基于SysTick的毫秒计数,用它来计算滴落间隔非常方便。计算滴速时直接用60000除以间隔毫秒数,得到每分钟滴数。需要说明的是,不是每个输液场景的滴速都是整数,实际临床中一个输液器的滴系数大约是每毫升15~20滴,这个参数在后续扩展输液时间估算功能时会用到。
中断服务函数里只做最轻量级的操作——记录时间和计数,所有计算都放到主循环里完成。这是嵌入式开发的黄金法则:中断里不做事关逻辑判断的重操作,防止阻塞其他更高优先级的中断响应。
4.3 液位检测与报警逻辑代码
液位检测逻辑相对简单,但有一点必须注意:不能直接拿GPIO的电平状态做瞬间判断输出报警,必须有去抖延时。原因是输液架晃动或气泡经过时,液位传感器的输出电平可能产生几十毫秒的误变化,直接报警会让护士烦死。
// 液位检测去抖逻辑 #define LIQUID_LEVEL_LOW 0 // 低电平表示液位过低 #define DEBOUNCE_TIME_MS 500 // 去抖时间500ms uint8_t CheckLiquidLevel(void) { static uint8_t low_level_count = 0; static uint8_t is_alerted = 0; GPIO_PinState level = HAL_GPIO_ReadPin(LIQUID_GPIO_Port, LIQUID_Pin); if (level == LIQUID_LEVEL_LOW) { if (low_level_count < DEBOUNCE_TIME_MS / 10) { low_level_count++; } else { is_alerted = 1; // 持续低电平超过500ms,确认液位过低 } } else { low_level_count = 0; is_alerted = 0; } return is_alerted; }这里用了计数方式代替延时函数,好处是不阻塞主循环。每次主循环周期是10ms,计数50次即500ms,达到时间阈值后才会确认低液位报警。这种“计数消抖”的思路在很多工业现场设备中都很常见,比直接延时优雅得多。
报警逻辑上分了两个等级:滴速异常和液位过低。滴速异常又分为滴速过快(>60滴/分)和滴速过慢(<20滴/分),不同异常类型对应不同的报警节奏。蜂鸣器驱动采用PWM输出,正常时静默,异常时以不同频率鸣叫。LED灯在正常状态闪烁指示系统运行,报警时保持常亮,方便医护人员快速定位到问题设备。
4.4 电机控制代码:脉冲速度映射与限位保护
步进电机的控制核心是脉冲频率和滴速之间的映射关系。整个蠕动泵机械系统有一个减速比和泵头容积,实际调试发现电机每转动一周大约排出2.5mL液体,对应约40滴(按每毫升16滴计算)。因此要控制滴速为X滴/分,电机需要的转速是X/40转/分,而28BYJ-48电机在四相八拍模式下转动一圈需要4096个脉冲,于是脉冲频率就是X×4096/40。
// 根据目标滴速计算电机脉冲频率 uint16_t CalcMotorSpeed(uint16_t target_drop_rate) { // 滴速 -> 电机转速(转/分) = 滴速/40 // 电机转速 -> 脉冲频率(Hz) = 转速*4096/60 // 简化后:pulse_freq = target_rate * 4096 / 40 / 60 uint32_t pulse_freq = (uint32_t)target_drop_rate * 4096 / 40 / 60; // 限幅保护:频率范围 10Hz ~ 2000Hz if (pulse_freq > 2000) pulse_freq = 2000; if (pulse_freq < 10) pulse_freq = 10; return (uint16_t)pulse_freq; }这里用定时器1的PWM输出模式驱动步进电机。将PWM频率设为上述计算值,占空比设为50%,输出引脚连接到ULN2003输入端,通过改变PWM频率即可改变电机转速。这种做法的好处是不占用CPU处理步进脉冲的时序,PWM由硬件自动产生,CPU只需要定期更新频率值。
实际测试发现,如果直接把计算出的脉冲频率设为目标值,电机启动瞬间扭矩不足,会出现失步现象,也就是电机没有跟上指令转速,滴速实际值低于设定值。解决办法是加了一个加速斜坡:启动时先从最低频率开始,每100ms增加一个步进,经过1秒左右达到目标频率。类似的概念可以参考工业伺服驱动器的S曲线加减速,只是我这里用最简单线性斜坡而已。
4.5 主循环与状态机完整流程
主循环的逻辑结构如下:
int main(void) { // 初始化外设 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_TIM1_Init(); OLED_Init(); // 显示启动界面 OLED_ShowString(0, 0, "Infusion System"); OLED_ShowString(0, 2, "Starting..."); HAL_Delay(1000); uint16_t target_rate = 40; // 默认目标滴速 40滴/分 uint16_t current_rate = 0; while (1) { // 1. 获取当前滴速 current_rate = CalculateDropRate(); // 2. 检测校准按键 if (KEY_Scan() == KEY_PRESS) { EnterCalibrationMode(&target_rate); } // 3. 液位检测 uint8_t low_level = CheckLiquidLevel(); // 4. 状态机逻辑 if (low_level) { // 液位过低:停止电机、触发报警 Motor_Stop(); Alert_Set(ALERT_LOW_LIQUID); OLED_ShowString(0, 3, "Liquid Low! "); } else if (current_rate > target_rate + 5 || current_rate < target_rate - 5) { // 滴速偏差超过5滴/分:调整电机转速 Motor_AdjustSpeed(target_rate); Alert_Set(ALERT_RATE_ABNORMAL); } else { // 正常范围:电机保持,关报警 Motor_Stop(); Alert_Clear(); } // 5. 刷新OLED显示 OLED_ShowRate(current_rate); OLED_ShowTargetRate(target_rate); // 6. 处理串口调试命令 UART_ProcessCommand(); HAL_Delay(10); // 10ms主循环周期 } }这套代码逻辑上覆盖了系统的主要工作场景。需要说明的是,在滴速正常时我选择了让电机停止,而不是保持低速持续泵送。原因在于蠕动泵的滚轮持续压管子会造成软管局部磨损,临床场景中更常见的是依靠重力输液,电机仅在需要加速补充时短暂工作。当然,如果你的应用场景要求持续匀速输液,可以把Motor_Stop()替换为Motor_Run(target_rate),逻辑上一样成立。
4.6 步进电机28BYJ-48驱动时序代码(完整实现)
28BYJ-48步进电机是5线4相电机,内部带有1:64减速齿轮箱。驱动时序上采用四相八拍方式,即A-AB-B-BC-C-CD-D-DA的顺序循环通电。具体代码如下:
// 四相八拍控制序列 const uint8_t motor_step_table[8] = { 0x01, // A相导通:0001 0x03, // AB相导通:0011 0x02, // B相导通:0010 0x06, // BC相导通:0110 0x04, // C相导通:0100 0x0C, // CD相导通:1100 0x08, // D相导通:1000 0x09 // DA相导通:1001 }; // 电机驱动引脚宏定义 #define MOTOR_IN1_PIN GPIO_PIN_0 #define MOTOR_IN2_PIN GPIO_PIN_1 #define MOTOR_IN3_PIN GPIO_PIN_2 #define MOTOR_IN4_PIN GPIO_PIN_3 #define MOTOR_PORT GPIOB // 一次脉冲推进一个步进 void Motor_Step(uint8_t step_index) { uint8_t value = motor_step_table[step_index]; HAL_GPIO_WritePin(MOTOR_PORT, MOTOR_IN1_PIN, (value & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_PORT, MOTOR_IN2_PIN, (value & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_PORT, MOTOR_IN3_PIN, (value & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_PORT, MOTOR_IN4_PIN, (value & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); } // 电机正转指定步数 void Motor_RunSteps(uint32_t steps) { for (uint32_t i = 0; i < steps; i++) { uint8_t step_index = (i % 8); Motor_Step(step_index); delay_us(600); // 步进间隔600us,对应约244Hz脉冲频率 // 注意:实际使用中用PWM硬件输出效果更好,延时方式仅用于调试验证 } }这里展示的是最简单的GPIO翻转方式驱动电机,在项目早期调试阶段用来验证电机转向和机械装配是否正确非常有用。但正式运行阶段,建议改用定时器PWM输出模式,避免CPU被电机控制占用过多时间。上文中CalcMotorSpeed函数就是为PWM模式准备的计算接口,配合STM32定时器的PWM输出功能,可以做到电机速度的精确控制而不占用主循环时间。
5. 仿真设计与实验验证
5.1 Proteus仿真环境搭建
这套项目的仿真文件是基于Proteus 8.9及以上版本构建的。在开始之前,先要确认电脑上安装了正确的STM32库文件,如果打开仿真文件时提示找不到STM32F103C8T6元件,通常是库文件缺失导致的,需要在Proteus的Library Manager里手动添加STM32库。
仿真的硬件连接和真实原理图完全一致。我在Proteus里放的元件清单包括:STM32F103C8T6单片机、LM016L液晶显示屏(这个项目用LM016L模拟OLED,因为Proteus的OLED模型不太好用)、电阻若干、LED指示灯、按键、滑动变阻器(模拟传感器信号变化)、直流电机模型(模拟蠕动泵)。
OLED在Proteus里没有现成模型,我是用LM016L LCD显示屏代替显示功能。LM016L是1602字符液晶,接口和OLED完全不同,所以仿真代码和真实代码之间做了微小的映射:真实的SSD1306 OLED驱动函数被替换为HD44780 LCD驱动函数。仿真文件里会单独提供一份适配LCD的工程代码,和真实硬件的工程代码分开存放,使用时注意区分不会混淆。
5.2 仿真验证方法与实验数据
仿真环境下,我用滑动变阻器模拟红外对管的输出信号变化,调节阻值模拟液滴经过光路时的电压波动。这样可以在没有真实硬件的条件下,验证中断触发、滴速计算、报警逻辑这几条核心代码路径是否正确。
验证方案分为四个测试用例:
| 测试用例 | 操作步骤 | 预期结果 | 仿真实测 |
|---|---|---|---|
| 滴速正常 | 设定信号间隔1500ms | 显示滴速40滴/分,无报警 | 显示40滴/分,状态正常 |
| 滴速过快 | 设定信号间隔600ms | 显示滴速100滴/分,触发滴速过快报警 | 显示100滴/分,蜂鸣器和LED动作 |
| 滴速过慢 | 设定信号间隔4000ms | 显示滴速15滴/分,触发滴速过慢报警 | 显示15滴/分,报警触发 |
| 液位过低 | 液位信号强制拉低超过500ms | 触发液位过低报警,电机停止 | 报警和电机停止均正常 |
从仿真结果来看,四个关键测试用例全部通过,逻辑判断和预期一致。仿真环境的价值在于可以快速验证代码逻辑的正确性,尤其适合在硬件还没到货的时候提前做软件开发。它的局限也是很明显的:仿真无法模拟真实传感器的信号噪声、电机负载变化、电源纹波等物理效应,所以仿真跑通了不等于硬件就能稳定运行,中间还有不少调试工作要做。
5.3 实物调试中的关键校准
实物调试和仿真完全是两回事,其中最重要的一个步骤是滴速检测的阈值校准。红外对管安装好之后,需要用示波器或万用表测量接收管输出的静态电压,然后调整发射管限流电阻,让无液滴遮挡时输出电压在3.0V左右,液滴经过时电压跌落到1.5V以下。NE555施密特整形电路的触发电平由外围电阻决定,实测我的配置大约是2.2V,这样高低电平都有充足的电平裕量。
另一个关键校准是滴速误差标定。用一个标准的100mL量筒接水,电机持续运行,人工数1分钟的滴数,和系统显示的数值做对比。由于实际输液器的滴系数在生产时有公差,标签上的“20滴/ml”只能作为参考,建议校准后把实际滴系数存到Flash里,断电不丢失。这套系统我实测校准后的滴速误差在±2滴/分以内。
6. 常见问题与排查技巧实录
6.1 问题速查表
我在调试这套系统时踩了不少坑,列成表格分享出来,基本覆盖了硬件和软件两个层面最常见的故障:
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 滴速检测完全没有脉冲 | 红外发射管方向接反 | 用万用表测发射管两端电压 | 更换方向,注意长脚正极 |
| 滴速计数跳变异常,忽高忽低 | 环境光干扰 | 观察波形是否出现毛刺 | 给红外对管加遮光罩 |
| 滴速偶尔多计一次 | 液滴边缘经过光路产生二次触发 | 用示波器查看脉冲波形 | 延长软件消抖时间至150ms |
| OLED无显示 | I2C地址错误 | 扫描I2C地址 | SSD1306默认地址0x3C或0x3D |
| 电机启动不转但嗡嗡响 | 步进脉冲频率过高失步 | 降低启动频率 | 启用加速斜坡 |
| 电机反转 | 四相通电顺序反了 | 改变控制序列顺序 | 翻转步进表 |
| 液位传感器误报警 | 电源纹波干扰信号 | 测量传感器输出波形 | 增加滤波电容 |
| 上电后系统反复重启 | 电源电压跌落 | 测量5V和3.3V纹波 | 加大电源电容 |
| 串口调试无数据 | 波特率不匹配 | 检查串口配置 | 统一为115200-8-N-1 |
6.2 滴速检测抗干扰的独家经验
滴速检测这块的干扰问题,是整套系统里花时间最多的。除了前面提到的软件消抖,我还总结了几条硬件层面的抗干扰经验。
第一,红外发射管和接收管必须固定牢靠。我一开始用杜邦线飞线测试,手一碰就出现误计数,后来改成硬连接并加了3D打印的固定支架,稳定性立刻上来了。实际病房环境里有输液泵的振动、患者翻身引起的管路晃动,如果传感器固定不牢,机械位移会造成光路偏转,产生大量虚假信号。
第二,滴壶本身的透光性和安装位置会影响检测成功率。不同厂家的输液器滴壶材质、颜色、透明度差异很大,有的发黄有的偏蓝。实测下来,透明或淡黄色的滴壶检测成功率最高,深色的滴壶对红外光的吸收严重,信号余量不足。如果遇到这种滴壶,需要适当增大发射管电流或改用更高功率的红外LED。
第三,环境光干扰是隐形杀手。病房里阳光直射、荧光灯频闪、患者床头灯都会干扰接收管。给传感器模组加遮光罩是最有效的方案,我用黑色热缩管套住对管区域,效果立竿见影。软件层面的对策是采样时间窗避开50Hz工频闪烁周期,但这个实现起来比较复杂,硬件解决更划算。
6.3 电机失步与过冲的排查思路
步进电机失步的排查需要区分机械和电气两类原因。机械方面,蠕动泵的滚轮与硅胶管之间压得太紧,摩擦力过大会导致电机带不动,表现为启动时“咔咔”响但不转。解决方法是调整滚轮压紧度,让软管刚好被压到60%左右变形量为佳,太紧了不但阻力大,还会加速软管老化。电气方面,驱动电流不足往往是因为ULN2003的输入上拉电阻太大,导致达林顿管没有完全饱和导通,线圈电压偏低。将上拉电阻从10K降到4.7K通常能解决问题。
电机过冲是另一个常见问题。停止瞬间如果直接切断PWM,电机由于惯性会多走几步,导致液体泵送量偏大。实际测试中减少过冲的有效方法是在停止前先降频到低速运行200ms左右再停机,相当于给电机一个减速缓冲期。这和变频器控制三相电机时的减速时间设置是一个道理。
6.4 Keil编译错误与运行异常排查笔记
配套代码是标准Keil MDK工程,推荐使用Keil 5.23以上版本打开。编译过程中最常见的三个错误提示,我在这里逐一说明:
第一种是“error: no stm32 target found! if your product embedsdebug authentication”。这个报错本质上是烧录器没找到芯片。排查步骤是先确认开发板电源灯是否亮,再确认ST-Link的SWD四线(SWDIO、SWCLK、GND、3.3V)连接是否正确,最后检查ST-Link驱动是否安装好,设备管理器里是否识别到“ST-Link Debug”设备。如果都不行,按住开发板复位键的同时点击烧录,松手的时机是烧录进度条刚开始走的那一刻,这个方法能解决很多连接不上芯片的问题。
第二种是“Error: L6218E: Undefined symbol”,说明编译时某个函数声明了但没定义。通常是因为某个.c文件没有被添加到工程里。在Keil左侧项目管理器中双击Target组名,把缺失的源文件Add进去再重新编译即可。
第三种是“Warning: #223-D: function ... declared implicitly”,这通常是因为头文件没有包含或函数原型的头文件路径没配好。在Options for Target的C/C++选项卡里,把Include Paths加上对应的头文件目录。
如果烧录后程序不运行,先检查BOOT0跳线帽是否在低电平位置。很多开发板出厂时BOOT0默认高电平,芯片会进入Bootloader模式而不是执行Flash中的应用代码。这个老生常谈的问题,每隔一段时间就会让一个新手多浪费个把小时。
7. 扩展方向与个人经验总结
项目做到这里,核心功能已经完整了。但作为开源项目,我觉得有必要聊聊后续可以往哪些方向扩展,给拿到这套资料的朋友指几个明确的路子。
第一个方向是无线化改造。目前的系统还是有线的,在病房里布线不美观也不方便。在现有串口接口上接一个HC-05蓝牙模块,就能把滴速数据发送到手机APP;如果加ESP8266或ESP32模块,可以走WiFi上报到护士站后台,那基本上就是一个简易的物联网输液监控系统雏形。电源方面可以加一节18650锂电池和充电管理芯片(如TP4056),实现便携使用。
第二个方向是数据记录与趋势分析。在STM32内部扩展一片AT24C02或W25Q64存储芯片,定期把滴速数据存下来,通过串口导出到PC端做曲线分析,可以看到输液全程的滴速变化趋势。临床上这个数据其实很有价值——某些药物输注速度变化趋势能反映患者是否出现不良反应的预兆。这部分属于嵌入式加数据处理的交叉方向,值得研究。
第三个方向是从单机向组网过渡。用RS485总线把多个输液监控节点挂到同一条总线上,每个节点分配地址,护士站用一个主控设备集中显示所有床位状态。RS485是工业现场最成熟可靠的通信方案,成本也不高,用作病房组网非常合适。如果你愿意接触无线组网,LoRa方案也是一个选项,一个网关可以覆盖一整层病房,就是成本稍微高了一些。
从我个人的体会来说,做这个项目最大的收获不是学会了某个具体型号的单片机,而是建立了一套完整的“传感器信号采集→嵌入式处理→执行机构控制→人机交互”的系统思维。这个思维模型放到任何IoT设备上都能复用。在具体实践中我还学到,医疗电子项目里安全可靠永远是第一位的。软件上要做多重判断,硬件上要留冗余,报警逻辑宁可误报也不能漏报——这个理念在后续做任何安全相关产品时都值得坚持。
最后分享一个小技巧:如果你是用Proteus仿真调试这套系统,建议调整好传感器信号发生器的频率之后,再配合“Debug→Digital Signal Trace”功能观察PA0引脚上的实际波形和中断触发情况。很多逻辑上的问题通过波形一眼就能看出来,比盯着代码凭空猜测高效得多。硬件调试阶段也一样,有条件的话把示波器探笔挂在红外接收管的输出端和MCU的引脚端各看一眼,你会发现信号路径上每一级的波形差异,这种经验是仿真永远给不了你的。
整套代码、原理图和仿真文件都做了开源处理,工程结构上分了Hardware、Core、App、Doc四个目录。任何一个文件都不是摆设,希望你能从里面读到设计思路而不只是能用就行。有问题欢迎在评论区交流,我看到了会尽量回复。