UART Autobaud em VHDL

UART autobaud em VHDL

Olá leitor! Neste artigo eu demonstro como implementar uma UART autobaud em VHDL. A sigla UART significa Universal Asynchronous Receiver Transmitter (ou Transmissor Receptor Assíncrono Universal) e nada mais é do que a interface de comunicação serial assíncrona comumente utilizada em microcontroladores para comunicação com equipamentos externos (como GPS). Por sua natureza assíncrona, a comunicação serial utilizando UARTs necessita que tanto o transmissor quanto o receptor estejam configurados para trabalhar na mesma velocidade de comunicação (a chamada baud rate). Uma UART autobaud é um tipo especial de UART que pode se sincronizar automaticamente com o transmissor remoto, adaptando a sua velocidade de comunicação à velocidade de comunicação do transmissor. O código apresentado neste artigo foi escrito especificamente para utilização no meu softcore FPz8, mas poderá ser facilmente adaptado para outras aplicações.

 

 

Princípios da comunicação assíncrona

 

Antes de nos aprofundarmos na construção da nossa UART, é importante entender alguns conceitos básicos sobre a comunicação assíncrona. Primeiramente, vale destacar que a denominação "assíncrona" vem da ausência de sincronismo real entre os dispositivos em comunicação. Isto vem se opor à comunicação síncrona utilizada em protocolos como I2C, SPI, etc, onde há um sinal de sincronismo (uma linha de relógio ou clock) que garante a sincronização entre os dispositivos durante a comunicação.

 

Pois bem, em não havendo um sinal de sincronismo, a comunicação assíncrona utiliza um padrão especial para sincronizar os dispositivos e delimitar um novo dado. Este padrão caracteriza-se por uma transição de marca (linha de comunicação em estado de repouso, normalmente nível lógico "1") para espaço (linha de comunicação em estado ativo, normalmente nível lógico "0"). Os bit são enviados em sequência, sendo que cada um possui uma duração fixa e que usualmente segue uma das velocidades padronizadas (300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600 ou 115200 bps).

 

O primeiro bit é chamado de bit de partida (start bit) e consiste sempre num espaço (nível lógico "0"), seguido por bits de dados (normalmente 8 e iniciando-se pelo bit menos significativo) e por um bit de parada (stop bit) que deve ser sempre uma marca (nível lógico "1"). Note que a linha, quando em estado inativo, deve permanecer sempre em marca (nível lógico alto). A figura 1 mostra um típico quadro assíncrono comumente conhecido como 8N1 (8 bits de dados, nenhum bit de paridade e 1 bit de parada).

 

Um típico quadro assíncrono
Figura 1 - Um típico quadro assíncrono

 

Observação: é possível inverter os níveis de comunicação serial, fazendo com que marca seja um nível lógico "0" e espaço um nível lógico "1". Neste caso, a partida é sinalizada pela transição de subida da linha!

 

 

Implementação em VHDL

 

A implementação de uma UART é relativamente simples, sendo necessário basicamente dois registradores de deslocamento além de timers para a transmissão e para a recepção de dados. De maneira geral, a transmissão é mais simples de implementar, pois tudo o que é necessário é (em termos de eletrônica digital) um registrador de deslocamento com entrada paralela e saída serial e um sinal de clock na velocidade de comunicação desejada.

 

No caso da recepção, o processo é um pouco mais complicado, pois é necessário amostrar o sinal preferencialmente na metade do período de cada bit. Por esta razão, nossa implementação utiliza dois tempos: um bit e meio bit. Após a detecção da borda de descida (de marca para espaço), o receptor aguarda meio tempo de bit e então verifica se a linha está em "0", caracterizando um bit de partida. Caso positivo, a cada tempo de bit completo a entrada é amostrada e o sinal é armazenado num registrador (em eletrônica digital seria um registrador de deslocamento com entrada serial e saída paralela). Após a amostragem dos oito bits de dados, o receptor verifica se o próximo bit está em nível "1", caracterizando um bit de parada. Caso positivo o dado lido é salvo num registrador, caso contrário, ocorreu um erro de recepção, o qual pode ter sido ocasionado por diversos fatores como: velocidades diferentes entre o transmissor e o receptor, ruído, falha de sincronização ou a recepção de um caractere break (caracterizado por manter a linha em nível "0" por um tempo superior a 10 tempos de bit). A figura 2 ilustra a sequencia de amostragens do sinal.

 

Amostras do sinal serial tomadas pelo receptor
Figura 2 - Amostras do sinal serial tomadas pelo receptor

 

A UART do FPz8 utiliza dois tipos de dados definidos pelo usuário para especificar o estado do transmissor (Tdbg_uarttxstate) e receptor (Tdbg_uartrxstate) da UART. Além disso, uma estrutura chamada Tdbg_uart contém todos os registradores utilizados pela UART.

 

Os estados possíveis para o receptor da UART são:

 

Os estados possíveis para o transmissor da UART são:

 

A seguir temos a estrutura com os registradores utilizados pela UART. Estão presentes dois registradores para controlar o estado do transmissor e do receptor, registradores utilizados para o deslocamento dos dados, contadores para controlar o número de bits enviados/recebidos, contadores para o controle da velocidade de transmissão e de recepção, além de flags para sinalização de estado da UART.

 

Observe que o bit WRT e a variável SIZE são específicos para a utilização no sistema de depuração do FPz8 e não seriam implementados no uso geral da UART. Note também que o flag RX_DONE é utilizado para sinalizar que um novo dado foi recebido e está disponível no registrador RX_DATA. O bit TX_EMPTY, por sua vez, sinaliza que o registrador de deslocamento de transmissão está vazio e que a aplicação pode escrever um novo dado a ser transmitido utilizando o registrador TX_DATA. Não há proteção contra buffer overrun, ou seja, se um novo dado for recebido sem que o anterior tenha sido lido, o dado anterior é descartado. O mesmo ocorre com o buffer de transmissão. Caso o leitor deseje, a implementação destes controles é simples e pode ser feita sem muito esforço.

 

Os registradores BITTIMETX e BITTIMERX controlam a duração dos bits (BITTIMETX armazena a duração de um bit completo ao passo que BITTIMERX armazena a duração de meio tempo de bit).

 

O código do transmissor é bastante simples e está apresentado na listagem abaixo.

 

No caso do receptor, existem alguns detalhes que valem a pena comentar:

  1. A entrada DBG_RX, por sua natureza assíncrona (relativamente ao FPGA), deve obrigatoriamente ser amostrada através de um ou dois flip-flops (nós utilizamos dois) de forma a garantir que a sua amostragem seja feita em perfeita sincronia com o clock do sistema. Coisas absolutamente estranhas podem acontecer se você não sincronizar entradas externas com o clock interno! A razão disso não é óbvia mas também não é muito difícil de entender: FPGAs são como blocos de montar lógicos, mas não implementam necessariamente um circuito lógico digital da forma como conhecemos. De fato, como FPGAs são constituídos de blocos lógicos (que normalmente incluem uma tabela de pesquisa ou look up table - LUT e um registrador) e de uma matriz de interconexão entre estes blocos, o resultado final é que mesmo circuitos lógicos mais simples podem utilizar diversos caminhos e blocos, resultando em diferentes atrasos e fazendo com que um sinal assíncrono possa ser interpretado de diversas formas conforme o arranjo lógico interno no FPGA. Um sinal assíncrono injetado num arranjo lógico complexo de um FPGA pode resultar nos mais diversos efeitos, inclusive levando o circuito a estados "impossíveis"! Por esta razão, é muito importante sincronizar os sinais externos antes de aplicá-los ao circuito lógico da sua aplicação. No caso em tela, a entrada DBG_RX é amostrada em RXSYNC2 que por sua vez é amostrada em RXSYNC1 que é o sinal efetivamente lido pela UART.
  2. A detecção do bit de partida consiste essencialmente na detecção de uma borda de descida na entrada de recepção da UART. Para isso, utilizamos um flip-flop adicional responsável por armazenar o estado anterior do sinal recebido. Assim, a comparação do sinal atual com o sinal anterior permite detectar facilmente uma borda de descida (ou subida se fosse o caso)! O "if" responsável por isso está dentro do estado DBGST_IDLE no código a seguir.

 

 

 

Detecção automática da velocidade

 

Por fim, antes de encerrarmos, ainda falta explicar como ocorre a detecção automática da velocidade! Pois bem, a UART aqui apresentada utiliza o caractere 0x80 como caractere de sincronização. Isto significa que após um reset ou após receber um BREAK, o primeiro dado que o transmissor remoto (aquele que está enviando dados para esta UART) deverá enviar é um caractere 0x80. A nossa UART irá medir o período do sinal e calcular a velocidade de comunicação a partir do mesmo! Mas por que 0x80?

 

Muitas UARTs dotadas da funcionalidade de autobaud utilizam caracteres como 0x55 (pois o mesmo consiste numa sequência alternada de "0s" e "1s"), mas a Zilog (no caso do Z8 encore que foi a base do FPz8) escolheu o caractere 0x80, uma vez que ele consiste numa grande sequência de "0s" (de fato, o caractere 0x80 consiste numa sequência de oito zeros, se incluirmos o start), o que inclusive facilita a medição do período e aumenta a precisão final!

 

Sendo assim, após um reset a UART vai automaticamente para o estado DBGST_NOSYNC e em seguida para DBGST_WAITSTART onde ela aguarda por uma borda de descida (o início do suposto caractere de sincronização). Ao detectar esta borda, o contador BAUDCNTRX é zerado e a UART passa para o estado de medição, aguardando que a linha de comunicação retorne ao nível lógico alto. Feito isso, o registrador BITTIMETX recebe a contagem dividida por 8, ou seja, o tempo de cada bit. E o registrador BITTIMERX recebe a contagem dividida por 16, ou seja, o tempo de meio bit! Após isso a UART está configurada para a velocidade correta e somente retornará ao modo de medição através de um reset ou se receber um caractere BREAK (ou seja, se a linha de comunicação permanecer em nível lógico "0" por mais do que 10 vezes o tempo de bit)!

 

Por hora é isso, o código completo desta UART está dentro do projeto do FPz8, espero que ela possa servir de inspiração para qualquer um que esteja precisando ou estudando UARTs!

 

 

Referências

 

FPz8

NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Fábio Pereira
Técnico em eletrônica, advogado, pós-graduado em projetos eletrônicos, autor de 9 livros na área de programação de microcontroladores (1 em inglês), entusiasta de eletrônica e computação. Desenvolvimento em diversas plataformas de 8, 16 e 32 bits, desde microcontroladores até desktop e celulares, utilizando C, C#, Java, Pascal, PHP dentre outras. Curte rock'n roll e cerveja. Atualmente residindo em Kitchener, ON, Canada, é também mantenedor do Embedded Systems Blog em http://embeddedsystems.io

Deixe um comentário

avatar
 
  Notificações  
Notificar