1、简介
智能小车、机械臂、摄像头云台会有多个舵机,而微控制器芯片的PWM输出引脚不够的情况下,就可以用PCA9685(16路舵机)来解决这一问题。
PCA9685是一款I2C总线控制的16通道LED控制器,专为红/绿/蓝/琥珀(RGBA)彩色背光应用而优化。每个LED输出都有自己的12位分辨率(4096步)固定频率单独PWM控制器,可在可编程频率下工作,典型频率为24 Hz到1526 Hz,占空比可调节从0%到100%,以使LED达到特定的亮度值。所有输出的PWM频率都设置为相同。
每个LED输出可以关闭或打开(无PWM控制),或设置为其单独的PWM控制器值。LED输出驱动器被编程为开漏,带有在5V时25mA的电流下沉能力,或者是totem极,带有在5V时25mA的下沉和10mA的源能力。PCA9685的工作电压范围为2.3V至5.5V,输入和输出均可容忍5.5V。LED可以直接连接到LED输出(高达25mA,5.5V),或通过外部驱动器和最少量的离散元件控制更大电流或更高电压的LED。
2、驱动板特性
• PCA9685允许交替的LED输出开启和关闭时间以最小化电流浪涌。每个16个通道的开启和关闭时间延迟可以独立编程。这个特性在PCA9635中不可用。
• PCA9685具有4096个步骤(12位PWM)的单独LED亮度控制。
• 当在系统中合并多个LED控制器时,如果使用PCA9635,多个设备之间的PWM脉冲宽度可能会有所不同。PCA9685具有可编程分频器,以调整多个设备的PWM脉冲宽度。
• PCA9685具有外部时钟输入引脚,可接受用户提供的时钟(最大50 MHz)代替内部25 MHz振荡器。此功能允许多个设备同步。PCA9635没有外部时钟输入功能。
• 与PCA9635一样,PCA9685也具有用于PWM控制的内置振荡器。但是,与PCA9635的典型频率97.6kHz相比,PCA9685用于PWM控制的频率可调节约为24 Hz至1526 Hz。这允许使用PCA9685与外部电源控制器。所有位都以相同的频率设置。
• LEDn输出引脚的上电复位(POR)默认状态在PCA9685的情况下为LOW。而对于PCA9635,则为HIGH。
• 所有PWM输出线上都放一个220欧姆系列电阻器来保护他们,并能轻易的驱动LED。
3、多驱动板级联
地址选择引脚使你可以把62个驱动板挂在单个12C总线上,总共有992路PWM输出,级联的每个驱动板都需要有一个唯一的访问地址。每个驱动板的初始I2C地址是0×40,可以通过右上角的跳线修改I2C地址。用焊锡将一个跳线连上就表示一个二进制数字"1"。有6个地址控制脚,通过这些引脚可以控制设备的i2c地址。7位的I2C地址为:0x40 + A5:A0
Board 0: Address = 0x40 Offset = binary 00000 (默认)
Board 1: Address = 0x41 Offset = binary 00001 (如上图,接上A0)
Board 2: Address = 0x42 Offset = binary 00010 (接上A1)
Board 3: Address = 0x43 Offset = binary 00011 (接上A0和A1)
Board 4: Address = 0x44 Offset = binary 00100 (接上A2)
4、外部电源接入
大多数的舵机设计电压都是在5~6V,尤其在多个舵机同时运行时,跟需要有大功率的电源供电。如果直接使用5V引脚直接为舵机供电,会出现一些难以预测的问题,所以建议你能有个合适的外部电源为驱动板供电。中间上面的绿色接线柱,是提供外围输入电压,舵机后面提示外围输入供电电压不要超过6V。V+线上放置一个大电容(在某些场合你会需要) 外围输入最大电压取决于这个10V1000uf的电容。
PCA9685-16路舵机是一个采用IIC通信,内置了PWM驱动器和一个时钟,这个意味着,这将和TLCG940系列有很大不同,你不需要不断发送信号占用你的单片机。它是5V的兼容,这意味你还可以用3.3V单片机控制并且安全地驱动到6V输出(当你想要控制白色或蓝色指示灯用3.4+正电压也是可以的)。约1.6Khz可调频PWM输出,为步进电机准备输出12位分辨率,可配置的推拉输出或开路输出,输出使能引脚能够快速禁用所有输出。
这个地方说明书上的意思我重新给同学们解释下。如果不仔细思考理解,可能很多同学有点晕。
VCC和V+这两个地方是有区别的。VCC是给PCA9685芯片供电的电压3-5V。而V+是给舵机供电的电压。那么问题来了,1是扩展板好几个地方都有V+,哪里是输入哪里是输出?2是V+的那个一会最大6V,一会6-10V是什么意思呢?
扩展板要给舵机供电,首先要有电压输入,如果仅接VCC,无法提供输出电压。要提供输出电压必须接上左图的绿色接线柱,这个输入电压范围为6-10V,然后V+的所有输出电压就有了。这个输出的电压都是来自绿色接线柱的输入电压,输入多少输出就是多少,但是为了保护舵机,扩展板提供稳压保护,即便输入的电压有9V,它输出的电压最大不超过6.5V,因为大多数舵机的工作电压5-6V。所以如果要带的舵机额定电压需要6.5V以上,就单独供电,把PWM信号线接扩展板即可。
这里我再扩展下,介绍输出电压、电流与负载的关系。这里我可以拿手机电池和手机充电器给大家举例。如果手机电池的的输入电压为5V,那么充电器的输出电压一定要5V才行不能大也不能小。如果输入电压大了不烧坏手机说明内部有保护的稳压电路,小了更不行,芯片不能工作。而充电器提供的输出电流则越大越好,当然价格也高,这个完全看负载所需的电流,只要电池所需要的输入电流小于等于充电器提供的输出电流都可以。但是要是充电器输出电流小于电池需要的输入电流则不行,它有一个阈值,低于阈值充不进去电,高于阈值则充电比较慢,也能充。舵机也是一样,所以最好是满足负载所需的电压和电流才行。
我们也做了很多实验了,树莓派或其他芯片提供的输出电压往往可以满足负载,但输出电流较弱,驱动大电流的设备往往需要单独供电。
5、电路原理图
6、PCA9685的驱动
该模块仍然采用IIC总线,可以查看其地址为0x40。
python
from __future__ import division
import logging
import time
import math
# Registers/etc:
PCA9685_ADDRESS = 0x40
MODE1 = 0x00
MODE2 = 0x01
SUBADR1 = 0x02
SUBADR2 = 0x03
SUBADR3 = 0x04
PRESCALE = 0xFE
LED0_ON_L = 0x06
LED0_ON_H = 0x07
LED0_OFF_L = 0x08
LED0_OFF_H = 0x09
ALL_LED_ON_L = 0xFA
ALL_LED_ON_H = 0xFB
ALL_LED_OFF_L = 0xFC
ALL_LED_OFF_H = 0xFD
# Bits:
RESTART = 0x80
SLEEP = 0x10
ALLCALL = 0x01
INVRT = 0x10
OUTDRV = 0x04
logger = logging.getLogger(__name__)
class PCA9685(object):
"""PCA9685 PWM LED/servo controller."""
def __init__(self, address=PCA9685_ADDRESS, i2c=None, **kwargs):
"""Initialize the PCA9685."""
# Setup I2C interface for the device.
if i2c is None:
import Adafruit_GPIO.I2C as I2C
i2c = I2C
self._device = i2c.get_i2c_device(address, **kwargs)
self.set_all_pwm(0, 0)
self._device.write8(MODE2, OUTDRV)
self._device.write8(MODE1, ALLCALL)
time.sleep(0.005) # wait for oscillator
mode1 = self._device.readU8(MODE1)
mode1 = mode1 & ~SLEEP # wake up (reset sleep)
self._device.write8(MODE1, mode1)
time.sleep(0.005) # wait for oscillator
def set_pwm_freq(self, freq_hz):
"""Set the PWM frequency to the provided value in hertz."""
prescaleval = 25000000.0 # 25MHz
prescaleval /= 4096.0 # 12-bit
prescaleval /= float(freq_hz)
prescaleval -= 1.0
logger.debug('Setting PWM frequency to {0} Hz'.format(freq_hz))
logger.debug('Estimated pre-scale: {0}'.format(prescaleval))
prescale = int(math.floor(prescaleval + 0.5))
logger.debug('Final pre-scale: {0}'.format(prescale))
oldmode = self._device.readU8(MODE1);
newmode = (oldmode & 0x7F) | 0x10 # sleep
self._device.write8(MODE1, newmode) # go to sleep
self._device.write8(PRESCALE, prescale)
self._device.write8(MODE1, oldmode)
time.sleep(0.005)
self._device.write8(MODE1, oldmode | 0x80)
def set_pwm(self, channel, on, off):
"""Sets a single PWM channel."""
self._device.write8(LED0_ON_L+4*channel, on & 0xFF)
self._device.write8(LED0_ON_H+4*channel, on >> 8)
self._device.write8(LED0_OFF_L+4*channel, off & 0xFF)
self._device.write8(LED0_OFF_H+4*channel, off >> 8)
def set_all_pwm(self, on, off):
"""Sets all PWM channels."""
self._device.write8(ALL_LED_ON_L, on & 0xFF)
self._device.write8(ALL_LED_ON_H, on >> 8)
self._device.write8(ALL_LED_OFF_L, off & 0xFF)
self._device.write8(ALL_LED_OFF_H, off >> 8)
一共这个舵机控制类就4个函数,init为初始化函数,set_pwm_freq设置频率,set_pwm控制单个舵机,set_all_pwm控制全部舵机。
7、实验代码与现象
(1)我手头没有多个舵机,先用多个LED流水灯做实验,多个舵机的实验代码我也放到最后,回头拿到机械臂之后,我再想办法连接到树莓派上面。因为这个PCA9685的输出最大电压为6.5V。机械臂上有的舵机电压可能要大于6.5V。(我回头要实验看下,这个9685能否给机械臂提供供电)
(2)我们这边要控制多个LED的灯光,可以多个灯不同时亮暗,采用多线程实现。
python
import pca9685
import time
import threading
Led_pwm = pca9685.PCA9685(busnum=1)
Led_pwm.set_pwm_freq(600) # 设置频率为60HZ
def LED0_ctrl():
for i in range(0,5000,10):
Led_pwm.set_pwm(0,0,i)
time.sleep(0.01)
def LED1_ctrl():
for i in range(0,3000,10):
Led_pwm.set_pwm(1,0,i)
time.sleep(0.01)
def LED2_ctrl():
for i in range(4000,0,-20):
Led_pwm.set_pwm(2,0,i)
time.sleep(0.03)
try:
while True:
t0=threading.Thread(target=LED0_ctrl) # 多线程
t1=threading.Thread(target=LED1_ctrl)
t2=threading.Thread(target=LED2_ctrl)
t0.start() # 开启线程
t1.start()
t2.start()
t0.join()
t1.join()
t2.join()
except KeyboardInterrupt:
exit()
(3)Python有个专门的舵机旋转角度控制函数,我们安装这些库来调用。
sudo pip install adafruit-circuitpython-pca9685
sudo pip install adafruit-circuitpython-servokit
python
from adafruit_pca9685 import PCA9685
from board import SCL, SDA
import busio
from adafruit_motor import servo
i2c_bus = busio.I2C(SCL, SDA)
pwm = PCA9685(i2c_bus) # 使用默认地址初始化PWM设备
pwm.frequency = 50 # 将频率设置为50 Hz
servo_12 = servo.Servo(pwm.channels[12]) # 指定第12通道的舵机(从0开始)
servo_15 = servo.Servo(pwm.channels[15]) # 指定第15通道的舵机
print('Moving servo on channel 0, press Ctrl-C to quit...')
servo_12.angle = 90
servo_15.angle = 90
while True:
# 伺服电机转动最小角度和最大角度
servo_12.angle = 0
servo_15.angle = 0
time.sleep(1)
servo_12.angle = 180
servo_15.angle = 180
time.sleep(1)
可以接舵机试试,控制角度非常方便。