[SOLVED] PulseIn incorrect values on low frequency

Hi guys, I've been working on a project for a few weeks and here's the basic rundown of it: I have a car's velocity sensor in hand and whenever the sensor's axis rotates it'll send x amount of pulses through its signal output, where x depends on the sensor's model. So I've attached it to a stepping motor and have build a circuit with arduino and a driver.

The arduino is sending pulse signals to the driver, rotating the motor at 4 different speeds (2, 4, 8 and 16Hz - or 120, 240, 480 and 960 RPM) and during each speed I'm reading the pulse signal with PulseIn, calculating the frequency and comparing to the motor's frequency so I can find out how many pulses the sensor's outputting during one revolution.

Everything's working as it should, except when I swapped the driver for a more robust one when I get to the 8 and 16 Hz frequencies, the measurements from PulseIn stop matching what I'm reading with an osciloscope, and I don't really know why. I've included the code below (sorry, the comments are in Portuguese) if any of you want to take a look.

I've also considered using interrupts, but I'm unfortunately already using pins 2 and 3 on my uno for the LCD display, as it was working properly with my previous driver. In any case, the frequency of the signal is getting to my pin correctly, it's just that the PulseIn is somehow reading an incorrect value and I have no clue as to why.

Any help would be greatly appreciated <3

#include <LiquidCrystal.h>
int input = 12;
int freq_pin = 8;
float PPR = 20 * 200;
int start = 13;


// Definição das Rampas de Rotação

float ramp1 = PPR;
float ramp2 = PPR * 2;
float ramp3 = PPR * 4;
float ramp4 = PPR * 8;
float ramp5 = PPR * 16;

// Definição de Variáveis

unsigned long  high_time;
unsigned long  low_time;
float time_period;
float frequency;
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

void setup()
{

  // Inicia comunicação Serial e Limpa dados do Excell
  
  Serial.begin(9600);
  Serial.println("CLEARDATA");

  // Seta pinos do sistema e inicia o display LCD
  
  pinMode(input, INPUT);
  pinMode(freq_pin, OUTPUT);
  pinMode(start, INPUT);
  lcd.begin(16, 4);

  // Mensagem inicial no display LCD
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("MTE-THOMSON");
  lcd.setCursor(0, 2);
  lcd.print("Teste Sensor Vel");
  delay(1000);


}

void loop()
{

  // Loop principal, inicia dados caso a chave do sistema esteja em LIGADO
  
  if (digitalRead(start) == LOW) {

    // Inicia as tabelas no Excell
    
    Serial.println("CLEARDATA");
    Serial.println("LABEL,ROTAÇÃO(RPM),PULSOS POR REVOLUÇÃO");

    // Escreve a mensagem de display no LCD
    
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Pulsos Por Rev");
    lcd.setCursor(0, 2);
    lcd.print("Velocidade");

    // Realiza primeira rampa na rotação do motor
    
    Rampa_Motor(ramp1, ramp2, 1);
    delay(500);

    // Chama a subrotina que realiza leitura do número de pulsos do sensor
    
    Leitura_Pulsos(ramp2, 1);
    delay(500);

    // Realiza a segunda rampa na rotação
    
    Rampa_Motor(ramp2, ramp3, 1);
    delay(500);

    // Chama a subrotina para realização da segunda leitura de pulsos
    
    Leitura_Pulsos(ramp3, 1);
    delay(500);

    // Realiza a terceira rampa na rotação

    Rampa_Motor(ramp3, ramp4, 1);
    delay(500);

    // Chama a subrotina para realização da terceira leitura de pulsos
    
    Leitura_Pulsos(ramp4, 1);
    delay(500);

    // Realiza a rampa de redução da rotação do motor para um novo ciclo de testes
    
    Rampa_Motor(ramp4, ramp5, 1);

    // Chama a subrotina para realização da terceira leitura de pulsos
    
    Leitura_Pulsos(ramp5, 1);
    delay(500);

    Rampa_Motor(ramp5, ramp1, 0);

  }

  // Caso o switch esteja desativado, o sinal de pulsos emitido para o motor é nulo

  else if (digitalRead(start) == HIGH) {
    noTone(freq_pin);
  }

}


// Subrotina de Leitura do número de pulsos do sensor

void Leitura_Pulsos(float rota, float fator) {

  // Definição de variáveis

  int pulso = 0;
  float contador = 0;
  float freq_Hz = rota / PPR;

  // Prints na Serial (para teste)

  Serial.print("Rotacao: ");
  Serial.println(rota);
  Serial.print("PPR: ");
  Serial.println(PPR);
  Serial.print("Freq Hz: ");
  Serial.println(freq_Hz);

  delay(200);

  // Loop de aquisição de pulsos

  for (int i = 0; i < 10; i++) {

    // A função pulseIn calcula o tempo que o sensor leva nas posições HIGH e LOW

    high_time = pulseIn(input, HIGH,2000000)*fator;
    delay(250);
    low_time = pulseIn(input, LOW,2000000)*fator;

    // Print na Serial (para testes)

    Serial.print("High Time: ");
    Serial.println(high_time);
    Serial.print("Low Time: ");
    Serial.println(low_time);

    // Caso os tempos em HIGH e LOW não sejam nulos (existe pulso), calculamos a frequência dos pulsos

    if (high_time && low_time != 0) {

      // Cálculo da frequência dos pulsos do sensor baseado nos tempos em HIGH e em LOW
      
      time_period = high_time + low_time;
      frequency = 1000000 / time_period;
      Serial.print("Frequency: ");
      Serial.println(frequency);
      pulso = pulso + frequency;
      contador = contador + 1;

    }

    // Caso os tempos em HIGH ou LOW sejam nulos (não há pulso ou há erro de leitura) saimos da subrotina e imprimimos mensagem de erro
    
    else if (high_time || low_time == 0) {
      lcd.setCursor(0, 1);
      lcd.print("Sem Leitura");
      Serial.println("SEM LEITURA,SEM LEITURA");
      return;
    }
  }

  // Cálculo do número de pulsos no sensor baseado na relação da frequência de seu sinal e na rotação do motor

  pulso = pulso / (freq_Hz * contador);

  // Display do número de pulsos e da rotação de testes do motor no display LCD

  lcd.setCursor(0, 1);
  lcd.print(pulso);
  lcd.print(" Pulsos");
  lcd.setCursor(0, 3);
  lcd.print(60 * freq_Hz);
  lcd.print(" RPM");

  // Prints em Serial (para testes)

  Serial.print("RPM: ");
  Serial.println(60 * freq_Hz);
  Serial.print("Pulsos: ");
  Serial.println(pulso);

  delay(500);
}

// Subrotina que realiza a rampa de aceleração do motor de passos

void Rampa_Motor(float freq_min, float freq_max, float sinal) {

  // Definição de variáveis

  float dfreq = ramp1 / 2;
  float dfreq_step = ramp1 / 2;
  float x_steps = abs(freq_max - freq_min) / dfreq_step;

  // Envio da frequência inicial

  tone(freq_pin, freq_min);

  // Caso o bit de sinal enviado seja 1, realizamos uma rampa de aceleração

  if (sinal == 1) {
    for (int i = 0; i < x_steps; i++) {
      tone(freq_pin, freq_min + dfreq);
      dfreq = dfreq + dfreq_step;
      delay(1);
    }

    Serial.print("Tone: ");
    Serial.println(freq_min + dfreq - dfreq_step);
  }

  // Caso o bit de sinal enviado seja 0, realizamos uma rampa de desaceleração

  else if (sinal == 0) {
    for (int i = 0; i < x_steps; i++) {
      tone(freq_pin, freq_min - dfreq);
      dfreq = dfreq + dfreq_step;
      delay(1);
    }

  }


}

You have a heavy use of float where it isn't needed. Float is slow and lack precision. Even if compiler does it best to use int in calculations instead of float, you better define variables as unsigned int or unsigned long.

I have no idea if your lack of precision depends on use of float, but you can always try to change. I have made some comments in your code.

float PPR = 20 * 200;                //= 4000
...
float ramp1 = PPR;                   //= 4000
float ramp2 = PPR * 2;               //= 8000  
float ramp3 = PPR * 4;               //=16000
float ramp4 = PPR * 8;               //=32000
float ramp5 = PPR * 16;              //=64000

unsigned long  high_time;             // thumbs up
unsigned long  low_time;       
...
Leitura_Pulsos(ramp2, 1);
...
Leitura_Pulsos(ramp5, 1);
...
void Leitura_Pulsos(float rota, float fator)   //rota=4000~64000, fator=1
... 
int pulso = 0;                                 //int=-32k~32k
float contador = 0;                            //Float for a counter?
float freq_Hz = rota / PPR;                    //Back to rampX factor, still only integer
...
high_time = pulseIn(input, HIGH,2000000)*fator;  //Good, unsigned long
delay(250);
low_time = pulseIn(input, LOW,2000000)*fator;
... 
if (high_time && low_time != 0) {              //Pure luck, your mistake didn't ended up with a fault. if (var) is same as if (var !=0). For consistent use same syntax for both, ie (high_time != 0 && low_time != 0)
...
pulso = pulso + frequency;                     //sum of 10 samples, frequency can't be more than roughly 3200 for correct value.
contador = contador + 1;                       //0~10
...
else if (high_time || low_time == 0)           //Same mistake, but here it becomes an error. This actually means 'if high_time not zero OR low_time equal zero'. You need to compare separately, if (high_time == 0 || low_time == 0).

Hey Gabriel, thanks for the reply!

I changed a bunch of things to float as I was getting some incorrect roundings on some variables (like freq_Hz), even though they were already float, but what was feeding the division was ints. Anyway, changed all that and not much changed.

I also understood the issue with the high_time || low_time and that's been fixed aswell, but the major problem still persists: it's reading very weird values when the frequencies get high. For example, when I rotate the sensor's axis on 16 rpm, since it's an 8-pulse per revolution sensor, I should be getting a reading of 128 Hz, but I'm instead reading 1115 (!) Hz.

Does anyone have any idea why?

Okay, so I've extensively tested the PulseIn function without anything else on the arduino and it seems to be working almost flawlessly.

This means the problem is some kind of noise on my circuit that's screwing my readings on anything above 16 Hz. I've posted the schematics of my circuit below and everything's mounted as a UNO shield.

Am I asking too much of the UNO for it to read the signal properly?

Projeto_Motor.pdf (18.4 KB)

How many pulses from Arduino does it take to make the stepper motor turn 1 revolution?

Holy crap, that solved it.

I was using the new stepper on a significantly higher microstep count, so my arduino was sending upwards of 64kHz to drive the motor. Somehow this was getting in its way of measuring outside pulses (my guess is that some clocks were interfering with each other?).

Reducing it to less microsteps has fixed the PulseIn readings. Thanks so much for the insight, 756E6C!