Help Needed: Accurate Pulse Count Using Quadrature Hall Sensor with BTS7960 Motor Driver

Hi everyone,

I'm working on a project involving a linear actuator with an integrated quadrature Hall sensor and a BTS7960 motor driver, all controlled via an Arduino Mega. The goal is to accurately track position using pulse counts and enable calibrated movement over a 300 mm stroke.

Hardware Setup:

Arduino Mega 2560
BTS7960 motor driver

  • RPWM: Pin 5
  • LPWM: Pin 6
  • REN: Pin 7
  • LEN: Pin 8
    Linear actuator with Hall sensor (Stroke of 300mm) (5V, GND, Hall_1, Hall_2)
  • Hall_1: Pin 2 (interrupt)
  • Hall_2: Pin 3 (interrupt)
  • 24V power supply for the actuator, passed through BTS7960
  • Datasheet :
    Instruction manual-电动推杆使用说明书(1) (1).pdf (1.3 MB)

Objectives:

  • Accurately calculate pulse counts (increment and decrement based on direction)
  • Eventually convert these pulses to millimeters for position tracking over 300 mm

Issue with the current code I'm working with provides me with inconsistent final readings, what should I look for to change and what sources should I go through to better understand the working logic to build a code to read a consistent maximum amount of pulses at the range of 0-300mm, so that I can derive how much pulses it takes to traverse 1mm.

This is what I have up to now in the code :

// Motor driver pins
#define RPWM 5
#define LPWM 6
#define REN 7
#define LEN 8

// Hall sensor pins
#define HALL_1 2
#define HALL_2 3

volatile long pulseCount = 0;
int speedPWM = 250;

void setup() {
  Serial.begin(115200);

  // Motor driver setup
  pinMode(RPWM, OUTPUT);
  pinMode(LPWM, OUTPUT);
  pinMode(REN, OUTPUT);
  pinMode(LEN, OUTPUT);
  digitalWrite(REN, HIGH);
  digitalWrite(LEN, HIGH);
  analogWrite(RPWM, 0);
  analogWrite(LPWM, 0);

  // Hall sensor setup
  pinMode(HALL_1, INPUT_PULLUP);
  pinMode(HALL_2, INPUT_PULLUP);

  // Count only rising edges on HALL_1
  attachInterrupt(digitalPinToInterrupt(HALL_1), countPulse, CHANGE);

  Serial.println("Ready. Use: f=forward, b=backward, s=stop/reset");
}

void loop() {
  if (Serial.available()) {
    char command = Serial.read();
    
    if (command == 'f') {
      analogWrite(RPWM, speedPWM);
      analogWrite(LPWM, 0);
      Serial.println("Motor Forward");
    } 
    else if (command == 'b') {
      analogWrite(RPWM, 0);
      analogWrite(LPWM, speedPWM);
      Serial.println("Motor Backward");
    } 
    else if (command == 's') {
      analogWrite(RPWM, 0);
      analogWrite(LPWM, 0);
      pulseCount = 0;
      Serial.println("Stopped and Reset Count");
    }
  }

  // Print current state
  Serial.print("Pulse Count: ");
  Serial.print(pulseCount);
  Serial.print(" | HALL_1: ");
  Serial.print(digitalRead(HALL_1));
  Serial.print(" | HALL_2: ");
  Serial.println(digitalRead(HALL_2));

  delay(200);
}

// Interrupt service routine
void countPulse() {
  pulseCount++;
}

Sorry, but in all that writing I see no place where you actually measure the movement linear actuator each time a pulse is received. What am I missing?

No right now my goal is to read the total pulse count to travel 300mm in the actuator since the built in limit switches will stop the actuator at the 300mm mark. I am usure on how to use both hall signals to get an accurate and consistent pulse count for the entire length of the actuator which is 300mm. Should there be some of calculation that should happen involving the two hall signals?

Yes. The position should be updated with the clock channel and incremented or decremented depending on the other (direction) channel.

Should I involve both signals from the hall sensor_1 and hall sensor_2, if so how would the interrupt functions be?

Use one for counting and the other for direction.

thanks will update you

Using one for count and one for direction will reduce the resolution since you're missing 3/4 of the pulses. Also, one of the advantages of a quadrature counter is error detection by using the Gray code. The inputs should only change in a certain sequence, otherwise theres an error.

So you should have interrupts connected to both inputs and use a Gray code state machine to do the actual counting.

Only missing half of the pulses. What you mean is counting edges.

IMO a beginner should be happy with understanding basic QE handling and only read later all the documents dealing with the possible complications and improvements.

What data are you seeing for pulseCount printed every 200 ms?

You need to stop interrupts and make a "protected" reading of pulseCount from the isr to avoid a read during the changing of a multibyte variable register. Work with the protected value in code after the reading.

// Print current state
  Serial.print("Pulse Count: ");
  noInterrupts();
  unsigned long xfrPulseCount = pulseCount;
  interrupts();
  //Serial.print(pulseCount);
  Serial.print(xfrPulseCount); 
  Serial.print(" | HALL_1: ");
  Serial.print(digitalRead(HALL_1));
  Serial.print(" | HALL_2: ");
  Serial.println(digitalRead(HALL_2));

The basic tutorial on interrupts is by Nick Gammon. It's worth reading to understand how to best handle them:

sounds a suitable task for ESP32 Pulse Counter (PCNT) module

for QEI encoded signals there is RotaryEncoderPCNT library

1 Like