Tengo un software que se ejecuta en un STM32F103 (también me gustaría poder usar el F4) donde me gustaría poder 'marcar la hora' de eventos (como interrupciones en pines externos) al microsegundo más cercano (si no mejor).
Como el software se ejecutará durante algún tiempo, quiero usar un temporizador de 64 bits y, para mí, tenía sentido usar el temporizador integrado SysTick. Creé SysTickMajor (un contador de 64 bits) que aumenta cada vez que SysTick se desborda, y una función getSystemTime que combina este contador y el valor actual de SysTick para obtener un tiempo preciso de 64 bits:
#define SYSTICK_RANGE 0x1000000 // 24 bit
volatile long long SysTickMajor = SYSTICK_RANGE;
voif onInit(void) {
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
SysTick_Config(SYSTICK_RANGE-1);
/* Super priority for SysTick - is done over absolutely everything. */
NVIC_InitStructure.NVIC_IRQChannel = SysTick_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void SysTick_Handler(void) {
SysTickMajor+=SYSTICK_RANGE;
}
long long getSystemTime() {
long long t1 = SysTickMajor;
long long time = (long long)SysTick->VAL;
long long t2 = SysTickMajor;
// times are different and systick has rolled over while reading
if (t1!=t2 && time > (SYSTICK_RANGE>>1))
return t2 - time;
return t1-time;
}
El único problema es que esto no parece ser 100% preciso; por ejemplo, si hago lo siguiente:
bool toggle = false;
while (true) {
toggle = !toggle;
LED1.write(toggle);
long long t = getSystemTime()() + 1000000;
while (getSystemTime() < t);
}
No obtendré una buena onda cuadrada: ocasionalmente habrá fallas (a pesar de que la interrupción de SysTick finaliza muy rápido), porque en algunos puntos, el tiempo devuelto por getSystemTime es completamente incorrecto.
ACTUALIZACIÓN: cuando esto sucede (estoy registrando el valor de tiempo en una interrupción provocada por un evento externo), descargo los últimos 3 valores en serie. Repetidamente obtengo cosas como:
>0x278-E2281A
0x278-E9999A
0x278-0001E5
>0x5BE-F11F51
0x5BE-F89038
0x5BE-0000FB
He incluido guiones para representar qué bits provienen de SysTick.
Ahora, solo para evitar dudas, he usado el código de starblue a continuación. También lo cambié para usar un valor de 32 bits para SysTickMajor, y saqué SysTick->VAL, por si acaso.
¡Parece que SysTick_Handler no se está adelantando a la otra interrupción!
Ahora revisé esto y tenía las prioridades de preferencia incorrectas anteriormente, pero ahora configuré y NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
luego configuré las interrupciones: SysTick prioridad de preferencia 0, y la otra interrupción es 7.
¿Hay algo más que deba hacer para que la preferencia funcione?
ACTUALIZACIÓN 2: creé un pequeño caso de prueba para este problema exacto (SysTick no se adelanta a otras interrupciones) y lo puse aquí:
Problemas de prioridad de interrupción (prioridad) de STM32
Entonces, ¿qué tiene de malo el código anterior? ¿Hay una mejor manera de obtener un buen temporizador de alta resolución? Parece algo obvio que la gente querría hacer, pero me cuesta encontrar ejemplos.
Como ha descubierto, manejar el desbordamiento del contador con un controlador de interrupciones es complicado y propenso a la carrera. Incluso con su aumento de prioridad, obtendrá la respuesta incorrecta si las interrupciones están deshabilitadas mientras lee el contador. Hay una técnica mucho más simple que funciona si no está leyendo el reloj desde el contexto de interrupción (tenga en cuenta que aquí estoy usando el contador de ciclos DWT , que está integrado en el procesador, por lo que es rápido y simple):
uint64_t last_cycle_count_64 = 0;
// Call at least every 2^32 cycles (every 59.6 seconds @ 72 MHz).
// Do not call from interrupt context!
uint64_t GetCycleCount64() {
last_cycle_count_64 += DWT->CYCCNT - (uint32_t)(last_cycle_count_64);
return last_cycle_count_64;
}
Si necesita llamarlo desde un contexto de interrupción (o con un sistema operativo preventivo):
volatile uint64_t last_cycle_count_64 = 0;
// Call at least every 2^32 cycles (every 59.6 seconds @ 72 MHz).
uint64_t GetCycleCount64() {
uint32_t primask;
asm volatile ("mrs %0, PRIMASK" : "=r"(primask));
asm volatile ("cpsid i"); // Disable interrupts.
int64_t r = last_cycle_count_64;
r += DWT->CYCCNT - (uint32_t)(r);
last_cycle_count_64 = r;
asm volatile ("msr PRIMASK, %0" : : "r"(primask)); // Restore interrupts.
return r;
}
Es posible que primero deba habilitar el contador de ciclos:
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // Set bit 0.
Su código tiene dos problemas:
No sabe si la interrupción que aumentó SysTickMajor
ocurrió antes o después de leer el valor del temporizador en time
.
Las operaciones en valores de 64 bits no son atómicas en un sistema de 32 bits. Entonces, el valor de SysTickMajor
podría actualizarse por la interrupción entre la lectura de las dos palabras de 32 bits.
Yo lo haría de la siguiente manera:
major0 = SysTickMajor;
minor1 = SysTick->VAL;
major1 = SysTickMajor;
minor2 = SysTick->VAL;
major2 = SysTickMajor;
if ( major0 == major1 )
{
time = major1 + minor1;
}
else
{
time = major2 + minor2;
}
Aquí se supone que si SysTickMajor
se cambia durante el primer bloque de tareas, no volverá a cambiar en el segundo bloque. (Esta suposición podría ser incorrecta si este código se interrumpe durante esta rutina y no se puede ejecutar durante mucho tiempo).
Consulte también esta pregunta sobre Stackoverflow .
SysTick->VAL
ya ha comenzado a ejecutar la instrucción para leer SysTickMajor
en major1
. El minor1
valor podría reflejar un temporizador que ha terminado, aunque major1
no lo haría. Usando bajo/alto/bajo, estas cosas no sucederán a menos que la diferencia de tiempo relativa entre las acciones de palabras altas y bajas exceda el tiempo entre las dos lecturas de palabras bajas.Acabo de encontrar la respuesta en un póster muy útil en el foro STM32:
Lo siguiente no es correcto. SysTick es un 'Controlador del sistema' y, como tal, la prioridad no se establece de esta manera en absoluto:
NVIC_InitStructure.NVIC_IRQChannel = SysTick_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // Highest priority
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
En realidad está configurado con:
NVIC_SetPriority(SysTick_IRQn, 0);
¡Llamar a ese código resuelve el problema!
Entonces, mi versión de getSystemTime podría haber tenido un problema, y el método de starblue es mejor que el mío; sin embargo, la causa real de los problemas fue la anterior.
Solo pensé en agregar otra posible mejora a getSystemTime que también probé:
do {
major0 = SysTickMajor;
minor = SysTick->VAL;
major1 = SysTickMajor;
while (major0 != major1);
return major0 - minor;
Esto anulará efectivamente el problema en el que podría (de alguna manera) terminar con el SysTick implementado dos veces en una sucesión muy rápida.
Las respuestas anteriores son geniales. Esta es la que más me gusta:
do {
major0 = SysTickMajor;
minor = SysTick->VAL;
major1 = SysTickMajor;
while (major0 != major1);
return major0 - minor;
Puede optimizarlo un poco más, ya que sabe que si tiene un rollover de SysTickMajor, no obtendrá otro rollover "en mucho tiempo". Deberías estar seguro haciendo esto:
major = SysTickMajor;
minor = SysTick->VAL;
if (major != SysTickMajor)
return SysTickMajor - SysTick->Val;
return major - minor;
Lo anterior asume, por supuesto, que SysTickMajor y VAL se declaran como volátiles. Hace varios años, en un trabajo anterior, descubrí que, lamentablemente, lo anterior aún no es perfecto en todos los procesadores. Estaba usando un STM32F407, que tiene un caché y está canalizado. A veces, la búsqueda del segundo SysTickMajor llegaba antes que el VAL aunque se emitieran en orden. Introduzca la barrera de la memoria (instrucción dmb):
major = SysTickMajor;
minor = SysTick->VAL;
__asm__ volatile("dmb");
if (major != SysTickMajor)
return SysTickMajor - SysTick->Val;
return major - minor;
En mi caso, estaba usando un TIM2 de 32 bits y un ISR de rollover de TIM2 que incrementa los 32 bits superiores de mi contador de ciclos de 64 bits, pero creo que debería actuar igual que lo que está haciendo con SysTick.
Eddy_Em
gordon williams
Eddy_Em
while (getSystemTime() < t);
: esta función tarda un tiempo, por lo que no podrás obtener el tiempo perfectamente. Trabaje en interrupciones o realice una operación atómica de cálculo de tiempo (la interrupción de SysTick modificaría algunalong long
variable mediante DMA u otro mecanismo, y la última línea puede ser comowhile(SystemTime - SystemTime0 < 1000000)
gordon williams