<< return to Pixycam.com

Pixy rejects i2c requests for 20ms after an i2c write sequence

Hello,
Got my pixy a week ago. A really cool little device!
Currently I’m writing a Pixy i2c driver for the fischertechnik TX controller, a device similar to lego mindstorms. Programming this in the proprietary graphical “RoboPro” language is rather painful and the i2c bit banging (guess) of this controller isn’t that performant. But ColorBlock reading works fine (got at least 14 blocks per frame) and my daughter is eager to build a robot with the pixy.

The Problem:
After writing some bytes to the pixy, e.g. to set the pan/tilt servos, the Pixy behaves weird. For about 20ms Pixy doesn’t accept any further i2c requests, neither read nor write. After the write sequence has been terminated by an i2c stop, for ~20ms the pixy doesn’t acknowledge the first address bytes of repeated read/write requests. I have checked this on a scope. Then after 20ms everything works fine again. As workaround I have added a simple wait but it’s a pity to lose a whole frame … I have already had a look at the i2c ISR handler … mhmm, tough code for a Sunday evening.

Would be cool if you could have a look at it.
Cheers,
Helmut

Hello,
On Monday evening ISRs are still tough, but I think I have tracked down the problem:
I guess that the masters’s i2c stop condition is reported to the i2c ISR (beginning of i2c.cpp) by an i2c status @I2C_I2STAT_S_RX_STA_STO_SLVREC_SLVTRX@

lpc43xx_i2c.h
/** A STOP condition or repeated START condition has
 * been received while still addressed as SLV/REC
 * (Slave Receive) or SLV/TRX (Slave Transmit) */
#define I2C_I2STAT_S_RX_STA_STO_SLVREC_SLVTRX	((0xA0))

But this one isn’t caught in the ISR’s switch-case. Thus in the default case

default:
m_i2c->CONCLR = I2C_I2CONCLR_AAC | I2C_I2CONCLR_SIC | I2C_I2CONCLR_STAC;
	break;

the “Assert acknowledge Clear"-bit @I2C_I2CONCLR_AAC@ is set, and so from now on Pixy responds to requests with a NoACKs. I don’t know if the ISR is still being called, as I didn’t found an according status “addressed but rejected with a NACK”-in the lpc4300 user manual.
I guess Pixy stays in this status until @I2c::update()@ is called. As @I2C_I2CONCLR_AAC@ is set, @I2c::update()@ calls @I2c::startSlave()@ that re-enables ACKnowledges by setting the @I2C_I2CONSET_AA@ config bit and Pixy is back and online again.
@I2c::update()@ is called once after sending the blobs in function @blobsLoop()@ in progblobs.cpp. This is kind of compatible with my 20ms (one frame length) recovery from NACK observation.

Possible solution:
If we add a @case I2C_I2STAT_S_RX_STA_STO_SLVREC_SLVTRX@ to the i2c ISR and just do a

case  I2C_I2STAT_S_RX_STA_STO_SLVREC_SLVTRX:
m_i2c->CONSET = I2C_I2CONSET_AA;
m_i2c->CONCLR = I2C_I2CONCLR_SIC;
break;

as in the other cases (see also table 1001 “Slave Receiver mode” page 1130 in the LPC43xx manual), the Pixy i2c receive should be fine … hopefully.
A quick and dirty solution would be to add another @ser_getSerial()->update();@ just behind the two @handleRecv()@ calls in @blobsLoop()@. But that would be rather ugly.
I’m not really sure if the above is correct and as I don’t have the Keil MDK-ARM, I can’t test this on my own (looking forward to the gcc build environment).

It would be great if you could check and fix this.

I have spotted another potential issue (or just a cosmetic glitch) in the functions @Blobs::getBlock (…)@ and @Blobs::getCCBlock(…)@, the callbacks that fill the serial transmission queue, when it runs empty in the ISR:

if (m_mutex || m_blobReadIndex>=m_numBlobs) // we're copying, so no blocks for now....
    {	// return a couple null words to give us time to copy
        // (otherwise we may spend too much time in the ISR)
        buf16[0] = 0;
        buf16[1] = 0;
        return 2;    
    }

It looks like that there are written 2 words to the transmission queue in order to keep the ISR quite and happy, i.e. to suppress unwanted expensive callbacks. But in fact only one word is written to the queue, as the unit of the return value is bytes not words.
So if you aren’t facing timing problem just remove one of the buf16 assignments to make it less confusing. If you really need both words, replace the 2 by a 4 in the return. As my i2c link is awfully slow, I’m happy with only one dummy null word.
Cheers,
Helmut

Hi Helmut,
The NXP I2C controller in Pixy is surprisingly complex! About how many bytes are you writing before you stop getting ACKs? (is that what’s happening?)

thanks!

Hi Rich,
it doesn’t depend on how many bytes the i2c master transmits to Pixy. As soon as the master ends his transmission by a i2c stop condition, Pixy gets into the NACK-NACK-NACK mode and rejects any further request for a frame length. I have re-checked this by sniffing the bus with a scope. Pixy keeps on doing this until Pixy’s i2c controller is reset by a call to @I2c::update(){ … I2c::startSlave() … }@, which happens once per frame in function @int blobsLoop()@ (progblobs.cpp).

I have just had a closer look into the LPC4300 user manual, which is quite good, and I think I have found a simple solution:
In the manual (http://cmucam.org/documents/20) on page 1143 the i2c slave receive states, that are not handled correctly in the ISR, are listed and the actions the ISR should take are given.

Chapter 43: LPC43xx I2C-bus interface
...
43.11 Software example
...
43.11.8 Slave Receiver states
...
43.11.8.6 State: 0x88
Previously addressed with own Slave Address. Data has been received and NOT ACK
has been returned. Received data will not be saved. Not addressed Slave mode is
entered.
1. Write 0x04 to CONSET to set the AA bit.
2. Write 0x08 to CONCLR to clear the SI flag.
3. Exit
...
43.11.8.8 State: 0x98
Previously addressed with General Call. Data has been received, NOT ACK has been
returned. Received data will not be saved. Not addressed Slave mode is entered.
1. Write 0x04 to CONSET to set the AA bit.
2. Write 0x08 to CONCLR to clear the SI flag.
3. Exit
43.11.8.9 State: 0xA0
A STOP condition or Repeated START has been received, while still addressed as a
Slave. Data will not be saved. Not addressed Slave mode is entered.
1. Write 0x04 to CONSET to set the AA bit.
2. Write 0x08 to CONCLR to clear the SI flag.
3. Exit
=> For all three states the manual recommends the same action!

The corresponding cases in Pixy’s i2c ISR look like this (I have removed the comments):
lpc43xx_i2c.h:


#define I2C_I2STAT_S_RX_PRE_SLA_DAT_NACK ((0x88))

#define I2C_I2STAT_S_RX_PRE_GENCALL_DAT_NACK ((0x98))
#define I2C_I2STAT_S_RX_STA_STO_SLVREC_SLVTRX ((0xA0))
i2c.cpp:

void I2c::slaveHandler(){

switch (stat)
{
case I2C_I2STAT_S_RX_PRE_SLA_DAT_NACK:      // 0x88
case I2C_I2STAT_S_RX_PRE_GENCALL_DAT_NACK:  // 0x98
	m_i2c->CONCLR = I2C_I2CONCLR_SIC;
	break;

 default:                                   // 0xA0   OUCH!  => NACK NACK NACK ATTACKS
	m_i2c->CONCLR = I2C_I2CONCLR_AAC | I2C_I2CONCLR_SIC | I2C_I2CONCLR_STAC;
	break;
}

}

Comparing this with the recipe from the user manual:

  • In the first block the setting of the “Assert ACK” flag @m_i2c->CONSET = I2C_I2CONSET_AA;@ is missing
  • A handling for the masters i2c stop condition @I2C_I2STAT_S_RX_STA_STO_SLVREC_SLVTRX@ is completely missing. Thus the default case is entered that forces Pixy by clearing the ACK bit (@I2C_I2CONCLR_AAC@) into the NACK-NACK-NACK mode.

Please try the following:
Just replace in the i2c ISR


case I2C_I2STAT_S_RX_PRE_SLA_DAT_NACK: // 0x88
case I2C_I2STAT_S_RX_PRE_GENCALL_DAT_NACK: // 0x98
m_i2c->CONCLR = I2C_I2CONCLR_SIC;
break;
by

case I2C_I2STAT_S_RX_PRE_SLA_DAT_NACK: // 0x88
case I2C_I2STAT_S_RX_PRE_GENCALL_DAT_NACK: // 0x98
case I2C_I2STAT_S_RX_STA_STO_SLVREC_SLVTRX: // 0xA0
m_i2c->CONSET = I2C_I2CONSET_AA;
m_i2c->CONCLR = I2C_I2CONCLR_SIC;
break;

and with a bit of luck Pixy should receive like a charm.
Please give it a try if you could spare some minutes.
Cheers,
Helmut

Hi Helmut,
Nice work! Thanks for delving into this. We are going to add this today and run it through some tests to make sure it doesn’t break anything. If all seems fine, it will be in the release for this Friday.

thanks!

Hi Rich,
Great! Thank you.
cheers,
Helmut

Hi Helmut, here’s a version to try (attached). Let me know if it helps!

Hi Rich,
Yes it helps: ACK ACK ACK … (-;
First I did a simple test that simply reads and writes some words alternately. No Problem now. Pixy now changes instantly from read to write and vice versa. No single NACK occured.
Also removed the ugly 20ms waits from my driver. Servo requests are now smoothly transmitted to Pixy, after all color blocks of a frame have been read.
Thank you for the quick fix. Great!
cheers,
Helmut

Hi,
I have just noticed that you have -removed- moved the “0”-padding from Blobs::getBlock(…) … to the ISR:

if (m_mutex || m_ccBlobReadIndex>=m_numCCBlobs) // we're copying, so no CC blocks for now....
{ // return a couple null words to give us time to copy
// (otherwise we may spend too much time in the ISR)
buf16[0] = 0;
buf16[1] = 0;
return 2;
}

---->

if (m_mutex || m_blobReadIndex>=m_numBlobs) // we're copying, so no blocks for now....
return 0;

Will you adapt the code also in Blobs::getCCBlock(…) ? (-;

Cheers,
Helmut

Helmut,
I am so happy that you have a Pixy and you are on this forum ---- excellent find!

thanks man! (again)

h2. (-:

The recommended interface for the Pixy is SPI.

Just place a small MCU between the Pixy and the RoboTX controller.

  • The small MCU gets the data from the Pixy through SPI.
  • The small MCU acts as an I²C slave for the RoboTX.

As a benefit, the small MCU processes the data, so the I²C driver for RoboTX only focuses on high level data, like

  • How many elements have been identified?
  • Is there an element corresponding to signature #3?

Hello Rei,

Yes, you are right. I took this option also into account. But I am coding this driver mainly for my daughter, who is in an age at which her top priority mission was to get the Pixy an adequate hair style. So fiddling around with an additional MCU with non-forgiving HW interfaces and less intuitive c(++) programming is a bit too challenging for her.

The data skimming on the additional interface MCU is rather an obligation than a benefit, because you still suffer from the TX/RoboPro i2c bottleneck, now between the interface MCU and the TX. This data filtering/compression has to be configured somehow. If you don’t want to hardcode it, you need to setup a configuration interface, which is also quite some effort. In fact for more advanced applications the most reasonable approach would be to kick out that TX-Controller completely and get the stuff done on a less restricted platform.

Last but not least: Even though programming something more serious with RoboPro tends to give pain, it is also great fun. Sometimes I just like to “paint” SW … the RoboPro Mondrian challenge (-; And for beginners it’s perfectly suited. Simple tasks are so easy to realize. Quick and easy success. Thus I decided to do it in RoboPro.

Cheers,
Helmut