Device tree : how to get uart characteristics from DTS?

I'm struggling with SDKConnect and the device tree stuff... and finding it hard to get good examples or docs TBH

So, I have the sample dts files for the nRF5340DK, which defines the config for the 'uart0' node:

&uart0 {
    status = "okay";
    current-speed = <115200>;
    pinctrl-0 = <&uart0_default>;
    pinctrl-1 = <&uart0_sleep>;
    pinctrl-names = "default", "sleep";
};
and the pinctrl mappings:
uart0_default: uart0_default {
        group1 {
            psels = <NRF_PSEL(UART_TX, 0, 20)>,
                <NRF_PSEL(UART_RTS, 0, 19)>;
        };
        group2 {
            psels = <NRF_PSEL(UART_RX, 0, 22)>,
                <NRF_PSEL(UART_CTS, 0, 21)>;
            bias-pull-up;
        };
    };

    uart0_sleep: uart0_sleep {
        group1 {
            psels = <NRF_PSEL(UART_TX, 0, 20)>,
                <NRF_PSEL(UART_RX, 0, 22)>,
                <NRF_PSEL(UART_RTS, 0, 19)>,
                <NRF_PSEL(UART_CTS, 0, 21)>;
            low-power-enable;
        };
    };
aliases {
    console = &uart0;
}
Given this, how to I get the port/pins for my console in my C code? and switch between 'default' and 'sleep' cases easily - given they are in 2 groups for one case and 1 group for the other?
I want to use the nrfx uarte driver, not the zephyr driver, so getting the struct device (via get_device_info()) doesn't help?
I was hoping to be able to do something like:
uint8_t rxd_pin = NRFX_PSEL_UART_RX_PORT(DT_PROP(DT_PROP(DT_ALIAS(console), group1)),psel) << 4 | NRFX_PSEL_UART_RX_PIN(DT_PROP(DT_PROP(DT_ALIAS(console), group1)),
(since I saw similar code to get the pins for an I2C node... NRFX_PSEL_SDA_PORT/PIN macros)
but no such macro seems to exist for uart? I can get the 'current-speed' property ok...
Any pointers on using the pinctrl macros to get NRF_PSEL components? I will confess to being a newbie on the whole device tree config stuff...
Maybe I'm looking in the wrong place - but honestly there are SO many files to keep in mind for the dts stuff....
thanks!
Parents
  • Hi BrianW,

    I empathize with your difficulties in getting starts with Devicetree. We do offer an online course to help with the early stages, but what you are trying to do is unfortunately more advanced than what the course covers.

    You will first want to look into the concept of phandle, then you can find the pin control API here: https://docs.nordicsemi.com/bundle/ncs-2.6.1/page/zephyr/build/dts/api/api.html#pinctrl-pin-control.

    Devicetree API is makes it more straightforward when using the Zephyr API to access peripheral. As you want to use nrfx drives, it becomes a little more complex.
    The benefit of using the API above is that your hardware description is still concentrated in the Devicetree, allowing the project to support multiple hardware with just the Devicetree board files change. You can just disable the nodes in Devicetree, and use pin number directly in code, at the cost of this benefit.

    Hieu

  • Devicetree API is makes it more straightforward when using the Zephyr API to access peripheral. As you want to use nrfx drives, it becomes a little more complex.

    Yes, it does. And I was kind of hoping for a reply with more specific help about how to do it, the documentation is VERY basic.

    Specific questions then:

    I want to get the uart config to populate an nrfx uarte config structure, 

    nrfx_uarte_config_t uarte_comm_params;
    Specifically .rxd_pin, .txd_pin, .rts_pin, .cts_pin, .baudrate, .config.parity,, .config.paritytype, .config.stop.
    I use an alias 'console' to reference the uart: 
    / {
        aliases {
            console = &uart0;
    };
    and then uart0 and its pinctrl as as previously.
    My C code wants to do:
    init_uart("console");
    but I'll accept 
    init_uart(DT_PATH(DT_ALIAS(console)))
    to get the string path that I can then use to find the node...
    Q1 : how to go from a runtime string "console" to a DT node to be able look up properties? Are there callable methods that do the same jobs as the macros to interrogate the DTS at runtime?
    Q2 : how to get the pinctrl property for the specific pin use? (eg the pin for rxd in 'default' pinctrl?)
    Q3 : how to get the gpio pin reference for the txd, rxd etc pins from the pinctrl (given the use of the PSEL() macro to merge pin use, port, gpio into a single 32 bit value)? Where are the macros to unpack NRF_PSEL?
    A code sample would be very helpful.
    thanks
  • by the way, I tried doing

    char* nodepath = DT_PATH(DT_ALIAS(console));

    and this fails to compile:

    C:/work/dev/nordic_connect/zephyr/include/zephyr/devicetree.h:90:17: error: 'DT_N_S_DT_N_S_soc_S_peripheral_40000000_S_uart_8000' undeclared (first use in this function); did you mean 'DT_N_S_soc_S_peripheral_40000000_S_uart_8000_ORD'?

    The duplicated "DT_N_S_" part makes me think that the DT_PATH and DT_ALIAS macros don't play well together, despite what I understand the parameter to DT_PATH to be? (a node id).

    any pointers on this?

  • BrianW said:
    Q1 : how to go from a runtime string "console" to a DT node to be able look up properties? Are there callable methods that do the same jobs as the macros to interrogate the DTS at runtime?
    BrianW said:

    The duplicated "DT_N_S_" part makes me think that the DT_PATH and DT_ALIAS macros don't play well together, despite what I understand the parameter to DT_PATH to be? (a node id).

    any pointers on this?

    Note in the device tree that the console is associated with an UART node by setting the zephyr-console property of the chosen node. 
    console is thus not an alias of the UART node.

    To access such chosen values, you can use DT_CHOSEN.

    DT_CHOSEN(zephyr_console)

    BrianW said:
    Q2 : how to get the pinctrl property for the specific pin use? (eg the pin for rxd in 'default' pinctrl?)

    You can get the pinctrl node by using DT_PINCTRL_BY_NAME.

    BrianW said:
    Q3 : how to get the gpio pin reference for the txd, rxd etc pins from the pinctrl (given the use of the PSEL() macro to merge pin use, port, gpio into a single 32 bit value)?

    There are a few methods.

    Under the pinctrl nodes are its group child node, and under the group nodes are the psels property.
    They are still Devicetree child nodes and properties, so they can be access with DT_PROP, DT_CHILD, DT_FOREACH_PROP_ELEM, DT_FOREACH_CHILD.

    Alternatively, you can use the Pin Control API.

    BrianW said:
    A code sample would be very helpful.

    Here is the sample, just replace the code into main.c in the Hello World sample.

    /*
     * References:
     * 	C:\ncs\v2.6.1\zephyr\drivers\pinctrl\pinctrl_nrf.c
     *  C:\ncs\v2.6.1\zephyr\include\zephyr\drivers\pinctrl.h
     *  C:\ncs\v2.6.1\zephyr\drivers\serial\uart_nrfx_uarte.c
     *  C:\ncs\v2.6.1\zephyr\dts\bindings\pinctrl\nordic,nrf-pinctrl.yaml
     *  C:\REDACTED\hello_world_261\build_52840dk\zephyr\include\generated\devicetree_generated.h
     *  C:\ncs\v2.6.1\zephyr\soc\common\nordic_nrf\pinctrl_soc.h
     *  https://devzone.nordicsemi.com/f/nordic-q-a/109455/how-to-use-device-tree-to-set-up-pdm-pin-configurations-with-nrfx-pdm-directly-no-dmic
     */
    
    #include <stdio.h>
    #include <zephyr/devicetree.h>
    #include <zephyr/drivers/pinctrl.h>
    
    // See also this file:
    #include <devicetree_generated.h>
    
    
    #define M_CONSOLE_UART_NODE_ID  DT_CHOSEN(zephyr_console)                       // uart0 node ID, DT_N_S_soc_S_uart_40002000
    #define M_PINCTRL_0_NODE_ID     DT_PINCTRL_BY_IDX(M_CONSOLE_UART_NODE_ID, 0, 0) // See also DT_PINCTRL_0, DT_PINCTRL_BY_NAME
    
    /**************************************************************************************************
     * Method 1
     *************************************************************************************************/
    #define M_PINCTRL_0_GROUP1_NODE_ID              DT_CHILD(M_PINCTRL_0_NODE_ID, group1)
    const uint32_t pinctrl_0_group1_psels[] =       DT_PROP(M_PINCTRL_0_GROUP1_NODE_ID, psels);
    const uint32_t pinctrl_0_group1_psels_size =    DT_PROP_LEN(M_PINCTRL_0_GROUP1_NODE_ID, psels);
    
    
    
    /**************************************************************************************************
     * Method 2
     *************************************************************************************************/
    #define PRINT_FROM_PSEL_MACRO_FN(node_id, prop, idx)                                    \
    	do {                                                                                \
    		print_psel(DT_PROP_BY_IDX(node_id, prop, idx));                                 \
    	} while(0);
    
    #define RUN_FOREACH_PSELS_ELEM_FN(GROUP_NODE_ID)                                        \
    	do {                                                                                \
    		printf("RUN_FOREACH_PSELS_ELEM_FN: %s\n", DT_NODE_FULL_NAME(GROUP_NODE_ID));    \
    		DT_FOREACH_PROP_ELEM(GROUP_NODE_ID, psels, PRINT_FROM_PSEL_MACRO_FN)            \
    	} while (0);
    
    #define RUN_FOREACH_PINCTRL_0_GROUP_FN(PINCTRL_NODE_ID) DT_FOREACH_CHILD(PINCTRL_NODE_ID, RUN_FOREACH_PSELS_ELEM_FN)
    
    
    
    void print_psel(const uint32_t psel) {
    	uint32_t pin = NRF_GET_PIN(psel);
    
    	switch (NRF_GET_FUN(psel)) {
    	case NRF_FUN_UART_TX:  printf("PSEL 0x%08x - TX:  %d\n", psel, pin); break;
    	case NRF_FUN_UART_RX:  printf("PSEL 0x%08x - RX:  %d\n", psel, pin); break;
    	case NRF_FUN_UART_RTS: printf("PSEL 0x%08x - RTS: %d\n", psel, pin); break;
    	case NRF_FUN_UART_CTS: printf("PSEL 0x%08x - CTS: %d\n", psel, pin); break;
    	}
    }
    
    // Refer pinctrl_configure_pins()
    void breakdown_pinctrl_state(const struct pinctrl_state* state)
    {
    	const pinctrl_soc_pin_t* pins = state->pins;
    	const uint8_t pin_cnt = state->pin_cnt;
    	
    	for (uint8_t i = 0U; i < pin_cnt; i++) {
    		print_psel(pins[i]);
    	}
    }
    
    
    /**************************************************************************************************
     * Method 3
     *************************************************************************************************/
    // Necessary to use PINCTRL_DT_DEV_CONFIG_GET later
    // Expands to: static const pinctrl_soc_pin_t __pinctrl_state_pins_0__device_dts_ord_113[] = {(6 | ((1U * 0) << 7U) | ((3U * 0) << 7U) | (0 << 9U) | ((1U * 0) << 13U) | (0 << 14U) ), (131077 | ((1U * 0) << 7U) | ((3U * 0) << 7U) | (0 << 9U) | ((1U * 0) << 13U) | (0 << 14U) ), (65544 | ((1U * 0) << 7U) | ((3U * 1) << 7U) | (0 << 9U) | ((1U * 0) << 13U) | (0 << 14U) ), (196615 | ((1U * 0) << 7U) | ((3U * 1) << 7U) | (0 << 9U) | ((1U * 0) << 13U) | (0 << 14U) ),} ; ; static const struct pinctrl_state __pinctrl_states__device_dts_ord_113[] = { { .id = 0U, .pins = __pinctrl_state_pins_0__device_dts_ord_113, .pin_cnt = ((size_t) (((int) sizeof(char[1 - 2 * !(!__builtin_types_compatible_p(__typeof__(__pinctrl_state_pins_0__device_dts_ord_113), __typeof__(&(__pinctrl_state_pins_0__device_dts_ord_113)[0])))]) - 1) + (sizeof(__pinctrl_state_pins_0__device_dts_ord_113) / sizeof((__pinctrl_state_pins_0__device_dts_ord_113)[0])))) } ,  }; static const struct pinctrl_dev_config __pinctrl_dev_config__device_dts_ord_113 = { .reg = 1073750016, .states = __pinctrl_states__device_dts_ord_113, .state_cnt = ((size_t) (((int) sizeof(char[1 - 2 * !(!__builtin_types_compatible_p(__typeof__(__pinctrl_states__device_dts_ord_113), __typeof__(&(__pinctrl_states__device_dts_ord_113)[0])))]) - 1) + (sizeof(__pinctrl_states__device_dts_ord_113) / sizeof((__pinctrl_states__device_dts_ord_113)[0])))), }
    // Note that ordinance varies with different build
    PINCTRL_DT_DEFINE(M_CONSOLE_UART_NODE_ID);
    void method_3(void)
    {
    	const struct pinctrl_dev_config* p_pinctrl_dev_cfg = 
    							PINCTRL_DT_DEV_CONFIG_GET(M_CONSOLE_UART_NODE_ID);
    	
    	uint8_t t_state_cnt = p_pinctrl_dev_cfg->state_cnt;
    	printf("pinctrl state_cnt = %d\n", t_state_cnt);
    	
    	for (uint8_t i = 0; i < t_state_cnt; i++)
    	{
    		const struct pinctrl_state* tp_pinctrl_state = &p_pinctrl_dev_cfg->states[i];
    
    		printf("  state #%02d - id = %d\n", i, tp_pinctrl_state->id);
    
    		uint8_t t_pin_cnt = tp_pinctrl_state->pin_cnt;
    		printf("  t_pin_cnt = %d\n", t_pin_cnt);
    
    		for (uint8_t j = 0; j < t_pin_cnt; j++)
    		{
    			const pinctrl_soc_pin_t t_pin = tp_pinctrl_state->pins[j];
    			print_psel(t_pin);
    		}
    	}
    }
    
    int main(void)
    {
    	printf("Hello World! %s\n", CONFIG_BOARD);
    	printf("Test console node full name: %s\n", DT_NODE_FULL_NAME(M_CONSOLE_UART_NODE_ID));
    	
    	printf("\n\n\nMethod 1 ===================================================================\n");
    	for (uint8_t i = 0; i < pinctrl_0_group1_psels_size; i++)
    	{
    		print_psel(pinctrl_0_group1_psels[i]);
    	}
    	
    	printf("\n\n\nMethod 2 ===================================================================\n");
    	RUN_FOREACH_PINCTRL_0_GROUP_FN(M_PINCTRL_0_NODE_ID);
    	
    	printf("\n\n\nMethod 3 ===================================================================\n");
    	method_3();
    
    	return 0;
    }
    

  • Excellent, thanks. Using the NRF_GET_PIN/NRF_GET_FUN macros was what I needed to  extract the pin definitions, and with the PINCTRL_DT_DEV_CONFIG_GET I can get the full set of state/groups and find the pins i want.

    My code now looks like this:

    uint32_t find_psel_in_pcd(const struct pinctrl_dev_config* p_pinctrl_dev_cfg, uint8_t state, uint32_t fun) {
        for (uint8_t i = 0; i < p_pinctrl_dev_cfg->state_cnt; i++)
        {
            const struct pinctrl_state* tp_pinctrl_state = &p_pinctrl_dev_cfg->states[i];
            if (tp_pinctrl_state->id==state) {
                for (uint8_t j = 0; j < tp_pinctrl_state->pin_cnt; j++) {
                    const pinctrl_soc_pin_t t_pin = tp_pinctrl_state->pins[j];
                    if (NRF_GET_FUN(t_pin)==fun) {
                        return(t_pin);
                    }
                }
            }
        }
        return 0;
    }
    PINCTRL_DT_DEFINE(DT_ALIAS(myconsole));
    bool dts_get_wuart_cfg(wuart_cfg_t* p_cfg) {
        const struct pinctrl_dev_config* p_pinctrl_dev_cfg =
                                PINCTRL_DT_DEV_CONFIG_GET(DT_ALIAS(myconsole));
        p_cfg->rxd_pin = find_psel_in_pcd(p_pinctrl_dev_cfg, PINCTRL_STATE_DEFAULT, NRF_FUN_UART_TX);
        p_cfg->txd_pin = find_psel_in_pcd(p_pinctrl_dev_cfg, PINCTRL_STATE_DEFAULT, NRF_FUN_UART_RX);
        p_cfg->rts_pin = find_psel_in_pcd(p_pinctrl_dev_cfg, PINCTRL_STATE_DEFAULT, NRF_FUN_UART_RTS);
        p_cfg->cts_pin = find_psel_in_pcd(p_pinctrl_dev_cfg, PINCTRL_STATE_DEFAULT, NRF_FUN_UART_CTS);
        p_cfg->baudrate = DT_PROP(DT_ALIAS(myconsole), current_speed);

    I'm still stuck with 1 point: I would like to pass the alias name ('myconsole') to the function so that I can call it for multiple uart configurations...  however, the use of the string "myconsole" in the macros makes this impossible (resolved at compile time). Am I right in thinking there is no way to find nodes in the DTS dynamically (ie with the node label, name or alias only known at run time)?

    thanks!

Reply
  • Excellent, thanks. Using the NRF_GET_PIN/NRF_GET_FUN macros was what I needed to  extract the pin definitions, and with the PINCTRL_DT_DEV_CONFIG_GET I can get the full set of state/groups and find the pins i want.

    My code now looks like this:

    uint32_t find_psel_in_pcd(const struct pinctrl_dev_config* p_pinctrl_dev_cfg, uint8_t state, uint32_t fun) {
        for (uint8_t i = 0; i < p_pinctrl_dev_cfg->state_cnt; i++)
        {
            const struct pinctrl_state* tp_pinctrl_state = &p_pinctrl_dev_cfg->states[i];
            if (tp_pinctrl_state->id==state) {
                for (uint8_t j = 0; j < tp_pinctrl_state->pin_cnt; j++) {
                    const pinctrl_soc_pin_t t_pin = tp_pinctrl_state->pins[j];
                    if (NRF_GET_FUN(t_pin)==fun) {
                        return(t_pin);
                    }
                }
            }
        }
        return 0;
    }
    PINCTRL_DT_DEFINE(DT_ALIAS(myconsole));
    bool dts_get_wuart_cfg(wuart_cfg_t* p_cfg) {
        const struct pinctrl_dev_config* p_pinctrl_dev_cfg =
                                PINCTRL_DT_DEV_CONFIG_GET(DT_ALIAS(myconsole));
        p_cfg->rxd_pin = find_psel_in_pcd(p_pinctrl_dev_cfg, PINCTRL_STATE_DEFAULT, NRF_FUN_UART_TX);
        p_cfg->txd_pin = find_psel_in_pcd(p_pinctrl_dev_cfg, PINCTRL_STATE_DEFAULT, NRF_FUN_UART_RX);
        p_cfg->rts_pin = find_psel_in_pcd(p_pinctrl_dev_cfg, PINCTRL_STATE_DEFAULT, NRF_FUN_UART_RTS);
        p_cfg->cts_pin = find_psel_in_pcd(p_pinctrl_dev_cfg, PINCTRL_STATE_DEFAULT, NRF_FUN_UART_CTS);
        p_cfg->baudrate = DT_PROP(DT_ALIAS(myconsole), current_speed);

    I'm still stuck with 1 point: I would like to pass the alias name ('myconsole') to the function so that I can call it for multiple uart configurations...  however, the use of the string "myconsole" in the macros makes this impossible (resolved at compile time). Am I right in thinking there is no way to find nodes in the DTS dynamically (ie with the node label, name or alias only known at run time)?

    thanks!

Children
  • BrianW said:
    I'm still stuck with 1 point: I would like to pass the alias name ('myconsole') to the function so that I can call it for multiple uart configurations...  however, the use of the string "myconsole" in the macros makes this impossible (resolved at compile time). Am I right in thinking there is no way to find nodes in the DTS dynamically (ie with the node label, name or alias only known at run time)?

    I am a little confused. How is the alias defined at runtime? In Zephyr, everything on the Devicetree is defined in compile time. Dynamic update of the Devicetree is not supported.

    It is not clear to me what "myconsole" is in your application. If it is the console that is printk() get routed to by default, then you could just use DT_CHOSEN(zephyr_console) like in my sample code.

  • In Zephyr, everything on the Devicetree is defined in compile time. Dynamic update of the Devicetree is not supported.

    I think this is what I was not really grasping, the concept that the DTS is that part you update per board and is then static for that particular hardware. I was thinking of a 'config file' that would be dynamic and  change at run time to suit the specific device environment (eg select different uart configs based on a device name..). 

  • BrianW said:
    I think this is what I was not really grasping, the concept that the DTS is that part you update per board and is then static for that particular hardware.

    I believe that you have gotten it correct now though Slight smile

    What I have heard in passing, but not verified, is the Devicetree solution does support dynamic changes. However, this is only in Linux, and not in Zephyr.

Related