3.1. Microcontroladores¶
A OpenMV Cam funciona sobre um microcontrolador (MCU): um único chip que combina uma CPU, memória de trabalho (RAM), armazenamento de programa (flash) e um conjunto de periféricos – blocos de hardware para interagir com o mundo externo.
Os periféricos são a parte interessante. Cada um é um trecho de silício dedicado a uma única tarefa: colocar um pino em nível alto ou baixo, medir uma tensão analógica, transmitir bytes por um barramento serial. A CPU configura e lê cada periférico por meio de registradores – endereços de memória fixos que o hardware monitora e atualiza.
O MicroPython encapsula esses registradores em classes dentro do módulo machine. machine.Pin(...) retorna um objeto que controla um pino de entrada/saída de uso geral (GPIO) – um fio que o chip pode manter em nível alto (cerca de 3,3 V) ou baixo (cerca de 0 V), ou ler como um desses dois estados quando algo externo o aciona. machine.ADC(...) expõe o conversor analógico-digital, que mede a tensão em um pino e a reporta como um número. machine.UART(...) opera um transmissor/receptor assíncrono universal (UART) – um periférico que envia e recebe bytes um bit por vez sobre um par de fios, TX (transmissão) e RX (recepção). Outras classes cobrem o restante dos periféricos. O script lê e escreve objetos Python; o MicroPython traduz cada acesso nas leituras e escritas de registrador correspondentes, e essas movimentam bits em fios físicos.
Um MCU agrupa CPU, memória e periféricos em um único chip. Cada periférico é exposto ao Python por uma classe no módulo machine.¶
3.1.1. O laço principal¶
Quase todo programa de microcontrolador compartilha o mesmo formato: uma configuração única no topo do script (importar módulos, configurar pinos, abrir barramentos) e, em seguida, um laço infinito while True: na parte de baixo. Dentro do laço, o programa lê entradas, toma decisões e atualiza saídas repetidamente. O laço é o programa; quando o script termina, o dispositivo para de fazer qualquer coisa.
# setup, runs once
from machine import Pin
led = Pin("P0", Pin.OUT)
# main loop, runs forever
while True:
led.value(1)
# ... do work ...
led.value(0)
# ... do other work ...
Esse formato – configurar uma vez e depois iterar para sempre – é o padrão do laço principal. Tudo o que vem a seguir trata do que vai dentro dele.
3.1.2. Controle em tempo real¶
Um programa de desktop é executado ao lado de muitos outros. O sistema operacional distribui seu trabalho entre uma ou mais threads – fluxos de execução independentes entre os quais ele alterna milissegundo a milissegundo. Quando uma thread aguarda E/S (disco, rede, o usuário movendo o mouse), o SO entrega a CPU a outra. O programa é em grande parte orientado a eventos: o gerenciador de janelas chama o seu código quando uma entrada chega, a biblioteca HTTP retoma o seu código quando bytes chegam no socket. Algo maior está chamando você.
Um programa de microcontrolador é o oposto. Por padrão, não há sistema operacional, nem escalonador, nem outra thread. O laço principal que acabamos de mostrar é o único laço. Os periféricos disparam interrupções ou expõem flags de status; o laço os consulta (polling) ou trata as interrupções diretamente. Se o laço travar em um time.sleep_ms(1000), o dispositivo não faz nada durante aquele segundo; não há outra thread para preencher a lacuna.
Duas consequências decorrem disso e se aplicam em todo lugar:
O tempo é real. Ler um pino duas vezes em um laço apertado leva microssegundos; dormir por dez milissegundos significa dez milissegundos em que nada mais acontece. O padrão de temporização não bloqueante é a resposta.
O hardware é real. Definir
machine.Pin.valuecomo1coloca aproximadamente 3,3 V em um fio físico; defini-lo como0coloca aproximadamente 0 V ali. Outras partes do circuito enxergam essa tensão imediatamente – inclusive quaisquer componentes que o pino pode danificar se for acionado de forma incorreta.