Quando estamos realizando o projeto de um sistema microcontrolado sempre devemos verificar quais periféricos teremos que usar para atender aos requisitos de hardware necessários para o projeto. Um dos principais pontos a se observar é a quantidade de dispositivos de comunicação serial (UART/I2C/SPI/I2S) vamos utilizar no projeto e qual é o processador/microcontrolador que iremos utilizar que atende esses requisitos.
Em algum ponto do projeto teremos a plena convicção de qual processador iremos utilizar, porém podemos cair em dois problemas: custo e disponibilidade. Esses dois parâmetros podem fazer o projeto se tornar inviável. Neste artigo iremos olhar para este problema do ponto de vista de uma implementação de um canal de comunicação UART.
Acabaram as UARTs do microcontrolador. E agora?
Este é um grande problema que todo projetista enfrenta. Uma das soluções que podem ser adotadas é a de usar um circuito integrado que implementa uma ou mais UARTs, como os SC16IS741AIPW, da NxP, e os ST16C1550, da Exar. O ponto ruim desta abordagem é o aumento de custo do projeto e o aumento de potência consumida. A outra abordagem que podemos ter para este problema é a que será apresentada neste artigo: implementação de uma serial via software.
Frame de comunicação serial
O frame de comunicação serial na configuração 8×2 (oito bits de dados, paridade e dois stop bits) apresenta o formato ilustrado na Figura 1.

A partir da Figura 1 podemos notar que o estado idle da linha de comunicação da UART é nível alto. Esta característica usaremos mais a frente no artigo.
O tempo de bit está relacionado ao baudrate da comunicação, como pode ser observado na tabela 1, onde apresentamos os baudrates mais utilizados em projetos.
Tabela 1: Baudrate e tempo de bit
|
Baudrate [bits por segundo] |
tempo de bit [us] |
|
9600 |
104.16 |
|
19200 |
52.08 |
|
38400 |
26.04 |
|
57600 |
17.36 |
|
115200 |
8.68 |
Implementação de uma UART emulada
Para implementar a comunicação serial temos que prover funções de recepção e transmissão de dados. Vamos discutir separadamente cada um dos casos em uma aplicação com microcontrolador Microchip PIC18F, porém os conceitos abordados aqui podem ser aplicados em qualquer microcontrolador.
Recepção
Através da figura 1 verificamos que o estado Idle da linha de comunicação serial é em nível alto e a condição de START é dada por uma transição de alto para baixo na linha Rx. Com isso podemos ter duas abordagens para a implementação da recepção serial: por polling ou via interrupção.
A diferença básica entre as duas abordagens é que na detecção do start bit por polling o processador sempre deve verificar se o IO associado ao sinal de recepção está em nível zero, enquanto na detecção por interrupção o processador não precisa verificar a cada loop de processamento se uma nova condição de start ocorreu.
Após a detecção da condição de start, devemos realizar a leitura do sinal RX nos intervalos de tempo dados pela Tabela 1.
Como exemplo, vamos implementar a recepção de dados em um PIC18F genérico com o sinal RX da comunicação serial conectado a um pino da porta B, como ilustrado na figura 2.
A escolha de RB0 para a recepção serial se deu simplesmente pois podemos configurar este pino para interrupção por borda de descida.
Após a detecção da borda de descida, desabilite todas as interrupções, espere um tempo de meiobit e, a partir daí, faça a aquisição do pino de IO a cada tempo de bit. Desabilitamos as interrupções pois como este processo é sensível a timming, não podemos ter outros processos rodando em paralelo e que possam fazer o processador perder tempo e errar a leitura do dado. A espera de um tempo de meiobit se faz necessária para que tenhamos certeza de que o sinal presente no pino de IO não está mais sujeito a transientes. Observe na figura 3 o instante da detecção da condição de start e a posterior leitura dos 8 bits de dados e a detecção do stop bit.
Note os instantes de leitura do pino RX aproximadamente no meio de cada bit recebido.
A seguir apresento uma implementação de recepção serial em um PIC18F:
// código de interrupção: compilador C18
#pragma interrupt InterruptHandler nosave=section(".tmpdata"),section("MATH_DATA")
void InterruptHandler( void )
{
if ( INTCONbits.INT0IF ) {
// devo rever se é isso mesmo....
// realiza a leitura do modem
ProcessaProtocolo( getcUART( ) );
INTCONbits.INT0IF = 0;
}
}
// readuart.asm
ReadUART
BANKSEL INTCON ; disable interruptions
bcf INTCON,GIE
bcf INTCON,PEIE
banksel BitCount
movlw 0x08 ; Count 8-bits
movwf BitCount
banksel SWRXD
WaitChar
btfsc SWRXD,SWRXDpin ; Detect a start bit (low)
goto WaitChar
call DelayRXHalfBitUART ; Delay until middle of start bit
banksel SWRXD
btfsc SWRXD,SWRXDpin ; If not still low, then go back
goto WaitChar ; and wait for start bit
GetChar ; Loop to get all 8-bits
call DelayRXBitUART ; Delay a full bit for middle of
banksel SWRXD
btfss SWRXD,SWRXDpin ; Set or clear the carry bit
bcf STATUS,C ; according to the state of the
btfsc SWRXD,SWRXDpin ; RXD I/O pin
bsf STATUS,C
banksel uartdata
rrcf uartdata,F ; Rotate the carry into data
decfsz BitCount,F ; Loop for 8-bits
goto GetChar
call DelayRXBitUART ; Delay a full bit time, middle of stop bit
call DelayRXHalfBitUART ; Delay half bit for end of stop bit
nop
banksel uartdata
movf uartdata,W,BANKED
BANKSEL INTCON ; enable interruptions
bsf INTCON,GIE
bsf INTCON,PEIE
return ; Return received character
GLOBAL ReadUART
END
Como estamos trabalhando com uma operação muito sensível escolhi realizar a implementação em Assembly. Nesta implementação temos as funções DelayRXBitUART e DelayRXHalfBitUART, responsáveis pelo delay para a realização da leitura do pino RX. Estas funções são dependentes do baudrate.
Transmissão
A transmissão, como a recepção, é altamente dependente do tempo, desabilitamos todas as interrupções e assim temos certeza de que o processador está ocupado somente com a transmissão de dados. A implementação mostrada na listagem abaixo mostra a transmissão de dados via o canal serial.
WriteUART
BANKSEL INTCON ; disable interruptions
bcf INTCON,GIE
bcf INTCON,PEIE
banksel uartdata
; Retrieve character from stack.
movlw 0xFF
movff PLUSW1,uartdata
movlw 0x09 ; Set bit counter for 9 bits
movwf BitCount
bcf STATUS,C ; Send start bit (carry = 0)
banksel SWTXD
goto SendStart
SendBit
banksel uartdata
rrcf uartdata,F ; Rotate next bit into carry
SendStart
banksel SWTXD
btfss STATUS,C ; Set or clear TXD pin according
bcf SWTXD,SWTXDpin ; to what is in the carry
btfsc STATUS,C
bsf SWTXD,SWTXDpin
nop
call DelayTXBitUART ; Delay for 1 bit time
banksel BitCount
decfsz BitCount,F ; Count only 9 bits
goto SendBit
nop
nop
banksel SWTXD
bsf SWTXD,SWTXDpin ; Stop bit is high
call DelayTXBitUART ; Delay for stop bit
call CPU_EnableAllInt
BANKSEL INTCON ; enable interruptions
bsf INTCON,GIE
bsf INTCON,PEIE
return
Vale observar que as implementações de recepção e transmissão emuladas dependem muito de que os tempos de duração dos bits sejam respeitados. Sendo assim, se faz necessário uma contagem precisa do tempo de execução de cada instrução para que se alcance os requisitos de tempo da comunicação.
Conclusões
Neste artigo discutimos um pouco sobre implementação de uma porta serial emulada. A implementação realizada para PIC apresenta alguns conceitos que podem ser utilizados em qualquer plataforma, tais como início da recepção por detecção de borda de descida no pino RX, desabilitação das interrupções durante a recepção e a transmissão e amostragem no meio do bit.







Excelente artigo! Fiquei com uma dúvida em relação à contagem do tempo de meio bit. Como o tempo é fator chave, qual é a forma mais precisa e eficiente de fazermos este acompanhamento? Obrigado!
Olá Nilton. Desculpe a demora… Eu simplesmente não recebi aviso de comentário no post.
Bom, você deve confiar no seu cálculo do bit. Se vc escorregar um pouco na determinação do tempo para o meio bit, não tem problemas. O que importa é que vc consiga realizar as leituras dentro da janela de cada bit. Por isso que é importante desligar as interrupções ao realizar a leitura.