¿Cómo usar correctamente las variables volátiles en Arduino?

Estaba haciendo un pequeño proyecto con un Arduino Uno. Implicó interrupciones ya que estoy usando codificadores para medir cuánto avanza el sistema de rueda diferencial. Mi robot solo avanza. Entonces uso solo un canal de cada codificador. Aquí están mis dos rutinas de interrupción:

ISR (INT0_vect){ encoderRPos = encoderRPos + 1; } ISR (INT1_vect){ encoderLPos = encoderLPos + 1; } 

Las variables encoderRPos y encoderLPos son de tipo volatile int. Entiendo que las variables que sufren cambios en cualquier rutina de interrupción deben ser de tipo volátil. Esto es para advertir a otras partes del código que usan estas variables que pueden cambiar en cualquier momento.

Pero lo que sucedió en mi código fue un poco extraño y no pude explicarlo. Así es como calculo la distancia movida por la rueda izquierda:

 #define distancePerCount 0.056196868 float SR = distancePerCount * (encoderRPos - encoderRPosPrev); float SL = distancePerCount * (encoderLPos - encoderLPosPrev); encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos; 

Pero cuando imprimo lo siguiente en mi monitor serial, noto una anomalía:

ingrese la descripción de la imagen aquí

Si observa la tercera columna, (SL) su valor es demasiado alto solo por un tiempo. Esto está alterando todos mis cálculos.

La única pista que puedo obtener es si tomo el valor de SL que obtuve ( 3682), que siempre es una constante, y calcula de nuevo (encodeLPos - encoderLPosPrev), obtendré 65519.66, que está cerca del valor máximo de unsigned int . ¡Lo que significa que (encoderLPos - encoderLPosPrev) está causando un desbordamiento mientras que los dos valores cuya diferencia se toma son alrededor de 5000 solamente!

Y logré resolverlo. Fue por suerte. Así es como modifiqué el código:

 static int encoderRPosPrev = 0; static int encoderLPosPrev = 0; int diffL = (encoderLPos - encoderLPosPrev); int diffR = (encoderRPos - encoderRPosPrev); float SR = distancePerCount * diffR; float SL = distancePerCount * diffL; encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos; 

No puedo comprender lo que ha sucedido. ¿Hay algo sobre las variables volátiles que debería haber conocido?

Actualización: Aquí está el código completo por si alguna vez desea echar un vistazo. Y está funcionando muy bien después de cambiarlo a lo que se sugirió en la respuesta aceptada.

Comentarios

  • Su pregunta dice qué dice la tercera columna de El resultado es … ¿Cuáles son las otras columnas? Edite la pregunta y agregue encabezados de columna
  • @ jwpat7 Las eliminé intencionalmente porque eso solo confundirá al lector. Pero la pregunta ya ha sido bien respondida por Majenko.
  • Es difícil dar respuestas detalladas de sus fragmentos. could you explain why it is not happening randomly but at a specific time every time I run the code? Also why does it give the particular value? – Probablemente podría hacer eso si viera el código completo. Mientras tanto, lea esto: gammon.com.au/interrupts
  • @NickGammon Aquí tienes: paste.ubuntu.com / 14085127
  • 3683 / .056196868 = 65537 por lo que parece que se incrementó en el momento equivocado, ¿no? Está accediendo a una variable que podría modificarse en una interrupción. varias veces en ese código, por lo que obtener una copia local, mientras las interrupciones están desactivadas, sería mucho más seguro.

Respuesta

Necesita aprender sobre las secciones críticas .

Qué Lo que probablemente está sucediendo es que las variables están siendo cambiadas por las rutinas de interrupción a la mitad de los cálculos. Su «corrección» reduce el tiempo dedicado a hacer el cálculo con las variables volátiles, por lo que es menos probable que haya una colisión.

Lo que debe hacer es copiar las variables volátiles a las variables locales con las interrupciones deshabilitadas para eso. breve período.

cli(); int l = encoderLpos; int r = encoderRpos; sei(); 

Debido a que Arduino es una CPU de 8 bits, se necesitan varias instrucciones de ensamblaje para realizar operaciones matemáticas en valores de 16 bits. El punto flotante es aún peor si se usan muchas instrucciones para una simple adición. La división y la multiplicación utilizan considerablemente más. Una interrupción tiene muchas oportunidades de dispararse durante esa lista de instrucciones. Al hacer una asignación como esa y luego usar las nuevas variables locales en sus cálculos, las instrucciones necesarias para lidiar con las variables volátiles se mantienen al mínimo absoluto. Al desactivar las interrupciones durante la asignación, garantiza que las variables nunca se podrán cambiar mientras las esté utilizando. Este fragmento de código se denomina sección crítica .

Comentarios

  • Este podría ser el caso. Solo me pregunto, ¿podría explicar por qué no sucede de manera aleatoria sino en un momento específico cada vez que ejecuto el código? Además, ¿por qué da el valor particular?
  • Aquí hay una gran referencia a cli / sei. nongnu.org/avr-libc/user-manual/… . Con la barrera de memoria, la declaración volátil no es realmente necesaria en el código anterior. Aquí hay una lectura divertida sobre este tema. kernel .org / doc / Documentation / volatile-consider-dangerous.txt
  • @MikaelPatel Nice, pero no tan relevante para las MCU.Se requiere volátil en esta situación para evitar que el compilador optimice las instancias en las que cree que ‘ no se está utilizando (el valor nunca cambia). El cli / sei está ahí para hacer que la operación atómica WRT sea el único otro hilo (interrupciones) que se ejecuta.
  • ¿Intentó compilar el código con y sin volátil? Pero con la sección crítica (cli / sei). Lo que estoy tratando de discutir es el concepto de barrera de memoria y cómo eso proporciona acceso volátil (y ordenamiento correcto) desde el compilador con tener que declarar variables como volátiles. A la mayoría de los programadores se les enseña que cualquier variable a la que se acceda en un ISR debe declararse volátil, pero hay mucho más en esta historia.
  • No ‘ creo que el compilador tiene mucho concepto de lo que hacen cli () y sei () y cómo eso afectaría cosas como la optimización de variables que no deberían ‘ t optimizarse. Todo lo que hacen sei () y cli () es manipular la bandera habilitada de interrupción global en su registro. No hacen nada por el flujo del código.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *