IIC接口介绍
i2c类实例化
I2C是设备之间的两线通信协议。在物理层它只需要两个信号线:SCL 和 SDA,分别是时钟和数据线。I2C 对象关联到总线,它可以在创建时初始化,也可以稍后初始化。
与I2C有关的类定义在machine中,我们可以通过
class machine.I2C(scl, sda, freq)
实例化一个I2C的类
其中
scl为i2c设备时钟引脚对象;sda为i2c设备数据线引脚对象;freq为SCL时钟频率(0 < freq ≤ 500000(Hz))。
i2c类中的函数介绍
I2C.init(scl, sda, freq)
函数说明:初始化i2c总线。
scl:SCL信号线的I/O口
sda:SDA信号线的I/O口
freq:SCL时钟频率
示例:
i2c.init(scl=Pin(22),sda=Pin(21))
I2C.scan()
函数说明:扫描0x08到0x77之间的I2C地址,并返回设备列表。
示例:
i2c.scan()
I2C.start()
函数说明:在总线上触发START状态(SCL为高电平时,SDA转为低电平)。
示例:
i2c.start()
I2C.stop()
函数说明:在总线上触发STOP状态 (SCL为高电平时,SDA转为高电平)。
示例:
i2c.stop()
I2C.write(buf)
函数说明:buf中的数据写入到总线,并返回写入的字节数。
buf:存储数据的缓冲区
注意:
使用write()函数时要与start函数一起使用,否则无法返回写入的字节数。
示例:
buf = b'123'
i2c.start()
i2c.write(buf)
I2C.readinto(buf, nack=True)
函数说明:从总线上读取数据并存放到buf,无返回值。
buf:存储数据的缓冲区
注意:
读取的字节数是buf的长度。在接收到最后一个字节之前,总线将发送ACK信号。在接收到最后一个字节后,如果nack为True,那么将发送一个NACK信号,否则将发送一个ACK信号。 示例:
buf=bytearray(3)
i2c.readinto(buf)
I2C.readfrom(addr, nbytes)
函数说明:从指定地址设备读取数据,返回读取对象,这个对象与I2C设备有关。
addr:i2c设备地址(可由scan函数读取出来)
nbytes:要读取数据的大小
示例:
data = i2c.readfrom(24, 8)
print(data)
运行结果:
b’\x00\x02\x00\x00\xe8\x03\xe8\x03’
I2C.readfrom_into(addr, buf)
函数说明:从指定地址设备读取buf.len()个数据到buf。
addr:i2c设备地址(可由scan函数读取出来)
buf:存储数据的缓冲区
示例:
buf = bytearray(8)
i2c.readfrom_into(24, buf)
print(buf)
运行结果:
bytearray(b’\x00\x02\x00\x00\xe8\x03\xe8\x03’)
I2C.writeto(addr, buf)
函数说明:将buf中的数据写入设备,并返回写入数据的大小。
addr:i2c设备地址(可由scan函数读取出来)
buf:存储数据的缓冲区
示例:
b = bytearray(3)
b[0] = 24
b[1] = 111
b[2] = 107
i = i2c.writeto(24,b)
I2C.readfrom_mem(addr, memaddr, nbytes, addrsize=8)
函数说明:从I2C设备的寄存器中读取并返回数据。
addr:i2c设备地址(可由scan函数读取出来)
memaddr:寄存器地址
nbytes:要读取的字节大小
addrsize:指定地址大小,默认为8位(在ESP8266上这个参数无效,地址大小总是8位)
示例:
b = i2c. readfrom_mem(24, 0x58, 3)
print(b)
运行结果:
b’\x00\x02\x01’
I2C.readfrom_mem_into(addr, memaddr, buf, addrsize=8)
函数说明:从I2C设备的寄存器中读取buf.len()个数据到buf,无返回值。
addr:i2c设备地址(可由scan函数读取出来)
memaddr:寄存器地址
buf:存储数据的缓冲区
addrsize:指定地址大小,默认为8位(在ESP8266上这个参数无效,地址大小总是8位),读取数据数量是buf的长度。
示例:
buf=bytearray(8)
i2c.readfrom_mem_into(24, 0x58, buf)
I2C.writeto_mem(addr, memaddr, buf, addrsize=8)
函数说明: 将buf 中的数据全部写入到从设备 addr 的内存 memaddr。
addr:i2c设备地址(可由scan函数读取出来)
memaddr:寄存器地址
buf:存储数据的缓冲区
addrsize:指定地址大小,默认为8位(在ESP8266上这个参数无效,地址大小总是8位),读取数据数量是buf的长度。
示例:
buf = b'123'
i2c.writeto_mem(24, 0x58, buf)
最佳实践
PCA9685 是最常见的多路PWM控制器,最高可以输出16路12位分辨率的PWM,可以用来控制LED或者舵机。这里我们着重讲解下如何控制舵机。
舵机控制
假设舵机为50HZ的控制频率,脉宽为0.5ms~2.5ms,12位分辨率(4096级),相关精度计算如下:
- PWM周期:
- 时间分辨率:
- 最大脉宽时间:
- 0-180度的舵机,角度分辨率:
寄存器介绍
MODE1
首先介绍MODE1寄存器,如下图:
在使用该寄存器的时候要注意:
- 如果未停止所有PWM输出就将其进入到睡眠模式,那么,所有输出通道在下一轮都将输出高电平。
- 睡眠后重新启动PWM的操作为:
- 注意,在设置PWM频率(写PRESCALE寄存器)的时候,要先设置为Sleep模式,请参考后面源码部分。
MODE2
该寄存器的各位功能如下图所示:
PWM ctrl
PWM通道寄存器如下图:
代码
PCA9685
import ustruct
import time
class PCA9685:
def __init__(self, i2c, 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):
self._write(0x00, 0x00)
def freq(self, freq=None):
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)
self._write(0x00, (old_mode & 0x7F) | 0x10)
self._write(0xfe, prescale)
self._write(0x00, old_mode)
time.sleep_us(5)
self._write(0x00, old_mode | 0xa1)
def pwm(self, index, on=None, off=None):
if on is None or off is None:
data = self.i2c.readfrom_mem(self.address, 0x06 + 4 * index, 4)
return ustruct.unpack('<HH', data)
data = ustruct.pack('<HH', on, off)
self.i2c.writeto_mem(self.address, 0x06 + 4 * index, data)
def duty(self, index, value=None, invert=False):
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)
servo
import pca9685
import math
class Servos:
def __init__(self, i2c, address=0x40, freq=50, min_us=600, max_us=2400,
degrees=180):
self.period = 1000000 / freq
self.min_duty = self._us2duty(min_us)
self.max_duty = self._us2duty(max_us)
self.degrees = degrees
self.freq = freq
self.pca9685 = pca9685.PCA9685(i2c, address)
self.pca9685.freq(freq)
def _us2duty(self, value):
return int(4095 * value / self.period)
def position(self, index, degrees=None, radians=None, us=None, duty=None):
span = self.max_duty - self.min_duty
if degrees is not None:
duty = self.min_duty + span * degrees / self.degrees
elif radians is not None:
duty = self.min_duty + span * radians / math.radians(self.degrees)
elif us is not None:
duty = self._us2duty(us)
elif duty is not None:
pass
else:
return self.pca9685.duty(index)
duty = min(self.max_duty, max(self.min_duty, int(duty)))
self.pca9685.duty(index, duty)
def release(self, index):
self.pca9685.duty(index, 0)
main
from machine import Pin, SoftI2C
import servo
i2c = SoftI2C(scl=Pin(18), sda=Pin(19), freq=100000)
servo = Servos(i2c, address=0x40, freq=50, min_us=650, max_us=2800, degrees=180)
for i in range(0, 180):
servo.position(0, i)
print(i)
time.sleep(0.2)