Calculate rpm from Encoder readings, using LS7366R

Hello, please give me some advice on my project about speed control of motors.

I am using a Sabertooth motor driver and an Arduino board to control two motors. The motors have hall effect encoders mounted to the tail. I also use a Dual LS7366R Quadrature Encoder Buffer to count the pulses of the encoders. The buffer board communicates with the Arduino Mega using SPI. I found the sample code for the LS7366R board here https://github.com/SuperDroidRobots/Encoder-Buffer-Breakout

Below is the code, I can get the encoder counts from the serial port and the values seem very clean. I need to calculate the rpm from those values. The idea I think is to take a certain time interval (like 50 ms) and using (new_count - old_count)/interval, the idea seems straigthforward but I just could not get it work. All the examples I can find are directing connecting the encoders to the Arduino and using interrupts to count the pulses of the encoders, like the one in Arduino Playground - ReadingRPM. I have the LS7366R to do the job and the Arduino can directly read values, I need to do a time derivative of those values. Anyone has than idea how to implement that, using Timers or anything? It may seem easy but it really bothers me for a few days.

#include <SabertoothSimplified.h>  // Sabertooth motor driver Library
#include <Sabertooth.h>
#include "Arduino.h"               // Arduino
#include <avr/io.h>                // General definations of registers
#include <util/delay.h>            // Delay functions
#include <SoftwareSerial.h>        // Serial Library
#include <SPI.h>                   // SPI Library

// ****************************************************
// Hardware Pin Definitions
// ****************************************************
#define SAB_RX 25
#define SAB_PORT 43 // SaberTooth S1
#define SAB_ESTOP 45 // SaberTooth S2
#define POT A8   // Pot to control the speed of the motors

// Slave Select pins for encoders 1 and 2
#define slaveSelectEnc1 49
#define slaveSelectEnc2 48


// ****************************************************
// These hold the current encoder count.
// Updated with current number once per loop
// ****************************************************
signed long encoder1count = 0;
signed long encoder2count = 0;


// Define variables and constants
int val = 0;
int driveMotorValue = 0;

// ****************************************************
// Initialize software serial for the motor controller
// ****************************************************
SoftwareSerial SWSerial(SAB_RX, SAB_PORT);
SabertoothSimplified DriveMotor(SWSerial);



void setup()
{
  // Initialize serial
  Serial.begin(9600);
  SWSerial.begin(9600);
  Serial.flush();	// Clear
  while (!Serial) {}	 // Flash L4 if we are waiting for a serial connection
  
  // Initialize connected I/O
  pinMode (SAB_ESTOP, OUTPUT);
  allStop(); // Stop mptors
  SPI.begin();
  
  pinMode(slaveSelectEnc1, OUTPUT);
  pinMode(slaveSelectEnc2, OUTPUT);
  
  initEncoders();
  clearEncoderCount(); 
  
}

void loop()
{

  // receive values from the POT, map the values to -127~127
  //val = analogRead(POT);
  //val = map(val, 0, 1023, -127, 127);
  //driveMotorValue = constrain(val, -127, 127);
  driveMotorValue = 30;
  
  DriveMotor.motor(1, driveMotorValue);
  encoder1count = readEncoder(1);
  
  DriveMotor.motor(2, driveMotorValue);
  encoder2count = readEncoder(2);
   
  Serial.print(driveMotorValue);
  Serial.print("Enc1: "); Serial.print(encoder1count); Serial.print(" Enc2: ");   

Serial.println(encoder2count);

}

void allStop()
{
  int motorZero = 0;
  //Serial.println("All Stop");
  digitalWrite(SAB_ESTOP, LOW);
  DriveMotor.motor(1,motorZero);  // Zero motors
  
}

void initEncoders() 
{
  // Raise select pins
  // Communication begins when you drop the individual select signal
  digitalWrite(slaveSelectEnc1, HIGH);
  digitalWrite(slaveSelectEnc2, HIGH);
  // digitalWrite(SAB_ESTOP,HIGH);

  // Initialize encoder 1
  //    Clock division factor: 0
  //    Negative index input
  //    free-running count mode
  //    x4 quadrature count mode (four counts per quadrature cycle)
  // NOTE: For more information on commands, see datasheet
  digitalWrite(slaveSelectEnc1, LOW);       // Begin SPI conversation
  SPI.transfer(0x88);                       // Write to MDR0
  SPI.transfer(0x03);                       // Configure to 4 byte mode
  digitalWrite(slaveSelectEnc1, HIGH);      // Terminate SPI conversation

  // Initialize encoder 2
  //    Clock division factor: 0
  //    Negative index input
  //    free-running count mode
  //    x4 quadrature count mode (four counts per quadrature cycle)
  // NOTE: For more information on commands, see datasheet
  digitalWrite(slaveSelectEnc2, LOW);       // Begin SPI conversation
  SPI.transfer(0x88);                       // Write to MDR0
  SPI.transfer(0x03);                       // Configure to 4 byte mode
  digitalWrite(slaveSelectEnc2, HIGH);      // Terminate SPI conversation
}


// ****************************************************
// Reads the Encoders to retrieve the updated value.
// RETURNS: long
// ****************************************************
long readEncoder(int encoder) 
{
  long count_value; 
  unsigned int count_1, count_2, count_3, count_4;
  // Read encoder 1
  if (encoder == 1) {
    digitalWrite(slaveSelectEnc1, LOW);     // Begin SPI conversation
    SPI.transfer(0x60); // Request count
    count_1  = SPI.transfer(0x00);   // Read highest order byte
    count_2 = SPI.transfer(0x00);
    count_3  = SPI.transfer(0x00);
    count_4  = SPI.transfer(0x00);  // Read lowest order byte

    digitalWrite(slaveSelectEnc1, HIGH);    // Terminate SPI conversation
  }

  // Read encoder 2
  else if (encoder == 2) {
    digitalWrite(slaveSelectEnc2, LOW);     // Begin SPI conversation
    SPI.transfer(0x60);                      // Request count
    count_1  = SPI.transfer(0x00);   // Read highest order byte
    count_2 = SPI.transfer(0x00);
    count_3  = SPI.transfer(0x00);
    count_4  = SPI.transfer(0x00);         // Read lowest order byte
    digitalWrite(slaveSelectEnc2, HIGH);    // Terminate SPI conversation
  }

  //  Calculate encoder count

    count_value= ((long)count_1<<24) + ((long)count_2<<16) + ((long)count_3<<8 ) + 

(long)count_4;
  
  return count_value;


}

void clearEncoderCount() {

  // Set encoder1's data register to 0
  digitalWrite(slaveSelectEnc1, LOW);     // Begin SPI conversation
  // Write to DTR
  SPI.transfer(0x98);
  // Load data
  SPI.transfer(0x00);  // Highest order byte
  SPI.transfer(0x00);
  SPI.transfer(0x00);
  SPI.transfer(0x00);  // lowest order byte
  digitalWrite(slaveSelectEnc1, HIGH);    // Terminate SPI conversation

  delayMicroseconds(100);  // provides some breathing room between SPI conversations

  // Set encoder1's current data register to center
  digitalWrite(slaveSelectEnc1, LOW);     // Begin SPI conversation
  SPI.transfer(0xE0);
  digitalWrite(slaveSelectEnc1, HIGH);    // Terminate SPI conversation

  // Set encoder2's data register to 0
  digitalWrite(slaveSelectEnc2, LOW);     // Begin SPI conversation
  // Write to DTR
  SPI.transfer(0x98);
  // Load data
  SPI.transfer(0x00);  // Highest order byte
  SPI.transfer(0x00);
  SPI.transfer(0x00);
  SPI.transfer(0x00);  // lowest order byte
  digitalWrite(slaveSelectEnc2, HIGH);    // Terminate SPI conversation

  delayMicroseconds(100);  // provides some breathing room between SPI conversations

  // Set encoder2's current data register to center
  digitalWrite(slaveSelectEnc2, LOW);     // Begin SPI conversation
  SPI.transfer(0xE0);
  digitalWrite(slaveSelectEnc2, HIGH);    // Terminate SPI conversation
}

Below is the code

No. What you have attached is an image of the output on the serial monitor.

You will need to post the actual code using the code tags provided by the </> icon at the top of the toolbar. You will see the word "code" twice in brackets, and you post your code in between the two words.

What have you tried? The reference you linked shows you how to read the count values.

First, you will need to know How many counts per revolution. The encoder documentation should have that.

Then you will read the values at a fixed interval. Follow the IDE 02 Digital example of "blink without delay" to read the encoder board at a fixed interval. Then do the math to figure RPM.

if(currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;  
    //do something

cattledog:
No. What you have attached is an image of the output on the serial monitor.

You will need to post the actual code using the code tags provided by the </> icon at the top of the toolbar. You will see the word "code" twice in brackets, and you post your code in between the two words.

What have you tried? The reference you linked shows you how to read the count values.

First, you will need to know How many counts per revolution. The encoder documentation should have that.

Then you will read the values at a fixed interval. Follow the IDE 02 Digital example of "blink without delay" to read the encoder board at a fixed interval. Then do the math to figure RPM.

if(currentMillis - previousMillis >= interval) {

previousMillis = currentMillis;  
   //do something

thanks for the reply. Yes I understand that example using currentMillis - previousMillis >= interval, then do something. I have the counts per revolution, 720. The idea I tried is similar:

old_reading = readEncoder(1);
if(currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;  
    new_reading = readEncoder(1);
    rpm = (new_reading - old_reading) / interval *(60*1000/720);
}

but I cannot get the rpm output, always zero. The only way I tried that worked is using delay() directly in the loop(), like

void loop()
{
      driveMotorValue = 30;  // fixed motor speed at value 30;
      DriveMotor.motor(1, driveMotorValue);
      old_reading = readEncoder(1);
      delay(500);
      new_reading = readEncoder(1);  // Read the encoder count again
      rpm = (new_reading - old_reading) / 500 *(60*1000/720); // interval is 500 ms
}

Of course this is not the way that should be used, and the delay influences the motor's spinning as it loses driving input for that amount of time.

You are making the old reading equal to readEncoder right before you enter the next block of code, and there will be no difference between old reading and new reading.

Try

if(currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;  
    new_reading = readEncoder(1);
    rpm = (new_reading - old_reading) / interval *(60*1000/720);
    old_reading = new_reading;
}

I'm never sure about the precedence of operations, so I am a big fan of using many ( ) until I'm sure

rpm = ((new_reading - old_reading) / interval) *(60*1000/720);

You also need to be careful with your declarations. I don't know what you've declared as a float or int, or a long, or the unsigned versions. I don't know your count numbers or your interval, but watch out that (new_reading-old_reading/interval) does not go to 0 or truncate due to integer math, and you will want to use 60*1000L to avoid integer overflow with the constants.

U & L formatters

By default, an integer constant is treated as an int with the attendant limitations in values. To specify an integer constant with another data type, follow it with:

a 'u' or 'U' to force the constant into an unsigned data format. Example: 33u
a 'l' or 'L' to force the constant into a long data format. Example: 100000L
a 'ul' or 'UL' to force the constant into an unsigned long constant. Example: 32767ul

cattledog:
You are making the old reading equal to readEncoder right before you enter the next block of code, and there will be no difference between old reading and new reading.

Try

if(currentMillis - previousMillis >= interval) {

previousMillis = currentMillis; 
    new_reading = readEncoder(1);
    rpm = (new_reading - old_reading) / interval (601000/720);
    old_reading = new_reading;
}




I'm never sure about the precedence of operations, so I am a big fan of using many ( ) until I'm sure


rpm = ((new_reading - old_reading) / interval) (601000/720);




You also need to be careful with your declarations. I don't know what you've declared as a float or int, or a long, or the unsigned versions. I don't know your count numbers or your interval, but watch out that (new_reading-old_reading/interval) does not go to 0 or truncate due to integer math, and you will want to use 60*1000L to avoid integer overflow with the constants.
https://www.arduino.cc/en/Reference/IntegerConstants

Hi, it worked, thank you very much! I added just one line of code you mentioned "old_reading = new_reading;", for the data types, rpm should be float, I used
rpm = (float(new_reading - old_reading)/interval)(601000/180);

but the encoder seems to be not doing quadrature, the real counts per revolution is only 180, not 720, I have to figure out the reason.

liluo:
Hi, it worked, thank you very much! I added just one line of code you mentioned "old_reading = new_reading;", for the data types, rpm should be float, I used
rpm = (float(new_reading - old_reading)/interval)(601000/180);

but the encoder seems to be not doing quadrature, the real counts per revolution is only 180, not 720, I have to figure out the reason.

My first thought is that the reason you are not seeing quadrature-type readings is because the LS7366R is in x1 mode. For full quadrature readings, you want to be in x4 mode. Check out page 4 of the data sheet here:

You're trying to write to the MDR0 byte in order to configure the encoder IC's. In the diagram, B7-B0 represent the eight different bits in the MDR0 byte. I imagine you want to be in x4 mode, which is determined by bits B1 and B0; none of the other bits are really important here and should be left as zero. Basically, you want to program the MDR0 byte as the binary number 00000011. That's what your code is doing when you use 'SPI.transfer(0x03);'. So everything seems correct here.

Is there any chance that the LS7366R's are being power cycled before you use them? When they lose power, that MDR0 byte is reset to zero, meaning the counter defaults to non-quadrature mode (which would only count one channel of your encoder and could possibly make it look like x1 mode).

The only other option I can think of off hand is that your encoder isn't really rated for 720 PPR. Do you have a link to the encoder you're using? There's a chance that it's only designed to read 180 PPR even with full quadrature counts.

EDIT: It just occurred to me that there is a chance you are sending the bits backwards when you try to write to MDR0 (you want to send either MSB or LSB first). If all else fails, you might want to try writing to MDR0 with 11000000 instead. You can change the lines in your initEncoders() method to this:

digitalWrite(slaveSelectEnc1, LOW);       // Begin SPI conversation
  SPI.transfer(0x88);                       // Write to MDR0
  SPI.transfer(0xC0);                       // Configure to 4 byte mode (writing 00000011 backwards in hex)
  digitalWrite(slaveSelectEnc1, HIGH);      // Terminate SPI conversation

jcarothers:
My first thought is that the reason you are not seeing quadrature-type readings is because the LS7366R is in x1 mode. For full quadrature readings, you want to be in x4 mode. Check out page 4 of the data sheet here:

http://cdn.usdigital.com/assets/general/LS7366R.pdf

You're trying to write to the MDR0 byte in order to configure the encoder IC's. In the diagram, B7-B0 represent the eight different bits in the MDR0 byte. I imagine you want to be in x4 mode, which is determined by bits B1 and B0; none of the other bits are really important here and should be left as zero. Basically, you want to program the MDR0 byte as the binary number 00000011. That's what your code is doing when you use 'SPI.transfer(0x03);'. So everything seems correct here.

Is there any chance that the LS7366R's are being power cycled before you use them? When they lose power, that MDR0 byte is reset to zero, meaning the counter defaults to non-quadrature mode (which would only count one channel of your encoder and could possibly make it look like x1 mode).

The only other option I can think of off hand is that your encoder isn't really rated for 720 PPR. Do you have a link to the encoder you're using? There's a chance that it's only designed to read 180 PPR even with full quadrature counts.

EDIT: It just occurred to me that there is a chance you are sending the bits backwards when you try to write to MDR0 (you want to send either MSB or LSB first). If all else fails, you might want to try writing to MDR0 with 11000000 instead. You can change the lines in your initEncoders() method to this:

digitalWrite(slaveSelectEnc1, LOW);       // Begin SPI conversation

SPI.transfer(0x88);                      // Write to MDR0
  SPI.transfer(0xC0);                      // Configure to 4 byte mode (writing 00000011 backwards in hex)
  digitalWrite(slaveSelectEnc1, HIGH);      // Terminate SPI conversation

Hi, thanks for your suggestions. From the data sheets, the motor encoders are hall-effect. The magnets are 5-pole, the gear motor has 1:24 gear ratio, so one revolution should read 120 pulses, with x4 quadrature mode, 480 pulses/rev. From the gear to my robot wheel, there is a gear-chain drive with a ratio of 1:1.5, so one wheel turn, the encoder should read 720 counts with x4. I tired directly interfacing the encoders with the Arduino board, using interrupts 0 and 1, the code is from the video tutorial https://www.youtube.com/watch?v=pSdH1Ah0wzg. I can get 180 pulses reading with x1 mode, 720 pulses/rev with x4 mode, but the 720/rev I can only get it when motors are running slow, the 720/rev reading drops to 180 when the motors run fast(relatively greater than 100 rpm). I searched this problem and found this post Quadrature Encoder too Fast for Arduino (with Solution) – Dr Rainer Hessmer talking about Quadrature Encoder too Fast for Arduino, but as my encoders only reads 720 pulses/rev, even at the maximum wheel rpm(about 200), the interrupt load is about 720*200/60 = 2400 per second, the Arduino interrupt should be able to handle that. Very strange...I tried the method as suggested in the post though, but didn't help.

And when I use the LS7366R, no matter what mode I write to the MDR0(SPI.transfer(0x03);//0x00, 0x01,0x10, 0xC0), the reading is always 180 counts/rev.

Any ideas?

Can you please provide a link to the data sheet for the encoder.

This is the link for the motors with hall encoders attached to the tail.

I was confused by the data sheet, and really do not know how may quadrature pulses per revolution you are supposed to read. It's possible that 120 (180 with 1-1:5 at the wheel) is the correct number with 4x.

Are you using the recommend 1K pullup resistors shown in the data sheet?

If 180 is the magic number, than you should be able to get the LS7366R to read 180/90/45 with 4/2/1 settings.

I didn't watch the YouTube link, but if you really are supposed to get 4x these numbers and should be reading 480/240/120 (720/360/180) you are correct in that these numbers are not too fast for the Arduino and the counts should not jump from 720 to 180 with motor speed. More likely they would become erratic. If you can post the encoder reading code from the you tube link, I can take a look at it but it would be very unusual for in not to be able to follow at 2400 cps.

I think you should focus on either the LS7366 solution or software decoding, and sort out the anomalies tht each path is showing. If you focus on the LS7366R solution try and figure out why you are not able to change the (1x/2x/4x) reading mode. The documentation seems pretty clear. If you focus on a software solution, check out the encoder tutorials and see if you can get stable readings at higher speeds.Arduino Playground - RotaryEncoders

hii...sir

i m doing one project which is related to this ls7366r...i want to count the pulses...where i have used rlc2ic sensor quadrature encoder which will give around 25 lakh counts...after that i m using ls 7366r counter
which will count that pulses and will give serial output .by using spi module of it...and arduino i want to print
that count on a serial monitor....can you give me a programming guidance...above blog is pretty same as my project...

Thanks ...:slight_smile:

I am trying to read angle from spi encoder directly by arduino. I tried most of the codes here but I get only zero as output on serial monitor. Can someone please suggest how should I approach this issue?

Kkksc, it is better if you start your own thread and give a link to this one to show us what you have already read. But you are here now so let's keep going.

How is it wired?

Can you get the encoder to work directly without the interface chip?