Beiträge: 3.729
Themen: 235
Registriert seit: Dec 2015
07.12.2019, 02:31
(Dieser Beitrag wurde zuletzt bearbeitet: 07.12.2019, 02:37 von Bernhard45.)
Hallo zusammen.
Nachdem mich in den letzten Tagen schon Anfragen erreichten, wann es denn los geht, will ich heute die erste Lektion des Einsteigerkurses Microcontrollerprogrammierung hochladen. Den Controller haben offensichtlich schon alle besorgt und mehr brauchen wir für Lektion 1 auch erstmal nicht. Es gab ja zahlreiche Wünsche so das 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.
Hier also der Link auf das PDF für die Lektion 1, inklusive der Hausaufgabe. (die Datei ist für das Forum leider zu groß)
Download - Lektion 1
Für das durcharbeiten der Lektion und der ersten Hausaufgabe denke ich sind 2-3 Wochen mehr als ausreichend, so das wir mit der Lektion 2 ungefähr zwischen Weihnachten und Neujahr starten können. 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 Nachbarthead "FAQ zum Einsteigerkurs uC-Programmierung" können Fragen gestellt werden oder es kann untereinander gegenseitig geholfen werden. Ich bitte nur die Leute, die mit uC schon vertrauter sind, nicht gleich Ihre Lösungen der Hausaufgaben zu präsentieren! Es soll wirklich jeder selbst versuchen das aktuelle Problem in der Hausaufgabe zu lösen. Ich werde Fragen beantworten und wenn nötig auch Tipps geben! Nachdem die Musterlösung online ist, können Kursmitglieder gern Ihre Version der Firmware vorstellen.
Habt dabei keine Angst, es geht hier nicht darum wer den optimiertesten oder schönsten Code geschrieben hat, es geht nur darum, das das gestellte Problem mit den Mitteln aus den Kursunterlagen gelöst wurde. Für die schnellen Kursteilnehmer, bitte verzichtet darauf bestimmte Themen schon vorzuziehen und in die Diskussion um die Hausaufgabe einzubringen. Das würde Einsteiger überfordern.
Noch eine Bitte: Lasst diesen Thread hier unkommentiert und schreibt nur im Nachbarthread zum Kurs!
Hier soll es nur die einzelnen PDF-Dateien oder Erklärvideos geben. DANKE!
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.729
Themen: 235
Registriert seit: Dec 2015
23.12.2019, 02:16
(Dieser Beitrag wurde zuletzt bearbeitet: 23.12.2019, 02:39 von Bernhard45.)
Hallo Kursteilnehmer!
Im FAQ-Thread 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:
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:
Ho Ho Ho… kommen wir nun zur Weihnachtslektion, zur Lektion 2.
Download - Lektion 2
Auch hier gibt es wieder eine kleine Hausaufgabe um den Stoff zu verfestigen. Eine Bearbeitungszeit von 2 bis 3 Wochen ist reichlich bemessen, um ohne Stress zu einer Lösung zu kommen. Vielleicht eine kleine Knobelaufgabe um durch die Feiertage zu kommen.
Fragen bitte wieder im FAQ-Nachbarthread und nicht hier stellen.
Vielen Dank!
Ich wünsche allen Kursteilnehmern fröhliche Feiertage und einen guten Wechsel ins nächste Jahrzehnt.
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.729
Themen: 235
Registriert seit: Dec 2015
Hallo Kursteilnehmer,
während ich in den letzten Wochen bereits einige Lösungen der Hausaufgabe der Lektion 2 sehen durfte, alle Lösungen gingen in die richtige Richtung und waren gut, kommt nun eine mittellange Musterlösung für unsere Ampelkreuzung.
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.
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 das auf diesem Port (gemapped durch ledPins) eine LED leuchtet, eine 0 bedeutet stromlos. Das letzte Element in der Beschreibung eines Schaltzustandes ist die Verweildauer in diesem Zustand in ms.
In Setup wird für alle Pins in ledPins die Datenrichtung auf Ausgang festgelegt, die serielle Schnittstelle bleibt aktiv.
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.
Lektion3.pdf (Größe: 693,31 KB / Downloads: 83)
Auch hier haben wir wieder eine Hausaufgabe. Die Ampel aus Lektion 2 wird uns nochmal beschäftigen.
Für alle schnellen 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 und Diskussionen wie immer in den FAQ-Thread und nicht hier hinein!
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.729
Themen: 235
Registriert seit: Dec 2015
29.01.2020, 23:01
(Dieser Beitrag wurde zuletzt bearbeitet: 30.01.2020, 01:00 von Bernhard45.)
Hallo Kursteilnehmer,
die Zeit für Lektion 3 ist bald abgelaufen und viele haben sich sogar noch an den Zusatz rangetraut. Bevor wir zu Lektion 4 kommen, hier erstmal die Musterlösung zur Hausaufgabe.
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 dann 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-Segmentanzeigenmoduls 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 Tastererkennnung umgeschrieben haben, kann hier auch ein delay dafür genommen werden ohne das der Tastendruck nicht oder schlechter erkannt bzw. ein Flankenwechsel am Tasterpin verschlafen 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.
Wer seine Lösung für die Hausaufgabe zeigen will, kann dies gern im FAQ machen.
In ungefähr einer Woche erscheint dann die 4. Lektion.
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.729
Themen: 235
Registriert seit: Dec 2015
05.02.2020, 15:24
(Dieser Beitrag wurde zuletzt bearbeitet: 05.02.2020, 15:41 von Bernhard45.)
Hallo zusammen,
wie versprochen 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" 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: 71)
Wie immer wird es ungefähr eine Woche vor Lektion 5 dann Musterlösungen zur Lektion 4 geben. Danach können Kursteilnehmer Ihre Lösung vorstellen.
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.729
Themen: 235
Registriert seit: Dec 2015
25.02.2020, 01:20
(Dieser Beitrag wurde zuletzt bearbeitet: 25.02.2020, 21:12 von Bernhard45.)
Hallo zusammen,
kommen wir also zu weiteren Musterlösungen:
Viele sind 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, die Sache eignet sich sehr gut für sehr niedrige Frequenzen zum Beispiel im Audiobereich. Für die Frequenzzählung können wir u.a. 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 dem 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 dann 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 also auf Pin5.
Im nächsten und größten Programmteil werden dann 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) ja rauswerfen und ein 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 auf 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 in 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 : - gut von der Variablen Count oben im Programm muss man die ZF nur noch addieren/subtrahieren! Problem? Denke nicht!
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: 674)
Vorverstärker2.png (Größe: 134,8 KB / Downloads: 675)
Es gibt unzählige andere Schaltungen die ebenso geeignet sind. Mit so einem Vorverstärker ist dann die Aufnahme der "HF" am Mischer 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.
________________________________________________________________________
Zusatz 2: IR Empfangen/Senden
An diese Aufgabe scheinen sich die wenigsten Teilnehmer rangetraut zu haben. War es die Komplexität oder gar die Zeit? Ich habe von nur zwei Kursteilnehmern erfahren die sich daran versucht haben, weil Sie so eine Lösung schon lange gesucht haben!
Problem beider Nutzer war es, das Sie für Ihre Smart-TVs zwei "Idiotenfernbedienungen" gesucht haben. Also Fernbedienungen die man noch im Zustand "geistiger Umnachtung" absolut sicher benutzen kann. Zwei Tasten für Kanal hoch/runter, zwei für Lautstärke und eine für Ein/Aus. So ein Ding ist ungemein praktisch wenn man im dunklen Fernsehzimmer oder Heimkino die Tasten erfühlen muss! Bei einer durchschnittlichen Fernbedienung mit 50 Tasten oder mehr kaum machbar.
Solche einfachen Universalfernbedienungen gibt es am Markt, jedoch meist mit Codebüchern und Codes die mal funktionieren, mal nicht.
Bei der Lösung mit dem Arduino braucht man diese Bücher nicht, auch fehlende oder fehlerhafte Codes sind kein Problem. Der Code wird einmal von der Originalfernbedinung gescannt (genauer sogar vermessen!) und bei Bedarf (Tastendruck) einfach wieder aus den vermessenen Signalpunkten synthetisiert. Egal ob TV, Radio, Heizung oder Lichtdimmer, es funktioniert immer da uns das Protokoll sch… egal ist.
Ich will diesen beiden Leuten hier den Platz einräumen, das Sie Ihre Arduino-Universalfernbedienung als Musterlösung vorstellen können.
Bitte macht Eure beiden Fernbedienungen in Ruhe fertig, packt ein paar Fotos samt Schaltplan und Quellcode zusammen und ich stelle das Projekt dann hier rein, so wie besprochen! Für alle anderen Teilnehmer, habt noch etwas Geduld bis zu dieser tollen Lösung, oder wagt Euch selbst noch an die Zusatzaufgabe.
In gut einer Woche geht es dann mit Lektion 5 weiter! 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.729
Themen: 235
Registriert seit: Dec 2015
03.03.2020, 19:54
(Dieser Beitrag wurde zuletzt bearbeitet: 03.03.2020, 19:57 von Bernhard45.)
Hallo,
ich weiß, einige Leute sind schon ganz hibbelig weil Sie keine neuen Aufgaben haben.
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)
In 4-5 Wochen gibt es dann wieder Musterlösungen zu den Aufgaben.
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.729
Themen: 235
Registriert seit: Dec 2015
30.03.2020, 14:01
(Dieser Beitrag wurde zuletzt bearbeitet: 30.03.2020, 14:32 von Bernhard45.)
Hallo,
aufgrund der aktuellen epidemiologischen Situation muss ich diesen uC-Kurs zur Zeit leider pausieren, da ich an anderer Front gebraucht werde.
Weitere Informationen dazu auch hier das Update vom Update 27.03.2020 https://www.mercyships.de/aktuelles/merc...ws/corona/
Bitte kommt gut durch die aktuelle Zeit und bleibt gesund!
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.729
Themen: 235
Registriert seit: Dec 2015
30.03.2020, 21:12
(Dieser Beitrag wurde zuletzt bearbeitet: 31.03.2020, 23:11 von Bernhard45.)
Hallo nochmals,
ich habe einen sehr aufgeregten Anruf eines Kursteilnehmers bekommen, nein der Kurs fällt nicht aus und wird auch nicht beendet! Wir pausieren einfach mal für einen Monat wenn es den Kursteilnehmern recht ist! Den Grund kann man aus dem Link im vorhergehenden Post entnehmen und da die Entscheidung die Africa Mercy in die Werft zu holen sehr kurzfristig kam, bräuchte mein Tag nun 48 statt 24 Stunden.
Ich kann natürlich auch einfach die Musterlösungen für L5 hochladen und das neue PDF, aber erfahrungsgemäß gibt es nach Musterlösungen oder neue Lektionen immer einen kleinen Berg an Mails mit Fragen oder Hilfsbedürftigkeit, die ich unter den gegebenen Umständen nicht zeitnah beantworten werden kann! Das ist sicher weder für die Kursteilnehmer, noch für mich befriedigend!
Falls jemanden die Aufgaben ausgegangen sind, an die Zusatzaufgabe hat sich wohl niemand richtig getraut, dann kann ich mir gern noch die ein oder andere (vielleicht leichtere) Zusatzaufgabe ausdenken.
Aber Unterstützung per Telefon, Skype und EMail wird nur sehr eingeschränkt möglich sein, mein Kopf ist zur Zeit durch viele andere logistische Sachen und Probleme belegt.
Ich bitte daher bei den Kursteilnehmern um Entschuldigung, aber ich denke die aktuelle Situation
kam für uns alle in wenigen Tagen/Wochen doch mehr oder minder überraschend.
Ich wünsche allen Kursteilnehmern ein paar ruhige Wochen und
bleibt bitte alle schön gesund.
!!!!! Es geht hier auf jeden Fall weiter !!!!!
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.729
Themen: 235
Registriert seit: Dec 2015
11.04.2020, 01:59
(Dieser Beitrag wurde zuletzt bearbeitet: 11.04.2020, 02:10 von Bernhard45.)
Hallo zusammen,
trotz aller Arbeit zur Zeit, möchte ich die Freizeit zwischendurch nutzen um den Kurs doch wieder langsam weiterzuführen. Ich weiß einige üben dieser Tage schon selbst weiter und wiederholen Themen oder versuchen eigene Ideen umzusetzen! Prima so, dann hat der Kurs bis jetzt sich ja gelohnt!
Von vielen Leuten habe ich auch schon richtige Lösungen per Mail präsentiert bekommen. Wahrscheinlich seid Ihr alle mittlerweile so gut eingearbeitet, dass es meine simplen Musterlösungen gar nicht mehr bedarf. Der Vollständigkeit gibt es sie hier aber dennoch.
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 das wir im Hauptprogramm zunächst das Laden des Kondensators über den definierten Strom eines Ladewiderstandes anschalten. 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 Ladens 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 Entladens) 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: 453)
220uF Elko:
220uF.JPG (Größe: 20,14 KB / Downloads: 457)
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: 457)
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: 457)
Aua, 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 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: 457)
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: 457)
Zur Steigerung der Genauigkeit haben wir den Ladewiderstand auch 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.
Viele haben das erkannt und in Ihren Hausaufgaben berücksichtigt! Bravo!
Weitere Musterlösungen zur 5. Lektion folgen in den nächsten Tagen.
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! Daheim mache ich dann mal neue Bilder und tausche Sie im Laufe der Zeit noch aus.
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.729
Themen: 235
Registriert seit: Dec 2015
11.04.2020, 14:19
(Dieser Beitrag wurde zuletzt bearbeitet: 11.04.2020, 17:43 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: 408)
4.7 kOhm
4.7kOhm.JPG (Größe: 27,14 KB / Downloads: 408)
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: 408)
oder 470 kOhm
470kOhm.JPG (Größe: 23,95 KB / Downloads: 408)
Die 10 Bit reichen ebnen 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 gemessenen Wert des bekannten Widerstandes einfügt, generell nimmt man für solche Anwendungen idealerweise aber sowieso Präzisionswiderstände.
Zum L-Meter:
Da habe ich gar keine Lösung zu bekommen!  Dabei wäre ein Lösungsansatz sehr einfach, wir haben Ihn in der vergangenen Lektion schon gehabt, 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.729
Themen: 235
Registriert seit: Dec 2015
16.04.2020, 23:31
(Dieser Beitrag wurde zuletzt bearbeitet: 16.04.2020, 23:35 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: 352)
PWM_LED2.JPG (Größe: 120,65 KB / Downloads: 352)
bzw. am Oszi betrachtet:
OSZI_PWM01.JPG (Größe: 103,68 KB / Downloads: 348)
OSZI_PWM02.JPG (Größe: 101,6 KB / Downloads: 348)
OSZI_PWM03.JPG (Größe: 103,39 KB / Downloads: 348)
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: 351)
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.
Alles klar?
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 demnächst an.
Weiterhin viel Spaß beim Basteln und Programmieren,
passt auf Euch auf und bleibt gesund,
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.729
Themen: 235
Registriert seit: Dec 2015
20.04.2020, 01:17
(Dieser Beitrag wurde zuletzt bearbeitet: 20.04.2020, 09:07 von Bernhard45.)
Kommen wir zur letzten Hausaufgabe aus Lektion 5:
1.4. Digitales Potentiometer
Ein Audiosignal (Mono genügt) soll über ein 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.
Nun die Aufgabe hatte es wohl in sich. Nach meinem Wissen hat es nur ein Teilnehmer geschafft eine Lösung zu entwickeln. Woran lag es?
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. In letzteren 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: 303)
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: 304)
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_OUT3.JPG (Größe: 103,09 KB / Downloads: 305)
DSP_IN_OUT4.JPG (Größe: 109,4 KB / Downloads: 303)
PS: warum die Oszibilder in der Forensoftware gerade gedreht werden ist mir nicht ersichtlich, auf dem Laptop liegen sie im Bildbetrachter jedenfall richtig orientiert vor oder Win10 will mich gerade verar*****.
Hoffe bei Euch werden die Bilder richtig rum dargestellt.
Hat sich denn jemand an die Zusatzaufgabe getraut oder war das wirklich zu viel?  Wenn sich keiner dran versucht hat, dann nehme ich die Aufgabe mit in den nächsten Kurs und zeige die Lösung Stück-für-Stück in einem Tutorial. Hier hatte ich ja schon mal einen Eindruck veröffentlicht was sich so machen lässt: https://radio-bastler.de/forum/showthrea...#pid122856
Mit so einem kleinen DSP könnte man die Empfangsqualität selbst bei massiven Störungen noch optimieren.
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.729
Themen: 235
Registriert seit: Dec 2015
23.05.2020, 23:55
(Dieser Beitrag wurde zuletzt bearbeitet: 24.05.2020, 00:02 von Bernhard45.)
Hallo Kursteilnehmer.
Wir haben vielerorts neue und schwere Aufgaben zu bewältigen. Als die letzten Lektionen veröffentlicht wurden, war die Welt noch eine andere. Viele Leute die ich kenne haben heute viele neue Aufgaben zu bewältigen, andere wiederum haben weniger zu tun, sind in Kurzarbeit oder gar komplett in die Arbeitslosigkeit und an den Rand der eigenen Existenz gekommen. Wann ist der richtige Zeitpunkt gekommen den Kurs wieder fortzuführen bzw. zum Abschluss zu bringen? Wahrscheinlich gibt es dazu viele unterschiedliche Ansichten, dennoch möchte ich heute zu einem Stück "Normalität" zurückkehren und 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, aber jeder der sich an C++ auf dem Arduino rantraut ist natürlich mit seiner Lösung willkommen. Wenn Ihr die Zeit dazu habt und keine anderen Sachen aktuell wichtiger sind, probiert es doch einfach mal aus und schreibt für die GPIOs eine klassenbasierte Lösung in C++. Ihr könnt jederzeit dazu im FAQ ein Post absetzen falls es Fragen oder Lösungen gibt.
Hier nun die PDF für Lektion 6:
Lektion6.pdf (Größe: 1,02 MB / Downloads: 32)
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")
|