恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

STM32F1双闭环PID电机控制:位置式PID+编码器反馈实战

  • 首页
  • 资讯中心
  • /
  • STM32F1双闭环PID电机控制:位置式PID+编码器反馈实战

相关资讯

STM32F103VET6智能手表方案:硬件原理图与蓝牙调试全流程 2026/9/12 13:29:56
Raspberry Pi Pico硬件开发实战:GPIO模式、PWM调光与USB Host固件详解 2026/9/12 13:29:56
生成式引擎优化(GEO)技术解析与应用 2026/9/12 13:24:55

最新资讯

钢结构围护行业口碑评价体系与选型指南
车载蓝牙开发实战:从HCI协议栈到AudioFlinger的全链路解析
机器学习基础与工程实践全解析
高校智慧后勤系统:物联网与云原生的数字化转型实践
Python入门:从Hello World到心形代码的编程之旅
济宁壁挂炉上门维修 本地靠谱师傅 不点火、故障码、漏水维修

今日推荐

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现
【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)
【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

本周热门

超人会飞不算本事:系统稳定依赖清晰规则与边界设计
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
基于CNN的调制信号识别:MATLAB实现时频图分类实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

STM32F1双闭环PID电机控制:位置式PID+编码器反馈实战

发布时间:2026/9/12 13:29:56
STM32F1双闭环PID电机控制:位置式PID+编码器反馈实战 简介本资源是一套基于STM32F1系列MCU实现直流有刷电机位置-速度双闭环PID控制的完整嵌入式开发工程面向嵌入式初学者、自动化专业学生及电机控制实践者解决电机精确定位与动态调速中常见的响应滞后、超调大、稳态误差等问题。压缩包共315个文件含110个头文件.h定义外设与PID结构体、74个C源文件.c实现HAL库驱动、编码器测速、双环PID算法及主控逻辑另有.o/.d等编译中间文件及Keil工程uvprojx、调试配置dbgconf和固件输出axf/hex整体9.45MB结构规范便于理解底层调度与模块划分。已有339人学习下载提供可直接编译运行的全功能代码涵盖ADC采样、TIM定时中断、PWM输出、编码器位置解算、位置式PID参数整定及异常保护逻辑附带典型HAL驱动文件如hal_adc.c、hal_tim.c等助读者深入掌握电机控制工程落地的关键细节。1. 这不是普通电机控制STM32-F1上用位置式PID实现双闭环本质是把“定位精度”和“动态响应”拆成两个时间尺度来解你手头那块STM32F103C8T6跑着裸机HAL库接的是带AB相编码器的直流有刷电机——如果只用单级PWM开环调速转到指定角度时抖动、超调、停不准加个速度环后加速快了但定位毛刺反而更明显而这份源代码真正落地的是位置环外环输出目标转速 → 速度环内环实时跟踪该转速 → 两环共用同一套定时器中断同一组ADC采样数据流的硬实时耦合结构。它不依赖CubeMX自动生成的冗余初始化所有PID计算在TIM2_UP_IRQHandler中完成误差更新、积分限幅、微分滤波全部在32位定点运算下闭环实测在1kHz控制周期下位置稳态误差≤0.3°阶跃响应超调5%。适合正在做机器人关节模组、精密云台或工业定位平台的嵌入式工程师尤其当你发现CubeMX生成的HAL驱动在高频率PID运算中出现ADC采样偏移、TIM捕获丢失脉冲、或HAL_Delay()导致控制周期抖动时这份代码就是你该撕开重读的底层范本。2. 双闭环架构设计与HAL库资源映射为什么必须用TIM2做主控中断而不能用SysTick2.1 位置环与速度环的时间尺度分离原理双闭环不是简单地把两个PID函数串起来而是通过控制带宽分层实现性能解耦位置环带宽通常设为10–50Hz对应机械系统惯性负责消除大范围定位误差速度环带宽需达200–500Hz受限于电机电感和PWM开关频率用于抑制扰动、抑制振荡。若两环共用同一中断源且未做时间隔离位置环的慢变指令会污染速度环的快速响应。本项目采用单一定时器中断TIM2 UP触发全链路计算但在函数内部用静态变量标记阶段每4次中断即250Hz执行一次位置环计算pos_pid_calc()每次中断都执行速度环计算spd_pid_calc()这样既保证速度环实时性又避免位置环高频扰动。关键参数在pid_config.h中定义#define POS_LOOP_PERIOD_MS 4 // 位置环周期 4 * TIM2中断周期 #define SPD_LOOP_PERIOD_MS 1 // 速度环周期 TIM2中断周期 #define TIM2_PERIOD_US 1000 // TIM2计数周期1ms → 中断频率1kHz2.2 HAL库外设选型强制约束ADCTIMGPIO的物理绑定关系STM32F103的编码器接口必须用TIM1/TIM2/TIM3/TIM4的通道1/2输入而本项目选用TIM2的CH1/CH2接编码器AB相——这直接锁定了以下HAL配置TIM2配置为编码器模式HAL_TIM_Encoder_Start(htim2, TIM_CHANNEL_ALL)同时启用更新中断__HAL_TIM_ENABLE_IT(htim2, TIM_IT_UPDATE)作为主控节拍ADC1仅启用通道1PA0采集电位器设定的目标位置分辨率12位采样时间71.5周期ADC_SAMPLETIME_71CYCLES_5以匹配电机机械响应TIM3复用为PWM输出PB0/PB1互补输出模式死区时间128nshtim3.Init.RepetitionCounter 0x7F提示若强行改用TIM1做编码器会导致HAL_TIM_Encoder_Start()失败——F1系列TIM1无编码器专用输入滤波器AB相脉冲边沿抖动会引发计数错乱。这是HAL库底层寄存器映射的硬约束非代码逻辑可绕过。2.3 位置式PID结构体定义与HAL兼容性改造标准位置式PID公式为$$u(k) K_p e(k) K_i \sum_{i0}^{k} e(i) \cdot T_s K_d \frac{e(k)-e(k-1)}{T_s}$$但HAL库的int32_t运算易溢出且TIM2中断周期T_s1ms需显式参与计算。源码中pid_struct.h定义typedef struct { int32_t setpoint; // 目标值单位编码器计数值 int32_t feedback; // 实际反馈值单位编码器计数值 int32_t error; // 当前误差 setpoint - feedback int32_t error_last; // 上次误差 int32_t integral; // 积分项已乘Ki*T_s单位控制量增量 int32_t derivative; // 微分项已乘Kd/T_s单位控制量增量 int32_t output; // 最终输出-32768 ~ 32767 // PID参数Q15定点数避免浮点运算 int16_t kp; // 比例增益放大1000倍存储 int16_t ki; // 积分增益放大1000000倍存储 int16_t kd; // 微分增益放大1000倍存储 int32_t integral_limit; // 积分限幅阈值防饱和 } pid_t;注意ki和kd的缩放倍数差异ki需补偿T_s0.001s故放大10⁶倍kd需除T_s故放大10³倍。此设计使所有运算保持整数且output可直接映射到TIM3的CCR1寄存器16位PWM占空比。3. 核心控制流程实现从编码器计数到PWM占空比的全链路代码解析3.1 编码器位置/速度联合解算encoder.cF1系列无硬件速度测量单元必须用软件测速。本项目采用M法测速单位时间脉冲数在TIM2更新中断中完成// TIM2更新中断服务函数1kHz void TIM2_UP_IRQHandler(void) { HAL_TIM_IRQHandler(htim2); static uint32_t last_count 0; static uint32_t count_diff 0; uint32_t current_count; // 读取TIM2计数器自动清零模式值当前编码器脉冲数 current_count __HAL_TIM_GET_COUNTER(htim2); count_diff current_count - last_count; // 单位脉冲数/ms last_count current_count; // 转换为RPM假设每转2000脉冲AB相各1000线则 // speed_rpm (count_diff * 60 * 1000) / 2000 count_diff * 30 motor_speed_rpm (int16_t)(count_diff * 30); // 位置值 TIM2计数器值已含方向正向累加/反向递减 motor_position_cnt (int32_t)current_count; }注意__HAL_TIM_GET_COUNTER()必须在中断内调用若在主循环读取因TIM2持续计数会导致count_diff计算错误。此处利用HAL库的TIM_EncoderMode自动处理AB相正交解码无需手动判断边沿。3.2 位置环与速度环协同计算pid_control.c位置环每4次中断触发速度环每次中断触发共享同一组反馈数据// 位置环计算每4ms执行一次 void pos_pid_calc(pid_t *pos_pid) { static uint8_t cnt 0; cnt; if(cnt 4) return; // 等待4次中断 cnt 0; // 更新误差目标位置来自ADC采样 pos_pid-setpoint adc_get_target_position(); // PA0电位器值*100 pos_pid-feedback motor_position_cnt; pos_pid-error pos_pid-setpoint - pos_pid-feedback; // 位置环输出 目标速度单位RPM int32_t target_speed_rpm pos_pid-kp * pos_pid-error / 1000; target_speed_rpm pos_pid-integral / 1000000; target_speed_rpm pos_pid-kd * (pos_pid-error - pos_pid-error_last) / 1000; // 积分限幅防止超调 if(target_speed_rpm 3000) target_speed_rpm 3000; if(target_speed_rpm -3000) target_speed_rpm -3000; pos_pid-integral pos_pid-ki * pos_pid-error; if(pos_pid-integral pos_pid-integral_limit) pos_pid-integral pos_pid-integral_limit; if(pos_pid-integral -pos_pid-integral_limit) pos_pid-integral -pos_pid-integral_limit; pos_pid-error_last pos_pid-error; spd_pid_set_target(target_speed_rpm); // 将位置环输出传给速度环 } // 速度环计算每1ms执行一次 void spd_pid_calc(pid_t *spd_pid) { // 速度反馈 motor_speed_rpm来自TIM2中断 spd_pid-feedback motor_speed_rpm; spd_pid-error spd_pid-setpoint - spd_pid-feedback; // 速度环输出 PWM占空比-32768 ~ 32767 spd_pid-output spd_pid-kp * spd_pid-error / 1000; spd_pid-output spd_pid-integral / 1000000; spd_pid-output spd_pid-kd * (spd_pid-error - spd_pid-error_last) / 1000; // 输出限幅映射到TIM3 CCR1 if(spd_pid-output 32767) spd_pid-output 32767; if(spd_pid-output -32767) spd_pid-output -32767; // 写入PWM寄存器TIM3_CH1 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, (uint32_t)(spd_pid-output 32768)); }关键逻辑说明位置环输出target_speed_rpm是有符号整数正负代表转向速度环接收后直接作为spd_pid-setpoint其误差计算自然包含方向信息。__HAL_TIM_SET_COMPARE()将output偏移32768后写入CCR1使-32768→0%32767→100%占空比完美匹配H桥驱动需求。3.3 HAL库关键初始化裁剪main.c精简版CubeMX生成的初始化常含冗余外设本项目仅保留必需模块int main(void) { HAL_Init(); SystemClock_Config(); // 72MHz HCLK // 仅初始化TIM2编码器、TIM3PWM、ADC1目标位置 MX_TIM2_ENCODER_Init(); // CH1PA0, CH2PA1 MX_TIM3_PWM_Init(); // CH1PB0, CH2PB1 MX_ADC1_Init(); // PA0 for potentiometer // 启动外设 HAL_TIM_Encoder_Start(htim2, TIM_CHANNEL_ALL); HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); HAL_ADC_Start(hadc1); // 开启TIM2更新中断主控节拍 HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn); while(1) { // 主循环仅做低频任务串口调试、LED状态指示 if(usart_debug_flag) uart_send_debug_data(); HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8); HAL_Delay(100); } }注意MX_TIM2_ENCODER_Init()中必须关闭TIM2的重复计数器htim2.Init.RepetitionCounter 0否则编码器计数会周期性归零MX_ADC1_Init()中禁用扫描模式和连续转换因只需单次采样电位器。4. PID参数整定与实时调试技巧如何用串口命令动态修改Kp/Ki/Kd并观察阶跃响应4.1 串口指令协议设计usart_debug.c为避免每次改参都重新烧录实现ASCII指令交互指令功能示例P123设置位置环Kp123P500→ Kp500S200设置速度环Kp200S300→ Kp300I10设置位置环Ki10I5→ Ki5单位10⁶倍D5设置位置环Kd5D2→ Kd2单位10³倍R复位所有PID参数R→ 恢复默认值解析逻辑在USART1_IRQHandler中实现// 接收缓冲区 uint8_t rx_buffer[16]; uint8_t rx_index 0; void USART1_IRQHandler(void) { HAL_UART_IRQHandler(huart1); uint8_t ch; HAL_UART_Receive(huart1, ch, 1, 1); if(ch 0 ch 9) { rx_buffer[rx_index] ch; if(rx_index 15) rx_index 0; } else if(ch \r || ch \n) { parse_command(rx_buffer, rx_index); rx_index 0; } } void parse_command(uint8_t *buf, uint8_t len) { if(len 2) return; switch(buf[0]) { case P: pos_pid.kp atoi((char*)buf[1]); break; case S: spd_pid.kp atoi((char*)buf[1]); break; case I: pos_pid.ki atoi((char*)buf[1]) * 1000000; break; case D: pos_pid.kd atoi((char*)buf[1]) * 1000; break; case R: reset_pid_params(); break; } }4.2 阶跃响应观测与参数调整策略连接USB转TTL模块至PA9/PA10用串口助手发送P100后立即发送阶跃指令如旋转电位器使目标位置突变2000脉冲抓取motor_position_cnt和motor_speed_rpm序列Time(ms) | Position(cnt) | Speed(RPM) ------------------------------------- 0 | 0 | 0 10 | 150 | 450 20 | 420 | 1260 30 | 780 | 2340 40 | 1200 | 2800 ← 速度峰值超调 50 | 1650 | 2100 60 | 1920 | 1200 70 | 2080 | 600 80 | 2160 | 300 90 | 2190 | 150 100 | 2198 | 30 ← 稳态误差2脉冲0.36°根据此曲线调整超调过大10%降低位置环Kp或增大速度环Kd增强阻尼响应过慢上升时间50ms增大位置环Kp或速度环Kp稳态误差存在增大位置环Ki注意积分饱和风险低速爬行振荡减小速度环Kd微分噪声放大提示F1系列ADC采样受电源纹波影响若motor_speed_rpm在静止时跳变±50RPM需在encoder.c中添加中值滤波对连续3次count_diff取中位数再计算速度。4.3 常见故障排查表现象可能原因验证方法解决方案电机完全不转TIM3 PWM未启动用示波器测PB0引脚是否有方波检查HAL_TIM_PWM_Start()返回值确认htim3.State HAL_TIM_STATE_READY位置环无响应ADC采样值恒为0HAL_ADC_GetValue(hadc1)打印结果检查PA0是否悬空MX_ADC1_Init()中hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT是否正确速度环输出抖动剧烈编码器AB相接反用手转动电机motor_position_cnt应单调增/减交换PA0与PA1接线或修改TIM2的编码器极性TIM_ICPOLARITY_BOTHEDGE烧毁H桥驱动芯片PWM占空比超过100%printf(PWM%d\n, spd_pid.output)在spd_pid_calc()中增加输出限幅if(spd_pid.output32767) spd_pid.output327675. 进阶优化在不增加硬件的前提下用HAL库原生功能提升抗干扰能力5.1 ADC采样同步触发避免软件延时引入相位差当前方案中adc_get_target_position()在主循环调用与TIM2中断不同步导致位置指令滞后。改用TIM2更新事件触发ADC采样// 修改MX_ADC1_Init()启用硬件触发 hadc1.Instance ADC1; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.ScanConvMode DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T2_TRGO; // TIM2 TRGO触发 hadc1.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_RISING; HAL_ADC_Init(hadc1); // 启动ADC硬件触发 HAL_ADCEx_Calibration_Start(hadc1); HAL_ADC_Start(hadc1); HAL_ADC_Start_IT(hadc1); // 开启转换结束中断 // 在ADC中断中读取目标值 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { target_position_raw HAL_ADC_GetValue(hadc); }此时target_position_raw与TIM2的current_count严格同步于同一毫秒时刻消除了控制环路中的隐含延迟。5.2 微分先行Derivative on Measurement结构防噪声标准PID的微分项作用于误差e(k)会将编码器量化噪声±1脉冲放大。改为作用于反馈量y(k)// 修改spd_pid_calc()中的微分计算 int32_t feedback_diff spd_pid-feedback - spd_pid-feedback_last; spd_pid-derivative spd_pid-kd * feedback_diff / 1000; // 作用于反馈而非误差 spd_pid-output ... - spd_pid-derivative; // 微分项取负号 spd_pid-feedback_last spd_pid-feedback;此结构使微分项仅响应真实速度变化对位置指令阶跃无响应大幅提升抗扰性。5.3 利用HAL库DMA实现零CPU占用的编码器计数当前__HAL_TIM_GET_COUNTER()在中断中执行占用约1.2μs CPU时间。启用DMA可释放CPU// 配置TIM2更新事件触发DMA传输计数器值到内存 hdma_tim2_up.Instance DMA1_Channel7; hdma_tim2_up.Init.Direction DMA_MEMORY_TO_PERIPH; hdma_tim2_up.Init.PeriphInc DMA_PINC_DISABLE; hdma_tim2_up.Init.MemInc DMA_MINC_ENABLE; hdma_tim2_up.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; hdma_tim2_up.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; hdma_tim2_up.Init.Mode DMA_CIRCULAR; hdma_tim2_up.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_tim2_up); // 关联TIM2更新事件到DMA __HAL_LINKDMA(htim2, hdma[TIM_DMA_ID_UPDATE], hdma_tim2_up); __HAL_TIM_ENABLE_DMA(htim2, TIM_DMA_UPDATE); // 在DMA传输完成回调中处理计数 void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma) { if(__HAL_DMA_GET_FLAG(hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma))) { // encoder_count_buffer[]中已存入最新计数值 process_encoder_dma_buffer(); } }此方案将编码器计数读取完全卸载至DMACPU在中断中仅做轻量级数据搬运为后续增加CAN通信或FFT分析预留算力。电机停稳时用万用表测H桥输出端电压若存在100mV直流偏置说明PID积分项未完全清零——此时执行R指令复位参数并检查pos_pid.integral是否在reset_pid_params()中被置零。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号