Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

I2C example with latest drivers

Hi,

I am using nRF52840 to communicate with the EEPROM. 24AA32AT-I/MC over I2C interface. The TWI scanner example detects the EEPROM address correctly for me .Now, I want to read/write the EEPROM. 

As you know nrf and other drivers are deprecated in the SDK 16 and its recommended to use nrfx/latest drivers. I just wanted to know that out of the 4 TWI/I2C examples in SDK 16, which is best suited for my application for further development and does not use the legacy drivers. 

Thanks

  • Hello,

    As you know nrf and other drivers are deprecated in the SDK 16 and its recommended to use nrfx/latest drivers. I just wanted to know that out of the 4 TWI/I2C examples in SDK 16, which is best suited for my application for further development and does not use the legacy drivers. 

    This is correct, and to avoid having to port the application to the nrfx drivers in the future, I would highly recommend using the nrfx drivers from the start.

    I would suggest the TWIS Slave and TWI Master mode example from the SDK, since it demonstrates reading and writing to a (simulated) EEPROM module.
    If you look into the function being used for the master side, they do utilize the nrfx_twim driver - depending on the configuration.

    The learning curve for the TWI and TWIM/TWIS drivers is sometimes quite steep - please do not hesitate to ask if you encounter any issues or questions!

    Best regards,
    Karl

  • Karl,

    I am using sdk 16, SES ,nRf52840 the simulated EEPROM example with an actual EEPROM (24AA32AT-I/MC) connected but I am not using any of the CLI/UART features of the example. Instead, I am trying to write direct TWI read and write functions, see attached code. In the write function, I am trying to write (0xff) to the EEPROM located at 0x52 address. During debugging and the the execution of write function, I am getting a DRV_TWI_ERR_ANACK error 0x0000820. I don't understand why I am getting this NACK . As you can see in the attached scope picture, the data pin is low on 9th clock pulse, hence it should be a perceived as ACK by the  nRF52. One interesting fact is that by using the same hardware settings, I am able to poll EEPROM address (0x52) while using the twi scanner example.

    If I remove all the breakpoints from the code and let it run its full course then I get the following condition, which I can only get out only by asserting the reset pin on nRF52.

                                      NRF_BREAKPOINT_COND;
                                   // On assert, the system can only recover with a reset.

    /**
     * Copyright (c) 2015 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    /**
     * @file
     * @brief File with example code presenting usage of drivers for TWIS slave and TWI in master mode
     *
     * @sa twi_master_with_twis_slave_example
     */
    
    /**
     * @defgroup twi_master_with_twis_slave_example Example code presenting usage of drivers for TWIS slave and TWI in master mode
     *
     * This code presents the usage of two drivers:
     * - @ref nrf_twi_drv (in synchronous mode)
     * - @ref nrf_twis_drv (in asynchronous mode)
     *
     * On the slave, EEPROM memory is simulated.
     * The size of the simulated EEPROM is configurable in the config.h file.
     * Default memory value of the device is 320 bytes. It is simulated using internal RAM.
     * This RAM area is accessed only by simulated EEPROM so the rest of the application can access it
     * only using TWI commands via hardware configured pins.
     *
     * The selected memory chip uses a 7-bit constant address. Word to access is selected during
     * a write operation: first byte sent is used as the current address pointer.
     *
     * A maximum of an 8-byte page can be written in a single access.
     * The whole memory can be read in a single access.
     *
     * When the slave (simulated EEPROM) is initialized, it copies the given part of flash
     * (see EEPROM_SIM_FLASH_ADDRESS in config.h) into RAM (enabling the use of the slave for
     * bootloader use cases).
     *
     * Many variables like length of sequential writes/reads, TWI instance to use, endianness of
     * of slave bype addressing can be configured in config.h file
     *
     * Differences between real chip and simulated one:
     * 1. Simulated chip has practically 0 ms write time.
     *    This example does not poll the memory for readiness.
     * 2. During sequential read, when memory end is reached, there is no support for roll-over.
     *    It is recommended for master to assure that it does not try to read more than the page limits.
     *    If the master is not tracking this and trying to read after the page ends, then the slave will start to NACK
     *    for trying to over-read the memory. The master should end the transaction when slave starts NACKing, which could
     *    mean that the master has read the end of the page.
     * 3. It is possible to write a maximum of EEPROM_SIM_SEQ_WRITE_MAX_BYTES bytes in a single
     *    sequential write. However, in simulated EEPROM the whole address pointer is incremented.
     *    In a real device, writing would roll-over in memory page.
     *
     * On the master side, we communicate with that memory and allow write and read.
     * Simple commands via UART can be used to check the memory.
     *
     * Pins to short:
     * - @ref TWI_SCL_M - @ref EEPROM_SIM_SCL_S
     * - @ref TWI_SDA_M - @ref EEPROM_SIM_SDA_S
     *
     * Supported commands will be listed after Tab button press.
     * @{
     */
    
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <ctype.h>
    #include <string.h>
    #include "config.h"
    //#include "eeprom_simulator.h"
    #include "nrf_drv_twi.h"
    #include "nrf_gpio.h"
    #include "app_error.h"
    #include "nrf.h"
    #include "bsp.h"
    #include "app_util_platform.h"
    #include "app_timer.h"
    #include "nrf_drv_clock.h"
    #include "nrf_delay.h"
    #define EEPROM_ADD 0x52
    
    //#include "nrf_log.h"
    //#include "nrf_log_ctrl.h"
    //#include "nrf_log_default_backends.h"
    
    //#include "nrf_cli.h"
    //#include "nrf_cli_uart.h"
    
    
    //NRF_CLI_UART_DEF(m_cli_uart_transport, 0, 64, 16);
    //NRF_CLI_DEF(m_cli_uart, "uart_cli:~$ ", &m_cli_uart_transport.transport, '\r', 4);
    
    /**
     * @brief TWI master instance.
     *
     * Instance of TWI master driver that will be used for communication with 
     * EEPROM memory.
     */
    static const nrf_drv_twi_t m_twi_master = NRF_DRV_TWI_INSTANCE(MASTER_TWI_INST);
    
    /**
     * @brief Initialize the master TWI.
     *
     * Function used to initialize the master TWI interface that would communicate with simulated EEPROM.
     *
     * @return NRF_SUCCESS or the reason of failure.
     */
    static ret_code_t twi_master_init(void)
    {
        ret_code_t ret;
        const nrf_drv_twi_config_t config =
        {
           .scl                = TWI_SCL_M,
           .sda                = TWI_SDA_M,
           .frequency          = NRF_DRV_TWI_FREQ_100K,
           .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
           .clear_bus_init     = false
        };
    
        ret = nrf_drv_twi_init(&m_twi_master, &config, NULL, NULL);
    
        if (NRF_SUCCESS == ret)
        {
            nrf_drv_twi_enable(&m_twi_master);
        }
    
        return ret;
    }
    
    /**
     *  The beginning of the journey
     */
    int main(void)
    {
        uint8_t clear_val = 0xFF;
        uint8_t addr = 0x00;
        ret_code_t err_code;
        uint8_t tx_buffer[EEPROM_ADDRESS_LEN_BYTES + EEPROM_SEQ_WRITE_MAX_BYTES]; 
        //Transmit buffer declaration
        uint8_t rx_data;
        //Transmit buffer declaration
        APP_ERROR_CHECK(err_code);
        /* Initializing TWI master interface for EEPROM */
        err_code = twi_master_init();
        APP_ERROR_CHECK(err_code);
         /* Initializing clock */
        err_code = nrf_drv_clock_init();
        APP_ERROR_CHECK(err_code);
         /* Initializing low freqiency clock */
        nrf_drv_clock_lfclk_request(NULL);
         /* Initializing Timer */
        err_code = app_timer_init();
        APP_ERROR_CHECK(err_code);
        
        memcpy(tx_buffer, &clear_val, EEPROM_SEQ_WRITE_MAX_BYTES); 
        //Load the Tx buffer with 0xFF
    
        err_code = nrf_drv_twi_tx(&m_twi_master, EEPROM_ADDR, tx_buffer, sizeof(tx_buffer), false);
        //Write buffer data to EEPROM
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_twi_rx(&m_twi_master, EEPROM_ADDR, &rx_data, 2);
        //Read from EEPROM
        APP_ERROR_CHECK(err_code);
    
       /* Main loop */
        while (1)
        {
        nrf_delay_ms(100);
        __WFI();
        }
     }
    

  • Hello,

    Jagbir said:
    During debugging and the the execution of write function, I am getting a DRV_TWI_ERR_ANACK error 0x0000820.

    The DRV_TWI_ERR_ANACK error code has value 8201, for future reference.
    From the looks of it, you are receiving an ACK with the SDA being low at the time of the ACK bit, so lets look into the error that is being generated.

    Jagbir said:
    If I remove all the breakpoints from the code and let it run its full course then I get the following condition, which I can only get out only by asserting the reset pin on nRF52.

    When you do have the breakpoint, this is where you get the DRV_TWI_ERR_ANACK error?
    And if not, could you step through the code and see where the error is generated?
    Is your call to nrf_drv_twi_rx successful?

    As I said, I would again recommend using the nrfx driver, since the nrf_drv* is legacy.
    The functionality of the nrfx TWIM driver is detailed in the API Reference. Having said that, you can use whichever you would like - just know that the nrf_drv* calls might be left out of future SDK releases.

    Taking a look at the code you provided: why do you error check after declaring the err_code variable?
    You do not need to add NOP cycles to you main loop while you are using the WFI/WFE, but I would suggest using the power managing driver, for convenience. 

    Looking forward to solving this issue together,

    Best regards,
    Karl

  • Karl,

    I had to insert a delay between read and write after which the code is working fine now.

    Moving forward, since TWI driver is deprecated so I am trying to switch to the TWIM driver. After looking through nrf_drv_twi.c/.h , I am concerned that nrf_drv_twi.c/.h code seems to handle both the TWI and TWIM requests through various if conditions. For example  twim_evt_handler(),nrfx_twim_init() functions uses twim, see attached code of the nrf_drv_twi.c

    Is it possible to completely bypass nrf_drv_twi.c/.h and use nrfx_twim.c/.h directly. Do I need to implement twim_evt_handler() in my code, if not using nrf_drv_twi.c/.h.

    /**
     * Copyright (c) 2017 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    
    #include "nrf_drv_twi.h"
    #include <nrf_delay.h>
    #include <hal/nrf_gpio.h>
    
    #ifdef TWIM_PRESENT
    #define INSTANCE_COUNT   TWIM_COUNT
    #else
    #define INSTANCE_COUNT   TWI_COUNT
    #endif
    
    #define SCL_PIN_INIT_CONF                                     \
        ( (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) \
        | (GPIO_PIN_CNF_DRIVE_S0D1     << GPIO_PIN_CNF_DRIVE_Pos) \
        | (GPIO_PIN_CNF_PULL_Pullup    << GPIO_PIN_CNF_PULL_Pos)  \
        | (GPIO_PIN_CNF_INPUT_Connect  << GPIO_PIN_CNF_INPUT_Pos) \
        | (GPIO_PIN_CNF_DIR_Input      << GPIO_PIN_CNF_DIR_Pos))
    
    #define SDA_PIN_INIT_CONF        SCL_PIN_INIT_CONF
    
    #define SDA_PIN_UNINIT_CONF                                     \
        ( (GPIO_PIN_CNF_SENSE_Disabled   << GPIO_PIN_CNF_SENSE_Pos) \
        | (GPIO_PIN_CNF_DRIVE_H0H1       << GPIO_PIN_CNF_DRIVE_Pos) \
        | (GPIO_PIN_CNF_PULL_Disabled    << GPIO_PIN_CNF_PULL_Pos)  \
        | (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos) \
        | (GPIO_PIN_CNF_DIR_Input        << GPIO_PIN_CNF_DIR_Pos))
    
    #define SCL_PIN_UNINIT_CONF      SDA_PIN_UNINIT_CONF
    
    #define SCL_PIN_INIT_CONF_CLR                                 \
        ( (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) \
        | (GPIO_PIN_CNF_DRIVE_S0D1     << GPIO_PIN_CNF_DRIVE_Pos) \
        | (GPIO_PIN_CNF_PULL_Pullup    << GPIO_PIN_CNF_PULL_Pos)  \
        | (GPIO_PIN_CNF_INPUT_Connect  << GPIO_PIN_CNF_INPUT_Pos) \
        | (GPIO_PIN_CNF_DIR_Output     << GPIO_PIN_CNF_DIR_Pos))
    
    #define SDA_PIN_INIT_CONF_CLR    SCL_PIN_INIT_CONF_CLR
    
    static nrf_drv_twi_evt_handler_t m_handlers[INSTANCE_COUNT];
    static void *                    m_contexts[INSTANCE_COUNT];
    
    static void twi_clear_bus(nrf_drv_twi_config_t const * p_config)
    {
        NRF_GPIO->PIN_CNF[p_config->scl] = SCL_PIN_INIT_CONF;
        NRF_GPIO->PIN_CNF[p_config->sda] = SDA_PIN_INIT_CONF;
    
        nrf_gpio_pin_set(p_config->scl);
        nrf_gpio_pin_set(p_config->sda);
    
        NRF_GPIO->PIN_CNF[p_config->scl] = SCL_PIN_INIT_CONF_CLR;
        NRF_GPIO->PIN_CNF[p_config->sda] = SDA_PIN_INIT_CONF_CLR;
    
        nrf_delay_us(4);
    
        for (int i = 0; i < 9; i++)
        {
            if (nrf_gpio_pin_read(p_config->sda))
            {
                if (i == 0)
                {
                    return;
                }
                else
                {
                    break;
                }
            }
            nrf_gpio_pin_clear(p_config->scl);
            nrf_delay_us(4);
            nrf_gpio_pin_set(p_config->scl);
            nrf_delay_us(4);
        }
        nrf_gpio_pin_clear(p_config->sda);
        nrf_delay_us(4);
        nrf_gpio_pin_set(p_config->sda);
    }
    
    #ifdef TWIM_PRESENT
    static void twim_evt_handler(nrfx_twim_evt_t const * p_event,
                                 void *                  p_context)
    {
        uint32_t inst_idx = (uint32_t)p_context;
        nrf_drv_twi_evt_t const event =
        {
            .type = (nrf_drv_twi_evt_type_t)p_event->type,
            .xfer_desc =
            {
                .type = (nrf_drv_twi_xfer_type_t)p_event->xfer_desc.type,
                .address          = p_event->xfer_desc.address,
                .primary_length   = p_event->xfer_desc.primary_length,
                .secondary_length = p_event->xfer_desc.secondary_length,
                .p_primary_buf    = p_event->xfer_desc.p_primary_buf,
                .p_secondary_buf  = p_event->xfer_desc.p_secondary_buf,
            }
        };
        m_handlers[inst_idx](&event, m_contexts[inst_idx]);
    }
    #endif // TWIM_PRESENT
    
    #ifdef TWI_PRESENT
    static void twi_evt_handler(nrfx_twi_evt_t const * p_event,
                                void *                 p_context)
    {
        uint32_t inst_idx = (uint32_t)p_context;
        nrf_drv_twi_evt_t const event =
        {
            .type = (nrf_drv_twi_evt_type_t)p_event->type,
            .xfer_desc =
            {
                .type = (nrf_drv_twi_xfer_type_t)p_event->xfer_desc.type,
                .address          = p_event->xfer_desc.address,
                .primary_length   = p_event->xfer_desc.primary_length,
                .secondary_length = p_event->xfer_desc.secondary_length,
                .p_primary_buf    = p_event->xfer_desc.p_primary_buf,
                .p_secondary_buf  = p_event->xfer_desc.p_secondary_buf,
            }
        };
        m_handlers[inst_idx](&event, m_contexts[inst_idx]);
    }
    #endif // TWI_PRESENT
    
    ret_code_t nrf_drv_twi_init(nrf_drv_twi_t const *        p_instance,
                                nrf_drv_twi_config_t const * p_config,
                                nrf_drv_twi_evt_handler_t    event_handler,
                                void *                       p_context)
    {
        uint32_t inst_idx = p_instance->inst_idx;
        m_handlers[inst_idx] = event_handler;
        m_contexts[inst_idx] = p_context;
    
        if(p_config->clear_bus_init)
        {
            /* Send clocks (max 9) until slave device back from stuck mode */
            twi_clear_bus(p_config);
        }
    
        ret_code_t result = 0;
        if (NRF_DRV_TWI_USE_TWIM)
        {
            result = nrfx_twim_init(&p_instance->u.twim,
                                    (nrfx_twim_config_t const *)p_config,
                                    event_handler ? twim_evt_handler : NULL,
                                    (void *)inst_idx);
        }
        else if (NRF_DRV_TWI_USE_TWI)
        {
            result = nrfx_twi_init(&p_instance->u.twi,
                                   (nrfx_twi_config_t const *)p_config,
                                   event_handler ? twi_evt_handler : NULL,
                                   (void *)inst_idx);
        }
        return result;
    }
    


    Thanks!

     

  • Hello,

    Jagbir said:
    I had to insert a delay between read and write after which the code is working fine now.

    As a general practice I would not recommend adding NOP instructions (nrf_delay_* functions) to fulfill timing requirements - since this is wasted power and computing time. Instead, you could for example have a timer that must trigger before continuing the transfer.
    One of the reasons why NOP cycles is subpar, is that the number of cycles is constant, regardless of it being interrupted by the SoftDevice.
    Furthermore, if you use a timer, you may connect it to the TWIM peripheral by PPI, and have a transaction or a number of transactions happen without requiring CPU intervention.

    Jagbir said:
    Is it possible to completely bypass nrf_drv_twi.c/.h and use nrfx_twim.c/.h directly. Do I need to implement twim_evt_handler() in my code, if not using nrf_drv_twi.c/.h.

    Yes, you may use the TWIM module directly. To do this, you would want to make use of the TWIM API Reference. As you can see in the nrfx_twim_init function you have to supply an event handler, unless you want the TWIM to be blocking. You can see the possible events to handle in the API Reference as well.
    From the looks of your code, I am not sure that your current twim_evt_handler is the best way to go about handling the different events, since it does not differentiate between the different possibilities.

    Best regards,
    Karl

Related