Pages: [1]   Go Down
Author Topic: Checking for data on the I2C bus (without Wire.h)  (Read 1059 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 10
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi! Here's a little problem that has had me stumped for the last couple of days, and I think it's time I throw in the towel and ask for some help smiley

I have have a Peggy2 pegboard ("Peggy"), which is essentially an Arduino Duemilanove with a 328p and a LOT of LEDs. (http://evilmadscience.com/productsmenu/tinykitlist/75). This talks to another board, "MegaMan", an Arduino MEGA2560, over I2C.

For reasons I'd rather not spend too long arguing (I do notice that the "use the Wire library for heaven's sake" answer is the most common here and for good reason I'd say smiley-wink ), I cannot use the Wire library on Peggy. On MegaMan, I do though. Peggy is a receiving slave and MegaMan a transmitting master. What MegaMan transmits to Peggy is either a header which indicates what animation Peggy is supposed to display (hardcoded into Peggy and referred to by a number after the header), or simply a full framebuffer, allowing Peggy to "stream" whatever MegaMan sends it, like for example a scrolling text. This all works quite well.

I do have one problem though, and that is that whenever I make Peggy run a built-in animation, which can take several seconds to complete, I want it to immediately interrupt that animation if new data is received on the I2C bus. I do for example have a "busy" animation (equivalent to the hour glass in Windows or Spinning Beach Ball of Death on Mac) that Peggy runs when waiting for data from MegaMan under certain conditions.

The I2C implementation on Peggy is *not* interrupt driven, as the screen update is run on a timer interrupt, and I spend most of my time on Peggy sitting in a while-loop waiting for a state change (it is after all the only purpose of Peggy, so there was no need to get very fancy):
Code:
while (!(TWCR & (1<<TWINT))) { } // wait for TWINT to be set

When the state change occurs, I do a
Code:
switch (TWSR)
, and if TWSR is either TWI_SRX_ADR_DATA_ACK or TWI_SRX_GEN_DATA_ACK (received a byte of data), I read a byte from the TWDR and reset the control register with
Code:
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)
Then I do stuff based on what the byte I received was, for example run my "waiting" animation.

There is additional error handling (I basically ignore errors, as a dropped frame or animation matters little) and such, and it again works very well. So far so good.

The problem arises when I try to build a mechanism to abort my animation upon data received on the I2C bus. I quite simply cannot get it to abort. The pseudo code for a typical animation is very simplified:

Code:
switch (animation) {
 case (1) : // "busy" animation
  for(iterations = 10; iterations >0; iterations--) {
   if (abortFlag) return; // if the abort flag (a volatile boolean) is set, we return back to reading data from the I2C bus
   drawBusyAnimationFram(iterations);
  }
 break;
}

The abortFlag is set to false after each completed byte read from the I2C bus.

I then tried using the ISR (TIMER0_COMPA_vect) whose only purpose is to update the "screen", i.e. Peggy's 625LEDs, to put a check for new data on the I2C bus and update the abortFlag correspondingly:

if (TWDR == 0xde) abortFlag = true; did not work (never triggered) - I tried it as 0xde is the start of the header MegaMan sends every time it has new data
if (TWCR & (1<<TWINT)) abortFlag = true; did not work (triggers all the time and prevents animations from running)
if ( (TWCR & (1<<TWINT)) && (TWSR == TWI_SRX_ADR_DATA_ACK) ) abortFlag = true; did not work (never triggers)
if (TWSR == TWI_SRX_ADR_DATA_ACK) abortFlag = true; did not work (never triggers)

This could of course be done without interrupts - one could simply substitute the if (abortFlag) return; with one of the checks above, but that didn't make a difference, and I'd rather not miss something that happens while I'm doing the animation, so I suppose the timer interrupt is better. I also tried using a dedicated I2C interrupt, like so (with the checks above):
Code:
ISR (TWI_vect)
{
if (TWSR == TWI_SRX_ADR_ACK) {
abortFlag = true;
}
}

I here got the same result as putting it in the timer interrupt. I must admit I'm at my witt's end here. Is there a tried & tested way of simply answering the question: do I have data waiting for me at the I2C bus, or better yet: is the data waiting for me at the I2C bus 0xde?
Logged

Global Moderator
Melbourne, Australia
Offline Offline
Brattain Member
*****
Karma: 511
Posts: 19306
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I have have a Peggy2 pegboard ("Peggy"), which is essentially an Arduino Duemilanove with a 328p ... I cannot use the Wire library on Peggy.

Why not?
Logged

http://www.gammon.com.au/electronics

Please post technical questions on the forum - not to me by personal message. Thanks a lot.

Offline Offline
Newbie
*
Karma: 0
Posts: 10
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Why not?

Because I don't want the overhead (I don't need error checking etc. and I do need every last drop of performance), don't want the interrupt (a while loop fits me better in this case), but do want complete control and, more importantly, do want to teach myself how to do I2C without using the library as I might need that outside the Arduino universe - and it's a challenge!  smiley

Just to prevent the potential avalanche of responses regarding the many benefits of Wire.h, let me just say that I know, but it is a final design decision smiley

I am simply trying to find out, without the help of libraries, how can I perform the (I sincerely hope) simple act of checking non-destructively for data on the I2C bus directly, and ideally if that data is a particular value, 0xde in my case.
Logged

Global Moderator
Melbourne, Australia
Offline Offline
Brattain Member
*****
Karma: 511
Posts: 19306
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Well it certainly should be possible. All the interrupt does is alert you to a status change, and that status change will be reflected in a register somewhere.

Quote
I do need every last drop of performance

Can you put some figures to this? What speed are you planning to run I2C? How fast do you need to react? Are you needing to do anything else, other than checking for the incoming data?

Quote
whenever I make Peggy run a built-in animation

Animations are typically drawn in something like 25 or 30 FPS. This isn't particularly fast.

Quote
I might need that outside the Arduino universe

The universe as you call it isn't particularly an Arduino one. I have stuff running on bare Atmega328 chips. I may use the Arduino IDE to program them, because that is easy. I'm not sure what you mean by that.
Logged

http://www.gammon.com.au/electronics

Please post technical questions on the forum - not to me by personal message. Thanks a lot.

Global Moderator
Melbourne, Australia
Offline Offline
Brattain Member
*****
Karma: 511
Posts: 19306
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
if (TWDR == 0xde) abortFlag = true; did not work (never triggered) - I tried it as 0xde is the start of the header MegaMan sends every time it has new data
if (TWCR & (1<<TWINT)) abortFlag = true; did not work (triggers all the time and prevents animations from running)
if ( (TWCR & (1<<TWINT)) && (TWSR == TWI_SRX_ADR_DATA_ACK) ) abortFlag = true; did not work (never triggers)
if (TWSR == TWI_SRX_ADR_DATA_ACK) abortFlag = true; did not work (never triggers)

If I may suggest, if you have problems with code post the whole thing. Not just snippets.
Logged

http://www.gammon.com.au/electronics

Please post technical questions on the forum - not to me by personal message. Thanks a lot.

Offline Offline
Newbie
*
Karma: 0
Posts: 10
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Well it certainly should be possible. All the interrupt does is alert you to a status change, and that status change will be reflected in a register somewhere.
That's my reasoning too, but as I am a) rather inexperienced with I2C and b) tried most of the registers that I could think of, I am now officially stumped smiley

Quote
Quote
I do need every last drop of performance

Can you put some figures to this? What speed are you planning to run I2C? How fast do you need to react? Are you needing to do anything else, other than checking for the incoming data?
Actually, the I2C speed is not an issue (I am using the Wire library on "MegaMan", i.e. the I2C master, to send data to Peggy). It's rather that I have nothing to spare, as the SPI communication between Peggy's AVR and four multiplexers that switches the LEDs on and off on the display eats most of the CPU time. The LEDs can only be on/off, and I need 16 tones of gray, so I use interlacing, and I need to do this *fast*. This means that I spend most of the power on managing the LEDs (pretty much like an old TV set). The current framebuffer containing the current values of all the LEDs (325bytes), needs to be transmitted to the display about 80 times/second minimum. In other words: I don't need the I2C communication to be incredibly fast, I just need it to be very light.
Quote
Quote
whenever I make Peggy run a built-in animation

Animations are typically drawn in something like 25 or 30 FPS. This isn't particularly fast.
Agreed, it's the interlacing that makes me run out of juice, as if I did "black&white" it would be fine, but with 16 iterations per frame to get grayscale, I ramps up quite a bit. But again, I2C speed in itself is not important, it's just to make the code very light so it fits in with the interrupt that updates the screen over SPI.
Quote
Quote
I might need that outside the Arduino universe

The universe as you call it isn't particularly an Arduino one. I have stuff running on bare Atmega328 chips. I may use the Arduino IDE to program them, because that is easy. I'm not sure what you mean by that.
A valid point, and like I said in the beginning, part of the reason why I would like to do this is to familiarize myself with exactly how this works. It's always easier to use the libraries of course, but when trying to understand how something really works, it can be both fun and educational to Do It Yourself(tm) smiley

Oh, and of course, here it's a matter of trying to slim down the overhead. Particularly when streaming live video over the I2C link. In such cases one does for example not care if a frame is lost - all that matters is speed, and with the SPI communication to the muxes, that works with very little left to spare.

The nice thing is that it all works perfectly, except that is, that I for the life of me cannot understand how to manually check if I should abort my animation and return to reading I2C in anticipation of new "orders" from MegaMan. Having read the datasheet to exhaustion I feel that I have tried all the sensible alternatives, yet I still can not get that simple check to trigger when new data has arrived...
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 10
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

If I may suggest, if you have problems with code post the whole thing. Not just snippets.

Certainly, the issue is only that the code is quite exhaustive. Nevertheless, here goes:
Setting up the slave:
Code:
void initTwiSlave(uint8_t addr)
{

PORTC |=  _BV(5) | _BV(4); // enable pullups

TWAR = (0<<TWGCE) |((uint8_t) (0xff & (addr<<1)));    // set slave address, no general call address
TWDR = 0xff; // Default content = SDA released

TWCR = (1<<TWINT) | // "clear the flag"  (clear I2C interrupt)
(1<<TWEA) | // send acks to master when getting address or data
(0<<TWSTA) | // not a master, cant do start (don't generate start condition)
(0<<TWSTO) | // Don't generate stop condition (doc says set these to 0)
          (0<<TWWC) |
(1<<TWEN) | // hardware I2C enabled
(0<<TWIE); // disable I2C interrupts

while (TWCR & (1<<TWIE)) { } // wait for the I2C transceiver to get ready /

}

Reading a byte from I2C:
Code:
uint8_t getTwiByte(void)
{
uint8_t result=0;

  keepListening:

  // wait for an state change
  while (!(TWCR & (1<<TWINT))) { } // wait for TWINT to be set


  //uint8_t sr = TWSR;
  switch (TWSR)
  {
    case TWI_SRX_ADR_DATA_ACK:  // received a byte of data data
    case TWI_SRX_GEN_DATA_ACK:
result = TWDR;
      TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);
break;


//        case TWI_SRX_GEN_ACK_M_ARB_LOST:
//        case TWI_SRX_ADR_ACK_M_ARB_LOST:
    case TWI_SRX_GEN_ACK:
    case TWI_SRX_ADR_ACK:      // receive our address byte
      TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);
      goto keepListening;
      break;

    case TWI_SRX_STOP_RESTART:       // A STOP or repeated START condition was received
    TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);
    goto keepListening;
      break;

    case TWI_SRX_ADR_DATA_NACK:   // data received, returned nack
    case TWI_SRX_GEN_DATA_NACK:
      TWCR = (1<<TWEN)|(1<<TWINT);
      goto keepListening;
break;

    case TWI_NO_STATE:
    goto keepListening;
    break;

    default:      // something bad happened. assuming a bus error, we try to recover from this
      // reset state and continue
      TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO);    // ignore
  while(TWCR & (1<<TWSTO)){ } // wait for stop condition to be exectued; TWINT will not be set after a stop
  result = 0xff;
      break;
    }
abortFlag = false; //debug debug

return result;
}

Managing the I2C input (this function never returns):
Code:
void serviceInputData(void)
{
uint8_t *ptr = frameBuffer;
uint8_t state = 0;
int counter = 0;
while (1)
{
uint8_t c = getTwiByte();

// very simple state machine to look for 6 byte start of frame
// marker and copy bytes that follow into buffer
if (state  <6) // state < 6 means we are not in the process of receiving frameBuffer data from MegaMan
{
          /*
          * We examine the byte we get from I2C, looking to see if we're building up a 0xdeadbeef1? header
          * Once we have 0xdeadbeef1 (we're then in state 5) we look at the following byte. If it is 1, we
          * read one more byte and execute the corresponding built-in graphical function with runCommand(),
          * if *not*, we get ready run display a 325B frameBuffer that follows from I2C, i.e. then we are in
          * "slave" mode, simply displaying whatever MegaMan sends us, until we have displayed one full
          * frameBuffer and we start looking for a new header again
          */

if      (state == 0 && c == 0xde) state++; // switching state to 1
else if (state == 1 && c == 0xad) state++; // switching state to 2
else if (state == 2 && c == 0xbe) state++; // switching state to 3
else if (state == 3 && c == 0xef) state++; // switching state to 4
else if (state == 4 && c == 0x01) state++; // switching state to 5 (from the next byte we determine what to do with the data)

      else if (state == 5 && c == 0x01) { // state is 5 AND we have a command header (0xdeadbeef11)
      runCommand(getTwiByte()); // sending the next byte, after de-ad-be-ef-01-01, to runCommand();
      state = 0; // command executed, resetting state and starting over
      } else if (state == 5 ) { // state is 5, but NO command header (0xdeadbeef10)
state++; // switching to state 6
counter = 0; // preparing the byte counter for transmission of 325B frameBuffer
ptr = frameBuffer; // pointing ptr to beginning of Peggy's frameBuffer
} else state = 0; // error: deviation from 0xdeadbeef1?, reset and start over

} else {
// inside of a frame, so save each byte to buffer - this is "slave mode" where we blindly display what MegaMan sends us
*ptr++ = c; // insert most recently read byte from I2C into frameBuffer on Peggy
counter++;
if (counter >= DISP_BUFFER_SIZE) {
// buffer filled, so reset everything to wait for next frame
counter = 0;
ptr = frameBuffer;
state = 0;
}
}
} // eternal while loop - never ends
}

As you can see here, the runCommand(getTwiByte()); is what we call if we want to run an animation. This is a large function that for every animation, does the if(abortFlag) return; in every iteration. Once the flag is set, it returns here.

Finally the interrupt. Notice that only the first couple of lines relate to the I2C, the rest is the screen update, but I included it to get it in context. The comented out lines are different approaches I tried. All to no avail.
Code:
ISR (TIMER0_COMPA_vect)
{

//if ( (TWCR & (1<<TWINT)) && (TWSR == TWI_SRX_ADR_DATA_ACK) ) abortFlag = true;
if (TWSR == TWI_SRX_ADR_DATA_ACK) abortFlag = true;
//if (TWCR & (1<<TWINT)) abortFlag = true;
//if (TWDR == 0xde) abortFlag = true;

/*
* There are 15 passes through this interrupt for each row per frame - ( 15 * 25) = 375 times per frame.
* during those 15 passes, a led can be on or off.
* if it is off the entire time, the perceived brightness is 0/15
* if it is on the entire time, the perceived brightness is 15/15
* giving a total of 16 average brightness levels from fully on to fully off.
* currentBrightness is a comparison variable, used to determine if a certain
* pixel is on or off during one of those 15 cycles.
* currentBrightnessShifted is the same value left shifted 4 bits:  This is just an optimization for
* comparing the high-order bytes.
*/
if (++currentBrightness >= MAX_BRIGHTNESS)
{
currentBrightness=0;
if (++currentRow > 24)
{
currentRow =0;
currentRowPtr = frameBuffer;
}
else
{
currentRowPtr += 13;
}
}
Logged

Global Moderator
Melbourne, Australia
Offline Offline
Brattain Member
*****
Karma: 511
Posts: 19306
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Well I have a certain amount of sympathy. I was doing a VGA monitor here and I have timing issues. And I wanted to receive I2C data for updating the screen.

In the end I put up with a bit of flicker.

The low-level I2C stuff looks so complicated (all those modes) that I haven't looked at exactly how the hardware registers work.

Your approach looks like it might work. There could be timing issues. I don't know whether you might get away with disabling interrupts during the time-critical part, and then enabling them and letting the I2C interrupts through.

Your code in getTwiByte looks to me like you have a loop waiting for stuff, so I'm not too sure how that is faster than interrupts. Interrupts have an overhead (around 1.5 to 3 uS) but so does sitting around waiting.
Logged

http://www.gammon.com.au/electronics

Please post technical questions on the forum - not to me by personal message. Thanks a lot.

Offline Offline
Newbie
*
Karma: 0
Posts: 10
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Now that was a cool project! (the VGA monitor) smiley

In my case it's a little bit more manageable in the sense that I have 25*25 = 625 "pixels" (LEDs) with only 4bit for each (16 tones of gray), so for a full screen it's only 13 bytes per row with 25 rows = 325 bytes to hold the full screen. It's happily scrolling a large font message streamed "live" from MegaMan (the Arduino Mega 2560 that is the I2C master) behind me as I write this.

You're right in that I avoid interrupts for the I2C bit. This is because the screen refresh is very timing sensitive indeed. And Peggy (as this pegboard is called) only really does two things: 1) constantly update the screen through a timer interrupt with whatever is currently in the frameBuffer (a 325B char array), and 2) wait for I2C input and trigger corresponding actions. Those actions are either a) display whatever data comes over I2C raw (like for my scrolling message), or b) run an animation (like the "please wait"-animation).

This is the reason I don't have an interrupt for the I2C: reading I2C data is the only purpose of it, so an eternal loop processing I2C data is really all I need. Normally I'd written that in the main loop() function, but the gentleman who wrote the original code took a "standard C" approach with main() which in turn calls serviceInputData().It's really serviceInputData() that serves as the loop() equivalent in this code. I thought about making it more Arduino'ish, but I did initially not want to make changes to the code. The original code only streamed data over I2C with no animation functions. This was done because the original project was to stream live video from a webcam to the LED display. Which, if I may say so, looked pretty awesome smiley

It was however not so useful for me, who wanted it to report status messages & such, and I decided to move some of the animation (like fade-out, fade-in, wait, error and such) to the Peggy, so that MegaMan would be free do some other things than just generate images to be sent vi I2C to Peggy. MegaMan also plays MP3 files and communicates with the third and last member of what is actually a trio of Arduinos/clones over XBee wireless (the last guys is called Wally, and is a living-room sentry turret with movement sensors and lasers - don't ask, I have too much free time on my hands at the moment!). smiley-grin

Anyway, my hope was that perhaps some I2C guru could slap me with a trout and say something like "why don't you simply check the XXXX register afters setting the Y bit you silly noob" or something, because I can't believe it's not doable. But, as I've exhausted all the options that seemed to make sense to me to no avail, and despite reading the TWI/I2C section of the data sheet to exhaustion, I still can not understand why not any of my checks would trigger when the next message comes in (or always trigger, which is just as useless)...
Logged

Pages: [1]   Go Up
Jump to: