Temporizador del sistema de alta resolución en STM32

3

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 es mejor).

Como el software se ejecutará por algún tiempo, quiero usar un temporizador de 64 bits, y para mí tenía sentido usar el temporizador SysTick incorporado. He creado SysTickMajor (un contador de 64 bits) que se incrementa 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; de vez en cuando se producen fallos (aunque la interrupción de SysTick se termina muy rápidamente), porque en los puntos el tiempo devuelto por getSystemTime es completamente incorrecto.

ACTUALIZACIÓN: cuando esto sucede (estoy registrando el valor de tiempo en una interrupción activada por un evento externo) vuelco los últimos 3 valores a la serie. Repetidamente me sale cosas como:

>0x278-E2281A 
 0x278-E9999A 
 0x278-0001E5

>0x5BE-F11F51
 0x5BE-F89038
 0x5BE-0000FB

He pegado 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 he cambiado para usar un valor de 32 bits para SysTickMajor, y he eliminado a SysTick- > VAL, por si acaso.

¡Parece que el SysTick_Handler no está adelantando a la otra interrupción!

Ahora verifiqué esto y tenía las prioridades de Preemption erróneas anteriormente, pero ahora establezco NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); y luego configuro las interrupciones: la prioridad de preferencia de SysTick 0, y la otra interrupción es 7.

¿Hay algo más que deba hacer para que la preferencia funcione?

ACTUALIZACIÓN2: creé un pequeño caso de prueba para este problema exacto (SysTick no sustituye a otras interrupciones) y lo he puesto aquí:

STM32 Problemas de prioridad de interrupción (preferencia)

Entonces, ¿qué hay de malo con el código de arriba? ¿Hay una mejor manera de obtener un buen temporizador de alta resolución? Parece algo obvio que la gente querría hacer pero estoy luchando por encontrar ejemplos.

    
pregunta Gordon Williams

3 respuestas

3

Acabo de encontrar la respuesta de 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:

  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 más agradable que el mío, sin embargo, la causa real de los problemas fue la anterior.

Pensé que agregaría otra posible mejora a getSystemTime que también probé:

do {
 major0 = SysTickMajor;
 minor = SysTick->VAL;
 major1 = SysTickMajor;
while (major0 != major1);
return major0 - minor;

Esto efectivamente anulará el problema donde podría (de alguna manera) terminar con la implementación de SysTick dos veces en una sucesión muy rápida.

    
respondido por el Gordon Williams
4

Como has descubierto, el manejo del desbordamiento de contador con un controlador de interrupción es complicado y propenso a la carrera. Incluso con su aumento de prioridad, obtendrá la respuesta incorrecta si las interrupciones se desactivan mientras lee el contador. Hay una técnica mucho más simple que funciona si no estás leyendo el reloj desde el contexto de interrupción (ten en cuenta que estoy usando Contador de ciclos DWT aquí, que está integrado en el procesador de manera rápida 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;
}
    
respondido por el SethML
2

Su código tiene dos problemas:

  • No sabes si la interrupción que aumentó el 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. Por lo tanto, el valor de SysTickMajor podría actualizarse mediante la interrupción entre la lectura de las dos palabras de 32 bits.

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 cambió durante el primer bloque de asignaciones, no volverá a cambiar en el segundo bloque. (Esta suposición podría ser errónea si este código se interrumpe durante esta rutina y no se puede ejecutar durante mucho tiempo).

Vea también esta pregunta en Stackoverflow .

    
respondido por el starblue

Lea otras preguntas en las etiquetas