PCA9685:I2C轉(zhuǎn)16路PWM,助力你的系統(tǒng)
1 基本介紹
1.1 該IC主要參數(shù)特征如下:
- I2C接口吭从,支持高達(dá)16路PWM輸出朝蜘,每路12位分辨率(4096級(jí))
- 內(nèi)置25MHz晶振,可不連接外部晶振涩金,也可以連接外部晶振,最大50MHz
- 支持2.3V-5.5V電壓暇仲,最大耐壓值5.5V,邏輯電平3.3V
- 具有上電復(fù)位步做,以及軟件復(fù)位等功能
注:本教程側(cè)重PCA9685的PWM輸出,但PCA9685亦可用于WS2812等LED顏色控制等奈附。
1.2 控制精度
假設(shè)舵機(jī)為50HZ的控制頻率全度,脈寬為0.5ms~2.5ms,12位分辨率(4096級(jí))斥滤,相關(guān)精度計(jì)算如下:
-
PWM周期:
-
時(shí)間分辨率:
-
最大脈寬時(shí)間:
-
最大脈寬時(shí)間可分成的份數(shù):
-
0-180度的舵機(jī)将鸵,角度分辨率:
2 硬件參數(shù)
2.1 封裝及引腳排列
PCA9685有兩種封裝:TSSOP28, HVQFN28,其相應(yīng)的引腳排列如下圖所示:
引腳功能描述如下圖所示:
2.2 器件地址
PCA9685的器件地址是由引腳A0佑颇,A1顶掉,A2,A3挑胸,A4痒筒,A5共同決定,并且該引腳不可懸空,由于有6個(gè)引腳共同決定器件地址簿透,因此移袍,可以有64個(gè)器件地址,由于該IC上電便保留LED All Call address (E0h老充,1110 000)以及Software Reset address(06h葡盗,0000 0110),實(shí)際僅有62個(gè)可用器件地址啡浊,因此觅够,理論上,1個(gè)I2C接口可控制16*62=992路PWM虫啥,其引腳控制器件地址的示意圖如下圖所示:
默認(rèn)情況下蔚约,若將A0-A5全部接地,則其器件地址為:0x40涂籽。
2.3 寄存器及其地址
默認(rèn)情況下苹祟,上電復(fù)位后,寄存器地址的默認(rèn)值均為0评雌,寄存器地址及其用途見下圖所示:
圖中節(jié)選的部分寄存器地址中树枫,主要關(guān)心以下寄存器:
- 模式設(shè)置寄存器:MODE1,MODE2景东。
- 脈寬(占空比)設(shè)置寄存器:LED0_ON_L,LED0_ON_H,LED0_OFF_L,LED0_OFF_H......LED15......每一路PWM通道占用4個(gè)寄存器砂轻。
- 周期(頻率)設(shè)置寄存器:PRE_SCALE。
接下來(lái)介紹以上寄存器的使用及其注意事項(xiàng)斤吐。
2.4 模式設(shè)置寄存器
2.4.1 MODE1寄存器
首先介紹MODE1寄存器,如下圖:
在使用該寄存器的時(shí)候要注意:
如果未停止所有PWM輸出就將其進(jìn)入到睡眠模式搔涝,那么,所有輸出通道在下一輪都將輸出高電平和措。
-
睡眠后重新啟動(dòng)PWM的操作為:
注意庄呈,在設(shè)置PWM頻率(寫PRESCALE寄存器)的時(shí)候,要先設(shè)置為Sleep模式派阱,請(qǐng)參考后面源碼部分诬留。
2.4.2 MODE2寄存器
該寄存器的各位功能如下圖所示:
2.5 PWM通道寄存器及其占空比設(shè)置
PWM通道寄存器如下圖:
由圖可知,對(duì)于每一個(gè)通道贫母,有4個(gè)寄存器文兑,每個(gè)寄存器圖解如下圖所示:
在設(shè)置PWM占空比的時(shí)候,首先腺劣,配置舵機(jī)的示例如下圖所示(ON < OFF的情況):
當(dāng)特殊情況下绿贞,PWM周期大于定時(shí)器一次計(jì)數(shù)時(shí),如下圖所示(ON>OFF的情況):
2.6 PWM周期(頻率)寄存器及其周期(頻率)設(shè)置
接下來(lái)介紹配置PWM頻率的寄存器:
一般情況下,在用內(nèi)置晶振誓酒,為25MHZ樟蠕,通過(guò)配置PRE_SCALE寄存器進(jìn)行配置贮聂,配置的PRE_SCALE寄存器的值與PWM頻率的關(guān)系如下圖所示:
如果在舵機(jī)控制中,采用內(nèi)置晶振寨辩,取osc_clock=25000000吓懈,update_rate=50(舵機(jī)控制頻率50Hz)
2.7 推薦硬件設(shè)計(jì)
首先,OE引腳須接低電平以確保IC使能靡狞,如果連接LED燈耻警,則推薦以下幾種連接方式,如下圖所示:
3 軟件設(shè)計(jì)
3.1 Micro:bit平臺(tái)TypeScript版
接下來(lái)進(jìn)行軟件設(shè)計(jì)部分講解甸怕,由于本次開發(fā)采用Micro:bit底層開發(fā)甘穿,采用的是TypeScript(JavaScript的超類),所以暫提供該語(yǔ)言,提供基本操作方法及其思路梢杭,日后再更新C,C++及其它平臺(tái)(STM32,Linux樹莓派温兼,Arduino等)操作方法,源碼如下武契,可結(jié)合DataSheet及以上教程理解:
/**
* 使用此文件來(lái)定義自定義函數(shù)和圖形塊募判。
* 想了解更詳細(xì)的信息,請(qǐng)前往 https://makecode.microbit.org/blocks/custom
*/
/**
* 自定義圖形塊
*/
//% weight=5 color=#0fbc11 icon="\uf113"
namespace Servo {
const PCA9685_ADDRESS = 0x40
const MODE1 = 0x00
const MODE2 = 0x01
const SUBADR1 = 0x02
const SUBADR2 = 0x03
const SUBADR3 = 0x04
const PRESCALE = 0xFE
const LED0_ON_L = 0x06
const LED0_ON_H = 0x07
const LED0_OFF_L = 0x08
const LED0_OFF_H = 0x09
const ALL_LED_ON_L = 0xFA
const ALL_LED_ON_H = 0xFB
const ALL_LED_OFF_L = 0xFC
const ALL_LED_OFF_H = 0xFD
const STP_CHA_L = 2047
const STP_CHA_H = 4095
const STP_CHB_L = 1
const STP_CHB_H = 2047
const STP_CHC_L = 1023
const STP_CHC_H = 3071
const STP_CHD_L = 3071
const STP_CHD_H = 1023
let initialized = false
function i2cwrite(addr: number, reg: number, value: number) {
let buf = pins.createBuffer(2)
buf[0] = reg
buf[1] = value
pins.i2cWriteBuffer(addr, buf)
}
function i2cread(addr: number, reg: number) {
pins.i2cWriteNumber(addr, reg, NumberFormat.UInt8BE);
let val = pins.i2cReadNumber(addr, NumberFormat.UInt8BE);
return val;
}
function initPCA9685(): void {
i2cwrite(PCA9685_ADDRESS, MODE1, 0x00)
setFreq(50);
setPwm(0, 0, 4095);
for (let idx = 1; idx < 16; idx++) {
setPwm(idx, 0, 0);
}
initialized = true
}
function setFreq(freq: number): void {
// Constrain the frequency
let prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
let prescale = prescaleval; //Math.Floor(prescaleval + 0.5);
let oldmode = i2cread(PCA9685_ADDRESS, MODE1);
let newmode = (oldmode & 0x7F) | 0x10; // sleep
i2cwrite(PCA9685_ADDRESS, MODE1, newmode); // go to sleep
i2cwrite(PCA9685_ADDRESS, PRESCALE, prescale); // set the prescaler
i2cwrite(PCA9685_ADDRESS, MODE1, oldmode);
control.waitMicros(5000);
i2cwrite(PCA9685_ADDRESS, MODE1, oldmode | 0xa1);
}
function setPwm(channel: number, on: number, off: number): void {
if (channel < 0 || channel > 15)
return;
let buf = pins.createBuffer(5);
buf[0] = LED0_ON_L + 4 * channel;
buf[1] = on & 0xff;
buf[2] = (on >> 8) & 0xff;
buf[3] = off & 0xff;
buf[4] = (off >> 8) & 0xff;
pins.i2cWriteBuffer(PCA9685_ADDRESS, buf);
}
/**
* Servo Execute
* @param degree [0-180] degree of servo; eg: 90, 0, 180
*/
//% blockId=setServo block="Servo channel|%channel|degree %degree"
//% weight=85
//% degree.min=0 degree.max=180
export function Servo(channel: number,degree: number): void {
if (!initialized) {
initPCA9685();
}
// 50hz: 20,000 us
let v_us = (degree * 1800 / 180 + 600); // 0.6 ~ 2.4
let value = v_us * 4096 / 20000;
setPwm(channel, 0, value);
}
/**
* Servo Execute
* @param pulse [500-2500] pulse of servo; eg: 1500, 500, 2500
*/
//% blockId=setServoPulse block="Servo channel|%channel|pulse %pulse"
//% weight=85
//% pulse.min=500 pulse.max=2500
export function ServoPulse(channel: number,pulse: number): void {
if (!initialized) {
initPCA9685();
}
// 50hz: 20,000 us
let value = pulse * 4096 / 20000;
setPwm(channel, 0, value);
}
}
以上便是Micro:bit驅(qū)動(dòng)PCA9685的源代碼咒唆,注意源代碼中的時(shí)間為us届垫,而教程中的時(shí)間為ms。
3.2 樹莓派平臺(tái)Python版
要運(yùn)行該程序全释,首先選裝python装处,安裝好Python后,還需要安裝樹莓派平臺(tái)的smbus庫(kù):
sudo apt-get install python-smbus
樹莓派平臺(tái)采用Python驅(qū)動(dòng)PCA9685的Python代碼如下所示:
#!/usr/bin/python
import time
import math
import smbus
# ============================================================================
# Raspi PCA9685 16-Channel PWM Servo Driver
# ============================================================================
class PCA9685:
# Registers/etc.
__SUBADR1 = 0x02
__SUBADR2 = 0x03
__SUBADR3 = 0x04
__MODE1 = 0x00
__PRESCALE = 0xFE
__LED0_ON_L = 0x06
__LED0_ON_H = 0x07
__LED0_OFF_L = 0x08
__LED0_OFF_H = 0x09
__ALLLED_ON_L = 0xFA
__ALLLED_ON_H = 0xFB
__ALLLED_OFF_L = 0xFC
__ALLLED_OFF_H = 0xFD
def __init__(self, address=0x40, debug=False):
self.bus = smbus.SMBus(1)
self.address = address
self.debug = debug
if (self.debug):
print("Reseting PCA9685")
self.write(self.__MODE1, 0x00)
def write(self, reg, value):
"Writes an 8-bit value to the specified register/address"
self.bus.write_byte_data(self.address, reg, value)
if (self.debug):
print("I2C: Write 0x%02X to register 0x%02X" % (value, reg))
def read(self, reg):
"Read an unsigned byte from the I2C device"
result = self.bus.read_byte_data(self.address, reg)
if (self.debug):
print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg))
return result
def setPWMFreq(self, freq):
"Sets the PWM frequency"
prescaleval = 25000000.0 # 25MHz
prescaleval /= 4096.0 # 12-bit
prescaleval /= float(freq)
prescaleval -= 1.0
if (self.debug):
print("Setting PWM frequency to %d Hz" % freq)
print("Estimated pre-scale: %d" % prescaleval)
prescale = math.floor(prescaleval + 0.5)
if (self.debug):
print("Final pre-scale: %d" % prescale)
oldmode = self.read(self.__MODE1);
newmode = (oldmode & 0x7F) | 0x10 # sleep
self.write(self.__MODE1, newmode) # go to sleep
self.write(self.__PRESCALE, int(math.floor(prescale)))
self.write(self.__MODE1, oldmode)
time.sleep(0.005)
self.write(self.__MODE1, oldmode | 0x80)
def setPWM(self, channel, on, off):
"Sets a single PWM channel"
self.write(self.__LED0_ON_L+4*channel, on & 0xFF)
self.write(self.__LED0_ON_H+4*channel, on >> 8)
self.write(self.__LED0_OFF_L+4*channel, off & 0xFF)
self.write(self.__LED0_OFF_H+4*channel, off >> 8)
if (self.debug):
print("channel: %d LED_ON: %d LED_OFF: %d" % (channel,on,off))
def setServoPulse(self, channel, pulse):
"Sets the Servo Pulse,The PWM frequency must be 50HZ"
pulse = pulse*4096/20000 #PWM frequency is 50HZ,the period is 20000us
self.setPWM(channel, 0, pulse)
if __name__=='__main__':
pwm = PCA9685(0x40, debug=True)
pwm.setPWMFreq(50)
while True:
# setServoPulse(2,2500)
for i in range(500,2500,10):
pwm.setServoPulse(0,i)
time.sleep(0.02)
for i in range(2500,500,-10):
pwm.setServoPulse(0,i)
time.sleep(0.02)
保存文件命名為: pca9685.py,命令行進(jìn)入該文件所在的路徑浸船,運(yùn)行該P(yáng)ython腳本:
sudo python pca9685.py
執(zhí)行該命令后妄迁,便可控制舵機(jī)從0度轉(zhuǎn)到180度,再?gòu)?80度轉(zhuǎn)到0度李命。