资讯详情

基于STM32的机械臂控制系统:PID与编码器闭环调参实践

📅 2026/9/17 14:16:35 | 华诺云谱 👁 阅读
基于STM32的机械臂控制系统:PID与编码器闭环调参实践
简介《工业机器人机械臂控制系统设计》是一份聚焦机器人控制领域的技术文献面向自动化、机电一体化专业学生及从事机械臂控制的工程人员帮助读者理解低成本机械臂控制方案的完整设计思路与实现路径。内容以STM32微控制器为核心搭建机械臂控制体系借助PID控制理论优化运行路径、提升鲁棒性与稳定性并通过力矩传感与电流检测模块判断力矩变化、补偿自重影响硬件层面讨论直流有刷电机选型、基于增量型编码器的位置方案与H桥驱动电路设计软件层面梳理主程序与中断程序结构及PID算法实现最终以仿真曲线验证方案是否科学有效。压缩包为单个PDF文件约1.15MB阅读与检索都比较方便适合打印存档或作为参考文献随时查阅。目前已有306人学习下载可结合文中知识点用于课程设计、毕业设计选题以及控制算法入门参考。1. STM32 机械臂控制系统的成本与实时性权衡一台 5 自由度机械臂的关节控制板如果选用 DSP 方案光是主控加外围就要吃掉大半预算换成 STM32F103 或 F407 级别的芯片省下的钱可以买到分辨率更高的增量式编码器和更低温漂的电流采样电阻。这个选题来自工业机器人机械臂控制系统设计里的一个判断在关节转角有限、负载中等的场景下控制器算力过剩并不一定带来更好的轨迹跟踪反倒是采样周期、编码器分辨率和电流环响应决定最终手感。很多人第一次做机械臂电机能转就以为成了真正上电才发现低速爬行、停不准、一碰就抖。适合已经能跑通单关节转动、但卡在“一动就抖、一停就过冲”的开发者。关键词里的工业机器人、机械臂、控制系统、STM32、PID、编码器接下来都会落到具体寄存器和参数上。2. 硬件驱动链路拆解有刷电机、H 桥与增量式编码器2.1 有刷直流电机在关节驱动里的选型边界机械臂关节驱动常见两条路有刷直流电机和无刷直流电机。无刷电机寿命长、效率高但驱动要三相逆变加换相逻辑低速大扭矩时还得做电流环成本和调试量都上一个台阶。有刷电机在启动和调速方面灵敏度更高控制方便成本投入更低配合 H 桥就能做到正反转和 PWM 调速。机械臂关节转角通常不超过 ±180°频繁启停、负载中等有刷电机的碳刷磨损在这个工况下并不致命。对比项有刷直流电机无刷直流电机驱动电路成本低单 H 桥即可高三相逆变加换相启动转矩大低速线性好需霍尔或无感算法调速线性度电压-转速近似线性依赖换相与电流环维护碳刷定期更换寿命长适用场景小负载、频繁启停高速、长时连续运转选型时还要看额定电压和堵转电流。24V 有刷电机配 12 位编码器在 5 自由度桌面机械臂上已经能跑到 0.1° 左右的定位分辨率。如果追求具身智能机械臂那种柔顺控制有刷电机加力矩传感反而比无刷方案更容易做重力补偿算法因为电流和力矩的线性关系更直接。2.2 H 桥、IR2106S 与 74ACT244 的驱动链路电机驱动用 4 个相同功率的 MOSFET 搭 H 桥电机接在桥臂中间。IR2106S 是半桥栅极驱动芯片负责把逻辑电平抬到 MOSFET 栅极需要的电压。问题在于 STM32 的 PWM 引脚输出电流只有几毫安直接推 IR2106S 的输入端会边沿变缓、发热增加所以中间加一级 74ACT244 做功率增强。常见做法是TIM1 输出两路互补 PWM经 74ACT244 缓冲后送入 IR2106S 的 HIN 和 LINIR2106S 再驱动上下桥 MOSFET。// TIM1 互补 PWM 初始化20kHz 载波驱动 H 桥左/右半桥 // 系统时钟 72MHz预分频 0自动重装载 3599 static void Motor_PWM_Init(void) { GPIO_InitTypeDef gpio {0}; TIM_TimeBaseInitTypeDef tb {0}; TIM_OCInitTypeDef oc {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); tb.TIM_Prescaler 0; tb.TIM_CounterMode TIM_CounterMode_Up; tb.TIM_Period 3599; // 72MHz / 3600 20kHz tb.TIM_ClockDivision TIM_CKD_DIV1; tb.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM1, tb); oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_OutputNState TIM_OutputNState_Enable; // 互补输出给 IR2106S oc.TIM_Pulse 0; oc.TIM_OCPolarity TIM_OCPolarity_High; oc.TIM_OCNPolarity TIM_OCNPolarity_High; oc.TIM_OCIdleState TIM_OCIdleState_Reset; TIM_OC1Init(TIM1, oc); // CH1 左半桥 TIM_OC2Init(TIM1, oc); // CH2 右半桥 TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }20kHz 载波避开人耳可听范围减少电机啸叫。死区时间要在 TIM1 的 BDTR 寄存器里设典型 500ns 到 1μs防止上下桥直通。TIM_Pulse 对应 CCR1/CCR2改这两个值就能改占空比。74ACT244 的使能脚记得拉低有效别悬空。2.3 增量式编码器的 TIM3 编码器模式配置编码器分增量型和绝对型。增量型以前一时刻为基准测角度改变量小角度精度高角度增大后误差会累积绝对型以运行后时刻为基准大角度不累积误差但小角度分辨率差。机械臂转角范围有限上电回零后不会累积大误差所以选增量型更划算。// TIM3 编码器接口模式PA6/PA7 接 A/B 相四倍频计数 void Encoder_Init(void) { GPIO_InitTypeDef gpio {0}; TIM_TimeBaseInitTypeDef tb {0}; TIM_ICInitTypeDef ic {0}; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; // 编码器开漏输出时需外接上拉 GPIO_Init(GPIOA, gpio); tb.TIM_Prescaler 0; tb.TIM_Period 65535; // 16 位满量程靠软件扩展高位 tb.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, tb); ic.TIM_Channel TIM_Channel_1; ic.TIM_ICPolarity TIM_ICPolarity_Rising; ic.TIM_ICSelection TIM_ICSelection_DirectTI; ic.TIM_ICPrescaler TIM_ICPSC_DIV1; ic.TIM_ICFilter 0x0F; // 最大滤波抑制电机换向毛刺 TIM_ICInit(TIM3, ic); ic.TIM_Channel TIM_Channel_2; ic.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInit(TIM3, ic); TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_SetCounter(TIM3, 0); TIM_Cmd(TIM3, ENABLE); }TIM_EncoderMode_TI12 表示 A/B 相都参与计数实现四倍频。CNT 是 16 位溢出后在 TIM3 中断里累加高 16 位得到 32 位绝对位置。ICFilter 设 0x0F 是最大滤波适合长线、电机干扰大的场合信号干净可以降到 0x03否则会吃掉高速段的脉冲。3. PID 闭环与 TIM2 10ms 位置环调度3.1 位置式与增量式 PID 在关节控制里的取舍位置式 PID 输出的是绝对控制量直接映射成 PWM 比较值增量式 PID 输出的是控制量增量适合带积分分离的速度环。机械臂关节要的是“转到某个编码器计数”位置式更直观也更容易做输出限幅。但位置式有一个坑积分项会一直累加电机堵转时输出饱和松开后要很久才退饱和表现就是过冲后回不来。所以积分限幅和 PWM 限幅必须同时做。3.2 带积分限幅和 PWM 限幅的 PID 实现typedef struct { float Kp, Ki, Kd; float target; // 目标编码器计数 float integral; // 积分累加 float last_err; float out_min, out_max; float integral_max; } PID_t; // TIM2 中断周期 10ms 调用 float PID_Compute(PID_t *pid, float feedback) { float err pid-target - feedback; float out; pid-integral err; // 积分限幅防止长时间偏差导致积分饱和 if (pid-integral pid-integral_max) pid-integral pid-integral_max; if (pid-integral -pid-integral_max) pid-integral -pid-integral_max; out pid-Kp * err pid-Ki * pid-integral pid-Kd * (err - pid-last_err); pid-last_err err; // 输出限幅映射到 PWM 比较值 if (out pid-out_max) out pid-out_max; if (out pid-out_min) out pid-out_min; return out; }Kp 决定响应速度Ki 消除稳态误差Kd 抑制超调。integral_max 一般取 out_max / Ki 的 0.6 到 0.8太小会导致稳态误差消不掉太大等于没限。10ms 周期下 Kd 对编码器噪声很敏感差分速度建议做一阶低通截止频率取 50Hz 左右。输出 out 再线性映射到 CCR 值注意死区和方向。3.3 中断优先级与调度顺序中断源优先级周期/触发职责TIM2110msPID 计算、PWM 更新CAN RX2事件接收目标位置与使能指令TIM33溢出编码器高 16 位扩展USART14事件上报位置、调试数据PID 中断里不要放浮点打印串口上报放到主循环否则 10ms 节拍会被打断。CAN 接收中断只做解析和置标志目标位置更新放在 PID 里统一生效。如果做机械臂轨迹规划目标位置由上位机按插补周期下发PID 只负责跟踪不要把插补算法塞进 10ms 中断。4. 串口整定与仿真曲线拟合从阶跃响应到 PID 参数4.1 逐参数整定流程整定顺序是先比例、再积分、最后微分。每次只改一个参数用串口发阶跃指令记录编码器位置序列。常见做法是用上位机画曲线肉眼判断超调量和调节时间比盯着数字快得多。步骤操作观察现象调整方向1Kp 从 0 逐步增大KiKd0响应变快出现等幅振荡取振荡前 60% 的 Kp2加 Ki从小往大稳态误差下降出现过冲减小 Ki 或加积分限幅3加 Kd超调减小噪声放大配合编码器滤波4缩采样周期响应更快抖动增加10ms 到 5ms 逐步试一旦曲线超调量超过 10%振荡会明显加剧。这时候先降 Kp再补一点 Kd。如果 PI 控制后稳态误差还在不要一味加 Ki先看编码器分辨率是不是不够或者电机死区有没有补偿。4.2 上位机拟合脚本import numpy as np import serial import matplotlib.pyplot as plt from scipy.optimize import curve_fit # 二阶欠阻尼阶跃响应模型 def step_model(t, K, wn, zeta): wd wn * np.sqrt(1 - zeta**2) return K * (1 - np.exp(-zeta * wn * t) * (np.cos(wd * t) (zeta / np.sqrt(1 - zeta**2)) * np.sin(wd * t))) ser serial.Serial(COM3, 115200, timeout0.1) raw [] while len(raw) 300: line ser.readline().decode().strip() if line: raw.append(float(line)) y np.array(raw) t np.arange(len(y)) * 0.01 # 10ms 采样周期 p0 [y[-1], 10.0, 0.5] popt, _ curve_fit(step_model, t, y, p0p0, maxfev10000) print(K%.3f wn%.3f zeta%.3f % tuple(popt)) plt.plot(t, y, labelmeasured) plt.plot(t, step_model(t, *popt), --, labelfit) plt.legend(); plt.xlabel(time/s); plt.ylabel(encoder count) plt.show()脚本从串口读 300 个 10ms 间隔的位置样本用二阶欠阻尼模型拟合得到自然频率 wn 和阻尼比 zeta。zeta 小于 0.6 说明超调偏大加 Kdwn 太小说明响应慢加 Kp。K 是稳态增益用来判断有没有静差。实际调试时把 measured 和 fit 画在同一张图曲线贴合度比单个数字更有说服力。4.3 判据与采样周期指标合格范围不达标时先动谁超调量 10%加 Kd 或降 Kp调节时间 0.3s加 Kp 或缩采样周期稳态误差 2 个编码器计数加 Ki 或检查死区振荡次数 2 次降 Kp加微分滤波采样周期从 10ms 缩到 5ms实时性更好但 PWM 噪声更容易进微分项。如果缩周期后抖动变大先把 Kd 减半再观察。机械臂重力补偿算法如果需要更快的力矩响应可以把电流环单独放到 1ms 定时器里位置环保持 10ms。5. 力矩保护与 CAN 总线指令接入的工程细节5.1 电流采样与力矩估算有刷电机的输出力矩近似正比于电枢电流用采样电阻加运放加 ADC 注入通道就能估力矩。常见做法是在 H 桥下桥臂串 5mΩ 采样电阻运放增益 20 倍STM32 的 ADC 注入通道在 PWM 周期中点采样避开开关噪声。把电流采样放在 1ms 定时器里独立于 10ms 位置环急停路径更短。力矩超过额定值 1.5 倍时直接封 PWM而不是等位置环下一拍再处理。这就是工业机器人里常说的力矩参数传感模块和电流参数检测模块的最小实现。5.2 CAN 帧格式与超限停机CAN ID数据长度字段含义0x1204Data[0..3]目标编码器计数小端0x1211bit0 使能bit1 急停关节使能与急停0x1222Data[0..1]电流上限单位 10mA// CAN 接收回调0x120 为目标位置0x121 为使能/急停 void CAN_RxHandler(CanRxMsg *msg) { switch (msg-StdId) { case 0x120: // 4 字节小端目标编码器计数 g_target (int32_t)(msg-Data[0] | (msg-Data[1] 8) | (msg-Data[2] 16) | (msg-Data[3] 24)); break; case 0x121: g_enable msg-Data[0] 0x01; if (msg-Data[0] 0x02) { // 急停位 Motor_PWM_Set(0, 0); g_fault FAULT_ESTOP; } break; default: break; } }急停位用 CAN 帧的 bit1而不是单独发一帧停止指令这样从节点可以在同一个接收中断里立即封 PWM。接收中断里不要做浮点运算只置标志和写寄存器。如果做多关节协同主机按 10ms 广播一次目标位置从节点在 TIM2 中断里统一更新避免 CAN 到达时刻不同导致关节不同步。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。