Lectura de un temporizador de 16 bits en una MCU de 8 bits

Dado que una MCU de 8 bits no puede leer todo el temporizador de 16 bits en un ciclo, esto crea una condición de carrera en la que la palabra baja puede cambiar entre lecturas. ¿Tiene la comunidad un método preferido para evitar estas condiciones de carrera? Actualmente estoy considerando detener el temporizador durante las lecturas, pero me gustaría saber si existe una solución más elegante.

FYI, esto es para un PIC16F690 .

Este es un problema del que se ocupan todos los microcontroladores, AFAIK. Cuando se lee un byte, el otro se bloquea, y al leer el otro byte se leerá desde el bloqueo. Problema resuelto.

Respuestas (4)

Windell tiene razón, si está hablando de PIC, la hoja de datos (y el hardware) ya manejan esto por usted.

Si no está usando un PIC, el método general que uso es hacer esto:

byte hi, lo;
word timer_value;

do {
    hi = TIMER_HI;
    lo = TIMER_LO;
} while(hi != TIMER_HI);

timer_value = (hi << 8) | lo;

Lo que esto hace es leer el byte superior seguido del byte inferior y continuar haciéndolo hasta que el byte alto no cambie. Esto maneja fácilmente el caso en el que el byte bajo del valor de 16 bits se desborda entre lecturas. Se supone, por supuesto, que no hay efectos secundarios al leer el registro TIMER_HI varias veces. Si su microprocesador en particular no lo permite, es hora de tirarlo y usar uno que no sea tan tonto. :-)

Este método TAMBIÉN asume que su temporizador no está cambiando tan rápidamente que corre el riesgo de desbordar los 8 bits bajos dentro de uno o dos ciclos de recuperación del procesador. Si está ejecutando un temporizador tan rápido (o un microprocesador tan lento), entonces es hora de repensar su implementación.

También puede usar esta técnica para leer un temporizador compuesto por un temporizador de hardware y un contador de software incrementado en la interrupción de desbordamiento.
Gracias, en realidad me gusta más esta técnica que tartamudear el temporizador durante una lectura.
Finalmente encontré una nota de la aplicación Microchip, "Familia de MCU de rango medio Micro PIC". Parece ser antiguo, pero parece describir la serie 16F con más detalle que la hoja de datos. La técnica sugerida para leer el temporizador de ejecución libre de 16 bits coincide bastante bien con su sugerencia, por lo que la estoy marcando como la respuesta.
En general, mi respuesta habría sido "hacer lo que hace el compilador": escribir algo en C y ver cómo se ve el ensamblaje.
O_Engenheiro El bucle se ejecuta dos veces para el caso de que se produzca un desbordamiento y una vez en todas las demás circunstancias. Si está repitiendo más que eso, entonces su reloj temporizador es MUY rápido o su CPU es MUY lenta.
@Andrew Kohlsmith, eliminé mi comentario. Lo siento, hice un análisis incorrecto del código. Gracias.
@Andrew Kohlsmith: si el temporizador está en modo síncrono, su enfoque está bien. Si el temporizador está funcionando en modo asíncrono y no es demasiado rápido (por ejemplo, está funcionando con un cristal de reloj de 32 KHz), es mejor volver a realizar el bucle si el LSB del temporizador ha cambiado. De lo contrario, existe el riesgo de que el temporizador cambie precisamente a medida que se lee, dando como resultado una lectura no válida.
@supercat No estoy seguro de entender; si el temporizador es asíncrono con el reloj de la CPU, como máximo se desactivará 1 temporizador; eso no significa que la lectura no sea válida. Sin embargo, si quiere decir que el patrón de bits en el registro no será válido, entonces es un diseño de CPU/periférico muy pobre; el proveedor debe sincronizar correctamente los registros periféricos.
@Andrew Kohlsmith: un contador que puede ser leído de manera confiable por una CPU cuyo reloj es asíncrono requiere una buena cantidad de silicio. Esencialmente, uno debe contar en código gris o convertir el conteo a código gris, luego sincronizarlo y luego convertir el valor de código gris nuevamente a binario "normal". Para los casos en los que el reloj de la CPU , cuando se ejecuta , sea al menos 3 veces más rápido que la señal que se va a contar, podría ser más fácil organizar que el contador se registre de forma sincrónica cuando la CPU está despierta y de forma asíncrona cuando está dormida. .
@Andrew Kohlsmith: ... pero cambiar entre sincronización sincrónica y asincrónica sin ganar o perder una cuenta, incluso en casos extremos, es difícil. Si el software puede leer el temporizador repetidamente hasta que obtiene la misma respuesta varias veces, eso permitirá el uso de hardware más simple de lo que sería necesario. Tenga en cuenta que la única vez que ese enfoque realmente no funcionará es cuando la tasa de conteo es bastante alta en comparación con la tasa de reloj de la CPU, lo que sugeriría que el enfoque de código gris es el correcto.
@Andrew Kohlsmith: tenga en cuenta que, a menos que se proporcione una instalación para la lectura síncrona de 16 bits, o que la tasa de conteo sea tan rápida que la CPU no pueda leer bajo-alto-bajo antes de cambios bajos, no hay una desventaja real en tener el contador devuelve valores corruptos si se lee mientras está cambiando (ciertamente es una desventaja menor que un conteo ocasional ganado o perdido causado por cambiar entre modos sincrónicos y asincrónicos).
@supercat interesante. He tratado con muchos microcontroladores a lo largo de los años y estoy aprendiendo VHDL y nunca antes me había encontrado con esto. También consulté con mi "tutor" de VHDL y él nunca había visto este tipo de problema en un microcontrolador. ¿Dónde has visto esto antes? ¡Apuesto a que fue un verdadero oso para depurar!
@Andrew Kohlsmith: La depuración de cosas asincrónicas no es demasiado difícil si se supone que las señales se esforzarán por cambiar en los peores momentos posibles. De hecho, en muchos casos prefiero los temporizadores totalmente desincronizados a aquellos que intentan evitar los problemas asociados con la asincronía, ya que en varias ocasiones he tenido que lidiar con errores de hardware relacionados con estos asuntos. Por ejemplo, una molestia no documentada en muchos temporizadores PIC es que si el temporizador tiene un valor que no sea FFFF, y está escrito con FFFF, la interrupción de desbordamiento...
...no sucederá en el próximo conteo, sino después de 65,537 conteos. Sospecho que el diseño surge del deseo de evitar que se active una interrupción espuria si, por ejemplo, el contador hubiera mantenido 128 y se estuviera escribiendo con 127 justo cuando entró un conteo, pero hubiera preferido simplemente que la documentación especificara que cualquier escritura al temporizador en modo asíncrono puede establecer erróneamente el indicador de interrupción, por lo que la operación segura requeriría deshabilitar la interrupción, escribir el temporizador, borrar el indicador y volver a habilitar la interrupción.
@Andrew Kohlsmith: Lo que más me gustaría tener sería un sistema con un contador asíncrono de 48 bits, que se ejecuta desde su propio suministro, y un registro de comparación en los 32 bits inferiores que podría funcionar incluso cuando el reloj principal del procesador se detuvo . No habría necesidad de escribir en el contador de 48 bits, y no me importaría tener que deshabilitar las interrupciones de comparación al escribir en el registro de comparación. Un sistema de temporizador como ese permitiría que un RTOS programe tareas con una precisión de 17us o 33us, y apague el oscilador principal en cualquier momento que no sea necesario.
@Andrew Kohlsmith: El EFM Gekko es casi perfecto; mi única queja es que el temporizador tiene solo 24 bits y no puede mantener el tiempo mientras la CPU está apagada. La serie ST Micro 32F parece algo mejor, con un reloj en tiempo real en forma de contador lineal de 32 bits y preescalar programable. Mi única queja con respecto a que los 32 bits son demasiado cortos (usando una tasa de conteo de 256 Hz, la cosa olvidaría la fecha si se apagara durante un año; a una tasa de conteo más rápida se desbordaría antes).
@Andrew Kohlsmith: Desafortunadamente, ST arruinó un poco las cosas con la serie 32L. El chip RTC tiene un preescalar configurable, pero desafortunadamente tiene que leerse en un formato YMDHMS que asume una tasa de incremento de 1 segundo, y sus registros de alarma asumen el formato HMS; si la tasa de conteo es de 256 Hz, los registros de alarma representarían unidades de 1/256 segundo, 15/64 segundo y 14 1/16 segundo (con el último ajuste cada 337,5 segundos). Mucho menos conveniente para usar con la programación RTOS.

¡Consulta tu hoja de datos! Hablará de esto , con detalles sangrientos, para su chip en particular.

No estoy seguro de que esto sea cierto para todos los registros de contador/temporizador de 16 bits en todos los PIC (¡tal vez alguien más pueda responder eso!) pero al menos para el PIC 18F que uso, la hoja de datos habla específicamente sobre cómo se maneja esto .

Cuando lee el byte bajo, almacena el byte alto en un registro temporal para el byte alto, de modo que cuando lea el byte alto a continuación, le brinde una instantánea instantánea completa del valor del temporizador.

El mismo proceso básico se usa en AVR (y Arduino, por supuesto), y para la mayoría de los otros casos en los que necesita leer o escribir registros de dos bytes a la vez en MCU de 8 bits.

yo también recuerdo haber leído esto en los documentos de AVR
Por supuesto, revisé la hoja de datos, pero en realidad no es tan sangriento como podría pensar. Nada en el texto del temporizador 1 (capítulo 6) indica que el byte alto se almacena en el búfer. Hay una subsección debajo del contador asíncrono (6.5.1) que dice "para escrituras, se recomienda que el usuario simplemente detenga el temporizador y escriba los valores deseados". No se menciona nada para las lecturas. Por eso vine a la comunidad.
Lo hace mucho más fácil ahora que ha publicado el chip real que está usando. ;) Y... lo llamaría bastante sangriento: dice explícitamente que puede ocurrir un desbordamiento entre las lecturas. Su solución, detener el temporizador, es de hecho poco elegante pero quizás segura , dependiendo de su aplicación.

El texto al que se refirió en su comentario (Sección 6.5.1) se refiere a la operación del temporizador asíncrono, donde el temporizador se cronometra desde una fuente externa al microcontrolador.

El descargo de responsabilidad sugiere iniciar y detener el temporizador porque el método general de Andrew (realmente cualquier método) no funcionará porque puede haber muchos tics (más de 256, incluso) del reloj externo y, por lo tanto, el temporizador en un solo ciclo del procesador . Supongo que no estás corriendo con este caso extremo.

Si, en cambio, está utilizando el oscilador interno como referencia, estará bien. Incluso con un preescalador de 1, tiene hasta 255 ciclos después de leer el byte alto para leer el byte bajo. Esta debe ser una operación de 2 ciclos. Todavía necesita el ciclo do/while en caso de que su byte alto cambie (¿Cuál es la diferencia entre 'hi' y 'TIMER_HI' si el valor de 'lo' es 0?) entre estos tiempos. Con un prescaler de 2 o más, ya no necesita esta prueba. Es solo con prescalers mucho menores que 1 que comienza a ser un problema, y ​​esto es imposible sin el reloj externo.

Sí, sé que fue un temporizador asíncrono, pero es lo más cerca que estuvo la hoja de datos de abordar el problema. Aunque ahora tiene más sentido por qué sugerirían detener el reloj en lugar de mencionar la solución de Andrew (aunque ni siquiera lo mencionan en la hoja de datos, sino en una nota de aplicación separada)

Hay un ejemplo en el Manual de referencia de la familia de MCU de rango medio PIC para leer y escribir Timer1 en modo de contador asíncrono.

Ejemplo: lectura de un temporizador de ejecución libre de 16 bits

; All interrupts are disabled
MOVF TMR1H, W ; Read high byte
MOVWF TMPH ;
MOVF TMR1L, W ; Read low byte
MOVWF TMPL ;
MOVF TMR1H, W ; Read high byte
SUBWF TMPH, W ; Sub 1st read with 2nd read
BTFSC STATUS,Z ; Is result = 0
GOTO CONTINUE ; Good 16-bit read
;
; TMR1L may have rolled over between the read of the high and low bytes.
; Reading the high and low bytes now will read a good value.
;
MOVF TMR1H, W ; Read high byte
MOVWF TMPH ;
MOVF TMR1L, W ; Read low byte
MOVWF TMPL ;
; Re-enable the Interrupt (if required)
CONTINUE ; Continue with your code
Sí, ese es el manual que confirmó que la plantilla de Andrew era el mejor compromiso. Aunque modifiqué su ejemplo para que se pareciera más al montaje; es decir, en lugar de hacer mientras, simplemente usé un si.