Double tachometer shows wrong signals after certain time

Hi I am new to this forum but I work with arduino for a few projets now. Right now I am building a miniature wind turbine in a small wind tunnel for education. I can set wind speed of the tunnel with a pwm signal to the motor. I measure the actual wind speed by taking the rpm of the fan (wich i have correlated to an thermal anemometer reading) . The fan has a built in tachometer. I am reading the rpm of the wind turbine as well by taking an opto sensor signal from a disk with 8 holes. Both RPM of the fan and the turbine is in the range of 0...1500 rpm. The turbine is also equipped with other sensors and functions. The turbine is connected via USB to a GUI written in python to plot data in real time and interact with the system. The computer programm triggers the serial connection aroud every 60 ms.
Difficulty is: The system has to show dynamic effects as precise as possible without time delay. So the classical tick counter does not help me with rpm measurement. I am using rotating buffer to low pass filter the measurement data and always get the most actual data.
Problem: With just one interrupt function I get very good data. As soon as both interrrupts are attached, after 420 seconds I get corrupted data for the fan rpm and the hx711 force sensor. After 840 seconds of running data is good again. But I can't see any overflow problem here.
Maybe you guys have tricks to improve the code. I know that lots is happening with all the sensor readings, serial connection and two interrupts. And I can see that this could cause problems. But I wonder why it works perfectly for the first 420 seconds.
Board is a Mega btw.

//LIBRARIES
#include <Wire.h>
#include <INA219_WE.h>
INA219_WE CurrentSensor = INA219_WE(0x40);
#define I2C_ADDRESS 
#include <Servo_Hardware_PWM.h>
Servo PitchServo; 

//PIN DEFINITION
const int pin_servo               = 2;  //orange cable
const int pin_turbine_tacho       = 18;  
const int pin_fan_tacho           = 19; //yellow cable
const int pin_fan_pwm             = 45; //green cable
const int pin_fan_on              = 43; //red cable //GND blue cable
const int pin_force_clk           = 8;
const int pin_force_data          = 9;
const int pin_turbine_start       = 48;
const int pin_torque_pwm          = 46;
const int pin_LED                 = 13;


//VARIABLES
uint8_t fan_pwm                 = 100; //PWM value 0-255
uint8_t torque_pwm              = 0;
uint16_t us_servo               = 900;
unsigned long time_fan_tick     = micros();
unsigned long time_turbine_tick = micros();
unsigned long dt_fan_mean       = 0;
unsigned long dt_turbine_mean   = 0;
bool rotating_fan               = false;
bool rotating_turbine           = false;
uint16_t speed_fan              = 0;
uint16_t speed_turbine          = 0;
float currentfloat              = 0;
uint16_t current                = 0;
uint32_t thrust_force           = 0;
uint32_t thrust_force_offset    = 0;

//ROTARY BUFFERS
unsigned long rot_turbine_tick[8];  //8 values stored for averaging (1/2 revolution)
int pos_turbine_tick = 0;
unsigned long rot_fan_tick[2];      //2 values stored for averaging (1 revolutions)
int pos_fan_tick = 0;

void setup() {
  // put your setup code here, to run once:
  //attach servo
  PitchServo.attach(pin_servo);
  PitchServo.writeMicroseconds(900);
  Wire.begin();

  //define pin functions
  pinMode(pin_turbine_tacho,INPUT_PULLUP);
  pinMode(pin_fan_tacho,INPUT_PULLUP);
  pinMode(pin_fan_pwm,OUTPUT);
  analogWrite(pin_fan_pwm,100);
  pinMode(pin_fan_on,OUTPUT);
  digitalWrite(pin_fan_on,HIGH);
  pinMode(pin_force_clk,OUTPUT);
  digitalWrite(pin_force_clk,LOW);
  pinMode(pin_force_data,INPUT);
  pinMode(pin_turbine_start,OUTPUT);
  digitalWrite(pin_turbine_start,HIGH);
  pinMode(pin_torque_pwm,OUTPUT);
  analogWrite(pin_torque_pwm,0);
  pinMode(pin_LED,OUTPUT);
  digitalWrite(pin_LED,HIGH);  

  if (CurrentSensor.init()){
  digitalWrite(pin_LED,HIGH);
  }
  else{
  digitalWrite(pin_LED,LOW);
  }
  
  CurrentSensor.powerUp();
  CurrentSensor.setADCMode(SAMPLE_MODE_64);//average 64 values
  CurrentSensor.setMeasureMode(CONTINUOUS); // choose mode and uncomment for change of default
  CurrentSensor.setPGain(PG_40);//max 0.4 A
  CurrentSensor.setBusRange(BRNG_16); //max 16V

  delay(10);
  //set gain for force sensor
  hx711_set_gain();
  delay(500);
  thrust_force_offset = hx711_read_force();
  
  //change the timer 5 to increase the PWM frequency for the fan
  TCCR5B  = TCCR5B & 0b11111000 | 0x02; //Timer 5 is a 16-Bit timer. resulting frequency ~ 4kHz

  //attach interrupt functions
  attachInterrupt(digitalPinToInterrupt(pin_turbine_tacho),turbine_interrupt,CHANGE);
  attachInterrupt(digitalPinToInterrupt(pin_fan_tacho),fan_interrupt,CHANGE);

  //begin serial connection
  Serial.begin(38400);
  delay(100);
}

void loop() {
  // put your main code here, to run repeatedly:

  //wait for data request
  while (Serial.available()==0){}
  Serial.read();

  //calculate data
  if (rotating_fan==true){
    if (micros()-time_fan_tick < 500000ul){
      dt_fan_mean=rot_fan_tick[0];
      dt_fan_mean+=rot_fan_tick[1];
      dt_fan_mean/=2;
      speed_fan=60000000/dt_fan_mean/2; //two ticks per rotation
    }
    else {
      speed_fan=0;
      rotating_fan=false;  
    }
  }
  
  if (rotating_turbine==true){
    if (micros()-time_turbine_tick < 625000ul){
      dt_turbine_mean=rot_turbine_tick[0];
      dt_turbine_mean+=rot_turbine_tick[1];
      dt_turbine_mean+=rot_turbine_tick[2];
      dt_turbine_mean+=rot_turbine_tick[3];
      dt_turbine_mean+=rot_turbine_tick[4];
      dt_turbine_mean+=rot_turbine_tick[5];
      dt_turbine_mean+=rot_turbine_tick[6];
      dt_turbine_mean+=rot_turbine_tick[7];
      dt_turbine_mean/=8;
      speed_turbine=60000000/dt_turbine_mean/16; //16 ticks per rotation
    }
    else {
      speed_turbine=0;
      rotating_turbine=false;  
    }
  }
  
  currentfloat=CurrentSensor.getCurrent_mA();
  current=(uint16_t)currentfloat;
  
  if (digitalRead(pin_force_data) == LOW){
    thrust_force=hx711_read_force();
  }
  //write data
  Serial.write((byte*)(&speed_fan),2);  // 2bytes
  Serial.write((byte*)(&speed_turbine),2);  // 2bytes
  Serial.write((byte*)(&current),2);  // 2bytes
  Serial.write((byte*)(&thrust_force),4);  // 4bytes

  //read data
  while (Serial.available()==0){}
  analogWrite(pin_fan_pwm,Serial.read());
  while (Serial.available()==0){}
  us_servo = Serial.read();
  while (Serial.available()==0){}
  us_servo |= (Serial.read()<<8); // 2 byte little endian
  PitchServo.writeMicroseconds(us_servo);
  while (Serial.available()==0){}
  analogWrite(pin_torque_pwm,Serial.read());
  while (Serial.available()==0){}
  if (Serial.read() == 1){
    digitalWrite(pin_turbine_start,LOW);
  }
  else {
    digitalWrite(pin_turbine_start,HIGH);
  }
}


void fan_interrupt(){
  if (micros()-time_fan_tick > 30000ul){
    rot_fan_tick[pos_fan_tick%2] = micros()-time_fan_tick;
    time_fan_tick = micros();
    pos_fan_tick++;
    rotating_fan = true;
  }
}

void turbine_interrupt(){
  if (micros()-time_turbine_tick > 1000ul){
    rot_turbine_tick[pos_turbine_tick%8] = micros()-time_turbine_tick;
    time_turbine_tick = micros();
    pos_turbine_tick++;
    rotating_turbine = true;
  }
}

uint32_t hx711_read_force(){
  uint32_t value = 0;
  uint32_t mask = 0x00800000; //1 at the 24th bit

  while (mask > 0){
    digitalWrite(pin_force_clk, HIGH);
    delayMicroseconds(1);   //  T2  >= 0.2 us
    if (digitalRead(pin_force_data) == HIGH)
    {
      value |= mask;
    }
    digitalWrite(pin_force_clk, LOW);
    delayMicroseconds(1);   //  keep duty cycle ~50%
    mask >>= 1;
  }
  hx711_set_gain();
  return value-thrust_force_offset;
}
void hx711_set_gain(){
    for (int i=0; i<3; i++){ //set gain
    digitalWrite(pin_force_clk, HIGH);
    delayMicroseconds(1);   //  T2  >= 0.2 us
    digitalWrite(pin_force_clk, LOW);
    delayMicroseconds(1);   //  keep duty cycle ~50%
  }
}

If there is no good solution I may have to put the two rpm measurements to seperate micro controllers.
HX711 function is taken from Rob Tillaarts HX711 library. Just for credits :slight_smile:

Before you do anything else, you need to fix your broken ISR implementation. You are breaking several common rules that you need to follow to make them work reliably. So that I don't miss any, instead of listing them, I'll just point you here:
https://gammon.com.au/interrupts

1 Like

One possible problem is the micros() value changing between lines of code. One thing I would try is putting:
unsigned long currentMicros = micros();
at the top of each function that uses 'micros()' more than once and use 'currentMicros' in place of calling 'micros()' each time.

1 Like

try

     speed_turbine=60000000UL/dt_turbine_mean/16; //16 ticks per rotation

Also:
Any variables shared between an ISR and other functions should be marked 'volatile'.

Any 'volatile' variable should only be read or written with the interrupts disabled so in each (non-ISR) function that uses a 'volatile' variable, call 'noInterrupts();', make a local copy of the variable, and then call 'interrupts();'.

Note: You should probably do the averaging in the ISR so you don't have to copy the entire array to a local array before averaging. You can calculate a running average without having to add all 8 samples together each time.

1 Like

@anon57585045 Thank you, I started to work my way trough it.
@johnwasser I declared the variables as volatile and switched interrupts off during the averaging. It helped with clearing a few messed up data points once in a while. But the main problem, that after a certain time the measurement is completely off is still there. I attach a graph where you can see the wind speed. Up to 260s measurement is fine. From 260s to 520s something weird happens. after 520s everything is fine again.


What confuses me the most is that also the thrust force sensor data (HX711) is corrupted during the same period of time.
I tried using the LED as a flag to see if the fan rpm gets unrealistic high values within the controller. And it does. So it cant be a problem of a broken serial connection.

I got it. Stupid mistake. After 2047 revolutions of the turbine, the integer pos_turbine_tick overflows. Which messes up the rotary buffer index and writing values all over the memory I guess.
Changed the position indices to uint8_t instead and it works perfectly for 15 minutes now.
Really don't know why I declared that as int in the beginning :man_facepalming:
Thank you for your help, really appreciated it :slight_smile:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.