2 PWM work, but start I2C, only one works until I2C transmits.

Hardware: Arduino MEGA as master, sends output to LCD(I2C) as slave, sends and receives data with Ardunio NANO (I2C) as slave.

This is for a motor controller for RC engine. I have When I run the component functions individually, they work fine. When I start I2C, starter works, fuel pump works until the next I2C transmission, then it stops.

I am assuming that there is a timer or interrupt issue, so I have swapped my PWM to all available. I removed all the delay functions (only there for testing) to see if that was the problem, and both PWM worked, but I2C stopped communicating when I used millis(). I can see that there is just a interrupt or provisioning problem here where the chip is looking at one and not the other.

For the given hardware, what would be the best PWM pins to use, considering the communication requirement of 1 transmission every 200ms to the arduino mega? Do I need to start digging into timer registers to get something like this to work? i was hoping that the 328p could handle this level of computation.

Arduino MEGA code:

#include  <Wire.h>
#include  <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3F,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
volatile int RUNStatus = 0; // Global volatile system run status. 0 = OFF, 1 = START, 2 = RUN, 3 = SHUTDOWN

void setup(){
  Wire.begin();        // join i2c bus (address optional for master)
  lcd.init();          // initialize the lcd 
  lcd.backlight();
  Serial.begin(9600);
}

void loop(){
  if (Serial.available()>0){
    Serial.print(RUNStatus);
    RUNStatus = Serial.read();
  }
  if(RUNStatus == 49){
    CMDstart();
  }
  int RPMcmd = 0; 
  WriteData(RPMcmd);
  ReadDATA();
  delay(200);
}

int WriteData(int x){
  Wire.beginTransmission(8); // transmit to device #8
  Wire.write(x);              // sends one byte
  Wire.endTransmission();    // stop transmitting
}

//Requests parameter data from the ECU. Min 20ms response time a normal I2C freq. 
void ReadDATA(){
  Wire.requestFrom(8, 5);    // request 5 bytes from slave device #8
  while (Wire.available()) {  // slave may send less than requested
    byte rpmH = Wire.read();  
    byte rpmM = Wire.read(); 
    byte rpmL = Wire.read(); 
    int egt = Wire.read(); 
    int cmd = Wire.read();
    int rpm = rpmH;
    rpm  = (rpm<<8)|rpmM;
    rpm  = (rpm<<8)|rpmL;    
    // In RPM will work for 500 - 99999 RPM (get negative values below 500 due to data rollover)
    //(will switch to KRPM later, better resolution for calibration)
    rpm = 21000000/rpm; // value compensation for microsec & duty cycle compressed data format 26086956
    LCD(rpm,egt,cmd);
  }
}

// Write data to LCD in fomat for 16 x 2 LCD
void LCD(int rpm, int egt, int cmd){
  lcd.setCursor(0,0);
  lcd.print("RPM:");
  lcd.setCursor(4,0);
  lcd.print("     ");
  lcd.setCursor(4,0);
  lcd.print(rpm);
  lcd.setCursor(0,1);
  lcd.print("EGT:");
  lcd.setCursor(4,1);
  lcd.print("   ");
  lcd.setCursor(4,1);
  lcd.print(egt);
  lcd.setCursor(9,0);
  lcd.print("CMD:");
  lcd.setCursor(13,0);
  lcd.print("   ");
  lcd.setCursor(13,0);
  lcd.print(cmd);
}

//Start command to ECM, automatically enters StartSEQ
int CMDstart(){
  char x = 's';
  char y = 'B';
  Serial.print(x);
  Wire.beginTransmission(8); // transmit to device #8
  Wire.write(x);              // sends one byte
  Wire.write(y);              // sends one byte
  Wire.endTransmission();    // stop transmitting
  RUNStatus = 1;
}

Arduino NANO code:

#include  <Wire.h>

int EGTsense = A2; //Analog EGT reading needs calibration, sim with trimpot
int FPpin = 3;  //Fuel Pump Run pin
int RPMsense = 5;  //RPM sense input pin
int STRpin = 11;    //Starter Run Pin
int FVpin = 8;     //Fuel Valve enable / disable
int IGpin = 12;
volatile int RPMcmdin = 0; //Command input from ECU, used in Start/Run functions
unsigned long previousMillis = 0;

void setup() {
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onRequest(requestEvent); // register event
  Wire.onReceive(receiveEvent); // register event
  pinMode(FPpin, OUTPUT);
  pinMode(STRpin, OUTPUT);
  pinMode(EGTsense,INPUT);
  pinMode(RPMsense, INPUT_PULLUP);
  pinMode(FVpin, OUTPUT);
  pinMode(IGpin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  //Starter(int); FUELvalve(int); FuelPump(int); Ignition(int)
  //StartSEQ(); ShutdownSEQ();
  StartSEQ();
  //delay(2000);
  //ShutdownSEQ();

}

// Collects and sends RPM, EGT, CMD values from ECM to ECU
void requestEvent() {
  int rpm = GetRPM();  
  byte RPMarray[3];
  RPMarray[0] = (rpm >> 16) & 0xFF;
  RPMarray[1] = (rpm >> 8) & 0xFF;
  RPMarray[2] = rpm & 0xFF;
  int egt = GetEGT();
  int cmd = RPMcmdin;
  Wire.write(RPMarray,3); 
  Wire.write(egt); 
  Wire.write(cmd);
}

// Receive RPM or Start command from ECU, Start or change program RPM setpoint
void receiveEvent(int howMany) {
  while (1 < Wire.available()) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
    if (c == 's'){
      StartSEQ();
    }
  }

  RPMcmdin = Wire.read();    // receive byte as an integer
  analogWrite(FPpin, RPMcmdin);      
}

// Rough start sequence, needs work with fuel pump and handoff to RUN function
void StartSEQ(){
  //Starter(int); FUELvalve(int); FuelPump(int); Ignition(int)
  //StartSEQ(); ShutdownSEQ();
  unsigned long currentMillis = millis();
  FUELvalve(1);
  Ignition(1);
  if (currentMillis - previousMillis > 100) {
    FuelPump(20);
  }
  if (currentMillis - previousMillis > 1000) {
    Starter(80);
  }
  //if (currentMillis - previousMillis < 5000) {
    //StartSEQ();
  //}
  previousMillis = currentMillis;
  return;
}

void ShutdownSEQ(){
  FUELvalve(0); //Shut the fuel valve
  FuelPump(0);   //Turn off the fuel pump
  int egt = GetEGT(); //Read EGT 
  if(egt>120){
    Starter(85); 
    //delay(5000);
    Starter(0);
    //delay(5000); 
    ShutdownSEQ();
  }else{
    Starter(0);
    return;
  }
}

// Only measures HIGH pulse width and sends value back to ECU for computation. 
// This requires 3 bytes, and is minimum load on processor.
int GetRPM(){
  unsigned long PWH = pulseIn(RPMsense,HIGH);
  return(PWH);
}

// Reads Thermocouple value. Will need calibration with real sensor at some point.
// If using k-type, use linear mapping requires component selections and testing
int GetEGT(){
  int measure = analogRead(EGTsense);
  int egt = map(measure, 0, 1023, 0, 255);
  return (egt);
}

// Run values need to be calibrated at 8.4V, need min 11.4A supply.
int Starter(int STRin){
  //2500RPM = 82PWM, 5000RPM = ???PWM Need to verify on Battery 
  analogWrite(STRpin, STRin);
  return;
}

int FuelPump(int FP){
  analogWrite(FPpin, FP);
  return;
}

int FUELvalve(int pos){
  //Pass in value of 1 opens valve, 0 closes valve.
  if (pos == 1){
    digitalWrite(FVpin, HIGH);
  }else{
    digitalWrite(FVpin, LOW);
  }
}

int Ignition(int i){
  if (i == 1){
    digitalWrite(IGpin, HIGH);
  }else{
    digitalWrite(IGpin, LOW);
  }
}

Not you true issue but On a mega an [int] is 16 bits. When I read this

int rpm = rpmH;
    rpm  = (rpm<<8)|rpmM;
    rpm  = (rpm<<8)|rpmL;

I have the impression you are trying to stuff 3 bytes into 2...

And on your nano

  int rpm = GetRPM();  // 16 BITS
  byte RPMarray[3];
  RPMarray[0] = (rpm >> 16) & 0xFF; // SHIFTING RIGHT YOUR 16 BITS BY 16 POSITIONS  IS SAME AS DOING 0 & 0xFF which is 0

Why are you messing around with RUNStatus?

if (Serial.available()>0){
    Serial.print(RUNStatus);
    RUNStatus = Serial.read();
  }

in your comment you say Global volatile system run status. 0 = OFF, 1 = START, 2 = RUN, 3 = SHUTDOWN but you allow this variable to be whatever comes from the serial line. How is the serial terminal configured, are you sending CR LF after an entry?

@OP

int WriteData(int x){
  Wire.beginTransmission(8); // transmit to device #8
  Wire.write(x);              // sends one byte
  Wire.endTransmission();    // stop transmitting
}

In your above MEGA codes, you are sending 1-byte data over I2C Bus. At the receiving end, the NANO reads 2-byte data. Does it match. When data reaches to the NANO over I2C Bus, the NANO enters into receiveEvent() handler, then is it necessary to execute the Wire.available() instruction? The receiver codes are:

// Receive RPM or Start command from ECU, Start or change program RPM setpoint
void receiveEvent(int howMany) {
  while (1 < Wire.available()) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
    if (c == 's'){
      StartSEQ();
    }
  }

  RPMcmdin = Wire.read();    // receive byte as an integer
  analogWrite(FPpin, RPMcmdin);      
}

After the issuance of the Wire.requestFrom() command, the data is always available in the FIFO buffer of the Master MEGA. So, is it necessary to execute the Wire.available() instruction?

Wire.requestFrom(8, 5);    // request 5 bytes from slave device #8
  while (Wire.available()) {  // slave may send less than requested

In your MEGA codes, you have a variable named int RPMcmd=0;; it has never been updated. It has been sent to NANO; but, the NNAO has not used it. The codes are:

int RPMcmd = 0; 
  WriteData(RPMcmd);
  ReadDATA();
  delay(200);
}

J-M-L:
Not you true issue but On a mega an [int] is 16 bits. When I read this

int rpm = rpmH;

rpm  = (rpm<<8)|rpmM;
    rpm  = (rpm<<8)|rpmL;


I have the impression you are trying to stuff 3 bytes into 2... 

And on your nano 

int rpm = GetRPM();  // 16 BITS
  byte RPMarray[3];
  RPMarray[0] = (rpm >> 16) & 0xFF; // SHIFTING RIGHT YOUR 16 BITS BY 16 POSITIONS  IS SAME AS DOING 0 & 0xFF which is 0





Why are you messing around with RUNStatus? 

if (Serial.available()>0){
    Serial.print(RUNStatus);
    RUNStatus = Serial.read();
  }


in your comment you say *Global volatile system run status. 0 = OFF, 1 = START, 2 = RUN, 3 = SHUTDOWN* but you allow this variable to be whatever comes from the serial line. How is the serial terminal configured, are you sending CR LF after an entry?

The int part is a good catch, I was getting weird negative numbers for low rpm and that would be the reason why. But that is on the MEGA processing side, and the communications hangup is on the NANO.

Regarding the Serial part, it was for testing. The serial communication is just the serial monitor now, but will be an GUI developed in MatLab for the final application. So for this I would send a 1 into the serial monitor and it would transmit it to initiate the start sequence. That was the basic starting point for the GUI platform and my team member is writing that portion so we just standardized some communication values now so that we could get them talking later. I do not send CR LF, what am I missing there?

Latter_Version:
I do not send CR LF, what am I missing there?

that's good then :slight_smile:

if your Serial Monitor was sending (as this is the default) CR and LF when you enter a command such as '1', that would actually impact RUNStatus due to the Serial code

if (Serial.available()>0){
    Serial.print(RUNStatus);
    RUNStatus = Serial.read();
  }

--> you would read the '1' so the code would start correctly but then you would still receive bytes and that would mess with your RUNStatus variable, and create havoc

GolamMostafa:
In your above MEGA codes, you are sending 1-byte data over I2C Bus. At the receiving end, the NANO reads 2-byte data. Does it match. When data reaches to the NANO over I2C Bus, the NANO enters into receiveEvent() handler, then is it necessary to execute the Wire.available() instruction? The receiver codes are:

After the issuance of the Wire.requestFrom() command, the data is always available in the FIFO buffer of the Master MEGA. So, is it necessary to execute the Wire.available() instruction?

Wire.requestFrom(8, 5);    // request 5 bytes from slave device #8

while (Wire.available()) {  // slave may send less than requested




In your MEGA codes, you have a variable named **int RPMcmd=0;**; it has never been updated. It has been sent to NANO; but, the NNAO has not used it. The codes are:

My thought on the receiver code (and it might be wrong) is that I could send an "int" and it would update command speed, or I could send a "char" and it would update the run mode. I should be sending a value between 0 and 255 to command speed changes, and will pass 's' when I want it to enter the start sequence. There are some inconsistencies in this part of the code, I was just laying it out when I noticed the communication problems with the I2C.

just noticed this as well:

rpm = [color=red]21000000[/color]/rpm; // value compensation for microsec & duty cycle compressed data format 26086956

as rpm is an int on 2 bytes, it cannot represent large values you should make that a long (or unsigned long since it should not be negative) and then write rpm = 21000000[color=green]ul[/color]/rpm; to make sure the math is done using unsigned long

Actually some how or another that has fixed it. I corrected the data type on the nano transmission side specifically, where I collect the RPM value and it started working just fine. I can adjust other sensors and it seems to run smooth. I just got through an ARM programming class where int was 4 bytes so it did not occur to me.

Thanks for the help.

Good :slight_smile: