domingo, 2 de setembro de 2012

Comunicação Serial no Netduino - Parte 1

   A muito tempo tenho interesse em publicar algo sobre comunicação serial com o Netduino, como algumas pessoas me perguntaram sobre isso resolvi escrever um post dedicado no blog a comunicação serial.
    E como há de ser, fã da MICROCHIP não vivo sem uma comunicação serial. Mesmo fazendo tudo pela USB eu precisava de um serial para comunicar minhas velhas aplicações com o Netduino.

        A princípio pode parecer uma grande regressão mas a comunicação serial ainda estará em voga e continuará ainda pelos próximos anos devido aos velhos aplicativos de baixa velocidade. Convenhamos, para aplicações de baixa velocidade torna-se muito trabalhoso e difícil implementar a comunicação USB que acaba sendo por vezes um preciosíssimo. Uma certa vez me deparei com um projeto de iluminação, se tratava de uma solução específica onde desejava-se comunicar com computador para realizar uma apresentação de como seria uma iluminação de rua controlável por sensores, naquela época ainda tinha pouca experiência com PIC e foi muito prático, utilizando o osciloscópio, ver se os sinais estavam corretos e implementar essa comunicação no tempo que tinha disponível, poucas horas...


A Comunicação Serial:

       A comunicação serial, na essência, é um processo de enviar dados um bit de cada vez, sequencialmente, através de um único canal de comunicação. Para comprar, na comunicação paralela os bits de cada informação são enviados ao mesmo tempo através de canais de comunicação diferentes porem associados um ao lado do outro.

        O conceito é bem amplo a tal ponto que é possível dizer que a comunicação serial é usada em toda comunicação de longo alcance e na maioria das aplacações comerciais, onde o custo de cabos e as dificuldades de sincronização tornam a comunicação paralela impraticável. Mais ainda, em curtas distâncias, barramentos seriais estão se tornando cada vez mais comuns devido ao ponto em que as desvantagens dos barramentos paralelos (densidade de interconexão) somados a redução do custo de implementação da serialização da informação estão tornando a comunicação serial um caminho sem volta para os novos dispositivos embarcados.

        Além disso mesmo que seja entre periféricos na mesma placa a quantidade de pinos utilizada se reduz drasticamente, por exemplo, para enviar um byte de 8 bits seria necessário 8 pinos enquanto na serial apenas 1 seria o suficiente.

       Através desse conceito a Ethernet, CAN, I2C, SPI são todas comunicações seriais no entanto quando falamos de comunicação serial com um PC ou outras placas lembramos do conector DB9 dos PC's antigos que usa o protocolo RS-232/RS-485. Na verdade é isso mesmo, quando comecei a estudar tinha muitas dúvidas a respeito disso mas a bem da verdade é que 90% das vezes que ouvimos falar em comunicação serial estamos falando do padrão RS-232.
  Só para não deixar passar em branco, abaixo temos uma lista das principais comunicações seriais que peguei na wikipédia:

       Dessa lista podemos ver alguns protocolos muito utilizados como o SATA, que substitui o tradicional cabo paralelo que causava diversos problemas nos PC antigos para comunicação com discos rígidos, e o protocolo de Ethernet que ninguém importa pela forma de envio dos dados devido a quantidade de camadas mas é importante destacar que trata-se de uma comunicação serial também.


       Assim, o que nos interessa mesmo é a "interface serial" ou "porta serial", conhecida como RS-232. Se você tem 25 anos ou mias certamente lembra-se da época em que a USB não era tão conhecida e os dispositivos se conectavam nos PC's fabricados pela IBM pela porta serial, os conectores DB-9. Os equipamentos mais velhos utilizavam a porta de comunicação serial para comunicar-se com o PC, tais como modems, mouses, teclados, algumas impressoras, scanners e outros equipamentos de hardware. O padrão RS-232 foi originalmente definido para uma comunicação por meio de 25 fios diferentes, portas paralelas (chamavam de porta de impressora LPT1 com um conector grande DB-25). A IBM ao utilizar o padrão para o seu projeto do IBM-PC, definiu que apenas 9 pinos seriam necessários e esse se tornou o padrão de porta serial. O padrão de 25 pinos (DB-25) ficou conhecido como paralelo porque tratava-se de duas comunicações seriais que poderiam funcionar em paralelo duplicando a velocidade, não vou entrar nesses detalhes aqui.

       A pergunta que vem agora a mente é que vantagem tem em utilizar esse padrão de comunicação antigo e obsoleto. A grande vantagem é que não existe nenhuma camada entre os dados que você recebe através da porta serial e o seu código, assim você pode "ver"  os dados sendo transmitidos através do barramento sendo ideal para comunicação com dispositivos de baixa velocidade. A titulo de comparação podemos comparar com a comunicação USB: No padrão USB exitem camadas de código intermediárias por baixo da sua camada de comunicação sendo necessário um driver para que um computador compreenda qual o dispositivo e como deve tratar as informações trocadas tornando mais complicado a implementação de coisas mais simples além disso a velocidade da porta USB é por vezes tão alta que interferências da protoboard podem derrubar a comunicação.

       Se vocês já fizeram a atualização de firmeware do Netduino lembram-se do "GPS Camera Driver" que era utilizado pelo SAM7X para comunicar com o PC pela porta USB. Esse driver foi feito para o SAM7X emular uma comunicação serial pela USB, isso é tanto verdade que quando você inicia o SAM-BA você selecionava uma USB emulada para embarque do software. Essa emulação da serial pela USB tem sido uma solução implementada pela maior parte dos fabricantes para aplicações mas simples como embarque do software e debug de código (veja a launchpad da Texas e o Super-Arduino da MICROCHIP).

       Essa comunicação foi feita pela Eletronics Industries Association (EIA), o padrão RS-232 (versão mais conhecida é o RS-232-C mas isso não importa muito) em 1969!! Para maiores detalhes leia o artigo da wikipédia.

A conexão

       Há de pensar porque são necessários 9 pinos no conector embora a conexão serial por definição necessite apenas de uma via para enviar os dados de um dispositivo para outro. Na verdade ocorre que existem algumas ponderações a se fazer sobre isso. Destaco primeiramente o sinal de referência; é necessário que exista uma via para interconectar as referências entre os dois dispositivos granindo que o sinal de tensão entre o GND (referência) e a via que transporta o sinal sejam iguais. Em segundo lugar tem-se a questão da direção, é necessário que os dois dispositivos possa enviar e receber informações o que através de um único barramento poderia ocasionar colisão então é interessante usar duas vias uma para envio outra para recepção de bits.

       Com isso podemos montar a conexão serial mais elementar a três fios que correspondem a mais 70% das aplicações de desenvolvimento e hobby onde se utiliza os pinos 2, 3, e 5 do conector DB9. É importante ressaltar que os pinos de envio (chamemos de TX) e de recepção (chamemos de RX) entre os dois dispositivos que desejamos conectar devem ser cruzados de tal forma que o que é enviado pelo primeiro através do seu pino TX é recebido na porta RX do segundo.


       Os demais pinos são dedicados ao controle do fluxo de dados aqui em baixo tem os diagramas de pinos da RS-232 e a tabela informando uma breve descrição da funcionalidade:
PINO
 DB-25
DB-9
Abreviação
Descrição
Transmit Data
2
3
TX
Transmissão de dados.
Receive Data
3
2
RX
Recepção de dados.
Request to Send
4
7
RTS
Requisição de envio. Ativo  antes de cada transmissão.
Clear to  Send
5
8
CTS
Limpa para enviar.
Data Set Ready
6
6
DSR
Sinaliza que o modem ou periférico está pronto
GND
7
5
SG
Terra comum do sinal.
Carrier Detect
8
1
CD
Detecção de transporte
Data Terminal Ready
20
4
DTR
Indica que o terminal DTR está pronto para iniciar troca de informações
Ring Indicator
22
9
RI
Indica “toque de chamada”


       Agora que entendido os pinos é importante ainda ressaltar que o padrão de comunicação  utilizado  pelo  RS232 é diferente do utilizado pelo TTL (ou CMOS 3,3V disponível no Netduino),  onde  o nível 1 está associado a 5V e o nível 0 ao 0V. No padrão RS232, o nível "1" está associado  a  uma  tensão  de  –3V  a  –18V  enquanto o  "0"  está  associado  a  uma tensão de 3V a 18V. Qualquer tensão dentro desta faixa será entendido como "1" ou "0". Essa é uma confusão que eu fiz durante muito tempo e não compreendia a necessidade do chip MAX232 para realizar a comunicação pela serial e com uma porção de informação desencontrada na internet ficou dificil de entender isso a primeira vista.

       Então, o Netdiuno disponibiliza essa versão TTL (muito embora no nível de 3,3V) nos pinos D0 e D1 (COM1) e o controle de fluxo fica nas nos pinos 8 e 7 como pode ser visto na figura abaixo:


       Agora que sabemos onde está localizado o canal de RX e TX no Netduino continuemos a explanação sobre a comunicação porque ainda não comentei nada ainda sobre a mensagem. A mensagem é representada por bits de dados individuais, que podem ser encapsulados  em  mensagens de  vários bits.  Um byte  (conjunto de  8  bits)  é  um  exemplo de  uma unidade de mensagem que pode trafegar através de um canal digital de comunicações e essa é a implementação de hardware da maior parte das portas seriais: mensagens encapsuladas em bytes de 8 bits assim a cada 8 bits recebidos um "buffer" em hardware acumula em um byte se você enviar vários isso cria um chamado “frame”, ou seja, unidade de mensagem de maior nível. Esses múltiplos níveis de encapsulamento facilitam o reconhecimento de mensagens e interconexões de dados complexos em especial mensagens de texto.

        A maioria das mensagens digitais são mais longas que alguns poucos bits, no nosso caso de pequenos textos (é o que eu pretendo enviar são strings). Por não ser prático nem econômico transferir todos os bits de uma mensagem simultaneamente, a mensagem é quebrada em partes menores e transmitida sequencialmente. A transmissão bit-serial converte a mensagem em um bit por vez através de um canal. Cada bit representa uma parte da mensagem. Os bits individuais são então  rearranjados  no  destino  para  compor  a  mensagem  original.  Em  geral,  um  canal  irá  passar apenas um bit por vez.


Características da comunicação

        A taxa de transferência refere-se a velocidade com que os dados são enviados através de um canal e é medido em transições elétricas por segundo. Na norma EIA, ocorre uma transição de sinal por bit, por exemplo uma taxa de 9600 bauds  corresponde  a  uma  transferência  de  9600  dados  por  segundo,  ou  um  período  de aproximadamente, 104 µs (1/9600 s). Os valores típicos de baude são:


Baud Rate
2400 4800 9600 19200 38400 57600 115200


        Importante notar que a mensagem trafega livremente no canal no entanto nem todo o canal pode ser utilizado, a isso chamamos de "eficiência do canal de comunicação" que tem por definição o número de bits de informação utilizável (dados) enviados através do canal por segundo. Ele não inclui bits de sincronismo, formatação, e detecção de erro que podem ser adicionados a informação antes da mensagem ser transmitida, e sempre será no máximo igual a um.

        Podemos endagar o seguinte questionamento: quando identificar o inicio e o fim de um frame de mensagens? Os dados serializados não são enviados de maneira uniforme através de um canal de maneira ininterrupta. Os pacotes com informação regulares são enviados seguidos de uma pausa. Os pacotes de dados binários são enviados dessa maneira, e cabe ao usuário (você programador) criar o seu protocolo ajustando pela pausa ou por um caractere validador de inicio de frame. Dessa maneira o circuito receptor dos dados deve saber o momento apropriado para ler os bits individuais desse canal e mais ainda deve saber identificar quando começa a ser enviada a informação e qual o período entre os bits (Baude Rate). Caso o conhecimento desse período esteja comprometido perdas podem ser ocasionadas na mensagem.

         Esse tipo de comunicação é dita assíncrona, isso porque não exite nada que informe o envio da mensagem como um sinal de relógio, a informação trafega por um canal único e o receptor deve saber reconhecer o protocolo, ou seja, tanto o transmissor quanto o receptor devem ser configurados antecipadamente para que a comunicação se estabeleça. através de um contador o receptor irá gerar um sinal interno a partir do envio da primeira mensagem segundo o tempo configurado no baud rate para receber as mensagens subsequentes do mesmo buffer.  No protocolo EIA os dados são enviados em pequenos pacotes de 10 ou 11 bits, dos quais 8 constituem a mensagem. Quando o canal está em repouso, o sinal correspondente no canal tem um nível alto. Um pacote de dados sempre começa com um nível lógico baixo é atingido sinalizando o receptor que um transmissão foi iniciada. Esse bit iniciador é conhecido na literatura como sendo o “start bit” sendo o responsável também pela inicialização do temporizador interno no receptor avisando que a transmissão começou e que serão necessário verificar o canal em intervalos fixos para ler a mensagem. Logo após o start bit, 8 bits de dados de mensagem são enviados segundo o baud rate do transmissor. O processo é encerrado com o “stop bit” que é exatamente um bit de paridade, a única proteção para uma perda na mensagem .

         O bit de paridade pode ser entendido como o número de bits '1' que estão sendo enviados no byte da mensagem. Para validar a mensagem, é adicionado, no final de cada transmissão RS-232 um dígito binário de paridade. Assim o próprio hardware já faz uma validação da detectar erros nas transmissões, já que o seu cálculo é extremamente simples (para colocação em um chip dedicado).
Existem dois tipos de código de paridade que são configuráveis na maioria dos processadores: a paridade par e a paridade ímpar. A paridade será par quando o número de bits de valor '1' for par; caso contrário, será ímpar.


 Primeiros passos no Netduino

         Como eu fumei o meu Netduino, vou ter que fazer tudo no Netduino Plus. Mas podemos começar pelo mais simples: fazendo o Netduino transmitir um sinal pelo TX e recebê-lo pelo RX. E faremos isso colocando um jumper entres os pinos D0 e D1, como na figura.


         Feito isso, o passo seguinte é iniciar o Visual Studio 2010 e criar o projeto com esse código:

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);

            //Send the first data!
            byte[] msg = Encoding.UTF8.GetBytes("Netduino!\n");
            serial.Write(msg, 0, msg.Length);
        }
        

        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);


        }

    }
}


         Se você tiver um osciloscópio você poderá facilmente ver o sinal trafegando pelo jumper:


        O resultado dessa brincadeira é que o código envia um dado inicial que é recebido pelo próprio Netduino que volta a enviar o dado novamente pelo mesmo barramento COM1. Exite uma série de coisas a serem comentadas sobre esse código, começando pela declaração da SerialPort que é declarada como static.
        Quando se declara um variável como static significa que a variável pertence a classe em si não dando acesso a modificadores externos e preservando o valor para acesso que nesse caso também é feito, além do main(void), pela função estática de interrupção de recebimento.
        A construção da porta serial você pode utilizar os construtores da SecretLabs como no exemplo abaixo, ou usar como eu fiz no código anterior com string e valores numéricos conhecidos.

serial = new SerialPort(SerialPorts.COM1, 56000, Parity.None, 8, StopBits.One);

        Depois de configura a porta serial é aberta para comunicação e é atribuído um evento a porta para cada sinalização que dados estão disponíveis para ler entrar em uma função para tratar o recebimento; é possível também checar se a porta está com dados para serem lidos através de um loop infinito mas isso é pouco eficiente logo tratei de trazer para o blog algo melhor e mais prático de ser utilizado.
         Por fim, é feito o envio de uma informação de texto. Note que a função SerialPort.Write(byte[]) recebe um vetor de bytes para serem enviados, isso significa que o texto não pode ser enviado diretamente e uma etapa de serialização é necessária. Essa disposição do texto em um array de bytes é feita Encoding.UTF8.GetBytes() que pertence ao System.Text.

  
          Uma vez enviado o dado, caso não exita nenhum mau contato no fio que você utilizou como jumper,  a mensagem será recebida e a função serial_DataReceived() será chamada, note que no inicio eu faço o programa esperar 100ms isso ajuda para que a mensagem não fique "picotada". Lembre-se que ao enviar "Netduino!\n" estamos enviando um frame de 10 bytes e essa interrupção será chamada após a recepção do 1º byte, caso não espere terá a seguinte visão ao debugar o código:


      Você pode testar até com 1ms que a resposta é perfeita ainda. Por fim, fazemos o processo inverso descobrimos quantos bytes estão disponíveis para leitura, após 1ms todos os bytes já estão no buffer de recepção e a mensagem é transferida para a variável do tipo byte[] e mais uma vez é necessária uma conversão para torná-la um string que é feita logo em seguida através de um construtor de string.




        Notadamente, meus propósitos aqui são um pouco mais amplos que isso. O Netduino, inicialmente não permitia suporte a comunicação livre USB,  ou seja, a USB servia tão somente para fazer embarque e desembarque do software e não para aplicações um engano terrível que eu mesmo cometi quando comprei a placa, pensava que sua conectividade básica era pela USB e com isso seria possível fazer os mais diversas aplicações. Isso a primeira vista uma decepção com o Netduino e o pessoal que trabalhava comigo também teve a mesma impressão.

        Mas tudo tem uma solução, a final, não se tratava apenas de um interessado em utilizar aquela USB. No fórum do Netduino tem um post do Chris Walker falando sobre isso, como usar a USB com um exemplo de um dispositivo USB HID. O problema é que para isso ser feito a parte de embarque do software e debug tem que migrar para a serial UART.

        O Netduino permite que o programa seja embarcado pela UART nos pinos I/O 1 e 2. Naturalmente é interessante utilizar o MAX232 para realizar a conversão com uma placa própria , por isso tratei de fazer a minha própria placa (importar aqui no Brasil é um grande problema).

        Nos próximos artigos dessa nova série sobre a comunicação serial farei mais alguns testes e no final pretendo apresentar uma shield desenhada por mim para programação do Netduino pela porta serial e em seguida pretendo iniciar a comunicação com a USB.

Nenhum comentário:

Postar um comentário