Olá leitor! Neste artigo (publicado originalmente no portal Embarcados) 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 e 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).

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.

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 todas as variáveis (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 as variáveis (registradores) utilizados pela UART. Estão presentes duas variáveis 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 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 e do 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 demonstrada utiliza o caractere 0x80 para a 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 será 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 porquê 0x80? Muitas UARTs dotadas da funcionalidade de autobaud utilizam caracteres como 0x55 (pois o mesmo consiste numa sequencia 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 sequencia de “0s” (de fato, o caractere 0x80 consiste numa sequencia 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

UART Autobaud em VHDL
Classificado como:                

5 ideias sobre “UART Autobaud em VHDL

  • 8 de maio de 2017 em 4:25 pm
    Permalink

    Boa tarde Fabio,

    primeiramente meus parabens pelos artigos e implementacoes feitas com logica programavel. Tambem sou usuario e entusiasta. Fabio o motivo do contato tambem eh para verificar se chegaste a desenvolver e implementar codigo VHDL ou Verilog para codificador e decodificador de IR como padrões RC5, RC6 Sony e outros? Estou iniciando desenvolvimento de um gateway IR e caso possa indicar fontes ajudaria em muito.

    Um abraço e obrigado!

    Fernando Eduardo Schmitz

    • 8 de maio de 2017 em 5:50 pm
      Permalink

      Olá Fernando!

      Obrigado pelo elogio! Pois é lógica programável é um assunto muito interessante e permite dar asas a imaginação! Olha, sobre encoder/decoder IR eu não pensei numa implementação em HDL. Suponho que implementar o modulador/demodulador para 38kHz seria simples, mas o codificador/decodificador seria mais trabalhoso. Sinceramente não sei se vale a pena implementar toda uma FSM relativamente complexa para fazer a codificação e decodificação num padrão qualquer, esta tarefa ainda me parece ser mais fácil de fazer via software do que via hardware.

      Fábio

      • 9 de maio de 2017 em 11:19 am
        Permalink

        Bom dia Fabio!
        Obrigado pelo retorno.
        Vou verificar a implementacao via fw com MCU.
        Um abraço,
        Fernando

  • 17 de maio de 2017 em 11:36 pm
    Permalink

    Olá Fábio!

    Fiquei  tempo longe da eletrônica e estou voltando. Tenho seu livro HCS08 teoria e prática, mas pelo que percebi esse microcontrolador foi descontinuado, é isso mesmo?

    Quem entrou no lugar?

    • 18 de maio de 2017 em 10:03 am
      Permalink

      Olá Leandro,

      Olha, me parece que a NXP (e agora a Qualcomm) não tem interesse em manter as linhas proprietárias de microcontroladores (como o HCS08, Coldfire, etc) e estão migrando para cores como o Cortex-M0 e M0+. Particularmente acho uma pena, pois os HCS08 são microcontroladores muito versáteis, simples e baratos.

Deixe uma resposta