在开发树莓派小车时,需要使用树莓派来控制直流电机,树莓派无法直接驱动直流电机,需要使用直流电机驱动芯片,最常见的有L298N,比如一般的TT马达用这个驱动就足够,但是如果是大功率电机,它的发热量和死区都比较大,因为我买的小车是大功率直流电机,所以使用的是TB6612FNG驱动芯片,它是东芝的芯片,能驱动两个直流电机,或者并联四个直流电机(左前和左后并联,右前和右后并联)。本文介绍如何使用TB6612FNG来驱动直流电机。
TB6612FNG驱动芯片
TB6612FNG驱动芯片结构如下图:
▲ TB6612FNG结构图
我买回来,实物是这样的,体积很小。
▲ 装车图
针脚很多, 对照TB6612FNG结构图,各针脚功能如下。
左边针脚 | 功能 | 右边针脚 | 功能 |
PWMA | A电机控制信号输入 | VM | 电机驱动电压输入端(4.5-15V) |
AIN2 | A电机输入端2 | VCC | 逻辑电平输入端(2.7-5.5V) |
AIN1 | A电机输入端1 | GND | 接地 |
STBY | 正常工作/待机 状态控制端 | AO1 | A电机输出端1 |
BIN1 | B电机输入端1 | AO2 | A电机输出端2 |
BIN2 | B电机输入端2 | BO2 | B电机输出端2 |
PWMB | B电机控制信号输入 | BO1 | B电机输出端1 |
GND | 接地 | GND | 接地 |
说明一下:
左边PWMA,PWMB,是通过PWM来控制A,B电机的转速,0停止,1最大转速。左边的接口全部要接树莓派的GPIO接口,STBY,用来控制AB两个电机的待机和工作,输入高电压1表示工作,0表示待机。
右边的VM接外接电源,用来直接给电机驱动电压,我这里买的是12V, 3A的18650锂电池,也不贵。这里需要注意的是,因为我买的是大功率电机,型号为:JGB37-520 DC12V200RPM 电机,所以最好用12V电压,这样转的快。
▲ 12V,18650充电电池,DC充电头可以用面包板电源充电,外接DC转接头可以引出正负极
最好是电机跟树莓派的电源分开。VM接到树莓派的5V接口上,树莓派的GND接口跟电机的驱动电源一起接地,这里可以通过面包板实现共同接地。
AIO1, AIO2, BIO1,BIO2,分别接电机的正负极,这里需要调试一下找到电机的正负极。方法为,直接将电机的正负极,接到电机驱动电源的正负极上,观察电机的转动方向。记下正负极,正极接到AIO1,BIO1上,负极接到AIO2,BIO2上。
以上操作在树莓派断电下进行,同时在操作电机和驱动电源的时候,注意不到将杜邦线碰到树莓派电路板。
控制电机的转速,可以通过PWMA和PWMB的值来控制A,B电机的转速。通过STBY的高低值,来控制待机和工作,通过AIO1,AIO2的高低值,来控制正反转。
▲ 通过树莓派GPIO接口,来输入不同的值来控制转速和运转方向
少废话,看代码
写代码之前,先要布线,将电机驱动电源,树莓派,驱动电机,通过杜邦线连起来。
▲ 接线图及说明
在.NET Core IoT的GitHub的源码里有DCMotor相关代码,我这里拿过来,首先是一个DCMotor抽象类,表示一个电机,因为电机类型有2针(只有IO1,IO2),3针的(除IO1和IO2之外,还有PWM控制转速):
/// <summary>
/// Direct current (DC) motor
/// </summary>
public abstract class DCMotor : IDisposable
{
private const int DefaultPwmFrequency = 50;
private bool _shouldDispose;
/// <summary>
/// Constructs generic <see cref="DCMotor"/> instance
/// </summary>
/// <param name="controller"><see cref="GpioController"/> related with operations on pins</param>
/// <param name="shouldDispose">True to dispose the Gpio Controller</param>
protected DCMotor(GpioController controller, bool shouldDispose)
{
_shouldDispose = shouldDispose;
Controller = controller;
}
public abstract void Break();
/// <summary>
/// Gets or sets the speed of the motor. Range is -1..1 or 0..1 for 1-pin connection.
/// 1 means maximum speed, 0 means no movement and -1 means movement in opposite direction.
/// </summary>
public abstract double Speed { get; set; }
/// <summary>
/// <see cref="GpioController"/> related with operations on pins
/// </summary>
protected GpioController Controller
{
get;
set;
}
/// <summary>
/// Disposes the <see cref="DCMotor"/> class
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases the resources used by the <see cref="DCMotor"/> instance.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_shouldDispose)
{
Controller?.Dispose();
Controller = null;
}
}
}
/// <summary>
/// Creates <see cref="DCMotor"/> instance which allows to control speed in both directions.
/// </summary>
/// <param name="speedControlChannel"><see cref="PwmChannel"/> used to control the speed of the motor</param>
/// <param name="directionPin">First pin used to control the direction of the motor</param>
/// <param name="otherDirectionPin">Second pin used to control the direction of the motor</param>
/// <param name="controller"><see cref="GpioController"/> related to <paramref name="directionPin"/> and <paramref name="otherDirectionPin"/></param>
/// <param name="shouldDispose">True to dispose the Gpio Controller</param>
/// <returns><see cref="DCMotor"/> instance</returns>
/// <remarks>
/// When speed is non-zero the value of <paramref name="otherDirectionPin"/> will always be opposite to that of <paramref name="directionPin"/>.
/// <paramref name="speedControlChannel"/> should be connected to enable pin of the H-bridge.
/// <paramref name="pinIn1"/> should be connected to H-bridge input corresponding to one of the motor inputs.
/// <paramref name="pinIn2"/> should be connected to H-bridge input corresponding to the remaining motor input.
/// Connecting motor directly to GPIO pin is not recommended and may damage your board.
/// </remarks>
public static DCMotor Create(PwmChannel speedControlChannel, int pinIn1, int pinIn2, GpioController controller = null, bool shouldDispose = true)
{
if (speedControlChannel == null)
{
throw new ArgumentNullException(nameof(speedControlChannel));
}
if (pinIn1 == -1)
{
throw new ArgumentOutOfRangeException(nameof(pinIn1));
}
if (pinIn2 == -1)
{
throw new ArgumentOutOfRangeException(nameof(pinIn2));
}
return new DCMotor3Pin(
speedControlChannel,
pinIn1,
pinIn2,
controller,
shouldDispose);
}
/// <summary>
/// Creates <see cref="DCMotor"/> instance which allows to control speed in both directions.
/// </summary>
/// <param name="speedControlPin">Pin used to control the speed of the motor with software PWM (frequency will default to 50Hz)</param>
/// <param name="directionPin">First pin used to control the direction of the motor</param>
/// <param name="otherDirectionPin">Second pin used to control the direction of the motor</param>
/// <param name="controller"><see cref="GpioController"/> related to <paramref name="speedControlPin"/>, <paramref name="directionPin"/> and <paramref name="otherDirectionPin"/></param>
/// <param name="shouldDispose">True to dispose the Gpio Controller</param>
/// <returns><see cref="DCMotor"/> instance</returns>
/// <remarks>
/// When speed is non-zero the value of <paramref name="pinIn2"/> will always be opposite to that of <paramref name="pinIn1"/>
/// PWM pin <paramref name="speedControlPin"/> should be connected to enable pin of the H-bridge.
/// <paramref name="pinIn1"/> should be connected to H-bridge input corresponding to one of the motor inputs.
/// <paramref name="pinIn2"/> should be connected to H-bridge input corresponding to the remaining motor input.
/// Connecting motor directly to GPIO pin is not recommended and may damage your board.
/// </remarks>
public static DCMotor Create(int speedControlPin, int pinIn1, int pinIn2, GpioController controller = null, bool shouldDispose = true)
{
if (speedControlPin == -1)
{
throw new ArgumentOutOfRangeException(nameof(speedControlPin));
}
if (pinIn1 == -1)
{
throw new ArgumentOutOfRangeException(nameof(pinIn1));
}
if (pinIn2 == -1)
{
throw new ArgumentOutOfRangeException(nameof(pinIn2));
}
controller = controller ?? new GpioController();
return new DCMotor3Pin(
new SoftwarePwmChannel(speedControlPin, DefaultPwmFrequency, 0.0, controller: controller),
pinIn1,
pinIn2,
controller,
shouldDispose);
}
}
因为我们是三针电机,所以把DCMotor3Pin弄过来:
internal class DCMotor3Pin : DCMotor
{
private PwmChannel _pwm;
private int _pinIN1;
private int _pinIN2;
private double _speed;
public DCMotor3Pin(
PwmChannel pwmChannel,
int pinIn1,
int pinIn2,
GpioController controller,
bool shouldDispose)
: base(controller ?? new GpioController(), controller == null ? true : shouldDispose)
{
if (pwmChannel == null)
{
throw new ArgumentNullException(nameof(pwmChannel));
}
_pwm = pwmChannel;
_pinIN1 = pinIn1;
_pinIN2 = pinIn2;
_speed = 0;
_pwm.Start();
Controller.OpenPin(_pinIN1, PinMode.Output);
Controller.Write(_pinIN1, PinValue.Low);
Controller.OpenPin(_pinIN2, PinMode.Output);
Controller.Write(_pinIN2, PinValue.Low);
}
/// <summary>
/// Gets or sets the speed of the motor.
/// Speed is a value from -1 to 1.
/// 1 means maximum speed, signed value changes the direction.
/// </summary>
public override double Speed
{
get
{
return _speed;
}
set
{
double val = Math.Clamp(value, -1.0, 1.0);
if (_speed == val)
{
return;
}
if (val == 0.0)
{
Controller.Write(_pinIN1, PinValue.Low);
Controller.Write(_pinIN2, PinValue.Low);
}
else if (val > 0.0)
{
Controller.Write(_pinIN1, PinValue.High);
Controller.Write(_pinIN2, PinValue.Low);
}
else
{
Controller.Write(_pinIN1, PinValue.Low);
Controller.Write(_pinIN2, PinValue.High);
}
_pwm.DutyCycle = Math.Abs(val);
_speed = val;
}
}
public override void Break()
{
_pwm.DutyCycle = 0;
Controller.Write(_pinIN1, PinValue.High);
Controller.Write(_pinIN2, PinValue.High);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_speed = 0.0;
_pwm?.Dispose();
_pwm = null;
}
base.Dispose(disposing);
}
}
可以看到,通过设置Speed大于小于等于0来,设置IO1和IO2的值,进而来控制正转反转和停止,通过pwm的大小,来控制速度。
以上代表单个电机,要控制小车的前进后退,我们需要两个电机,所以,新建一个DCCar,表示一辆小车,代码如下:
public class DCCar : IDisposable
{
private int standbyGpio;
private DCMotor leftMotor;
private DCMotor rightMotor;
GpioController controller;
public DCCar(int standBy, int pmwA, int aIn1, int aIn2, int pmwb, int bIn1, int bIn2)
{
standbyGpio = standBy;
leftMotor = DCMotor.Create(pmwA, aIn1, aIn2);
rightMotor = DCMotor.Create(pmwb, bIn1, bIn2);
controller = new GpioController(PinNumberingScheme.Logical);
//make stand by stop
controller.OpenPin(standbyGpio, PinMode.Output);
}
public void MoveForward(double speed)
{
double s = Math.Clamp(Math.Abs(speed), 0, 1);
leftMotor.Speed = s;
rightMotor.Speed = s;
controller.Write(standbyGpio, PinValue.High);
}
public void MoveBack(double speed)
{
double s = Math.Clamp(Math.Abs(speed), 0, 1);
leftMotor.Speed = -s;
rightMotor.Speed = -s;
controller.Write(standbyGpio, PinValue.High);
}
public void TurnLeft(double speed)
{
double s = Math.Clamp(Math.Abs(speed), 0, 1);
leftMotor.Speed = -s;
rightMotor.Speed = s;
controller.Write(standbyGpio, PinValue.High);
}
public void TurnRight(double speed)
{
double s = Math.Clamp(Math.Abs(speed), 0, 1);
leftMotor.Speed = s;
rightMotor.Speed = -s;
controller.Write(standbyGpio, PinValue.High);
}
public void Stop()
{
leftMotor.Break();
rightMotor.Break();
controller.Write(standbyGpio, PinValue.Low);
}
public void Dispose()
{
Stop();
leftMotor?.Dispose();
leftMotor = null;
rightMotor?.Dispose();
rightMotor = null;
controller?.Dispose();
controller = null;
}
}
可以看到,左右转是通过控制左右两个电机的正反转来实现的。
现在,在Main函数里,来实例化DCCar,并通过一定的延时来控制前进,后退,左转右转。
static void TestCar()
{
using (DCCar car = new DCCar(20, 23, 12, 16, 18, 13, 25))
{
while (true)
{
Console.WriteLine("move forward");
DoAction(car.MoveForward);
Thread.Sleep(1250);
Console.WriteLine("move back");
DoAction(car.MoveBack);
Thread.Sleep(1250);
Console.WriteLine("turn left");
DoAction(car.TurnLeft);
Thread.Sleep(1250);
Console.WriteLine("turn right");
DoAction(car.TurnRight);
Thread.Sleep(1250);
car.Stop();
Thread.Sleep(1250);
}
}
}
static void DoAction(Action<double> a)
{
for (double i = 0.1; i <= 1; i += 0.1)
{
a(i);
Thread.Sleep(1250);
}
}
发布,部署,运行,如果一切正常,就会看到,小车会先前进,大约1250毫秒后后退,然后左转,然后右转,然后停止,然后又再次循环。
能驱动DC马达,就是制作小车的第一步,本文讲解了如何使用TB6612FNG来驱动大功率电机,如果是TT马达,则可以用用L298N,这里就不展示了,希望给大家带来帮助。
满满的正能量~