3.24. I2C em código¶
machine.I2C encapsula um controlador I2C por hardware. Construa um com o id do barramento e uma frequência de relógio opcional:
from machine import I2C
i2c = I2C(2, freq=400_000)
id seleciona qual o bloco I2C de hardware a utilizar; os números disponíveis e os pinos SDA / SCL a que mapeiam dependem da placa (consulte a Placas OpenMV). freq é a frequência do relógio SCL – 100_000 é o modo standard, 400_000 é a velocidade «rápida» comum que a maioria dos sensores suporta.
3.24.1. Pesquisar no barramento¶
scan() percorre o espaço de endereços de 7 bits e devolve uma lista de todos os dispositivos que reconheceram o seu endereço:
>>> i2c.scan()
[0x40, 0x68]
Esta é a primeira coisa a tentar após ligar um novo barramento. Se um dispositivo está na lista, a ligação e os pull-ups estão corretos. Se a lista está vazia (ou mais curta do que o esperado), suspeite dos pull-ups, da ligação ou dos pinos de endereço do dispositivo antes de suspeitar do código.
3.24.2. O padrão mapeado em memória¶
A maioria dos sensores I2C expõe o seu estado como um conjunto de registos internos: flags de estado num endereço, bytes de medição noutro, configuração num terceiro. A biblioteca possui dois métodos de conveniência que mapeiam diretamente para o padrão «endereçar o dispositivo, apontar para um registo, depois ler ou escrever dados».
Ler um registo:
addr = 0x68
reg = 0x3B
data = i2c.readfrom_mem(addr, reg, 6)
# `data` is 6 bytes starting at register 0x3B
Essa única chamada emite um START, envia o endereço do dispositivo com o bit de escrita, envia o endereço do registo (um repeated start), reenvia o endereço com o bit de leitura, lê 6 bytes, envia NACK ao último e envia STOP. O dispositivo vê a sequência padrão «ler do registo»; o script vê uma chamada Python.
Escrever num registo:
i2c.writeto_mem(addr, 0x6B, b"\x00")
Essa sequência é a mesma em imagem espelhada: START, endereço + escrita, registo, byte de dados, STOP.
Para dispositivos cujo endereço de registo tem dois bytes de largura (alguns EEPROMs e memórias com endereçamento de 16 bits) passe addrsize=16:
data = i2c.readfrom_mem(addr, 0x0100, 32, addrsize=16)
3.24.3. Ler um sensor¶
Um ciclo curto a ler o identificador WHO_AM_I e o eixo X do acelerómetro num sensor MEMS típico:
import time
import struct
from machine import I2C
i2c = I2C(2, freq=400_000)
ADDR = 0x68
WHO_AM_I = 0x75
ACCEL_X_HI = 0x3B
print("WHO_AM_I:", i2c.readfrom_mem(ADDR, WHO_AM_I, 1)[0])
while True:
raw = i2c.readfrom_mem(ADDR, ACCEL_X_HI, 2)
x = struct.unpack(">h", raw)[0]
print("accel X:", x)
time.sleep_ms(100)
readfrom_mem() devolve um objeto bytes, pelo que a leitura do WHO_AM_I indexa o primeiro (e único) byte com [0]. A leitura do acelerómetro são dois bytes brutos que têm de ser combinados num único valor de 16 bits com sinal; o formato struct ">h" faz isso como big-endian com sinal – a ordem de bytes que a maioria dos sensores I2C utiliza, oposta à convenção little-endian comum em ligações UART. struct.unpack() devolve sempre um tuplo (um elemento por carácter de formato), pelo que [0] extrai o valor único.
3.24.4. Acesso de baixo nível¶
Para dispositivos que não seguem o padrão de mapa de registos, dois métodos de nível mais baixo fornecem controlo direto sobre a fase de endereçamento e a direção dos dados:
i2c.writeto(addr, b"\x01\x02\x03") # write 3 bytes to addr
data = i2c.readfrom(addr, 4) # read 4 bytes from addr
São úteis para dispositivos com interfaces de fluxo de comandos (displays, alguns relógios de tempo real) onde os auxiliares de conveniência do mapa de registos não se encaixam bem.
3.24.5. Bit-banging quando não há bloco de hardware disponível¶
machine.SoftI2C fornece a mesma API em pinos GPIO arbitrários, implementando o protocolo por software através de bit-banging:
from machine import SoftI2C, Pin
i2c = SoftI2C(scl=Pin("P5"), sda=Pin("P4"), freq=100_000)
Use-o quando o dispositivo tem de estar em pinos que não estão ligados a um bloco I2C de hardware, ou quando todos os blocos de hardware estão em uso. O ciclo de bit-bang é mais lento do que o I2C por hardware e o CPU fica ocupado durante toda a transação, mas a API é idêntica à de I2C, pelo que o código existente muda apenas alterando o construtor.