Cómo interpretar comandos complejos de una serie con arduino

3

En un proyecto que involucra una comunicación en serie y un Arduino, me gustaría usar la interfaz en serie para ejecutar varias rutinas en la placa.

La idea es enviar una cadena única con etiquetas y valores para ejecutar varias instrucciones al mismo tiempo. Digamos que queremos establecer el rumbo de un avión en 180 °, la altitud a 3 metros y 20 cm desde el suelo y mantener un perfil horizontal con balanceo y ángulo de inclinación de 0 °. La cadena sería:

  

X, rumbo, 180, tirada, 0, tono, 0, altitud, 3.20, X

Por motivos de simplicidad, supongo que enviaré una cadena menos compleja como:

  

X, tag1, tag2, tag3, val3, X

     

X, Roll, Con, Kp, 1.12, X

Para recibir y elaborar la cadena, he intentado usar 3 matrices de caracteres y algunos contadores. Aquí está el código:

   byte byteRead;
   // Store decimal numbers, determine decimal point
   double num1, num2;
   double complNum,counter;
   int numOfDec;
   boolean mySwitch=false;
   // Use a boolean var to enter in the command receiving mode. 
   // If you are interpreting several commands type this could be a way
   boolean cmplx = false;
   // arrays to store tags
   char opt1[3];
   char opt2[3];
   char opt3[2];  
   // Counters to determine tags
   int optCount=0,letterCount=0;

   void setup() 
   {                
     Serial.begin(9600);
     num1=0;
     num2=0;
     complNum=0;
     counter=1;
     numOfDec=0;
   }

   void loop() 
   {
     /*  check if data has been sent from the computer: */
     while (Serial.available()) 
     {
       /* read the most recent byte */
       byteRead = Serial.read();

       if (byteRead == 'X')
       {
         if (!cmplx)
         {
           // begin of the string
           cmplx = true;
         }
         else
         {
           // end of the string - reset values
           cmplx=false;
           optCount=0;
           /* Create the double from num1 and num2 */
           complNum=num1+(num2/(counter));
           /* Reset the variables for the next round */

           // Debug Stuff ignore it
           Serial.println();
           Serial.print("     opt1: ");
           Serial.print(opt1);
           Serial.print("     opt2: ");
           Serial.print(opt2);
           Serial.print("     opt3: ");
           Serial.print(opt3);
           Serial.print("    NUMBER: ");
           Serial.print(complNum);
           Serial.print("     letterCount: ");
           Serial.print(letterCount);
           Serial.print("    optCount: ");
           Serial.print(optCount);

           // How to reset arrays?
           opt1[0] = (char)0;
           opt2[0] = (char)0;
           opt3[0] = (char)0;
           num1=0;
           num2=0;
           complNum=0;
           counter=1;
           mySwitch=false;
           numOfDec=0;
         }
       }
       if (byteRead==44)
       {
         // Comma
         optCount++;
         // Debug stuff
         Serial.println();
         Serial.print("Virgola numero: ");
         Serial.println(optCount);
         letterCount = 0;
       }
       // Listen for a capital letter or a normal one
       if ((byteRead>=65 && byteRead<=90) || (byteRead>=97 && byteRead<=122))
       {
         // Debug stuff        
         Serial.println();
         Serial.print("lettera (Ascii value): ");
         Serial.print(byteRead);
         Serial.print("   ");
         if (cmplx)
         {
          if (optCount==1 && letterCount<=3)
          {
           opt1[letterCount] = byteRead;
           // Debug stuff
           Serial.print("letterCount: ");
           Serial.print(letterCount);
           Serial.print("opt1: ");
           Serial.print(opt1);
          }
          else if (optCount==2 && letterCount<=3)
          {
           opt2[letterCount] = byteRead;
           // Debug stuff
           Serial.print("letterCount: ");
           Serial.print(letterCount);
           Serial.print("opt2: ");
           Serial.print(opt2);
          }
          else if (optCount==3 && letterCount<=2)
          {
           opt3[letterCount] = byteRead;
           // Debug stuff
           Serial.print("letterCount: ");
           Serial.print(letterCount);
           Serial.print("opt3: ");
           Serial.print(opt3);
          }
          letterCount++;
         }
       }
       //listen for numbers between 0-9
       if(byteRead>47 && byteRead<58)
       {
          //number found
          if (cmplx)
          {
            /* If mySwitch is true, then populate the num1 variable
            otherwise populate the num2 variable*/
            if(!mySwitch)
            {
              num1=(num1*10)+(byteRead-48);
            }
            else
            {
              num2=(num2*10)+(byteRead-48);       
              // Counters used to correctly store decimal numbers
              counter=counter*10;
              numOfDec++;
            }
          }
       }
      // Looks for decimal points
      if (byteRead==46)
      {
          mySwitch=true;
      }
   }
 }

Una vez que los arreglos opt1, opt2 y opt3 estén correctamente rellenados, puedo compararlos con etiquetas y luego llamar a la rutina correspondiente.

El problema

Estoy bastante cerca, el código almacena los números decimales correctamente, pero no con las matrices . La salida me sale insertando esta cadena

X, Rol, Con, Kd, 1.12, X

es el siguiente:

lettera: 88   
virgola numero: 1

lettera: 82   letterCount: 0   opt1: R
lettera: 111   letterCount: 1   opt1: Ro
lettera: 108   letterCount: 2   opt1: Rol
virgola numero: 2

lettera: 67   letterCount: 0   opt2: C
lettera: 111   letterCount: 1   opt2: Co
lettera: 110   letterCount: 2   opt2: Con
virgola numero: 3

lettera: 75   letterCount: 0   opt3: K
lettera: 100   letterCount: 1   opt3: KdX,Rol,Con,Kd,1.12,X  <- WTF?
virgola numero: 4

virgola numero: 5

opt1: RolConKdX,Rol,Con,Kd,1.12,X   <- WTF?
opt2: ConKdX,Rol,Con,Kd,1.12,X  <- WTF? 
opt3: KdX,Rol,Con,Kd,1.12,X  <- WTF?
NUMBER: 1.12 
lettera: 88 

¿Cómo restablecer las matrices de caracteres rápidamente y por qué las matrices están completamente desordenadas?

    
pregunta UserK

1 respuesta

4

Realmente creo que deberías dar un paso atrás y reconsiderar la idea de hacer esto con cadenas, y considerar diseñar un protocolo binario. Además de organizar mejor su código, terminará con algo que es menos propenso a errores, da como resultado transmisiones más cortas (y un código más compacto), y se prestará mejor para futuras mejoras. La compatibilidad hacia atrás también será enormemente más fácil! Por ejemplo, podría crear algunas estructuras simples como la que se muestra a continuación, que luego podrían colocarse o recuperarse fácilmente de un búfer de envío o recepción, mediante el uso de punteros de estructura.

    typedef struct mcTag {
       unsigned char srcAddr;
       unsigned char dstAddr;
       unsigned long version
       unsigned char numCmds;
       unsigned char hdrLength;
       unsigned char cmdLength;
       unsigned short totalLen
       unsigned short crc } MyControlHdr;

    typedef struct ctrTag {
      unsigned char cmd;
      long param1;
      long param2;
      long param3;
      long param4;   } MyCommand;

    unsigned char buffer[255];  // or worst case message size
    MyControlHdr * pCtrlHdr = (MyControlHdr *)(&buffer[0]);

Nuevamente, lo anterior es solo para ilustrar una idea. Al definir un encabezado para comenzar cada mensaje, ahora puede agregar cosas como las direcciones de origen y destino. Esa pequeña adición podría permitirle a su proyecto enviar y recibir fácilmente de múltiples remitentes, o al menos diferenciar entre ellos. El encabezado se colocaría primero en el búfer de comunicación y se llenaría con el puntero pCtrlHdr, tan fácilmente como esto ...

pCtrlHdr->srcAddr = 1;
pCtrlHdr->dstAddr = 1;    // maybe you'll make 2555 a broadcast address?
pCtrlHdr->version = 1;    // possible way to let a receiver know a code version
pCtrlHdr->numCmds = 2;    // how many commands will be in the message
pCtrlHdr->hdrLength = sizeof(MyControlHdr );  // tells receiver where commands start
pCtrlHdr->cmdLength = sizeof(MyCommand );     // tells receiver size of each command 
   // include total length of entire message
pCtrlHdr->totalLen= sizeof(MyControlHdr ) + (sizeof(MyCommand) * pCtrlHdr->numCmds);

Luego, podría colocar sus comandos en el búfer (2 en este caso) como este

MyCommand * pMyCmd = (MyCommand *)(&buffer[sizeof(MyControlHdr)]);
// first command in this message
pMyCmd->cmd = 3;      // might indicate heading
pMyCmd->param1 =100;  // various data, perhaps X,Y, and Z
pMyCmd->param2 =5;
pMyCmd->param3 =0
pMyCmd->param4 =0;

pMyCmd++;           // moves pointer to next command position in message
pMyCmd->cmd = 6;    // might indicate a speed directive
pMyCmd->param1 =8;  // various data, unused items left 0
pMyCmd->param2 =0;
pMyCmd->param3 =0
pMyCmd->param4 =0;

Finalmente, es posible que desee agregar un CRC de todo el mensaje, nuevamente en el encabezado. Por supuesto, esto requeriría una función de cálculo de CRC por separado.

pCtrlHdr->crc = 0;   // dummy temp value
pCtrlHdr->crc = calcCrc(buffer, pCtrlHdr->totalLen); // crc function

Nuevamente, esto es solo un ejemplo simple de lo que podrías hacer con un protocolo binario, y aquí están algunas de las ventajas.

  1. El receptor puede probar instantáneamente la integridad de todo el mensaje, al verificar el CRC transmitido contra un cálculo local. ¡Los CRC son importantes! Los controladores de serie son conocidos por soltar caracteres, convirtiendo un "100" en un "00" !!!. Y, si alguna vez decide migrar a la radio, puede buscar fácilmente un mal mensaje causado por interferencia.
  2. Si alguna vez agrega algo al encabezado o estructura de comando y se compromete a agregar siempre nuevos elementos al FINAL de cada estructura, ahora puede escribir un código de receptor que pueda compensar fácilmente los cambios de su versión y seguir siendo compatible. Y dado que el tamaño de los encabezados y los comandos se incluyen en el mensaje, el código del receptor puede incluso compensar los tamaños expandidos de encabezados y comandos. La inclusión de una versión real también es útil para detectar cuándo las cosas cambian hasta el punto en que podría existir un problema de compatibilidad.
  3. El compilador ahora realiza todos los tamaños y posiciones de búfer en el código, lo que reduce el riesgo de error.
  4. La mayor ventaja es que este tipo de planificación de protocolo hará que su código sea mucho más fácil de escribir, depurar y mantener.

En ese último punto, en mi vida anterior escribí una tonelada de controladores de comunicación a lo largo de los años para recuperar datos y realizar el control de muchos dispositivos, en serie, radio y enlaces de red, con varios otros codificadores. Cuando había un dispositivo con un protocolo binario para ser tratado, siempre era un placer escribir el controlador. Pero cuando se trataba de un protocolo ASCII, que consistía en cadenas y números decimales, todos nos estremecíamos. Las longitudes a las que tendríamos que pasar para tener en cuenta cada posible error consumirían demasiado tiempo, serían difíciles de probar por completo y, al final, a menudo se quedarían cortos y requerirían soluciones infinitas. Y tan malo, ya que era ASCII (presumiblemente para facilitar la lectura por parte de un humano) cualquier actualización o cambio de dispositivo en el dispositivo siempre causaría errores imprevistos, rompiendo no solo nuestro propio código de controlador sino también el del fabricante. :-) Un protocolo binario, con un poco de planificación, hará que su proyecto sea mucho más fácil de mantener, y definitivamente se agradecerá a sí mismo por hacer un esfuerzo adicional cuando el proyecto se expanda o vaya a un enlace de radio. :-)

    
respondido por el Randy

Lea otras preguntas en las etiquetas