STM32自由摆平板闭环控制:角度采样与PID调参实践

发布时间:2026/10/28 3:11:37
STM32自由摆平板闭环控制:角度采样与PID调参实践

简介:《自由摆的控制系统》是一份面向电子设计竞赛、嵌入式控制课程设计及自动控制学习者的完整方案文档,围绕摆杆摆动过程中保持末端平板稳定这一问题展开,可作为动态角度检测与电机随动控制的参考范例。文档共1个docx文件,压缩包约36KB,篇幅精炼但内容完整,便于直接查阅与二次整理。内容以STM32F103VET6为主控,涵盖TFT液晶显示、WDD35D4导电塑料角位移传感器实时测角、A/D采样反馈、四宏细分驱动器驱动步进电机等关键环节,并给出摆架框架、检测系统、控制驱动系统三部分结构。文中还对控制器、角度检测、电机与电源模块进行方案比较,说明选型依据,并涉及硬币相对静止、激光笔打靶等基本要求与发挥部分,适合读者借鉴硬件搭建、控制思路和方案论证写法。目前已有308人学习下载。

1. 自由摆不是“摆着玩”:这套平板控制系统到底解决什么问题

摆杆一松手,末端平板跟着来回摆,板上硬币不能滚、激光笔还得稳稳打到靶心——自由摆的控制系统就是为这个场景做的。它用 STM32F103VET6 当控制核心,WDD35D4 导电塑料角位移传感器实时测摆杆和平板角度,TFT 液晶做状态显示,步进电机经四宏细分驱动器带动平板转角。适合正在做嵌入式课程设计、控制类学科竞赛,或者想拿一套完整“传感器采样—角度解算—电机执行—闭环调参”链路练手的人。硬件不算贵,但机械安装、模拟采样和步进丢步这几个环节,每个都能让人调一晚上。

2. 方案与硬件架构:STM32F103VET6 为核心的三大模块怎么搭

这套系统的骨架拆开看很朴素:一个摆架负责让平板自由摆动,一组传感器负责把角度变成电压,一块 STM32 负责算,驱动器加步进电机负责把平板拉回目标角度。真正花时间的是选型和电气连接,因为自由摆是一个动态系统,传感器测的是实时角度,电机执行又有延迟,任何一环选错,后面调 PID 都是白费劲。

2.1 控制器和电源:为什么不用 51,12V 电池怎么分压

控制器选型上,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.5V~7V,再进 L7805 稳到 5V,5V 给 TFT、传感器和电机驱动器逻辑,3.3V 由板载 LDO 给 STM32 和 ADC 参考。模拟部分最好单独一路 LC 滤波,不要和电机驱动逻辑共用一根细地线,否则 ADC 读数会跟着脉冲频率跳。

供电轨来源供给对象注意点
12V电池步进驱动器主电源并 470μF 电解电容,抑制启停跌落
6.5~7VLM2576 降压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°/16=0.1125°,对于平板转角补偿已经够细。四宏细分驱动器的作用是把单片机发出的脉冲放大成电机绕组电流,同时做细分,减弱低频振动。这里要算清楚脉冲当量:如果电机轴和平板轴直接连接、减速比 1:1,那么平板转 1° 需要 1/0.1125≈8.89 个脉冲;如果中间加了一级 2:1 减速,脉冲数还要乘 2。这个数后面 PID 输出换算和限位保护都要用,不能凭感觉写。

提示:步进电机的保持力矩和驱动器电流拨码要匹配。电流设小了带不动平板,设大了电机发热、低速啸叫,常见做法是先按电机额定电流的 70% 设,再用手轻推平板测试保持力。

2.3 机械与电气连接:摆架、传感器、驱动器怎么装

机械上分三块:摆架框架负责支撑,摆杆末端装电机,平板固定在电机轴上;传感器同轴安装在平板转轴或摆杆转轴处,测的是实际转角,不是电机走了多少步。这个“同轴”很关键,传感器如果通过齿轮间接测,齿轮间隙会让读数滞后,PID 会越调越振。摆杆轴承要顺畅,不能有卡滞,否则自由摆周期会变,控制模型对不上。平板本身尽量轻,硬币放在板面中心附近,激光笔固定在平板边缘,尽量让激光笔轴线和平板法线平行。

电气连接上,WDD35D4 一般三根线:供电、地、滑动端输出。STM32 的 ADC 输入不能超过 3.3V,而传感器常见 5V 供电、输出 0~5V,所以中间必须分压。常见做法是用 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 触发 ADC,1kHz 采样,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,周期设 1ms,ADC 每 1ms 自动采一轮,DMA 把两个通道的结果写到adc_dma_buf[0]和[1]。NbrOfConversion=2表示一次触发扫两个通道,顺序按 Rank 来,别把平板和摆杆接反。如果沿用 10 位配置,DataAlign不变,但换算满量程要改成 1023,HAL_ADCEx_Calibration_Start仍然要调,校准能明显减小零漂。

3.2 中值滤波加一阶低通:把机械抖动压下去

自由摆摆动时,摆杆和轴承的机械振动会叠加到传感器上,ADC 原始值画出来是一条毛刺很重的曲线。直接用这个值做 PID,微分项会把毛刺放大成电机抖动。我一般先用 9 点滑动中值滤波去掉偶发尖峰,再串一个一阶低通压高频。中值滤波窗口不要太大,9 个点已经有 4 个采样周期的滞后,再大控制会发钝;低通系数取 0.2~0.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是本次中值结果,alpha=0.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.3V;10 位版本把 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/16=0.1125°。平板直接装在电机轴上,减速比 1:1,转 1° 需要约 8.89 个脉冲;如果平板和电机之间加了同步带,减速比 2:1,就要 17.78 个脉冲。驱动器电流拨码按电机额定电流设,42 步进电机常见 1.5A~2A,设太大电机会烫,设太小带载丢步。

细分档位每脉冲角度转 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_us=500对应 1kHz,电机转速大概 1kHz/16/200=0.3125 转/秒。remain带符号,正数正转,负数反转,走到 0 自动停,不会一直发脉冲。加速时上层每 10ms 把period_us减一点,减速时反过来加,别在stepper_task里做浮点运算,1ms 中断里越简单越稳。

4.3 控制算法:位置式 PID、死区和目标角切换

基础要求是平板保持水平,目标角target=0°;发挥部分要让硬币在摆动中相对静止,目标角不能固定为 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(); /* 水平模式 target=0;硬币模式在这里改 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,超过就老老实实加分压,10k+20k 是最省事的组合;传感器供电单独走 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 没用,得先查执行机构。从那以后我每次改完控制参数,都强制先跑一遍阶跃加正弦,再放

本文还有配套的精品资源,点击获取