简介:面向备战电子设计竞赛、完成单片机课程设计或开展悬挂运动控制实验的读者,这份Word文档以SPCE061A单片机为核心,围绕主控制、电机驱动、检测反馈与悬挂模块四部分,给出从方案论证、系统框图到硬件电路和程序实现的完整参考。资料包共1个doc文件,约517KB,体量轻便,便于直接查阅原理图与代码片段。内容先比较位移传感器、步进电机、直流电机加光电编码盘等定位方案,指出相对坐标掉帧、步进电机失步与功耗偏高等问题,进而说明编码盘测量角位移、局部闭环修正误差的实现思路;寻迹部分对比图像识别与反射式红外传感器阵列,采用多个红外对管分辨黑白轨迹,反馈链路则用无线数传替代有线连接,以减少线缆牵引误差。文中还涉及L298驱动直流电机正反转与停转、PWM调速、点阵液晶和触摸屏设定参数并显示状态,以及漆包线间隔刮漆探测实际路程等细节。整体内容偏重工程取舍,保留多种方案的对比理由,便于在课程报告或答辩中说明选型依据。已有993人学习,可作为梳理模块选型、搭建调试框架的案头资料。
1. 从一张坐标纸说起:悬挂运动控制系统要交付的到底是什么
在 80cm×100cm 的坐标纸上,让一支笔从原点走到任意指定点,误差不超过 5mm;再画一个直径 50cm 的圆,圆心偏差压在 1cm 以内;最后贴着一条随手画的黑线走完全程,偏差 1mm。这就是一套悬挂运动控制系统要交付的全部内容。它是典型的双线悬吊结构:两根线各由一个直流电机收放,笔挂在两根线的交点下方,靠左右线长差的配合把笔送到平面上的任何位置。麻烦的地方不在机械,而在于你很难知道笔此刻到底在哪——线在带轮上打滑、编码盘丢脉冲、电机和无线模块互相干扰,误差会一路累积下去。这套方案用 SPCE061A 做核心,配光电编码盘测角、局部闭环校正段纠偏、无线数传回传寻迹信号,把位置误差按在毫米级。适合要交自动化、测控类毕设或课程设计、手上有原理图和代码却不知道怎么讲清楚的人,直接照着结构做一遍。
2. 方案论证与选型:四条技术路线的取舍与理由
选型这一章不是走过场。悬挂结构最怕的是"先做出来再想办法",因为一旦电机、传感器、通信方式定错,后面所有算法都建立在错误的前提上。下面四条路线,每一条当时都有两个看起来更能打的备选,最后都没选,理由值得写清楚——答辩时被问得最多的也正是这些。
2.1 位置测量:为什么放弃位移传感器和步进电机
最早的想法很讨巧:把光电鼠标拆了当悬挂物,从鼠标接口直接取坐标偏移量,精度高、现成、不用自己算。但这条路走不通的地方在于它是相对坐标。鼠标每帧给出的是相对上一帧的位移,一旦掉帧或者笔被碰了一下,系统没有任何办法知道自己错了,因为它压根不知道自己在哪。另外那几年市面上鼠标基本是 USB 接口,协议栈对一颗 16 位单片机来说太重,短期内写不出来。这个方案被否掉,不是因为精度,是因为它没有绝对参考。
第二个备选是步进电机。步进电机转角可控,一步 0.9°,配上带轮能算出线位移,理论上毫米级没问题。问题出在功率上:题目要求悬挂物重量不小于 100g,还得有足够的加速度,这就需要较大的转矩;而步进电机转矩越大,静止时的锁死电流越大,电源功耗直接顶到天花板。更麻烦的是高频失步——运转频率一高,转子跟不上定子磁场,丢一步就是 0.9°,而且系统自己不知道丢了。要闭环就必须加编码器,那就绕回第三条路。
最终选的是直流电机加光电编码盘。编码盘与电机轴同轴,透射式红外对管数码盘缝隙,把脉冲变成角位移,这是工业控制里的常规做法。直流电机便宜、好买、驱动电路简单,配一片 L298 就能正反转;步进电机则要配上时序脉冲产生电路,硬件复杂度反而上去了。
| 方案 | 精度 | 成本 | 硬件复杂度 | 致命问题 |
|---|---|---|---|---|
| 位移传感器(鼠标原理) | 高 | 低 | 低 | 相对坐标,掉帧无法自校正 |
| 步进电机 | 毫米级(理论) | 中 | 高,需时序驱动 | 大转矩下功耗高,高频失步 |
| 直流电机+光电编码盘 | 毫米级(实测) | 低 | 低,L298 即可 | 需自行处理方向判别与丢脉冲 |
这张表里最后一行那个"致命问题"是真会咬人的,后面第 5 章会专门讲怎么把它压住。
2.2 轨迹探测:放弃摄像头,用 8 对红外对管拼出方向感
寻迹的第一反应是用 CCD 或 CMOS 成像,拍一张坐标纸的照片,让单片机做图像识别找黑线走向。这个方案在两三周的周期里做不完:图像二值化、细化、方向提取,每一步都要调参,而且一颗 16 位单片机的算力和 RAM 都不够跑完整帧。它被否掉是时间原因,不是原理原因。
选的是反射式红外对管阵列。原理很朴素:黑色吸收光、白色反射光,反射强度不同,光敏三极管的导通状态就不同。把 8 对管子围成一个环套在画笔周围,调整间距,让黑线无论往哪个方向延伸,都至少有一对管子压在线上。这个"至少有一对"是整个寻迹算法的地基,间距定错了后面全乱。
具体取值可以这样核算:环半径取 30mm,8 对均布,相邻两对之间的弧长约 23.6mm;黑线宽度按 20mm 画,那么任意方向上黑线至少能盖住一对管子的中心。如果线画得细(10mm 以下),就要么缩小环半径,要么加密到 12 对,否则会出现"整条线从两对管中间穿过去"的死区。这个数字关系是实打实算出来的,不是拍脑袋。
2.3 控制策略:局部闭环怎么把累计误差按死在半路
纯开环的做法是:算出现在到目标点两根线各要收放多少,换算成脉冲数发给电机,数够了就停。问题在于所有误差都是单向累积的——带轮打滑一点点、码盘漏掉一个脉冲、线在轮上叠了一层,单次看不出来,走完 50cm 之后就偏出去好几毫米,而且系统毫无察觉。
局部闭环的思路是在悬线上造绝对参考点。悬线用 0.29mm 漆包线,在中部按固定长度剥出一小段裸线;检测端用两个图钉做成一对触点,一个接高电平,一个接单片机输入。笔运动时线从两个图钉之间滑过,平时漆层绝缘、输入保持高电平;一旦滑到裸线段,两个图钉被裸铜短接,输入被拉低,程序立刻知道"线长此刻应该等于标定值",直接把当前累计计数强制写成这个值。误差就此归零,重新开始累积。
这跟伺服系统里的回零开关、光栅尺上的参考标记是同一个思想:用离散的绝对参考点切碎长距离累积误差。代价也要说清楚——校正点之间照样会漂,所以裸线段的位置和长度要按行程设计,一般让整段行程里能经过 1 到 2 次校正点最合适;裸线段太短,高速通过时可能采样不到;太长,一次经过会连续触发好几回,得靠软件加封锁窗。
2.4 通信方式:无线数传替掉线缆,省下的不只是几根线
探测模块装在悬挂物上,跟着笔一起在纸面上跑;主控在架子外面。两者之间要传 8 路红外状态和若干控制信号。最直接的做法是拉一根排线,成本几乎为零。
但排线本身有重量和刚度。笔在纸面上走,排线会随位置变化产生方向不定的拖拽力,这个力直接作用在两根悬线上,让左右线长差偏离计算值——误差不大,但它是随位置变化的系统性偏差,标定都标不掉。越往纸的边缘走,拖拽越明显。这就是有线方案最坑的地方:你以为是算法不准,其实是线在拽。
所以改成无线数传。用的是现成的微功率数传模块,视距条件下标称可靠传输 500m,这里实际通信距离不到 2m,余量充足。链路建立之后,探测端负责把 8 路红外的位图打包成一帧发出去,主控端解析后决定转向。代价是引入了延迟和丢包:一帧往返十几毫秒,丢一帧就可能让笔走成 Z 字。处理办法写在 5.2 里——探测端本地先做一次粗判断,只把结论发上去,而不是把原始数据甩给主控。
3. 硬件设计:四个模块的电路与参数核算
硬件部分最容易出的问题是"参数对不上":电机选大了电源扛不住,选小了加速度不够;码盘分辨率算错了,程序里补偿半天也补不回来。这一章把每个模块的关键数字过一遍,给的是可以从原理图直接核对的值。
3.1 直流伺服回路:减速比、牵引力与脉冲当量
执行机构是带减速箱的直流电机,额定 12V,减速比 30:1,输出力矩大于 2400 g·cm,带轮平均直径 12mm。牵引力可以按力矩除以半径估算:半径 6mm 即 0.6cm,2400 ÷ 0.6 = 4000g。悬挂物要求大于 100g,也就是说拉力余量有四十倍,加速度能拉得很高,机构响应才跟得上算法。
真正要算清楚的是脉冲当量,也就是"一个编码脉冲对应多少毫米悬线"。码盘装在电机轴上,测的是电机转角,经减速箱折算到输出轴,再由带轮转成线位移:
- 带轮周长 = π × 12mm ≈ 37.7mm,即输出轴转一圈收放 37.7mm 线;
- 减速比 30:1,输出轴一圈对应电机轴 30 圈;
- 码盘按 20 线、单路计数,电机轴一圈 20 个脉冲,输出轴一圈就是 600 个脉冲;
- 脉冲当量 = 37.7 ÷ 600 ≈ 0.063mm。
这个数字是理论分辨率。实测精度远达不到,因为还有打滑、线的叠绕、码盘边缘的机械误差,最后能稳定在毫米级就已经很好。但它决定了控制周期的上限:一次点到点运动走 500mm,约 8000 个脉冲,中断处理必须扛得住这个频率。
/* 编码盘脉冲 → 悬线长度换算,SPCE061A 外部中断中累加计数 */ #define PPR 20 /* 码盘刻线数,单路计数 */ #define GEAR_RATIO 30 /* 减速箱减速比 */ #define PULLEY_D 12.0f /* 带轮平均直径,单位 mm */ #define PI_F 3.1415926f /* 一个脉冲对应的悬线长度,编译期算好,运行时不重复除 */ const float MM_PER_PULSE = (PI_F * PULLEY_D) / (PPR * GEAR_RATIO); static volatile long g_pulse_l = 0; /* 左线累计脉冲,正为正转收线 */ static volatile long g_pulse_r = 0; /* 中断服务里根据方向标志做加减,不要在这里做浮点运算 */ void IRQ_Encoder(void) { if (g_dir_l > 0) g_pulse_l++; else g_pulse_l--; } float pulse_to_mm(long pulse) { return pulse * MM_PER_PULSE; }代码里刻意把浮点乘法放在中断外面。中断只做整数加减,是因为 SPCE061A 没有浮点单元,一次软浮点乘除要几百个周期,放在中断里必然丢脉冲。工程上这种地方翻一次车就够记一辈子:现象是"电机转得越快,位置越不准",查半天以为是机械问题,其实是中断超时。
3.2 L298 驱动:两根 IO 管正反转的真值表
电机驱动没有自己搭 H 桥,直接用专用芯片 L298 的两路功率输出。一片 L298 带两个电机刚好,外围只需要续流二极管。控制端只有两根线:一路接 PWM 输出(记作 C),一路接普通 IO 做方向(记作 D)。
真值很简洁:D 输出低电平时电机正转;D 输出高电平时电机反转;C 端没有 PWM 时电机停转。实际使用中还有个细节——L298 有约 1.5V 的饱和压降,12V 供电到电机端只剩 10V 出头,占空比要按这个折算,否则算出来的加速度永远达不到。
/* L298 单路电机控制:duty 0~255 对应占空比,dir 决定方向 */ void motor_out(int ch, int duty, int dir) { if (duty <= 0) { pwm_set(ch, 0); /* C 端无输出,电机自由停转 */ io_clr(DIR_PIN(ch)); return; } if (duty > 255) duty = 255; /* 占空比钳位,防止溢出反转 */ if (dir > 0) io_clr(DIR_PIN(ch)); /* D 低 → 正转,收线 */ else io_set(DIR_PIN(ch)); /* D 高 → 反转,放线 */ pwm_set(ch, duty); }duty的上限钳位不能省。占空比超过 255 会回绕成小值,现象是"给满速反而变慢",这种 bug 靠看代码很难发现,只能靠加钳位堵死。
3.3 光电前端:反射式判黑白、透射式数脉冲
两种光电传感器用的是同一套信号调理电路,区别只在光路结构。
反射式用于判黑线。红外发射管和光敏三极管并排朝下,探测到黑线时反射回来的光很弱,光敏三极管不导通,A 点被上拉电阻拉成高电平;探测到白色底板时反射强,三极管导通,A 点被拉低。这个电平再送进 74HC14 施密特反相器整形,单片机收到的就是干净的数字量:黑线对应低电平,白底对应高电平。
施密特触发器在这里不是可选项。光电管的输出在黑白交界处是缓慢过渡的模拟量,直接进 IO 口会在阈值附近疯狂抖动,一秒能翻转几十次。加了施密特整形之后只有一个干净的边沿,计数和判位才准。
透射式用于数码盘。发射管和接收管分列码盘两侧,码盘转过一条缝隙就通一次光,接收端出一个脉冲。信号处理路径和反射式完全一致。区别是这里对边沿的响应速度要求高得多,所以接收管后面的负载电阻要取小一些,上升沿才够陡。
3.4 局部闭环装置:图钉开关与定长裸线段
这套装置是整个系统里最"土"也最见效的部分,成本几毛钱。
悬线选 0.29mm 漆包线,一是细、轻,对笔的拖拽小;二是漆层本身就是绝缘层,正好用来做过零检测。在线的中段按固定长度把漆刮掉,露出一段裸铜。检测端用两个图钉,一个接高电平,一个接单片机输入并加下拉。线在正常段滑动时,漆层隔开两个图钉,输入保持低;滑到裸线段,两个图钉被裸铜短接,输入被拉高,产生一个校正信号。
这个信号的用法是"强制赋值"而不是"累加修正":一旦捕获到上升沿,程序立刻把当前线长的脉冲计数写成标定值L_CAL,误差清零。这也是为什么它叫局部闭环——它不修正控制量,它直接修正状态量。
提示:图钉触点的压力要调。压太紧,线走不动还会磨破漆层;压太松,裸线段过去了都没接触上。我一般先把线穿过去,用手拉一段,感觉到明显阻力但不卡死为止。
4. 算法与软件流程:三套运动解算的推导与落地
机械和电路只能保证"能动",能不能"走到位"全看算法。三种运动模式对应三套解算:点到点用勾股定理,圆周用余弦定理,寻迹用一组位图加状态轮换。它们的共同点是都不做实时轨迹规划,而是把连续运动拆成足够小的步,一步一步逼近。
4.1 点到点:勾股定理算出左右线差再折算脉冲
设左定滑轮中心 A(0, H),右定滑轮中心 B(W, H),H 是横梁高度,坐标系以坐标纸左上角为原点、y 轴向下为正。悬挂物在 P(x, y) 时,两根线的长度用勾股定理直接写出来:
- L_A = √(x² + (H−y)²)
- L_B = √((W−x)² + (H−y)²)
要从当前位置走到目标点 T(tx, ty),只需要算两组线长,相减就得到每个电机该收放多少线,再除以脉冲当量换成脉冲数。这里不需要解什么逆运动学方程,因为结构本身就是解耦的——两根线各管一个自由度。
#define H_BAR 1000.0f /* 横梁高度 mm,按实测标定 */ #define W_BAR 800.0f /* 两滑轮中心距 mm */ /* 单根悬线长度:滑轮横坐标 px,悬挂点 (x, y) */ float line_len(float x, float y, float px) { float dx = x - px; float dy = H_BAR - y; return sqrtf(dx * dx + dy * dy); } /* 点到点:算出两轴的脉冲增量,交给运动子程序执行 */ void plan_goto(float tx, float ty) { float la0 = line_len(g_x, g_y, 0.0f); float lb0 = line_len(g_x, g_y, W_BAR); float la1 = line_len(tx, ty, 0.0f); float lb1 = line_len(tx, ty, W_BAR); /* 目标线长减当前线长,正数表示要收线 */ g_need_l = (long)((la1 - la0) / MM_PER_PULSE); g_need_r = (long)((lb1 - lb0) / MM_PER_PULSE); g_mode = MODE_GOTO; }两个轴要同时启动、按各自速率走。如果让一个轴走完再走另一个,笔会先画一条横线再画一条竖线,走出直角拐弯而不是直线。正确做法是两个电机同时给 PWM,谁先到谁先停,笔走的是一条近似直线——严格说是一段折线,但步长足够小时看不出来。
这里有个容易忽略的细节:线长变化量和脉冲数之间是线性关系,但两根线的张力不是。当笔走到靠近某个滑轮的位置时,那根线短、张力大,另一根线长、张力小,两边同样的占空比产生的实际速度不同,会走歪。处理办法是在运动过程中按剩余脉冲数动态调整占空比,让两边同时到达。这就是"先到先停"之外要补的一层。
4.2 圆周运动:余弦定理配角度步进
画圆不能像点到点那样一次算完,得把圆周拆成很多小段。设左滑轮 A、右滑轮 B、圆心 O、半径 R,笔当前在圆周上的 C 点,∠AOC 记作 α,∠BOC 记作 β,两条线长用余弦定理:
- AC = √(AO² + R² − 2·AO·R·cos α)
- BC = √(BO² + R² − 2·BO·R·cos β)
而 α 和 β 不是独立的:A、O、B 三点固定,∠AOB 是常量,三角形 AOB 和点 C 的相对位置决定了 α + β = 2π − ∠AOB。所以只要让 α 从 0 按固定步长递增到 2π,β 就唯一确定了,两根线长也随之确定。控制过程就是不断地"α 加一步、算线长、换成脉冲、走一小段"。
#define STEP_DEG 0.2f /* 角度步进,0.2° */ const float STEP_RAD = STEP_DEG * PI_F / 180.0f; /* 圆周运动:α 递增,逐点解算两条线长 */ void run_circle(float ox, float oy, float r) { float ao = line_len(ox, oy, 0.0f); /* 圆心到左滑轮距离 */ float bo = line_len(ox, oy, W_BAR); /* 圆心到右滑轮距离 */ float ang_aob = angle_aob(ox, oy); /* ∠AOB,结构定死后是常量 */ for (float a = 0.0f; a <= 2 * PI_F; a += STEP_RAD) { float ac = sqrtf(ao*ao + r*r - 2*ao*r*cosf(a)); float b = 2 * PI_F - ang_aob - a; /* α + β = 2π − ∠AOB */ float bc = sqrtf(bo*bo + r*r - 2*bo*r*cosf(b)); step_to_line(ac, bc); /* 走一小步,内部做差值与脉冲换算 */ if (key_pressed()) break; /* 留一个中断出口 */ } }步长 0.2° 是有讲究的。半径 250mm 时,0.2° 对应的弧长约 0.87mm,跟脉冲当量在同一量级,再细也没意义,因为机械精度到不了;再粗就会在圆周上看出棱角。如果画的圆更大,步长要相应减小,保持"每步弧长不超过 1mm"这个原则。
角度步进还有一个隐藏好处:它是开环推进的,不依赖位置反馈。哪怕中途丢了几步,圆还是闭合的,只是某个局部的半径略微偏一点,不会像点到点那样越走越偏。这也是为什么圆周运动的实测圆心偏差能压到 4~9mm。
4.3 任意轨迹寻迹:主探测器轮换加三对屏蔽
寻迹是三种模式里最难写的,因为黑线是随手画的,没有任何先验信息,系统必须一边走一边判断方向。
8 对红外对管围成一圈,编号 0~7。某一时刻可能有一对、两对甚至三对管子压到黑线,程序读到的是一张 8 位位图。如果简单地"取压线管子的平均方向",会出大问题:黑线是弯的,笔头附近可能同时压住当前段和下一段,两个方向一平均,笔就往夹角方向走了,直接脱线。
解决办法是引入"主探测器"的概念。每次只认一个主方向,并且屏蔽掉主探测器之后的三个管子,8 对里只用 5 对。主探测器不是固定的,它跟着黑线走:每次探测完,把本次压线的所有管子相对于当前主探测器的角度偏移取平均,得到下一个状态的主探测器。这样笔转弯时主探测器跟着转,永远是"朝前看"。
/* 8 路红外位图 → 下一个主探测器编号 */ unsigned char pick_main(unsigned char hit, unsigned char last) { int sum = 0, cnt = 0; for (int i = 0; i < 8; i++) { if (!(hit & (1 << i))) continue; /* 相对角度归一到 -4 ~ +3,避免跨 0 号位时正负跳变 */ int off = ((i - last + 8 + 4) % 8) - 4; sum += off; cnt++; } if (cnt == 0) return last; /* 全白,保持原方向 */ /* 四舍五入后再模 8,得到新的主探测器编号 */ int avg = (sum >= 0) ? (sum + cnt / 2) / cnt : (sum - cnt / 2) / cnt; return (unsigned char)((last + avg + 8) % 8); } /* 屏蔽主探测器之后的 3 对,只保留 5 对参与下一次判断 */ unsigned char mask_tail(unsigned char hit, unsigned char main_idx) { for (int k = 1; k <= 3; k++) hit &= ~(1 << ((main_idx + k) % 8)); return hit; }off那行取模是关键。如果直接写i - last,当 last=0、i=7 时会得到 +7,实际应该是 −1,方向判断整个反掉。加上+8+4再取模再减 4,就把任意两个编号的差值归一化到 −4~+3,这是处理环形数组的通用写法,踩过一次就忘不掉。
转向控制律很简单:主探测器的偏移量就是误差,偏移为正说明线在右边,左电机多收一点、右电机多放一点;偏移为负则相反。误差大的时候给大占空比,误差小的时候给小占空比,接近零时维持当前速度直行。这就是最朴素的 P 控制,够用了,加积分反而会因为无线延迟而震荡。
4.4 主程序状态机与人工调零
主程序是个典型的按键轮询状态机。上电初始化、显示欢迎画面,然后进主循环读键值,六个按键分别对应六件事:手动调零、设定坐标、执行运动、画圆、寻迹、人工轨迹。
手动调零是每次开机必做的。系统上电时不知道笔在哪,必须让两个电机同时收线直到顶到限位,把线长计数值清零,建立坐标原点。调零子程序里给四个方向键:左正转、左反转、右正转、右反转,人工把笔挪到原点附近,再按键确认,程序把当前位置写成 (0, 0) 对应的线长。
运动子程序是共用的:读入目标坐标 → 读当前线长 → 算两轴脉冲差 → 开 PWM 输出 → 循环显示实时坐标 → 判断是否到位 → 关输出。三个运动模式(点到点、圆周、寻迹)都调用它,只是喂进去的目标点来源不同。
提示:调零之后不要立刻跑长距离。先在原点附近走一个 50mm 的小方块,确认两边线长计数是同步增减的,再放开跑。左右计数一正一负,说明某一路的方向判别接反了,跑远了会直接把线绷断。
5. 避坑与排查:调试台上最容易翻车的五件事
这一章写的都是实际调出来的毛病,不是理论上的可能性。每一条都有明确的现象,现象背后有原因,原因对应一个具体改法。按这个顺序查,能省掉大量"以为算法错了其实是硬件"的时间。
5.1 编码盘丢脉冲:越走越偏,且偏差随速度增大
现象:让它走 100mm,低速时准,一给大占空比就少走两三毫米,跑完整个行程能差出 1cm。
原因:脉冲太窄,中断响应不过来。码盘 20 线,电机高速旋转时一个脉冲的宽度可能只有几十微秒,而中断服务里如果做了浮点运算、或者被 PWM 刷新和液晶刷屏占着,就会漏掉边沿。
解决:中断里只做整数加减,所有浮点换算挪到主循环;把编码器中断优先级提到最高;液晶刷屏改成分块刷新,不要整屏重写。如果还丢,就换分辨率低一些的码盘——分辨率高但丢脉冲,还不如分辨率低但不丢。
5.2 无线延迟与丢包:寻迹走成 Z 字
现象:寻迹时笔左右摆动,线越弯摆得越厉害,直线段反而正常。
原因:反馈回路被无线链路拉长了。探测端采到数据、打包、发出去、主控解析、算出转向、电机响应,一个来回十几到几十毫秒。在这段时间里笔已经往前走了,等指令到的时候已经偏了,一修就过冲。
解决:把判断下沉到探测端。探测端本地先做一次主探测器选择,只把"该往左/往右/直行 + 偏移量"这一个小字节发上去,主控收到直接执行,不做二次运算。帧里加序号和校验,超时没收到新帧就沿用上一帧的转向,宁可直行也不要乱转。
5.3 红外阵列阈值漂移:换张纸就误判
现象:同一套参数,在 A 张纸上好好的,换 B 张纸就整片误判,或者环境灯一开就乱。
原因:反射式光电管输出的是模拟量,纸面反光率、环境光、供电电压下降都会让阈值偏移。用固定阈值比较,等于把系统绑死在某一张纸上。
解决:上电做一次自标定。让笔在白纸上停一秒,记下 8 路各自的基准值;再让笔压到黑线上停一秒,记下黑线值;取两者中点作为动态阈值。这比调电位器靠谱得多,而且换纸不用重调。
5.4 图钉开关抖动:一次经过触发多次校正
现象:走过校正段时,计数被连续改写好几次,位置数据在那一瞬间跳变。
原因:机械触点的弹跳。裸线段滑过图钉的几十毫秒里,接触时断时续,边沿触发会捕获到一串脉冲。
解决:软件消抖加封锁窗。捕获到第一个上升沿后,先延时 5ms 再确认电平,确认有效才执行校正;校正完立刻封锁该中断 200ms,等线完全滑过去再放开。
/* 校正段中断:消抖 + 封锁窗 */ void IRQ_Calib(void) { if (g_lock_ms > 0) return; /* 封锁窗内直接丢弃 */ if (read_calib_pin() == 0) return; /* 只要上升沿 */ delay_ms(5); /* 5ms 后复检,滤掉弹跳 */ if (read_calib_pin() == 0) return; g_pulse_l = CAL_PULSE_L; /* 强制赋值,误差清零 */ g_pulse_r = CAL_PULSE_R; g_lock_ms = 200; /* 200ms 内不再响应 */ }CAL_PULSE_L和CAL_PULSE_R是校正点对应的理论脉冲数,必须在安装时实测标定,不能算——线在带轮上绕了几层,算出来的值跟实际能差好几个脉冲。
5.5 机械打滑与线长累积:同一个圆画两次圆心不一样
现象:连续画两个相同的圆,第二个圆心明显偏了;或者走完一次长距离,回原点时差出好几毫米。
原因:线在带轮上叠绕。第一层和第二层的有效半径不同,同样的脉冲数对应的线长不一样,跑得越远叠得越多,误差越大。
解决:带轮开 V 型槽,让线始终落在槽底,不叠层;加一个张紧轮保持张力恒定;每次长距离运动前先回零,把累积误差清掉;单次行程尽量不超过校正段间距的两倍,让闭环校正有机会介入。
6. 验收与答辩:用五张测试表把"毫米级"说清楚
做到能跑只是及格线,能证明它达标的才算交付。我的做法是把功能指标拆成五张表,每张表对应一类测试,每类测三次取最差成绩。答辩时先报指标,再报数据,最后主动说短板——这个顺序比"先讲原理再讲实现"有效得多,因为评委最想知道的就三件事:做没做出来、准不准、哪里不准。
第一张是功能对照表,把题目要求的每一项和实际实现的性能并排列出来,让"做到没做到"一眼可见。
| 功能项 | 要求 | 实测 |
|---|---|---|
| 任意设定坐标点 | 触摸屏菜单下设定 | 点阵液晶+触摸屏,6 键菜单 |
| 自定义轨迹运动 | 80cm×100cm 纸面,时间<150s,误差<5mm | 达标,阶梯/直线/抛物线均完成 |
| 圆周运动 | 圆心任意、直径含 50cm,误差≤1cm | 达标,直径可任意设定 |
| 原点到点运动 | 时间<100s | 达标 |
| 实时坐标显示 | 液晶显示画笔坐标 | 达标 |
| 连续黑线寻迹 | 时间<120s,偏差<1mm | 达标 |
| 断续黑线寻迹 | 时间<120s,偏差<1mm | 达标 |
第二张是点到点测试表,取三个有代表性的目标点,各测三次,记录时间和落点。这里必须记"最差的一次",不能记平均值——评委问的是"能不能保证",不是"平均怎么样"。
| 目标位置 | 第一次(时间/落点) | 第二次 | 第三次 |
|---|---|---|---|
| (80, 60) | 65s / (79.8, 60.2) | 67s / (79.4, 60.5) | 65s / (79.8, 60.5) |
| (70, 70) | 57s / (69.6, 69.2) | 55s / (69.6, 69.6) | 59s / (69.7, 69.5) |
| (60, 80) | 67s / (59.7, 79.8) | 67s / (59.4, 79.5) | 70s / (59.8, 79.8) |
三组数据的最大偏差出现在 (60,80) 那一组,X 方向偏了 0.6cm。这个点离左滑轮最远、线最长、打滑最明显,正好能用来说明局部闭环为什么必要。
第三、四张是寻迹表,连续和断续分开测,因为断线处的表现完全是两回事:连续线考验方向跟随,断线考验"失去目标后能不能保持原方向继续走"。
| 序号 | 连续线最大偏差 | 耗时 | 断续线最大偏差 | 耗时 |
|---|---|---|---|---|
| 1 | 2mm | 37s | 3mm | 47s |
| 2 | 1mm | 39s | 5mm | 49s |
| 3 | 3mm | 42s | 2mm | 49s |
第五张是圆周表,圆心偏差和轨迹偏差分开记。这两个指标说的不是一回事:圆心偏差是整体平移,多半是原点标定误差;轨迹偏差是圆的形状不圆,多半是步长或张力问题。答辩时能分开解释,说明真的调过。
| 圆心位置 | 圆心偏差 | 最大轨迹偏差 | 耗时 |
|---|---|---|---|
| (25, 25) | 9mm | 13mm | 57s |
| (25, 50) | 4mm | 12mm | 59s |
| (50, 25) | 7mm | 10mm | 62s |
表格摆出来之后,主动说短板:靠近边缘时张力变化大,圆心偏差会到 9mm;断续线在断口处靠惯性直行,最多偏 5mm。这些数字不丢人,藏起来才丢人——被追问出来的那一刻,前面所有数据都不可信了。
从那以后我每次交方案,都会先问自己一句:这套东西如果让别人照着做,他能不能复现我的数据?如果不能,那说明我记的是结果,不是方法。测试条件、仪器、重复次数、最差值,这四样缺一不可,缺了就不是验收,是自证。这套悬挂运动控制系统的原理图、源码和完整文档我已经整理在一起,按上面的结构对着看,能少走不少弯路。希望帮到你。
本文还有配套的精品资源,点击获取