domingo, 23 de setembro de 2012

Comunicação Serial no Netduino - Parte 2

    Nessa segunda parte sobre a comunicação serial no Netduino pretendo falar um pouco do impacto do controle de fluxo na comunicação RS-232, quando se recomenda utilizá-la e porque muitos entusiastas de eletrônica simplesmente resolvem não utilizar essa parte importante da comunicação.


   Muitos fatores podem influenciar na transmissão dos dados entre duas aplicações, aqui vamos testar a comunicação serial TTL diretamente utilizando os pinos de 3,3V do Netduino para verificar alguns problemas que na primeira parte eu tratei com software, ou simplesmente não foi necessário de se tratar pela forma com que a solução foi enjambrada. 

       O teste em si é tão simples quanto o da primeira parte sobre comunicação serial mas nesse caso utilizaremos 2 placas de Netduino para estabelecer a comunicação entre elas e realizar alguns teste no canal (a interligação elétrica entre as duas placas...).

1ª Etapa - Teste do sincronismo da conexão


   Para simplificar um pouco mais vamos colocar uma placa apenas transmitindo e outra recebendo. O código abaixo é da placa de transmissão que envia os dados de periodicamente a pouco mais de um segundo entre os pacotes de informação. A mensagem é a mesma "Netduino!\n"
using System;
using System.IO.Ports;
using System.Threading; using Microsoft.SPOT;
using Microsoft.SPOT.Hardware; using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.NetduinoPlus;
using System.Text; namespace NetduinoPlusSerialExample
{
    public class Program
    {
        static SerialPort serial;
        static OutputPort Led;         
  
  public static void Main()
        {
            // initialize the serial port for COM1 (using D0 & D1)       
            serial = new SerialPort("COM1", 56000,
                                    Parity.None, 8, StopBits.One);
            // open the serial-port, so we can send & receive data       
            serial.Open();
            // add an event-handler for handling incoming data       
         
            Led = new OutputPort(Pins.ONBOARD_LED, false); 
            while (true)
            {
                Led.Write(true);
                Thread.Sleep(1000);
                Led.Write(false);
                byte[] msg = 
                 Encoding.UTF8.GetBytes("Netduino!\n");
                serial.Write(msg, 0, msg.Length);
            }
        } 
    }
}

       O código de recebimento é apenas uma interrupção de recebimento praticamente iguala ao apresentado na parte 1 dessa série sobre a comunicação serial.
using System;
using System.IO.Ports;
using System.Threading; using Microsoft.SPOT;
using Microsoft.SPOT.Hardware; using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.NetduinoPlus;
using System.Text; namespace NetduinoPlusSerialExample
{
    public class Program
    {
        static SerialPort serial;
        static OutputPort Led;         
  
  public static void Main()
        {
        
   // initialize the serial port for COM1 (using D0 & D1)       
            serial = new SerialPort("COM1", 56000,
                                    Parity.None, 8, StopBits.One);
            // open the serial-port, so we can send & receive data       

            serial.Open();

            // add an event-handler for handling incoming data       
            serial.DataReceived +=
                new SerialDataReceivedEventHandler(serial_DataReceived);
    Led = new OutputPort(Pins.ONBOARD_LED, false);             
    Thread.Sleep(Timeout.Infinite);         
     } 
  
  
        private static void serial_DataReceived(Object sender,
            SerialDataReceivedEventArgs e)
        {
            Thread.Sleep(100);            
   
   int bytesToRead = serial.BytesToRead;
            
   //start reading the stream
            if (bytesToRead > 0)
            {
                // get the waiting data
                byte[] buffer = new byte[bytesToRead];
                serial.Read(buffer, 0, buffer.Length);
                string restoredText =
                  new string(Encoding.UTF8.GetChars(buffer));
                Debug.Print(restoredText);                 
    
    //Resend
                serial.Write(buffer, 0, buffer.Length);                 Led.Write(true);
            } 
            Led.Write(false); 
        }     
 }
}
    

     Lembre-se de debugar apenas a placa que recebe os dados, e ligar a placa que transmite os dados depois. Feito isso você vai receber as mensagens "Netduino!" no seu console de saída de debug. Eu utilizei a alimentação de 3,3V da placa de recebimento para alimentar a placa de transmissão, note pela foto que o led de PWR ficou meio apagado, isso porque ele está ligado no 5V que eu não estou usando, mas a placa está ótima, só precisamos alimentar o 3,3V porque é o nível do processador.

       Agora podemos fazer os três testes no canal: o primeiro é tentar mandar uma mensagem muito grande tipo: "O vasco é vice pelos séculos dos seculos !\n". Até aqui tudo bem, se você repetir o procedimento de reprogramar o código na placa que transmite, debugar a placa de recepção e ligar em seguida a placa que transmite. 
     
       Na sequência repita o procedimento retirando previamente a pausa de 100ms na da interrupção serial_DataReceived().

//Thread.Sleep(100);

 Ocorrerá um erro durante a deugação! No meu caso a placa recebeu algumas mensagens na janela de output do debug e em seguida ocorreu um erro: "An unhandled exception of type 'System.Exception' occurred in mscorlib.dll". A titulo de curiosidade a saída do meu debug ficou assim:

Diamonds é um daqueles perfumes noturnos e amadeirados que faz qualquer mulher p
erder o rumo. Não
 passa dos li
mites em term
os de amadeir
ado e não ab
usa do olfato
 alheio.

Diamo
nds é um d
aqueles perfu
mes noturnos
 e amadeirados
 que faz qual
quer mulher p
erder o rumo.
 Não passa d
os limites em
 termos de am
A first chance exception of type 'System.Exception' occurred in mscorlib.dll
An unhandled exception of type 'System.Exception' occurred in mscorlib.dll

        A pergunta agora é: porque isso acontece. Isso acontece porque a serial não tem um bom controle de fluxo de mensagens, diferente dos protocolos de rede e de Ethernet e USB a serial é apenas o meio de comunicação e cabe a você fazer uma camada de código para tratar possíveis problemas de comunicação. Na forma que o hardware (o processador) é feito quando você envia uma mensagem de 8bits (1 char) de cada vez, e o primeiro recebimento (o primeiro char) faz o trigger da função de recebimento mas como você mandou uma frase inteira outros dados (char's) estão sendo enviados  e o tempo que demora até executar a função:

serial.Read(buffer, 0, buffer.Length);

Outros dados são recebidos mas de forma descontrolada em torno de 12 caracteres como podemos ver pela mensagem acima. O atraso fixo faz com que a mensagem seja toda recebi antes da leitura assim quando executamos a leitura da serial uma mensagem completa pode ser interpretada. O erro ao final é causado por esse atropelo entre os envios de mensagens e a interpretação do código.

2ª Etapa - Teste do byte danificado
 
      Um teste que você pode fazer é ligar a placa de transmissão antes de debugar o código de recebimento de mensagens na outra placa. Ocorrerá um erro também, esse erro é causado pela entrada de um pacote com um caractere que não pode ser interpretado pelo código ASCII  para um string e provoca um erro na linha:

 string restoredText =
                    new string(Encoding.UTF8.GetChars(buffer));

      Essa caractere é fruto ou de uma oscilação provocada no momento em que a energia foi conectada a placa ou por uma condição de imeout que leu o restante do byte como nivel lógico alto, é comum quando a leitura receber algo próximo de 0xFF nesses casos. 

      O que é de praxe entre os programadores é descartar esses erros e olhar a mensagem a frente, assim você pode resolver esses dois problemas com uma temporização de recebimento e um try{}cach{} para ignorar qualquer erro que venha prejudicar seu código.


3ª Etapa - Teste do comprimento da conexão

    O terceiro teste, é mais uma continuação do anterior, é o teste do comprimento de cabo, aqui eu fiz o teste usando um cabo de 60 metros para ver a perturbação no sinal e não ficou nada bom  como podem ver pelas imagens obtidas com o osciloscópio. Repare que o sinal de saída (sinal em amarelo obtido na extremidade do pino do receptor) oscila muito e o sinal na placa que transmite recebe uns spikes de comutação que podem até danificar a placa. 

    Logicamente o sinal nem era interpretado pelo receptor, as oscilações em níveis exagerados provocavam ruído de comutação causando erro na leitura do byte. 
    Esse teste fiz para que se note que a comunicação serial (TTL) é ruim para realização de transmissões mais longas.

As Soluções

     Falei muito dos problemas, mas vamos ver as soluções para os problemas que identificamos acima. O 3º item é o mais complexo de se resolver, diz respeito ao canal de comunicação, o que se faz é a compensação capacitiva mais conhecida pelos pessoal de rádio e TV como o "casamento de impedâncias". Uma proteção legal é colocar nos extremos uns diodos zener de 3,3V para limitar uma possível tensão elevada sustentada.

    Para resolver o 2º problema da entrada errada de sinal é utilizar o controle de fluxo, isso é, usar os pinos CTS e RTS que eu comentei anteriormente. Utilizá-los é bom mas não soluciona o problema por completo, em especial no caso de teste (contraditório eu sei), geralmente o controle de fluxo previne em especial problemas relacionados com baud rate

     Para entender como isso acontece primeiro necessita-se de uma breve explanasão sobre o sinal de disparo de envio; o sinal que dispara o envio é um clock interno do dispositivo transmissor que geralmente é compartilhado com outros periféricos e através de um ganho lógico se ajusta ao valor desejado. O casamento imperfeito entre o valor exato de de baud rate e o clock utilizado pode causar perda do sincronismo em pacotes muito grandes. Quando a mensagem a se enviar tem muitos bytes esse controle por CTS e RTS faz com que o clock de envio seja "sincronizado" com o clock do receptor a cada 9 bytes transmitidos fazendo com que um pequeno erro em cada transmissão não se acumule causando problemas de distorção de pacotes.

     Voltando ao problema 2, o pino RTS atua como uma prevenção de disparo de transmissão, ou seja se o RTS não tiver sido ajustado pelo receptor como "estou disponível para receber um dado" o dado não é enviá-lo assim a placa transmissora só armazena o dado no seu buffer interno para enviar quando disponível. No caso de desconexão entre os aparelhos é necessário um resistor de pull-up mantendo o RTS para que no momento que ele esteja desconectado o pino seja mantido em nível lógico alto.

    A mais complicada de resolver mesmo é a primeira porque necessita de um tratamento em termos de protocolo. Todos os testes que eu fiz pela serial envolvem envio de informações através de strings de texto mas de fato qualquer tipo de informação empacotada pode ser enviada. Veja o exemplo do projeto de controle eletrônico do RogerCom, ele utilizou um protocolo único em que cada mensagem de 8bits não significa uma letra mais sim uma mensagem em si.

    O que tem que ser feito é uma camada de tratamento que será implementada nos dois dispositivos que estão se comunicando, geralmente o que eu faço é  empacotar as mensagens com um terminador e um inciador, ex.: . Quando isso é lido pelo dispositivo receptor é possivel reconhecer através do sóftware que a mensagem está completa, tem um caractere inciador "<" e um terminador ">" e uma mensagem do tipo variável = valor numérico. Muito além o transmissor não precisa deliberadamente enviar suas mensagens, ele pode aguardar o receptor informar algo para ele transmitir a sua mensagem (já vendo o ponto de vista bidirecional) como por exemplo: ".


     Dessa forma, com um código próprio é possível realizar uma comunicação mais estável e segura, o receptor pergunta algo para o transmissor que detêm o dado, e fica aguardando, se responder ele verifica se está corretor caso contrário, não tenha respondido ou a mensagem esteja danificada ele realiza uma nova requisição ao transmissor.


      Na próxima parte sobre a comunicação serial vou apresentar a plaquinha serial que converte um sinal de TTL para diferencial e poderemos analisar o efeito no par trançado de cabos transmitindo a informação. Além disso vou fazer um driver para encapsular a mensagem como eu disse acima mandando também dados contidos em um arquivo de configuração.

Nenhum comentário:

Postar um comentário