This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

GPIO timing and coherency issues on the LM4F

I have an application where I toggle a pin on one GPIO port, then immediately read a value from another GPIO port. The idea is that the external hardware reacts much faster than the LM4F. The GPIO is accessed via the AHB aperture.

The (pseudocode) sequence looks something like:

gpio_set(some_port, some_pin);
gpio_read(another_port, data_pins);

I was getting wrong data from the GPIO read, so I checked the timing with a logic analyzer. As soon as some_pin is set, the HW reacts, and data_pins reach their correct levels. Since external timing was not the issue, I reduced the core clock from 80MHz to 16MHz, with the same results. I ended up putting 4 "nop" instructions between the set and read. Three "nop"s were insufficient. Four "nop"s are always needed regardless of the core frequency.

This leads me to infer that internally the GPIO read happens before the GPIO write. How can I ensure coherence between different GPIO ports?

  • Alexandru Gagniuc said:
    How can I ensure coherence between different GPIO ports?

    From reading a LM4F datasheet, think you need to insert a Data Memory Barrier (DMB) instruction between the gpio_set and gpio_read. According to the Write buffer section of the Cortex-M4 Technical Reference Manual the DMB instruction waits for the Write Buffer to drain before continuing.

  • Chester Gillon said:
    think you need to insert a Data Memory Barrier (DMB) instruction between the gpio_set and gpio_read.

    Thanks for the suggestion. I already tried this, but still need about 3 "nop"s. A memory barrier guarantees that the register is written before the other register is read. It does not however guarantee that the GPIO peripheral will have reacted, or have latched on to fresh data. I couldn't find any information in the datasheet about the peripheral timing.

  • Alexandru Gagniuc said:
    It does not however guarantee that the GPIO peripheral will have reacted, or have latched on to fresh data. I couldn't find any information in the datasheet about the peripheral timing

    Agree that the datasheet doesn't mention the GPIO peripheral timing. I created the following test program to run on a Stellaris Launchpad, using TivaWare 1.0 and the compiler to optimise for maximum speed:

    inline int32_t
    GPIOPinRead(uint32_t ui32Port, uint8_t ui8Pins)
    {
        //
        // Return the pin value(s).
        //
        return(HWREG(ui32Port + (GPIO_O_DATA + (ui8Pins << 2))));
    }

    inline void
    GPIOPinWrite(uint32_t ui32Port, uint8_t ui8Pins, uint8_t ui8Val)
    {
        //
        // Write the pins.
        //
        HWREG(ui32Port + (GPIO_O_DATA + (ui8Pins << 2))) = ui8Val;
    }

    uint32_t global_apb_errors;
    uint32_t global_ahb_errors;

    int
    main(void)
    {
     uint32_t iteration;
     uint32_t test_bit_0, test_bit_1;
     uint32_t result_bit_0, result_bit_1;
     uint32_t apb_errors = 0;
     uint32_t ahb_errors = 0;

        //
        // Set the clocking for a 80MHz system clock
        //
        SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ |
                           SYSCTL_OSC_MAIN);

        // Use the following connections on a Stellaris launchpad to check for
        // coherence between reading/writing pins on different GPIOs:
        // a. J2.06 (PB7) is also connected via 0-ohm resistor to J3.04 (PD1).
        // b. J2.07 (PB6) is also connected via 0-ohm resistor to J3.03 (PD0).
        SysCtlGPIOAHBDisable (SYSCTL_PERIPH_GPIOB);
        SysCtlGPIOAHBDisable (SYSCTL_PERIPH_GPIOD);
        SysCtlPeripheralEnable (SYSCTL_PERIPH_GPIOB);
        SysCtlPeripheralEnable (SYSCTL_PERIPH_GPIOD);

        GPIOPinTypeGPIOInput (GPIO_PORTB_BASE, GPIO_PIN_7);
        GPIOPinTypeGPIOOutput (GPIO_PORTD_BASE, GPIO_PIN_1);

        GPIOPinTypeGPIOOutput (GPIO_PORTB_BASE, GPIO_PIN_6);
        GPIOPinTypeGPIOInput (GPIO_PORTD_BASE, GPIO_PIN_0);

     // Test GPIO loopback using APB aperture
        for (iteration = 0; iteration < 100; iteration++)
        {
         for (test_bit_0 = 0; test_bit_0 <= 1; test_bit_0++)
         {
          for (test_bit_1 = 0; test_bit_1 <= 1; test_bit_1++)
          {
              GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_1, test_bit_0 * GPIO_PIN_1);
           result_bit_0 = GPIOPinRead(GPIO_PORTB_BASE, GPIO_PIN_7);
           GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_6, test_bit_1 * GPIO_PIN_6);
           result_bit_1 = GPIOPinRead(GPIO_PORTD_BASE, GPIO_PIN_0);

           if (test_bit_0 != (result_bit_0 != 0))
           {
            apb_errors++;
           }
           if (test_bit_1 != (result_bit_1 != 0))
           {
            apb_errors++;
           }
          }
         }
        }

     // Test GPIO loopback using AHB aperture
        SysCtlGPIOAHBEnable (SYSCTL_PERIPH_GPIOB);
        SysCtlGPIOAHBEnable (SYSCTL_PERIPH_GPIOD);
        for (iteration = 0; iteration < 100; iteration++)
        {
         for (test_bit_0 = 0; test_bit_0 <= 1; test_bit_0++)
         {
          for (test_bit_1 = 0; test_bit_1 <= 1; test_bit_1++)
          {
           GPIOPinWrite(GPIO_PORTD_AHB_BASE, GPIO_PIN_1, test_bit_0 * GPIO_PIN_1);
           result_bit_0 = GPIOPinRead(GPIO_PORTB_AHB_BASE, GPIO_PIN_7);
           GPIOPinWrite(GPIO_PORTB_AHB_BASE, GPIO_PIN_6, test_bit_1 * GPIO_PIN_6);
           result_bit_1 = GPIOPinRead(GPIO_PORTD_AHB_BASE, GPIO_PIN_0);

           if (test_bit_0 != (result_bit_0 != 0))
           {
            ahb_errors++;
           }
           if (test_bit_1 != (result_bit_1 != 0))
           {
            ahb_errors++;
           }
          }
         }
        }

        // Prevent the compiler optimising out the errors results
        // (set a breakpoint here to view results)
        global_apb_errors = apb_errors;
        global_ahb_errors = ahb_errors;
        return apb_errors + ahb_errors;
    }

    The theory was that it would highlight a discrepancy between the GPIO write and read-back (on a different GPIO port). Zero errors were reported for the AHB access, and 600 errors for the APB access. This is speed senstive, since originally didn't inline the GPIOPinWrite or GPIOPinRead functions and there were zero errors. I don't understand why only the APB access reported errors, since the AHB bus is documented as providing better back-to-back access performance than the APB bus.

  • Chester Gillon said:
           GPIOPinWrite(GPIO_PORTD_AHB_BASE, GPIO_PIN_1, test_bit_0 * GPIO_PIN_1);
           result_bit_0 = GPIOPinRead(GPIO_PORTB_AHB_BASE, GPIO_PIN_7);

    Can we have a look at the dis-assembly around this area? I think the "test_bit_0 * GPIO_PIN_1" might introduce an extra instruction which will mess up the timings.

    Something like the following is giving me issues:

    f24b 0308 movw r3, #45064 ; 0xb008
    f2c4 0305 movt r3, #16389 ; 0x4005
    2200 movs r2, #0
    601a str r2, [r3, #0]
    f44f 4312 mov.w r3, #37376 ; 0x9200
    f2c4 0305 movt r3, #16389 ; 0x4005
    681b ldr r3, [r3, #0]

  • Alexandru Gagniuc said:
           GPIOPinWrite(GPIO_PORTD_AHB_BASE, GPIO_PIN_1, test_bit_0 * GPIO_PIN_1);
           result_bit_0 = GPIOPinRead(GPIO_PORTB_AHB_BASE, GPIO_PIN_7);

    Can we have a look at the dis-assembly around this area? I think the "test_bit_0 * GPIO_PIN_1" might introduce an extra instruction which will mess up the timings.[/quote]The disassembly in the debugger is showing me the following:

     98           HWREG(ui32Port + (GPIO_O_DATA + (ui8Pins << 2))) = ui8Val;
    00000392:   F8CE5000 STR.W           R5, [R14, #0]
     89           return(HWREG(ui32Port + (GPIO_O_DATA + (ui8Pins << 2))));
    00000396:   F8D8C000 LDR.W           R12, [R8, #0]

    Where:
    - R14 = 0x40059100 = GPIO Port B (AHB aperture)
    - R8 = 0x4005B004 = GPIO Port D (AHB aperture)

    i.e. the optimiser has generated back-to-back instructions for writing to GPIOB followed by reading from GPIOD.

  • Chester Gillon said:
    This is speed senstive, since originally didn't inline the GPIOPinWrite or GPIOPinRead functions and there were zero errors.

    Found that:

    a) Reducing the CPU speed didn't make the APB errors go away.

    b) Inserting 4 NOPs between the GPIO write and read made the APB errors go away.

    Not sure if the "delay" is in the GPIO write and/or GPIO read.

  • I have no idea why you are seeing the error on the APB and not AHB bus, but your symptoms are exactly what I am experiencing.