简介:十八届智能车单车越野组沁恒方案分享,聚焦CH32V307主控下的GPS基础循迹,适合蓝桥杯智能车竞赛单车越野组参赛者和嵌入式开发学习者。文档详细列出K车模、LQ-CH32V307母板、WCH-LINK-RV下载器、无刷电机及驱动、光电传感器、双频GNSS定位模块等硬件选型,并给出编码器、电机驱动、陀螺仪等模块的连接方式与注意事项。控制部分重点讲解动量轮平衡调试,包括并级PID与串级PID的实现与参数换算,以及基于GPS与陀螺仪的循迹路径跟踪思路,附关键代码示例。资源为1个PDF文件,共837KB,篇幅紧凑、可直接对照实践。已有915人学习下载,对于准备单车越野组比赛或研究直立平衡算法的读者,是一份高性价比的参考方案。
1. 方案背景与整体设计思路
第十八届全国大学生智能车竞赛的单车越野组,算是这几年规则变化里比较有意思的一个方向。单车本身是两轮自平衡结构,天然就带着不稳定性,再加上越野赛道里有坡道、碎石、草地这类非平路元素,控制难度比传统四轮组高了不少。而这套沁恒方案最核心的思路,是把GPS作为基础定位手段参与循迹,而不是像室内组那样完全依赖电磁或摄像头。
注意这里的用词是“基础循迹”,不是“纯GPS导航”。GPS在室外开阔环境下的精度确实够用,但智能车竞赛跑的是固定赛道,赛车需要的是“知道自己大概在哪条路上”,而不是“从A点导航到B点”。所以这套方案的实际做法是:用GPS拿到全局坐标,再配合车身上的陀螺仪、编码器做局部姿态修正,最后通过预先采集的赛道点序列计算出横向偏差,送给转向和速度环去执行。
选择沁恒方案的一个重要原因是芯片本身的定位。CH32V307VCT6是沁恒基于RISC-V内核设计的MCU,主频能跑到144MHz,带FPU,内部集成2路CAN、多路USART,还有丰富的高级定时器。放在单车越野这个场景里,它的算力足够跑通GPS解析、坐标转换、PID控制和自平衡姿态解算,而且外围接口齐全——GPS模块走串口,编码器走定时器正交解码,陀螺仪走I2C或SPI,全部都能直接接,不需要额外加转接芯片,这在国内竞赛方案里算是非常省心的。
如果说这套方案的选型逻辑,我觉得可以用一句话概括:用最容易买到的通用模块,做一套能稳定完赛的最小系统。CH32V307官方有完整的库函数和例程,GPS模块用ublox或中科微的方案都很常见,编码器和陀螺仪也是智能车玩家手里最不缺的传感器。相比某些方案用树莓派或者高性能DSP,这套体系的学习门槛低,出问题也好排查,特别适合第一次参加单车组、或者从传统四轮组转过来的队伍。
适合参考这份方案的读者大概是两类:一类是准备参加下一届智能车竞赛、尤其是单车越野或室外循迹方向的在校学生,另一类是想把手头CH32V307开发板利用起来、做一套室外GPS定位小车的爱好者。不管哪类人,只要把基础循迹这套框架跑通,后面加摄像头、加激光雷达甚至加RTK,都只是挂在同一个框架上的扩展模块而已。
2. GPS基础循迹的核心实现细节
2.1 经纬度坐标与平面坐标的转换处理
GPS模块输出的原始数据是WGS84坐标系下的经纬度,比如北纬34度、东经108度这样。但循迹算法没法直接在经纬度上算横向偏差,因为纬度方向和经度方向每度对应的物理距离不一样,直接拿经纬度差值算PID会得到完全错误的结果。所以第一步必须做坐标投影,把经纬度转到平面直角坐标系。
推荐的做法是高斯-克吕格投影,或者更简单的UTM投影。UTM把地球表面分成60个投影带,每带6度经度,在带内用米为单位的平面坐标。具体到竞赛场景,一个赛道的范围通常只有几百米,完全落在同一个投影带内,所以误差完全可以接受。若嫌UTM在边缘区域略有畸变,也可以用本地切平面近似——找一个赛道中心点作为原点,用等距圆柱投影公式把经纬度差换算成米。
// 以赛道中心点为原点,将WGS84经纬度转换为本地平面坐标(单位:米) #define LAT_REF 34.1234567 #define LON_REF 108.9876543 void gps_to_local(double lat, double lon, float *x, float *y) { double dlat = lat - LAT_REF; double dlon = lon - LON_REF; *y = (float)(dlat * 111320.0); // 纬度方向每度约111.32km *x = (float)(dlon * 111320.0 * cos(LAT_REF * M_PI / 180.0)); // 经度方向需乘以cos(纬度) }这是最简化的本地切平面换算,实测在数百米范围内精度能达到厘米到分米级别,对智能车循迹完全够用。这里有个细节:经度换算时必须乘以cos(纬度),很多新手会漏掉,结果在赛道纵向上的比例是对的,横向全错,车跑起来就像喝醉了一样。
如果你用的GPS模块能直接输出UTM坐标,那就省事很多。但要注意UTM坐标是分带的,不同带的坐标值会有跳变,建议固定使用同一个投影带参数,不要自动切换。
2.2 赛道路径的采集与匹配算法
基础循迹的前提是手里有一条“参考路径”。这条路径怎么来?一般有两种方式:第一种是拿着GPS模块沿赛道走一圈,每隔0.5米或1米记录一个坐标点,生成一个点序列文件;第二种是赛事组委会直接公布赛道GPS坐标文件,选手下载后解析使用。两种方式到手后都可以转成本地平面前提下的(x, y)点集。
得到点集之后,算法要做的是“找最近点”。每一帧拿到当前GPS坐标,遍历参考路径上的所有点,找到距离最近的那个,这个点对应的索引就是车在路径上的“投影位置”。有了投影位置,就可以计算横向偏差——当前坐标到最近路径点的方向向量,结合车的航向角,算出车偏移了中心线多少。
实际工程里不会真的每帧全量遍历几百上千个点,那样浪费算力。常见做法是先用一个粗略的“当前索引窗口”缩小范围,比如设定上下各50个点的搜索区间,只有当车跑出这个区间时才重新全局搜索。因为赛车的运动是连续的,上一帧在哪个位置,下一帧大概率还在附近,用滑动窗口可以把最近点搜索优化到微秒级。
int find_nearest_point(float cur_x, float cur_y, int last_idx, int search_range) { int start = last_idx - search_range; int end = last_idx + search_range; float min_dist = 1e9f; int min_idx = last_idx; for (int i = start; i <= end; i++) { int idx = (i + path_len) % path_len; // 环形路径取模 float dx = cur_x - path_x[idx]; float dy = cur_y - path_y[idx]; float d = dx * dx + dy * dy; if (d < min_dist) { min_dist = d; min_idx = idx; } } return min_idx; }参考路径本身也要做预处理。一是平滑处理,因为手工采集的点会有抖动,可以用滑动平均或三次样条拟合,避免路径上出现尖锐的拐角;二是等间距重采样,保证每个相邻点之间的距离大致相等,这样在计算航向角、曲率的时候不会因为点距不均而产生跳变。
2.3 GPS定位三边测量算法的补充解析
很多做GPS定位的同学会问:GPS模块自己就输出坐标了,为什么还要学习三边测量算法?这里有一个概念需要澄清:GPS接收机内部确实是靠“三边测量”或“伪距定位”来解算自身位置的,但模块输出的位置是已经解算好的结果。我们学习三边测量,更多是为了理解定位误差是怎么来的,以及在某些基站定位(比如伪GPS、室内定位模拟器)场景下,如何自己写代码解算位置。
三边测量的核心是:已知三个以上基站的坐标和到目标的距离,联立圆方程组解出目标位置。GPS里的“基站”就是卫星,“距离”就是伪距。对于智能车竞赛来说,如果你只是用现成GPS模块,不需要自己写三边测量;但如果你在做的是“基站定位循迹”,比如在场地四周布置几个UWB基站,那就需要用到这个算法了。
// 经典的三边测量最小二乘解法(示意) // 已知(mx[i], my[i])为基站坐标,d[i]为测量距离 // 构造 Ax = b,用最小二乘求解目标位置 (x, y) void trilateration(float mx[], float my[], float d[], int n, float *out_x, float *out_y) { // 取第一个基站作为参考,构造n-1个方程 // A = [2*(mx[i]-mx[0]), 2*(my[i]-my[0])] // b = d[0]^2 - d[i]^2 + mx[i]^2 - mx[0]^2 + my[i]^2 - my[0]^2 // 解线性方程组即可 }理解了原理之后你会发现,GPS模块输出的坐标其实已经是对多颗卫星信号做了融合解算的结果,模块内部做了大量滤波和校正。所以在做循迹的时候,不需要重复造轮子,只需关注模块输出的坐标质量即可。但如果你的应用场景是“模拟GPS信号”之类的测试环境,三边测量算法就是必需品了。
3. 实操过程与核心环节实现
3.1 沁恒CH32V307环境搭建与GPS模块连接
先说一下硬件连接。CH32V307VCT6开发板上串口资源丰富,我们选用USART1接GPS模块,波特率9600或115200,具体看模块默认配置。GPS模块的TXD接MCU的RXD,RXD接TXD,共地,用3.3V或5V供电取决于模块手册。CH32V307的串口接收用中断或DMA都行,推荐DMA加空闲中断,这样在处理长串NMEA语句的时候不容易丢字节。
软件环境方面,沁恒提供了MounRiver Studio,开箱即用,不需要额外配置工程模板。新建工程后,在main函数里初始化串口和GPIO,然后进入主循环不断解析串口接收到的数据。
// NMEA数据解析:提取GNGGA语句中的经纬度 void parse_nmea_line(char *line) { if (strncmp(line, "$GNGGA", 6) == 0) { // 按逗号分割 char *p = line; int field = 0; char *lat_str = NULL, *lon_str = NULL; while ((p = strchr(p, ',')) != NULL) { p++; field++; if (field == 2) lat_str = p; // 纬度 if (field == 4) lon_str = p; // 经度 } // 转换为十进制经纬度,并调用 gps_to_local 转平面坐标 } }注意GNGGA语句中纬度格式是“ddmm.mmmm”,要转成十进制度数需要把“度分”格式拆开:前两位是度,后面是分,分除以60加到度上。经度同理。另外推荐使用GNGGA或GNRMC语句,这两种包含定位质量指示,可以根据定位状态决定是否更新坐标,避免在未定位时用无效数据污染路径匹配。
3.2 五路循迹传感器的引入与GPS互补
纯GPS循迹在开阔场地确实能跑,但一遇到桥洞、树荫、高架旁边这类遮挡严重的区域,GPS信号会明显变差,坐标漂移达到几米甚至几十米。这时候如果只靠GPS,车的横向偏差会瞬间跳变,随之而来的就是转向剧烈抖动。为了避免这种问题,沁恒方案里通常会加一组五路灰度循迹传感器作为近场修正。
所谓五路循迹,就是一字排开五个红外灰度传感器,分别检测赛道黑线或白线的位置。正常行驶时车在赛道中间,中间三个传感器里靠近中心的有信号;车偏左时,左侧传感器先触线。通过五个传感器的ON/OFF组合,可以粗略判断车相对赛道的横向偏移量。这个偏移量不需要很精确,只要能区分“偏左、居中、偏右、大偏左、大偏右”这几种状态即可。
GPS负责全局粗定位,五路灰度负责局部细纠偏,两者融合策略简单有效:当GPS的横向偏差绝对值小于阈值(比如0.5米)时,相信灰度传感器,用它给出的偏移量参与转向;当灰度传感器全部离地或找不到线时,切回GPS数据。这样一来,GPS的长时间稳定性和灰度传感器的短时间高精度就结合起来了。
我个人在调试时发现,这种融合策略的关键不是算法多复杂,而是阈值切换要平滑。直接硬切换会让转向输出产生跳变,车会抖一下。建议在切换点附近做一个线性过渡区,比如GPS偏差从0.4米到0.6米之间,灰度权重从1线性降到0,GPS权重从0升到1,这样过渡就柔顺很多。
3.3 单车自平衡与转向控制的联动
单车越野组和四轮组的本质区别在于,单车必须先解决“站得住”的问题,才有资格谈“跑得快”。沁恒方案里通常用MPU6050或ICM20602陀螺仪做姿态解算,加上编码器反馈车轮转速,构成一个典型的自平衡闭环。
自平衡环的运行频率要比循迹环高得多。一般建议姿态环跑200Hz到500Hz,而GPS循迹环只有10Hz到20Hz(GPS模块输出频率也不支持太高)。这两个环之间怎么协同?答案是级联:外环是GPS循迹,输出期望转向角和期望速度;内环是自平衡和转向控制,跟踪外环指令。外环频率低没关系,内环在两次外环更新之间保持上一次的指令即可。
实际工程中要注意的是,转向控制不能直接叠加到直立平衡环的输出上。单车的转向靠的是车身倾斜和车轮转速差,如果直接把转向PID输出加到电机PWM上,会破坏自平衡的稳定性。正确做法是:转向环输出的是“目标倾斜角”,把目标倾斜角作为姿态环的输入,让车身通过倾斜来实现转向。这个逻辑类似于骑自行车,你拐弯时是先倾斜车身,而不是直接拧车把。
// 外环:GPS循迹计算横向偏差 -> 目标倾斜角 float lateral_error = calc_lateral_error(cur_pos, nearest_path_point); float target_lean_angle = lean_pid.compute(lateral_error); // 内环:姿态环跟踪目标倾斜角 float current_lean_angle = imu.get_angle_x(); float balance_output = balance_pid.compute(target_lean_angle - current_lean_angle);4. 常见问题与排查技巧实录
4.1 GPS数据跳变与静态漂移
GPS模块在静止状态下,坐标也会在小范围内随机漂移,通常半径为1到3米。这个漂移在循迹时会造成横向偏差的抖动,严重时车会像“画龙”一样左右摆。解决办法首先是硬件层面——把GPS天线放在车的最高点,远离电机、电池等金属和电磁干扰源;其次是软件层面——对GPS坐标做低通滤波或卡尔曼滤波。
我实测下来,最简单的滑动平均滤波(取最近5到10个点的平均值)就能明显改善漂移。但注意滑动窗口不宜过大,否则车真正拐弯时坐标响应会变慢,形成转弯迟滞。更进阶的做法是用卡尔曼滤波融合GPS和陀螺仪积分,GPS负责修正长期漂移,陀螺仪积分负责短期平滑,两者互相修正,效果非常好。
如果你的场地周围有高楼或金属围栏,GPS反射会造成多径效应,坐标会突然跳出几十米。遇到这种情况,一个非常实用的防护手段是:设定单帧位移最大阈值。如果当前坐标与上一帧坐标的距离超过合理范围(比如5米),就判定这次GPS数据无效,暂时用上一帧数据,等待下一帧更新。这种简单的野值剔除策略,能挡住绝大多数多径跳变。
4.2 启动阶段无法定位或定位缓慢
GPS冷启动时搜星可能需要几十秒甚至几分钟,这对比赛来说是不可接受的。建议的解决办法是:在赛道调试阶段就把GPS模块放在比赛场地,持续上电至少10分钟,让模块下载星历数据;比赛前不要完全断电,如果必须断电,尽量缩短断电时间,这样模块进入热启动状态,重新定位时间能压缩到几秒。
另外,GPS模块的NMEA语句里有定位质量指示字段,GGA语句中的第6个字段为0表示未定位,为1表示单点定位,为2表示差分定位。循迹代码里一定要判断这个字段,未定位时不要使用坐标。否则在启动阶段车还没定位成功就已经开始跑了,第一次算出的横向偏差可能指向几十米外,车会直接冲出去。
4.3 经纬度坐标在本地地图绘制时的大幅偏移问题
热词里提到了一个很常见的现象:原生GPS坐标在天地图这类国内地图上绘制时,会偏移几百米。这个偏移是因为国内地图使用的坐标加偏了,不是GPS本身错了。智能车比赛中我们用的是原始GPS坐标,不需要做加偏纠偏,所以这个问题在竞赛里不存在。但如果你把采集到的赛道坐标导入地图软件查看,就会看到明显的偏移,这是正常的。只需要在代码里统一使用WGS84坐标处理即可,不要在中间混入火星坐标。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查与解决办法 |
|---|---|---|
| 坐标跳变几十米 | 多径效应或野值 | 增加单帧位移阈值过滤,天线远离金属物 |
| 车跑起来左右画龙 | GPS静态漂移未滤波 | 加滑动平均或卡尔曼滤波,减小转向P |
| 转弯明显滞后 | 滑动窗口过大或外环频率低 | 缩小滤波窗口,提高GPS更新率或做预测补偿 |
| 启动即冲出去 | 未判断定位质量 | 增加定位质量字段检查,未定位时禁止循迹 |
| 树荫/桥洞下失控 | GPS信号遮挡 | 引入五路灰度传感器做近场修正 |
| 自平衡和转向相互影响 | 操控叠加方式错误 | 转向应输出目标倾斜角,而非直接加PWM |
5. 个人调试心得与扩展建议
调试这套方案的过程中,有一个体会特别深:GPS循迹和室内循迹最大的区别在于“信任度”。室内电磁或摄像头数据每秒更新几百次,你几乎可以把传感器数据当作真值来用。但GPS的数据是慢的、有噪声的、偶尔还撒谎的,你必须建立一套数据可信度评估机制——什么时候信它,什么时候不信,什么时候降权使用。这个思维转变,比任何算法本身都重要。
另外一个小技巧分享给大家:调试时不要一上来就跑全赛道,先在赛道上选一段100米左右的直线段,把车放在中心线上,观察输出的横向偏差是否在零附近。再用遥控器(或手动推车)把车故意偏移0.5米,观察横向偏差是否稳定输出0.5米左右的数值。这个“静态标定”能帮你快速验证坐标转换和路径匹配是否正确,能省下大量的赛道试跑时间。
至于后续扩展,这套框架的可玩性其实很高。比如把GPS模块换成RTK高精度定位,定位精度能从米级提升到厘米级,很多控制策略都能简化;再比如用ESP32或树莓派做视觉识别辅助,识别路肩、障碍物甚至交叉路口,就能从“基础循迹”升级到“高级感知”。甚至你可以在参考路径上标注不同的速度区间,下坡前提前减速,出弯后提前加速,这些都是基于现有GPS框架的小改动。
本文还有配套的精品资源,点击获取