一.硬件要求:
- Arduino Uno 开发板
- 两个红外线传感器用于检测小球位置
- 一个 SG90 舵机用于控制板子的倾斜角度
- 一个 HC-05 蓝牙模块,用于通过蓝牙与 Arduino 进行通信
- 一个超声波传感器用于测量距离
二.功能要求:
- 使用两个红外线传感器检测小球位置。
- 使用 SG90 舵机控制板子的倾斜角度。
- 使用超声波传感器测量距离。
- 使用 HC-05 蓝牙模块进行蓝牙通信。
- 实现了根据小球位置调整板子倾斜角度的功能。
- 实现了区域切换功能,并确保在每个区域停留的时间相同。
- 实现了在存在小球掉落风险时暂停蓝牙任务的功能。
三.代码实现
#include <Servo.h>
#include <SoftwareSerial.h>
// 定义最大测距范围
#define MAX_DISTANCE 200 // 最大测距距离,单位:厘米
// 舵机控制引脚
#define SERVO_PIN 9
// 红外线传感器引脚
#define IR_SENSOR_1 A0
#define IR_SENSOR_2 A1
// 超声波传感器引脚
#define TRIG_PIN 10
#define ECHO_PIN 11
// 蓝牙模块引脚
#define BLUETOOTH_RX 2
#define BLUETOOTH_TX 3
// 定义PID参数
#define KP 1.0 // 比例系数
#define KI 0.2 // 积分系数
#define KD 0.1 // 微分系数
// 定义PID控制器的输出范围
#define OUTPUT_MIN 0
#define OUTPUT_MAX 180
// 定义目标倾斜角度
#define TARGET_ANGLE_1 -30.0 // 第一个区域的倾斜角度
#define TARGET_ANGLE_2 0.0 // 第二个区域的倾斜角度
#define TARGET_ANGLE_3 30.0 // 第三个区域的倾斜角度
// 定义每个区域停留的时间(毫秒)
#define STAY_TIME 3000 // 3秒
// 创建舵机对象
Servo servo;
// 创建PID控制器对象
class PIDController {
private:
double lastError;
double integral;
public:
PIDController() : lastError(0), integral(0) {}
double compute(double input, double target) {
double error = target - input;
integral += error;
double derivative = error - lastError;
lastError = error;
double output = KP * error + KI * integral + KD * derivative;
// 限制输出范围
if (output > OUTPUT_MAX) {
output = OUTPUT_MAX;
} else if (output < OUTPUT_MIN) {
output = OUTPUT_MIN;
}
return output;
}
};
PIDController pid;
// 定义当前区域
int currentZone = 1;
// 上次区域切换时间
unsigned long lastZoneChangeTime = 0;
// 声明蓝牙通信对象
SoftwareSerial bluetooth(BLUETOOTH_RX, BLUETOOTH_TX);
void setup() {
// 初始化舵机控制引脚
servo.attach(SERVO_PIN);
// 初始化超声波引脚
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// 初始化蓝牙模块
Serial.begin(9600);
bluetooth.begin(9600);
}
void loop() {
// 读取超声波传感器数据
int distance = getDistance();
// 根据超声波距离调整舵机角度
double angle;
if (distance <= 10) {
angle = TARGET_ANGLE_1; // 如果小球距离太近,倾斜板子使小球往里滚
} else {
angle = pid.compute(getActualAngle(), getTargetAngle());
}
// 控制舵机
servo.write(angle);
// 如果距离上次区域切换超过STAY_TIME,则切换到下一个区域
if (millis() - lastZoneChangeTime >= STAY_TIME) {
currentZone = (currentZone % 3) + 1; // 循环切换区域
lastZoneChangeTime = millis();
}
// 处理蓝牙指令
handleBluetoothCommand();
}
// 函数:获取实际倾斜角度
double getActualAngle() {
// 读取两个红外线传感器的值
int sensorValue1 = analogRead(IR_SENSOR_1);
int sensorValue2 = analogRead(IR_SENSOR_2);
// 根据传感器读数计算倾斜角度
double angle1 = map(sensorValue1, 0, 1023, -90, 90); // 将传感器1的值映射到-90到90度的范围
double angle2 = map(sensorValue2, 0, 1023, -90, 90); // 将传感器2的值映射到-90到90度的范围
// 计算平均倾斜角度作为实际倾斜角度
double actualAngle = (angle1 + angle2) / 2;
return actualAngle;
}
// 函数:获取目标倾斜角度
double getTargetAngle() {
// 根据当前区域选择目标倾斜角度
switch(currentZone) {
case 1:
return TARGET_ANGLE_1;
case 2:
return TARGET_ANGLE_2;
case 3:
return TARGET_ANGLE_3;
default:
return 0.0;
}
}
// 函数:获取超声波测距结果
int getDistance() {
// 发送一个10微秒的高电平脉冲给Trig引脚
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// 计算脉冲持续的时间
long duration = pulseIn(ECHO_PIN, HIGH);
// 将时间转换为距离(单位:厘米)
int distance = duration * 0.034 / 2;
// 如果距离超出最大测距范围,返回最大距离
if (distance >= MAX_DISTANCE || distance <= 0) {
return MAX_DISTANCE;
} else {
return distance;
}
}
// 函数:处理蓝牙指令
void handleBluetoothCommand() {
// 检查是否有数据可读
if (bluetooth.available() > 0) {
// 读取接收到的数据
char command = bluetooth.read();
// 根据接收到的指令设置目标区域
switch(command) {
case '1': // 调整舵机到第一个区域的角度
currentZone = 1;
break;
case '2': // 调整舵机到第二个区域的角度
currentZone = 2;
break;
case '3': // 调整舵机到第三个区域的角度
currentZone = 3;
break;
default:
break;
}
}
// 每个区域停留时间相同
if (currentZone == currentZone) {
// 如果当前区域与目标区域相同,则等待STAY_TIME毫秒
delay(STAY_TIME);
// 切换到下一个区域
currentZone = (currentZone % 3) + 1;
// 更新上次区域切换时间
lastZoneChangeTime = millis();
}
}
功能再度说明:
使用两个红外线传感器检测小球位置。
使用 SG90 舵机控制板子的倾斜角度。
使用超声波传感器测量距离。
使用 HC-05 蓝牙模块进行蓝牙通信。
实现了根据小球位置调整板子倾斜角度的功能。
实现了区域切换功能,并确保在每个区域停留的时间相同。
实现了在存在小球掉落风险时暂停蓝牙任务的功能
四。存在的(隐藏)问题:
1.硬件连接的效果可能和代码有一定出入。
2.运行过程中的实际运动可能与整体不符。
3.硬件本身可能存在一定问题,就比如舵机的齿轮卡住不转需要拧几下才能转动
4.硬件组装不稳定
5.虽然代码在编译器上通过,但难免会存在隐含问题导致硬件运动过程中出现偏差,要根据整体还要不断调试,是必不可少也是最麻烦的环节。
文章说明:为打电赛做准备。由于是三人一组,在商量好任务后,大家一起先以2023年的电赛C题的题目为原型进行制作,这里我做的是软件部分,首先我进行的是初步编写的工作,在后面与硬件连接时的适配说不定还要改好多东西,光想想就让人头疼,就相当于留个初稿纪念纪念吧。
#include <Servo.h>
#include <SoftwareSerial.h>
// 定义舵机控制引脚
#define SERVO_PIN 9
// 定义超声波传感器引脚
#define TRIG_PIN 10
#define ECHO_PIN 11
// 定义红外线传感器引脚
#define IR_SENSOR_LEFT A0
#define IR_SENSOR_RIGHT A1
// 定义蓝牙模块引脚
#define BLUETOOTH_RX 2
#define BLUETOOTH_TX 3
// 定义PID参数
#define KP 1.0 // 比例系数
#define KI 0.2 // 积分系数
#define KD 0.1 // 微分系数
// 定义PID控制器的输出范围
#define OUTPUT_MIN 0
#define OUTPUT_MAX 180
// 定义每个区域停留的时间(毫秒)
#define STAY_TIME 2000 // 2秒
// 定义每个区域的倾斜角度
#define ANGLE_1 -30.0 // 第一个区域的倾斜角度
#define ANGLE_2 -15.0 // 第二个区域的倾斜角度
#define ANGLE_3 0.0 // 第三个区域的倾斜角度
#define ANGLE_4 15.0 // 第四个区域的倾斜角度
#define ANGLE_5 30.0 // 第五个区域的倾斜角度
// 定义红外线传感器阈值
#define IR_THRESHOLD 500
// 定义超声波传感器最大测距范围(厘米)
#define MAX_DISTANCE 200
// 创建PID控制器对象
class PIDController {
private:
double lastError;
double integral;
public:
PIDController() : lastError(0), integral(0) {}
double compute(double input, double target) {
double error = target - input;
integral += error;
double derivative = error - lastError;
lastError = error;
double output = KP * error + KI * integral + KD * derivative;
// 限制输出范围
if (output > OUTPUT_MAX) {
output = OUTPUT_MAX;
} else if (output < OUTPUT_MIN) {
output = OUTPUT_MIN;
}
return output;
}
};
// 创建PID控制器对象
PIDController pid;
// 创建舵机对象
Servo servo;
// 定义当前区域
int currentZone = 1;
// 上次区域切换时间
unsigned long lastZoneChangeTime = 0;
// 创建蓝牙通信对象
SoftwareSerial bluetooth(BLUETOOTH_RX, BLUETOOTH_TX);
void setup() {
// 初始化舵机控制引脚
servo.attach(SERVO_PIN);
// 初始化超声波引脚
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// 初始化红外线传感器引脚
pinMode(IR_SENSOR_LEFT, INPUT);
pinMode(IR_SENSOR_RIGHT, INPUT);
// 初始化蓝牙模块
Serial.begin(9600);
bluetooth.begin(9600);
}
void loop() {
// 读取超声波传感器数据
int distance = getDistance();
// 根据超声波距离调整舵机角度
double angle;
if (distance <= 10) {
angle = ANGLE_1; // 如果小球距离太近,倾斜板子使小球往里滚
} else {
// 使用getActualAngle()函数获取实际倾斜角度
angle = pid.compute(getActualAngle(), ANGLE_3);
}
// 控制舵机
servo.write(angle);
// 如果距离上次区域切换超过STAY_TIME,则切换到下一个区域
if (millis() - lastZoneChangeTime >= STAY_TIME) {
currentZone = (currentZone % 5) + 1; // 循环切换区域
lastZoneChangeTime = millis();
}
// 处理蓝牙指令
handleBluetoothCommand();
}
// 函数:获取超声波测距结果
int getDistance() {
// 发送一个10微秒的高电平脉冲给Trig引脚
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// 计算脉冲持续的时间
long duration = pulseIn(ECHO_PIN, HIGH);
// 将时间转换为距离(单位:厘米)
int distance = duration * 0.034 / 2;
// 如果距离超出最大测距范围,返回最大距离
if (distance >= MAX_DISTANCE || distance <= 0) {
return MAX_DISTANCE;
} else {
return distance;
}
}
// 函数:处理蓝牙指令
void handleBluetoothCommand() {
// 检查是否有数据可读
if (bluetooth.available() > 0) {
// 读取接收到的数据
char command = bluetooth.read();
// 根据接收到的指令设置目标区域
switch (command) {
case '1': // 调整舵机到第一个区域的角度
currentZone = 1;
break;
case '2': // 调整舵机到第二个区域的角度
currentZone = 2;
break;
case '3': // 调整舵机到第三个区域的角度
currentZone = 3;
break;
case '4': // 调整舵机到第四个区域的角度
currentZone = 4;
break;
case '5': // 调整舵机到第五个区域的角度
currentZone = 5;
break;
default:
break;
}
}
}
// 函数:获取实际倾斜角度
double getActualAngle() {
// 读取红外线传感器数据
int leftSensorValue = analogRead(IR_SENSOR_LEFT);
int rightSensorValue = analogRead(IR_SENSOR_RIGHT);
// 根据传感器数据判断小球所在区域,并返回对应的倾斜角度
if (leftSensorValue > IR_THRESHOLD && rightSensorValue > IR_THRESHOLD) {
return ANGLE_3; // 小球在区域3
} else if (leftSensorValue > IR_THRESHOLD) {
return ANGLE_4; // 小球在区域4
} else if (rightSensorValue > IR_THRESHOLD) {
return ANGLE_2; // 小球在区域2
} else {
return ANGLE_1; // 小球在区域1
}
}
这个代码是对真实电赛题的进一步靠近,比上面稍微全面了一点,能力到这了,在精确的写不下去了……