TWI/I2C implementation with nRFx TWIS Driver

TWI/I2C implementation with nRFx TWIS Driver

Objectives

  • Learn to add and configure an I2C/TWI Master Zephyr driver to an NRF Connect SDK project.
  • Learn to add and configure an I2C/TWI Slave nRFX driver to an NRF Connect SDK project.
  • Demonstrate the I2C/TWI Master and I2C/TWI Slave communication between two nRF52840DKs.

The initial objective of this blog is to understand how to use and configure both the Zephyr I2C/TWI Master and Slave drivers. However, no Zephyr I2C/TWIS Slave Driver supports Nordic I2C/TWI Slave hardware to date. As a result, the TWIM software will use the Zephyr i2c driver, and the TWIS software will use the nRFX driver (NRFX_DRV) paradigm. So, this tutorial also provides insight into integrating a Nordic nRFX driver into NRF Connect SDK.

It is assumed the reader has already installed nRF Connect SDK, the required Toolchain, and is familiar with the process of adding a peripheral to an application. If the reader is unfamiliar with this process, it is recommended to first review  Adding a Peripheral to an NCS Zephyr project. The sample code for the Master and Slave will use TWIM1 and TWIS0, respectively.  Using the technique provided in Adding a Peripheral to an NCS Zephyr project to merge the Master and Slave examples, one could implement the functionality of this tutorial on a single DK.

The tutorial portion of this document is based on NRF Connect SDK version 1.6.1 and requires the use of two nRF52840-DKs.

The tutorial will demonstrate how to add support for a TWI Master (TWIM) and TWI Slave (TWIS). The initial application for both TWIM and TWIS applications is  <install directory> \zephyr\samples\basic\blinky.

The provided code is configured to write a fixed message “MSG FROM TWIM” to the TWIS. But by setting TWI_LOOPBACK to true the TWIM will first write the message “Begin Loopback.” Each subsequent message written by the TWIM will echo the previously received message fragment read from the TWIS. The complete TWIS message is “This is a circular TWIS transmit buffer.”

If any information in this document contradicts the referenced documentation, the reference documentation is assumed to be more accurate.

I2C or TWI?

Though technically different constructs, TWI and I2C are often considered to be operationally synonymous. Nordic has adopted the TWI nomenclature, while Zephyr has adopted I2C. For this document, I2C will only be used to be consistent with referenced Zephyr software and documentation.

TWIM setup using Zephyr driver

  1. Build the blinky sample for the nRF52840DK to ensure all the build tools are installed and working correctly and you have a good starting point.
  2. Copy all the contents of blinky to a new directory named blinky-twim
  3. Create a text file nrf52840dk_nrf52840.overlay and save it to the directory blinky-twim/boards. This directory may need to be created.
  4. Add the following node properties to blinky-twim/boards/nrf52840dk_nrf52840.overlay.


    Impact:
    1. This overlay modifies the node with identifier &i2c1.
    2. The compatible is a node property and is critically important to understand. If you are unfamiliar with this property, please review Devicetree Important properties. The compatible nordic,nrf-twim, states the i2c node describes the Nordic nRF TWIM controller. We are using the Nordic TWIM, because Nordic TWI does not support EasyDMA or I2C clock stretching. It is also worth noting that the nRF52840 product specification indicates the TWI peripheral has been deprecated.
    3. Enables/allocates the i2c node.
    4. Sets the i2c clock frequency to 100KHz.
    5. According to the Devicetree Specification, node names should reflect the function of the device. However, to avoid the implication that this child or sub-node of &i2c1 has a standard Zephyr driver, we will use twis_device1 instead of the functional names i2c or bus. (See section 2.2.2 of the Devicetree Specification for a list of recommended node names.) The Devicetree Specification is very in-depth and covers several use cases. The Devicetree Guide is a reduced scope Devicetree Specification tailored for Zephyr users.

      For this label, it's important to understand TWIS is the attached device and not the TWIM peripheral on the IC.
    6. If a driver existed for the targeted slave device, its compatible would be set here. For example, compatible = "adi,adt7420";
    7. "label" is an arbitrary string. We will use this to describe the slave device that the master is addressing.
    8. The reg property value is dependent on the compatible property and is also described Devicetree Guide Important properties. For this tutorial, the address is arbitrary. However, when a standard i2c device is used, the address of that device must be used. For example, the default address for the ADI adt7420 is 0x00.

      The TWIM project retains the default sda and scl pins configuration set in nrf52840dk_nrf52840.dts

      Note: Starting at line 166 of nRF52840dk_nrf52840.dts, the default description for i2c1 node includes the comment, "Cannot be used together with spi1."  Though SPIM1 and SPIS1 have also deprecated SPI1, they each share the same Base Address as TWIM1 and TWIS1. Only one of these four peripherals can be enabled at a time.

  5. Searching the Kconfig reference to see the available config_i2c options, we find a couple of options that should be of interest. CONFIG_I2C and CONFIG_I2C_1_NRF_TWIM. CONFIG_I2C is simply enough. But we find the CONFIG_I2C_NRF_TWIM is not directly assignable and is dependent on I2C_NRFX and I2C. Therefore, we all need to add is the following to <install directory \zephyr\samples\basic\blinky-twim\prj.conf


    Note: Earlier versions of NRF Connect SDK use the MACRO I2C_1 instead of I2C_NRFX.

    Instead of searching the kconfig reference, one will likely find using an Interactive Kconfig interface a better option.

  6. Replace <install directory>\zephyr\samples\basic\blinky-twim\src\main.c  with the following code.


    Highlights of changes made to main.c:
    • Added the include files drivers/i2c.h and sys/printk.h
    • Established twim_dev1 bindings
    • Print TWIMs configuration
    • Use of the i2c_write function.
    • Use of the i2c_read functions.
  7. Verify that blinky-twim successfully builds and program an nRF52840DK. Confirm that LED1 is blinking.

TWIS setup using nRF driver

  1. Copy all the contents of the previously tested blinky project to a new directory called <..>\zephyr\samples\basic\blinky-twis
  2. Create a text file nrf52840dk_nrf52840.overlay and save it to the directory blinky-twis/boards. This directory may need to be created.
  3. Copy the following node properties to blinky-twis/boards/ nbrf52840dk_nrf52840.overlay.


    Impact:
    1. This overlay modifies the node with identifier &i2c0.
    2. We would typically use the compatible nordic, nrf-twis However, because the i2c slave API/driver in Zephyr is not compatible with Nordic’s TWIS hardware, using nordic, nrf-twis will result in build errors. Therefore, to access and control the TWI hardware, the nRFx TWIS driver is used.

      Note: The compatible is set to nordic,nrf-twi in nrf52840dk_nrf82840.dts.

    3. A Zephyr and nRFx TWIS driver would potentially occupy the same memory space. To prevent this, Zephyr needs to be told not to use this space. This is done by setting "status" to disabled.

      Though disabled, we can still use the Devicetree to configure the hardware for this project. Therefore, we will continue to set up &i2c0. See the provided code for implementation.

    4. sda-pin is a node-specific property defined in nordic,nrf-twi. We will use this property to replace the hardcoded definitions in the nRFx TWIS driver.
    5. scl-pin is a node-specific property defined in nordic,nrf-twi. We will use this property to replace the hardcoded definitions in the nRFx TWIS driver.
    6. "interrupts" is a Base Property defined in nordic,nrf-twi this line sets the Instantiation ID to 4 and the Priority to 2.
    7. The nRF52840’s TWI slave can listen for two addresses at the same time. Though only twi_device1 is used in this tutorial. To use both addresses, a separate child node needs to be created for each address.
    8. "label" is an arbitrary string. This tutorial uses it to describe the slave device and its address. In case, it's convenient to use the same label we used to describe the slave device in the TWIM peripheral node in TWIM setup using Zephry driver above.
    9. TWIS address1
    10. The nRF52840’s TWI slave can listen for two addresses at the same time. Though only twi_device1 is used in this tutorial, the twi_device2 child node is provided as a reference.
    11. "label" is an arbitrary string used to describe the slave device's second address.
    12. TWIS address2
    13. This overlay modifies the node with identifier &spi0.
    14. Because SPI1 and TWI1 share the same space, SPI1 must also be disabled.
  4. Searching the Kconfig reference to review the available nrfx_twis options, we find the macros CONFIG_NRFX_TWIS and CONFIG_NRFX_TWIS1 of the most interest. The NRFX_TWIS description states NRFX_TWIS is selected by NRFX_TWIS*. Because we want to enable NRX_TWIS1, we need to add the following to <install directory> \zephyr\samples\basic\blinky-twis\prj.conf

     
    Instead of searching the kconfig reference, one would likely find using an Interactive Kconfig interface a better option.

  5. Replace <install directory>\zephyr\samples\basic\blinky-twis\src\main.c with the following code.


    Highlights of changes made to main.c:
    • The nRFx driver example used to create this example uses some defined macros to set up the hardware. When running a Zephyr project, the better practice is to use the Devicetree for purpose.

      The macro DEVICETREE_CONF has been added to switch between nRFx and Devicetree configuration methodologies.
    • The Zephyr Operating System manages the vector table. Therefore, we need to associate the NRFX IRQ handler with the i2c1’s hardware interrupt vector. We do this by using the IRQCONNECT macro.



      nrfx_isr is the entry point of the nRFx twis driver and a wrapper for nrfx_twis_0_irq.

      Within the nrfx driver the nrfx_twis_state_machine function callbacks to the applications twis_event_handler.
  6. Verify that blinky-twis successfully builds and program a second nRF52840DK. Confirm LED1 is blinking.

Testing

  1. Power off both nRF52840-DKs.
  2. Connect the two nRF5284-DKs according to the following Table.

    TWIM TWIS
    GND GND GND
    P0.31 SCL P0.27
    P0.30 SDA P0.26
  3. Power the two DKs.
  4. Using your favorite terminal program, connect to the virtual UART (JLINK CDC UART) on each DK. (115200, N, 8, 1)
  5. Reset the DK running blinky-twis
  6. Reset the DK running blinky-twim

Output

TIWM

TWIS

Test Options

Throughout both sets of the code, several printk statements have been commented out. You may want to uncomment any of these to assist in fully understanding the flow of the application.

There are a few TWI Test Configuration Options within main-twi.c, which should now be called main.c in the blinky-twim project.

ECHO WRITES:  When set to false, each device will only print what it has received. By selecting this macro ECHO WRITES to true, the TWIM will also output the message it has written to the slave device.

MSG_EXCHANGE_CNT: The number of message exchanges to be sent between the TWIM and TWIS.

INFINITE_MSG_EXCHANGE: When set to true, the message exchange is continuous.

TWI_LOOPBACK: When set to true, the first message sent by the master will be “Begin Loopback.” The subsequent messages sent by the TWIS will be returned to the TWIS.

TWI_BUFFER_SIZE must be set to the same value in both main.c files to change the length of the exchange messages.c files.

Additional Resources

The TWIS nRFX code was derived from RF5_SDK_17.0.2\examples\peripheral\twi_master_with_twis_slave (download nRF5 SDK 17.2).

A deep dive into the Zephyr 2.5 device model (I highly recommend viewing this video.)

Source Code

TWI_I2C implementation with nRFx TWIS Driver.zip