This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

nrf_gpio_pin_read and optimized code might return wrong value

Hi, I have found an interesting behavior from this code:

nrf_gpio_cfg_input(7,NRF_GPIO_PIN_NOPULL);
if (nrf_gpio_pin_read(7)==0)
{
 NRF_LOG_INFO("ID1(P0.%d) is LOW.",7);
}else{
 NRF_LOG_INFO("ID1(P0.%d) is HIGH.",7);
}

the nrf_gpio_pin_read will always return 0 as if the pin was low, but the pin is in fact connected to 1.8V through a 10k resistor. Further investigating first added a delay_ms(1) before the if, and that fixed the issue, but still didn't make sense. Then rebuilt the code with

#pragma GCC push_options
#pragma GCC optimize ("O0")
...
#pragma GCC pop_options

around the gpio_init function and it was working without the delay. Started suspecting that maybe it is an analog issue, so we tried without the 10k resistor, but the behavior was the same.

After this we started dissecting the generated assembly code and found that with the default optimized code output has the str.w instruction (used to set the pin direction) directly followed by the read of the pin ldr.w. So started adding nops between the two and found that it won't work with one, but will work with 2 or more. The non-working one looks like this (this is with 1 nop, 0 no is the same without that):

   reg->PIN_CNF[pin_number] = ((uint32_t)dir << GPIO_PIN_CNF_DIR_Pos)
   2647e:       f04f 43a0       mov.w   r3, #1342177280 ; 0x50000000
   26482:       2200            movs    r2, #0
   //------------INTERESTING PART FROM HERE---------------
   **26484:       f8c3 271c       str.w   r2, [r3, #1820] ; 0x71c
        __asm__("nop");
   **26488:       bf00            nop
    return p_reg->IN;
   **2648a:       f8d3 2510       ldr.w   r2, [r3, #1296] ; 0x510
        if (nrf_gpio_pin_read(ID_PIN1)==0)
   2648e:       f012 0f80       tst.w   r2, #128        ; 0x80
        {
                NRF_LOG_INFO("ID1(P0.%d) is LOW.",ID_PIN1);
   26492:       6862            ldr     r2, [r4, #4]
        if (nrf_gpio_pin_read(ID_PIN1)==0)
   26494:       d10f            bne.n   264b6 <board_init+0x42>

And the working one is:

 26484:       f8c3 271c       str.w   r2, [r3, #1820] ; 0x71c
        __asm__("nop");
   26488:       bf00            nop
        __asm__("nop");
   2648a:       bf00            nop
    return p_reg->IN;
   2648c:       f8d3 2510       ldr.w   r2, [r3, #1296] ; 0x510
        if (nrf_gpio_pin_read(ID_PIN1)==0)
   26490:       f012 0f80       tst.w   r2, #128        ; 0x80
        {
                NRF_LOG_INFO("ID1(P0.%d) is LOW.",ID_PIN1);
   26494:       6862            ldr     r2, [r4, #4]
        if (nrf_gpio_pin_read(ID_PIN1)==0)
   26496:       d10f            bne.n   264b8 <board_init+0x44>

So it seems that if the direction change and the read gets within 3 clock cycles it will return the wrong value. An obvious fix of course is to make the assembly longer eg. by creating a volatile uint32_t zero=0 and make the if use that instead of "0", but I think this could be fixed in a nicer way (especially since this is not bullet proof). Is this a known behavior of the nRF52832? Should it be fixed in the SDK by simply adding 2 nops to the direction change static inline function?

Thanks Levente

Parents Reply Children
No Data
Related