【屠龙勇士】BIT睿信书院屠龙勇士心得分享
今天上午完成了本年度的屠龙比赛,由于本队开发和搭建任务由我全栈完成,故可将代码和心得完全公开给大家。如果科协的某位大大发现本文,也可以作为一个指导文章或者基础程序。考虑到学习压力时间不足,博文和代码都尽显草莽之色,但作为一个基础的指引,能够大大降低入门门槛。
该项目的Github传送门~
- Github 链接DragonSlay-BIT
- Gitcode镜像Gitcode镜像
屠龙心得
1. 关卡说明
1.1 关卡1:自动循迹
- 包括直线、锐角弯、钝角弯、S弯、斜坡;通过斜坡可额外加5分
- 禁止以非红外巡线的方式完成此关卡
1.2 关卡2:超声波避障
- 包括狭长通道、多个转弯
- 终点前设有粗黑色横线,若小车行驶至该横线处自动停下,则额外加5分
1.3 关卡3:屠龙勇士
- 出口1无守卫龙,出口2设有守卫龙,每撞倒一条龙得5分
- 使用泡沫球射击守卫龙。每辆小车共有6次射击机会;
-
- 若不使用舵机控制,手动放置小球,每击中一条守卫龙得5分
-
- 通过舵机控制泡沫球自动装填并发射命中守卫龙则得10分,多次命中同一条龙不重复得分
2. 接线指导与测试代码
该内容如果与Repo下./分模块组装/布局图.drawio
有任何冲突,应当优先考虑布局图的接线方案。
2.1 电压测试器
直接插到航模电池的充电口(即单只电池口);有金属片的一面向上
- 插入后将会响一声(特别洪亮)
- 然后显示总电压、分电压(用的3s就显示三个的分别电压)
- 按住喇叭后面的微动按钮,可以调整报警值;若电池低于报警值,蜂鸣器和红灯将会报警
1.2 L298N 电机驱动模块
由于本文主要服务于比赛,就不额外讲这个模块的可选配置了,直接按照我的方案来做就好。
这个接线在接线柱放到最松的时候,可以直接怼进去(可以适度用钳子压扁);再拧紧,防止虚接(徐姐发热)
一下是接线图,除了电池正负极外,不需要纠结电机的正负极,这个到时候直接调整代码即可。
这个时候参考布局图.draw
接线;在测试代码里面选择电机驱动代码进行测试
1.2.2 电机驱动测试代码
// Ardunio + L298n;使用analogWrite以控制输出电平
// in1 - in4 分别接到 5、6、9、10
// 完成高速、低速;前后左右转;每一轮3s;将当轮情况输出到终端
// 定义电机控制引脚
#define IN1 5
#define IN2 6
#define IN3 3
#define IN4 11
// 定义速度等级
#define HIGH_SPEED 200 // 高速 (PWM值 0-255)
#define LOW_SPEED 80 // 低速
void setup()
{
// 设置电机控制引脚为输出
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
// 初始化串口通信
Serial.begin(9600);
Serial.println("L298N Motor Control Demo");
}
void loop()
{
// 高速前进(两个电机正转)
motorControl(HIGH_SPEED, HIGH_SPEED);
Serial.println("高速前进");
delay(3000);
// 低速前进
motorControl(LOW_SPEED, LOW_SPEED);
Serial.println("低速前进");
delay(3000);
// 高速后退(两个电机反转)
motorControl(-HIGH_SPEED, -HIGH_SPEED);
Serial.println("高速后退");
delay(3000);
// 低速后退
motorControl(-LOW_SPEED, -LOW_SPEED);
Serial.println("低速后退");
delay(3000);
// 左转(左轮后退,右轮前进)
motorControl(-HIGH_SPEED, HIGH_SPEED);
Serial.println("高速左转");
delay(3000);
// 低速左转
motorControl(-LOW_SPEED, LOW_SPEED);
Serial.println("低速左转");
delay(3000);
// 右转(左轮前进,右轮后退)
motorControl(HIGH_SPEED, -HIGH_SPEED);
Serial.println("高速右转");
delay(3000);
// 低速右转
motorControl(LOW_SPEED, -LOW_SPEED);
Serial.println("低速右转");
delay(3000);
// 停止
motorControl(0, 0);
Serial.println("停止");
delay(2000);
delay(20000);
}
// 电机控制函数(修正版)
// 参数:左轮速度,右轮速度(范围-255~255,负值表示反转)
void motorControl(int leftSpeed, int rightSpeed)
{
// 限制速度范围
leftSpeed = constrain(leftSpeed, -255, 255);
rightSpeed = constrain(rightSpeed, -255, 255);
// constrain(x, a, b)
/*
x:要限制的值(可以是整数或浮点数)
a:范围下限(最小值)
b:范围上限(最大值)
*/
// 控制左轮(IN1/IN2)
if (leftSpeed > 0)
{
analogWrite(IN1, leftSpeed);
analogWrite(IN2, 0);
}
else if (leftSpeed < 0)
{
analogWrite(IN1, 0);
analogWrite(IN2, -leftSpeed);
}
else
{
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
}
// 控制右轮(IN3/IN4)
if (rightSpeed > 0)
{
analogWrite(IN3, rightSpeed);
analogWrite(IN4, 0);
}
else if (rightSpeed < 0)
{
analogWrite(IN3, 0);
analogWrite(IN4, -rightSpeed);
}
else
{
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
}
1.3 HC-SR04 雷达模块
逐个插进去,如图。所有的T端口插在面包板上进行汇流(少了材料记得找组织方补充)
这个时候参考布局图.draw
接线;在测试代码里面选择雷达代码进行测试
1.3.2 雷达测试代码
// 使用Arduino+sr04 进行雷达测距
// 一共有三组雷达,分别是左3前2右1;端口为2-TG1;3-TG2;4-TG3;8-EC1;12-EC2;13 - EC3
// 定义超声波模块引脚
const int trigPinLeft = 4;
const int trigPinFront = 7;
const int trigPinRight = 2;
const int echoPinLeft = 13;
const int echoPinFront = 12;
const int echoPinRight = 8;
void setup() {
Serial.begin(9600); // 初始化串口通信
// 设置引脚模式
pinMode(trigPinLeft, OUTPUT);
pinMode(echoPinLeft, INPUT);
pinMode(trigPinFront, OUTPUT);
pinMode(echoPinFront, INPUT);
pinMode(trigPinRight, OUTPUT);
pinMode(echoPinRight, INPUT);
Serial.println("三向超声波雷达测距系统已启动");
}
void loop() {
// 测量三个方向的距离
float distanceLeft = getDistance(trigPinLeft, echoPinLeft);
float distanceFront = getDistance(trigPinFront, echoPinFront);
float distanceRight = getDistance(trigPinRight, echoPinRight);
// 打印测量结果
Serial.print("左侧距离: ");
Serial.print(distanceLeft);
Serial.print(" cm | 前方距离: ");
Serial.print(distanceFront);
Serial.print(" cm | 右侧距离: ");
Serial.print(distanceRight);
Serial.println(" cm");
delay(200); // 适当延时
}
// 超声波测距函数
float getDistance(int trigPin, int echoPin) {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH);
float distance = duration * 0.034 / 2; // 计算距离(cm)
// 过滤异常值
if(distance > 400 || distance < 2) {
distance = -1; // 表示超出测量范围
}
return distance;
}
1.4 六路红外循迹
这个时候参考布局图.draw
接线;在测试代码里面选择六路红外代码进行测试
1.4.2 红外测试代码
// 定义红外循迹模块引脚
#define IR_1 A0 // 红外模块1
#define IR_2 A1 // 红外模块2
#define IR_3 A2 // 红外模块3
#define IR_4 A3 // 红外模块4
#define IR_5 A4 // 红外模块5
#define IR_6 A5 // 红外模块6
// 存储红外模块的检测值
int irValues[6]; // 用于存储6个红外模块的检测值
void setup() {
// 设置引脚模式
pinMode(IR_1, INPUT);
pinMode(IR_2, INPUT);
pinMode(IR_3, INPUT);
pinMode(IR_4, INPUT);
pinMode(IR_5, INPUT);
pinMode(IR_6, INPUT);
// 初始化串口通信
Serial.begin(9600);
Serial.println("Six-channel IR line follower testing started...");
}
void loop() {
// 读取6个红外模块的值
irValues[0] = digitalRead(IR_1);
irValues[1] = digitalRead(IR_2);
irValues[2] = digitalRead(IR_3);
irValues[3] = digitalRead(IR_4);
irValues[4] = digitalRead(IR_5);
irValues[5] = digitalRead(IR_6);
// 输出红外模块的检测值
Serial.print("IR 1: ");
Serial.print(irValues[0]);
Serial.print(" | IR 2: ");
Serial.print(irValues[1]);
Serial.print(" | IR 3: ");
Serial.print(irValues[2]);
Serial.print(" | IR 4: ");
Serial.print(irValues[3]);
Serial.print(" | IR 5: ");
Serial.print(irValues[4]);
Serial.print(" | IR 6: ");
Serial.println(irValues[5]);
delay(200); // 每隔200毫秒检测一次
}
1.5 SG90 舵机
这个时候参考布局图.draw
接线;在测试代码里面选择舵机代码进行测试
1.5.2 舵机测试代码
#include <Servo.h> // 引入Servo库
// 定义舵机引脚
#define SERVO_PIN 10
// 创建舵机对象
Servo myServo;
// 定义舵机初始角度
int angle = 0; // 初始角度为0度
void setup() {
// 将舵机连接到指定引脚
myServo.attach(SERVO_PIN);
// 初始化串口通信
Serial.begin(9600);
Serial.println("Servo control started...");
}
void loop() {
// 设置舵机角度
myServo.write(angle); // 将舵机转动到指定角度
// 输出当前角度
Serial.print("Servo angle: ");
Serial.println(angle);
// 更新角度
angle += 30; // 每次增加30度
// 如果角度超过180度,重置为0度
if (angle > 180) {
angle = 0;
}
delay(1000); // 每隔1秒转动一次
}
1.6 蓝牙
参考布局图.draw
接线;不用单独测试。但是每次通过串口刷写数据的时候,必须断开蓝牙的上传和下载接线。因为串口线公用,接蓝牙会导致程序刷写失败。
1.6.2 蓝牙测试代码
void setup() {
// 初始化硬件串口(用于与PC通信)
Serial.begin(9600);
// 初始化软件串口(用于与蓝牙模块通信)
BTSerial.begin(9600);
Serial.println("Bluetooth Test Started");
}
void loop() {
// 如果从蓝牙模块接收到数据,则将其打印到串口监视器
if (BTSerial.available()) {
char receivedChar = BTSerial.read();
Serial.print("Received: ");
Serial.println(receivedChar);
}
// 如果从串口监视器接收到数据,则将其发送到蓝牙模块
if (Serial.available()) {
char sendChar = Serial.read();
BTSerial.write(sendChar);
Serial.print("Sent: ");
Serial.println(sendChar);
}
}
1.7 整体布局图(高清图请前往Repo取)
3. 调参心得
- 能跑的代码就是好代码,走的再慢再丑,能完赛的就是好代码。大幅改代码一定要备份(可用git或用复制备份代码,做好批注)
- 电压影响:你的电池的电压的高低,可能会影响马达输出。如果你的代码健壮性不足,可能会早上调的好模型,到了晚上电池不足时,就会特别糟糕
- 轮胎影响:测试的时候可能会烧胎(尤其是超声波前期测试容易撞墙),烧胎后抓地力会受影响。同电压影响,这会让健壮性不足的模型有糟糕的表现
- 比赛策略:某些要求属于进阶要求(如锐角转弯、循迹爬坡、超声转弯决策),请谨慎考虑你的技术能力和赛场情况,优先保证你的模型的普适性(即当你认为你的代码已经足够好之前,反复从多个方向多个场景测试代码)。(例如我在赛前放弃了红外爬坡,一方面我动力调的比较小,二方面爬坡需要改红外的高度,要改结构的同时也对红外灵敏度有影响)(在赛中锐角失败,放弃了第二次尝试等等)
- 蓝牙:不知道是我的蓝牙软件的原因还是小车软件的原因,容易出现输入一个信息后,蓝牙软件卡死闪退……如果你发现了问题所在,可以在博文留言或者给Repo提交PR,感激不尽。
4. 寄语
最后最后,我希望队内最后至少有一个人,对小车的所有技术细节有基础的了解。这个感悟是我在赛前调参感悟的。这样的全能视角能帮助你在模拟赛道上快速调优,而不是像无头苍蝇一样乱试。
5. 小车三维照片~~
-
上视图
-
前视图
-
右视图
-
后视图
-
左视图