Difference Due/Nano on reading motor encoder

Hi everyone.
I’m using Arduino to real the encoder of a DC motor (Pololu MG37Dx70L) ant it works quite well when I use Arduino Due. The resolution is ±0.16 r/s which is correct, according to the number of pulses-per-revolution of the encoder.
The problem is that when I use Arduino Nano, running the same code, the resolution is quite worse. I get an error about 6 times larger.
I’m using two external interrupts to read channel A and B of the encoder in both micros and the algorithm to calculate the velocity is exactly the same. The loop is temporized to last exactly 10 msec in both micros.

Any ideas about this difference?. I think it must be something with the external interrupts.

#define PPV 3840.0

float C2RS = (2*3.14/PPV)/0.01 ;

attachInterrupt(digitalPinToInterrupt(pinENC_chA),encoder_chA,CHANGE) ;

attachInterrupt(digitalPinToInterrupt(pinENC_chB),encoder_chB,CHANGE) ;

void encoder_chA (){if(digitalRead(pinENC_chA)==digitalRead(pinENC_chB)){ quad--;} else {quad++;} }

void encoder_chB (){if(digitalRead(pinENC_chA)==digitalRead(pinENC_chB)){ quad++;} else {quad--;} }

enc_act=quad ; vel=(enc_act-enc_ant)*C2RS ; enc_ant=enc_act ;

How fast is the motor turning?

BTW: formatting your code with multiple statements per line will eventually come back to bite you. Just sayin'

Well it could be the speed difference between the two devices.

Hard to tell with the very small segment of code you posted.
Normally we need all the code, and if it is too big and cluttered with other stuff it might be useful to write a minimum code that just shows your problem.

Hi
Thanks for you answer.

How fast is the motor turning?
In the example, the speed is arround 10 r/s. But the difference can be seen in any speed.

BTW: formatting your code with multiple statements per line will eventually come back to bite you. Just sayin'
Not really sure what do you mean. Find attached the full code for due. The code for nano is the same except by using digital inputs 2 and 3 (instead of 40 and 41) for the interrupts.

Best regards


#define pinENC_chA 40
#define pinENC_chB 41
#define PWM1 9
#define PWM2 10

//#define PPV 8960.0  //64*70*2
//#define VMAX 8
#define PPV 3840.0  //64*30*2
#define VMAX 20

int t1 = 0 ;
int t2 = 0 ;
int T = 10 ;                                          // Periodo de muestreo (milisegundos)
//int T = 2000 ;                                      // Periodo de muestreo (microsegundos)

float U ;
int D1=0, D2=0 ;

float Tm = T/1000.0 ;                                 // Periodo de muestreo segundos
//float Tm = T/1000000.0 ;                            // Periodo de muestreo segundos
float C2D = 360.0/PPV ;                               // Conversión cuentas a deg
float C2RS = (2*3.14/PPV)/Tm ;                        // Conversión cuentas a rad/seg
float vel = 0.0 , vel_ant = 0.0 ;
float pos = 0.0 ;
long quad = 2240 ;
long enc_ant = 0 ;
long enc_act = 0 ;
int enc_dif = 0 ;

   
void setup() {
  
  Serial.begin(115200) ;                             // Inicializa comunicación serie

  attachInterrupt(digitalPinToInterrupt(pinENC_chA),encoder_chA,CHANGE) ; 
  attachInterrupt(digitalPinToInterrupt(pinENC_chB),encoder_chB,CHANGE) ;

  pinMode(PWM1,OUTPUT) ; pinMode(PWM2,OUTPUT) ;

}

void loop() {

  enc_act=quad ;
  enc_dif=enc_act-enc_ant ;
  pos=(enc_act)*C2D ; pos=((int)pos % 360)-180.0 ;
  vel=(enc_act-enc_ant)*C2RS ;
  enc_ant=enc_act ; 
  Serial.print(vel) ; Serial.print("\t") ;
  Serial.print(vel-vel_ant) ; Serial.print("\n") ;
  vel_ant=vel ; 

  U=5.0 ;
  if (U>10) {U=10 ;} ; if (U<-10) {U=-10 ;} ; 
  if (U>0) {D1=map(abs(U)*25,0,10*25,0,255) ; D2=0 ;}
  else {D2=map(abs(U)*25,0,10*25,0,255) ; D1=0 ;}
  analogWrite(PWM1,D1) ; analogWrite(PWM2,D2) ;


  t2=millis() ;                                      // Temporización
//t2=micros() ;                                        // Temporización
  if (t2-t1>T) {
    digitalWrite(13,HIGH) ; Serial.println(t2-t1) ;}
  else {digitalWrite(13,LOW) ;}
  while (t2-t1<T) {t2=millis() ;} t1=millis() ;
//while (t2-t1<T) {t2=micros() ;} t1=micros() ;
}


void encoder_chA (){if(digitalRead(pinENC_chA)==digitalRead(pinENC_chB)){ quad--;} else {quad++;} }
void encoder_chB (){if(digitalRead(pinENC_chA)==digitalRead(pinENC_chB)){ quad++;} else {quad--;} }

Hi
Thanks for your answer

Well it could be the speed difference between the two devices.
There is only one motor. Channel A and B are fed into both micros so the measured speed is the same.

Hard to tell with the very small segment of code you posted.
Normally we need all the code, and if it is too big and cluttered with other stuff it might be useful to write a minimum code that just shows your problem.

Find the code in post #4 of this theme.

Best regards

It's considered bad practice to have multiple statements on the same line. It's very easy to make mistakes that way and they can be hard to find.

Any variable used in an ISR and outside the ISR must be declared as volatile.

So

volatile long quad = 2240 ;

It needs to be volatile to prevent the compiler from optimising it out.

Now long in a Uno is a 32 bit value, I think it might not be that in other processors, so it is best to make it explicit how big you want a variable to be.

Use the int32_t instead of long, so it will be actually

volatile int32_t quad = 2240 ;

Now the problem with reading a variable 4 bytes long when interrupts are enabled and expected is that the number might change during the process of reading it. This can cause problems if the code reads one byte of the variable, then you get an interrupt and the number changes, and then the program reads the other bytes. You can see that this number can be badly out, especially when a number changes leaving it corrupted.

To get round this before you read the quad variable you should disable the interrupts, then read the variable, and finally enable the interrupts again.

noInterrupts();
enc_act=quad ;
interrupts();

Finally your reading of the encoder to get the step and direction is not very good. You should be using a state machine, which will self correct any contact bounce. This is best done by using a library. This is a good one that gets it right, sadly many libraries get it wrong.
from Encoder Library, for Measuring Quadarature Encoded Position or Rotation Signals

On the hardware front you might want to place a 0.1uF ceramic capacitor between encoder outputs and ground.
Also on the Uno these contacts need pulling up to 5V, but on the Due it needs to be pulled up to 3V3. This might be part of the encoder module or you need to fit them externally.

1 Like

Thank you very much. I take note on your advices and will try them tomorrow.
But what annoys me is the why the same code, same HW, same anything works fine in Arduino Due (the small error is caused by the encoder resolution) and not with the Arduino nano.

Thanks for the advice on the format of the program but maybe too old to remove bad habits :slight_smile:

I have the same problem with a big 8000 kg tank, I can move it with my Mercedes semi,
but it does not work with my EQS, both being Mercedes. Really annoying.

Very funny but the code I'm trying to 'move' is not an '8000kg tank'. I'm pretty sure Arduino nano is mode that able to run a 'quad++' in 10 msec. In fact, I can put some extra operations to increase computational cost and the results does not worsen at all. I can run the code in 100msec and the diference persists. I don't think its about how powerfull is the micro.

There are a lot of differences between the two processors used, 5V/3.3V, 8bit/32bit, clock-rate etc.,
so I would not expect the same interrupts using code to run unchanged.

I just think your expectations are unrealistic.

There is a reason why the code behaves differently.

If you really have 10 ms between events, you don't need any interrupts.

Yes but the two systems are not identical. They both run off different machine code so the compiler will not give the same results even if it is fed the same input.

It is like saying you have two identical racing cars, red and blue, but the red one always wins. Could this be something to do with the driver?

1 Like

Yes, I know, they are not the same. Price is not the same either.
The encoder has 6470 PPR. I want the control loop to have a settling time about 200 msec so I have to use a sample time of 10-20 msec. With PPR=4480 and Ts=0.01, the resolution of the encoder is (2pi/PPR)/Ts=0.14 r/s and this is exactly the resolution I get with Arduino Due. This measure cannot be improved because it's structural to the encoder. No matter if I use the best supercoputer in the world.
My question was, is Arduino Nano able to reach this resolution or not?. Maybe not, I don't know but I think it should be. Computational cost is not so high. I will try your advices about the volatile variables and disabling the interrupts. I think that can improve the measure.
I don't want to use a library because I want my students to do the work by themselves but I will have a look to the one you suggested.
One more question if I may. Can you recommend a library to work with timer interrupts in Arduino nano?. I use DueTimer.h with Due but it don't work with the Nano.

Best regards and thanks again for your help.

If you want them to do it correctly then you should get them to implement a state machine like this:-

The Nano can handle it. I think I calculated that the Nano can manage a 1,024 (or was it 4096?) PPR encoder rotating at 3,000RPM for a project.

The problem was that at that speed there wasn't time to do anything else but count the pulses. In your situation at a much lower speed, you should have no trouble. Pay attention to the people who are telling you to incorporate a state machine to the decoding process.

Hi

I've done some more trials and I've realized that while the counter (‘quad’ variable in my program) always increases in the due version (motor turning CW) but in the nano, sometimes decrease its value. As far as I can understand, that means some kind of bouncing on the nano version. Don't know why this happens as the ChA and ChB signals are the same for both micros. I think the state machine implementation will help to avoid this.

I've tried the encoder library you suggested but the difference Due-Nano persists.

What I’m pretty sure is that none of the micros skip the encoder edges. After a while (5 minutes or so) of reading both 'quad' variables reach approximately the same value. If the nano version was skipping some of the pulses, its value should be noticeable smaller after some time.

Thanks again

That's what I thought. The difference is still present even with the motor turning really slow (maybe 20 rpm or less). I think is about how the interrupts work.
I will implement the state machine as suggested.
Thanks again

Hi everyone

Finally, I’ve solved the problem. It was not about the Arduino nano capabilities. It was a timming problem.

The loop is temporized to last exactly 10 msec and that’s what happens in due. But (I don’t know why) in the nano one out of 6 or so loops last 9.something msec. As the duration of the loop is smaller, the number of pulses counted is smaller. When calculating the derivative:

(enc_act-enc_ant)/Ts

I assume Ts=10 msec and so, the velocity is smaller.

It can be solved, using the real duration of the loop, or using micros() instead of millis() to have a more precise timing. This is the result for both due (left) and nano (right). The error is in both cases around 0.16 msec, as expected from the encoder resolution. Blue line is the measured velocity and red line is the error.


Thanks for the useful comments and best regards

PS: Just in case you are interested, find attached the code.

#define pinENC_chA 2
#define pinENC_chB 3
#define PWM1 9
#define PWM2 10

//#define PPV 8960.0  //64*70*2
//#define VMAX 8
#define PPV 3840.0  //64*30*2
#define VMAX 20

int t1 = 0 ;
int t2 = 0 ;
//int T = 10 ;                                          // Periodo de muestreo (milisegundos)
int T = 10000 ;                                      // Periodo de muestreo (microsegundos)

float U ;
int D1=0, D2=0 ;

//double Tm = T/1000.0 ;                                 // Periodo de muestreo segundos
double Tm = T/1000000.0 ;                            // Periodo de muestreo segundos
double C2D = 360.0/PPV ;                               // Conversión cuentas a deg
double C2RS = (2*3.14/PPV)/Tm ;                        // Conversión cuentas a rad/seg
double vel = 0.0 , vel_ant = 0.0 ;
double pos = 0.0 ;
volatile long quad = 2240 ;
long enc_ant = 0 ;
long enc_act = 0 ;
int enc_dif = 0 ;

bool ChA_ant = 0 , ChB_ant = 0 ;
bool ChA_act = 0 , ChB_act = 0 ;

//long mm_ant = 0 , mm_act = 0 ;

   
void setup() {
  
  Serial.begin(115200) ;                             // Inicializa comunicación serie

  attachInterrupt(digitalPinToInterrupt(pinENC_chA),encoder,CHANGE) ; 
  attachInterrupt(digitalPinToInterrupt(pinENC_chB),encoder,CHANGE) ;

  pinMode(PWM1,OUTPUT) ; pinMode(PWM2,OUTPUT) ;

}

void loop() {

  noInterrupts() ; enc_act=quad ; interrupts() ;
  enc_dif=enc_act-enc_ant ;
  pos=(enc_act)*C2D ; pos=((int)pos % 360)-180.0 ;
  vel=(enc_act-enc_ant)*C2RS ; 
  //mm_act=micros() ; 
  //Serial.println(mm_act-mm_ant) ; 
  //vel=vel/(mm_act-mm_ant) ; mm_ant=mm_act ;
  enc_ant=enc_act ;
  //Serial.print(quad) ; Serial.print("\t") ;
  //Serial.print(enc_dif) ; Serial.print("\t") ;
  Serial.print(vel) ; Serial.print("\t") ;
  Serial.print(vel-vel_ant) ; Serial.print("\n") ;
  vel_ant=vel ; 

  U=5.0 ;
  if (U>10) {U=10 ;} ; if (U<-10) {U=-10 ;} ; 
  if (U>0) {D1=map(abs(U)*25,0,10*25,0,255) ; D2=0 ;}
  else {D2=map(abs(U)*25,0,10*25,0,255) ; D1=0 ;}
  analogWrite(PWM1,D1) ; analogWrite(PWM2,D2) ;


//t2=millis() ;                                      // Temporización
  t2=micros() ;                                        // Temporización
  if (t2-t1>T) {
    digitalWrite(13,HIGH) ; Serial.println(t2-t1) ;}
  else {digitalWrite(13,LOW) ;}
//while (t2-t1<T) {t2=millis() ;} t1=millis() ;
  while (t2-t1<T) {t2=micros() ;} 
  t1=micros() ;
}


void encoder () {
  ChA_ant=ChA_act ; ChA_act=digitalRead(pinENC_chA) ;
  ChB_ant=ChB_act ; ChB_act=digitalRead(pinENC_chB) ;

  if (!ChA_ant && !ChB_ant && ChA_act && !ChB_act) {quad++ ;}
  if (!ChA_ant && ChB_ant && !ChA_act && !ChB_act) {quad++ ;} 
  if (ChA_ant && !ChB_ant && ChA_act && ChB_act) {quad++ ;}
  if (ChA_ant && ChB_ant && !ChA_act && ChB_act) {quad++ ;}

  if (!ChA_ant && !ChB_ant && !ChA_act && ChB_act) {quad-- ;}
  if (!ChA_ant && ChB_ant && ChA_act && ChB_act) {quad-- ;} 
  if (ChA_ant && !ChB_ant && !ChA_act && !ChB_act) {quad-- ;}
  if (ChA_ant && ChB_ant && ChA_act && !ChB_act) {quad-- ;}
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.