Timer 1, 3, 4 und 5 mal hergehört ....

Ich befasse mich grade damit, wie ich o.g. (16-Bit-) Timern auf meinem Mega mal ein wenig Kommunikationsfreundlichkeit mit mir beibiege - insbesondere wie man das Proggie schlank & übersichtlich hält. Wie das prinzipiell funktioniert, ist mir nach ewiger Leserei mittlerweile (halbwegs) klar. .... sind da doch div. Register wie z.B. TCCR1A und-B, TCNT1, TCCR1B, TIMSK1 u.v.m. und die zugehörige ISR z.B. TIMER1_OVF_vect je nach Anwendung entsprechend zu setzen.

Nun entspricht hier z.B. die "1" in o.g. Registern-Namen/ISR dem jeweiligem Timer. Im ersten Ansatz würde ich nun hingehen wollen und je zu setzendem Timer ein paar Funktionen (oder einer Funktion mit switch/case) zu spendieren, welche ich dann über einer Nummer ansprechen kann.

Warum das ? Wer meine Postings hier ein klein wenig mitverfolgt hat, wird gelesen haben, das ich an einer neuen Stepper-Lib arbeite, welche dynamsich eine Anzahl von Motoren steuern kann. Jedem initialisieretem Motor wird dabei auch ein Interrupt zugewiesen, der das Timing macht. In diesem Fall also max. 4 Motoren / Timer - das sollte für den armen Mega zur Auslastung auch reichen ....

@serenifly + michael_x: Meine dynamische Speicherverwaltung funzt mittlereile (immer noch) 1A ! Ich habe ein Array aus Strukturen mit Daten für jeweils einen Motor, welche ich im Programm z.B. mit Motor[x].Pin/Wert/Status anspreche. Jetzt hätte ich gerne dazu noch die o.g. Timer-Register/ISR dazu ! ... so das daraus werden würde: Motor[x].TCCRA, Motor[x].TCNT, Motor[x].TCCR, Motor[x].TIMSK etc.

C++ traue ich ja einiges zu - d.h. das geht bestimmt - aber wie ? Help please. :roll_eyes:

So genau ist mir das nicht klar. Willst du für jeden Motor ein Abbild aller nötigen Register? Willst du die Bits darin einzeln adressieren (setzten/löschen)?

Ein klares JEIN .... :. Nicht direkt ... (oder besser doch ?) Ich wünsche mir: - Motor]0[.TCNT = TCNT1 - Motor]1[.TCNT = TCNT3 - Motor]2[.TCNT = TCNT4 - Motor]3[.TCNT = TCNT5 Das müsste ich dann zwar auch einmalig im Setup oder sonstwo zuweisen, aber restliches Handling wäre wesentlich einfacher.

Sprich: eine Zuweisung wie TCNT1 = 34286; soll dann das gleiche sein wie Motor]0[.TCNT = 34286;

Ich rate jetzt mal: Motor]0[.*TCNT = *TCNT1 würde funktionieren ? Nein ? 8)

Nachtrag: Als Gimmick wäre dann noch genial: Wenn ich nur einen Motor habe (oder auch 2 oder nur 3), dass ich dann die Timer nach Wunsch zuweisen kann. Also z.B. nur Int3-5 auf die Motoren max 1-3 je nach "Laune" oder Erforderniss ....

Also ich habe das bei mir mal probiert. TCNTx, etc. sind nur defines (Hurrah für VisualStudio. Da sieht man das gleich :) ). Das heißt man kann die Werte beliebigen Variablen zuweisen.

das hier geht z.B:

byte tcnt1 = TCNT1;
Serial.println(tcnt, BIN);

Aber ich glaube wenn du das so willst wie du da beschreibst, musst du das irgendwie in Methoden kapseln.

Sowas vielleicht:

setTimer(byte val)
{
    TCNT1 = val;
}

Wie man das allgemein machen kann und die Timer austauscht, wüsste jetzt aber nicht

Hmmm - ich hab mal folgendes on the fly probiert:

int TCNTa, TCNTb, TCNTc, TCNTd = 0;
void PrintRegister()
{
  Serial.println((int) &TCNT1, HEX);
  Serial.println((int) &TCNT3, HEX);
  Serial.println((int) &TCNT4, HEX);
  Serial.println((int) &TCNT5, HEX);
  TCNTa = TCNT1;
  TCNTb = TCNT3;
  TCNTc = TCNT4;
  TCNTd = TCNT5;
  Serial.println((int) &TCNTa, HEX);
  Serial.println((int) &TCNTb, HEX);
  Serial.println((int) &TCNTc, HEX);
  Serial.println((int) &TCNTd, HEX);
}

Dabei kommt raus in HEX:

84 94 A4 124 31D 31F 321 323

So was wie: - Serial.println((int) *TCNT1, HEX); - *TCNTa = *TCNT1; - &TCNTa = &TCNT1; geht nicht ....

..aber: Irgendwo heute habe ich gelesen, das diese Register im 2-stelligen Hex-Bereich liegen. Also wären die ersten 4 Serial.prints als verwertbares Ergebnis schon recht gut ....

Nur wie bekomme ich die auf Motor]0[.TCNT ????

Wenn ich in VS auf TCNT1 gehe zeigt das als tooltip “#define TCNT1_SFR_MEM16(0x84)” an. Die Adresse stimmt also :slight_smile: SFR steht für “special function register”, was alle Register sind die spezielle Namen haben. 16 würde auch auf 2 Byte hinweisen. Aber wenn das höherwertige Byte 0 ist, zeigt er es nicht an.

*TCNTa = *TCNT1; <— geht nicht weil es kein Pointer ist. Du kannst nur einen Pointer dereferenzieren

&TCNTa = &TCNT1; <— Willst du wirklich die Adresse der Variable ändern?

TCNTa = &TCNT1 <— wenn überhaupt so. Dann kannst du TCNTa verschiedene Register-Adressen zuweisen

Jetzt hab ich mal geändert:

volatile uint16_t TCNTa; // ob volatile vor oder nach uint16_t ist wurscht

TCNTa = &TCNT1;

Das wird:
Serial_Test_2_ino:143: error: invalid conversion from ‘volatile uint16_t*’ to ‘uint16_t’
&TCNTa = &TCNT1; geht nicht.
Serial_Test_2_ino:143: error: lvalue required as left operand of assignment

Willst du wirklich die Adresse der Variable ändern?

Ja, so was in der Art - Wenn ich Motor]0[.TCNT was zuweisen/lesen will, soll das “so” sein, als wenn ich es mit TCNT1 mache.
Das muss doch irgendwie gehen …

Ich blicke hier auch nicht durch und probiere nur rum. Bzw. damit habe ich noch nichts gemacht :) Wäre mal nett zu wissen in welcher Datei diese defines gesetzt sind.

Dann so: volatile uint16_t* TCNTa = &TCNT1;

Das kompiliert. Dann müsstest du Serial.println(*TCNTa) machen

TERWI: Ja, so was in der Art - Wenn ich Motor]0[.TCNT was zuweisen/lesen will, soll das "so" sein, als wenn ich es mit TCNT1 mache. Das muss doch irgendwie gehen ....

Schon klar. Aber &TCNTa ist die Speicheradresse in der dieser Int steht. Du willst deren Inhalt ändern. Nicht wo diese Variable gespeichert ist.

"invalid conversion from 'volatile uint16_t*' to 'uint16_t'" heißt, das die Adresse die &TCNT1 liefert ein Pointer ist und wir das nicht einer int Variablen zuweisen können. Also machen wir unsere Ziel-Variable in einen Pointer und schon kompiliert es.

EDIT: Sorry, da stand ich jetzt auch auf dem Schlauch. Natürlich gibt der Referenz-Operator einen Pointer zurück. So macht das ja auch wenn man bei Methoden einen Pointer als Argument hat:

void function(int* val)
{}

int test = 123;
function(&test);

HA ! So geht's:

volatile uint16_t* TCNTa;
volatile uint16_t* TCNTb;
volatile uint16_t* TCNTc;
volatile uint16_t* TCNTd;

void PrintRegister()
{
  Serial.println("--- ORIGINAL ---");
  Serial.println((int) &TCNT1, HEX);
  Serial.println((int) &TCNT3, HEX);
  Serial.println((int) &TCNT4, HEX);
  Serial.println((int) &TCNT5, HEX);
  TCNTa = &TCNT1;
  TCNTb = &TCNT3;
  TCNTc = &TCNT4;
  TCNTd = &TCNT5;
  Serial.println("--- COPY ---");
  Serial.println((int) TCNTa, HEX);
  Serial.println((int) *TCNTb, HEX);
  Serial.println((int) &TCNTc, HEX);
  Serial.println((int) TCNTd, HEX);
}

Das wird dann:

--- ORIGINAL --- 84 94 A4 124 --- COPY --- 84 F1 33F 124

Mit * oder & davor kommt was falsche bei raus - s. copy 2. und 3. Wert. 1 und 4 passt. .....uuuuund CHECK ! :grin: (Boooaah watt ne Bastelei ....)

& musst du als “Adresse von” lesen. Wenn du das bei einer Variable machst dann gibt er dir die Speicherzelle aus in der die Variable steht. Deshalb ist &TCNTc hier sinnlos. &TNCT1 ist die Adresse von TNCT1. Das wird dann einem Pointer *TNCTa zugewiesen. TNCTa ist dann die Blanke Adresse. Und *TCNTa sollte eigentlich der Inhalt des TCNT1 Registers sein (das worauf der Pointer zeigt).

Was mich hier nur die ganze Zeit verwirrt hat war, dass man das bei defines eigentlich nicht machen kann. Jedenfalls steht es so überall. Da der Kompiler da nur die Werte nimmt und fest als Konstanten einträgt. Oder heißt das einfach dass TNCT1 den Wert des defines “#define TCNT1_SFR_MEM16(0x84)” hat?

Ne andere Sache:
Wie läuft das überhaupt mit den Registern im Hintergrund? Wenn das jetzt auf 0x84 zeigt, kann man dann auch sowas wie “0x84 = 123” machen und der schreibt das direkt auf das Register?

da bin ich gestern ganz drüber weg gekommen …
Und richtig: Wenn ich wie im letzten Beispiel mit *TCNTb anfrage, bekomme ich F1 zurück - das wird der Inhalt des Speicherbereichs sein.
TCNTa-d haben ja nun die gleiche Speicheradresse wie TCNT1-5.

Wie läuft das überhaupt mit den Registern im Hintergrund? Wenn das jetzt auf 0x84 zeigt, kann man dann auch sowas wie “0x84 = 123” machen und der schreibt das direkt auf das Register?

Ja, so sehe ich das bzw. gehe davon aus, das dem so ist. Das war ja auch das Ziel !
Gestern abend hatte ich da keinen Nerv mehr drauf und heute dauerts noch ne Weile.

TCNTx ist jeweils das Zählregister eines Timers. Wenn ich mit dem Overflow-Event arbeite, muss hier z.B. nach jedem Interrupt wieder die “Zählzeit” eingetragen werden.
Trage ich dann z.B. einfach einen kleineren Wert ein, wird die Interrupt länger, entsprechend kürzer wenn der Zähler höher steht. Es wird von Zählerstand nach 65535 zum Überlauf (integer-register) gezählt.
Das ermöglicht so eine total einfache Veränderung der Interruptzeit mit nur einem Befehl.
Logo hält sich das in bestimmten Grenzen - je nach dem wie der Vorteiler in TCCRxB gesetzt ist. Aber den kann man ja auch mal eben schnell umsetzen.

Das ganze hat den Sinn, das der Takt für einen Stepper ausschließlich vom Interrupt und seiner Wiederholgeschwindigkeit abhängig ist.
Zählen und “feuern” macht die Hardware für mich - so kann man sich (verbotene) Delays oder das abzählen / vergleichen von mircoseconds sparen.
Das spart Zeit - in einer ISR (je nach Taktgeschw.) darf man eh nicht so viel Rechenzeit verballern…

Hier wird ja oft diese kleine Beispiel mit “Blink without delay” zitiert, um das bremsende delay zu sparen.
Das geht noch schöner mit “Blink by interrupt”:
Stammt von http://www.letmakerobots.com/node/28278 - created by RobotFreak aus “Arduino101-Timers.zip”

void setup() 
{
  // initialize timer1 
  pinMode(ledPin, OUTPUT);
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 34286;            // preload timer 65536-16MHz/256/2Hz
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_OVF_vect)        // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  TCNT1 = 34286;            // preload timer
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}

void loop() 
{
}

Wer mal ein bischen spielen möchte, erweitere das Proggie um eine verstellbare int-var mit dem Wert zwischen 1 und (max. sinnvoll) 64000.