El valor del PIN de Arduino se atasca

Estoy usando un programa de Python para enviar un mensaje a través del puerto serie a Arduino. Este mensaje contiene el número de pin del pin y si debe ser HIGHo LOW. Por ejemplo: el envío 2H\ndebe establecer el pin 2 en HIGH.

pinsarray asigna los números de pin en los mensajes al número de pin real de arduino. 1Hcorresponde al pin 22 de Arduino Mega.

Al enviar manualmente un mensaje a la vez, las cosas funcionan bien. Sin embargo, cuando Python envía una serie de 30 mensajes de este tipo uno tras otro en un bucle sin retrasos, el pin 1 siempre se atasca en el valor que se haya establecido en la primera serie de mensajes.

Ejemplo:

1L\n
2H\n
3H\n
4H\n
5H\n
...

seguido por

1H\n
2H\n
3H\n
4H\n
5H\n
...

hará que el pin 1 se atasque LOWcuando debería estar alto.

En arduino, este es el código que analiza el mensaje y establece los valores de los pines.

void setPinValues(String message) {

  for(int i = 1; i <= sizeof(pins) / sizeof(pins[0]); i++ ) {
    String pinNumber = String(i);

    if( message == pinNumber + "H\n" ) {
      pinValues[i] = HIGH;
    }
    if( message == pinNumber + "L\n" ) {
      pinValues[i] = LOW;
    }
  }

}


void loop(){
    if( Serial.available() > 0 ) {
        received = Serial.read();
        message += received;
        if(received == '\n') {

            // Set pin values
            setPinValues(message);

            // Write to pins        
            for (int i = 1; i <= sizeof(pins) / sizeof(pins[0]); i++) {
                digitalWrite(pins[i], pinValues[i]);
            }

            // Clear buffer
            message = "";

        }       

    }

}

Arduino Mega se comunica con el sistema Windows 8 x64 a través de USB usando una tasa de baudios de 57600. Cuando se usa una tasa de baudios más baja de 9600, este problema no se ve.

Además, a una velocidad de transmisión de 57600, si tuviera que reemplazar setPinValuescon el siguiente código, el pin 1 se enciende y apaga correctamente.

void setPinValues(String message) {

  for(int i = 1; i <= sizeof(pins) / sizeof(pins[0]); i++ ) {
    if( message == String(1) + "H\n" ) {
      pinValues[1] = HIGH;
    }
    if( message == String(1) + "L\n" ) {
      pinValues[1] = LOW;
    }
    if( message == String(2) + "H\n" ) {
      pinValues[2] = HIGH;
    }
    if( message == String(2) + "L\n" ) {
      pinValues[2] = LOW;
    }
    if( message == String(3) + "H\n" ) {
      pinValues[3] = HIGH;
    }
    if( message == String(3) + "L\n" ) {
      pinValues[3] = LOW;
    }
  }

}

¿Ambas versiones de no setPinValueshacen lo mismo? ¿Por qué la reducción de la velocidad en baudios evita el problema? No puedo usar una tasa de baudios más baja porque el búfer USB se llena y las cosas se vuelven lentas.


Para comparar el último carácter Ho L:

String pinValueString = message.substring(message.length() - 1);
char pinValueBuffer[2];
pinValueString.toCharArray(pinValueBuffer,2);

if(pinValueBuffer[0] == 'H') {
    digitalWrite(pins[pinNumber], HIGH);
}
Recuerda que estás en una plataforma integrada. Debe preocuparse por la eficiencia de su código. Aquí usaría una interrupción y almacenaría en búfer todos los valores recibidos. De esta manera me aseguraría de que no se pierda ninguno de los chr recibidos. Y luego, en un ciclo principal, procesaría el contenido del búfer y actuaría en los pines en consecuencia. También podría encender un LED en caso de que el búfer se llene (para avisarle). O podría asegurar que el procesamiento sea siempre más rápido que la velocidad a la que se reciben los chr en el peor de los casos.
@Blup1980 IIRC Arduino se encarga de la interrupción en serie por usted. Serial.available()y Serial.read()son solo envoltorios alrededor del búfer en serie adjunto a la interrupción por Arduino cuando llama Serial.begin(), por lo que en realidad ya está haciendo lo que sugiere :) El problema probablemente radica en el llenado del búfer de 128 bytes y la eliminación de bytes debido al procesamiento muy lento.
Solo para tu información. Ahora hay una pila dedicada a Arduino arduino.stackexchange.com

Respuestas (2)

No estoy seguro si este es tu problema, pero setPinValuesme parece raro:

void setPinValues(String message) {
  for(int i = 1; i <= sizeof(pins) / sizeof(pins[0]); i++ ) {
    String pinNumber = String(i);

    if( message == pinNumber + "H\n" ) {
      pinValues[i] = HIGH;
    }
    if( message == pinNumber + "L\n" ) {
      pinValues[i] = LOW;
    }
  }
}

Saliendo de los límites

Cada vez que llame setPinValues, ise extenderá de 1a length(pins), por lo que si se pinsdeclaró como pins[5], iserá 1,,, y . Suponiendo que esto es un problema en sí mismo, ¡porque ni siquiera existiría!2345length(pins) == length(pinValues)pinValues[5]

pinValues[5]solo crea pinValues[0]para (sí, sé que es extraño, está declarando 5 elementos pero, dado que C está indexado en 0 , pinValues[4]son 0, , , y ), por lo que NUNCA debe llegar a 5 o estará fuera de los límites, lo cual es comportamiento indefinido (y bien podría ser la fuente de sus problemas) porque está escribiendo en la ubicación de memoria incorrecta.1234i

La forma correcta de hacer esto es recorrer desde 0hasta i < sizeof(pins) / sizeof(pins[0])(observe el estricto operador menor que <) que va desde 0hasta length(pins) - 1y luego solo String pinNumber = String(i + 1);. De esa manera, 1Hcambiará el valor de pinValues[0], asignando sus mensajes en serie indexados en 1 a matrices C indexadas en 0.

El bucle es innecesario

No estoy seguro de por qué estás dando vueltas pins. Está haciendo un trabajo innecesario y probablemente acaparando el uC (especialmente porque, como señala Dave Tweed en la otra respuesta, está usando funciones costosas como Stringy comparación de cadenas con ==).

messagetiene una sola instancia, por ejemplo, 1H\npor lo que no se necesita un bucle. char pinNumber = message[0] - '0';almacenará el valor entero del primer carácter en pinNumber, con el que podrá indexar la matriz.

¿Cómo funciona?

  • message[0]sería un chartipo (por ejemplo '1')
  • '0'es también un char, que corresponde al valor ASCII de 0 (48 en decimal).
  • Si message[0]contiene el char '0', entonces el resultado es'0' - '0' == 48 - 48 == 0
  • Dado que los números son contiguos y aumentan en el conjunto ASCII, el resultado de la resta es el número real como un entero de un solo byte.
  • '1' - '0' == 49 - 48 == 1y así sucesivamente.

También es posible que desee rechazar entradas falsas:

if (pinNumber >= sizeof(pins) / sizeof(pins[0]))
  return;

Más trabajo espurio

En cada loop()iteración haces esto:

// Write to pins        
for (int i = 1; i <= sizeof(pins) / sizeof(pins[0]); i++) {
  digitalWrite(pins[i], pinValues[i]);
}

Que tiene el mismo problema con los fuera de límites que antes.

También hace un trabajo innecesario, ya que está escribiendo TODOS los valores de los pines, ya sea que hayan cambiado o no. ¿Por qué no digitalWrite(pins[pinNumber], HIGH or LOW);entras directamente setPinValuesy evitas este bucle? Incluso podría deshacerse de la pinValuesmatriz si realmente no necesita almacenarla (o puede conservarla si la necesita por algún motivo).

Mi alternativa setPinValues

void setPinValue(char pin, char value) {
  char pinNumber = pin - '0';  // Get the integer value of the ASCII char

  if (pinNumber >= sizeof(pins) / sizeof(pins[0]))  // Reject out-of-bounds input
    return;

  switch (value) {
    case 'H':
      pinValues[pinNumber] = HIGH;  // Not necessary if you don't want to store the pin values
      digitalWrite(pins[pinNumber], HIGH);
      break;
    case 'L':
      pinValues[pinNumber] = LOW;  // Not necessary if you don't want to store the pin values
      digitalWrite(pins[pinNumber], LOW);
  }
}

Y luego simplemente llámalo desde loop()comosetPinValue(message[0], message[1]);

Déjame saber si esto resuelve tu problema.

¡Gracias! Si hay más de 10 pines, por lo que existe la posibilidad de recibirlos 13H\n, char pinNumber = message[0] - '0';¿seguirá funcionando? Estoy pensando en dividir un mensaje como 13H\nen 13y Husando strstr(), pero no estoy seguro de cómo se puede hacer
@Nyxynyx no sin un bucle escaneando todos los caracteres. En ese caso String.toInt()es tu mejor amigo. IIRC, ni siquiera necesita dividir la cadena, ya que .toInt()analizará hasta el primer carácter no numérico, por lo que, siempre que su cadena comience con el número pin, estará bien. ¡Sería genial si confirma si .toInt()funciona así, para futuras referencias!
¡ Sí, String.toInt()funciona como se esperaba! Para comparar el último carácter H o ​​L, uso el código con el que acabo de actualizar la pregunta original. ¿Será eso lo óptimo? Usé ifcondicionales porque al usar switch, recibí el errorswitch quantity not an integer
@Nyxynyx switch quantity not an integersignifica que no pusiste charcomo el tipo de tu valor de cambio, ¿verdad? chares un tipo de datos entero, por lo que debe funcionar (si no, ¡simplemente (int)cámbielo!) Además, []funciona con StringAFAIK, por lo que no necesita la sección intrincada .substringy , solo para obtener el o . .toCharArraymessage[message.length() - 1]'H''L'
@Nyxynyx (pasó el tiempo de edición) en realidad sería message[message.length() - 2]para tenerlo \nen cuenta, aunque sería más simple y solo message += received; si received != '\n' .
Utilicé message.replace("\n", "");el que supongo que es más lento.
@Nyxynyx '\n'es un solo carácter (si no lo fuera, no podría ponerlo entre comillas simples). Es posible que lo esté confundiendo "\r\n"(observe las comillas dobles, es una cadena de caracteres) que es una convención solo de Windows, solo para archivos de texto y completamente ajena a las comunicaciones en serie que son independientes de la codificación. No uses .replaceni nada por el estilo, es más lento e innecesario. ¡En los dispositivos integrados, debe mantener las cosas al mínimo!
¡Esa es una respuesta tan completa que merece un +2! Numerosos pecados de codificación cubiertos y corregidos con estilo.
Si estuviera usando más de 10 pines, cambiaría el formato del mensaje para usar siempre dos dígitos para el número de pin: 01H, 05L, 13H, etc. De esa manera, H/L siempre es una cadena[2], para facilitar acceso.

Es un problema de rendimiento. La primera versión de setPinValues()está haciendo muchas llamadas a String()cada vez que se llama, y ​​esta es una función no trivial. Esto hace setPinValues()que tarde mucho más de lo que piensas en terminar, por lo que no es demasiado sorprendente que el Arduino finalmente no pueda seguir el ritmo de los mensajes entrantes a 57600 bps.

En la segunda versión de setPinValues(), los argumentos de las String()llamadas son todos constantes, por lo que se evalúan en tiempo de compilación. Esta versión se ejecuta mucho más rápido y puede mantenerse al día en 57600.