3.1. Microcontroladores¶
A OpenMV Cam funciona num microcontrolador (MCU): um único chip que combina uma CPU, memória de trabalho (RAM), armazenamento de programas (flash) e um conjunto de periféricos – blocos de hardware para interagir com o mundo exterior.
Os periféricos são a parte interessante. Cada um é uma peça de silício dedicada a uma única tarefa: colocar um pino a alto ou baixo, medir uma tensão analógica, enviar bytes por um barramento série. A CPU configura e lê cada periférico através de registos – endereços de memória fixos que o hardware monitoriza e atualiza.
O MicroPython encapsula esses registos em classes dentro do módulo machine. machine.Pin(...) devolve um objeto que controla um pino de entrada/saída de uso geral (GPIO) – um fio que o chip pode manter a alto (cerca de 3,3 V) ou a 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 num pino e a reporta como um número. machine.UART(...) executa um transmissor/recetor assíncrono universal (UART) – um periférico que envia e recebe bytes um bit de cada vez através de um par de fios, TX (transmissão) e RX (receção). Outras classes cobrem os restantes periféricos. O script lê e escreve objetos Python; o MicroPython traduz cada acesso nas leituras e escritas de registo correspondentes, e essas movem bits em fios físicos.
Um MCU agrupa CPU, memória e periféricos num único chip. Cada periférico é exposto ao Python por uma classe no módulo machine.¶
3.1.1. O ciclo principal¶
Quase todos os programas de microcontrolador partilham a mesma estrutura: configuração única no início do script (importar módulos, configurar pinos, abrir barramentos), depois um ciclo infinito while True: no final. Dentro do ciclo, o programa lê entradas, toma decisões e atualiza saídas repetidamente. O ciclo é o programa; quando o script termina, o dispositivo deixa 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 ...
Esta estrutura – configurar uma vez, depois ciclar para sempre – é o padrão do ciclo principal. Tudo o que se segue diz respeito ao que vai dentro dele.
3.1.2. Controlo em tempo real¶
Um programa de ambiente de trabalho funciona ao lado de muitos outros. O sistema operativo agenda o seu trabalho em uma ou mais threads – fluxos de execução independentes entre os quais alterna milissegundo a milissegundo. Quando uma thread aguarda I/O (disco, rede, o utilizador a mover o rato), o SO entrega a CPU a outra. O programa é maioritariamente orientado a eventos: o gestor de janelas chama o seu código quando chega um input, a biblioteca HTTP retoma o seu código quando chegam bytes no socket. Algo maior está a chamá-lo.
Um programa de microcontrolador é o oposto. Por defeito não há sistema operativo, não há agendador e não há outra thread. O ciclo principal mostrado anteriormente é o único ciclo. Os periféricos disparam interrupções ou expõem flags de estado; o ciclo faz polling ou trata as interrupções diretamente. Se o ciclo ficar parado num time.sleep_ms(1000), o dispositivo não faz nada durante esse segundo; não há outra thread para preencher o vazio.
Duas consequências surgem e aplicam-se em todo o lado:
O tempo é real. Ler um pino duas vezes num ciclo apertado demora microssegundos; dormir dez milissegundos significa dez milissegundos durante os quais mais nada acontece. O padrão de temporização não bloqueante é a resposta.
O hardware é real. Definir
machine.Pin.valuepara1coloca aproximadamente 3,3 V num fio físico; defini-lo para0coloca aproximadamente 0 V. Outras partes do circuito veem essa tensão imediatamente – incluindo quaisquer componentes que o pino pode danificar se for acionado incorretamente.