
简介《自由摆的控制系统》是一份面向电子设计竞赛、嵌入式控制课程设计及自动控制学习者的完整方案文档围绕摆杆摆动过程中保持末端平板稳定这一问题展开可作为动态角度检测与电机随动控制的参考范例。文档共1个docx文件压缩包约36KB篇幅精炼但内容完整便于直接查阅与二次整理。内容以STM32F103VET6为主控涵盖TFT液晶显示、WDD35D4导电塑料角位移传感器实时测角、A/D采样反馈、四宏细分驱动器驱动步进电机等关键环节并给出摆架框架、检测系统、控制驱动系统三部分结构。文中还对控制器、角度检测、电机与电源模块进行方案比较说明选型依据并涉及硬币相对静止、激光笔打靶等基本要求与发挥部分适合读者借鉴硬件搭建、控制思路和方案论证写法。目前已有308人学习下载。1. 自由摆不是“摆着玩”这套平板控制系统到底解决什么问题摆杆一松手末端平板跟着来回摆板上硬币不能滚、激光笔还得稳稳打到靶心——自由摆的控制系统就是为这个场景做的。它用 STM32F103VET6 当控制核心WDD35D4 导电塑料角位移传感器实时测摆杆和平板角度TFT 液晶做状态显示步进电机经四宏细分驱动器带动平板转角。适合正在做嵌入式课程设计、控制类学科竞赛或者想拿一套完整“传感器采样—角度解算—电机执行—闭环调参”链路练手的人。硬件不算贵但机械安装、模拟采样和步进丢步这几个环节每个都能让人调一晚上。2. 方案与硬件架构STM32F103VET6 为核心的三大模块怎么搭这套系统的骨架拆开看很朴素一个摆架负责让平板自由摆动一组传感器负责把角度变成电压一块 STM32 负责算驱动器加步进电机负责把平板拉回目标角度。真正花时间的是选型和电气连接因为自由摆是一个动态系统传感器测的是实时角度电机执行又有延迟任何一环选错后面调 PID 都是白费劲。2.1 控制器和电源为什么不用 5112V 电池怎么分压控制器选型上51 单片机不是不能跑而是跑不动这套东西。自由摆控制需要同时做两路 ADC 采样、中值滤波、角度换算、PID 运算、步进脉冲状态机和 TFT 刷新51 的指令周期和片内 RAM 很快就见底稍微把控制周期压到 5ms 就会顾此失彼。STM32F103VET6 是 Cortex-M3 内核72MHz 主频512KB Flash、64KB RAM片内 ADC 多通道用库函数开发也省事这也是项目正文最终选它的原因。这里有个容易混的点项目正文写的是“8 通道 10 位 ADC”实际 STM32F103 的 ADC 是 12 位初始化时按 12 位右对齐配满量程用 4095如果手里的例程是 10 位版本换算系数要改成 1023两套代码混用角度会直接差 4 倍。电源部分原文给的是 12V 电池经 L7805 和 LM2576 转换后给系统供电。L7805 是线性稳压输出 5V纹波小但压差大时发热明显LM2576 是开关降压效率高适合把 12V 先降到一个中间电压。常见接法是12V 电池直接进步进驱动器的主电源同时经 LM2576 降到 6.5V7V再进 L7805 稳到 5V5V 给 TFT、传感器和电机驱动器逻辑3.3V 由板载 LDO 给 STM32 和 ADC 参考。模拟部分最好单独一路 LC 滤波不要和电机驱动逻辑共用一根细地线否则 ADC 读数会跟着脉冲频率跳。供电轨来源供给对象注意点12V电池步进驱动器主电源并 470μF 电解电容抑制启停跌落6.57VLM2576 降压L7805 输入开关频率噪声要加 LC 滤波5VL7805 线性稳压TFT、WDD35D4、驱动器逻辑加散热片电流余量留 30%3.3V板载 LDOSTM32、ADC 参考模拟地和数字地单点连接引脚分配我一般先落一个头文件后面 ADC、定时器、TFT 都按这个表来改板子只改这里。下面这段只放关键引脚完整工程里还会把按键、串口和限位开关加进去。/* board_pinmap.h —— 自由摆平板控制板引脚分配 */ #ifndef BOARD_PINMAP_H #define BOARD_PINMAP_H #include stm32f1xx_hal.h /* WDD35D4 两路模拟输出一路测摆杆一路测平板 */ #define SENSOR_PLATE_PORT GPIOA #define SENSOR_PLATE_PIN GPIO_PIN_0 /* ADC1_IN0平板倾角 */ #define SENSOR_ARM_PORT GPIOA #define SENSOR_ARM_PIN GPIO_PIN_1 /* ADC1_IN1摆杆角度 */ /* 步进驱动器PUL 走定时器通道DIR/ENA 普通 GPIO */ #define STEP_PUL_PORT GPIOA #define STEP_PUL_PIN GPIO_PIN_2 /* TIM2_CH3 复用推挽 */ #define STEP_DIR_PORT GPIOB #define STEP_DIR_PIN GPIO_PIN_0 #define STEP_ENA_PORT GPIOB #define STEP_ENA_PIN GPIO_PIN_1 /* TFT 液晶SPI1 手动片选刷新时不影响控制节拍 */ #define TFT_CS_PORT GPIOA #define TFT_CS_PIN GPIO_PIN_4 #define TFT_DC_PORT GPIOA #define TFT_DC_PIN GPIO_PIN_5 #define TFT_RST_PORT GPIOA #define TFT_RST_PIN GPIO_PIN_6 #endif这段头文件的作用是把“哪个脚接什么”固定下来。ADC 通道和 GPIO 复用关系必须和 CubeMX 里的配置一致比如 PA0 做 ADC1_IN0 时不能再配成普通输出PUL 脚要设成复用推挽否则定时器波形出不来。实际改板时最容易翻车的是 DIR 和 ENA 接反上电后平板会往一个方向顶死后面避坑章会细说。2.2 角度传感器和电机WDD35D4 与步进电机的组合逻辑测角方案原文对比了三种普通电位器、ADXL345 加速度传感器、WDD35D4 导电塑料角位移传感器。普通电位器便宜但线性度差来回磨损后阻值跳变用在自由摆这种需要连续动态测量的场合角度噪声会直接灌进 PID。ADXL345 是加速度原理静态测倾角很好用但自由摆摆动时传感器同时感受到重力和摆动加速度解算出来的角度在动态段会偏越摆得快越不准。WDD35D4 是电阻分压式角位移传感器转轴带动电刷在导电塑料膜上滑动输出与角度成比例的电压属于绝对位置传感器断电再上电不会丢零位精度也够用。它的代价是电刷有机械磨损安装时要注意同轴度不能让转轴受侧向力。电机选的是步进电机而不是伺服。伺服闭环性能好、高速制动强但价格高配套驱动器也贵步进电机开环控制给脉冲就走16 细分后每步 1.8°/160.1125°对于平板转角补偿已经够细。四宏细分驱动器的作用是把单片机发出的脉冲放大成电机绕组电流同时做细分减弱低频振动。这里要算清楚脉冲当量如果电机轴和平板轴直接连接、减速比 1:1那么平板转 1° 需要 1/0.1125≈8.89 个脉冲如果中间加了一级 2:1 减速脉冲数还要乘 2。这个数后面 PID 输出换算和限位保护都要用不能凭感觉写。提示步进电机的保持力矩和驱动器电流拨码要匹配。电流设小了带不动平板设大了电机发热、低速啸叫常见做法是先按电机额定电流的 70% 设再用手轻推平板测试保持力。2.3 机械与电气连接摆架、传感器、驱动器怎么装机械上分三块摆架框架负责支撑摆杆末端装电机平板固定在电机轴上传感器同轴安装在平板转轴或摆杆转轴处测的是实际转角不是电机走了多少步。这个“同轴”很关键传感器如果通过齿轮间接测齿轮间隙会让读数滞后PID 会越调越振。摆杆轴承要顺畅不能有卡滞否则自由摆周期会变控制模型对不上。平板本身尽量轻硬币放在板面中心附近激光笔固定在平板边缘尽量让激光笔轴线和平板法线平行。电气连接上WDD35D4 一般三根线供电、地、滑动端输出。STM32 的 ADC 输入不能超过 3.3V而传感器常见 5V 供电、输出 05V所以中间必须分压。常见做法是用 10kΩ 和 20kΩ 电阻分压5V 输出降到 3.33V 左右再进 ADC分压比在代码里要乘回去。步进驱动器一侧PUL 接脉冲DIR 接方向ENA 接使能另外还有共阳/共阴接法按驱动器手册来。单片机和驱动器必须共地否则脉冲电平没有参考电机走位会随机。连接项单片机侧外设侧说明平板角度PA0 / ADC1_IN0WDD35D4 输出经分压分压比 1.5代码要乘回摆杆角度PA1 / ADC1_IN1第二路传感器与平板路分开标定步进脉冲PA2 / TIM2_CH3驱动器 PUL脉冲上升沿有效步进方向PB0驱动器 DIR高低电平对应正反转步进使能PB1驱动器 ENA低电平使能常见TFTSPI1 PA4/5/6液晶模块片选手动控制3. 角度采集链路WDD35D4 从模拟电压到角度值的完整实现自由摆控制里角度是唯一的反馈量采样链路不稳后面 PID 就是在噪声上做微分电机一定会抖。这一章按“接线—采样—滤波—标定—异常检测”的顺序拆代码可以直接往工程里搬但参数要按自己传感器和机械结构改。3.1 接线、ADC 配置与采样节拍WDD35D4 的输出是模拟电压STM32 用 ADC1 的规则通道扫描两路平板角度和摆杆角度。采样节拍不要随手用HAL_Delay轮询那样控制周期会被 TFT 刷新和串口打印拖乱。常见做法是用定时器 TRGO 触发 ADC1kHz 采样DMA 把结果搬到缓冲区控制任务只读最新值。ADC 采样时间设长一点比如 71.5 个周期因为传感器输出阻抗加上分压电阻后不算低采样时间太短采样保持电容充不满读数会偏低。/* 两路 ADC DMA 定时器触发1kHz 采样 */ static void sensor_adc_init(void) { ADC_ChannelConfTypeDef ch {0}; hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_ENABLE; /* 扫描两路 */ hadc1.Init.ContinuousConvMode DISABLE; /* 由定时器触发 */ hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T2_TRGO; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; /* 右对齐12 位 */ hadc1.Init.NbrOfConversion 2; HAL_ADC_Init(hadc1); ch.Channel ADC_CHANNEL_0; /* 平板角度 */ ch.Rank ADC_REGULAR_RANK_1; ch.SamplingTime ADC_SAMPLETIME_71CYCLES_5; HAL_ADC_ConfigChannel(hadc1, ch); ch.Channel ADC_CHANNEL_1; /* 摆杆角度 */ ch.Rank ADC_REGULAR_RANK_2; HAL_ADC_ConfigChannel(hadc1, ch); HAL_ADCEx_Calibration_Start(hadc1); /* 上电先校准 */ HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_dma_buf, 2); }这段初始化的关键参数是ExternalTrigConv和SamplingTime。TRGO 来自 TIM2周期设 1msADC 每 1ms 自动采一轮DMA 把两个通道的结果写到adc_dma_buf[0]和[1]。NbrOfConversion2表示一次触发扫两个通道顺序按 Rank 来别把平板和摆杆接反。如果沿用 10 位配置DataAlign不变但换算满量程要改成 1023HAL_ADCEx_Calibration_Start仍然要调校准能明显减小零漂。3.2 中值滤波加一阶低通把机械抖动压下去自由摆摆动时摆杆和轴承的机械振动会叠加到传感器上ADC 原始值画出来是一条毛刺很重的曲线。直接用这个值做 PID微分项会把毛刺放大成电机抖动。我一般先用 9 点滑动中值滤波去掉偶发尖峰再串一个一阶低通压高频。中值滤波窗口不要太大9 个点已经有 4 个采样周期的滞后再大控制会发钝低通系数取 0.20.3太小响应慢太大滤波等于没做。#define FILTER_WIN 9 static uint16_t win[2][FILTER_WIN]; static uint8_t win_idx; /* 9 点中值滤波窗口小插入排序足够快 */ static uint16_t median9(const uint16_t *src) { uint16_t t[FILTER_WIN]; for (int i 0; i FILTER_WIN; i) t[i] src[i]; for (int i 1; i FILTER_WIN; i) { uint16_t key t[i]; int j i - 1; while (j 0 t[j] key) { t[j 1] t[j]; j--; } t[j 1] key; } return t[FILTER_WIN / 2]; } /* 一阶低通alpha 越大越跟手越小越平滑 */ static float lowpass(float x, float y, float alpha) { return alpha * x (1.0f - alpha) * y; }median9每次对 9 个历史值排序取中间那个计算量很小放在 1kHz 采样中断里也不会拖垮 CPU。lowpass的y是上一次输出x是本次中值结果alpha0.25时截止频率大概在几十赫兹正好压住机械振动又不影响自由摆的低频运动。两个通道各用一套窗口和状态不要共用一个win_idx否则平板和摆杆的数据会串。3.3 零点、量程与非线性修正角度标定怎么做传感器装上去以后输出电压和角度不是天然对应的。常见做法是两点标定把平板放在水平位置记下电压v0再把平板转到 90° 位置记下v90。中间按线性换算公式是angle (v - v0) * 90 / (v90 - v0) offset。offset是安装零偏如果传感器转轴和平板法线没对准水平时读数不是 0就用这个参数补。WDD35D4 的导电塑料膜两端有无效区标定范围不要贴着 0° 和 360° 端点尽量在 -45°45° 或 0°90° 的工作段内取两点线性度更好。typedef struct { float v0; /* 0° 对应的 ADC 电压单位 V */ float v90; /* 90° 对应的 ADC 电压单位 V */ float offset; /* 安装零偏单位 度 */ float div; /* 分压比5V 经 10k/20k 分压为 1.5 */ } AngleCalib; static float adc_to_voltage(uint16_t raw, float div) { /* 12 位 ADC参考 3.3V10 位版本把 4095 改成 1023 */ return raw * 3.3f / 4095.0f * div; } static float voltage_to_angle(float v, const AngleCalib *c) { float k 90.0f / (c-v90 - c-v0); return (v - c-v0) * k c-offset; }div是分压比5V 经 10k 和 20k 分压后进 ADC实际电压是 ADC 读数的 1.5 倍所以要在换算时乘回去。v0和v90不要用理论值必须现场拿万用表或串口打印实际 ADC 值记录。标定完把两组数写进 Flash 或头文件下次上电直接加载。平板和摆杆两路要分别标定因为两个传感器的安装角度和分压电阻误差不一样共用一组参数会引入几度的固定偏差。3.4 采样异常检测断线、超量程和参考电压跌落模拟链路最常见的故障是传感器断线、分压电阻虚焊、ADC 输入过压。代码里要加一层保护连续多次读到 0 或满量程就判定传感器异常电机停止输出TFT 上显示故障码。另外如果 3.3V 参考被电机启停拉低所有角度会同时往下掉这种不是传感器坏而是电源问题要靠硬件加电容和软件限幅一起处理。我一般会在控制任务里做软限幅角度超过机械行程 ±5° 就强制回中避免 PID 积分饱和后平板顶到限位。4. 执行机构与控制闭环细分驱动器加步进电机怎么把平板拉回目标角执行链路分两层底层是定时器产生脉冲、驱动器放大电流、步进电机走步上层是 PID 根据角度误差算出“这一拍该走多少脉冲”。自由摆是动态系统平板的目标角不一定是固定 0°基础要求是保持水平发挥部分要让硬币相对静止目标角得跟着摆杆状态变。这一章把脉冲当量、脉冲生成、PID 和主循环串起来。4.1 驱动器拨码与细分16 细分下的脉冲当量四宏细分驱动器的细分由拨码开关设定常见有 1、2、4、8、16、32 几档。项目里用 16 细分步进电机步距角 1.8°那么每个脉冲对应 1.8/160.1125°。平板直接装在电机轴上减速比 1:1转 1° 需要约 8.89 个脉冲如果平板和电机之间加了同步带减速比 2:1就要 17.78 个脉冲。驱动器电流拨码按电机额定电流设42 步进电机常见 1.5A2A设太大电机会烫设太小带载丢步。细分档位每脉冲角度转 1° 所需脉冲适用场景11.8°0.56只做粗略定位振动大40.45°2.22低速轻载80.225°4.44一般控制160.1125°8.89本项目兼顾平顺和速度320.05625°17.78高精度但脉冲频率要求高拨码设置完最好用记号笔在驱动器上写一下调试时换过拨码又忘了脉冲当量对不上PID 输出会整体差一倍。电机线序也要确认四线步进电机两组线圈接错电机会原地抖动不走这时候先别怀疑程序拿万用表量线圈通断。4.2 定时器脉冲与加减速别让电机启动就丢步步进电机的脉冲不能一上来就给很高频率启动频率超过电机的牵入频率就会丢步表现为“程序算走了 100 步平板只动了 80 步”。常见做法是用定时器中断翻转 PUL 脚周期由period_us控制上层根据剩余脉冲数和当前速度做梯形加减速加速段周期从 5ms 逐步减到 0.2ms匀速段保持减速段再拉长。下面这个状态机放在 1ms 定时器里跑非阻塞不占死循环。typedef struct { int32_t remain; /* 剩余脉冲数正负表示方向 */ uint32_t period_us; /* 当前脉冲周期越小越快 */ uint32_t last_tick; uint8_t level; /* PUL 电平上升沿驱动器采一次 */ } Stepper; static void stepper_task(Stepper *s, uint32_t now_us) { if (s-remain 0) { HAL_GPIO_WritePin(STEP_PUL_PORT, STEP_PUL_PIN, GPIO_PIN_RESET); return; } /* 方向脚在脉冲前建立避免第一个脉冲方向错 */ HAL_GPIO_WritePin(STEP_DIR_PORT, STEP_DIR_PIN, s-remain 0 ? GPIO_PIN_SET : GPIO_PIN_RESET); if (now_us - s-last_tick s-period_us) { s-last_tick now_us; s-level !s-level; HAL_GPIO_WritePin(STEP_PUL_PORT, STEP_PUL_PIN, s-level ? GPIO_PIN_SET : GPIO_PIN_RESET); if (!s-level) { /* 下降沿计一个完整脉冲 */ s-remain (s-remain 0) ? -1 : 1; } } }period_us是半周期实际脉冲频率是1/(2*period_us)。比如period_us500对应 1kHz电机转速大概 1kHz/16/2000.3125 转/秒。remain带符号正数正转负数反转走到 0 自动停不会一直发脉冲。加速时上层每 10ms 把period_us减一点减速时反过来加别在stepper_task里做浮点运算1ms 中断里越简单越稳。4.3 控制算法位置式 PID、死区和目标角切换基础要求是平板保持水平目标角target0°发挥部分要让硬币在摆动中相对静止目标角不能固定为 0°得根据摆杆角度和角速度给一个补偿角。硬币随平板摆动时需要水平方向的加速度平板倾斜可以让重力沿板面的分量参与提供这个加速度同时改变支持力。实际调试时先用水平模式把 PID 调稳再切到硬币模式目标角用target k1*arm_deg k2*arm_rate试出来k1、k2靠实验凑别指望一次算准。typedef struct { float kp, ki, kd; float target; /* 目标角水平模式为 0 */ float integral; float last_err; float out_limit; /* 输出限幅单位脉冲/控制周期 */ float deadband; /* 死区度 */ } PID; static float pid_calc(PID *p, float measure, float dt) { float err p-target - measure; /* 小误差进死区避免电机来回抖 */ if (fabsf(err) p-deadband) { p-integral * 0.95f; /* 缓慢泄放积分防止退出死区时突跳 */ return 0.0f; } float deriv (err - p-last_err) / dt; p-integral err * dt; p-last_err err; float out p-kp * err p-ki * p-integral p-kd * deriv; /* 输出限幅 抗积分饱和 */ if (out p-out_limit) { out p-out_limit; p-integral - err * dt; } else if (out -p-out_limit) { out -p-out_limit; p-integral - err * dt; } return out; }dt是控制周期主循环里固定 0.005s不要用HAL_GetTick的毫秒值直接除时间戳抖动会让微分项爆掉。out_limit按最大脉冲频率折算比如每 5ms 最多走 20 个脉冲就设 20。deadband一般取 0.2°0.5°太小电机在目标附近来回走太大硬币会滚。PID 输出是“本周期应该走的脉冲数”正负号直接给stepper_move不用再单独算方向。4.4 主循环与 TFT 显示5ms 控制节拍怎么排主循环不能把 TFT 刷新、串口打印和 PID 混在一起跑SPI 刷屏一次可能几毫秒控制周期立刻被拉长。常见做法是定时器给 5ms 节拍控制任务只做采样读取、PID、电机更新TFT 每 50ms 刷一次串口波形每 20ms 发一帧。这样控制节拍稳定屏幕看起来也不闪。void control_task(void) { uint32_t now HAL_GetTick(); if (now - last_control 5) return; /* 5ms 控制周期 */ last_control now; float plate_deg get_plate_angle(); /* 已滤波、已标定 */ float arm_deg get_arm_angle(); /* 水平模式 target0硬币模式在这里改 pid_plate.target */ float pulses pid_calc(pid_plate, plate_deg, 0.005f); /* 脉冲数换算成速度绝对值给频率符号给方向 */ stepper_set_speed(stepper, fabsf(pulses) * 180.0f); stepper_move(stepper, (int32_t)pulses); static uint8_t disp_cnt; if (disp_cnt 10) { /* 50ms 刷一次屏 */ disp_cnt 0; tft_show_status(plate_deg, arm_deg, pulses); } }stepper_set_speed里的 180 是经验系数把“每周期脉冲数”映射成脉冲频率实际要根据电机和机械负载调。stepper_move把脉冲数累加到remain底层状态机自己走完。TFT 显示建议至少放四个量平板角度、摆杆角度、PID 输出、当前模式调参时一眼能看出是传感器跳还是电机没跟上。按键调kp/ki/kd比反复烧录快得多哪怕只做三个按键加一个确认键调试效率也完全不一样。5. 避坑与排查自由摆平板控制最容易翻车的 5 个点这套系统硬件不复杂但机电耦合强很多问题看起来像程序 bug实际是接线、电源或机械安装。下面五条是按我调试时踩坑频率排的每条按“现象—原因—解决”写照着排查能少走不少弯路。5.1 上电后平板往一个方向猛转限位也挡不住现象程序下载完一上电电机就朝一个方向连续转角度读数变化但 PID 像没起作用平板顶到机械限位后电机堵转啸叫。原因最常见是 DIR 方向脚和驱动器实际逻辑反了PID 输出正方向电机却往负方向走误差越大输出越大形成正反馈。其次是 ENA 使能脚电平配错驱动器一直使能单片机复位期间 GPIO 处于浮空驱动器把干扰当成脉冲。还有一种是单片机地和驱动器地没共脉冲电平没有参考走位随机。解决先拔掉 PUL 线只给 DIR 和 ENA手动改方向脚电平确认平板正反转和代码符号一致再把 ENA 默认拉高按驱动器手册确认使能电平复位期间电机保持释放最后用万用表量单片机和驱动器地之间的电压必须接近 0V。机械限位只能当最后一道保护不能靠它停机。5.2 角度读数静止时也跳动起来跳得更厉害现象平板不动TFT 上角度在小数点后一位来回跳摆杆一摆角度曲线毛刺更大PID 输出跟着抖电机发出细碎响声。原因WDD35D4 如果是 5V 供电输出直接进 STM32 ADC 会超过 3.3V内部钳位二极管导通读数被削顶分压电阻虚焊或阻值不匹配也会让读数漂。另一个常见原因是传感器供电和电机驱动逻辑共用 5V电机启停时电源跌落ADC 参考跟着动。采样时间设得太短采样保持电容充不满也会让读数偏低且不稳。解决先量传感器输出到 ADC 引脚的电压确认不超过 3.3V超过就老老实实加分压10k20k 是最省事的组合传感器供电单独走 LC 滤波地和 ADC 地单点连接ADC 采样时间加到 71.5 周期以上软件上加中值加低通但别用低通掩盖硬件问题。分压比记得在代码里乘回去否则角度整体偏小。5.3 硬币在摆动中还是滚平板看起来“水平”但就是不稳现象静态调水平很准一摆起来硬币就往一侧滚加快摆动直接掉板激光笔在靶面上画圈打不中中心。原因基础模式目标角固定 0°但摆动时平板有水平加速度硬币需要摩擦力提供加速度靠水平板面硬扛摩擦不够就滚。控制周期太长也会导致补偿滞后5ms 和 20ms 完全是两个效果。机械上平板和电机轴连接有间隙PID 算完了电机也走了平板实际角度还差一点传感器读数滞后。解决先确认控制周期真的是 5ms用示波器翻 PUL 脚看脉冲间隔别只看代码发挥部分切到目标角补偿模式用摆杆角度和角速度算target从很小的系数开始试机械间隙用顶丝或涨紧套消除传感器尽量和负载同轴安装硬币测试前先把平板静态调平再用手缓慢摆摆杆观察角度曲线和电机响应是否同相。5.4 步进电机丢步、啸叫走 100 步实际只转了 80 步现象空载转得很顺装上平板就丢步低速时电机啸叫高速时直接堵转PID 输出一直加角度就是回不来。原因驱动器电流拨码设小了带不动负载细分设太高脉冲频率上去了但电机响应跟不上加速曲线太陡启动频率超过牵入频率电源功率不够12V 在电机启停时跌到 9V 以下驱动器欠压保护。解决按电机额定电流的 70% 起步调电流摸电机外壳温度烫手就降一档细分从 8 或 16 开始别一上来就 32加减速用梯形曲线启动频率先设 200Hz稳定后再往上提12V 电源并 470μF 以上电解电容条件允许换功率更大的电池或电源。丢步是开环步进的固有风险关键位置加一个光电限位做回零校准比反复调 PID 管用。5.5 激光笔打靶总是偏换了角度也不对现象平板水平时激光点偏靶心几厘米摆动后轨迹是椭圆调 PID 只能让椭圆变小中心始终偏。原因激光笔安装轴線和平板法线不平行平板转角度时激光点画圆而不是走直线传感器零位和机械水平没对齐软件显示 0° 时平板实际是 1°2°靶面距离变了但补偿系数没改角度和光点位移比例对不上。解决先把平板用水平仪调平再调激光笔支架让光点在近处和远处靶面上重合说明射线和平板法线平行软件零位用按键或串口命令现场清零不要用理论电压靶距变化后重新标定angle-光点位移的比例。激光笔供电单独走线和电机线分开避免 PWM 干扰造成光点闪烁。6. 二次开发把这套控制板改成你自己的实验平台这套自由摆平板控制的代码结构不复杂很适合当二次开发底座。想换传感器可以把 WDD35D4 换成增量式编码器接定时器编码器模式角度闭环从模拟采样变成计数抗干扰更好也可以加一片六轴 IMU做互补滤波把摆杆角速度也纳入前馈。想研究控制算法把位置式 PID 换成“前馈 PD”前馈项用摆杆角加速度估算能明显减小摆动过程中的相位滞后。改造方向改动点验证方法编码器闭环定时器编码器模式替换 ADC 角度手动转轴看计数是否连续IMU 融合I2C/SPI 读六轴互补滤波静态倾角对比水平仪前馈补偿摆杆角速度/角加速度参与目标角正弦摆动下看硬币是否滑动上位机波形串口按 CSV 发角度、PID 输出上位机画曲线看相位差无线调参串口透传模块替换有线调 Kp 时看阶跃响应验证不要一上来就放硬币。先做阶跃响应平板目标角从 0° 跳到 5°用串口把角度和 PID 输出打成 CSV看超调、调节时间和稳态误差再做正弦跟随手动让摆杆按固定周期摆记录平板角度和摆杆角度看两条曲线的相位差和幅值比。硬币测试放在最后而且先从慢摆开始逐步加快每次只改一个参数。串口输出我一般用下面这种最笨的格式上位机直接按逗号拆不需要额外协议。/* 20ms 发一帧波特率 115200上位机按 CSV 画图 */ void telemetry_task(void) { static uint32_t last; if (HAL_GetTick() - last 20) return; last HAL_GetTick(); printf(%.2f,%.2f,%.2f,%.2f\r\n, get_plate_angle(), /* 平板角度 */ get_arm_angle(), /* 摆杆角度 */ pid_plate.target, /* 当前目标角 */ pid_plate.integral); /* 积分项看是否饱和 */ }printf重定向到串口时注意别放在 5ms 控制任务里放 20ms 任务或者主循环低优先级位置否则串口阻塞会把控制节拍拉长。integral打出来很有用积分一直往上顶说明输出限幅了或者机械卡住这时候调 Kp 没用得先查执行机构。从那以后我每次改完控制参数都强制先跑一遍阶跃加正弦再放本文还有配套的精品资源点击获取