3.17. Fluxos de bits e medição de pulsos

Alguns dispositivos precisam de padrões de pulso com temporização precisa, em vez de um sinal PWM de frequência constante. Um LED RGB WS2812 codifica cada bit como um pulso de largura específica; um sensor de distância ultrassônico HC-SR04 responde com um pulso de eco cuja largura é o tempo de voo de ida e volta; um controle remoto IR envia um cabeçalho seguido de bits de dados como sequências de liga-desliga.

O módulo machine tem duas funções para esse tipo de GPIO com temporização precisa:

  • bitstream() envia um trem de pulsos com temporizações separadas para os bits 0 e 1.

  • time_pulse_us() mede a largura de um pulso de entrada em microssegundos.

3.17.1. Enviando um fluxo de bits

machine.bitstream() recebe um pino, uma codificação, uma especificação de temporização e os bytes a enviar. A codificação mais comum (0) é a modulação por duração de pulso alto-baixo: cada bit é um pulso alto de uma largura seguido de um pulso baixo de outra, com os bits 0 e 1 tendo temporizações distintas.

Duas formas de onda empilhadas. A de cima mostra um bit 0: um breve pulso alto (high_0) seguido de um período baixo mais longo (low_0). A de baixo mostra um bit 1: um pulso alto mais longo (high_1) seguido de um período baixo mais curto (low_1).

A codificação por duração de pulso alto-baixo: um 0 e um 1 consistem cada um de uma fase alta seguida de uma fase baixa, com larguras distintas.

O exemplo canônico é o LED RGB WS2812 (NeoPixel), que espera bits a 800 kHz com estas temporizações:

  • 0: 400 ns em alto, depois 850 ns em baixo.

  • 1: 800 ns em alto, depois 450 ns em baixo.

from machine import Pin, bitstream

pin = Pin("P7", Pin.OUT)

# (high_0, low_0, high_1, low_1) in nanoseconds
timing = (400, 850, 800, 450)

# one LED: GRB order, three bytes per LED (red shown here)
bitstream(pin, 0, timing, b"\x00\xff\x00")

O MCU gera os pulsos por bit-banging nas larguras solicitadas; em câmeras rápidas o suficiente para isso, a temporização é precisa dentro de dezenas de nanossegundos.

Aviso

bitstream() desabilita todas as interrupções durante toda a transmissão para poder manter o controle preciso da temporização dos pulsos. A duração da chamada escala linearmente com o número de bytes – na temporização do WS2812 (cerca de 10 µs por byte), enviar 100 bytes pausa a CPU por cerca de 1 ms, 1000 bytes por 10 ms e 10000 bytes por 100 ms completos. Qualquer coisa além de algumas centenas de bytes por chamada arrisca travamentos perceptíveis – divida atualizações longas em pedaços menores, com a chamada retornando entre cada pedaço para que o restante do script possa rodar.

Nota

Para acionar fitas WS2812 / NeoPixel em particular, o módulo neopixel envolve bitstream() em uma interface de mais alto nível que cuida do embaralhamento da ordem das cores e das atualizações em massa da fita. Recorra a bitstream() diretamente quando o protocolo não for WS2812, mas ainda assim couber em um formato PDM alto-baixo.

3.17.2. Medindo um pulso de entrada

machine.time_pulse_us() mede a largura de um único pulso em um pino. Ela aguarda a linha atingir o nível especificado (o início do pulso), depois mede por quanto tempo a linha permanece naquele nível (o fim do pulso) e retorna a duração em microssegundos.

O uso clássico é um sensor de distância ultrassônico HC-SR04. A câmera envia um pulso de disparo de 10 µs, depois aguarda que o pino de eco retorne um pulso cuja largura é o tempo de ida e volta do som:

import time
from machine import Pin, time_pulse_us

trigger = Pin("P7", Pin.OUT, value=0)
echo    = Pin("P8", Pin.IN)

while True:
    trigger.value(1)
    time.sleep_us(10)
    trigger.value(0)

    width = time_pulse_us(echo, 1, timeout_us=30_000)
    if width > 0:
        # sound at ~343 m/s; round trip / 2 / 343 = distance (m)
        distance_cm = (width * 343) / 2 / 10_000
        print(distance_cm, "cm")

    time.sleep_ms(100)

O terceiro argumento é o timeout em microssegundos, aplicado separadamente a “aguardar o início do pulso” e “aguardar o fim do pulso”. No timeout, a função retorna um valor negativo identificando qual fase falhou: -2 se o pulso nunca começou, -1 se ele começou mas nunca terminou dentro da janela.

Ambas as metades do timeout importam para sensores reais. Um HC-SR04 pode levar de um a dois milissegundos para iniciar seu eco, e o próprio eco pode ter dezenas de milissegundos de duração para objetos distantes. Dimensionar timeout_us para o alcance máximo necessário mantém a medição limitada.