chipKIT® Development Platform

Inspired by Arduino™

Wire onReceive(handler) issue

Created Fri, 30 Mar 2012 17:17:10 +0000 by dc101


dc101

Fri, 30 Mar 2012 17:17:10 +0000

Using the examples code for the Wire library, I'm simply trying to transmit the word Hello from an Arduino Uno to a Chipkit Uno32.

I'm having a problem with the onReceive event handler on the Chipkit, not reporting the correct number of bytes received. On the Chipkit, onReceive seems to always report 1 byte received. However, if I switch the code and make the Arduino the slave, and the Chipkit the master, the Arduino correctly reports 5 bytes received.

I've used the same code on both, making the mandatory send/write received/read changes. At first I thought it was because I was sending a string, but I changed it to a byte array, inserting the correct number of bytes, and the Chipkit still reported 1 byte received. I've also verified that both I2C buses are set to 100KHz in the Wire libs.

Any ideas?

Sender:

#include <Wire.h>

void setup()
{
  Wire.begin(); // join i2c bus (address optional for master)
}

void loop()
{
  Wire.beginTransmission(55); // transmit to device #55
  Wire.send("Hello");        // sends five bytes
  Wire.endTransmission();    // stop transmitting

  delay(500);
}

Receiver:

#include <Wire.h>
char c = 0;

void setup()
{
  Wire.begin(55);                // join i2c bus with address #4
  Wire.onReceive(receiveEvent);  // register event
  Serial.begin(57600);           // start serial for output
}

void loop()
{
  delay(100);
}

void receiveEvent(int howMany)
{
 Serial.println(howMany);
 while(Wire.available()) // loop through all but the last
 {
   c = Wire.read(); // receive byte as a character
   Serial.print(c);         // print the character
 }
}

photomankc

Sat, 31 Mar 2012 05:43:04 +0000

Yes, I have that issue too. The handler will return after each byte received so you will never get a number greater than 1. In my case I had a system where I always knew how many bytes should follow the first so I set up a global counter that incremented with each byte and didnt act until all bytes were collected but I would prefer it worked the way it does on the Arduino.


Ryan K

Sat, 31 Mar 2012 21:30:24 +0000

Hello,

It is due to this section of code in the twi.c file in the wire library utilities folder:

case TW_SR_DATA:
	twi_rxBuffer[0] = ptwi->ixRcv.reg;
	twi_onSlaveReceive(twi_rxBuffer, 1);
	// Release clock line
	ptwi->ixCon.set = (1 << _I2CCON_SCLREL);
	ptwi->ixStat.clr = (1 << _I2CSTAT_I2COV) | (1 << _I2CSTAT_RBF);
	break;

The twi_onSlaveReceive(twi_rxBuffer, 1); always has a 1 as the parameter and will be called every time data is received. Hopefully I'll have time soon to adjust that section to count and wait until a transmission is done.

Best Regards, Ryan K Digilent


dc101

Sun, 01 Apr 2012 00:08:58 +0000

Ryan,

Thanks for pointing me in the right direction.

I thought it might be somewhere around that section of code, but wasn't sure.

At first glance, it doesn't seem like a difficult fix. I tried taking the TW_MR_DATA code, modifying it slightly, adding some TW_SR_ACK and NACK_SENT code, but I just ended up breaking it. Now I get nothing :) Ohwell, I'll try again tomorrow.

It seems like this should work though, as it's essentially what the standard Arduino code does. I have a feeling that it's missing some I2C processing though, be it a stop/start/ack or nack.

-Tim


photomankc

Mon, 23 Apr 2012 03:51:57 +0000

Hello, It is due to this section of code in the twi.c file in the wire library utilities folder:

case TW_SR_DATA:
twi_rxBuffer[0] = ptwi->ixRcv.reg;
twi_onSlaveReceive(twi_rxBuffer, 1);
// Release clock line
ptwi->ixCon.set = (1 << _I2CCON_SCLREL);
ptwi->ixStat.clr = (1 << _I2CSTAT_I2COV) | (1 << _I2CSTAT_RBF);
break;

The twi_onSlaveReceive(twi_rxBuffer, 1); always has a 1 as the parameter and will be called every time data is received. Hopefully I'll have time soon to adjust that section to count and wait until a transmission is done. Best Regards, Ryan K Digilent

Any ideas when we might expect this? It would completely break my current code but would be well worth it not to have to count bytes for every command.


Ryan K

Fri, 27 Apr 2012 00:41:29 +0000

Hello,

I haven't had a chance to take a good look at it, I've been quite busy here. I'll see what I can do in the upcoming weeks. I hope to have a fix for it by mid May.

Best Regards, Ryan K Digilent


TheInterestingObject

Thu, 17 Jan 2013 22:01:09 +0000

This problem still persists. Is there anyone out there that has a fix for this problem? Is there a new time frame for when this library might get an official fix?


avenue33

Tue, 22 Jan 2013 19:05:13 +0000

I faced the very same problem and found the issue was referenced in the forum.

The twi_onSlaveReceive(twi_rxBuffer, 1); always has a 1 as the parameter and will be called every time data is received. Hopefully I'll have time soon to adjust that section to count and wait until a transmission is done.

Ryan, could you please give a look at the twi.c file? Thanks!


avenue33

Sat, 26 Jan 2013 23:41:02 +0000

I opened an issue at the GitHub repository.

See :arrow: https://github.com/chipKIT32/chipKIT32-MAX/issues/310


mkirby

Thu, 07 Feb 2013 19:06:45 +0000

Just a heads up to everybody on this thread.....I'm an employee for Digilent, and I am currently looking into fixing this issue.


avenue33

Thu, 07 Feb 2013 21:01:42 +0000

Hi!

Thank you for your answer and feel free to release betas so we can test them.

Good luck with the development!


mkirby

Tue, 12 Feb 2013 02:01:22 +0000

The issue unfortunately is inherent to how the interrupt system works for the I2C bus on pic32 microcontrollers. There is no stop bit received interrupt for the slave side of transmission. This means that data reception cannot be driven by an interrupt routine because there's no way to know when transmission is complete. To counter for this, the original library was built to only allow for one data byte transmission. The problem can be fixed by doing away with the interrupt driven method and instead writing a function for it, but this would cause the program to block when waiting for a message. In short, no solution to this problem is going to solve everything, so we're trying to decide which issues to tackle with the updated library.


avenue33

Tue, 12 Feb 2013 04:55:51 +0000

Thank you for the update. It seems to be really tricky.


avenue33

Sun, 24 Mar 2013 15:28:49 +0000

Hi!

Any news on this issue?

Sadly, it isn't included in the priority list available at Get involved with the chipKIT development community :(

Thanks.


Jacob Christ

Sat, 08 Feb 2014 15:36:19 +0000

I've created a fix that allows chipKIT I2C to behave like Atmel Arduino I2C with stop bit interrupt. The workaround comes at the cost of burning a few hundred CPU cycles in the slave interrupt at the end of each received byte to wait for detection of the stop bit. There is also the possibility of missing a stop bit if it comes later than the wait. I've tested this in MPIDE version 20130715 using two Fubarino SD's .

If some other people can verify that this doesn't break there code and solves the problem I think we can get it rolled into the chipKIT core.

Jacob

The modification to twi.c that is needed to make this work is here:

case TW_SR_DATA:
			//twi_rxBuffer[twi_rxBufferIndex] = (uint8_t)((ptwi->ixStat.reg & 0xff00) >> 8);
			//twi_rxBufferIndex++;
			//twi_rxBuffer[twi_rxBufferIndex] = (uint8_t)(ptwi->ixStat.reg & 0x00ff);
			//twi_rxBufferIndex++;
			
			if( twi_rxBufferIndex < TWI_BUFFER_LENGTH )
			{
				twi_rxBuffer[twi_rxBufferIndex] = ptwi->ixRcv.reg;
				twi_rxBufferIndex++;
			}
			
			// Release clock line
			ptwi->ixCon.set = (1 << _I2CCON_SCLREL);
			ptwi->ixStat.clr = (1 << _I2CSTAT_I2COV) | (1 << _I2CSTAT_RBF);

			// Burn a few hundred cycles to wait for the stop condition
			// (burns about four cycles per iteration)
			int i;
			for(i = 0; i < 250; i++)
			{
				asm volatile("nop");
			}

			// If the stop flag is set then invoke the twi_onSlaveRecieve callback
			if(ptwi->ixStat.reg & (1 << _I2CSTAT_P) )
			{
				twi_onSlaveReceive(twi_rxBuffer, twi_rxBufferIndex);
				twi_rxBufferIndex = 0; // Reset the index to zero
			}

			break;

The send code:

#include <Wire.h>

void setup()
{
  Wire.begin(); // join i2c bus (address optional for master)
  Serial.begin(115200);           // start serial for output
  delay(3000);
  Serial.println("sender...");
}

void loop()
{
  Serial.println("sending...");
  Wire.beginTransmission(55); // transmit to device #55
  Wire.send("012345678901234567890123456789");        // sends five bytes
  Wire.endTransmission();    // stop transmitting

  delay(1000);
}

The receive code (without exposed I2C register):

#include <Wire.h>
char c = 0;

void setup()
{
  Wire.begin(55);                // join i2c bus with address #4
  Wire.onReceive(receiveEvent);  // register event
  Serial.begin(115200);           // start serial for output
  delay(3000);
  Serial.println("receiver...");
}

void loop()
{
  delay(100);
}

void receiveEvent(int howMany)
{
  Serial.print(howMany);
  Serial.print(" ");
  while(Wire.available()) // loop through all but the last
  {
    c = Wire.receive(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  Serial.println(" ");         // print the character
}

The receive code (with some an exposed I2C register):

#include <p32xxxx.h>
#include <pins_arduino.h>
#include <p32_defs.h>
#include <Wire.h>
char c = 0;

static p32_i2c *	ptwi = (p32_i2c *)_I2C1_BASE_ADDRESS;

void setup()
{
  Wire.begin(55);                // join i2c bus with address #4
  Wire.onReceive(receiveEvent);  // register event
  Serial.begin(115200);           // start serial for output
  delay(3000);
  Serial.println("receiver...");
}


void loop()
{
  static int state = 0;

  if(state == 0 && ptwi->ixStat.reg & (1 << _I2CSTAT_S) )
  {
    Serial.println("start");
    state = 1;
  }
  if(state == 1 && ptwi->ixStat.reg & (1 << _I2CSTAT_P) )
  {
    Serial.println("stop");
    state = 0;
  }
}

void receiveEvent(int howMany)
{
  Serial.print(howMany);
  Serial.print(" ");
  while(Wire.available()) // loop through all but the last
  {
    c = Wire.receive(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  Serial.println(" ");         // print the character
}

avenue33

Sat, 08 Feb 2014 15:46:19 +0000

Thank you! I'll test the code.

:)


Jacob Christ

Sat, 08 Feb 2014 16:02:53 +0000

If the stop bit is not being detected try to bump up the number of itterations in the delay loop:

// Burn a few hundred cycles to wait for the stop condition
         // (burns about four cycles per iteration)
         int i;
         for(i = 0; i < 250; i++)
         {
            asm volatile("nop");
         }

Jacob


Jacob Christ

Wed, 12 Feb 2014 18:01:21 +0000

So the other day I had a concern that this fix might not work if two messages came in really fast (as the delay in looking for the stop bit in the interrupt may be too long if two messages came in really fast).... To test my concern I modified the send program to transmit two messages really fast then delay for 1 second. The good news is that this didn't break the patch.

Jacob

#include <Wire.h>

void setup()
{
  Wire.begin(); // join i2c bus (address optional for master)
  Serial.begin(115200);           // start serial for output
  delay(3000);
  Serial.println("sender...");
}

void loop()
{
  Serial.println("sending...");
  Wire.beginTransmission(55); // transmit to device #55
  Wire.send("Hello");
  Wire.endTransmission();    // stop transmitting
  Wire.beginTransmission(55); // transmit to device #55
  Wire.send("World!");
  Wire.endTransmission();    // stop transmitting

  delay(1000);
}

avenue33

Mon, 17 Nov 2014 17:39:46 +0000

I went back to my project based on I²C and tried the solution. It works!

The only modification on the slave side was the number of cycles.

// Burn a few hundred cycles to wait for the stop condition
// (burns about four cycles per iteration)
long i;
for (i = 0; i < 290; i++)
{
    asm volatile("nop");
}

On the master side, an Arduino Uno, a short delay was needed between two consecutive sendings.

delay(20);

Thank you!


avenue33

Sat, 29 Nov 2014 13:42:29 +0000

Any news?

The solution proposed by Jacob Christ at TWI I2C Wire Library Limited to 1 Byte in Slave Mode #310 helps a lot but isn't fully satisfactory as it requires changing the number of loops for each master.

This is a serious limitation for the PIC32 MCU. I'm afraid I'll have to consider another MCU from another manufacturer because of that specific missing feature.


screamingtiger

Thu, 07 May 2015 04:09:02 +0000

Is there a fix yet or an alternative library to use? You need at least 2 bytes because I need the register and the value.


photomankc

Fri, 08 Jul 2016 18:49:28 +0000

This is interesting. After 3 years I'm back again at designing an I2C stepper drive and this is still an issue. I guess based on the information on how the Pic32 handles things it's simply unlikely to ever be any different. I had dumped my previous code but remembered there was a complication with I2C on this platform and glad I bumped back into this thread. I'll make my messages fixed-length, a little less efficient on the bus but easier to deal with. I suppose one could also work up a protocol where byte 2 contains the number of bytes in the message to make variable length messages possible.

It's a reasonable trade-off for the speed of the Pic32 but man I2C is easier on the Arduino. Sounds like it would be best to code around this issue in our code rather than to implement something that needs to be tweaked for each scenario. I think this time around i'll work up an i2c-packet object that will handle keeping track of the bytes and if they all got transmitted or not.


avenue33

Fri, 08 Jul 2016 19:33:12 +0000

For my projects based on I²C slave, I'm using now silicon form Texas Instruments, mainly the MSP430G2553 and the Cortex-M4 TM4C123.

The MCU adjusts to standard mode (100 kHz), fast mode (400 kHz) and even fast mode+ (1 MHz for the TM4C) with no need for extra code.