Falar com o hardware
Mapeamento em memória, registos e polling com máscaras de bits.
Nesta página
Um periférico, visto do programa, é um conjunto de registos indentados por endereços: escreves num endereço para lhe dar ordens e lês de outro para saber o estado e receber dados. A técnica chama-se mapeamento em memória (memory-mapped I/O), e dominá-la é falar com o hardware. Tudo o resto da cadeira é variação sobre este tema com registos diferentes.
Registos e portas
Cada periférico expõe alguns endereços. Por convenção, uns são de estado (dizem o que se passa), outros de dados (transportam os bytes) e outros de controlo (configuram o comportamento). Lês o estado, decides, e só depois tocas nos dados ou no controlo. Nunca escrevas num registo sem saber que bits estás a mexer: muitos registos misturam campos independentes no mesmo byte, e escrever o byte inteiro pode mudar um campo que querias intacto.
A leitura e a escrita fazem-se com funções de acesso a portas, que recebem o endereço e o valor. O padrão de cada operação é sempre o mesmo: máscara para isolar o campo, comparação para decidir, escrita cirúrgica para atuar.
Máscaras de bits
Uma máscara é um valor com 1s exatamente nas posições que te interessam, combinada com o registo através de AND para leitura ou OR para escrita:
uint8_t status = ler_registo(REG_ESTADO);
if (status & BIT_PRONTO) {
uint8_t dado = ler_registo(REG_DADOS);
/* processar o byte recebido */
}
BIT_PRONTO vale 1 << 0 se o bit de pronto for o bit 0. O teste status & BIT_PRONTO é diferente de zero quando o bit está ligado, seja qual for o resto do byte. Para ligar um bit sem tocar nos outros, usa OR: controlo | BIT_ATIVAR. Para desligar, usa AND com o complemento: controlo & ~BIT_ATIVAR. Estes três gestos, AND para testar, OR para ligar, AND com NOT para desligar, resolvem quase todas as manipulações de registos da cadeira.
Um exemplo concreto: o registo de estado vale 0x21, ou seja 0b00100001. Se o bit de pronto for o bit 0, 0x21 & 0x01 dá 0x01, diferente de zero, por isso há dado à espera. Se o bit de erro for o bit 5, 0x21 & 0x20 dá 0x20, por isso há também um erro assinalado. Dois testes independentes no mesmo byte, cada um cego ao outro campo.
Polling: o ciclo de espera
O polling pergunta ao dispositivo em ciclo até ele estar pronto. Se já viste a comparação entre mecanismos em Entrada e saída, aqui está a implementação:
while (!(ler_registo(REG_ESTADO) & BIT_PRONTO)) {
/* espera ativa: o processador não faz mais nada */
}
uint8_t dado = ler_registo(REG_DADOS);
Repara nas duas leituras separadas: o estado lê-se dentro do ciclo, os dados lêem-se uma vez fora dele. Ler os dados dentro do ciclo consumiria bytes antes de os processares. E repara que o ciclo não tem corpo: a espera ativa é propositada, mas é também o preço, porque o processador fica preso aqui até o dispositivo responder.
Para levar para a próxima página
O polling funciona e é simples, mas ata o processador ao ritmo do periférico mais lento. A página sobre Interrupções inverte a relação: em vez de perguntares sem parar, subscreves o evento e segues a tua vida até o hardware te chamar.