É um anseio entre os iniciantes na área de programação em C, a criação de programas que tenham uma interface amigável, com botões, menus entre outros componentes do windows. Aqueles que ja usam o arduino ou outros microcontroladores já deve ter sentido falta de um aplicativo com essas características e que se comunicasse com o PC via serial. Nesse tutorial, como é o primeiro e em um nível bem básico, será mostrado passo a passo como criar uma GUI que será capaz de enviar dados para o arduino, afim de comandar um LED(ligar e desligar), e um servomotor. O aplicativo será feito no ambiente do Visual Studio, na linguagem C#.É recomendável o Visual C# no minimo 2010, pois contem um componente novo, que é um tanto interessante para quem deseja plotar de gráficos, o Chart. Nesse e nos próximos tutoriais,não será exigido grande conhecimento da linguagem C#, basta que se tenha um pouco de conhecimento em C++, e caso, seja necessário o uso de alguma estrutura exclusiva do C#, será feita uma breve explicação. 1º PASSO: Criar um novo projeto em linguagem C#
Após criar um novo projeto,deverão aparecer 3 elementos muito importantes da IDE: A aba Design onde podemos editar a aparência do programa (formulário). A caixa de ferramentas (ou Toolbox) que contem os elementos que farão parte do nosso programa.Ex:Botões, caixas de rolagem, menus, etc.
A aba de propriedades (ou properties) onde podemos predefinir alguns comportamentos que os elementos (controles) terão.Ex:O texto que o formulário exibe, se um botão apresenta alguma imagem, etc.
2º PASSO: Design.
Iremos usar osa seguintes componentes: • • •
button hScrollBar SerialPort
Após achar cada um na caixa de ferramentas, arrastem-os e soltem no formulário. Redimensionem-os de modo que fique parecido com a figura abaixo. Naturalmente, para cada controle adicionado é atribuido a ele um nome que corresponde a classe a que pertencem e a um numero.Ex: button1, button2,hScrollBar1 button2,hScrollBar1 ,etc. Neste tutorial, mudei somente o nome do objeto SerialPort1 para Porta, na aba de propriedades.
Mudem também outras propriedades selecionando cada controle no formulário : Porta: Porta: •
•
PortName - "COMX"(X = Nº da porta em que o microcontrolador estará conectado) BaudRate - baud rate que está configurado no seu microcontrolador
button1: button1: •
Text - Off
hScrollBar1: hScrollBar1: •
Maximum - 189 (180 +LargeChange-1)
Form1(formulário): Form1(formulário): •
FormBorderStyle FormBorderStyle - FixedSingle (não permite redimencionamento do formulario) MaximizeBox - false (desabilita botão de maximizar)
Alguns objetos tem propriedades em comum, por exemplo experimente trocar a forma que o cursor é mostrado quando esta sobre o button1 e o hScrollBar1, selecione os dois ao mesmo tempo e mude a propriedade Cursor para hand. Após isso apertem F5 para rodar o programa.Ele deve estar com essa cara.
3º PASSO: Programação.
A programação para GUI geralmente é orientada a eventos o que significa que o controle de fluxo é guiado por indicações externas.Ex:Clique do mouse, tecla digitada, um novo dado no buffer serial,etc. O evento principal de cada controle, é acessado quando se da um duplo clique no elemento.Porém existem vários outros eventos que podem ser vinculados a cada controle, por exemplo nesse projeto usaremos dois eventos do formulário que são: load() e o FormClosing().
Nesse projeto serão usadas três funções do objeto Porta: • • •
Open(); Write(); Close();
O Porta.Open() foi colocado dentro do evento de load() do formulário, o que significa que será estabelecida conexão serial a partir do momento que o programa for carregado. O Porta.Close() foi colocado dentro do evento FormClosing(), para que seja encerrada a conexão com a porta serial assim que o programa for fechado. O Porta.Write() será usado no botão e na barra de rolagem, para enviar pro microcontrolador a informação que queremos. Ao dar um duplo clique no formulário deverá aparecer uma nova aba contendo alguns códigos, e o evento de Load .
No Load deve ficar assim: private void Form1_Load(object sender, EventArgs e) { try { Porta.Open(); } catch { MessageBox.Show("Cabo desconectado","Erro"); Close();
} }
O try e catch são usados para tratar exceções(erros), que irão aparecer se o programa for executado sem que a porta exista, no caso do arduino a porta só existirá se o cabo estiver conectado. Funciona da seguinte forma,"tente executar o que estiver dentro do try, caso ocorra a exceção faça o que estiver no catch" Nesse programa, "caso não consiga abrir comunicação com a serial, exiba uma mensagem ( MessageBox.Show("Cabo desconectado","Erro") ) e feche o programa usando o método Close().(Não confundir com Porta.Close()).
Indo no evento de FormClosing() devemos encerrar a comunicação: private void Form1_FormClosing(object sender, FormClosingEventArgs e) { try { if (Porta.IsOpen) { Porta.Close(); } } catch {} }
A estrutura try e catch se faz presente novamente,sempre que houver risco de desconexão do cabo de comunicação ela deve ser usada, sob pena de ver seu programa travar. Nesse evento uma condição simples, caso a porta esteja "aberta", feche-a. Dando um duplo clique no botão abrirá o evento de click private void button1_Click(object sender, EventArgs e) { try { if (button1.Text == "Off")
{ Porta.Write(comando, 1, 1); button1.Text = "On"; } else { Porta.Write(comando, 2, 1); button1.Text = "Off"; } } catch { MessageBox.Show("Cabo desconectado", "Erro"); Close(); } }
Antes de falar deste código, um array do tipo byte deve ser declarado pois a função Porta.Write() exige um array de bytes ou chars, então façam a declaração da seguinte forma: byte[] comando = { 0, 200, 201 };
antes de public Form1()
O primeiro elemento iremos usar para comandar o angulo do servo, de 0 a 180. Como o byte chega até 255, temos um intervalo entre 181 e 255 livre para arbitrarmos um byte para acender e outro para apagar o led. No caso arbitrei 200 para acender e 2001 para apagar; Neste evento iremos acender ou apagar o led," Se o botão estiver como titulo Off mande pela serial o 200" pois é o elemento 1 do array comando. o terceiro parâmetro da Porta.Write(comando, 1, 1) se refere a quantos bytes serão enviados, no caso só um o 200.Com isso o led deverá acender. Depois de enviar reescreva o Texto do botão para "On"; Quando o usuário clicar de novo será enviado um byte equivalente a 201 desligando o led e reescrevendo o texto do botão para "Off". No evento Scroll da barra de rolagem ocorre o seguinte: private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) { try { comando[0] = (byte)hScrollBar1.Value; Porta.Write(comando, 0, 1); Text = Convert.ToString(hScrollBar1.Value);
} catch { MessageBox.Show("Cabo desconectado", "Erro"); Close(); } }
A linha comando[0] = (byte)hScrollBar1.Value atribui ao primeiro elemento do array o valor atual da barra de rolagem.Logo em seguida o valor é transmitido para o microcontrolador que controla o servomotor. Depois esse valor é convertido para string e passado para o titulo do formulário para poder ser visualizado pelo usuário. O programa do arduino é este:
#include Servo servo1; void setup(){ Serial.begin(9600); pinMode(13,OUTPUT); servo1.attach(9); } void loop(){ if(Serial.available()){ byte dado = Serial.read();
if(dado <= 180) servo1.write(dado);
if(dado == 200) digitalWrite(13, HIGH);
if(dado == 201) digitalWrite(13, LOW); } } E o do aplicativo deve ficar assim:
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;
namespace Exemplo1 { public partial class Form1 : Form { byte[] comando = { 0, 200, 201 };
public Form1() { InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e) { try { Porta.Open(); } catch { MessageBox.Show("Cabo desconectado","Erro"); Close(); } }
private void Form1_FormClosing(object sender, FormClosingEventArgs e) { try { if (Porta.IsOpen) { Porta.Close(); } } catch {}
}
private void button1_Click(object sender, EventArgs e) { try { if (button1.Text == "Off") { Porta.Write(comando, 1, 1); button1.Text = "On"; } else { Porta.Write(comando, 2, 1); button1.Text = "Off"; } } catch { MessageBox.Show("Cabo desconectado", "Erro"); Close(); } }
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e)
{ try { comando[0] = (byte)hScrollBar1.Value; Porta.Write(comando, 0, 1); Text = Convert.ToString(hScrollBar1.Value); } catch { MessageBox.Show("Cabo desconectado", "Erro"); Close(); } } } } Com isso concluímos o nosso aplicativo, adaptem a suas necessidades.Até a próxima. É uma pergunta recorrente aqui no fórum, principalmente entre os que estão fazendo teste com carrinhos, ou robôs que precisem de orientação sobre o uso das teclas direcionais do PC. Neste tutorial, estarei mostrando como identificar que essas teclas foram pressionadas. Mostrarei dois exemplos, o primeiro mais rápido e fácil de criar, e o outro mais geral. Primeiro exemplo: Formulário “limpo”, sem outros componentes gráficos.
Nesse exemplo o único componente extra será o SerialPort usado pra a comunicação com o arduino. Ajustem nas propriedades o nome da porta e o baudRate.No meu caso alterei também o nome de serialPort1 para Porta.
Na lista de eventos do formulário existe um evento chamado KeyDown que é chamado quando uma tecla é pressionada quando o formulário tem foco.
Clicando nele, será aberto o código para a edição desse evento, modifique de forma que fique semelhante a isso: private void Form1_KeyDown(object sender, KeyEventArgs e) { try { Porta.Open(); if (e.KeyCode == Keys.Up) { Porta.Write("U"); Text = "Up"; }
if (e.KeyCode == Keys.Down) { Porta.Write("D"); Text = "Down"; } if (e.KeyCode == Keys.Right) { Porta.Write("R"); Text = "Right"; } if (e.KeyCode == Keys.Left) { Porta.Write("L"); Text = "Left"; } Porta.Close(); } catch { Text = "Erro na comunicação"; } } Pegarei para a explicação o seguinte trecho: if (e.KeyCode == Keys.Right) {
Porta.Write("R"); Text = "Right"; }
Trata-se de uma condição simples, o objeto e guarda entre outras coisas, a informação de qual tecla disparou esse evento. O que foi feito foi comparar essa informação com “Keys.Right” que representa o código da tecla direcional, nesse caso direita.Como resultado afirmativo podemos enviar o dado que nos for conveniente para o arduino, nesse caso enviei a um caráter ‘ R’ e escrevi no titulo do formulário “Right”. Porta.Open(),Porta.Close() e try ... catch já expliquei no tutorial passado.
Todo o programa se resume a isso. Segundo exemplo: Formulário que contenha outros componentes gráficos.
Para casos onde temos que usar outros elementos gráficos como botões, listas, caixas de texto no formulário, o método passado não funciona porque ele não receberá mais foco, e sim algum outro elemento já listado. Nesses casos devemos escolher quais dos elementos vão gerar o evento de KeyDown para usarmos o método passado. Nesse segundo exemplo adicionei 4 botões.
Pra quem já escreveu o Form1_KeyDown primeiro exemplo, podemos usa-lo como KeyDown de todos os quatro botões, aqui renomeei para teclado. Selecionando todos e relacionando aos eventos de KeyDown a mesma rotina teclado.
Até aqui alguem poderia pensar que não há diferença entre o primeiro exemplo e o segundo. De fato se fossemos usar outras teclas, por exemplo, as de caracteres, isso resolveria nosso caso. Porém queremos usar as direcionais que por padrão são usadas para navegar(mudar o foco) entre os controles, nesse caso botões. Sendo assim a priori não geram um evento de KeyDown. Isso pode ser revertido usando um evento que ocorre antes do KeyDown, o PreviewKeyDown. Acesse esse evento de qualquer controle edite o código da seguinte forma: private void detectar_tecla(object sender, PreviewKeyDownEventArgs e) { if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.Left || e.KeyCode == Keys.Right) { e.IsInputKey = true;
} } Aqui troquei o nome da função para detectar_tecla mas não é obrigatório.O mais importante é entender como ela funciona. Foi feita uma comparação para saber se a tecla que gerou o evento foi uma direcional, se sim, atribua a e.IsInputKey o valor true. O que significa que será gerado também um evento KeyDown logo em seguida. Repito, para teclas, como as alfabéticas, não se precisa usar o evento PreviewKeyDown que por padrão iram gerare.IsInputKey = true para essas teclas, desencadeando assim o KeyDown automaticamente . Depois atribua detectar_tecla aos PreviewKeyDown de todos os botões.
No evento do Click de cada botão coloquem o comando correspondente a sua tecla direcional, por exemplo: private void button3_Click(object sender, EventArgs e) {
try { Porta.Open(); Porta.Write("R"); Text = "Right"; Porta.Close(); } catch { Text = "Erro na comunicação"; } } Assim o evento do Click faz a mesma coisa do teclado, com uma pequena diferença. Caso o usuário mantenha o teclado pressionado o programa enviará informações de forma continua da mesma forma de quando se mantém pressionada uma tecla alfanumérica num editor de texto. Assim conseguimos enviar informações para o arduino, através de comando por teclado, adaptem a suas necessidades. Até a próxima. Código completo: http://pastebin.com/HNSwwJ9v Até agora sempre definíamos as propriedades como o nome da porta e BaudRate no projeto, para fins de testes funciona muito bem. Porém isso deixa o programa pouco flexível, já que a porta que o arduino usa pode mudar dependendo do PC onde ele tem os drives instalados. Neste tutorial estarei mostrando como dar o poder de escolha ao usuário de qual porta e baud rate usar, e para aqueles que usam o arduino, a escolha de habilitar ou não o reset via serial. Vou aproveitar e mostrar também o como salvar essas configurações para que da próxima vez que o programa for executado ele “lembre” delas e as tome como default. Primeiro passo: adicionar novos itens.
Depois de criar um novo projeto, no menu projeto escolham a opção adicionar novo item. Adicionem um novo formulário e coloquem o nome de “Config_Serial.cs” .
Depois adicionem um novo arquivo de configurações e coloquem o nome de “Minhas_Config.settings” .
Antes trabalhar com esses novos itens adicionem os seguintes controles no formulario1( Form1).
MenuStrip SerialPort
• •
Alterei o nome de serialPort1 para Porta fiz algumas modificações no menuStrip1 para que ficasse assim:
Apenas troquei a cor atribuindo a propriedade BackColor a cor GradientActiveCaption e criei um menu conexão adicionei as opções configurar e conectar. Dando um duplo click na opção configurar teremos acesso ao evento dele (Click). Qual é a ideia? Configurar qual porta e velocidade que vamos usar. Só que eu preferi fazer esse procedimento em outro formulário pra ganhar mais espaço no principal, pra isso adicionamos o formulário Config_Serial anteriormente. Bom, precisamos chamar esse novo formulário através do menu então façam o seguinte: private void configurarToolStripMenuItem_Click(object sender, EventArgs e) { Config_Serial janela2 = new Config_Serial(this); janela2.ShowDialog(); } Primeiro declaramos uma nova variável do tipo Config_serial , que corresponde ao nome que demos ao segundo formulário, com o nome janela2 e inicializamos ela passando este formulário ( Form1) como parâmetro usando a palavra this. Na segunda linha o segundo formulário é exibido.
É importante entender porque passar o Form1 como parâmetro neste caso. O que acontece é que as variáveis de um formulário não podem ser acessadas por outro. Como vamos mudar duas propriedade do objeto Porta através do segundo formulário, e aquele esta no primeiro, com esse procedimento poderemos mudar campos desse objeto. Também é importante que mudem a propriedade Modifiers do objeto Porta para public. No evento da opção Conectar do menu deixem parecido com isso: private void conectarToolStripMenuItem_Click(object sender, EventArgs e) { try { if (Porta.IsOpen) { Porta.Close(); conectarToolStripMenuItem.Text = "Conectar"; } else { Porta.Open(); if (reset) { Porta.DtrEnable = true; Porta.DtrEnable = false; } conectarToolStripMenuItem.Text = "Desconectar"; } } catch(SystemException erro)
{ MessageBox.Show(erro.Message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error); } } Traduzindo: se estiver conectado, desconecta e muda o texto do menu para “Conectar”.
Senão, conecta e muda o texto para “Desconectar”.
Se ocorrer algum erro mostra a descrição dele, que esta na variável erro.Message num formulário com titulo “Erro”, que tem um botão de ok e um ícone de Error .
Assim podemos iniciar e encerrar a comunicação quando quisermos. Abro um parêntese para falar do seguinte trecho: if (reset) { Porta.DtrEnable = true; Porta.DtrEnable = false; } Aqueles que usam o arduino, devem ter notado que o Serial Monitor é capaz de resetar o arduino assim que é aberto. Esse trecho de código faz o mesmo, se a variável reset for verdadeira, reseta o arduino ao conectar. Declarem essa variável com antecedência: public bool reset = false; public porque vamos acessa-la também no segundo formulário. 2º passo: segundo
formulário.
Agora que terminamos o formulário principal, vamos trabalhar o segundo que é onde faremos as configurações da porta. Adicionem os seguintes controles: • • •
1 x GroupBox 2 x Label: 2 x ComboBox
• •
1 x Checkbox 1 x Timer
Redimencionem e mudem os text para que fique dessa forma:
•
•
•
Mudem a propriedade FormBorderStyle para FixedToolWindow para impedir redimensionamento em tempo de execução, e retirar os botões de maximizar e minimizar. Mudem também para DropDownList da propriedade DropDownStyle dos dois comboBox’s No timer1 deixem um intervalo de 1000 ms e deixem a propriedade enabled em true.
Esse timer vai servir para atualizar a lista de portas que o PC tem disponível. No comboBox correspondente ao baud rate, no meu caso o comboBox2, coloquei as velocidades padrão do Serial Monitor na propriedade items.
Vejam o resultado:
No meu caso só existe a porta que o arduino está, mas quem tiver mais de uma, pode acontecer de os nomes ficarem fora de ordem. Pra corrigir isso basta mudar a propriedade Sorted do comboBox1 para true. No evento checkBox1_CheckedChanged façam o mesmo que o exemplo abaixo: private void checkBox1_CheckedChanged(object sender, EventArgs e)
{ if(checkBox1.Checked) { reset2 = true; } else { reset2 = false; } } Declarem antes a variável bool reset2 = false; que será responsável por habilitar o reset do arduino ao conectar. Ok, o usuário escolheu, qual porta, qual velocidade, e até se deseja resetar o arduino. Agora resta passar essa informações pro Form1 já que é nele que está o objeto Porta. Arbitrei que iria passar esses dados quando fechasse as configurações. Lembrem se que passamos o Form1 como parâmetro quando fizemos: Config_Serial janela2 = new Config_Serial(this);
Para usar esse parâmetro faremos o seguinte: Form1 janela1; public Config_Serial(Form1 x) { InitializeComponent(); janela1 = x; } Logo que clicamos em algum controle para ter acesso a algum evento esse método já é automaticamente gerada para nós logo no começo do código. Ele originalmente vem sem parâmetros. O que fizemos aqui foi declarar um objeto do tipo Form1com nome janela1 e recebeu o valor x passado pelo formulário anterior.
Agora podemos passar as informações para o formulário anterior fazendo: private void Config_Serial_FormClosing(object sender, FormClosingEventArgs e) { try { janela1.Porta.BaudRate = Convert.ToInt32(comboBox2.Text); janela1.Porta.PortName = comboBox1.Text; janela1.Text = comboBox1.Text + "/" + comboBox2.Text; janela1.reset = reset2; } catch { DialogResult escolha; escolha = MessageBox.Show("Prencha todos os campos ", "Aviso", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation); if (escolha == System.Windows.Forms.DialogResult.OK) { e.Cancel = true; } } } O código acima tenta passar os dados para o formulário anterior, caso não consiga ele mostra uma mensagem que diz:"Prencha todos os campos corretamente" , e dá duas opções ao usuário: •
•
Ok: significa que o evento será cancelado(e.cancel ) para correção das informações Cancel: segue com o evento, fechando o formulário.
Passo 3: Configurar
definições.
Ufa! Conseguimos, por aqui termina para quem se deu por satisfeito com o resultado. Porém é um pouco chato sempre ter que ir no menu e escolher de novo os parâmetros para configurar a conexão serial, seria interessante que o programa guardasse essas informações mesmo depois de fechado. Pra isso mesmo que adicionamos no começo do projeto um arquivo de configurações com o nome de minhas_config.settings. Ele vai guardar dados que serão usados na próxima vez que o programa for executado. Ele deve ter essa cara:
Aqui já defini alguns valores que eu desejo guardar. A ideia é atualizar e salvar esses valores quando fecharmos o programa, logo devemos usar o evento FormClosing do Form1 private void Form1_FormClosing(object sender, FormClosingEventArgs e) { try { Minhas_Config.Default.Nome_Porta = Porta.PortName; Minhas_Config.Default.Baud_Rate = Porta.BaudRate; Minhas_Config.Default.restart_arduino = reset; Minhas_Config.Default.Save(); } catch { MessageBox.Show("Erro ao salvar!Reconfigure quando executar novamente","Erro"); }