TVout with I2C and other interrupts

Here's my setup: 4 encoders, tv, and an I2C connection to another MCU. The other MCU will be requesting information 2 or 3 times a second.

Ideally, I would love to have the encoders interrupt driven, but that messes with the display whenever they're being used. I haven't tried the I2C yet, but from reading and knowing it's interrupt based, there's bound to be issues there too.

I'm using Enableinterrupt to handle the interrupts and Rotary to handle the encoders. When not using the encoders, the image is rock solid. When using them, not so much. Is there any way to have other interrupts only trigger on the verticle blanking interval. I know I can have the encoders work via poling, but that just feels way too dirty.

I can solve the encoder issue with poling, but how do I fix the I2C issue? The amount of information sent will likely be 4 bytes (the status of each encoder) plus whatever overhead I2C uses.

Any information you geniuses can provide would be amazing.

Edit: Forgot to mention, the main (other) MCU will also be running TVout, so just pushing data to it will cause an issue on that end.

Any information you geniuses can provide would be amazing.

Flattery and 10 cents used to get you cup of coffee.

How about hand drawn outline of your setup with connections and without giving away any trade secrets - general description HOW it is suppose to operate? ( Personally - it is your business WHAT it does)
Perhaps explaining how you propose to run 4 (encoder?) interrupts on WHAT hardware and I2C via interrupt also.
Maybe you are just using wrong terminology ?

It's just 4 rotary encoders hooked up to an Uno (also have Mega available, but only use those when I have to). The tvout connections are connected the normal way.
Encoders will be hooked up to any pins as pin change interrupts. I2C will be hooked up via SDA and SCL ( think it's a4 and a5 on the uno, but it's been a while).

The one I'm having an issue with will be an I2C slave. The master will be requesting information from it. The master will also have a tvout connection.

scoticus:
I know I can have the encoders work via poling, but that just feels way too dirty.

Yes, that's probably the best way.

Instead of I2C, I would suggest a polled serial interface between the two Arduinos. More information here.

With only 4 bytes to send, you should be able to time this easily, mostly when transmitting. You can send 2 characters before it starts blocking.

On the receiving side, you just call USART_receive when it's convenient. Then check for available chars when it's convenient.

Like I2C, the MCU hardware takes care of shifting bits out for you. Unlike I2C, it's asynchronous, so the hardware can do the work when it's convenient (i.e., TVOut is not too busy).

Hi /dev

I actually read that already, but honestly didn't understand it completely. I'm thinking of reworking my approach to maintain future sanity. The final product will actually have 3 different displays, each with controls for it (it's an escape room type game). Initially I wanted each one as it's own particular block, but now after your response I'm thinking that's going to get incredibly complicated incredibly quickly.

What I'm thinking is having 1 central mega mcu that recieves all the different control information. (right now there will be probably about 10 buttons, 6 encoders, maybe some other stuff here and there). This would also allow me to have them interrupt driven (I tried polling the encoders and it missed way too many pulses). This would then send the data out via SPI to individual nano's that would control each display.

Just wondering (before I spend all this time hooking it up) if you'd see any issue with that. I'm unfamiliar with SPI, but guess there's no time to learn it but the present.

scoticus:
This would then send the data out via SPI to individual nano's that would control each display.

SPI is also synchronous, so the receiving unit may not like when the data arrives.

I would suggest RS-485 serial. These can be wired together to form a serial "network". It's also more immune to noise, especially if you have the displays more than a few inches away.

Serial-to-RS-485 modules are very cheap and use almost any pair of wires for the connection. And you can do testing with direct connections, if you want. It's also easy to add a "listener" node that displays all the traffic on the network, whether it's from the master or the slave.

You can make this harder, but... :wink:

Hey /Dev

Thanks so much again for your help. I just realized that the mega has 3 different serial ports, so that should make the communication with the three monitors considerably easier. I've never used the USART receive before. Are you saying that if I do a serial print to that line, it will go into the buffer on the other end, then I can just read the contents of the buffer at my leisure? Will the buffer just overwrite when I send more data?

I remember handling all this manually back in my PIC days, but it's been around 10 years.

Thanks again

This is working perfectly!! For reference, here’s the sending and receiving code:

Sender:

#define EI_ARDUINO_INTERRUPTED_PIN
#include <EnableInterrupt.h>
#include <Rotary.h>
#define intRE1 2
#define RE1 3
Rotary r = Rotary(intRE1, RE1);
int count = 0;
int RE1State;

void setup() {
  Serial3.begin(9600);
  Serial.begin(9600);
  pinMode(intRE1, INPUT_PULLUP);
  pinMode(RE1, INPUT_PULLUP);
  enableInterrupt(intRE1, RE1Int, CHANGE);
  enableInterrupt(RE1, RE1Int, CHANGE);
  RE1State = digitalRead(intRE1);
}

void loop() {
}
void RE1Int() {
  unsigned char result = r.process();
  switch (result) {
    case DIR_NONE:
      break;
    case DIR_CCW:
      count++;
      Serial3.write(count);
      break;
    case DIR_CW:
      count--;
      Serial3.write(count);
      break;
  }
}

Receiver:

#include <TVout.h>
TVout TV;
int test = 1;
int oldrec = 1;

void setup() {
  //  Serial.begin(9600);
  TV.begin(NTSC, 100, 96);
  Serial.begin(9600);
}

void loop() {
  //  TV.clear_screen();

  if (Serial.available() > 0) {
    test = Serial.read();
    //   TV.clear_screen();
    TV.draw_rect(20, 20, oldrec, 56, BLACK);
    TV.draw_rect(20, 20, test, 56, WHITE);
    oldrec = test;
  }
}

I’ve noticed that I have to unplug the TX and RX wires when I’m uploading code because it uses the serial pins to upload the code. No big deal, but took me a bit to figure out why it wasn’t working.

Also wondering if you see any further issues popping up with this method as things get more complex.

I've never used the USART receive before.

Serial, Serial1 2 and 3 are the variables that control the MCU USARTs.

So I'm pretty sure you've done a print. Doing a receive is just as simple:

 if (Serial.available ()) {
    char c = Serial.read ();

    // do something with c...

Read some of the serial communication examples here, especially Serial Input Basics.

Are you saying that if I do a serial print to that line, it will go into the buffer on the other end, then I can just read the contents of the buffer at my leisure? Will the buffer just overwrite when I send more data?

For typical interupt-driven Serial sketches, yes.

For the polled serial library, it cannot buffer more than 2 characters, because that's all the MCU USART can hold on to while TVout is busy. Checking for characters every time through loop should be fast enough. You will store them in your own buffer until it's convenient to look at them. And respond.

Also wondering if you see any further issues popping up with this method as things get more complex.

Only if the serial or encoder ISRs take too long. :slight_smile:

Yeah, I always write the worst ISR’s and optimize them later. I really have to get out of that habit.

Ok, so everything worked great, until I added extra encoders and got to the point where I was sending more than one byte. Everything works great up until sending 256, then nothing happens. So it’s going to display each encoder as four different bars on the screen. It gets to the limit of the first fine. Then when I try the second, it gets to 56 and stops. I tried waiting until 2 bytes were recieved, and shifting the first left, but nothing really happened on the screen with that. Just looking for a little more guidance and think I’ll be good to go from here. Here’s my updated code:

Sender (Rotary Encoder MCU):

#define EI_ARDUINO_INTERRUPTED_PIN
#include <EnableInterrupt.h>
#include <Rotary.h>
#define intRE1 62
#define RE1 63
#define intRE2 64
#define RE2 65
#define intRE3 66
#define RE3 67
#define intRE4 68
#define RE4 69
//creates a different instant of the rotary function for each encoder
Rotary r1 = Rotary(intRE1, RE1);
Rotary r2 = Rotary(intRE2, RE2);
Rotary r3 = Rotary(intRE3, RE3);
Rotary r4 = Rotary(intRE4, RE4);
//array to hold the four different values of each encoder
int count[4];
int RE1State;
int bottom = 0;
int top = 90;
int setFlagRE = 0;

void setup() {
	Serial3.begin(115200);
	Serial.begin(9600);
	pinMode(intRE1, INPUT_PULLUP);
	pinMode(RE1, INPUT_PULLUP);
	pinMode(intRE2, INPUT_PULLUP);
	pinMode(RE2, INPUT_PULLUP);
	pinMode(intRE3, INPUT_PULLUP);
	pinMode(RE3, INPUT_PULLUP);
	pinMode(intRE4, INPUT_PULLUP);
	pinMode(RE4, INPUT_PULLUP);
	enableInterrupt(intRE1, RE1Int, CHANGE);
	enableInterrupt(RE1, RE1Int, CHANGE);
	enableInterrupt(intRE2, RE1Int, CHANGE);
	enableInterrupt(RE2, RE1Int, CHANGE);
	enableInterrupt(intRE3, RE1Int, CHANGE);
	enableInterrupt(RE3, RE1Int, CHANGE);
	enableInterrupt(intRE4, RE1Int, CHANGE);
	enableInterrupt(RE4, RE1Int, CHANGE);
//	RE1State = digitalRead(intRE1);
}

void loop() {
if(setFlagRE > 0){
	ProcessRots(setFlagRE);
	Serial.println(setFlagRE);
	setFlagRE = 0;
	}
}
//set flag based on which rotary encoder was triggered
void RE1Int() {
	switch (arduinoInterruptedPin) {
	case intRE1:
		setFlagRE = 1;
		break;
	case RE1:
		setFlagRE = 1;
		break;
	case intRE2:
		setFlagRE = 2;
		break;
	case RE2:
		setFlagRE = 2;
		break;
	case intRE3:
		setFlagRE = 3;
		break;
	case RE3:
		setFlagRE = 3;
		break;
	case intRE4:
		setFlagRE = 4;
		break;
	case RE4:
		setFlagRE = 4;
		break;

	}
}
//process rotary encoder information using rotary library
void ProcessRots(int RENum) {
	unsigned char result;
	//first decides which rotary encoder function to call
	switch (RENum) {
	case 1:
		result = r1.process();
		break;
	case 2:
		result = r2.process();
		break;
	case 3:
		result = r3.process();
		break;
	case 4:
		result = r4.process();
		break;
	}
	//figures out the result and counts up or down in the appropriate array
	switch (result) {
	case DIR_NONE:
		break;
	case DIR_CCW:
		if (count[RENum - 1] > bottom) {
			count[RENum - 1]--;
		}
		break;
	case DIR_CW:
		if (count[RENum - 1] < top) {
			count[RENum - 1]++;
		}
		break;
	}
	//adds the encoder number to the 100's, then adds the value to the 10's/1's
	int trans = RENum * 100 + count[RENum - 1];
	
	Serial.println(trans);
	Serial3.write(trans);

}

Reciever (TV MCU):

#include <TVout.h>
TVout TV;
//int test = 1;
int oldRec1 = 1;
int oldRec2 = 1;
int oldRec3 = 1;
int oldRec4 = 1;


void setup() {
  //  Serial.begin(9600);
  TV.begin(NTSC, 120, 96);
  Serial.begin(115200);
}

void loop() {
  //  TV.clear_screen();

  if (Serial.available() > 1) {
    int REVal = Serial.read();
    //pulls out Encoder number
    int barNum = abs(REVal / 100);
    //pulls out Encoder value
    int barVal = REVal % 100;
    //draws each bar based on value
    switch (barNum) {
      case 1:
        TV.draw_rect(20, 90 - oldRec1, 10, oldRec1, BLACK, BLACK);
        TV.draw_rect(20, 90 - barVal, 10, barVal, WHITE, WHITE);
        oldRec1 = barVal;
        break;
      case 2:
        TV.draw_rect(40, 90 - oldRec2, 10, oldRec2, BLACK, BLACK);
        TV.draw_rect(40, 90 - barVal, 10, barVal, WHITE, WHITE);
        oldRec2 = barVal;
        break;
      case 3:
        TV.draw_rect(60, 90 - oldRec3, 10, oldRec3, BLACK, BLACK);
        TV.draw_rect(60, 90 - barVal, 10, barVal, WHITE, WHITE);
        oldRec3 = barVal;
        break;
      case 4:
        TV.draw_rect(80, 90 - oldRec4, 10, oldRec4, BLACK, BLACK);
        TV.draw_rect(80, 90 - barVal, 10, barVal, WHITE, WHITE);
        oldRec4 = barVal;
        break;
    }
  }
}

Instead of 4 values for the flag, I think you might need 4 bits. Then you can process each of the bits that were set. When combined with disabled interrupt clearing of the bits, it should allow catching any combination of simultaneous interrupts:

void loop() {
  cli();
    uint8_t oldFlag = setFlagRE;
    setFlagRE = 0;
  sei();

  if (oldFlag > 0){
    ProcessRots( oldFlag );
    Serial.println( oldFlag );
  }
}

void RE1Int() {
  switch (arduinoInterruptedPin) {
    case intRE1:
    case RE1:
 setFlagRE |= 0x01;
 break;
    case intRE2:
    case RE2:
 setFlagRE |= 0x02;
 break;
    case intRE3:
    case RE3:
 setFlagRE |= 0x04;
 break;
    case intRE4:
    case RE4:
 setFlagRE |= 0x08;
 break;
 }
}

void ProcessRots(int RENum) {
  unsigned char result;

  // decide which rotary encoder function to call
  if (RENum & 0x01) {
    result = r1.process();
    handle( result, 1 );
  }
  if (RENum & 0x02) {
    result = r2.process();
    handle( result, 2 );
  }
  if (RENum & 0x04) {
    result = r3.process();
    handle( result, 3 );
  }
  if (RENum & 0x08) {
    result = r4.process();
    handle( result, 4 );
  }

}

void handleResult( uint8_t result, uint8_t index )
{
  // figures out the result and counts up or down in the appropriate array
  switch (result) {

    case DIR_NONE:
      break;

    case DIR_CCW:
      if (count[index - 1] > bottom) {
        count[index - 1]--;
      }
      break;

    case DIR_CW:
      if (count[index - 1] < top) {
        count[index - 1]++;
      }
      break;
  }

  // adds the encoder number to the 100's, then adds the value to the 10's/1's
  int trans = index * 100 + count[index - 1];
 
  Serial.println(trans);
  Serial3.write(trans);

} // handleResult

Thanks so much again for your help. Adding in your code changes definetly made things more responsive. My last question was more about how to send more than one byte unblocked without things going crazy. I tried a bunch of solutions myself that all failed horribly, and after a while stumbled upon this thread: http://forum.arduino.cc/index.php?topic=492055.0 where the second reply gives one of those “half ingenious! half why didn’t I think of that?” ways to send 14 bits worth of data while still ensuring parity and no blocking on the receiving end.

I really love this community, I would have spent so much time on trying to figure out solutions and ended up with a horrible non-optimized mess. Now I’m just hoping to get to the point where I can give back a fraction of the help I’ve received. Thanks again for all of your help /dev!