Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Einsteigerkurs Mikrocontrollerprogrammierung (Release Candidate 1 zur Freigabe)
#1
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 1  Softwareinstallation und serielle Kommunikation

Heute will ich die erste Lektion des Einsteigerkurses in die Mikrocontroller-Programmierung hochladen. Den Controller haben offensichtlich schon alle besorgt und mehr brauchen wir für Lektion 1 auch erstmal nicht. Es gab zahlreiche Wünsche, so dass einige Änderungen notwendig wurden:
  • Der Kurs soll auch offline funktionieren
  • Der Kurs soll auf ebook-Readern lesbar sein
  • Das Material sollte ausdruckbar und abheftbar sein 
Nun ich denke mit der PDF-Form kann ich die meisten Nutzerwünsche erfüllen. 

Als Basis dieses Kurses wurde die populäre Arduino - Platform gewählt.
Der Kurs besteht aus 6 Lektionen mit Erklärungen zum Thema Mikrocontroller Hard- und Software:
  • Lektion 1  Softwareinstallation und serielle Kommunikation
  • Lektion 2 Digital I/O - Ausgänge des Mikrocontroller schalten
  • Lektion 3 Digital I/O - Eingänge des Mikrocontroller auslesen
  • Lektion 4  Interrupts, weitere binäre I/O-Funktionen in der Arduino-Welt
  • Lektion 5  ADC / DAC, analoge Signale
  • Lektion 6 externe Bibliotheken, C++

Zusätzlich enthält jede Lektion Anwendungs- und Übungs-Beispiele (sogenannte Hausaufgaben!).

Hier also der Link auf das PDF für die Lektion 1 Softwareinstallation und serielle Kommunikation, inklusive der Hausaufgabe:

.pdf   Lektion 1.pdf (Größe: 3,31 MB / Downloads: 16)


Vor der Veröffentlichung der nächsten Lektion, werde ich immer eine Musterlösung für die Hausaufgabe aus der vorhergehenden Lektion präsentieren. 

Im Nachbar-Thread "FAQ zum Einsteigerkurs µC-Programmierung" können Fragen gestellt und es kann untereinander gegenseitig geholfen werden.

Viel Spaß mit Lektion 1, Bernhard.
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#2
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 2  Digital I/O - Ausgänge des Mikrocontroller schalten

Im Nachbar-Thread "FAQ zum Einsteigerkurs µC-Programmierung" gab es ja schon erste Lösungen zu bewundern.
Unter den unzähligen Möglichkeiten der Umsetzung bieten sich doppelte, ineinander verschachtelte Schleifen an.
Die Zählvariable einer jeden Schleife ist dabei gleich ein Faktor in der Multiplikation. 

Hier nun meine Musterlösung:

PHP-Code:
void setup() 
{
 
 // put your setup code here, to run once:
 
 Serial.begin(9600);
}

void loop() 
{
   // put your main code here, to run repeatedly:
 
  Serial.println("Das kleine 1x1\n\n");
   for (int i 011i++) 
   {
 
    Serial.print("\t");
 
    Serial.print(i);
 
  }
 
 
  Serial
.println("\n------------------------------------------------------------------------------------------------");
 
 
  for 
(int i 011i++) 
  {
 
   Serial.print(i); Serial.print("|"); 
 
   for (int x 011x++) 
    {
 
     Serial.print("\t");
 
     Serial.print(i*x);
 
   }
 
   Serial.println();
 
 }
  delay(100000);



Die Ausgabe des Programms über den Seriellen Monitor sieht dann so aus:

   

Kommen wir nun zur Lektion 2.

.pdf   Lektion 2.pdf (Größe: 2,07 MB / Downloads: 4)

Auch hier gibt es wieder eine kleine Hausaufgabe, um den Stoff zu verfestigen.

Fragen bitte wieder im Nachbar-Thread "FAQ zum Einsteigerkurs µC-Programmierung" und nicht hier stellen.

Vielen Dank!

Viel Spaß mit Lektion 2, Bernhard.
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#3
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 3  Digital I/O - Eingänge des Mikrocontroller auslesen


Als Erstes möchte ich meine mittellange Musterlösung für unsere Ampelkreuzung aus der Hausaufgabe vorstellen. 

Ich habe explizit jeden Schaltzustand im Regelbetrieb aufgeführt, ich denke das erleichtert das "durchsteppen" der Ampelschaltung. Unter den Hausaufgaben waren Lösungen mit sehr stark komprimierten Zustandstabellen. Das war fast schon rekordverdächtig, wenn auch nicht immer leicht nachzuvollziehen, was die Anlage nun gerade macht.


PHP-Code:
int ledPins[9] = {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;

// 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 setup() 
{
 
 // alle benötigten Pins auf Ausgang festlegen
 
 for (int i 09i++) 
  {
 
   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 0iDurchgang 5iDurchgang++) 
     {
 
       for (int x 02x++) 
        {
 
         for (int i 09i++)
          
 
            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 09i++)
       
 
         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 048x++) 
  {
 
    for (int i 09i++) 
 
    {
        digitalWrite(ledPins[i], ampelphasen_regelbetrieb[x][i]);
     }
 
    delay(ampelphasen_regelbetrieb[x][9]);
 
 // for (int x = 0; x < 48; x++) {


Im Feld ledPins werden die I/O-Portnummern der einzelnen "Lampen" der Ampel angegeben bzw. welche LED an welchem Controllerport hängt.

Danach kommt ein Flag (boolean) in dem gespeichert wird, ob die Ampel bereits angefahren und im Regelbetrieb ist, oder nicht.

Die beiden folgenden Felder speichern die Schaltzustände im Anlauf- und Regelbetrieb. Eine 1 bedeutet, dass auf diesem Port (gemapped durch ledPins) eine LED leuchtet.
Eine 0 bedeutet die LED leuchtet nicht. Das letzte Element in der Beschreibung eines Schaltzustandes enthält die Verweildauer in diesem Zustand in ms.

In Setup wird für alle Pins in ledPins die Datenrichtung auf Ausgang festgelegt und die serielle Schnittstelle aktiviert.

Das eigentliche loop-Programm läuft praktisch nur noch durch die Tabelle (Feld, Array) der Schaltzustände hindurch, wobei einmalig eine Anlaufphase ausgeführt wird.

Kommen wir nun zur nächsten Lektion, zur Lektion 3. 


.pdf   Lektion_3.pdf (Größe: 952,29 KB / Downloads: 0)

Auch hier haben wir wieder eine Hausaufgabe. Die Ampel aus Lektion 2 wird uns nochmal beschäftigen.
Für alle Kursteilnehmer und Leute die nicht genug bekommen, gibt es hinter der Hausaufgabe ein paar Zusatzaufgaben!

Ich wünsche viel Spaß beim Studieren und Lösen der Aufgaben.

Fragen bitte wieder im Nachbar-Thread "FAQ zum Einsteigerkurs µC-Programmierung" und nicht hier stellen.

Gruß Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#4
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 4  Interrupts, weitere binäre I/O-Funktionen in der Arduino-Welt

Bevor wir zu Lektion 4 kommen, möchte ich hier erst die Musterlösung zur Hausaufgabe zeigen.

PHP-Code:
int ledPins[9] = {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 09i++) 
   {
 
     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 02x++) 
          {
 
            for (int i 0i9i++) 
                    
                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 09i++) 
 
         {
            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 048x++) 
  {
 
    for (int i 09i++) 
 
    {
        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(51); // Fussgänger Rot an
 
       digitalWrite(60); // Fussgänger Gruen aus
 
       digitalWrite(70); // Fussgänger Blicklicht aus
 
    }

 
    // nach Ende einer Grünphase in Schaltzustand 21 für nächsten Durchlauf wieder sperren
 
    if ((x==21) && bGruenanforderung)
 
    {
        bGruenanforderung false;
     }
 
    // Wurde wärend einer Grünphase eine Grünanforderung erkannt, gilt diese für den nächsten Durchlauf
 
    if ((x==21) && bGruen_im_naechsten_Durchlauf
     {
 
       bGruen_im_naechsten_Durchlauf false;
 
       bGruenanforderung true          
     
}
 
       
     
// altes Delay mit Programmblockierung: delay(ampelphasen_regelbetrieb[x][9]);
 
    // neues "Delay" ohne Programmblockierung
 
    time millis();
 
    while(millis() < time ampelphasen_regelbetrieb[x][9]) 
     {
 
       pruefeGruenanforderung(x);
 
    }
 
         
  
// for (int x=0; x<48; x++) {



Im Prinzip haben wir funktionell nur das delay durch 

PHP-Code:
 
// altes Delay mit Programmblockierung: delay(ampelphasen_regelbetrieb[x][9]);
// neues "Delay" ohne Programmblockierung
  time millis();
 
 while(millis() < time ampelphasen_regelbetrieb[x][9]) 
  {
 
    pruefeGruenanforderung(x);
  

ersetzt. Dadurch das der Controller nun in der Zeit zwischen dem Umschalten nicht mehr blockiert ist, kann er fast permanent auf eine Grünanforderung per Taster warten.

Die anderen geänderten Codeteile befassen sich mit der Tastendruckerkennung und der "Verriegelung" und "Freigabe" der Grünphase für die Fussgängerampel.

____________________________________________________________________________________________________________

Zum Zusatz:

1. Entwickle eine Funktion zur Ansteuerung eines 7-Segment-Anzeigenmoduls wobei über zwei Taster zwischen 0-9 auf- und abgeschaltet werden kann.

PHP-Code:
int SegPins[8] = {12111098765};
// 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] = 
{
 
 truetruetruetruetruetruefalsefalse    } ,  // = 0
 
 falsetruetruefalsefalsefalsefalsefalse} ,  // = 1
 
 truetruefalsetruetruefalsetruefalse   } ,  // = 2  
 
 truetruetruetruefalsefalsetruefalse   } ,  // = 3
 
 falsetruetruefalsefalsetruetruefalse  } ,  // = 4
 
 truefalsetruetruefalsetruetruefalse   } ,  // = 5
 
 truefalsetruetruetruetruetruefalse    } ,  // = 6
 
 truetruetruefalsefalsefalsefalsefalse } ,  // = 7
 
 truetruetruetruetruetruetruefalse     } ,  // = 8
 
 truetruetruetruefalsetruetruefalse    } ,  // = 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 08i++)
  {
 
    pinMode(SegPins[i],OUTPUT);
  }
 
 // Alle Anzeigen aus
 
 for (int i 09i++)
 
   
     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 08i++)
  {
 
    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] = {12111098765};
// 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] =
{
 
 truetruetruetruetruetruefalsefalse    } ,  // = 0
 
 falsetruetruefalsefalsefalsefalsefalse} ,  // = 1
 
 truetruefalsetruetruefalsetruefalse   } ,  // = 2  
 
 truetruetruetruefalsefalsetruefalse   } ,  // = 3
 
 falsetruetruefalsefalsetruetruefalse  } ,  // = 4
 
 truefalsetruetruefalsetruetruefalse   } ,  // = 5
 
 truefalsetruetruetruetruetruefalse    } ,  // = 6
 
 truetruetruefalsefalsefalsefalsefalse } ,  // = 7
 
 truetruetruetruetruetruetruefalse     } ,  // = 8
 
 truetruetruetruefalsetruetruefalse    } ,  // = 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] = {14151617};
// Pinnummer zum Multiplexen des 7-Segment-Anzeigenmoduls
// Digit1, Digit2, Digit3, Digit4

int iZahl 0;

void setup() 
{
 
 // 7-Segment Pins initialisieren
 
 for (int i 08i++)
  {
 
    pinMode(SegPins[i], OUTPUT);
  }
 
 // Alle Anzeigen aus
 
 for (int i 09i++)
  
 
    digitalWrite(SegPins[i], HIGH);
  }
 
 pinMode(TASTER_AUFWAERTS_PININPUT_PULLUP);
 
 pinMode(TASTER_ABWAERTS_PININPUT_PULLUP);

 
 for (int i 04i++)
  {
 
    pinMode(ModulPins[i], OUTPUT);
  }
 
 for (int i 04i++)
  {
 
    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 08i++)
    {
 
      digitalWrite(SegPins[i], !Zahlen[einer][i]);
 
   }
    delay(MS_FOR_FPS);
 
   digitalWrite(ModulPins[3], HIGH);

 
   digitalWrite(ModulPins[2], LOW);
 
   for (int i 08i++)
    {
 
      digitalWrite(SegPins[i], !Zahlen[zehner][i]);
 
   }
    delay(MS_FOR_FPS);
 
   digitalWrite(ModulPins[2], HIGH);

 
   digitalWrite(ModulPins[1], LOW);
 
   for (int i 08i++)
 
       digitalWrite(SegPins[i], !Zahlen[hunderter][i]);
 
   delay(MS_FOR_FPS);
 
   digitalWrite(ModulPins[1], HIGH);

 
   digitalWrite(ModulPins[0], LOW);
 
   for (int i 08i++)
    {
 
      digitalWrite(SegPins[i], !Zahlen[tausender][i]);
 
   }
    delay(MS_FOR_FPS);
 
   digitalWrite(ModulPins[0], HIGH);
}

void loop() 
{
 
   PruefeAufTastendruckAufwaerts();
 
   PruefeAufTastendruckAbwaerts();
 
   siebenseg(iZahl);



Die Funktion siebenseg zerlegt die Zahl im Übergabeparameter in Einer, Zehner, Hunderter und Tausender. Für jede einzelne Anzeige entsteht also ein Wert zwischen 0-9. Danach wird dieser Wert einzeln auf die jeweilig aktiv geschaltete Anzeige ausgegeben, der Wert also auf den "Datenbus" gesetzt und die gemeinsame Anode (oder Kath.) durchgeschaltet.
Das Ganze wird "etwas" eingebremst, gerade so das wir kein Flackern der Anzeige sehen, aber die Anzeige auch nicht mit hunderten kHz überfahren. Da wir die Tastendruckerkennung umgeschrieben haben, kann hier auch ein delay dafür genommen werden ohne das der Tastendruck nicht oder schlechter erkannt bzw. ein Flankenwechsel am Taster-Eingang nicht erfasst wird.

Den Code für die Zusatzaufgabe "Uhrzeit" lasse ich weg, da er sich nur noch um einen "Zähler", der im Minutentakt hochgezählt wird, unterscheidet. Bei 23 erfolgt der Überlauf der ersten beiden Stellen auf 00, bei 59 für die letzten beiden mit Übertrag auf die ersten beiden Stellen. Die beiden Tasterroutinen incrementieren dann auf der Minutenstelle mit 1, auf der Stundenseite mit 100, jeweils bis zum Überlauf.

Gruß Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#5
Hallo zusammen, 

hier nun die Lektion 4 unseres Kurses. 

Bevor wir uns analogen Signalen und deren Verarbeitung und Erzeugung in einem Prozessor widmen, wollen wir 
uns zum "Bergfest" des Kurses nochmal kurz mit der digitalen I/O, Interrupts und mit der "Vermessung" von Digitalsignalen beschäftigen.
Als Hausaufgabe und im Zusatz kommt eine erste, für den Radiobastler interessante und nicht unnütze Anwendung auf den Tagesplan!

Download Lektion4.pdf 
.pdf   Lektion4.pdf (Größe: 805,78 KB / Downloads: 69)

Gruß 
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#6
Hallo zusammen, 

kommen wir also zu weiteren Musterlösungen:

Viele sind in der Hausaufgabe auf die richtige Lösung bei Frequenzmessung und Frequenzzählung gekommen.

Eine Frequenzmessung könnte man mit den Arduino-Bibliotheken ganz einfach so umsetzen.

PHP-Code:
#define MESS_PIN 2

float Frequenz;
float DutyCycle  
unsigned long ulHighPulse
;
unsigned long ulLowPulse;
unsigned long ulPeriode;

void setup()
{
 
Serial.begin(9600);
 
pinMode(MESS_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, dieser Weg eignet sich sehr gut für sehr niedrige Frequenzen zum Beispiel im Audiobereich. Für die Frequenzzählung können wir aber auch die Interruptfunktionalität nutzen:

PHP-Code:
// Frequenzzählung per Interrupt, Grenzfreq ca. 150 kHz
#define INTERRUPT_PIN 2
volatile unsigned long ulCount 0;


void setup() {
 
  Serial.begin(9600);  
   interrupts
();
 
  pinMode(INTERRUPT_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: 14)

PHP-Code:
// Frequenzzählerklasse für Arduino/Teensy
// Arduino Nano/Mega bis ca. 7.4 MHz

class FrequenzzaehlerKlasse {
public:
 
 static void start(uint16_t msec);
 
 static uint8_t ready(void);
 
 static uint32_t get(void);
 
 static void stop(void);
};


// Arduino Mega
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
 
 // #define COUNTER_USE_TIMER1 // T1 is not connected
 
 // #define COUNTER_USE_TIMER3 // T3 is not connected
 
 // #define COUNTER_USE_TIMER4 // T4 is not connected
 
 #define COUNTER_USE_TIMER5    // T5 is pin 47
 
 #define TIMER_USE_TIMER2

// Teensy 3.0 & 3.1 & LC
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
 
 #define COUNTER_USE_LPTMR     // LPTMR is pin 13  (has LED connected)
 
 #define TIMER_USE_INTERVALTIMER

// Teensy 2.0
#elif defined(__AVR_ATmega32U4__)
 
 #define COUNTER_USE_TIMER1    // T1 is pin 11  (has LED connected)
 
 #define TIMER_USE_TIMER4H

// Teensy++ 1.0 & 2.0
#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)
 
 #define COUNTER_USE_TIMER1    // T1 is pin 6   (has LED connected)
 
 //#define COUNTER_USE_TIMER3  // T3 is pin 13
 
 #define TIMER_USE_TIMER2

// Sanguino
#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__)
 
 #define COUNTER_USE_TIMER1    // T1 is pin 1
 
 #define TIMER_USE_TIMER2

// Arduino Duemilanove, Diecimila, LilyPad, Mini, Fio, etc
#elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
 
 #define COUNTER_USE_TIMER1    // T1 is pin 5
 
 #define TIMER_USE_TIMER2

#else
 
 #error "Unknown chip, please edit me with timer+counter definitions"

#endif

/**********************************************/
/*   Counter Hardware Abstraction             */
/**********************************************/
#if defined(COUNTER_USE_LPTMR) // 16 bit LPTMR on Freescale Kinetis

static inline void counter_init(void)
{
 
 SIM_SCGC5 |= SIM_SCGC5_LPTIMER;
 
 LPTMR0_CSR 0;
 
 LPTMR0_PSR 0b00000100// bypass prescaler/filter
 
 LPTMR0_CMR 0xFFFF;
 
 LPTMR0_CSR 0b00100110// counter, input=alt2, free running mode
 
 CORE_PIN13_CONFIG PORT_PCR_MUX(3);
}

static 
inline void counter_start(void)
{
 
 LPTMR0_CSR 0b00100111// enable 
}

static 
inline void counter_shutdown(void)
{
 
 LPTMR0_CSR 0;
}

static 
inline uint16_t counter_read(void)
{
 
 LPTMR0_CNR 0// writing cause sync with hardware
 
 return LPTMR0_CNR;
}

static 
inline uint8_t counter_overflow(void)
{
 
 return (LPTMR0_CSR 0x80) ? 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 denen wir die Frequenzzählung starten und anhalten können, zusätzlich geben wir beim Start die Torzeit vor.
Mit der Methode ready fragen wir beim Frequenzzähler nach, ob er schon ein neues Ergebnis hat, welches wir dann mittels der Methode get erfragen können.

Im nächsten Block haben wir ein paar Compiler-Anweisungen. Der Compiler "schaut" also nach welchen Microcontroller wir programmieren wollen. __AVR_ATmega1280__  __MK20DX128__ , …. sind alles verschiedene Microcontrollertypen. Für unser Arduino Nano mit dem ATmega328 oder ATmega168 (alt) wird also nur dieser Block in die Compilierung einbezogen:

PHP-Code:
// Arduino Duemilanove, Diecimila, LilyPad, Mini, Fio, etc
#elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
 
 #define COUNTER_USE_TIMER1    // T1 is pin 5
 
 #define TIMER_USE_TIMER2 

In diesem Block wird nichts anderes gemacht, als die beiden Schlüsselwörter COUNTER_USE_TIMER1  und TIMER_USE_TIMER2
definiert. Auf diese Schlüsselwörter wird im weiteren Programm "geprüft", sie bedeuten nichts anderes, als das der Frequenzzähler den Timer1 des ATmega als Zähler und den Timer2 als Timer nutzen soll. Die zu zählende Frequenz kommt auf Pin5.

Im nächsten und größten Programmteil werden der Zähler und der Timer direkt per Registerzugriff konfiguriert und zwar für jeden einzelen Microcontrollertyp den wir oben aufgeführt haben. Nur dadurch ist diese Frequenzzählerklasse auf einer Reihe unterschiedlicher Controller nutzbar.  Zusätzlich wird in dem Teil auch "geschaut", mit welcher Taktfrequenz der Controller denn eigentlich laufen soll. Wichtig für zeitkritische Codeteile. (bei einem Frequenzzähler im Prinzip ja alles )

PHP-Code:
/**********************************************/
/*   Counter Hardware Abstraction             */
/**********************************************/
#if defined(COUNTER_USE_LPTMR) // 16 bit LPTMR on Freescale Kinetis

static inline void counter_init(void)
{
 
 SIM_SCGC5 |= SIM_SCGC5_LPTIMER;
 
 LPTMR0_CSR 0;
 
 LPTMR0_PSR 0b00000100// bypass prescaler/filter
 
 LPTMR0_CMR 0xFFFF;
 
 LPTMR0_CSR 0b00100110// counter, input=alt2, free running mode
 
 CORE_PIN13_CONFIG PORT_PCR_MUX(3);
}

static 
inline void counter_start(void)
{
 
 LPTMR0_CSR 0b00100111// enable 
}

static 
inline void counter_shutdown(void)
{
 
 LPTMR0_CSR 0;
}

static 
inline uint16_t counter_read(void)
{
 
 LPTMR0_CNR 0// writing cause sync with hardware
 
 return LPTMR0_CNR;
}

static 
inline uint8_t counter_overflow(void)
{
 
 return (LPTMR0_CSR 0x80) ? 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) rauswerfen und einen Frequenzzähler nur für den Arduino Nano bzw. für Arduinos mit ATmega328 und 168 erstellen. Zwei sehr kurze Implementierungen per direkter Registerprogrammierung habe ich von Kursteilnehmern schon gesehen! Ich bin angenehm überrascht und es zeigt doch das selbst Programmieranfänger mit beiden Wegen (Arduino-Highlevel/Register) klar kommen und wunderbare Ergebnisse erzielen können. Das Ergebnis zeigt aber auch, wie uns die Arduino-Bibliotheken funktionell einschränken und welche Leistungseinbußen man hinnehmen muss. Dafür ist "Arduino" natürlich leichter erlernbar, weil die Befehle fast im Klartext sind und keine "kryptischen" Registerbezeichnungen enthalten. Das Datenblatt des Controllers braucht man in der Arduino-Welt deshalb kaum oder gar nicht.  

Sind wir mit der Aufgabe schon fertig? Nein, denn 

1. Die ZF-Korrektur : Von der Variablen Count oben im Programm muss man die ZF nur noch addieren/subtrahieren!

2. Der Controller kann immer noch nur TTL-Pulse zählen. Wir brauchen also einen HF-Verstärker.

Für andere (fertige) Frequenzzähler habe ich hier im Forum schon einige solche Vorverstärker mal gezeigt, zum Beispiel diese hier:


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

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

Es gibt unzählige andere Schaltungen die ebenso geeignet sind. Mit so einem Vorverstärker ist dann die Aufnahme der "HF" am Mischer eines Radios kein Problem mehr und man kann nach Korrektur um den Betrag der ZF die eingestellte Empfangsfrequenz ablesen.

3. Die Ansteuerung an das 7.Seg-Modul läuft analog der Musterlösung von Lektion 3, da aber jeder eine eigene Anschaltung des Moduls vorgenommen hat, belasse ich es in dieser Musterlösung bei der Ausgabe auf der seriellen Schnittstelle.

In Lektion 5 kümmern wir uns um "Analogsignale".

Gruß 
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#7
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 5  ADC / DAC, analoge Signale

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

Endlich toben wir uns mit Analogsignalen richtig aus!

Diesmal gibt es auch nur eine Zusatzaufgabe in der wir den Arduino Nano als Signalprozessor nutzen wollen, die zu bearbeitende Audiodatei findet ihr hier:


.zip   NFgestoert120Hz_SR8kHz.zip (Größe: 229,11 KB / Downloads: 16)  

Viel Spaß mit Lektion 5 und dem uC-Basteln,

Keep coding
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#8
Hallo zusammen, 

Fangen wir mit der Aufgabe 1.1 aus Lektion 5 an. Hier nochmal der Aufgabentext:

1.1 . Kondensatormessgerät
 
Modifiziere das oben gezeigte Programm zur Kapazitätsmessung von Kondensatoren dahingehend, dass auch kleinere Kapazitäten noch mit ausreichender Genauigkeit ermittelt werden können. 

Zur Erinnerung nochmal das Kapazitätsmessprogramm aus dem PDF der 5. Lektion.

PHP-Code:
#define ADC_PIN        A0          // ueber diesen Pin ueberwachen wir die Aufladung des Kondensators
#define LADE_PIN       13          // ueber diesen Pin laden wir den Kondensator auf
#define ENTLADE_PIN    11          // ueber diesen Pin entladen wir den Kondensator
#define LADEWIDERSTAND  10000.0F   // 10k Ladewiderstand

unsigned long ulStartzeit;
unsigned long ulLadeZeit;
float microFarads               
float nanoFarads
;

void setup(){
 
 pinMode(ADC_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, dass wir im Hauptprogramm zunächst das Laden des Kondensators über den definierten Strom eines Ladewiderstandes bewirken. Gleichzeitig merken wir uns den Zeitpunkt, wann wir das Aufladen des Kondensators starten. In der While-Schleife prüfen wir kontinuierlich den Ladezustand des Kondensators und zwar solange, bis 63,2% "Füllstand" erreicht ist. Danach notieren wir uns wieder die Zeit, um die Zeitdauer des Ladevorgangs zu bestimmen. Anschließend erfolgt die Berechnung der Kapazität und die formatierte Ausgabe auf der Seriellen Schnittstelle. Ist das geschehen, wird der Kondensator über den Entladepin mit einem Entladewiderstand geleert. Danach erfolgt eine neue Messung und so weiter. Wird ein noch geladener Kondensator angeschlossen (denkt dran was der ATmega so maximal am Pin sehen darf!), so ist die erste Messung zu ignorieren. Durch Umstellung des Codes (Vorziehen des Entladevorgangs) könnte man dies auch verhindern.

Hier mal eine Beispielmessung für ein paar Kondensatoren.

10uF Elko:


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


220uF Elko:


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

Soweit so gut für "große" Kondensatoren, die für das Aufladen etwas Zeit brauchen.

Bei kleineren Kondensatoren sieht die Sache anders aus:

0.47 uF: 


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

Wie wir sehen, wird bei kleinen Kondensatoren die Ladezeit auch immer kleiner und wir kommen langsam an die Auflösungsgrenze unserer Zeitmessung mit millis() !

0.1 uF:


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

Jetzt kann sich der Wert aber stark unterscheiden. Wir haben die Auflösungsgrenze für die Zeitmessung erreicht und nichts geht mehr.

Hier kommt jetzt der modifizierte Quellcode aus Aufgabe 1.1 zum Zuge. Viele Kursteilnehmer haben das Problem mit der Zeitauflösung richtig erkannt.

PHP-Code:
#define ADC_PIN        A0          // ueber diesen Pin ueberwachen wir die Aufladung des Kondensators
#define LADE_PIN       13          // ueber diesen Pin laden wir den Kondensator auf
#define ENTLADE_PIN    11          // ueber diesen Pin entladen wir den Kondensator
#define LADEWIDERSTAND  9938.0F   // 10k Ladewiderstand, ausgemessen !!!
#define ZEIT_NULLMESSUNG 112       // Messzeit ohne Kondensator zur Korrektur

unsigned long ulStartzeit;
unsigned long ulLadeZeit;
float microFarads               
float nanoFarads
;

void setup(){
 
 pinMode(ADC_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: 627)

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

Zur Steigerung der Genauigkeit haben wir den Ladewiderstand ausgemessen und den pauschalen Soll-Wert dafür im Quellcode gegen den tatsächlich gemessenen Widerstandswert ausgetauscht. Da der ganze Programmcode auch bei offenen Leitungen etwas Rechenzeit benötigt, haben wir zusätzlich diese von der CPU benötige Zeit in Form von ZEIT_NULLMESSUNG erfasst und bei der Bestimmung der Ladezeit auf 62,3% Füllstand berücksichtigt. Auch so erhöhen wir die Genauigkeit. 

Weitere Musterlösungen zur 5. Lektion folgen.

Gruß 
Bernhard

PS: Natürlich sind das bei micros() in den Bildern nicht mehr Millisekunden sondern Mikrosekunden in der Ausgabe auf der seriellen Schnittstelle, sehe ich jetzt erst, mein Fehler!
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#9
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: 577)

4.7 kOhm


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

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

10 Ohm


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


oder 470 kOhm


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

Die 10 Bit reichen hier nicht mehr aus, um den Spannungsabfall über den unbekannten Widerstand genau zu erfassen. Eine Lösung des Problems könnte die parallele Verwendung mehrerer bekannter Widerstände (klein, mittel, groß) und die Erfassung der unterschiedlichen Spannungsabfälle mit mehreren ADC-Kanälen sein. Allein in der Software wird dann eine Bereichserkennung/Auto-Ranging realisiert, die nachschaut welcher bekannte R zum unbekannten R am Besten passt und welche Messung den genausten Widerstandswert ergibt.
Eine andere aber teurere Lösung wäre die Nutzung eines (externen) ADC mit 14, 16, 18, … Bit-Auflösung. Hier würde man das Genauigkeitsproblem einfach mit einer größeren ADC-Auflösung beseitigen. 
Eine weitere Genauigkeitssteigerung erreicht man, wenn man im Quellcode den tatsächlich ausgemessenen Wert des bekannten Widerstandes einfügt, generell nimmt man für solche Anwendungen idealerweise aber sowieso Präzisionswiderstände.

Zum L-Meter: 

Stichwort Frequenzzähler! 

Mit einem bekannten C und einem unbekannten L bauen wir einen Oszillator auf! Die Oszillatorfrequenz ermitteln wir mit dem Quellcode des Frequenzzählers von oben. Über die serielle Schnittstelle geben wir aber nicht die ermittelte Oszillatorfrequenz aus, sondern wir berechnen mit der Thomsonschen Schwingungsgleichung (nach L umgestellt natürlich) den Wert für die gesuchte Induktivität und geben diesen statt der Frequenz aus.

Weitere Musterlösungen zu Lektion 5 folgen.

Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#10
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: 521)
.jpg   PWM_LED2.JPG (Größe: 120,65 KB / Downloads: 521)

bzw. am Oszi betrachtet:


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



Für eine Ansteuerung eines Modellbauservos müssen wir uns fragen wie so ein Servo überhaupt per PWM angesprochen werden will.
Die meisten Servos (nicht alle!) wollen ein Signal nach folgenden Muster haben:


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

Jedes "Frame" am Servo Eingang hat eine Zeitdauer von 20ms, die eigentliche Position der Rudermaschine wird aber durch die Länge des High-Pegels bestimmt. Dieser liegt im Idealfall zwischen 1 und 2ms , wobei eine gewisse geringe Toleranz unter den verschiedenen Modellen und Herstellern doch noch als recht normal anzusehen ist.


Hier eine Animation der Ansteuerung einer solchen Rudermaschine.

   
(zur Animation ggf. aufs Bild klicken, es ist ein GIF)
 
Der Code dafür könnte auf einem Arduino und mittels Software-PWM so aussehen:

PHP-Code:
#define PWM_OUT 3
#define ADC_PIN A0
#define PULSE_LAENGE 20000 // 20ms = 20000 us

int iADCWert 0;

void setup() {
 
 Serial.begin(9600);
 
 pinMode(PWM_OUT,OUTPUT);
 
 pinMode(ADC_PIN,INPUT);  
}

void loop() {
 
 // put your main code here, to run repeatedly:

 
 iADCWert analogRead(ADC_PIN);
 
 iADCWert map(iADCWert,0,1023,1000,2000); // 0-1023 auf 1 bis 2ms skalieren 
 
 Serial.print("Pulselänge:"); Serial.println(iADCWert);
 
 digitalWrite(PWM_OUT,HIGH);
 
 delayMicroseconds(iADCWert);
 
 digitalWrite(PWM_OUT,LOW);
 
 delayMicroseconds(PULSE_LAENGE-iADCWert);
 
   



In einer Endlosschleife wird zunächst die Stellung des Potis mit einem ADC ermittelt. Der eingelesene Wert ist bei 10-Bit Auflösung also zwischen 0 und 1023.  Nun muss dieser Wert auf den Zeitbereich 1 bis 2ms abgebildet werden. Eine solche Verhältnisbildung kann auf "normalen" Rechenweg erfolgen, aber die Arduino-Bibliotheken bieten dafür auch eine wunderbare Funktion namens map an.

Schauen wir uns den Syntax von map an.

Syntax

map(value, fromLow, fromHigh, toLow, toHigh);

Parameters

value: the number to map.
fromLow: the lower bound of the value’s current range.
fromHigh: the upper bound of the value’s current range.
toLow: the lower bound of the value’s target range.
toHigh: the upper bound of the value’s target range.

Returns

The mapped value.


Nach dem Mappen des per Poti eingestellten Zeitwertes, erfolgt ganz normal die Erzeugung des High- und Low-Teils am Ausgabepin des Controllers.

Bis hierin waren die Hausaufgaben wohl relativ einfach wie viele eingesandte Lösungen zeigen. Die nächste Aufgabe 1.4 "Digitales Potentiometer" war dann wohl schon nicht mehr so einfach zu lösen, aber das schauen wir uns im nächsten Abschnitt an.

Weiterhin viel Spaß beim Basteln und Programmieren, 

Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#11
Kommen wir zur letzten Hausaufgabe aus Lektion 5:

1.4. Digitales Potentiometer   

Ein Audiosignal (Mono genügt) soll über einen ADC des Arduino digitalisiert werden und in der Lautstärke zwischen 0 und 200% einstellbar sein (also abgeschwächt/verstärkt werden). Die digital bearbeitete NF soll über einen DAC wieder ausgegeben werden. Der Grad der Verstärkung/Abschwächung kann über zwei Taster (+/-) oder ein Potentiometer eingestellt werden.  
Ein mögliches Clippen des Signals bei zu großen Amplituden kann zunächst unberücksichtig bleiben und muss nicht vom Algorithmus abgefangen werden.

Hier mal ein kurzer Beispielcode wie so etwas könnte aussehen:

PHP-Code:
#define ADC_PIN A0

#define TASTER1 2
#define TASTER2 3

#define WARTEZEIT 25 

bool bFlankenwechselT1 false;
bool bFlankenwechselT2 false;

unsigned long ulTime 0;

volatile float fFaktor 1;

void setup() {
 
 // put your setup code here, to run once:
 
 Serial.begin(9600);
 
 initR2RDAC();
 
 initTimer4ADC();
 
 pinMode(TASTER1,INPUT_PULLUP);
 
 pinMode(TASTER2,INPUT_PULLUP);
 
 ADC_Init();
}

void initTimer4ADC() {  
// TIMER 2 for interrupt frequency 15037.593984962406 Hz:
cli(); // stop interrupts
TCCR2A 0// set entire TCCR2A register to 0
TCCR2B 0// same for TCCR2B
TCNT2  0// initialize counter value to 0
// set compare match register for 15037.593984962406 Hz increments
OCR2A 132// = 16000000 / (8 * 15037.593984962406) - 1 (must be <256)
// turn on CTC mode
TCCR2B |= (<< 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. Im letzten Fall, wird über ein Interrupt ein gewisses Arbeitstempo fest vorgegeben. Der ATmega328 hat ja mehrere Timer die dazu genutzt werden können. Im oberen Beispiel wurde der T2 so konfiguriert, das er mit 15037.593984962406 Hz arbeitet. Die Berechnung dazu geht übrigens sehr einfach mit entsprechenden Onlinerechnern. Über die Begriffe "Arduino Timer Interrupt Calculator" kommt man zu vielen Webseiten die solche Rechner anbieten, zum Beispiel hier:

http://www.8bit-era.cz/arduino-timer-int...lator.html

Hier gibt man die Taktfrequenz des Controllers und die gewünschte Interruptfrequenz ein, danach wird automatisch der Code für die Registerkonfiguration generiert, siehe Bild:

   

Man kopiert also nur noch den Code den man benötigt rüber in die Entwicklungsumgebung. 

Die eigentliche Funktionalität des Potentiometers steckt, wie unschwer zu erkennen,  in der Interrupt-Serviceroutine. In unserem Fall also

PHP-Code:
ISR(TIMER2_COMPA_vect){
 
  //interrupt commands for TIMER 2 here
 
  write2DAC(map(ADC_Read(0)*fFaktor,0,1024,0,255));


Was macht die verschachtelte Anweisung?

1. Es wird das Signal über den ADC-Kanal 0 eingelesen,

2. danach wird das Signal über die Variable fFaktor abgeschwächt oder verstärkt, die Variable selbst wird über zwei Taster um den Wert 0,01 erhöht oder verringert. Die Abfrage der Taster ist in diesem Beispiel einzig und allein die Aufgabe der loop. 

3. Da uns das Clipping nach Aufgabentext erstmal nicht interessiert, kann der skalierte Wert aus dem ganzen 10-Bitwertebereich in ein 8-Bitwertebereich ge-mapped werden. Auch ein 10-Bit zu 10-Bit wäre denkbar, aber hier im Beispiel wird nur ein 8-Bit R2R-DAC realisiert. 

4. der auf 8-Bit festgelegte Wert wird über write2DAC (unsere eigene Routine für einen R2R-DAC) ausgegeben.

Das Ergebnis sieht dann so aus.


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

Oben sehen wir das Eingangssignal welches von unserem "DSP" digitalisiert wird, unten ist das Ausgangssignal zu sehen. Hier wird weder "verstärkt" noch "abgeschwächt".

Ziehen wir beide Signale mal auf gleiche Höhe.


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

Wir sehen das das Ausgangssignal dem Eingangssignal einen kleinen Moment hinterher hängt. Dies kommt durch die im "DSP" verbrauchte Rechenzeit welche benötigt wird das Signal zu digitalisieren, zu skalieren und wieder auf den DAC zu schreiben. Je nach Effektivität des Programmcodes oder der Geschwindigkeit eines DSPs, lässt sich diese Zeitdifferenz weiter schrumpfen, ganz werden wir Sie nie los, selbst mit dem schnellsten DSP den wir haben können. 

Über die beiden Taster können wir dann das Ausgangssignal "verstärken" oder "abschwächen".


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

Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#12
Kurs „Einstieg in die Mikrocontroller-Programmierung”
Lektion 6 -  externe Bibliotheken, C++

Hallo Kursteilnehmer. 

Heute möchte ich den Einstiegskurs mit der 6. Lektion abschließen.

In der letzten Lektion geht es um Fremdbibliotheken für den Arduino und um einen kurzen Einblick in die Weltansicht "C++".

Eine Hausaufgabe wird es diesmal nicht geben.

Hier nun die PDF für Lektion 6: 
.pdf   Lektion6.pdf (Größe: 1,02 MB / Downloads: 36)

Danke für das aktive Mitmachen und fürs Durchhalten! Ihr habt die Lektionen und Aufgaben großartig bewältigt!

Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren


Möglicherweise verwandte Themen…
Thema Verfasser Antworten Ansichten Letzter Beitrag
  Einsteigerkurs Mikrocontrollerprogrammierung Bernhard45 13 6.604 23.05.2020, 23:55
Letzter Beitrag: Bernhard45

Gehe zu: