电机驱动模块在电子制作中会经常用到,下面以L298N型号的模块为例介绍其使用方法。模块引脚说明如下:
电机驱动板1块
零知-标准板
杜邦线若干
直流减速电机TT马达2个,带车轮(便于实验观察)
我们把零知标准板和电机驱动板相连接,驱动2路电机工作。接线图如下:
引脚连接:
最后我们把电机驱动板的12v接到一个9v的电源上(如干电池组),然后GND连接到零知标准板的地。
根据上述连接线后,我们使用程序来控制电机的运行:
/**
* 文件: 电机驱动.ino by 零知实验室([url=http://www.lingzhilab.com]www.lingzhilab.com[/url])
* -^^- 零知开源,让电子制作变得更简单! -^^-
* 时间: 2018/10/13 15:01
* 说明:
**/
//控制命令
#define STOP 0
#define FORWARD 1
#define BACKWARD 2
#define TURNLEFT 3
#define TURNRIGHT 4
//电机控制引脚
#define LEFT_MOTOR1 2
#define LEFT_MOTOR2 3
#define RIGHT_MOTOR1 4
#define RIGHT_MOTOR2 5
#define LEFT_ENA 11
#define RIGHT_ENB 12
int LED_STATE = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(LEFT_MOTOR1, OUTPUT);
pinMode(LEFT_MOTOR2, OUTPUT);
pinMode(RIGHT_MOTOR1, OUTPUT);
pinMode(RIGHT_MOTOR2, OUTPUT);
pinMode(LEFT_ENA,OUTPUT);
pinMode(RIGHT_ENB,OUTPUT);
motor_run(STOP);
}
void loop() {
// put your main code here, to run repeatedly:
int cmd = 1;
motor_run(cmd);
delay(1000);
motor_run(0);
}
引脚模式设置:
void setup() {
//引脚模式设置
pinMode(LED_BUILTIN, OUTPUT);//板载LED
Serial.begin(9600);//用于调试
pinMode(LEFT_MOTOR1, OUTPUT);
pinMode(LEFT_MOTOR2, OUTPUT);
pinMode(RIGHT_MOTOR1, OUTPUT);
pinMode(RIGHT_MOTOR2, OUTPUT);
pinMode(LEFT_ENA,OUTPUT);
pinMode(RIGHT_ENB,OUTPUT);
pinMode(SR04_TRIG, OUTPUT);
pinMode(SR04_ECHO, INPUT);
myServo.attach(SERVO_PWM);
pinMode(LINE1,INPUT);
pinMode(LINE2,INPUT);
pinMode(LINE3,INPUT);
pinMode(LINE4,INPUT);
BLE_SERIAL.begin(9600);
BLE_SERIAL.println("AT+NAMELingzhiBleCar");//设置蓝牙设备名称
}
电机驱动函数接口
该函数接口实现根据不同命令进行电机的动作,包含:前进、后退、左转、右转和停止功能。具体代码如下:
void motor_run(int cmd){
switch(cmd)
{
case FORWARD:
#ifdef DEBUG_PRINT
Serial.println("向前");
#endif
analogWrite(LEFT_ENA,highSpeed);
digitalWrite(LEFT_MOTOR1, HIGH);
digitalWrite(LEFT_MOTOR2, LOW);
analogWrite(RIGHT_ENB,highSpeed);
digitalWrite(RIGHT_MOTOR1, HIGH);
digitalWrite(RIGHT_MOTOR2, LOW);
break;
case BACKWARD:
#ifdef DEBUG_PRINT
Serial.println("向后");
#endif
digitalWrite(LEFT_MOTOR1, LOW);
digitalWrite(LEFT_MOTOR2, HIGH);
digitalWrite(RIGHT_MOTOR1, LOW);
digitalWrite(RIGHT_MOTOR2, HIGH);
break;
case TURNLEFT:
#ifdef DEBUG_PRINT
Serial.println("左转");
#endif
analogWrite(LEFT_ENA,lowSpeed);
analogWrite(RIGHT_ENB,highSpeed);
digitalWrite(LEFT_MOTOR1, HIGH);
digitalWrite(LEFT_MOTOR2, LOW);//C
digitalWrite(RIGHT_MOTOR1, HIGH);
digitalWrite(RIGHT_MOTOR2, LOW);
break;
case TURNRIGHT:
#ifdef DEBUG_PRINT
Serial.println("右转");
#endif
analogWrite(LEFT_ENA,highSpeed);
analogWrite(RIGHT_ENB,lowSpeed);
digitalWrite(LEFT_MOTOR1, HIGH);
digitalWrite(LEFT_MOTOR2, LOW);
digitalWrite(RIGHT_MOTOR1, HIGH);//C
digitalWrite(RIGHT_MOTOR2, LOW);
break;
case STOP:
default:
#ifdef DEBUG_PRINT
Serial.println("停止");
#endif
digitalWrite(LEFT_ENA,1);
digitalWrite(RIGHT_ENB,1);
digitalWrite(LEFT_MOTOR1, LOW);
digitalWrite(LEFT_MOTOR2, LOW);
digitalWrite(RIGHT_MOTOR1, LOW);
digitalWrite(RIGHT_MOTOR2, LOW);
break;
}
}
然后在loop()函数中调用,进行调试:
void loop() {
if(Serial.available()>0)
{
Serial.print("cmd:");
int cmd = Serial.read() - 48;
Serial.println(cmd);
motor_run(cmd);
}
}
调试时根据小车实际运行的方向进行参数的调试,直到实际运行的方向与预定方向一致。
超声避障该功能先调试在固定方向时的距离测量,具体代码如下:
int getInstance()
{
digitalWrite(SR04_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(SR04_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(SR04_TRIG, LOW);
int distance = pulseIn(SR04_ECHO, HIGH);
distance = distance/58.0;
return distance;
}
现在我们再结合舵机,在前方、左边、右边方向上不断探测小车前面的障碍物距离,并且在障碍物距离为70cm时候按照当前方向进行调整,实现自动避障。具体实现如下:
void avoidance(int avoidDistance)
{
int i = 0;
int time = 30;//舵机速度
int disFor = 0;//探测的距离
int disLeft = 0;
int disRight = 0;
int d = 70;
//首先探测正前方
myServo.write(d);
for(i=0;i<5;i++)
{
disFor += getDistance();
delay(50);
}
disFor = disFor/5;
#ifdef DEBUG_PRINT
Serial.print(" 前方距离:");
Serial.println(disFor);
#endif
if(disFor <= avoidDistance)//正前方有障碍物
{
//停车
motor_run(0);
int t = 0;
int total = 0;
//探测右边
for(i=d; i>0;i--)
{
myServo.write(i);
delay(time);
}
for(i=0;i<5;i++)
{
disRight += getDistance();
delay(50);
}
disRight = disRight/5;
#ifdef DEBUG_PRINT
Serial.print(" 右侧距离:");
Serial.println(disRight);
#endif
myServo.write(d);
//探测左边
for(i=d; i<170;i++)
{
myServo.write(i);
delay(time);
}
for(i=0;i<5;i++)
{
disLeft += getDistance();
delay(50);
}
disLeft = disLeft/5;
#ifdef DEBUG_PRINT
Serial.print(" 左侧距离:");
Serial.println(disLeft);
#endif
myServo.write(d);
int rollTime = 1000;
//两边都有障碍物,掉头
if((disLeft <= avoidDistance) && (disRight <= avoidDistance))
{
motor_run(3);
Serial.println("--掉头");
delay(rollTime*2);
}
else if(disLeft >= disRight)
{
motor_run(3);//左转
delay(rollTime);
}
else if(disLeft < disRight)
{
motor_run(4);//右转
delay(rollTime);
}
motor_run(0);//停止
}else{
motor_run(1);//无障碍,前进
}
//delay(300);
}
程序中的有些参数需根据自己实际调试时的情况进行调整。在loop中调用该函数,把小车放在一个有很多隔板的场地,然后就可以观察到小车会根据前方障碍物的情况自动避障。
自动巡线自动巡线是根据红外探测黑白区域实现,这里是在黑色区域上沿着白线前进,遇到一方出白线时自动调整方向以便一直沿着白线前进,知道遇到两边检测到都是黑色,结束。代码如下:
void lineTrace()
{
int IR_1 = digitalRead(LINE1);
int IR_2 = digitalRead(LINE2);
if(IR_1 == 0 && IR_2 == 0)//在白色线上
{
motor_run2(1);//前进
}
if(IR_1 == 1 && IR_2 == 0)//Line1 出白线
{
motor_run2(3);//左转
}
if(IR_1 == 0 && IR_2 == 1)//Line2 出白线
{
motor_run2(4);//右转
}
}
由于这里在拐弯时需要尽量短时间完成,因此把电机驱动改为如下motor_run2();如下:
//拐弯时候一个轮子动,一个不懂,更快拐弯
void motor_run2(int cmd)
{
int low = 90;
int high = 120;
switch(cmd)
{
case FORWARD:
#ifdef DEBUG_PRINT
Serial.println("向前");
#endif
analogWrite(LEFT_ENA,low);
analogWrite(RIGHT_ENB,low);
digitalWrite(LEFT_MOTOR1, HIGH);
digitalWrite(LEFT_MOTOR2, LOW);
digitalWrite(RIGHT_MOTOR1, HIGH);
digitalWrite(RIGHT_MOTOR2, LOW);
break;
case BACKWARD:
#ifdef DEBUG_PRINT
Serial.println("向后");
#endif
digitalWrite(LEFT_MOTOR1, LOW);
digitalWrite(LEFT_MOTOR2, HIGH);
digitalWrite(RIGHT_MOTOR1, LOW);
digitalWrite(RIGHT_MOTOR2, HIGH);
break;
case TURNLEFT:
#ifdef DEBUG_PRINT
Serial.println("左转");
#endif
analogWrite(LEFT_ENA,low);
analogWrite(RIGHT_ENB,low);
digitalWrite(LEFT_MOTOR1, LOW);
digitalWrite(LEFT_MOTOR2, LOW);//C
digitalWrite(RIGHT_MOTOR1, HIGH);
digitalWrite(RIGHT_MOTOR2, LOW);
break;
case TURNRIGHT:
#ifdef DEBUG_PRINT
Serial.println("右转");
#endif
analogWrite(LEFT_ENA,low);
analogWrite(RIGHT_ENB,low);
digitalWrite(LEFT_MOTOR1, HIGH);
digitalWrite(LEFT_MOTOR2, LOW);
digitalWrite(RIGHT_MOTOR1, LOW);
digitalWrite(RIGHT_MOTOR2, LOW);
break;
case STOP:
default:
#ifdef DEBUG_PRINT
Serial.println("停止");
#endif
digitalWrite(LEFT_ENA,1);
digitalWrite(RIGHT_ENB,1);
digitalWrite(LEFT_MOTOR1, LOW);
digitalWrite(LEFT_MOTOR2, LOW);
digitalWrite(RIGHT_MOTOR1, LOW);
digitalWrite(RIGHT_MOTOR2, LOW);
break;
}
}
蓝牙控制
蓝牙模块与手机APP进行通信,接收到APP发来的信息后根据命令进行动作:
void bleControl()
{
if(Serial1.available()>0){
char x = Serial1.read();
int cmd = (int)(x);//字符转数字
Serial.print("recv cmd:");
Serial.println((int)(x));
motor_run(cmd);
//current_dir = cmd;
}
delay(500);
}
把电机驱动的操作作为一个函数motor_run(int cmd);如下:
/**
* 根据命令点击进行相应动作
* cmd 命令
*/
int highSpeed = 200;
int lowSpeed = 100;
void motor_run(int cmd)
{
switch(cmd)
{
case FORWARD:
Serial.println("FORWARD"); //输出状态
analogWrite(LEFT_ENA,highSpeed);
digitalWrite(LEFT_MOTOR1, LOW);
digitalWrite(LEFT_MOTOR2, HIGH);
analogWrite(RIGHT_ENB,highSpeed);
digitalWrite(RIGHT_MOTOR1, LOW);
digitalWrite(RIGHT_MOTOR2, HIGH);
break;
case BACKWARD:
Serial.println("BACKWARD"); //输出状态
digitalWrite(LEFT_MOTOR1, HIGH);
digitalWrite(LEFT_MOTOR2, LOW);
digitalWrite(RIGHT_MOTOR1, HIGH);
digitalWrite(RIGHT_MOTOR2, LOW);
break;
case TURNLEFT:
Serial.println("TURN LEFT"); //输出状态
analogWrite(LEFT_ENA,lowSpeed);
analogWrite(RIGHT_ENB,highSpeed);
digitalWrite(LEFT_MOTOR1, LOW);
digitalWrite(LEFT_MOTOR2, HIGH);//C
digitalWrite(RIGHT_MOTOR1, LOW);
digitalWrite(RIGHT_MOTOR2, HIGH);
break;
case TURNRIGHT:
Serial.println("TURN RIGHT"); //输出状态
analogWrite(LEFT_ENA,highSpeed);
analogWrite(RIGHT_ENB,lowSpeed);
digitalWrite(LEFT_MOTOR1, LOW);
digitalWrite(LEFT_MOTOR2, HIGH);
digitalWrite(RIGHT_MOTOR1, LOW);//C
digitalWrite(RIGHT_MOTOR2, HIGH);
break;
case STOP:
default:
Serial.println("STOP"); //输出状态
digitalWrite(LEFT_ENA,1);
digitalWrite(RIGHT_ENB,1);
digitalWrite(LEFT_MOTOR1, LOW);
digitalWrite(LEFT_MOTOR2, LOW);
digitalWrite(RIGHT_MOTOR1, LOW);
digitalWrite(RIGHT_MOTOR2, LOW);
break;
}
}
我们把程序上传到零知-标准板上,调整程序中cmd的值,看看效果吧
完整工程:电机驱动.7z(点击下载)