Low level driver implementation without Zephyr on nRF Connect SDK / How to get UARTE running

Hi,

I've been working with the nRF5x SDK for many years and happily implemented low level drivers on register level, sometimes also using the nrf_drv drivers, running FreeRTOS and using a simple makefile build system.

Now I need to port one project to the nRF5340, which means moving on to the nRF Connect SDK, and life has become hell. I don't mind using Zephyr for IPC and Bluetooth related topics, but I need full control over my device drivers as they sometimes implement critical features with hard real-time constraints. There are also scenarios where I need to connect different drivers via the PPI and handle low latency interrupts.

Developing software with Visual Studio Code and the Nordic plugin is really exhausting. Even though the plugin should have installed the necessary addons, VS Code asks me each time I start it if I want to install the plugins that should be already there (like Git and C++). Also, errors pop up and disappear all the time. And VS Code suddelny switches to other projects and/or build targets without any notice. But well, the most critical part is that I cannot get my device drivers running properly anymore.

One example: I started a new project based on zephyr/samples/basic/threads, using the nRF5340DK as a build target. There, I wanted to get the UARTE2 device running. But it is really difficult, I had problems using the nrfx_uarte driver as it didn't find UARTE2 even though I tried to enable it in proj.conf ("CONFIG_NRFX_UARTE2=y" did not work). I have lost a whole week already, and so I decided to go down to register level and implement my own UARTE driver (as I will need to add special low latency handling later anyway). But I still can't get the UARTE interface running on low level.

I have two particular question and hope somebody can help:

1. How can I get full access to the hardware devices while Zephyr is running on the background? Just including <nrf.h> or <nrf5340_application.h> does not help. To get the definitions for the UARTE2 device, I have to include <nrfx_uarte.h>. One more issue is that the UARTE device does not seem to work as expected, which brings me to my second question:

2. If I execute the following initialization code which should start receiving UART data, the TASKS_STARTRX task does not trigger an EVENTS_RXSTARTED event as expected. The device registers are initialized correctly as I can see in the VS Code plugin during debugging, but the event is not set. I observe similiar behavior when I try to transmit data instead of receiving (set the TXD buffer and size, trigger TASKS_STARTTX and wit for EVENTS_TXSTARTED which never appears. What could be the reason for this?

	NRF_UARTE2->ENABLE = 0;
	while (NRF_UARTE2->ENABLE != 0);
	NRF_UARTE2->PSEL.RXD = UART_RX_PIN;
	NRF_UARTE2->PSEL.TXD = UART_TX_PIN;
	NRF_UARTE2->PSEL.RTS = -1;
	NRF_UARTE2->PSEL.CTS = -1;
	NRF_UARTE2->CONFIG = 0;
	NRF_UARTE2->BAUDRATE = NRF_UARTE_BAUDRATE_115200;
	NRF_UARTE2->SHORTS = NRF_UARTE_SHORT_ENDRX_STARTRX;
	NRF_UARTE2->TXD.PTR = 0;
	NRF_UARTE2->TXD.MAXCNT = 0;
	NRF_UARTE2->RXD.PTR = (uint32_t)(m_rx_buffer);
	NRF_UARTE2->RXD.MAXCNT = 5;
	NRF_UARTE2->INTENCLR = 0xFFFFFFFF;
	NRF_UARTE2->ENABLE = 1;
	while (!NRF_UARTE2->ENABLE);
	NRF_UARTE2->EVENTS_RXSTARTED = 0;
	NRF_UARTE2->TASKS_STARTRX = 1;
	while (!NRF_UARTE2->EVENTS_RXSTARTED); // never returns!

It doesn't matter whether I run this code in one of the sample's Zephyr threads, or it I add a main() function to the sample and run the initialization there. (I found out that I can just add a main() function and do some initialization stuff there, the threads will be started automatically once the main() function returns.)

Please help me to gain control over the silicon once more Slight smile

Parents
  • Hi,

     

    Now I need to port one project to the nRF5340, which means moving on to the nRF Connect SDK, and life has become hell. I don't mind using Zephyr for IPC and Bluetooth related topics, but I need full control over my device drivers as they sometimes implement critical features with hard real-time constraints. There are also scenarios where I need to connect different drivers via the PPI and handle low latency interrupts.

    Developing software with Visual Studio Code and the Nordic plugin is really exhausting. Even though the plugin should have installed the necessary addons, VS Code asks me each time I start it if I want to install the plugins that should be already there (like Git and C++). Also, errors pop up and disappear all the time. And VS Code suddelny switches to other projects and/or build targets without any notice. But well, the most critical part is that I cannot get my device drivers running properly anymore.

    I fully understand your situation and I apologize for the rough path to getting your setup up and running.

    If you end up implementing low-level functionality directly, be aware of how zephyr kernel wraps the interrupt vectors. Here's an example on how to enable a given ISR vector:

    https://docs.zephyrproject.org/3.2.0/kernel/services/interrupts.html#defining-a-regular-isr 

      

    One example: I started a new project based on zephyr/samples/basic/threads, using the nRF5340DK as a build target. There, I wanted to get the UARTE2 device running. But it is really difficult, I had problems using the nrfx_uarte driver as it didn't find UARTE2 even though I tried to enable it in proj.conf ("CONFIG_NRFX_UARTE2=y" did not work). I have lost a whole week already, and so I decided to go down to register level and implement my own UARTE driver (as I will need to add special low latency handling later anyway). But I still can't get the UARTE interface running on low level.

    Recently we added nrfx samples in the repository:

    https://github.com/zephyrproject-rtos/hal_nordic/tree/master/nrfx/samples/src/nrfx_uarte

     

    You can use these if you want to use any of the nrfx drivers directly in NCS.

     

    1. How can I get full access to the hardware devices while Zephyr is running on the background? Just including <nrf.h> or <nrf5340_application.h> does not help. To get the definitions for the UARTE2 device, I have to include <nrfx_uarte.h>. One more issue is that the UARTE device does not seem to work as expected, which brings me to my second question:

    You can include <nrf.h> directly. This shall include the correct device, based on the board that you compile for.

     

    2. If I execute the following initialization code which should start receiving UART data, the TASKS_STARTRX task does not trigger an EVENTS_RXSTARTED event as expected. The device registers are initialized correctly as I can see in the VS Code plugin during debugging, but the event is not set. I observe similiar behavior when I try to transmit data instead of receiving (set the TXD buffer and size, trigger TASKS_STARTTX and wit for EVENTS_TXSTARTED which never appears. What could be the reason for this?

    When writing on bare metal level, I recommend that you use the defines from nrf*_bitfields.h, to ensure that you're setting the correct bit in the register:

    NRF_UARTE2->ENABLE = UARTE_ENABLE_ENABLE_Enabled << UARTE_ENABLE_ENABLE_Pos;

    I see that you have used these defines for most of your setup, but it looks like the ENABLE register is set to the incorrect value in your code snippet.

    .ENABLE for bus peripherals are not '1'. The reasoning behind this is that the hardware peripheral itself can be setup in UART/SPI*/TWI* mode under-the-hood.

    example: NRF_UARTEx->ENABLE is '8', while NRF_SPIMx->ENABLE is '7'.

     

    Kind regards,

    Håkon

  • Hi Håkon,

    thank you very much for the detailed reply, especially for the hint about zephyr interrupt handling. I'll look into this later.

    I guess there will be no way around these zephyr based custom board definitions ;) I'll make some time to get more familiar with them. It's a bit confusing with all the abstraction layers and files that override other files, and I couldn't find a direct link to the syntax for these descriptions, but I'll watch some more tutorials and give it a try.

    And sorry for getting the ENABLE value wrong... I must have overlooked this as the events and tasks usually work with "1", so I missed that there are multiple bits defined for ENABLE in the product specification. With this fixed, I got my sample code running.

    Best regards,

    Michael

  • Hi Michael,

     

    I'm glad to hear that you got things working!

    puz_md said:
    I guess there will be no way around these zephyr based custom board definitions ;) I'll make some time to get more familiar with them. It's a bit confusing with all the abstraction layers and files that override other files, and I couldn't find a direct link to the syntax for these descriptions, but I'll watch some more tutorials and give it a try.

    Yes, this is true. The kernel will declare the interrupt handlers, which then requires you to use the API that it provides for these.

    Note that you can also use IRQ_DIRECT_CONNECT for higher realtime requirements: https://docs.zephyrproject.org/3.2.0/kernel/services/interrupts.html#defining-a-direct-isr

    We use this for anything that includes RADIO peripheral, as protocol handling normally requires tighter timing.

     

    Cheers,

    Håkon

Reply
  • Hi Michael,

     

    I'm glad to hear that you got things working!

    puz_md said:
    I guess there will be no way around these zephyr based custom board definitions ;) I'll make some time to get more familiar with them. It's a bit confusing with all the abstraction layers and files that override other files, and I couldn't find a direct link to the syntax for these descriptions, but I'll watch some more tutorials and give it a try.

    Yes, this is true. The kernel will declare the interrupt handlers, which then requires you to use the API that it provides for these.

    Note that you can also use IRQ_DIRECT_CONNECT for higher realtime requirements: https://docs.zephyrproject.org/3.2.0/kernel/services/interrupts.html#defining-a-direct-isr

    We use this for anything that includes RADIO peripheral, as protocol handling normally requires tighter timing.

     

    Cheers,

    Håkon

Children
No Data
Related