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

nrf52832 uart tx works only with NRF_UARTE0 read after EndTx

FormerMember
FormerMember

Greetings, There is a bit of strange operation in UARTE peripheral in nrf52832 with the DK. In the simple code below, if both of the if statements with comments 'Check 1' and 'Check 2' are removed the 'hello' prints are not sent and I get some junk value. If either of the two reads to the UARTE registers is made in these if statement, then the 'hello' prints are sent out correctly. What's happening? I don't see any errata related to UARTE peripheral.

Thanks.

Toolchain: GCC 4.9.3q2 on Linux

#include <stdbool.h>
#include <stdint.h>
#include "nrf_delay.h"
#include "nrf52-gpio.h"
#include "nrf52-clock.h"
#include "board.h"
#include "nrf52-uart.h"

int main(void)
{
	nrf_gpio_cfg_output(LED1);
	nrf_gpio_cfg_output(LED2);
	nrf_gpio_cfg_output(LED3);
	nrf_gpio_cfg_output(LED4);

	/* Configure TX and RX pins from board.h */
	nrf_gpio_cfg_output(TX_PIN_NUMBER);
	nrf_gpio_cfg_input(RX_PIN_NUMBER, GPIO_PIN_CNF_PULL_Disabled);
	NRF_UARTE0->PSEL.TXD = TX_PIN_NUMBER;
	NRF_UARTE0->PSEL.RTS = RX_PIN_NUMBER;

	nrf_gpio_cfg_output(RTS_PIN_NUMBER);
	nrf_gpio_cfg_input(CTS_PIN_NUMBER, GPIO_PIN_CNF_PULL_Disabled);
	NRF_UARTE0->PSEL.RTS = RTS_PIN_NUMBER;
	NRF_UARTE0->PSEL.CTS = CTS_PIN_NUMBER;
	NRF_UARTE0->CONFIG = (UARTE_CONFIG_HWFC_Enabled << UARTE_CONFIG_HWFC_Pos);

	/* Configure other UART parameters, BAUD rate is defined in nrf52-uart.h	*/
	NRF_UARTE0->BAUDRATE = (UARTE_BAUDRATE << UARTE_BAUDRATE_BAUDRATE_Pos);
	NRF_UARTE0->ENABLE = (UARTE_ENABLE_ENABLE_Enabled << UARTE_ENABLE_ENABLE_Pos);

    // Toggle LEDs.
    while (true)
    {
    	uint32_t i;
        for (i = 0; i < 2; i++)
        {
        	if(i){
        		NRF_P0->OUTSET = (1<<LED1);
        	} else {
        		NRF_P0->OUTCLR = (1<<LED1);
        	}
        	nrf_delay_ms(500);

            uint8_t str[] = "hello\n";
            uint32_t len = 6;

            NRF_UARTE0->EVENTS_ENDTX = 0;
            NRF_UARTE0->TASKS_STOPTX = 0;

        	NRF_UARTE0->TXD.PTR = (uint32_t)((uint8_t *) str);
        	NRF_UARTE0->TXD.MAXCNT = (uint32_t) len;

        	NRF_UARTE0->TASKS_STARTTX = 1;

        	while((0 == NRF_UARTE0->EVENTS_ENDTX)&&(0 == NRF_UARTE0->EVENTS_TXSTOPPED)){}

            /*****Check 1*****/
        	if(NRF_UARTE0->EVENTS_ENDTX){
        		NRF_P0->OUTSET = (1<<LED3);
        	}

            /*****Check 2*****/    
        	if(6 == NRF_UARTE0->TXD.AMOUNT){
        		NRF_P0->OUTSET = (1<<LED2);
        	}

            NRF_UARTE0->EVENTS_ENDTX = 0;
            NRF_UARTE0->TASKS_STOPTX = 0;
        }
    }
}
  • s'ok - I can work through this as it is, would be good if I could build it myself. I can see some odd differences already. The TX PTR is loaded from the contents of the SP, however in the asm version that's properly set up beforehand (see address 60: where it's copied from r2) but in the other version it's not set up at all as far as I can tell, so it should have random garbage in it. There's also no link to the string at the end of the assembly (that word at 0x88 in the asm version but missing from the other version), so yes it seems it was optimised out. However the registers are still written so .. currently making no sense at all.

    And why the addition of the r0 asm in there makes a difference is currently also beyond me.

    Will look some more, perhaps I'll see if I can set up a makefile and build it too.

  • just copy the code from my comments to blinky main.c file in nRF52 SDk and use the existing makefile. It took me a lot of time to narrow it down to that single ASM r0 write and the compiler go bananas without it : )

  • Not really an answer but the comments are too restricted.

    I have a shorter version which exhibits the same optimisation 'issue'

    #include <stdint.h>
    
    struct UART
    {
        __volatile uint32_t padding[200];
        __volatile uint32_t TXD;
    };
    
    #define NRF_UARTE0 ((struct UART*)0x40002000)
    
    int main(void)
    {
        char str[] = "hello\n";
        NRF_UARTE0->TXD = (uint32_t)str;
    }
    

    when compiled main looks like this

    00000000 <main>:
       0:   4b03            ldr     r3, [pc, #12]   ; (10 <main+0x10>)
       2:   b082            sub     sp, #8
       4:   2000            movs    r0, #0
       6:   f8c3 d320       str.w   sp, [r3, #800]  ; 0x320
       a:   b002            add     sp, #8
       c:   4770            bx      lr
       e:   bf00            nop
      10:   40002000        .word   0x40002000
    

    What's happening is space is reserved on the stack for 'str' and that address is put into TXD but it's never initialised with 'hello\n"

    An easy workaround is to change char str[] = "hello\n"; to char *str="hello\n";, then the output looks like this (making the variable volatile does the same thing)

    00000000 <main>:
       0:   4b02            ldr     r3, [pc, #8]    ; (c <main+0xc>)
       2:   4a03            ldr     r2, [pc, #12]   ; (10 <main+0x10>)
       4:   f8c3 2320       str.w   r2, [r3, #800]  ; 0x320
       8:   2000            movs    r0, #0
       a:   4770            bx      lr
       c:   40002000        .word   0x40002000
      10:   00000000        .word   0x00000000
    

    The contents of the address at main+0x10 is fixed up at link time to point to the .rodata initialised string.

    This is compiled at -Os however -O1 does the same (as does -O2).

    So what appears to be happening is space is reserved for the string on the stack, but it's not initialised. If you make the string longer you get more space reserved (ie sub sp, #8 becomes sub sp, #).

    If you call strlen() on it, it initialises the string properly. So gcc has decided that it needs to allocate space on the stack for the string, however the contents isn't used so it doesn't bother to initialise it. This may or may not be related to how the register is defined at uint32_t but changing it around hasn't helped.

    Is this a gcc bug? If it is it's not recent, I tried it with last year's Q1 release and it's still there. I think it's a bug myself and the memory should be initialised but there may be some bizarre C rule I don't know which makes that code undefined and allows the compiler to make the choice it does.

  • Excellent, we are getting close. Did you see if this behavior is same with GCC on nRF51? If not then GCC is not following standard on one of them. Sounds like a bug

  • DIdn't read all, but isn't it possible that "hello" ends up in flash? UARTE can work only on RAM data locations.

Related