Problema en el control de sincronización del reloj con ARM Tiva C

Cuando accedo a un registro periférico (como un puerto GPIO) justo después de habilitar la activación del reloj del periférico, ocurre una falla grave. Por ejemplo:

  • Este código no funciona (genera una falla grave):

    /* enable clock to GPIOF at clock gating control register */
    SYSCTL_RCGCGPIO_R |= SYSCTL_RCGCGPIO_R5;
    /* enable the GPIO pins for the LED (PF3, 2 1) as output */
    GPIO_PORTF_DIR_R = 0x0E;
    while(1) {}
    
  • Pero con este pequeño nopajuste, funciona:

    /* enable clock to GPIOF at clock gating control register */
    SYSCTL_RCGCGPIO_R |= SYSCTL_RCGCGPIO_R5;
    asm(" nop");
    asm(" nop");
    /* enable the GPIO pins for the LED (PF3, 2 1) as output */
    GPIO_PORTF_DIR_R = 0x0E;
    while(1) {}
    

El código debería funcionar bien sin demora después de habilitar el reloj, pero necesito saber qué está sucediendo aquí.

Larga historia:

Si ejecuto el primer código, me da un error de bus. Probé otros puertos en GPIO y también probé algún canal UART y el problema persiste. Lo que obtuve es que: solo agregar suficientes asm(" nop");instrucciones en el lugar correcto hace que el código funcione.

Parece que el problema está en el tiempo de retraso entre habilitar la activación del reloj de un periférico y acceder a sus registros. Cualquier solución que implemente agregar un retraso suficiente funciona. Intenté diferentes cosas, como usar una función de retraso o usar instrucciones asm ("nop") o simplemente agregar un punto de interrupción de depuración en la línea justo después de habilitar el reloj. ¿Tiene esto algo que ver con la latencia de activación del reloj o el sesgo del reloj o expresiones similares?

He depurado el código de acuerdo con este formulario de documento TI. Mi registro de fallas lee un valor de 0x0000.0400después de la falla, lo que significa que "Ocurrió una falla en el bus, pero no se conoce la dirección exacta de la falla" . Revisé el marco de la pila de excepciones y analicé el código de desensamblaje que se ejecuta antes de que ocurra la falla y parece que no hay problema. El documento dice que esta falla generalmente ocurre debido a la STRinstrucción, pero revisé los punteros de memoria y todo está bien.

Desmontaje del código que no funciona:

11            SYSCTL_RCGCGPIO_R |= SYSCTL_RCGCGPIO_R5;
          main():
0000026c:   4913                ldr        r1, [pc, #0x4c]
0000026e:   6808                ldr        r0, [r1]
15            GPIO_PORTF_DIR_R = 0x0E;
00000270:   4A13                ldr        r2, [pc, #0x4c]
11            SYSCTL_RCGCGPIO_R |= SYSCTL_RCGCGPIO_R5;
00000272:   F0400020            orr        r0, r0, #0x20
00000276:   6008                str        r0, [r1]
15            GPIO_PORTF_DIR_R = 0x0E;
00000278:   200E                movs       r0, #0xe
0000027a:   6010                str        r0, [r2]

Desmontaje del código de trabajo:

11            SYSCTL_RCGCGPIO_R |= SYSCTL_RCGCGPIO_R5;
          main():
0000026c:   4914                ldr        r1, [pc, #0x50]
0000026e:   6808                ldr        r0, [r1]
00000270:   F0400020            orr        r0, r0, #0x20
00000274:   6008                str        r0, [r1]
00000276:   BF00                nop        
00000278:   BF00                nop        
15            GPIO_PORTF_DIR_R = 0x0E;
0000027a:   4912                ldr        r1, [pc, #0x48]
0000027c:   200E                movs       r0, #0xe
0000027e:   6008                str        r0, [r1]

Según mis experimentos, parece que después de habilitar el reloj para GPIO se requieren más de 1 ciclo de reloj antes de acceder a los registros del puerto GPIO. Para UART es más alto, alrededor de 5 a 6 ciclos de reloj (lo he medido con el depurador).

IDE: Code Composer Studio. Compilador: TI v18.12.2.LTS (con nivel de optimización de 2). Sistema operativo: Ubuntu Bionic 18.04.2 LTS.

Entonces, ¿es necesaria esa demora? o por desgracia, tengo un problema de hardware?

Puede intentar agregar una instrucción de barrera de memoria para asegurarse de que su comando de escritura de habilitación de reloj haya llegado completamente al destino.
@Oldfart Lo siento, ¿podría explicar la barrera de la memoria?
Lo siento, estaba desconectado después de comentar, pero veo que Filo ha respondido cómo hacerlo. También puede buscar en la WWW usando la frase "instrucción de barrera de memoria".

Respuestas (2)

Como recuerdo, este es un problema conocido con los procesadores Tiva: debe esperar dos o tres ciclos de reloj después de habilitar el reloj antes de poder usar el periférico. Algunas personas, lamentablemente, agregarían una declaración que solo lea el valor en algún registro volátil para matar el tiempo. Desafortunadamente, a veces una declaración no era suficiente. La situación condujo a algunos hacks muy feos.

TI finalmente agregó el receptor Listo para periféricos de entrada/salida de uso general (PRGPIO). Este registro tiene un bit para cada puerto y puede leer el bit para ver cuándo el puerto está listo para usarse. Registros similares están disponibles para otros dispositivos periféricos, como el registro Universal Asynchronous Receiver/Transmitter Peripheral Ready.

¡GRACIAS! ¡Intenté usar los registros de relaciones públicas y funciona bien!

Tiva es un Cortex-M4F. Si hay algún tipo de almacenamiento en búfer de escritura involucrado, es posible que la habilitación del reloj aún no llegue al periférico (o surta efecto), mientras que el código intenta acceder al periférico (aún deshabilitado) y obtiene una falla grave.

Comenzaría tratando de agregar una instrucción __DSB();(o ) entre la activación del reloj y el acceso al periférico.asm("dsb");

¿Quieres decir asm(" dsb");? el compilador no reconoce __DSB();. El asm por alguna razón funciona para GPIO pero no funciona para UART como este código:SYSCTL_RCGCUART_R |= SYSCTL_RCGCUART_R7; asm(" dsb"); UART7_CTL_R = 0x00;