classe I2C – um protocolo serial de dois fios

I2C é um protocolo de dois fios para comunicação entre dispositivos. No nível físico, ele consiste em 2 fios: SCL e SDA, as linhas de clock e de dados, respectivamente.

Os objetos I2C são criados associados a um barramento específico. Eles podem ser inicializados no momento da criação ou inicializados posteriormente.

Imprimir o objeto I2C fornece informações sobre sua configuração.

Existem implementações de I2C tanto em hardware quanto em software, por meio das classes I2C e SoftI2C. O I2C de hardware utiliza o suporte de hardware subjacente do sistema para realizar as leituras/escritas e geralmente é eficiente e rápido, mas pode ter restrições sobre quais pinos podem ser usados. O I2C de software é implementado por bit-banging e pode ser usado em qualquer pino, mas não é tão eficiente. Essas classes têm os mesmos métodos disponíveis e diferem principalmente na forma como são construídas.

Nota

O barramento I2C requer circuitos de pull-up tanto em SDA quanto em SCL para seu funcionamento. Normalmente esses são resistores na faixa de 1 a 10 kOhm, conectados de cada SDA/SCL ao Vcc. Sem eles, o comportamento é indefinido e pode variar de bloqueio, reinicialização inesperada do watchdog, até apenas valores incorretos. Frequentemente, esse circuito de pull-up já vem embutido na placa do MCU ou em placas de breakout do sensor, mas não há uma regra para isso. Portanto, verifique em caso de problemas. Veja também este excelente guia de aprendizado da Adafruit sobre o cabeamento I2C.

Exemplo de uso:

from machine import I2C

i2c = I2C(freq=400000)          # create I2C peripheral at frequency of 400kHz
                                # depending on the port, extra parameters may be required
                                # to select the peripheral and/or pins to use

i2c.scan()                      # scan for peripherals, returning a list of 7-bit addresses

i2c.writeto(42, b'123')         # write 3 bytes to peripheral with 7-bit address 42
i2c.readfrom(42, 4)             # read 4 bytes from peripheral with 7-bit address 42

i2c.readfrom_mem(42, 8, 3)      # read 3 bytes from memory of peripheral 42,
                                #   starting at memory-address 8 in the peripheral
i2c.writeto_mem(42, 2, b'\x10') # write 1 byte to memory of peripheral 42
                                #   starting at address 2 in the peripheral

Construtores

class machine.I2C(id: int, *, scl: Pin | None = None, sda: Pin | None = None, freq: int = 400000, timeout: int = 50000)

Constrói e retorna um novo objeto I2C usando os seguintes parâmetros:

  • id identifica um periférico I2C específico. Os valores permitidos dependem da porta/placa específica

  • scl deve ser um objeto de pino que especifica o pino a ser usado para SCL.

  • sda deve ser um objeto de pino que especifica o pino a ser usado para SDA.

  • freq deve ser um inteiro que define a frequência máxima para SCL.

  • timeout é o tempo máximo em microssegundos permitido para transações I2C. Esse parâmetro não é permitido em algumas portas.

Observe que algumas portas/placas terão valores padrão de scl e sda que podem ser alterados neste construtor. Outras terão valores fixos de scl e sda que não podem ser alterados.

Métodos Gerais

init(scl: Pin, sda: Pin, *, freq: int = 400000) None

Inicializa o barramento I2C com os argumentos fornecidos:

  • scl é um objeto de pino para a linha SCL

  • sda é um objeto de pino para a linha SDA

  • freq é a taxa de clock de SCL

No caso do I2C de hardware, a frequência de clock real pode ser menor do que a frequência solicitada. Isso depende do hardware da plataforma. A taxa real pode ser determinada imprimindo o objeto I2C.

scan() List[int]

Varre todos os endereços I2C entre 0x08 e 0x77, inclusive, e retorna uma lista daqueles que respondem. Um dispositivo responde se puxar a linha SDA para baixo após seu endereço (incluindo um bit de escrita) ser enviado no barramento.

Operações I2C primitivas

Os métodos a seguir implementam as operações primitivas do barramento do controlador I2C e podem ser combinados para realizar qualquer transação I2C. Eles são fornecidos caso você precise de mais controle sobre o barramento; caso contrário, os métodos padrão (veja abaixo) podem ser usados.

Esses métodos estão disponíveis apenas na classe SoftI2C.

start() None

Gera uma condição START no barramento (SDA transita para baixo enquanto SCL está alto).

stop() None

Gera uma condição STOP no barramento (SDA transita para alto enquanto SCL está alto).

readinto(buf: bytearray, nack: bool = True, /) None

Lê bytes do barramento e os armazena em buf. O número de bytes lidos é o comprimento de buf. Um ACK será enviado no barramento após o recebimento de todos os bytes, exceto o último. Após o recebimento do último byte, se nack for verdadeiro, um NACK será enviado; caso contrário, um ACK será enviado (e, nesse caso, o periférico assume que mais bytes serão lidos em uma chamada posterior).

write(buf: bytes) int

Escreve os bytes de buf no barramento. Verifica se um ACK é recebido após cada byte e para de transmitir os bytes restantes se um NACK for recebido. A função retorna o número de ACKs que foram recebidos.

Operações padrão do barramento

Os métodos a seguir implementam as operações padrão de leitura e escrita do controlador I2C que têm como alvo um dispositivo periférico específico.

readfrom(addr: int, nbytes: int, stop: bool = True, /) bytes

nbytes do periférico especificado por addr. Se stop for verdadeiro, uma condição STOP é gerada ao final da transferência. Retorna um objeto bytes com os dados lidos.

readfrom_into(addr: int, buf: bytearray, stop: bool = True, /) None

Lê em buf a partir do periférico especificado por addr. O número de bytes lidos será o comprimento de buf. Se stop for verdadeiro, uma condição STOP é gerada ao final da transferência.

O método retorna None.

writeto(addr: int, buf: bytes, stop: bool = True, /) int

Escreve os bytes de buf no periférico especificado por addr. Se um NACK for recebido após a escrita de um byte de buf, os bytes restantes não são enviados. Se stop for verdadeiro, uma condição STOP é gerada ao final da transferência, mesmo que um NACK seja recebido. A função retorna o número de ACKs que foram recebidos.

writevto(addr: int, vector: tuple | list, stop: bool = True, /) int

Escreve os bytes contidos em vector no periférico especificado por addr. vector deve ser uma tupla ou lista de objetos com o protocolo de buffer. O addr é enviado uma vez e, em seguida, os bytes de cada objeto em vector são escritos sequencialmente. Os objetos em vector podem ter comprimento zero, caso em que não contribuem para a saída.

Se um NACK for recebido após a escrita de um byte de um dos objetos em vector, os bytes restantes, e quaisquer objetos restantes, não são enviados. Se stop for verdadeiro, uma condição STOP é gerada ao final da transferência, mesmo que um NACK seja recebido. A função retorna o número de ACKs que foram recebidos.

Operações de memória

Alguns dispositivos I2C atuam como um dispositivo de memória (ou conjunto de registradores) que pode ser lido e escrito. Nesse caso, há dois endereços associados a uma transação I2C: o endereço do periférico e o endereço de memória. Os métodos a seguir são funções de conveniência para comunicação com esses dispositivos.

readfrom_mem(addr: int, memaddr: int, nbytes: int, *, addrsize: int = 8) bytes

nbytes do periférico especificado por addr a partir do endereço de memória especificado por memaddr. O argumento addrsize especifica o tamanho do endereço em bits. Retorna um objeto bytes com os dados lidos.

readfrom_mem_into(addr: int, memaddr: int, buf: bytearray, *, addrsize: int = 8) None

Lê em buf a partir do periférico especificado por addr começando no endereço de memória especificado por memaddr. O número de bytes lidos é o comprimento de buf. O argumento addrsize especifica o tamanho do endereço em bits.

O método retorna None.

writeto_mem(addr: int, memaddr: int, buf: bytes, *, addrsize: int = 8) None

Escreve buf no periférico especificado por addr a partir do endereço de memória especificado por memaddr. O argumento addrsize especifica o tamanho do endereço em bits.

O método retorna None.

classe SoftI2C – barramento I2C emulado por software

A classe SoftI2C implementa I2C por meio de bit-banging em pinos GPIO arbitrários. Ela expõe o mesmo conjunto de métodos que I2C além das operações primitivas de baixo nível do barramento (start(), stop(), readinto(), write()) para chamadores que precisam montar transações não padronizadas. Use-a quando os pinos de que você precisa não estiverem conectados a um bloco I2C de hardware, quando você precisar de mais barramentos do que o hardware fornece, ou para conversar com dispositivos que exigem sequências incomuns (clocks extras, starts repetidos após escritas, etc.).

Construtores

class machine.SoftI2C(scl: Pin, sda: Pin, *, freq: int = 400000, timeout: int = 50000)

Constrói um barramento I2C de software controlado por scl / sda.

freq é a taxa de clock de SCL desejada em Hz (a taxa real é normalmente menor devido à sobrecarga do laço de bit-bang).

timeout é o tempo máximo em microssegundos para aguardar o clock stretching (SCL mantido em nível baixo por outro dispositivo no barramento); ao expirar, um OSError(ETIMEDOUT) é levantado.

Métodos Gerais

init(scl: Pin, sda: Pin, *, freq: int = 400000) None

Reinicializa o barramento I2C de software com os pinos e a frequência fornecidos. Equivalente a construir um novo SoftI2C no mesmo objeto.

scan() List[int]

Varre todos os endereços I2C entre 0x08 e 0x77, inclusive, e retorna uma lista daqueles que responderam.

Operações I2C primitivas

Os métodos a seguir implementam as operações primitivas do barramento do controlador I2C e podem ser combinados para realizar qualquer transação I2C. Eles são exclusivos do SoftI2C – a classe I2C de hardware não os expõe.

start() None

Gera uma condição START no barramento (SDA transita para baixo enquanto SCL está alto).

stop() None

Gera uma condição STOP no barramento (SDA transita para alto enquanto SCL está alto).

readinto(buf: bytearray, nack: bool = True, /) None

Lê bytes do barramento para buf. len(buf) bytes são lidos; um ACK é enviado após cada byte, exceto o último. Após o último byte, nack=True (o padrão) envia um NACK para encerrar a transferência; nack=False envia um ACK para que o dispositivo permaneça selecionado para um readinto() subsequente.

write(buf: bytes) int

Escreve buf no barramento, verificando o ACK após cada byte. A transmissão para no primeiro NACK. Retorna o número de ACKs recebidos.

Operações padrão do barramento

Os métodos a seguir implementam as operações padrão de leitura e escrita do controlador I2C que têm como alvo um dispositivo periférico específico.

readfrom(addr: int, nbytes: int, stop: bool = True, /) bytes

nbytes do dispositivo no endereço de 7 bits addr. Se stop for verdadeiro, uma condição STOP é gerada ao final da transferência.

readfrom_into(addr: int, buf: bytearray, stop: bool = True, /) None

len(buf) bytes do dispositivo em addr para buf. Se stop for verdadeiro, uma condição STOP é gerada ao final da transferência.

writeto(addr: int, buf: bytes, stop: bool = True, /) int

Escreve buf no dispositivo em addr. A transmissão para no primeiro NACK. Se stop for verdadeiro, uma condição STOP é sempre gerada ao final da transferência (mesmo em caso de NACK precoce). Retorna o número de ACKs recebidos.

writevto(addr: int, vector: tuple | list, stop: bool = True, /) int

Escreve a concatenação dos buffers em vector no dispositivo em addr como uma única transação. Buffers vazios são ignorados. Comporta-se como writeto() em relação à semântica de stop e ao valor de retorno.

Operações de memória

Alguns dispositivos I2C atuam como um dispositivo de memória (ou conjunto de registradores) que pode ser lido e escrito. Nesse caso, há dois endereços associados a uma transação I2C: o endereço do periférico e o endereço de memória. Os métodos a seguir são auxiliares de conveniência para conversar com esses dispositivos.

readfrom_mem(addr: int, memaddr: int, nbytes: int, *, addrsize: int = 8) bytes

nbytes do dispositivo em addr a partir do registrador memaddr. addrsize é a largura do endereço de registrador em bits (normalmente 8 ou 16).

readfrom_mem_into(addr: int, memaddr: int, buf: bytearray, *, addrsize: int = 8) None

Lê em buf a partir do dispositivo em addr começando no registrador memaddr.

writeto_mem(addr: int, memaddr: int, buf: bytes, *, addrsize: int = 8) None

Escreve buf no dispositivo em addr a partir do registrador memaddr.