Atmega16 100kHz Encodersignal

Gast #5191556
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!
#5191559
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.
Gast #5191565
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.
#5191584
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????
Gast #5191608
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.
#5191616
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
#5191675
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!
Gast #5191682
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.
Gast #5191790
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
Gast #5192261
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...

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren