Forum: Mikrocontroller und Digitale Elektronik Toggelgeschwindigkeit für Ports


von Rüdiger B. (fenris)


Lesenswert?

Hallo!

Ich bin kompletter Neuling auf dem Gebiet Microcontroller und habe 
dementsprechend die eine oder andere Frage. Fangen wir einfach mal mit 
der ersten an. ;)

Hintergrund: Ich würde gerne einen 8 Kanal AD-Wandler auslesen 
(MAX1270).
Das Board mit dem ich arbeite hat einen ATMega128 und läuft mit einem
3,6864 MHz Baudratenquarz. Der Takteingang des AD-Wandlers ist mit PA0 
verbunden.

Nun experimentiere ich gerade mit verschiedenen Möglichkeiten herum 
diesen Takteingang anzusprechen, u.a. auch indem ich mithilfe eines 
Timers den Port toggel, was mich zu meiner eigentlichen Frage führt.

Um mal die maximale Porttoggelgeschwindigkeit zu ermitteln hab ich mir 
gedacht ich schreibe einfach:

int main(void)
{
 init_devices();
 while(1)
  {
   PORTC ^= ( 1 << PINC3 );
  }
 return 0;
}

PINC3 ist einfach mit dem Oszilloskop zu erreichen, deshalb wird der 
verwendet und nicht wie oben PA0.

Hier wird janun nichts anderes abgearbeitet als das Toggeln, aber egal 
wie schnell ich meinen Timer konfiguriere, ich komme nicht über eine 
Frequenz von ca. 370 kHz. Ich denke mal, dass ich das prinzipiell schon 
richtig mache, denn wenn ich den Timer z.B. auf 1000 Hz einstelle, dann 
erreiche ich auch die zu erwartenden 500 Hz. Nur scheint irgendwie nach 
oben hin Schluß zu sein.
Kann es sein, dass die ganze Abarbeitung in etwa 20 Takte dauert und ich 
deshalb in etwa 1/10 meiner Quarzfrequenz erhalte?

Gruss, Rüdiger

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Dein Codeausschnitt berücksichtig in keiner Weise den Timer --> Ergo ist 
die Toggelgeschwindigkeit immer gleich.

von (prx) A. K. (prx)


Lesenswert?

Erstens solltest mit Optimierung übersetzen, wenn's dir drauf ankommt.

Zweitens geht es auch schneller:
1
while(1)
2
{
3
   PORTC |= ( 1 << PINC3 );
4
   PORTC &=~( 1 << PINC3 );
5
   PORTC |= ( 1 << PINC3 );
6
   PORTC &=~( 1 << PINC3 );
7
   PORTC |= ( 1 << PINC3 );
8
   PORTC &=~( 1 << PINC3 );
9
   PORTC |= ( 1 << PINC3 );
10
   PORTC &=~( 1 << PINC3 );
11
}
gibt etwas mehr als 4 CPU-Takte pro Pin-Takt.
Nocht schneller:
1
uint8_t portc_1 = PORTC & ~(1<<PINC3);
2
uint8_t portc_h = PORTC |  (1<<PINC3);
3
while(1)
4
{
5
   PORTC = portc_l;
6
   PORTC = portc_h;
7
   PORTC = portc_l;
8
   PORTC = portc_h;
9
   PORTC = portc_l;
10
   PORTC = portc_h;
11
   PORTC = portc_l;
12
   PORTC = portc_h;
13
}
mit gut 2 CPU-Takten pro Pin-Takt.

von Johannes M. (johnny-m)


Lesenswert?

Das Toggeln, was Du da machst, hat überhaupt nichts mit dem Timer zu 
tun! Die Geschwindigkeit ist durch die Ausführungszeit der Schleife 
bestimmt.

Wenn Du mit dem Timer einen Portpin toggeln willst, dann musst Du den 
Timer entsprechend konfigurieren (sinnvolerweise CTC-Betrieb) und einen 
Pin, den der Timer auch hardwaremäßig ansteuern kann, benutzen. Da muss 
man aber nicht viel ausprobieren: Die maximale Toggelfrequenz per 
Hardware ist F_CPU / 2.

von Johannes M. (johnny-m)


Lesenswert?

@A. K.:
Warum sollte die "&="/"|="-Kombination schneller sein als ein EXOR?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rüdiger B. wrote:

> Kann es sein, dass die ganze Abarbeitung in etwa 10 Takte dauert und ich
> deshalb in etwa 1/10 meiner Quarzfrequenz erhalte?

Wenn du ohne Optimierung arbeitest, kann das gut sein.

Selbst mit Optimierung ist es nicht sonderlich schnell, da du ja
den Compiler anweist, dass er jeweils den Wert des Ports erst wieder
einlesen muss -- obwohl das für deine Funktion (Emulieren des SPI-
Taktes) gar nicht notwendig wäre.  Eine entrollte Schleife wäre
nach der Hardware-SPI (weiß der Geier, warum du die nicht gleich
genommen hast) die schnellste Lösung:
1
#include <avr/io.h>
2
3
void send_spi_clk(void)
4
{
5
  uint8_t v, nv;
6
  v = PORTC | (1 << 3);
7
  nv = v & ~(1 << 3);
8
  PORTC = v; PORTC = nv;
9
  PORTC = v; PORTC = nv;
10
  PORTC = v; PORTC = nv;
11
  PORTC = v; PORTC = nv;
12
  PORTC = v; PORTC = nv;
13
  PORTC = v; PORTC = nv;
14
  PORTC = v; PORTC = nv;
15
  PORTC = v; PORTC = nv;
16
}

Mit Optimierung macht der Compiler daraus:
1
.global send_spi_clk
2
        .type   send_spi_clk, @function
3
send_spi_clk:
4
/* prologue: frame size=0 */
5
/* prologue end (size=0) */
6
        in r24,53-0x20
7
        ori r24,lo8(8)
8
        mov r25,r24
9
        andi r25,lo8(-9)
10
        out 53-0x20,r24
11
        out 53-0x20,r25
12
        out 53-0x20,r24
13
        out 53-0x20,r25
14
        out 53-0x20,r24
15
        out 53-0x20,r25
16
        out 53-0x20,r24
17
        out 53-0x20,r25
18
        out 53-0x20,r24
19
        out 53-0x20,r25
20
        out 53-0x20,r24
21
        out 53-0x20,r25
22
        out 53-0x20,r24
23
        out 53-0x20,r25
24
        out 53-0x20,r24
25
        out 53-0x20,r25
26
/* epilogue: frame size=0 */
27
        ret
d. h. nach dem Setup der Werte wird mit f[CPU]/2 getaktet.

von (prx) A. K. (prx)


Lesenswert?

Johannes M. wrote:
> @A. K.:
> Warum sollte die "&="/"|="-Kombination schneller sein als ein EXOR?

Weil es dafür die cbi/sbi Befehle gibt. Für XOR gibt es keine 
Entsprechung, daher wird load/alu/store draus. Schonmal mindestens 1 
Takt mehr, evtl. 2.

Angenehmer wird es mit der neueren Generation, hier also Mega1284 oder 
so, denn da geht dann
  PINC = (1<<PINC3);
als Hardware-Toggle.

von Johannes M. (johnny-m)


Lesenswert?

A. K. wrote:
> Johannes M. wrote:
>> @A. K.:
>> Warum sollte die "&="/"|="-Kombination schneller sein als ein EXOR?
>
> Weil es dafür die cbi/sbi Befehle gibt.
Stimmt natürlich. Wenn der Compiler die dann auch benutzt....

von Rüdiger B. (fenris)


Lesenswert?

Oha, so schnell so viele Antworten, danke schonmal dafür. :)

Als erstes: da hab ich mich etwas unglücklich ausgedrückt natürlich. Ich 
habe mit dem Timer (im CTC Betrieb) experimentiert und da dann beim 
Compare Match getoggelt. Dabei fiel mir aus, dass das "langsamer" 
abläuft als ich eigentlich erwartet habe, und habe dann angefangen 
testweise im main zu toggeln. Die Geschichte mit dem Timer wird 
natürlich überhaupt nicht klar aus meinem obrigen Beitrag, sorry wenns 
Misverständnisse gab.

@A.K.
Deine erste Variante hab ich gerade selbst herausgefunden (stolzbin) 
;), die zweite werd ich mir auf jeden Fall mal merken.

@Jörg
Opimierung ist auf s gestellt, möglicherweise könnte man da vielleicht 
noch etwas rausholen. Aber ist ja eh nur zum generellen Verständnis 
erstmal.
Das mit der entrollten Schleife (lese ich so zum ersten mal) bringt mich 
eigentlich erst hierher. Ich habe ein Funktion in der der ADC 
angesprochen und ausgelesen werden soll. Das hatte ich in meinem ersten 
Eifer dann einfach mal so "runterprogrammiert", nach dem Motto:
Ports setzen, Takt simulieren mittels toggeln, Ports neu setzen, nochmal 
toggeln usw.
Das funktionierte dann nicht und ich fing an mir Gedanken zu machen, ob 
das eventuelle am Takt liegen könnte und ich in der Form vielleicht 
einfach zu schnell war, oder der Takt zu unsymmetrisch ist. Dann hab ich 
erst mit delay experimentiert, und letztendlich dann mit Timern, wo mir 
dann auffiel, dass ich wohl ganz und gar nicht zu schnell bin (der 
MAX1270 verträgt 0,1 ... 2 MHz als externen Takt) und der Fehler 
woanders liegen muss.
Aber dann denke ich mal, dass das Prinzip mit der entrollten Schleife 
wohl funktionieren sollte. Da teste ich nochmal etwas rum und melde mich 
dann ggf. morgen in einem anderen Thread. ;)

Vielen dank nochmal, Rüdiger

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.