Details dazu sind auch unter Die Timer und Zähler des AVR bei mikrocontroller.net zu finden.
Nach dieser Lektion sollten Sie:
Nach dieser Lektion sollten Sie:
2_sound_und_timer.hex
als firmware auf den Atmega88 Chip2_sound_und_timer
für einen ATmega88 an/*============================================================================= Experiment 2: Sound-Generator ============= =============== Dateiname: 2_sound_und_timer.c Autoren : Peter Blinzinger Prof. G. Gruhler (Hochschule Heilbronn, Fakultät T1) D. Chilachava (Georgische Technische Universität) T. Fischer (Hochschule Heilbronn, Fakultät T1) Version: 1.3 vom 19.09.2022 Hardware: MEXLE2020 Ver. 1.0 oder höher AVR-USB-PROGI Ver. 2.0 Software: Entwicklungsumgebung: AtmelStudio 7.0 C-Compiler: AVR/GNU C Compiler 5.4.0 Funktion: Auf einem kleinen Lautsprecher (Buzzer) wird nur einem MEXLE-Board ein sirenenartiger Sound ausgegeben. Zwischen den auf- und absteigenden Tönen bleibt die Frequenz kurz stabil. Die Frequenz wird mit dem Timer 0 (im CTC-Mode) erzeugt und direkt über den Output-Compare-Pin im Toggle-Mode ausgegeben. Displayanzeige: +----------------+ |- Experiment 2 -| | Creating Sound | +----------------+ Tastenfunktion: keine Jumperstellung: Schalter muss fuer den Buzzer betätigt sein Fuses im uC: CKDIV8: Aus (keine generelle Vorteilung des Takts) Header-Files: lcd_lib_de.h (Library zur Ansteuerung LCD-Display Ver. 1.3) =============================================================================*/
Autor
: Der folgende Code basiert auf eine Version verschiedener Autoren, diese sind hier angegeben. Wenn Sie einen eigenen Code generieren sollte hier Ihr Name stehen. Dies ermöglicht eine Nachvollziehbarkeit bei UnklarheitenVersion
: Um die Aktualität der Software zu erkennen sollte mindestens das Datum angegeben und bei Änderung immer aktualisiert werdenHardware
: Die getestete bzw. verwendete Hardware sollte angegeben werden, um nachvollziehen zu können, wie der Code getestet werden kann. Bei Simulationen sollte hier mindestens der verwendete Chipsatz (z.B. ATmega88) angegeben werden. Software
: Zum weiterverwenden des Codes ist die Angabe der Entwicklungsumgebung (engl. integated Development environment IDE) wichtig. Nicht selten gibt es bei größeren Projekten Schwierigkeiten, wenn IDE und Compiler geändert werden.Funktion
: Die Funktion des Programms sollte kurz erklärt werden. Damit wird dem Leser bereits vor dem Code schon Hinweise gegebenDisplay-Anzeige
: Ähnlich der Funktion ist auch eine Darstellung der (erwartbaren) Anzeige sinnvoll.Tastenfunktion
: Bei zukünftigen Anwendungen kann eine Eingabe von Tastenstellungen sinnvoll sein. Dies sollte hier angegeben werdenJumperstellungen
: Jumper bieten die Möglichkeit unterschiedliche Schaltungsteile der Hardware zu verbinden oder zu trennen. Damit können Hardwarefunktionalitäten aktiviert oder deaktiviert werden. Wenn verschiedene Jumperstellungen für ein Programm wichtig sind, so sollten diese angegeben werden.Fuses im uC
: Beim Microcontroller (auch μC oder uC abgekürzt) bieten die Möglichkeit interne Konfigurationen anzupassen. Diese werden über Fuses eingestellt werden. Sind hier Funktionen für das Programm notwendig, so sollten diese angegeben werdenHeader-Files
: Werden weitere Softwareteile genutzt, so sollten diese über Header-Dateien eingebunden. Diese sollten bereits schon vor dem eigentlichen Codeteil kurz erklärt werden. // Deklarationen ============================================================== // Festlegung der Quarzfrequenz #ifndef F_CPU // optional definieren #define F_CPU 18432000UL // MiniMEXLE nutzt einen ATmega88 mit 18,432 MHz Quarz #endif // Include von Header-Dateien #include <avr/io.h> // I/O Konfiguration (intern weitere Dateien) #include <util/delay.h> // Definition von Delays (Wartezeiten) #include "lcd_lib_de.h" // Funktionsbibliothek zum LCD-Display // Konstanten #define MIN_PER 143 // minimale Periodendauer in "Timerticks" #define MAX_PER 239 // maximale Periodendauer in "Timerticks" #define WAIT_TIME 2000 // Wartezeit zwischen Flanken in ms // Makros #define SET_BIT(BYTE, BIT) ((BYTE) |= (1 << (BIT))) // Bit Zustand in Byte setzen #define CLR_BIT(BYTE, BIT) ((BYTE) &= ~(1 << (BIT))) // Bit Zustand in Byte loeschen #define TGL_BIT(BYTE, BIT) ((BYTE) ^= (1 << (BIT))) // Bit Zustand in Byte wechseln (toggle) // Funktionsprototypen void initDisplay(void); // Initialisierung Display und Startanzeige void initPorts(void); // Initialisierung der I/O-Ports void initTimer(void); // Timer 0 initialisieren (Soundgenerierung) void init(void); // generelle Initialisierungsfunktion
Quarzfrequenz
: Die Taktfrequenz des Microcontrollers kann entweder intern oder extern definiert werden. Diese Frequenz sollte immer angegeben werden. Wird dies nicht vorgenommen, kann es Probleme bei der Handhabung von Wartezeiten („Delays“) geben. In Simulide kann die Frequenz des externen Quarz eingegeben werden - diese sollte zum in der Software angegebenen Frequenz passen. Die Angabe #ifndef
ist hier eine Compiler-Direktive und keine C-Code. Wie alle anderen Deklarationen sind alle Zeilen nach der einem #
vor der Ausführung des Codes im Controller zur Zeit der hexfile-Erstellung im Compiler wichtig. #ifndef
prüft hierbei, ob ein Symbol bereits in einem anderen File definiert wurde.Header includes
: Header-Dateien sollten bereits aus der Informatik 2 bekannt sein. Bei IDEs wird häufig zwischen Dateien unterschieden, welche in den Ordnern der IDE und Dateien, welche im Projektordner liegen. Hier sollen folgende Header-Dateien genutzt werden:<avr/io.h>
: Header-Datei, welche Input/Output Bezeichner für Pins und Ports definiert. Da im Folgenden bestimmte Ports angesprochen werden sollen, ist diese Header-Datei wichtig. Die Header-Datei liegt im Unterordner avr
in den Ordnern der IDE. Diese wurde bereits schon im Bespiel 1_hello_blinking_world verwendet.<util/delay.h>
: Header-Datei, welche einen einfachen Umgang mit Wartezeiten ermöglicht. Diese wurde bereits schon im Bespiel 1_hello_blinking_world verwendet."lcd_lib_de.h"
: Diese Header-Datei sollte im Projektordner eingefügt sein. Bei einem neuen Projekt ist sie dies noch nicht.
Bitte fügen Sie die Datei lcd_lib_de.h in den Projektordner ein
Dazu sollten Sie im Solution Explorer auf das Projekt rechts-klicken (hier 2. Sound und Timer
) » Add » Existing Item… (siehe Bild rechts).
Die gewünschte Datei (hier: die heruntergeladene lcd_lib_de.h
) auswählen und mit Add hinzufügen. Die Datei sollte nun im Solution Explorer angezeigt werden.
#defines
: Über #defines
kann vorgegeben werden, welcher Text durch den Präprozessor im Code ersetzt werden soll. Damit können Konstanten oder kurze Codeersetzungen (Makros) vorgegeben werden. In diesem Programm soll ein maximale und minimale Periodendauer, sowie eine Haltedauer für den höchsten und niedrigsten Ton vorgegeben werden. Zusätzlich sind drei Standardmakros vorgegeben, um SET_BIT(BYTE, BIT)
), CLR_BIT(BYTE, BIT)
), oderTGL_BIT(BYTE, BIT)
). Funktionsprototypen
: Wie regulär bei der C-Programmierung sollten die verwendeten Funktion dem Compiler bekanntgemacht werden. // Hauptprogramm ============================================================== int main() { init(); // Ports und Timer 0 initialisieren initDisplay(); // Display aktivieren while(1) // Start der unendlichen Schleife { for (OCR0A=MAX_PER; OCR0A>MIN_PER; OCR0A--) // Frequenz erhöhen { _delay_ms(10); // in Schritten von 10ms } _delay_ms(WAIT_TIME); // Wartezeit hohe Frequenz TGL_BIT(PORTB,PB2); for (OCR0A=MIN_PER; OCR0A<MAX_PER; OCR0A++) // Frequenz absenken { _delay_ms(10); // in Schritten von 10 ms } _delay_ms(WAIT_TIME); // Wartezeit niedrige Frequenz TGL_BIT(PORTB,PB2); } // Ende der unendlichen Schleife }Das Hauptprogramm besteht aus folgenden Teilen:
init()
und initDisplay()
vorgenommenwhile(1){
…}
vorgegeben.OCR0A
von der maximalen Periodendauer MAX_PER
zur minimalen MIN_PER
heruntergezählt und beim Zählschritt jeweils 10 Millisekunden gewartet (_delay_ms(10)
). Wie im Video dargestellt, bietet es sich an für die Details zum Output Compare Register (OCR0A
) den entsprechenden Teil des ATmega88-Datenblatts durchzulesen. Als leichten Einstieg kann auch die deutsche Übersetzung des ATmega88-Datenblatts per Index nach OCR0A
durchsucht werden.WAIT_TIME
Millisekunden gehalten werden. // Funktionen ================================================================= // Generelle Initialisierungsfunktion void init() { initPorts(); // Ports auf Ausgang schalten initTimer(); // Timer zur Sounderzeugung starten } // Initialisierung der I/O-Ports void initPorts() { DDRB |= (1<<DDB2); // Port B, Pin 0 (zur LED) auf Ausgang DDRD |= (1<<DDD5); // Port D, Pin 5 (zum Buzzer) auf Ausgang } // Intialisierung des Timers 0 fuer Sounderzeugung void initTimer() { TCCR0A = (1<<WGM01) |(1<<COM0B0); // CTC Mode waehlen und Toggle Mode nur für OC0B aktivieren TCCR0B = (1<<CS01 | 1<<CS00); // Timer-Vorteiler /64 OCR0A = MAX_PER; // Start mit tiefstem Ton }
void init()
: Bei längeren Programmen bietet es sich an eine übergeordnete init-Funktion anzulegen, aus welcher die einzelnen Initialisierungen von Sensoren und ähnlichem aus aufgerufen werden. void initPorts()
: In dieser Funktion werden die Data Direction Register der Ports B und D korrekt zugewiesen. void initTimer()
: für das Timer Modul muss der gewünschte „Clear Timer on Compare“ Modus und Hardware-Vorteiler gewählt werden. COM0B0 = 0
ein Invertieren des Ausgangs eingestellt wurde, wäre die höchste ausgegebene Frequenz $f_{\rm out, max}= f_{\rm Timer}/2 = 144'000 ~\rm Hz$. OCR0A
wiefolgt gesetzt werden: \begin{align*} {\rm OCR0A}=f_{\rm out, max}/f_{\rm out}-1=144'000 ~\rm Hz / 1'000~\rm Hz - 1= 143 \end{align*} Dies entspricht gerade MIN_PER = 143
. // Initialisierung der Display-Anzeige void initDisplay() // Start der Funktion { lcd_init(); // Initialisierungsroutine aus der lcd_lib lcd_gotoxy(0,0); // Cursor auf 1. Zeile, 1. Zeichen lcd_putstr("- Experiment 2 -"); // Ausgabe Festtext: 16 Zeichen lcd_gotoxy(1,0); // Cursor auf 2. Zeile, 1. Zeichen lcd_putstr(" Creating Sound "); // Ausgabe Festtext: 16 Zeichen } // Ende der FunktionIn der Funktion
void initDisplay
wird das Display angewiesen Daten auszugebenlcd_init()
: Diese Unterfunktion sollte immer ausgeführt werden, bevor das Display angesprochen werden soll. lcd_gotoxy(x,y)
: Diese Funktion weist das Display an die kommende Ausgabe an der Position x,y auszugeben.lcd_putstr(string)
: Gibt einen vordefinierten Text an der aktuellen Position aus.
Sie sollten sich nach der Übung die ersten Kenntnisse mit dem Umgang der Umgebung angeeignet haben. Bitte arbeiten Sie folgende Aufgaben durch:
main()
auf WAIT_TIME
und dann auf Goto implementation. Sie werden feststellen, dass der Cursor auf die Deklaration des Wertes springt. Versuchen Sie selbiges bei initDisplay
an lcd_putstr
und wählen Sie die Variante, welche ein {…}
angefügt hat. Hier sehen Sie die den Code in der header-Datei, der für die Übergabe des Strings an das Display verantwortlich ist. Dort kann in gleicher Art lcd_putc
und lcd_write
weiterverfolgt werden. In lcd_write
und lcd_enable
wird die Übergabe der Werte an das Display abgearbeitet. Dazu werden zunächst die Daten am Datenport ausgegeben und anschließend die Steuerleitung gepulst aktiviert. lcd_gotoxy
nach. Wie wird das übertragen?OCR0A
als Vorgabe genutzt, aber OC0B
als Ausgabe gewählt (siehe Zeile: TCCR0A = (1<<WGM01) |(1<<COM0B0)
)?