ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于STM32的智能输液监护系统设计与PID闭环控制实现

基于STM32的智能输液监护系统设计与PID闭环控制实现 1. 项目整体设计与功能拆解1.1 输液监护到底需要监护什么我第一次去医院输液时盯着那滴管数了一晚上滴速眼睛都快废了。后来跟护士聊才知道普通重力输液器全靠输液管上的滚轮手动调速药液快慢完全看人工而且随着液面下降实际滴速会越来越慢出现气泡、堵塞、回血这些异常更是只能靠家属肉眼发现。这让我决定做一个能自动看管输液的设备。这套开源项目就是一台以 STM32F103C8T6 为核心的智能输液监护调控系统。它有三个最核心的功能一是实时监测滴速二是自动闭环调节滴速三是异常报警。你在设备上设定好目标滴速比如每分钟 40 滴系统会通过红外对管检测滴管中液滴下落产生的脉冲算出当前实际滴速再由主控里的 PID 算法输出 PWM 信号给蠕动泵让实际滴速始终咬住目标值。出现气泡、管路阻塞、输液完成、滴速偏差过大时蜂鸣器和 OLED 会同步提醒。升级版还额外增加了电机电流反馈、串口数据上报接口方便后续接 WiFi 模块做远程监护。这套资料里包含完整的 MDK 工程源码、PDF 原理图、Proteus 仿真工程以及我实际调试中发现的问题记录。适合三类人看正在做嵌入式课程设计的本科生、想练手医疗电子项目的开发爱好者、以及需要一套完整闭环控制样例来学习 STM32传感器PID 的入门者。照着硬件清单买料、烧录代码就能把整机跑起来。1.2 升级版“升”在哪几个地方市面上很多教学项目只做一个“滴速监测”用红外对管计个数然后显示在 LCD 上就结束了。但真正输液监护场景里光“看”不“管”是不够的。升级版相比基础版做了这些增强功能点基础版升级版滴速监测有有且加入滤波和平均算法自动调速无人工滚轮PID 控制蠕动泵闭环气泡检测无加装第二路对管检测管路内空气段阻塞检测无检测电机电流突变滴速归零双判断输液完成判断无连续无滴卸荷判定状态跳转远程通信接口无UART 输出结构化数据帧可接 4G/WiFi 模块交互方式数码管/1602 简单显示OLED 显示全部参数 按键设定目标值这套系统名义上是“输液监护”但剥离掉医疗外壳后它其实是一个典型的传感器采集 执行器驱动 闭环控制的嵌入式样例。把滴速换成温度、湿度、转速把蠕动泵换成加热器、风扇控制逻辑可以原样套用。所以就算你不做医疗方向这个工程的软件框架和调试思路也值得抄一份。在设计架构上我刻意把“检测、控制、交互、报警”拆成独立模块每个模块之间只通过结构体和变量接口通信不互相耦合。这样好处很明显改传感器型号不会牵扯到电机驱动代码显示模块从 OLED 换成 TFT 也只在 BSP 层修改主逻辑不用动。开源代码不是拿来跑通就完事更重要的是借鉴它的分层思想。2. 硬件方案与原理图设计要点2.1 主控为什么选 STM32F103C8T6主控选型时我曾经纠结过 STM32G0、GD32 甚至 ESP32。最后回到 STM32F103C8T6原因不复杂它的资料存量最大CubeMX 支持完善Proteus 里有现成仿真模型而且 5V 容忍引脚多驱动常见的 3.3V 逻辑同时还能直接读取 5V 比较器输出。博世等大厂虽然新型号多但对学习开源项目来说 F103 仍是最稳妥的选择。最小系统电路是常规做法8MHz 晶振作为 HSE两个 20pF 负载电容NRST 接 10k 上拉到 3.3V并联 100nF 对地电容BOOT0 通过 10k 下拉到 GND保证从 Flash 启动PA13/PA14 引出 SWD 下载口。这里要特别说一下晶振电容的计算方法。常见贴片晶振的负载电容标称 CL20pF芯片引脚对地寄生电容 Cs 大约 3~5pF匹配电容 C 按公式 C 2 × (CL - Cs) 计算取 CL20pF、Cs3pF 的话C 34pF所以选 33pF 是标准答案。如果晶振起振不稳定或者频率偏差大先检查这两个电容别随手焊 20pF 上去。电源脚滤波也不可马虎STM32F103C8T6 的 VDD 引脚至少放一个 100nF 陶瓷电容最好再并联一个 10uF 钽电容VDDA 引脚单独接一个 10uH 磁珠和一个 1uF 电容到模拟地。很多新手画板子只放一个去耦电容导致 ADC 采样跳字、PWM 输出抖动其实都是电源纹波惹的祸。2.2 滴速检测电路红外对管 比较器整形滴速检测是整个系统中直接影响控制精度的部分。我最初图省事直接把红外接收管电压接到 STM32 的 ADC 引脚用软件阈值判断液滴。实测发现环境光变化、水滴半透明折射都会造成电压缓慢漂移软件阈值要么上限要么下限频繁误判。所以升级版在传感器输出端加了一级比较器整形。发射管用 IR204接收管用光电三极管 PT334或者直接买现成的红外光电对管模块也行。发射管串联一个 150Ω 限流电阻接到 5V注意红外管正向压降只有 1.2V电流大约 25mA 左右接收管的集电极接 5V、发射极串联 10k 到地这样接收管电压会随光线强弱变化。关键比较器用 LM393同相输入端接接收管中间点反相输入端接一个 10k 电位器分压得到的参考电压初始调到 2.5VLM393 是开漏输出必须加一个 10k 上拉电阻到 3.3V否则输出永远是低电平。同时我在同相输入端和输出端之间跨接一个 1MΩ 正反馈电阻构成迟滞比较器让脉冲边沿干脆利落避免临界抖动造成外部中断被反复触发。滴下一滴输出引脚就产生一个下降沿或上升沿取决于接线这个信号直接进 STM32 的 PA0 外部中断引脚。调试这个电路时需要注意的是红外管的发射方向必须正对滴管中线滴管外侧要加遮光套否则周围灯光会直射进接收管信号完全淹没。比较器参考电压也不是固定 2.5V要实际观察无液滴通过时接收管电压大概值再把参考调到这个值以下 0.3~0.5V 位置让有液滴遮挡时电压突变到参考电压以下比较器翻转才可靠。2.3 蠕动泵驱动与阻塞检测电路蠕动泵是这个项目的执行器。我选择的是一款常见的小型直流蠕动泵减速电机额定电压 6V空载电流 80mA堵转电流 400mA 左右。如果选蠕动泵泵头自带的步进电机方案控制会复杂很多这里直接用直流电机配合 PWM 调速精度足够。电机驱动芯片用 TB6612FNG相比 L298N 它内部是 MOSFET 管导通压降小、发热低而且体积不大。VM 引脚接 6V 电机电源VCC 接 3.3V 逻辑电源AIN1/AIN2 控制方向PWMA 接 STM32 定时器 PWM 输出我用 TIM2_CH1PA0 占用了所以改为 PA1。TB6612 输出端 A01、A02 之间并联一个 0.1uF 电容吸收换向尖峰另外给电机两端反并联一个 1N5819 肖特基二极管续流。阻塞检测是升级版新加的。我在电机供电回路串联一个 0.1Ω 采样电阻用差分放大电路把电压送到 ADC或者直接买 INA180 电流检测模块ADC 通道采样电机电流。正常输液时电流大概 80~120mA当管路折叠或者泵管老化堵塞时电流迅速升到 300mA 以上。但单看电流不够输液完成时电机空转电流反而变小所以要结合滴速传感器如果电流偏大且滴速持续为 0判定为阻塞如果电流很小且滴速为 0判定为输液完成或管路漏液。多条件联合判断能大幅降低误报。2.4 人机交互与报警模块显示采用 0.96 寸 I2C 接口 OLED主控 I2C1 的 PB6、PB7 直接连接。OLED 体积小、功耗低在 12FPS 刷新下工作电流不到 20mA完全可以直接用 3.3V 供电。显示界面按状态划分正常时显示当前滴速/目标滴速/累计滴数运行中显示“滴速稳定”提示报警时显示具体异常代码比如 ERR01 代表滴速过高。三个按键分别用于“设定值增加”“设定值减少”“启动/停止”直接接到 STM32 的 PB0、PB1、PB10 引脚内部上拉按下接地。这里要注意 STM32 的 PB2 是 BOOT1 引脚不要误用。按键处理在主循环里用非阻塞方式扫描每隔 20ms 读一次识别短按和长按长按设定值可快速增减。蜂鸣器选择有源 5V 蜂鸣器因为 A 型有源蜂鸣器内置振荡源MCU 输出高低电平就能控制驱动管用 S8050 三极管基极串联 1k 电阻到 PA8发射极接地集电极接蜂鸣器负极蜂鸣器正极接 5V。和蜂鸣器并联一个 1N4148 二极管续流防止关断瞬间的反向电动势打坏三极管。2.5 电源系统设计别让电机的瞬间电流拖垮 MCU整个系统的外部电源我用一个 12V/1A 适配器输入第一级通过 LM2596-ADJ 降压到 6V 给蠕动泵电机第二级用 AMS1117-3.3 降到 3.3V 给 MCU 和逻辑电路。为什么不直接从 12V 降 3.3V因为电机启动和调速时电流变化剧烈如果共用一条降压路径MCU 电压会被拉低导致频繁复位。我的具体做法是LM2596 输出 6V 后先给一颗 470uF 电解电容再分两路一路直接进电机驱动另一路经过 LM1117-3.3 给控制部分。这样电机瞬态大电流被大电容吸收不至于影响后面的线性稳压器。电源输入串联一个防反接二极管 SS34极性接反时不会烧板子。数字地和模拟地怎么处理也有讲究。LM2596 这种开关电源是干扰大户开关噪声会通过地线耦合到高灵敏度接收管和 ADC。我在 PCB 上把电源地、电机驱动地、传感器模拟地分开走最后在 MCU 底部单点汇合。实际测试发现如果不做分离滴速传感器脉冲波形在电机转动时会叠加明显毛刺外部中断偶尔会多计数。所以做这个小项目时地线布局比你想的更影响可靠性。3. 软件实现与关键代码逻辑3.1 工程结构与开发环境搭建软件工程是围绕 HAL 库 CubeMX 搭的。先打开 STM32CubeMX选 STM32F103C8T6配置外部晶振 8MHzAPB2 总线时钟拉到 72MHz。需要启用的外设有GPIO、TIM2PWM 输出、TIM3系统基时、看门狗可选、USART1、ADC1、I2C1以及外部中断 EXTI0。生成工程时选择 MDK-ARM V5 工具链代码接口选了 HAL 库。工程目录我按照个人习惯做了分层SmartInfusion_Upgrade/ ├── Core/ │ ├── Inc/ │ └── Src/ // main.c, stm32f1xx_it.c ├── APP/ │ ├── app_main.c/h // 应用主循环 │ ├── interactive.c/h // 按键、显示、Lang库 │ ├── pid_control.c/h // PID算法 │ └── alarm_machine.c/h // 状态机与报警 ├── BSP/ │ ├── drip_sensor.c/h // 滴速检测 │ ├── motor.c/h // 电机驱动 │ ├── oled.c/h // OLED驱动 │ └── adc_measure.c/h // 电流/电源电压采样 ├── MDK-ARM/ │ └── SmartInfusion.uvprojx └── Docs/ ├── 原理图_V1.2.pdf └── 仿真工程Proteus/这里推荐大家在工程里单独建一个debug_log.h定义不同颜色的串口打印宏调试时直接LOG_INFO(target: %d, target_speed);输出。我在调试 PID 时全靠这行日志省掉了接示波器的折腾。3.2 液滴计数与滴速计算核心代码滴速检测的关键点是外部中断、时间戳记录、滤波平均。我在stm32f1xx_it.c里写 EXTI0 中断回调把计数和时间戳放进一个环形数组里/* drip_sensor.c */ #define DRIP_BUF_SIZE 16 volatile uint16_t drip_count 0; volatile uint32_t last_drip_time 0; volatile uint32_t drip_interval[DRIP_BUF_SIZE]; volatile uint8_t drip_index 0; void DripSensor_EXTI_Callback(void) { uint32_t now_ticks HAL_GetTick(); uint32_t interval now_ticks - last_drip_time; last_drip_time now_ticks; /* 过滤明显时间差如误触发小于50ms */ if (interval 50) return; drip_interval[drip_index] interval; drip_index (drip_index 1) % DRIP_BUF_SIZE; drip_count; } uint16_t DripSensor_GetSpeed(void) { uint32_t sum 0; uint32_t cnt 0; /* 计算最近 N 个有效间隔的平均值 */ cnt (drip_index DRIP_BUF_SIZE) ? DRIP_BUF_SIZE : drip_index; if (cnt 0) cnt 1; for (uint8_t i 0; i cnt; i) { sum drip_interval[i]; } /* 平均间隔(ms)换算为滴/分钟 */ if (sum 0) return 0; return (uint16_t)(60000UL * cnt / sum); }这里用环形数组存最近 16 次滴间隔做简单算术平均。为什么要保留 16 次而不是每次只算相邻间隔因为输液滴速本身有抖动一次偶然的快或慢会干扰 PID 计算取平均后控制会更稳定。同时我设置了一个 50ms 死区如果红外对管受到干扰产生密集脉冲直接就过滤掉不会污染数据。但要注意HAL_GetTick()在外部中断回调中调用是否安全。HAL 库的 tick 递增是在 SysTick 中断里做的外部中断优先级如果高于 SysTick代码逻辑上会有一点风险所以我设置 SysTick 优先级为最高0外部中断为次高1确保时间戳正确。3.3 PID 闭环控制蠕动泵PID 算法在pid_control.c里实现采用的是增量式 PID。增量式 PID 的输出是 PWM 占空比的增量而不是绝对占空比这样在启动时不会瞬间给满电压切除积分饱和也比较好处理。/* pid_control.c */ typedef struct { int16_t target; int16_t current; float Kp, Ki, Kd; int32_t err_prev; int32_t err_prev2; int32_t out; int32_t out_max; } PID_t; int32_t PID_Calc(PID_t *pid) { int32_t err pid-target - pid-current; int32_t incremental (int32_t)(pid-Kp * (err - pid-err_prev) pid-Ki * err pid-Kd * (err - 2 * pid-err_prev pid-err_prev2)); pid-out incremental; if (pid-out pid-out_max) pid-out pid-out_max; if (pid-out 0) pid-out 0; /* 电机不能反转 */ pid-err_prev2 pid-err_prev; pid-err_prev err; return pid-out; }采样周期我定在 1 秒。为什么不是 10ms因为滴速是一个慢变量一秒才有几十个脉冲PID 控制器如果跑太快计算出来的误差变化实际上只是噪声会导致电机忽快忽慢。每次 PID 计算得到输出out后直接赋值给__HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, out)占空比范围 0~999对应 PWM 频率 20kHz。PWM 频率太高或太低都不行太高电机电感和驱动桥发热太低会有啸叫我用 20kHz 是听不到声音的临界点。PID 参数调试后面会详细说这里先给一组能够跑起来的初始值Kp60Ki12Kd15上限 500。按照这个值系统能在 20 秒内把滴速稳定在目标附近。3.4 系统状态机与报警管理报警逻辑用状态机实现。状态包括空闲IDLE、运行正常RUNNING、暂停PAUSE、报警ALARM。状态机在alarm_machine.c中每 100ms 执行一次typedef enum { SYS_IDLE, SYS_RUNNING, SYS_PAUSE, SYS_ALARM } SysState; SysState state SYS_IDLE; uint8_t alarm_code 0; void AlarmMachine_Update(SysState *state, uint8_t *alarm_code) { uint16_t target Interactive_GetTargetSpeed(); uint16_t actual DripSensor_GetSpeed(); uint16_t current_mA ADC_GetMotorCurrent(); switch (*state) { case SYS_IDLE: if (key_start_pressed()) { *state SYS_RUNNING; Motor_Enable(1); } break; case SYS_RUNNING: if (key_start_pressed()) { *state SYS_PAUSE; Motor_Enable(0); } if (target 0 actual (target 15)) { *state SYS_ALARM; *alarm_code ERR_DRIP_TOO_FAST; } if (current_mA 300 actual 0) { *state SYS_ALARM; *alarm_code ERR_BLOCK; } if (current_mA 30 actual 0) { *state SYS_ALARM; *alarm_code ERR_COMPLETE; } break; case SYS_PAUSE: if (key_start_pressed()) { *state SYS_RUNNING; Motor_Enable(1); } break; case SYS_ALARM: if (key_long_pressed()) { *state SYS_IDLE; *alarm_code 0; Motor_Enable(0); } break; } }报警时蜂鸣器按照报警码不同输出不同频率和时长异常滴速用短促单响阻塞用连续长鸣输液完成用两声急促短响。这样可以不用看屏幕只听声音就能大概知道出了什么问题。另外我觉得状态机的好处是后续增加暂停后自动恢复、护士站远程解除报警等新功能都非常容易只要在对应状态里多加几个条件跳转就行。3.5 数据上报与远程监护接口升级版在串口上增加了一组格式化输出通过 USART1 发到电脑或者无线模块。数据帧格式设计得很简单$INF,target,actual,drip_count,alarm_code,checksum\r\n例如$INF,40,39,1024,0,9A\r\nchecksum是前面所有字节异或和转成大写十六进制附在末尾。上位机或者云端解析时先校验 checksum再更新界面。我这里用主动上报的方式每 1 秒发一帧。如果想接 ESP8266直接在 UART 外再接一个缓存区把串口收到的数据重新打包成 AT 指令发出去就能做成简单的物联网输液监护。比较关键的是串口打印会用printf而 F103 没有硬件 FPU调试时如果printf里带了%f会非常慢甚至阻塞主循环。我建议所有传上来的数据都不要转浮点滴速是整数就发整数PID 输出也是整数这样做省内存也省时间。4. 仿真验证与硬件调试实录4.1 Proteus 仿真工程怎么搭很多人拿到仿真文件直接点运行结果屏幕黑黑的没反应就开始怀疑仿真有问题。其实 Proteus 搭 STM32 工程有几个核心步骤先添加 STM32F103C8T6 芯片放置 8MHz 晶振、两个 22pF 电容、复位电路直接放一个 RESET 按钮更直观然后将 PD0/PD1 对应的 OSC_IN/OSC_OUT 接上去BOOT0 接地VDDA 接一个 100nF 滤波电容到地。如果这些最小系统不完整仿真芯片不会启动仿真界面上也看不出报错。滴速传感器在仿真里是一个比较抽象的东西。我没有按照真实红外对管电路去搭那样太麻烦而是用 PULSE 信号源接入 PA0 外部中断引脚来模拟液滴脉冲。PULSE 频率通过属性设置比如要模拟每分钟 40 滴就把脉冲周期设成 1500ms占空比 10%。为什么是 1500ms因为 60000ms / 40滴 1500ms 一滴。这样仿真运行时程序里的滴速计算和真实传感器输入完全一样。大家也可以用一个方波发生器模块来模拟不同滴速观察 OLED 显示和电机 PWM 输出是否跟随。Proteus 里的 TB6612 模型不是标配可以用直流电机模型 开关代替或者直接用 PWM 电压直接作用在电机模型上。我的仿真文件里简化了驱动电路用一个 MOTOR 元器件接受 PWM 电压这样只看控制逻辑的话足够了。仿真时注意在 STM32 的虚拟终端查看串口输出确认状态机跳变正常。4.2 PID 参数整定全过程记录调试 PID 是最花时间的部分。我使用四步法整定参数证据都记录在项目文档里。第一步开环测试。把 PID 输出固定为常数比如占空比输出 300、400、500记录对应的实际滴速。实测得到一组近似线性关系PWM 占空比 300 时滴速 20滴/分400 时 33滴/分500 时 45滴/分。这说明蠕动泵电机转速和滴速基本成正比没有死区特别大的问题。第二步只加比例 P。把 Kp 从 20 往上加Ki 和 Kd 先设 0。目标滴速定在 40 滴/分观察响应。Kp40 时系统最终稳定到 38 滴/分存在稳态误差有约 2 滴/分的偏差因为拧紧泵管需要克服弹性阻力。Kp 加到 80 后稳态误差减小到 1 滴/分但开始出现小幅振荡周期大约 15 秒。第三步加入积分 I 消除稳态误差。Ki 从 2 开始加加到 12 时稳态误差归零但是系统响应变慢而且出现了轻微超调。为了抑制超调我加入微分项Kd15观察系统在 20 秒内增加到目标值后没有超过 42 滴/分最后稳定在 40 滴/分。最终的 PID 参数表如下大家可以直接抄作业作为初始值也必须根据自己的电机和管路再做微调参数初始值最终值调节依据Kp4080增大 Kp 缩小稳态误差同时观察是否振荡Ki512消除稳态误差同时注意积分饱和Kd015抑制超调让响应更平滑采样周期1000ms1000ms滴速是慢变量太短会引入噪声输出上限700500防止电机过冲导致滴速超目标过多这里必须提醒不同蠕动泵泵头压紧度、硅胶管壁厚不同同一个 PWM 占空比对应的滴速差别很大。所以我后来在软件里加入了一个自校准函数上电后先让电机在占空比 30% 跑 5 秒测出基准滴速并做线性映射。这样换一个泵头也能很快适应。4.3 硬件调试中遇到的典型故障与排查思路调试过程中我记录了三个最典型的故障场景每个都能写出一段辛酸史。先看滴速计数不准确的问题。现象是 OLED 显示的滴速忽高忽低有时候一分钟显示 300 滴有时候直接归零。排查步骤是这样先用万用表量接收管中间点电压发现没有液滴时电压稳定在 4.3V有液滴时掉到 1.8V信号本身是好的再用示波器看比较器输出结果发现输出是在 1.8V 到 4.3V 之间来回抖根本没有形成干净的数字边沿。原因是我把 LM393 的参考电压调得太高导致液滴还没有完全遮住光路输出就已经翻转等液滴移开又反复越阈值。我把参考电压往下调到 1.5V并且加了一个 1MΩ 正反馈电阻后输出才稳定。第二个是电机转得慢滴速始终上不去。我起初以为是 PID 参数问题后来发现电机供电电压只有 4.2V而理想是 6V。原因是 LM2596 反馈电阻虚焊导致输出电压偏低。重新补焊后电机转速恢复PID 才真正能调。这件事提醒我如果系统整体表现异常先查电源各级电压再调控制算法不要上来就动代码。第三个是单片机频繁复位。压力测试时电机一启动 MCU 就重启。用示波器抓 VDD 波形看到电机的 6V 电源线上有大约 2V 的跌落且复位引脚 NRST 在电机启动瞬间居然有一个下降到 1.2V 的毛刺。这个毛刺不是真实复位而是地线阻抗高导致的地弹。解决措施是把电机驱动地线从 MCU 的地线路径上分开驱动板直接接到电源输入端地 MCU 的复位引脚和地之间并联一个 100nF 电容滤除高频噪声之后复位问题再没出现过。4.4 从零复现开源资料的快速上手顺序如果你刚下载这个开源项目我建议不要直接打开 Keil 工程一顿编译然后烧录先按下面顺序走一遍效率最高。第一步看 README 里的引脚分配表确定自己手上的板子和原理图是否一致。我提供的引脚分配表非常重要因为换一个引脚代码里的所有 GPIO 初始化都要跟着改。第二步打开Docs/原理图_V1.2.pdf对照 BOM 清单把物料备齐。不要试图用脚位完全不同的传感器模块直接替换红外观测模块需要提前确认输出极性是低电平有效还是高电平有效。我的电路是低电平脉冲有效如果你的模块是高电平有效需要改一下外部中断触发方式。第三步先在 Proteus 里跑仿真确认虚拟终端能看到$INF数据帧OLED 显示数字变化。仿真通过之后再用实物模块连接 STM32 最小系统板。实物的传感器和电机接线可以先不接先用一个按钮接到 PA0 模拟液滴脉冲测试整个软件流程。这一招特别好用能帮你把软件问题提前暴露出来。第四步最终将传感器装在滴管夹具里启动电机开始联调 PID。如果发现响应太慢或者超调修改pid_control.c中的三个参数即可不需要重新编译整个项目只改这一个文件就行。5. 从硬件到软件这些坑我已经替你踩过5.1 硬件焊板的几个反直觉细节红外对管的长短脚我焊错过一次。发射管长脚接正极短脚接负极接收管反过来短脚接正极长脚接负极。很多新手第一次焊反把器件烤坏就是因为没看规格书只看封装。更稳的做法是焊前用万用表二极管档测一遍正向压降 1V 左右的是发射管反向截止的是接收管。比较器 LM393 是开漏输出这一点特别容易踩坑。仿真是不会告诉你开漏输出不加负载能不能翻转的但实物上如果忘了加上拉输出永远低电平滴速永远是 0。我后来把所有开漏输出器件都单独列成清单LM393、DS18B20、I2C 总线都要加上拉。电机供电的稳压器 LM2596它的反馈电阻分压网络布局要尽量靠近 FB 引脚否则输出纹波巨大。而且散热焊盘要开大一点12V 降到 6V 时1A 负载下功耗 6W不加散热片芯片会烫到怀疑人生。我后来在 LM2596 底部打过孔阵列才算压住温升。5.2 软件移植最容易踩的隐形坑项目代码虽然开源但多数人移植到自己板子上还是会遇到问题。第一个隐形坑是 HSE_VALUE 是否匹配。CubeMX 生成的系统时钟默认按照 8MHz 外部晶振计算如果你换了 12MHz 晶振而不改 HSE_VALUE整个系统时钟会乱了套串口波特率乱码、延时误差巨大还不好定位原因。第二个坑是中断优先级配置。外部中断、定时器中断、串口中断的优先级如果设置不恰当会影响滴速计数的准确性。我把 SysTick 中断优先级设为 0滴速外部中断设为 1PID 定时器更新中断设为 2这样时间戳永远优先于控制中断控制中断又优先于其他 IO 处理。改动优先级要在HAL_NVIC_SetPriority中明确设置不要使用默认值。第三个坑是使用 HAL 库时的HAL_Delay调用。HAL_Delay 依赖 SysTick它只在主循环或线程上下文里能用。如果你在外部中断或定时器中断回调里调用轻则卡死重则嵌套中断导致栈溢出。我全工程搜索过确认没有任何中断回调里调用HAL_Delay。需要延时的地方要么用状态标志要么用变量计数。第四个坑是浮点输出。F103 没有 FPU虽然 IAR 或 MDK 会自动调用软件浮点库但纯软件浮点计算很耗时。我在滴速计算中用了整型乘除避免float运算PID 内部虽然用了 float 系数但每次都只在 1 秒周期内运行不会对主循环造成压力。串口输出绝对不要用printf(%.2f, ...)否则一帧数据打印可能消耗几毫秒影响中断响应的实时性。5.3 没有示波器怎么把传感器调到能用示波器不是人人都有但在没有示波器的情况下通过两个工具也能完成调试。一个是万用表一个是 OLED 屏幕。把滴速传感器内部电压接到 ADC 通道再把 ADC 数值实时显示在 OLED 上。手捏滴管模拟一滴液体观察数字是否有明显跳变。如果跳变量大超过 1V说明传感器基本正常工作。然后接上比较器把比较器输出接一个 LED 指示灯观察液滴通过时 LED 是否间隔闪烁如果闪得均匀说明脉冲正常。最后再把 LED 换成 STM32 的外部中断引脚通过串口打印滴速。这套方法虽然土但在现场环境下比搬一台示波器好用得多。5.4 在开源项目中写文档是一个加分项最后分享一下我后续维护这套开源项目的一个心得。最初我只是把代码和原理图传上去结果有 10 多个用户私信问接线、问引脚定义。后来我花了一下午写了一个 README内容包括引脚分配表、烧录步骤、常见问题链接、测试视频链接提问量立刻下降了一半。做开源项目“甩代码”是最懒的做法真正有价值的是把设计思路、取舍原因、调试过程记录下来。这也是我在项目文档里加入“设计笔记”章节的原因。即使你只把代码作为自己的积累写一份良好的调试记录也能让你半年之后回想起整个系统时不需要重新翻代码猜逻辑。这套系统仍有不少可以扩展的方向比如加入实时时钟记录输液历史、接入 LoRa 组网让护士站大屏同时看到八床病人的输液状态、增加语音提示模块。但不管怎么扩展最底层的东西还是那套健壮的滴速检测和闭环控制这也是我在升级版里最花心思的部分。希望这份开源资料能帮你少熬几个夜也把嵌入式里最迷人的“传感-决策-执行”链路彻底玩明白。
返回列表