EmbeddedRelated.com
Forums

Atmel AVR, too slow encoder tics counting

Started by Umpa May 6, 2004
Hello,

I have problem with counting ticks of an encoder.
I can achieve about 5 kHz without errors.
Above this frequency, I loose ticks.

I use second AVR processor as the generator of
two signals -- I just test my device, without real encoder.

What to do to improve frequency ?

Below, I present my program.
Processor: AVR ATmega16
You should connect an Encoder in the way:
Canal A : PD2 i PD3
Canal B: PD4

#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include "lcd.h"

long int licz = 0;
int flagaA = 0, flagaB = 0;


SIGNAL (SIG_INTERRUPT0)
{
   if (!(inb(PIND) & 16))  /* if 0 on canal B */
        flagaA = 1;
    else                       /* otherwise, (1 on canal B) */
        flagaB = 1;
}


SIGNAL (SIG_INTERRUPT1)
{
    if (inb(PIND) & 16) {     /* if 1 on canal B */
        if (flagaA)               /* if flagaA is set */
            licz++, flagaA = 0;
    } else if (flagaB)            /* if flagaB is set */
        licz--, flagaB = 0;
}


int main(void)
{
    char s[16];

    outp(0, DDRD);

    /* Setting interrupts INT 0 i INT1 -- rising, falling edge
     */
    outp((1<<INT0)|(1<<INT1), GIMSK);
    outp((1<<ISC01)|(1<<ISC00) | (1<<ISC11), MCUCR);


    initLCD();

    sei();  /* star interrupts */

    for (;;) {
        cursorHome();
        sprintf(s, "%ld ", licz);
        showStringOnLCD(s);
    }

    return 0;
}


Umpa wrote:
> Hello, > > I have problem with counting ticks of an encoder. > I can achieve about 5 kHz without errors. > Above this frequency, I loose ticks. > > I use second AVR processor as the generator of > two signals -- I just test my device, without real encoder. > > What to do to improve frequency ?
If you want a lot faster, use hardware, like a CPLD. If the AVR has an Up/Dn counter mode a la 89C52, the PLD needs only do the conditioning to Dirn and Count signals. If you only have UP counters, you can allocate two, and internally take the difference. PLD then generates Clk_UP, and Clk_DN pulse trains. 5KHz sounds about right for LONGINT SW - To speed SW further, try assembler and do cascaded or queued BYTE increments, rather than a full long int INC. Latter is simpler to write, but every INC handles all bytes. In the AVR, register access is much better than DATA access, so either move all the counter to regs, or if that chews too much REG resource, move the LSB byte to one reg, and cascade into pointer/data access only on overflow. For that, you will need to use assembler.... -jg
How fast are you running the clock on your CPU?
What is your clock src?

Phil W



"Umpa" <&#4294967295;&#4294967295;&#4294967295;&#4294967295;@umpaumpalala.com> wrote in message
news:c7efnb$qna$1@atlantis.news.tpi.pl...
> Hello, > > I have problem with counting ticks of an encoder. > I can achieve about 5 kHz without errors. > Above this frequency, I loose ticks. > > I use second AVR processor as the generator of > two signals -- I just test my device, without real encoder. > > What to do to improve frequency ? > > Below, I present my program. > Processor: AVR ATmega16 > You should connect an Encoder in the way: > Canal A : PD2 i PD3 > Canal B: PD4 > > #include <stdio.h> > #include <avr/io.h> > #include <avr/interrupt.h> > #include <avr/signal.h> > #include "lcd.h" > > long int licz = 0; > int flagaA = 0, flagaB = 0; > > > SIGNAL (SIG_INTERRUPT0) > { > if (!(inb(PIND) & 16)) /* if 0 on canal B */ > flagaA = 1; > else /* otherwise, (1 on canal B) */ > flagaB = 1; > } > > > SIGNAL (SIG_INTERRUPT1) > { > if (inb(PIND) & 16) { /* if 1 on canal B */ > if (flagaA) /* if flagaA is set */ > licz++, flagaA = 0; > } else if (flagaB) /* if flagaB is set */ > licz--, flagaB = 0; > } > > > int main(void) > { > char s[16]; > > outp(0, DDRD); > > /* Setting interrupts INT 0 i INT1 -- rising, falling edge > */ > outp((1<<INT0)|(1<<INT1), GIMSK); > outp((1<<ISC01)|(1<<ISC00) | (1<<ISC11), MCUCR); > > > initLCD(); > > sei(); /* star interrupts */ > > for (;;) { > cursorHome(); > sprintf(s, "%ld ", licz); > showStringOnLCD(s); > } > > return 0; > } > >
"Umpa" <&#4294967295;&#4294967295;&#4294967295;&#4294967295;@umpaumpalala.com> wrote in message
news:c7efnb$qna$1@atlantis.news.tpi.pl...
> Hello, > > I have problem with counting ticks of an encoder. > I can achieve about 5 kHz without errors. > Above this frequency, I loose ticks. > > I use second AVR processor as the generator of > two signals -- I just test my device, without real encoder. > > What to do to improve frequency ?
Your interrupt service routines will translate to only a few assembler instructions. There's plenty processing power in the AVR to cope with this at 5 kHz and higher. You may want to check your LCD output routines. Check if it's disabling interrupts somewhere. How do you determine that you loose ticks, not by reading the the LCD I suppose? Rob
> How do you determine that you loose ticks, not by reading the the LCD I > suppose? >
My generator generates known numer of ticks on both chanels with different phase. Yea, I read LCD -- it shows smaller numer at higher frequencies. When I add "cli" at begining of interrupt function and "sei" at the end -- LCD shows "0" at higher frequencies. And it works correctly at smaller frequencies. So, I think, that processor tries to run interrupt function when previous hasn`t finished yet. I think, I must optimalize my code and write it in assembler. The second thing is, when I change type of "licz" to int (not long int), it can count ticks faster. So, I`m almost sure, my code is too slow. Now, I use only INT0 in my program, and I can count ticks with a little bit more frequency -- but not enough still. I need about 10 kHz. unsigned long int licz = 0; unsigned char flagaA = 0, flagaB = 0; SIGNAL (SIG_INTERRUPT0) { // cli(); if (PIND & 4) { (!(PIND & 16)) ? (flagaA = 1) : (flagaB = 1); } else { if (PIND & 16) { flagaA && (licz++, flagaA = 0); } else { flagaB && (licz--, flagaB = 0); } } // sei(); } int main(void) { char s[16]; outp(0, DDRD); outp((1<<INT0), GIMSK); outp((1<<ISC00), MCUCR); sei(); .... }
> How fast are you running the clock on your CPU? > What is your clock src? >
I have ATmega16 -- 16 MHz. Clock source is internal I think, I didn`t connect any quartz. Did I do something wrong? Umpa.
Thanks for smart advices.

> 5KHz sounds about right for LONGINT SW -
My processor has 16 MHz. Even if execution of interrupt procedure lasts 1000 cycles (I don`t think so it lasts as long), we should have 16000000 / 1000 = 16 kHz. I think, my program should be able to count with higher frequencies according to high processor speed, and not so slow (I think not slower than e.g. 1000 cycles) interrupt procedure. Umpa.
This code:

#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>

unsigned long int licz = 0;
unsigned char flagaA = 0, flagaB = 0;


SIGNAL (SIG_INTERRUPT0)
{
//    cli();
   if (PIND & 4) {
       (!(PIND & 16)) ? (flagaA = 1) : (flagaB = 1);
   } else {
        if (PIND & 16) {
            if (flagaA)
                licz++, flagaA = 0;
        } else {
            if (flagaB)
                licz--, flagaB = 0;
        }
   }
// sei();
}

int main(void)
{
    outp(0, DDRD);

    outp((1<<INT0), GIMSK);
    outp((1<<ISC00), MCUCR);

    sei();

    for (;;)
        ;

    return 0;
}




Looks that in assembler:


encoder.elf:     file format elf32-avr

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000014a  00000000  00000000  00000094  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00800060  0000014a  000001de  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000006  00800060  00800060  000001de  2**0
                  ALLOC
  3 .noinit       00000000  00800066  00800066  000001de  2**0
                  CONTENTS
  4 .eeprom       00000000  00810000  00810000  000001de  2**0
                  CONTENTS
Disassembly of section .text:

00000000 <.text>:
   0: 0c 94 2a 00  jmp 0x54
   4: 0c 94 47 00  jmp 0x8e
   8: 0c 94 45 00  jmp 0x8a
   c: 0c 94 45 00  jmp 0x8a
  10: 0c 94 45 00  jmp 0x8a
  14: 0c 94 45 00  jmp 0x8a
  18: 0c 94 45 00  jmp 0x8a
  1c: 0c 94 45 00  jmp 0x8a
  20: 0c 94 45 00  jmp 0x8a
  24: 0c 94 45 00  jmp 0x8a
  28: 0c 94 45 00  jmp 0x8a
  2c: 0c 94 45 00  jmp 0x8a
  30: 0c 94 45 00  jmp 0x8a
  34: 0c 94 45 00  jmp 0x8a
  38: 0c 94 45 00  jmp 0x8a
  3c: 0c 94 45 00  jmp 0x8a
  40: 0c 94 45 00  jmp 0x8a
  44: 0c 94 45 00  jmp 0x8a
  48: 0c 94 45 00  jmp 0x8a
  4c: 0c 94 45 00  jmp 0x8a
  50: 0c 94 45 00  jmp 0x8a
  54: 11 24        eor r1, r1
  56: 1f be        out 0x3f, r1 ; 63
  58: cf e5        ldi r28, 0x5F ; 95
  5a: d4 e0        ldi r29, 0x04 ; 4
  5c: de bf        out 0x3e, r29 ; 62
  5e: cd bf        out 0x3d, r28 ; 61
  60: 10 e0        ldi r17, 0x00 ; 0
  62: a0 e6        ldi r26, 0x60 ; 96
  64: b0 e0        ldi r27, 0x00 ; 0
  66: ea e4        ldi r30, 0x4A ; 74
  68: f1 e0        ldi r31, 0x01 ; 1
  6a: 02 c0        rjmp .+4       ; 0x70
  6c: 05 90        lpm r0, Z+
  6e: 0d 92        st X+, r0
  70: a0 36        cpi r26, 0x60 ; 96
  72: b1 07        cpc r27, r17
  74: d9 f7        brne .-10      ; 0x6c
  76: 10 e0        ldi r17, 0x00 ; 0
  78: a0 e6        ldi r26, 0x60 ; 96
  7a: b0 e0        ldi r27, 0x00 ; 0
  7c: 01 c0        rjmp .+2       ; 0x80
  7e: 1d 92        st X+, r1
  80: a6 36        cpi r26, 0x66 ; 102
  82: b1 07        cpc r27, r17
  84: e1 f7        brne .-8       ; 0x7e
  86: 0c 94 9a 00  jmp 0x134
  8a: 0c 94 00 00  jmp 0x0
  8e: 1f 92        push r1
  90: 0f 92        push r0
  92: 0f b6        in r0, 0x3f ; 63
  94: 0f 92        push r0
  96: 11 24        eor r1, r1
  98: 8f 93        push r24
  9a: 9f 93        push r25
  9c: af 93        push r26
  9e: bf 93        push r27
  a0: 82 9b        sbis 0x10, 2 ; 16
  a2: 0a c0        rjmp .+20      ; 0xb8
  a4: 84 99        sbic 0x10, 4 ; 16
  a6: 04 c0        rjmp .+8       ; 0xb0
  a8: 81 e0        ldi r24, 0x01 ; 1
  aa: 80 93 64 00  sts 0x0064, r24
  ae: 39 c0        rjmp .+114     ; 0x122
  b0: 81 e0        ldi r24, 0x01 ; 1
  b2: 80 93 65 00  sts 0x0065, r24
  b6: 35 c0        rjmp .+106     ; 0x122
  b8: 84 9b        sbis 0x10, 4 ; 16
  ba: 1a c0        rjmp .+52      ; 0xf0
  bc: 80 91 64 00  lds r24, 0x0064
  c0: 88 23        and r24, r24
  c2: 79 f1        breq .+94      ; 0x122
  c4: 80 91 60 00  lds r24, 0x0060
  c8: 90 91 61 00  lds r25, 0x0061
  cc: a0 91 62 00  lds r26, 0x0062
  d0: b0 91 63 00  lds r27, 0x0063
  d4: 01 96        adiw r24, 0x01 ; 1
  d6: a1 1d        adc r26, r1
  d8: b1 1d        adc r27, r1
  da: 80 93 60 00  sts 0x0060, r24
  de: 90 93 61 00  sts 0x0061, r25
  e2: a0 93 62 00  sts 0x0062, r26
  e6: b0 93 63 00  sts 0x0063, r27
  ea: 10 92 64 00  sts 0x0064, r1
  ee: 19 c0        rjmp .+50      ; 0x122
  f0: 80 91 65 00  lds r24, 0x0065
  f4: 88 23        and r24, r24
  f6: a9 f0        breq .+42      ; 0x122
  f8: 80 91 60 00  lds r24, 0x0060
  fc: 90 91 61 00  lds r25, 0x0061
 100: a0 91 62 00  lds r26, 0x0062
 104: b0 91 63 00  lds r27, 0x0063
 108: 01 97        sbiw r24, 0x01 ; 1
 10a: a1 09        sbc r26, r1
 10c: b1 09        sbc r27, r1
 10e: 80 93 60 00  sts 0x0060, r24
 112: 90 93 61 00  sts 0x0061, r25
 116: a0 93 62 00  sts 0x0062, r26
 11a: b0 93 63 00  sts 0x0063, r27
 11e: 10 92 65 00  sts 0x0065, r1
 122: bf 91        pop r27
 124: af 91        pop r26
 126: 9f 91        pop r25
 128: 8f 91        pop r24
 12a: 0f 90        pop r0
 12c: 0f be        out 0x3f, r0 ; 63
 12e: 0f 90        pop r0
 130: 1f 90        pop r1
 132: 18 95        reti
 134: cf e5        ldi r28, 0x5F ; 95
 136: d4 e0        ldi r29, 0x04 ; 4
 138: de bf        out 0x3e, r29 ; 62
 13a: cd bf        out 0x3d, r28 ; 61
 13c: 11 ba        out 0x11, r1 ; 17
 13e: 80 e4        ldi r24, 0x40 ; 64
 140: 8b bf        out 0x3b, r24 ; 59
 142: 81 e0        ldi r24, 0x01 ; 1
 144: 85 bf        out 0x35, r24 ; 53
 146: 78 94        sei
 148: ff cf        rjmp .-2       ; 0x148



> You may want to check your LCD output routines. Check if it's disabling > interrupts somewhere. >
I didn`t disable interrupts myself in any code. But, after translating code to assembler, I can see many "cli" instructions in my source code. When I cut LCD library and left pure interrupt function and main(), there were no "cli" instructions in source code. Umpa.
Now, I did loops at the begining
of main():

 for(i=0;i<10000;i++)
    for(j=0;j<10000;j++)
        ;

after that I initialize LCD and
display counter -- to avoid "cli"
instructions (there are no such instructions
in C code, but they apear in assembler code
generated by winavr) which could be in
LCD library.
It improved situation (I can count with a little
bit more frequencies), but not as much I would like.

Umpa.