"""
esp32是与电脑用数据线连接的芯片,esp32通过i2c将数据传给pca9685,pca9685控制舵机运动
因此,esp32是芯片,pca9685是舵机驱动器
"""
# 为简化可扩展性,标准Python模块的MicroPython版本通常有 u (micro)前缀。
# ustruct用于对数据按指定格式打包
import ustruct
import time
class PCA9685:
def __init__(self, i2c, address=0x40):
#PCA9685 是一个I2C 从设备,有个设备ID,或者叫从 地址。从地址是如下确定的:
# A5-A0 缺省是开放的, 地址是0x40,所以address默认0x40
self.i2c = i2c
self.address = address
self.reset()
def _write(self, address, value):
self.i2c.writeto_mem(self.address, address, bytearray([value]))
def _read(self, address):
return self.i2c.readfrom_mem(self.address, address, 1)[0]
def reset(self):
'''重置PCA9685-生成对象时默认调用'''
self._write(0x00, 0x00) # Mode1
def freq(self, freq=None):
'''freq函数用于传入舵机频率后,计算pca9685内部的高低电平长短计量单位'''
def pwm(self, index, on=None, off=None):
"""
设置脉冲宽度,index为舵机对应的pca96856编号,on为0,off为目标值
off = 307,舵机回中位
off = 102,脉宽为0.5ms,普通180°舵机则转到0°
off = 512,脉宽为2.5ms,普通180°舵机则转到180°
"""
def duty(self, index, value=None, invert=False):
'''对pwm函数的再一次封装,加入了反向计算转动'''
```python
def freq(self, freq=None):
'''设置脉冲频率,一般是50Hz'''
if freq is None:
return int(25000000.0 / 4096 / (self._read(0xfe) - 0.5))
prescale = int(25000000.0 / 4096.0 / freq + 0.5)
old_mode = self._read(0x00) # 读取Mode 1
self._write(0x00, (old_mode & 0x7F) | 0x10) # Mode 1, sleep
self._write(0xfe, prescale) # Prescale
self._write(0x00, old_mode) # Mode 1
time.sleep_us(5) # 规定要求500us的时间,才能完成重置调整
self._write(0x00, old_mode | 0xa1) # Mode 1, autoincrement on
# 由于12bit是由8bit + 8bit组合成的,
# 当第1个8bit满了之后,需要对mode1进行自增位的改变,进行第2个bit写入
注:当舵机频率越高,意味着每次发出的脉冲时间更短,
则prescale会更小,不同的prescale计量出的脉宽是不同的
1、而要将数据写入寄存器之间,需要先将第0位(0x00)寄存器的MODE1设置为0
首先0x7f相当于0111 1111,而与mode1的读取出来的8个二进制位bit,进行&与的位运算,即,只要同位为1,即为1,否则为0。因此&与运算结束后,只对mode1的8bit中的第0位进行修改为0,其余后7位&111 1111是不会发生改变的。
mode1的8bit: xxxx xxxx
0x7f的8bit: 0111 1111
最后mode1&0xf7f = 0xxx xxxx,mode1的第0个bit改为0
2、将第4位(0x10)寄存器的MODE1进行设置为1
mode1的8bit: xxxx xxxx
0x7f的8bit: 0111 1111
mode1&0xf7f = 0xxx xxxx,mode1的第0个bit改为0
0xxx xxxx|0x10 => 0xxx xxxx|0001 0000,这|位或运算,是两个都是0,才为0
0xxx xxxx
0001 0000
最后位或运算是对mode的第4位,设置为1,即最终mode为 0xx1 xxxx
其实就是将mode设置了第0位为0(all call),第4位为1(sleep),对应的mode1的含义如下图
def pwm(self, index, on=None, off=None):
'''设置脉冲宽度,index为舵机对应的pca96856编号,on为0,off为目标值'''
if on is None or off is None:
data = self.i2c.readfrom_mem(self.address, 0x06 + 4 * index, 4)
return ustruct.unpack(', data)
data = ustruct.pack(', on, off)
self.i2c.writeto_mem(self.address, 0x06 + 4 * index, data)
当起始寄存器位置on为空或是停止寄存器off为空,则读取第index个舵机的寄存器起始位置,并读取4个寄存器的数据,作为第index个舵机的所有数据
这里需要明确,pca9685总共有256个寄存器,每个寄存器是8bit,除了前6个寄存器是用来控制模式的,后边的寄存器大多用于控制LED灯(pca9685本来是设计用于控制LED等的,这里用来控制舵机,是个人做法)
而更需要注意的是,假设我需要pca9685控制4个舵机,则第1个舵机的位置,从0x06+40开始,到0x06+41结束,相当于1个舵机占用4个寄存器
第2个舵机的位置,从0x06+41的位置开始,到0x06+42结束,占用4个寄存器。
每个舵机为什么占用四个寄存器
由于存储是12bit的数据,因此需要2个8bit的寄存器分别作为ON的低位寄存器和高位寄存器(即LED0_ON_L和LED0_ON_H),并且要注意,是L在前,H在后
1. 如果数据是 0x7f=>0111 1111,其中0111是高位 1111是低位
2. 那么在寄存器中的存储顺序,应该是0x06存1111,0x07存0111
3. 映射到ustruct.unpack中的格式是"
def duty(self, index, value=None, invert=False):
'''
对pwm函数的再一次封装,加入了反向计算转动
'''
if value is None:
pwm = self.pwm(index)
if pwm == (0, 4096):
value = 0
elif pwm == (4096, 0):
value = 4095
value = pwm[1]
if invert:
value = 4095 - value
return value
if not 0 <= value <= 4095:
raise ValueError("Out of range")
if invert:
value = 4095 - value
if value == 0:
self.pwm(index, 0, 4096)
elif value == 4095:
self.pwm(index, 4096, 0)
else:
self.pwm(index, 0, value)
当没给value传值时,会获取pca9685对应index舵机的原数据,而获取出来的pwm是以元组的方式,表示起始位置on和截止位置off的表示值。
*注意:在pca9685中,12bit的数据由8bit+8bit组合而成。*
当on数据为0,off的数据为4096时=>0000 0000 0010,
表示LED全灭,即数据值为空
当off的数据为0,on的数据为4096时=>0000 0000 0010,
表示LED灯全亮,即数据值4095
invert用于当舵机倒装时,角度值相反
当传入的value不为None时,判断范围之外,依然要进行value为0及value为4095的全灭全亮操作
on为0,off为4096=>为全灭=>value为0
on为4096,off为0=>为全亮=>value为4095