Errata 219 is incomplete or misleading. Unexpected behaviour at 100kHz. TWIM clock too short after clock stretch.

Potential TLDR:

The nRF52 and possibly nRF53 are unusable at 100kHz with targets that require clock stretching and enforce a reasonable minimum clock period. This covers a very broad selection of targets, in particular lot of TI parts included in COTS battery packs.

Context:

nRF52840 on PC10056 dev board operating as an I2C controller with a target that uses clock stretching while operating at "standard speed" (100kHz).

nRF Connect SDK v2.7.0-5cb85570ca43

Dev kit has DETECT (shield detect) pulled low to enable the onboard pull ups.

I note that there are quite a few semi-related previous posts, however the threads contain a lot of conjecture and hand waving. None that I have found really get to the bottom of the issue or have a viable solution. 

This issue has come up on pre-production hardware as an intermittent bug, I have replicated it on the dev kit to avoid distraction about hardware. However this is problem in a mature project with considerable NRE behind it.

Issue observed

When reading from the target (and possibly in other transactions) with i2c_write_read_dt() the TWIM occasionally produces a significantly shortened clock pulse after the target stretches the clock. I have observed this in particular on first clock after a stretch, after the controller has sent an ack in response to a byte from the target. I suspect I have seen it elsewhere, however, that was before I appreciated what I was looking at. 

The shortened clock is of varying length down to 1.2us, when optimistically measured at the first observable change in the trace. In practice, after considering thresholds for high and low, it ends up in the region of 850ns from the point of view of the target in a practical design.

In this case, the target disregards clock periods under 4us. A feature that mitigates the effect of noise on SCL. Not an unreasonable margin of 20% from the expected 5us clock period. 

If there is any doubt about this being a feature, I have seen the lack of this feature in other targets cause no end of headache during compliance testing. An example result being a locked up bus during ESD testing. 

It is also of note that even if the bus had no capacitance and the pull ups were magic resulting in no rise time, the clock pulse is still only 1.2us. 

Errata:

The only mention of TWIM in the errata document(r3) is errata 219, TWIM: I2C timing spec is violated at 400 kHz. This errata states:

Conditions: Using TWIM at 400 kHz.

It seems very reasonable to consider this errata as not applicable when operating at 100kHz. In fact, on first reading, it appears that 100kHz would be a viable mitigation for problems at 400kHz.

If short clocks are considered an error at 400kHz, resulting in an errata. Surely this would also be considered an error at 100kHz where expectations on valid clock length would be longer. Noting that if the device does not conform to a documented standard (eg I2C) and does not provide full documentation on behaviour, a user can only work from reasonable expectation. The datasheet does not seem to document the minimum clock high period to highlight that it is not as might be expected.

Mitigation:

Working on the principle that this is the issue documented in errata 219 is highly likely to be the same thing, I tested the workaround provided with the FREQUENCY value scaled to match the context of 100kHz. This does not resolve the issue. Even with the clock speed reduced to ~12kHz the TWIM is producing clock pulses at ~1.2uS.

I have looked at using the bit-bang driver in zephyr, it did not work fully, requiring more debugging. Also, the bus is used quite a bit elsewhere in the system so bit-banging is not really an acceptable route to go.

Other parts:

Given that we are strongly tied into the nordic ecosystem with a lot of sunk NRE, we could consider moving to a different part. I note that the nRF5340 errata Rev1 v1.9 contains the same errata, almost word for word as ID47. Presuming that this means it has inherited the same peripheral IP block, including the issue. It seems that moving to the nRF53 will not solve the issue. The nRF54  does not currently have any public documentation.

Conclusion:

The nRF52 and possibly nRF53 is unusable at 100kHz with targets that require clock stretching and enforce a reasonable minimum clock period. This covers a very broad selection of targets, in particular lot of TI parts included in COTS battery packs.

Questions:

1) Is there a mitigation for this other than bit banging or is it simply impossible to use nordic BLE parts for many "I2C" applications? 

2) Should the errata not be more clear that it applies (even more so) at standard speed (100kHz). 

3) Does this also apply to the nrF53?

4) Has the issue been carried to the nRF54 or is this a potential route out of a corner?

Thanks in advance.

Tom

EDIT: Corrected errata no

Parents
  • Errata 219; I spent too much time on this but it is a big issue given quite a few devices use clock stretching. All my comments in the previous posts in this thread apply, this is just a summery of the work-around code I am using in the expectation it will be useful to others.

    The code below uses two spare output pins and a spare 6-CC timer; the 64Mhz/16MHz clock source is not important, either the internal RC or an external 32MHz crystal, as the clock is supplied by the TWIM peripheral to both timer and slave. Both spare output pins are configured as open-drain (H0D1); the optional spoof ACK pin connects to SDA and the required spoof STRETCH pin connects to SCL. The optional spoof ACK pin allows testing without a slave SPIS device attached; the spoof STRETCH pin is required to extend any asynchronous slave stretch to a safe length where the following SCL clock pulse is normal width and not corrupted as a consequence of badly-handled external asynchronous signals. Should the cases where stretch from the slave is involved be predictable the stretch can be on selective bytes, but if not predictable every transmitted byte has to be spoof stretched.

    Tested on nRF52832 and nRF52833. There is an option to use a separate counter to count clock edges for the spoof ACK, but that's not required if only 2 ACKs are required.

    // Errata 219
    //
    // Set a spare port pin to be H0D1 output and connect to SCL
    // Set another spare port pin to be H0D1 output and connect to SDA if spoofing the ACK
    // Start a Timer at the same time as the TWIM events STARTED using PPI
    // Set the Timer CC[0] register to trigger the spare port pin Low via PPI after a time equal to 9 x SCL
    //  clock cycles, say 90 uSecs, after the 9th clock and coincident with a slave clock stretch if one is
    //  in progress but before the next byte clock if no slave stretch is in progress
    // Set the Timer CC[1] to clear the timer and set the port pin High (actually float as open-drain) via
    //  PPI to end the synchronous stretch say after another 100uSec, ie at 190uSec
    // Stop and Clear the Timer and set the port pin High (actually float as open-drain) on the TWIM events
    //  STOPPED using PPI
    // Tune this synchronous stretch to be longer than the longest slave stretch but without generating
    //  the race-hazard pulse
    // The clocks for both TWIM and Timer are synchronous from 16MHz regardless of whether using the crystal.
    // The synchronous stretch could be added for every byte regardless of whether requested by the slave.
    
    #define WHO_AM_I          0x0F
    #define I_AM_LSM6DS       0x6A
    #define LSM6DS3TR_ADDRESS 0xD4
    
    // Set sizes to suit, will be prefixed by i2c address byte
    static uint8_t TxBuffer[] = {WHO_AM_I};
    static uint8_t RxBuffer[3];
    static NRF_TWIM_Type * const pTWIM = NRF_TWIM0;
    // nRF52833 DK
    #define I2C_SCL_PIN    13  // open-drain output pin
    #define I2C_SDA_PIN    14  // open-drain output pin
    #define LED_RED_PIN    15
    #define LED_GREEN_PIN  16
    #define LED_BLUE_PIN   17
    
    // Uncomment this next line to use feedback pin from SCL to operate Ack counter
    //#define FEATURE_COUNTER_FOR_SPOOF_ACK
    // Uncomment this next line to just Tx, don't start a subsequent Rx
    #define FEATURE_TX_ONLY_NO_RX
    
    #define PIN_TWIM_STRETCH   3  // synchronous stretch open-drain output pin
    #define PIN_TWIM_SPOOF_ACK 4  // spoof ACK open-drain output pin
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
    #define I2C_SCL_INPUT_PIN  28 // feedback pin from SCL to operate Ack counter
    #endif
    
    #define PPI_STRETCH_TXSTART     0 // Start pTimerStretch with TWIM Tx
    #define PPI_STRETCH_RXSTART     1 // Start pTimerStretch with TWIM Rx
    #define PPI_STRETCH_STOP        2 // Stop pTimerStretch after TWIM tx
    #define PPI_STRETCH_LOW         3 // Drive synchronous stretch output pin low
    #define PPI_STRETCH_HIGH        4 // Float synchronous stretch output pin
    #define PPI_SPOOF_ACK_1_LOW     5 // Drive synchronous spoof ACK output pin low
    #define PPI_SPOOF_ACK_1_HIGH    6 // Float synchronous spoof ACK output pin
    #define PPI_SPOOF_ACK_2_LOW     7 // Drive synchronous spoof ACK output pin low
    #define PPI_SPOOF_ACK_2_HIGH    8 // Float synchronous spoof ACK output pin
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
    #define PPI_SCL_INPUT           9 // Monitor SCL output pin
    #endif
    
    #define GPIOTE_TWIM_STRETCH   0 // GPIOTE to control synchronous stretch output pin
    #define GPIOTE_TWIM_SPOOF_ACK 1 // GPIOTE to control spoof ACK output pin
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
    #define GPIOTE_TWIM_SPOOF_SCL 2 // Monitor SCL output pin
    #endif
    
    // TIMER0 is used by the RADIO, so don't use for now
    static NRF_TIMER_Type * const pTimerStretch = NRF_TIMER3; // 6 x CC
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
    static NRF_TIMER_Type * const pCounterAck   = NRF_TIMER4; // 6 x CC
    #endif
    
    void I2C_init(void)
    {
       pTWIM->ENABLE = 0;
       // Testing i2c twi twim, i2c pins both released
       NRF_P0->OUT = (1 << I2C_SCL_PIN) | (1 << I2C_SDA_PIN);
       // Start with green led on
       NRF_P0->OUT |= (1 << LED_RED_PIN) | (0 << LED_GREEN_PIN) | (1 << LED_BLUE_PIN);
       // Configue port pins
       // Configuration                       Direction    Input            Pullup         Drive Level      Sense Level
       // ================================    ==========   ==============   ============   ==============   =============
       NRF_P0->PIN_CNF[LED_RED_PIN]        = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[LED_GREEN_PIN]      = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[LED_BLUE_PIN]       = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[I2C_SCL_PIN]        = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[I2C_SDA_PIN]        = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[PIN_TWIM_STRETCH]   = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[PIN_TWIM_SPOOF_ACK] = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
       NRF_P0->PIN_CNF[I2C_SCL_INPUT_PIN]  = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLUP   | PIN_DRIVE_S0S1 | PIN_SENSE_OFF);
    #endif
       // Set up i2c peripheral
       pTWIM->PSEL.SCL=I2C_SCL_PIN;
       pTWIM->PSEL.SDA=I2C_SDA_PIN;
       pTWIM->FREQUENCY = TWI_FREQUENCY_FREQUENCY_K100; //100/250/400 KHz
    #if defined(FEATURE_TX_ONLY_NO_RX)
       // Do just Tx then stop, don't start a subsequent Rx
       pTWIM->SHORTS = (TWIM_SHORTS_LASTTX_STOP_Enabled << TWIM_SHORTS_LASTTX_STOP_Pos);
    #else
       // Do Tx followed by Rx response then stop
       pTWIM->SHORTS = (TWIM_SHORTS_LASTTX_STARTRX_Enabled << TWIM_SHORTS_LASTTX_STARTRX_Pos) | (TWIM_SHORTS_LASTRX_STOP_Enabled << TWIM_SHORTS_LASTRX_STOP_Pos);
    #endif
       // Transmit data
       pTWIM->TXD.MAXCNT = sizeof(TxBuffer);
       pTWIM->TXD.PTR = (uint32_t)&TxBuffer[0];
       // 1-255 bytes receive data
       pTWIM->RXD.MAXCNT = sizeof(RxBuffer);
       pTWIM->RXD.PTR = (uint32_t)&RxBuffer[0];
       pTWIM->RXD.LIST = 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();
    
       // Configure GPIOTE->TASKS_OUT[GPIOTE_TWIM_STRETCH] to control PIN_TWIM_STRETCH
       NRF_GPIOTE->CONFIG[GPIOTE_TWIM_STRETCH] = (GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos)     |
                                                 (GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos)  |
                                                 (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos) |
                                                 (PIN_TWIM_STRETCH              << GPIOTE_CONFIG_PSEL_Pos);
       // Configure GPIOTE->TASKS_OUT[GPIOTE_TWIM_SPOOF_ACK] to read
       NRF_GPIOTE->CONFIG[GPIOTE_TWIM_SPOOF_ACK] = (GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos)     |
                                                   (GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos)  |
                                                   (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos) |
                                                   (PIN_TWIM_SPOOF_ACK            << GPIOTE_CONFIG_PSEL_Pos);
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
       // Configure GPIOTE->TASKS_OUT[GPIOTE_TWIM_SPOOF_ACK] to control PPI_SCL_INPUT falling edge
       // Data is transferred Most Significant Bit (MSB) first. Any number of data bytes can be transferred from the
       // master to slave between the START and STOP conditions. Data on the SDA line must remain stable
       // during the high phase of the clock period, as changes in the data line when the SCL is high are
       // interpreted as control commands (START or STOP).
       NRF_GPIOTE->CONFIG[GPIOTE_TWIM_SPOOF_SCL] = (GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos)     |
                                                   (GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos)  |
                                                   (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) |
                                                   (I2C_SCL_INPUT_PIN             << GPIOTE_CONFIG_PSEL_Pos);
    #endif
    
    // Stretch the 2nd byte before the associated Ack clock cycle
    #define SPOOF_STRETCH_LENGTH_USECS (16*(2*10)-0)   // 16MHz   20uSecs Start synch stretch
    #define SPOOF_2ND_ACK_STOP_OFFSET  (SPOOF_STRETCH_LENGTH_USECS-(16*(1*10)))
    #define SPOOF_STRETCH_START_USECS  (16*(20*10+4))  // 16MHz   80uSecs Start synch stretch
    #define SPOOF_STRETCH_STOP_USECS   (SPOOF_STRETCH_START_USECS+SPOOF_STRETCH_LENGTH_USECS)
    #define SPOOF_1ST_ACK_START_USECS  (16*(10*10+9))  // 16MHz   80uSecs Start synch spoof 1st byte Ack
    #define SPOOF_1ST_ACK_STOP_USECS   (16*(11*10+6))  // 16MHz   90uSecs End synch spoof 1st byte Ack
    #define SPOOF_2ND_ACK_START_USECS  (16*(20*10+9))  // 16MHz   80uSecs Start synch spoof 2nd byte Ack
    #define SPOOF_2ND_ACK_STOP_USECS   (16*(21*10+6))  // 16MHz   90uSecs End synch spoof 2nd byte Ack
    // Cycle ends on 2nd Ack, make sure strecth has gone by then
    STATIC_ASSERT(SPOOF_STRETCH_STOP_USECS < SPOOF_2ND_ACK_START_USECS+SPOOF_STRETCH_LENGTH_USECS, "SPOOF_STRETCH_STOP_USECS not < SPOOF_2ND_ACK_START_USECS");
    
       // TWIM Stretch: Configure pTimerStretch as Timer to generate EVENTS_COMPARE[0] at 90uSecs, EVENTS_COMPARE[1] at 190uSecs
       pTimerStretch->MODE = TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos;
       pTimerStretch->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
       pTimerStretch->SHORTS = TIMER_SHORTS_COMPARE5_CLEAR_Enabled << TIMER_SHORTS_COMPARE5_CLEAR_Pos;
       pTimerStretch->PRESCALER   =  0;  // 16MHz clock
       pTimerStretch->CC[0]       = SPOOF_STRETCH_START_USECS;  // 16MHz* 90  =  90uSecs Start synch stretch
       pTimerStretch->CC[1]       = SPOOF_STRETCH_STOP_USECS;   // 16MHz*190 = 190uSecs  End synch stretch, clear timer to restart
       pTimerStretch->CC[2]       = SPOOF_1ST_ACK_START_USECS;  // 80uSecs Start synch spoof Ack 1st byte
       pTimerStretch->CC[3]       = SPOOF_1ST_ACK_STOP_USECS;   // 90uSecs End synch spoof Ack 1st byte
       pTimerStretch->CC[4]       = SPOOF_2ND_ACK_START_USECS+SPOOF_2ND_ACK_STOP_OFFSET;  // 80uSecs Start synch spoof Ack 2nd byte
       pTimerStretch->CC[5]       = SPOOF_2ND_ACK_STOP_USECS+SPOOF_2ND_ACK_STOP_OFFSET;   // 90uSecs End synch spoof Ack 2nd byte
       //pTimerStretch->TASKS_START =  1;
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[0] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_STRETCH]
       NRF_PPI->CH[PPI_STRETCH_LOW].EEP  = (uint32_t)&pTimerStretch->EVENTS_COMPARE[0];
       NRF_PPI->CH[PPI_STRETCH_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_STRETCH];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[1] and GPIOTE->TASKS_SET[GPIOTE_TWIM_STRETCH]
       NRF_PPI->CH[PPI_STRETCH_HIGH].EEP = (uint32_t)&pTimerStretch->EVENTS_COMPARE[1];
       NRF_PPI->CH[PPI_STRETCH_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_STRETCH];
    #if !defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
       // Data is transferred Most Significant Bit (MSB) first. Any number of data bytes can be transferred from the
       // master to slave between the START and STOP conditions. Data on the SDA line must remain stable
       // during the high phase of the clock period, as changes in the data line when the SCL is high are
       // interpreted as control commands (START or STOP)
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[2] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_1_LOW].EEP  = (uint32_t)&pTimerStretch->EVENTS_COMPARE[2];
       NRF_PPI->CH[PPI_SPOOF_ACK_1_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[3] and GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_1_HIGH].EEP = (uint32_t)&pTimerStretch->EVENTS_COMPARE[3];
       NRF_PPI->CH[PPI_SPOOF_ACK_1_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[4] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_2_LOW].EEP  = (uint32_t)&pTimerStretch->EVENTS_COMPARE[4];
       NRF_PPI->CH[PPI_SPOOF_ACK_2_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[5] and GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_2_HIGH].EEP = (uint32_t)&pTimerStretch->EVENTS_COMPARE[5];
       NRF_PPI->CH[PPI_SPOOF_ACK_2_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK];
    #endif
       // Configure PPI channel with connection between TWIM->EVENTS_TXSTARTED and TIMER->TASKS_START
       NRF_PPI->CH[PPI_STRETCH_TXSTART].EEP  = (uint32_t)&pTWIM->EVENTS_TXSTARTED;
       NRF_PPI->CH[PPI_STRETCH_TXSTART].TEP  = (uint32_t)&pTimerStretch->TASKS_START;
       //NRF_PPI->FORK[PPI_STRETCH_TXSTART].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_STRETCH];
    #if !defined(FEATURE_TX_ONLY_NO_RX)
       // Configure PPI channel with connection between TWIM->EVENTS_RXSTARTED and TIMER->TASKS_START
       NRF_PPI->CH[PPI_STRETCH_RXSTART].EEP  = (uint32_t)&pTWIM->EVENTS_RXSTARTED;
       NRF_PPI->CH[PPI_STRETCH_RXSTART].TEP  = (uint32_t)&pTimerStretch->TASKS_START;
       //NRF_PPI->FORK[PPI_STRETCH_RXSTART].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_STRETCH];
    #endif
       // Configure PPI channel with connection between TWIM->EVENTS_STOPPED and TIMER->TASKS_STOP
       NRF_PPI->CH[PPI_STRETCH_STOP].EEP = (uint32_t)&pTWIM->EVENTS_STOPPED;
       NRF_PPI->CH[PPI_STRETCH_STOP].TEP = (uint32_t)&pTimerStretch->TASKS_STOP;
       NRF_PPI->FORK[PPI_STRETCH_STOP].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[GPIOTE_TWIM_STRETCH];
    
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
       // Configure PPI channel with connection between fallling edge of SCL and pCounterAck clock
       NRF_PPI->CH[PPI_SCL_INPUT].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_TWIM_SPOOF_SCL]; // Event
       NRF_PPI->CH[PPI_SCL_INPUT].TEP = (uint32_t)&pCounterAck->TASKS_COUNT;
    
       // TWIM Ack: Configure pCounterAck as Counter to generate ACK after every 8 SCL clock cycles
       pCounterAck->MODE = TIMER_MODE_MODE_Counter << TIMER_MODE_MODE_Pos;
       pCounterAck->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
       pCounterAck->SHORTS = TIMER_SHORTS_COMPARE1_CLEAR_Enabled << TIMER_SHORTS_COMPARE1_CLEAR_Pos;
       pCounterAck->PRESCALER   =  0;  // 16MHz clock
       pCounterAck->CC[0]       =  7;//8;  // Start synch spoof Ack
       pCounterAck->CC[1]       = 10;  // End synch spoof Ack, clear counter to restart
       pCounterAck->CC[2]       = 17;//8;  // Start synch spoof Ack
       pCounterAck->CC[3]       = 20;  // End synch spoof Ack, clear counter to restart
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[0] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_1_LOW].EEP  = (uint32_t)&pCounterAck->EVENTS_COMPARE[0];
       NRF_PPI->CH[PPI_SPOOF_ACK_1_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[1] and GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_1_HIGH].EEP = (uint32_t)&pCounterAck->EVENTS_COMPARE[1];
       NRF_PPI->CH[PPI_SPOOF_ACK_1_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[2] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_2_LOW].EEP  = (uint32_t)&pCounterAck->EVENTS_COMPARE[2];
       NRF_PPI->CH[PPI_SPOOF_ACK_2_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[3] and GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_2_HIGH].EEP = (uint32_t)&pCounterAck->EVENTS_COMPARE[3];
       NRF_PPI->CH[PPI_SPOOF_ACK_2_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK];
       pCounterAck->TASKS_START =  1;
    #endif
    
       // Enable PPI channels
       NRF_PPI->CHENSET = (1UL << PPI_STRETCH_TXSTART  ) |
                          (1UL << PPI_STRETCH_RXSTART  ) |
                          (1UL << PPI_STRETCH_STOP     ) |
                          (1UL << PPI_STRETCH_LOW      ) |
                          (1UL << PPI_STRETCH_HIGH     ) |
                          (1UL << PPI_SPOOF_ACK_1_LOW  ) |
                          (1UL << PPI_SPOOF_ACK_1_HIGH ) |
                          (1UL << PPI_SPOOF_ACK_2_LOW  ) |
                          (1UL << PPI_SPOOF_ACK_2_HIGH ) |
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
                          (1UL << PPI_SCL_INPUT        ) |
    #endif
                          (0);
    
       // Enable TWIM - note 6 not 5 for TWIM
       pTWIM->ENABLE = 6;
    }
    
    void I2C_startTX(uint8_t addr)
    {
       char InfoPacket[120] = "";
       uint32_t Timeout = 200; // Some big number of uSecs
       pTWIM->ADDRESS=addr;
       //snprintf(InfoPacket, sizeof(InfoPacket)-1, "I2C_startTX addr: 0x%x 0x%x\n", &(pTWIM->ADDRESS), addr);
       //uartSend(InfoPacket, strlen(InfoPacket));
       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
    #if defined(FEATURE_TX_ONLY_NO_RX)
       // Just wait for Tx, not starting a subsequent Rx
       while ((pTWIM->EVENTS_LASTTX == 0) && (pTWIM->EVENTS_STOPPED == 0) && (pTWIM->EVENTS_ERROR == 0) && (--Timeout > 0))
    #else
       // Wait until the transfer receive start event is indicated
       while (pTWIM->EVENTS_RXSTARTED == 0) ;
       // Wait for Rx response
       while ((pTWIM->EVENTS_LASTRX == 0) && (pTWIM->EVENTS_STOPPED == 0) && (pTWIM->EVENTS_ERROR == 0) && (--Timeout > 0))
    #endif
       {
          __DSB();
          nrf_delay_us(50);
       }
       if ((pTWIM->ERRORSRC) || (pTWIM->EVENTS_ERROR == 1))
       {
          // Write '1' to clear Anak and Dnak
          pTWIM->ERRORSRC = pTWIM->ERRORSRC;
          pTWIM->EVENTS_ERROR = 0;
          pTWIM->TASKS_STOP   = 1;
          __DSB();
          while(pTWIM->EVENTS_STOPPED == 0) ;
       }
       // Disable TWIM
       pTWIM->ENABLE = 0;
    }
    
    void Test_I2C(void)
    {
       I2C_init();
       I2C_startTX(LSM6DS3TR_ADDRESS | 0x01);
       while(1) ;
    }
    

Reply
  • Errata 219; I spent too much time on this but it is a big issue given quite a few devices use clock stretching. All my comments in the previous posts in this thread apply, this is just a summery of the work-around code I am using in the expectation it will be useful to others.

    The code below uses two spare output pins and a spare 6-CC timer; the 64Mhz/16MHz clock source is not important, either the internal RC or an external 32MHz crystal, as the clock is supplied by the TWIM peripheral to both timer and slave. Both spare output pins are configured as open-drain (H0D1); the optional spoof ACK pin connects to SDA and the required spoof STRETCH pin connects to SCL. The optional spoof ACK pin allows testing without a slave SPIS device attached; the spoof STRETCH pin is required to extend any asynchronous slave stretch to a safe length where the following SCL clock pulse is normal width and not corrupted as a consequence of badly-handled external asynchronous signals. Should the cases where stretch from the slave is involved be predictable the stretch can be on selective bytes, but if not predictable every transmitted byte has to be spoof stretched.

    Tested on nRF52832 and nRF52833. There is an option to use a separate counter to count clock edges for the spoof ACK, but that's not required if only 2 ACKs are required.

    // Errata 219
    //
    // Set a spare port pin to be H0D1 output and connect to SCL
    // Set another spare port pin to be H0D1 output and connect to SDA if spoofing the ACK
    // Start a Timer at the same time as the TWIM events STARTED using PPI
    // Set the Timer CC[0] register to trigger the spare port pin Low via PPI after a time equal to 9 x SCL
    //  clock cycles, say 90 uSecs, after the 9th clock and coincident with a slave clock stretch if one is
    //  in progress but before the next byte clock if no slave stretch is in progress
    // Set the Timer CC[1] to clear the timer and set the port pin High (actually float as open-drain) via
    //  PPI to end the synchronous stretch say after another 100uSec, ie at 190uSec
    // Stop and Clear the Timer and set the port pin High (actually float as open-drain) on the TWIM events
    //  STOPPED using PPI
    // Tune this synchronous stretch to be longer than the longest slave stretch but without generating
    //  the race-hazard pulse
    // The clocks for both TWIM and Timer are synchronous from 16MHz regardless of whether using the crystal.
    // The synchronous stretch could be added for every byte regardless of whether requested by the slave.
    
    #define WHO_AM_I          0x0F
    #define I_AM_LSM6DS       0x6A
    #define LSM6DS3TR_ADDRESS 0xD4
    
    // Set sizes to suit, will be prefixed by i2c address byte
    static uint8_t TxBuffer[] = {WHO_AM_I};
    static uint8_t RxBuffer[3];
    static NRF_TWIM_Type * const pTWIM = NRF_TWIM0;
    // nRF52833 DK
    #define I2C_SCL_PIN    13  // open-drain output pin
    #define I2C_SDA_PIN    14  // open-drain output pin
    #define LED_RED_PIN    15
    #define LED_GREEN_PIN  16
    #define LED_BLUE_PIN   17
    
    // Uncomment this next line to use feedback pin from SCL to operate Ack counter
    //#define FEATURE_COUNTER_FOR_SPOOF_ACK
    // Uncomment this next line to just Tx, don't start a subsequent Rx
    #define FEATURE_TX_ONLY_NO_RX
    
    #define PIN_TWIM_STRETCH   3  // synchronous stretch open-drain output pin
    #define PIN_TWIM_SPOOF_ACK 4  // spoof ACK open-drain output pin
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
    #define I2C_SCL_INPUT_PIN  28 // feedback pin from SCL to operate Ack counter
    #endif
    
    #define PPI_STRETCH_TXSTART     0 // Start pTimerStretch with TWIM Tx
    #define PPI_STRETCH_RXSTART     1 // Start pTimerStretch with TWIM Rx
    #define PPI_STRETCH_STOP        2 // Stop pTimerStretch after TWIM tx
    #define PPI_STRETCH_LOW         3 // Drive synchronous stretch output pin low
    #define PPI_STRETCH_HIGH        4 // Float synchronous stretch output pin
    #define PPI_SPOOF_ACK_1_LOW     5 // Drive synchronous spoof ACK output pin low
    #define PPI_SPOOF_ACK_1_HIGH    6 // Float synchronous spoof ACK output pin
    #define PPI_SPOOF_ACK_2_LOW     7 // Drive synchronous spoof ACK output pin low
    #define PPI_SPOOF_ACK_2_HIGH    8 // Float synchronous spoof ACK output pin
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
    #define PPI_SCL_INPUT           9 // Monitor SCL output pin
    #endif
    
    #define GPIOTE_TWIM_STRETCH   0 // GPIOTE to control synchronous stretch output pin
    #define GPIOTE_TWIM_SPOOF_ACK 1 // GPIOTE to control spoof ACK output pin
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
    #define GPIOTE_TWIM_SPOOF_SCL 2 // Monitor SCL output pin
    #endif
    
    // TIMER0 is used by the RADIO, so don't use for now
    static NRF_TIMER_Type * const pTimerStretch = NRF_TIMER3; // 6 x CC
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
    static NRF_TIMER_Type * const pCounterAck   = NRF_TIMER4; // 6 x CC
    #endif
    
    void I2C_init(void)
    {
       pTWIM->ENABLE = 0;
       // Testing i2c twi twim, i2c pins both released
       NRF_P0->OUT = (1 << I2C_SCL_PIN) | (1 << I2C_SDA_PIN);
       // Start with green led on
       NRF_P0->OUT |= (1 << LED_RED_PIN) | (0 << LED_GREEN_PIN) | (1 << LED_BLUE_PIN);
       // Configue port pins
       // Configuration                       Direction    Input            Pullup         Drive Level      Sense Level
       // ================================    ==========   ==============   ============   ==============   =============
       NRF_P0->PIN_CNF[LED_RED_PIN]        = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[LED_GREEN_PIN]      = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[LED_BLUE_PIN]       = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[I2C_SCL_PIN]        = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[I2C_SDA_PIN]        = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[PIN_TWIM_STRETCH]   = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[PIN_TWIM_SPOOF_ACK] = (PIN_OUTPUT | PIN_DISCONNECT | PIN_PULLUP   | PIN_DRIVE_H0D1 | PIN_SENSE_OFF);
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
       NRF_P0->PIN_CNF[I2C_SCL_INPUT_PIN]  = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLUP   | PIN_DRIVE_S0S1 | PIN_SENSE_OFF);
    #endif
       // Set up i2c peripheral
       pTWIM->PSEL.SCL=I2C_SCL_PIN;
       pTWIM->PSEL.SDA=I2C_SDA_PIN;
       pTWIM->FREQUENCY = TWI_FREQUENCY_FREQUENCY_K100; //100/250/400 KHz
    #if defined(FEATURE_TX_ONLY_NO_RX)
       // Do just Tx then stop, don't start a subsequent Rx
       pTWIM->SHORTS = (TWIM_SHORTS_LASTTX_STOP_Enabled << TWIM_SHORTS_LASTTX_STOP_Pos);
    #else
       // Do Tx followed by Rx response then stop
       pTWIM->SHORTS = (TWIM_SHORTS_LASTTX_STARTRX_Enabled << TWIM_SHORTS_LASTTX_STARTRX_Pos) | (TWIM_SHORTS_LASTRX_STOP_Enabled << TWIM_SHORTS_LASTRX_STOP_Pos);
    #endif
       // Transmit data
       pTWIM->TXD.MAXCNT = sizeof(TxBuffer);
       pTWIM->TXD.PTR = (uint32_t)&TxBuffer[0];
       // 1-255 bytes receive data
       pTWIM->RXD.MAXCNT = sizeof(RxBuffer);
       pTWIM->RXD.PTR = (uint32_t)&RxBuffer[0];
       pTWIM->RXD.LIST = 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();
    
       // Configure GPIOTE->TASKS_OUT[GPIOTE_TWIM_STRETCH] to control PIN_TWIM_STRETCH
       NRF_GPIOTE->CONFIG[GPIOTE_TWIM_STRETCH] = (GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos)     |
                                                 (GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos)  |
                                                 (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos) |
                                                 (PIN_TWIM_STRETCH              << GPIOTE_CONFIG_PSEL_Pos);
       // Configure GPIOTE->TASKS_OUT[GPIOTE_TWIM_SPOOF_ACK] to read
       NRF_GPIOTE->CONFIG[GPIOTE_TWIM_SPOOF_ACK] = (GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos)     |
                                                   (GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos)  |
                                                   (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos) |
                                                   (PIN_TWIM_SPOOF_ACK            << GPIOTE_CONFIG_PSEL_Pos);
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
       // Configure GPIOTE->TASKS_OUT[GPIOTE_TWIM_SPOOF_ACK] to control PPI_SCL_INPUT falling edge
       // Data is transferred Most Significant Bit (MSB) first. Any number of data bytes can be transferred from the
       // master to slave between the START and STOP conditions. Data on the SDA line must remain stable
       // during the high phase of the clock period, as changes in the data line when the SCL is high are
       // interpreted as control commands (START or STOP).
       NRF_GPIOTE->CONFIG[GPIOTE_TWIM_SPOOF_SCL] = (GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos)     |
                                                   (GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos)  |
                                                   (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos) |
                                                   (I2C_SCL_INPUT_PIN             << GPIOTE_CONFIG_PSEL_Pos);
    #endif
    
    // Stretch the 2nd byte before the associated Ack clock cycle
    #define SPOOF_STRETCH_LENGTH_USECS (16*(2*10)-0)   // 16MHz   20uSecs Start synch stretch
    #define SPOOF_2ND_ACK_STOP_OFFSET  (SPOOF_STRETCH_LENGTH_USECS-(16*(1*10)))
    #define SPOOF_STRETCH_START_USECS  (16*(20*10+4))  // 16MHz   80uSecs Start synch stretch
    #define SPOOF_STRETCH_STOP_USECS   (SPOOF_STRETCH_START_USECS+SPOOF_STRETCH_LENGTH_USECS)
    #define SPOOF_1ST_ACK_START_USECS  (16*(10*10+9))  // 16MHz   80uSecs Start synch spoof 1st byte Ack
    #define SPOOF_1ST_ACK_STOP_USECS   (16*(11*10+6))  // 16MHz   90uSecs End synch spoof 1st byte Ack
    #define SPOOF_2ND_ACK_START_USECS  (16*(20*10+9))  // 16MHz   80uSecs Start synch spoof 2nd byte Ack
    #define SPOOF_2ND_ACK_STOP_USECS   (16*(21*10+6))  // 16MHz   90uSecs End synch spoof 2nd byte Ack
    // Cycle ends on 2nd Ack, make sure strecth has gone by then
    STATIC_ASSERT(SPOOF_STRETCH_STOP_USECS < SPOOF_2ND_ACK_START_USECS+SPOOF_STRETCH_LENGTH_USECS, "SPOOF_STRETCH_STOP_USECS not < SPOOF_2ND_ACK_START_USECS");
    
       // TWIM Stretch: Configure pTimerStretch as Timer to generate EVENTS_COMPARE[0] at 90uSecs, EVENTS_COMPARE[1] at 190uSecs
       pTimerStretch->MODE = TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos;
       pTimerStretch->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
       pTimerStretch->SHORTS = TIMER_SHORTS_COMPARE5_CLEAR_Enabled << TIMER_SHORTS_COMPARE5_CLEAR_Pos;
       pTimerStretch->PRESCALER   =  0;  // 16MHz clock
       pTimerStretch->CC[0]       = SPOOF_STRETCH_START_USECS;  // 16MHz* 90  =  90uSecs Start synch stretch
       pTimerStretch->CC[1]       = SPOOF_STRETCH_STOP_USECS;   // 16MHz*190 = 190uSecs  End synch stretch, clear timer to restart
       pTimerStretch->CC[2]       = SPOOF_1ST_ACK_START_USECS;  // 80uSecs Start synch spoof Ack 1st byte
       pTimerStretch->CC[3]       = SPOOF_1ST_ACK_STOP_USECS;   // 90uSecs End synch spoof Ack 1st byte
       pTimerStretch->CC[4]       = SPOOF_2ND_ACK_START_USECS+SPOOF_2ND_ACK_STOP_OFFSET;  // 80uSecs Start synch spoof Ack 2nd byte
       pTimerStretch->CC[5]       = SPOOF_2ND_ACK_STOP_USECS+SPOOF_2ND_ACK_STOP_OFFSET;   // 90uSecs End synch spoof Ack 2nd byte
       //pTimerStretch->TASKS_START =  1;
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[0] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_STRETCH]
       NRF_PPI->CH[PPI_STRETCH_LOW].EEP  = (uint32_t)&pTimerStretch->EVENTS_COMPARE[0];
       NRF_PPI->CH[PPI_STRETCH_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_STRETCH];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[1] and GPIOTE->TASKS_SET[GPIOTE_TWIM_STRETCH]
       NRF_PPI->CH[PPI_STRETCH_HIGH].EEP = (uint32_t)&pTimerStretch->EVENTS_COMPARE[1];
       NRF_PPI->CH[PPI_STRETCH_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_STRETCH];
    #if !defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
       // Data is transferred Most Significant Bit (MSB) first. Any number of data bytes can be transferred from the
       // master to slave between the START and STOP conditions. Data on the SDA line must remain stable
       // during the high phase of the clock period, as changes in the data line when the SCL is high are
       // interpreted as control commands (START or STOP)
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[2] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_1_LOW].EEP  = (uint32_t)&pTimerStretch->EVENTS_COMPARE[2];
       NRF_PPI->CH[PPI_SPOOF_ACK_1_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[3] and GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_1_HIGH].EEP = (uint32_t)&pTimerStretch->EVENTS_COMPARE[3];
       NRF_PPI->CH[PPI_SPOOF_ACK_1_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[4] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_2_LOW].EEP  = (uint32_t)&pTimerStretch->EVENTS_COMPARE[4];
       NRF_PPI->CH[PPI_SPOOF_ACK_2_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[5] and GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_2_HIGH].EEP = (uint32_t)&pTimerStretch->EVENTS_COMPARE[5];
       NRF_PPI->CH[PPI_SPOOF_ACK_2_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK];
    #endif
       // Configure PPI channel with connection between TWIM->EVENTS_TXSTARTED and TIMER->TASKS_START
       NRF_PPI->CH[PPI_STRETCH_TXSTART].EEP  = (uint32_t)&pTWIM->EVENTS_TXSTARTED;
       NRF_PPI->CH[PPI_STRETCH_TXSTART].TEP  = (uint32_t)&pTimerStretch->TASKS_START;
       //NRF_PPI->FORK[PPI_STRETCH_TXSTART].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_STRETCH];
    #if !defined(FEATURE_TX_ONLY_NO_RX)
       // Configure PPI channel with connection between TWIM->EVENTS_RXSTARTED and TIMER->TASKS_START
       NRF_PPI->CH[PPI_STRETCH_RXSTART].EEP  = (uint32_t)&pTWIM->EVENTS_RXSTARTED;
       NRF_PPI->CH[PPI_STRETCH_RXSTART].TEP  = (uint32_t)&pTimerStretch->TASKS_START;
       //NRF_PPI->FORK[PPI_STRETCH_RXSTART].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_STRETCH];
    #endif
       // Configure PPI channel with connection between TWIM->EVENTS_STOPPED and TIMER->TASKS_STOP
       NRF_PPI->CH[PPI_STRETCH_STOP].EEP = (uint32_t)&pTWIM->EVENTS_STOPPED;
       NRF_PPI->CH[PPI_STRETCH_STOP].TEP = (uint32_t)&pTimerStretch->TASKS_STOP;
       NRF_PPI->FORK[PPI_STRETCH_STOP].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[GPIOTE_TWIM_STRETCH];
    
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
       // Configure PPI channel with connection between fallling edge of SCL and pCounterAck clock
       NRF_PPI->CH[PPI_SCL_INPUT].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_TWIM_SPOOF_SCL]; // Event
       NRF_PPI->CH[PPI_SCL_INPUT].TEP = (uint32_t)&pCounterAck->TASKS_COUNT;
    
       // TWIM Ack: Configure pCounterAck as Counter to generate ACK after every 8 SCL clock cycles
       pCounterAck->MODE = TIMER_MODE_MODE_Counter << TIMER_MODE_MODE_Pos;
       pCounterAck->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
       pCounterAck->SHORTS = TIMER_SHORTS_COMPARE1_CLEAR_Enabled << TIMER_SHORTS_COMPARE1_CLEAR_Pos;
       pCounterAck->PRESCALER   =  0;  // 16MHz clock
       pCounterAck->CC[0]       =  7;//8;  // Start synch spoof Ack
       pCounterAck->CC[1]       = 10;  // End synch spoof Ack, clear counter to restart
       pCounterAck->CC[2]       = 17;//8;  // Start synch spoof Ack
       pCounterAck->CC[3]       = 20;  // End synch spoof Ack, clear counter to restart
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[0] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_1_LOW].EEP  = (uint32_t)&pCounterAck->EVENTS_COMPARE[0];
       NRF_PPI->CH[PPI_SPOOF_ACK_1_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[1] and GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_1_HIGH].EEP = (uint32_t)&pCounterAck->EVENTS_COMPARE[1];
       NRF_PPI->CH[PPI_SPOOF_ACK_1_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[2] and GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_2_LOW].EEP  = (uint32_t)&pCounterAck->EVENTS_COMPARE[2];
       NRF_PPI->CH[PPI_SPOOF_ACK_2_LOW].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_CLR[GPIOTE_TWIM_SPOOF_ACK];
       // Configure PPI channel with connection between TIMER->EVENTS_COMPARE[3] and GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK]
       NRF_PPI->CH[PPI_SPOOF_ACK_2_HIGH].EEP = (uint32_t)&pCounterAck->EVENTS_COMPARE[3];
       NRF_PPI->CH[PPI_SPOOF_ACK_2_HIGH].TEP = (uint32_t)&NRF_GPIOTE->TASKS_SET[GPIOTE_TWIM_SPOOF_ACK];
       pCounterAck->TASKS_START =  1;
    #endif
    
       // Enable PPI channels
       NRF_PPI->CHENSET = (1UL << PPI_STRETCH_TXSTART  ) |
                          (1UL << PPI_STRETCH_RXSTART  ) |
                          (1UL << PPI_STRETCH_STOP     ) |
                          (1UL << PPI_STRETCH_LOW      ) |
                          (1UL << PPI_STRETCH_HIGH     ) |
                          (1UL << PPI_SPOOF_ACK_1_LOW  ) |
                          (1UL << PPI_SPOOF_ACK_1_HIGH ) |
                          (1UL << PPI_SPOOF_ACK_2_LOW  ) |
                          (1UL << PPI_SPOOF_ACK_2_HIGH ) |
    #if defined(FEATURE_COUNTER_FOR_SPOOF_ACK)
                          (1UL << PPI_SCL_INPUT        ) |
    #endif
                          (0);
    
       // Enable TWIM - note 6 not 5 for TWIM
       pTWIM->ENABLE = 6;
    }
    
    void I2C_startTX(uint8_t addr)
    {
       char InfoPacket[120] = "";
       uint32_t Timeout = 200; // Some big number of uSecs
       pTWIM->ADDRESS=addr;
       //snprintf(InfoPacket, sizeof(InfoPacket)-1, "I2C_startTX addr: 0x%x 0x%x\n", &(pTWIM->ADDRESS), addr);
       //uartSend(InfoPacket, strlen(InfoPacket));
       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
    #if defined(FEATURE_TX_ONLY_NO_RX)
       // Just wait for Tx, not starting a subsequent Rx
       while ((pTWIM->EVENTS_LASTTX == 0) && (pTWIM->EVENTS_STOPPED == 0) && (pTWIM->EVENTS_ERROR == 0) && (--Timeout > 0))
    #else
       // Wait until the transfer receive start event is indicated
       while (pTWIM->EVENTS_RXSTARTED == 0) ;
       // Wait for Rx response
       while ((pTWIM->EVENTS_LASTRX == 0) && (pTWIM->EVENTS_STOPPED == 0) && (pTWIM->EVENTS_ERROR == 0) && (--Timeout > 0))
    #endif
       {
          __DSB();
          nrf_delay_us(50);
       }
       if ((pTWIM->ERRORSRC) || (pTWIM->EVENTS_ERROR == 1))
       {
          // Write '1' to clear Anak and Dnak
          pTWIM->ERRORSRC = pTWIM->ERRORSRC;
          pTWIM->EVENTS_ERROR = 0;
          pTWIM->TASKS_STOP   = 1;
          __DSB();
          while(pTWIM->EVENTS_STOPPED == 0) ;
       }
       // Disable TWIM
       pTWIM->ENABLE = 0;
    }
    
    void Test_I2C(void)
    {
       I2C_init();
       I2C_startTX(LSM6DS3TR_ADDRESS | 0x01);
       while(1) ;
    }
    

Children
No Data
Related