Electronic – Encoder too fast for a poor Atmega328

atmega328pencoder

I've been playing with a couple servomotors i got from surplus of pick and place machines.
So far, i've decoded their pinout and i'm working on reading the encoder, here comes the problem:

From what i observed, motor comes fitted with a 4096 cuadrature pulses per revolution +1 index pulse each revolution.
Doing some testing in the Arduino IDE showed that spinning the motor a little faster and the encoder starts loosing steps…

I decided to migrate the code to AS7 and flush all the Arduino overhead, but the chip seems incapable of dealing with it. Correct me if i'm wrong about the following:

With 2048 cpr (just using the rising pulse of one cuadrature channel) and a rotational speed of 3000rpm, one revolution is complete in 0.02seconds.

Assuming the previous 20mS / 2048ppr we have one rising edge every 0.097mS -> 97uS give or take.

Is that time enought for executing the following ISR?:

#define F_CPU 16000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>

volatile int count;

int main(void)
{

    DDRD  = (0<<PORTD2) | (0<<PORTD3)| (0<<PORTD4); 
    PORTD = (0<<PORTD2) | (0<<PORTD3)| (0<<PORTD4);  
    EICRA = (1 << ISC11) | (1 << ISC01);  // Configure interrupt trigger on rising edge of INT0  
    EIMSK = (1 << INT0); //ebable INT0 
    sei();

    while (1) 
    {

    }
}

ISR (INT0_vect){

    uint8_t i = ((PIND & 0b00010000)>>4);


        if (i == 1) {

            count = count +1;

            }else{

            count = count -1;
        }       

    EIFR = (1<<INTF0);
}

If not, how should i do it… Dedicated counter IC?

Thanks

**Edit:**capture of the logic analyzer comparing one revolution (index) to encoder A

capture

Best Answer

I have run your code through avr-gcc with -Os optimization (YMMV if you use different compiler, flags etc., but in could be good starting point) and disassembled result, here is it:

00000090 <__vector_1>:
 2  90: 1f 92           push    r1
 2  92: 0f 92           push    r0
 1  94: 0f b6           in  r0, 0x3f    ; 63
 2  96: 0f 92           push    r0
 1  98: 11 24           eor r1, r1
 2  9a: 8f 93           push    r24
 2  9c: 9f 93           push    r25
1 2 9e: 4c 9b           sbis    0x09, 4 ; 9
2 . a0: 06 c0           rjmp    .+12        ; 0xae <__vector_1+0x1e>
. 2 a2: 80 91 00 01     lds r24, 0x0100
. 2 a6: 90 91 01 01     lds r25, 0x0101
. 2 aa: 01 96           adiw    r24, 0x01   ; 1
. 2 ac: 05 c0           rjmp    .+10        ; 0xb8 <__vector_1+0x28>
2 . ae: 80 91 00 01     lds r24, 0x0100
2 . b2: 90 91 01 01     lds r25, 0x0101
2 . b6: 01 97           sbiw    r24, 0x01   ; 1
 2  b8: 90 93 01 01     sts 0x0101, r25
 2  bc: 80 93 00 01     sts 0x0100, r24
 1  c0: 81 e0           ldi r24, 0x01   ; 1
 1  c2: 8c bb           out 0x1c, r24   ; 28
 2  c4: 9f 91           pop r25
 2  c6: 8f 91           pop r24
 2  c8: 0f 90           pop r0
 1  ca: 0f be           out 0x3f, r0    ; 63
 2  cc: 0f 90           pop r0
 2  ce: 1f 90           pop r1
 4  d0: 18 95           reti

Numbers before instruction addresses is my addition to disassembler output, number of cycles for execution based on AVR Instruction Set Manual. If I am counting it correctly, it is 43 cycles in total + 5 cycles for interrupt response (+ about 3 cycles for pin change to propagate). The ISR code can be hand-optimized into much shorter if necessary. But it even so it is about 50 cycles, 3 us @ 16 MHz.

PIND is read 12 cycles after ISR start, about 20 cycles (1.25 us) after INT0 edge. Should be still OK.

You do not have much marginal, but it should work. OTOH any other ISR will probably "kill" it as ATmega does not have interrupt controller with priority handling. Btw. in the code you put here, there is no processing of count variable, so even for test it need to be more complicated. Are you sure, you are not doing anything affecting ISR latency there?

As an alternative solution -- if you can consider XMega for your project (assuming you want to use AVR), these have hardware support for encoder and you can drive counter by it without need for FW interaction.

Related Topic