Reading Magnetic Encoder from Pololu

I'm trying to read the RPM of a 6V micro geared motor that has a 298:1 gearbox (Micro Metal Gearmotor (Extended back shaft) - 298:1), using a magnetic encoder from Pololu (Pololu - Magnetic Encoder Pair Kit for Micro Metal Gearmotors, 12 CPR, 2.7-18V)

Motor datasheet: https://tinyurl.com/ybrmdaqk

I'm driving the motor using a DRV8833 also from pololu.

The motor power is supplied via 2x lithium-ion batteries, that when charged output 7.4v. Power to the motor is about 7.2v due to the driver dropout.

The magnetic encoder is attached to the motor shaft and produces 12 counts per revolution. Because of the 298:1 gearbox on the output shaft there should be 12*298 pulses per revolution of the output shaft, 3576 pulses per revolution.

I'm using an STM32 BluePill with the Official Arduino Core from STM (GitHub - stm32duino/Arduino_Core_STM32: STM32 core support for Arduino)

This is the code I have at the moment to measure the pulses and read out the RPM of two of these motors:

// MOTOR & ENCODER TEST

// References:
// Timer interrupt: http://stm32duino.com/viewtopic.php?f=48&t=4120


// Maximum PWM Value based on 16 bit timer
const int MAX_PWM = 65535;

// DRV8833 motor driver pins
const int M_L_in1Pin = PB8;
const int M_L_in2Pin = PB9;
const int M_R_in1Pin = PB6;
const int M_R_in2Pin = PB7;
const int M_SleepPin = PB5;

// Pololu magnetic encoder pins
const int M_R_EncoderA = PB12;
const int M_R_EncoderB = PB13;
const int M_L_EncoderA = PB14;
const int M_L_EncoderB = PB15;

// Pololu magnetic encoder variables
static stimer_t TimHandle;              // Handler for stimer
const float M_Encoder_PulsesPerTurn = 3576.0; // 12 pulses per turn of magnetic disk on motor, 298:1 gearbox, 3576 turns of magnetic disk per 1 turn of output shaft
volatile unsigned long M_L_Count = 0;   // Variable to hold encoder counts
volatile unsigned long M_R_Count = 0;   // Variable to hold encoder counts
float M_L_RPM = 0;                      // Variable to hold left motor RPM
float M_R_RPM = 0;                      // Variable to hold right motor RPM


// FUNCTIONS
void M_L_Encoder_Event(){
  // Left Motor
  if (digitalRead(M_L_EncoderA) == HIGH) {
    if (digitalRead(M_L_EncoderB) == LOW) {
      M_L_Count++;
    } else {
      M_L_Count--;
    }
  } else {
    if (digitalRead(M_L_EncoderB) == LOW) {
      M_L_Count--;
    } else {
      M_L_Count++;
    }
  }
}
void M_R_Encoder_Event(){
  // Right Motor
  if (digitalRead(M_R_EncoderA) == HIGH) {
    if (digitalRead(M_R_EncoderB) == LOW) {
      M_R_Count--;
    } else {
      M_R_Count++;
    }
  } else {
    if (digitalRead(M_R_EncoderB) == LOW) {
      M_R_Count++;
    } else {
      M_R_Count--;
    }
  }
}

void calculate_RPM(stimer_t *htim){ // Ran every 10milliseconds
  unsigned int M_L_Pulses;
  unsigned int M_R_Pulses;
  noInterrupts();       // Disable interrupts
  M_L_Pulses = M_L_Count;
  M_R_Pulses = M_R_Count;
  M_L_Count = 0;
  M_R_Count = 0;
  interrupts();         // Re-Enable interrupts

  // Pulses multiplied by 100 because interrupt every 10milliseconds (Convert to pulses per second)
  // Divide by pulses per turn of output shaft.
  // Multiply by 60 to go from seconds to minutes
  M_L_RPM = ((M_L_Pulses * 100.0) / M_Encoder_PulsesPerTurn) * 60.0;  
  M_R_RPM = ((M_R_Pulses * 100.0) / M_Encoder_PulsesPerTurn) * 60.0;
  
}
void setup() {
  // Start serial connection
  Serial.begin(115200);

  // pinModes
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(M_L_EncoderA, INPUT);
  pinMode(M_L_EncoderB, INPUT);
  pinMode(M_R_EncoderA, INPUT);
  pinMode(M_R_EncoderB, INPUT);
  pinMode(M_L_in1Pin, OUTPUT);
  pinMode(M_L_in2Pin, OUTPUT);
  pinMode(M_R_in1Pin, OUTPUT);
  pinMode(M_R_in2Pin, OUTPUT);
  pinMode(M_SleepPin, OUTPUT);

  // Set resolutions based on STM32F103
  analogWriteResolution(16);  // Set to 16-bit timer

  // Disable motor controller sleep
  digitalWrite(M_SleepPin, HIGH);

  // Encoder Interrupts
  attachInterrupt(M_L_EncoderA, M_L_Encoder_Event, CHANGE);
  attachInterrupt(M_R_EncoderA, M_R_Encoder_Event, CHANGE);

  // Setup timer
  TimHandle.timer = TIM1;
  // Timer set to 10ms
  TimerHandleInit(&TimHandle, 10000 - 1, ((uint32_t)(getTimerClkFreq(TIM1) / (1000000)) - 1));
  attachIntHandle(&TimHandle, calculate_RPM);
}

void loop() {
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Toggle pin each loop to indicate code is still running, Pin = PC13
    
  // Motor left forward
  analogWrite(M_L_in1Pin, MAX_PWM);
  digitalWrite(M_L_in2Pin, LOW);
    
  // Motor right forward
  analogWrite(M_R_in1Pin, MAX_PWM);
  digitalWrite(M_R_in2Pin, LOW);

  // Print everything to serial port.
  Serial.print("R:"); Serial.print(M_R_RPM); Serial.print(" L:"); Serial.println(M_L_RPM);
}

The code basically increments/decrements a counter each time an interrupt is triggered on the encoder output and then there's a timer interrupt every 10ms which counts the pulses since its last run and converts that to an RPM value.

The output of my oscilloscope is:

Imgur

Yellow and Light blue are the encoder outputs and pink is pin PC13 which toggles each loop() cycle.

This shows that the magnetic encoder is working.

The motor datasheet says the no-load speed of the output should be 75+-10% RPM.
However I'm getting outputs of around 45RPM for both motors.
I know these are Chinese motors and quality can sometimes be questionable but from 75RPM to 45RPM seems too much difference.

Am I doing something wrong here?

I see you have programmed the encoder interrupts to trigger on "change" which means you get two interrupts each time the encoder is activated. One interrupt when the LED light is sensed and another interrupt when the light goes out.

Paul

Oh, I see it is a magnetic sensor, but the same logic applies!

Use INPUT_PULLUP, not INPUT for encoder inputs, they nearly all are open-collector.

I've found the problem.
After doing some reading on quadrature encoders I realised that I'm only interrupting on one of the encoders channels which means half as many pulses per revolution.

This topic helped a lot: https://www.avrfreaks.net/comment/2026456#comment-2026456

After taking this into account I'm now reading an RPM of around 90. This is within the tolerance band of the motor (+-10%) when driving it at 7v.

MarkT:
Use INPUT_PULLUP, not INPUT for encoder inputs, they nearly all are open-collector.

Thanks for that, I've adjusted my code now.

May I know how did you rectify your mistake? Thanks.