概述:
本篇文章主要讲述在第一篇中代码程序的详细解释,包含各段代码的功能及作用,以方便大家能够更好的了解并分析代码的用途。
一.总代码程序
// 定义左右轮脉冲计数器和时间标记
int leftCounter = 0, rightCounter = 0;
unsigned long time = 0, old_time = 0; // 时间标记,用于计算速度
unsigned long time1 = 0; // 时间标记,用于其他计时功能
float lv, rv; // 左、右轮速度
// 定义运动控制命令
#define STOP 0
#define FORWARD 1
#define BACKWARD 2
#define TURNLEFT 3
#define TURNRIGHT 4
#define CHANGESPEED 5
// 定义电机引脚
int leftMotor1 = 2;
int leftMotor2 = 3;
int rightMotor1 = 4;
int rightMotor2 = 5;
bool speedLevel = 0; // 速度档位,0表示低速,1表示高速
// 定义PWM引脚
int leftPWM = 9;
int rightPWM = 10;
void setup() {
// 初始化串口通信,波特率为9600
Serial.begin(9600);
// 设置外部中断0,当引脚0电平从高到低变化时触发RightCount_CallBack函数
attachInterrupt(0, RightCount_CallBack, FALLING);
// 设置外部中断1,当引脚1电平从高到低变化时触发LeftCount_CallBack函数
attachInterrupt(1, LeftCount_CallBack, FALLING);
// 设置电机引脚为输出模式
pinMode(leftMotor1, OUTPUT);
pinMode(leftMotor2, OUTPUT);
pinMode(rightMotor1, OUTPUT);
pinMode(rightMotor2, OUTPUT);
// 设置PWM引脚为输出模式
pinMode(leftPWM, OUTPUT);
pinMode(rightPWM, OUTPUT);
}
void loop() {
// 调用速度检测函数
SpeedDetection();
// 如果串口有数据可读
if (Serial.available() > 0) {
// 读取蓝牙模块发送到串口的数据
int cmd = Serial.read();
// 如果接收到的数据不是换行符,则进行控制
if (cmd != 10) {
// 打印接收到的命令
Serial.print(cmd - 48);
// 根据命令控制小车运动
motorRun(cmd - 48);
}
// 如果当前是高速档位
if (speedLevel) {
// 设置左右轮PWM值为120,对应较低速度
analogWrite(leftPWM, 120);
analogWrite(rightPWM, 120);
} else {
// 设置左右轮PWM值为250,对应较高速度
analogWrite(leftPWM, 250);
analogWrite(rightPWM, 250);
}
}
}
// 速度计算函数
bool SpeedDetection() {
// 获取当前时间(毫秒)
time = millis();
// 如果计时时间达到1秒
if (abs(time - old_time) >= 1000) {
// 关闭外部中断0和1
detachInterrupt(0);
detachInterrupt(1);
// 计算左右轮转速(转/分钟),并打印到串口
lv = (float)leftCounter * 60 / 20;
rv = (float)rightCounter * 60 / 20;
Serial.print("left:");
Serial.print(lv);
Serial.print(" right:");
Serial.println(rv);
// 重置左右轮脉冲计数器,记录当前时间,重新开启外部中断0和1
leftCounter = 0;
rightCounter = 0;
old_time = millis();
attachInterrupt(0, RightCount_CallBack, FALLING);
attachInterrupt(1, LeftCount_CallBack, FALLING);
return 1;
} else {
return 0;
}
}
// 右轮编码器中断服务函数,当引脚0电平从高到低变化时触发,增加右轮脉冲计数器
void RightCount_CallBack() {
rightCounter++;
}
// 左轮编码器中断服务函数,当引脚1电平从高到低变化时触发,增加左轮脉冲计数器
void LeftCount_CallBack() {
leftCounter++;
}
// 小车运动控制函数,根据命令控制小车运动状态
void motorRun(int cmd) {
switch (cmd) {
case FORWARD:
Serial.println("FORWARD");
// 设置电机引脚电平,使小车前进
digitalWrite(leftMotor1, HIGH);
digitalWrite(leftMotor2, LOW);
digitalWrite(rightMotor1, HIGH);
digitalWrite(rightMotor2, LOW);
break;
case BACKWARD:
Serial.println("BACKWARD");
// 设置电机引脚电平,使小车后退
digitalWrite(leftMotor1, LOW);
digitalWrite(leftMotor2, HIGH);
digitalWrite(rightMotor1, LOW);
digitalWrite(rightMotor2, HIGH);
break;
case TURNLEFT:
Serial.println("TURN LEFT");
// 设置电机引脚电平,使小车左转
digitalWrite(leftMotor1, HIGH);
digitalWrite(leftMotor2, LOW);
digitalWrite(rightMotor1, LOW);
digitalWrite(rightMotor2, HIGH);
break;
case TURNRIGHT:
Serial.println("TURN RIGHT");
// 设置电机引脚电平,使小车右转
digitalWrite(leftMotor1, LOW);
digitalWrite(leftMotor2, HIGH);
digitalWrite(rightMotor1, HIGH);
digitalWrite(rightMotor2, LOW);
break;
case CHANGESPEED:
Serial.println("CHANGE SPEED");
// 切换速度档位
if (speedLevel)
speedLevel = 0;
else
speedLevel = 1;
break;
default:
Serial.println("STOP");
// 停止所有电机
digitalWrite(leftMotor1, LOW);
digitalWrite(leftMotor2, LOW);
digitalWrite(rightMotor1, LOW);
digitalWrite(rightMotor2, LOW);
}
}
二.代码分段解释
1.第一段
// 定义左右轮脉冲计数器和时间标记
int leftCounter = 0, rightCounter = 0;
unsigned long time = 0, old_time = 0; // 时间标记,用于计算速度
unsigned long time1 = 0; // 时间标记,用于其他计时功能
float lv, rv; // 左、右轮速度
这段代码定义了一系列变量,用于控制小车的运动。下面是对每个变量的解释及它们的功能:
(1).左右轮脉冲计数器:
int leftCounter = 0;:定义了一个整型变量leftCounter,用于记录左轮的脉冲计数。每当左轮转动时,编码器会发送脉冲信号,这个信号会被用来增加leftCounter的值。
int rightCounter = 0;:类似地,rightCounter用于记录右轮的脉冲计数。
脉冲计数器的主要用途是计算轮子的转速和行驶的距离。通过对比单位时间内脉冲数的变化,可以推算出轮子的转速,从而知道小车的移动速度或行驶的距离。
(2).时间标记:
unsigned long time = 0;:定义了一个无符号长整型变量time,用于记录当前的时间戳(通常是毫秒数)。这个变量通常通过调用一个获取当前时间的函数(如Arduino中的millis())来更新。
unsigned long old_time = 0;:old_time用于存储上一次测量或计算时的时间戳。通过比较time和old_time,可以计算出时间间隔,进而计算出轮子在这段时间内的转速变化,从而得到速度。
时间标记的主要作用是帮助计算轮子的速度。通过比较不同时间点上的脉冲计数,可以计算出轮子的平均速度。
(3).其他计时功能的时间标记:
unsigned long time1 = 0;:这是一个额外的时间标记,用于其他需要计时的功能或任务。具体用途取决于代码的其他部分,用于延迟、定时任务或其他计时相关的功能。
(4).轮子速度:
float lv, rv;:这两个浮点型变量分别用于存储左轮和右轮的速度。速度通常是通过比较脉冲计数和时间标记计算得出的。具体计算方式可能涉及在特定时间间隔内计算脉冲数的变化,并转换为速度单位(如转/分钟或米/秒)。
总结
这段代码为小车的运动控制和监控提供了必要的变量和初始值。通过记录脉冲计数和时间标记,可以计算出轮子的速度,从而实现对小车运动的精确控制。
2.第二段
// 定义运动控制命令
#define STOP 0
#define FORWARD 1
#define BACKWARD 2
#define TURNLEFT 3
#define TURNRIGHT 4
#define CHANGESPEED 5
(1).STOP:定义为0,表示停止运动。
(2).FORWARD:定义为1,表示向前运动。
(3).BACKWARD:定义为2,表示向后运动。
(4).TURNLEFT:定义为3,表示向左转弯。
(5).TURNRIGHT:定义为4,表示向右转弯。
(6).CHANGESPEED:定义为5,表示改变运动速度。
3.第三段
// 定义电机引脚
int leftMotor1 = 2;
int leftMotor2 = 3;
int rightMotor1 = 4;
int rightMotor2 = 5;
bool speedLevel = 0; // 速度档位,0表示低速,1表示高速
// 定义PWM引脚
int leftPWM = 9;
int rightPWM = 10;
(1).电机引脚定义:
leftMotor1 和 leftMotor2:定义了左电机的两个引脚。在电机驱动配置中,一个电机需要两个引脚来控制其正反转。这里leftMotor1和leftMotor2是连接左电机的两个控制引脚。
rightMotor1 和 rightMotor2:类似地,定义了右电机的两个引脚。
(2).速度档位定义:
speedLevel:这是一个布尔变量,用于表示电机的速度档位。尽管名为“速度档位”,但这里只定义了两个状态(0或1),可能表示低速和高速。通常,为了表示多个速度档位,可能会使用枚举(enum)或整数变量,并定义多个不同的速度值。
(3).PWM引脚定义:
leftPWM 和 rightPWM:PWM(脉宽调制)是一种模拟电路数字控制技术,通过高分辨率编程产生各种模拟电压水平。在这里,leftPWM和rightPWM分别定义了控制左电机和右电机的PWM信号的引脚。通过改变这些引脚的PWM信号的占空比,可以用来控制电机的速度。
4.第四段
void setup() {
// 初始化串口通信,波特率为9600
Serial.begin(9600);
// 设置外部中断0,当引脚0电平从高到低变化时触发RightCount_CallBack函数
attachInterrupt(0, RightCount_CallBack, FALLING);
// 设置外部中断1,当引脚1电平从高到低变化时触发LeftCount_CallBack函数
attachInterrupt(1, LeftCount_CallBack, FALLING);
// 设置电机引脚为输出模式
pinMode(leftMotor1, OUTPUT);
pinMode(leftMotor2, OUTPUT);
pinMode(rightMotor1, OUTPUT);
pinMode(rightMotor2, OUTPUT);
// 设置PWM引脚为输出模式
pinMode(leftPWM, OUTPUT);
pinMode(rightPWM, OUTPUT);
}
这段代码是一个Arduino的setup()函数,它负责初始化Arduino板的配置。
(1).Serial.begin(9600);
这一行初始化了串行通信,并将波特率设置为9600。波特率是两个设备之间传输信息时使用的通信速度。这允许Arduino与计算机或其他设备通过串行端口进行通信。
(2).attachInterrupt(0, RightCount_CallBack, FALLING);
这里配置了一个外部中断。当Arduino的数字引脚2(注意,对于大多数Arduino,attachInterrupt函数的第一个参数0代表数字引脚2,尽管代码中没有明确指出)上的信号从高电平变为低电平(即检测到下降沿)时,将调用RightCount_CallBack函数。这个中断允许Arduino在不执行主循环中的代码时响应外部事件。
(3).attachInterrupt(1, LeftCount_CallBack, FALLING);
类似地,这里配置了另一个外部中断。当数字引脚3(在大多数Arduino中,attachInterrupt函数的第一个参数1代表数字引脚3)上的信号从高电平变为低电平时,将调用LeftCount_CallBack函数。
(4).pinMode(leftMotor1, OUTPUT); 到 pinMode(rightPWM, OUTPUT);
这些行将指定的引脚设置为输出模式。这意味着Arduino可以向这些引脚发送电压,以驱动连接的设备。这里定义了左电机和右电机的控制引脚以及两个PWM(脉宽调制)引脚。PWM引脚通常用于控制模拟设备,如调节电机的速度。
5.第五段
void loop() {
// 调用速度检测函数
SpeedDetection();
// 如果串口有数据可读
if (Serial.available() > 0) {
// 读取蓝牙模块发送到串口的数据
int cmd = Serial.read();
// 如果接收到的数据不是换行符,则进行控制
if (cmd != 10) {
// 打印接收到的命令
Serial.print(cmd - 48);
// 根据命令控制小车运动
motorRun(cmd - 48);
}
// 如果当前是高速档位
if (speedLevel) {
// 设置左右轮PWM值为120,对应较低速度
analogWrite(leftPWM, 120);
analogWrite(rightPWM, 120);
} else {
// 设置左右轮PWM值为250,对应较高速度
analogWrite(leftPWM, 250);
analogWrite(rightPWM, 250);
}
}
}
这段代码是Arduino的loop()函数,该函数是Arduino程序的主循环,它会不断地重复执行其中的代码。现在我们来进行分析这段代码的具体功能:
(1).调用速度检测函数:
SpeedDetection();
这行代码调用了一个名为SpeedDetection的函数,是用于检测小车的当前速度或进行速度相关的处理。
(2).检查串口是否有数据可读:
if (Serial.available() > 0) {
使用Serial.available()函数来检查是否有数据可读。如果有数据,则执行大括号内的代码。
(3).读取蓝牙模块发送到串口的数据:
int cmd = Serial.read();
通过Serial.read()函数读取串口接收到的数据,并将其存储在变量cmd中。这里假设接收到的数据是整数形式。
(4).检查接收到的数据是否不是换行符:
if (cmd != 10) {
这里检查接收到的数据是否不是换行符(ASCII值为10)。如果不是换行符,则执行下面的代码块。
(5).打印接收到的命令:
Serial.print(cmd - 48);
将接收到的数据(cmd)减去48,这可能是为了将字符形式的数字(例如ASCII码表示的'0'到'9')转换为实际的整数值。然后,使用Serial.print()函数将转换后的值打印到串口监视器。
(6).根据命令控制小车运动:
motorRun(cmd - 48);
调用motorRun函数,并传入转换后的命令值作为参数。这个函数负责根据接收到的命令来控制小车的运动,例如前进、后退、左转、右转等。
(7).根据速度档位设置PWM值:
if (speedLevel) {
analogWrite(leftPWM, 120);
analogWrite(rightPWM, 120);
} else {
analogWrite(leftPWM, 250);
analogWrite(rightPWM, 250);
}
根据全局变量speedLevel的值(0表示低速,1表示高速)来设置左右两边电机的PWM值。如果speedLevel为真(即1,表示高速),则左右两边电机的PWM值设置为120,这导致小车以较低的速度运行。如果speedLevel为假(即0,表示低速),则PWM值设置为250,小车以较高的速度运行。这里使用了analogWrite()函数,这是Arduino提供的用于输出PWM信号的函数。
总结
这段代码的主要功能是不断地检测小车的速度,并通过串口接收来自蓝牙模块的控制命令来控制小车的运动。同时,根据设定的速度档位来调整小车的运行速度。
6.第六段
// 速度计算函数
bool SpeedDetection() {
// 获取当前时间(毫秒)
time = millis();
// 如果计时时间达到1秒
if (abs(time - old_time) >= 1000) {
// 关闭外部中断0和1
detachInterrupt(0);
detachInterrupt(1);
// 计算左右轮转速(转/分钟),并打印到串口
lv = (float)leftCounter * 60 / 20;
rv = (float)rightCounter * 60 / 20;
Serial.print("left:");
Serial.print(lv);
Serial.print(" right:");
Serial.println(rv);
// 重置左右轮脉冲计数器,记录当前时间,重新开启外部中断0和1
leftCounter = 0;
rightCounter = 0;
old_time = millis();
attachInterrupt(0, RightCount_CallBack, FALLING);
attachInterrupt(1, LeftCount_CallBack, FALLING);
return 1;
} else {
return 0;
}
}
这段代码是一个速度检测函数,它主要用于计算并打印左右轮的转速。
millis(), detachInterrupt(), attachInterrupt(),Serial.print() 等函数是Arduino常见的函数。以下是对这段代码的详细分析:
(1).函数定义:
bool SpeedDetection() : 定义了一个返回布尔值的函数SpeedDetection。
(2).获取当前时间:
time = millis();:使用millis()函数获取当前时间(从Arduino上电开始计算的毫秒数),并将其存储在time变量中。
(3).检查时间差:
if (abs(time - old_time) >= 1000):使用abs()函数计算当前时间与上一次记录的时间old_time之间的差的绝对值。如果这个时间差大于或等于1000毫秒(即1秒),则执行下面的代码块。
(4).关闭外部中断:
detachInterrupt(0);:关闭外部中断0。
detachInterrupt(1);:关闭外部中断1。关闭中断是为了确保在计算转速和重置计数器的过程中,不会有新的脉冲信号被记录,这样可以避免计算错误。
(5).计算转速:
lv = (float)leftCounter * 60 / 20;:计算左轮的转速。这里假设每20毫秒有一个脉冲信号,所以60秒内有多少脉冲信号就可以计算出转速(转/分钟)。
rv = (float)rightCounter * 60 / 20;:类似地计算右轮的转速。
(6).打印转速:
使用Serial.print()函数将左右轮的转速打印到串口。
(7).重置计数器和时间:
leftCounter = 0; 和 rightCounter = 0;:重置左右轮的脉冲计数器。
old_time = millis();:记录当前时间为下一次检测的时间基准。
(8).重新开启外部中断:
attachInterrupt(0, RightCount_CallBack, FALLING);:当外部中断0的引脚检测到下降沿时,调用RightCount_CallBack函数。
attachInterrupt(1, LeftCount_CallBack, FALLING);:当外部中断1的引脚检测到下降沿时,调用LeftCount_CallBack函数。这两个函数应该是用于计数左右轮的脉冲信号。
(9).返回值:
如果时间差达到或超过1秒,函数返回1。
否则,函数返回0。
注意:
使用detachInterrupt()和attachInterrupt()时需要谨慎,因为关闭和重新开启中断可能会导致丢失脉冲信号或产生其他行为。
7.第七段
// 右轮编码器中断服务函数,当引脚0电平从高到低变化时触发,增加右轮脉冲计数器
void RightCount_CallBack() {
rightCounter++;
}
// 左轮编码器中断服务函数,当引脚1电平从高到低变化时触发,增加左轮脉冲计数器
void LeftCount_CallBack() {
leftCounter++;
}
以下是对RightCount_CallBack和LeftCount_CallBack这两个中断服务函数以及它们在整体程序中的作用的解释:
1. RightCount_CallBack函数解释:
功能:当右轮编码器的引脚检测到下降沿信号时,该函数会被调用。
作用:增加rightCounter的值,用于记录右轮编码器产生的脉冲数量。
重要性:脉冲数量是计算右轮转速的依据,因此这个函数对于速度检测至关重要。
2. LeftCount_CallBack函数解释:
功能:当左轮编码器的引脚检测到下降沿信号时,该函数会被调用。
作用:增加leftCounter的值,用于记录左轮编码器产生的脉冲数量。
重要性:与右轮类似,这个函数是计算左轮转速的基础,对速度检测同样重要。
3. 中断服务函数在整体程序中的作用:
实时性:通过中断的方式,程序可以实时响应编码器的信号,而不需要不断轮询编码器的状态,提高了效率。
准确性:每次编码器的引脚电平变化时,都会准确地触发对应的中断服务函数,从而确保脉冲计数的准确性。
速度检测基础:rightCounter和leftCounter的值是计算左右轮转速的依据,因此这两个中断服务函数是速度检测功能的基础。
总结
RightCount_CallBack和LeftCount_CallBack这两个中断服务函数在速度检测中起到了关键作用,它们确保了左右轮编码器产生的脉冲信号能够被准确记录,进而用于计算转速。
8.第八段
// 小车运动控制函数,根据命令控制小车运动状态
void motorRun(int cmd) {
switch (cmd) {
case FORWARD:
Serial.println("FORWARD");
// 设置电机引脚电平,使小车前进
digitalWrite(leftMotor1, HIGH);
digitalWrite(leftMotor2, LOW);
digitalWrite(rightMotor1, HIGH);
digitalWrite(rightMotor2, LOW);
break;
case BACKWARD:
Serial.println("BACKWARD");
// 设置电机引脚电平,使小车后退
digitalWrite(leftMotor1, LOW);
digitalWrite(leftMotor2, HIGH);
digitalWrite(rightMotor1, LOW);
digitalWrite(rightMotor2, HIGH);
break;
case TURNLEFT:
Serial.println("TURN LEFT");
// 设置电机引脚电平,使小车左转
digitalWrite(leftMotor1, HIGH);
digitalWrite(leftMotor2, LOW);
digitalWrite(rightMotor1, LOW);
digitalWrite(rightMotor2, HIGH);
break;
case TURNRIGHT:
Serial.println("TURN RIGHT");
// 设置电机引脚电平,使小车右转
digitalWrite(leftMotor1, LOW);
digitalWrite(leftMotor2, HIGH);
digitalWrite(rightMotor1, HIGH);
digitalWrite(rightMotor2, LOW);
break;
case CHANGESPEED:
Serial.println("CHANGE SPEED");
// 切换速度档位
if (speedLevel)
speedLevel = 0;
else
speedLevel = 1;
break;
default:
Serial.println("STOP");
// 停止所有电机
digitalWrite(leftMotor1, LOW);
digitalWrite(leftMotor2, LOW);
digitalWrite(rightMotor1, LOW);
digitalWrite(rightMotor2, LOW);
}
}
这个motorRun函数是用于控制小车运动状态的。它根据传入的命令cmd来决定小车应该执行哪种动作。以下是该函数的详细解释:
1.函数定义
void motorRun(int cmd)
该函数没有返回值(void),并接受一个整数参数cmd,这个参数用于指示小车应该执行哪种动作。
switch语句
函数内部使用了一个switch语句来根据cmd的值执行不同的动作。
2.动作类型
FORWARD: 当cmd为FORWARD时,小车前进。
打印"FORWARD"到串口。
设置左电机的两个引脚leftMotor1和leftMotor2,使得电机向前转。
设置右电机的两个引脚rightMotor1和rightMotor2,使得电机向前转。
BACKWARD: 当cmd为BACKWARD时,小车后退。
打印"BACKWARD"到串口。
设置左电机的两个引脚,使得电机向后转。
设置右电机的两个引脚,使得电机向后转。
TURNLEFT: 当cmd为TURNLEFT时,小车左转。
打印"TURN LEFT"到串口。
设置左电机的引脚,使得电机向前转。
设置右电机的引脚,使得电机向后转。
TURNRIGHT: 当cmd为TURNRIGHT时,小车右转。
打印"TURN RIGHT"到串口。
设置左电机的引脚,使得电机向后转。
设置右电机的引脚,使得电机向前转。
CHANGESPEED: 当cmd为CHANGESPEED时,切换速度档位。
打印"CHANGE SPEED"到串口。
切换speedLevel的值。如果speedLevel为true或非零值时,则将其设置为0;否则,设置为1。
注意:这个函数只改变了speedLevel的值,并没有实际改变电机的速度。实际改变电机速度可能需要另外的函数或逻辑。
default: 如果cmd不是上述的任何一个值,则执行默认动作,即停止小车。
打印"STOP"到串口。
将所有电机的引脚设置为低电平,停止所有电机。
3.电机控制
在控制电机时,digitalWrite函数用于设置引脚的电平。一般来说,电机控制需要两个引脚,一个用于控制电机的方向(正转或反转),另一个用于控制电机的电源。在这个函数中,假设leftMotor1和rightMotor1用于控制方向,而leftMotor2和rightMotor2用于控制电源。当电机引脚为高电平时,电机可能正转;当为低电平时,电机可能反转或停止,这取决于电机的具体接线和逻辑。
总结
这个motorRun函数是一个简单但功能完整的运动控制函数,用于根据命令控制小车的不同动作。在实际应用中,还需要添加更多的逻辑来处理电机速度的控制、错误处理以及与其他系统组件的交互。
三.总结
以上为本篇文章的所有内容,本文详细的介绍了上一篇文章所编写的代码程序各片段的作用和功能,如在实验测试中遇到某些问题,欢迎在评论区共同讨论。如有疑问或提供建议,请指出,谢谢!!!