Themabewertung:
  • 3 Bewertung(en) - 5 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Einsteigerkurs Microcontrollerprogrammierung
#1
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")
Zitieren
#2
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")
Zitieren
#3
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. 


.pdf   Lektion3.pdf (Größe: 693,31 KB / Downloads: 40)

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")
Zitieren
#4
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] = {3245678910};
// 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] = {
 
 {0101000101000},  // A1-A4 gelb an, A5/6 rot an, A7 aus, für 1000ms
 
 {0001000001000},  // A1-A4 gelb aus, A5/6 rot an, A7 aus, für 1000ms
 
 {1001001001000},  // A1-A6 rot, A7 aus, für 1000ms
};

int ampelphasen_regelbetrieb[48][10] = {
 
 {1101001001000},  // Regelbetrieb Zustand 1
 
 {1100101001000},  //    "         Zustand 2
 
 {0010111001000},  //
 
 {0010101001000},  //
 
 {0010111001000},  //
 
 {0010101001000},  //
 
 {0010111001000},  // 
 
 {0010101001000},  // 
 
 {0010111001000},  // 
 
 {0010101001000},  // 
 
 {0010111001000},  // 
 
 {0010101001000},  // 
 
 {0010111001000},  // 
 
 {0010101001000},  // 
 
 {0010111001000},  // 
 
 {0010101001000},  // 
 
 {0010111001000},  // 
 
 {0010101001000},  // 
 
 {0010111001000},  // 
 
 {0010101001000},  // 
 
 {0010111001000},  // 
 
 {0010101001000},  // 
 
 {0101001001000},  // 
 
 {1001001001000},  // 

 
 {1001001001000},  // 
 
 {1001001101000},  // 
 
 {1001000011000},  // 
 
 {1001000011000},  // 
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000011000},  //
 
 {1001000101000},  //
 
 {1001001001000  //
};

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_PININPUT_PULLUP); 
 
 Serial.begin(9600);
}

void loop() {
 
 // Ampel Initialisierung schon durchlaufen?
 
 if (bInitialisierungDurchlaufen==false) {
 
   
    
// Ampel Initialisierung 10s Gelb blinken, Fussgänger rot
 
   for(int iDurchgang=0iDurchgang<5iDurchgang++) {
 
     for (int x=0x<2x++) {
 
       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=0iDurchgang<10iDurchgang++) {
 
       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=0x<48x++) {
 
    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_PININPUT_PULLUP);
 
 pinMode(TASTER_ABWAERTS_PININPUT_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_PININPUT_PULLUP);
 
 pinMode(TASTER_ABWAERTS_PININPUT_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")
Zitieren
#5
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 
.pdf   Lektion4.pdf (Größe: 805,78 KB / Downloads: 37)

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")
Zitieren
#6
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_PININPUT);
}

void loop()
{
 
ulHighPulse pulseIn(MESS_PINHIGH);
 
ulLowPulse pulseIn(MESS_PINLOW);
 
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_PININPUT);
 
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), ISR_funktionRISING);
}

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. 


.txt   Frequenzzaehler.ino.txt (Größe: 18,48 KB / Downloads: 7)

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) ? 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 saveTCCR1AsaveTCCR1B;

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 saveTCCR3AsaveTCCR3B;

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 saveTCCR4AsaveTCCR4B;

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 saveTCCR5AsaveTCCR5B;

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_interrupt1000);
}

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 saveTCCR2AsaveTCCR2B;
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 saveTCCR4AsaveTCCR4BsaveTCCR4CsaveTCCR4DsaveTCCR4EsaveOCR4C;
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 indexlength;

 
 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 1disable_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) ? 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 saveTCCR1AsaveTCCR1B;

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 saveTCCR3AsaveTCCR3B;

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 saveTCCR4AsaveTCCR4B;

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 saveTCCR5AsaveTCCR5B;

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_interrupt1000);
}

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 saveTCCR2AsaveTCCR2B;
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 saveTCCR4AsaveTCCR4BsaveTCCR4CsaveTCCR4DsaveTCCR4EsaveOCR4C;
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 indexlength;

 
 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 1disable_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:


.png   Vorverstärker1.png (Größe: 121,33 KB / Downloads: 210)

.png   Vorverstärker2.png (Größe: 134,8 KB / Downloads: 210)

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")
Zitieren
#7
Hallo, 

ich weiß, einige Leute sind schon ganz hibbelig weil Sie keine neuen Aufgaben haben. 

Hier und heute nun die Lektion 5 unseres Einsteigerkurses: 
.pdf   Lektion5.pdf (Größe: 989,89 KB / Downloads: 32)

Endlich toben wir uns mit Analogsignalen richtig aus!  Big Grin 

Diesmal gibt es auch nur eine Zusatzaufgabe in der wir den Arduino Nano als Signalprozessor nutzen wollen, die zu bearbeitende Audiodatei findet ihr hier:  
.zip   NFgestoert120Hz_SR8kHz.zip (Größe: 229,11 KB / Downloads: 9)  

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")
Zitieren
#8
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")
Zitieren
#9
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")
Zitieren


Gehe zu: