用编码器写自动Encoder-Based Autonomous
3.1 编码器解决了什么问题?3.1 What Problem Do Encoders Solve?
上一节我们发现,时间控制的最大问题是不一致。原因是:时间没法告诉你电机实际转了多少。Last lesson we found the biggest problem with time control is inconsistency. The reason: time can't tell you how much the motor actually turned.
编码器就不一样了——它直接测量电机的旋转角度。不管电池满不满、地面滑不滑,"转 500 度"就是 500 度。Encoders are different -- they directly measure the motor's rotation angle. No matter the battery level or surface friction, "turn 500 degrees" means exactly 500 degrees.
时间控制就像蒙着眼睛走路,数"一步、两步、三步"——步子大小不一定一样。Time control is like walking blindfolded, counting "one step, two steps, three steps" -- step sizes may vary.
编码器控制就像睁开眼睛看地上的刻度线——走到 50 厘米标记就停,精确可靠。Encoder control is like walking with your eyes open, looking at marks on the ground -- stop at the 50 cm mark, precise and reliable.
3.2 spinFor:最简单的编码器控制3.2 spinFor: The Simplest Encoder Control
VEX 电机自带编码器,有一个特别方便的函数:spinFor——转到指定角度后自动停。VEX motors have built-in encoders and a very convenient function: spinFor -- it automatically stops after turning to a specified angle.
void autonomous() {
// 前进:两个电机各转 500 度
LeftMotor.spinFor(forward, 500, degrees); // 阻塞:等左电机转完
// 注意:上面这行执行完了,左电机已经转完 500 度
// 但右电机还没动!
RightMotor.spinFor(forward, 500, degrees); // 再等右电机转完
// 问题:左右电机不是同时转的!
}
void autonomous() {
// 前进:两个电机各转 500 度
LeftMotor.spinFor(forward, 500, degrees); // 阻塞:等左电机转完
// 注意:上面这行执行完了,左电机已经转完 500 度
// 但右电机还没动!
RightMotor.spinFor(forward, 500, degrees); // 再等右电机转完
// 问题:左右电机不是同时转的!
}
void autonomous() {
// 前进:两个电机各转到 500 度位置
left_motor.move_absolute(500, 100); // 目标 500 度,速度 100
// 注意:move_absolute 是非阻塞的!
// 代码会立刻执行下一行
right_motor.move_absolute(500, 100);
// 两个电机同时在转
// 但我们需要等它们转完再做下一步
pros::delay(1500); // 粗略等待
}
3.3 先碰壁:阻塞 vs 非阻塞3.3 Hit the Wall: Blocking vs Non-Blocking
如果两个 spinFor 都是阻塞的(默认行为),程序会先等左电机转完,再让右电机转。结果:机器人先歪着走,再歪回来,根本不是直线!If both spinFor calls are blocking (default behavior), the program waits for the left motor to finish, then starts the right motor. Result: the robot swerves left then right -- not a straight line at all!
解决方法:让第一个电机非阻塞启动,两个电机同时转:Solution: Start the first motor as non-blocking so both motors spin at the same time:
void autonomous() {
// 正确写法:左电机非阻塞启动,右电机阻塞等待
LeftMotor.spinFor(forward, 500, degrees, false); // false = 不等,立刻执行下一行
RightMotor.spinFor(forward, 500, degrees); // 阻塞等右电机转完
// 到这里,两个电机都转完了 500 度
// 因为右电机是阻塞的,程序会等右电机转完才往下走
// 而左电机是和右电机同时开始转的
}
关键:最后一个 spinFor 不加 false(阻塞),前面的都加 false(非阻塞)。这样所有电机同时转,程序等最后一个转完再继续。Key: The last spinFor doesn't have false (blocking), all previous ones have false (non-blocking). This way all motors spin together, and the program waits for the last one to finish.
void autonomous() {
// 正确写法:左电机非阻塞启动,右电机阻塞等待
LeftMotor.spinFor(forward, 500, degrees, false); // false = 不等,立刻执行下一行
RightMotor.spinFor(forward, 500, degrees); // 阻塞等右电机转完
// 到这里,两个电机都转完了 500 度
}
关键:最后一个 spinFor 不加 false(阻塞),前面的都加 false(非阻塞)。Key: The last spinFor doesn't have false (blocking), all previous ones have false (non-blocking).
void autonomous() {
// PROS 的 move_absolute 本身就是非阻塞的
// 两个电机同时启动
left_motor.move_absolute(500, 100);
right_motor.move_absolute(500, 100);
// 等到两个电机都到位
while (fabs(left_motor.get_position() - 500) > 5 ||
fabs(right_motor.get_position() - 500) > 5) {
pros::delay(10);
}
}
PROS 的 move_absolute 本身就是非阻塞的,需要自己写 while 循环等待到位。PROS's move_absolute is non-blocking by default, so you need to write your own while loop to wait until the motors reach their target.
3.4 封装一个直走函数3.4 Wrapping It in a Drive Function
每次都写两行 spinFor 太麻烦,而且容易忘记加 false。我们把它封装成一个函数:Writing two spinFor lines every time is tedious and easy to forget the false. Let's wrap it in a function:
// 直走函数:正数前进,负数后退
void driveStraight(double deg) {
LeftMotor.setPosition(0, degrees); // 清零编码器
RightMotor.setPosition(0, degrees);
if (deg > 0) {
LeftMotor.spinFor(forward, deg, degrees, false);
RightMotor.spinFor(forward, deg, degrees);
} else {
LeftMotor.spinFor(reverse, -deg, degrees, false);
RightMotor.spinFor(reverse, -deg, degrees);
}
}
void autonomous() {
driveStraight(500); // 前进 500 度
driveStraight(-300); // 后退 300 度
}
// 直走函数:正数前进,负数后退
void driveStraight(double deg) {
LeftMotor.setPosition(0, degrees);
RightMotor.setPosition(0, degrees);
if (deg > 0) {
LeftMotor.spinFor(forward, deg, degrees, false);
RightMotor.spinFor(forward, deg, degrees);
} else {
LeftMotor.spinFor(reverse, -deg, degrees, false);
RightMotor.spinFor(reverse, -deg, degrees);
}
}
void autonomous() {
driveStraight(500); // 前进 500 度
driveStraight(-300); // 后退 300 度
}
// 直走函数:正数前进,负数后退
void driveStraight(double deg) {
// 记录起始位置
double startL = left_motor.get_position();
double startR = right_motor.get_position();
left_motor.move_absolute(startL + deg, 100);
right_motor.move_absolute(startR + deg, 100);
// 等到位
while (fabs(left_motor.get_position() - (startL + deg)) > 5 ||
fabs(right_motor.get_position() - (startR + deg)) > 5) {
pros::delay(10);
}
}
void autonomous() {
driveStraight(500); // 前进 500 度
driveStraight(-300); // 后退 300 度
}
有了 driveStraight() 函数,写自动程序就像写步骤清单一样简单。而且如果要调整底盘逻辑,只需要改函数内部,不用到处改。With the driveStraight() function, writing autonomous is as easy as writing a checklist. And if you need to adjust the drivetrain logic, you only change one function instead of editing everywhere.
3.5 对比:时间 vs 编码器3.5 Comparison: Time vs Encoder
| 对比项Comparison | 时间控制Time Control | 编码器控制Encoder Control |
|---|---|---|
| 一致性Consistency | 差,每次不一样Poor, different each time | 好,每次基本一样Good, nearly the same each time |
| 受电池影响?Affected by battery? | 严重影响Heavily affected | 几乎不影响Almost no effect |
| 代码复杂度Code complexity | 简单Simple | 稍复杂Slightly more complex |
| 适合场景Best for | 快速调试Quick testing | 正式比赛Competition matches |
下面这段代码,机器人会怎么动?How will the robot move with this code?
LeftMotor.spinFor(forward, 300, degrees);
RightMotor.spinFor(forward, 300, degrees);
下面这段代码,机器人会怎么动?How will the robot move with this code?
LeftMotor.spinFor(forward, 300, degrees);
RightMotor.spinFor(forward, 300, degrees);
下面这段代码,机器人会怎么动?How will the robot move with this code?
left_motor.move_absolute(300, 100);
// 没有等待代码
right_motor.move_absolute(300, 100);