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: 88)

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: 75)

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: 15)

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: 710)

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

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: 61)

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: 17)  

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
#10
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_PININPUT);
 
 
  pinMode
(LADE_PINOUTPUT);     
  digitalWrite
(LADE_PINLOW);  

  Serial
.begin(9600);             
}

void loop(){
 
 digitalWrite(LADE_PINHIGH);  // 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) 
 
 }

 
 ulLadeZeitmillis() - 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_PINLOW);             // Ladepin aus
 
 pinMode(ENTLADE_PINOUTPUT);            // Entladepin auf Ausgang
 
 digitalWrite(ENTLADE_PINLOW);          // und low => also auf Masse
 
 
  while
(analogRead(ADC_PIN) > 0){          // entlade solange bis Kondensator leer ist
 
 }

 
 pinMode(ENTLADE_PININPUT);             // 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:


.jpg   10uF.JPG (Größe: 16,22 KB / Downloads: 489)


220uF Elko:


.jpg   220uF.JPG (Größe: 20,14 KB / Downloads: 493)

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: 


.jpg   0.47uF.JPG (Größe: 15,15 KB / Downloads: 493)

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:


.jpg   0.1uFvorher.JPG (Größe: 13,92 KB / Downloads: 493)

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_PININPUT);
 
 
  pinMode
(LADE_PINOUTPUT);     
  digitalWrite
(LADE_PINLOW);  

  Serial
.begin(9600);             
}

void loop(){
 
 digitalWrite(LADE_PINHIGH);  // 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) 
 
 }

 
 ulLadeZeitmicros() - 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_PINLOW);             // Ladepin aus
 
 pinMode(ENTLADE_PINOUTPUT);            // Entladepin auf Ausgang
 
 digitalWrite(ENTLADE_PINLOW);          // und low => also auf Masse
 
 
  while
(analogRead(ADC_PIN) > 0){          // entlade solange bis Kondensator leer ist
 
 }

 
 pinMode(ENTLADE_PININPUT);             // 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:


.jpg   0.1uFnachher.JPG (Größe: 18,56 KB / Downloads: 493)

das sieht schon besser aus und wir können auch noch kleinere Kapazitäten bestimmen.
Hier zum Beispiel ein 22nF-Kondensator:


.jpg   22nFuntereG.JPG (Größe: 24,12 KB / Downloads: 493)

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


.jpg   2_2kOhm.JPG (Größe: 22,51 KB / Downloads: 444)

4.7 kOhm


.jpg   4.7kOhm.JPG (Größe: 27,14 KB / Downloads: 444)

Aber Achtung, die Genauigkeit steht und fällt ADC-auflösungsbedingt mit der Größe und dem Verhältnis Runbekannt/Rbekannt.

10 Ohm


.jpg   10Ohm.JPG (Größe: 21,02 KB / Downloads: 444)


oder 470 kOhm


.jpg   470kOhm.JPG (Größe: 23,95 KB / Downloads: 444)

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!  Sad  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")
Zitieren
#12
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. 


.jpg   PWM_LED1.JPG (Größe: 121,46 KB / Downloads: 388)
.jpg   PWM_LED2.JPG (Größe: 120,65 KB / Downloads: 388)

bzw. am Oszi betrachtet:


.jpg   OSZI_PWM01.JPG (Größe: 103,68 KB / Downloads: 384)
.jpg   OSZI_PWM02.JPG (Größe: 101,6 KB / Downloads: 384)
.jpg   OSZI_PWM03.JPG (Größe: 103,39 KB / Downloads: 384)



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:


.jpg   Servo_Impulsdiagramm.gif.jpg (Größe: 12,1 KB / Downloads: 387)

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")
Zitieren
#13
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 |= (<< WGM21);
// Set CS22, CS21 and CS20 bits for 8 prescaler
TCCR2B |= (<< CS22) | (<< CS21) | (<< CS20);
// enable timer compare interrupt
TIMSK2 |= (<< 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. */
 
 (voidADCW;
}

/* ADC Einzelmessung */
uint16_t ADC_Readuint8_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.


.jpg   DSP_IN_OUT.JPG (Größe: 98,25 KB / Downloads: 339)

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.


.jpg   DSP_IN_OUT2.JPG (Größe: 92,41 KB / Downloads: 340)

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".


.jpg   DSP_IN_OUT3.JPG (Größe: 103,09 KB / Downloads: 341)
.jpg   DSP_IN_OUT4.JPG (Größe: 109,4 KB / Downloads: 339)

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? Big Grin 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")
Zitieren
#14
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: 
.pdf   Lektion6.pdf (Größe: 1,02 MB / Downloads: 35)

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")
Zitieren


Möglicherweise verwandte Themen…
Thema Verfasser Antworten Ansichten Letzter Beitrag
  FAQ zum Einsteigerkurs uC-Programmierung Bernhard45 254 86.476 14.04.2020, 19:22
Letzter Beitrag: Tubefan

Gehe zu: