Comunicação Serial com C# e Arduino

imagem de destaque

Uma interface entre o arduino e um computador muitas vezes é importante durante o desenvolvimento de projeto ou para controle de dispositivos através de uma interface gráfica. Pode-se usar uma aplicação no computador para Aquisição e exibição de dados em forma de gráfico durante algum experimento em laboratório ou estudo. Como já foi visto aqui no artigo sobre o Arduino Uno e sobre comunicação Serial com Arduino, a comunicação entre a placa e o computador é feira através de uma porta serial emulada através do driver da USB. Já foi exibido aqui uma aplicação de comunicação serial desenvolvida com a plataforma JAVA. Neste artigo vamos ensinar como desenvolver uma aplicação para Windows usando a plataforma .Net usando o ambiente Visual Studio da Microsoft, com a linguagem C#. Será desenvolvido um terminal simples, onde se poderá enviar e receber caracteres através de uma interface gráfica. Esse artigo também servirá de base para desenvolvermos uma aplicação envolvendo botões e outros elementos visuais.

0file_newproject


Primeiro passo é iniciar um novo projeto Windows Forms Application em C#:

00novoProjeto

Agora vamos inserir os componentes no Form. O primeiro a ser inserido será um botão e deve-se mudar a sua propriedade Name para “btConectar” e a sua propriedade Text para “Conectar”, conforme exibido a seguir:

01_btConectar

Inserir um comboBox logo a frente do botão btConectar, inserido anteriormente:

02combobox

Inserir outro botão, logo abaixo do btConectar, e mudar a sua propriedade Text para “Enviar” e Name para btEnviar:

03btEnviar

Agora vamos inserir um textBox, que receberá os dados a serem enviados. Após ser inserido, mudar a sua propriedade Name para “textBoxEnviar”:

04textBoxEnviar

Agora vamos inserir um textBox maior, que exibirá os dados recebidos. Mudar as propriedades Name para “textBoxReceber”, Multiline para “True” e ScrollBar para “Vertical”. A aparência do Form ficará da seguinte forma:

05textBobReceber


Próximo passo é inserir um componente timer que será responsável pela atualização das portas COM disponíveis no PC. Selecione o componente timer e clique dentro do Form. Será exibido logo abaixo o componente timer1. Troque a propriedade Name para “timerCOM” e Interval para 1000, conforme exibido a seguir:

06timerCOM 

Por último vamos inserir o componente de comunicação serial, o SerialPort. Selecione o componente SerialPort e depois clique dentro do Form. Será exibido este componente ao lado do timerCOM:

07serialPort

Com os componentes inseridos no Form, vamos para a codificação.

Antes de conectar a porta Serial, é necessário verificar as portas COMs disponíveis para uso, e qual a porta o usuário deseja conectar. Para isso vamos atualizar a cada segundo a ComboBox com as portas disponíveis. Vamos criar um método privado dentro da classe Form1, que será chamado de atualizaListaCOMs. Clique com o botão direito no Form e selecione a opção View code. Insira o método atualizaListaCOMs(), conforme exibido no código a seguir:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports; // necessário para ter acesso as portas
namespace interfaceArduinoVS2013
{
    public partial class Form1 : Form
    {
    public Form1()
    {
        InitializeComponent();
    }
    private void atualizaListaCOMs()
    {
        int i;
        bool quantDiferente; //flag para sinalizar que a quantidade de portas mudou
        i = 0;
        quantDiferente = false;
        //se a quantidade de portas mudou
        if (comboBox1.Items.Count == SerialPort.GetPortNames().Length)
        {
            foreach (string s in SerialPort.GetPortNames())
            {
                if (comboBox1.Items[i++].Equals(s) == false)
                {
                    quantDiferente = true;
                }
            }
        }
        else
        {
            quantDiferente = true;
        }
        //Se não foi detectado diferença
        if (quantDiferente == false)
        {
            return;                     //retorna
        }
        //limpa comboBox
        comboBox1.Items.Clear();
        //adiciona todas as COM diponíveis na lista
        foreach (string s in SerialPort.GetPortNames())
        {
            comboBox1.Items.Add(s);
        }
        //seleciona a primeira posição da lista
        comboBox1.SelectedIndex = 0;
    }
}
}


Para testar a aplicação é necessário clicar no botão Start ou pressionar a tecla F5. Se tiver alguma porta disponível para comunicação, esta será listada dentro da comBox, conforme exibido a seguir:

08testeAtualizaCOM

Na imagem acima nota-se que apenas a COM5 estava disponível. Caso uma placa Arduino seja inserida, é necessário que atualize automaticamente a lista. Para isso vamos usar o timerCOM que está configurado para gerar um evento a cada segundo. Inicialmente deve-se habilitar o timer logo após a inicialização do Form e colocar o método de atualização dentro do evento timerCOM_tick, conforme exibido a seguir:

Obs.: Para gerar o evento timerCOM_tick basta dar duplo clique no componente timerCOM na aba design.

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;  // necessário para ter acesso as portas
namespace interfaceArduinoVS2013
{
    public partial class Form1 : Form
    {
    public Form1()
    {
        InitializeComponent();
        timerCOM.Enabled = true;
    }
    private void atualizaListaCOMs()
    {
        int i;
        bool quantDiferente; //flag para sinalizar que a quantidade de portas mudou
        i = 0;
        quantDiferente = false;
        //se a quantidade de portas mudou
        if (comboBox1.Items.Count == SerialPort.GetPortNames().Length)
        {
           foreach (string s in SerialPort.GetPortNames())
            {
                if (comboBox1.Items[i++].Equals(s) == false)
                {
                    quantDiferente = true;
                }
            }
       }
        else
        {
            quantDiferente = true;
        }
        //Se não foi detectado diferença
        if (quantDiferente == false)
        {
            return;                     //retorna
        }
        //limpa comboBox
        comboBox1.Items.Clear();
        //adiciona todas as COM diponíveis na lista
        foreach (string s in SerialPort.GetPortNames())
        {
            comboBox1.Items.Add(s);
        }
            //seleciona a primeira posição da lista
        comboBox1.SelectedIndex = 0;
    }
    private void timerCOM_Tick(object sender, EventArgs e)
    {
        atualizaListaCOMs();
    }
}
}

insira outro Arduino ou crie uma porta COM virtual para verificar que é atualizado automaticamente o comboBox:

09timerAtualizaCOMs

No exemplo acima foi criada uma COM virtual com o auxílio do programa VSPE, que pode ser baixado aqui.

Agora já se pode escolher em qual porta a aplicação vai conectar. O evento click do btConectar será usado para  fazer a conexão. Para criar esse evento, selecione a aba de design do Form e dê um duplo clique no botão conectar. Será gerado o evento e agora deve-se  inserir o código para conexão. O botão conectar também servirá para desconectar quando a porta já estiver conectada, confira o código a seguir:


10testConectar

É necessário colocar uma proteção para que o programa não seja fechado e deixe a porta COM aberta, dessa forma impedindo que outros programas possam usá-la. Para isso vamos fechar a porta dentro do evento Form1_FormClosed:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;  // necessário para ter acesso as portas
namespace interfaceArduinoVS2013
{
    public partial class Form1 : Form
    {
    public Form1()
    {
        InitializeComponent();
        timerCOM.Enabled = true;
    }
    private void atualizaListaCOMs()
    {
        int i;
        bool quantDiferente; //flag para sinalizar que a quantidade de portas mudou
        i = 0;
        quantDiferente = false;
        //se a quantidade de portas mudou
        if (comboBox1.Items.Count == SerialPort.GetPortNames().Length)
        {
            foreach (string s in SerialPort.GetPortNames())
            {
                if (comboBox1.Items[i++].Equals(s) == false)
                {
                    quantDiferente = true;
                }
            }
        }
        else
        {
            quantDiferente = true;
        }
        //Se não foi detectado diferença
        if (quantDiferente == false)
        {
            return;                     //retorna
        }
        //limpa comboBox
        comboBox1.Items.Clear();
        //adiciona todas as COM diponíveis na lista
        foreach (string s in SerialPort.GetPortNames())
        {
            comboBox1.Items.Add(s);
        }
        //seleciona a primeira posição da lista
        comboBox1.SelectedIndex = 0;
    }
    private void timerCOM_Tick(object sender, EventArgs e)
    {
        atualizaListaCOMs();
    }
    private void btConectar_Click(object sender, EventArgs e)
    {
        if (serialPort1.IsOpen == false)
        {
            try
            {
                serialPort1.PortName = comboBox1.Items[comboBox1.SelectedIndex].ToString();
                serialPort1.Open();
            }
            catch
            {
                return;
                }
            if (serialPort1.IsOpen)
            {
                btConectar.Text = "Desconectar";
                comboBox1.Enabled = false;
            }
        }
        else
        {
            try
            {
                serialPort1.Close();
                comboBox1.Enabled = true;
                btConectar.Text = "Conectar";
            }
            catch
            {
                return;
            }
        }
    }
    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        if(serialPort1.IsOpen == true)  // se porta aberta
          serialPort1.Close();         //fecha a porta
    }
}
}

O processo para conexão e fechamento da porta serial já está feito, e o próximo passo é fazer o programa enviar para o Arduino o que for digitado dentro do textBoxEnviar. Para isso, dentro do evento btEnviar_Click, deve-se inserir o seguinte código:

private void btEnviar_Click(object sender, EventArgs e)
        {
            if(serialPort1.IsOpen == true)          //porta está aberta
            serialPort1.Write(textBoxEnviar.Text);  //envia o texto presente no textbox Enviar
        }

A recepção de dados requer um pouco mais de atenção. Inicialmente deve-se criar um evento serialPort1_DataReceived e uma variável global do tipo String. O processo de recepção acontece em uma Thread diferente da atualização dos componentes. A atualização do textBoxRebecer deve ser feita fora do evento de recepção da serial. Para isso criamos uma função trataDadoRecebido. Confira como ficará o código completo da aplicação:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;  // necessário para ter acesso as portas
namespace interfaceArduinoVS2013
{
    public partial class Form1 : Form
    {
        string RxString;
        public Form1()
        {
            InitializeComponent();
            timerCOM.Enabled = true;
        }
        private void atualizaListaCOMs()
        {
            int i;
            bool quantDiferente;    //flag para sinalizar que a quantidade de portas mudou
            i = 0;
            quantDiferente = false;
            //se a quantidade de portas mudou
            if (comboBox1.Items.Count == SerialPort.GetPortNames().Length)
            {
                foreach (string s in SerialPort.GetPortNames())
                {
                    if (comboBox1.Items[i++].Equals(s) == false)
                    {
                        quantDiferente = true;
                    }
                }
            }
            else
            {
                quantDiferente = true;
            }
            //Se não foi detectado diferença
            if (quantDiferente == false)
            {
                return;                     //retorna
            }
            //limpa comboBox
            comboBox1.Items.Clear();
            //adiciona todas as COM diponíveis na lista
            foreach (string s in SerialPort.GetPortNames())
            {
                comboBox1.Items.Add(s);
            }
            //seleciona a primeira posição da lista
            comboBox1.SelectedIndex = 0;
        }
        private void timerCOM_Tick(object sender, EventArgs e)
        {
            atualizaListaCOMs();
        }
        private void btConectar_Click(object sender, EventArgs e)
        {
            if (serialPort1.IsOpen == false)
            {
                try
                {
                    serialPort1.PortName = comboBox1.Items[comboBox1.SelectedIndex].ToString();
                    serialPort1.Open();
                }
                catch
                {
                    return;
                }
                if (serialPort1.IsOpen)
                {
                    btConectar.Text = "Desconectar";
                    comboBox1.Enabled = false;
                }
            }
            else
            {
                try
                {
                    serialPort1.Close();
                    comboBox1.Enabled = true;
                    btConectar.Text = "Conectar";
                }
                catch
                {
                    return;
                }
            }
        }
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if(serialPort1.IsOpen == true)  // se porta aberta
             serialPort1.Close();            //fecha a porta
        }
        private void btEnviar_Click(object sender, EventArgs e)
        {
            if(serialPort1.IsOpen == true)          //porta está aberta
            serialPort1.Write(textBoxEnviar.Text);  //envia o texto presente no textbox Enviar
        }
        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            RxString = serialPort1.ReadExisting();              //le o dado disponível na serial
            this.Invoke(new EventHandler(trataDadoRecebido));   //chama outra thread para escrever o dado no text box
        }
        private void trataDadoRecebido(object sender, EventArgs e)
        {
            textBoxReceber.AppendText(RxString);
        }
    }
}


Para testar a aplicação junto ao Arduino, vamos fazer o upload do seguinte sketch:

void setup()
{
  Serial.begin(9600);  //inicia comunicação serial com 9600
}
void loop()
{
  if(Serial.available())        //se algum dado disponível
  {
    char c = Serial.read();   //le o byte disponivel
    Serial.write(c);           //retorna o que foi lido
  }
}

Nesse programa o Arduino simplesmente retornará o dado que ele receber. Dessa forma, quando enviarmos dados pelo programa, estes serão exibidos no computador por meio do textBoxRecebe. A figura abaixo exibe os dados enviados e recebidos pela aplicação:

11testeComunicação

Agora que a aplicação está completa, ou seja, já conseguimos enviar e receber dados, vamos a um exemplo funcional. Conforme foi exibido no Artigo sobre comunicação serial no Arduino, vamos aproveitar o exemplo que acenderá o led através do comando vindo pela serial. Carregue o seguinte exemplo no Arduino:

/*
 * comandos via serial
 * inverte o estado do led conctado a saída 13 do arduino quando recebe o caracter 'A' pela serial
 */
const int LED = 13;
void setup() {
  Serial.begin(9600);    //configura comunicação serial com 9600 bps
  pinMode(LED,OUTPUT);   //configura pino do led como saída
}
void loop() {
   if (Serial.available()) //se byte pronto para leitura
   {
    switch(Serial.read())      //verifica qual caracter recebido
    {
      case 'A':                  //caso 'A'
        digitalWrite(LED,!digitalRead(LED)); //inverte estado do LED
      break;
    }
  }
}


Execute a aplicação, conectando a porta na qual o Arduino está ligado e envie o caractere ‘A’. Verifique o resultado no LED conectado ao pino 13 da placa Arduino:

12testeArduino

O download da aplicação completa pode ser feito através do link: Aplicação C# para interface serial com Arduino.
Lembre-se, você deve ser registrado e estar logado no site para fazer o download.





Postagem em destaque

Sistemas Supervisórios: o que são?

Sistemas Supervisórios: o que são? Quando você visita as instalações de uma indústria pode perceber, mesmo junto às máquinas mais pesa...