简介:这是一份面向电子设计竞赛选手与嵌入式控制学习者的自由摆平板控制系统设计文档,围绕如何在摆杆摆动过程中保持末端平板水平这一动态控制问题展开。方案以STM32F103VET6为控制核心,配合TFT液晶显示、高精度WDD35D4导电塑料角位移传感器与四宏细分驱动器驱动的步进电机,通过A/D采样实时获取摆杆与平板角度并闭环调节转向与转角,实现硬币相对静止、激光笔命中接收靶等题目要求。文中还完整比较了控制器、角度检测、电机与电源四类模块的备选方案及取舍理由,并给出摆架框架、检测系统与控制驱动系统三部分的结构划分。压缩包内为1个docx文档,约36KB,已积累308人学习下载,适合用于课程设计、竞赛复盘或嵌入式动态控制方案参考。
1. 需求先立住:自由摆平板控制在控制什么、误差窗口有多窄
自由摆的控制系统,题目念起来一句话,做起来是一串连锁反应:摆杆靠重力自由摆动,末端平板上放一枚硬币,要求整个摆动过程硬币不滑落;发挥部分还要让平板上的激光笔始终打在接收靶上。控制目标就变成了——摆杆角每变化一度,平板都得反向补回去,工程上能接受的误差窗口通常只有 ±1.5°,再大硬币就开始挪位置了。
这套东西适合机电一体化、自动化、嵌入式方向的课程设计和毕业设计,因为它把角位移传感器、A/D 采样、步进驱动、机械间隙、闭环控制串成一条完整链路,哪一环虚了,硬币会当场提醒你。下面按我实际搭的顺序,从选型讲到验收。
2. 总体架构与选型:三层结构、四组方案对比与引脚分配
2.1 摆架、检测、驱动三层结构,各自负责什么
整个系统按物理功能切成三层,后面所有代码和接线都挂在这三层上,不要混着做。
摆架框架层是被控对象本身:摆架座、摆杆、轴承、末端电机座,平板固定在电机轴上。这一层的机械质量直接决定后面算法的上限——轴承有旷量、平板配重偏心、摆杆刚度不够,再好的控制律也压不住抖动,通常的做法是先把摆杆摆动一次看自由衰减,正常应该平顺衰减,如果出现周期性的"顿"或者异响,先修机械再写代码。
检测层负责把机械状态变成数字:一组 WDD35D4 导电塑料角位移传感器测摆杆角,另一组装在平板转轴上测平板相对摆杆的转角,两路信号经分压后进 STM32 的 A/D,采样值经过滤波和标定换算成角度值。
驱动层是执行端:STM32 输出脉冲和方向信号,经四宏细分驱动器放大后带动步进电机,电机轴直接或经一级减速带动平板转动。电源从 12V 电池进来,一路走 LM2576 开关降压供大电流负载,一路走 L7805 线性稳压供传感器和显示,再经 3.3V LDO 给主控。
| 层级 | 组成 | 交付物 | 常见失误 |
|---|---|---|---|
| 摆架框架 | 底座、摆杆、轴承、电机座、平板 | 自由摆动周期、机械零点 | 配重偏心导致静差 |
| 检测系统 | WDD35D4 × 2、分压网络、RC 滤波 | 角度值(度)、采样率 | 采样窗口太短读数跳 |
| 控制驱动 | STM32、四宏驱动器、步进电机 | 步数指令、转向 | 细分与电流档位不匹配 |
这个分层的价值在于排查:硬币掉了,先看角度显示准不准(检测层),再看电机听不听话(驱动层),最后才怀疑算法。
2.2 四组方案的取舍:主控、传感器、执行器、电源
主控:51 还是 STM32。51 便宜、资料多,但运算速度慢、片内资源少,跑两路 A/D 加滤波加闭环再加液晶刷新会很吃力。选 STM32F103VET6 的理由很实际:Cortex-M3 内核主频够用,库函数开发门槛低,片内多通道 ADC 配合 DMA 可以直接把采样搬进内存不占 CPU,功耗也低。有个细节要澄清——不少资料写它"8 通道 10 位 ADC",实际 F103 系列是 12 位逐次逼近型,配置时按 12 位写,需要粗分辨率就右移取高位,别被这句话带偏把精度白白丢掉。
角度检测:电位器、ADXL345 还是 WDD35D4。普通电位器线性度差,角度误差太大,压不住 ±1.5° 的窗口;ADXL345 是基于重力加速度分量算倾角,静态测倾没问题,但自由摆是动态过程,摆杆的切向加速度会直接污染读数,摆得越快错得越离谱。WDD35D4 是导电塑料角位移传感器,本质是精密电位器,输出与转角成线性关系的电阻,精度高、响应快、不怕动态加速度,代价是需要一个稳定的激励电压和一次认真的标定。
执行器:伺服还是步进。伺服性能好、能高速制动、惯量小,但价格高,对课设/毕设预算不友好。步进电机脉冲驱动、开环定位精度够用,配合 16 细分能明显减弱低频振动,控制逻辑简单。代价是丢步不可自察,所以必须靠角度传感器构成半闭环来兜底。
电源:铅酸直供还是多级变换。铅酸电池电流大但太重,整机结构会变笨。最终走 12V 电池 + 多级变换:大电流支路用 LM2576 开关降压(效率高、发热小),传感器激励和运放用 L7805 线性稳压(纹波小、噪声低)。这里别图省事全用线性稳压,12V 降到 5V 压差 7V,几百毫安就是一两瓦的发热,夏天能把板子烤到烫手。
| 对比项 | 方案 A | 方案 B | 方案 C | 选择 |
|---|---|---|---|---|
| 主控 | 51 单片机 | STM32F103VET6 | — | B |
| 角度检测 | 普通电位器 | ADXL345 | WDD35D4 | C |
| 执行器 | 伺服电机 | 步进电机 + 细分驱动 | — | B |
| 电源 | 铅酸直供 | 12V + L7805/LM2576 多级变换 | — | B |
2.3 外设资源分配与系统初始化骨架
选型定下来之后第一件事是分引脚,分完再画板、再写代码,避免中途改线。下面这份分配表可以照着改,具体以手里板子的实际丝印为准。
| 功能 | 外设资源 | 引脚 | 说明 |
|---|---|---|---|
| 摆杆角采样 | ADC1_IN0 | PA0 | WDD35D4 分压输出 |
| 平板角采样 | ADC1_IN1 | PA1 | 同上 |
| 电池电压检测 | ADC1_IN2 | PA2 | 12V 经电阻分压到 3.3V 内 |
| 步进脉冲 STEP | TIM1_CH1 | PA8 | 每周期翻转一次产生一个脉冲 |
| 步进方向 DIR | GPIO 推挽 | PB0 | 高电平正转 |
| 驱动器使能 ENA | GPIO 推挽 | PB1 | 低有效,上电先拉高再释放 |
| TFT 液晶 | SPI1 | PA5/PA6/PA7 | 片选 PA4,仅刷数字区域 |
| 调试串口 | USART1 | PA9/PA10 | 上位机看角度曲线 |
初始化顺序有讲究:先时钟、再 GPIO、再 ADC 校准、最后开定时器。ADC 校准必须在使能转换之前做,否则前几十次采样值整体偏移,很多人以为是传感器坏了。
/* 系统启动骨架:顺序不能乱,尤其 ADC 校准要在启动转换之前 */ int main(void) { SystemInit(); /* 72MHz 主频 */ Delay_Init(); /* SysTick 延时基准 */ GPIO_Config(); /* DIR / ENA / LED / 按键 */ USART1_Config(115200); /* 调试串口 */ TFT_Init(); /* 液晶屏,先清屏再画框架 */ AngleAdc_Init(); /* 双通道 ADC + DMA,内部含校准 */ Stepper_Init(); /* TIM1 脉冲定时器,先不使能 */ Stepper_Enable(1); /* 释放驱动器,电机抱死 */ LCD_DrawFrame(); /* 静态框架只画一次,后面只刷数字 */ while (1) { Control_Loop(); /* 5ms 周期控制 */ if (++g_lcd_cnt >= 20) { /* 100ms 刷一次显示,别每周期刷 */ g_lcd_cnt = 0; LCD_Refresh(); } Delay_ms(5); } }这里有个容易被忽略的点:液晶刷新和控制在同一个循环里,如果每个控制周期都刷屏,SPI 写屏的时间会把控制周期拉长到几十毫秒,摆杆动得快的时候补偿就跟不上了。显示只刷变化的数字区域,静态框架上电画一次,控制周期才能稳住。
2.4 控制周期与时间预算
控制周期定 5ms,对应 200Hz。这个数字不是拍脑袋:自由摆的摆动周期一般在 1~2 秒量级,按 200Hz 采样,一个周期内有几百个采样点,相位滞后可以接受;再快也没意义,因为步进电机的响应跟不到那么细。
时间预算要算清楚:5ms 里,两路 ADC 转换耗时(12 位、239.5 周期采样时间,单次约 21μs,两通道约 42μs)可以忽略;中值滤波 8 点排序加角度解算大约几十微秒;剩下的几乎全留给脉冲输出和液晶。所以液晶必须降频到 100ms,串口打印更要压缩,调试时只在需要的时候打一次。
3. 角度检测与信号链:WDD35D4 从分压到角度值的完整换算
3.1 接线与分压网络:别把 5V 输出直接怼进 3.3V 的 ADC
WDD35D4 是三线制:两端接激励电压,中间抽头输出电压。抽头电压与转角在一定电气角范围内成线性关系。接线要遵守两条:
第一,激励电压必须是稳定的基准,不能直接拿电机电源或者电池裸电压当激励。电池从 12.6V 掉到 11V,分压比跟着变,角度读数就整体漂,这属于典型的"上电准、跑一会儿不准"。
第二,抽头输出幅度必须落在 0~3.3V 之内。如果激励用 5V,满量程输出接近 5V,直接进 STM32 的 ADC 会打坏输入保护二极管。通常的做法是激励也用 3.3V,或者在抽头后面加一级电阻分压把幅度压到 3.0V 以内,再用电压跟随器做阻抗缓冲。
传感器输出阻抗不低(几 kΩ 量级),后面直接接 ADC 采样电容,等效就是 RC 充电。所以抽头到 ADC 之间串 1kΩ、对地并 0.1μF,构成截止频率约 1.6kHz 的低通,既能滤掉电机干扰,又不至于把动态信号拖慢。
提示:分压电阻用 1% 金属膜,两个通道用同一批次的电阻,否则两路的角度斜率不一致,后面对比摆杆角和平板角时会出现固定偏差。
3.2 ADC 采样配置与 DMA 双通道循环搬运
两路角度必须同步采样,否则算"绝对倾角 = 摆杆角 + 平板相对角"时,两个角来自不同时刻,误差会直接进闭环。用 ADC1 的扫描模式加 DMA 循环搬运,两通道在同一个转换序列里连续转完,时间差只有二十几微秒,可以认为同步。
/* 双通道 ADC1 规则组 + DMA1 循环搬运:CH0 摆杆角,CH1 平板角 */ #define ANG_ADC_CH_NUM 2 static volatile uint16_t adc_raw[ANG_ADC_CH_NUM]; void AngleAdc_Init(void) { GPIO_InitTypeDef gpio; ADC_InitTypeDef adc; DMA_InitTypeDef dma; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* 模拟输入模式,禁止上下拉,否则外部下拉会把分压比带偏 */ gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; gpio.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &gpio); DMA_DeInit(DMA1_Channel1); dma.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; dma.DMA_MemoryBaseAddr = (uint32_t)adc_raw; dma.DMA_DIR = DMA_DIR_PeripheralSRC; dma.DMA_BufferSize = ANG_ADC_CH_NUM; dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable; dma.DMA_MemoryInc = DMA_MemoryInc_Enable; dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; dma.DMA_Mode = DMA_Mode_Circular; /* 循环模式,CPU 随时取最新值 */ DMA_Init(DMA1_Channel1, &dma); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DeInit(ADC1); adc.ADC_Mode = ADC_Mode_Independent; adc.ADC_ScanConvMode = ENABLE; /* 双通道扫描 */ adc.ADC_ContinuousConvMode = ENABLE; /* 连续转换,DMA 自己搬 */ adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; adc.ADC_DataAlign = ADC_DataAlign_Right; adc.ADC_NbrOfChannel = ANG_ADC_CH_NUM; ADC_Init(ADC1, &adc); /* 传感器输出阻抗高,采样窗口取最长档,短窗口必然读不准 */ ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); /* 上电校准必须在使能转换之前完成,否则前若干次采样整体偏移 */ ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }采样时间选 239.5 周期不是随手写的:ADC 时钟按 12MHz 算,一次转换约 (239.5+12.5)/12MHz ≈ 21μs,两通道 42μs,相对 5ms 控制周期占比不到 1%。有人为了"提高采样率"把它改成 1.5 周期,结果读数随电机转速乱跳,原因就是采样电容还没充到传感器输出电压就关门采样了。
3.3 中值滤波、两点标定与角度解算
从 ADC 值到角度值要做两件事:去野值、建映射。
去野值用中值滤波而不是简单平均。电机换向、驱动器斩波会在模拟通道上打出一两个明显偏离的采样点,平均值会把这颗"老鼠屎"摊到整个结果里,中值滤波直接把它筛掉。
/* 8 点采样,排序后取中间 4 点平均:既滤脉冲干扰,又保留一点平滑 */ static uint16_t Median4(uint16_t *buf, uint8_t n) { uint8_t i, j; uint16_t tmp; for (i = 0; i < n - 1; i++) { /* 冒泡排序,n 只有 8,开销可忽略 */ for (j = 0; j < n - 1 - i; j++) { if (buf[j] > buf[j + 1]) { tmp = buf[j]; buf[j] = buf[j + 1]; buf[j + 1] = tmp; } } } return (uint16_t)((buf[2] + buf[3] + buf[4] + buf[5]) >> 2); }标定用两点法,把机构装好、传感器装好之后再标,不要在台面上拿手转着标——装上去之后机械零位就变了。做法是:把平板摆到两个已知角度(比如 -30° 和 +30°,用量角器或者水平仪确认),分别记录 ADC 值,解出斜率和截距。
typedef struct { float k; /* 斜率:度 / LSB */ float b; /* 截距:度 */ float limit_l; /* 机械下限,防算出离谱值 */ float limit_h; /* 机械上限 */ } AngleCal_t; static AngleCal_t cal_rod = {0.0f, 0.0f, -70.0f, 70.0f}; static AngleCal_t cal_plate = {0.0f, 0.0f, -95.0f, 95.0f}; /* 两点标定:adc_a/adc_b 是实测 ADC 均值,deg_a/deg_b 是对应角度 */ void AngleCal_TwoPoint(AngleCal_t *cal, float adc_a, float deg_a, float adc_b, float deg_b) { cal->k = (deg_b - deg_a) / (adc_b - adc_a); cal->b = deg_a - cal->k * adc_a; } float Angle_Read(const AngleCal_t *cal, uint16_t raw) { float deg = cal->k * (float)raw + cal->b; if (deg < cal->limit_l) deg = cal->limit_l; if (deg > cal->limit_h) deg = cal->limit_h; return deg; }截距的意义是把"机械水平"和"电信号零点"对齐。这两个零点通常不重合,传感器装上去必然带几度的安装偏差,靠标定吃掉它,比在结构上硬调省事得多。斜率的意义是分辨率:12 位 ADC、3.3V 基准、传感器电气角 340° 左右,理论上每 LSB 约 0.08°,比步进电机 16 细分后的 0.1125°/步还细,够用。
3.4 为什么必须装两个传感器:摆杆角和平板角的物理关系
这是整套设计里最容易想错的地方。平板转轴上那组传感器测的是"平板相对摆杆"的转角,不是平板相对水平面的倾角。摆杆自己也在转,所以平板的绝对倾角等于两者之和:
绝对倾角 = 摆杆角 + 平板相对角
题目要求的"平板始终保持水平",翻译成控制目标就是:不管摆杆转到哪,让绝对倾角等于 0,也就是平板相对角要等于负的摆杆角。发挥部分要求激光打靶,靶点位置由绝对倾角决定,目标就变成让绝对倾角等于某个固定值。
如果只装一组传感器(只测平板相对角),把目标设成 0,看起来平板"水平"了,实际上它只是相对摆杆水平,摆杆一歪,硬币立刻滑。这是我在调试阶段见过的翻车现场里最典型的一个:显示一切正常,硬币每次都在摆到最高点时掉下去。
4. 执行机构与驱动:四宏细分驱动器、脉冲发生与位置环落地
4.1 驱动器接线、细分与电流档位
四宏细分驱动器一般有六根控制端子:PUL+、PUL-、DIR+、DIR-、ENA+、ENA-,加上四根电机相线。STM32 是 3.3V 电平,直接把脉冲脚接 PUL+、PUL- 接单片机地,驱动器内部光耦就能正常导通;如果现场干扰大,脉冲和方向都加一级高速光耦隔离,代价是延迟增加几微秒,对 2kHz 以内的脉冲完全无影响。
细分档位通过拨码设置,档位和单步角度的对应关系如下(按 1.8° 两相混合式步进电机算):
| 细分档位 | 每转脉冲数 | 单步角度 | 适用场景 |
|---|---|---|---|
| 整步 | 200 | 1.8° | 只做粗定位,噪声大 |
| 半步 | 400 | 0.9° | 低成本场合 |
| 4 细分 | 800 | 0.45° | 中速跟踪 |
| 8 细分 | 1600 | 0.225° | 常用档位 |
| 16 细分 | 3200 | 0.1125° | 本项目选择 |
选 16 细分是为了压低频振动——步进电机整步运行在低速段会有明显的"咯噔"感,这个振动会通过摆杆传到传感器上,被当成角度波动采进来,形成自激。但细分不是越高越好,细分越高单脉冲转矩越小,电流档位如果还按整步的设,电机就会出现"听着在转、实际没动"的丢步。电流档位按电机铭牌额定电流设,宁可比额定略高一档,别低。
4.2 脉冲发生与梯形加减速
脉冲用定时器更新中断翻转 IO 产生,比 PWM 方式更灵活,因为频率要随剩余步数动态改。定时器预分频到 1MHz 计数,ARR 决定脉冲频率:ARR=499 对应 2kHz(约 225°/s),ARR=1999 对应 500Hz(约 56°/s)作为起步频率。
/* TIM2 更新中断:每次中断翻转 STEP 引脚,ARR 决定当前转速 */ static volatile int32_t step_remain = 0; /* 剩余脉冲数,正为正向,负为反向 */ #define STEP_ARR_FAST 499 /* 高速档,约 2kHz */ #define STEP_ARR_SLOW 1999 /* 起步/到位档,约 500Hz */ void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); if (step_remain == 0) { /* 走完就关定时器,避免空转发热 */ TIM_Cmd(TIM2, DISABLE); return; } /* 翻转 STEP 引脚,一个完整脉冲需要一个高电平加一个低电平 */ GPIO_WriteBit(GPIOA, GPIO_Pin_8, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_8))); if (step_remain > 0) step_remain--; else step_remain++; /* 简化的梯形策略:剩余步数多时提速,快到位时降速,防止冲过头 */ int32_t abs_remain = (step_remain >= 0) ? step_remain : -step_remain; TIM_SetAutoreload(TIM2, (abs_remain > 40) ? STEP_ARR_FAST : STEP_ARR_SLOW); } } /* 上层调用:设定方向后启动脉冲,非阻塞,主循环继续跑 */ void Stepper_Move(int32_t steps) { if (steps == 0) return; if (steps > 0) GPIO_ResetBits(GPIOB, GPIO_Pin_0); /* DIR 低 = 正向 */ else GPIO_SetBits(GPIOB, GPIO_Pin_0); TIM_Cmd(TIM2, DISABLE); step_remain = (steps > 0) ? steps : -steps; TIM_SetAutoreload(TIM2, STEP_ARR_SLOW); /* 一律从慢速起步 */ TIM_SetCounter(TIM2, 0); TIM_Cmd(TIM2, ENABLE); }这段代码的关键在"非阻塞"三个字:设定好剩余步数就返回,主循环不用等电机走完,5ms 后继续算下一个控制量。如果改成死等走完,控制周期会被拉长到几十毫秒,摆动过程中补偿就跟不上了。方向脚在启动前设好,因为 DIR 信号需要提前脉冲至少几微秒建立,中途切换方向容易丢步。
4.3 从角度误差到步数的位置环
步进电机本身是开环的,但有了角度传感器就有了反馈,构成一个"半闭环"位置环。最简的形式是比例控制:每个控制周期读一次绝对倾角,算出和目标角的误差,乘以系数换成步数下发给电机。
系数怎么定:16 细分下平板直连时每步 0.1125°,也就是 1° 误差对应约 8.9 步。如果中间加了 1:3 减速带,平板转 1° 电机要转 3°,系数就是 26.7。
#define PLATE_KP 8.9f /* 步/度,直连时约 1/0.1125;有减速比要乘 */ #define PLATE_DEAD_ZONE_DEG 0.15f /* 死区,小于它不动,防止来回点头 */ #define STEP_MAX_PER_CYCLE 10 /* 单周期限幅 = 5ms × 2kHz */ /* 输入绝对倾角与目标绝对倾角,输出本周期要走的步数(带方向) */ int32_t Plate_ControlOnce(float abs_plate_deg, float target_deg) { float err = target_deg - abs_plate_deg; /* 死区:误差极小时不动作,机械回差会让它反复来回 */ if (err > -PLATE_DEAD_ZONE_DEG && err < PLATE_DEAD_ZONE_DEG) return 0; int32_t steps = (int32_t)(err * PLATE_KP); /* 限幅:一个周期内电机最多能走的步数,超了也发不出去 */ if (steps > STEP_MAX_PER_CYCLE) steps = STEP_MAX_PER_CYCLE; if (steps < -STEP_MAX_PER_CYCLE) steps = -STEP_MAX_PER_CYCLE; return steps; }限幅值不是随便填的:控制周期 5ms,脉冲频率上限 2kHz,5ms 内最多只能发 10 个脉冲,所以单周期步数上限就是 10。很多人把 Kp 调得很大,算出几十步,程序里又照发不误,结果定时器队列积压,电机一直走停走停,看起来像"卡顿",其实是限幅没做。
4.4 软限位、回零与机械回差补偿
步进电机是开环的,一旦丢步,位置就永久偏了,所以必须有兜底:
软限位——角度超出机械安全范围时立即停脉冲并反向。这个判断放在控制周期里,比放在中断里安全。
回零——上电时先朝一个方向慢速转,撞到机械挡块或者触发光电开关就停,把当前位置记为零点,再反向退回一个小角度。没有回零流程的系统,每次上电零点都不一样,标定参数就白标了。
回差补偿——齿轮或联轴器有间隙,正向到位和反向到位差那么一两度。通常的做法是恒定单向逼近:无论目标在上还是在下,都先多走 2~3 步再反向退回来,让齿面始终在同一侧接触。这个技巧很土但极其有效,能把到位抖动压掉一大半。
5. 常见问题排查:采样跳变、电机丢步与到位抖动的五类现场故障
5.1 角度读数在静止时上下跳 ±2°
现象:摆杆不动,液晶上的角度值在 ±2° 范围内乱跳,电机跟着小幅度来回修正。
原因:三个常见来源——采样时间设得太短(ADC 采样电容没充满)、传感器输出没有阻抗缓冲(几 kΩ 的输出阻抗直接驱动采样保持电路)、模拟地和数字地混在一起(电机电流在地线上的压降被采进来了)。
解决:采样时间改到 239.5 周期;抽头后加电压跟随器或者至少串 1kΩ + 并 0.1μF;模拟地单独走一根线回电源地,别和电机地共用一段铜皮;软件上 8 点中值加均值再上一道。
5.2 电机一转,角度读数就漂
现象:电机静止时读数是稳的,一旦开始走脉冲,角度值立刻抖,而且抖的幅度跟转速相关。
原因:脉冲信号线和传感器信号线捆在一起走,驱动器斩波产生的高频干扰通过线间电容耦合进模拟通道;或者驱动器与主控共地但接地点选在驱动器侧,地电流绕了一大圈回到单片机。
解决:脉冲线、电机相线走一侧,传感器线走另一侧,交叉处垂直过;脉冲和方向加光耦隔离;模拟部分单独 LDO 供电。做完这三条,抖动基本能压到 0.2° 以内。
5.3 平板到位后来回"点头"
现象:误差已经很小了,电机还在走两步退两步,平板在原地小幅抖动,硬币反而更容易滑。
原因:死区设得太小或者干脆没设,比例系数偏大,加上机械回差,形成极限环。
解决:先加 0.1~0.2° 死区,再把 Kp 降一档观察,最后上单向逼近策略(多走 2~3 步再倒退到位),让齿面始终单侧接触。这三步按顺序做,不要一次性全改,否则不知道是哪一步起的作用。
注意:死区不是越大越好。死区太大,平板在小角度摆动时会"钝",硬币在低幅摆动阶段更容易滑,通常 0.15° 是个比较平衡的值,具体还要按机械刚性微调。
5.4 摆动快的时候电机丢步
现象:慢速摆动时一切正常,摆幅加大后平板明显跟不上,角度误差越来越大,停下来看位置已经偏了。
原因:起步频率太高(直接从 2kHz 起振,电机来不及跟上)、细分调得太高但电流档位没同步调大、加减速策略缺失。
解决:一律从 500Hz 起步,剩余步数大于 40 时才切高速档;电流档位按电机铭牌额定值设置,不要为了"省电"往低调;摆动幅度大的场景把单周期限幅适当放大,让电机有足够的行程追赶。
5.5 上电后零点每次都不同
现象:同一台设备,第一次上电平板是水平的,重启之后角度显示偏了好几度,前面标定的参数像失效了一样。
原因:没有回零流程,每次上电电机轴停的位置是随机的;或者标定时用的是台面手转的角度,装机之后机械零位已经变了。
解决:加上电回零(慢速撞挡块或光电开关),把回零位置写进零点变量;标定必须在整机装配完成后进行,标完把 k、b 两个值写进 Flash,掉电不丢;如果电池电压波动大,激励源别用电池裸电压,用 LDO 稳压后的 3.3V。
6. 验收与答辩:指标复现清单和演示脚本的走位
验收这一步,评委看的不是你讲了多少控制理论,而是几个可复现的动作能不能稳定做出来。我一般会提前准备一张指标表,每项都写清测试方法和合格判据,现场按表走,不临场发挥。
| 验收项 | 测试方法 | 合格判据 |
|---|---|---|
| 平板水平保持 | 摆杆从 60° 释放,全程记录绝对倾角 | 摆动全程偏差 ≤ ±1.5° |
| 硬币实验 | 平板上放一枚硬币,连续摆动 10 个周期 | 硬币不滑落、不位移超过 5mm |
| 阶跃响应 | 目标角从 0° 跳到 20°,记录响应曲线 | 300ms 内进入 ±1° 死区,超调 ≤ 3° |
| 激光打靶 | 目标点固定在 1m 外,读光点位置 | 偏移 ≤ 2cm,重复 5 次一致 |
| 回零一致性 | 连续上电 5 次,记录回零后角度读数 | 极差 ≤ 0.5° |
| 续航 | 满电连续运行 | 满足演示全程,无异常复位 |
答辩的演示顺序很关键,别一上来就放大招。先做静态:平板在静止状态下设定 0°、+15°、-15°,让评委看清角度显示和执行的一致性;再做慢速小摆幅,证明闭环是稳的;最后才做满幅摆动加硬币。这个顺序的好处是,万一满幅阶段出问题,前面的基础分已经拿到了。
现场还有两个细节值得提前准备。一是把调试串口接出来,用上位机实时画角度曲线,评委问"你怎么知道误差是多少"的时候,曲线比嘴说有用得多。二是准备一份失败预案:如果硬币掉了,别急着捡起来重来,先说清掉在哪、为什么掉(是限幅不够还是死区偏大),然后当场改一个参数重做一次——能当场定位并修好,比一次成功更能说明你真的懂这套系统。
有次调试我把死区从 0.15° 调到 0.05°,想着响应更快,结果整个演示过程平板一直在原地小幅抖动,硬币在第三次摆动时就滑到边上了,台下看着像系统不稳定,其实是参数自己给自己挖的坑。从那以后我每次改控制参数都强制走一遍"静态—小摆幅—满幅"三段验证,任何一段不过就不往下走。希望帮到你。
本文还有配套的精品资源,点击获取