摘要:本文介绍如何使用超声波传感器和舵机实现小车的自动避障功能
接下来就来实现自动避障功能的主程序了。在之前的实验中,大家已经发现了,Arduino的主程序是由2个函数组成,一个是初始化的setup()函数,一个是主循环函数loop()。先来实现主函数setup()。
setup()函数主要是实现系统的初始化工作,这个函数在系统启动后被执行一次,用于完成各种资源的分配和相关功能的初始化。在这个避障小车中,主要涉及了控制行进的GPIO以及LEDC控制器的初始化、舵机的初始化和超声波传感器的初始化。下面就来看一下整个初始化代码。
void setup() { // 初始化串口 Serial.begin(115200); // 初始化LEDC PWM控制器 for (int i = 0; i < 4; i++) { ledcSetup(wheels_ch[i], MOTO_PWM_FREQ, MOTO_PWM_RESOLUTIONS_BIT); ledcAttachPin(wheels_pin[i], wheels_ch[i]); } // 初始化超声波传感器 pinMode(ULTRASONIC_TRIG, OUTPUT); pinMode(ULTRASONIC_ECHO, INPUT); // 初始化舵机 servo.attach(STEERING_ENGINE, MOTO_PWM_CH, 500, 2500); servo.write(STEERING_ENGINE, 90); delay(10000); // 启动延时 Serial.println("Setup finished!"); } |
在上面的代码可以看到,初始化工作依次完成了以下这些内容:
- 初始化串口:这个主要是为了功能测试的时候,输出一些相关信息,方便程序的开发和调试工作。
- 初始化LEDC控制器:LEDC控制器的初始化按照之前的介绍就是2步:ledcSetup()函数用来设置LEDC控制器,ledcAttachPin()用来将控制器与GPIO引脚绑定。
- 初始化超声波传感器:主要用来设置超声波传感器的Trig控制引脚为输出,Echo控制引脚为输入模式。
- 初始化舵机:利用attach()设置了舵机工作的相关参数,然后的write()函数,让舵机回到初始向前的位置,使超声波传感器朝向正前方,为运行做好准备。
- Delay()函数在这里是一个便利性的设计,非必须的。它的目的是延长智能小车启动的时间,不让其在通电完成后就立刻开始向前行驶。因为setup()函数执行完毕后,立刻就会循环执行loop()函数,如果没有这个延时,小车立刻就要开始向前行进了。
接下来就来看一下主程序loop()函数,在这个函数中,只是调用了car_avoid_run()函数,如下所示:
void loop() { // put your main code here, to run repeatedly: car_avoid_run(); } |
car_avoid_run()函数,这个函数是本程序的核心,用来完成一次超声波测距,然后再根据返回的障碍物的距离来决定下一步是需要转向行驶还是继续执行。程序的代码如下所示:
// 小车避障行驶 void car_avoid_run() { // 超声波测量前方障碍物的距离 float dist = turn_check_distance(90); if (dist < DISTANCE_LIMIT) { // 小于设定的停车距离 car_stop(); // 小车停止前进 dist = turn_check_distance(90); //再次测量障碍物的距离 if( dist<15 ) { //如果距离前方障碍物小于15里面 car_back(200); //小车后退行驶200毫秒 } //测量左右两侧的距离,返回下一步行驶的方向 int d = find_direction(); car_turn(d); //小车转向 } //小车继续行驶 car_run(); } |
在car_avoid_run()函数的执行逻辑如下:
- 利用turn_check_distance()函数得到正前方障碍物的距离
- 如果障碍物距离小于停车距离,那么通过find_direction()函数来决定下一步的行驶方向,并用car_turn()函数完成转向。
- 小车继续前行
turn_check_distance()函数有一个参数,这个参数用来指定测距的方向,也就是舵机需要转到哪个角度。需要注意的是,在这个小车中使用的是180度的舵机,因此90度为正前方,0度为左侧,180度为右侧。这个函数的实现方法如下:
// 转到某个方向,然后测量距离 float turn_check_distance(int direction) { //判断上次测量的是不是同一个方向 if( lastDir!=direction ) { //不是同一个方向,则控制舵机旋转 servo.write(STEERING_ENGINE,direction); delay(500); //记录本次测量的方向 lastDir = direction; } //返回障碍物的距离 return checkdistance(); } |
在turn_check_distance()函数中,用到了之前定义的全局变量lastDir,这个变量用来记录上一次舵机的朝向,如果本次测量的方向与上一次相同,那么就不进行舵机的旋转操作了,提高了运行的效率。控制舵机的转向使用了Servo对象的write()方法,这个方法在之前已经介绍过了。在这里需要强调的是,舵机的旋转需要一定的时间,一定要等待舵机旋转完成之后,再进行超声波测距工作,这样测量的数据才能准确。
在这个函数的最后,是调用checkdistance()函数来测量前方障碍物的距离。
checkdistance()函数在之前也已经实现过了,是按照超声波测距模块的协议完成了一次超声波测距的工作,代码如下所示:
// 超声波测距函数 float checkdistance() { digitalWrite(ULTRASONIC_TRIG, LOW); delayMicroseconds(2); digitalWrite(ULTRASONIC_TRIG, HIGH); delayMicroseconds(10); digitalWrite(ULTRASONIC_TRIG, LOW); float distance = pulseIn(ULTRASONIC_ECHO, HIGH) / 58.00; delay(10); return distance; } |
接下来看一下car_stop()函数,这个函数用来停止小车的行驶。代码如下所示:
// 小车停止 void car_stop() { car_change_status(0,0,0,0); } |
car_stop()函数就是调用了car_change_status()函数,改变了小车轮子的运动状态。car_change_status()函数有4个参数,分别表示控制2个轮子的4个LEDC控制器的PWM占空比分辨率,用来调整4个轮子的转速。当这4个参数都是0时,小车停止运动。具体实现代码如下所示:
// 改变小车4轮的运行状态 void car_change_status(uint32_t wl1,uint32_t wl2,uint32_t wr1,uint32_t wr2) { ledcWrite(WL1_CHANNEL, wl1); ledcWrite(WL2_CHANNEL, wl2); ledcWrite(WR1_CHANNEL, wr1); ledcWrite(WR2_CHANNEL, wr2); } |
其中的wl1和wl2用来控制左轮的运动状态,根据参数的不同组合可以实现左轮的向前、向后运动以及停止运动。同样的,wr1和wr2则是用来控制右轮的。
car_back()用来控制小车向后行驶,这个函数有一个参数,用于指定向后行驶的时间,单位是毫秒数,该函数的实现方法如下所示:
// 小车向后行驶 void car_back(int msSecond) { // 向后行驶指定的时间 car_change_status(0,SPEED_L,0,SPEED_R); delay(msSecond); // 停止行驶 car_stop(); } |
find_direction()函数则用来找到下一步需要行驶的方向。它首先会测量左、右两侧障碍物的距离,然后如果两个距离都小于停车距离,那么小车下一步就掉头行驶,返回向后(也就是270度方向)。否则,返回障碍物距离远的那一侧(0为左侧、180为右侧)。实现方法如下:
// 得到前进的方向 int find_direction() { //测量左侧障碍物的距离 float d0 = turn_check_distance(0); //测量右侧障碍物的距离 float d180 = turn_check_distance(180); //判断两侧的障碍物距离是不是都小于停止距离 if (d0 < DISTANCE_LIMIT && d180 < DISTANCE_LIMIT) { return 270; //小车掉头行驶 } return d0 > d180 ? 0 : 180; //返回障碍物远的方向 } |
实现小车转向的方法是由car_turn()函数来完成的,这个函数根据转向的角度,来控制2轮不同的转动方向来实现小车的转向,具体的实现方法如下:
// 小车转弯 void car_turn(int direction) { if (direction == 0) { //向左转 car_change_status(SPEED_L,0,0,SPEED_R); delay(TURN_TIME); } else if (direction == 180) { //向右转 car_change_status(0,SPEED_L,SPEED_R,0); delay(TURN_TIME); } else { //掉头 car_change_status(0,SPEED_L,SPEED_R,0); delay(TURN_TIME*2); } Serial.println(direction); } |
在这里需要提醒的是每个转向的运行时间是由后边的延时函数决定的,这是一个需要测量后的出来的时间,要根据个人的情况进行调整,如果转的角度大了,就把延迟时间调短些,如果转动角度小了,就把延迟时间调大些。
最后一个car_run()函数就是控制小车向前行驶。实现方法如下:
// 小车向前行驶 void car_run() { car_change_status(SPEED_L,0,SPEED_R,0); } |
好了,到这里,整个避障小车的代码就列举完了,可以把代码输入到Arduino IDE中,然后编译、上传,让自己的小车跑起来吧!