timer interrupts and i2c don't play together nicely?

Hello all,

This may appear as a double post somehow, I posted last night but something went wrong so I am posting again this morning.

The big picture… I am using an Arduino Uno to handle the real time aspects of a robot I am building which is ultimately controlled by a Raspberry Pi. The UNO handles the motion control using timer interrupts to run two stepper motors through stepper drives. That portion of the UNO sketch is working great. I should note that I am programming in PYTHON 3.4 on the PI.

Now I am working on having the PI send commands to the UNO so it knows what to do.

I started out trying basic UART serial - ran into all sorts of problems with sending/receiving LONGS - mostly on the PI side and PYTHON 3.4.

I switched to doing the communications with i2c and now have that working pretty good, until…

Now I am combining the two programs (Stepper control & i2c communications) into one program and things went south bad.

in the Setup function, you can see the section that sets up the timers. If that is not there, the program runs fine and communicates fine with the PI. However, with the timer code included, the UNO acts as if it is re-booting over and over again. I determined this by having the serial monitor on while the program runs. Without the timer settings, “i2c is online!” prints once and everything is as expected. With the timer settings, “i2c is online!” prints over and over again with a LOT of strange characters as if the entire program is corrupt and is rebooting.

I am really stuck on this problem, about 50 hours of trying to figure it out and I can’t make headway. Even my good friend “Google” couldn’t steer me in the right direction.

I sure hope someone here can point out my issues and offer a suggestion to fix it. FYI, this is my first or second C program I have written in over 30 years so I am sure it is something I am doing very wrong.

Chris D

#include <Wire.h>

//i2c Specific variables
#define SLAVE_ADDRESS 8
int outputID;
byte output1;
byte output2;
byte output3;
byte output4;
byte byte_array[8];
long i2c_Val_1;
long i2c_Val_2;
int i2c_cmd[9];
long Return_Val2;
int New_Command_Flag;

// From main module-------------
unsigned int Target_SPS; // Target Steps Per Second to achive
// positional control parameters
long Steps;// Current steps made
long Target_Steps; // Target steps to be made
long Steps_To_Go; // Steps remaining

// fixed parameters
unsigned int Acc_Dec_Increment; // Change in velocity in timer2 - fixed parameter
unsigned int Min_SPS; // Minimum allowed steps per second in actual motion - zero is special condition to stop motion
unsigned int Max_SPS; // Maximum allowed steps per second in actual motion - physical limit of speed @ 195 RPM
long Command;
long Main_Loop_Delay; // NOTE THIS MUST BE ADDED TO THE STEPPER LOGIC MODUAL

void setup() {
New_Command_Flag = 0;

// #####################
// TIMER INTERRUPT SECTION STARTS HERE
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCCR1B |= (1 << CS11); // 8 prescaler
TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt
//set timer2 interrupt at 17 mSecs
TCCR2A = 0;// set entire TCCR2A register to 0
TCCR2B = 0;// same for TCCR2B
TCNT2 = 0;//initialize counter value to 0
// set compare match register for 8khz increments
OCR2A = 255;// 17 mSecs
// turn on CTC mode
TCCR2A |= (1 << WGM21);
// Set CS21 bit for 1024 prescaler
TCCR2B |= (1 << CS20);
TCCR2B |= (1 << CS21);
TCCR2B |= (1 << CS22);
// enable timer compare interrupt
TIMSK2 |= (1 << OCIE2A);
interrupts(); // enable all interrupts
// TIMER INTERRUP SECTIONS END HERE
// #####################

Wire.begin(SLAVE_ADDRESS);
Wire.onReceive(ReadCommand); // Received a transmission from Master
Wire.onRequest(SendData); // Master requests data
Serial.begin(9600); // set up Serial library at 9600 bps
Serial.println(“i2c is online!”);
}

//This routine is where the i2c interface looks for data that is requested.
//Notice how the outputID is used to determine what byte to provide for access.
//outputID is set in the routine ReadCommand - look near the bottom of the list so see the assignment.
//It handles a total of 8 bytes which make up 2 longs which are unpacked by routine UnPack_Longs
void SendData() {
switch (outputID) {
Serial.println(outputID);
case 100:
Wire.write(byte_array[0]);
break;
case 101:
Wire.write(byte_array[1]);
break;
case 102:
Wire.write(byte_array[2]);
break;
case 103:
Wire.write(byte_array[3]);
break;
case 104:
Wire.write(byte_array[4]);
break;
case 105:
Wire.write(byte_array[5]);
break;
case 106:
Wire.write(byte_array[6]);
break;
case 107:
Wire.write(byte_array[7]);
break;
}
}

//This routine handles all requests from the Raspberry Pi via i2c.
//Incomming data is in the form of one byte followed by two longs.
//All variables are transmitted bi-directionaly, byte-by-byte which requires packing or unpacking of the longs
//The first byte is the “command” which is either setting data or requesting data
//If setting data, then either the first or second long will contain the setting value
//If requesting data, then the outputID is set to the location of the variables containing the data
void ReadCommand(int byteCount) {
//Serial.println(“HANDLING MSG ************************”);
//get commands from Raspberry Pi via i2c
New_Command_Flag = 1;

}

//This routine takes 2 sets of 4 bytes (as transmitted from Raspberry Pi) and packs them into
// two longs (i2c_Val_1 , i2c_Val_2)

void Pack_Longs(){
uint8_t data[4];
data[0] = i2c_cmd[4];
data[1] = i2c_cmd[3];
data[2] = i2c_cmd[2];
data[3] = i2c_cmd[1];
unsigned long testint;
testint = (unsigned long)(&data);
i2c_Val_1 = testint;
data[0] = i2c_cmd[8];
data[1] = i2c_cmd[7];
data[2] = i2c_cmd[6];
data[3] = i2c_cmd[5];
testint = (unsigned long)(&data);
i2c_Val_2 = testint;
}

// This routine takes two longs (i2c_Val_1 , i2c_Val_2) and unpacks them into a byte array
// for transmission to the Raspberry Pi via i2c.
void UnPack_Longs(){
byte_array[0] = (i2c_Val_1 >> 24) & 0xFF;
byte_array[1] = (i2c_Val_1 >> 16) & 0xFF;
byte_array[2] = (i2c_Val_1 >> 8) & 0xFF;
byte_array[3] = i2c_Val_1 & 0xFF;
byte_array[4] = (i2c_Val_2 >> 24) & 0xFF;
byte_array[5] = (i2c_Val_2 >> 16) & 0xFF;
byte_array[6] = (i2c_Val_2 >> 8) & 0xFF;
byte_array[7] = i2c_Val_2 & 0xFF;
}

void loop() {
// put your main code here, to run repeatedly:
if (New_Command_Flag == 1){
int x = 0;
while(Wire.available()) {
i2c_cmd = Wire.read();
x++;
}
if (i2c_cmd[0] == 0) // Move Forward xxxx steps @ xxxx SPS
{
Pack_Longs(); //Convert bytes into longs
Target_Steps = i2c_Val_1;
Target_SPS = i2c_Val_2;
UnPack_Longs(); //Convert longs into bytes
// Serial.println(“FWD”);
// Serial.print("Steps = ");
// Serial.println(i2c_Val_1);
// Serial.print("SPS = ");
// Serial.println(i2c_Val_2);

}
else if (i2c_cmd[0] == 16) // Setting Main_Loop_Delay parameter
{
Pack_Longs(); //Convert bytes into longs
Main_Loop_Delay = i2c_Val_1;
// Serial.println(“SET MAIN LOOP DELAY”);
// Serial.print("LOOP DELAY = ");
// Serial.println(i2c_Val_1);
// Serial.print("SPS = ");
// Serial.println(i2c_Val_2);
i2c_Val_1 = Main_Loop_Delay;
i2c_Val_2 = Main_Loop_Delay;
UnPack_Longs(); //Convert longs into bytes
}
else if (i2c_cmd[0] >= 100)
{
outputID = i2c_cmd[0];
}
}
}

You’ve enabled timer1 overflow interrupt without defining an ISR for it.

(deleted)

Hi guys,

Thanks for commenting...

Yes, adding in the ISR corrected the problem. I was able to then put the i2c code into the stepper motor portion of the program. While I know I am getting communications, I am getting an extreme amount of errors. I will post a new version of the program (Using code tags this time) if the program will fit in the post. The last version had to be stripped down to fit.

I am currently trying to figure out what I can remove and have to leave so that the problem still occurs and the program will fit in the post (Using code tags as requested).

Chris

Here is the top half of the program, Bottom half will be in next post.

The problem is, each communication from the to the UNO now results in 5 Input/Output Errors (on the PI side) each time I try to exchange information. On the PI side, I have a loop sending the various commands to the UNO, then fetching the response from the UNO (9 bytes for the response). This works fine in the standalone version of the i2c test program, but not so much in the whole program with the rest of the stepper drive code.

#include <Wire.h>


#define LeftWheelStep 13  // left wheel step signal
#define LeftWheelDir 12   // left wheel dir signal
#define RightWheelStep 11 // right wheel step signal
#define RightWheelDir 10  // right wheel dir signal
#define Joy_X_A 0         // Joystick analog input for X axis
#define Joy_Y_A 1         // Joystick analog input for Y axis


//i2c Specific variables
#define SLAVE_ADDRESS 8
int outputID;
byte byte_array[8];
long i2c_Val_1;
long i2c_Val_2;
int i2c_cmd[9];
long Return_Val2;
int New_Command_Flag;

// SYSTEM CONTROL PARAMETERS
byte Command; // read Command from Serial port - single Byte ranging from 0 ~ 9
boolean L_FWD;
boolean L_REV;
boolean R_FWD;
boolean R_REV;
byte End_Move_Alert;
byte ACC_DEC_Adjust;

// velocity control parameters
unsigned int Counter_Preload; // 16 bit counter preload value for overflow
unsigned int SPS; // Current Steps Per Second
unsigned int Target_SPS; // Target Steps Per Second to achive
long Steps_To_Dec; // Dynamic value based on distance to accelerate - set by Timer 2
int ACC_Flag; // Flag to allow Steps_To_Dec distance to be set only once in Timer 2
int X_Joy_A; // Analog value from X axis of Joystick
int Y_Joy_A; // Analog value from Y axis of Joystick

// positional control parameters
long Steps;// Current steps made
long Target_Steps; // Target steps to be made
long Steps_To_Go; // Steps remaining

// fixed parameters
unsigned int Acc_Dec_Increment; // Change in velocity in timer2 - fixed parameter
unsigned int Min_SPS; // Minimum allowed steps per second in actual motion - zero is special condition to stop motion
unsigned int Max_SPS; // Maximum allowed steps per second in actual motion - physical limit of speed @ 195 RPM
long Main_Loop_Delay;

// Serial interface variables
String Serial_Command; // String input from command prompt
String temp1, temp2; // temporary strings
char inByte; // Byte input from command prompt
char carray[10]; // character array for string to int // manipulation
long a, b, c; // temporary numbers
byte CMD;
long CMD_Steps;
long CMD_SPS;


//--------------------------------------------------------------------------------------------------------------
// Start up code
void setup()
{
  Acc_Dec_Increment = 100; // range from 50 to 100 tested, seems fine in that range with free running motors
  Min_SPS = 100; // 3 RPM aka 47 IPM aka 3.9 FPM aka .45 MPH
  Max_SPS = 6500; // 195 RPM aka 3063 IPM aka 255 FPM aka 2.9 MPH
  R_FWD = true;
  R_REV = false;
  L_FWD = true;
  L_REV = false;
  Command = 99;
  End_Move_Alert = 1;
  ACC_DEC_Adjust = 0;
  // set pin mode (directions)
  pinMode(LeftWheelStep, OUTPUT);
  pinMode(LeftWheelDir, OUTPUT);
  pinMode(RightWheelStep, OUTPUT);
  pinMode(RightWheelDir, OUTPUT);
   
  //configure / activate serial port
  Wire.begin(SLAVE_ADDRESS);
  Wire.onReceive(ReadCommand); // Received a transmission from Master
  Wire.onRequest(SendData); // Master requests data
  Serial.begin(9600);           // set up Serial library at 9600 bps
  Serial.println("Razzy Drive System Online!");  // Alerts Raspberry Pi the drive system is online

   // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCCR1B |= (1 << CS11);    // 8 prescaler 
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
  //set timer2 interrupt at 17 mSecs
  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0
  // set compare match register for 8khz increments
  OCR2A = 255;// 17 mSecs
  // turn on CTC mode
  TCCR2A |= (1 << WGM21);
  // Set CS21 bit for 1024 prescaler
  TCCR2B |= (1 << CS20);   
  TCCR2B |= (1 << CS21);   
  TCCR2B |= (1 << CS22);   
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);
  interrupts();             // enable all interrupts
}//END Setup


//--------------------------------------------------------------------------------------------------------------
//This routine is where the i2c interface looks for data that is requested.
//Notice how the outputID is used to determine what byte to provide for access.
//outputID is set in the routine ReadCommand - look near the bottom of the list so see the assignment.
//It handles a total of 8 bytes which make up 2 longs which are unpacked by routine UnPack_Longs
void SendData() {
  switch (outputID) {
    Serial.println(outputID);
   case 100:
    Wire.write(byte_array[0]);
   break; 
   case 101:
    Wire.write(byte_array[1]);
   break; 
   case 102:
    Wire.write(byte_array[2]);
   break; 
   case 103:
    Wire.write(byte_array[3]);
   break; 
   case 104:
    Wire.write(byte_array[4]);
   break; 
   case 105:
    Wire.write(byte_array[5]);
   break; 
   case 106:
    Wire.write(byte_array[6]);
   break; 
   case 107:
    Wire.write(byte_array[7]);
   break; 
  }
}// End SendData

//--------------------------------------------------------------------------------------------------------------
//This routine handles all requests from the Raspberry Pi via i2c and sets a flag for parsing in main loop.
void ReadCommand(int byteCount) {
  //Serial.println("HANDLING MSG ************************");
  //get commands from Raspberry Pi via i2c
  New_Command_Flag = 1;
} // End ReadCommand

//--------------------------------------------------------------------------------------------------------------
//This routine takes 2 sets of 4 bytes (as transmitted from Raspberry Pi) and packs them into
// two longs  (i2c_Val_1 , i2c_Val_2)
void Pack_Longs(){
  uint8_t data[4];
  data[0] = i2c_cmd[4];
  data[1] = i2c_cmd[3];
  data[2] = i2c_cmd[2];
  data[3] = i2c_cmd[1];
  unsigned long testint; 
  testint = *(unsigned long*)(&data);
  i2c_Val_1 = testint;
  data[0] = i2c_cmd[8];
  data[1] = i2c_cmd[7];
  data[2] = i2c_cmd[6];
  data[3] = i2c_cmd[5];
  testint = *(unsigned long*)(&data);
  i2c_Val_2 = testint;
} //END Pack_longs

//--------------------------------------------------------------------------------------------------------------
// This routine takes two longs (i2c_Val_1 , i2c_Val_2) and unpacks them into a byte array
// for transmission to the Raspberry Pi via i2c.
void UnPack_Longs(){
     byte_array[0] = (i2c_Val_1 >> 24) & 0xFF;
     byte_array[1] = (i2c_Val_1 >> 16) & 0xFF;
     byte_array[2] = (i2c_Val_1 >> 8) & 0xFF;
     byte_array[3] = i2c_Val_1 & 0xFF;
     byte_array[4] = (i2c_Val_2 >> 24) & 0xFF;
     byte_array[5] = (i2c_Val_2 >> 16) & 0xFF;
     byte_array[6] = (i2c_Val_2 >> 8) & 0xFF;
     byte_array[7] = i2c_Val_2 & 0xFF;
}// END UnPack_Longs

Bottom half of the code…

//--------------------------------------------------------------------------------------------------------------
// Timer 1 interrupt routine - sends pulse to step motor driver 
ISR(TIMER1_OVF_vect)        // interrupt service routine 
{
  if (Steps_To_Go > 0){  //decision to only allow motion if Step_To_Go ??? what about Jog motion?
    TCNT1 = Counter_Preload;   // preload timer
    digitalWrite(LeftWheelStep,1); // pulse left wheel stepper
    digitalWrite(LeftWheelStep,0);
    digitalWrite(RightWheelStep,1);// pulse right wheel stepper
    digitalWrite(RightWheelStep,0);
    Steps += 1;
    Steps_To_Go -= 1;
  }
}// END Timer 1 interrupt routine


//--------------------------------------------------------------------------------------------------------------
// Timer 2 interrupt routine - constant frequency 
ISR(TIMER2_COMPA_vect)
{
  ACC_DEC_Adjust = 1;
}// END Timer 2 interrupt routine


//--------------------------------------------------------------------------------------------------------------
// Main Loop
void loop() {
  // put your main code here, to run repeatedly:
  if (New_Command_Flag == 1){
    int x = 0;
    while(Wire.available()) {
      i2c_cmd[x] = Wire.read();
      x++;
    } 
    if (i2c_cmd[0] == 0)            // Move Forward  xxxx steps @ xxxx SPS
    {
       Pack_Longs(); //Convert bytes into longs
       Target_Steps = i2c_Val_1;
       Target_SPS = i2c_Val_2;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 1) // Move Reverse  xxxx steps @ xxxx SPS
    {
       Pack_Longs(); //Convert bytes into longs
       Target_Steps = i2c_Val_1;
       Target_SPS = i2c_Val_2;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 2) // Pivot CW  xxxx steps @ xxxx SPS
    {
       Pack_Longs(); //Convert bytes into longs
       Target_Steps = i2c_Val_1;
       Target_SPS = i2c_Val_2;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 3)  // Pivot CCW  xxxx steps @ xxxx SPS
    {
       Pack_Longs(); //Convert bytes into longs
       Target_Steps = i2c_Val_1;
       Target_SPS = i2c_Val_2;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 4)  // STOP - Hard Stop, no decel
    {
       Pack_Longs(); //Convert bytes into longs      
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 9)  // Set Arduino into JOG mode
    {
       Pack_Longs(); //Convert bytes into longs
       UnPack_Longs(); //Convert longs into bytes
       Command = 9;
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 10)  // Requested current mode (Command)
    {
       Pack_Longs(); //Convert bytes into longs
            // prepare response 
       Command = 1; // for testing only
       i2c_Val_1 = Command;
       i2c_Val_2 = Command;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;       
    }
    else if (i2c_cmd[0] == 11) // Requested the number of steps to go
    {
       Pack_Longs(); //Convert bytes into longs
       // prepare response 
       Steps_To_Go = 111111; // for testing only
       i2c_Val_1 = Steps_To_Go;
       i2c_Val_2 = Steps_To_Go;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 12) // Requested the number of Steps Traveled
    {
       Pack_Longs(); //Convert bytes into longs
       Steps = 121212;//For testing only
       i2c_Val_1 = Steps;
       i2c_Val_2 = Steps;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 13) // Setting ACC DEC INCREMENT parameter
    {
       Pack_Longs(); //Convert bytes into longs
       Acc_Dec_Increment = i2c_Val_1;
       i2c_Val_1 = Acc_Dec_Increment;
       i2c_Val_2 = Acc_Dec_Increment;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 14) // Setting MIN_SPS parameter
    {
       Pack_Longs(); //Convert bytes into longs
       Min_SPS = i2c_Val_1;
       i2c_Val_1 = Min_SPS;
       i2c_Val_2 = Min_SPS;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 15) // Setting MAX_SPS parameter
    {
       Pack_Longs(); //Convert bytes into longs
       Max_SPS = i2c_Val_1;
       i2c_Val_1 = Max_SPS;
       i2c_Val_2 = Max_SPS;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
    else if (i2c_cmd[0] == 16) // Setting Main_Loop_Delay parameter
    {
       Pack_Longs(); //Convert bytes into longs
       Main_Loop_Delay = i2c_Val_1;
       i2c_Val_1 = Main_Loop_Delay;
       i2c_Val_2 = Main_Loop_Delay;
       UnPack_Longs(); //Convert longs into bytes
       New_Command_Flag = 0;
    }
     else if (i2c_cmd[0] >= 100) 
    {
      outputID = i2c_cmd[0];
      New_Command_Flag = 0;
    }
  }// END New_Command Section

  if (ACC_DEC_Adjust == 1)
  {
    if (SPS < Target_SPS){            // Accelerate up to Target speed but limit to Max_SPS
      SPS = SPS + Acc_Dec_Increment;
      if (SPS > Max_SPS)SPS = Max_SPS;
      Counter_Preload = (65535 - (2000000 / SPS)); // Calc timer preload for given SPS
      }
    if (SPS == Target_SPS){           // Record the number of steps it took to achieve velocity - used to start deceleration when needed
      if (ACC_Flag == 1){
        Steps_To_Dec = Steps;
        ACC_Flag = 0;
      }
    }
    if (SPS > Target_SPS){           // Decelerate to Target_SPS but do not go below Min_SPS so that moves can be completed
      SPS = SPS - Acc_Dec_Increment;
      if (SPS < Min_SPS) SPS = Min_SPS;
      Counter_Preload = (65535 - (2000000 / SPS)); // Calc timer preload for given SPS
    }
    ACC_DEC_Adjust = 0;
  }//END ACC_DEV_Adjust
 
}// END Main Loop