Go Down

Topic: Hall effect automotive speed sensor noise (Read 222 times) previous topic - next topic

techie66

I have a ZF gs100701 (Datasheet, open collector) set up next to a drive sprocket on a motorcycle. The sensor is powered from vehicle +12V. I am interfacing it with a Sleepy Pi2 (ATMega328p) with a 1k external pullup to 3V3 on the output pin, which is connected to the "arduino". The code sets up an interrupt on FALLING edge that calculates microseconds since the last pulse and averages the readings with a minimal ring-type buffer.

When the engine is off, it appears to work fine. When the engine is started, there appears a lot of random triggers. The first thing I tried was a simple RC filter with 1k series and 100nF to ground. That killed the noise, but it missed a lot of actual pulses. I played around with different capacitors and resistors and nothing really worked. Any type of RC circuit seemed to either let noise in or would filter the actual signal. At higher wheel speeds (ie higher frequency signals) the interrupt would fire less often than at lower speeds leading me to believe that cap. rise time was causing me to miss signal edges.

My RPM signal comes in as a +12V pulse, so I removed the wires completely to rule out interference and there was no change. Still random ghost pulses when the bike is running and no other filtering.

Then I thought perhaps, since according to the datasheet 5v required a 1k pullup, that with 3V3 logic 1k was too weak. I put in a 680 resistor that helped a bit, but there is still a lot of noise.

What have I missed? What should I try next?

(code in next post)

techie66

Code: [Select]
#include <SleepyPi2.h>
#include <Time.h>
#include <LowPower.h>
#include <PCF8523.h>
#include <Wire.h>
#include <PinChangeInterrupt.h>

#define SLAVE_ADDRESS 0x04

const unsigned long     MAX_UINT16_T = 4294967295,
                        MICROS_SECOND = 1000000,
                        SHUTDOWN_DELAY = 60000;

const int               bikeOnPin = 12,
 rpmPulsePin = 2,
 wheelPulsePin = 3,
 ledPin = 13;
const uint8_t rpm_pulses = 10,
       speed_pulses = 10;
 // MPH_CONV = in/mi * h/min * in/pulse
//speed = ((100 * 60 * MICROS_SECOND / SPEED_interval) * MPH_CONV * speed_pulses);
//const float MPH_CONV = 1.96071428571*60/63360;
const uint32_t MPH_CONV = 1.96071428571*60*100*60*MICROS_SECOND*speed_pulses/63360;
//rpm = ((60 * MICROS_SECOND / RPM_interval) * rpm_pulses);
const uint32_t RPM_CONV = 60*MICROS_SECOND*rpm_pulses;

uint16_t                rpm = 0,
 temp_oil = 0,
 speed = 0;
float supply_voltage = 0.0;
volatile unsigned long  RPM_interval = 0,
 SPEED_interval = 0,
                        rpm_time_last = 0,
                        speed_time_last = 0,
                        rpm_wkg_micros = 0,
                        speed_wkg_micros = 0;
volatile unsigned long rpm_pulse_times[rpm_pulses] = {0},
 speed_pulse_times[speed_pulses] = {0};
volatile bool           bike_running = false;
unsigned long           wkgTime = 0,
                        time,
                        time_BikeOff = 0;

volatile uint8_t        rpm_pulse_count = 0,
 speed_pulse_count = 0;;

boolean                 debug = true;

// callback for received data
void receiveData(int byteCount){
  // Maybe store a cmd to output requested data
}

// callback for sending data
void sendData(){
  Wire.write((const uint8_t*)&rpm,sizeof(rpm));
  Wire.write((const uint8_t*)&temp_oil,sizeof(temp_oil));
  Wire.write((const uint8_t*)&speed,sizeof(speed));
  Wire.write((const uint8_t*)&supply_voltage,sizeof(supply_voltage));
}

// ISR to get pulse interval for RPM(engine)
// a little longer than I'd like, since it handles the averaging
// but it saves clock cycles overall by only calculating interval once
void rpmPulse() {
  // Save current micros
  rpm_wkg_micros = micros();
  // increment counter and reset if necessary
  rpm_pulse_count++;
  if (rpm_pulse_count >= rpm_pulses) {
    rpm_pulse_count = 0;
  }
  // decrement RPM_interval by oldest interval
  RPM_interval -= rpm_pulse_times[rpm_pulse_count];
  // save new interval and add to RPM_interval
  rpm_pulse_times[rpm_pulse_count] = (rpm_wkg_micros - rpm_time_last);
  RPM_interval += rpm_pulse_times[rpm_pulse_count];
  // Save last time we were called
  rpm_time_last = rpm_wkg_micros;
}


// ISR to get pulse interval for wheel(engine)
// same documentation as rpmPulse
void wheelPulse() {
  speed_wkg_micros = micros();
  speed_pulse_count++;
  if (speed_pulse_count >= speed_pulses) {
    speed_pulse_count = 0;
  }
  SPEED_interval -= speed_pulse_times[speed_pulse_count];
  speed_pulse_times[speed_pulse_count] = (speed_wkg_micros - speed_time_last);
  SPEED_interval += speed_pulse_times[speed_pulse_count];
  speed_time_last = speed_wkg_micros;
}

// ISR to signal bike is on
void bikeOn() {
  bike_running = !digitalRead(bikeOnPin);
}

// Output serial debug if debug is true
void debug_out(const char str[]) {
  if (debug) {
    Serial.write(str,strlen(str));
  }
}

void setup() {
  SleepyPi.enableWakeupAlarm(false);
  SleepyPi.rtcInit(false);
  SleepyPi.enablePiPower(false);
  SleepyPi.enableExtPower(false);

  // initialize i2c as slave
  Wire.begin(SLAVE_ADDRESS);
  
  // define callbacks for i2c communication
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);

  // Setup pins
  pinMode(ledPin,OUTPUT); // Interrupt
  pinMode(rpmPulsePin,INPUT_PULLUP); // Interrupt
  pinMode(wheelPulsePin,INPUT_PULLUP); // Interrupt
  pinMode(bikeOnPin, INPUT_PULLUP);

  digitalWrite(ledPin,HIGH);

  // PinChangeInterrupt can always be abbreviated with PCINT
  attachPCINT(digitalPinToPCINT(bikeOnPin), bikeOn, CHANGE);
  disablePCINT(digitalPinToPCINT(bikeOnPin));

  attachInterrupt(digitalPinToInterrupt(rpmPulsePin), rpmPulse, FALLING);//Initialize the intterrupt pin (Arduino digital pin 2);
  attachInterrupt(digitalPinToInterrupt(wheelPulsePin), wheelPulse, FALLING);//Initialize the intterrupt pin (Arduino digital pin 3);
  
  if (debug) {
    Serial.begin(9600); // start serial for output
    Serial.println("Ready!");
  }
}

void loop() {
  bool pi_running;
  float rpi_current = 0.0;

  pi_running = SleepyPi.checkPiStatus(50,false);
  bike_running = !digitalRead(bikeOnPin);
  rpi_current = SleepyPi.rpiCurrent();

  if (debug) {
    Serial.print("Current: ");
    Serial.print(rpi_current);
    Serial.print(" pi_running=");
    Serial.print(pi_running);
    Serial.print(" bike_on=");
    Serial.println(bike_running);
  }

  while(pi_running == false && bike_running == false) {
    debug_out("Going to Sleep\r\n");
    delay(500);
    SleepyPi.enablePiPower(false);
    SleepyPi.enableExtPower(false);
    digitalWrite(ledPin,LOW);
    enablePCINT(digitalPinToPCINT(bikeOnPin));
    SleepyPi.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
    disablePCINT(digitalPinToPCINT(bikeOnPin));
    digitalWrite(ledPin,HIGH);
    pi_running = SleepyPi.checkPiStatus(50,false);
    bike_running = !digitalRead(bikeOnPin);
  }


  if (bike_running) {
    time_BikeOff = 0;
    if (pi_running) {
      // Turn on power
      // Usually redundant, but keeps from resetting the Pi when turning off software power jumper
      SleepyPi.enablePiPower(true);
      SleepyPi.enableExtPower(true);
    }
    else {
      // Bike is on, but Pi is not, reset power to Pi
      // This handles a situation where Pi shutdown on its own
      SleepyPi.enablePiPower(true);
      SleepyPi.enableExtPower(true);
      debug_out("Wait 5s for pi to start running.\r\n");
      delay(5000);
      pi_running = SleepyPi.checkPiStatus(50,false);
      if (!pi_running) {
        debug_out("Turning off Pi.\r\n");
        SleepyPi.enablePiPower(false);
        SleepyPi.enableExtPower(false);
        delay(50);
        debug_out("Turning on Pi.\r\n");
        SleepyPi.enablePiPower(true);
        SleepyPi.enableExtPower(true);
      }
    }
      
  }
  else {
    if (time_BikeOff == 0) {
      time_BikeOff = time;
    }
    if(pi_running) {
      if (time - time_BikeOff > SHUTDOWN_DELAY) {
        SleepyPi.piShutdown();
        SleepyPi.enableExtPower(false);
        SleepyPi.enablePiPower(false);
      }
    }
  }
  time = millis();

  delay(10);  // voltage reading is artificially high if we don't delay first
  supply_voltage = SleepyPi.supplyVoltage();

  // Calculate RPM
  wkgTime = RPM_interval;
  if (micros() > ( rpm_time_last + MICROS_SECOND ) ){
    wkgTime = MAX_UINT16_T;
    rpm = 0;
  }
  
  if (wkgTime < MAX_UINT16_T) {
    // ( s/min * us/s / us/C*pulse ) * C = RPM
    // RPM_CONV is constant to solve equation since only variable is pulse time
    rpm = (RPM_CONV / wkgTime);
  }

  // Calculate Speed (x100)
  wkgTime = SPEED_interval;
  if (micros() > ( speed_time_last + MICROS_SECOND ) ){
    wkgTime = MAX_UINT16_T;
    speed = 0;
  }
  
  if (wkgTime < MAX_UINT16_T) {
    // Each pulse is 1.96071428571", SPEED_interval is the time it takes for speed_pules
    // ( A * s/min * us/s) / us/C*in ) * CONV * C = MPH*100
    // A = multiply constant to give us MPH*100
    // CONV = in/mi * h/min * in/pulse
    // C = pulses
    // MPH_CONV is a constant calculated to solve to equation, since the only variable is pulse time
    speed = ( MPH_CONV / wkgTime);
  }
  if (debug) {
      delay(500);
      Serial.print("Speed Pulses:");
      Serial.print(speed_pulse_count,DEC);
      Serial.print(" SPEED: ");
      Serial.println(speed,DEC);
      Serial.print("RPM Pulses:");
      Serial.print(rpm_pulse_count,DEC);
      Serial.print(" RPM: ");
      Serial.println(rpm,DEC);
  }
  
}

MarkT

You need to reduce the interference, which means shielding, attention to avoiding ground loops,
reducing impedance of sensitive signals, careful conditioning of the power supply to the arduino.

I guess the interference comes from spark plug leads, distributer, magneto or even alternator,
and in most automotive systems is pretty gross.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

techie66

I read your suggestions and thought D'oh I forgot about power supply filtering. So I went and tried putting some caps between + and - on my +12V input  (1000uF, 100nF) Turned it back on and guess what? No change! I had really thought that was the golden ticket. For better or worse, I kept playing around with filtering for a bit and found out that the noise disappeared at higher RPMs. Strange. I finally checked the sensor itself. I adjusted the orientation of the sensor just a smidge and suddenly I had a very solid signal. So undeterred, I put a simple 10nF cap to GND on the input pin and presto! All the low frequency noise was attenuated.

I very much appreciate the suggestions, they gave me some new possibilities to explore. Funny that you mention the gross electrical environment in a vehicle, because you are very right and I have been dealing with it on several fronts since I started the project.

jpom

I tried interrupts on my speedo project (car). Conflicts with Arduino timing functions; too tricky to solve.

Switched to using pin polling and works a treat.

Powered from car's USB.

MarkT

I have a ZF gs100701 (Datasheet, open collector) set up next to a drive sprocket on a motorcycle. The sensor is powered from vehicle +12V. I am interfacing it with a Sleepy Pi2 (ATMega328p) with a 1k external pullup to 3V3 on the output pin, which is connected to the "arduino". The code sets up an interrupt on FALLING edge that calculates microseconds since the last pulse and averages the readings with a minimal ring-type buffer.

When the engine is off, it appears to work fine. When the engine is started, there appears a lot of random triggers. The first thing I tried was a simple RC filter with 1k series and 100nF to ground. That killed the noise, but it missed a lot of actual pulses. I played around with different capacitors and resistors and nothing really worked. Any type of RC circuit seemed to either let noise in or would filter the actual signal. At higher wheel speeds (ie higher frequency signals) the interrupt would fire less often than at lower speeds leading me to believe that cap. rise time was causing me to miss signal edges.

My RPM signal comes in as a +12V pulse, so I removed the wires completely to rule out interference and there was no change. Still random ghost pulses when the bike is running and no other filtering.

Then I thought perhaps, since according to the datasheet 5v required a 1k pullup, that with 3V3 logic 1k was too weak. I put in a 680 resistor that helped a bit, but there is still a lot of noise.

What have I missed? What should I try next?

(code in next post)

Fully shielded cables to the sensor?  Only grounded at the Arduino end?  Shielded spark plug leads?
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

techie66

#6
Apr 03, 2019, 01:05 am Last Edit: Apr 03, 2019, 01:08 am by techie66
Sensor does not have shielded cable. Spark leads are shielded. Sensor is grounded at MCU.

The issue seems to be resolved at this point with the changes I outlined above.

wvmarle

Then I thought perhaps, since according to the datasheet 5v required a 1k pullup, that with 3V3 logic 1k was too weak. I put in a 680 resistor that helped a bit, but there is still a lot of noise.
That are some pretty strong pull-ups, that you still see noise means you're in a very noisy environment.

A small cap on the sensor lead would indeed filter out a lot of that noise - but as prevention is always better than a cure you may be even better off by using a shielded cable to your spark plug. That should keep the noise out to begin with.
Quality of answers is related to the quality of questions. Good questions will get good answers. Useless answers are a sign of a poor question.

Go Up