资讯动态

STM32注射泵电机控制:编码器反馈与PID闭环调参实战

发布时间:2026/9/16 1:40:04 来源:尧图企业网站定制
简介一套面向STM32嵌入式开发与医疗设备控制场景的智能注射控速系统资料围绕STM32监控、电机控制、脉搏检测与人机交互展开适合有C语言和单片机基础的学习者用于项目参考或毕业设计。资源共225个文件以C源码、H头文件、Keil工程相关格式uvprojx、uvoptx、map、hex等为主同时包含39个o目标文件和38个crf编译辅助文件压缩包约5.6MB目录结构清晰便于直接打开工程查看代码和编译配置。已有116人学习下载。资料详细展示了基于PWM的电机调速实现、通过ADC采集脉搏信号的流程、LCD显示模块的驱动方式以及STM32CubeMX/Keil环境下的固件开发思路结合工程中的分层代码可帮助读者快速理解智能注射系统的整体框架、掌握嵌入式外设驱动的综合调用方法是一份兼顾原理讲解与实际工程代码的实用参考。1. 注射速度的关键不在算法复杂度而在闭环是否真的闭合智能注射控速系统挂在 STM32 上核心问题不是“控制器有多智能”而是“电机到底转没转到你让它转的速度”。手推注射 10 mL 用时还能估算但 0.2 mL/h 的速率靠开环 PWM 根本维持不住。电机负载变化、电池电压跌落、活塞阻力波动任何一项都会让实际流量偏离设定值。所以这套系统首先要做的是监控闭环编码器测角位移按固定周期计算平均速度PID 把速度误差拉回来。“监控”和 motor control 在这里不是两个模块而是同一件事的两面。做这类设备的工程师功夫主要花在选型、参数整定和故障判定上最容易被绊住的就是三件事定时器怎么配、PID 放在哪个中断、堵转和超速怎么判得准。2. 电机与控制链路选型编码器、驱动芯片与 STM32 定时器2.1 电机选型中的关键矛盾步进电机的丢步不可检测步进电机在注射泵里很常见便宜、开环控制简单用细分驱动一圈能到几千步。但开环步进有一个对监控系统致命的缺点丢步不可检测。负载大时电机会往前走几步又弹回计数器上 PWM 步步数已经到位实际行程却偏了。对注射这类对累计量敏感的场景监控不到丢步就等于输错了总量。所以我一般建议优先选带编码器的直流减速电机或者带霍尔传感器的无刷电机。这类系统成本高一些但编码器装在电机轴或出轴侧位置真值能持续读回来。如果坚持用步进电机必须把编码器加在传动末端并且编码器线数不能太低。每圈 12 个脉冲是底线四倍频后每圈 48 个计数低于这个分辨率低速堵转很难被及时察觉。选型时还要把丝杠导程和减速比纳入计算电机每转的编码器计数乘以减速比再除以丝杠导程得到的分辨率应优于系统最小流量档位对应的推进距离。拿 0.1 mL/h 的低速档来说如果注射器内径 20 mm每 10 分钟推进 0.53 mm对应到编码器上至少要有几十个计数否则速度闭环在低速段就是靠噪声在工作。2.2 用 STM32 定时器输出 20kHz PWM 的参数确认电机控制常用的 PWM 频率是 20 kHz高于人耳听觉范围还能让电感的电流纹波可控。以 STM32F103 为例内核和APB1总线都是 72 MHzTIM2 挂在 APB1 上。设置预分频 PSC0自动重载 ARR3599输出频率就是 72 MHz / 3600 20 kHz占空比分辨率约为 0.028%。如果从 F103 移植到 G431 这类新系列定时器时钟变为更高频率PSC/ARR 必须重新算。用 CubeMX 更换芯片型号后时钟树会自动重配但工程里手动写的定时器初始化代码仍会生效出问题先核对这两行。配置 PWM 输出的常见代码如下/* 以 STM32F103 HAL 为例TIM1_CH1 输出 20kHz PWM */ TIM_HandleTypeDef htim1; htim1.Instance TIM1; htim1.Init.Prescaler 0; /* 72MHz 不分频 */ htim1.Init.Period 3600 - 1; /* 20kHz, 周期 50us */ htim1.Init.CounterMode TIM_COUNTERMODE_UP; HAL_TIM_PWM_Init(htim1); TIM_OC_InitTypeDef oc {0}; oc.OCMode TIM_OCMODE_PWM1; oc.Pulse 1800; /* 初始 50% 占空比 */ oc.OCPolarity TIM_OCPOLARITY_HIGH; HAL_TIM_PWM_ConfigChannel(htim1, oc, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1);代码里 PSC 决定计数时钟分频ARR 决定计数周期两者共同决定 PWM 频率 Pulse 是比较值运行时用__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, pulse)更新占空比即可。注意 TIM1 和 TIM8 是高级定时器PWM 输出前还要把主输出使能HAL_TIM_PWM_Start正确调用曾有人在 F103 上只配置了 BRK 相关寄存器没开主输出波形怎么都出不来。频率改到 16 kHz 以下时电机啸叫会明显变大不建议把周期拉得太长。2.3 编码器接入 TIM 编码器模式并换算成速度编码器不用 GPIO 外部中断计数直接用定时器的编码器接口模式硬件自动处理正交相位不用软件判方向。以 TIM2 的 CH1/CH2 接入两路正交信号为例配置代码/* 编码器输入PA0/PA1 - TIM2_CH1/CH2 */ GPIO_InitTypeDef io {0}; io.Mode GPIO_MODE_INPUT; io.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, io); /* PA0/PA1 复用为 TIM2 输入 */ TIM_Encoder_InitTypeDef enc {0}; enc.EncoderMode TIM_ENCODERMODE_TI12; /* TI1TI2 四倍频 */ enc.IC1Polarity TIM_ICPOLARITY_RISING; enc.IC2Polarity TIM_ICPOLARITY_RISING; htim2.Instance TIM2; htim2.Init.Period 0xFFFF; HAL_TIM_Encoder_Init(htim2, enc); HAL_TIM_Encoder_Start(htim2, TIM_CHANNEL_ALL);四倍频模式下TIM2-CNT 的每个计数代表 1/4 个编码器线数。读取差值后按下列公式换算成速度int16_t delta_cnt (int16_t)(TIM2-CNT - last_cnt); last_cnt TIM2-CNT; /* 每 1ms 调用一次delta_cnt/(4*ENC_LINES) 是电机轴圈数 */ float speed_rpm (float)delta_cnt / 4.0f / ENC_LINES / 0.001f * 60.0f;ENC_LINES是编码器每圈线数0.001f是速度环采样周期单位是秒。换算成注射速度时再把转速乘以减速比倒数、丝杠导程或轮周长。关键点编码器信号线必须接在支持编码器模式的定时器通道上而且同一组占用不要和 PWM 通道冲突。参考 IO 规划如下信号STM32 GPIO说明电机 PWMPA8TIM1_CH120kHz 输出驱动芯片 PWM 输入方向控制PC2高电平正转低电平反转驱动使能PC3高电平使能驱动输出电流 ADCPA612 位 ADC 采样电机工作电流编码器 APA0TIM2_CH1正交编码输入编码器 BPA1TIM2_CH2正交编码输入2.4 驱动与隔离方向、使能和电流上限的 IO 规划驱动芯片选择上常见做法是使用片内集成 H 桥的半桥驱动如 DRV 系列、TB6612、IFX 系列等根据电机额定电流选用。驱动输出能力要留有 1.5 倍以上余量堵转瞬间冲击电流很容易超过额定值。驱动芯片的电流检测引脚引出后接一个采样电阻放大再进 STM32 的 ADC采样窗口避开 PWM 开关瞬间通常取中心对齐采集中间点这样读到的才是平均电流而不是毛刺。隔离问题在这类设备上比较重要电机电源是 24 V 或 12 VSTM32 是 3.3 V 逻辑PWM 信号直接过去容易把逻辑电平拉坏。我会在 PWM、方向、使能三根线和驱动之间放数字隔离器编码器信号返回时也做电平转换或隔离。共地处理要统一模拟地、功率地单点相连避免电机启停时逻辑电压出现尖峰。这步没做好后面串口看到的 PID 曲线会莫名出现毛刺而且往往只在电机加电才出现。3. 用 PID 做注射速度闭环motorcontrol 的核心实现3.1 位置环和速度环为什么必须串级只做速度闭环电机会稳定地转但注射泵最终关心的是“一段时间内走了多少距离”也就是位置。速度环的稳态误差不会完全消失位置就会慢慢漂。反过来只做位置环响应太慢遇到阻力突变容易超调。所以 motor control 的常见结构是串级外环位置环输出作为内环速度环的目标值内环速度环输出 PWM 占空比。速度环跑 1 kHz位置环跑 1050 Hz内外环频差足够大才稳定。位置环的输入是目标位置与当前编码器累计位置的差经 P 控制或 PI 控制后限幅输出作为速度目标。例如设定 10 秒推完 5 mm位置环输出目标速度约 30 mm/min。如果外环直接输出大速度值内环会尽力跟随但当活塞到达终点时位置误差减小速度目标也会平滑降到零不会冲过头。这就是先串级再调参的价值。3.2 PID 代码放在定时器中断的正确位置速度环的关键是代码执行时间必须稳定否则采样间隔抖动会直接变成速度噪声。常见做法是把速度环放在基础定时器的更新中断里中断频率就是采样频率。1 kHz 中断里只做三件事读编码器、跑 PID、更新占空比。避免在中断里做浮点打印、Flash 写入、串口等待。增量式 PID 实现对比位置式更适合做限幅和抗积分饱和typedef struct { float kp, ki, kd; float target; /* 目标速度 */ float integral; /* 积分累加值 */ float last_error; /* 上一次误差 */ } PidCtrl; float pid_run(PidCtrl *pid, float actual) { float error pid-target - actual; float i_term pid-ki * pid-integral; float d_term pid-kd * (error - pid-last_error); float out pid-kp * error i_term d_term; pid-integral error; if (pid-integral 200.0f) pid-integral 200.0f; if (pid-integral -200.0f) pid-integral -200.0f; pid-last_error error; if (out 1000.0f) out 1000.0f; if (out -1000.0f) out -1000.0f; return out; }代码中pid-integral限制在 ±200是为了防止低速长时间堵转时积分饱和。out限幅到 ±1000对应 PWM 占空比的偏移量例如 50% 基线上加 100 就是增加约 10% 占空比。中断里调用pid_run后把返回值换算成占空比阈值写入比较寄存器。注意pid-target由位置环更新中断外用volatile修饰避免编译器缓存导致读不到更新值。3.3 用串口把 PID 曲线“拉平”再调参只靠示波器看 PWM 不够直观我会把目标速度、实际速度、PID 输出、误差四个量周期性地从串口发出来。发送频率不能太高否则中断或主循环被阻塞一般每 50 ms 打一条。重定向printf到 USART 后代码像这样/* 主循环里每 50ms 执行一次 */ void debug_send_pid(PidCtrl *pid, float speed_actual, float pwm_out) { char buf[128]; int n snprintf(buf, sizeof(buf), target%.2f actual%.2f out%.1f err%.2f\r\n, pid-target, speed_actual, pwm_out, pid-target - speed_actual); HAL_UART_Transmit(huart1, (uint8_t*)buf, n, 10); }上位机用串口绘图工具打开选择按回车分隔的数据就能看到一条条曲线。STM32 串口调试 PID 的重点是观察三段启动瞬间有没有超调、稳态时实际速度发散还是收敛、阻力变化后多久追回目标值。超调大就降 kp 或加 kd稳态有静差就加 ki曲线高频抖动则检查编码器读数是不是带了噪声不要急着调参。3.4 PID 初值表和调节顺序不同电机的电气常数和机械惯量差别很大但初值可以从一个基准开始试。速度环目标的量纲是 mm/min实际速度也是 mm/mink 值就按这个量纲取。常用初值推荐如下参数初值范围现象与调整方向kp0.52.0低于 0.3 响应慢高于 3 容易啸叫ki0.010.05偏大导致低频振荡静差消不掉再加kd0.0010.005抑制超调但对编码器噪声敏感积分限幅100300低速档用小限幅高速档加大调节顺序按“先 P、再 I、最后 D”的经典路数。先只保留 kp给定一个中等速度目标看实际速度跟不跟得上出现等幅振荡kp 减一半再试。跟随稳定后加 ki每次增加 0.005观察到静差被吃干净就停加多了曲线会出现低频波浪。最后加 kd 压启动超调起始设 0.001对噪声敏感就退回零。整个调参过程要保证编码器信号敢上电测速否则调出来的是假参数。4. 监控与保护堵转、超速、位置与掉电保持4.1 堵转检测两个维度同时判断堵转是注射系统里最危险的异常电机卡住但 PWM 还在推轻则烧驱动重则管路压力异常。只用电流判据不可靠因为不同阻力下正常工作电流差别很大。我会同时看编码器和电流PWM 持续输出但编码器计数长时间不变化判定为停转采样电流同时超过堵转阈值立即切断输出并置错误标志。typedef struct { int16_t last_cnt; uint32_t last_time_ms; uint32_t stall_ms; /* 持续静置时间 */ uint8_t triggered; } StallDetect; void stall_check(StallDetect *sd, int16_t now_cnt, uint32_t now_ms) { if (abs((int16_t)(now_cnt - sd-last_cnt)) 2) { if (now_ms - sd-last_time_ms STALL_TIMEOUT_MS) { sd-triggered 1; /* 200ms 内计数变化不足 2 */ } } else { sd-last_cnt now_cnt; sd-last_time_ms now_ms; } }STALL_TIMEOUT_MS一般取 200500 ms按最低速度档算一下最低速时每 1 ms 可能只有 1 个计数阈值设 2 个计数可避免误判。电流判据作为第二道闸采样值超过预设上限立即刹车。注意堵转后不要马上松 PWM先置位错误码等上位机确认清除错误再重新使能。4.2 超速与位置越限监控超速往往是 PID 参数振荡或外力拖动导致。当实际速度超过目标速度的 1.2 倍并且持续时间超过采样窗口就应触发超速保护。监控逻辑放在速度环中断里用一个简单的计数器if (speed_actual pid.target * 1.2f) { if (over_speed_cnt 10) { error_flags | ERROR_OVER_SPEED; disable_motor(); } } else { over_speed_cnt 0; }位置越限需要区分边界和软限位。编码器累计位置超过软件软限位就减速接近碰到硬限位或超出最大安全行程立即停机。位置记录最好带符号正转加计数、反转减计数否则反转后位置监控会错乱。上电时先执行回零动作寻找机械原点或限位开关清除累计位置基准这是位置监控可靠运行的前提。4.3 掉电瞬间位置保持与再启动衔接注射中途掉电最怕重新上电后不记得之前推了多少直接从头开始会造成剂量超量。位置信息要能在掉电瞬间保存下来。STM32 内部 Flash 是常见存储介质但擦除整扇区需要时间掉电瞬间可能来不及。我会用双扇区方案正常运行期间每 30 秒把当前位置交替写入两个 Flash 扇区写完一个扇区后擦除另一个掉电检测中断到来时只需写当前值到已经擦好的扇区。掉电检测使用 PVD 可编程电压检测触发外部中断void PVD_IRQHandler(void) { if (__HAL_PWR_GET_FLAG(PWR_FLAG_PVDO)) { /* 掉电瞬间保存位置到备用扇区 */ HAL_FLASH_Unlock(); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, BACKUP_SECTOR_ADDR, motor_position); HAL_FLASH_Lock(); } __HAL_PWR_CLEAR_FLAG(PWR_FLAG_PVDO); }这种写法的前提是BACKUP_SECTOR_ADDR所在扇区已经擦除过否则HAL_FLASH_Program会返回错误。PVD 阈值要选得比系统工作电压低、比复位电压高给中断留出充足时间。如果系统有 RTC 备份寄存器且 VBAT 持续供电也可以把位置存进备份寄存器速度更快但要注意备份寄存器断电即失存储周期和应急电源策略要一致。4.4 错误码设计与状态上报结构体监控状态要能被上位机或本地屏准确识别错误码设计要稳定。一个字节错位就能定位问题后续加历史记录也更方便。以下是这套系统的错误码表可以作为工程里的枚举定义错误码含义处理建议0x00正常无操作0x01堵转切断输出等待人工清除0x02超速记录速度值降 PWM0x03位置越限停机并提示回零0x04掉电恢复上电后核对存储位置0x05电流超限检查机械阻力与驱动电流上报结构体建议用固定长度串口协议里带上帧头、数据长度和 CRCtypedef struct { uint8_t head; uint8_t len; uint8_t status; uint8_t error; uint32_t position; float speed_actual; uint16_t crc; } MotorMonitorFrame;MotorMonitorFrame定义后要检查对齐串口发送时用结构体指针转字节流需注意小端字节序。这里的关键是监控状态和错误码必须记录在 RAM 里掉电恢复时先读 Flash 中的错误码再决定是否允许自动重启。注射系统的逻辑是发生堵转或超速后不允许电机自动恢复必须由操作人员确认并手动复位避免设备在无人看管时重新推进。5. 系统联调与参数标定技巧5.1 用梯形斜坡避免机械冲击直接让速度环从零跳到目标值活塞和管路会因为冲击出现一个速度尖峰低速注射还会造成起始段过冲。我会在目标速度入口加斜坡限幅每 1 ms 速度环周期让目标值增加固定步长模拟加速过程。float ramp_update(float target, float step) { static float current 0.0f; if (target current) { current step; if (current target) current target; } else if (target current) { current - step; if (current target) current target; } return current; }step取最大加速度除以速度环频率。例如期望 500 ms 内完成从零到 30 mm/min 的加速step 30 / 500 0.06。停机时同样限幅减速避免突然刹停产生负压。带负载后若出现爬行把加速时间适当拉长到 1 秒机械响应会平滑很多。5.2 全行程标定与验证手段试运行阶段不能只看曲线平滑要看累计位置是否与设定一致。我会在注射过程中每推进固定行程打一个标记比如每推进 10 mm 在方格纸上记录一次时间反推各区间平均速度实测速度与目标速度误差应在标定精度范围内若某段误差明显偏大基本指向机械回差或编码器松动。空载测试和带负载测试要分开做带载后 PID 的 ki 往往需要略增以克服静摩擦。调好的参数按高速档、低速档分别保存不要用一组参数硬跑全量程。5.3 现场联调的排查顺序联调时容易遇到的现象和排查顺序整理如下出现异常先看电机有没有转再看编码器读数有没有跟随最后才动 PID。现象可能原因排查顺序电机嗡嗡响不出力PWM 频率太低、使能信号没拉高测波形、量使能引脚堵转误报频繁编码器接线虚、CNT 不变化用手转电机看串口速度速度波动大电流采样噪声进入 PID看 PWM 与电流波形掉电恢复位置不对Flash 扇区未预擦除检查备用扇区状态最后的调试技巧是给已经调好的参数在 Flash 里留一份出厂备份和一份运行参数区参数修改可以随时回滚。固件升级时备份区不动避免一次参数错调就让整机返厂。调好后把错误码表和标定记录冻结在工程里后续改机械结构或更换电机时按 PWM 频率、编码器线数、速度环初值、堵转阈值的顺序重新走一遍即可。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价