Forum: Mikrocontroller und Digitale Elektronik Atmega16 100kHz Encodersignal


von Wer wird da gleich... (Gast)


Lesenswert?

Hallo,

ich muss einen Drehgeber mit 100kHz simulieren. Vormals waren es 15kHz 
aber die Anforderungen wurden wiedermal geändert.

Aus diesem Grund ist ein Atmega16 bestückt (16MHz Quarz).

Ich habe eigentlich nur vier Zustände, die regemlmäßig durchlaufen 
werden:

/A/B
A/B
AB
/AB

Dann geht das Rondo von vorne los.

Ursprünglich war angedacht, über RS232 die Frequenz änderbar zu 
gestalten. Daher hatte ich per TWI den Teiler für den Überlauf 
übertragen und im Timer Overflow Interrupt aus einem Array die Werte für 
die Ausgänge ausgelesen und gesetzt.

Mit dieser Methode komme ich aber nicht ansatzweise an meine 100kHz 
heran.

Jetzt habe ich den Quelltext mal komplett zusammengestrichen (nurnoch 
einen Zähler von 1-4 und Ausgabe der Werte auf den Port).

Damit erreiche ich bei maximaler Optimierung gerade einmal 120kHz.

Wieso ist das so langsam? Das würde ja bedeuten, dass der Prozessor für 
diese lapidare Aufgabe über 100 Takte benötigt?!

Habe ich einen prinzipiellen Denkfehler oder ist der Prozessor wirklich 
so langsam?

Danke für Input!

von Walter T. (nicolas)


Lesenswert?

Wenn der Quelltext so zusammengestrichen wurde, wird wohl auch nur noch 
wenig drinstehen, was der Geheimhaltung bedarf.

Wird der Grau-Code über eine Tabelle erzeugt? Oder über 
Shift-Operationen mit XOR?

Sind die Port/Pin-Adressen fest oder Variablen?

Es gibt schon ein paar Möglichkeiten, das langsam zu bekommen.

von Patrick J. (ho-bit-hun-ter)


Lesenswert?

Hi

Wer kümmert sich um die RS232-Kommunikation?
Vll sind dort Deine 100 Takte zu finden.

MfG

von Wolfgang (Gast)


Lesenswert?

Wer wird da gleich... schrieb:
> Wieso ist das so langsam? Das würde ja bedeuten, dass der Prozessor für
> diese lapidare Aufgabe über 100 Takte benötigt?!

Das wird an deinem Programm liegen.

Der Prozessor kann es auf jeden Fall schneller. Wenn es also wirklich 
schnell sein soll, nimmst du den ganzen Timer- und Interrupt-Kram raus 
und lässt den Prozessor die Pulse direkt mit Zeitsteuerung z.B. über 
NOP-Befehle ausgeben.

von Mario M. (thelonging)


Lesenswert?

Sind die Fuse-Bits richtig gesetzt? Default läuft der AtMega16 mit 
internem Oszillator mit 1 MHz. Das wären dann 8 Takte für 125 kHz.

von Bubbi B. (blubbi)


Lesenswert?

Sorry, konnte nix mehr schreiben weil ein Bug in der Forensoftware das 
verhindert hat GRML

Der Quelltext ist recht primitiv (is ja nix meghr da):
1
#define _XTAL_FREQ 16000000
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <stdint.h>       
5
#include <stdlib.h>         
6
#include <util/delay.h>
7
#include "TWI_Slave.h"
8
#include "avr/sleep.h"
9
10
unsigned int overflowCounter,byte0,byte1,byte2,byte3,i, overflowcounter2 = 0;
11
unsigned int encodervalue[4];
12
13
int main( void )
14
{
15
  DDRC = 0x3C;
16
  
17
  encodervalue[0]=0x0C;
18
  encodervalue[1]=0x18;
19
  encodervalue[2]=0x30;
20
  encodervalue[3]=0x24;
21
22
  while(1)
23
  {
24
  
25
  i++;
26
  if (i==4)
27
  {i=0;}
28
  PORTC=((PORTC&0xC3)|encodervalue[i]);
29
  }
30
}

Fuses sind auf "High Speed Ext. Crystal 16 MHz" (oder so ähnlich) 
gesetzt. Oder muss ich andere Fuses auch noch setzen????

von Felix U. (ubfx)


Lesenswert?

Bubbi B. schrieb:
> Oder muss ich andere Fuses auch noch setzen????

CLKDIV8 darf nicht gesetzt sein, das ist iirc bei Auslieferung gesetzt.

von Bubbi B. (blubbi)


Lesenswert?

Felix U. schrieb:
> Bubbi B. schrieb:
>> Oder muss ich andere Fuses auch noch setzen????
>
> CLKDIV8 darf nicht gesetzt sein, das ist iirc bei Auslieferung gesetzt.

Hat der Atmega16 nicht.

von Wolfgang (Gast)


Lesenswert?

Bubbi B. schrieb:
> if (i==4)
>   {i=0;}

Damit erzeugst du dir einen (vermutlich) ungewollten Jitter im Signal, 
i.e. der Zustand bei i=3 ist um die Ausführungszeit für das 0-setzen von 
i länger. Gleichmäßiger läuft das ab, wenn du einfach immer ein
1
i &= 0x03;
durchläufst.


Die ganze Indizierung mit i=0..3 kann man durch linearen Code ersetzen, 
das würde noch die Zeit für den indizierten Array-Zugriff sparen.

von Weingut P. (weinbauer)


Lesenswert?

PORTC=((PORTC&0xC3)|encodervalue[i] ...

Da musst Du den Port erstmal lesen, mit c3 verunden, encodervalue lesen, 
mit dem vorangegangenen verodern und dann in Port schreiben, das sind 
schon einige Schritte ...

schreib doch einfach direkt in Port, sind ja nur 4 Zustände, ergo 4 
Zeilen Code, zur Not in ASM

out PORTC, 0xirgendwas

von Felix U. (ubfx)


Lesenswert?

Bubbi B. schrieb:
> Hat der Atmega16 nicht.

Sorry, das habe ich verwechselt.

Schneller geht es aber auch, wenn du die encodervalues in 8-bit statt 
16-bit ints schreibst, weil der Compiler die dann in Registern behalten 
kann. Dasselbe gilt natürlich für i. Dann noch das, was der Weinbauer 
gesagt hat, dann bist du wohl schon doppelt so schnell.

: Bearbeitet durch User
von Bubbi B. (blubbi)


Lesenswert?

Wolfgang schrieb:
> Das wird an deinem Programm liegen.

Vermutlich. Ich hab das Programm jetzt komplett reduziert und setze stur 
den Port.

2,6 MHz...

Irre, wieviel so wenig Befehle und ein paar optimierungsprobleme (16- 
statt 8bit Zugriff etc.) den Prozessor extrem verlangsamen o.O

Selbst wenn ich alles bis ultimo optimiere; ich weiß nicht, ob der 
Prozessor das wirklich packt, SPI auch noch zu machen, wenn er 100.000 
Hz erreichen soll!

Danke euch!

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

Bubbi B. schrieb:
> SPI auch noch zu machen,

Wenn SPI oder UART per Interrupt zuschlagen wird nur in dem Moment der 
Übertragung der Output gestört. Aber das man doch sicher nicht ständig 
machen, also läuft die Ausgabe genauso schnell wenn man die Vorgabe per 
Schnittstelle einbaut.
ok, fast genauso schnell, es muss ja noch die einstellbare Verzögerung 
in die Schleife.

von c-hater (Gast)


Lesenswert?

Bubbi B. schrieb:

> Vermutlich. Ich hab das Programm jetzt komplett reduziert und setze stur
> den Port.
>
> 2,6 MHz...

Zyklus oder Schrittrate?

Wenn Zylus, dann hast du das ist das Optimum für diese Busy-Loop 
erreicht. Sowas kann C gut.

Allerdings: Jitter schon ohne Störungen durch ISRs: 300%. Das kommt 
durch das Schließen der Schleife. Kann man aber kompensieren, indem man 
nach jeder der ersten drei Portausgaben 2x nop einfügt. Dann ist der 
Jitter weg. Asm rules...

Allerdings sinkt dadurch natürlich die Zyklusfrequenz von 16M/6 auf 
16M/12, also auf die Hälfte.

Egal, auch dann: Wenn da nur eine einzige in C geschriebene ISR 
reingrätscht, bist du locker bei einigen 1000% Jitter...

Nö, nö, der Ansatz mit einer Timer-ISR war schon OK, bloß die Sprache 
war falsch. Mit Asm kann man 1MHz ohne jeden Hauch von Jitter erreichen 
und auch konkurrierende ISRs wirken sich längst nicht so schädlich aus 
(wenn sie ebenfalls liebevoll Asm-optimiert werden).

Falls es dich interessiert, so würde dein Problem in einer richtigen 
Sprache gelöst werden:
1
.EQU ENC_A_BIT = 0                      ;nur als Beispiele
2
.EQU ENC_B_BIT = 1
3
.EQU ENC_PORT = PORTC
4
5
.EQU ENC_A_MSK = 1<<ENC_A_BIT
6
.EQU ENC_B_MSK = 1<<ENC_B_BIT
7
8
.DEF ENC_STAT = R16                     ;optional, ansonsten +4 ticks für register save/restore
9
10
.CSEG
11
.ORG OVF0addr                           ;[4][4][4][4]   "in place" auf dem Vektor, ansonsten +2 ticks for rjmp
12
    sbrc ENC_STAT,ENC_A_BIT             ; 2  2  1  1
13
    rjmp _isr_a_high                    ;       2  2
14
    nop                                 ; 1  1          optional: sorgt nur für Jitterfreiheit (jedenfalls so lange ohne ISR-Konkurrenz)
15
16
    sbrc ENC_STAT,ENC_B_BIT             ; 2  1
17
    ldi ENC_STAT,ENC_A_MSK|ENC_B_MSK    ;    1
18
    sbrs ENC_STAT,ENC_B_BIT             ; 1  2
19
    ldi ENC_STAT,ENC_B_MSK              ; 1
20
    out ENC_PORT,ENC_STAT               ; 1  1
21
    reti                                ; 4  4
22
    
23
    _isr_a_high:
24
    sbrs ENC_STAT,ENC_B_BIT             ;       2  1
25
    ldi ENC_STAT,0                      ;          1
26
    sbrc ENC_STAT,ENC_B_BIT             ;       1  2
27
    ldi ENC_STAT,ENC_A_MSK              ;       1
28
    out ENC_PORT,ENC_STAT               ;       1  1
29
    reti                                ;       4  4
30
                                        ;-- -- -- --
31
                                        ;16 16 16 16

von Peter D. (peda)


Lesenswert?

Wer wird da gleich... schrieb:
> Damit erreiche ich bei maximaler Optimierung gerade einmal 120kHz.

Nimm einfach den Timer T1 mit Toggle Output on Compare, da sollten bis 
4MHz gehen und ganz ohne Interruptlast.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Peter D. schrieb:

> Nimm einfach den Timer T1 mit Toggle Output on Compare, da sollten bis
> 4MHz gehen und ganz ohne Interruptlast.

Das kann man natürlich auch machen und ist für dieses einfache Problem 
die bei weitem effizienteste Lösung.

Aber ganz so einfach ist sie auch nicht umzusetzen. In der Nähe von Vmax 
werden sowohl Änderungen der Schrittfrequenz als auch Richtungswechsel 
zum Alptraum. Jedenfall, wenn sie einigermassen glitchfrei erfolgen 
sollen.

Und schon wieder heisst es: Assembler rules...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.