High-Speed capture mit ATmega Timer

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

von Michael Dreher

Dieser Artikel nimmt am Artikelwettbewerb 2012/2013 teil.

Verarbeitung einer Messung in LibreOffice

Die Timer der ATmega Mikrocontrollerreihe bieten Unterstützung für vielfältige Aufgaben wie PWM, zyklische IRQ Aufrufe oder als Zeitbasis für Messungen. In diesem Artikel geht es darum, diese Timer für hochauflösende Zeitmessungen bei Signalaufzeichnungen zu verwenden wie sie bei der Analyse von Binärsignalen benötigt werden. Es kommt der sogenannte Input Capture Mode (ICP) des Timers zum Einsatz, bei dem ein Timer seinen Wert bei einem Flankenwechsel eines externen Signals zwischenspeichert.

Der Focus des Artikels liegt auf der Lösung der bei der Umsetzung auftretenden Probleme und weniger auf der theoretischen Betrachtung der Timer.

Der Begriff "Highspeed" ist natürlich relativ zur Taktfrequenz des ATmega zu sehen. Bei dem hier realisiertem Projekt werden bis zu 3600 Flankenwechsel eines digitalen Eingangssignal mit einer Auflösung von 62,5 ns gespeichert. Die Zeitspanne zwischen zwei Flanken muss zwischen 3 µs und 65 ms liegen. Der obere Grenzwert kann angepasst werden.

Unterschied zwischen Messung und Protokollerkennung

Wenn das Protokoll und damit der grobe Verlauf eines externen Signals bereits bekannt ist, kann man sich dessen Eigenschaften zu nutze machen und die Hard- und Software bestmöglich darauf abstimmen. Als Beispiel sei ein IR-Empfänger für Signale von IR-Fernbedienungen genannt.

Fernbedienungen modulieren ihr Ausgangssignal typischerweise mit Frequenzen zwischen 36 und 40 kHz. Es gibt Bauelemente (TSOP1736, 38, 40) um diese Modulation beim Empfang für eine bessere Signalempfindlichkeit auszunutzen (Fremdlichtunterdrückung) und die Modulation rückgängig zu machen um nur das Nutzsignal am Ausgang zu erhalten.

Der Mikrocontroller bekommt vom TSOP17xx ein bereits aufbereitetes Signal, welches Pulsbreiten von mindestens 200 µs enthält (ein kompletter Burst aus z.B. 10 einzelnen Perioden), was gegenüber einer Einzel-Pulsbreite von 6 µs bei einem modulierten Signal sehr viel einfacher zu verarbeiten ist.

Hier ein Vergleich der Signale von SDP8600 (Channel 0) mit TSOP1738 (Channel 1, Ausgang negiert): HighSpeedCaptureAtmegaTimer comarison TSOP1738.png

Bei der Analyse und Vermessung von unbekannten Signalen wird daher eine sehr viel schnellere und exaktere Erfassung benötigt als bei der Verarbeitung von bekannten Signalen.

Als Untersuchungsobjekt wurde ein IR-Signal gewählt, in der Praxis kann aber jedes beliebige Signal vermessen werden, sofern es sich an die Eingangs genannten zeitlichen Randbedingungen hält. Der Focus der IR-Messung liegt auf der Bestimmung der Modulationsfrequenz und des Puls/Pause Verhältnisses.

Es geht explizit nicht darum ein bestimmtes IR Protokoll (wie z.B. RC5) zu implementieren. Die Erfassung soll möglichst exakt und möglichst schnell sein um auch hochfrequente Signale verarbeiten zu können.

Der Aufbau kann auch dazu dienen eine bestehende Implementierung eines IR-Senders zu überprüfen, quasi als einfacher Ein-Kanal „Logik Analyzer“.

Hardwareaufbau

Aufbau mit Arduino Leonardo

Der Hardwareaufbau des IR-Analysators ist denkbar einfach:

  • Ein ATmega-Board. Getestete Arduino Boards siehe Tabelle unten.
  • Ein Honeywell SDP8600 Infrarot Optoschmitt Detektor (wichtig: im Gegensatz zu TSOP17xx ohne eingebauten Demodulator)
  • Ein Stützkondensator von 0,1 µF parallel zur Spannungsversorgung des SPD8600

Der SDP8600 wird an GND, +5V und Pin ICP1 des ATmega angeschlossen, da der 16-Bit Timer1 verwendet wird.

Beim Arduino Mega 2560 Board sind die ICP Pins ICP1 und ICP3 nicht nach außen geführt, daher muss auf ICP4 oder ICP5 ausgewichen werden.

Zum Debuggen habe ich einen Ausgangs-Pin des ATmega an einen Logic Analyzer angeschlossen. Er wird vom Programm gesetzt, wenn ein Flankenwechsel erkannt wird und zurückgesetzt, wenn das Abspeichern des Wertes beendet ist. Diesen Pin bezeichne ich im weiteren als 'Dbg Pin'.

Verwendete Pins und Arduino Digital Pin Nummern
MCU Arduino Board ICP Pin Dbg Pin Anzahl speicherbarer Werte Arduino Pin Mapping Tabelle
ATmega328P, ATmega168 Duemilanove, Uno ICP1 / PB0
Arduino Pin 8
PD6
Arduino Pin 6
1080 PinMapping168
ATmega32U4 Leonardo ICP1 / PD4
Arduino Pin 4
PD6
Arduino Pin 12
1500 PinMapping32u4
ATmega2560 Mega 2560 ICP4 / PL0
Arduino Pin 49
PL6
Arduino Pin 43
3600 PinMapping2560

Software

Auflösung und Wertebereich der Messungen

Für die Zeitmessung wird der 16-Bit Timer1 verwendet. Dieser hat eine Auflösung von bis zu 1/F_CPU. Beim Arduino entspricht dies 1/16 MHz = 62,5 ns. Für den maximalen Timer-Wert 65535 ergibt dies eine Zeitspanne von 4,096 ms. Längere Zeiträume können bei dieser Auflösung nicht direkt mit dem Hardware-Timer gemessen werden. Für einige Anwendungsfälle ist dieser Zeitraum zu kurz.

Es gibt mehrere Möglichkeiten den Zeitraum zu erweitern:

  • Timer Prescaler (Vorteiler) für das Taktsignal des Timers verwenden. Über die Prescaler Bits CS12 bis CS10 in Register TCCR1B kann man das System-Taksignal der CPU um folgende Faktoren runterteilen: 1, 8, 64, 256, 1024
  • Software-Überlaufbehandlung des Timers

Der Nachteil beim Einsatz des Prescalers ist, dass sich die Auflösung dadurch verringert. Um Zeiträume von bis zu 65 ms erfassen zu können, müsste ein Prescaler-Wert von 64 verwendet werden. Dadurch würde die Auflösung von 62,5 ns auf 4 µs sinken. Damit könnte der genannte Zweck, Zeiten in der Größenordnung von 8 µs mit einer Genauigkeit von +-2% genau zu vermessen um die Modulationsfrequenz zu bestimmen, nicht erreicht werden. Aus diesem Grund verwendet dieses Projekt die Überlaufbehandlung.

Die Überlaufbehandlung inkrementiert einen Software-Zähler, wenn der Wertebereich des Timers überschritten wird. Damit können beide Ziele erreicht werden: eine hochauflösende Messung mit einem großen Messbereich.

Vergleich Logic Analyzer Messung mit ATmega Messung

Nachfolgende Grafik zeigt die Erfassung zweier Pulse, mit einer ON-Zeit von 4,5 µs und einer OFF-Zeit von 21,6 µs. Die Messung wurde parallel mit dem ATmega und einem Logic-Analyzer durchgeführt und die Kurven übereinandergelegt. Die orangene Linie 'LA' zeigt die Logic-Analyzer Messung, die schwarze Linie 'AVR' die ATmega Messung. Die Zeitskala ist in Sekunden, die Grafik zeigt einen Zeitraum von 40 µs. Wie man sieht, weichen die beiden Kurven kaum voneinander ab: HighSpeedCaptureAtmegaTimer 3Pulse.png

Die blaue 'Dbg' Kurve zeigt die Rückmeldung vom ATmega Programm an den Logic Analyzer. Hier kann man die Reaktionszeit (0,79 µs) und die Programmlaufzeit für die Bearbeitung der Flanken (2,38 µs) ablesen. Durch den aktivierten noise canceler (Bit ICNC1 in TCCR1B) ist diese Kurve gegenüber den anderen beiden um 4 CPU Zyklen (0,25 µs) nach rechts verschoben.

Warten auf Flankenwechsel mit ISR

Als erster Ansatz wurde eine ISR (Interrupt Service Routine) verwendet, welche bei jedem Flankenwechsel getriggert wurde. Dies war zu langsam um mit dem Signal schritthalten zu können und wurde daher verworfen.

Der System Overhead beim Aufruf einer ISR in C ist recht hoch, da Register gesichert und wieder restauriert werden müssen (5 Register, insgesamt ca. 13 Zyklen, siehe [diesen Artikel]). Hinzu kommen noch 4 Zyklen, bis zur Ausführung der ersten Instruction der ISR, außerdem können andere IRQs (z.B. der Arduino Timer0 IRQ oder USB IRQs vom ATmega32U4) ins Gehege kommen und die Erfassung ausbremsen.

Für die Speicherung von Zuständen müssen globale Variablen verwendet werden, welche dann nicht in Registern gehalten werden können und jedes mal neu geladen werden. Man kann zwar auch globale Variablen in Register legen, diese stehen dann aber im restlichen Programm nicht mehr zur freien Verfügung.

Für die Variablen auf die von der ISR und vom normalen Programm zugegriffen wird, muss der type qualifier “volatile“ verwendet werden, was dem Compiler einiger Optimierungsmöglichkeiten beraubt und den Code unter Umständen nochmal deutlich langsamer macht.

Warten auf Flankenwechsel mit Polling

Die schnellere Variante der Flankenabfrage ist polling des Timer Flag Registers. Der grobe Ablauf des Programms ist sehr einfach. Hier der Pseude-Code für die komplette Erfassungs-Schleife: <c> IRQs deaktivieren While(Ende nicht erreicht) { Warten auf einen Flankenwechsel (Bit ICF1 in TIFR1) Auslesen des Input Capture Wertes (ICR1) Speichern der Zeitdifferenz zum letzten Ereignis in einem Array Trigger für Timer invertieren (TCCR1B ^= _BV(ICES1)) } IRQs erlauben </c>

Die Schleife für das Warten auf den Flankenwechsel besteht aus nur 3 Assembler Instructions, was deutlich kürzer und schneller ist als die ISR Variante.

Der C-Code <c> uint8_t tifr; while(! (tifr = (TIFR1 & (_BV(ICF1) | _BV(OCF1A))))) { } </c>

Wird vom avr-gcc übersetzt zu <avrasm> loop: in r18, TIFR1 andi r18, _BV(ICF1) | _BV(OCF1A) breq loop </avrasm>

In dieser Schleife passiert folgendes:

  1. Es wird abgefragt, ob ein Ereignis aufgetreten ist, für welches ein Timer-Wert durch den Input Capture gespeichert wurde (TIFR1 & (_BV(ICF1))
  2. Es wird abgefragt, ob der Output Compare erkannt hat, dass ein Differenz-Überlauf passiert ist (TIFR1 & (_BV(OCF1A)), Details siehe Abschnitt Keine Angst vor Überläufen
  3. Der Wert der UND Verknüpfung wird gespeichert um ihn weiter unten weiterzuverwenden und TIFR nicht erneut auslesen zu müssen.

Der im Arduino Leonardo verbaute ATmega32U4 verwendet keinen externen USB-seriell Wandler sondern implementiert direkt das USB Protokoll. Reagiert ein USB Device einige Zeit nicht auf die Anfragen des USB Hosts, wird es abgekoppelt. Dies passiert z.B. wenn man die IRQ Bearbeitung für einige Zeit abschaltet wie es in diesem Projekt gemacht wird. Ein Workaround ist, die IRQs nur so kurz wie möglich zu sperren, d.h. erst nachdem die erste Flanke erkannt wurde. Eine stabile Lösung ist dies aber nicht, daher ist für diese Anwendung der ATmega32U4 nicht zu empfehlen.

Zeitmessung

Um die Zeitdifferenz zwischen zwei Ereignissen zu messen gibt es mehrere Möglichkeiten:

  1. Nach jedem Ereignis den Timer auf 0 zurücksetzen
  2. Den Timer weiterlaufen lassen und immer die Differenz zum letzten Timer-Wert zu bilden.

Die erste Variante hört sich erst einmal verlockend einfach an. Zwischen dem Erkennen einer Flanke und dem Zurücksetzen des Timers vergehen aber einige µs. Der nächste Timer Messwert wäre genau um diesen Versatz zu klein. Sofern dieser Versatz bekannt ist, kann man ihn herausrechnen, bei einem Test lag er bei mir bei 30 CPU Zyklen. Mit jeder Programm- oder Compileränderungen muss dieser Versatz aber neu bestimmt werden.

Die zweite Variante ist eleganter, da sie den Timer durchlaufen lässt und die Differenz zwischen zwei Timer-Werten bildet. Ein Versatz wird dadurch komplett vermieden.

Keine Angst vor Überläufen

Es gibt unterschiedliche Überläufe zu betrachten:

  1. Überlauf des Timer Wertes TCNT1. Dies ist kein Problem und wird durch die vorzeichenlose Modulo 2^16 Berechnung der Differenz kompensiert, Rechenbeispiel siehe unten
  2. Überlauf der Differenz zwischen zwei Zeitpunkten, d.h. wenn die Differenz zwischen zwei Flanken größer als 65535 ist (Zeitspanne größer als 4,096 ms). Diese Überlaufe müssen mitgezählt werden (in ovlCnt) und als obere Bits der Zeitdifferenz gespeichert werden.
Beispiele für erfasste Timer-Werte
Zeitpunkt Signal CLKs Timer1 T(n) – T(n-1) ovlCnt Timer1 (in HEX)
T5 0->1 10000 10000 0x2710
T6 1->0 43600 43600 33600 0 0xAA50
T7 0->1 206444 9836 162844 2 0x266C

Die Differenz zwischen T(7) und T(6) muss über die Differenz von Timer1 und ovlCnt bestimmt werden:

T(7) - T(6) = (9836 - 43600) MOD 2^16 + 2^16 * 2 (ovlCnt) = 31772 + 2^16 * 2 = 162844

Die Differenz-Berechnung erfolgt Modulo 2^16, da der Datentyp uint16_t verwendet wird. Diese Art der Berechnung ist etwas gewöhnungsbedürftig, daher eine genauere Ausführung in hexadezimaler Schreibweise:

Timer1(7) - Timer1(6) = 0x266C – 0xAA50 = 0xFFFF7C1C (als uint32_t) = 0x7C1C (als uint16_t) = 31772

Um den Differenzwerte größer als 65535 zu verarbeiten, muss man die Überläufe der Timer-Differenz mitzählen, was in der Variable ovlCnt passiert. Bei der Differenz zwischen T(7) und T(6) von 162844, wurde der Wert 43600 bei T(6) zwischendrin noch zweimal erreicht, d.h. die Differenz ist um 2*2^16 größer:

t_diff(7,6) = (0x266C – 0xAA50) + 2*0x10000 = 0x27C1C = 162844

Mehr zur Theorie der Modulo Arithmetik finden bei den Links unter Siehe auch

Hier ein Bild, welches die Zeitpunkte und den Verlauf von Timer1 darstellt: HighSpeedCaptureAtmegaTimer Overflows.png

'Capture' bezeichnet die Erfassungszeitpunkte T(5) bis T(7), bei welchen Timer1 aufgrund der Flanke von 'Signal' den aktuellen Wert speichert.

Damit man nicht „von Hand“ vergleichen muß, ob der Wert 43600 zwischen zwei Flanken nochmal vorkam (die Zeitpunkte mit 'ovlCnt++' in der Grafik markiert sind), kann man den Output Compare Wert setzen und dies die Hardware des ATmega machen lassen. Wenn der Timer diesen Wert erreicht, wird das Output Compare Flag OCF1A im Timer Flag Register TIFR1 gesetzt und daraufhin die Variable ovlCnt erhöht.

Speicherung der ermittelten Werte

Die Zeit-Differenzwerte werden in einem Array gespeichert. Das für die Wertspeicherung verfügbare RAM hängt davon ab, wie viel Speicher für andere Zwecke benötigt wird (Stack, globale Variablen). Bei Arduino 1.0.3 mit ATmega328P (2048 Byte RAM) bleibt Platz für ca. 870 16-Bit Werte, mit dem ATmega32U4 kommt man auf ca. 1050. Die Puffergröße im Programm ist relativ zur Arbeitsspeichergröße RAMEND festgelegt, d.h. bei größerem Speicher erweitert sich automatisch der Puffer. Der vom Programm belegte Speicher wird durch projectRamUsage festgelegt: <c> const uint16_t projectRamUsage = 380 + 64; // das absolute Minimum ist 240 + 64 const uint16_t bufSize ((RAMEND - 0x100 - projectRamUsage) / sizeof(uint16_t)); </c>

Wenn das Programm erweitert wird, oder in einer anderen Umgebung eingesetzt wird, muss die Konstante projectRamUsage angepasst werden, da es sonst abstürzt.

Bei einer Auflösung von 62,5 ns können in einem uint16_t Wert maximal Zeiten von 4 ms gespeichert werden. Um den Messbereich zu erweitern greift man zu einem Trick. Bei kurzen Zeitspannen ist die absolute zeitliche Auflösung sehr wichtig, damit die prozentuale Ungenauigkeit nicht zu groß wird. Um bei langen Zeitspannen dieselbe prozentuale Genauigkeit zu erhalten, benötigt man eine geringere absolute zeitliche Auflösung. Dies ist vergleichbar mit der Messbereichsumschaltung bei einem Digitalmultimeter.

Dies könnte man dadurch erreichen, dass man Fließkommazahlen verwendet, welche durch die getrennte Verarbeitung des Exponenten eine automatische Messbereichsumschaltung eingebaut haben. Diese haben aber den Nachteil, dass die Verarbeitung ohne Hardwareunterstützung auf einem Mikrocontroller sehr langsam ist und sie viel Platz benötigten (4 Byte für einfache oder sogar 8 Byte für doppelte Genauigkeit pro Wert).

Eine Alternative ist eine spezielle Codierung, des 16-Bit Wertes. Das höchste Bit wird als Umschalter zwischen zwei Messbereichen verwendet:

  • Messbereich 1: Wenn Bit 2^15=0 ist, dann enthält der Wert die Differenz-Zeit ohne weitere Skalierung. Damit sind alle Werte zwischen 0 und 32767 ohne Lücken darstellbar (zeitliche Auflösung 62,5 ns, maximal 2,048 ms).
  • Messbereich 2: Wenn Bit 2^15 den Wert 1 hat, enthält die Zahl die Differenz-Zeit mit einer zeitlichen Auflösung von 2 µs. Der Wertebereich und die zeitliche Auflösung sind also jeweils um den Faktor 32 verschoben. Dadurch sind Zeiten bis zu 65,535 ms darstellbar.

Der Verschiebungsfaktor zwischen den beiden Messbereichen kann über die Konstante RANGE_EXTENSION_BITS (Standardwert: 4 Bits) geändert werden. Es gilt:

Verschiebungsfaktor = 2^(RANGE_EXTENSION_BITS + 1)

Um die Zahlen schneller verarbeiten zu können und weniger Schiebebefehle bei der Ablage zu benötigen werden im Messbereich 2 die Bits 2^16 bis 2^19 (der Überlauf-Zähler) in den unteren 4 Bits abgelegt und erst beim Auslesen in die korrekte Position geschoben, siehe Funktion packTimeVal(). Beim Auslesen der Daten ist dann eine Dekodierung notwendig. Dies erfolgt außerhalb der zeitkritischen Erfassungs-Schleife in der Funktion unpackTimeVal().

Ende der Erfassung

Für den Abbruch der Erfassung gibt es zwei Kriterien:

  1. Der Puffer für die Speicherung der Werte ist voll
  2. Die maximal darstellbare Zeit wurde überschritten (entspricht einem Timeout, d.h. kein Ereignis für eine bestimmte Zeit). Dies passiert, wenn 65 ms lang keine Flanke erkannt wird.

Am Anfang der Erfassung wird endlos auf eine low->high Flanke gewartet, da man sonst nach dem Start der Erfassung innerhalb von 65 ms das zu analysierende Signal anlegen müsste.

Ausgabe der Daten

Die erfassten Werte in drei Formaten über die serielle Schnittstelle ausgegeben.

1. In einem kompakten Format, bei welchem die einzelnen High- und Low- Zeiten durch + und - markiert sind, die Werte sind in ns:<c> capture[1056 values]= +9625 -16625 +9625 -16625 +9687 -16562 +9687 -16625 +9625 -16625 +9625 -16625 +9687 -16625 +9625 -16625 +9625 -16625 +9687 -799562 +9625 -16625 +9625 -16625 +9687 -16562 +9687 -16625 +9625 -16625 +9625 -16625 +9687 -16625 +9625 -16625 +9625 -16625 +9687 -1855375 +9625 -16625 +9687 -16562 +9687 -16625 +9625 -16625 +9625 -16625 +9687 -16625 +9625 -16625 +9625 -16625 +9625 -16625 +9687 -799687 +9625 -16625 +9687 -16562 +9687 -16625 +9625 -16625 +9625 -16625 +9687 -16562 +9687 -16625 +9625 -16625 +9625 -16625 +9687 -799687 ...</c>


2.

Verarbeitung einer Messung in LibreOffice

Im CSV-Format welches direkt in OpenOffice oder Excel importiert werden kann um ein Diagramm zu erstellen. Für jede Flanke werden zwei Zeilen ausgegeben um eine senkrechte Linie im Diagramm darzustellen: <c>

"Time [ns]";"Signal";"Duration" 0;0;0 1;1;9625 9625;1;0 9626;0;16625 26250;0;0 26251;1;9625 35875;1;0 35876;0;16625 52500;0;0 52501;1;9687 ...</c>


3. Als Zusammenfassung werden die Periodendauer und die Modulationsfrequenz ausgegeben (hierbei werden nur Pulse/Perioden berücksichtigt, die maximal 10% über der kürzesten Periodendauer liegen) :<c> Number of sample periods used for average values: 720 Carrier frequency [Hz]: 38186 Medium period [ns]: 26187 Medium pulse width [ns]: 10875 Duty cycle [%]: 41</c>

Source Code

Jetzt geht es endlich ans Eingemachte! Der hier abgedruckte Source Code ist ein vereinfachter Ausschnitt mit den Funktionen auf die im Text eingegangen wurde. Es fehlen die Anpassungen für ATmega32U4 und ATmega2560. Das gesamte Projekt mit dem Code für die andere MCUs ist im Abschnitt Downloads zu finden

<c> // Copyright (c) 2012 Michael Dreher <michael(at)5dot1.de> // this code may be distributed under the terms of the General Public License V2 (GPL V2)

uint16_t captureData[bufSize]; // the buffer where the catured data is stored int16_t captureCount; // number of values stored in captureData

// Wait for a signal on pin ICP1 and store the captured time values in the array 'captureData' void startCapture(void) {

 unsigned char sreg = SREG;
 cli(); // disable IRQs
 register uint8_t ices1_val = _BV(ICES1); 
 register uint8_t tccr1b = TCCR1B | ices1_val; // trigger on rising edge
 TCCR1B = tccr1b;
 OCR1A = TCNT1 - 1;
 TIFR1 = _BV(ICF1) | _BV(OCF1A) | _BV(TOV1); // clear all timer flags
 register uint16_t ovlCnt = 0;
 register uint16_t prevVal = 0;
 register uint8_t tifr; // cache the result of reading TIFR1 (masked with ICF1 and OCF1A)
 register uint16_t *pCapDat; // pointer to current item in captureData[]
 for(pCapDat = captureData; pCapDat <= &captureData[bufSize - 1]; )
 {
   // wait for edge or overflow (output compare match)
   while(! (tifr = (TIFR1 & (_BV(ICF1) | _BV(OCF1A))))) {
   }
   uint16_t val = ICR1;
   OCR1A = val; // timeout based on previous trigger time

   if(tifr & _BV(OCF1A)) // check for overflow bit
   {
     if(pCapDat != captureData) // ignore overflow at the beginning of the capture
     {
       ovlCnt++;
       if(ovlCnt >= _BV(RANGE_EXTENSION_BITS))
       {
         TIFR1 = _BV(ICF1) | _BV(OCF1A); // clear input capture and output compare flag bit
         break; // maximum value reached, treat this as timeout and abort capture
       }
     }
     TIFR1 = _BV(ICF1) | _BV(OCF1A); // clear input capture and output compare flag bit
     continue;
   }
   tccr1b ^= ices1_val; // toggle the trigger edge
   TCCR1B = tccr1b;
   TIFR1 = _BV(ICF1) | _BV(OCF1A); // clear input capture and output compare flag bit
   uint16_t diffVal = val - prevVal;
   if(ovlCnt || (diffVal & 0x8000))
   {
     diffVal = packTimeVal(diffVal, ovlCnt);
     ovlCnt = 0;
   }

   *pCapDat = diffVal;
   pCapDat++;
   prevVal = val;
 }

 // the first array entry contains only the starting time and no time
 // difference, therefore ist is no longer needed and will be removed
 captureCount = (pCapDat - captureData) - 1; // correct count
 for(int16_t i = 0; i < captureCount; i++)
 {
   captureData[i] = captureData[i + 1];
 }
 SREG = sreg; // enable IRQs

}

inline uint16_t packTimeVal(uint16_t diffVal, uint16_t ovlCnt) {

 // overflow part is stored in the lower RANGE_EXTENSION_BITS bits and not in
 // the upper bits because that makes the code smaller here (less shifting)
 return (0x8000 | ((diffVal >> 1) & (0x7fff & ~(_BV(RANGE_EXTENSION_BITS) - 1))) | ovlCnt);

}

inline uint32_t unpackTimeVal(uint32_t val) {

 if(val & 0x8000)
 {
   val = val & 0x7fff;
   uint32_t valOvl =  (val & (_BV(RANGE_EXTENSION_BITS) - 1)) << 16;
   uint32_t valTim =  (val << 1) & (0x7fff & ~(_BV(RANGE_EXTENSION_BITS) - 1));
   val = valOvl | valTim;
 }
 
 return val;

} </c>

Downloads

Das Download-Archiv enthält ein Arduino Projekt. Die Kern-Funktionalität (die Erfassung) kommt ohne die Arduino Libraries aus, die Arduino Funktionen werden nur die Ausgabe der Werte verwendet.

SourceCode für ATmega168, ATmega328 und ATmega32U4. Diese Variante ist zu empfehlen, wenn man den Code lesen und verstehen will, da keine Maros für die Registernamen verwendet werden
SourceCode für ATmega2560. Alle Timer-Registernamen wurden durch Makros ersetzt, so dass man den verwendeten Timer durch die Änderung des zentralen defines CAP_TIM geändert werden kann, worunter leider die Lesbarkeit gelitten hat. Funktioniert natürlich weiterhin mit ATmega168, ATmega328 und ATmega32U4:

Siehe auch

Lizenz

Dieser Artikel unterliegt der Creative Commons Attribution-Share Alike Lizenz (CC BY-SA 2.0 DE)