Taktmerker für Arduino

Hi,

in der Steuerungstechnik gibt es so was wie Taktmerker. Das sind boolsche Einheiten, die in einem bestimmten Zyklus den Zustand wechseln. z.B. 1Hz; 0,5Hz oder ähnlich. Bei S7 z.B. gibt es in der Firmware ein Byte, in dem 8 verschiedene Systemtakte fest verankert sind. Ich finde das sehr nützlich, wenn man z.B. eine LED blinken lassen will. Dann muss man nicht mühsam die LED ein und Ausschalten, sondern kann den Ausgang der LED einfach mit dem gewünschten Taktmerker ver-&-en.

Jetzt die Frage: Ist schon mal jemanden so etwas ähnliches für den Arduino über den Weg gelaufen? (Ja, ich weiss: Die Profis bauen sich so was selber, aber dazu zähle ich mich nicht ;)

Gruß/hk007

hk007: Jetzt die Frage: Ist schon mal jemanden so etwas ähnliches für den Arduino über den Weg gelaufen?

In diesem Posting habe ich mal jemandem einen konfigurierbaren "Multi-Timer" gepostet: http://forum.arduino.cc/index.php?topic=229814.msg1658763#msg1658763

Da sind allerdings 10 Zähler realisiert, die mit unterschiedlichem Takt hochzählen.

Könnte man natürlich leicht ändern, so dass statt 10 hochzählenden Zählern 10 boolsche Variablen getaktet werden.

Ja die Taktmerker der CPU sind mir bekannt ;)

Lassen sich recht einfach, realisieren.

(PseudoCode) boolean cycle_05hz boolean cycle_1hz ...

if(millis()%500 == 0) cycle_05hz ^= 1; if(millis()%1000 == 0) cycle_1hz ^=1;

Erfordert aber einen schnellen Durchlauf der loop. Da ich nicht die Arduino Cores nutze habe ich mir für den Timer die millis selber erstellt und lasse auch während dieser ISR die Werte errechnen.

@jurs Das schau ich mir mal in einer ruhigen Minute an.

@sschultewolter Ohhh....schön einfach...das gefällt mir ;D Füe das Blinken einer LED sollte deine Einschränkung kein Problem sein. Delay() kommt mir nicht in die loop.

sschultewolter: Da ich nicht die Arduino Cores nutze habe ich mir für den Timer die millis selber erstellt und lasse auch während dieser ISR die Werte errechnen.

Hört sich interessant an. Rückst du da nähere Infos dazu raus :cold_sweat:

Der avr-gcc Code ist für den atmega1284p. Weiß gerade nicht, ob der kompatibel ist zu millis, wenn die gleichen Timer genutzt werden. Kann über die WH Tage mal nachschauen.

Habe meine Snippets gerade nicht zur Verfügung. Lehnt aber an folgendes an
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Die_Timer_und_Zähler_des_AVR#CTC_Clear_Timer_on_Compare_Match_.28Auto_Reload.29

Werte müssen natürlich an deinen Quarz/Resonator angepasst werden. Z.B. müsste das soweit ich noch weiß
TCCR0B |= (1<<CS01); // Prescaler 8

TCCR0B |= (1<<CS01) | (1<<CS00); // Prescaler 64
sein

In der ISR (TIMER0_COMPA_vect) findet das wie oben statt. Ggf. natürlich optimierbar, wenn es sich um vielfache eines anderen Taktgebern halten.

sschultewolter: if(millis()%500 == 0) cycle_05hz ^= 1; if(millis()%1000 == 0) cycle_1hz ^=1;

Habs mal getestet. So einfach gehts wohl doch nicht... Hat schon noch große Einschränkungen: - Wenn die loop zu schnell ist, dann invertiert er das cycle_xx zweimal (oder öfter) :'( - Wenn er nicht genau trifft millis()= 499 -> millis()=501 dann wirds auch nix :'(

hi,

ich hab’ das mal gemacht, um 25mal in der sekunde zu aktualisieren:

mill_akt = millis() % 40;
if (mill_akt < mill_alt) {
tu was…
}
mill_alt = mill_akt;

gruß stefan

Hi,

hab mal etwas rumgerechnet, mit deinem (sschultewolter) Vorschlag im Hinterkopf
Dann kam das dabei raus :slight_smile:

boolean takt_05hz;
boolean takt_1hz;

void loop(){
takt_05hz = (long (millis() / 500)) %2;
takt_1hz = (long (millis() / 1000)) %2; 
...
}

Gedankengang:
Ich teile die millis durch die Periodendauer in ms. Durch das Wandeln auf long (abrunden) kommen dann ganze Zahlen für die Dauer einer Periode raus. Diese mit modulo 2 ergibt eine wechselnde 0<->1

Beispiel Takt 0,5Hz:
millis() = 0 - 499 → 0 → “0” (LOW)
millis() = 500 - 999 → 1 → “1” (HIGH)
millis() = 1000 -1499 → 2 → “0” (LOW)
millis() = 1500 -1999 → 3 → “1” (HIGH)

Sehe momentan kein Problem damit.

Einwände?


Edit: Wobei 500ms high und 500ms low → 2Hz sind, oder?

500ms high und 500ms low sind bei mir 1 Hz. Jede Sekunde ist einmal Flankenwechsel High->Low und einmal Low-> High

michael_x: 500ms high und 500ms low sind bei mir 1 Hz. Jede Sekunde ist einmal Flankenwechsel High->Low und einmal Low-> High

Oh oh, da hat er wohl recht. Hab zuviel Energie auf die Berechnung gelegt, da hab ich Elementares aus den Augen verloren. Einigen wir uns auf das?

takt_2hz = (long (millis() / 250)) %2;
takt_1hz = (long (millis() / 500)) %2;
takt_05hz = (long (millis() / 1000)) %2;

Die Modulo Funktion sollte doch eigentlich ausreichen, wenn es innerhalb der ISR läuft. Im loop muss man halt dann drauf achten, dass diese wirklich mindestens jede ms durchlaufen wird.

mill_akt = millis() % 40;
if (mill_akt < mill_alt) {
tu was…
}
mill_alt = mill_akt;

Ohne nachzuschauen vermute ich ein Code Snippet aus der unvollendeten Equniox :wink:

hi,

gutes gedächtnis, der mann. gutes gedächtnis...

gruß stefan

boolean takt_1hz = (millis() / 500) & 1;  // sollte mindestens genauso gut gehen

… und nicht drauf vertrauen, dass der Compiler %2 bestimmt optimiert.

Das kann beliebig selten oder oft gerechnet werden. (Na ja, so häuftig wie es gebraucht wird, eben)
Stellt also keine Anforderungen an die loop - Zykluszeiten.

hi,

habt Ihr bei Euren lösungen nicht 2 probleme?

  1. was passiert. wenn die loop länger dauert als 1 millisekunde?
  2. was passiert, wenn die loop schneller ist als eine halbe millisekunde?

gruß stefan

Eisebaer: hi,

habt Ihr bei Euren lösungen nicht 2 probleme?

  1. was passiert. wenn die loop länger dauert als 1 millisekunde?
  2. was passiert, wenn die loop schneller ist als eine halbe millisekunde?

gruß stefan

Ich mit meiner Lösung nicht, weil es ein Blinktakt sein soll, und wenn ich eine Millisekunde später oder eher umschalte, dann erkennt das kein Mensch.

@michael_x

... und nicht drauf vertrauen, dass der Compiler %2 bestimmt optimiert.

Das versteh ich inhaltlich nicht.

Und deine Lösung "boolean takt_1hz = (millis() / 500) & 1;" hab ich auch noch nicht so richtig durchblickt.

% 2 ist eine Division und braucht entsprechend lange. Der AVR hat keine Assembler OpCodes für Division und Multiplikation! (anders als z.B. der 8051/52 und selbst da dauert das ganze 4 Zyklen).

& 1 ist einfach ein UND und kann damit direkt und kurz in Assembler ausgeführt werden

Man sollte zwar annehmen, dass der Compiler optimiert, aber das ist nicht unbedingt der Fall. Wenn man dagegen UND mit 1 macht kann man sicher sein dass es schnell geht. Da kommt 1 raus wenn das niederwertigste Bit 1 ist, und 0 wenn es 0 ist. Also ein einfacher Test auf gerade/ungerade

Serenifly: Da kommt 1 raus wenn das niederwertigste Bit 1 ist, und 0 wenn es 0 ist. Also ein einfacher Test auf gerade/ungerade

Oh na klaaaaar, wo du es sagst. War mir nur befremdlich, dass man das Ergebnis einer Division mit einer Bool verwurschtelt.

Ich habs mal ohne meine Wandlung auf long probiert. Geht auch. Heißt dass, wenn man eine long durch eine int teilt, dann kommt keine float raus, sondern wieder eine Ganzzahl und wird damit automatisch abgerundet?

Bevor ich es vergesse: Allen ein frohes Weihnachtsfest!!!!!

Heißt dass, wenn man eine long durch eine int teilt, dann kommt keine float raus, sondern wieder eine Ganzzahl und wird damit automatisch abgerundet?

Ja. Die Nachkomma-Stellen werden abgeschnitten. Das ist auch in anderen Sprachen so die mit C verwandt sind.

Python ist da interessant. Da gibt es / für Gleitkomma-Divisionen und // für Ganzzahl-Divisionen (wobei nicht abgeschnitten wird sondern gegen negativ unendlich gerundet wird). Zumindest ab 3.0. Frühe Versionen verhielten sich wie C.

Ich hätte da eher einen Typkonflikt des Compilers erwartet.

C ist nur schwach typisiert. Das macht sehr viele Konversionen implizit.