Skript
Bildschirmlupe an!
SW2 Hello Display World - fast Counter
- Wdh. Hello Blinking World:
DDRx
,PORTx
,_Delay_ms()
-->R
steht fast immer für Register- --> einmal kompilieren und in Simulide aufbauen
- Welche „Vorgaben für die SW-Entwicklung“ wurden verletzt? --> Keine magic numbers, sondern #defines !
siehe Weiterführende Fragen und Infos
- Heute „Hello World“ in echt! Timer + Displayausgabe
- „Kapitel 2 Sound und Timer bitte nachträglich anschauen“
- Frage an Studis „Wer weiß nicht was PWM ist?“
In MC Studio
- neues Projekt
02_timer
- jetzt neu: mit Display!
- --> Bibliothek aus wiki herunterladen!
- Project --> Add --> existing Item (NICHT drag & drop)
- bei mir --> F2 Namen ändern auf
lcd_lib_de.h
- Split Screen
- Was tun, um Lib in main einzufügen?
#include
!#inc
+ <Tab>- --> Unterschied
<lib.h>
vs"lib.h"
- Durchsicht der
lcd_lib_de.h
F_CPU
- --> CPU Frequenz, wichting für genaues Timing der delays
- hier
18,432 MHz
--> Minimexle Frequenz - Warum
18'432'000 Hz
?- ILIAS --> Elektronik Labor --> MiniMEXLE Schaltbild
- „Schreck!“ sowiel Krams auf dem Schaltplan!
- Wo ist der Quarz? Quarz schwingt mechanisch im E-Feld --> Schaut im Bild aus wie ein Kondensator
- defines --> keine Magic numbers
- Funktionsprotoypen --> bitte immer am anfan angeben --> gut für eine Übersicht
- als erstes immer Initialisierung (anlegen der Variablen, verschiedene Konfigurationen etc.)
lcd_i
+ <tab>- schon mal kompilieren (immer mal kompilieren zum test, ob noch alles klappt)
- noch nicht lauffähig, da nichts angezeigt !
- einen String ausgeben!
- welche Unterfunktion wohl geeignet?
- Hinweis auf Inkonsistenz bei Namensgebung
- Eingabe
lcd_displayMessage("Hallo!", 0,0 )
--> Hinweis auf Zählanfang 0 nicht 1!
- Flashen auf Minimexle
- Add Target --> STK500 --> ersten COM Port auswählen (und - falls es nicht passt - den nächsten)
- Tools --> Device Programming
- Apply --> Device Signature sichtbar?
- --> Memories --> Program
- Ausgabe von
Hallo! Zähler:
- kann
ä
nicht schreiben , sondern schreibtµ
, warum?- --> Datasheet lesen!
- Am besten in der Schaltung den Namen suchen
- Googeln nach DEM16216 Datasheet --> Datenblatt etwas kurz? Blockdiagramm (immer schön Bilder in eigene Dokus machen!) --> ST7066U!
- Googeln nach ST7066U Datasheet
- Kurzes darüberscrollen über das Datasheet
- --> Character code Table! --> ist da
ä
drin? In einer schon… In der anderen isµ
beim gleichen Bitmuster - Also: was tun? entweder
á
nutzen, oderldc_putc(11100001);
--> was wurde vergessen? -->%
!
- Vergleich in Simulide:
- Aufbau der Schaltung: mega88 + Hd44780 (ist kompatibel zu ST7066U)
- Wie verbinden? Siehe lib (wenn gut beschrieben) oder MEXLE Schaltung
- In lib: Port-Bits.
PIN_EN
,PIN_RS
--> wo in Simulide? - Für was steht
EN
? --> Enable.RS
--> Register Select PORT_DATA
: vonPORTC
nur die ersten 4 bits (0…3)- in Simulide
18,432 MHz
eingeben! - hex file Flashen
- --> animation einschalten (High/Low wird angezeigt)
- es wird noch nichts ausgegeben?? --> im Code schauen oder im Schaltplan!
PC0
aufD4
,PC1
aufD5
,PC2
aufD6
,PC3
aufD7
- jetzt klappts, aber
ä
an falscher Position
lcd_gotoxy
einfügen- In Simulide autoload einschalten!
Jetzt: aufsteigende Zahlen ausgeben Was tun?
- Laufvariable anlegen und nutzen:
uint8_t i=0;
undi++
in der Schleife - wie gibt man Zahlen aus?
sprintf
? (kann in String einen Zahlenwert ausgeben) sprintf(output_str, "i:%03u", i);
3
--> drei Dezimalstellen,u
--> unsignedoutput_str
deklarierenlcd_displayMessageoutput_str, 1,0);
- kompilieren -->
#include <stdio.h>
vergessen - Simulation herunterdrehen
aktuell zählt er nicht so schnell wie die CPU kann, sondern so schnell wie er es ausgeben kann. Die CPU kann aber schneller!
- Blick ins Datenblatt des atmega88
- Blockbild des atmega
- Vergleich mit Zahnarztpraxis
- „Zahnarzt“ macht nicht alles, sondern nur komplexere Dinge
- viele Helfer (Servants) die der CPU zuarbeiten
- PORT's unten kennen wir schon. Sind die Türsteher (doorman) für die Anschlüsse
- Aber auch Analog Digital Wandler, nicht flüchtiger Speicher EEPROM (non-volatile memory) und mehr
- Wir werden Timer und Counter nutzen
- T/C im Inhaltsverzeichnis
- gibts mehrere: 8 bit TC0, 16 bit TC1 und 8 bit TC2
- wir nehmen 16 bit Timer/Counter und gehen zu diesem Kapitel
- 16 bit Timer/Counter
- wieder Blockbild, diesmal vom Timer / Counter
- wieder echt kompliziert auf den ersten Blick
- wichtig sind immer die Register
TCNTn
- --> timer Counter ; für was steht
n
? in Mathe? --> Schaubild gilbt für alle 3 Zähler. Hier ist n=1, da TC1! - das ist der eigentliche aufsteigende Zählwert
- kann auch beschrieben werden
OCRnA
--> Output Compare („Wert zum gegen-checken“)TCCRnA
--> für was steht TC? Timer Counter! Für was R? Register! --> hier neu: C für control
- Zeilen mit
sprintf
undlcd_displayMessage
kopieren# - bei Ausgabe Position ändern:
lcd_displayMessageoutput_str, 1,**7**);
- diesmal:
sprintf(output_str, "TC:%03u", TCNT1);
--> an zweiter Pos ausgeben. --> wichtig:3
in5
ändern!- kompilieren und in Simulide starten
- TC zählt noch nicht!
- siehe Blockdiagramm: Control logic --> steht im folgenden in der Register Description
- Blick in die Tabellen, was bei Initialisierung des uC mit 0 passiert
Speziell die Register description! - bei vielen ergibt
0
normal operation - aber bei CS (clock select) bedeutet
000
= keine Clock. Da kommt nix raus! - mit CS kann der Vorteiler (prescaler) gesetzt werden, welcher für die CPU Clk zum zählen herunterteilt
- gut wäre
CS10
setzen --> schnellster Zähler - Wo ist
CS10
? im RegisterTCCR1B
- Eingabe von
TCCR1B |= 1<<CS10;
vor der main loop - jetzt zählt der Zähle echt schnell. Schneller als die Anzeige!
- Neue Challenge: Blinken ohne
_delay_ms()
- Mit Zähler möglich! Wie? mal selbst überlegen!
- z.B.
if(TCNT1>x){doBlink();TCNT1=0;};
- geht auch besser! Denn der Knecht kann noch mehr:
- Blick ins Datenblatt --> viele Seiten!
- Blick auf Block Diagramm des TC:
TCNTn
wird doch verglichen!- wo geht das Vergleichsergebnis hin?
- zu „Waveform generation“ und
OC1A (Int.req.)
- leider etwas irreführend, weil 2x
OC1A
(OC1A (Int.req.) müsste OCF1A heißen für Flag --> siehe Text)
- jetzt erstmal Datenblatt nach
OC1A
durchsuchen!- Oh! Kommt als Pin am Chip vor.
- Warum wohl?
- was könnte das „Waveform generation“ machen?
- suchen im Datenblatt nach Waveform generat (ohne ion!)
- (zunächst im 8-Bit Timer gefunden) Output compare unit, block diagram
- hier als „Waveform generator“
- Der Generator wird über die Register bits WGM und COM geändert
- (besser im 16-bit TC1 das Block diagramm nachsehen)
- wieder Register description ansehen
- erste Bits im TCCR1A sind COM..
- Blick in die Tabelle: was muss eingestellt werden, um eine Ausgabe für die blinkende LED zu bekommen?
11
z.B. delay für dauerhaftes Leuchten nach ein paar Millisekunden- wir brauchen
COM1B1=1
undCOM1B0=0
TCCR1A |= 1«COM1B0;
einfügen- UND:
DDRB = 255;
- Output-Pin togglt! Yeah
- Problem: wir können die Geschwnindigkeit nich richtig einstellen
- Also Challenge: zeitlich einstellbares toggeln
- Bisher: timer zählt stur von 0 … 65535, also er verwendet OCR1A gar nicht!
- Wäre gut den MaxWert einstellen zu können, also nach erreichen von OCR1A wieder zurück auf 0.
- Lösung über „Clear Timer on Compare“: CTC im Datenblatt suchen
- timing diagramm erklären
- Mal alle Wave Generation Modes ansehen:
- Normal: 0 … 65535, und dann wieder Sprung auf 0 --> OC1A ändert sich nur bei max Wert
- Fast PWM:
- 0 … max Wert, und dann wieder Sprung auf 0 --> OC1A bei TCNT>= OCR1A gleich 0, sonst 1
- Fast PWM kann verschiedene max Werte haben: 255, 511, 1023, ICR1 und sogar OCR1A selbst
- Ansteigende Flanke immer bei 0 --> links bündig!
- PWM, phase correct
- 0 … max Wert, und dann: max Wert … 0 --> OC1A bei TCNT>= OCR1A gleich 0, sonst 1
- halb so schnell weil doppelte Rampe
- mitten zentriert
- Für uns wichtig Mode 4: da denn OCR1A der Maxwert, also die Dauer für Ein (und Dauer für Aus) beim PWM
- Wir müssen
WGM12
setzen, das ist aber inTCCR1B
! TCCR1B |= 1«WGM12;
- Und OCR1A setzen:
OCR1AH = 0xFF;
,OCR1AL = 0xFF;
- Wichtig immer LED an Port bei Simulide! nicht nur auf die Animation vertrauen!
- Mit CS Clock select: kann es auch langsamer ausgegeben werden
- Ggf. auch Sound Code ansehen.
--> Ergebnisse:
- bitte immer eindeutige und konistente Namen in Ihrer Doku nutzen!
- Sonst werden nachfolgende Leser Probleme beim verstehen bekommen..
Einschub - Debugging
Debugging Beispiel: DisplayAndTimer_v02 in ILIAS
kleine Änderungen:
- Variable
str
initialisieren:char str[3]=„“;
- Umbenennen der Variablen über
Refactoring
»Rename
--> z.B.SwCounter
undOutputStr
- Beispielhaft: statt Deklaration in Funktion nun als globale Variablen und umgekehrter Reihenfolge
char OutputStr[3]=„“;
char SwCounter;
(also keine Initialisierung!) - Kompilieren und am Bildschirm den Output ansehen
- Problem: Zähler scheint nur zwischen 48 und 57 zu zählen!
Tipps
- Tipp 1: Verwenden von nicht genutzten Registern
- nach erstem
lcd_putstr
:TWDR=SwCounter;
- nach zweiten
lcd_putstr
:SPDR=SwCounter;
- In Simulide: MCU Monitor » Blick auf die beiden Register
- Komisch: OutputStr verändert sich zwischen beiden!
- Tipp 2: Nur absolut notwendigen Code betrachten
- Code soweit auskommentieren wie es nur geht.
- Tipp 3: Im Fall von Deklarationen: map Datei
- Nach
SwCounter
suchen - RAM Table über
MCU Monitor
- auf Byte PC umschalten
- Suchen der Speicherzelle
- Geschwindigkeit reduzieren
- Tipp 4: Logic Analyzer
PORTB|=1«PB3;
nach erstemlcd_putstr
PORTB&=~1«PB3;
nach zweitemlcd_putstr
- Logic analyzer beschreiben: wichtig Time Pos korrekt einstellen
- Trigger in Logic Analyzer
- Tipp 5: Hyper-V
- Windows Eingabe:
Hyper-V-Schnellerstellung
- Installation dauert einige Minuten
- Simulide in VM ist stabiler
- Tipp 6: Scope
- Tracks: Aufteilen des Bildschirms
- Trigger
- Auto
- Tipp 7: Scripted Modules
- Beispiel DAC
- im Komponentenexplorer: Scripted » DAC
- im Dateiexplorer: Data » scripted » DAC
- Alle Dateien anklicken --> nix passiert
- rechten Editorbereich in Simulide öffnen („hereinschieben“)
- Known Problems
- Ampmeter only for DC! (about 10 Hz)
- FETs
- use Relays instead
Change- Inductance: 1 nH
- Resistance to 1 mOhm
- IOn 100 mA
- IOff 50 mA
- Beispiel: Halbbrücke (auch als FET Variante ok)
- Alternative mit Relays
- Hier besser als Umschalter umzusetzen, da Kurzschluss im Zwischenstadium
Interrupts und Zeitslots
Wdh:
- Bisher:
- Ausgabe auf dem Display in while-Schleife „so schnell wie's geht“
- Counter TC1 läuft autonom und unabhängig vom Programmcode (nach Config)
- Counter kann unterschiedlich schnell laufen (prescaler)
- Counter kann auch Ausgabe antriggern
- Nun:
- welche Modi gibts? Was kann man noch mit dem Timer/Counter starten?
- Anschauen der verschiedenen Modi:
- Normal: 0 … 65535, und dann wieder Sprung auf 0 --> OC1A ändert sich nur bei max Wert
- Fast PWM:
- 0 … max Wert, und dann wieder Sprung auf 0 --> OC1A bei TCNT>= OCR1A gleich 0, sonst 1
- Fast PWM kann verschiedene max Werte haben: 255, 511, 1023, ICR1 und sogar OCR1A selbst
- Ansteigende Flanke immer bei 0 --> links bündig!
- PWM, phase correct
- 0 … max Wert, und dann: max Wert … 0 --> OC1A bei TCNT>= OCR1A gleich 0, sonst 1
- halb so schnell weil doppelte Rampe
- mitten zentriert
- weitere Register des TC anschauen
- TCNT1H/TCNT1L: ist der eigentliche Counter Wert
- OCR1AH/OCR1AL: ist Vergleichswert für den ersten Vergleich
- OCR1BH/OCR1BL: ist Vergleichswert für den zweiten Vergleich
- ICR1H/ICR1L: ist „Zwischenspeicher“ der mit dem Counter-Wert gesetzt wird, sobald Pin ICP1 sich ändert
- erst TIFR1:
- zeigt Ereignisse an (über Flags): z.B. Vergleichswert ist erreicht, oder Maximalwert ist erreicht
- wenn ein Ereignis eintritt, dann kann ein Interrupt ausgelöst werden
- TIMSK1 : Ist eine Maske, die angibt, welcher Interrupt aktiv ist
- Mal Overflow Interrupt testen:
- bei Initialisierung:
TIMSK2 |= (1«TOIE1);
- außerhalb von main:
ISR()
--> suchen nach ISR (Goto implementation) liefert keine praktikable Antwort was das tut (Interrupt Service routine erklären)- wir brauchen zumindest einen „vector“ (Zeiger auf die Sprungadresse welche im Interruptfall abgearbeitet werden soll)
- woher bekommen?
- am besten da nachschauen, wo auch PORTB und PB1 definiert ist
- suchen nach vector
TIMER1_OVF_vect
!
- Eingeben von
ISR(T
bietet schonTIMER1_OVF_vect
an ISR(TIMER1_OVF_vect)
{
}
- was machen wir da drin? am besten z.B. Port B3 toggeln
PORTB ^= (1 « PB3);
einfügen
- testen --> toggelt!
- Wie könnte man nun die Ausgabe nur alle paar Zentelsekunden ausführen lassen?
- Alle Zeilen in den Interrupt? --> bloß nicht!
- SW_Flag in ISR setzen und in main auswerten
uint8_t IntFlag=0;
als globale VariableIntFlag=1;
in die Interrupt Routineif(IntFlag==1)
{
IntFlag=0;
…
}
- Geschwindigkeit zu langsam?
- statt
TCCR1B |= 1«CS12;
besserTCCR1B |= 1«CS11;
- Aber wie kommt man z.b. genau auf eine Millisekunde?
- Man nehme:
- Takt: 18.432 MHz
- 8-Bit Counter: zählt bis 256 (16 Bit geht nicht genau auf…)
- --> zählt 72'000x pro Sekunde bis 256 und löst Interrupt aus
- Prescaler von 8: zählt 8x so langsam, also nur 9'000 pro Sekunde und löst interrupt aus
- Im Interrupt von 9 herunterzählen: bei jeder 0 wäre es eine Millisekunde
- up-Down-Counter ansehen