This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Best practices for very slow interrupt based i2c devices that require handshaking

Hello, this question applies to all Hercules processors, in particular the RM57 and RM46.

I am trying to read and write to a  device on the I2C bus that runs at a very slow 1 kbps data rate.   There is a bit of handshaking involved, and sometimes that device will be powered off and will "Nack" as a result. Since the device is so slow, using polling is not an option, and I need to use interrupt-based communications.

The way I'm currently approaching the issue is having a timer to manage the state machine that does the handshaking.  This timer interrupt is a state machine that runs at a certain rate, say 100 Hz, which cycles through the following states:

(1)write an address pointer with 1 byte of data (a device address pointer, say memory location 0x27)

(2)read from that address y bytes of data and wait for end of transmission.

(3) check for unresponsive devices and NACKs as necessary.

 

Here's what I've done so far, which seems to work.

(1) set up interrupts that trigger as TX and RX interrupts (ICRRDY and ICXRDY)

(2) set up an RTI at, say 100 Hz

 

State Machine:

(0) i2cinit() then move to state 1

(1) wait x cycles in a cycle wait state then move to step 2 once wait cycle is complete.

(2) check for bus busy,  reset i2c (i2cinit() ) if bus busy not cleared in x cycles,  otherwise set up i2c as master transmitter and send a start, one address,one byte, and a stop condition

(3) wait y cycles in a cycle wait state until bus busy cleared (transmission is complete), go to step 0 if bus busy not cleared, or 4 if cleared

(4) set up for a read of  z bytes, send start and address, and begin the interrupt process of reading z bytes, go to step 5.

(5) wait c cycles in a cycle wait state until bus busy cleared (receipt is complete) and no improper NACKs and read resulting data, go to step 2 if everything went okay, otherwise go to step 0 (i2c init) if bus busy doesn't clear in c cycles.

 

Here are my questions:

(1) This method seems awfully convoluted, but the only way I can see doing hardware handshaking with a slow device over i2c in a completely non-blocking way.  Any other suggestions on how to accomplish this without polling or DMA? Is this a reasonable solution?  

(2) Do I have enough interrupts (ICRRDY and ICXRDY) set up? I did not set up ARDY or NACK for interrupts. NACKs are checked manually in the state machine  e.g. (if( (i2cREG1->STR & (uint32)I2C_NACK)  go to  i2c reset state)

(3)  I'm confused on what things I need to check for in order to make sure i2cwrite and i2cread finish completely.  For example, is bus busy bit the easiest and best way to check for completed read and write cycles?

(4) Aside from calling i2cinit() everytime the bus goes down, is there an easier way to get the i2c bus reset back to a ready state?

Best,

 

Josh

 

  • Hi Josh,

    Joshua Karch said:
    (1) This method seems awfully convoluted, but the only way I can see doing hardware handshaking with a slow device over i2c in a completely non-blocking way.  Any other suggestions on how to accomplish this without polling or DMA? Is this a reasonable solution?  


    Well I don't think this is convoluted at all.   In fact you'll see this sort of interrupt triggered state machine in many device drivers.  

    The only thing I would say is that you would probably want to move the state machine to a non ISR level task and only perform the minimum operations inside the ISR as a best practice.    


    Now at the same time it's not 'simple' - if that is what you mean by 'convoluted'.   That I'd agree to.  But I think you are going down the right path otherwise there is no point in using a 200MHz or 300MHz CPU for this type of application.     It'd be like spending a lot of effort on optimizing the proverbial 'idle task'. 

    Joshua Karch said:
    (2) Do I have enough interrupts (ICRRDY and ICXRDY) set up? I did not set up ARDY or NACK for interrupts. NACKs are checked manually in the state machine  e.g. (if( (i2cREG1->STR & (uint32)I2C_NACK)  go to  i2c reset state)

    Well it probably wouldn't hurt to enable all the I2C interrupts in the I2C module - but point them at the same interrupt level & VIM channel.
    That way if the I2C signals *anything* you'll get the interrupt - and inside your ISR make sure you check all the flags and have a way to notify the
     state machine..      I don't know the I2C well enough to tell you that all NACK will come with a RRDY interrupt.. and why take the chance.

    I need to better understand 3 and 4.    Especially for 4) what do you mean by the bus going down exactly?    Does the slave wind up holding the SCL low indefinitely?

  • Anthony,
    Thank you for the quick reply-- I appreciate the feedback about my approach, it seemed like the best approach for bare metal, and something I would place in a thread with an RTOS.

    The issue I seem to have is that SCL does indeed get locked low, and I am not sure what's causing it. When it happens, I don't get to transfer data, and a timeout calls i2cinit(), which I think is an overkill. I made sure to cause a stop condition when the transfer is complete, but it doesn't always happen, and I think that may happen because my state machine is not using the proper bits to determine when the stop happens and the bus is ready for a new transaction.

    I think the biggest issues I have is figuring out what bits are used to signify that a write is complete, what bits for a read is complete, and whether an error occurred.

    Some devices on the i2c bus can be selectively powered down at any time, so in that case I'd like to ignore unresponsive devices.


    Best,
    Josh
  • Hi Josh,

    Part of this problem may be related to the slave devices.

    Holding SCL low is the mechanism by which the slave can throttle back the bit rate - it's like inserting extra wait states in a memory transaction.. and it's how you approach the 0Hz end of the bit rate.

    So one question might be is the slave behaving correctly when it holds SCL low? (Is that how it was designed and does it expect to have you wait for it..)

    I am not sure you mean this by 'powered down' ... if you mean clocks off and low power state ignore. But if you mean the actual VCC/VDD is switched off.. then if you remove power to a device but leave it on the bus, a lot of times what happens is that the ESD protection circuitry which acts like a diode to the power rail will get switched on - and there will be a lot of current flowing into the IO pin as you are in some sense trying to raise the VCC/VDD rail voltage through this path. Since the pullup on I2C is pretty weak - I would think that if this is happening it will leave the SCL/SDA line at about 0.5V to 1V and would be perceived by the MCU as a '0' on both SCL and SDA.

    If that is happening I don't know how you'd recover the bus until the device is powered up again.

    -Anthony
  • Josh,

    Regaring your question about which bits to poll, I think this is covered in the TRM in the I2C chapter
    section XX.3.1 Master Transmitter Mode and XX.3.2 Master Receiver Mode (XX is the I2C Module chapter # of the particular TRM)

    This is of course assuming you are using the I2C as a master. It explains in Master transmit mode you should wait on both MST and BB.
    And in Master Receive mode you wait on RSFULL to indicate data is available.
  • Anthony,
    So basically the issues I am having with intermittent interruptions in data transmissions comes from the fact that I've used i2csend versus i2csendbyte. Looking more carefully, however, it appears that i2csendbyte is a dangerous function which temporarily "fixed" my problem.

    When I use the i2cSend function to send two bytes, I never have an issue, but if I use i2cSend to send one byte,the bus busy bit sometimes doesn't clear, which makes me think I'm not handling the interrupt correctly. If I use i2cSendByte, it occasionally gets stuck in the infinite while loop, so I need to find a way to determine when i2cSend completes, because the bus busy bit seems to stay on and needs to be reset.

    Once bus busy locks in, my state machine cycles until timeout then resets the i2c bus.

    So basically the issue comes down to the bus busy bit not clearing when using i2cSend to send one byte. I need to figure out what to look for for a single interrupt byte transfer so I know when the transmission is complete.