Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
07.12.2019, 02:31
(Dieser Beitrag wurde zuletzt bearbeitet: 24.04.2023, 09:32 von norbert_w.)
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 1 Softwareinstallation und serielle Kommunikation
Heute will ich die erste Lektion des Einsteigerkurses in die Mikrocontroller-Programmierung hochladen. Den Controller haben offensichtlich schon alle besorgt und mehr brauchen wir für Lektion 1 auch erstmal nicht. Es gab zahlreiche Wünsche, so dass einige Änderungen notwendig wurden: - Der Kurs soll auch offline funktionieren
- Der Kurs soll auf ebook-Readern lesbar sein
- Das Material sollte ausdruckbar und abheftbar sein
Nun ich denke mit der PDF-Form kann ich die meisten Nutzerwünsche erfüllen.
Als Basis dieses Kurses wurde die populäre Arduino - Platform gewählt.
Der Kurs besteht aus 6 Lektionen mit Erklärungen zum Thema Mikrocontroller Hard- und Software: - Lektion 1 Softwareinstallation und serielle Kommunikation
- Lektion 2 Digital I/O - Ausgänge des Mikrocontroller schalten
- Lektion 3 Digital I/O - Eingänge des Mikrocontroller auslesen
- Lektion 4 Interrupts, weitere binäre I/O-Funktionen in der Arduino-Welt
- Lektion 5 ADC / DAC, analoge Signale
- Lektion 6 externe Bibliotheken, C++
Zusätzlich enthält jede Lektion Anwendungs- und Übungs-Beispiele (sogenannte Hausaufgaben!).
Hier also der Link auf das PDF für die Lektion 1 Softwareinstallation und serielle Kommunikation, inklusive der Hausaufgabe:
Lektion 1.pdf (Größe: 3,31 MB / Downloads: 16)
Vor der Veröffentlichung der nächsten Lektion, werde ich immer eine Musterlösung für die Hausaufgabe aus der vorhergehenden Lektion präsentieren.
Im Nachbar-Thread " FAQ zum Einsteigerkurs µC-Programmierung" können Fragen gestellt und es kann untereinander gegenseitig geholfen werden.
Viel Spaß mit Lektion 1, Bernhard.
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
23.12.2019, 02:16
(Dieser Beitrag wurde zuletzt bearbeitet: 24.04.2023, 11:37 von norbert_w.)
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 2 Digital I/O - Ausgänge des Mikrocontroller schalten
Im Nachbar-Thread " FAQ zum Einsteigerkurs µC-Programmierung" gab es ja schon erste Lösungen zu bewundern.
Unter den unzähligen Möglichkeiten der Umsetzung bieten sich doppelte, ineinander verschachtelte Schleifen an.
Die Zählvariable einer jeden Schleife ist dabei gleich ein Faktor in der Multiplikation.
Hier nun meine Musterlösung:
PHP-Code: void setup() { // put your setup code here, to run once: Serial.begin(9600); }
void loop() { // put your main code here, to run repeatedly: Serial.println("Das kleine 1x1\n\n"); for (int i = 0; i < 11; i++) { Serial.print("\t"); Serial.print(i); } Serial.println("\n------------------------------------------------------------------------------------------------"); for (int i = 0; i < 11; i++) { Serial.print(i); Serial.print("|"); for (int x = 0; x < 11; x++) { Serial.print("\t"); Serial.print(i*x); } Serial.println(); } delay(100000); }
Die Ausgabe des Programms über den Seriellen Monitor sieht dann so aus:
Kommen wir nun zur Lektion 2.
Lektion 2.pdf (Größe: 2,07 MB / Downloads: 4)
Auch hier gibt es wieder eine kleine Hausaufgabe, um den Stoff zu verfestigen.
Fragen bitte wieder im Nachbar-Thread " FAQ zum Einsteigerkurs µC-Programmierung" und nicht hier stellen.
Vielen Dank!
Viel Spaß mit Lektion 2, Bernhard.
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
08.01.2020, 20:50
(Dieser Beitrag wurde zuletzt bearbeitet: 24.04.2023, 12:16 von norbert_w.)
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 3 Digital I/O - Eingänge des Mikrocontroller auslesen
Als Erstes möchte ich meine mittellange Musterlösung für unsere Ampelkreuzung aus der Hausaufgabe vorstellen.
Ich habe explizit jeden Schaltzustand im Regelbetrieb aufgeführt, ich denke das erleichtert das "durchsteppen" der Ampelschaltung. Unter den Hausaufgaben waren Lösungen mit sehr stark komprimierten Zustandstabellen. Das war fast schon rekordverdächtig, wenn auch nicht immer leicht nachzuvollziehen, was die Anlage nun gerade macht.
PHP-Code: int ledPins[9] = {3, 2, 4, 5, 6, 7, 8, 9, 10}; // Ampel A2/4 Pins: Rot=3, Gelb=2, Grün=4 // Ampel A5/A6 Pins: Rot=5, Grün=6 // Ampel A7 Pins: Gelb=7 // Ampel A1/A3 Pins: Rot = 8, Gelb = 9, Grün = 10
bool bInitialisierungDurchlaufen = false;
// Beschreibung der einzelnen Schaltzustände der Anlage, Wartezeit/Takt jeweils an letzter Stelle int ampelphasen_initialisierung[3][10] = { {0, 1, 0, 1, 0, 0, 0, 1, 0, 1000}, // A1-A4 gelb an, A5/6 rot an, A7 aus, für 1000ms {0, 0, 0, 1, 0, 0, 0, 0, 0, 1000}, // A1-A4 gelb aus, A5/6 rot an, A7 aus, für 1000ms {1, 0, 0, 1, 0, 0, 1, 0, 0, 1000}, // A1-A6 rot, A7 aus, für 1000ms };
int ampelphasen_regelbetrieb[48][10] = { {1, 1, 0, 1, 0, 0, 1, 0, 0, 1000}, // Regelbetrieb Zustand 1 {1, 1, 0, 0, 1, 0, 1, 0, 0, 1000}, // " Zustand 2 {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 1, 0, 1, 0, 0, 1, 0, 0, 1000}, // {1, 0, 0, 1, 0, 0, 1, 0, 0, 1000}, //
{1, 0, 0, 1, 0, 0, 1, 0, 0, 1000}, // {1, 0, 0, 1, 0, 0, 1, 1, 0, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 1, 0, 1000}, // {1, 0, 0, 1, 0, 0, 1, 0, 0, 1000} // };
void setup() { // alle benötigten Pins auf Ausgang festlegen for (int i = 0; i < 9; i++) { pinMode(ledPins[i], OUTPUT); } Serial.begin(9600); }
void loop() { // Ampel Initialisierung schon durchlaufen? if (bInitialisierungDurchlaufen==false) { // Ampel Initialisierung 10s Gelb blinken, Fussgänger rot for(int iDurchgang = 0; iDurchgang < 5; iDurchgang++) { for (int x = 0; x < 2; x++) { for (int i = 0; i < 9; i++) { digitalWrite(ledPins[i], ampelphasen_initialisierung[x][i]); } delay(ampelphasen_initialisierung[x][9]); } // for (int x=0; x<2; x++) } // for(int iDurchgang=0; iDurchgang<5; iDurchgang++)
// 10s alle Ampeln Rot for(int iDurchgang = 0; iDurchgang < 10; iDurchgang++) { for (int i = 0; i < 9; i++) { digitalWrite(ledPins[i], ampelphasen_initialisierung[2][i]); } delay(ampelphasen_initialisierung[2][9]); } bInitialisierungDurchlaufen = true; } // if (bInitialisierungDurchlaufen==false)
// Ampel Regelbetrieb, Takt wird durch letztes Element im Feld ampelphasen_regelbetrieb bestimmt for (int x = 0; x < 48; x++) { for (int i = 0; i < 9; i++) { digitalWrite(ledPins[i], ampelphasen_regelbetrieb[x][i]); } delay(ampelphasen_regelbetrieb[x][9]); } // for (int x = 0; x < 48; x++) { }
Im Feld ledPins werden die I/O-Portnummern der einzelnen "Lampen" der Ampel angegeben bzw. welche LED an welchem Controllerport hängt.
Danach kommt ein Flag (boolean) in dem gespeichert wird, ob die Ampel bereits angefahren und im Regelbetrieb ist, oder nicht.
Die beiden folgenden Felder speichern die Schaltzustände im Anlauf- und Regelbetrieb. Eine 1 bedeutet, dass auf diesem Port (gemapped durch ledPins) eine LED leuchtet.
Eine 0 bedeutet die LED leuchtet nicht. Das letzte Element in der Beschreibung eines Schaltzustandes enthält die Verweildauer in diesem Zustand in ms.
In Setup wird für alle Pins in ledPins die Datenrichtung auf Ausgang festgelegt und die serielle Schnittstelle aktiviert.
Das eigentliche loop-Programm läuft praktisch nur noch durch die Tabelle (Feld, Array) der Schaltzustände hindurch, wobei einmalig eine Anlaufphase ausgeführt wird.
Kommen wir nun zur nächsten Lektion, zur Lektion 3.
Lektion_3.pdf (Größe: 952,29 KB / Downloads: 0)
Auch hier haben wir wieder eine Hausaufgabe. Die Ampel aus Lektion 2 wird uns nochmal beschäftigen.
Für alle Kursteilnehmer und Leute die nicht genug bekommen, gibt es hinter der Hausaufgabe ein paar Zusatzaufgaben!
Ich wünsche viel Spaß beim Studieren und Lösen der Aufgaben.
Fragen bitte wieder im Nachbar-Thread " FAQ zum Einsteigerkurs µC-Programmierung" und nicht hier stellen.
Gruß Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
29.01.2020, 23:01
(Dieser Beitrag wurde zuletzt bearbeitet: 16.06.2022, 10:31 von Bernhard45.)
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 4 Interrupts, weitere binäre I/O-Funktionen in der Arduino-Welt
Bevor wir zu Lektion 4 kommen, möchte ich hier erst die Musterlösung zur Hausaufgabe zeigen.
PHP-Code: int ledPins[9] = {3, 2, 4, 5, 6, 7, 8, 9, 10}; // Ampel A2/4 Pins: Rot=3, Gelb=2, Grün=4 // Ampel A5/A6 Pins: Rot=5, Grün=6 // Ampel A7 Pins: Gelb=7 // Ampel A1/A3 Pins: Rot = 8, Gelb = 9, Grün = 10
bool bInitialisierungDurchlaufen = false; unsigned long time; // für nichtblockierendes Delay
#define TASTER_PIN 11 #define WARTEZEIT_IN_MS 5 unsigned long ulTime = 0; bool bFlankenwechsel = false; bool bGruenanforderung = false; bool bGruen_im_naechsten_Durchlauf = false;
// Beschreibung der einzelnen Schaltzustände der Anlage, Wartezeit/Takt jeweils an letzter Stelle int ampelphasen_initialisierung[3][10] = { {0, 1, 0, 1, 0, 0, 0, 1, 0, 1000}, // A1-A4 gelb an, A5/6 rot an, A7 aus, für 1000ms {0, 0, 0, 1, 0, 0, 0, 0, 0, 1000}, // A1-A4 gelb aus, A5/6 rot an, A7 aus, für 1000ms {1, 0, 0, 1, 0, 0, 1, 0, 0, 1000}, // A1-A6 rot, A7 aus, für 1000ms };
int ampelphasen_regelbetrieb[48][10] = { {1, 1, 0, 1, 0, 0, 1, 0, 0, 1000}, // Regelbetrieb Zustand 1 {1, 1, 0, 0, 1, 0, 1, 0, 0, 1000}, // " Zustand 2 {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 1, 1, 0, 0, 1000}, // {0, 0, 1, 0, 1, 0, 1, 0, 0, 1000}, // {0, 1, 0, 1, 0, 0, 1, 0, 0, 1000}, // {1, 0, 0, 1, 0, 0, 1, 0, 0, 1000}, //
{1, 0, 0, 1, 0, 0, 1, 0, 0, 1000}, // {1, 0, 0, 1, 0, 0, 1, 1, 0, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 0, 1, 1000}, // {1, 0, 0, 1, 0, 0, 0, 1, 0, 1000}, // {1, 0, 0, 1, 0, 0, 1, 0, 0, 1000} // };
void pruefeGruenanforderung(int imTakt) { if (digitalRead(TASTER_PIN) == HIGH) { if (digitalRead(TASTER_PIN) == LOW) { // Flankenwechsel erkannt und Zeit notiert ulTime = millis(); bFlankenwechsel = true; } } if (bFlankenwechsel) if ((millis()-ulTime) > WARTEZEIT_IN_MS ) // ist der Taster immer noch gedrückt? if (digitalRead(TASTER_PIN) == LOW) { bFlankenwechsel = false;
// Abfrage ob wir gerade im Grünphase (egal ob freigegeben oder nicht) sind if ((imTakt >= 1) && (imTakt <= 21)) { bGruen_im_naechsten_Durchlauf = true; } else { bGruenanforderung = true; } Serial.print("Bei "); Serial.print(millis()); Serial.println(":Gruenanfoderung erfolgt!"); } }
void setup() { // alle benötigten Pins auf Ausgang festlegen for (int i = 0; i < 9; i++) { pinMode(ledPins[i], OUTPUT); } pinMode(TASTER_PIN, INPUT_PULLUP); Serial.begin(9600); }
void loop() { // Ampel Initialisierung schon durchlaufen? if (bInitialisierungDurchlaufen==false) { // Ampel Initialisierung 10s Gelb blinken, Fussgänger rot for(int iDurchgang = 0; iDurchgang < 5; iDurchgang++) { for (int x = 0; x < 2; x++) { for (int i = 0; i< 9; i++) { digitalWrite(ledPins[i], ampelphasen_initialisierung[x][i]); } delay(ampelphasen_initialisierung[x][9]); } // for (int x = 0; x < 2; x++) } // for(int iDurchgang = 0; iDurchgang < 5; iDurchgang++)
// 10s alle Ampeln Rot for(int iDurchgang = 0; iDurchgang < 10; iDurchgang++) { for (int i = 0; i < 9; i++) { digitalWrite(ledPins[i], ampelphasen_initialisierung[2][i]); } delay(ampelphasen_initialisierung[2][9]); } bInitialisierungDurchlaufen = true; } // if (bInitialisierungDurchlaufen==false) {
// Ampel Regelbetrieb, Takt wird durch letztes Element im Feld ampelphasen_regelbetrieb bestimmt for (int x = 0; x < 48; x++) { for (int i = 0; i < 9; i++) { digitalWrite(ledPins[i], ampelphasen_regelbetrieb[x][i]); } // Wenn keine Grünanforderung erfolgte, Fussgängerampel auf Rot/Blinklicht aus => Schaltzustand aus Tabelle überschreiben if (bGruenanforderung==false) { digitalWrite(5, 1); // Fussgänger Rot an digitalWrite(6, 0); // Fussgänger Gruen aus digitalWrite(7, 0); // Fussgänger Blicklicht aus }
// nach Ende einer Grünphase in Schaltzustand 21 für nächsten Durchlauf wieder sperren if ((x==21) && bGruenanforderung) { bGruenanforderung = false; } // Wurde wärend einer Grünphase eine Grünanforderung erkannt, gilt diese für den nächsten Durchlauf if ((x==21) && bGruen_im_naechsten_Durchlauf) { bGruen_im_naechsten_Durchlauf = false; bGruenanforderung = true; } // altes Delay mit Programmblockierung: delay(ampelphasen_regelbetrieb[x][9]); // neues "Delay" ohne Programmblockierung time = millis(); while(millis() < time + ampelphasen_regelbetrieb[x][9]) { pruefeGruenanforderung(x); } } // for (int x=0; x<48; x++) { }
Im Prinzip haben wir funktionell nur das delay durch
PHP-Code: // altes Delay mit Programmblockierung: delay(ampelphasen_regelbetrieb[x][9]); // neues "Delay" ohne Programmblockierung time = millis(); while(millis() < time + ampelphasen_regelbetrieb[x][9]) { pruefeGruenanforderung(x); }
ersetzt. Dadurch das der Controller nun in der Zeit zwischen dem Umschalten nicht mehr blockiert ist, kann er fast permanent auf eine Grünanforderung per Taster warten.
Die anderen geänderten Codeteile befassen sich mit der Tastendruckerkennung und der "Verriegelung" und "Freigabe" der Grünphase für die Fussgängerampel.
____________________________________________________________________________________________________________
Zum Zusatz:
1. Entwickle eine Funktion zur Ansteuerung eines 7-Segment-Anzeigenmoduls wobei über zwei Taster zwischen 0-9 auf- und abgeschaltet werden kann.
PHP-Code: int SegPins[8] = {12, 11, 10, 9, 8, 7, 6, 5}; // Pinnummer zum Anschluß des 7-Segment-Anzeigenmoduls // A,B,C,D,E,F,G,DP
// Definition für Zahlen 0 bis 9, wobei true = Segement an und false = Segement aus bool Zahlen[10][8] = { { true, true, true, true, true, true, false, false } , // = 0 { false, true, true, false, false, false, false, false} , // = 1 { true, true, false, true, true, false, true, false } , // = 2 { true, true, true, true, false, false, true, false } , // = 3 { false, true, true, false, false, true, true, false } , // = 4 { true, false, true, true, false, true, true, false } , // = 5 { true, false, true, true, true, true, true, false } , // = 6 { true, true, true, false, false, false, false, false } , // = 7 { true, true, true, true, true, true, true, false } , // = 8 { true, true, true, true, false, true, true, false } , // = 9 };
#define TASTER_AUFWAERTS_PIN 4 #define TASTER_ABWAERTS_PIN 2 #define ENTPRELLZEIT_IN_MS 25
unsigned long ulTimeTasterAuf = 0; bool bFlankenwechselTasterAuf = false; bool bSchonGedruecktAuf = false;
unsigned long ulTimeTasterAb = 0; bool bFlankenwechselTasterAb = false; bool bSchonGedruecktAb = false; int iZahl = 0;
void setup() { // 7-Segment Pins initialisieren for (int i = 0; i < 8; i++) { pinMode(SegPins[i],OUTPUT); } // Alle Anzeigen aus for (int i = 0; i < 9; i++) { digitalWrite(SegPins[i], HIGH); } pinMode(TASTER_AUFWAERTS_PIN, INPUT_PULLUP); pinMode(TASTER_ABWAERTS_PIN, INPUT_PULLUP); Serial.begin(9600); }
void PruefeAufTastendruckAufwaerts() { if ((digitalRead(TASTER_AUFWAERTS_PIN) == LOW) && !bFlankenwechselTasterAuf) { ulTimeTasterAuf = millis(); bFlankenwechselTasterAuf = true; }
if (bFlankenwechselTasterAuf && !bSchonGedruecktAuf) { if ( (millis()-ulTimeTasterAuf) > ENTPRELLZEIT_IN_MS ) { // ist der Taster immer noch gedrückt? if (digitalRead(TASTER_AUFWAERTS_PIN) == LOW) { if (iZahl < 9) { iZahl++; } bSchonGedruecktAuf = true; } } }
if (digitalRead(TASTER_AUFWAERTS_PIN) == HIGH) { bSchonGedruecktAuf = false; } }
void PruefeAufTastendruckAbwaerts() { if ((digitalRead(TASTER_ABWAERTS_PIN) == LOW) && !bFlankenwechselTasterAb) { ulTimeTasterAb = millis(); bFlankenwechselTasterAb = true; }
if (bFlankenwechselTasterAb && !bSchonGedruecktAb) { if ( (millis()-ulTimeTasterAb) > ENTPRELLZEIT_IN_MS ) { // ist der Taster immer noch gedrückt? if (digitalRead(TASTER_ABWAERTS_PIN) == LOW) { if (iZahl > 0) { iZahl--; } bSchonGedruecktAb = true; } } }
if (digitalRead(TASTER_ABWAERTS_PIN) == HIGH) { bFlankenwechselTasterAb = false; bSchonGedruecktAb = false; } }
void siebenseg(int zahl) { for (int i = 0; i < 8; i++) { digitalWrite(SegPins[i],!Zahlen[zahl][i]); } }
void loop() { PruefeAufTastendruckAufwaerts(); PruefeAufTastendruckAbwaerts(); siebenseg(iZahl); }
Hier hat der Controller nun nicht mehr soviel Zeit um direkt den Flankenwechsel H->L just in Time zu erkennen, wir haben die Flankenerkennung deshalb abgeändert, die Entprellung funktioniert nach wie vor ohne(!) delay. Wir haben auch eine Verblockung vorgenommen, so dass ein Tastendruck auch nur eine Erhöhung oder Herabsetzung des Zählers bewirkt.
2. die Erweiterung der Funktion siebenseg auf 4-Stellen bedeutet das wir nun Multiplexen müssen. Eine mögliche Lösung wäre:
PHP-Code: int SegPins[8] = {12, 11, 10, 9, 8, 7, 6, 5}; // Pinnummer zum Anschluß des 7-Segment-Anzeigenmoduls // A,B,C,D,E,F,G,DP
// Definition für Zahlen 0 bis 9, wobei true = Segement an und false = Segement aus bool Zahlen[10][8] = { { true, true, true, true, true, true, false, false } , // = 0 { false, true, true, false, false, false, false, false} , // = 1 { true, true, false, true, true, false, true, false } , // = 2 { true, true, true, true, false, false, true, false } , // = 3 { false, true, true, false, false, true, true, false } , // = 4 { true, false, true, true, false, true, true, false } , // = 5 { true, false, true, true, true, true, true, false } , // = 6 { true, true, true, false, false, false, false, false } , // = 7 { true, true, true, true, true, true, true, false } , // = 8 { true, true, true, true, false, true, true, false } , // = 9 };
#define TASTER_AUFWAERTS_PIN 4 #define TASTER_ABWAERTS_PIN 2 #define ENTPRELLZEIT_IN_MS 25
unsigned long ulTimeTasterAuf = 0; bool bFlankenwechselTasterAuf = false; bool bSchonGedruecktAuf = false;
unsigned long ulTimeTasterAb = 0; bool bFlankenwechselTasterAb = false; bool bSchonGedruecktAb = false;
int ModulPins[4] = {14, 15, 16, 17}; // Pinnummer zum Multiplexen des 7-Segment-Anzeigenmoduls // Digit1, Digit2, Digit3, Digit4
int iZahl = 0;
void setup() { // 7-Segment Pins initialisieren for (int i = 0; i < 8; i++) { pinMode(SegPins[i], OUTPUT); } // Alle Anzeigen aus for (int i = 0; i < 9; i++) { digitalWrite(SegPins[i], HIGH); } pinMode(TASTER_AUFWAERTS_PIN, INPUT_PULLUP); pinMode(TASTER_ABWAERTS_PIN, INPUT_PULLUP);
for (int i = 0; i < 4; i++) { pinMode(ModulPins[i], OUTPUT); } for (int i = 0; i < 4; i++) { digitalWrite(ModulPins[i], HIGH); } Serial.begin(9600); }
void PruefeAufTastendruckAufwaerts() { if ((digitalRead(TASTER_AUFWAERTS_PIN) == LOW) && !bFlankenwechselTasterAuf) { ulTimeTasterAuf = millis(); bFlankenwechselTasterAuf = true; }
if (bFlankenwechselTasterAuf && !bSchonGedruecktAuf) { if ( (millis()-ulTimeTasterAuf) > ENTPRELLZEIT_IN_MS ) { // ist der Taster immer noch gedrückt? if (digitalRead(TASTER_AUFWAERTS_PIN) == LOW) { if (iZahl < 9999) { iZahl++; } bSchonGedruecktAuf = true; } } }
if (digitalRead(TASTER_AUFWAERTS_PIN) == HIGH) { // bFlankenwechselTasterAuf = false; bSchonGedruecktAuf = false; } }
void PruefeAufTastendruckAbwaerts() { if ((digitalRead(TASTER_ABWAERTS_PIN) == LOW) && !bFlankenwechselTasterAb) { ulTimeTasterAb = millis(); bFlankenwechselTasterAb = true; }
if (bFlankenwechselTasterAb && !bSchonGedruecktAb) { if ( (millis()-ulTimeTasterAb) > ENTPRELLZEIT_IN_MS ) { // ist der Taster immer noch gedrückt? if (digitalRead(TASTER_ABWAERTS_PIN) == LOW) { if (iZahl > 0) { iZahl--; } bSchonGedruecktAb = true; } } }
if (digitalRead(TASTER_ABWAERTS_PIN) == HIGH) { bFlankenwechselTasterAb = false; bSchonGedruecktAb = false; } }
void siebenseg(int zahl) { #define MS_FOR_FPS 5 byte einer = zahl % 10; byte zehner = (zahl / 10) % 10; byte hunderter = (zahl / 100) % 10; byte tausender = (zahl / 1000) % 10; digitalWrite(ModulPins[3], LOW); for (int i = 0; i < 8; i++) { digitalWrite(SegPins[i], !Zahlen[einer][i]); } delay(MS_FOR_FPS); digitalWrite(ModulPins[3], HIGH);
digitalWrite(ModulPins[2], LOW); for (int i = 0; i < 8; i++) { digitalWrite(SegPins[i], !Zahlen[zehner][i]); } delay(MS_FOR_FPS); digitalWrite(ModulPins[2], HIGH);
digitalWrite(ModulPins[1], LOW); for (int i = 0; i < 8; i++) digitalWrite(SegPins[i], !Zahlen[hunderter][i]); delay(MS_FOR_FPS); digitalWrite(ModulPins[1], HIGH);
digitalWrite(ModulPins[0], LOW); for (int i = 0; i < 8; i++) { digitalWrite(SegPins[i], !Zahlen[tausender][i]); } delay(MS_FOR_FPS); digitalWrite(ModulPins[0], HIGH); }
void loop() { PruefeAufTastendruckAufwaerts(); PruefeAufTastendruckAbwaerts(); siebenseg(iZahl); }
Die Funktion siebenseg zerlegt die Zahl im Übergabeparameter in Einer, Zehner, Hunderter und Tausender. Für jede einzelne Anzeige entsteht also ein Wert zwischen 0-9. Danach wird dieser Wert einzeln auf die jeweilig aktiv geschaltete Anzeige ausgegeben, der Wert also auf den "Datenbus" gesetzt und die gemeinsame Anode (oder Kath.) durchgeschaltet.
Das Ganze wird "etwas" eingebremst, gerade so das wir kein Flackern der Anzeige sehen, aber die Anzeige auch nicht mit hunderten kHz überfahren. Da wir die Tastendruckerkennung umgeschrieben haben, kann hier auch ein delay dafür genommen werden ohne das der Tastendruck nicht oder schlechter erkannt bzw. ein Flankenwechsel am Taster-Eingang nicht erfasst wird.
Den Code für die Zusatzaufgabe "Uhrzeit" lasse ich weg, da er sich nur noch um einen "Zähler", der im Minutentakt hochgezählt wird, unterscheidet. Bei 23 erfolgt der Überlauf der ersten beiden Stellen auf 00, bei 59 für die letzten beiden mit Übertrag auf die ersten beiden Stellen. Die beiden Tasterroutinen incrementieren dann auf der Minutenstelle mit 1, auf der Stundenseite mit 100, jeweils bis zum Überlauf.
Gruß Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
05.02.2020, 15:24
(Dieser Beitrag wurde zuletzt bearbeitet: 16.06.2022, 10:33 von Bernhard45.)
Hallo zusammen,
hier nun die Lektion 4 unseres Kurses.
Bevor wir uns analogen Signalen und deren Verarbeitung und Erzeugung in einem Prozessor widmen, wollen wir
uns zum "Bergfest" des Kurses nochmal kurz mit der digitalen I/O, Interrupts und mit der "Vermessung" von Digitalsignalen beschäftigen.
Als Hausaufgabe und im Zusatz kommt eine erste, für den Radiobastler interessante und nicht unnütze Anwendung auf den Tagesplan!
Download Lektion4.pdf
Lektion4.pdf (Größe: 805,78 KB / Downloads: 69)
Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
25.02.2020, 01:20
(Dieser Beitrag wurde zuletzt bearbeitet: 16.06.2022, 10:51 von Bernhard45.)
Hallo zusammen,
kommen wir also zu weiteren Musterlösungen:
Viele sind in der Hausaufgabe auf die richtige Lösung bei Frequenzmessung und Frequenzzählung gekommen.
Eine Frequenzmessung könnte man mit den Arduino-Bibliotheken ganz einfach so umsetzen.
PHP-Code: #define MESS_PIN 2
float Frequenz; float DutyCycle; unsigned long ulHighPulse; unsigned long ulLowPulse; unsigned long ulPeriode;
void setup() { Serial.begin(9600); pinMode(MESS_PIN, INPUT); }
void loop() { ulHighPulse = pulseIn(MESS_PIN, HIGH); ulLowPulse = pulseIn(MESS_PIN, LOW); ulPeriode = ulHighPulse + ulLowPulse; DutyCycle = (ulHighPulse / (float) ulPeriode) * 100.0; Frequenz = 1000000 / (float) ulPeriode;
delay(1000); Serial.print(Frequenz); Serial.print("Hz, Duty: "); Serial.println (DutyCycle);
}
Mit dem pulseIn-Befehl wird uns die Sache sehr einfach gemacht, dieser Weg eignet sich sehr gut für sehr niedrige Frequenzen zum Beispiel im Audiobereich. Für die Frequenzzählung können wir aber auch die Interruptfunktionalität nutzen:
PHP-Code: // Frequenzzählung per Interrupt, Grenzfreq ca. 150 kHz #define INTERRUPT_PIN 2 volatile unsigned long ulCount = 0;
void setup() { Serial.begin(9600); interrupts(); pinMode(INTERRUPT_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), ISR_funktion, RISING); }
void ISR_funktion() { //Serial.println("Interrupt an Pin 2 ausgelöst"); ulCount++; }
unsigned long ulTime = 0;
void loop() { if (millis()-ulTime>1000) { noInterrupts(); Serial.print("Frequenz = "); Serial.print(ulCount); Serial.println(" Hz"); ulCount = 0; ulTime=millis(); interrupts(); } }
Der Controller kann pro Sekunde allerdings nur eine gewisse Anzahl an ISRs bearbeiten, der ganze "Wasserkopf" daran sorgt dafür das wir ohne externen Vorteiler nicht viel höher als knapp unter 150 kHz kommen.
Verzichten wir auf die ganzen High-Level Bibliotheken der Arduino Welt und setzen auf direkte Registerprogrammierung, dann stehen uns ganz andere Möglichkeiten zur Verfügung, nämlich die gesamten Timer/Counter des ATmegas. Hier mal eine alte C++ Implementierung eines Frequenzzählers mit der wir ohne externen Vorteiler schon auf ungefähr 7.4 MHz kommen.
Frequenzzaehler.ino.txt (Größe: 18,48 KB / Downloads: 14)
PHP-Code: // Frequenzzählerklasse für Arduino/Teensy // Arduino Nano/Mega bis ca. 7.4 MHz
class FrequenzzaehlerKlasse { public: static void start(uint16_t msec); static uint8_t ready(void); static uint32_t get(void); static void stop(void); };
// Arduino Mega #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) // #define COUNTER_USE_TIMER1 // T1 is not connected // #define COUNTER_USE_TIMER3 // T3 is not connected // #define COUNTER_USE_TIMER4 // T4 is not connected #define COUNTER_USE_TIMER5 // T5 is pin 47 #define TIMER_USE_TIMER2
// Teensy 3.0 & 3.1 & LC #elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) #define COUNTER_USE_LPTMR // LPTMR is pin 13 (has LED connected) #define TIMER_USE_INTERVALTIMER
// Teensy 2.0 #elif defined(__AVR_ATmega32U4__) #define COUNTER_USE_TIMER1 // T1 is pin 11 (has LED connected) #define TIMER_USE_TIMER4H
// Teensy++ 1.0 & 2.0 #elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) #define COUNTER_USE_TIMER1 // T1 is pin 6 (has LED connected) //#define COUNTER_USE_TIMER3 // T3 is pin 13 #define TIMER_USE_TIMER2
// Sanguino #elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) #define COUNTER_USE_TIMER1 // T1 is pin 1 #define TIMER_USE_TIMER2
// Arduino Duemilanove, Diecimila, LilyPad, Mini, Fio, etc #elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) #define COUNTER_USE_TIMER1 // T1 is pin 5 #define TIMER_USE_TIMER2
#else #error "Unknown chip, please edit me with timer+counter definitions"
#endif
/**********************************************/ /* Counter Hardware Abstraction */ /**********************************************/ #if defined(COUNTER_USE_LPTMR) // 16 bit LPTMR on Freescale Kinetis
static inline void counter_init(void) { SIM_SCGC5 |= SIM_SCGC5_LPTIMER; LPTMR0_CSR = 0; LPTMR0_PSR = 0b00000100; // bypass prescaler/filter LPTMR0_CMR = 0xFFFF; LPTMR0_CSR = 0b00100110; // counter, input=alt2, free running mode CORE_PIN13_CONFIG = PORT_PCR_MUX(3); }
static inline void counter_start(void) { LPTMR0_CSR = 0b00100111; // enable }
static inline void counter_shutdown(void) { LPTMR0_CSR = 0; }
static inline uint16_t counter_read(void) { LPTMR0_CNR = 0; // writing cause sync with hardware return LPTMR0_CNR; }
static inline uint8_t counter_overflow(void) { return (LPTMR0_CSR & 0x80) ? 1 : 0; }
static inline void counter_overflow_reset(void) { LPTMR0_CSR = 0b10100111; }
#elif defined(COUNTER_USE_TIMER1) // 16 bit Timer 1 on Atmel AVR
static uint8_t saveTCCR1A, saveTCCR1B;
static inline void counter_init(void) { saveTCCR1A = TCCR1A; saveTCCR1B = TCCR1B; TCCR1B = 0; TCCR1A = 0; TCNT1 = 0; TIFR1 = (1<<TOV1); TIMSK1 = 0; }
static inline void counter_start(void) { TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10); }
static inline void counter_shutdown(void) { TCCR1B = 0; TCCR1A = saveTCCR1A; TCCR1B = saveTCCR1B; }
static inline uint16_t counter_read(void) { return TCNT1; }
static inline uint8_t counter_overflow(void) { return TIFR1 & (1<<TOV1); }
static inline void counter_overflow_reset(void) { TIFR1 = (1<<TOV1); }
#elif defined(COUNTER_USE_TIMER3) // 16 bit Timer 3 on Atmel AVR
static uint8_t saveTCCR3A, saveTCCR3B;
static inline void counter_init(void) { saveTCCR3A = TCCR3A; saveTCCR3B = TCCR3B; TCCR3B = 0; TCCR3A = 0; TCNT3 = 0; TIFR3 = (1<<TOV3); TIMSK3 = 0; }
static inline void counter_start(void) { TCCR3B = (1<<CS32) | (1<<CS31) | (1<<CS30); }
static inline void counter_shutdown(void) { TCCR3B = 0; TCCR3A = saveTCCR3A; TCCR3B = saveTCCR3B; }
static inline uint16_t counter_read(void) { return TCNT3; }
static inline uint8_t counter_overflow(void) { return TIFR3 & (1<<TOV3); }
static inline void counter_overflow_reset(void) { TIFR3 = (1<<TOV3); }
#elif defined(COUNTER_USE_TIMER4) // 16 bit Timer 4 on Atmel AVR
static uint8_t saveTCCR4A, saveTCCR4B;
static inline void counter_init(void) { saveTCCR4A = TCCR4A; saveTCCR4B = TCCR4B; TCCR4B = 0; TCCR4A = 0; TCNT4 = 0; TIFR4 = (1<<TOV4); TIMSK4 = 0; }
static inline void counter_start(void) { TCCR4B = (1<<CS42) | (1<<CS41) | (1<<CS40); }
static inline void counter_shutdown(void) { TCCR4B = 0; TCCR4A = saveTCCR4A; TCCR4B = saveTCCR4B; }
static inline uint16_t counter_read(void) { return TCNT4; }
static inline uint8_t counter_overflow(void) { return TIFR4 & (1<<TOV4); }
static inline void counter_overflow_reset(void) { TIFR4 = (1<<TOV4); }
#elif defined(COUNTER_USE_TIMER5) // 16 bit Timer 5 on Atmel AVR
static uint8_t saveTCCR5A, saveTCCR5B;
static inline void counter_init(void) { saveTCCR5A = TCCR5A; saveTCCR5B = TCCR5B; TCCR5B = 0; TCCR5A = 0; TCNT5 = 0; TIFR5 = (1<<TOV5); TIMSK5 = 0; }
static inline void counter_start(void) { TCCR5B = (1<<CS52) | (1<<CS51) | (1<<CS50); }
static inline void counter_shutdown(void) { TCCR5B = 0; TCCR5A = saveTCCR5A; TCCR5B = saveTCCR5B; }
static inline uint16_t counter_read(void) { return TCNT5; }
static inline uint8_t counter_overflow(void) { return TIFR5 & (1<<TOV5); }
static inline void counter_overflow_reset(void) { TIFR5 = (1<<TOV5); }
#endif // COUNTER_USE_***
/**********************************************/ /* Timer Hardware Abstraction */ /**********************************************/ #if defined(TIMER_USE_INTERVALTIMER) // Teensyduino IntervalTimer
static IntervalTimer itimer; static void timer_interrupt(void);
static inline uint16_t timer_init(uint16_t msec) { return msec; }
static inline void timer_start(void) { // IntervalTimer is capable of longer intervals, but // LPTMR can overflow. Limiting to 1 ms allows counting // up to 65.535 MHz... LPTMR on Teensy 3.1 can do 65 MHz! itimer.begin(timer_interrupt, 1000); }
static inline void timer_shutdown(void) { itimer.end(); }
#define TIMER_ISR_VECTOR timer_interrupt #ifdef ISR #undef ISR #endif #define ISR(name) void name (void)
#elif defined(TIMER_USE_TIMER2) // 8 bit Timer 2 on Atmel AVR
/* 1ms 2ms 4ms 8ms 16 MHz 128x125 256x125 256x250 1024x125 12 MHz 64x188 128x188 256x188 1024x94 //Not exact ms values: 2% error 8 MHz 64x125 128x125 256x125 256x250 4 MHz 32x125 64x125 128x125 256x125 2 MHz 8x250 32x125 64x125 128x125 1 MHz 8x125 8x250 32x125 64x125 */ #if F_CPU == 16000000L #define TIMER2_OCR2A_1MS_VAL 124 // div 125 #define TIMER2_TCCR2B_1MS_VAL (1<<CS22) | (1<<CS20) // div 128 #define TIMER2_OCR2A_2MS_VAL 124 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS22) | (1<<CS21) // div 256 #define TIMER2_OCR2A_4MS_VAL 249 // div 250 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) | (1<<CS21) // div 256 #define TIMER2_OCR2A_8MS_VAL 124 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) | (1<<CS20) // div 1024 #elif F_CPU == 12000000L #define TIMER2_OCR2A_1MS_VAL 187 // div 188 #define TIMER2_TCCR2B_1MS_VAL (1<<CS22) // div 64 #define TIMER2_OCR2A_2MS_VAL 187 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS22) | (1<<CS20) // div 128 #define TIMER2_OCR2A_4MS_VAL 187 // div 250 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) | (1<<CS21) // div 256 #define TIMER2_OCR2A_8MS_VAL 93 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) | (1<<CS20) // div 1024 #elif F_CPU == 8000000L #define TIMER2_OCR2A_1MS_VAL 124 // div 125 #define TIMER2_TCCR2B_1MS_VAL (1<<CS22) // div 64 #define TIMER2_OCR2A_2MS_VAL 124 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS22) | (1<<CS20) // div 128 #define TIMER2_OCR2A_4MS_VAL 124 // div 125 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) | (1<<CS21) // div 256 #define TIMER2_OCR2A_8MS_VAL 249 // div 250 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) // div 256 #elif F_CPU == 4000000L #define TIMER2_OCR2A_1MS_VAL 124 // div 125 #define TIMER2_TCCR2B_1MS_VAL (1<<CS21) | (1<<CS20) // div 32 #define TIMER2_OCR2A_2MS_VAL 124 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS22) // div 64 #define TIMER2_OCR2A_4MS_VAL 124 // div 125 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) | (1<<CS20) // div 128 #define TIMER2_OCR2A_8MS_VAL 124 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) // div 256 #elif F_CPU == 2000000L #define TIMER2_OCR2A_1MS_VAL 249 // div 250 #define TIMER2_TCCR2B_1MS_VAL (1<<CS21) // div 8 #define TIMER2_OCR2A_2MS_VAL 124 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS21) | (1<<CS20) // div 32 #define TIMER2_OCR2A_4MS_VAL 124 // div 125 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) // div 64 #define TIMER2_OCR2A_8MS_VAL 124 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) // div 128 #elif F_CPU == 1000000L #define TIMER2_OCR2A_1MS_VAL 124 // div 125 #define TIMER2_TCCR2B_1MS_VAL (1<<CS21) // div 8 #define TIMER2_OCR2A_2MS_VAL 249 // div 250 #define TIMER2_TCCR2B_2MS_VAL (1<<CS21) // div 8 #define TIMER2_OCR2A_4MS_VAL 124 // div 125 #define TIMER2_TCCR2B_4MS_VAL (1<<CS21) | (1<<CS20) // div 32 #define TIMER2_OCR2A_8MS_VAL 124 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) // div 64 #else #error "Clock must be 16, 12, 8, 4, 2 or 1 MHz" #endif
static uint8_t saveTCCR2A, saveTCCR2B; static uint8_t startTCCR2B;
static inline uint16_t timer_init(uint16_t msec) { uint16_t gate_len;
saveTCCR2A = TCCR2A; saveTCCR2B = TCCR2B; TCCR2B = 0; TCCR2A = (1<<WGM21); if ((msec & 7) == 0) { gate_len = msec >> 3; OCR2A = TIMER2_OCR2A_8MS_VAL; startTCCR2B = TIMER2_TCCR2B_8MS_VAL; } else if ((msec & 3) == 0) { gate_len = msec >> 2; OCR2A = TIMER2_OCR2A_4MS_VAL; startTCCR2B = TIMER2_TCCR2B_4MS_VAL; } else if ((msec & 1) == 0) { gate_len = msec >> 1; OCR2A = TIMER2_OCR2A_2MS_VAL; startTCCR2B = TIMER2_TCCR2B_2MS_VAL; } else { gate_len = msec; OCR2A = TIMER2_OCR2A_1MS_VAL; startTCCR2B = TIMER2_TCCR2B_1MS_VAL; } TIFR2 = (1<<OCF2A); TCNT2 = 0; return gate_len; }
static inline void timer_start(void) { GTCCR = (1<<PSRASY); TCCR2B = startTCCR2B; TIMSK2 = (1<<OCIE2A); }
static inline void timer_shutdown(void) { TCCR2B = 0; TIMSK2 = 0; TCCR2A = saveTCCR2A; TCCR2B = saveTCCR2B; }
#define TIMER_ISR_VECTOR TIMER2_COMPA_vect
/* There is a typical latency from the timer interrupt until the first actual line of code executes. Here is a typical compiler output of approximately 34 cycles. When starting, this same delay is used to begin counting, so the first reading will not have 34 cycles of extra measurement. Because each measurement period gates instantly after the previous, this approximate correction only affects the first measurement. If you do not define TIMER_LATENCY_CYCLES, this extra delay is skipped (saving a tiny bit of code space), and the only downside is a slight inaccuracy in the first measurement. 2 00000326 <__vector_13>: 2 326: 1f 92 push r1 2 328: 0f 92 push r0 1 32a: 0f b6 in r0, 0x3f ; 63 2 32c: 0f 92 push r0 1 32e: 11 24 eor r1, r1 2 330: ef 92 push r14 2 332: ff 92 push r15 2 334: 0f 93 push r16 2 336: 1f 93 push r17 2 338: 2f 93 push r18 2 33a: 3f 93 push r19 2 33c: 4f 93 push r20 2 33e: 5f 93 push r21 2 340: 8f 93 push r24 2 342: 9f 93 push r25 2 344: af 93 push r26 2 346: bf 93 push r27 */ #define TIMER_LATENCY_CYCLES 34
#elif defined(TIMER_USE_TIMER4H) // 10 bit "high speed" Timer 4 on Atmel AVR
#define TIMER4H_OCR4C_VAL 124 // always div 125 #if F_CPU == 16000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS43) // div 128 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS43) | (1<<CS40) // div 256 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS43) | (1<<CS41) // div 512 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS43) | (1<<CS41) | (1<<CS40) // div 1024 #elif F_CPU == 8000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS42) | (1<<CS41) | (1<<CS40) // div 64 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS43) // div 128 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS43) | (1<<CS40) // div 256 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS43) | (1<<CS41) // div 512 #elif F_CPU == 4000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS42) | (1<<CS41) // div 32 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS42) | (1<<CS41) | (1<<CS40) // div 64 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS43) // div 128 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS43) | (1<<CS40) // div 256 #elif F_CPU == 2000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS42) | (1<<CS40) // div 16 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS42) | (1<<CS41) // div 32 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS42) | (1<<CS41) | (1<<CS40) // div 64 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS43) // div 128 #elif F_CPU == 1000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS42) // div 8 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS42) | (1<<CS40) // div 16 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS42) | (1<<CS41) // div 32 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS42) | (1<<CS41) | (1<<CS40) // div 64 #else #error "Clock must be 16, 8, 4, 2 or 1 MHz" #endif
static uint8_t saveTCCR4A, saveTCCR4B, saveTCCR4C, saveTCCR4D, saveTCCR4E, saveOCR4C; static uint8_t startTCCR4B;
// input is the number of milliseconds required // output is the number of interrupts needed for that number of milliseconds static inline uint16_t timer_init(uint16_t msec) { uint16_t gate_len;
saveTCCR4A = TCCR4A; saveTCCR4B = TCCR4B; saveTCCR4C = TCCR4C; saveTCCR4D = TCCR4D; saveTCCR4E = TCCR4E; saveOCR4C = OCR4C; TCCR4B = 0; TCCR4A = 0; TCCR4C = 0; TCCR4D = 0; TCCR4E = 0; OCR4C = TIMER4H_OCR4C_VAL; if ((msec & 7) == 0) { gate_len = msec >> 3; startTCCR4B = TIMER4H_TCCR4B_8MS_VAL | (1<<PSR4); } else if ((msec & 3) == 0) { gate_len = msec >> 2; startTCCR4B = TIMER4H_TCCR4B_4MS_VAL | (1<<PSR4); } else if ((msec & 1) == 0) { gate_len = msec >> 1; startTCCR4B = TIMER4H_TCCR4B_2MS_VAL | (1<<PSR4); } else { gate_len = msec; startTCCR4B = TIMER4H_TCCR4B_1MS_VAL | (1<<PSR4); } TIFR4 = (1<<TOV4); TCNT4 = 0; return gate_len; }
static inline void timer_start(void) { TCCR4B = startTCCR4B; TIMSK4 = (1<<TOIE4); }
static inline void timer_shutdown(void) { TCCR4B = 0; TIMSK4 = 0; OCR4C = saveOCR4C; TCCR4A = saveTCCR4A; TCCR4C = saveTCCR4C; TCCR4D = saveTCCR4D; TCCR4E = saveTCCR4E; TCCR4B = saveTCCR4B; }
#define TIMER_ISR_VECTOR TIMER4_OVF_vect #define TIMER_LATENCY_CYCLES 34
#endif // TIMER_USE_***
static inline void timer_isr_latency_delay(void) { #ifdef TIMER_LATENCY_CYCLES #ifdef __AVR__ uint8_t cycles_times_3 = TIMER_LATENCY_CYCLES / 3; asm volatile( "L_%=_loop:" "subi %0, 1" "\n\t" "brne L_%=_loop" "\n\t" : "+d" (cycles_times_3) : "0" (cycles_times_3) ); #endif }
/**********************************************/ /* Board Specific Interrupts (to hog) */ /**********************************************/ static inline void disable_other_interrupts(void) { } static inline void restore_other_interrupts(void) { } #endif
static uint16_t count_msw; static uint32_t count_prev; static volatile uint32_t count_output; static volatile uint8_t count_ready; static uint16_t gate_length; static uint16_t gate_index;
/**********************************************/ /* Implementation of Class */ /**********************************************/ void FrequenzzaehlerKlasse::start(uint16_t msec) { if (msec < 1) return; gate_index = 0; count_msw = 0; count_prev = 0; count_ready = 0; counter_init(); gate_length = timer_init(msec); uint8_t status = SREG; cli(); timer_start(); timer_isr_latency_delay(); counter_start(); SREG = status; }
uint8_t FrequenzzaehlerKlasse::ready(void) { return count_ready; }
uint32_t FrequenzzaehlerKlasse::get(void) { uint32_t count; uint8_t status;
status = SREG; cli(); #if defined(TIMER_USE_TIMER2) && F_CPU == 12000000L float correct = count_output * 0.996155; count = (uint32_t) (correct+0.5); #else count = count_output; #endif count_ready = 0; SREG = status; return count; }
void FrequenzzaehlerKlasse::stop(void) { timer_shutdown(); counter_shutdown(); }
ISR(TIMER_ISR_VECTOR) { uint16_t count_lsw; uint32_t count; uint16_t index, length;
count_lsw = counter_read(); if (counter_overflow()) { counter_overflow_reset(); count_msw++; } index = gate_index + 1; length = gate_length; if (index >= length) { gate_index = 0; count = ((uint32_t)count_msw << 16) + count_lsw; count_output = count - count_prev; count_prev = count; count_ready = 1; restore_other_interrupts(); } else { if (index == length - 1) disable_other_interrupts(); gate_index = index; } }
FrequenzzaehlerKlasse FrequenzZaehler;
void setup() { Serial.begin(9600); FrequenzZaehler.start(1000); }
void loop() { if (FrequenzZaehler.ready()) { unsigned long count = FrequenzZaehler.get(); Serial.print("Frequenz = "); Serial.print(count); Serial.println(" Hz"); } //FreqCount.end(); }
Eigentlich hat der Frequenzzähler drei Dateien (Header und Cpp-Datei, Datei für eigentliches Programm), für diese Musterlösung habe ich mal alles in eine Datei gepackt.
Zuerst deklarieren wir C++ (Cpp)-typisch eine Klasse:
Zitat:class FrequenzzaehlerKlasse {
public:
static void start(uint16_t msec);
static uint8_t ready(void);
static uint32_t get(void);
static void stop(void);
};
Das neue Objekt (siehe objektorientierte Programmierung) ist also FrequenzzaehlerKlasse, von der wir später eine Instanz ableiten können.
Diese Instanz besitzt die Methoden start und stop mit denen wir die Frequenzzählung starten und anhalten können, zusätzlich geben wir beim Start die Torzeit vor.
Mit der Methode ready fragen wir beim Frequenzzähler nach, ob er schon ein neues Ergebnis hat, welches wir dann mittels der Methode get erfragen können.
Im nächsten Block haben wir ein paar Compiler-Anweisungen. Der Compiler "schaut" also nach welchen Microcontroller wir programmieren wollen. __AVR_ATmega1280__ __MK20DX128__ , …. sind alles verschiedene Microcontrollertypen. Für unser Arduino Nano mit dem ATmega328 oder ATmega168 (alt) wird also nur dieser Block in die Compilierung einbezogen:
PHP-Code: // Arduino Duemilanove, Diecimila, LilyPad, Mini, Fio, etc #elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) #define COUNTER_USE_TIMER1 // T1 is pin 5 #define TIMER_USE_TIMER2
In diesem Block wird nichts anderes gemacht, als die beiden Schlüsselwörter COUNTER_USE_TIMER1 und TIMER_USE_TIMER2
definiert. Auf diese Schlüsselwörter wird im weiteren Programm "geprüft", sie bedeuten nichts anderes, als das der Frequenzzähler den Timer1 des ATmega als Zähler und den Timer2 als Timer nutzen soll. Die zu zählende Frequenz kommt auf Pin5.
Im nächsten und größten Programmteil werden der Zähler und der Timer direkt per Registerzugriff konfiguriert und zwar für jeden einzelen Microcontrollertyp den wir oben aufgeführt haben. Nur dadurch ist diese Frequenzzählerklasse auf einer Reihe unterschiedlicher Controller nutzbar. Zusätzlich wird in dem Teil auch "geschaut", mit welcher Taktfrequenz der Controller denn eigentlich laufen soll. Wichtig für zeitkritische Codeteile. (bei einem Frequenzzähler im Prinzip ja alles )
PHP-Code: /**********************************************/ /* Counter Hardware Abstraction */ /**********************************************/ #if defined(COUNTER_USE_LPTMR) // 16 bit LPTMR on Freescale Kinetis
static inline void counter_init(void) { SIM_SCGC5 |= SIM_SCGC5_LPTIMER; LPTMR0_CSR = 0; LPTMR0_PSR = 0b00000100; // bypass prescaler/filter LPTMR0_CMR = 0xFFFF; LPTMR0_CSR = 0b00100110; // counter, input=alt2, free running mode CORE_PIN13_CONFIG = PORT_PCR_MUX(3); }
static inline void counter_start(void) { LPTMR0_CSR = 0b00100111; // enable }
static inline void counter_shutdown(void) { LPTMR0_CSR = 0; }
static inline uint16_t counter_read(void) { LPTMR0_CNR = 0; // writing cause sync with hardware return LPTMR0_CNR; }
static inline uint8_t counter_overflow(void) { return (LPTMR0_CSR & 0x80) ? 1 : 0; }
static inline void counter_overflow_reset(void) { LPTMR0_CSR = 0b10100111; }
#elif defined(COUNTER_USE_TIMER1) // 16 bit Timer 1 on Atmel AVR
static uint8_t saveTCCR1A, saveTCCR1B;
static inline void counter_init(void) { saveTCCR1A = TCCR1A; saveTCCR1B = TCCR1B; TCCR1B = 0; TCCR1A = 0; TCNT1 = 0; TIFR1 = (1<<TOV1); TIMSK1 = 0; }
static inline void counter_start(void) { TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10); }
static inline void counter_shutdown(void) { TCCR1B = 0; TCCR1A = saveTCCR1A; TCCR1B = saveTCCR1B; }
static inline uint16_t counter_read(void) { return TCNT1; }
static inline uint8_t counter_overflow(void) { return TIFR1 & (1<<TOV1); }
static inline void counter_overflow_reset(void) { TIFR1 = (1<<TOV1); }
#elif defined(COUNTER_USE_TIMER3) // 16 bit Timer 3 on Atmel AVR
static uint8_t saveTCCR3A, saveTCCR3B;
static inline void counter_init(void) { saveTCCR3A = TCCR3A; saveTCCR3B = TCCR3B; TCCR3B = 0; TCCR3A = 0; TCNT3 = 0; TIFR3 = (1<<TOV3); TIMSK3 = 0; }
static inline void counter_start(void) { TCCR3B = (1<<CS32) | (1<<CS31) | (1<<CS30); }
static inline void counter_shutdown(void) { TCCR3B = 0; TCCR3A = saveTCCR3A; TCCR3B = saveTCCR3B; }
static inline uint16_t counter_read(void) { return TCNT3; }
static inline uint8_t counter_overflow(void) { return TIFR3 & (1<<TOV3); }
static inline void counter_overflow_reset(void) { TIFR3 = (1<<TOV3); }
#elif defined(COUNTER_USE_TIMER4) // 16 bit Timer 4 on Atmel AVR
static uint8_t saveTCCR4A, saveTCCR4B;
static inline void counter_init(void) { saveTCCR4A = TCCR4A; saveTCCR4B = TCCR4B; TCCR4B = 0; TCCR4A = 0; TCNT4 = 0; TIFR4 = (1<<TOV4); TIMSK4 = 0; }
static inline void counter_start(void) { TCCR4B = (1<<CS42) | (1<<CS41) | (1<<CS40); }
static inline void counter_shutdown(void) { TCCR4B = 0; TCCR4A = saveTCCR4A; TCCR4B = saveTCCR4B; }
static inline uint16_t counter_read(void) { return TCNT4; }
static inline uint8_t counter_overflow(void) { return TIFR4 & (1<<TOV4); }
static inline void counter_overflow_reset(void) { TIFR4 = (1<<TOV4); }
#elif defined(COUNTER_USE_TIMER5) // 16 bit Timer 5 on Atmel AVR
static uint8_t saveTCCR5A, saveTCCR5B;
static inline void counter_init(void) { saveTCCR5A = TCCR5A; saveTCCR5B = TCCR5B; TCCR5B = 0; TCCR5A = 0; TCNT5 = 0; TIFR5 = (1<<TOV5); TIMSK5 = 0; }
static inline void counter_start(void) { TCCR5B = (1<<CS52) | (1<<CS51) | (1<<CS50); }
static inline void counter_shutdown(void) { TCCR5B = 0; TCCR5A = saveTCCR5A; TCCR5B = saveTCCR5B; }
static inline uint16_t counter_read(void) { return TCNT5; }
static inline uint8_t counter_overflow(void) { return TIFR5 & (1<<TOV5); }
static inline void counter_overflow_reset(void) { TIFR5 = (1<<TOV5); }
#endif // COUNTER_USE_***
/**********************************************/ /* Timer Hardware Abstraction */ /**********************************************/ #if defined(TIMER_USE_INTERVALTIMER) // Teensyduino IntervalTimer
static IntervalTimer itimer; static void timer_interrupt(void);
static inline uint16_t timer_init(uint16_t msec) { return msec; }
static inline void timer_start(void) { // IntervalTimer is capable of longer intervals, but // LPTMR can overflow. Limiting to 1 ms allows counting // up to 65.535 MHz... LPTMR on Teensy 3.1 can do 65 MHz! itimer.begin(timer_interrupt, 1000); }
static inline void timer_shutdown(void) { itimer.end(); }
#define TIMER_ISR_VECTOR timer_interrupt #ifdef ISR #undef ISR #endif #define ISR(name) void name (void)
#elif defined(TIMER_USE_TIMER2) // 8 bit Timer 2 on Atmel AVR
/* 1ms 2ms 4ms 8ms 16 MHz 128x125 256x125 256x250 1024x125 12 MHz 64x188 128x188 256x188 1024x94 //Not exact ms values: 2% error 8 MHz 64x125 128x125 256x125 256x250 4 MHz 32x125 64x125 128x125 256x125 2 MHz 8x250 32x125 64x125 128x125 1 MHz 8x125 8x250 32x125 64x125 */ #if F_CPU == 16000000L #define TIMER2_OCR2A_1MS_VAL 124 // div 125 #define TIMER2_TCCR2B_1MS_VAL (1<<CS22) | (1<<CS20) // div 128 #define TIMER2_OCR2A_2MS_VAL 124 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS22) | (1<<CS21) // div 256 #define TIMER2_OCR2A_4MS_VAL 249 // div 250 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) | (1<<CS21) // div 256 #define TIMER2_OCR2A_8MS_VAL 124 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) | (1<<CS20) // div 1024 #elif F_CPU == 12000000L #define TIMER2_OCR2A_1MS_VAL 187 // div 188 #define TIMER2_TCCR2B_1MS_VAL (1<<CS22) // div 64 #define TIMER2_OCR2A_2MS_VAL 187 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS22) | (1<<CS20) // div 128 #define TIMER2_OCR2A_4MS_VAL 187 // div 250 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) | (1<<CS21) // div 256 #define TIMER2_OCR2A_8MS_VAL 93 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) | (1<<CS20) // div 1024 #elif F_CPU == 8000000L #define TIMER2_OCR2A_1MS_VAL 124 // div 125 #define TIMER2_TCCR2B_1MS_VAL (1<<CS22) // div 64 #define TIMER2_OCR2A_2MS_VAL 124 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS22) | (1<<CS20) // div 128 #define TIMER2_OCR2A_4MS_VAL 124 // div 125 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) | (1<<CS21) // div 256 #define TIMER2_OCR2A_8MS_VAL 249 // div 250 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) // div 256 #elif F_CPU == 4000000L #define TIMER2_OCR2A_1MS_VAL 124 // div 125 #define TIMER2_TCCR2B_1MS_VAL (1<<CS21) | (1<<CS20) // div 32 #define TIMER2_OCR2A_2MS_VAL 124 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS22) // div 64 #define TIMER2_OCR2A_4MS_VAL 124 // div 125 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) | (1<<CS20) // div 128 #define TIMER2_OCR2A_8MS_VAL 124 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) // div 256 #elif F_CPU == 2000000L #define TIMER2_OCR2A_1MS_VAL 249 // div 250 #define TIMER2_TCCR2B_1MS_VAL (1<<CS21) // div 8 #define TIMER2_OCR2A_2MS_VAL 124 // div 125 #define TIMER2_TCCR2B_2MS_VAL (1<<CS21) | (1<<CS20) // div 32 #define TIMER2_OCR2A_4MS_VAL 124 // div 125 #define TIMER2_TCCR2B_4MS_VAL (1<<CS22) // div 64 #define TIMER2_OCR2A_8MS_VAL 124 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) | (1<<CS21) // div 128 #elif F_CPU == 1000000L #define TIMER2_OCR2A_1MS_VAL 124 // div 125 #define TIMER2_TCCR2B_1MS_VAL (1<<CS21) // div 8 #define TIMER2_OCR2A_2MS_VAL 249 // div 250 #define TIMER2_TCCR2B_2MS_VAL (1<<CS21) // div 8 #define TIMER2_OCR2A_4MS_VAL 124 // div 125 #define TIMER2_TCCR2B_4MS_VAL (1<<CS21) | (1<<CS20) // div 32 #define TIMER2_OCR2A_8MS_VAL 124 // div 125 #define TIMER2_TCCR2B_8MS_VAL (1<<CS22) // div 64 #else #error "Clock must be 16, 12, 8, 4, 2 or 1 MHz" #endif
static uint8_t saveTCCR2A, saveTCCR2B; static uint8_t startTCCR2B;
static inline uint16_t timer_init(uint16_t msec) { uint16_t gate_len;
saveTCCR2A = TCCR2A; saveTCCR2B = TCCR2B; TCCR2B = 0; TCCR2A = (1<<WGM21); if ((msec & 7) == 0) { gate_len = msec >> 3; OCR2A = TIMER2_OCR2A_8MS_VAL; startTCCR2B = TIMER2_TCCR2B_8MS_VAL; } else if ((msec & 3) == 0) { gate_len = msec >> 2; OCR2A = TIMER2_OCR2A_4MS_VAL; startTCCR2B = TIMER2_TCCR2B_4MS_VAL; } else if ((msec & 1) == 0) { gate_len = msec >> 1; OCR2A = TIMER2_OCR2A_2MS_VAL; startTCCR2B = TIMER2_TCCR2B_2MS_VAL; } else { gate_len = msec; OCR2A = TIMER2_OCR2A_1MS_VAL; startTCCR2B = TIMER2_TCCR2B_1MS_VAL; } TIFR2 = (1<<OCF2A); TCNT2 = 0; return gate_len; }
static inline void timer_start(void) { GTCCR = (1<<PSRASY); TCCR2B = startTCCR2B; TIMSK2 = (1<<OCIE2A); }
static inline void timer_shutdown(void) { TCCR2B = 0; TIMSK2 = 0; TCCR2A = saveTCCR2A; TCCR2B = saveTCCR2B; }
#define TIMER_ISR_VECTOR TIMER2_COMPA_vect
/* There is a typical latency from the timer interrupt until the first actual line of code executes. Here is a typical compiler output of approximately 34 cycles. When starting, this same delay is used to begin counting, so the first reading will not have 34 cycles of extra measurement. Because each measurement period gates instantly after the previous, this approximate correction only affects the first measurement. If you do not define TIMER_LATENCY_CYCLES, this extra delay is skipped (saving a tiny bit of code space), and the only downside is a slight inaccuracy in the first measurement. 2 00000326 <__vector_13>: 2 326: 1f 92 push r1 2 328: 0f 92 push r0 1 32a: 0f b6 in r0, 0x3f ; 63 2 32c: 0f 92 push r0 1 32e: 11 24 eor r1, r1 2 330: ef 92 push r14 2 332: ff 92 push r15 2 334: 0f 93 push r16 2 336: 1f 93 push r17 2 338: 2f 93 push r18 2 33a: 3f 93 push r19 2 33c: 4f 93 push r20 2 33e: 5f 93 push r21 2 340: 8f 93 push r24 2 342: 9f 93 push r25 2 344: af 93 push r26 2 346: bf 93 push r27 */ #define TIMER_LATENCY_CYCLES 34
#elif defined(TIMER_USE_TIMER4H) // 10 bit "high speed" Timer 4 on Atmel AVR
#define TIMER4H_OCR4C_VAL 124 // always div 125 #if F_CPU == 16000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS43) // div 128 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS43) | (1<<CS40) // div 256 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS43) | (1<<CS41) // div 512 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS43) | (1<<CS41) | (1<<CS40) // div 1024 #elif F_CPU == 8000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS42) | (1<<CS41) | (1<<CS40) // div 64 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS43) // div 128 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS43) | (1<<CS40) // div 256 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS43) | (1<<CS41) // div 512 #elif F_CPU == 4000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS42) | (1<<CS41) // div 32 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS42) | (1<<CS41) | (1<<CS40) // div 64 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS43) // div 128 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS43) | (1<<CS40) // div 256 #elif F_CPU == 2000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS42) | (1<<CS40) // div 16 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS42) | (1<<CS41) // div 32 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS42) | (1<<CS41) | (1<<CS40) // div 64 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS43) // div 128 #elif F_CPU == 1000000L #define TIMER4H_TCCR4B_1MS_VAL (1<<CS42) // div 8 #define TIMER4H_TCCR4B_2MS_VAL (1<<CS42) | (1<<CS40) // div 16 #define TIMER4H_TCCR4B_4MS_VAL (1<<CS42) | (1<<CS41) // div 32 #define TIMER4H_TCCR4B_8MS_VAL (1<<CS42) | (1<<CS41) | (1<<CS40) // div 64 #else #error "Clock must be 16, 8, 4, 2 or 1 MHz" #endif
static uint8_t saveTCCR4A, saveTCCR4B, saveTCCR4C, saveTCCR4D, saveTCCR4E, saveOCR4C; static uint8_t startTCCR4B;
// input is the number of milliseconds required // output is the number of interrupts needed for that number of milliseconds static inline uint16_t timer_init(uint16_t msec) { uint16_t gate_len;
saveTCCR4A = TCCR4A; saveTCCR4B = TCCR4B; saveTCCR4C = TCCR4C; saveTCCR4D = TCCR4D; saveTCCR4E = TCCR4E; saveOCR4C = OCR4C; TCCR4B = 0; TCCR4A = 0; TCCR4C = 0; TCCR4D = 0; TCCR4E = 0; OCR4C = TIMER4H_OCR4C_VAL; if ((msec & 7) == 0) { gate_len = msec >> 3; startTCCR4B = TIMER4H_TCCR4B_8MS_VAL | (1<<PSR4); } else if ((msec & 3) == 0) { gate_len = msec >> 2; startTCCR4B = TIMER4H_TCCR4B_4MS_VAL | (1<<PSR4); } else if ((msec & 1) == 0) { gate_len = msec >> 1; startTCCR4B = TIMER4H_TCCR4B_2MS_VAL | (1<<PSR4); } else { gate_len = msec; startTCCR4B = TIMER4H_TCCR4B_1MS_VAL | (1<<PSR4); } TIFR4 = (1<<TOV4); TCNT4 = 0; return gate_len; }
static inline void timer_start(void) { TCCR4B = startTCCR4B; TIMSK4 = (1<<TOIE4); }
static inline void timer_shutdown(void) { TCCR4B = 0; TIMSK4 = 0; OCR4C = saveOCR4C; TCCR4A = saveTCCR4A; TCCR4C = saveTCCR4C; TCCR4D = saveTCCR4D; TCCR4E = saveTCCR4E; TCCR4B = saveTCCR4B; }
#define TIMER_ISR_VECTOR TIMER4_OVF_vect #define TIMER_LATENCY_CYCLES 34
#endif // TIMER_USE_***
static inline void timer_isr_latency_delay(void) { #ifdef TIMER_LATENCY_CYCLES #ifdef __AVR__ uint8_t cycles_times_3 = TIMER_LATENCY_CYCLES / 3; asm volatile( "L_%=_loop:" "subi %0, 1" "\n\t" "brne L_%=_loop" "\n\t" : "+d" (cycles_times_3) : "0" (cycles_times_3) ); #endif }
/**********************************************/ /* Board Specific Interrupts (to hog) */ /**********************************************/ static inline void disable_other_interrupts(void) { } static inline void restore_other_interrupts(void) { } #endif
Oben haben wir die Klasse "FrequenzzaehlerKlasse" deklariert, also angekündigt, nun muss sie auch definiert und implementiert werden.
PHP-Code: /**********************************************/ /* Implementation of Class */ /**********************************************/ void FrequenzzaehlerKlasse::start(uint16_t msec) { if (msec < 1) return; gate_index = 0; count_msw = 0; count_prev = 0; count_ready = 0; counter_init(); gate_length = timer_init(msec); uint8_t status = SREG; cli(); timer_start(); timer_isr_latency_delay(); counter_start(); SREG = status; }
uint8_t FrequenzzaehlerKlasse::ready(void) { return count_ready; }
uint32_t FrequenzzaehlerKlasse::get(void) { uint32_t count; uint8_t status;
status = SREG; cli(); #if defined(TIMER_USE_TIMER2) && F_CPU == 12000000L float correct = count_output * 0.996155; count = (uint32_t) (correct+0.5); #else count = count_output; #endif count_ready = 0; SREG = status; return count; }
void FrequenzzaehlerKlasse::stop(void) { timer_shutdown(); counter_shutdown(); }
ISR(TIMER_ISR_VECTOR) { uint16_t count_lsw; uint32_t count; uint16_t index, length;
count_lsw = counter_read(); if (counter_overflow()) { counter_overflow_reset(); count_msw++; } index = gate_index + 1; length = gate_length; if (index >= length) { gate_index = 0; count = ((uint32_t)count_msw << 16) + count_lsw; count_output = count - count_prev; count_prev = count; count_ready = 1; restore_other_interrupts(); } else { if (index == length - 1) disable_other_interrupts(); gate_index = index; } }
Und zwar jede Methode für sich, also start, stop, ready und get.
Das eigentliche Frequenzzählerprogramm steht dann in diesen wenigen Zeilen.
PHP-Code: FrequenzzaehlerKlasse FrequenzZaehler;
void setup() { Serial.begin(9600); FrequenzZaehler.start(1000); }
void loop() { if (FrequenzZaehler.ready()) { unsigned long count = FrequenzZaehler.get(); Serial.print("Frequenz = "); Serial.print(count); Serial.println(" Hz"); } //FreqCount.end(); }
Hier ist wieder die Arduino-typische Struktur eines Programms erkennbar, setup und loop.
Als erstes bilden wird eine Instanz der FrequenzzaehlerKlasse mit dem Namen FrequenzZaehler. Mit dieser Instanz arbeiten wir.
In setup() wird neben der seriellen Schnittstelle (übrigens auch eine C++-Klasse!) der FrequenzZaehler mit der Torzeit 1000ms gestartet.
In der Dauerschleife loop wird solange gewartet, bis der Frequenzzaehler einen neuen Wert ermittelt hat. Dieser wird dann mit get abgeholt und auf die serielle Schnittstelle geschickt.
Zugegeben, würde man den Frequenzzaehler nicht für so viele unterschiedliche Microcontroller implementieren, wäre die Datei viel viel kleiner. Wer will kann die überflüssigen Teile (also die anderen CPUs) rauswerfen und einen Frequenzzähler nur für den Arduino Nano bzw. für Arduinos mit ATmega328 und 168 erstellen. Zwei sehr kurze Implementierungen per direkter Registerprogrammierung habe ich von Kursteilnehmern schon gesehen! Ich bin angenehm überrascht und es zeigt doch das selbst Programmieranfänger mit beiden Wegen (Arduino-Highlevel/Register) klar kommen und wunderbare Ergebnisse erzielen können. Das Ergebnis zeigt aber auch, wie uns die Arduino-Bibliotheken funktionell einschränken und welche Leistungseinbußen man hinnehmen muss. Dafür ist "Arduino" natürlich leichter erlernbar, weil die Befehle fast im Klartext sind und keine "kryptischen" Registerbezeichnungen enthalten. Das Datenblatt des Controllers braucht man in der Arduino-Welt deshalb kaum oder gar nicht.
Sind wir mit der Aufgabe schon fertig? Nein, denn
1. Die ZF-Korrektur : Von der Variablen Count oben im Programm muss man die ZF nur noch addieren/subtrahieren!
2. Der Controller kann immer noch nur TTL-Pulse zählen. Wir brauchen also einen HF-Verstärker.
Für andere (fertige) Frequenzzähler habe ich hier im Forum schon einige solche Vorverstärker mal gezeigt, zum Beispiel diese hier:
Vorverstärker1.png (Größe: 121,33 KB / Downloads: 844)
Vorverstärker2.png (Größe: 134,8 KB / Downloads: 845)
Es gibt unzählige andere Schaltungen die ebenso geeignet sind. Mit so einem Vorverstärker ist dann die Aufnahme der "HF" am Mischer eines Radios kein Problem mehr und man kann nach Korrektur um den Betrag der ZF die eingestellte Empfangsfrequenz ablesen.
3. Die Ansteuerung an das 7.Seg-Modul läuft analog der Musterlösung von Lektion 3, da aber jeder eine eigene Anschaltung des Moduls vorgenommen hat, belasse ich es in dieser Musterlösung bei der Ausgabe auf der seriellen Schnittstelle.
In Lektion 5 kümmern wir uns um "Analogsignale".
Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
03.03.2020, 19:54
(Dieser Beitrag wurde zuletzt bearbeitet: 16.06.2022, 11:43 von Bernhard45.)
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 5 ADC / DAC, analoge Signale
Hier und heute nun die Lektion 5 unseres Einsteigerkurses:
Lektion5.pdf (Größe: 989,89 KB / Downloads: 58)
Endlich toben wir uns mit Analogsignalen richtig aus!
Diesmal gibt es auch nur eine Zusatzaufgabe in der wir den Arduino Nano als Signalprozessor nutzen wollen, die zu bearbeitende Audiodatei findet ihr hier:
NFgestoert120Hz_SR8kHz.zip (Größe: 229,11 KB / Downloads: 16)
Viel Spaß mit Lektion 5 und dem uC-Basteln,
Keep coding
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
11.04.2020, 01:59
(Dieser Beitrag wurde zuletzt bearbeitet: 16.06.2022, 11:04 von Bernhard45.)
Hallo zusammen,
Fangen wir mit der Aufgabe 1.1 aus Lektion 5 an. Hier nochmal der Aufgabentext:
1.1 . Kondensatormessgerät
Modifiziere das oben gezeigte Programm zur Kapazitätsmessung von Kondensatoren dahingehend, dass auch kleinere Kapazitäten noch mit ausreichender Genauigkeit ermittelt werden können.
Zur Erinnerung nochmal das Kapazitätsmessprogramm aus dem PDF der 5. Lektion.
PHP-Code: #define ADC_PIN A0 // ueber diesen Pin ueberwachen wir die Aufladung des Kondensators #define LADE_PIN 13 // ueber diesen Pin laden wir den Kondensator auf #define ENTLADE_PIN 11 // ueber diesen Pin entladen wir den Kondensator #define LADEWIDERSTAND 10000.0F // 10k Ladewiderstand
unsigned long ulStartzeit; unsigned long ulLadeZeit; float microFarads; float nanoFarads;
void setup(){ pinMode(ADC_PIN, INPUT); pinMode(LADE_PIN, OUTPUT); digitalWrite(LADE_PIN, LOW);
Serial.begin(9600); }
void loop(){ digitalWrite(LADE_PIN, HIGH); // Ladepin auf High, Kondensator faengt an zu laden ulStartzeit = millis(); // notiere Startzeitpunkt des Ladens
while(analogRead(ADC_PIN) < 648){ // lade bis 63.2% von 1023 (5V) }
ulLadeZeit= millis() - ulStartzeit; // wie lange das das aufladen auf 63.2% gedauert? // convert milliseconds to seconds ( 10^-3 ) and Farads to microFarads ( 10^6 ), net 10^3 (1000) microFarads = ((float)ulLadeZeit / LADEWIDERSTAND) * 1000; Serial.print(ulLadeZeit); Serial.print(" mS ");
if (microFarads > 1){ Serial.print((long)microFarads); Serial.println(" microFarad"); } else { // wenn Wert kleiner als 1 uF, konvertiere nach nF nanoFarads = microFarads * 1000.0; Serial.print((long)nanoFarads); Serial.println(" nanoFarad"); }
// Kondensator entladen digitalWrite(LADE_PIN, LOW); // Ladepin aus pinMode(ENTLADE_PIN, OUTPUT); // Entladepin auf Ausgang digitalWrite(ENTLADE_PIN, LOW); // und low => also auf Masse while(analogRead(ADC_PIN) > 0){ // entlade solange bis Kondensator leer ist }
pinMode(ENTLADE_PIN, INPUT); // Entladung abschalten }
Wir sehen, dass wir im Hauptprogramm zunächst das Laden des Kondensators über den definierten Strom eines Ladewiderstandes bewirken. Gleichzeitig merken wir uns den Zeitpunkt, wann wir das Aufladen des Kondensators starten. In der While-Schleife prüfen wir kontinuierlich den Ladezustand des Kondensators und zwar solange, bis 63,2% "Füllstand" erreicht ist. Danach notieren wir uns wieder die Zeit, um die Zeitdauer des Ladevorgangs zu bestimmen. Anschließend erfolgt die Berechnung der Kapazität und die formatierte Ausgabe auf der Seriellen Schnittstelle. Ist das geschehen, wird der Kondensator über den Entladepin mit einem Entladewiderstand geleert. Danach erfolgt eine neue Messung und so weiter. Wird ein noch geladener Kondensator angeschlossen (denkt dran was der ATmega so maximal am Pin sehen darf!), so ist die erste Messung zu ignorieren. Durch Umstellung des Codes (Vorziehen des Entladevorgangs) könnte man dies auch verhindern.
Hier mal eine Beispielmessung für ein paar Kondensatoren.
10uF Elko:
10uF.JPG (Größe: 16,22 KB / Downloads: 623)
220uF Elko:
220uF.JPG (Größe: 20,14 KB / Downloads: 627)
Soweit so gut für "große" Kondensatoren, die für das Aufladen etwas Zeit brauchen.
Bei kleineren Kondensatoren sieht die Sache anders aus:
0.47 uF:
0.47uF.JPG (Größe: 15,15 KB / Downloads: 627)
Wie wir sehen, wird bei kleinen Kondensatoren die Ladezeit auch immer kleiner und wir kommen langsam an die Auflösungsgrenze unserer Zeitmessung mit millis() !
0.1 uF:
0.1uFvorher.JPG (Größe: 13,92 KB / Downloads: 627)
Jetzt kann sich der Wert aber stark unterscheiden. Wir haben die Auflösungsgrenze für die Zeitmessung erreicht und nichts geht mehr.
Hier kommt jetzt der modifizierte Quellcode aus Aufgabe 1.1 zum Zuge. Viele Kursteilnehmer haben das Problem mit der Zeitauflösung richtig erkannt.
PHP-Code: #define ADC_PIN A0 // ueber diesen Pin ueberwachen wir die Aufladung des Kondensators #define LADE_PIN 13 // ueber diesen Pin laden wir den Kondensator auf #define ENTLADE_PIN 11 // ueber diesen Pin entladen wir den Kondensator #define LADEWIDERSTAND 9938.0F // 10k Ladewiderstand, ausgemessen !!! #define ZEIT_NULLMESSUNG 112 // Messzeit ohne Kondensator zur Korrektur
unsigned long ulStartzeit; unsigned long ulLadeZeit; float microFarads; float nanoFarads;
void setup(){ pinMode(ADC_PIN, INPUT); pinMode(LADE_PIN, OUTPUT); digitalWrite(LADE_PIN, LOW);
Serial.begin(9600); }
void loop(){ digitalWrite(LADE_PIN, HIGH); // Ladepin auf High, Kondensator faengt an zu laden ulStartzeit = micros(); // notiere Startzeitpunkt des Ladens
while(analogRead(ADC_PIN) < 648){ // lade bis 63.2% von 1023 (5V) }
ulLadeZeit= micros() - ulStartzeit - ZEIT_NULLMESSUNG; // wie lange das das aufladen auf 63.2% gedauert? // Umrechnung: us zu Sekunden ( 10^-6 ) und Farad zu mikroFarad ( 10^6 ) microFarads = ((float)ulLadeZeit / LADEWIDERSTAND); Serial.print(ulLadeZeit); Serial.print(" us ");
if (microFarads > 1){ Serial.print((long)microFarads); Serial.println(" microFarad"); } else { // wenn Wert kleiner als 1 uF, konvertiere nach nF nanoFarads = microFarads * 1000.0; Serial.print((long)nanoFarads); Serial.println(" nanoFarad"); }
// Kondensator entladen digitalWrite(LADE_PIN, LOW); // Ladepin aus pinMode(ENTLADE_PIN, OUTPUT); // Entladepin auf Ausgang digitalWrite(ENTLADE_PIN, LOW); // und low => also auf Masse while(analogRead(ADC_PIN) > 0){ // entlade solange bis Kondensator leer ist }
pinMode(ENTLADE_PIN, INPUT); // Entladung abschalten }
Durch die Umstellung der Zeitmessung von millis() auf micros() gewinnen wir eine zeitliche Auflösung der Ladezeit, die es uns erlaubt, auch kleinere Kapazitäten zuverlässig zu erfassen.
Hier die 0.1 uF nochmals:
0.1uFnachher.JPG (Größe: 18,56 KB / Downloads: 627)
Das sieht schon besser aus und wir können auch noch kleinere Kapazitäten bestimmen.
Hier zum Beispiel ein 22nF-Kondensator:
22nFuntereG.JPG (Größe: 24,12 KB / Downloads: 627)
Zur Steigerung der Genauigkeit haben wir den Ladewiderstand ausgemessen und den pauschalen Soll-Wert dafür im Quellcode gegen den tatsächlich gemessenen Widerstandswert ausgetauscht. Da der ganze Programmcode auch bei offenen Leitungen etwas Rechenzeit benötigt, haben wir zusätzlich diese von der CPU benötige Zeit in Form von ZEIT_NULLMESSUNG erfasst und bei der Bestimmung der Ladezeit auf 62,3% Füllstand berücksichtigt. Auch so erhöhen wir die Genauigkeit.
Weitere Musterlösungen zur 5. Lektion folgen.
Gruß
Bernhard
PS: Natürlich sind das bei micros() in den Bildern nicht mehr Millisekunden sondern Mikrosekunden in der Ausgabe auf der seriellen Schnittstelle, sehe ich jetzt erst, mein Fehler!
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
11.04.2020, 14:19
(Dieser Beitrag wurde zuletzt bearbeitet: 16.06.2022, 11:10 von Bernhard45.)
Zur Aufgabe 1.2, hier nochmal der Aufgabentext:
1.2. Ohmmeter (oder L-Meter)
Schreibe ein Programm zum Ausmessen von Widerständen (oder Induktivitäten). Überlege wie die Schaltung am Arduino für diese Aufgabe auszusehen hätte. Die Messwerte sollen über die serielle Schnittstelle oder 7-Segment-Multiplexanzeige ausgegeben werden.
Haben auch alle richtig gemacht die bei mir eine Lösung eingereicht haben. Hier haben wir einen einfachen Spannungsteiler aus einem unbekannten und einem bekannten Widerstand. Mit dem ADC messen wir den Spannungsabfall über den unbekannten R, die Eingangsspannung (Ref-Spannung des ADC ist ja definiert), und ermitteln daraus den Widerstandswert.
PHP-Code: // Widerstände messen // _____ // -|_R1__| - VCC //ANALOG_PIN -' _____ // '-|_R2__| - GND // // R1 = bekannter Widerstand // R2 = zu messender Widerstand
#define REF_SPANNUNG 5.000 #define ANALOG_PIN A0 #define R1_OHM 1500.0 //Wert des bekannten Widerstandes
long Messwert; float Spannung_R2; //Spannung über dem zu messenden Widerstand float Widerstand;
void setup() { Serial.begin(9600); }
void loop() { //10 Messungen machen und Mittelwert bilden Messwert=0; for(int i=0;i<10;i++){ Messwert+=analogRead(ANALOG_PIN); } Messwert=trunc(Messwert/10); //Spannung berechnen Spannung_R2=(REF_SPANNUNG/1023.0)*Messwert; Serial.print("Spannung ueber R2 betraegt: "); Serial.print(Spannung_R2); Serial.println(" Volt!"); //Berechnung: (R2 = R1 * (U2/U1)) Widerstand=R1_OHM*(Spannung_R2/(REF_SPANNUNG-Spannung_R2)); Serial.print("Der Widerstand hat "); Serial.print(Widerstand); Serial.println(" Ohm."); Serial.println(); delay(1000); }
Typische Messergebnisse sehen so aus:
2.2 kOhm
2_2kOhm.JPG (Größe: 22,51 KB / Downloads: 577)
4.7 kOhm
4.7kOhm.JPG (Größe: 27,14 KB / Downloads: 577)
Aber Achtung, die Genauigkeit steht und fällt ADC-auflösungsbedingt mit der Größe und dem Verhältnis Runbekannt/Rbekannt.
10 Ohm
10Ohm.JPG (Größe: 21,02 KB / Downloads: 577)
oder 470 kOhm
470kOhm.JPG (Größe: 23,95 KB / Downloads: 577)
Die 10 Bit reichen hier nicht mehr aus, um den Spannungsabfall über den unbekannten Widerstand genau zu erfassen. Eine Lösung des Problems könnte die parallele Verwendung mehrerer bekannter Widerstände (klein, mittel, groß) und die Erfassung der unterschiedlichen Spannungsabfälle mit mehreren ADC-Kanälen sein. Allein in der Software wird dann eine Bereichserkennung/Auto-Ranging realisiert, die nachschaut welcher bekannte R zum unbekannten R am Besten passt und welche Messung den genausten Widerstandswert ergibt.
Eine andere aber teurere Lösung wäre die Nutzung eines (externen) ADC mit 14, 16, 18, … Bit-Auflösung. Hier würde man das Genauigkeitsproblem einfach mit einer größeren ADC-Auflösung beseitigen.
Eine weitere Genauigkeitssteigerung erreicht man, wenn man im Quellcode den tatsächlich ausgemessenen Wert des bekannten Widerstandes einfügt, generell nimmt man für solche Anwendungen idealerweise aber sowieso Präzisionswiderstände.
Zum L-Meter:
Stichwort Frequenzzähler!
Mit einem bekannten C und einem unbekannten L bauen wir einen Oszillator auf! Die Oszillatorfrequenz ermitteln wir mit dem Quellcode des Frequenzzählers von oben. Über die serielle Schnittstelle geben wir aber nicht die ermittelte Oszillatorfrequenz aus, sondern wir berechnen mit der Thomsonschen Schwingungsgleichung (nach L umgestellt natürlich) den Wert für die gesuchte Induktivität und geben diesen statt der Frequenz aus.
Weitere Musterlösungen zu Lektion 5 folgen.
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
16.04.2020, 23:31
(Dieser Beitrag wurde zuletzt bearbeitet: 16.06.2022, 11:13 von Bernhard45.)
Hallo und einen guten Abend ins Forum!
Die nächste Musterlösung, hier nochmal der Aufgabentext:
1.3. Servotester/Dimmer
Über ein Potentiometer oder Trimmer soll ein Modellbauservo (Alternativ die Helligkeit einer LED) einstellbar sein.
Auch diese Aufgabe haben viele gemeistert, z.T. wurde auf die Hardware PWM des Controller zurückgegriffen, was bei der Einstellung der Helligkeit prima funktioniert.
PWM_LED1.JPG (Größe: 121,46 KB / Downloads: 521)
PWM_LED2.JPG (Größe: 120,65 KB / Downloads: 521)
bzw. am Oszi betrachtet:
OSZI_PWM01.JPG (Größe: 103,68 KB / Downloads: 517)
OSZI_PWM02.JPG (Größe: 101,6 KB / Downloads: 517)
OSZI_PWM03.JPG (Größe: 103,39 KB / Downloads: 517)
Für eine Ansteuerung eines Modellbauservos müssen wir uns fragen wie so ein Servo überhaupt per PWM angesprochen werden will.
Die meisten Servos (nicht alle!) wollen ein Signal nach folgenden Muster haben:
Servo_Impulsdiagramm.gif.jpg (Größe: 12,1 KB / Downloads: 520)
Jedes "Frame" am Servo Eingang hat eine Zeitdauer von 20ms, die eigentliche Position der Rudermaschine wird aber durch die Länge des High-Pegels bestimmt. Dieser liegt im Idealfall zwischen 1 und 2ms , wobei eine gewisse geringe Toleranz unter den verschiedenen Modellen und Herstellern doch noch als recht normal anzusehen ist.
Hier eine Animation der Ansteuerung einer solchen Rudermaschine.
(zur Animation ggf. aufs Bild klicken, es ist ein GIF)
Der Code dafür könnte auf einem Arduino und mittels Software-PWM so aussehen:
PHP-Code: #define PWM_OUT 3 #define ADC_PIN A0 #define PULSE_LAENGE 20000 // 20ms = 20000 us
int iADCWert = 0;
void setup() { Serial.begin(9600); pinMode(PWM_OUT,OUTPUT); pinMode(ADC_PIN,INPUT); }
void loop() { // put your main code here, to run repeatedly:
iADCWert = analogRead(ADC_PIN); iADCWert = map(iADCWert,0,1023,1000,2000); // 0-1023 auf 1 bis 2ms skalieren Serial.print("Pulselänge:"); Serial.println(iADCWert); digitalWrite(PWM_OUT,HIGH); delayMicroseconds(iADCWert); digitalWrite(PWM_OUT,LOW); delayMicroseconds(PULSE_LAENGE-iADCWert); }
In einer Endlosschleife wird zunächst die Stellung des Potis mit einem ADC ermittelt. Der eingelesene Wert ist bei 10-Bit Auflösung also zwischen 0 und 1023. Nun muss dieser Wert auf den Zeitbereich 1 bis 2ms abgebildet werden. Eine solche Verhältnisbildung kann auf "normalen" Rechenweg erfolgen, aber die Arduino-Bibliotheken bieten dafür auch eine wunderbare Funktion namens map an.
Schauen wir uns den Syntax von map an.
Syntax
map(value, fromLow, fromHigh, toLow, toHigh);
Parameters
value: the number to map.
fromLow: the lower bound of the value’s current range.
fromHigh: the upper bound of the value’s current range.
toLow: the lower bound of the value’s target range.
toHigh: the upper bound of the value’s target range.
Returns
The mapped value.
Nach dem Mappen des per Poti eingestellten Zeitwertes, erfolgt ganz normal die Erzeugung des High- und Low-Teils am Ausgabepin des Controllers.
Bis hierin waren die Hausaufgaben wohl relativ einfach wie viele eingesandte Lösungen zeigen. Die nächste Aufgabe 1.4 "Digitales Potentiometer" war dann wohl schon nicht mehr so einfach zu lösen, aber das schauen wir uns im nächsten Abschnitt an.
Weiterhin viel Spaß beim Basteln und Programmieren,
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
20.04.2020, 01:17
(Dieser Beitrag wurde zuletzt bearbeitet: 16.06.2022, 11:39 von Bernhard45.)
Kommen wir zur letzten Hausaufgabe aus Lektion 5:
1.4. Digitales Potentiometer
Ein Audiosignal (Mono genügt) soll über einen ADC des Arduino digitalisiert werden und in der Lautstärke zwischen 0 und 200% einstellbar sein (also abgeschwächt/verstärkt werden). Die digital bearbeitete NF soll über einen DAC wieder ausgegeben werden. Der Grad der Verstärkung/Abschwächung kann über zwei Taster (+/-) oder ein Potentiometer eingestellt werden.
Ein mögliches Clippen des Signals bei zu großen Amplituden kann zunächst unberücksichtig bleiben und muss nicht vom Algorithmus abgefangen werden.
Hier mal ein kurzer Beispielcode wie so etwas könnte aussehen:
PHP-Code: #define ADC_PIN A0
#define TASTER1 2 #define TASTER2 3
#define WARTEZEIT 25
bool bFlankenwechselT1 = false; bool bFlankenwechselT2 = false;
unsigned long ulTime = 0;
volatile float fFaktor = 1;
void setup() { // put your setup code here, to run once: Serial.begin(9600); initR2RDAC(); initTimer4ADC(); pinMode(TASTER1,INPUT_PULLUP); pinMode(TASTER2,INPUT_PULLUP); ADC_Init(); }
void initTimer4ADC() { // TIMER 2 for interrupt frequency 15037.593984962406 Hz: cli(); // stop interrupts TCCR2A = 0; // set entire TCCR2A register to 0 TCCR2B = 0; // same for TCCR2B TCNT2 = 0; // initialize counter value to 0 // set compare match register for 15037.593984962406 Hz increments OCR2A = 132; // = 16000000 / (8 * 15037.593984962406) - 1 (must be <256) // turn on CTC mode TCCR2B |= (1 << WGM21); // Set CS22, CS21 and CS20 bits for 8 prescaler TCCR2B |= (0 << CS22) | (1 << CS21) | (0 << CS20); // enable timer compare interrupt TIMSK2 |= (1 << OCIE2A); sei(); // allow interrupts } volatile int iSample;
ISR(TIMER2_COMPA_vect){ //interrupt commands for TIMER 2 here write2DAC(map(ADC_Read(0)*fFaktor,0,1024,0,255)); }
// 8-Bit R2R-DAC von D2 bis D9, LSB D2:MSB D9 void initR2RDAC() { DDRD = DDRD | 0b11110000; // D2-D7 Output DDRB = DDRB | 0b00001111; // D8-D9 Output }
void write2DAC(int wert) { PORTD = (wert & 0b00001111) << 4; //nehme vom 8-Bit Wert die untersten 6 Bits, schiebe diese um 2 Stellen nach oben, schreibe dann den Wert auf PortD (Freilassen von RX/TX) PORTB = (wert & 0b11110000) >> 4; // schreibe die hochsten 2 Bits auf PortB = D8 und D9 }
/* ADC initialisieren */ void ADC_Init(void) { // die Versorgungsspannung AVcc als Referenz wählen: ADMUX = (1<<REFS0); // oder interne 1.1V Referenzspannung als Referenz für den ADC wählen: // ADMUX = (1<<REFS1) | (1<<REFS0);
// Bit ADFR ("free running") in ADCSRA steht beim Einschalten // schon auf 0, also single conversion ADCSRA = (1<<ADPS1) | (1<<ADPS0); // Frequenzvorteiler ADCSRA |= (1<<ADEN); // ADC aktivieren
/* nach Aktivieren des ADC wird ein "Dummy-Readout" empfohlen, man liest also einen Wert und verwirft diesen, um den ADC "warmlaufen zu lassen" */
ADCSRA |= (1<<ADSC); // eine ADC-Wandlung while (ADCSRA & (1<<ADSC) ) { // auf Abschluss der Konvertierung warten } /* ADCW muss einmal gelesen werden, sonst wird Ergebnis der nächsten Wandlung nicht übernommen. */ (void) ADCW; }
/* ADC Einzelmessung */ uint16_t ADC_Read( uint8_t channel ) { // Kanal waehlen, ohne andere Bits zu beeinflußen ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F); ADCSRA |= (1<<ADSC); // eine Wandlung "single conversion" while (ADCSRA & (1<<ADSC) ) { // auf Abschluss der Konvertierung warten } return ADCW; // ADC auslesen und zurückgeben } void loop() { // put your main code here, to run repeatedly: if ((digitalRead(TASTER1)==LOW) && (!bFlankenwechselT1)) { bFlankenwechselT1 = true; ulTime = millis(); }
if (bFlankenwechselT1) if ( (millis()-ulTime) > WARTEZEIT) if (digitalRead(TASTER1)==LOW) { bFlankenwechselT1=false; if (fFaktor<1.99) { fFaktor+=0.01; Serial.println(fFaktor); } }
if ((digitalRead(TASTER2)==LOW) && (!bFlankenwechselT2)) { bFlankenwechselT2 = true; ulTime = millis(); }
if (bFlankenwechselT2) if ( (millis()-ulTime) > WARTEZEIT) if (digitalRead(TASTER2)==LOW) { bFlankenwechselT2=false; if (fFaktor>0.01) { fFaktor-=0.01; Serial.println(fFaktor); } } }
Nun über die Notwendigkeit der Filterung bei ADCs und DACs sind wir im Kurs schon eingegangen, das möchte ich nicht wiederholten, man kann es ja nachlesen.
Um die Aufgabe zu lösen, konnte man den "DSP-Poti" in zwei Modis realisieren. Einem Free-Running Modus, hierbei arbeitet der Controller so schnell er nur kann, oder einem getakteten Modus. Im letzten Fall, wird über ein Interrupt ein gewisses Arbeitstempo fest vorgegeben. Der ATmega328 hat ja mehrere Timer die dazu genutzt werden können. Im oberen Beispiel wurde der T2 so konfiguriert, das er mit 15037.593984962406 Hz arbeitet. Die Berechnung dazu geht übrigens sehr einfach mit entsprechenden Onlinerechnern. Über die Begriffe "Arduino Timer Interrupt Calculator" kommt man zu vielen Webseiten die solche Rechner anbieten, zum Beispiel hier:
http://www.8bit-era.cz/arduino-timer-int...lator.html
Hier gibt man die Taktfrequenz des Controllers und die gewünschte Interruptfrequenz ein, danach wird automatisch der Code für die Registerkonfiguration generiert, siehe Bild:
Man kopiert also nur noch den Code den man benötigt rüber in die Entwicklungsumgebung.
Die eigentliche Funktionalität des Potentiometers steckt, wie unschwer zu erkennen, in der Interrupt-Serviceroutine. In unserem Fall also
PHP-Code: ISR(TIMER2_COMPA_vect){ //interrupt commands for TIMER 2 here write2DAC(map(ADC_Read(0)*fFaktor,0,1024,0,255)); }
Was macht die verschachtelte Anweisung?
1. Es wird das Signal über den ADC-Kanal 0 eingelesen,
2. danach wird das Signal über die Variable fFaktor abgeschwächt oder verstärkt, die Variable selbst wird über zwei Taster um den Wert 0,01 erhöht oder verringert. Die Abfrage der Taster ist in diesem Beispiel einzig und allein die Aufgabe der loop.
3. Da uns das Clipping nach Aufgabentext erstmal nicht interessiert, kann der skalierte Wert aus dem ganzen 10-Bitwertebereich in ein 8-Bitwertebereich ge-mapped werden. Auch ein 10-Bit zu 10-Bit wäre denkbar, aber hier im Beispiel wird nur ein 8-Bit R2R-DAC realisiert.
4. der auf 8-Bit festgelegte Wert wird über write2DAC (unsere eigene Routine für einen R2R-DAC) ausgegeben.
Das Ergebnis sieht dann so aus.
DSP_IN_OUT.JPG (Größe: 98,25 KB / Downloads: 473)
Oben sehen wir das Eingangssignal welches von unserem "DSP" digitalisiert wird, unten ist das Ausgangssignal zu sehen. Hier wird weder "verstärkt" noch "abgeschwächt".
Ziehen wir beide Signale mal auf gleiche Höhe.
DSP_IN_OUT2.JPG (Größe: 92,41 KB / Downloads: 474)
Wir sehen das das Ausgangssignal dem Eingangssignal einen kleinen Moment hinterher hängt. Dies kommt durch die im "DSP" verbrauchte Rechenzeit welche benötigt wird das Signal zu digitalisieren, zu skalieren und wieder auf den DAC zu schreiben. Je nach Effektivität des Programmcodes oder der Geschwindigkeit eines DSPs, lässt sich diese Zeitdifferenz weiter schrumpfen, ganz werden wir Sie nie los, selbst mit dem schnellsten DSP den wir haben können.
Über die beiden Taster können wir dann das Ausgangssignal "verstärken" oder "abschwächen".
DSP_IN_OUT4.JPG (Größe: 109,4 KB / Downloads: 473)
DSP_IN_OUT3.JPG (Größe: 103,09 KB / Downloads: 475)
Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Beiträge: 3.725
Themen: 235
Registriert seit: Dec 2015
23.05.2020, 23:55
(Dieser Beitrag wurde zuletzt bearbeitet: 16.06.2022, 11:43 von Bernhard45.)
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 6 - externe Bibliotheken, C++
Hallo Kursteilnehmer.
Heute möchte ich den Einstiegskurs mit der 6. Lektion abschließen.
In der letzten Lektion geht es um Fremdbibliotheken für den Arduino und um einen kurzen Einblick in die Weltansicht "C++".
Eine Hausaufgabe wird es diesmal nicht geben.
Hier nun die PDF für Lektion 6:
Lektion6.pdf (Größe: 1,02 MB / Downloads: 36)
Danke für das aktive Mitmachen und fürs Durchhalten! Ihr habt die Lektionen und Aufgaben großartig bewältigt!
Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung.
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
|