Tick counting problem

J

JJ

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;
}
 
B

Bartc

JJ said:
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

You might try posting to comp.arch.embedded too, somebody there might be
more familiar with the capabilities of this cpu, in case it is not a C
problem.
 
J

JJ

Make the ISR run faster. You'll have to optimize the ISR code as much
as possible. You can do this by doing less work in the ISR or perhaps
rewriting the ISR in assembler. Maybe get rid of some/all of those if
statements. How many ints/second do you need to handle?

Hello,

10 kHz would be enough.

I have ATmega16 -- 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.
 
B

Bartc

JJ said:
Hello,

10 kHz would be enough.

I have ATmega16 -- 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.

Try making the interrupt routine slower (by inserting delays). If the
performance gets worse then perhaps the routine does need speeding up
(although 10K operations per second doesn't sound too demanding).

I don't really understand your code or your processor, or how you're
counting whatever it is you're counting, but in the following:
for (;;) {
cursorHome();
sprintf(s, "%ld ", licz);
showStringOnLCD(s);
}

All these 3 routines probably take hundreds of times longer to execute than
your interrupt routines.

Is it possible they are causing some interrupts to be missed? That they
could temporarily mask interrupts?

How do you establish that ticks are being missed: that after say 3 seconds
at 5kHz, the count only shows 12000 or something? Suppose you just did an
empty loop for 3 seconds (experiment with a loop count that delays by that
much), then displayed the count, would it be 12000 or 15000?
 
J

JJ

Do those do one instruction per clock cycle? A PIC running at that speed
would only execute 4,000,000 instructions per second max.


You're just going to have to look at the assembler output from the C
compiler to see how many instructions it generates. It does seem like your
code should execute in less than 1000 cycles though. There is always some
extra overhead in ISR entry and exit that eats up some cycles, and jumps eat
up cycles too while the pipeline is refilled. I'd start by looking at the
generated assembler.

Hello,

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
 
M

myforwik

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

You need to look at the listing assembly code of your interrupt
routines and look up the instruction set for the controller and work
out how many cycles your interrupt consume. Many instructions are 2 or
3 or 4 cycles per instruction. I would guess that it probably takes
200 to 400 cycles. Based on this you can guestimate based on the speed
of your CPU clock. The way you have the two interrupts really means
you will need to be able to run each interrupt twice within a single
period of your input pulse to ensure you will not miss any pulses.
Also your LCD routines probably even block interrupts when they write
to output.

You best way is to not use interrupts but do it in a main loop,
continusally check pin and increment a counter when it changes from
low to high or whatever, and then set an output pin if the count value
is over a certain amount, and then use a second MCU to read that pin
and use the LCD. Either that or code the subroutine in assembly. You
might be able to get away with only a couple of instructions if you
are very careful about how you use the registers, but either way the
interupt and return from interrupt instructions I believe use alot of
cpu cycles, I believe it was at least 12 or 24.

Basically speaking the cpu isn't fast enough for what you want to do.
Look at stepping up your clock speed. From what you say I would guess
you are running at 4Mhz
 
J

JJ

Do those do one instruction per clock cycle? A PIC running at that speed
would only execute 4,000,000 instructions per second max.


You're just going to have to look at the assembler output from the C
compiler to see how many instructions it generates. It does seem like your
code should execute in less than 1000 cycles though. There is always some
extra overhead in ISR entry and exit that eats up some cycles, and jumps eat
up cycles too while the pipeline is refilled. I'd start by looking at the
generated assembler.

Hello,

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.

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.
 
B

Bartc

JJ said:
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.
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;

}

The interaction doesn't seem simple. You have two inputs: you are counting
one input on B, but input A seems to reverse the counting direction of input
B.

Your only output is the net count on B.

What are the frequencies of the two inputs? How have you established these
(with frequency counter/scope..)? What is the phase with respect to each
other? Could one signal interrupt while processing the other?

If you have a second count that only counts the total pulses on B, how does
this behave with higher frequencies?

--
Bartc




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;
}
 
R

Richard Bos

JJ said:
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 ?

Simple: overclock your computer. I recommend cooling with some kind of
fluid, preferably something colder than mere water.

Richard
 
J

JJ

Try making the interrupt routine slower (by inserting delays). If the
performance gets worse then perhaps the routine does need speeding up
(although 10K operations per second doesn't sound too demanding).

I don't really understand your code or your processor, or how you're
counting whatever it is you're counting, but in the following:


All these 3 routines probably take hundreds of times longer to execute
than your interrupt routines.

Is it possible they are causing some interrupts to be missed? That they
could temporarily mask interrupts?

How do you establish that ticks are being missed: that after say 3
seconds at 5kHz, the count only shows 12000 or something? Suppose you
just did an empty loop for 3 seconds (experiment with a loop count that
delays by that much), then displayed the count, would it be 12000 or
15000?

Hello,

My generator generates known numer of ticks on both chanels with different
phase.
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();
.....
}
 
B

Bartc

JJ said:
My generator generates known numer of ticks on both chanels with different
phase.
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.

OK, but with a 16MHz device I would have thought it could service 10K
interrupts per second.

The interaction between the two inputs is still difficult to grasp but, have
you thought of making use of any automatic counters on the chip? (Or even
have an external counter for the low bits at least.)

Have you tried making licz short int or char? Although dealing with overflow
efficiently might require some assembler.

And, with the two inputs asynchronous (not sure if your single INT0 below is
triggered by both signals), it's quite possible to have one interrupt while
servicing another. (This is why I suggested asking in comp.arch.embedded as
someone may have already solved a similar problem.)
 
J

JJ

OK, but with a 16MHz device I would have thought it could service 10K
interrupts per second.

The interaction between the two inputs is still difficult to grasp but, have
you thought of making use of any automatic counters on the chip? (Or even
have an external counter for the low bits at least.)

Have you tried making licz short int or char? Although dealing with overflow
efficiently might require some assembler.

Hello,

Thanks Bart and Anthony for all the advices - it is great be able to
benefit from this group's experience.

I did two things:
-- now my program uses only one interrupt - it made
my program faster, but only a little bit,
-- I changed internal frequency rate - default it is 1MHz,
I changed it to 8MHz and my program runs 8 times
faster ;)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top