
简介这份PDF实验报告面向乐高EV3教育平台的学习者与指导教师围绕RobotC环境下的巡线小车编程实践展开帮助读者从电机控制、传感器读取到程序结构逐步建立机器人控制的基本思路。内容以6学时实验为主线涵盖电机转速与差速转向、触碰传感器和光电传感器的初始化与读值判断、while循环与wait1Msec延时配合以及利用光电传感器在黑线边沿反复修正方向实现巡线的原理并延伸至超声波测距与电机角度控制等模块。资源包共1个PDF文件约756KB为完整的标准实验报告文档含实验原理、示例代码与图示说明便于对照理解与复现。已有583人学习适合作为课堂实验配套材料或自学参考帮助读者把C语言编程、硬件控制与传感器应用串联起来提升动手调试与问题解决能力。1. 从一份实验报告到一台能跑的车乐高 RobotC 巡线小车到底在做什么把光电传感器装在小车底盘右侧让它盯着黑线边缘读值一变就调左右轮差速——听起来像玩具但真跑起来10 秒内跑完全程、不脱线、不卡顿靠的是一整套电机控制、传感器采样和参数整定的配合。这份资源就是围绕乐高 Mindstorms NXT/EV3 平台用 RobotC 的 C 语言 API 把巡线小车从组装、接线、写程序到调试跑通的完整实验记录包含电机转速与转角控制、触碰/光电/超声波三类传感器的初始化与读取、差速转向原理以及最终 10.03 秒完赛的代码思路和结构改进复盘。适合刚接触 RobotC 的学生、带实验课的老师以及想拿一套能直接复现的巡线控制骨架做二次开发的开发者。它不教你 C 语言语法但把“传感器读值怎么变成电机功率”这条链路讲得很具体拿来就能改阈值、改速度、改转向逻辑。2. 硬件与开发环境NXT 主控、RobotC 固件和传感器接线2.1 器材清单与端口分配这份实验报告里列出的器材很朴素一套 LEGO Mindstorms 教育机器人 NXT 版本、一台装好 RobotC 的电脑、若干连接线。真正决定程序能不能跑起来的是端口分配有没有和代码里的初始化对上。RobotC 的传感器读取依赖端口号代码里写S1物理线就必须插在 1 号口插错口不会报编译错误只会读回一个莫名其妙的常量这是新手最容易翻车的地方。器材/接口型号或端口作用代码中的对应主控NXT 版本运行 RobotC 固件、执行用户程序task main()入口电机 A/BmotorA / motorB左右轮驱动差速转向motor[motorA]、motor[motorB]光电传感器S1示例读地面反射光强度判断黑白sensorLightActive触碰传感器S1示例碰撞检测读值 0/1sensorTouch超声波传感器S1示例测距单位英寸超量程返回 -1sensorSONAR注意表里三个传感器都写了 S1这是原文示例的写法实际车上不可能共用一个口。我一般会固定成光电走 S1、触碰走 S2、超声波走 S3然后在代码顶部用#pragma config一次性声明后面所有读取都用传感器名而不是端口号改接线时只动一行配置。2.2 RobotC 工程建立与固件更新RobotC 不是普通 C 编译器它要把程序编译成 NXT 能执行的固件字节码再通过 USB 或蓝牙下载。流程上先装 RobotC 开发环境用它的 Firmware Update 把主控固件刷到匹配版本再新建一个LEGO Mindstorms NXT类型的工程。工程建好后平台类型、传感器类型这些编译期配置都写在文件头部编译时由预处理器展开所以顺序不能乱先声明传感器再写task main()。#pragma config(Sensor, S1, light1, sensorLightActive) // 1号口接光电传感器 #pragma config(Sensor, S2, touch1, sensorTouch) // 2号口接触碰传感器 #pragma config(Sensor, S3, sonar1, sensorSONAR) // 3号口接超声波传感器 #pragma config(Motor, motorA, leftMotor, tmotorNXT, PIDControl, encoder) #pragma config(Motor, motorB, rightMotor, tmotorNXT, PIDControl, encoder) task main() { // 程序主体 }这段配置的作用是把“物理端口”翻译成“有名字的传感器对象”。Sensor后面的S1是端口light1是你在代码里用的名字sensorLightActive告诉编译器这个口接的是主动式光电传感器。电机那两行里的PIDControl和encoder表示启用内置编码器和闭环控制后面做转角控制时nMotorEncoder才有意义。参数改错最典型的后果是编译能过、下载能跑但SensorValue[light1]永远返回 0 或 1023这时候先别怀疑算法回头查端口和类型。2.3 在线调试窗口把传感器读值变成可见的数字RobotC 有一个很多人忽略的功能在线调试。程序下载运行后调试窗口能实时显示SensorValue、nMotorEncoder这些变量的当前值不用在代码里插displayText也能看到光电传感器在黑线和白底上的读数差。这一步对巡线特别关键因为阈值不是拍脑袋定的是看出来的。常见做法是先把车架空手动把光电传感器从黑线移到白底再移回来记录调试窗口里读值的变化范围取中间值当阈值。原文里用的阈值是 45这个数字只对当时那台车的传感器高度、地面材质和环境光有效换一块场地就得重新测。我一般会在调试阶段临时加一段把读值打到 NXT 液晶屏上的代码跑一圈回来再定阈值比盲调靠谱得多。3. 核心控制链路拆解从电机 API 到差速转向3.1 电机转速控制与差速转向的三种形态RobotC 控制电机最直接的方式是给motor[]赋一个 -100 到 100 的功率值正数正转、负数反转、0 停止。这个值不是精确转速而是 PWM 占空比所以同样赋 75电池电量不同、地面摩擦不同实际轮速会有差异。巡线算法本质上就是不断改这两个功率值。差速转向有三种形态原文配图讲得很清楚外侧轮快、内侧轮慢车走大弧线一侧转、一侧停转弯半径小一些两侧反向转可以原地打转。巡线时最常用的是第一种和第二种的混合——偏得少就微调差速偏得多就一侧停甚至反转把车头拉回来。task main() { motor[motorA] 100; // 左轮全速 motor[motorB] 60; // 右轮降速车向左偏转 wait1Msec(500); // 保持 500ms motor[motorA] 0; // 左轮停 motor[motorB] 80; // 右轮继续继续左转 wait1Msec(300); }这段代码演示的是开环差速没有传感器反馈纯靠时间控制转向角度。参数上wait1Msec的毫秒数决定了转多少度但受电量和地面影响很大所以只能用于粗略动作。真正巡线必须闭环也就是把光电读值接进来决定功率下一节展开。3.2 光电传感器巡线阈值判断与边沿摇摆巡线的核心逻辑不复杂光电传感器照地面黑线吸光、白底反光读值差异明显。把传感器装在黑线一侧车走正时读值稳定在某个中间值附近车往白底偏读值变大车压到黑线读值变小。程序不断采样根据读值决定往哪边修。原文的代码思路是“光电置于黑线右侧亮度过大说明向右偏反之向左偏向某方向偏就增大该方向电机功率”。这是一个典型的 bang-bang 控制没有比例项转向动作比较生硬但胜在简单、跑得动。#pragma config(Sensor, S1, light1, sensorLightActive) task main() { int threshold 45; // 黑白分界阈值需实测 int baseSpeed 70; // 基础速度 int turnSpeed 30; // 转向时的内侧轮速度 wait1Msec(100); // 等传感器读数稳定 while (true) { int lightVal SensorValue[light1]; if (lightVal threshold) // 读到白底车偏右向左修 { motor[motorA] turnSpeed; // 左轮慢 motor[motorB] baseSpeed; // 右轮快 } else // 读到黑线车偏左向右修 { motor[motorA] baseSpeed; motor[motorB] turnSpeed; } } }逐段看threshold是分界值必须用调试窗口实测原文的 45 只作参考baseSpeed和turnSpeed的差值决定修正力度差值越大转向越猛越容易在直道上画龙wait1Msec(100)放在循环外是为了避开上电瞬间传感器读数不稳原文也特别提醒过这一点。循环里没有延时采样频率取决于主控执行速度这会导致高频抖动实际调的时候往往要在循环里加一个几毫秒的wait1Msec来降低采样率、平滑转向。提示单光电传感器只能判断“偏左还是偏右”判断不了“偏多少”所以直道上会持续左右摇摆。这是原理决定的不是代码写错了。想跑得直要么加第二个光电做双边沿检测要么引入比例控制。3.3 触碰与超声波传感器安全兜底和距离触发巡线车跑起来最怕撞墙和冲出场地触碰和超声波就是两道保险。触碰传感器读值只有 0 和 1按下为 1代码里用while(SensorValue(touch1) 0)包住前进逻辑一旦撞上就跳出循环执行反转或停止。超声波读回的是英寸距离超过 255 英寸返回 -1这个 -1 要单独处理否则 20的判断会把“没检测到”当成“很远”车会一直往前冲。#pragma config(Sensor, S2, touch1, sensorTouch) #pragma config(Sensor, S3, sonar1, sensorSONAR) task main() { while (SensorValue[touch1] 0) // 未触碰时前进 { int dist SensorValue[sonar1]; if (dist ! -1 dist 20) // 有效距离且小于20英寸 { motor[motorA] 0; motor[motorB] 0; break; // 停车退出 } motor[motorA] 75; motor[motorB] 75; } motor[motorA] -75; // 触碰后倒车 motor[motorB] -75; wait1Msec(1000); }这里dist ! -1的判断是血泪经验超声波在空旷场地经常读回 -1如果不排除-1 20成立车会在什么都没碰到的时候突然停车。参数 20 是停车距离单位英寸按车长和反应时间调速度越快这个值要留得越大。3.4 电机转角控制nMotorEncoderTarget 的精确停位转速控制是“给功率、跑多久”转角控制是“转多少度、自动停”。NXT 电机内置编码器360 度分辨率RobotC 用nMotorEncoderTarget[]设定增量配合motor[]启动转到位后电机自动停。原文的例子是以 6 度为单位一点点前进用来演示精确控制。task main() { int nMoveSize 6; // 每次转动增量单位度 int nSpeed 50; // 电机功率 int nDelay 600; // 每步之间的停顿 bFloatDuringInactiveMotorPWM false; // 制动模式停得稳 nMotorEncoder[motorA] 0; // 编码器清零 nMotorEncoderTarget[motorA] 0; while (true) { nMotorEncoderTarget[motorA] nMoveSize; // 设定目标增量 motor[motorA] nSpeed; // 启动电机 wait1Msec(nDelay); } }关键点在bFloatDuringInactiveMotorPWM false制动模式下电机停转时会有保持力功率和速度线性度好浮动模式下 40 以下功率几乎不转线性区很差。做巡线时如果用的是转角控制而不是纯功率控制这一行必须设成制动否则低速段完全不可控。nMotorEncoderTarget只设定目标不启动电机后面必须跟一句motor[]这是原文特意强调的漏了就是“设了目标车不动”。4. 巡线程序组装把传感器、电机和循环拼成一台能跑的车4.1 主循环结构采样、判断、执行的固定节奏把前面几块拼起来巡线程序的主循环就是一个固定节奏读光电、和阈值比、算左右轮功率、写电机、短暂延时。这个结构简单到几乎不会出错出错的地方全在参数和机械上。我一般会把阈值、基础速度、转向速度、循环延时这四个量提到文件顶部做成常量调车时只改这四行不动逻辑。#pragma config(Sensor, S1, light1, sensorLightActive) #pragma config(Motor, motorA, leftMotor, tmotorNXT, PIDControl, encoder) #pragma config(Motor, motorB, rightMotor, tmotorNXT, PIDControl, encoder) const int THRESHOLD 45; // 黑白分界实测确定 const int BASE_SPEED 70; // 直道基础速度 const int TURN_SPEED 25; // 转向内侧轮速度 const int LOOP_DELAY 5; // 循环延时毫秒 task main() { wait1Msec(100); // 上电稳定 while (true) { int v SensorValue[light1]; if (v THRESHOLD) // 偏白向左修 { motor[leftMotor] TURN_SPEED; motor[rightMotor] BASE_SPEED; } else // 偏黑向右修 { motor[leftMotor] BASE_SPEED; motor[rightMotor] TURN_SPEED; } wait1Msec(LOOP_DELAY); } }LOOP_DELAY是调车时最容易被忽略的参数。设成 0采样频率过高电机对每一个微小读值波动都响应车会高频抖动设成 20 以上响应变慢过弯会冲出去。5 到 10 毫秒是常见起点具体看车速和弯道半径。4.2 阈值实测与速度整定10.03 秒背后的调参过程原文最终成绩是 10.03 秒这个数字不是一次写出来的是反复调阈值和速度调出来的。调参顺序我建议固定成三步先架空测阈值再低速跑直道最后逐步提速。第一步把车架空或用手推着沿黑线走看调试窗口里光电读值在黑线和白底上的两个稳定区间取中间值当THRESHOLD。如果两个区间有重叠说明传感器太高或环境光太强先调机械高度。第二步BASE_SPEED设到 40 左右TURN_SPEED设到 20跑完整一圈观察是脱线还是画龙。脱线说明转向力度不够加大两者差值画龙说明转向过猛减小差值或加大LOOP_DELAY。第三步直道稳定后每次加 10 的BASE_SPEED直到过弯开始冲出去再退回上一档。原文 10.03 秒对应的速度大概就在这个临界点附近再快就牺牲稳定性。4.3 LCD 显示给定值与反馈值调试期的黑匣子原文的扩展要求里提到“在控制器 LCD 界面上显示给定值和传感器反馈数据”这个功能在调参阶段价值很大。NXT 屏幕小但显示两三个数字够用跑完一圈不用连电脑就能看到过程中读值有没有跳变、有没有长时间贴着一个极值。task main() { while (true) { int v SensorValue[light1]; displayTextLine(1, Light: %d, v); displayTextLine(2, Thr: %d, THRESHOLD); displayTextLine(3, Spd: %d, BASE_SPEED); wait1Msec(100); } }displayTextLine的行号从 0 开始刷新太快会闪100 毫秒一次比较合适。这个显示任务可以和巡线主循环放在同一个task main里也可以拆成独立 task 并行跑RobotC 支持多任务但共享变量时要注意读写时序简单场景下合在一起更省心。5. 避坑与排查巡线车跑不直、读值乱跳、转向卡顿的现场记录5.1 现象车一上电就原地打转光电读值恒定不变原因几乎都是端口和配置对不上。#pragma config里写的是 S1线插在 S2或者传感器类型写成了sensorTouch却接了光电编译不报错运行时SensorValue返回一个固定值程序按固定值判断车就朝一个方向死转。解决先看调试窗口里读值随遮挡变化是否变化不变就查三处——物理端口、#pragma里的端口号、传感器类型常量。三处对齐后读值一定会有反应。5.2 现象直道上左右高频抖动车像在“画龙”原因是单光电 bang-bang 控制本身没有比例项加上循环里没有延时采样频率远高于机械响应速度每一个读值噪声都被放大成一次转向指令。解决先加wait1Msec(5~10)降采样再把BASE_SPEED和TURN_SPEED的差值调小让每次修正柔和一点如果还抖考虑加第二个光电做双边沿或者引入简单的比例控制用(v - THRESHOLD)乘以一个系数去调差速而不是非黑即白。5.3 现象过弯时冲出去直道却跑得很好原因是速度整定只看了直道过弯时转向修正来不及。BASE_SPEED偏高、LOOP_DELAY偏大、或者光电传感器装得离轮轴太远都会导致这个结果。解决原文的改进建议里专门提到这一点——光电传感器在 y 轴上离轮轴太远转向时传感器划过的线速度大信号延迟明显车会频繁卡顿。把传感器往轮轴方向移近同时把过弯段的速度降下来必要时在弯道前用超声波或第二个光电做预判减速。5.4 现象超声波明明前方无障碍车却突然停车原因是SensorValue[sonar1]返回了 -1表示超出量程或读取失败而判断条件写成了dist 20-1 满足条件误触发停车。解决所有超声波判断前先排除 -1写成dist ! -1 dist 20。另外超声波对软质障碍物和斜面的反射很差别把它当唯一保险触碰传感器该装还得装。5.5 现象转角控制设了目标值电机不转或转过冲不转的原因通常是只写了nMotorEncoderTarget[]没跟motor[]目标设了但没给功率。过冲的原因多半是bFloatDuringInactiveMotorPWM还是默认的浮动模式低速段线性度差电机停不住。解决目标值后面紧跟启动语句需要精确停位时把制动模式打开nMotorEncoder在每段动作前清零避免累计误差。6. 二次开发从单光电摇摆到比例控制与双传感器方案这套代码最值得改的地方是把 bang-bang 换成比例控制。单光电的读值不是只有“黑”和“白”两态黑线边缘是一个连续过渡区读值能反映偏移程度。把(v - THRESHOLD)乘一个比例系数Kp直接加到左右轮差速上车会沿着边沿平滑跟随而不是左右撞。const int THRESHOLD 45; const int BASE_SPEED 60; const int KP 2; // 比例系数从小往大调 task main() { wait1Msec(100); while (true) { int error SensorValue[light1] - THRESHOLD; // 偏移量 int correction error * KP; // 比例修正 motor[leftMotor] BASE_SPEED correction; motor[rightMotor] BASE_SPEED - correction; wait1Msec(5); } }KP从 1 开始试每次加 0.5直到直道稳定但过弯开始振荡再退回一档。correction可能超出 -100 到 100RobotC 会自动截断但截断意味着饱和饱和段里比例控制退化成 bang-bang所以BASE_SPEED要留够余量。这套改法我在几台不同底盘上都试过直道抖动明显小于原始版本代价是要多花时间调KP。再进一步是双光电方案两个传感器跨在黑线两侧读值差直接反映车头偏角直道可以做到几乎不摆。但双光电对安装间距要求高黑线宽度一变就得重调原文的改进建议里也提到“最初用两个光电走弯路”说明器材约束下单光电反而是更稳的起点。我的习惯是先用单光电把阈值和速度跑通确认机械没问题再上双光电或比例控制每次只改一个变量改完跑三圈记录脱线次数用数据判断有没有变好而不是凭一圈的感觉。从那以后我每次调巡线车都强制先把阈值实测一遍再动速度参数这个顺序反了后面所有调参都是白费。希望帮到你。本文还有配套的精品资源点击获取