Arduino/stm32 智能小车设计(二)
本节进行Arduino智能小车的代码讲解。
一、I/O口定义与初始化函数
如下图RGB指示灯用到10、11、12三个I/O口,分别对应蓝色、红色、绿色。当给对应的I/O口低电平时,对应颜色的灯亮。也可以用PWM进行调色,实现不同的颜色。这里先把控制端的原理图粘贴上来以便后面原理说明。
Arduino封装了很多底层的细节,使得学习者不需要太多的关注底层电路的原理。
Arduino一共有14个数字I/O口,分别为0-13,定义与初始化看代码;6个模拟I/O口,分别为A0-A5,定义与初始化看代码。
已下是所有I/O端口定义:
//0:TDX蓝牙 1:RXD蓝牙 2:DATA红外遥控
//3:蜂鸣器 4:按键 5:IN2左电机
//6:IN3右电机 7:IN4右电机 8:IN1左电机
//9:悬空 10:RGB_B 11:RGB_R
//12:RGB_G 13:流水灯
//A0:Echo超声波回声脚 A1:Trig超声波触发脚
//A2:左循迹输入 A3:右循迹输入
//A4:右壁障输入 A5:左壁障输入
//I/O口 0、1 是Arduino唯一的一组串口通信口(代码里无需定义)。用于蓝牙数据的接发,具体串口通信的知识可以看我的另外一篇博客了解。链接:
https://blog.csdn.net/weixin_43729724/article/details/101287363
//数字端口定义
int DATA= 2; //红外数据输入端口
int BEEP=3; //蜂鸣器输出
int KEY=4; //按键输入
int IN2=5; //左电机
int IN3=6; //右电机
int IN4=7; //右电机
int IN1=8; //左电机
//9脚悬空,不需要定义
int RGB_B=10; //RGB蓝色
int RGB_R=11; //RDB红色
int RGB_G=12; //RGB绿色
int Water_L=13; //流水灯I/O
//模拟I/O口定义
int Trig =A0; //Trig 触发脚输出
int Echo = A1; //Echo回声脚输入
int XunJi_R = A3; //右寻迹输入
int XunJi_L = A2; //左寻迹输入
int BiZhang_R = A4; //右红外避障输入
int BiZhang_L = A5; //左红外避障输入
/************************************************************************
* @描述: Arduino初始化函数
* @参数: None
* 功能: 定义I/O口模式,并赋初值
* @返回值: None
**********************************************************************/
void setup()
{
Serial.begin(9600); //波特率9600 (蓝牙通讯设定波特率)
pinMode(DATA,INPUT);
pinMode(BEEP,OUTPUT);
pinMode(KEY,INPUT);
pinMode(IN2,OUTPUT);
pinMode(IN3,OUTPUT);
pinMode(IN4,OUTPUT);
pinMode(IN1,OUTPUT);
pinMode(RGB_B,OUTPUT);
pinMode(RGB_R,UTPUT);
pinMode(RGB_G,UTPUT);
pinMode(Water_L,OUTPUT);
pinMode(Trig,OUTPUT);
pinMode(Echo,INPUT);
pinMode(XunJi_R,INPUT);
pinMode(XunJi_L,INPUT);
pinMode(BiZhang_R,INPUT);
pinMode(BiZhang_L,INPUT);
digitalWrite(BEEP,LOW);
digitalWrite(RGB_B,HIGH);
digitalWrite(RGB_R,HIGH);
digitalWrite(RGB_G,HIGH);
digitalWrite(Water_L,HIGH);
}
二、前进、后退、左转、右转、左旋转、右旋转API函数
原理图如下:
电机驱动原理:L293DD是一款专用的电机驱动芯片,因为I/O口驱动电流受限,而电机运转需要高电流驱动。当8高5低时,电流从OUT1->OUT2驱动做电机正转,反之亦凡。I/O口6、7驱动右电机运动。
代码如下:
/************************************************************************
* @描述:
* @参数: speed:控制速度0-255
* 功能: 小车前进
* @返回值: None
**********************************************************************/
void run(int speed)
{
analogWrite(IN1,speed);//PWM比例0~255调速,左右轮差异略增减
digitalWrite(IN2,LOW);
analogWrite(IN4,speed);//PWM比例0~255调速,左右轮差异略增减
digitalWrite(IN3,LOW);
}
/************************************************************************
* @描述:
* @参数: None
* 功能: 刹车
* @返回值: None
**********************************************************************/
void brake()
{
digitalWrite(IN1,LOW);
digitalWrite(IN2,LOW);
digitalWrite(IN3,LOW);
digitalWrite(IN4,LOW);
}
/************************************************************************
* @描述: 左转(左轮不动,右轮前进)
* @参数: speed:控制速度0-255
* 功能: 小车左转
* @返回值: None
**********************************************************************/
void left(int speed)
{
digitalWrite(IN1,LOW);
digitalWrite(IN2,LOW);
analogWrite(IN4,speed);//PWM比例0~255调速,右轮前进
digitalWrite(IN3,LOW);
}
/************************************************************************
* @描述: 左旋转(左轮后退,右轮前进)
* @参数: speed:控制速度0-255
* 功能: 小车左旋转
* @返回值: None
**********************************************************************/
void spin_left(int speed)
{
digitalWrite(IN1,LOW);
analogWrite(IN2,speed);
analogWrite(IN4,speed);
digitalWrite(IN3,LOW);
}
/************************************************************************
* @描述: 右转(左轮前进,右轮不动)
* @参数: speed:控制速度0-255
* 功能: 小车右转
* @返回值: None
**********************************************************************/
void right(int speed)
{
analogWrite(IN1,speed);
digitalWrite(IN2,LOW);
digitalWrite(IN4,LOW);
digitalWrite(IN3,LOW);
}
/************************************************************************
* @描述: 右旋转(左轮前进,右轮后退)
* @参数: speed:控制速度0-255
* 功能: 小车右旋转
* @返回值: None
**********************************************************************/
void spin_right(int speed)
{
analogWrite(IN1,speed);
digitalWrite(IN2,LOW);
digitalWrite(IN4,LOW);
analogWrite(IN3,speed);
}
/************************************************************************
* @描述:
* @参数: speed:控制速度0-255
* 功能: 小车后退
* @返回值: None
**********************************************************************/
void back(int speed)
{
digitalWrite(IN1,LOW);
analogWrite(IN2,speed);
digitalWrite(IN4,LOW);
analogWrite(IN3,speed);
}
三、红外避障与红外寻迹API函数
红外避障原理:如图红外对管D24由一个发射管和一个接收管组成,发射管发出红外线,当遇到障碍物时反射红外线由接收管接收,使运放IN1-端口电压变化,当调节R16电位器使IN1+电压变化,当IN1+ 的电压大于IN1-的电压时,OUT1输出高电平,被A5接收。
红外寻迹原理:如图红外对管U8由一个发射管和一个接收管组成,发射管发出红外线,当没有探测到黑线时,一直反射红外线被接收管接受,IN4-端口输入固定电压,当探测到黑线时,红外线被吸收,接受管接受不到红外线,IN4-电压降低,对比IN4-、IN4+两端电压,OUT4输出高低电平,被A2接收。
代码如下:
int BiZhang_L_S; //左红外传感器状态
int BiZhang_R_S; //右红外传感器状态
/************************************************************************
* @描述: 当没有探测到障碍物时,A4、A5为高电平,当探测到高电平时为低电平
* @参数: speed:控制速度0-255
* 功能: 红外避障
* @返回值: None
**********************************************************************/
void Infrared_Avoid(int speed)
{
//有信号为LOW 没有信号为HIGH
BiZhang_R_S = digitalRead(BiZhang_R);
BiZhang_L_S = digitalRead(BiZhang_L);
if (BiZhang_L_S == HIGH && BiZhang_R_S == HIGH)
run(speed);
else if (BiZhang_L_S == HIGH & BiZhang_R_S == LOW)
left(speed);
else if (BiZhang_R_S == HIGH & BiZhang_L_S == LOW)
right(speed);
else // 都是有障碍物
spin_right(speed);
}
int XunJi_L_S; //左循迹红外传感器状态
int XunJi_R_S; //右循迹红外传感器状态
/************************************************************************
* @描述: 当没有探测到黑线时,A4、A5为高电平
* @参数: speed:控制速度0-255
* 功能: 红外寻迹
* @返回值: None
**********************************************************************/
void Find_Tracking(int speed)
{
XunJi_R_S = digitalRead(XunJi_R);
XunJi_L_S = digitalRead(XunJi_L);
if (XunJi_R_S ==LOW && XunJi_L_S == LOW)
run(speed);
else if (XunJi_L_S == LOW & XunJi_R_S == HIGH)
right(speed);
else if (XunJi_R_S == LOW & XunJi_L_S == HIGH)
left(speed);
else
brake();
}
四 、超声波测距原理
超声波模块如图:
产品特色:
1、典型工作用电压:5V。
2、超小静态工作电流:小于2mA。
3、感应角度:不大于15 度。
4、探测距离:2cm-400cm
5、高精度:可达0.3cm。
6、盲区(2cm)超近。
超声波时序如图:
原理图如下:
代码如下:
/************************************************************************
* @描述:
* @参数: None
* 功能: 量出前方距离
* @返回值: int:距离(单位:CM)
**********************************************************************/
int Ultrasonic_Distance()
{
digitalWrite(Trig, LOW); //给触发脚低电平2μs
delayMicroseconds(2);
digitalWrite(Trig, HIGH); //给触发脚高电平12μs,这里至少是10μs
delayMicroseconds(12);
digitalWrite(Trig, LOW); //持续给触发脚低电
float Time = pulseIn(Echo, HIGH); //读取高电平时间(单位:微秒)
return Time/58; //公式:us/58 = CM
}
五、蓝牙控制原理
由于蓝牙通信用的是 0、1两个通用串行通信I/O口,Arduino官方提供了,串口通信的专用API函数供用户使用,这里我们用的是Serial.available()
函数说明见链接:https://www.arduino.cc/reference/en/language/functions/communication/serial/available/
手机APP或者电脑上位机通过蓝牙发送字符串给给蓝牙模块(蓝牙模块就相当于是一个中转站),Arduino控制板用过0、1串行端口读取到来自蓝牙的数据。然后,我们根据来自APP或者上位机的字符串来判断功能。比如,手机发送:0X00代表前进、0X01 代表后退等等。这样我们只需要用一个switch语句实现蓝牙API函数。
我的代码如下:
int speed = 150; //小车运动速度
/************************************************************************
* @描述:
* @参数: None
* 功能: 蓝牙控制
* @返回值: int:距离(单位:CM)
**********************************************************************/
void Bluetooth_Control()
{
if (Data_Received)
{
switch(IN_String[1])
{
case '1': run(speed);
break;
case '2': back(speed);
break;
case '3': left(speed);
break;
case '4': right(speed);
break;
case '0': brake();
break;
default: brake();
break;
}
if(IN_String[3] == '1') //旋转
{
spin_left();
}
else if(IN_String[3] == '2') //旋转
{
spin_right();
}
if(IN_String[5] == '1') //鸣笛0.5S
{
digitalWrite(BEEP,HIGH);//发声音
delay(500);
digitalWrite(BEEP,LOW);//不发声音
}
if(IN_String[7] == '1') //加速
{
speed +=50;
if(speed > 255) speed = 255;
}
if(IN_String[9] == '1') //减速
{
speed -= 50;
if(speed < 50) speed = 100;
}
IN_String = ""; // clear the string
Data_Received = false;
}
}
int Bluetooth_Data; //接收蓝牙的数据
String IN_String = ""; //用来储存接收到的内容
boolean Start_flag = false; //数据开始接收的状态标志
boolean Data_Received = false; //接收数据结束标志
/************************************************************************
* @描述: 串口中断函数
* @参数: None
* 功能: 接收来自蓝牙模块的数据
* @返回值: None
**********************************************************************/
void serialEvent()
{
while (Serial.available())
{
Bluetooth_Data= Serial.read();
if(Bluetooth_Data== '$')
{
Start_flag = true;
}
if(Start_flag == true)
{
IN_String += (char) Bluetooth_Data;
}
if (Bluetooth_Data== '#')
{
Data_Received = true;
Start_flag = false;
}
}
}
还有红外遥控的代码不想敲了,个人感觉蓝牙遥控更加方便实用。
接下来写一个综合例程:
#define KEYMODE_1 1 // 定义模式1
#define KEYMODE_2 2 // 定义模式2
#define KEYMODE_3 3 // 定义模式3
#define KEYMODE_4 4 // 定义模式4
int keyMode = 1;
/************************************************************************
* @描述: 按键模式切换
* @参数: None
* 功能: 按键子程序
* @返回值: None
**********************************************************************/
void KeyScanTask()
{
static u8 keypre = 0;
if((keypre == 0 ) && !digitalRead(KEY))
{
keypre = 1; //置1,避免持续按下按键时再次进入此函数。
switch(keyMode)
{
case KEYMODE_1:keyMode = KEYMODE_2; break;
case KEYMODE_2:keyMode = KEYMODE_3; break;
case KEYMODE_3:keyMode = KEYMODE_4; break;
case KEYMODE_4:keyMode = KEYMODE_1; break;
default: break;
}
}
if(digitalRead(KEY)) //按键被放开
{
keypre = 0;//置0,允许再次切换LED模式
}
}
/************************************************************************
* @描述: 模式处理函数
* @参数: None
* 功能: 任务程序
* @返回值: None
**********************************************************************/
void LEDTask()
{
switch(keyMode)
{
case KEYMODE_1: digitalWrite(RGB_B,LOW);
digitalWrite(RGB_R,HIGH);
digitalWrite(RGB_G,HIGH);
Infrared_Avoid(200); //红外避障
break;
case KEYMODE_2: digitalWrite(RGB_B,HIGH);
digitalWrite(RGB_R,LOW);
digitalWrite(RGB_G,HIGH);
Find_Tracking(200); //红外寻迹
break;
case KEYMODE_3: digitalWrite(RGB_B,HIGH);
digitalWrite(RGB_R,HIGH);
digitalWrite(RGB_G,LOW);
Bluetooth_Control(); //红外寻迹
break;
case KEYMODE_4: digitalWrite(RGB_B,LOW);
digitalWrite(RGB_R,LOW);
digitalWrite(RGB_G,LOW);
//红外寻迹,此处可以写一个超声波避障的API
break;
default:
break;
}
}
/************************************************************************
* @描述:
* @参数: None
* 功能: loop函数相当于 Arm的main函数
* @返回值: None
**********************************************************************/
void loop()
{
KeyScanTask();
LEDTask();
}