How to put the UART to sleep (low power mode)

I believe it started with SDK 2.0.0 but the board config files now define different ways to specify how hardware is to run.

&pinctrl {

    uart1_default: uart1_default {
		group1 {
			psels = <NRF_PSEL(UART_TX, 0, 26)>,
				<NRF_PSEL(UART_RTS, 0, 29)>;
		};
		group2 {
			psels = <NRF_PSEL(UART_RX, 0, 27)>,
				<NRF_PSEL(UART_CTS, 0, 28)>;
			bias-pull-up;
		};
	};

	uart1_sleep: uart1_sleep {
		group1 {
			psels = <NRF_PSEL(UART_TX, 0, 26)>,
				<NRF_PSEL(UART_RX, 0, 27)>,
				<NRF_PSEL(UART_RTS, 0, 29)>,
				<NRF_PSEL(UART_CTS, 0, 28)>;
			low-power-enable;
		};
	};
	
&uart1 {
	status = "okay";
	pinctrl-0 = <&uart1_default_alt>;
	pinctrl-1 = <&uart1_sleep_alt>;
	pinctrl-names = "default", "sleep";
};

I took this to mean that I can dynamically toggle these configurations from "uart1_default" to "uart1_sleep" from perhaps a function call. Did find the pinctrl_apply_state() function call but it's return a -2 that state id doesn't exist even though it is defined. Here's my code.

uart = device_get_binding("UART_1");
int err = pinctrl_apply_state(uart->config, PINCTRL_STATE_SLEEP); // Returns -2
Any ideas?
Parents
  • Hello,

    You are correct, 'pin control' was introduced in v2.0.0. However, it only controls the pin state. To get the UART to a low power state, you need to use it via the power management subsystem. The code snippet below shows an example of how you can use the API to power down your UART.

    prj.conf

    # Device power management
    CONFIG_PM_DEVICE=y

    Code 

    #include <zephyr/pm/device.h>
    
    ...
    
    void some_function(void) 
    {
        int err;
        /* Power down the UART device */
        err = pm_device_action_run(<uart_dev>, PM_DEVICE_ACTION_SUSPEND);
        if (err) {
            printk("pm_device_action_run() failed (%d)\n", err);
        }
        
        ...
        /* Power it on again */
        err = pm_device_action_run(<uart_dev>, PM_DEVICE_ACTION_RESUME);
        if (err) {
            printk("pm_device_action_run() failed (%d)\n", err);
        }

    Best regards,

    Vidar

  • Sweet, I got it to compile! Will let you know. Big thanks!

  • My apologies, I don't quite follow. Are you saying I should make a change to the below to indicate "input, disconnect"? I tried replacing low-power-enable; with input,disconnect but that doesn't compile.

    &pinctrl {

        uart0_default: uart0_default {
            group1 {
                psels = <NRF_PSEL(UART_TX, 0, 26)>,
                    <NRF_PSEL(UART_RTS, 0, 27)>;
            };
            group2 {
                psels = <NRF_PSEL(UART_RX, 0, 0)>,
                    <NRF_PSEL(UART_CTS, 0, 6)>;
                bias-pull-up;
            };
        };

        uart0_sleep: uart0_sleep {
            group1 {
                psels = <NRF_PSEL(UART_TX, 0, 26)>,
                    <NRF_PSEL(UART_RX, 0, 0)>,
                    <NRF_PSEL(UART_RTS, 0, 27)>,
                    <NRF_PSEL(UART_CTS, 0, 6)>;
                low-power-enable;
            };
        };
  • Sorry for not being clearer. No, you don't need to change anything for the UART. What I meant to point out is that there may be other pins not assigned to the UART that need to be configured as "input disconnect" as well. The easiest way to get an overview of all the pin configurations is probably by using the peripheral viewer in VS Code when you debug the application. You can place a breakpoint at the function where you enter system off to check what the GPIO configurations are at that point.

    The 'low-power-enable' property will make the pin control module configure the UARTE pins as input disconnect when the uart0_sleep group is applied via pm_device_action_run()->uarte_nrfx_pm_action() -> pinctrl_configure_pins()

    GPIO configurations viewed in VS code

    0x2 means the pin is configured as input disconnect and can safely be left floating. 

  • Except for the few pins setup for BLE Reset, none of the other GPIO pins are pinned out. In VS Code debugger, GPIO P0 and P1 pins have the value of 0x00000000. I assume that's ok.

  • Assume you meant 0x00000002. Either way, if there is a problem with the pin configurations, you should see that when you measure the current draw.

  • This is working really well and we are getting the reduced power as per the datasheet.

    The next problem is I need to be able to restart the 52840 from the 9160. Spent a day trying to figure this out to no avail. Not sure what I need to do. Info online seems old and unrelated. Our 9160 pin 31 is what's connected to the 52840 pin 18 but not sure how to define that and activate it.

Reply Children
  • The SDK examples enable the pinreset functionality on P0.18 by default (you can read the PSELRESET[n] register to confirm this). So, I assume the problem you're experiencing is with toggling of the IO from the 9160? 

    The bt_hci_transport_setup() function in https://github.com/nrfconnect/sdk-zephyr/blob/main/boards/arm/nrf9160dk_nrf9160/nrf52840_reset.c demonstrates one way you can assign a GPIO to control the reset line to the 52840.

    Example

    C code:

    #include <zephyr/drivers/gpio.h>
    
    #define RESET_NODE DT_NODELABEL(nrf52840_reset)
    
    
    #define RESET_GPIO_CTRL  DT_GPIO_CTLR(RESET_NODE, gpios)
    #define RESET_GPIO_PIN   DT_GPIO_PIN(RESET_NODE, gpios)
    #define RESET_GPIO_FLAGS DT_GPIO_FLAGS(RESET_NODE, gpios)
    
    void reset_nrf52840(void)
    {
    	int err;
    	const struct device *port = DEVICE_DT_GET(RESET_GPIO_CTRL);
    
    	if (!device_is_ready(port)) {
    		printk("nrf52840_reset is not ready\n");
    	}
    
    	/* Configure pin as output and initialize it to inactive state. */
    	err = gpio_pin_configure(port, RESET_GPIO_PIN,
    				 RESET_GPIO_FLAGS | GPIO_OUTPUT_INACTIVE);
    	if (err) {
    		printk("gpio_pin_configure() failed (err: %d)\n", err);
    		return;
    	}
    
    	/* Assert the reset line */
    	err = gpio_pin_set(port, RESET_GPIO_PIN, 1);
    	if (err) {
    		printk("gpio_pin_set() failed (err: %d)\n", err);
    		return;
    	}
        
        k_sleep(K_MSEC(10));
    
    	/* Release reset line */
    	err = gpio_pin_set(port, RESET_GPIO_PIN, 0);
    	if (err) {
    		printk("gpio_pin_set() failed (err: %d)\n", err);
    		return;
    	}
    
    	printk("nrf52840 is reset\n");
    
    }
    
    
    void main(void)
    {
    	reset_nrf52840();
    	...

    DTS overlay:

    &nrf52840_reset {
    	status = "okay";
    	gpios = <&gpio0 31 GPIO_ACTIVE_LOW>;
    };

  • I was able to code it out. Looks to be working. These crazy macro will be the death of me.

    void ble_reset()
    {
        if(!device_is_ready(gpio_dev))
        {
    		LOG_ERR("nrf52840_reset is not ready");
            return;
    	}
    
        // Configure pin as output and initialize it to inactive state.
    	int err = gpio_pin_configure(gpio_dev, GPIO_BLE_RESET_PIN, 0 | GPIO_OUTPUT_INACTIVE);
    	if(err)
        {
    		LOG_ERR("gpio_pin_configure() failed (err: %d)", err);
    		return;
    	}
    
        // Assert the reset line
    	err = gpio_pin_set(gpio_dev, GPIO_BLE_RESET_PIN, 1);
    	if(err)
        {
    		LOG_ERR("gpio_pin_set() failed (err: %d)", err);
    		return;
    	}
        
        k_sleep(K_MSEC(10));
    
    	// Release reset line
    	err = gpio_pin_set(gpio_dev, GPIO_BLE_RESET_PIN, 0);
    	if(err)
        {
    		LOG_ERR("gpio_pin_set() failed (err: %d)", err);
    		return;
    	}
    
    	printk("nrf52840 is reset\n");
    }

  • Excellent, thanks for the update. Regarding the macro errors, if you have the time, there was a great talk about this topic by Marti at the Zephyr developer summit where he explains the "macrobatics," as he called it. Link to the recording if you are interested: https://www.youtube.com/watch?v=w8GgP3h0M8M.  

  • I wasn't able to get past the macro errors which is why I coded it. Thanks for the video. I now understand why macros are used but I'll need to watch the video a few times. macros to this degree is very complicated. Would like to understand it better.

    Thanks got all your help! I greatly appreciate it!

  • I agree it's complicated (when it doesn't work). But I think I know why it did not work for you. I was building for the 'nrf9160dk_nrf9160' board which includes the device tree binding for 'nrf52840_reset' node: https://github.com/nrfconnect/sdk-zephyr/commit/71223ad0d0e6f152912f314ea2df3fa656995bb6 . This may not be included if you have created a custom board.

Related