这几年做嵌入式项目,后台私信里问得最多的除了怎么入门,就是"有没有一个能完整写完简历上的项目"。有些朋友做一个温湿度监测就敢往作品集里放,不是不行,而是面试官早看腻了。真正有区分度的,往往是那种"能感知、会决策、有执行"的闭环系统。
这次开源的项目,就是围绕这个思路做的:一个基于STM32智能输液监护调控系统升级版。它不只是一块屏幕显示个滴速,而是把"检测—判断—执行—报警"整条链路全部跑通。主控用的 STM32F103C8T6,配红外滴速检测、液位检测、蠕动泵调节、OLED 显示和声光报警,配套资料包含完整可编译的代码、原理图和 Proteus 仿真文件。如果你正在找综合性的STM32实战项目,或者想把手头的传感器和执行机构串成一个有业务逻辑的系统,这份工程值得你花一个周末从头过一遍。
1. 为什么老式输液方案必须加"调控":从监护到闭环的升级逻辑
我见过不少开源的输液监护项目,绝大多数只做了"监测端":红外管夹在滴壶上,测出滴速,超限就蜂鸣报警,完事。这类方案从教学演示角度没错,但从实际使用逻辑上有个明显的断档——报警之后呢?如果护士正在忙别的患者,或者夜间陪护的人睡着了,报警声只能制造焦虑,并不能改变滴速过快或过慢的事实。
升级版的核心思路,是把"监"和"控"合并成一条闭环:
- 感知层:红外对管检测液滴下落,产生脉冲;电容/光电液位传感器判断瓶内液位是否低于安全线。
- 决策层:STM32 根据脉冲间隔计算实时滴速,与预设目标滴速做比较,跑一个增量式PID算法,得出执行量。
- 执行层:蠕动泵根据PID输出调节输液管道的挤压速度,从而改变实际流量。
- 人机层:OLED 显示目标滴速、实测滴速、累计输液量和系统状态;按键设定参数;超限或液位低时蜂鸣器与LED 同时报警。
这套结构放到任何一本控制类教材里,就是标准的"测控系统"。但从工程落地角度,它逼着你处理一堆教材上不会写的事:红外对管怎么滤除抖动?滴定脉冲怎么在定时器中断里准确计时?蠕动泵的步进电机怎么在PID输出和实际转速之间做映射?这些细节恰恰是面试官追问的深水区,也是这份开源项目最值得看的部分。
有朋友可能会问:用注射泵不是更精确吗?对,医用注射泵确实精度更高,但成本和结构复杂度也高一个量级。蠕动泵配合滴速闭环,在工程训练场景下已经能很好地体现"反馈调节"的完整逻辑,而且执行机构换成一个铁芯夹管阀或舵机也能跑同一套控制代码,硬件适应性很好。这也是我最终保留蠕动泵方案的原因。
2. 硬件选型与原理图设计:主控、滴速检测、蠕动泵的取舍
这版硬件设计的核心原则就一句话:用最常用的器件,搭出最能讲清楚控制链路的系统。原理图我分成了主控最小系统、滴速检测、液位检测、蠕动泵驱动、人机交互和电源管理几个子模块,分开看每个都不复杂,合起来就是一个完整产品雏形。
2.1 主控为什么还是 STM32F103C8T6
虽然现在国产替代芯片一大堆,APM32、GD32可以直接兼容,但开源项目选型我还是坚持用 STM32F103C8T6。原因很实际:资料密度最高,遇到问题最容易搜到答案。这颗芯片具备72MHz主频、20KB RAM、64KB Flash,跑滴速检测+PID+OLED刷新完全够用。最重要的是它有丰富的定时器资源,后面测脉冲间隔和产生PWM都要靠这个。
最小系统的设计没有什么玄学,但有几个地方我要特别提醒:
- 8MHz 晶振的负载电容不要照抄。很多原理图直接放两个20pF,其实应该按晶振规格书的负载电容算,C_load 通常是 10~22pF,两个谐振电容大概取 2 倍 C_load,再减去杂散电容。用 8MHz 晶振配合 10~22pF 对地电容基本都不会出问题。可别小看这几个电容,选不对轻则起振慢,重则干脆不起振。
- BOOT0 引脚要留跳线或电阻。虽然大部分时候是 Boot0=0 从 Flash 启动,但只有烧录过一次之后才这么说,新片子在烧录调试阶段可能会需要擦除重来,BOOT0 拉高进串口 ISP 是个保底手段。
- NRST 复位电路和 3.3V 去耦电容不要省。去耦我用了 4 个 100nF 分别贴在 VDD 引脚附近,再加一个 10uF 钽电容做整体滤波,对 ADC 采样的稳定性影响很大。
STM32F103C8T6 的引脚分配我没有在原理图里写死全部,但建议做个表格固化下来,方便后面写代码时查:
| 功能模块 | 引脚 | 说明 |
|---|---|---|
| 滴速检测输入 | PA0 | 外部中断,上升沿捕获 |
| 蠕动泵步进电机脉冲 | PA1 | 定时器PWM输出或GPIO翻转 |
| 步进电机方向 | PA2 | 高低电平控制正反转 |
| OLED SCL | PB6 | I2C1 时钟 |
| OLED SDA | PB7 | I2C1 数据 |
| 液位传感器 | PA4 | ADC 采样或 GPIO 读取 |
| 蜂鸣器 | PA5 | 有源蜂鸣器,高电平驱动 |
| 按键1/2/3 | PB0/PB1/PB10 | 目标参数调节 |
2.2 滴速检测:红外对管是性价比最高的方案
红外对管检测液滴是最成熟的做法。对管分对射式和反射式两种。对射式需要液滴从红外发射管和接收管之间穿过,溶液滴落时会遮挡红外光线,接收端输出电平跳变。实际做的时候用槽型光耦(比如ITR9606)更方便,机械结构固定得好,光路不容易受环境干扰。
电路上核心是一个比较器整形电路。红外接收管输出的模拟信号变化比较缓慢,不可能直接进 STM32 外部中断,否则一个液滴可能触发十几次抖动。常规做法是用 LM393 搭一个滞回比较器,把缓慢变化的模拟信号整形成干净的方波。滞回比较器的正反馈电阻取值要注意,回差电压太大了会漏检小液滴,太小了又滤不掉干扰。我测试下来,1k 反馈电阻配合 10k 分压,回差大概 0.3V 左右,滴速在 20~80 滴/分钟范围内都能稳定出脉冲。
2.3 液位检测:不要只靠一个传感器
输液瓶液位检测,很多新手就装一个红外液位传感器贴在瓶颈处,低于位置就报警。实际做升级版时我加了两个检测点:一个装在瓶子中上部,用来提示"液位偏低,准备换液";另一个装在接近瓶口的位置,用来触发"即将滴空,强停泵"。硬件上就是两路相同的红外反射传感器,分别接两个 GPIO 或两路 ADC,逻辑上做成不同优先级的状态。
用 ADC 读比用 GPIO 读更稳。因为瓶身倾斜、灯光反射等因素会让传感器输出处于中间态,直接当数字量读可能误判。ADC 采到电压后做个简单的滑动滤波,再设阈值判断,误报率会低很多。
2.4 蠕动泵驱动和电源的关键细节
蠕动泵可以买成品,也可以自己用步进电机加 3D 打印泵头组装。开源项目里我用的是 28BYJ-48 步进电机加 ULN2003 驱动板的组合,便宜、好买、驱动简单。虽然 28BYJ-48 是减速电机,转速不算快,但蠕动泵本来就不需要高转速,它需要的是扭矩和稳定。控制方法用四相八拍,每给一个脉冲转子走一步,流量和脉冲频率基本线性相关。
电源部分要格外小心。ULN2003 驱动板和使用 STM32 的控制板如果共用同一个 5V 电源,电机一启动瞬间电流会把电压拉垮,轻则 OLED 闪屏,重则 STM32 复位。我在这版原理图里把电机电源和逻辑电源分开走线,用了一个 47uF 电容和一个 0.1uF 电容给电机电源做局部滤波,两个电源地单点连接。这种做法在纯数字电路里看不出来,但接上电机跑起来,差别立竿见影。
3. 闭环控制代码的核心:滴速测量、PID调节与异常状态机
代码是整个项目里含金量最高的部分,我按功能模块拆成了滴速测量、PID控制器、Motor驱动、显示、按键、报警、状态机这几个文件。这里不讲每一行代码怎么写的(文件里都开源了),重点讲架构思路和关键算法的工程化处理。
3.1 滴速测量避免被中断淹没
液滴检测最简单的方式是外部中断计数,每一滴触发一次 HAL_GPIO_EXTI_Callback,计数变量加一,定时 1 秒后把计数乘以 60 得出滴速。这个方法在实验室演示没问题,但实际跑起来有个隐患:如果滴速很快,或者中断回调里还干了别的事,脉冲就会丢失或者被延迟处理。
我在升级版里改用输入捕获模式——把红外比较器输出的方波接到定时器的输入捕获通道,直接测量两次上升沿之间的时间间隔。这样液滴脉冲的计时有硬件定时器做保证,不占用 CPU 中断资源。滴速的计算公式是:
滴速(滴/分钟) = 60000 / (两次上升沿间隔毫秒数)如果连续 3 秒没有新的脉冲进来,就判定为管路堵塞或输液完成,进入报警状态。
这里有个工程细节容易被忽略:液滴间隔并不完全均匀。蠕动泵的脉动、输液管的高度变化都会让滴速在小范围内波动。所以我没有直接用相邻两滴的间隔去算瞬时滴速,而是维护了一个 5 滴的滑动窗口,取平均间隔再换算滴速。这么处理之后,PID 的输入信号平滑很多,不会因为一两个异常间隔就产生误调节。
3.2 PID 参数整定和限幅设计
PID 算法的输出对象是步进电机的速度。增量式 PID 的标准公式我不再重复,重点说几个在输液场景下的特殊处理:
- 输出限幅:电机速度不能无限制,我给 PWM 占空比或脉冲频率设置了 0~100 的整数范围。这个限幅同时也就自然地约束了滴速调节范围,避免大误差时电机猛然狂转。
- 积分限幅和积分分离:这是很多新手最容易忽略的。如果目标滴速和当前滴速差得太多,积分项会积累一个很大的值,导致超调严重。我先判断误差的绝对值,大于某个阈值时只走 PD,小于阈值才引入积分项。
- 死区:当实测滴速与目标滴速误差在 ±2 滴/分钟以内时,我直接不调整电机的输出。这个死区看着不起眼,但能明显减少步进电机的频繁启停噪音,也让 OLED 上显示的数值稳定下来,体验好很多。
参数整定没有捷径,我的调试顺序是这样的:先设 Kp 小一点,观察滴速能否朝目标方向移动;再逐步加大 Kp,直到出现轻微振荡;然后加入 Ki 消除稳态误差;最后用 Kd 压一下超调。输液这个对象响应很慢,一个完整的调节周期可能要几十秒甚至几分钟,所以测试时要耐心,不要一看两秒钟没反应就去乱拧参数。
3.3 状态机:比直接写 if else 要清晰得多
系统运行时不止"输液"这一种状态。我把代码里跑逻辑的主循环做成了一个状态机,状态迁移关系大概是:
初始化 → 待机(参数设置) → 输液运行 → 暂停/继续 → 完成/异常在"输液运行"状态下,程序每一轮循环做四件事:读取滴速、执行PID计算、更新电机输出、刷新显示。在此之上,又有两个高优先级异常可以打断这个循环:
- 液位低(第二个检测点触发):立即停止电机,蜂鸣器长鸣,OLED 显示 "REPLACE"。
- 连续滴速超限超过 10 秒:虽然不停止输液,但进入报警状态,直到人工干预或滴速回落到安全范围。
状态机的好处是逻辑清晰,后续要加"护士呼叫"、"远程微信通知"之类的功能,只需要新增状态和迁移条件,不会把主循环写得像一锅粥。
4. 仿真是怎么搭起来的:Proteus 的器件替代思路与信号模拟
仿真文件用的是 Proteus,具体器件我没有完全照搬实物,因为 Proteus 的元件库里不可能什么都有。系统设计得好的一个标志就是:仿真模型和实物之间能互相印证,而不是各说各话。
4.1 用虚拟器件模拟传感器信号
Proteus 里找不到红外对管滴速传感器,没关系,我用的替代方案是一个脉冲信号源。把脉冲频率设定在目标滴速附近,比如 60 滴/分钟就设成 1Hz 的方波,接到 STM32 的输入捕获引脚上,代码吃到的电平和真实传感器整形后的方波电平是一模一样的。这样调试滴速计算和 PID 逻辑完全没有问题。
液位传感器在 Proteus 里用一个开关加一个上拉电阻模拟。开关闭合代表液位正常,开关打开代表液位过低。纯数字量输入在仿真阶段够用了,不需要非得上 ADC。
4.2 蠕动泵的仿真替代:PWM 控制的直流电机
Proteus 里没有 28BYJ-48 的库模型,我换成了直流电机加 PWM 调速。你会看到,这个替代其实只改变了"执行机构"的物理模型,控制逻辑完全一致:STM32 根据 PID 输出调整 PWM 占空比,占空比越大电机转得越快。你可以用虚拟示波器观察 PWM 波形的变化,来验证 PID 是否在朝正确的方向调节。
模拟调试图里可以同时拉出两个信号观察:滴速脉冲和 PWM 输出。当滴速低于目标值时,你会看到 PWM 占空比在 PID 作用下逐步增加,电机转速提升,滴速回升。这个过程和实物联调时的现象基本一致,对于理解闭环控制非常有帮助。
4.3 仿真里也要看"坏情况"
很多人在仿真里只跑理想波形,这样做不到锻炼的价值。我建议至少试三组极端输入来检验系统的鲁棒性:
- 把脉冲信号突然从 60 滴/分钟跳到 120 滴/分钟,观察 PID 能否把速度压回来。
- 把脉冲信号直接停掉,模拟堵管,观察报警状态能否在预期时间内触发。
- 把液位模拟开关强制打开,观察能否立即停机并显示异常。
这三组测试我在开源说明文件里都写了预期结果,如果你打开仿真跑出来的现象和我写的不一致,说明代码或连线有地方没对上,排查过程本身就是一次很好的训练。
5. 升级版最容易翻车的三个地方:烧录失败、晶振不起振、虚拟串口掉驱动
这一节的内容是从我和不少读者的实操教训里总结出来的,按踩坑频率排序。这些坑几乎和功能代码无关,但每一个都能让你卡上一晚上。
5.1 ST-Link 报错:"No STM32 Target Found"
用 ST-Link + Keil/CubeIDE 烧录时,突然弹出"No STM32 target found! If your product embeds debug authentication...",第一反应别怀疑芯片坏了,大概率是连接或复位状态问题。按顺序排查:
- SWDIO 和 SWCLK 接反了没有。这俩引脚旁边如果有排阻、跳线帽,先确认有没有短路。
- 目标板供电是否正常。ST-Link 的 VCC 引脚能不能供上 3.3V,用万用表量一下 VSENSE 是否在合理范围。
- 复位脚是否被拉死。如果板子上的 NRST 被电容和按键电路搞得一直处于低电平,连接会失败。
- 芯片是不是已经被锁死。读保护(RDP)开启后,SWD 在默认状态下会连不上,需要先用 ST-Link Utility 做全片擦除。
这几个步骤里,前三项是最常见的,第四项属于进阶情况。确认没问题后重新插拔 ST-Link,再把 Keil 里 Flash Download 选项的 Reset and Run 勾上,基本能解决。
5.2 晶振不起振
最小系统画得没问题,程序也能烧进去,但串口输出的时钟完全不对,或者 UART 波特率乱码——八九不离十是外部晶振没正常工作。排查步骤:
- 先确认有没有按 CubeMX 里的 HSE 配置。很多人用内部时钟也能跑,但 USB 和外设时序会出怪问题。
- 用示波器或逻辑分析仪看 OSC_IN / OSC_OUT 是不是有正弦波。没有示波器就把程序改成用内部 HSI 跑一个 LED 闪烁,如果能闪,说明代码逻辑没问题,问题就在晶振电路。
- 换晶振的匹配电容,8MHz 晶振配 10~22pF,不要乱改负载电容太大或太小。
- 晶振和地、电源走线保持距离,不要把晶振输出线拉到高频信号附近。
5.3 STM32 Virtual COM Port 设备出现黄色感叹号
STM32 模拟 USB 串口在很多教科书项目里都会用,烧录完插上电脑,设备管理器里出现"STM32 Virtual COM Port"但带黄色感叹号,这是驱动签名问题。老系统上可以用禁用驱动程序强制签名的方式,或者手动指定驱动路径为 ST 官方驱动目录。Win10/11 下最省事的办法是直接把驱动文件解压后手动更新,指定到 INF 所在文件夹。这个坑和代码一点关系都没有,但是卡住你烧录调试的拦路虎。
6. 开源文件怎么用:从原理图到产物的完整清单
最后说一下仓库里的文件结构和打开方式,方便你拿到后快速跑起来。
6.1 代码目录结构
project/ ├── Core/ │ ├── Inc/ // 头文件 │ └── Src/ // 主程序、中断、滴速测量等 ├── Drivers/ │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ ├── Hardware/ │ ├── motor.c / motor.h │ ├── oled.c / oled.h │ ├── pump.c / pump.h │ ├── sensor.c / sensor.h │ └── buzzer.c / buzzer.h ├── PID/ │ └── pid.c / pid.h ├── MDK-ARM/ // Keil 工程 └── README.md代码是在 STM32CubeMX 生成的框架上改的,HAL 库版本建议用 1.8.0 以上。把仓库 clone 下来后,直接用 Keil MDK 打开 MDK-ARM 下的 .uvprojx 文件,编译一次确认工具链没问题。如果提示缺少芯片包,打开 Pack Installer 安装 STM32F1xx 的 Device Family Pack 即可。
6.2 原理图的打开与检查
原理图我习惯用嘉立创EDA(专业版)打开,也是新手最容易上手的工具。打开后先对照第 2 章那张引脚分配表,确认每个外设引脚和你代码里配置的一致。最稳妥的方法是跑完代码后观察现象,再回到原理图上找对应功能模块,整个过程会强迫你把数据手册、代码和电路三者串起来。
6.3 从"能跑"到"能展示"的收尾建议
如果你打算用这个项目做课设、毕设或者面试作品,我强烈建议在状态机和 OLED 显示上再花半天时间:
- 给 OLED 加一屏历史曲线或者最多滴速/最低滴速记录,面试时能拿真实数据说话。
- 把报警条件做成可配置的,比如液位阈值和超限时间,让操作者不用改代码就能适应不同场景。
- 如果对无线通信有基础,HC-05 蓝牙模块或 ESP8266 可以很自然地加到这套系统的串口上,做手机端显示。
这些扩展我当时花了一个晚上全部加上,效果立竿见影——面试官看到的不再是"一个单片机实验",而是一个考虑了人机交互和场景适配的完整系统。
最后说点个人体会。做这类闭环项目,最难的不是某个传感器怎么驱动,也不是 PID 公式怎么调,而是被卡住的时候能不能忍住不摔板子、一步步用逻辑去推。很多朋友私信说"照着你的仿真跑通了,但实物一上电就懵",我的回答永远是一样的:先仿真,再焊板,再联调,每一步都只改一个变量。这套方法论的价值,比这一百多个C文件加起来都大。希望这份开源资料能帮你在调试路上少熬两个夜。