Pregunta

Esta pregunta es acerca de la programación de microcontroladores pequeños sin un sistema operativo. En particular, estoy interesado en los PIC en este momento, pero la pregunta es general.

he visto varias veces el siguiente patrón de tiempo de mantenimiento:

código de interrupción del temporizador (decir se activa el temporizador cada segundo):

...
if (sec_counter > 0)
  sec_counter--;
...

código de la línea principal (no interrupción):

sec_counter = 500; // 500 seconds

while (sec_counter)
{
  // .. do stuff
}

El código de la línea principal puede repetir, poner el contador a diversos valores (no sólo segundos) y así sucesivamente.

Me parece que hay una condición de carrera aquí cuando la asignación a sec_counter en el código de la línea principal no es atómica. Por ejemplo, en PIC18 la asignación se traduce a 4 declaraciones ASM (carga cada byte en el momento y seleccionando el byte derecha desde el banco de memoria antes de eso). Si el código de interrupción viene en medio de esto, el valor final puede estar dañado.

Curiosamente, si el valor asignado es inferior a 256, la asignación es atómica, así que no hay problema.

Estoy en lo cierto acerca de este problema? ¿Qué patrones que se utiliza para poner en práctica este tipo de comportamiento correctamente? Veo varias opciones:

  • Desactivar interrumpe antes de cada asignación a sec_counter y permitir después - esto no es bastante
  • No utilice una interrupción, pero un temporizador separado que se inició y luego encuestados. Esta es limpia, pero utiliza todo un temporizador (en el caso anterior el temporizador de disparo de 1 segundo se puede utilizar para otros fines).

¿Alguna otra idea?

¿Fue útil?

Solución

La arquitectura PIC es tan atómica como se pone. Se asegura de que todas las operaciones de lectura-modificación-escritura en un archivo de memoria son 'atómica'. Aunque lleva 4-relojes para llevar a cabo la totalidad de lectura-modificación-escritura, los 4-relojes se consumen en una sola instrucción y la siguiente instrucción usa el siguiente ciclo de 4-reloj. Es la forma en que funciona la tubería. En 8-relojes, dos instrucciones están en trámite.

Si el valor es mayor que 8 bits, se convierte en un problema ya que el PIC es una máquina de 8 bits y operandos más grandes se manejan en múltiples instrucciones. Eso va a introducir cuestiones atómicas.

Otros consejos

Escribir el valor a continuación, compruebe que es el valor requerido parece ser la alternativa más simple.

do {
 sec_counter = value;
} while (sec_counter != value);

Por cierto, usted debe hacer la variable volátil que si usar C.

Si es necesario leer el valor entonces usted puede leerlo dos veces.

do {
    value = sec_counter;
} while (value != sec_counter);

Es imprescindible disponer de desactivar la alarma antes de ajustar el contador. Feo que sea, es necesario. Es una buena práctica para desactivar SIEMPRE la interrupción antes de configurar los registros de hardware o de software de variables que afectan el método ISR. Si está escribiendo en C, se debe considerar todas las operaciones que no atómica. Si usted encuentra que usted tiene que mirar a los generados montaje demasiadas veces, entonces puede ser mejor abandonar C y el programa en el montaje. En mi experiencia, esto no suele ser el caso.

En cuanto al tema discutido, esto es lo que sugieren:

ISR:
if (countDownFlag)
{
   sec_counter--;
}

y establecer el contador:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;

Es necesario una variable adicional y es mejor para envolver todo en una función:

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

De esta manera abstracta el método de partida (y ocultar la fealdad si es necesario). Por ejemplo, puede cambiar fácilmente para que se inicie un temporizador de hardware sin afectar a las personas que llaman del método.

Debido a que los accesos a la variable sec_counter no son atómica, no hay realmente ninguna manera de no tener que desactivar las interrupciones antes de acceder a esta variable en el código de la línea principal y restaurar el estado de interrupción después de que el acceso si desea un comportamiento determinista. Esto probablemente sería una mejor opción que dedicar un temporizador HW para esta tarea (a menos que tenga un excedente de temporizadores, en cuyo caso puede ser que también utilizar uno).

Si descarga TCP libre de Microchip / IP Stack hay rutinas de allí que utilizan un desbordamiento del contador de tiempo para realizar un seguimiento del tiempo transcurrido. Específicamente "tick.c" y "tick.h". Sólo tienes que copiar los archivos a través de su proyecto.

Dentro de esos archivos se puede ver cómo lo hacen.

No es tan curioso acerca de los menos de 256 movimientos siendo atómica - mover un valor de 8 bits es un código de operación por lo que es tan atómica como se obtiene.

La mejor solución en un microcontrolador, como el PIC es deshabilitar las interrupciones antes de cambiar el valor del temporizador. Incluso se puede comprobar el valor de la bandera de interrupción cuando se cambia la variable en el circuito principal y manejarlo si lo desea. Que sea una función que cambia el valor de la variable e incluso se podría llamar de la ISR también.

Bueno, lo que hace el código ensamblador comparación parece?

adoptadas para dar cuenta de que cuenta en sentido descendente, y que es justo comparar un cero, debe ser seguro si se comprueba primero el MSB, a continuación, el LSB. No puede haber corrupción, pero en realidad no importa si se trata en el medio entre 0x100 y 0xff y comparar el valor corrompido es 0x1ff.

La forma en que está utilizando el temporizador ahora, no va a contar los segundos enteros de todos modos, ya que es posible cambiarlo en medio de un ciclo. Por lo tanto, si no se preocupan por él. La mejor manera, en mi opinión, sería leer el valor, y luego simplemente comparar la diferencia. Se tarda un par de OPs más, pero no tiene problemas multi-threading. (Debido a que el temporizador tiene prioridad)

Si son más estrictos sobre el valor del tiempo, que podría desactivar automáticamente el temporizador una vez que la cuenta regresiva hasta 0 y cero el contador interno del temporizador y activar una vez que lo necesite.

Mover la porción de código que sería en el main () para un funcionamiento adecuado, y haga que sea condicionalmente llamado por el ISR.

Además, para evitar cualquier tipo de retrasar o garrapatas que falta, elegir este ISR temporizador para ser una de alta prio interrupción (el PIC18 tiene dos niveles).

Uno de los enfoques es tener una interrupción mantener una variable de bytes, y tienen algo más, que es llamada por lo menos una vez cada 256 veces el contador es golpeado; hacer algo como:

// ub==unsigned char; ui==unsigned int; ul==unsigned long
ub now_ctr; // This one is hit by the interrupt
ub prev_ctr;
ul big_ctr;

void poll_counter(void)
{
  ub delta_ctr;

  delta_ctr = (ub)(now_ctr-prev_ctr);
  big_ctr += delta_ctr;
  prev_ctr += delta_ctr;
}

Una ligera variación, si no le importa forzando el mostrador de la interrupción para estar sincronizado con el LSB de su gran contador:

ul big_ctr;
void poll_counter(void)
{
  big_ctr += (ub)(now_ctr - big_ctr);
}

Nadie abordó la cuestión de lectura registros de hardware de varios bytes (por ejemplo, un contador de tiempo. El temporizador podría darse la vuelta e incrementar su segundo byte, mientras que lo estás leyendo.

Digamos que es 0x0001ffff y lo lee. Es posible que se 0x0010ffff o 0x00010000.

El registro periférica 16 bits es volátil a su código.

Para cualquier volátiles "variables", utilizo la técnica de doble lectura.

do {
       t = timer;
 } while (t != timer);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top