3.24. I2C em código¶
machine.I2C encapsula um controlador I2C de hardware. Construa um com o id do barramento e uma frequência de clock opcional:
from machine import I2C
i2c = I2C(2, freq=400_000)
id seleciona qual bloco I2C de hardware usar; os números disponíveis e os pinos SDA / SCL para os quais eles mapeiam dependem da placa (veja a Placas OpenMV). freq é a taxa de clock do SCL – 100_000 é o modo padrão, 400_000 é a velocidade “rápida” comum que a maioria dos sensores suporta.
3.24.1. Varrendo o barramento¶
scan() percorre o espaço de endereços de 7 bits e retorna uma lista de todos os dispositivos que confirmaram seu endereço:
>>> i2c.scan()
[0x40, 0x68]
Esta é a primeira coisa a tentar depois de ligar um barramento novo. Se um dispositivo estiver na lista, a fiação e os pull-ups estão corretos. Se a lista estiver vazia (ou mais curta que o esperado), suspeite dos pull-ups, da fiação ou dos pinos de endereço do dispositivo antes de suspeitar do código.
3.24.2. O esquema mapeado em memória¶
A maioria dos sensores I2C expõe seu estado como um conjunto de registradores internos: flags de status em um endereço, bytes de medição em outro, configuração em um terceiro. A biblioteca tem dois métodos de conveniência que mapeiam diretamente para o esquema “endereçar o dispositivo, apontar para um registrador, depois ler ou escrever dados”.
Lendo um registrador:
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 registrador (um repeated start), reenvia o endereço com o bit de leitura, lê 6 bytes, dá NACK no último e dá STOP. O dispositivo vê a sequência padrão de “ler do registrador”; o script vê uma única chamada Python.
Escrevendo em um registrador:
i2c.writeto_mem(addr, 0x6B, b"\x00")
Essa sequência é a mesma em imagem espelhada: START, endereço + escrita, registrador, byte de payload, STOP.
Para dispositivos cujo endereço de registrador tem dois bytes de largura (algumas EEPROMs e memórias endereçadas em 16 bits), passe addrsize=16:
data = i2c.readfrom_mem(addr, 0x0100, 32, addrsize=16)
3.24.3. Lendo um sensor¶
Um pequeno loop lendo o identificador WHO_AM_I e o eixo X do acelerômetro em um 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() retorna um objeto bytes, então a leitura de WHO_AM_I indexa o primeiro (e único) byte com [0]. A leitura do acelerômetro são dois bytes brutos que precisam ser montados em um único valor de 16 bits com sinal; o formato ">h" do módulo struct faz isso como big-endian com sinal – a ordem de bytes que a maioria dos sensores I2C usa, oposta à convenção little-endian comum em enlaces UART. struct.unpack() sempre retorna uma tupla (um elemento por caractere de formato), então [0] extrai o único valor.
3.24.4. Acesso de baixo nível¶
Para dispositivos que não seguem o esquema de mapa de registradores, dois métodos de mais baixo nível dão controle direto sobre a fase de endereço 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
Eles são úteis para dispositivos com interfaces de fluxo de comandos (displays, alguns relógios de tempo real) onde os auxiliares de conveniência de mapa de registradores não se encaixam bem.
3.24.5. Bit-banging quando nenhum bloco de hardware está disponível¶
machine.SoftI2C fornece a mesma API em pinos GPIO arbitrários, fazendo bit-banging do protocolo em software:
from machine import SoftI2C, Pin
i2c = SoftI2C(scl=Pin("P5"), sda=Pin("P4"), freq=100_000)
Use-o quando o dispositivo precisar estar em pinos que não estão ligados a um bloco I2C de hardware, ou quando todos os blocos de hardware estiverem em uso. O loop de bit-bang é mais lento que o I2C de hardware e a CPU fica ocupada durante toda a transação, mas a API é idêntica à de I2C, de modo que o código existente migra alterando apenas o construtor.