PCA9685中文简要文档

前言

近来用做了个树莓派控制的小车,可是树莓派的 PWM 输出的确不怎么样,在用 RPi.GPIO 库输出 PWM 波时还出现了找不到原因的 bug(占空比无法保持,始终自动减小至0后跳回原值),找了半天资料也没有结果,无奈之下只好用 PCA9685 这一 I2C 接口的 PWM 扩展板。
这里简单记录一下它的使用方法。本人水平有限,如有不当之处请指正。

简介

PCA9685 是 NXP 产的 I2C 总线接口的16路12位的 PWM 发生芯片(模块),需要2.3V ~ 5.5V供电,频率可调(24Hz ~ 1526Hz),厂商提供的文档以控制 LED 灯为例进行说明,一些细节问题还是要看厂商的文档。
它在 I2C 总线上的地址初始是0x40,可硬改;内部是寄存器寻址范围是0x00 ~ 0x45和0xfa ~ 0xff,其中0x00和0x01是两个模式寄存器 MODE1 和 MODE2 [1];0x02 ~ 0x05是子地址和广播地址;从0x06 ~ 0x45每4个一组共16组,依次表示第0~15个 PWM 输出端电平高低跳变的控制数据[2];对0xfa ~ 0xfd写数据可同时设置前面16组寄存器的内容(读无效);0xfe是频率寄存器[3],0xff为测试模式,不要动它。
[1]: MODE1寄存器共8位,从高位到低位含义为:7.restart 重新按寄存器的内容输出PWM信号,写1后动作执行并自动回0,6.extclk 选择外部/内部时钟,5.AI 自增使能,4.sleep 振荡器不工作,3~0.位是子地址和广播地址的使能位。MODE2暂时用不着。
[2]: 每组4个,地址由低到高为:led_on_h、led_on_l、led_off_h、led_off_h。led_on_h的高7~5位无用,第4位为常开使能,第3~0位和led_on_l的全部8位共12位共同组成跳变到高电平的时间t(在每个周期的t/4096时由低电平跳变到高电平),led_off_h 与 led_off_l同理。
[3]: 频率寄存器的值由公式 value=(25000000/(4096*freq))-1 得出。

具体使用

上电后 MODE1 中的 sleep 为1(即振荡器不工作),首先我们要设置频率(仅当 sleep 位为1时向频率寄存器写入的值才有效),设置好频率之后关闭 sleep 并向 restart 位写1后设置的频率生效并开始输出 PWM 波。接下来可以设置占空比,设置立即生效,如果观察不到输出波形请检查常开使能和常关使能是否有效。

示例程序(Python)

参考了因特网上一些资料,在此表示感谢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# -*- coding: utf-8 -*-  
import RPi.GPIO as GPIO
import time
import smbus

class PCA9685:
mode1adr=0x00 #0x00
mode2adr=0x01
subadr1=0x02
subadr2=0x03
subadr3=0x04
allcalladr=0x05
led0_on_l_adr=0x06
led0_on_h_adr=0x07
led0_off_l_adr=0x08
led0_off_h_adr=0x09
led1_on_l_adr=0x0a
led1_on_h_adr=0x0b
led1_off_l_adr=0x0c
led1_off_h_adr=0x0d
#....................
all_led_on_l_adr=0xfa
all_led_on_h_adr=0xfb
all_led_off_l_adr=0xfc
all_led_off_h_adr=0xfd
pre_scale=0xfe #freq

init_mode=0x01

freq = 0

def __init__(self,i2c_ch,PCA9685adr):
self.PCA9685adr = PCA9685adr
self.bus = smbus.SMBus(i2c_ch)
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,self.init_mode)
self.setFreq(50)

def toByte(self,x1):
if x1<0xff:
x_l=x1
x_h=0x00
else :
x_h=x1/256
x_l=x1-x_h*256
return x_l,x_h

def write_16(self,led_num,uInt16):
(uInt16L,uInt16H)=self.toByte(uInt16)
self.bus.write_byte_data(self.PCA9685adr,led_num,uInt16L)
self.bus.write_byte_data(self.PCA9685adr,led_num+1,uInt16H)

def write_8(self,led_num,uInt8):
self.bus.write_byte_data(self.PCA9685adr,led_num,uInt8)


def setFreq(self,freq):
data=(25000000/(4096*freq))-1
data=int(data+0.49)
oldmode = self.bus.read_byte_data(self.PCA9685adr,self.mode1adr)
newmode = (oldmode & 0x7F) | 0x10
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,newmode)
self.bus.write_byte_data(self.PCA9685adr,self.pre_scale,data)
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,oldmode)
print str(freq)+' F'
time.sleep(0.005)
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,oldmode|0xa1)
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,oldmode)
self.freq = freq
def getFreq(self):
return self.freq

def setDuty(self,led_num,duty):
data = int(float(duty)/100*4095)
print str([duty,data])+' E'
if duty < 0:
self.write_16(self.led0_on_l_adr+4*led_num,0)
self.write_16(self.led0_off_l_adr+4*led_num,0)
if duty < 100 and duty >= 0:
self.write_16(self.led0_on_l_adr+4*led_num,0)
self.write_16(self.led0_off_l_adr+4*led_num,data)
elif duty >= 100:
self.write_16(self.led0_on_l_adr+4*led_num,0x1000)
self.write_16(self.led0_off_l_adr+4*led_num,0x0FFF)