Timerinterrupts am Uno: Timer 2

Funktionierendes Script, um Timer-Register Nr. 2 am Atmel AVR 328P zu schalten. Besonderheiten: im CTC-Modus bleiben die Pins 3 und 11 passiv, es müssen aktiv die Timer-Interrupts gesetzt werden. Im 011-Fast-PWM werden automatisch die Pins 3 und 11 angesteuert. Achtung: die ‚tone()‘-Funktion läuft nicht mehr bei Nutzung de Timer2-Registers.

void setup() {
  //Disable Interrupts, damit man die Register setzen kann.
  cli();
  /////////////////////////////////////////////////
  // BIT Nr.      1        2        3        4         5       6        7       8
  // TCCR2A = [ COM2A1 | COM2A0 | COM2B1 | COM2B0 |   --   |   --   |  WGM21 | WGM20 ]
  // TCCR2B = [ FOC2A  | FOC2B  |   --   |   --   |  WGM22 |  CS22  |  CS21  | CS20  ]
  /////////////////////////////////////////////////
  //Register 2A und 2B löschen.
  TCCR2A = 0;
  TCCR2B = 0;
  //Counter auf 0 zurücksetzen
  TCNT2  = 0;
  /////////////////////////////////////////////////
  //Compare Match Output, Funktionsmodus setzen
  // 00: OC2A aus | 10: NichtInvertiert | 11: Invertiert
  TCCR2A |= (1 << COM2A1);
  TCCR2A |= (0 << COM2A0);
  // 00: OC2B aus | 10: NichtInvertiert | 11: Invertiert
  TCCR2A |= (1 << COM2B1);
  TCCR2A |= (0 << COM2B0);

  //WGM: Waveform Generation Bits
  //100: PWM Phasenkorrigiert bei 255   | 010: CTC Clear Timer on Compare  !!!!
  //101: PWM Phasenkorrigiert bei OCR0A | 011: Fast-PWM bei OCR2A und OCR2B!!!!
  TCCR2B |= (0 << WGM22);
  TCCR2A |= (1 << WGM21);
  TCCR2A |= (0 << WGM20);

  // Prescaler Values, Basis ist 16 MHz;  000: "Disabled" Timer ist ausgeschaltet
  // 001: /1 | 010: /8 | 011: /32 | 100: /64 DEFAULT | 101: /128 | 110: /256 | 111: /1024
  TCCR2B |= (1 << CS22);
  TCCR2B |= (0 << CS21);
  TCCR2B |= (1 << CS20);
  /////////////////////////////////////////////////
  //Duty Cyles für die Pins: Output Compare Register
  OCR2A = 255; // Pin 11 für 011-Fast-PWM-Modus und beliebigem Pin bei 010-CTC-Modus
  OCR2B = 1; // Pin 3
  /////////////////////////////////////////////////
  // Nur relevant bei CTC-Modus
  TIMSK2 |= (1 << OCIE2A); // Für Interrupt-Einschalten für OCR2A bei CTC-Modus an beliebigem Pin.
  TIMSK2 |= (1 << OCIE2B); // Für Interrupt-Einschalten für OCR2A bei CTC-Modus an beliebigem Pin.
  //Enable interrupts
  sei();
  /////////////////////////////////////////////////
  //Für Fast-PWM-Modus 011
  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  
  //Für CTC-Modus an beliebigem Pin, hier 12:
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);
}

void loop() {
}

ISR(TIMER2_COMPA_vect) {
  digitalWrite(12,!digitalRead(12));
}
///////////////////////////////////
ISR(TIMER2_COMPB_vect) {
  digitalWrite(13,!digitalRead(13));
}

AC-Coupling mit dem Verstärker

Heute kam der LMC6482IN-OPV mit der Post. Er ist Rail-to-Rail-Out fähig. Damit kann man den unten beschriebenen Transimpedanz-verstärker so erweitern, dass man damit seinen Puls messen kann:

Abb. 1: Screenshot Serieller Plotter der Arduino-Entwicklungsumgebung

Abb. 2: Simulierte Schaltung in Multisim Blue

Erläuterung der Schaltung:

a) Transimpedanzverstärker, siehe unten.

b) Hochpass: Ein Herzschlag ist eine periodische Bewegung. Das Blut wird dabei ‚ruckartig‘ durch die Gefäße gepumpt. Ist das Blutgefäß gerade voll, schwillt es an, wird damit dicker und absorbiert mehr Licht. Die perio-dische Bewegung zeigt sich also über eine periodische Volumenänderung in den Blutgefäßen als messbare Helligkeitsänderung. Diese periodische Helligkeitsänderung wird vom Schaltungsteil a) in eine Wechselspannung umgewandelt.

Allerdings ist die Fotodiode dabei niemals komplett abgedunkelt, es bleibt also eine Resthelligkeit, die einen Gleichspannungsanteil erzeugt. Diesen Gleichspannungsanteil muss man entfernen; damit „schält“man quasi den sehr schwachen Wechelspannunganteil heraus. Man spricht von einem ‚Filtern‘ der Spannung: Der hier verwendete Hochpassfilter sperrt die unveränderlichen Gleichspannungsanteile, die ‚hochfrequenten‘ periodischen Spannungsimpulse werden aber durchgelassen.

Diese Aufgabe übernimmt der Kopplungskondensator mit 2.2μF. Kondensatoren wirken auf Wechselspannungen wie Widerstände (Blindwiderstand). Dabei gilt zweierlei:

  • Je höher die Kapazität des Kondensators, desto geringer ist sein sogenannter ‚kapazitiver Blindwiderstand‘.
  • Je höher die Frequenz der Wechselspannung, desto geringer ist der ‚kapazitive Blindwiderstand‘.

Die Größe des Kondensators muss daher so gewählt werden, dass die zu erwartende Frequenz noch durchgelassen wird, niedrigere Frequenzen aber nicht mehr, bzw. deutlich abgeschwächt werden.

Auf das gefilterte Wechselspannungssignal (AC-Signal genannt, „AC“ für „Alternating Current“) wird nun noch ein Offset von 2.5Volt gelegt, damit es für den 5Volt-betriebenen zweiten Operationsverstärker optimal vorbereitet ist. Diesen 2.5Volt-Offset erzeugen die beiden Reihenwiderstände, die zusammen einen Spannungsteiler bilden.

c) AC-Gain: „Gain“ bedeutet „Verstärkung“. Da es sich hier um eine Nicht-Invertierende Verstärkerschaltung eines Operationsverstärkers handelt, besitzt die Verstärkung hier den Wert (1+R1/R2), das ist hier etwa (1+100kΩ/4,7kΩ) = 22,27. Damit wird das äußerst schwache AC-Signal der Herzschlagsmessung verstärkt. Der parallel zum 100kΩ -Widerstand geschaltete 1nF-Kondensator filtert hochfrequentes Rauschen – ähnlich wie der 10pF-Kondensator in der Transimpedanzstufe.

Die Schaltung „Inverting Amplifier (AC Coupled)“ ist vom Prinzip her in diesem Aufsatz vom MIT (Massachusetts Institute of Technology) sehr schön beschrieben:

https://ocw.mit.edu/courses/media-arts-and-sciences/mas-836-sensor-technologies-for-interactive-environments-spring-2011/readings/MITMAS_836S11_read02_bias.pdf

 

Fotodiode am Transimpedanzverstärker

Linienerkennung muss ein Roboter ständig durchführen. Oftmals ist es problematisch, dass die nötigen Lichtmessungen nicht genau genug und nicht schnell genug ablaufen. Der Roboter schießt trotz Interrupt-Programmierung über das Ziel hinaus.

Die fertig gekauften Bauteile zur Helligkeitsmessung funktionieren meist über Fototransistoren oder langsame LDRs, deren Messwerte am Spannungsteiler mit LM393-Comparatoren in „0“ oder „1“, also in Binärwerte gewandelt werden.

Besser wäre hier eine schnelle Helligkeitsmessung, möglichst mit linearem Messwertverlauf. Daher kommen Fotodioden und deren Fotostrom-Aufbereitung mit einem Transimpedanzverstärker in Frage.

Prinzip des Transimpedanzverstärkers

Ein Transimpedanzverstärker wandelt den Fotostrom der Fotodiode in eine proportionale Ausgangsspannung um. Die Wandlung von Strom zu Spannung erfolgt an einem sogenannten Operationsverstärker. Die Verstärkung der Spannung wird mit der Größe eines Widerstands eingestellt: Je größer der Widerstand, desto größer die Ausgangsspannung. Der Kondensator unterdrückt eventuelles Rauschen.

Abb. 1: Schema des Testaufbaus

Erklärung zur Fotodiode:

Eine Fotodiode ist eine kleine Solarzelle; die verwendete BPW34 besitzt eine Fläche von 7,5 Quadratmillimetern und liefert damit nur einen sehr schwachen Strom von maximal 50μA bei lediglich 0,2-0,3 Volt Spannung. Damit kann der Arduino nichts anfangen!

Kurze Erklärung zum Operationsverstärker:

Der Strom der Fotodiode wird durch einen Operationsverstärker in eine Spannung gewandelt. Ein Operationsverstärker ist ein integriertes elektronisches Bauteil, welches grundsätzlich so aufgebaut ist:

Abb. 2: Darstellungen des Operationsverstärkers

  1. Zwei Anschlüsse zur Spannungsversorgung: : Ein „VDD“ für die positive Spannungsversorgung und einen an Masse (GND, „Ground“).
  2. Einen Ausgang: An „Out“ erhält man das vom Operationsverstärker aufgearbeitete Spannungs-Signal.
  3. Zwei Eingänge: Einen nicht invertierenden Eingang „IN+“ und einen invertierenden Eingang „IN-„. Prinzipiell wird der Spannungsunterschied zwischen den beiden Eingängen gemessen und sehr hoch verstärkt: Typischerweise etwa um den Faktor 100.000. Da man solche Verstärkungen nicht benötigt, stellt man mittels Widerständen die gewünschte Verstärkung ein.Hier eine sehr gute Online-Simulation eines Operationsverstärkers:
    http://www.falstad.com/circuit/e-amp-invert.html
    Es handelt sich hierbei um die Grundschaltung dieses Bauteils.
Erklärung zum weiteren Signalweg:

Die Ausgangsspannung des Operationsverstärkers wird vom Analog-Digital-Wandler des Arduinos gelesen und in einen Wertebereich zwischen 0 (dunkel: kein Fotostrom) und 1024 (hell: voller Fotostrom) gewandelt. In dieser Schaltung wird der Eingang der Analoge Eingang „A0“ des Arduinos verwendet.

Ziel der Schaltung

Wichtig ist, dass der Aufbau einfach und schnell und mit günstigen Bauteilen umgesetzt werden kann.

Die folgende Schaltung wurde in Multisim Blue simuliert, dann am Arduino aufgebaut und getestet:

Abb. 3: Foto des Testaufbaus
Foto des TestaufbausAbb. 4: Fritzingzeichnung des Testaufbaus
Fritzing-Zeichnung

Als Bauteile werden benötigt:
  1. TLC271ACP Operationsverstärker (SingleSupply, recht günstig)
  2. BPW 34 Fotodiode (liefert 50 μAmpere Kurzschlussstrom, günstig)
  3. Metallwiderstand 100 kΩ
  4. Keramik-Kondensator 10 pF

Dieser Operationsverstärker kann direkt am Arduino mit 5V betrieben werden; da er aber nicht „Rail-to-Rail-Out“-fähig ist, reicht die Ausgangsspannung nur bis ca. 3.8Volt. Diese 3.8 Volt erzeugen bei der AD-Wandlung immerhin einen Wertebereich von 0 bis ca. 850 bei der 10Bit-Auflösung des Arduino-ADCs. Ein passender Rail-to-Rail-OpAmp wäre z.B. der LMC6482 (wird getestet; die Simulation lief vielversprechend)

Erläuterung zu Rail-to-Rail-Out:

Ein Operationsverstärker benötigt eine Spannungsversorgung, ähnlich wie ein normales elektronisches Gerät. Üblicherweise reicht aber die Ausgangsspannung -also die Spannung, die der OpAmp am Ausgang maximal liefern kann – nicht an die Spannungsversorgung heran. Eine besondere Eigenschaft ist es daher, wenn ein OpAmp das schafft, dann wird diese Konstruktionsform als „Rail2Rail-Out“ bezeichnet. Der TLC271 kommt ca. 1.2 Volt bis an die Versorgungsspannung heran, schafft also bei 5Volt Spannungsversorgung lediglich 3.8 Volt maximal.

Der Simulationsaufbau in Multisim Blue:

Abb. 5: Aufbau in Multisim BlueMultisim Opamp mit Fotodiode

Abb. 6: Ersatzschaltung in Multisim BlueMultisim Ersatzschaltung

In der Ersatzschaltung wird die Fotodiode als Dreiecksstromquelle angenähert; das angeschlossene Oszilloskop zeigt den simulierten Spannungsverlauf.

Erläuterung der Simulation

Die Schaltung simuliert den Strom der Fotodiode, indem eine ständig wechselnde Stromstärke (hier mit 1kHz, das sind 1000 Hertz, also 1000 Wechsel pro Sekunde) auf die Schaltung wirkt. Die Simulation der Wechselspannung ist nötig, weil Operationsverstärker ihre Eigenschaften mit steigender Frequenz ändern: Meistens wird die Verstärkung schwächer und es kommen Signalverzerrungen hinzu. 

Programmierung am Arduino

Der Arduino-Code für den Test ist sehr einfach: Es wird eine Serielle Verbindung zum Arduino aufgebaut, die über USB-Kabel ausgelesen wird. Etwa alle 25 Millisekunden wird dabei der aktuelle Helligkeitswert am Analogport „A0“ ausgelesen und unmittelbar an PC übermittelt:

int test;
void setup() {
   pinMode(A0, INPUT);
   Serial.begin(9600);
}

void loop() {
   test = analogRead(A0);
   Serial.println(test);
   delay(25);
}
Programmierung in Processing

Um die Daten zu visualisieren, genügt eigentlich der Serielle Plotter der Arduino-Programmierumgebung. Allerdings ist eine portable, Arduino-unabhängige Lösung praktischer, wie z.B. mittels Processing. Hier der Screenshot des Processing-Applets:

Abb. 7: Screenshot des Processing-AppletsScreenshot Experimentalapplet

Der Vollständigkeit halber noch der Quelltext des Processing-Applets:

import processing.serial.*;
Serial arduinoKommunikation;
String messwert;
int x, xalt;
int y, yalt;

void setup()
{
 size(640, 600);
 background(102);
 String portName = Serial.list()[0];
 arduinoKommunikation = new Serial(this, portName, 9600);
 x = y = xalt = yalt = 0;
}
void draw()
{
 if ( arduinoKommunikation.available() > 0) 
 {
 messwert = arduinoKommunikation.readStringUntil('\n');
 if (messwert != null) {
 if (x==0) {
 stroke(0);
 fill(102);
 rect(0, 0, 640, 600);
 line(0, 560, 640, 560);
 line(0, 48, 640, 48);
 stroke(0);
 fill(0);
 textSize(12);
 text("null-Linie", 570, 575);
 text("1024-Linie", 570, 44);
 xalt = x;
 yalt = y;
 }
 messwert = trim(messwert);
 y = 560-Integer.parseInt(messwert)/2;
 //println(messwert);
 stroke(102);
 fill(102);
 rect(xalt+5, yalt-25, 45, 25);
 stroke(51);
 for (int i=1; i<10; i++) {

 line(0, 48+i*51, 640, 48+i*51);
 }
 fill(102);
 stroke(255);
 rect(x+5, y-25, 45, 25);
 stroke(255);
 fill(255);
 line(x, y, xalt, yalt);
 textSize(18);
 if (y<100) {
 text(messwert, x+10, y-5);
 } else {
 text(" " + messwert, x+10, y-5);
 }
 xalt = x;
 yalt = y;
 x = x+1;
 x=x%640;
 }
 }
}

Interrupts am Arduino: Rundenzähler

#include <Wire.h>
#include „Adafruit_LEDBackpack.h“
#include „Adafruit_GFX.h“
#include <LiquidCrystal.h>
//
Adafruit_7segment matrix = Adafruit_7segment();
LiquidCrystal lcd(9, 8, 5, 4, 6, 7);
//
volatile unsigned long zaehlerrechts = 0;
volatile unsigned long millisrechts = 0;
volatile unsigned long speedrechts = 65000;
volatile unsigned long zaehlerlinks = 0;
volatile unsigned long millislinks = 0;
volatile unsigned long speedlinks = 65000;
//
void setup() {
    Wire.begin();
    attachInterrupt(0, rechtsHoch, CHANGE);
    attachInterrupt(1, linksHoch, CHANGE);
    matrix.begin(0x70);
    millisrechts = millislinks = millis();
    lcd.begin(16, 2);
    delay(500);
}
//
void loop() {
    lcd.setCursor(0, 0);
    lcd.print(„schnellste Runde“ );
    lcd.setCursor(0, 1);
    lcd.print(“ „);
    lcd.setCursor(0, 1);
    if (speedrechts < 65000) {
        lcd.print(speedrechts + „ms“);
    } else {
        lcd.print(„—- ms“);
    }
    lcd.setCursor(8, 1);
    lcd.print(“ „);
    lcd.setCursor(8, 1);
    if (speedlinks < 65000) {
        lcd.print(speedlinks + “ ms“);
    } else {
        lcd.print(„—- ms“);
    }
    int zaehler = 100 * zaehlerrechts + zaehlerlinks;
    matrix.writeDigitNum(0, (zaehler / 1000), false);
    matrix.writeDigitNum(1, (zaehler / 100) % 10, false);
    matrix.drawColon(true);
    matrix.writeDigitNum(3, (zaehler / 10) % 10, false);
    matrix.writeDigitNum(4, zaehler % 10, false);
    matrix.writeDisplay();
    delay(100);
}
void rechtsHoch() {
    unsigned long temprechts = millis() – millisrechts;
    if (temprechts > 700) {
        zaehlerrechts++;
        if (speedrechts > temprechts) {
            speedrechts = temprechts;
        }
        millisrechts = millis();
    }
}
void linksHoch() {
    unsigned long templinks = millis() – millislinks;
    if (templinks > 700) {
        zaehlerlinks++;
        if (speedlinks > templinks) {
            speedlinks = templinks;
        }
        millislinks = millis();
    }
}