Initializing SPI and TWI drivers on Zephyr

Hi

Followup on this ticket: Enabling SPI and TWI drivers on Zephyr

First the fundamentals, I am using:

  • nRF52840-DK
  • nRF Connect for VS Code: v2024.7.13
  • nRF Connect Ver. 2.5.1

I am trying to create a demonstration project, where I need both SPI and TWI/I2C.

My project now compiles and runs, however I cot some issues.

The SPI is working fine. During initialization I call the nrfx_spim_init to setup the driver and assign the pins to the peripheral. It works. 

But when initilalizing the I2C with the nrfx_twim_init function it returns an error code NRF_WRONG_STATE. It appers that the driver is already initialized during the Zephyr init. (Called from bg_thread_main)

It could be OK that the driver is initialized by Zephyr, I believe that it is the preffered war to do it. But only if it is done properly. By setting a breakpoint in nrfx_spim_init I see that neither the pin configuration nor the speed is set according to the devicetree overlay file I have written.  But how do I do this correctly? 

Right now my overlay file looks like this, but I have tried a lot of things. With no change in behavior at all.

&spi1 {
    status = "okay";
    compatible = "nordic,nrf-spim";
    pinctrl-0 = <&spi1_default>;    
};


&i2c0 {
    status = "okay";
    compatible = "nordic,nrf-twim";
    pinctrl-0 = <&i2c0_tempDEF>;
    pinctrl-1 = <&i2c0_tempSLP>;
    pinctrl-names = "default", "sleep";
    clock-frequency = <100000>;
};

&pinctrl{
    i2c0_tempDEF: i2c0_tempDEF {
        group1 {
            psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
                <NRF_PSEL(TWIM_SCL, 0, 27)>;
        };
    };

    i2c0_tempSLP: i2c0_tempSLP {
        group1 {
            psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
                <NRF_PSEL(TWIM_SCL, 0, 27)>;
            low-power-enable;
        };
    };
};

Using the default configuration gives the same result. No matter what: speed is set to 100000 and SCL is set to 0 and SDA is set to 0. So it will never work.

How do I control the initialization from Zephyr?

Or can I disable the I2C initialization from Zephyr and do it my self, which I originally tried to do?

By the way: I tried to omit the i2c0 section in the overlay file to verify that it was read. And it was, as the project did not compile with out it. 

Any help would be appreciated.

Kasper

Parents
  • First off in Zephyr with device trees is to realize the device trees get "preprocessed" or "compiled" down to a header file in your build/zephyr/include/generated directory called "devicetree_generated.h".  That is the device tree mess all gets processed to this one header file, nothing more or less. 
    The source code for drivers in Zephyr then look at the "devicetree_generated.h" header file to see if they need to create a driver for you or not.  For example when you include the I2C in the device tree the nordic I2C driver source file then knows to build driver  and before main()  the driver is configured and ready to be used. 

    &i2c1 {
    	compatible = "nordic,nrf-twim";
    	status = "okay";
    	pinctrl-0 = <&i2c1_default>;
    	pinctrl-1 = <&i2c1_sleep>;
    	pinctrl-names = "default", "sleep";
    };


    Above the above device tree says to use the "nordic-nrf-twim" driver and use the pins and such defined for I2C1.  Then what I do is in my device tree I add the following: 
    &i2c1 {
    	tlv320aic3206: tlv320aic3206@18 {
    		compatible="i2c-device";
    		label="TLV320AIC3206";
    		reg=<0x18>;
    	};
    	sx1509b: sx1509b@3E {
    		compatible="i2c-device";
    		label="SX1509B";
    		reg=<0x3E>;
    	};
    	ltc2959: ltc2959@63 {
    		compatible="i2c-device";
    		label="LTC2959";
    		reg=<0x63>;
    	};
    };

    Here this says that I have three chips on the I2C and what the I2C address is for each chip.  Each chip will be a generic i2c-device driver, that is not any custom driver per chip, rather I will have to use I2C read/writes to talk to the chips in my code. 

    Zephyr still creates a driver for each chip, "i2c-device".  All device tree drivers are referenced by getting a pointer to the 'device', but since these are "i2c-device" we can use the "i2c_dt_spec" special purpose device type pointers. 
    static const struct i2c_dt_spec dev_i2c = I2C_DT_SPEC_GET(DT_NODELABEL(tlv320aic3206));
    static const struct i2c_dt_spec dev_i2c_sx1509b = I2C_DT_SPEC_GET(DT_NODELABEL(sx1509b));
    static const struct i2c_dt_spec dev_i2c_ltc2959 = I2C_DT_SPEC_GET(DT_NODELABEL(ltc2959));


    Again the these macros are part of the device tree confusion, and basically a way to go from your device tree label to get a pointer to the actual device driver.  Once you have these pointers to the device driver you can use them with I2C driver API.docs.zephyrproject.org/.../i2c.html
    i2c_write_read_dt()
    i2c_write_dt()
    i2c_read_dt()
    



    Getting started the whole device tree system is a big confusing mess, and I found little documentation that helped me.  Once I figured out that the device tree compiles to the one header file, it helped a lot.  That is the device tree header file is the glue for everything.

    Also note that the Zephyr nomenclature is that the "compatible" part of the device tree selects which driver that node in device trees uses.  However Nordic's UARTE driver does not use the compatible flag as designed.  Keep this in mind if you want to write custom 'out of tree' UARTE drivers as that it is very hard to do so without a lot of work.   The Zephyr guys indicate this is a bug in Nordic's driver... 

Reply
  • First off in Zephyr with device trees is to realize the device trees get "preprocessed" or "compiled" down to a header file in your build/zephyr/include/generated directory called "devicetree_generated.h".  That is the device tree mess all gets processed to this one header file, nothing more or less. 
    The source code for drivers in Zephyr then look at the "devicetree_generated.h" header file to see if they need to create a driver for you or not.  For example when you include the I2C in the device tree the nordic I2C driver source file then knows to build driver  and before main()  the driver is configured and ready to be used. 

    &i2c1 {
    	compatible = "nordic,nrf-twim";
    	status = "okay";
    	pinctrl-0 = <&i2c1_default>;
    	pinctrl-1 = <&i2c1_sleep>;
    	pinctrl-names = "default", "sleep";
    };


    Above the above device tree says to use the "nordic-nrf-twim" driver and use the pins and such defined for I2C1.  Then what I do is in my device tree I add the following: 
    &i2c1 {
    	tlv320aic3206: tlv320aic3206@18 {
    		compatible="i2c-device";
    		label="TLV320AIC3206";
    		reg=<0x18>;
    	};
    	sx1509b: sx1509b@3E {
    		compatible="i2c-device";
    		label="SX1509B";
    		reg=<0x3E>;
    	};
    	ltc2959: ltc2959@63 {
    		compatible="i2c-device";
    		label="LTC2959";
    		reg=<0x63>;
    	};
    };

    Here this says that I have three chips on the I2C and what the I2C address is for each chip.  Each chip will be a generic i2c-device driver, that is not any custom driver per chip, rather I will have to use I2C read/writes to talk to the chips in my code. 

    Zephyr still creates a driver for each chip, "i2c-device".  All device tree drivers are referenced by getting a pointer to the 'device', but since these are "i2c-device" we can use the "i2c_dt_spec" special purpose device type pointers. 
    static const struct i2c_dt_spec dev_i2c = I2C_DT_SPEC_GET(DT_NODELABEL(tlv320aic3206));
    static const struct i2c_dt_spec dev_i2c_sx1509b = I2C_DT_SPEC_GET(DT_NODELABEL(sx1509b));
    static const struct i2c_dt_spec dev_i2c_ltc2959 = I2C_DT_SPEC_GET(DT_NODELABEL(ltc2959));


    Again the these macros are part of the device tree confusion, and basically a way to go from your device tree label to get a pointer to the actual device driver.  Once you have these pointers to the device driver you can use them with I2C driver API.docs.zephyrproject.org/.../i2c.html
    i2c_write_read_dt()
    i2c_write_dt()
    i2c_read_dt()
    



    Getting started the whole device tree system is a big confusing mess, and I found little documentation that helped me.  Once I figured out that the device tree compiles to the one header file, it helped a lot.  That is the device tree header file is the glue for everything.

    Also note that the Zephyr nomenclature is that the "compatible" part of the device tree selects which driver that node in device trees uses.  However Nordic's UARTE driver does not use the compatible flag as designed.  Keep this in mind if you want to write custom 'out of tree' UARTE drivers as that it is very hard to do so without a lot of work.   The Zephyr guys indicate this is a bug in Nordic's driver... 

Children
No Data
Related