L298N芯片驱动电机
一、控制原理
L298N可以控制两个电机,具体原理为IN1、IN2、IN3、IN4四个输入端口接收控制器发出的电信号,两个输出端分别控制两组直流电机转动。输入端的逻辑控制表如下:
GPIO | GPIO.0 | GPIO.1 | GPIO.2 | GPIO.3 | |
---|---|---|---|---|---|
DC Motor | Motion | IN1 | IN2 | IN3 | IN4 |
M1 | Forward | High | Low | / | / |
M1 | Reverse | Low | High | / | / |
M1 | Stop | Low | Low | / | / |
M2 | Forward | / | / | High | Low |
M2 | Reverse | / | / | Low | High |
M2 | Stop | / | / | Low | Low |
注意:一般选择12V电源供电,但L298N最大工作电压高达46V,电压越大,输出给电机的电压也越大,转速越快。在芯片电压足够大的情况下应考虑电机的最大工作电压。
二、Python代码实现基础驱动
- PWM 控制(命令行输入控制电机转向)
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
IN1 = 12
IN2 = 11
IN3 = 13
IN4 = 15
GPIO.setup(IN1, GPIO.OUT)
GPIO.setup(IN2, GPIO.OUT)
GPIO.setup(IN3, GPIO.OUT)
GPIO.setup(IN4, GPIO.OUT)
p1 = GPIO.PWM(IN1, 50)
P2 = GPIO.PWM(IN28, 50)
P3 = GPIO.PWM(IN3, 50)
p4 = GPIO.PWM(IN4, 50)
def forward(time_sleep,speed):
p1.start(speed)
p2.start(0)
p3.start(speed)
p4.start(0)
time.sleep(time_sleep)
print("forward")
def reverse(time_sleep,speed):
p1.start(0)
p2.start(speed)
p3.start(0)
p4.start(speed)
time.sleep(time_sleep)
print("reverse")
def left(time_sleep,speed): #单轮左转
p1.start(speed)
p2.start(0)
p3.start(0)
p4.start(0)
time.sleep(time_sleep)
print("single-left")
def right(time_sleep,speed):#单轮右转
p1.start(0)
p2.start(0)
p3.start(speed)
p4.start(0)
time.sleep(time_sleep)
print("single-right")
def left_0(time_sleep,speedF,speedR):#双轮左转
p1.start(speedF)
p2.start(0)
p3.start(0)
p4.start(speedR)
time.sleep(time_sleep)
print("double-left")
def right_0(time_sleep,speedF,speedR):
p1.start(0)
p2.start(speedR)
p3.start(speedF)
p4.start(0)
print("double-right")
time.sleep(time_sleep)
def stop(time_sleep):#双轮右转
p1.start(0)
p2.start(0)
p3.start(0)
p4.start(0)
print("stop")
time.sleep(time_sleep)
try:
while True:
cmd = str(input("按以下键后回车(w,前进;x,后退;s,停止):"))
direction = cmd
if direction == "w" or "s" or "a" or "d" or "q" or "e":
if direction == "w": # 前进
forward(1,100)
stop(0.1)
elif direction == "s": # 后退
reverse(1,30)
stop(0.1)
elif direction == "a": # 单轮左转
left(1,50)
stop(0.1)
elif direction == "d": # 单轮右转
right(1,50)
stop(0.1)
elif direction == "q": # 双轮左转
left_0(1,50,50)
stop(0.1)
elif direction == "e": # 双轮右转
right_0(1,50,50)
stop(0.1)
elif direction == "x": # 停止移动
stop(0.1)
else:
print("命令无法识别")
break
except KeyboardInterrupt:
GPIO.cleanup()
代码简化:需要一次性设置多个引脚的输入输出时,可以创建列表,一次性执行setup。
IN1 = 12
IN2 = 11
IN3 = 13
IN4 = 15
pinlist=[IN1,IN2,IN3,IN4]
GPIO.setup(pinlist, GPIO.OUT)
- Robot库主要方法
from gpiozeros import Robot
robot = Robot(left=(18, 17), right=(27, 22)) #传参为控制对应电机IN口接通的GPIO引脚的BCM值
robot.forward
robot.backward
robot.right
robot.left
robot.stop
三、光耦对射传感器+测速码盘实现精确控制
上面的代码只能通过time.sleep()挂起进程,推迟执行后续代码,从而控制电机转动时间。那么如何较为精确地按照圈数控制电机运动?
第一幅图为一个简单的测速码盘,第二幅图为FC-33对射测速传感器。当FC-33模块槽中有遮挡时,输出端口OUT输出高电平;无遮挡时,OUT输出低电平。FC-33有一个指示灯,槽口无遮挡时指示灯亮,有遮挡时灯灭,可用于工作状态检查。
将测速码盘与电机同轴固定,电机工作过程中,码盘透光孔转过对射槽时OUT输出低电平,不透光的部分经过时输出高电平。(转为数字信号后就是一连串010101010101)
将OUT输出端口连接GPIO引脚,设置该GPIO引脚为输入。GPIO.add_event_detect方法对一个引脚添加监听,当引脚输入发生改变时,GPIO.event_detected方法返回TRUE。该方法有三种detect模式,GPIO.RISING是当监听到引脚输入由低电平变为高电平时,返回TRUE;GPIO.FALLING当监听到引脚输入由高电平变为低电平时返回TRUE;注意,GPIO.BOTH是监测到一组电平高→低,低→高时返回TRUE。
这个测速码盘有20个透光孔,所以电机每转一圈会经历20次GPIO.RISING/GPIO.FALLING/GPIO.BOTH,以此为思路写代码。
GPIO_in=40; # BOARD 40引脚
GPIO.setup(ENCODE, GPIO.IN)
GPIO.add_event_detect(GPIO_in,GPIO.RISING)
GPIO.add_event_detect(GPIO_in,GPIO.FALLING)
GPIO.add_event_detect(GPIO_in,GPIO.BOTH)
def revforward(speed):
if(speed>100):
speed=100
p1.start(speed)
p2.start(0)
p3.start(speed)
p4.start(0)
def revstop():
p1.start(0)
p2.start(0)
p3.start(0)
p4.start(0)
while True:
rev=int(input("Enter number of revolution:"))
counter = 0
revforward(50)
GPIO_in=40;
event = GPIO.add_event_detect(GPIO_in,GPIO.BOTH)
while(counter < int(20*rev)):
if(GPIO.event_detected(GPIO_in)):
counter = counter + 1
GPIO.remove_event_detect(GPIO_in)
revstop()
break
四、测速
在之前代码的基础上,如何测出当前电机转速,从而得出小车运行速度?思路很简单,获取两次系统时间,用圈数除以运行时间差值,得到电机转速,再获取小车轮胎直径,即可简单地计算当前运行速度。
我们模拟一个加速过程,PWM值从90到100,每转5圈加速一次(PWM值加1)。
for i in range(90,110,1):
#之前的方法有写,超过100的pwm值设为100,这里只是为了延长最大速度的时间
rev = 5
cnt = 0
revforward(i)
GPIO_in=40;
event = GPIO.add_event_detect(GPIO_in,GPIO.BOTH)
t0 = time.time()
while(cnt< int(20*rev)):
if(GPIO.event_detected(GPIO_in)):
cnt = cnt + 1
t1 = time.time()
print("speed_pwm ",i,"rotate speed ",rev / (t1-t0))
GPIO.remove_event_detect(GPIO_in)
revstop()
break
数据如下图:
用MATLAB绘制图像。
clear all; close all; clc;
rotate_speed=[ ~~~ ]; %省略,数据如上图
rs=rotate_speed.';
t=zeros(1,20);
for i=1:1:20
if i==1
t(1,1)=5/rs(1,1);
end
if i~=1
t(1,i)=5/rs(1,i)+t(1,i-1);
end
end
t=[0,t];
rs=[rs,0];
figure
for i=1:20
plot([t(1,i),t(1,i+1)],[rs(1,i),rs(1,i)],'r','linewidth',2)
hold on
plot([t(1,i+1),t(1,1+i)],[rs(1,i),rs(1,i+1)],'k')
hold on
end
xlabel("time(s)")
ylabel("rotate speed(r/s)")
可以很明显的看到,在PWM值到100之后,转速数据是有问题的。这时候转速本应稳定维持一个较大的数值,实际却在一段突变后稳定维持在15附近。第一反应是电机在快要停止时会有减速过程,会拖慢那一段的平均转速,但是不应该有这么长时间的误差。其次想到的是光耦对射传感器分辨率不足。
为了验证猜测,又进行了第二组试验,一直以PWM=100的转速运行,每转20圈求取一次平均转速。数据如下:
同样方法用MATLAB绘图。发现这次的转速稳定在了17左右。几乎可以认定是传感器分辨率不足的原因。
P.S. 本次实验由于一些客观因素存在,误差可能比较大,又由于一些奇奇怪怪的意外(传感器失踪),现在实验无法继续进行,后续可能会更新数据。
五、Further Questions
- 两侧电机的传感器数据很可能不一致,如何同步?
- More and more precise experiments to verify whether the problem is caused because of low-resolution of sensor?
- Resolution of sensor can’t be find on Internet.