Rundenzähler - Hardware-, Timer Interrupt oder...

Hallo,

in den kommenden Tagen werde ich mir nach über einem Jahr Interesse an dem Thema endlich einen Arduino zulegen. Ich habe vor mir einen Rundenzähler für meine Rennbahn zu bauen.

Ideen habe ich da viele, aber im Moment hänge ich schon beim Einlesen in die Programmierung, denn ich bin mir nicht sicher, was der beste Weg ist die Zeiten zu erfassen.

Nachdem was ich bisher gelesen haben gibt das grundsätzlich drei Wege:

  • Pollen der Eingänge in der Hauptschleife
  • Ein zeitgesteuerter Interrupt, der alle 1/1000 sec die Eingänge pollt
  • Hardware Interrupts (2), die man mit den Eingängen verbindet

Die dritte Lösung hätte den Nachteil, dass man maximal 2 Sensoren verwenden könnte, die aber dann super genau. Was würde denn passieren, wenn beide Interrupts genau gleichzeitig angestoßen würden?

Die zweite Lösung könnte dann mehrere Eingänge abfragen (ggf. auch über einen Multiplexer/Shiftregster), aber hier müßten dann die Impulse der Eingänge ggf. länger anstehen oder reicht hier eine einfache IR Lichtschranke, dass das Signal lang genug ansteht?

Die erste erscheint mir da am schlechtesten, aber hier lasse ich mich gerne vom Gegenteil überzeugen.

Im ersten Wurf sollen erstmal nur die Rundenzeiten gemessen und die Rundenzahl gezählt werden. Die Zahl der Runden und die Zeiten sollen über den seriellen Port an den PC gesendet werden. Später würde ich mir wünschen dass der Arduino auch in zwei Modi arbeiten kann:

  • autark
    Anzeige der Renndaten auf einem LCD und einstellen des Rennmodus (Rennzeit in Minuten oder maximale Anzahl an Runden) über zwei Taster
  • PC gesteuert
    Anzeige der Rundenzahlen, letzte Rundenzeit und schnellste Rundenzeit auf dem Display, alles andere soll der PC steuern

Nebenbei sollen noch 'n paar Gimmick, wie Startampel und schalten der Spannung der Trafos gesteuert werden.

Nun zu meiner Frage, wie sollte man die Sensoren der IR Lichtschranke am Besten abfragen, damit man das Projekt später einfach weiter ausbauen kann?

Timer Interrupt, Hardware Interrupt oder Loop()?

Gruß
KiWiX

du kannst doch mit millis() die zeit seit programmstart messen. damit hast du für alle ereignisse die selbe basis und kannst den rest einfach berechnen. ich würde einfach ir-fotodioden nutzen und diese an die digitalen eingänge anschliessen.
das ganze einfach über in den loop() abfragen.

Genau das waere auch mein erster Ansatz.. fuer so eine Rennbahn brauchst du ja auch kaum mehr als 1/1000s aufloesung, oder? Muss ja nicht immer extrem kompliziert sein ;0)

Was deine zwei spaeteren Modi angeht kann ich dir als blutiger Anfaenger zumindest versichern, dass die Ausgabe ueber das Keypad Shield von NUElectronics (oder auch bei Ebay zu bekommen) simep und gut ist - und man hat 5 Buttons gleich dazu ;0)

ich würde mir nen alten kassenrollen drucker besorgen und nach jeder runde die daten ausdrucken, imho sollte man die einfach seriell ansteuern können.
wäre ein nettes projekt.

den zum beispiel:

http://cgi.ebay.de/Bondrucker-STAR-DP8340-Kassendrucker_W0QQitemZ160309758016QQcmdZViewItemQQptZDrucker?hash=item160309758016&_trksid=p3286.c0.m14&_trkparms=72%3A1229|66%3A2|65%3A12|39%3A1|240%3A1318

Hallo,

danke schon mal für die Antworten, aber das mit der Loop() soll wirklich die Beste Lösung sein. Mmmh, da bin ich noch nicht so überzeugt, denn ich befürchte, wenn ich mal 'ne Taste drücke dann könnte mein Programm ja mit dem Menü beschäftigt sein und dann könnte mir ja eine Durchfahrt verlohren gehen. Oder wenn man das Display mit neuen Daten beschickt könnte auch ein Event verlohren gehen. Deshalb war mein Ansatz das mit einem Interrupt zu machen, ein "Event" abspeichert, dass dann in der Loop abgearbeitet werden kann, wenn Zeit dafür ist. Aber ich denke da geht jetzt wohl doch probieren über studieren.

@Nachtwind: Der Tipp mit dem Display ist wirklich gut, auch wenn da doch einige Ports gebraucht werden, aber dafür sind die Tasten wenigstens auf einem Analog Port. Das werde ich wohl einplanen und anschaffen, zumal das wirklich bezahlbar ist. Mal ausprobieren ob man mit 16x2 Zeichen sinnvoll 4 Spuren anzeigen kann (wobei am Anfang eh' nur zwei Spuren verwendet werden).

@bara.munchies: Das mit dem Drucker ist vielleicht ein nettes Projekt, aber steht im Gegensatz zu dem "Papierlosen Büro" :slight_smile: (auch wenn ich da nicht wirklich dran glaube)

Meine Idee wäre jetzt folgende: Planung für 4 Spuren a zwei Sensoren, macht also 8 Eingänge. Über eine "oder" Schaltung alle zusammen fassen und auf einen Interrupt legen. Wird dieser ausgelöst werden die Ports aus einem Multiplexer ausgelesen und wenn hier ein "Event" anliegt wird die Zeit in einer globalen Variablen bespeichert. So hätte man ein paar Eingänge mehr und könnte auf mehrere Interrupts verzichten und die Zeitnahme an sich wäre ziemlich genau (wahrscheinlich genauer wie man braucht)

Ich werde dann am Wochenende erst mal den Arduino bestellen und die Lichtschranken mit ein paar Taster simulieren udn dann sehe ich mal weiter.

Gruß
KiWiX

ich würds auch auf jeden fall mal probieren, wenn ich andere projekte sehe glaube ich kaum, dass der arduino bei deiner idee mit nem loop zu langsam ist.

Hallo zusammen,

also nach dem Tipp mit dem Keypad Shield bin ich stolzer Besitzer eines Freeduino und eben dem Keypad Shield.

Für mein Vorhaben habe ich jetzt mal ein kleines Testprogramm geschrieben, dass mit millis und delay Zeiten berechnet und dann auf dem LCD Display anzeigt. Mmh ja und wenn man das Ganze in einer kleinen for() Schleife macht, dann kann man leider ganz gut erkennen, dass die Ausgabe der Zeit über das LCD Display zwischen 11 und 34 Millisekunden benötigt und dass ist dann für eine Zeitmessung in einer loop() zu langsam.

Also werde ich wohl nicht um Interrupts herumkommen.

Gruß
KiWiX

Hallo,
nochmal ein kleiner Nachtrag:

Nachdem ich von der 4bit_mod library auf die LiquidCrystal umgestiegen bint (dort habe ich nur den _rw_pin für das KeyPad Shield besonders behandelt) haben sich die Zeiten zur Anzeige doch etwas von ca. 30ms auf 2ms reduziert :slight_smile:

Also Finger weg, von der 4bit_mod Library :slight_smile:

Gruß
KiWiX

Mmmmh, rege Anteilnahme, aber ich poste trotzdem mal munter weiter.

Also ich habe jetzt mit dem MsTimer2 und Hardware Interrupts gespielt und das Ergebnis war etwas ernüchternd. Werden Interrupts mit LiquidCrytstal und/oder Serial verwendet, dann geht das ordentlich in die Hosen, denn ich habe es immer wieder geschafft, den ATMEL und/oder mein Programm zum Abstürzen/Hängen zu bringen, das scheint nicht zu funktionieren. Ich hatte zwar gelesen, dass man in einem Interrupt kein delay() verwenden darf, aber das ein Interrupt während eines delay() die serielle Kommuniktaion oder die Displaysteuerrung zerhaut war mit neu.
Also muss ich wohl alles in der loop() abhandeln, somit ist zumindest eine Genauigkeit auf 'n tausendstel Sekunde nicht mehr machbar, simit sind drei Stellen hinterm Komma bei der Anzeige gelogen, also mache ich es wie Carrera und gebe die Genauigkeit und gehe auf 'ne Hunderstel zurück.
So jetzt werde ich erst mal meine Hauptschleife umstrukturieren.
Gruß
KiWiX

Hallo Kiwix,
ich schlage mich gerade mit ähnlichen Problemen rum. Ich habe ein Display mit SKPANG library und einem Schalter gemacht. Die Genauigkeit auf 1000stel ist natürlich gelogen wie Du schon richtig sagtest. Da wir die Einheit mal auf der Strasse verwenden wollen wäre auch die Aufzeichnung der Lufttemperatur nicht schlecht. Also habe ich einen DS1820 eingebunden und schon hing der Arduino in den Seilen. Zeitverzögerungen von 0,3Sekunden inklusive, da die Abfrage des DS1820 erste bei Lapcounterknopfbetätigung ausgeführt wurde.

Hast Du vielleicht in der Zwischenzeit einen Interrupts basierten Zeitnehmer hinbekommen?

Ich schlage mich gerade auch mit ähnlichen Problemen rum. Was mir dazu einfällt ist folgendes:

  1. Man kann den Interrupt auf alle Ports legen.
  2. Man kann in der IRQ Routine abfragen welcher Pin "schuld" war.

Damit ist schon mal die Sache mit dem "es gehen nur 2 Pins" vom Tisch und einem Multiplexer braucht man auch nicht.

Was die IRQs angeht: die gehen bei mir problemlos mit LCD und seriell. Du darfst nur nicht versuchen LCD oder serielle Schnittstelle in den IRQs selber anzusprechen. Dann haengt es sich bei mir auch immer sofort.

Und dann fällt mir noch ein: man kann sich auch den Quellcode der Arduino Libs anschauen, da geht einem doch gleich ein Licht auf. Vor allem sieht man dann auch was man modifizieren koennte um das Timing genauer zu machen. Soll heissen: die IRQs lösen zwar theoretisch sofort aus, aber in der Praxis kann es passieren, daß gerade IRQs gesperrt sind (weil z.B. der Timer gerade zuschlägt oder ein Delay läuft). Wenn man das abstellen will wird man wohl in den Arduino Libs rumpfuschen muessen. Habe ich nur noch nicht genau zu Ende analysiert, sonst könnte ich Dir schon sagen wie es geht :wink:

Hallo,

bin gerade erst aus 'm Urlaub.

@soulid:
Eine Zeitmessung auf Basis von Interrupts habe ich aufgegeben.

@Udo Klein:
Also lt. Doku kann man eine Interrupt Routine nur mit zwei (bzw. beim Mega auf sechs) Pins verwenden.
siehe: http://arduino.cc/en/Reference/AttachInterrupt
Also Deine möglich Lösung kann ich da erst mal nicht nachvollziehen.

Aber was ich festgestellt habe, ist dass man LCD und seriell eben nicht parallel verwenden kann, denn wenn ein Interrupt just in dem Moment ausgelöst wird, wenn z.B. das LCD oder die serielle Kommunikation ein Delay verwendet (also wenn man genau diese ein-zwei Millisekunden trifft), dann kommt der Arduino durcheinander.

Ich bin jetzt mal den Weg gegangen, die Pins in der Hauptschleife abzufragen und bei Änderungen zu verarbeiten. Damit hängt der maximale Messfehler an der längsten Zeit beim aktualisieren des LCD Displays und den entsprechenden Berechnungen. Das sind dann im Moment so zwischen 3 und 15ms.
Somit habe ich mich dazu entschlossen die Zeit auf dem Display und für die serielle Kommunikation mit max. Hunderstel zu verwenden. Das ist zwar manchmal immer noch zu ungenau, aber ich kann damit erst mal leben.

Allerdings pausiert das Projekt jetzt im Sommer und wird erst wieder im Herbst aufgenommen.

Gruß
KiWiX

Hi Kiwix,

in der Tat steht das so in der Arduino Referenz. Aber der Arduino ist ein Atmel 168 (oder was besseres) und da steht im Datenblatt in Kapitel 11:

The External Interrupts are triggered by the INT0 and INT1 pins or any of the PCINT23..0 pins.
Observe that, if enabled, the interrupts will trigger even if the INT0 and INT1 or PCINT23..0 pins
are configured as outputs. This feature provides a way of generating a software interrupt. The
pin change interrupt PCI2 will trigger if any enabled PCINT23..16 pin toggles. The pin change
interrupt PCI1 will trigger if any enabled PCINT14..8 pin toggles. The pin change interrupt PCI0
will trigger if any enabled PCINT7..0 pin toggles. The PCMSK2, PCMSK1 and PCMSK0 Registers
control which pins contribute to the pin change interrupts. Pin change interrupts on
PCINT23..0 are detected asynchronously. This implies that these interrupts can be used for
waking the part also from sleep modes other than Idle mode.

Danach wird beschrieben wie es weitergeht. Leider bin ich immer noch nicht dazugekommen das zu implementieren. Das was Du sagst bedeutet letztendlich nur, daß es eben direkt implementiert werden muss, ohne die Hilfe der Arduino Libraries.

Viele Grüße, Udo

Nachtrag: schau mal hier: Arduino Playground - PcInt

Mittlerweile bin ich dazugekommen alles auszuprobieren. PCINT geht mit jedem Pin.

Z.B. so:

// Pin to interrupt map:
// D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2
// D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0
// A0-A5 (D16-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1
//

void pcint_init() {

      pinMode(c_pcint_break - 2, INPUT); 
      digitalWrite(c_pcint_break - 2, HIGH); 

      PCattachInterrupt(c_pcint_break);      // this is responsible for the function call
}

volatile uint8_t *port_to_pcmask[] = {
  &PCMSK0,
  &PCMSK1,
  &PCMSK2
};


 void PCattachInterrupt(uint8_t pin) {
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *pcmask;

  // map pin to PCIR register
  if (port == NOT_A_PORT) {
    return;
  } 
  else {
    port -= 2;
    pcmask = port_to_pcmask[port];
  }
  
  // set the mask
  *pcmask |= bit;
  // enable the interrupt
  PCICR |= 0x01 << port;
}

void PCdetachInterrupt(uint8_t pin) {
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *pcmask;

  // map pin to PCIR register
  if (port == NOT_A_PORT) {
    return;
  } 
  else {
    port -= 2;
    pcmask = port_to_pcmask[port];
  }

  // disable the mask.
  *pcmask &= ~bit;
  // if that's the last one, disable the interrupt.
  if (*pcmask == 0) {
    PCICR &= ~(0x01 << port);
  }
}


static void PCint(uint8_t port) {


}


SIGNAL(PCINT0_vect) {
  PCint(0);
}
SIGNAL(PCINT1_vect) {
  PCint(1);
}
SIGNAL(PCINT2_vect) {
  PCint(2);
}

Den Teil wie man wieder auf die Pins kommt brauche ich nicht und habe ich weggelassen. Die Quelle die ich zitiert habe hat da aber einen kleinen Fehler. Für analog Pins stimmt die Zuordnung nicht.

Was den Timer angeht: den aus den Libraries habe ich abgeklemmt. Stattdessen habe ich einen im CTC Mode genommen. So:

ISR(TIMER2_COMPA_vect) {
    
    ++tick;

}


// the OCR Register is used to manipulate the counter resolution.
// In CTC mode the counter is cleared to zero when the counter
// value (TCNT2) matches the OCR2A. The OCR2A defines the 
// Prescaler = 64 --> OCR2A = 24 will result in 10kHz ticks
#define ISR_FREQ 24    

void dispatcher_init() {
    // setISRtimer

    TCCR2A = (1<<WGM21);                     // WGM22=0 + WGM21=1 + WGM20=0 = Mode2 (CTC)
    TCCR2B = (1<<CS22)|(0<<CS21)|(0<<CS20);  // set prescaler to /64 (250 kHz)
    TCNT2 = 0;                               //clear counter
    OCR2A = ISR_FREQ;                        // set TOP (divisor) - see #define

    // start ISR
    TIMSK2=(1<<OCIE2A);               
}

Der Timer läuft dann bei mir auf etwa 40ppm genau. Also so gut wie das Quarz. Allerdings nur solange ich in der ISR nicht zuviel Zeit verbrauche. Sobald ich >1600 Takte brauche geht der Timer nach.

Ganz wichtig: sobald der "normale" Timer abgeschaltet ist geht die Millisekunden Verzögerungsfunktion natürlich nicht mehr. Die Microsekundenfunktion muss auch gepatched werden, sie darf IRQs nicht abschalten. Die Änderung ist unkritisch weil die Funktion IRQs vor dem Rücksprung eh aktiviert und diese dann ja sofort ausgeführt werden --> IRQs sperren ist da also eh sinnlos.

Viele Grüße,
Udo