ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

轮式自主格斗机器人整体设计与策略分析:从边界识别到状态机决策

轮式自主格斗机器人整体设计与策略分析:从边界识别到状态机决策 简介《轮式自主格斗机器人整体设计与策略分析》是一篇面向机器人竞赛备赛者、机电一体化与自动控制方向学生及参赛指导教师的技术文献聚焦轮式自主格斗机器人如何自动登台、自主搜寻对手、将对手推下擂台并在跌落后自主爬回擂台这一完整对抗任务。PDF全文约1.27MB共1个文件为期刊论文原版排版含摘要、系统方案、软件策略与硬件设计等章节便于直接检索与打印研读。文中围绕自主移动机构、场地环境与目标感知、高功率密度驱动、嵌入式单片机控制以及基于深度学习的目标特征识别等模块展开给出调试软件与驱动器选型的实操细节并总结了依据比赛数据迭代优化的思路可作为格斗机器人方案设计、算法选型与赛前调试的参考。目前已有2158人学习下载。1. 一台能自己找对手的轮式格斗机器人难在哪直径 1.2 米的黑色圆台一圈 5 厘米宽的白色边线两台没有遥控器的机器裁判哨响后三秒内必须启动。绝大多数队伍第一次上场的剧本高度相似机器人确实找到了对手也确实把对手顶到了边线然后因为来不及减速冲出了台面——赢的那一分和输的那一分是同一个动作造成的。轮式自主格斗机器人真正难的地方不在能不能动而在整体设计与策略分析这两件事必须一起做底盘轴距决定了最小转向半径铲刀离地高度决定了能不能把对手铲起来传感器安装位置决定了边沿余量而所有这些机械与电气参数最后都要折算成策略代码里的两三个阈值。只改机械不动策略机器人会更快地冲出边界只改策略不动机械机器人会在原地打滑空转。下面按硬件怎么定、信息怎么来、决策怎么做、现场怎么验的顺序把一套能复现的方案讲清楚面向参加自主对抗类赛事的学生队伍以及做移动机器人对抗决策研究的工程师。2. 轮式自主格斗机器人的整体架构与关键部件选型2.1 按控制频率把系统切成三层轮式自主格斗机器人的架构一旦画错后面所有调参都是补窟窿。我一般按频率切三层感知层负责采样100200 Hz 跑灰度阵列、测距和陀螺仪决策层负责判断2050 Hz 跑状态机和阈值比较执行层负责输出1 kHz 跑 PWM 更新和电流限幅。这样分的原因很直接——灰度传感器和陀螺仪需要高频采样才能抓到瞬间的边线穿越和急停时的角速度尖峰但状态机如果也跑在 1 kHz会因为测距数据的刷新率只有 2060 Hz 而在同一帧数据上反复切换状态出现抖动式决策。执行层独立出来是为了让电机在决策层卡顿时仍然输出上一个有效指令而不是瞬间归零。层级典型频率主要职责常见坑感知层100200 Hz灰度采样、测距、IMU 积分测距刷新率跟不上重复读同一帧决策层2050 Hz状态机、阈值判断、目标速度频率过高导致状态来回跳变执行层1 kHzPWM 更新、电流限幅、死区补偿与决策层耦合卡顿即失控2.2 底盘与轮系两轮差速、四轮全向还是履带选型的第一分叉点是轮系。两轮差速加前后牛眼轮是最常见的选择两个驱动轮左右分布前后各一个万向支撑结构简单、推进效率高、推力集中在一条轴线上缺点是转向时靠左右轮速差最小转向半径受轮距限制。四轮全向麦克纳姆轮或全向轮可以横向平移看起来在对抗中很占便宜但滚子与地面的接触是间断的冲击瞬间容易打滑而且控制上要做运动学解算代码复杂度显著上升。履带抓地力最强可转向时的滑动摩擦会吃掉大量扭矩且重量大、重心高被顶翻之后基本无法自救。轮系转向灵活度推进效率控制复杂度被顶翻后自救两轮差速 牛眼轮中高低较难四轮全向高中高难履带差速低中中极难机械参数的取值我一般卡在这么几条线上轮径 6080 mm兼顾线速度和越障轮距 180220 mm太宽转向慢太窄抗侧向推力差轴距不超过轮距整机重心高度控制在轮距的四分之一以内重心越低急停和受冲击时越不容易前翻前置铲刀前缘离地 38 mm倾角 15°25°角度太小铲不起对手太大则变成一块斜面板把对手往上抬、反作用力把自己顶退。2.3 主控、测距与驱动的选型清单测距方案直接决定策略能做到多细。红外反射式传感器成本最低但受环境光和对手表面颜色影响大超声波量程够近距离有盲区且两台机器人互相发射时容易串扰ToF 激光模块体积小、刷新快是目前近距离检测对手方位性价比较高的选择。边界检测不用测距思路用 8 路反射式灰度阵列贴在底盘前缘通过反射强度差异判断下方是黑色台面还是白色边线。传感器量程刷新率主要缺点红外反射230 cm高受颜色与环境光干扰超声波2400 cm2050 Hz近距离盲区、多机串扰ToF 激光2200 cm3060 Hz强环境光下量程衰减灰度阵列判断有无200 Hz需要现场标定阈值主控常见做法是 STM32F4/F7 系列定时器资源够、中断延迟可控能同时跑 1 kHz 执行环和 100 Hz 感知环。ESP32 适合成本敏感的场景但要注意 Wi-Fi 中断对实时性的干扰。视觉方案可以加一块树莓派或类似的计算板但只用来做上位机识别不参与 1 kHz 的电机环。驱动方面有刷 370/520 电机配双 H 桥是最省事的组合响应快、扭矩够无刷配电调功率密度更高但低速线性度和换向延迟需要额外处理。供电上动力电和逻辑电必须分开走母线并 4701000 µF 低阻电容用来吸收撞击瞬间电机堵转造成的电压塌陷——这一步不做比赛里最常见的故障就是一撞就复位。2.4 一个 100 Hz 控制环的代码骨架/* 100 Hz 主控制环骨架采样 - 融合 - 决策 - 执行 */ void ControlLoop_100Hz(void) /* 由定时器中断或 RTOS 周期任务调用 */ { /* 1. 采样灰度阵列(边界) 测距(对手) 陀螺仪(自身角速度) */ uint16_t gray[8]; Sensor_GrayRead(gray, 8); float dist_cm Sensor_RangeRead(); /* 超声波/ToF单位 cm */ float gyro_z IMU_GetGyroZ(); /* 已减零偏单位 deg/s */ /* 2. 融合把原始读数压成决策层能用的几个量 */ EdgeInfo_t edge Edge_Analyze(gray, 8); /* bitmask 最近边线方向 */ OppInfo_t opp Opp_Estimate(dist_cm); /* 距离 置信度 */ /* 3. 决策20~50 Hz 的有限状态机这里做 4 分频即 25 Hz */ static uint8_t div 0; if (div 4) { div 0; Strategy_Step(edge, opp, gyro_z); } /* 4. 执行角度环 速度环输出 PWM1 kHz 层里做插值 */ Motor_SetPWM(Motion_GetLeftPWM(), Motion_GetRightPWM()); }这段骨架的关键在第三步的分频。测距模块的刷新率通常在 2060 Hz 之间如果决策层跟采样层同为 100 Hz就会出现连续四五个决策周期读到完全相同的距离值状态机在对准和冲击之间反复横跳。分频成 25 Hz 之后每个决策周期都能拿到新数据。注意Edge_Analyze和Opp_Estimate必须在决策之前完成且不能有阻塞操作——一旦里面有delay或等待 I2C 的忙等整个 100 Hz 环的抖动会直接传到电机上。3. 边界识别与对手定位从原始读数到可用状态3.1 灰度阵列阈值标定别用出厂默认值灰度阵列最容易翻车的地方是阈值。同一块板子在赛场的黑色台面上读数是 300回到练习用的木地板上可能变成 1800如果阈值写死在代码里一到场地就会出现站在板子中间就报警或者压着白线还往前冲。正确做法是把三组数据都抓回来算。# 灰度阵列阈值标定把串口抓到的三组数据算成一组可用阈值 import numpy as np # 三个 CSV每行 8 列对应 8 路灰度通道的 12 位 ADC 值 surfaces { black_arena: np.genfromtxt(calib_black.csv, delimiter,), white_line: np.genfromtxt(calib_white.csv, delimiter,), wood_floor: np.genfromtxt(calib_wood.csv, delimiter,), } for name, data in surfaces.items(): med np.median(data, axis0) # 中位数抗离群点 print(f{name:12s} median{med.astype(int)}) # 阈值取黑白中位数中点后向黑侧偏 15% 的余量避免白色反光误判 black np.median(surfaces[black_arena], axis0) white np.median(surfaces[white_line], axis0) thr black (white - black) * 0.35 print(threshold , thr.astype(int)) np.savetxt(gray_thr.txt, thr, fmt%d) # 检查练习场地的木地板会不会被误判成白线 wood np.median(surfaces[wood_floor], axis0) print(wood false-hit channels:, int(np.sum(wood thr)), / 8)系数 0.35 是可以调的值越小越保守白线要更亮才触发漏检风险上升值越大越敏感地面反光或胶带边缘的高光就会误触发。抓数据时每块表面至少采 200 帧机器人在表面上来回移动覆盖不同角度和光照方向。输出里的wood false-hit channels是最该看的一行——如果练习场地上 8 路里有 2 路以上被判成白线说明阈值要么偏低要么这个场地的材质反光特性和赛场差太远必须换场地重标。3.2 单点测距给不出方位加一路滤波链路一个测距模块只能告诉你对手有多远给不出在哪个方向。常见做法有两种一种是左右各装一个模块夹角 30°45°两边读数之差配合机头朝向推算出大致方位另一种是用舵机带动单个模块做扇形扫描代价是方位更新率降到 510 Hz。前者适合快速对抗后者适合需要精确角度的场合。原始读数不能直接用超声波的多径反射和 ToF 在黑色吸光表面的偶发丢点都会制造野值。我一般串两级滤波三级中值去野值再跟一阶低通。/* 测距数据链3 点中值去野值 一阶低通去抖动 */ #define RANGE_BUF 3 float Range_Filter(float raw) { static float buf[RANGE_BUF] {0}; static uint8_t idx 0; static float y 0.0f; buf[idx] raw; idx (idx 1) % RANGE_BUF; /* 1. 中值三个样本排序取中间值滤掉单次多径/串扰野值 */ float a buf[0], b buf[1], c buf[2], med; if ((a b b c) || (c b b a)) med b; else if ((b a a c) || (c a a b)) med a; else med c; /* 2. 一阶低通alpha 越小越平滑但相位滞后越大 */ const float alpha 0.45f; y y alpha * (med - y); return y; }alpha 0.45在 25 Hz 决策频率下大约带来 2 到 3 个周期的滞后换算成 2 m/s 的车速是 1624 cm 的位置误差。这个数不能凭感觉调如果发现机器人冲到对手面前才转向先把alpha提到 0.6 试试代价是对手快速横向移动时距离读数会更跳。中值滤波的窗口不建议超过 3窗口拉长到 5 虽然更平滑但对手突然出现在近距离时的响应会明显变慢。3.3 把原始读数压成决策层能消费的五个量决策层不该直接看到 8 路 ADC 和一堆浮点距离只应该看到几个语义清晰的量。这是我的常用接口约定变量类型取值决策用途edge.maskuint8位掩码哪几路压线用于判断边沿方向edge.dirint8-1/0/1边线在左、无、右opp.dist_cmfloat0200三级距离区间切换opp.bearing_degfloat-9090角度环的目标输入opp.conffloat01低于 0.3 视为丢失opp.conf的算法可以很简单连续有效读数计数除以窗口长度读数超出量程或方差过大时计为无效。失效保护逻辑必须写死——连续 10 帧在 25 Hz 下约 400 ms置信度低于 0.3无条件切回搜索状态原地旋转。少了这一条机器人会在对手被铲翻到视野外之后傻站着全速顶空气。4. 策略分析有限状态机、转向闭环与对抗决策4.1 状态机骨架安全优先级最高的写法策略分析的第一个决定是用什么形式表达决策。神经网络和强化学习在论文里很好看但在只有两三周调试时间的比赛场景里有限状态机是唯一能保证可解释、可回滚的方案。状态不用多六个够用等待、搜索、对准、冲击、顶推、脱离。状态进入条件退出条件输出动作IDLE上电检测到开赛信号电机锁定SEARCH对手置信度低置信度 0.6原地慢速旋转ALIGN对手可信但角度偏|bearing| 8°角度环转向CHARGE对准完成距离 15 cm全速前进PUSH距离 15 cm距离 25 cm降速顶推ESCAPEedge.mask ! 0脱离动作完成先退后转/* 策略层25 Hz 调用的有限状态机 */ typedef enum { ST_IDLE, ST_SEARCH, ST_ALIGN, ST_CHARGE, ST_PUSH, ST_ESCAPE } State_t; static State_t st ST_IDLE; void Strategy_Step(const EdgeInfo_t *edge, const OppInfo_t *opp, float gyro_z) { /* 安全优先级最高边沿标志一亮无条件抢占进入 ESCAPE */ if (edge-mask ! 0 st ! ST_ESCAPE) { st ST_ESCAPE; Escape_Enter(edge-dir); /* 记录脱离方向决定先退哪一侧 */ } switch (st) { case ST_IDLE: if (Match_Started()) st ST_SEARCH; break; case ST_SEARCH: /* 原地慢速旋转找对手 */ Motion_Spin(SEARCH_RPM); if (opp-conf 0.6f) st ST_ALIGN; break; case ST_ALIGN: /* 角度环把机头指向对手 */ Motion_FaceTo(opp-bearing_deg); if (fabsf(opp-bearing_deg) 8.0f) st ST_CHARGE; if (opp-conf 0.3f) st ST_SEARCH; break; case ST_CHARGE: /* 全速冲击 */ Motion_Forward(FULL_PWM); if (opp-dist_cm 15.0f) st ST_PUSH; if (opp-conf 0.3f) st ST_SEARCH; break; case ST_PUSH: /* 贴身顶推保持机头指向 */ Motion_Forward(PUSH_PWM); if (opp-dist_cm 25.0f) st ST_ALIGN; break; case ST_ESCAPE: /* 脱离边沿先退后转 */ if (Escape_Done()) st ST_SEARCH; break; } }两个设计点值得说明。第一edge-mask ! 0的抢占判断放在switch之前而不是塞进各个case里——边界信息是安全信号不该依赖当前处于什么状态。第二PUSH状态用降速的PUSH_PWM而不是全速因为贴身状态下对手的反作用力和台面摩擦同时作用全速容易把自己顶得侧滑反而失去接触。4.2 转向控制外环管角度、内环管角速度轮式差速车的转向本质是左右轮速差但直接给差速的代码几乎不可能调稳因为轮胎打滑会让实际转角和指令转角脱节。我的做法是双环外环输入是目标方位角和陀螺仪积分得到的当前朝向之差输出是目标角速度内环输入是目标角速度和陀螺仪实测角速度之差输出是左右轮的 PWM 差分量。/* 增量式 PID外环输入角度误差输出目标角速度 */ typedef struct { float kp, ki, kd, out_min, out_max, i_limit, last_e, i_acc; } PID_t; float PID_Step(PID_t *p, float err, float dt) { float d (err - p-last_e) / dt; p-i_acc err * dt; if (p-i_acc p-i_limit) p-i_acc p-i_limit; /* 积分限幅抗饱和 */ if (p-i_acc -p-i_limit) p-i_acc -p-i_limit; float u p-kp * err p-ki * p-i_acc p-kd * d; if (u p-out_max) u p-out_max; if (u p-out_min) u p-out_min; p-last_e err; return u; }参数建议起步值调大后的表现判定依据角度环 Kp1.52.5收敛快但过冲、机头来回摆阶跃响应超调 15%角度环 Ki00.1消除稳态偏差过大会缓慢自转静止 5 s 无漂移角度环 Kd0.010.05抑制摆动过大会对噪声敏感手推扰动后 1 次回正速度环 Kp0.81.5响应快过大会电流尖峰空载转速误差 5%速度环 Ki0.20.5带载不降速过大会堵转升温顶墙 3 s 不啸叫调参顺序必须从内到外先标定陀螺仪零偏上电静止采 500 次取均值写进 flash再测 PWM 死区从 0 递增到车轮刚开始转动的那一点前面这段补偿掉然后调速度环最后调角度环。反过来先调角度环会因为速度环本身不稳而永远调不出干净的阶跃响应。角度环的死区建议设 ±2°太小会让机器人在对准时持续微动浪费扭力也增加误触边线的概率。4.3 对抗决策什么时候该撞什么时候该绕状态机管的是流程对抗决策管的是在同一个状态下选哪个方向。这部分是策略分析里最见水平的地方本质上是在进攻收益和出界风险之间做交换。4.3.1 三个输入变量的决策表对手距离自身离边沿机头朝向动作 80 cm中心任意慢速旋转搜索不盲目加速4080 cm中心偏离 30°边转向边前进弧线接近1540 cm中心对准全速冲击 15 cm中心对准降速顶推保持接触任意已压线任意优先脱离放弃本次进攻1540 cm靠近边沿对手在圆心侧侧移半步再对准别正面顶 15 cm靠近边沿对手背对边沿边沿压制保持推力不放速最后两行是胜负手。当自己已经靠近边沿而对手在圆心一侧时正面冲击的反作用力会把自己推出界正确做法是先做一个小幅度的横向位移把重心挪回内侧再重新对准。反过来如果对手已经背对边沿、自己在圆心侧那就应该保持推力不松因为对手每被推一厘米它的边沿余量就少一厘米而自己始终处于安全区。4.3.2 自杀式冲击的开关怎么用有一类场景值得单独留一个宏开关对手已经压线、机头角度良好、距离在 20 cm 以内。这时候一次不减速的冲击有很大概率把它送出去代价是自己也可能因为惯性滑出。要不要开取决于两个实测值——场地的摩擦系数和自身的制动距离。我的习惯是赛前用 4.4 节的扫参脚本算一遍过冲量如果过冲小于 15 cm 就打开否则关掉改用降速顶推。这个判断不要靠感觉因为不同场地的亚克力板和木质台面摩擦差距能到一倍以上。4.4 参数整定顺序与离线扫参决策阈值冲击距离、脱离判定时间、置信度门限不适合在实车上一个个试太慢且损耗大。用二维质点模型先把量级扫出来再上车微调。# 二维质点模型扫参估算冲击切换距离对应的过冲量 import numpy as np def simulate(d_trigger, v2.2, a_acc4.0, a_brake3.0, dt0.005, maxt1.2): v: 冲击速度 m/s; a_brake: 制动减速度 m/s^2; d_trigger: 切换距离 m x, vel, t 0.0, 0.0, 0.0 while t maxt: vel min(vel a_acc * dt, v) # 加速段 if x d_trigger: # 到达切换距离进入制动 vel max(vel - a_brake * dt, 0.0) x vel * dt t dt return x # 停止位置 for d in np.arange(0.10, 0.45, 0.05): stop simulate(d) print(ftrigger{d:.2f} m - stop{stop:.3f} m overshoot{stop-d:.3f} m)v用实测的最高线速度填a_brake在目标场地上实测——加速到全速后松油门量制动距离反推。输出的overshoot就是策略里必须预留的边沿余量如果算出来是 0.18 m那么ESCAPE状态就必须在距离边线还有 20 cm 以上时就能被触发否则物理上刹不住。这个脚本的价值不在精度而在于给出一个不可能更小的下界避免把冲击切换距离设成 8 cm 这种物理上无法实现的数。5. 台架验证、失效排查与实战技巧5.1 在实验室里复现比赛问题上场调试是最贵的调试方式。我的做法是把赛场要素拆开到台架上用 5 cm 宽白色美纹纸在木板上贴出直径 1.2 m 的圆环复现边界用纸箱套一层深色布当静态对手再用一台小风扇吹动纸箱模拟横向移动。灰度阈值标定、脱离逻辑、对准逻辑都能在这个台架上跑通。数据必须落盘。25 Hz 决策频率下把t_ms、gray_mask、dist_cm、pwm_l、pwm_r、state六列以 CSV 形式从串口打出来一场 3 分钟的对局大约 4500 行直接存文本完全够用。# 日志回放把一次对局的关键量画在一张图上定位问题 import numpy as np import matplotlib.pyplot as plt log np.genfromtxt(match_01.csv, delimiter,, namesTrue) t log[t_ms] / 1000.0 fig, ax plt.subplots(3, 1, sharexTrue, figsize(9, 7)) ax[0].plot(t, log[gray_mask]); ax[0].set_ylabel(edge mask) ax[1].plot(t, log[dist_cm]); ax[1].set_ylabel(dist / cm) ax[2].plot(t, log[pwm_l], labelPWM L) ax[2].plot(t, log[pwm_r], labelPWM R) ax[2].set_ylabel(PWM); ax[2].set_xlabel(t / s); ax[2].legend(locupper right) plt.tight_layout(); plt.savefig(match_01_review.png, dpi120)看图的顺序是固定的先看edge mask有没有在应该触发的时候触发再看dist_cm有没有出现不合理的跳变最后对照PWM判断执行是否跟上。三条线一起看绝大多数莫名其妙的动作都能定位到具体是哪一层出的问题。现象可能原因验证方法压线不返回灰度阈值偏黑白线未触发用calib_white.csv复算阈值对准时来回抖角度环 Kp 过大或零偏未标定上电静止看gyro_z均值冲到对手前才转向低通alpha过小相位滞后大提到 0.6 对比响应近身突然丢失目标超声波盲区或多径换 ToF 或加红外接近开关一撞就复位母线电压塌陷加大低阻电容、动力逻辑分开供电原地打滑推不动死区未补偿或轮胎磨损测 PWM 起始转动点5.2 边沿记忆用 300 ms 历史压住误出界只靠当前帧的灰度读数判断边界会漏掉最高速的那一瞬间——机器人从白线上方掠过的几十毫秒里采样点正好落在两次读取之间或者被胶带反光干扰判成黑色。补的办法是给边界加一段记忆一旦触发过就保持一段时间同时给脱离动作设一个最短执行时间防止刚退半步就急着回去进攻。/* 边沿记忆一旦触发过边界强制走完一段脱离动作再允许加速 */ #define EDGE_MEM_MS 300 #define ESCAPE_MIN_MS 400 static uint32_t last_edge_ms 0; /* 最近一次边界触发时刻 */ static uint32_t escape_start_ms 0; void Edge_Remember(uint8_t mask, uint32_t now_ms) { if (mask ! 0) last_edge_ms now_ms; /* 刷新触发时刻 */ } int Edge_Cooling(uint32_t now_ms) /* 1 仍在冷却期 */ { return (now_ms - last_edge_ms) EDGE_MEM_MS; } /* 决策层再加一条硬约束 */ if (st ST_ESCAPE) { if ((now_ms - escape_start_ms) ESCAPE_MIN_MS) return; /* 最短脱离 */ if (!Edge_Cooling(now_ms)) st ST_SEARCH; }EDGE_MEM_MS和ESCAPE_MIN_MS的关系是这个技巧的核心前者是记住压过线的时间后者是必须走完脱离动作的时间。两个值都给 300 ms 会导致退到一半就掉头回去两个值都给 800 ms 又会让机器人在对手已经冲过来的时候还在慢慢悠悠地退白送一分。我的起步配置是 300/400然后在贴了白线的木板上做 50 次全速冲击统计两组数据——压线后未能脱出的次数、脱离过程中过冲出界的次数取两者之和最小的那组。这个统计做完通常能把比赛里最恼人的那类边界误判压到两位数以下。本文还有配套的精品资源点击获取
返回列表