Forum: Mikrocontroller und Digitale Elektronik Takterzeugung in Assembler sinnvol?


von C. H. (hedie)


Lesenswert?

Hallo liebe Community

Ich stehe vor folgendem problem:

Ich muss in meiner Anwendung einen Rechteck Takt erzeugen. DutyCycle 
vorzugsweise 1/1

Die Geschwindigkeit sollte 500kHz + sein... (Je mehr desto besser)

Ich habe mir dazu mal folgenden Chip genauer angeschaut:
DS1085 http://www.maxim-ic.com/datasheet/index.mvp/id/3491
Dieser erzeugt selbstständig Signale im bereich von
8.1kHz - 133MHz bei einer genauigkeit von 0.75%

Soweit so gut....

Das problem ist, ich sollte den Takt in meinem uC programm definiert 
Starten können. Also genau wissen das der Takt jetzt mit der Steigenden 
Flanke beginnt etc.

deshalb dachte ich daran, Das Taktsignal mit dem AVR zu erzeugen. Dieser 
muss während der Signal Erzeugung nichts weiteres tun ausser die 
erzeugung und nach einer definierten zeit (zähler stand) wieder stoppen.

Ist es sinnvoll solch ein Signal mit dem AVR mit ein bisschen Assembler 
zu erzeugen?

Mit welchen Maximalen Taktraten kann ich in etwa rechnen bei einem 12Mhz 
bzw. einem 20MHz AVR Takt?

Danke schonmal

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:

> Ist es sinnvoll solch ein Signal mit dem AVR mit ein bisschen Assembler
> zu erzeugen?

Und was erhoffst du dir dabei von Assembler?

Deinen Takt kann der Timer des AVR ganz alleine erzeugen. Ein paar 
Konfigurationsbits richtig setzen und du hast deinen Takt an einem Pin.
(Das ist eine PWM mit einstellbarem TOP Wert und einem Comparewert genau 
in der Mitte).

> Mit welchen Maximalen Taktraten kann ich in etwa rechnen bei einem 12Mhz
> bzw. einem 20MHz AVR Takt?

theoretisch müsste man auf die Halfte der Taktfrequenz kommen.
Die eigentliche Frage ist aber eine andere: Wie genau kannst du von der 
Maximalfrequnz ausgehend abstufen und ist es erlaubt bei hohen 
Frequenzen von 1:1 abzuweichen.

von C. H. (hedie)


Lesenswert?

Karl heinz Buchegger schrieb:
> theoretisch müsste man auf die Halfte der Taktfrequenz kommen.
> Die eigentliche Frage ist aber eine andere: Wie genau kannst du von der
> Maximalfrequnz ausgehend abstufen und ist es erlaubt bei hohen
> Frequenzen von 1:1 abzuweichen.

Die Maximalfrequenz ist eigentlich irrelevant. Es spielt also keine 
rolle ob es 1MHz 2 oder 5MHz sind. Wobei 5MHz ein idealer Wert sein 
dürfte.

Ich nehme an, das ich durch Setzen eines Flags den Timer direkt Starten 
kann oder?

von C. H. (hedie)


Lesenswert?

Karl heinz Buchegger schrieb:
> und ist es erlaubt bei hohen
> Frequenzen von 1:1 abzuweichen.

Ja es ist minim erlaubt

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:

> Ich nehme an, das ich durch Setzen eines Flags den Timer direkt Starten
> kann oder?

So ist es.
Vorteiler setzen    -> der Timer läuft
Vorteiler wegnehmen -> der Timer steht

von C. H. (hedie)


Lesenswert?

Karl heinz Buchegger schrieb:
> So ist es.
> Vorteiler setzen    -> der Timer läuft
> Vorteiler wegnehmen -> der Timer steht

Ok sehr schön :)

Und damit ich weiss wie viele Takte bereits ausgegeben wurden, wäre es 
meiner meinung nach die beste lösung eine Variable in der Interrupt 
Funktion des Timers zu erhöhen. Was meinst du?

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:
> Karl heinz Buchegger schrieb:
>> So ist es.
>> Vorteiler setzen    -> der Timer läuft
>> Vorteiler wegnehmen -> der Timer steht
>
> Ok sehr schön :)
>
> Und damit ich weiss wie viele Takte bereits ausgegeben wurden, wäre es
> meiner meinung nach die beste lösung eine Variable in der Interrupt
> Funktion des Timers zu erhöhen. Was meinst du?

Dass du dann bei hohen Taktfrequenzen Schwierigkeiten kriegen wirst, 
weil auch der ISR Aufruf und dessen Abarbeitung Zeit braucht.

von Claudio Hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Dass du dann bei hohen Taktfrequenzen Schwierigkeiten kriegen wirst,
> weil auch der ISR Aufruf und dessen Abarbeitung Zeit braucht.

Ja, doch anderst ist es ja kaum zu lösen oder?

von Claudio Hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Dass du dann bei hohen Taktfrequenzen Schwierigkeiten kriegen wirst,
> weil auch der ISR Aufruf und dessen Abarbeitung Zeit braucht.

Wäre es denn im ISR Aufruf sinnvoll direkt mit Assembler befehlen den 
eigenen Zähler zu erhöhen und zu prüfen, ob der gewünschte wert bereits 
erreicht ist?

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:

> Wäre es denn im ISR Aufruf sinnvoll direkt mit Assembler befehlen den
> eigenen Zähler zu erhöhen und zu prüfen, ob der gewünschte wert bereits
> erreicht ist?

In dem Fall bin ich tatsächlich geneigt 'ja' zu sagen.
Nicht weil der Compiler erhöhen und Abfrage nicht genausogut hinkriegst 
wie du selber.
Sondern, weil du höchst wahrscheinlich einen besseren Funktion 
Prolog/Epilog hinkriegst als der Compiler.

von Claudio Hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> In dem Fall bin ich tatsächlich geneigt 'ja' zu sagen.
> Nicht weil der Compiler erhöhen und Abfrage nicht genausogut hinkriegst
> wie du selber.
> Sondern, weil du höchst wahrscheinlich einen besseren Funktion
> Prolog/Epilog hinkriegst als der Compiler.

Ok sehr schön....

Vielen Dank für deine Hilfe...

Ich werde es mal versuchen :)

nur noch eine kleine frage, was ist mit prolog und epilog genau gemeint?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Claudio Hediger schrieb:
> Karl heinz Buchegger schrieb:
>> Dass du dann bei hohen Taktfrequenzen Schwierigkeiten kriegen wirst,
>> weil auch der ISR Aufruf und dessen Abarbeitung Zeit braucht.
>
> Wäre es denn im ISR Aufruf sinnvoll direkt mit Assembler befehlen den
> eigenen Zähler zu erhöhen und zu prüfen, ob der gewünschte wert bereits
> erreicht ist?

Das kann ebenfalls die Harware erledigen. Einfach das Signal extern auf 
einen Eingang zurückgeben und einen zweiten Timer damit hochzählen 
lassen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Claudio Hediger schrieb:

> nur noch eine kleine frage, was ist mit prolog und epilog genau gemeint?

http://en.wikipedia.org/wiki/Function_prologue

von Claudio Hediger (Gast)


Lesenswert?

Johann L. schrieb:
> Das kann ebenfalls die Harware erledigen. Einfach das Signal extern auf
> einen Eingang zurückgeben und einen zweiten Timer damit hochzählen
> lassen.

Ahh das ist ja perfekt :)

Vielen Dank...

Ich verwende einen Atmega8 soweit ich weiss hat dieser einen 16bit 
counter..

Ich muss nämlich auf 32768 Zählen... somit sollte das ja bei diesem 
Controller kein problem sein oder?

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:

> nur noch eine kleine frage, was ist mit prolog und epilog genau gemeint?

Solange du das fragen musst .... lass Assembler Assembler sein und 
programmiere alles in C.

von Claudio Hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Solange du das fragen musst .... lass Assembler Assembler sein und
> programmiere alles in C.

Das hatte ich nach dem tipp, die hardware zählen zu lassen auch vor :)

Trozdem Danke

von Vuvuzelatus (Gast)


Lesenswert?

Andere unkomplizierte Möglichkeit: Den Burst einfach durch Pintoggeln in 
einer Schleife generieren.
1
GenerateBurst:
2
    ldi   r16, 0
3
    ldi   r17, 1
4
    out   DDRB, r17
5
6
    rjmp  LoopH
7
8
LoopL:
9
    out   PORTB, r16
10
11
    subi  ZL, 1
12
    sbci  ZH, 0
13
    breq  Fertig
14
15
LoopH:
16
    out   PORTB, r17
17
    
18
    nop
19
    rjmp  LoopL
20
21
Fertig:
22
    ret

Vor dem Aufruf muss das Z-Registerpaar auf die gewünschte Anzahl der 
Pulse gesetzt werden, z. B. erzeugt...
1
    ldi   ZL, Low (32768)
2
    ldi   ZH, High(32768)
3
    rcall GenerateBurst

...einen genau 32768 Pulse langen Burst mit der Taktfrequenz FOSC/4 auf 
Pin 0 von Port B.

von spess53 (Gast)


Lesenswert?

Hi

>Den Burst einfach durch Pintoggeln in einer Schleife generieren.

Pintoggeln geht bei neueren AVRs durch das Schreiben einer 1 in das 
entsprechende Bit des PIN-Registers.

'Writing a logic one to PINxn toggles the value of PORTxn, independent 
on the value of DDRxn.'

MfG Spess

von Claudio Hediger (Gast)


Lesenswert?

Vuvuzelatus schrieb:
> Andere unkomplizierte Möglichkeit: Den Burst einfach durch Pintoggeln in
> einer Schleife generieren.
> GenerateBurst:
>     ldi   r16, 0
>     ldi   r17, 1
>     out   DDRB, r17
>
>     rjmp  LoopH
>
> LoopL:
>     out   PORTB, r16
>
>     subi  ZL, 1
>     sbci  ZH, 0
>     breq  Fertig
>
> LoopH:
>     out   PORTB, r17
>
>     nop
>     rjmp  LoopL
>
> Fertig:
>     ret
>
> Vor dem Aufruf muss das Z-Registerpaar auf die gewünschte Anzahl der
> Pulse gesetzt werden, z. B. erzeugt...
>     ldi   ZL, Low (32768)
>     ldi   ZH, High(32768)
>     rcall GenerateBurst
>
> ...einen genau 32768 Pulse langen Burst mit der Taktfrequenz FOSC/4 auf
> Pin 0 von Port B.

Wow vielen Dank!

Das ist ja fantastisch :)

Ich nehme an, das ich bevor ich diesen Code ausführe, die register auf 
dem Stack sichern mus mittels push oder?

Ich weiss ja nicht wo der Compiler welches register verwendet

Zum ende muss ich sie wieder mit pop herstellen oder?

Besten Dank

von Claudio Hediger (Gast)


Lesenswert?

Ich pushe mal kurz :)

von Karl H. (kbuchegg)


Lesenswert?

Das kriegt dir der C-Compiler genauso hin.
Aber ganz so einfach ist es dann auch wieder nicht.
Du willst ja die Frequenz auch einstellen können.

von Claudio Hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Das kriegt dir der C-Compiler genauso hin.
> Aber ganz so einfach ist es dann auch wieder nicht.
> Du willst ja die Frequenz auch einstellen können.


Oh das tut mir leid... anscheinend habe ich da was falsches 
geschrieben...
Die frequenz muss nicht unbedingt einstellbar sein.
Trozdem Danke für den Hinweis :)

von Claudio Hediger (Gast)


Lesenswert?

Sorry für doppelpost ich kann eben nicht editieren war nicht 
angemeldet...

Ich hab den Assembeler code um folgendes erweitert
1
"push ZL \n\t"
2
    "push ZH \n\t"
3
    "push r16 \n\t"
4
    "push r17 \n\t"
5
6
7
"pop r17 \n\t"
8
    "pop r16 \n\t"
9
    "pop ZH \n\t"
10
    "pop ZL \n\t"

Mein jetziger Code
1
void fast_clock(void)
2
{
3
  asm volatile
4
  {  
5
    "push ZL \n\t"
6
    "push ZH \n\t"
7
    "push r16 \n\t"
8
    "push r17 \n\t"
9
    
10
    "ldi   ZL, Low (32768) \n\t"
11
    "ldi   ZH, High(32768) \n\t"
12
    
13
    "GenerateBurst: \n\t"
14
    "ldi   r16, 0 \n\t"
15
    "ldi   r17, 1 \n\t"
16
    "out   DDRB, r17 \n\t"
17
    
18
    "rjmp  LoopH \n\t"
19
    
20
    "LoopL: \n\t"
21
    "out   PORTB, r16 \n\t"
22
    
23
    "subi  ZL, 1 \n\t"
24
    "sbci  ZH, 0 \n\t"
25
    "breq  Fertig \n\t"
26
    
27
    "LoopH: \n\t"
28
    "out   PORTB, r17 \n\t"
29
    
30
    "nop \n\t"
31
    "rjmp  LoopL \n\t"
32
    
33
    "Fertig: \n\t"
34
    "pop r17 \n\t"
35
    "pop r16 \n\t"
36
    "pop ZH \n\t"
37
    "pop ZL \n\t"
38
    "ret \n\t"
39
  }
40
}

Wie könnte ich die anzahl takte mittels Parameter in der Funktion 
übergeben?

Danke :)

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:

> Oh das tut mir leid... anscheinend habe ich da was falsches
> geschrieben...
> Die frequenz muss nicht unbedingt einstellbar sein.

Okay. Kann auch sein, dass ich im Eröffnungsposting etwas rausgelesen 
habe, was nicht dort steht.

Das ändert die Situation allerdings.

D.h. du willst nur eine bestimmte Anzahl an Pulsen möglichst schnell 
rausblasen.

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:

> Wie könnte ich die anzahl takte mittels Parameter in der Funktion
> übergeben?

Indem du den Compiler arbeiten lässt :-)
1
#include <avr/io.h>
2
3
void Burst( uint16_t Count )
4
{
5
  while( Count-- )
6
    PINB |= ( 1 << PB0 );
7
}
8
9
int main()
10
{
11
  Burst( 32 );
12
}

wird übersetzt zu (wenn man den Compiler am inlining hindert)
1
  6c:  02 c0         rjmp  .+4        ; 0x72 <Burst+0x6>
2
  6e:  b0 9a         sbi  0x16, 0  ; 22
3
  70:  01 97         sbiw  r24, 0x01  ; 1
4
  72:  00 97         sbiw  r24, 0x00  ; 0
5
  74:  e1 f7         brne  .-8        ; 0x6e <Burst+0x2>
6
  76:  08 95         ret

und das ist doch nicht schlecht. Den 2ten sbiw könnte man noch 
einsparen. Aber sonst sehe ich nicht mehr viel Potential.

von Claudio Hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> wird übersetzt zu (wenn man den Compiler am inlining hindert)

Was genau meinst du mit ihn am inlining hindern?

Karl heinz Buchegger schrieb:
> D.h. du willst nur eine bestimmte Anzahl an Pulsen möglichst schnell
> rausblasen.

Ja und ich muss genau wissen wie lange das die clocks sind also welche 
Taktrate sie haben.

Karl heinz Buchegger schrieb:
> und das ist doch nicht schlecht. Den 2ten sbiw könnte man noch
> einsparen. Aber sonst sehe ich nicht mehr viel Potential.

Wie lange dauert denn in diesem fall der assembler code?
und ist es ein duty cycle cvon 1/1?

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:

> Was genau meinst du mit ihn am inlining hindern?

Bei dir passt irgendwie gar nichts zusammen.
Auf der einen Seite willst du alles unbedingt in Assembler machen, auf 
der anderen Seite schimmert immer wieder durch, dass du davon (sagen 
wirs mal freundlich) keine Ahnung hast.

Die Frage gehört auch dazu

> Wie lange dauert denn in diesem fall der assembler code?

genauso wie die hier

> und ist es ein duty cycle cvon 1/1?

die letzte lässt sogar darauf schliessen, dass du von Programmieren 
nicht allzuviel Ahnung hast.

Natürlich ist der Duty Cycle 1:1. In jedem Durchlauf wird der Pin 
getoggelt (in den anderen Zustand gebracht). 1 Durchlauf dauert immer 
gleich lang (bis auf ev. den ersten bzw. den letzten), daher kann der 
Duty Cycle nur 1:1 sein.

von Claudio Hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Bei dir passt irgendwie gar nichts zusammen.
> Auf der einen Seite willst du alles unbedingt in Assembler machen, auf
> der anderen Seite schimmert immer wieder durch, dass du davon (sagen
> wirs mal freundlich) keine Ahnung hast.

Hmmm also Ich habe früher mal mit Assembler die AVRs programmiert... das 
ist jedoch schon ein weillchen her.
Alles möchte ich bestimmt nicht in assembler machen. Mir geht es hier 
wirklich nur um diese kleine zeitkritische stelle.
Zudem ist es meiner meinung nach nicht schlimm etwas nicht zu wissen.
Wenn ich ja der Profi in assembler wäre, würde ich ja wohl kaum ein 
Topic in diesem Forum verfassen, oder etwa nicht?

Leider weiss ich immer noch nicht was du damit gemeint hast :)

Karl heinz Buchegger schrieb:
>> und ist es ein duty cycle cvon 1/1?
>
> die letzte lässt sogar darauf schliessen, dass du von Programmieren
> nicht allzuviel Ahnung hast.
>
> Natürlich ist der Duty Cycle 1:1. In jedem Durchlauf wird der Pin
> getoggelt (in den anderen Zustand gebracht). 1 Durchlauf dauert immer
> gleich lang (bis auf ev. den ersten bzw. den letzten), daher kann der
> Duty Cycle nur 1:1 sein.


Ich weiss das beim Pin Toggeling ein Tastverhältnis von 1:1 entsteht, 
jedoch war ich irritiert, da beim ASM Code lediglich eine 0 mit sbi 
geschrieben wird
1
 6c:  02 c0         rjmp  .+4        ; 0x72 <Burst+0x6>
2
  6e:  b0 9a         sbi  0x16, 0  ; 22   <------------
3
  70:  01 97         sbiw  r24, 0x01  ; 1
4
  72:  00 97         sbiw  r24, 0x00  ; 0
5
  74:  e1 f7         brne  .-8        ; 0x6e <Burst+0x2>
6
  76:  08 95         ret

Falls sbi 0x16, 0 den Pin automatisch bei jedem durchgang toggelt, ist 
es für mich auch absolut verständlich das es ein Duty Cycle von 1:1 
ergibt

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:

> Hmmm also Ich habe früher mal mit Assembler die AVRs programmiert...
> das ist jedoch schon ein weillchen her.

Das muss schon sehr lange her sein. Du scheinst so gut wie alles 
vergessen zu haben. Daher wäre es besser, wenn du von der Prämisse 
ausgehst: Ich steig wieder bei 0 ein.

> jedoch war ich irritiert, da beim ASM Code lediglich eine 0 mit sbi
> geschrieben wird

Schau dir bitte im Instruction Set an, was der Befehl sbi wirklich 
macht!
Und was bei neueren AVR ein Bitsetzen am PIN Register bewirkt.

von Karl H. (kbuchegg)


Lesenswert?

Claudio Hediger schrieb:

> Zudem ist es meiner meinung nach nicht schlimm etwas nicht zu wissen.

Da hast du recht.
Das ist nicht schlimm.

Warum es geht: Wenn du in C schreibst, dann schreib auch in C

Die meisten, die das das erste mal versuchen, wissen gar nicht worauf 
sie sich einlassen, wenn sie Assembler mit C mischen wollen. Die meisten 
denken auch, dass sie den Compiler ja leicht übertrumpfen. So nach dem 
Motto: Ich krieg gerade mal eine for-Schleife im 3ten Anlauf fehlerfrei 
hin, aber hey: so schwer kann das doch nicht sein, den Compiler 
auszustechen.

Wenn du Laufzeit Probleme hast, dann sieh erst mal zu, dass du sie mit 
dem Compiler lösen kannst. Viele Codestellen, die als ineffizienter 
Assemblercode vom Compiler erzeugt werden, haben ihre Ursache darin, 
dass der Programmierer C nicht beherrscht und daher dem Compiler 
entweder zuviele Freiheitsgrade lässt oder dem Compiler Nebenbedingungen 
aufs Auge drückt die ihn am Optimieren hindern.

Auf Assembler weicht man als C-Programmierer nur dann aus, wenn man 
tatsächlich über jeden Takt die Kontrolle braucht und das auch nur dann, 
wenn man sich angesehen hat, was der Compiler erzeugt und man einen Weg 
hat, wie man das besser machen kann.

Gut: Dein Problem ist von der Sorte - jeder Taktzyklus zählt.
Aber auch dann, schreibt man sich immer erst eine entsprechende 
C-Routine, sieht sich an, wie der Compiler das umsetzt und tauscht dann 
Funtkionsinhalte durch Assembler Code aus.

Und dann stellt sich die Frage nach: Wie kriege ich da Argumente rein, 
wie mache ich Dinge variabel so gar nicht mehr. Der Compiler hat das 
Framework erzeugt und nur noch die innersten Konstrukte tausch ich aus.

Als C-Programmiere fängt man so gut wie nie damit an, gleich erstmal 
einen Assemblerteil zu postulieren nur weil man das Gefühl hat, das man 
den brauchen wird.


Wenn dein Auto (deiner Meinung nach) zuviel Sprit braucht, dann kann 
dein erster Gedanke nicht sein, wie du die Verbrennung in den 
Brennkammern optimieren kannst. Sondern du schaust dir zb erst einmal 
den Reifendruck an, nimmst die Dachträger vom Dach etc.

von Claudio Hediger (Gast)


Lesenswert?

Vielen Dank für dein langes Posting :)

Ich stimme dem in allen punkten zu....

Ich werde mich nun also mal an die sache ranmachen und erstmal mit C 
weiterfahren....

Danke nochmals :)

von Claudio hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> #include <avr/io.h>
>
> void Burst( uint16_t Count )
> {
>   while( Count-- )
>     PINB |= ( 1 << PB0 );
> }
>
> int main()
> {
>   Burst( 32 );
> }

Ich hab nun den Code getestet....

Leider kommt damit kein Clock raus :(

von Karl H. (kbuchegg)


Lesenswert?

Claudio hediger schrieb:

> Leider kommt damit kein Clock raus :(

Das war auch kein komplettes vollständiges Beispiel, sondern es ging nur 
um die Fragestellung: Was macht der Compiler aus der Schleife in der 
Funktion Burst.

Um das im Assembler Listing zu sehen, muss ich zb keinen Port auf 
Ausgang schalten. Ich kann auch davon ausgehen, dass der µC tatsächlich 
einer von der Sorte ist, der mittels Bitsetzen im Pin Register den 
Ausgabeport toggelt (das hängt aber vom tatsächlichen Typ des AVR-µC ab. 
Ältere können das nicht)

von MWS (Gast)


Lesenswert?

Die älteren Atmel beherrschen das Toggeln bei Schreiben auf den 
Eingangspin nicht, der ATM8 könnte dazugehören. Außerdem muss das DDRx 
Register auf Ausgang gesetzt werden, sonst geht auch nix.

von Claudio hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> (das hängt aber vom tatsächlichen Typ des AVR-µC ab.
> Ältere können das nicht)

Ich verwende einen Atmega8 wie bereits weit(er) oben geschrieben.

Diese kann es leider nicht :(

Somit würde mir nur noch die Assembler variante bleibe oder sehe ich da 
was nicht?

von Karl H. (kbuchegg)


Lesenswert?

Claudio hediger schrieb:

> Somit würde mir nur noch die Assembler variante bleibe oder sehe ich da
> was nicht?

Und wie würdest du das dann in Assembler schreiben?


(Die C Variante sieht genau gleich aus wie die Assembler Variante. Wenn 
du es in C nicht kannst, kriegst du es auch in Assembler nicht hin. Ich 
kann ehrlich dieses ewige sofortige 'dann muss ichs in Assembler machen' 
schon nicht mehr hören. Für dich scheint Assembler die Lösung aller 
Probleme zu bedeuten)

Schreibs komplett in Assembler, dann stellt sich die Frage schon nicht 
mehr.

von Claudio hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Die C Variante sieht genau gleich aus wie die Assembler Variante

Ok jetzt hab ichs endlich kapiert :P sorry für die soooo lange leitung

ich habs nun also so programmiert:
1
void fast_clock(void)
2
{
3
  unsigned char ucR16 = 0x02;
4
  unsigned char ucR17 = 0x00;
5
6
  while(1)
7
  {
8
    PORTC &= ucR17;
9
    PORTC |= ucR16;
10
  }
11
}

Einfach mal als kleiner test
Ich komme jedoch auf 1Mhz getaktet ist der AVR mit 12Mhz
Müsste das nicht flotter sein?

von Karl H. (kbuchegg)


Lesenswert?

Claudio hediger schrieb:

> ich habs nun also so programmiert:

>
1
> void fast_clock(void)
2
> {
3
>   unsigned char ucR16 = 0x02;
4
>   unsigned char ucR17 = 0x00;
5
> 
6
>   while(1)
7
>   {
8
>     PORTC &= ucR17;
9
>     PORTC |= ucR16;
10
>   }
11
> }
12
>
>
> Einfach mal als kleiner test
> Ich komme jedoch auf 1Mhz getaktet ist der AVR mit 12Mhz
> Müsste das nicht flotter sein?

Du möchtest hier keine Variablen benutzen.
Denn dann bleibt dem Compiler nichts anderes übrig, als tatsächlich die 
Operationen so auszuführen, wie du sie hingeschrieben hast

   Wert vom Port holen
   Variablenwert holen
   miteinander verunden
   Wert an den Port schreiben

1
#define CLOCK_PIN   PC1
2
#define CLOCK_PORT  PORTC
3
4
void fast_clock(void)
5
{
6
  while(1)
7
  {
8
    CLOCK_PORT &= ~( 1 << CLOCK_PIN );
9
    CLOCK_PORT |=  ( 1 << CLOCK_PIN );
10
  }
11
}


 1 << CLOCK_PIN ist eine Konstante (0x02). Und erst jetzt ermöglichst du 
dem Compiler zu optimieren was nur geht (sofern der Optimizer 
eingeschaltet ist).

Der Umweg über die #define hat nur den Zweck, die komplette 
Hardwarekonfiguration an einer Stelle beisammen zu haben. Ändert sich 
der Pin, brauchst du das nur beim CLOCK_PIN an einer Stelle ändern und 
neu kompilieren.
1
void fast_clock(void)
2
{
3
  while(1)
4
  {
5
    PORTC &= ~( 1 << PC1 );
6
    PORTC |=  ( 1 << PC1 );
7
  }
8
}

würde genau dasselbe machen, aber die Hardwareinfo ist über mehrere 
Stellen verstreut.

AVR-GCC-Tutorial

von Claudio hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> void fast_clock(void)
> {
>   while(1)
>   {
>     PORTC &= ~( 1 << PC1 );
>     PORTC |=  ( 1 << PC1 );
>   }
> }

Ok vielen dank...

Also ich hab das mal soweit getestet....
Ich komme damit auf etwa 1.2Mhz

Merkwürdigerweise ist das signal alles andere als stabil!

Es schwankt zwischen 1Mhz und 1.5Mhz hin und her!

von Karl H. (kbuchegg)


Lesenswert?

Claudio hediger schrieb:
> Karl heinz Buchegger schrieb:
>> void fast_clock(void)
>> {
>>   while(1)
>>   {
>>     PORTC &= ~( 1 << PC1 );
>>     PORTC |=  ( 1 << PC1 );
>>   }
>> }
>
> Ok vielen dank...
>
> Also ich hab das mal soweit getestet....
> Ich komme damit auf etwa 1.2Mhz

Assembler Code ansehen.

Das müsste in eine Schleife

    cbi  ...
    sbi  ...
    rjmp ...

übersetzt werden. Das sind in Summe 6 Takte, d.h du bist bei 12/6 = 2Mhz

Durch Loop Unrolling ginge da noch was, aber wesentlich schneller wirds 
nicht mehr. Und du willst ja auch noch Pulse zählen.

von Claudio hediger (Gast)


Lesenswert?

fast clock section in assembler
1
00000048 <fast_clock>:
2
  48:  a9 98         cbi  0x15, 1  ; 21
3
  4a:  a9 9a         sbi  0x15, 1  ; 21
4
  4c:  fd cf         rjmp  .-6        ; 0x48 <fast_clock>

von Claudio hediger (Gast)


Lesenswert?

so siehts aus wenn ich count runterzähle
1
0000004a <fast_clock>:
2
  4a:  03 c0         rjmp  .+6        ; 0x52 <fast_clock+0x8>
3
  4c:  a9 98         cbi  0x15, 1  ; 21
4
  4e:  a9 9a         sbi  0x15, 1  ; 21
5
  50:  01 97         sbiw  r24, 0x01  ; 1
6
  52:  00 97         sbiw  r24, 0x00  ; 0
7
  54:  d9 f7         brne  .-10       ; 0x4c <fast_clock+0x2>
8
  56:  08 95         ret

von Karl H. (kbuchegg)


Lesenswert?

Claudio hediger schrieb:

> Merkwürdigerweise ist das signal alles andere als stabil!
> Es schwankt zwischen 1Mhz und 1.5Mhz hin und her!

Da zweifle ich mal die Verwendung des 12Mhz Quarzes an.

Interne 8Mhz würden bedeuten:

  8 / 6 = 1.33Mhz

und das passt ziemlich gut zu deinen 1.2Mhz. Und auch die Schwankungen 
würden sich dadurch erklären.

-> Fuse-Bits ansehen, ob der Mega tatsächlich auf Quarz gefused ist.

von Claudio hediger (Gast)


Angehängte Dateien:

Lesenswert?

Hmmm also wenn ich den Quarz entferne läuft nix mehr und die Fuses sind 
meiner Meinung nach auch korrekt...

Siehe angehängtes bild

von Karl H. (kbuchegg)


Lesenswert?

Claudio hediger schrieb:
> Hmmm also wenn ich den Quarz entferne läuft nix mehr

OK. Das genügt mir um den Quarz zu akzeptieren. Das ist ein deutliches 
Indiz.
Aber ein Quarz schwingt nicht so unregelmässig (ausser er ist defekt)

Irgendwelche Interrups aktiv?

von MWS (Gast)


Lesenswert?

Was soll denn eigentlich das hier sein ?
1
sbiw  r24, 0x00

Völlig unsinnige Anweisung. Kostet 2 Zyklen.

von Claudio hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Claudio hediger schrieb:
>> Hmmm also wenn ich den Quarz entferne läuft nix mehr
>
> OK. Das genügt mir um den Quarz zu akzeptieren. Das ist ein deutliches
> Indiz.
> Aber ein Quarz schwingt nicht so unregelmässig (ausser er ist defekt)
>
> Irgendwelche Interrups aktiv?

Es sind keine Interrupts aktiv nach dem main geht direkt in die 
Endlosschleofe mit der Takterzeugung.

Ich habe den quarz kurzerhand ausgetauscht... Leider das selbe ergebnis

von Karl H. (kbuchegg)


Lesenswert?

Claudio hediger schrieb:

> Ich habe den quarz kurzerhand ausgetauscht... Leider das selbe ergebnis

Hmm.
Gibts doch gar nicht.

Das Messverfahren ist über jeden Zweifel erhaben?

von Claudio hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Hmm.
> Gibts doch gar nicht.
>
> Das Messverfahren ist über jeden Zweifel erhaben?

hmmm merkwürdig es geht nun plötzlich :S

nun ja... vielleicht hab ich es nicht richtig kompiliert

das duty cycle ist 8:2 (2 ist low)

Kann man das noch etwas optimieren?

von Karl H. (kbuchegg)


Lesenswert?

Claudio hediger schrieb:

> das duty cycle ist 8:2 (2 ist low)
>
> Kann man das noch etwas optimieren?


Jetzt ist der Zeitpunk, an dem man auf Assembler muss

Aus
1
  4c:  a9 98         cbi  0x15, 1  ; 21
2
  4e:  a9 9a         sbi  0x15, 1  ; 21
3
  50:  01 97         sbiw  r24, 0x01  ; 1
4
  52:  00 97         sbiw  r24, 0x00  ; 0
5
  54:  d9 f7         brne  .-10       ; 0x4c <fast_clock+0x2>
6
  56:  08 95         ret

muss
1
        cbi  0x15, 1  ; 21
2
        sbiw  r24, 0x01  ; 1
3
        sbi  0x15, 1  ; 21
4
        brne  .-8
5
        ret

werden. Dann sollte sich ein 1:1 Verhältnis einstellen.
Das kriegen wir nicht mehr in C gebacken, weil wir ihm nicht 
vorschreiben können, dass er Teile des Schleifenkonstrukts mitten 
zwischen die Setz und Lösch Anweisungen reinziehen soll.
Ich bin in inline_Assembler aber zu schwach. Mit diesen Clobber Listen 
hab ich so meine liebe Mühe :-)
Aber ich probiers mal, wenn ich zu Hause bin.

von Claudio hediger (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Aber ich probiers mal, wenn ich zu Hause bin.

Vielen Dank :)

Ich bin sehr froh um deine Hilfe!

von MWS (Gast)


Lesenswert?

Karl Heinz, will Dir jetzt den Spaß nicht verderben ;-)

> Jetzt ist der Zeitpunk, an dem man auf Assembler muss
Nein, muss man nicht.

Man muss nur geschickt umstellen.
Das hier braucht 8 Zyklen, entsprechend 1,5MHz bei genau 1:1 
Tastverhältnis:
1
unsigned int Ctr = 65535;
2
while(Ctr)
3
{
4
  PORTC |= ( 1 << PC1 );
5
    Ctr--;
6
      PORTC &= ~( 1 << PC1 );
7
}

Und der Compiler baut auch den Murks nicht rein, daß er ein SBIW 0 
verwendet, zumindest hier bei mir nicht. :-)

von Karl H. (kbuchegg)


Lesenswert?

MWS schrieb:
> Karl Heinz, will Dir jetzt den Spaß nicht verderben ;-)

Ganz im Gegenteil.
Wenn Assembler vermeidbar ist, dann umso besser

>
1
unsigned int Ctr = 65535;
2
> while(Ctr)
3
> {
4
>   PORTC |= ( 1 << PC1 );
5
>     Ctr--;
6
>       PORTC &= ~( 1 << PC1 );
7
> }

Kopfklatsch.
Manchmal ist alles so naheliegend und man siehts trotzdem nicht.
Ich war so sehr auf
   while( Ctr-- )
fixiert, dass ich in die Richtung gar nicht nachgedacht habe.

Danke.

von Vuvuzelatus (Gast)


Lesenswert?

Man kann sogar einfach meine Assemblerroutine von oben so in C abbilden, 
dass der Compiler wieder den gleichen Maschinencode daraus macht. Der 
"Trick": Man lässt das 'breq' und das 'rjmp' zu 'goto's werden - der 
Compiler übersetzt sie dann brav wieder in 'breq' und 'rjmp' zurück. Im 
Ergebnis bekommt man damit wieder die mit dieser Methode maximal 
erreichbare Pulsfrequenz FOSC/4.

1
#include <avr/io.h>
2
#define F_CPU 20.000
3
4
5
//--------------------------------------
6
7
void GenerateBurst (unsigned int n)
8
9
{
10
  PORTB = 1;
11
  asm volatile ("nop");
12
  asm volatile ("nop");
13
  asm volatile ("nop");
14
15
  Loop:
16
  PORTB = 0;
17
    
18
  n--;
19
  if (n==0) goto Fertig;
20
      
21
  PORTB = 1;
22
  asm volatile ("nop");
23
  goto Loop;
24
25
  Fertig:;
26
}
27
28
29
//--------------------------------------
30
31
int main()
32
{
33
  DDRB = 1;  // Pin PB0 als Ausgang konfigurieren
34
35
  while(1)
36
    {
37
    GenerateBurst(5);
38
    }
39
  
40
  return 0;
41
}

von Claudio Hediger (Gast)


Lesenswert?

Vuvuzelatus schrieb:
> Im
> Ergebnis bekommt man damit wieder die mit dieser Methode maximal
> erreichbare Pulsfrequenz FOSC/4.

Fantastisch :) Vielen Dank!

Aber klappt das auch wenn man nur einen Pin ändern möchte und nicht den 
gesamten Port?

also mit
1
PORTC &= ~( 1 << PC1 );

von spess53 (Gast)


Lesenswert?

Hi

>Im Ergebnis bekommt man damit wieder die mit dieser Methode maximal
>erreichbare Pulsfrequenz FOSC/4.

Eher FOSC/8.

MfG Spess

von Vuvuzelatus (Gast)


Lesenswert?

>Aber klappt das auch wenn man nur einen Pin ändern möchte und nicht den
>gesamten Port?
>
>also mit
>
>PORTC &= ~( 1 << PC1 );

Klar, nur die Pulsfrequenz ist dann etwas geringer, nämlich FOSC/10 
statt FOSC/8, weil dieses Konstrukt in sbi/cbi übersetzt wird, die je 
zwei Takte benötigen. PORTB = 0 und PORTB = 1 wird über out erledigt, 
das nur einen Takt verbraucht.

>Eher FOSC/8.

Jooo... danke, Spess :-)

Meine "FOSC/4" ist falsch, ich korrigiere: Von jeder Flanke bis zur 
nächsten sind es 4 Takte, aber da ein Puls aus zwei Flanken besteht 
(L-->H und H-->L), ist die resultierende Frequenz FOSC/8.

von Vuvuzelatus (Gast)


Lesenswert?

Noch eine verbesserte Version. Wieder FOSC/8, aber ohne Nebenwirkung auf 
den Pins PB1 bis PB7. Alle Pins außer PB0 bleiben auf den Zuständen, die 
sie beim Aufruf der Routine haben. Falls anderer Ausgabepin als PB0 
gewünscht, einfach im Code ändern.
1
void GenerateBurst (uint16_t n)
2
3
{
4
  uint8_t pL = PORTB |  (1<<PB0);
5
  uint8_t pH = PORTB & ~(1<<PB0);
6
7
  PORTB = pH;
8
  asm volatile ("nop");
9
  asm volatile ("nop");
10
  asm volatile ("nop");
11
12
  Loop:
13
  PORTB = pL;
14
    
15
  n--;
16
  if (n==0) goto Fertig;
17
      
18
  PORTB = pH;
19
  asm volatile ("nop");
20
  goto Loop;
21
22
  Fertig:;
23
}

von spess53 (Gast)


Lesenswert?

Hi

Mal ehrlich. Mit Assembler wäre man schon zehn mal fertig. Statt dessen 
wird hier tagelang mit C Problemen herum gedoktert, die man ohne C nicht 
hätte.

MfG Spess

von Karl H. (kbuchegg)


Lesenswert?

spess53 schrieb:
> Hi
>
> Mal ehrlich. Mit Assembler wäre man schon zehn mal fertig. Statt dessen
> wird hier tagelang mit C Problemen herum gedoktert, die man ohne C nicht
> hätte.
>

Übertreib nicht.
Ein C-Kundiger hat die Schleife genauso schnell, wie du das alles in 
Assembler (ok. bei der Schleife hab ich etwas geschlafen um sie auf 1:1 
zu bringen)
Und vom ganzen Rest rundherum reden wir mal nicht :-)

Wenn sich Vuvuzelatus spielen will, soll er das tun.
Für den Rest ist
1
void Burst( unsigned int Ctr )
2
{
3
  while(Ctr)
4
  {
5
    PORTC |= ( 1 << PC1 );
6
    Ctr--;
7
    PORTC &= ~( 1 << PC1 );
8
  }
9
}
perfekt und spielt alle Stückchen die man braucht.

von Claudio Hediger (Gast)


Lesenswert?

Vielen Dank

Läuft nun alles einwandfrei :)

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.