Porta série e UART
Tramas assíncronas, registos UART, DLAB, interrupções, FIFOs e filas.
Para enviar MOVE +5, a aplicação prepara bytes e a UART converte-os numa sequência de bits. A UART reconstrói os bytes recebidos, mas não lhes atribui o significado de movimento nem delimita a mensagem; essa estrutura pertence ao protocolo, definido em Protocolos de comunicação. Em 2025/26, a série é matéria extra do projeto.
Comunicação assíncrona
Em transmissão série, os bits seguem um após outro. Em comunicação assíncrona, emissor e recetor usam relógios independentes e combinam previamente a duração de cada bit. O start bit permite ao recetor localizar o início de cada carácter e amostrar os bits seguintes com o seu relógio.
Na configuração 7E1, há sete bits de dados, paridade par e um stop bit. Somando o start bit, cada carácter ocupa dez bits. Na configuração 8N1, há oito dados e nenhuma paridade, também dez bits no total. O número de bits úteis não é sempre o número de bits transmitidos.
A taxa de bits mede bit/s; a taxa de símbolos mede baud. Nesta linha binária há um bit por símbolo, por isso os valores coincidem, mas essa igualdade não vale para todas as comunicações. Em 7E1, cada carácter só transporta sete bits úteis, mesmo que a API o guarde num byte de C.
Em 8N1 a 9600 bit/s, o máximo ideal é bytes de dados por segundo, ou seja, uma mensagem de 20 bytes ocupa pelo menos ms, sem pausas entre caracteres nem bytes de controlo da aplicação.
A paridade par faz o total de bits a 1 nos dados e na paridade ser par. Deteta um número ímpar de inversões nesses bits, mas duas inversões conservam a paridade. Não substitui a validação de framing ou do conteúdo da mensagem.
Simplex só comunica num sentido. Half-duplex comunica nos dois sentidos, mas um de cada vez. Full-duplex permite transmissão nos dois sentidos em simultâneo. Ter uma UART não determina sozinho o protocolo de turnos da aplicação.
Registos e endereços
COM1 tem base 0x3F8 e IRQ4; COM2 tem base 0x2F8 e IRQ3. Os offsets são relativos à base:
| Offset | Leitura | Escrita |
|---|---|---|
| 0 | RBR, byte recebido | THR, byte a transmitir |
| 1 | IER, interrupções ativas | IER |
| 2 | IIR, causa de interrupção | FCR, controlo das FIFOs |
| 3 | LCR, parâmetros da trama | LCR |
| 4 | MCR, controlo de modem | MCR |
| 5 | LSR, estado da linha | Só leitura no modelo 16550 clássico |
| 6 | MSR, estado de modem | Só leitura no modelo 16550 clássico |
| 7 | Scratch register | Scratch register |
Os offsets 0 e 1 têm outra interpretação quando DLAB, bit 7 de LCR, vale 1: tornam-se DLL e DLM, os bytes baixo e alto do divisor. Antes de receber ou transmitir dados, garante DLAB a 0.
LCR e divisor
Os bits 1 a 0 de LCR selecionam 5, 6, 7 ou 8 bits de dados, por códigos 00 a 11. O bit 2 seleciona um stop bit quando vale 0; quando vale 1 seleciona dois, ou 1,5 se houver cinco bits de dados. O bit 3 ativa paridade e os bits seguintes escolhem o tipo.
Para 8N1, LCR sem DLAB vale 0x03. Para 7E1, vale 0x1A: código 2 para sete dados, bit 3 para paridade e bit 4 para paridade par.
Com o relógio nominal da UART clássica, a taxa de bits é:
A 9600 bit/s, . A sequência é ler e conservar LCR, ativar DLAB, escrever DLL no offset 0 e DLM no offset 1, e limpar DLAB com a configuração de trama pretendida. Para 7E1, poderias escrever LCR 0x9A, DLL 0x0C, DLM 0x00 e finalmente LCR 0x1A.
Enquanto DLAB estiver a 1, escrever em base + 0 altera o divisor, não transmite um carácter, por isso deixar DLAB ativo explica “enviei, mas não chegou nada”. A taxa pedida deve ter divisor representável e não nulo; quantifica o erro quando não divide 115200 exatamente.
LSR e polling
| Bit de LSR | Significado |
|---|---|
| 0 | Há dados recebidos |
| 1 | Overrun, houve perda por falta de espaço |
| 2 | Erro de paridade |
| 3 | Erro de framing, como stop bit inválido |
| 4 | Break recebido |
| 5 | THR vazio, pode aceitar outro carácter |
| 6 | THR e registo de deslocamento vazios |
| 7 | Erro associado à FIFO, conforme o modelo |
Para transmitir, espera bit 5 e escreve THR, porque o bit 6 só é necessário quando queres saber se o último carácter já saiu fisicamente, por exemplo antes de fechar a ligação. Para enviar 0x41, confirma primeiro DLAB a 0, espera até o bit 5 de LSR ficar a 1, escreve 0x41 em THR e só consultas o bit 6 no fim, quando precisares de garantir que a linha ficou livre.
Para receber, verifica bit 0 e erros, lê RBR e aplica a política do protocolo aos bytes inválidos. Usa os efeitos de leitura especificados para a UART concreta; uma leitura de estado pode limpar alguns flags. Guarda o estado antes de o voltares a ler.
Polling com espera ilimitada pode prender o programa se o outro processo nunca enviar o byte esperado. Usa um timeout e distingue ausência de dado, falha da chamada Minix e erro reportado pela UART.
IER e IIR
IER ativa causas: bit 0 para receção, bit 1 para THR vazio, bit 2 para estado de receção e bit 3 para estado do modem. Ativar uma causa na UART e subscrever a IRQ no Minix são passos diferentes, ambos necessários.
No IIR, bit 0 a 1 significa que não há interrupção pendente. Se estiver a 0, os bits de identificação indicam a causa mais prioritária. Os valores usuais, depois de aplicar a máscara 0x0E, são:
| Identificador | Causa |
|---|---|
0x06 | Estado da linha de receção |
0x04 | Dados recebidos |
0x0C | Timeout de caracteres na FIFO |
0x02 | THR vazio |
0x00 | Estado de modem |
O handler consulta IIR, atende a causa pela operação exigida e volta a consultar até não haver causas pendentes, dentro de uma política que evite monopolizar o ciclo. No caso de receção, retira os bytes disponíveis para uma fila. No caso de erro, lê o estado necessário e informa o nível superior.
Para transmissão por interrupções, mantém uma fila de saída. Quando a fila recebe o primeiro byte após um período inativo, inicia a transmissão se THR estiver pronto e ativa a causa apropriada. Quando não há mais bytes, evita deixar interrupções de transmissão inúteis ativas. Esperar apenas por uma futura interrupção pode deixar o primeiro byte na fila sem arrancar.
FIFO e fila da aplicação
A FIFO de hardware absorve pequenos atrasos. No modelo 16550 com FIFO de 16 bytes, FCR bit 0 ativa as FIFOs; bits 1 e 2 pedem limpeza de receção e transmissão; bits 7 a 6 escolhem limiares de receção 1, 4, 8 ou 14 bytes. Esses limiares não são a capacidade da fila da aplicação.
Um limiar alto pode reduzir o número de interrupções, mas aumenta a espera dos primeiros bytes. Se a mensagem for menor do que o limiar, a causa de timeout de caracteres permite tratar os bytes sem esperar indefinidamente que a FIFO encha.
A fila em software separa leitura do dispositivo e parsing de mensagens. Um único char received perde valores se o handler ler vários bytes antes de a aplicação os consumir. Numa fila circular, conserva cabeça, cauda e quantidade ou uma convenção inequívoca para distinguir cheio de vazio.
Se a fila encher, define uma política: descartar com sinalização de perda, pausar o produtor se o protocolo o permitir ou aumentar capacidade depois de medir a carga. Parar de ler a UART não impede o outro lado de transmitir e pode causar overrun.
Verificar a comunicação
No ambiente da cadeira, duas máquinas virtuais podem ligar as portas série através de sockets. Verifica primeiro bytes conhecidos num sentido e depois no outro. A ligação virtual pode não modelar todos os efeitos elétricos ou de temporização de uma ligação física; não uses esse resultado para dispensar parâmetros corretos numa UART real.
O Minix pode já ter um driver para a porta. Em modo de interrupções, usa a subscrição exclusiva prevista pelo ambiente para evitar consumo concorrente. Ao terminar, desativa as causas que ativaste na UART, repõe parâmetros alterados quando aplicável e remove a subscrição. Como esses bytes se tornam mensagens recuperáveis é o tema de Protocolos de comunicação.
Exercícios
Contar bits por carácter
Uma UART usa 19200 bit/s em 7E1. Qual é a taxa máxima ideal de caracteres de dados por segundo, sem pausas? Conta os bits de cada carácter antes de dividir.
Pista
Solução
Cada carácter ocupa dez bits e a taxa é 19200/10 = 1920 caracteres por segundo. Cada carácter contém sete bits úteis. O protocolo da aplicação pode reduzir ainda mais a taxa de payload.
Erros frequentes
Dividir apenas por sete ou confundir bytes úteis da aplicação com toda a transmissão.
Escrever no registo certo
COM1 tem LCR = 0x83. O programa escreve 0x41 em 0x3F8 esperando enviar a letra A. O que alterou e o que devia fazer?
Pista
Solução
Alterou DLL, o byte baixo do divisor, porque DLAB está a 1. Devia limpar DLAB mantendo a configuração 8N1, por exemplo LCR = 0x03, verificar LSR.THRE e só depois escrever 0x41 em THR. O divisor pode agora precisar de reposição.
Confere a tua resposta:
- Identifiquei DLL no offset 0 com DLAB ativo.
- Limpei DLAB antes de escrever dados.
- Preservei os parâmetros da trama.
Erros frequentes
Interpretar qualquer escrita no endereço base como transmissão, sem atender ao estado de LCR.
Despejar causas pendentes da UART
Uma notificação UART leva o handler a ler IIR. As leituras sucessivas,
intercaladas com o atendimento correto de cada causa, dão 0xC4, 0xC2
e 0xC1. Há bytes disponíveis para receber e um byte na fila de transmissão.
Que ações fazes e quando paras? Um handler que atende apenas a primeira
causa trata toda esta notificação? Usa o bit 0 e a máscara 0x0E.
Pista
Solução
Em 0xC4, o bit 0 é zero e a identificação é 0x04: recolhe os dados
recebidos para a fila. Depois volta a consultar IIR.
Em 0xC2, ainda há pendência e a causa 0x02 é THR vazio: envia o byte da
fila e aplica a política de ativação de interrupções de transmissão.
Volta a consultar IIR.
Em 0xC1, o bit 0 vale 1. Para, pois não há causa pendente. Não interpreta
0xC1 & 0x0E = 0 como uma nova causa de modem. Atender só 0xC4 deixaria
por tratar a transmissão que a consulta seguinte revela.
Confere a tua resposta:
- Atendi receção em 0xC4 e THR vazio em 0xC2.
- Voltei a ler IIR após cada atendimento.
- Parei em 0xC1, sem despachar uma falsa causa de modem.
Estimar quando se perdem dados
Uma FIFO de receção de 16 bytes começa vazia. A UART recebe sem pausas a 115200 bit/s em 8N1. Durante 2 ms o programa não retira nenhum byte. A FIFO chega para evitar overrun? Ignora outros buffers e assume que o primeiro start bit começa no início desse intervalo.
Pista
Solução
Completam-se 23 caracteres em 2 ms, mas só cabem 16. O décimo sétimo termina após 170/115200 s, cerca de 1,476 ms. Se nenhum byte sair da FIFO, já ocorre overrun antes dos 2 ms. Uma fila de software só ajuda quando o handler corre e transfere os bytes para ela.
Confere a tua resposta:
- Contei 23 caracteres completos.
- Comparei com capacidade 16.
- Localizei perda antes de 2 ms e distingui FIFO de fila.
Erros frequentes
Contar capacidade da fila de software como se o hardware a preenchesse sozinho, ou dividir por oito em 8N1.
Distinguir THR vazio de linha livre
Queres enviar um carácter e, no fim, garantir que a linha ficou livre antes de fechar. Que bits de LSR consultas em cada momento?
Pista
Solução
Espera pelo bit 5 para escrever em THR. No fim, espera pelo bit 6, que indica THR e registo de deslocamento vazios. Guarda LSR numa variável antes de testar, porque voltar a ler pode limpar flags.
Confere a tua resposta:
- Esperei bit 5 para escrever o carácter.
- Usei bit 6 só para o fim da transmissão.
- Guardei LSR antes de decidir.
Erros frequentes
Esperar só pelo bit 5 antes de fechar ou ler LSR duas vezes para a mesma decisão.