This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Creation of custom DTS file for describing custom device connections

Hi,

I need to create a custom dts for a custom board where I need to define the pins used for interfacing to some external devices. The generic "gpio-leds"/"gpio-keys", to my best understanding, do not feed my needs as I need more values per entry.

I would like to be able to create something like:

// Just for illustration, everything is hypothetical.
/ {
    model = "Huge bird detector NRF52832";
    compatible = "savanna,struthio-camelus-detector";
    
    detectors {
		compatible = "sc-detector";
		
		// Definition of first channel
		det0: det_0 {
		    irqpin = <&gpio0 13 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
		    // ainpin and dettype are integer values, ainpin is an index
		    // into an ADC channel, dettype is just a flag.
			ainpin = <1>;
			dettype = <1>
			label = "S-C detector channel 1";
		};
	};
	
	// ...etc...
};

Where can I find detailed information covering this topic and, possibly, some examples?

I'm using nRF Connect SDK version 1.9.1 and Visual Studio Code.

Thanks a lot in advance.

BR

Parents
  • Hi,

    I didn't find much information about this, so I'll share here what I've found out for others with similar questions.

    For creating a custom DTS file format it is necessary to create the bindings file, YAML. The custom bindings file must be located in route dts\bindings\, relative to the custom board's root path. This is the case when you want to keep all the custom board specifics independent from the nRF Connect SDK for being able to use different sdk versions.

    The format for the YAML file is described in Zephyr's documentation, and here's an example of minimum definition we need for supporting the dts source from my original post:

    description: The parent node, a detectors list
    
    compatible: "savanna,struthio-camelus-detector"
    
    child-binding:
        description: Struthio Camelus Detector, child node
        properties:
            irq-pins:
                  type: array
                  required: true
            label:
              required: true
              type: string
              description: Human readable string describing the device

    The type: array in property irq-pins establishes that this property will be expanded to an array of values.

    For simplifying things, I introduced a label "n" in the detectors node, and added another child node, like here below. The label can have any convenient value, I just used n since I was tired of typing useless code.

    n: detectors {
    	model = "Huge birds detector NRF52832";
        compatible = "savanna,struthio-camelus-detector";
    
    	// Definition of first channel
    	det0: det_0 {
    	    irq-pins = <13 (GPIO_PULL_UP)>;// | GPIO_ACTIVE_LOW)>;
    		label = "S-C detector channel 1";
    	};
    	
    	// Definition of second channel
    	det1: det_1 {
    	    irq-pins = <17 (GPIO_PULL_UP)>;// | GPIO_ACTIVE_LOW)>;
    		label = "S-C detector channel 2";
    	};
    };

    The text "savanna," will be interpreted a manufacturer id by Zephyr, and the tools will complain for unknown manufacturer. The file named vendor-prefixes.txt, located under zephyr\dts\bindings, contains the list of known manufacturer id's. You may want to add an entry there for your manufacturer id and name.

    Using the values generated by Zephyr's dts files compiler is not exactly straight forward when starting from zero, and the documentation provides only a limited set of examples.

    One intermediate generated file that becomes very handy for understanding what's behind scenes if the header file named devicetree_unfixed.h. This file is created under path zephyr\include\generated, relative to build directory, after making a pristine build, or when rebuilding after modifying the dts file.

    As an example. Here is an excerpt containing some of the macros generated for the first child node, det_0. I'll not dive into it any deeper as it is more or less self-explicative.

    (...extc...)
    
    /* Generic property macros: */
    #define DT_N_S_detectors_S_det_0_P_irq_pins {13 /* 0xd */, 16 /* 0x10 */}
    #define DT_N_S_detectors_S_det_0_P_irq_pins_IDX_0 13
    #define DT_N_S_detectors_S_det_0_P_irq_pins_IDX_0_EXISTS 1
    #define DT_N_S_detectors_S_det_0_P_irq_pins_IDX_1 16
    #define DT_N_S_detectors_S_det_0_P_irq_pins_IDX_1_EXISTS 1
    #define DT_N_S_detectors_S_det_0_P_irq_pins_FOREACH_PROP_ELEM(fn) fn(DT_N_S_detectors_S_det_0, irq_pins, 0) \
    	fn(DT_N_S_detectors_S_det_0, irq_pins, 1)
    #define DT_N_S_detectors_S_det_0_P_irq_pins_FOREACH_PROP_ELEM_VARGS(fn, ...) fn(DT_N_S_detectors_S_det_0, irq_pins, 0, __VA_ARGS__) \
    	fn(DT_N_S_detectors_S_det_0, irq_pins, 1, __VA_ARGS__)
    #define DT_N_S_detectors_S_det_0_P_irq_pins_LEN 2
    #define DT_N_S_detectors_S_det_0_P_irq_pins_EXISTS 1
    #define DT_N_S_detectors_S_det_0_P_label "S-C detector channel 1"
    
    (...extc...)

    Now in the code, there are several macros defined in Zephy's header file devicetree.h for accessing the values. I'll not dive into them, the documentation is vague for my taste and most code in Zephyr's implementation only uses very simple cases, but it can hint in the right direction if read carefully and with near-photographic memory.

    Using the values in an individualised way is nearly straightforward, like initialising a variable with the first item in the list of values of property irq-pin of node det0, which would be:

    // this will expand to macro DT_N_S_detectors_S_det_0_P_irq_pins, which has value 13
    static int irq_pin = DT_PROP_BY_IDX(DT_NODELABEL(det0), irq_pins, 0);

    Using the child nodes in the form of a list requires some more keystrokes.

    This code creates an array of string pointers with the values of the label property of child nodes, which is directly taken from example code available in Zephyr's DT_FOREACH_CHILD macro documentation:

    #define LABEL_AND_COMMA(node_id) DT_LABEL(node_id),
    const char *child_labels[] = {
    	DT_FOREACH_CHILD(DT_NODELABEL(n), LABEL_AND_COMMA)
    };

    For creating an array of structured elements things go a bit further, and here is where I didn't receive any help or hint neither lists.zephyrproject.org nor from stackoverflow.com.

    Here is the relevant part of the sample code:

    struct detector_data {
    	int pin;
    	int flags;
    };
    
    #define NODE_INFO_AND_COMMA(node_id) \
    	{ \
    		.pin=DT_PROP_BY_IDX(node_id, irq_pins, 0),\
    		.flags=DT_PROP_BY_IDX(node_id, irq_pins, 1),\
    	},
    
    const struct detector_data _det_data[] = {
    	DT_FOREACH_CHILD(DT_NODELABEL(n), NODE_INFO_AND_COMMA)
    };
    

    There are even more subtle things when trying to take full advantage of device trees, like referencing one device/node from others.

    The tool that came very handy for understanding what was occurring behind the scenes, surprisingly, was not available in the context commands list in VS Code, it only appears in the lightbulb when you first click in a macro, then double-click it. And I think there is some bug in VS Code as it does not show-up always, sometimes I need to repeat the procedure one or two more times before the bulb pops-up. Clicking in the bulb will show one single command, "Insert macro", that will replace the macro name with its expansion.

    BTW, the debugger integrated in the nRF Connect SDK for VS Code, if you integrate the previous example code and make no use of _det_data in your code, will not say the constant array was optimised out and will display a group of wrong values when hovering over the constant definition with the mouse. That silly behaviour made me lost a lot of time.

    I hope this is useful for others.

    BR.

Reply
  • Hi,

    I didn't find much information about this, so I'll share here what I've found out for others with similar questions.

    For creating a custom DTS file format it is necessary to create the bindings file, YAML. The custom bindings file must be located in route dts\bindings\, relative to the custom board's root path. This is the case when you want to keep all the custom board specifics independent from the nRF Connect SDK for being able to use different sdk versions.

    The format for the YAML file is described in Zephyr's documentation, and here's an example of minimum definition we need for supporting the dts source from my original post:

    description: The parent node, a detectors list
    
    compatible: "savanna,struthio-camelus-detector"
    
    child-binding:
        description: Struthio Camelus Detector, child node
        properties:
            irq-pins:
                  type: array
                  required: true
            label:
              required: true
              type: string
              description: Human readable string describing the device

    The type: array in property irq-pins establishes that this property will be expanded to an array of values.

    For simplifying things, I introduced a label "n" in the detectors node, and added another child node, like here below. The label can have any convenient value, I just used n since I was tired of typing useless code.

    n: detectors {
    	model = "Huge birds detector NRF52832";
        compatible = "savanna,struthio-camelus-detector";
    
    	// Definition of first channel
    	det0: det_0 {
    	    irq-pins = <13 (GPIO_PULL_UP)>;// | GPIO_ACTIVE_LOW)>;
    		label = "S-C detector channel 1";
    	};
    	
    	// Definition of second channel
    	det1: det_1 {
    	    irq-pins = <17 (GPIO_PULL_UP)>;// | GPIO_ACTIVE_LOW)>;
    		label = "S-C detector channel 2";
    	};
    };

    The text "savanna," will be interpreted a manufacturer id by Zephyr, and the tools will complain for unknown manufacturer. The file named vendor-prefixes.txt, located under zephyr\dts\bindings, contains the list of known manufacturer id's. You may want to add an entry there for your manufacturer id and name.

    Using the values generated by Zephyr's dts files compiler is not exactly straight forward when starting from zero, and the documentation provides only a limited set of examples.

    One intermediate generated file that becomes very handy for understanding what's behind scenes if the header file named devicetree_unfixed.h. This file is created under path zephyr\include\generated, relative to build directory, after making a pristine build, or when rebuilding after modifying the dts file.

    As an example. Here is an excerpt containing some of the macros generated for the first child node, det_0. I'll not dive into it any deeper as it is more or less self-explicative.

    (...extc...)
    
    /* Generic property macros: */
    #define DT_N_S_detectors_S_det_0_P_irq_pins {13 /* 0xd */, 16 /* 0x10 */}
    #define DT_N_S_detectors_S_det_0_P_irq_pins_IDX_0 13
    #define DT_N_S_detectors_S_det_0_P_irq_pins_IDX_0_EXISTS 1
    #define DT_N_S_detectors_S_det_0_P_irq_pins_IDX_1 16
    #define DT_N_S_detectors_S_det_0_P_irq_pins_IDX_1_EXISTS 1
    #define DT_N_S_detectors_S_det_0_P_irq_pins_FOREACH_PROP_ELEM(fn) fn(DT_N_S_detectors_S_det_0, irq_pins, 0) \
    	fn(DT_N_S_detectors_S_det_0, irq_pins, 1)
    #define DT_N_S_detectors_S_det_0_P_irq_pins_FOREACH_PROP_ELEM_VARGS(fn, ...) fn(DT_N_S_detectors_S_det_0, irq_pins, 0, __VA_ARGS__) \
    	fn(DT_N_S_detectors_S_det_0, irq_pins, 1, __VA_ARGS__)
    #define DT_N_S_detectors_S_det_0_P_irq_pins_LEN 2
    #define DT_N_S_detectors_S_det_0_P_irq_pins_EXISTS 1
    #define DT_N_S_detectors_S_det_0_P_label "S-C detector channel 1"
    
    (...extc...)

    Now in the code, there are several macros defined in Zephy's header file devicetree.h for accessing the values. I'll not dive into them, the documentation is vague for my taste and most code in Zephyr's implementation only uses very simple cases, but it can hint in the right direction if read carefully and with near-photographic memory.

    Using the values in an individualised way is nearly straightforward, like initialising a variable with the first item in the list of values of property irq-pin of node det0, which would be:

    // this will expand to macro DT_N_S_detectors_S_det_0_P_irq_pins, which has value 13
    static int irq_pin = DT_PROP_BY_IDX(DT_NODELABEL(det0), irq_pins, 0);

    Using the child nodes in the form of a list requires some more keystrokes.

    This code creates an array of string pointers with the values of the label property of child nodes, which is directly taken from example code available in Zephyr's DT_FOREACH_CHILD macro documentation:

    #define LABEL_AND_COMMA(node_id) DT_LABEL(node_id),
    const char *child_labels[] = {
    	DT_FOREACH_CHILD(DT_NODELABEL(n), LABEL_AND_COMMA)
    };

    For creating an array of structured elements things go a bit further, and here is where I didn't receive any help or hint neither lists.zephyrproject.org nor from stackoverflow.com.

    Here is the relevant part of the sample code:

    struct detector_data {
    	int pin;
    	int flags;
    };
    
    #define NODE_INFO_AND_COMMA(node_id) \
    	{ \
    		.pin=DT_PROP_BY_IDX(node_id, irq_pins, 0),\
    		.flags=DT_PROP_BY_IDX(node_id, irq_pins, 1),\
    	},
    
    const struct detector_data _det_data[] = {
    	DT_FOREACH_CHILD(DT_NODELABEL(n), NODE_INFO_AND_COMMA)
    };
    

    There are even more subtle things when trying to take full advantage of device trees, like referencing one device/node from others.

    The tool that came very handy for understanding what was occurring behind the scenes, surprisingly, was not available in the context commands list in VS Code, it only appears in the lightbulb when you first click in a macro, then double-click it. And I think there is some bug in VS Code as it does not show-up always, sometimes I need to repeat the procedure one or two more times before the bulb pops-up. Clicking in the bulb will show one single command, "Insert macro", that will replace the macro name with its expansion.

    BTW, the debugger integrated in the nRF Connect SDK for VS Code, if you integrate the previous example code and make no use of _det_data in your code, will not say the constant array was optimised out and will display a group of wrong values when hovering over the constant definition with the mouse. That silly behaviour made me lost a lot of time.

    I hope this is useful for others.

    BR.

Children
No Data
Related