Arduino 蓝牙遥控 + 超声避障小车 做自己的第一台智能小车

charlesc
发布于 2020-11-6 19:39
浏览
0收藏

Arduino 蓝牙遥控 + 超声避障小车 做自己的第一台智能小车-鸿蒙开发者社区

成品预览Arduino 蓝牙遥控 + 超声避障小车 做自己的第一台智能小车-鸿蒙开发者社区
先上别人的买家秀

 

再看看手里的半成品(超声波模块被我拔了):Arduino 蓝牙遥控 + 超声避障小车 做自己的第一台智能小车-鸿蒙开发者社区

电路图

 

在[Bluetooth App-Controlled Dual Motor Device](https://fritzing.org/projects/bluetooth-app-controlled-dual-motor-device "Bluetooth App-Controlled Dual Motor Device")基础上进行修改:

 

材料

 

1、Arduino UNO 一块(淘宝或者阿里)

2、L298N 电机驱动一个(淘宝 6 块钱)
1、三轮小车底座一个(淘宝 15 块钱左右)
4、铜柱、螺丝、螺母、杜邦线若干
5、电池四节、电池盒一个
6、HC-06 蓝牙模块一个
7、蓝牙串口助手 app
8、HC_SR04 超声波模块一个
9、差速电机两个,一般买底盘会带,为了防止烧坏建议多备用一个
10、Arduino 开发工具

 

Arduino UNO 片上资源

 

(https://imgkr.cn-bj.ufileos.com/459e063a-1fd7-4220-b025-2e0154a1594f.png)

 

其中,左下角部分支持模拟量(Analog)的 PIN(A0 到 A5)的编号是 14 到 19。

 

L298N 模块 PWM 调速

 

如何理解 PWM

 

PWM,英文全称 Pulse Width Modulation,中文脉冲宽度调制。脉冲宽度调制是将模拟信号变换为脉冲的一种技术。

 

假设一个场景:一个电灯接在电路中正常工作。突然,好巧不巧,电路中的开关虚接了,导致电灯忽明忽暗。如果闪烁的频率足够快,那么人眼是无法分辨出来电灯的闪烁的,电影、视频都是这个原理。但是有一点,电灯的亮度一定会下降。当灯亮的时间占总时间的比例大的时候,人眼的感觉灯会变得亮一些,灯暗的时间占得比例越大,那么人眼的感觉自然会是灯变暗了。

 

如果理解上面的场景,那么就能够理解 PWM 的精髓了。PWM 是一种对模拟信号电平进行数字编码的方法。上述场景中提到的电灯亮的时间占总时间的比例,可以类比 PWM 中的“占空比”概念。“占空比”是指一个脉冲循环内,通电时间占总时间的比例。例如脉冲宽度 0.001ms,信号周期 0.004ms 的脉冲序列,占空比为 0.25。

 

如果脉冲(方波)的峰值电压为 5V,在脉冲占空比为 0.25 时候,等效电压值为方波峰值电压和占空比的乘积 1.25V

 

PS: 我已经讲得足够简单了,如果还读不懂的话,建议提前去补补课。接下来的部分难度会比这个还要难。

 

L298N 模块

 

L298 是 ST 公司生产的双全桥驱动器(DUAL FULL-BRIDGE DRIVER),[datasheet](http://www.waveshare.net/datasheet/ST_PDF/L298.PDF "datasheet")。工作电压在 4.8-46V 之间,输出电流高达 2A。可以驱动两个二相电机,一般用来四轮驱动小车,驱动两轮小车的话,真有种杀鸡焉用宰牛刀的感觉。

 

L298N 模块可以直接与单片机相连,控制起来非常方便。

 

L298N 逻辑功能表

Arduino 蓝牙遥控 + 超声避障小车 做自己的第一台智能小车-鸿蒙开发者社区

其中,IN3、IN4、ENB 的逻辑与上表相同。

 

HC_SR04 超声波模块测距

 

HC_SR04 工作原理

 

https://imgkr.cn-bj.ufileos.com/1b918bfa-7826-4418-b43b-d33cf09161cb.png)

 

观察以上时序图,当提供一个 10us 以上的触发信号,HC_SR04 内部将发出 8 个 40kHz 的周期电平并检测回波。一旦检测出有回波信号,则停止输出回响信号。故回响信号的脉冲宽度和距离成正比。由此则可计算出距离。

 

取声速 343m/s(干燥、室温 20 度),1 微秒传过的距离是 0.0343 厘米。取倒数,则得到声音每传播 1 厘米,需要 29.15 微秒。

 

又因为从声音发出到接收回波,声音走过的路程应该是距离的 2 倍,实际距离 1 厘米,需要对应 58.3 微秒,取整 58 即可。

 

故在程序中可见这样的代码:

 

```c++

int getDistance () {

  digitalWrite(ultrasonicOutputPin, LOW);

  delayMicroseconds(2);

  digitalWrite(ultrasonicOutputPin, HIGH);

  delayMicroseconds(10);

  digitalWrite(ultrasonicOutputPin, LOW);



  int distance = pulseIn(ultrasonicInputPin, HIGH);

  distance = distance / 58;

  return distance > 0 ? distance: 0;

}

 

在上述代码中,ultrasonicOutputPin 连接超声波模块的 Trig 引脚,ultrasonicInputPin连接超声波模块的 Echo 引脚。

基于超声波模块的工作原理,故超声波测距适用于坚硬平整的较大物体,其他场景性能不佳。

## HC-06(HC-05)蓝牙模块

HC-06 和 HC-05 模块大致差不多,对于 Arduino 的用户,一般从淘宝直接买 JY-MCU,本人也不例外。模块使用起来和串口类似。模块提供的[用户指引(User Guide)](https://core-electronics.com.au/attachments/guides/Product-User-Guide-JY-MCU-Bluetooth-UART-R1-0.pdf "用户指引(User Guide)")中提供了丰富详尽的例子,同样在下面的文章中我也会简单介绍。

考虑到小车的拓展性,例如之后可能会接 ESP8266 啥的,暂时先不占用 Arduino 的硬件串口,留着以后使用,蓝牙模块与板子的连接使用软串口的方式。同样软串口相关的内容会在下面进行阐述。

软串口连接蓝牙模块的参考代码如下:

```cpp
#include <SoftwareSerial.h>

SoftwareSerial bluetoothSerial(11, 12); // RX, TX

void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  bluetoothSerial.begin(57600);
   bluetoothSerial.print("AT");
  delay(1000);
  bluetoothSerial.println("AT+VERSION");
  delay(1000);
  bluetoothSerial.println("AT+BAUD7");
  delay(1000);
  bluetoothSerial.println("AT+NAMEarduino_car");
  delay(1000);
}
void bluetoothTaskCallback() {
  if (bluetoothSerial.available()) {
    int bluetoothCmd = (int)bluetoothSerial.read();
    Serial.print("bluttooth Command: ");
    Serial.println(bluetoothCmd);

    // TODO: 蓝牙传入参数校验
    motorCtrl(bluetoothCmd);
  }
  if (Serial.available()) {
    Serial.print("Serial available: ");
    Serial.println(Serial.read());
    bluetoothSerial.write(Serial.read());
  }
}

 

 

在上述代码中,Arduino 软串口的接收引脚(RX)为 11,与蓝牙模块的发送引脚(TX)连接。发送引脚(TX)为 12,与蓝牙模块的接收引脚(RX)。

 

在板子初始化过程中,使用 AT 指令对蓝牙模块进行了一些设置:

 

  • AT+BAUD7 设置蓝牙模块的波特率为 57600bps(对于 Arduino 软串口的最高速率)。只有两边波特率设置的一样,才可以正常通信。
  • AT+NAMEarduinocar 设置蓝牙的名称为“arduinocar”,方便我们使用蓝牙调试助手进行连接。

 

软件模拟串口

 

除了硬件串口(板子上的 D0、D1 口),Arduino 还提供了 SoftwareSerial 类库,支持用户将其他数字引脚(D\*)通过程序模拟成串口通讯引脚。

 

软件模拟串口简称软串口,使用方法和硬件串口基本一样。类库的详细介绍见[Arduino Reference SoftwareSerial Libray](https://www.arduino.cc/en/Reference/softwareSerial "Arduino Reference SoftwareSerial Libray")。

 

在 Reference 中指出,软串口有着以下一系列的局限性:

The library has the following known limitations:
>

- If using multiple software serial ports, only one can receive data at a time.

- Not all pins on the Mega and Mega 2560 support change interrupts, so only the following can be used for RX: 10, 11, 12, 13, 14, 15, 50, 51, 52, 53, A8 (62), A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), A15 (69).

- Not all pins on the Leonardo and Micro support change interrupts, so only the following can be used for RX: 8, 9, 10, 11, 14 (MISO), 15 (SCK), 16 (MOSI).

- On Arduino or Genuino 101 the current maximum RX speed is 57600bps

- On Arduino or Genuino 101 RX doesn't work on Pin 13
>

If your project requires simultaneous data flows, see Paul Stoffregen's [AltSoftSerial library](http://www.pjrc.com/teensy/tdlibsAltSoftSerial.html "AltSoftSerial library"). AltSoftSerial overcomes a number of other issues with the core SoftwareSerial, but has it's own limitations. Refer to the [AltSoftSerial site](http://www.pjrc.com/teensy/tdlibsAltSoftSerial.html "AltSoftSerial site") for more information.

 

翻译过来的话,大意如下:

 

这个类库有以下已知限制:
>

- 如果使用多个软串口,则一次只能接收到一个数据,串口之间会相互干扰。

- 因为 Mega 和 Mega2560 板子上并不是所有的引脚都支持更改中断(interrupt),所以只有以下引脚能做软串口的接收引脚(RX): 10, 11, 12, 13, 14, 15, 50, 51, 52, 53, A8 (62), A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), A15 (69)。

- 在 Arduino 和 Genuino 101 上,最大的接收速率是 57600bps。

- 在 Arduino 和 Genuino 101 上,接收引脚(RX)不能是 13 号引脚。
>

如果你的项目中需要同步数据流,那么请参阅 Paul Stoffregen 的 AltSoftSerial 库。AltSoftSerial 还解决了 SoftwareSerial 中一些其他的问题,但是它也有着自己的局限性。更多信息请参考 AltSoftSerial 网站。

 

蓝牙串口助手

 

为了方便,我选择使用 Android 手机作为蓝牙主机(Master),蓝牙串口模块做从机(Salve)。或者叫 Leader 和 Follower 会更好,Master 和 Salve 有违反人权之嫌疑。

 

(https://imgkr.cn-bj.ufileos.com/91d0dfb0-398b-4e01-84e4-68024c2c0326.png)

 

蓝牙串口助手的设置过程比较繁琐,我在设置过程中参考了 CSDN 博主“不懂音乐的欣赏者”的 [Arduino 智能小车——蓝牙小车](https://blog.csdn.net/qq_16775293/article/details/77489166 "Arduino 智能小车——蓝牙小车")一文。

 

通过手机的蓝牙串口助手,可以给小车下达一系列指令,指令的内容如下:

Arduino 蓝牙遥控 + 超声避障小车 做自己的第一台智能小车-鸿蒙开发者社区

具体的控制函数有些复杂,之后可以考虑单独用一篇文章阐述一下。

 

任务调度

 

目前来讲,小车支持蓝牙遥控、调速,并且在前进的方向放置了超声波模块检测距离,防止碰撞。两个任务可以放在 void loop() 中,通过 delay() 控制时间。这种方式貌似可以解决问题(虽然有些 low),但是如果任务多起来的话,显然会力不从心。

 

为了更好地协调两个任务,保证两个任务有条不紊的运行,故引入了调度器。调度器可以以特定的周期执行任务。

 

代码中使用的调度器 [Github Repo](https://github.com/arkhipenko/TaskScheduler "Github Repo"):https://github.com/arkhipenko/TaskScheduler

 

调用的参考代码:

 

#include <TaskScheduler.h>

Scheduler runner;

void ultrasonicTaskCallback();
void bluetoothTaskCallback();

Task bluetoothTask(150,TASKFOREVER,&bluetoothTaskCallback, &runner, true);
Task ultrasonicTask(500,TASKFOREVER,&ultrasonicTaskCallback, &runner,true);

void loop() {
  runner.execute();
}

 

结语

 

文章当中涉及到很大一部分内容是工科学生基本学习过的内容,比如 PWM、串口通信、超声波测距、电机控制等等。但是在写作的过程中,发现通过短短的几千个字就讲清楚如何做一个 Arduino 小车,并让它跑起来恐怕还是有些困难。因为这些东西看起来简单,但是涉及到的知识范围还是蛮广的。

 

在实验的过程中,如果有什么问题的话,可以在评论区留言讨论。或者点击右上角发布提问。

 

完整代码

 

完整代码访问 [arduinocar](https://github.com/Raoul1996/arduinocar "GitHub") 获取,或者公众号对话框回复“arduino 小车”也可。

 

具体对应的 PIN 依据实际情况请进行修改,本文中为了方便作图,修改了部分 GPIO 号,保证图文对应

#include <SoftwareSerial.h>
#include <TaskScheduler.h>

#define STOP 0
#define FORWARD 1
#define BACKWARD 2
#define TURNLEFT 3
#define TURNRIGHT 4
#define CHANGESPEED 5


#define DEFAULTVOLTAGE 150

#define MINVOLTAGE 0

#define MAXVOLTAGE 255

#define LOWSPEED 0
#define HIGHSPEED 1

SoftwareSerial bluetoothSerial(11, 12); // RX, TX

 Scheduler runner;

 void ultrasonicTaskCallback();
 void bluetoothTaskCallback();

Task bluetoothTask(150,TASKFOREVER,&bluetoothTaskCallback, &runner, true);
Task ultrasonicTask(500,TASKFOREVER,&ultrasonicTaskCallback, &runner,true);


// declare motor driver module ctrl pin:
int leftMotorPin1 = 7;
int leftMotorPin2 = 6;
int rightMotorPin1 = 5;
int rightMotorPin2 = 4;

int leftPWM = 10;
int rightPWM = 9;

int ultrasonicInputPin = 2; // Echo
int ultrasonicOutputPin = 3; // Trig

int motorAction = STOP;
int motorVoltage = MINVOLTAGE;
int motorSpeedLevel = HIGHSPEED;

int distance = 0;
/
  motor ctrl matrix
  1. stop
  2. forward
  3. backward
  4. turn left
  5. turn right
/
int ctrlMatrix[5][4] = {
  {LOW, LOW, LOW, LOW},
  {LOW, HIGH, LOW, HIGH},
  {HIGH, LOW, HIGH, LOW},
  {HIGH, LOW, LOW, HIGH},
  {LOW, HIGH, HIGH, LOW}
};


void motorRun(int cmd) {
  Serial.print("recv cmd: ");
  Serial.println(cmd);
  digitalWrite(leftMotorPin1, ctrlMatrix[cmd][0]);
  digitalWrite(leftMotorPin2, ctrlMatrix[cmd][1]);
  digitalWrite(rightMotorPin1, ctrlMatrix[cmd][2]);
  digitalWrite(rightMotorPin2, ctrlMatrix[cmd][3]);
}


void changeSpeed(int voltage) {
  Serial.print("recv voltage: ");
  Serial.println(voltage);
  analogWrite(leftPWM, voltage);
  analogWrite(rightPWM, voltage);
}

void changeSpeedLevel(int level) {
  Serial.print("recv voltage level: ");
  Serial.println(level);
  if (level == HIGHSPEED) {
    analogWrite(leftPWM, MAXVOLTAGE);
    analogWrite(rightPWM, MAXVOLTAGE);
  } else {
    analogWrite(leftPWM, DEFAULTVOLTAGE);
    analogWrite(rightPWM, DEFAULTVOLTAGE);
  }

}
void bluetoothTaskCallback() {
  if (bluetoothSerial.available()) {
    int bluetoothCmd = (int)bluetoothSerial.read();
    Serial.print("bluttooth Command: ");
    Serial.println(bluetoothCmd);
    motorCtrl(bluetoothCmd);
  }
  if (Serial.available()) {
    Serial.print("Serial available: ");
    Serial.println(Serial.read());
    bluetoothSerial.write(Serial.read());
  }
}

void ultrasonicTaskCallback() {
  int distance = getDistance();
  if (distance <= 30) {
    motorRun(STOP);
    Serial.print("distance is dangerous: ");
    Serial.println(distance);
    if (bluetoothSerial.available()) {
      bluetoothSerial.print("distance is dangerous: ");
      bluetoothSerial.println(distance);
    }
    changeSpeedLevel(LOWSPEED);
    motorRun(STOP);
  } else if (distance >= 100) {
    if (bluetoothSerial.available()) {
      bluetoothSerial.print("distance is fine,speed up: ");
      bluetoothSerial.println(distance);
    }
    motorRun(motorAction);
    changeSpeedLevel(HIGHSPEED);
  } else {
    if (bluetoothSerial.available()) {
      bluetoothSerial.print("distance is just ok: ");
      bluetoothSerial.println(distance);
    }
    motorRun(motorAction);
  }

}

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(57600);
  bluetoothSerial.begin(57600);
   bluetoothSerial.print("AT");
  delay(1000);
  bluetoothSerial.println("AT+VERSION");
  delay(1000);
  bluetoothSerial.println("AT+BAUD7");
  delay(1000);
  bluetoothSerial.println("AT+NAMEarduino_car");
  delay(1000);
  // set pin mode for motor
  pinMode(leftMotorPin1, OUTPUT);
  pinMode(leftMotorPin2, OUTPUT);
  pinMode(rightMotorPin1, OUTPUT);
  pinMode(rightMotorPin2, OUTPUT);
  pinMode(leftPWM, OUTPUT);
  pinMode(rightPWM, OUTPUT);

  pinMode(ultrasonicInputPin, INPUT);
  pinMode(ultrasonicOutputPin, OUTPUT);
  changeSpeedLevel(motorSpeedLevel);
  runner.startNow();
  Serial.println("setup has been down.");
}
void motorCtrl(int cmd) {
  if (cmd >= STOP && cmd <= TURNRIGHT) {
    motorAction = cmd;
    motorRun(motorAction);
  } else if (cmd > DEFAULTVOLTAGE && cmd <= MAXVOLTAGE) {
    motorVoltage = cmd;
    changeSpeed(cmd);
  } else if (cmd == CHANGESPEED) {
    if (motorSpeedLevel == LOWSPEED) {
      motorSpeedLevel = HIGHSPEED;
    } else {
      motorSpeedLevel = LOWSPEED;
    }
    changeSpeedLevel(motorSpeedLevel);
  }

}

int getDistance () {
  digitalWrite(ultrasonicOutputPin, LOW);
  delayMicroseconds(2);
  digitalWrite(ultrasonicOutputPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(ultrasonicOutputPin, LOW);

  int distance = pulseIn(ultrasonicInputPin, HIGH);
  distance = distance / 58;
  return distance > 0 ? distance: 0;
}

void loop() {
  runner.execute();
}

 

已于2020-11-6 19:41:02修改
1
收藏
回复
举报
回复
    相关推荐