4
\$\begingroup\$

When I access a peripheral register (such as a GPIO port) just after enabling the clock gating of the peripheral, a hard fault happens. For example:

  • This code doesn't work (generates a hard fault):

    /* 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) {}
    
  • But with this little nop tweak, it works:

    /* 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) {}
    

The code should work fine with no delay after enabling the clock, but I need to know what is happening here?

Long Story:

If I run the first code it gives me a bus error. I tried other ports in GPIO and also tried some UART channel and the problem persists. What I got is that: just adding sufficient asm(" nop"); instructions in the right place make the code works.

It appears that the problem is with the delay time between enabling the clock gating of a peripheral and accessing it's registers. Any solution that implements adding a sufficient delay works. I tried different things like using a delay function or using asm(" nop") instructions or just adding a debug breakpoint in the line just after enabling clock. Is this have something to do with clock gating latency or clock skew or such expressions?

I've debugged the code according to this document form TI. My fault register reads a value of 0x0000.0400 after fault which means that "A bus fault occurred, but that the exact faulting address is not known". I've checked the exception stack frame and analyzed the disassembly code that is executed before the fault happens and it seems that there's no problem. The document says that this fault occurs usually due to the STR instruction, but I've checked the memory pointers and it's all fine.

Disassembly of the not-working code:

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]

Disassembly of the working code:

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]

From my experiments, it seems like after enabling clock for GPIO requires more than 1 clock cycles before accessing the GPIO port registers. For UART it is higher, about 5 to 6 clock cycles (I've measured that with the debugger).

IDE: Code Composer Studio. Compiler: TI v18.12.2.LTS (with optimization level of 2). OS: Ubuntu Bionic 18.04.2 LTS.

So, is that delay required? or unfortunately, I have a hardware problem?

\$\endgroup\$
3
  • \$\begingroup\$ You might try adding a memory barrier instruction to make sure your clock enable write command has made it fully to the destination. \$\endgroup\$
    – Oldfart
    Jun 18, 2019 at 16:37
  • \$\begingroup\$ @Oldfart Sorry, could you explain the memory barrier? \$\endgroup\$ Jun 18, 2019 at 16:49
  • \$\begingroup\$ Sorry, was off line after commenting but I see filo has answered how to do that. You can also search the WWW using the "memory barrier instruction" phrase. \$\endgroup\$
    – Oldfart
    Jun 18, 2019 at 18:00

2 Answers 2

2
\$\begingroup\$

As I recall this is a known issue with the Tiva processors: you have to wait two or three clock cycles after enabling the clock before you can actually use the peripheral. Some people would, sadly, add a statement that just reads the value in some volatile register to kill time. Unfortunately, sometimes one statement wasn't enough. The situation lead to some very ugly hacks.

TI eventually added the General-Purpose Input/Output Peripheral Ready (PRGPIO) reister. This register has one bit for each port, and you can read the bit to see when the port is ready to use. Similar registers are available for other peripheral devices, such as the Universal Asynchronous Receiver/Transmitter Peripheral Ready register.

\$\endgroup\$
1
  • \$\begingroup\$ THANK YOU! I tried using the PR registers and it works fine! \$\endgroup\$ Jun 23, 2019 at 10:19
0
\$\begingroup\$

Tiva is a Cortex-M4F. If there is some kind of write buffering involved, then the clock enable may not yet reach the peripheral (or take effect), while the code is trying to access the (still disabled) peripheral and you get a hard fault.

I would start by trying to add a __DSB(); (or asm("dsb");) instruction between clock enable and accessing the peripheral.

\$\endgroup\$
1
  • \$\begingroup\$ Do you mean asm(" dsb"); ? compiler doesn't recognise __DSB();. The asm one for some reason works for GPIO but doesn't work for UART like this code : SYSCTL_RCGCUART_R |= SYSCTL_RCGCUART_R7; asm(" dsb"); UART7_CTL_R = 0x00; \$\endgroup\$ Jun 18, 2019 at 17:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.