Hola a todos
En esta ocación les traigo un ejemplo que desarrollé como parte de un proyecto del grupo de investigación de robótica de la UTN. Se trata de la comunicación bilateral entre un Arduino Duemilanove y una PC con Windows.
La estructura de la comunicación es muy simple. Consta de un programa para la PC, hecho en VC#, que envía comandos separados con coma y un parser que los decodifica en el Arduino para realizar tareas específicas.
La trama se compone de la siguiente manera:
COMANDO PRINCIPAL, COMANDOS SECUNDARIOS, FIN DE LA TRAMA.
Por ejemplo, tenemos el siguiente comando: 3,1,* que significa LECTURA DE SENSORES ANALÓGICOS, LEE EL SENSOR 1, FIN DEL COMANDO.
El código para el Arduino es el siguiente:
El código para la PC es el siguiente:
Para poder probar los ejemplos hay que tener Visual C# 2010 instalado en la PC.
En esta ocación les traigo un ejemplo que desarrollé como parte de un proyecto del grupo de investigación de robótica de la UTN. Se trata de la comunicación bilateral entre un Arduino Duemilanove y una PC con Windows.
La estructura de la comunicación es muy simple. Consta de un programa para la PC, hecho en VC#, que envía comandos separados con coma y un parser que los decodifica en el Arduino para realizar tareas específicas.
La trama se compone de la siguiente manera:
COMANDO PRINCIPAL, COMANDOS SECUNDARIOS, FIN DE LA TRAMA.
Por ejemplo, tenemos el siguiente comando: 3,1,* que significa LECTURA DE SENSORES ANALÓGICOS, LEE EL SENSOR 1, FIN DEL COMANDO.
El código para el Arduino es el siguiente:
Código:
/*
Programa de prueba: Prueba de funcionamiento de la comunicación bilateral entre el arduino y la PC.
Programador: Moyano Jonathan.
Fecha: Abril 2012.
Versión: v0.5 Revisión: rev1.0
Placa: Arduino Duemilanove con ATmega328P.
*/
// Hacemos algunas definiciones de hardware.
int LED1 = 2; // El LED 1 está conectado al pin 2.
int LED2 = 3; // El LED 2 está conectado al pin 3.
// Declaramos algunas variables globales:
char cadenaRX[10] = "\0"; // Cadena para guardar los diferentes comandos.
boolean comandoCompleto = false; // Bandera que muestra cuando se termina de
// recibir un comando.
char *ptr; // Punteros auxiliares.
byte NEXT_char=0x00; // Contador de datos entrantes desde la USART.
byte comando=0x00; // Variable que guarda el comando principal.
byte MotorSelect=0x00; // Guarda el número de motor seleccionado.
int valor2=0x00; // Valor del sensor 2.
int valor=0x00; // Valor del sensor 1.
// Configuramos los pines usados y demás hardware asociado:
void setup(){
// Configuramos los pines asociados con los 2 led's.
pinMode(LED1,OUTPUT);
pinMode(LED2,OUTPUT);
// Configuramos el puerto serie para trabajar a 9.6Kbps.
Serial.begin(9600);
// Borramos el contenido del buffer de la USART.
memset(cadenaRX, 0, sizeof(cadenaRX));
}
void loop(){
// Si necesitamos procesar el sentido de giro de algún eje..
if(comando==0x01){
switch(MotorSelect){ // Determinamos a cuál motor nos referimos...
case 1: // En caso de que sea el motor 1.
// Movemos a la derecha.
if(valor==0){digitalWrite(LED1,HIGH); digitalWrite(LED2,LOW); comando=0x00; MotorSelect=0x00; valor=0x00;}
// Caso contrario movemos a la izquierda.
if(valor==1){digitalWrite(LED2,HIGH); digitalWrite(LED1,LOW); comando=0x00; MotorSelect=0x00; valor=0x00;}
break;
}
}
// Si necesitamos procesar el valor PWM del control de velocidad de algún eje..
if(comando==0x02){
switch(MotorSelect){ // Determinamos a cuál motor nos referimos...
case 1: // En caso de que sea el motor 1.
// Seteamos la velocidad.
analogWrite(5,valor);
break;
}
}
// Si necesitamos procesar el valor analógico de algún sensor...
if(comando==0x03){
switch(MotorSelect){ // Colocamos el motor al cuál el sensor está asociado..
case 1: // Para el motor 1.
// Leemos ambos sensores analógicos y enviamos el valor a la aplicación.
valor = analogRead(0); // Leemos el sensor 1.
valor2 = analogRead(1); // Leemos el sensor 2.
Serial.print("3"); // enviamos comando de lectura para sensores analógicos.
Serial.print(","); // delimitador.
Serial.print(valor); // Enviamos el valor del primer sensor. (10 bits).
Serial.print(","); // delimitador.
Serial.print(valor2); // Enviamos el valor del segundo sensor. (10 bits).
Serial.print(","); // delimitador.
Serial.print("*"); // Fin de comando.
Serial.println();
delay(100);
break;
}
}
// Parser.
if (comandoCompleto) {
// Extraemos primer comando.
ptr=strtok(cadenaRX,",");
// Formateamos y guardamos el contenido en una variable.
comando = atoi(ptr);
// Extraemos el segundo comando.
ptr=strtok(NULL,",");
// Formateamos y guardamos el contenido en una variable.
MotorSelect = atoi(ptr);
// Extraemos el tercer comando.
ptr=strtok(NULL,",");
// Formateamos y guardamos el contenido en una variable.
valor = atoi(ptr);
// Borramos el contenido del buffer de la USART.
memset(cadenaRX, 0, sizeof(cadenaRX));
// Inicializamos el index de lectura del buffer.
NEXT_char=0x00;
// información procesada..reseteamos la bandera para un nuevo comando.
comandoCompleto = false;
}
}
// Evento de recepción de datos por puerto serie.
void serialEvent() {
while(Serial.available()){ // Si el puerto está listo para recibir datos..
// Tomamos los datos entrantes.
char inChar = (char)Serial.read();
// Guardamos los datos en una cadena.
cadenaRX[NEXT_char] = inChar;
if(cadenaRX[NEXT_char]!='*')NEXT_char++;
if(NEXT_char==30)NEXT_char=0x00;
// Si recibimos el caracter final de comando, seteamos la bandera
// para que el bucle principal de programa pueda procesar la información.
if(inChar == '*') {
comandoCompleto = true;
}
}
}
El código para la PC es el siguiente:
Código:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO.Ports;
namespace PuertoSerie_Arduino_1
{
public partial class Form1 : Form
{
// Variables gloables.
string buffer = ""; // Creamos un buffer que contiene los datos que se enviarán al Arduino.
bool graficar_adc = false; // Activa o desactiva la posibilidad de graficar los datos provenientes de los sensores.
string data = ""; // Contiene datos provenientes de los sensores en forma de cadena.
string[] arreglo; // Usado para empaquetar los datos provenientes de los sensores.
volatile Int32 valor1 = 0x00; // Contiene el dato proporcionado por el sensor 1.
volatile Int32 valor2 = 0x00; // Contiene el dato proporcionado por el sensor 2.
public Form1()
{
InitializeComponent();
// Podemos llamar a objetos desde subprocesos que no los crearon.
Control.CheckForIllegalCrossThreadCalls = false;
}
private void Form1_Load(object sender, EventArgs e)
{
// Seleccionamos el estilo del ComBox al desplegarse.
this.PuertosDisponibles.DropDownStyle = ComboBoxStyle.DropDownList;
// Enumeramos los puertos series disponibles en nuestra PC.
this.PuertosDisponibles.DataSource = System.IO.Ports.SerialPort.GetPortNames();
// Enumeramos los puertos series según el número de Index.
this.PuertosDisponibles.TabIndex = 0;
// Desactivamos los controles al iniciar.
LED1_Button.Enabled = false;
LED2_Button.Enabled = false;
PWM_Send.Enabled = false;
PWM_Value.Enabled = false;
GraficaDAC.Enabled = false;
ADC_Value_1.Enabled = false;
ADC_Value_2.Enabled = false;
TimerAdq.Enabled = false;
}
private void ConectarDispositivo_Click(object sender, EventArgs e)
{
try
{
if (ConectarDispositivo.BackColor == Color.Red)
{
// Tratamos de abrir el puerto serie seleccionado.
Arduino.Open();
if (Arduino.IsOpen) // Si enlazó OK...
{
// Programación gráfica del botón cuando queremos conectarnos.
ConectarDispositivo.BackColor = Color.LimeGreen;
ConectarDispositivo.ForeColor = Color.Black;
ConectarDispositivo.Text = "CONECTADO";
// Activamos los controles.
LED1_Button.Enabled = true;
LED2_Button.Enabled = true;
PWM_Send.Enabled = true;
PWM_Value.Enabled = true;
GraficaDAC.Enabled = true;
ADC_Value_1.Enabled = true;
ADC_Value_2.Enabled = true;
}
}
else
{
// Intentamos cerrar las comunicaciones.
Arduino.Close();
if (Arduino.IsOpen == false) // Si cesaron las comunicaciones...
{
// Programación gráfica del botón cuando queremos desconectarnos.
ConectarDispositivo.BackColor = Color.Red;
ConectarDispositivo.ForeColor = Color.White;
ConectarDispositivo.Text = "DESCONECTADO";
// Desactivamos los controles.
LED1_Button.Enabled = false;
LED2_Button.Enabled = false;
PWM_Send.Enabled = false;
PWM_Value.Enabled = false;
GraficaDAC.Enabled = false;
ADC_Value_1.Enabled = false;
ADC_Value_2.Enabled = false;
graficar_adc = false;
// Eliminamos el valor de la barra de desplazamiento cada vez que desconectamos.
ADC_Value_1.Value = 0;
ADC_Value_2.Value = 0;
// Dejamos de capturar datos para graficarlos.
TimerAdq.Enabled = false;
}
}
}
catch
{
// En caso de no poder, mostramos un mensaje.
MessageBox.Show("No es posible conectarse al puerto seleccionado,\n" +
"asegurate que no lo esté usando otra aplicación.", "Aviso:",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void PuertosDisponibles_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
// Tratamos de seleccionar un puerto de la lista.
Arduino.PortName = Convert.ToString(PuertosDisponibles.SelectedValue);
}
catch {
// En caso de no poder, mostramos un mensaje.
MessageBox.Show("Hay un puerto de comunicaciones abierto,\n" +
"asegurate que no lo esté usando otra aplicación.", "Aviso:",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
private void LED1_Button_Click(object sender, EventArgs e)
{
// Desactiva la posibilidad de graficar.
TimerAdq.Enabled = false;
graficar_adc = false;
// Comando SENTIDO_MOTOR + comando MOTOR_SELECCIONADO + SENTIDO_SELECCIONADO + fin de instrucción.
buffer = "1" + "," + "1" + "," + "1" + "," + "*";
try
{
Arduino.Write(buffer); // Envía el paquete.
}
// En caso de que no pueda enviarlo, mostramos un mensaje de error.
catch
{
MessageBox.Show("Error al enviar dato, puerto serie ocupado o desconectado.", "Error:",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void LED2_Button_Click(object sender, EventArgs e)
{
// Desactiva la posibilidad de graficar.
TimerAdq.Enabled = false;
graficar_adc = false;
// Comando SENTIDO_MOTOR + comando MOTOR_SELECCIONADO + SENTIDO_SELECCIONADO + fin de instrucción.
buffer = "1" + "," + "1" + "," + "0" + "," + "*";
try
{
Arduino.Write(buffer); // Envía el paquete.
}
// En caso de que no pueda enviarlo, mostramos un mensaje de error.
catch
{
MessageBox.Show("Error al enviar dato, puerto serie ocupado o desconectado.", "Error:",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
if (Arduino.IsOpen) // Si las comunicaciones están abiertas...
{
try
{
Arduino.Close(); // Tratamos de cerrar la comunicación.
}
catch
{
// En caso de no poder, mostramos un mensaje.
MessageBox.Show("No es posible cerrar el puerto seleccionado,\n" +
"asegurate que no lo esté usando otra aplicación.", "Aviso:",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
public void Arduino_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
while (Arduino.IsOpen && graficar_adc==true)
{
data = Arduino.ReadLine(); // Leemos lo que hay en el puerto serie.
arreglo = data.Split(','); // Separamos los diferentes datos contenidos en la trama según el delimitador.
if (arreglo[0] == "3") // Si el primer dato de la trama es el comando para leer el ADC...
{
// arreglo[0] -> Comando específico: datos de los CAD.
// arreglo[1] -> Datos CAD 1.
// arreglo[2] -> Datos CAD 2.
// arreglo[3] -> Delimitador de los datos.
// Mostramos los datos de los CAD en las barras de progreso.
ADC_Value_1.Value = Convert.ToInt16(arreglo[1]);
valor1 = Convert.ToInt32(arreglo[1]);
ADC_Value_2.Value = Convert.ToInt16(arreglo[2]);
valor2 = Convert.ToInt32(arreglo[2]);
}
}
}
private void PWM_Send_Click(object sender, EventArgs e)
{
// Desactiva la posibilidad de graficar.
TimerAdq.Enabled = false;
graficar_adc = false;
// Comando PWM + comando MOTOR_SELECCIONADO + valor de PWM + fin de instrucción.
if (Convert.ToInt32(PWM_Value.Text) > 255) // Si el cuadro de texto toma un valor mayor a 255...
{
PWM_Value.Text = "0"; // Dejamos el valor en 0.
}
buffer = "2" + "," + "1" + "," + PWM_Value.Text + "," + "*";
try
{
Arduino.Write(buffer); // Envía el paquete.
}
// En caso de que no pueda enviarlo, mostramos un mensaje de error.
catch {
MessageBox.Show("Error al enviar dato, puerto serie ocupado o desconectado.", "Error:",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void GraficaDAC_Click(object sender, EventArgs e)
{
graficar_adc = true; // Comenzamos a graficar datos.
TimerAdq.Enabled = true; // Encendemos el timer que graficará los datos usando el componentes chart.
// Comando SENTIDO_MOTOR + comando MOTOR_SELECCIONADO + SENTIDO_SELECCIONADO + fin de instrucción.
buffer = "3" + "," + "1" + "," + "*";
try
{
Arduino.Write(buffer); // Envía el paquete.
}
// En caso de que no pueda enviarlo, mostramos un mensaje de error.
catch
{
MessageBox.Show("Error al enviar dato, puerto serie ocupado o desconectado.", "Error:",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void TimerAdq_Tick(object sender, EventArgs e)
{
// Grafica las señales de mis sensores.
GraficaCartesiana1.Series["SENSOR 1"].Points.AddY(valor1);
GraficaCartesiana1.Series["SENSOR 2"].Points.AddY(valor2);
// En cuanto tenemos la cantidad de datos necesarios para la lectura...
// Comenzamos a borrar la gráfica para que no se aglutinen los trazos.
if (GraficaCartesiana1.Series["SENSOR 1"].Points.Count > 250)
{
// Borra desde X = 0.
GraficaCartesiana1.Series["SENSOR 1"].Points.RemoveAt(0);
}
if (GraficaCartesiana1.Series["SENSOR 2"].Points.Count > 250)
{
// Borra desde X = 0.
GraficaCartesiana1.Series["SENSOR 2"].Points.RemoveAt(0);
}
}
}
}
Para poder probar los ejemplos hay que tener Visual C# 2010 instalado en la PC.