How to make I2C operate inside interrupts

(Code below)Hello, I have a problem that I cant quite wrap my head around. I have an arduino setup where I use an MPU6050 gyro/accelarometer + Arduino nano to measure angle. The chip uses I2C and to do the calculations I firstly get roll and pitch angles relative to the eart’s gravitational vector using the accelerometer while the conrtaption is stationary. Since It will go on a mobile platform (quadcopter) I then use the Mems gyro and multiply the rotational speed readout by the time passed and add it to the previous values. I have to capture all the readouts as every lost data means a slight permenant error on the final values. I figured I’d use the interrupt function provided by the MPU6050 and set an interrupt to fire whenever there is new data available, but as you may know I2C doesn’t function inside any Interrupt. However I figured out that it does work in the serialEvent function. I made a very wonky system that sends a byte to the hardware UART whenever the interrupt fires, via a software UART port on the pins 4(rx) 5(tx).(I dont want to rely on the unpredictableness of the loop function and plan on using it on something else) Essentially It worked but when I tweaked the code a little bit Software serial ports stopped working inside the interrupt. If you have any idea how I can cause a rising input to the arduino to allow me to read data from the sensor via I2C. If you know an arduino board/any microcontroller that may allow me to do that I would be really thankful.

Code:

#include <Wire.h>
#include <Math.h>
#include <SoftwareSerial.h>
SoftwareSerial InterruptSerial (4, 5);
volatile int16_t acc_raw_values = {0,0,0};
volatile int16_t gyro_raw_values = {0,0,0};
volatile double angle_values = {0,0,0};
void setup() {
TCCR1A = 0;// set entire TCCR1A register to 0
TCCR1B = 0;// same for TCCR1B
TCNT1 = 0;//initialize counter value to 0
// set compare match register for 1hz increments
OCR1A = 7812;// = (1610^6) / (11024) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS10 and CS12 bits for 1024 prescaler
TCCR1B |= (1 << CS12) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
Wire.begin();
Serial.begin(9600);
InterruptSerial.begin(9600);
updateRegister(0x6B,0);
//updateRegister(25,255); //Sample rate divider
updateRegister(26,6); //Config: DLPF 6 (5 hz)
updateRegister(27,0); //Gyro full scale range 250 degrees/s
updateRegister(28,0); //Accelarometer full scale range -+2g
updateRegister(56,1); //Interrupts when new data is ready
acc_angle_reset();
attachInterrupt(digitalPinToInterrupt(2), count, RISING);
}
ISR(TIMER1_COMPA_vect){
Serial.print("Roll: "); Serial.println(angle_values[0]);
Serial.print("Pitch: "); Serial.println(angle_values[1]);
}
void count(){
InterruptSerial.print(“a”);
}
void loop() {

}
void updateRegister(int address, int value){
Wire.beginTransmission(0x68);
Wire.write(address);
Wire.write(value);
Wire.endTransmission();
}

void serialEvent(){
while(Serial.available())
{Serial.read();}
gyro_update();
}
void acc_angle_reset(){
Wire.beginTransmission(0x68);
Wire.write(59);
Wire.endTransmission(0);
Wire.requestFrom(0x68,6,1);
acc_raw_values[0] = (Wire.read()<<8);
acc_raw_values[0] |= Wire.read();
acc_raw_values[1] = (Wire.read()<<8);
acc_raw_values[1] |= Wire.read();
acc_raw_values[2] = (Wire.read()<<8);
acc_raw_values[2] |= Wire.read();
Wire.endTransmission();
angle_values[0]=57.2957795* atan(acc_raw_values[0]/sqrt(square(acc_raw_values[1]) + square(acc_raw_values[2]))); //roll
angle_values[1]=57.2957795 * atan(acc_raw_values[1]/sqrt(square(acc_raw_values[0]) + square(acc_raw_values[2]))); //pitch
}
void gyro_update(){
Wire.beginTransmission(0x68);
Wire.write(67);
Wire.endTransmission(0);
Wire.requestFrom(0x68,6,1);
gyro_raw_values[0] = (Wire.read()<<8);
gyro_raw_values[0] |= Wire.read();
gyro_raw_values[1] = (Wire.read()<<8);
gyro_raw_values[1] |= Wire.read();
gyro_raw_values[2] = (Wire.read()<<8);
gyro_raw_values[2] |= Wire.read();
Wire.endTransmission();
angle_values[0] = (gyro_raw_values[1]/131) + angle_values[0];
angle_values[1] = (gyro_raw_values[0]/131) + angle_values[1];
}

In the ISR, set a volatile global variable to true. In the loop function, when the flag is true, use I2C to read results from the MPU6050. Then set the flag to false. This example works this way.

https://github.com/jrowberg/i2cdevlib/blob/master/Arduino/MPU6050/examples/MPU6050_DMP6_ESPWiFi/MPU6050_DMP6_ESPWiFi.ino

batuorhungunduz, what is your background ? Are you a software engineer with experience in an other language or processors ? Or are you a beginner that knows how to gather pieces of software that are a few years behind ? Or is this a project as a student and you have been given a good start by the professor ?

First of all, the MPU-9250 is better than the MPU-6050. The AHRS sensor fusion works well on small microcontrollers like the Arduino boards. You can find examples for MPU-9250 with AHRS at Sparkfun. Never use the SoftwareSerial in a quadcopter. The SoftwareSerial can use almost the complete Arduino processing time. There will only be some time left to blink a few leds. Avoid the writing directly to the timer registers if possible. When you write directly to a timer register, you are stuck with that type of microcontroller. The smaller the interrupt service routine, the better everything works. Setting a variable as gdsports already wrote is the shortest possible option, and thus the best option. Do not use Wire.endTransmission() after a Wire.requestFrom(). The Wire.endTransmission() should only be used when writing data to a sensor.

The SerialEvent() is a silly function. It is not called when a byte is received, but it is done by polling after every loop() iteration. When you program the sketch in a good way, there is no need to use that function. Most prefer not to use the SerialEvent() but to poll the serial input in the loop() because then all the variables in the loop() can be used.

Koepel: batuorhungunduz, what is your background ? Are you a software engineer with experience in an other language or processors ? Or are you a beginner that knows how to gather pieces of software that are a few years behind ? Or is this a project as a student and you have been given a good start by the professor ?

First of all, the MPU-9250 is better than the MPU-6050. The AHRS sensor fusion works well on small microcontrollers like the Arduino boards. You can find examples for MPU-9250 with AHRS at Sparkfun. Never use the SoftwareSerial in a quadcopter. The SoftwareSerial can use almost the complete Arduino processing time. There will only be some time left to blink a few leds. Avoid the writing directly to the timer registers if possible. When you write directly to a timer register, you are stuck with that type of microcontroller. The smaller the interrupt service routine, the better everything works. Setting a variable as gdsports already wrote is the shortest possible option, and thus the best option. Do not use Wire.endTransmission() after a Wire.requestFrom(). The Wire.endTransmission() should only be used when writing data to a sensor.

The SerialEvent() is a silly function. It is not called when a byte is received, but it is done by polling after every loop() iteration. When you program the sketch in a good way, there is no need to use that function. Most prefer not to use the SerialEvent() but to poll the serial input in the loop() because then all the variables in the loop() can be used.

Im just a begginer hobbyist that doesnt know all that much. This is a school project and all the programming work is on me. I have seen the MPU9250 but I figured that I dont really need the magnetometer. I dont have all that much knowledge into the advanced parts of arduino coding. I also dont think I have enough time or effort to learn a completely different microcontroller and I would appreciate it if you could give more detail on what you said about that gdsports part.

gdsports:
In the ISR, set a volatile global variable to true. In the loop function, when the flag is true, use I2C to read results from the MPU6050. Then set the flag to false. This example works this way.

i2cdevlib/MPU6050_DMP6_ESPWiFi.ino at master · jrowberg/i2cdevlib · GitHub

I get what you want but as long as the loop doesn’t loop faster than 2khz I think I will lose some data. I also have to get some sort of PID angle correction system which will definetly slow things down. I wouldn t like to be a hypocryte but If you two know a more capable microconroller which uses the arduino IDE (STM32 or something) I would like to hear about it

The Arduino Nano is a 5V board with 5V SDA and SCL signals. The MPU-6050 is a 3.3V sensor with 3.3V SDA and SCL signals. Have you solved that problem.

You can continue with the Nano, and if you need to, then you can scale up to the Arduino Zero later on.

Remove the SoftwareSerial please. Don't use the SerialEvent(). The MPU-6050 and MPU-9250 have a processing unit inside. If you upload 'dmp' code to the sensor, the sensor can sample data at a fixed rate and put it in the FIFO buffer and activate the interrupt signal. In the Arduino you would check the interrupt, set a flag, and read data in the loop() if the flag is set.

Is 2kHz too slow ? The MPU-6050 + Nano is for about 100Hz sample rate. Do you know how noisy the MPU-6050 is ?

Useful background information on using the DMP inside the MPU6050 to get orientation.

http://www.geekmomprojects.com/mpu-6050-dmp-data-from-i2cdevlib/

gdsports: Useful background information on using the DMP inside the MPU6050 to get orientation.

http://www.geekmomprojects.com/mpu-6050-dmp-data-from-i2cdevlib/

Koepel: The Arduino Nano is a 5V board with 5V SDA and SCL signals. The MPU-6050 is a 3.3V sensor with 3.3V SDA and SCL signals. Have you solved that problem.

You can continue with the Nano, and if you need to, then you can scale up to the Arduino Zero later on.

Remove the SoftwareSerial please. Don't use the SerialEvent(). The MPU-6050 and MPU-9250 have a processing unit inside. If you upload 'dmp' code to the sensor, the sensor can sample data at a fixed rate and put it in the FIFO buffer and activate the interrupt signal. In the Arduino you would check the interrupt, set a flag, and read data in the loop() if the flag is set.

Is 2kHz too slow ? The MPU-6050 + Nano is for about 100Hz sample rate. Do you know how noisy the MPU-6050 is ?

I have heard about the dmp of the mpu6050 before but i didnt really look into it as it seemed like the manufacturers didnt want us simple folk to use it. I did however hear that someone craked the codes but i brushed it off thinking it may not be reliable. I will definetely look into it and thanks for the link. My MPU6050 is on a gy521 board so it just works ok with 5v. As to slow things down and to bring the noise down i set the digital low pass filter to max that brings the output rate to 1khz. It actually worked just fine with the accelerometer alone but i cant use it in a mobile platform. I did think of setting a sample rate divider as you might have seen in the code but i didnt want a slow sampling rate. What i want is the fastest sampling rate and slow as possible output rate with the extra data being averaged out to make the least of noise. If the dmp is as amazibg as the datasheet claims i suppose will be able to make all the complex calculations and just output the angle data to the arduino. Thanks for your support.

The GY-521 board has a voltage regulator to make 3.3V for the MPU-6050. The SDA and SCL are however directly connected to pins of the MPU-6050.

That is the problem. On one side are the SDA and SCL pins of the Arduino Nano. Which are 5V signals. On the other side are the SDA and SCL pins of the MPU-6050. Which are 3.3V signals. You can connect them and hope that nothing gets broken. It might work, even though it is outside the specifications of the datasheets. You could do it right, and use a 3.3V Arduino board or a 5V Arduino board with a I2C level shifter.

The manufacturer of the MPU-6050 is Invensense. They finally decided to make the code for the dmp open. I think that the I2Cdevlib still uses the dmp code that is collected with reverse engineering. That dmp code is okay, you can use it. It is not "cracked" code, but reverse engineered (that sounds a lot better, isn't it ?).