nRF52840 I2C could not start SCL signal after setting TASKS_STARTTX to 1

I was using Seeed Xiao BLE Sense as the I2C master and Arduino's Wire library. The basic I2C communication was working but it could not be stopped for switching the pins for other purpose. So I turned to direct TWI register access hoping I could resolve that problem. But the basic I2C communication could not start with this method. The SCL clock wasn't toggling after setting TASKS_STARTTX to 1. Here is my code:

#define SCL_PIN 5
#define SDA_PIN 4

void I2C_init(void)
{ 
    NRF_TWI1->ENABLE = 0x00000000;

    NRF_GPIO->PIN_CNF[SCL_PIN] = 
        (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);    

    NRF_GPIO->PIN_CNF[SDA_PIN] = 
        (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); 	

    NRF_TWI1->PSELSCL=SCL_PIN;
    NRF_TWI1->PSELSDA=SDA_PIN;

    NRF_TWI1->FREQUENCY = 0x01980000;   //100 KHz /
    //NRF_TWI1->FREQUENCY = 0x04000000;     //250 Khz
    //NRF_TWI1->FREQUENCY = 0x06680000;     //400 Khz

    NRF_TWI1->ENABLE = 0x00000005;
}

void I2C_disable(void)
{
    NRF_TWI1->ENABLE = 0x00000000;
}


void I2C_startTX(unsigned char addr) 
{ 
    cprintf("I2C_startTX addr: 0x%x 0x%x\n", &(NRF_TWI1->ADDRESS), addr);
    NRF_TWI1->ADDRESS=addr;
    NRF_TWI1->TASKS_STARTTX=1; 
}

I tried both TWI0 and TWI1, all 3 different clock speeds. From a logic analyzer, the SDA start signal (high->low) was there, but SCL was not there.

To be clear, the above experiments were performed with Arduino. Any help will be highly appreciated.

  • Maybe try this:

    // Set sizes to suit
    static uint8_t TxBuffer[] = "Test";
    static volatile uint8_t RxBuffer[3];
    
    void I2C_init(void)
    {
        NRF_TWI1->ENABLE = 0;
    
        NRF_GPIO->PIN_CNF[SCL_PIN] =
            (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
          | (GPIO_PIN_CNF_DRIVE_H0D1     << 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);
    
        NRF_GPIO->PIN_CNF[SDA_PIN] =
            (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
          | (GPIO_PIN_CNF_DRIVE_H0D1     << 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);
    
        NRF_TWI1->PSELSCL=SCL_PIN;
        NRF_TWI1->PSELSDA=SDA_PIN;
    
        NRF_TWI1->FREQUENCY = 0x01980000;   //100 KHz /
        //NRF_TWI1->FREQUENCY = 0x04000000;     //250 Khz
        //NRF_TWI1->FREQUENCY = 0x06680000;     //400 Khz
    
        NRF_TWI1->SHORTS = TWIM_SHORTS_LASTTX_STOP_Msk;
        // Device address and clock speed
        // Transmit data
        NRF_TWI1->TXD.MAXCNT = sizeof(TxBuffer);
        NRF_TWI1->TXD.PTR = (uint32_t)&TxBuffer[0];
        // 1-255 bytes receive data
        NRF_TWI1->RXD.MAXCNT = 0;
        NRF_TWI1->RXD.PTR = (uint32_t)&RxBuffer[0];
        // Clear all events
        NRF_TWI1->EVENTS_ERROR     = 0;
        NRF_TWI1->EVENTS_SUSPENDED = 0;
        NRF_TWI1->EVENTS_TXSTARTED = 0;
        NRF_TWI1->EVENTS_RXSTARTED = 0;
        NRF_TWI1->EVENTS_STOPPED   = 0;
        NRF_TWI1->EVENTS_LASTRX    = 0;
        NRF_TWI1->EVENTS_LASTTX    = 0;
        // Disable all interrupts
        NRF_TWI1->INTENCLR = 0xFFFFFFFFUL;
        __DSB();
    
        // Enable TWIM - note 6 not 5 for TWIM
        NRF_TWI1->ENABLE = 6;
    }
    
    void I2C_startTX(unsigned char addr) 
    {
        uint32_t Timeout = 2000; // Some big number of uSecs
        cprintf("I2C_startTX addr: 0x%x 0x%x\n", &(NRF_TWI1->ADDRESS), addr);
        NRF_TWI1->ADDRESS=addr;
        NRF_TWI1->TASKS_STARTTX = 1;
        __DSB();
        // Wait until the transfer start event is indicated
        while (NRF_TWI1->EVENTS_TXSTARTED == 0) ;
    
        // Wait until the transfer end event or error is indicated
        while ((NRF_TWIM0->EVENTS_LASTTX == 0) && (NRF_TWI1->EVENTS_STOPPED == 0) && (NRF_TWI1->EVENTS_ERROR == 0) && (--Timeout > 0))
        {
            __DSB();
            nrf_delay_us(500);
        }
        if ((NRF_TWI1->ERRORSRC) || (NRF_TWI1->EVENTS_ERROR == 1))
        {
            NRF_TWI1->TASKS_STOP = 1;
            NRF_TWI1->ERRORSRC = NRF_TWI1->ERRORSRC;
            while(NRF_TWI1->EVENTS_STOPPED == 0) ;
        }
        // Disable TWIM
        NRF_TWI1->ENABLE = 0;
    }

    Edit: Note also changing S0D1 to H0D1 for more reliable performance

  • Thanks for the reply. I'd like to give it a try. Just want to know,

    1. Has TWI been phased out and I2C is only supported by TWIM? I noticed TWI and TWIM share the same base address.

    2. In you example, should the NRF_TWIM0 be NRF_TWI1 in order to operate on TWI1/TWIM1? Is it a typo?

       while ((NRF_TWIM0->EVENTS_LASTTX == 0) && (NRF_TWI1->EVENTS_STOPPED == 0) && (NRF_TWI1->EVENTS_ERROR == 0) && (--Timeout > 0))

    Please clarify. Thanks!

  • 1. TWI is still available and still works, and yes just using ENABLE=5 activates TWI not TWIM. TWI, TWIM, SPI, SPIM are all the exact same hardware registers, they just work in different modes, which implies registers have to be carefully set to avoid unexpected side effects. I just normally use TWIM

    2. Typo, yes; should be TWIM1 not TWIM0. For this reason I usually use

    static NRF_TWIM_Type * const pTWIM = NRF_TWIM1;

    and then everything is pTWIM-> to avoid that kind of typo

    // Set sizes to suit
    static uint8_t TxBuffer[] = "Test";
    static volatile uint8_t RxBuffer[3];
    static NRF_TWIM_Type * const pTWIM = NRF_TWIM1;
    
    void I2C_init(void)
    {
        pTWIM->ENABLE = 0;
        NRF_GPIO->PIN_CNF[SCL_PIN] =
            (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);
        NRF_GPIO->PIN_CNF[SDA_PIN] =
            (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);
        pTWIM->PSELSCL=SCL_PIN;
        pTWIM->PSELSDA=SDA_PIN;
        pTWIM->FREQUENCY = 0x01980000;   //100 KHz /
        //pTWIM->FREQUENCY = 0x04000000;     //250 Khz
        //pTWIM->FREQUENCY = 0x06680000;     //400 Khz
        pTWIM->SHORTS = TWIM_SHORTS_LASTTX_STOP_Msk;
        // Device address and clock speed
        // Transmit data
        pTWIM->TXD.MAXCNT = sizeof(TxBuffer);
        pTWIM->TXD.PTR = (uint32_t)&TxBuffer[0];
        // 1-255 bytes receive data
        pTWIM->RXD.MAXCNT = 0;
        pTWIM->RXD.PTR = (uint32_t)&RxBuffer[0];
        // Clear all events
        pTWIM->EVENTS_ERROR     = 0;
        pTWIM->EVENTS_SUSPENDED = 0;
        pTWIM->EVENTS_TXSTARTED = 0;
        pTWIM->EVENTS_RXSTARTED = 0;
        pTWIM->EVENTS_STOPPED   = 0;
        pTWIM->EVENTS_LASTRX    = 0;
        pTWIM->EVENTS_LASTTX    = 0;
        // Disable all interrupts
        pTWIM->INTENCLR = 0xFFFFFFFFUL;
        __DSB();
        // Enable TWIM - note 6 not 5 for TWIM
        pTWIM->ENABLE = 6;
    }
    void I2C_startTX(unsigned char addr) 
    {
        uint32_t Timeout = 2000; // Some big number of uSecs
        cprintf("I2C_startTX addr: 0x%x 0x%x\n", &(pTWIM->ADDRESS), addr);
        pTWIM->ADDRESS=addr;
        pTWIM->TASKS_STARTTX = 1;
        __DSB();
        // Wait until the transfer start event is indicated
        while (pTWIM->EVENTS_TXSTARTED == 0) ;
        // Wait until the transfer end event or error is indicated
        while ((pTWIM->EVENTS_LASTTX == 0) && (pTWIM->EVENTS_STOPPED == 0) && (pTWIM->EVENTS_ERROR == 0) && (--Timeout > 0))
        {
            __DSB();
            nrf_delay_us(500);
        }
        if ((pTWIM->ERRORSRC) || (pTWIM->EVENTS_ERROR == 1))
        {
            pTWIM->TASKS_STOP = 1;
            pTWIM->ERRORSRC = pTWIM->ERRORSRC;
            while(pTWIM->EVENTS_STOPPED == 0) ;
        }
        // Disable TWIM
        pTWIM->ENABLE = 0;
    }

    nRF52832 peripheral sharing is different to (say) nRF52811; I use a te

    /*
     * Table 20: Peripherals sharing an ID - nRF52832
     *  ===================================
     *  Instance
     *  ID  3 (0x40003000) SPIM SPIS SPI TWIM TWIS TWI
     *  ID  4 (0x40004000) SPIM SPIS SPI TWIM TWIS TWI
     *  ID 35 (0x40023000) SPIM SPIS SPI
     */
    STATIC_ASSERT ((uint32_t)NRF_TWIM0 == (uint32_t)NRF_SPI0, "NRF_TWIM0 differs from NRF_SPI0");
    STATIC_ASSERT ((uint32_t)NRF_TWIM1 == (uint32_t)NRF_SPI1, "NRF_TWIM1 differs from NRF_SPI1");
    
    st similar to this as unexpected collisions in definitions can be a problem

  • Thanks. Just tried the code. Still, SCL line doesn't have anything.

  • Just noticed this:

        NRF_GPIO->PIN_CNF[SCL_PIN] =
            (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
          | (GPIO_PIN_CNF_DRIVE_H0D1     << 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);
    should be
        NRF_GPIO->PIN_CNF[SCL_PIN] =
            (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
          | (GPIO_PIN_CNF_DRIVE_H0D1     << 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);  <<== !
    

Related