Arduino Note
Arduino 智能小车
Arduino 智能小车————超声波避障
准备材料
超声波模块HC-SR04

在这里简单说下超声波测距的原理,相信大家也都知道。超声波发射装置发出超声波,它的根据是接收器接到超声波时的时间差,与雷达测距原理相似。 超声波发射器向某一方向发射超声波,在发射时刻的同时开始计时,超声波在空气中传播,途中碰到障碍物就立即返回来,超声波接收器收到反射波就立即停止计时。
1) 采用Trig引脚触发,给至少10us的高电平脉冲信号
2) 模块自动发送8个40kHz的方波,自动检测是否有信号返回
3) 有信号返回,通过Echo引脚输出一个高电平脉冲,高电平脉冲持续的时间就是超声波从发射到反射返回的时间。距离=(高电平脉冲时间*340)/2。(声音在空气中传播速度为340m/s)
| 主要技术参数 | |
|---|---|
| 工作电压 | DC5V |
| 静态电流 | <2mA |
| 输出电平 | 0-5V |
| 感应角度 | ≤15度 |
| 探测距离 | 2cm-450cm |
| 最高精度 | 0.3cm |
舵机
在这里推荐9G舵机SG90,或者其他类似的舵机,这种舵机体积比较小,扭矩虽然不是大, 但是足够带动简易云台,很方便在小车上使用,大家购买时注意舵机的转动角度,有55度的,180度的等等,大家可以根据需要购买。

舵机固定架

舵机安装
舵机在安装之前大家一定要记得校准,为什么要校准那,这个跟舵机的工作原理有关。控制信号由接收机的通道进入信号调制芯片,获得直流偏置电压。它内部有一个基准电路,产生周期为20ms,宽度为1.5ms的基准信号,将获得的直流偏置电压与电位器的电压比较,获得电压差输出。最后,电压差的正负输出到电机驱动芯片决定电机的正反转。当电机转速一定时,通过级联减速齿轮带动电位器旋转,使得电压差为0,电机停止转动。
舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。以180度角度伺服为例,那么对应的控制关系是这样的:
| 高电平时间 | 对应位置 |
|---|---|
| 0.5ms | 0度 |
| 1.0ms | 45度 |
| 1.5ms | 90度 |
| 2.0ms | 135度 |
| 2.5ms | 180度 |
也就是说当对舵机输入相同控制信号时,舵机会运动到固定位置,他的动作不是做圆周运动,而是在运动范围内,每一个位置对应一个控制信号。
因此我们需要在将舵机安装在固定架上之前,需要先将舵机初始化好,舵机一般为三根线:棕色——GND,红色——VCC,橙色——控制信号。因此我们将棕色线接到GND,红色线接到“+5V”引脚,橙色线接到“10”引脚,初始化程序如下:
#include <Servo.h> //加入含有舵机控制库的头文件 #define PIN_SERVO 10 //舵机信号控制引脚 Servo myservo; void setup() { myservo.attach(PIN_SERVO); //舵机初始化 } void loop() { myservo.write(90); //PWM输出 }
在舵机初始化好之后将其安装在固定架上,然后装配在小车上,此时保证超声波模块超前。
超声波接线
超声波模块有四个引脚,“VCC”接到Arduino UNO开发板的“+5V”引脚,“GND”接到开发板“GND”引脚,“Trig”引脚接到开发板“8”引脚,“Echo”引脚接到开发板“7”引脚。

代码测试
#include <Servo.h> #define STOP 0 #define FORWARD 1 #define BACKWARD 2 #define TURNLEFT 3 #define TURNRIGHT 4 int leftMotor1 = 16; int leftMotor2 = 17; int rightMotor1 = 18; int rightMotor2 = 19; int leftPWM = 5; int rightPWM = 6; Servo myServo; //舵机 int inputPin=7; // 定义超声波信号接收接口 int outputPin=8; // 定义超声波信号发出接口 void setup() { // put your setup code here, to run once: //串口初始化 Serial.begin(9600); //舵机引脚初始化 myServo.attach(9); //测速引脚初始化 pinMode(leftMotor1, OUTPUT); pinMode(leftMotor2, OUTPUT); pinMode(rightMotor1, OUTPUT); pinMode(rightMotor2, OUTPUT); pinMode(leftPWM, OUTPUT); pinMode(rightPWM, OUTPUT); //超声波控制引脚初始化 pinMode(inputPin, INPUT); pinMode(outputPin, OUTPUT); } void loop() { // put your main code here, to run repeatedly: avoidance(); } void motorRun(int cmd,int value) { analogWrite(leftPWM, value); //设置PWM输出,即设置速度 analogWrite(rightPWM, value); 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; default: Serial.println("STOP"); //输出状态 digitalWrite(leftMotor1, LOW); digitalWrite(leftMotor2, LOW); digitalWrite(rightMotor1, LOW); digitalWrite(rightMotor2, LOW); } } void avoidance() { int pos; int dis[3];//距离 motorRun(FORWARD,200); myServo.write(90); dis[1]=getDistance(); //中间 if(dis[1]<30) { motorRun(STOP,0); for (pos = 90; pos <= 150; pos += 1) { myServo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } dis[2]=getDistance(); //左边 for (pos = 150; pos >= 30; pos -= 1) { myServo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position if(pos==90) dis[1]=getDistance(); //中间 } dis[0]=getDistance(); //右边 for (pos = 30; pos <= 90; pos += 1) { myServo.write(pos); // tell servo to go to position in variable 'pos' delay(15); // waits 15ms for the servo to reach the position } if(dis[0]<dis[2]) //右边距离障碍的距离比左边近 { //左转 motorRun(TURNLEFT,250); delay(500); } else //右边距离障碍的距离比左边远 { //右转 motorRun(TURNRIGHT,250); delay(500); } } } int getDistance() { digitalWrite(outputPin, LOW); // 使发出发出超声波信号接口低电平2μs delayMicroseconds(2); digitalWrite(outputPin, HIGH); // 使发出发出超声波信号接口高电平10μs,这里是至少10μs delayMicroseconds(10); digitalWrite(outputPin, LOW); // 保持发出超声波信号接口低电平 int distance = pulseIn(inputPin, HIGH); // 读出脉冲时间 distance= distance/58; // 将脉冲时间转化为距离(单位:厘米) Serial.println(distance); //输出距离值 if (distance >=50) { //如果距离小于50厘米返回数据 return 50; }//如果距离小于50厘米小灯熄灭 else return distance; }
代码详解
“Trig”引脚控制超声波发出声波,对应int outputPin=8;
“Echo”引脚反应接收到返回声波,对应int inputPin=7;
int inputPin=7; // 定义超声波信号接收接口 int outputPin=8; // 定义超声波信号发出接口
int getDistance()函数解析
超声波发出引脚“Trig”为高时对外发出超声波,为保证发出10μs声波,因此在发送之前需要将该引脚拉低,并给他一定反应时间。
digitalWrite(outputPin, LOW); // 使发出发出超声波信号接口低电平2μs
delayMicroseconds(2);
之后发送10μs超声波
digitalWrite(outputPin, HIGH); // 使发出发出超声波信号接口高电平10μs,这里是至少10μs
声波发送之后禁止其继续发送,同时开始检测是否反射回来的声波
digitalWrite(outputPin, LOW); // 保持发出超声波信号接口低电平 int distance = pulseIn(inputPin, HIGH); // 读出脉冲时间
pulseIn()单位为微秒,声速344m/s,所以距离cm=344*100/1000000*pulseIn()/2约等于pulseIn()/58.0
distance= distance/58; // 将脉冲时间转化为距离(单位:厘米)
超声波模块工作受物体表面反射程度影响,并且在传播过程中信号强度容易衰减,因此该模块适用的检测距离有限,一般在50cm以内相对正确,而且我们在避障时不需要检测太远的距离,因此超过50cm以上的都按50cm计算
if (distance >=50)
{
//如果距离小于50厘米返回数据
return 50;
}//如果距离小于50厘米小灯熄灭
else
return distance;
void avoidance()函数解析
小车前进过程中只检测前方距离障碍的距离,并且控制舵机,保持超声波模块位于正前方。
motorRun(FORWARD,200);
myServo.write(90);
dis[1]=getDistance(); //中间
当检测到小车前方距离障碍距离小于30cm时停车,检测两边距离。
motorRun(STOP,0);
控制舵机每次运动一个周期后都返回正前方位置。由于舵机运动需要一定的时间,因此在每转过一个角度的时候都延时delay(15),供其运动。
for (pos = 90; pos <= 150; pos += 1)
{
myServo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
当运动到最左边时检测小车左边距离障碍的距离
dis[2]=getDistance(); //左边
向右边运动,检测右边距离
for (pos = 150; pos >= 30; pos -= 1)
{
myServo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
if(pos==90)
dis[1]=getDistance(); //中间
}
dis[0]=getDistance(); //右边
将前边、左边、右边距离障碍的距离都检测结束之后,舵机回到最初位置。
for (pos = 30; pos <= 90; pos += 1)
{
myServo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
注意事项
1.舵机使用时要防止其堵转,因为点击堵转时电流会增大,很容易烧坏舵机。
2.舵机的红色电源线接入电压一般要大于等于其工作电压,供电不足会导致舵机不停自传。
3.Arduino
《Servo.h》库里提供的write()函数输出的PWM即为舵机专用的20ms为周期的PWM波,如果使用其他开发板或者函数的话,请务必保证输出方波周期为20ms,否则舵机不会受控制
总结
这一篇讲解了舵机和超声波模块的使用方法,舵机在大家以后的开发生涯中应该会经常用到,因此舵机的使用规则(控制周期为20ms)请大家一定要记清楚,在舵机不受控制的时候建议大家可以购买一个舵机测试仪来测试舵机。

该模块使用很简单,在“INPUT”端接入VCC和GND,在OUTPUT端按照提示接上舵机,即可转动旋钮来控制舵机运转,在实际工程中经常会用来测试。