Wichtig: diese Anleitung soll die
hardwarespezifischen Unterschiede der Programmierung zwischen PCs
und Mikrokontrollern vermitteln und richtet sich damit an Leser,
die in irgendeiner Weise bereits Vorkenntnisse in Programmierung
allgemein besitzen. BASCOM besitzt eine ganz brauchbare Hilfe mit
reichlich Beispielcode, mit der sich die genaue Syntax der
Befehle wie If-Abfragen, Schleifen etc. schnell herausfinden
lässt. Einige Teile sind interaktiv und ermöglichen
es z.B. Erklärungen und Beispielrechnungen mit eigenen Werten nachzuvollziehen.
Dazu muß jedoch Javascript aktiviert sein
Um zu verstehen warum ein Microcontroller
manchmal ein unerwartetes Ergebnis präsentiert, sollte man nicht
nur wissen, wie man eine Binärzahl in eine Dezimalzahl
umrechnet, sondern man sollte das Prinzip kennen, wie
Zahlensysteme zusammenhängen, daß alle absolut gleichwertig
sind und das man mit allen nach den selben Regeln identisch
rechnen kann - ohne diese vorher in "normale" Zahlen
umrechnen zu müssen.
Vorweg: Vermeiden Sie bei Mikroprozessorprogrammen wo immer
möglich Fließkommazahlen!
Während es bei der üppigen Rechneleistung eines PCs kaum eine
Rolle spielt wieviele Dezimalbrüche man miteinander verrechnet,
kostet eine einzige Fließkommaoperation beim µC spürbar
Speicher und Geschwindigkeit.
Hier nun erstmal 2 kleine Beispiele, mit denen Sie testen
können, ob Ihr Zahlenverständnis wirklich ausreichend ist um
auch mal einem Programmfehler auf die Schliche kommen zu können:
Für alle die das Zahlenscript
schon gelesen haben, oder auch auch
ohne dies die richtigen Antworten geben konnten habe ich hier
noch ein paar kleine, nicht weiter dokumentierte Hilfsprogramme
abgelegt, die bei den nötigen Berechnungen sehr hilfreich sein
können.
(nach oben)
Ganz kurz vorweg 6 Punkte zur
BASCOM-Syntax, die sich bei unterschiedlichen Hochsprachen
häufiger unterscheiden:
- es ist nicht nötig Befehle oder Zeilen durch ein Semikolon
o.ä. abzuschließen
- Strings bzw. Textkonstanten werden durch Gänsefüßchen
gekapselt
- Kommentare werden durch ein Hochkomma eingeleitet und enden
automatisch mit einem erzwungenen Zeilenumbruch (Enter-Taste)
- Binärzahlen werden durch &B und Hexadezimalzahlen mit
&H eingeläutet. Dezimalzahlen sind natürlich ohne Präfix.
- die Zählweise bei Bits beginnt generell mit Bit 0, während
Arrays stets mit Element 1 beginnen
- das Gleicheitszeichen wird sowohl für Vergleiche als auch für
Wertzuweisungen identisch verwendet
Ein fertig geschriebenes Programm wird durch F7 compiliert und
verläuft dies ohne Fehler kann es mit F4 auf den Chip
"gebrannt" werden. Fehler beim Compilieren können
durch Doppelklick auf die Fehlermeldung (Statuszeile ganz unten)
direkt angesprungen werden.
Ein Bascom-Programm beginnt immer damit dem Compiler mitzuteilen
für welchen Mikrokontrollertyp das Programm vorgesehen ist. Dies
erfolgt indem man die Definitionsdatei benennt, in der alle
prozessorspezifischen Adressen und Eigenheiten abgelegt sind.
Diese Dateien befinden sich im Bascom-Stammverzeichnis und dort
kann man im Zweifelsfalle auch nachschauen, wie die Datei für
den gewünschten Prozessor genau heißt.
| $regfile = "M8def.dat" | < Definitionsdatei (hier für ATmega8) |
| $crystal = 8000000 | < Quarzfrequenz in HZ angeben (hier 8MHz) |
| Dim x As Byte | < Variablen definieren (hier x als Byte 0 - 255) |
| (Befehle) | |
| Do | < Beginn der Hauptschleife |
| (Befehle) | |
| Loop | < Ende der Hauptschleife |
| End | < Programmende (für Compiler erforderlich) |
Zu Anfang (also vor der Hauptschleife) definiert man Variablen, die Funktionen der Anschlüsse, konfiguriert die Hardware usw. Diesen Teil des Programmes bezeichne ich im Folgenden als Header. Der Header enthält alle Anweisungen, die beim Starten des Prozessors einmalig und vor allen anderen Programmteilen ausgeführt werden sollen.
Welche Zahlentypen verwendbar sind und
welche Werte darin gespeichert werden können entnehmen Sie bitte
der Bascom-Hilfe. Am häufigsten wird man wohl Bit (0 oder 1) ;
Byte ( 8 Bit = 0 -255) und Word (16 Bit = 2 Byte = 0 - 65535)
verwenden.
Nach dem Header folgt üblicherweise für Mikrocontroller eine
Endlosschleife, da das Programm sonst nur einmal durchlaufen
würde.
Vor oder auch nach der Hauptschleife kann man später noch beliebige Funktionsteile anfügen, die später Basic-üblich einfach durch Goto oder Gosub über Ihren Namen (Label) angesprungen werden können. Wird ein Unterprogramm von einem beliebigen Programmpunkt aus per Gosub Unterprogramm1 angesprungen, führt ein Return am Ende des Unterprogrammes dazu, daß das Programm bei erreichen des Return-Befehles wieder direkt nach dem Gosub-Befehl fortgesetzt wird. Das Unterprogramm selbst wird einfach durch seinen Namen, abgeschlossen durch einen Doppelpunkt eingeleitet und der Aufruf erfolgt ohne Doppelpunkt:
Gosub Unterprogramm1
(Befehle)
Unterprogramm1:
Return
Es ist nicht erforderlich Subroutinen bzw.
Funktionen im Header zu definieren (Funktionsprototypen o.ä.)
oder durch einen Befehl der Art Function oder Procedure
einzuläuten - es genügt schlicht das Label (der Name mit
Doppelpunkt).
Call by Value (Variablen- bzw. Pointerübergabe) an Funktionen
geht nicht - also ausschließlich globale Variablen benutzen.
(nach oben)
Bevor man die Anschlüsse benutzen kann
muss man festlegen, welche Pins als Ein- oder Ausgänge genutzt
werden sollen. Alle Pins sind bidirektional - man kann sich also
aussuchen wo man was anschließt. Die Einstellung sollte also zu
Anfang des Programms (Header) vorgenommen werden - kann aber
später jederzeit wieder geändert werden.
Die Pins sind in mit Buchstaben bezeichnete Ports unterteilt,
denen jeweils Register-Bytes zugeordnet sind, die die Richtung
der Daten speichern (Register DDR), die Eingangswerte zum
Einlesen beinhalten (Register Pin) oder die Ausgabewerte
(Register Port).
![]() |
Der Mega8 besitzt 3
Ports: B; C und D, deren Anschlüsse im Bild ersichtlich
sind. Nochmal der Hinweis: die Zählweise beginnt immer mit 0. Die Angaben in Klammern beziehen sich auf optionale Verwendungen des Pins: Pin23 - 28 lassen sich z.B. auch als Analogeingänge konfigurieren, über Pin 2 und 3 lässt sich eine serielle Verbindung zum Computer aufbauen usw. Die Pins 9 und 10 (PB6 und 7) sind nur frei verfügbar, wenn der Mega8 auf internen Takt eingestellt ist, da sie sonst für den Quarzoszillator verwendet werden. Pin1 ist standardmäßig mit der Reset-Funktion belegt, womit PC6 auch nicht mehr uneingeschränkt verwendbar ist. Die Reset-Funktion lässt sich prinzipiell auch deaktivieren um den Pin nutzbar zu machen - allerdings lässt sich der Chip dann nicht mehr einfach umprogrammieren. Ich rate davon ab und werde auch nicht näher darauf eingehen. |
Man kann jeweils den Zustand aller Pins
eines Ports gleichzeitig verändern/einlesen, indem man ein
ganzes Byte in ein Register schreibt/ausliest.Jedes Bit des Bytes
bezieht sich dann auf einen Pin des Ports. Will man nur den
Zustand eines Bits, also eines einzigen Pins verändern oder
abfragen muß man an den Befehl nur einen Punkt und die Nummer
des zu verändernden Pins anhängen.
Also "RegisterPortbuchstabe.Pinnummer = Wert"
Es empfiehlt sich die Werte direkt binär anzugeben
(&B...) weil man so einfach abzählen kann, welcher Pin
welchen Wert bekommt:
Ein-/Ausgänge festlegen:
| DDRD = &B00000101 | schaltet PD0 und PD2 als Ausgang, alle Übrigen werden als Eingänge konfiguriert. |
| DDRD.2 = &B0 | setzt nur PD2 auf 0, also auf Eingang und lässt die übrigen Stellen von DDRD unverändert. |
Pins Ein- und Ausschalten:
| PortD = &B00000101 | schaltet die Pins PD0 und PD2 auf 1 (+5V) und setzt die Übrigen auf 0V |
| PortD.2 = &B1 | schaltet nur PD2 auf 1 (+5V) und lässt die anderen unverändert |
Signal an den Pins einlesen:
Die Eingänge eines AVRs brauchen immer
eindeutige Spannungs-Signale: entweder 0V oder +5V. Ein Schalter
kann aber nicht 2 verschiedene Spannungen ausgeben, sondern nur
einen Stromkreis schließen oder öffnen. Deshalb muss man
zusätzlich wie im Bild unten einen Widerstand vorsehen. Andere
Schaltkreise, Fernsteuerungsempfänger oder Sensoren mit
Digitalausgängen liefern meistens solche Signale und können
ohne zusätzlichen Widerstand angeschlossen werden.
| DDRD.2 = 0 x = PinD.2 x ist 1 bei gedrückter Taste |
DDRD.2 = 0 x = PinD.2 x ist 0 bei gedrückter Taste |
![]() |
![]() |
|
Auch Transistoren
machen letztlich nichts anderes als Schalter oder Relais:
Kontakt auf oder zu. Deshalb müssen auch Transistoren
mit Pull-Widerständen versehen werden, wie im Bild
links. Da am Transistorausgang nicht mehr als die 5V vom Pullupwiderstand rauskommen können, ist diese Schaltung auch geeignet um andere Spannungen als 5V einzulesen. Mit dem 10k Basiswiderstand und als Transistor z.B. einem BC550 erhält man bei beliebigen Eingangsspannungen ab ca. 1-2V bis 30V am AVR ein sauberes 0-Signal und mit 0V oder ganz ohne irgendein Signal am Eingang eine logische 1 am AVR. |
Das Eingänge häufig nur mit Tasten oder Transistoren gesteuert werden sollen wussten auch die Entwickler der AVRs und haben die Pullupwiderstände gleich mit eingebaut. Allerdings muss man diese extra einschalten - da sie im Grundzustand nicht aktiv sind. Statt sich noch ein Register merken zu müssen hat man dem Benutzer das Leben leicht gemacht: den Pullupwiderstand eines Pins schaltet man ein, indem man eine 1 ausgibt, obwohl der Pin als Eingang eingestellt wurde:
![]() |
So kann man sich die
extra Pullupwiderstände sparen, wenn man einen Taster
oder Transistor anschließt, der 0V (Masse) schaltet: DDRD.2 = 0 PortD.2 = 1 x = PinD.2 x ist 0 bei gedrückter Taste |
Vielleicht ist es aufgefallen: wenn man einen einzelnen Pin, bzw.
ein einzelnes Bit ein und ausschalten will ist es egal ob man das
&B schreibt, weil die Werte 1 und 0 dezimal identisch sind
mit der binären Zahl. Wenn man mehrere Bits gleichzeitig ändern
will - also ein Byte angibt muß man darauf achten. Der Befehl
DDRC = &B00001001 schaltet C.0 und C.3 als Ausgänge den Rest
als Eingang. Dezimal muss 00001001 umgerechnet werden: DDRC=9
macht also genau das selbe.
Abschließend noch ein zusammengefasstes Beispiel für die
digitalen Ein- und Ausgänge:
| DDRB = &B00110000 | schaltet B4 und B5 als Ausgänge und B0-B3 als Eingänge, B6 und B7 gibts beim Mega8 garnicht | |
| PortB = &B00010010 | Ausgang B4 wird auf +5V und B5 auf 0V geschaltet und nur der Pullup für den Eingang B1 wird aktiviert - die anderen Eingänge sind "offen" und müssen mit Ausgängen anderer Bausteine verbunden werden. |
Bei Eingängen, an die garnichts
angeschlossen wird sollte man immer den Pullup aktivieren, da sie
sonst zufällige Zustände haben und das Programm anfangen kann
komische Sachen zu machen.
(nach oben)
Mit dem Mega8 kann man 6 analoge Spannungssignale im Bereich von 0 bis +5V einlesen, z.B. von Sensoren. Standardmäßig werden die Spannungswerte mit 10 Bit binär gewandelt und ergeben damit eine Zahl von 0 (0V) bis 1023 (+5V). Die 6 Analogeingänge ADC0 - ADC5 befinden sich an den selben Pins wie der Digitalport C - also Pin 23 bis 28. Die Werte einzulesen ist vergleichsweise einfach. Der ADC muß nur mit einer Zeile im Header konfiguriert und mit einer weiteren gestartet und ab dann stehen die Werte zur verfügung:
| Config Adc = Single , Prescaler = Auto | |
| Start Adc |
Ist das einmal im Header geschehen, kann man den aktuellen Wert eines Analog-Kanals jederzeit mit der Funktion Getadc(Kanalnummer) auslesen:
| x = Getadc(4) | schreibt den Wert der Spannung an Pin 27 (ADC4) in die Variable x |
Man beachte, daß die Zahl 10 Bit - also
bis 1023 groß sein kann und damit natürlich nicht in einer
Variable der Größe Byte gespeichert werden kann. Deshalb sollte
x zuvor mindestens als Word (16Bit) definiert worden sein. Einen
Variablentyp mit genau 10 Bit gibt es nicht - Word ist das
Nächstgrößere.
(nach oben)
Technisch gehört die Ausgabe von
"analogen" Spannungen hinter die Einführung in die
Timer, thematisch passt es hier aber besser, nach den analogen
Eingängen. Eine Analogspannung kann ein Prozessor eigentlich
nicht ausgeben, mit einem Trick aber etwas Ähnliches:.
Ein Motor würde z.B. an 6V ca. halb so schnell laufen wie bei
12V. Das Selbe - also halbe Drehzahl - erreicht man, wenn man an
den Motor für eine halbe Sekunde die volle 12V-Spannung anlegt
und Ihn dann wieder für eine halbe Sekunde ausschaltet. Die
volle Drehzahl kann er nicht erreichen, das geht nur, wenn die
12V dauerhaft anliegen. Die halbe Drehzahl ergibt sich dadurch,
daß der Motor die halbe Zeit beschleunigt und die andere Hälfte
abbremst.
So ein Signal, bei dem die Ein- und Ausschaltzeit prozentual
eingestellt wird (Puls-Breiten-Modulation) kann ein Controller
erzeugen. Die Ein- und Ausschaltzeiten folgen dabei viel
schneller als im Beispiel mit der halben Sekunde. Praktisch so
schnell, daß eine LED nur schwach zu leuchten schneint und man
garnicht bemerkt, daß diese kurz eingeschaltet und danach
zeitweise ganz ausgeschalten ist. Der Mega8 stellt zwei
PWM-Ausgänge zur Verfügung: OC1A (Pin15) und OC1B (Pin16)
Um diese benutzen zu können muß im Header der Timer1 für die
PWM-Funktion konfiguriert, aktiviert und gestartet werden:
| Config Timer1 = Pwm , Pwm = 10 , Compare A Pwm = Clear Down , Compare B Pwm = Clear Down , Prescale = 1 |
| Enable Timer 1 |
| Start Timer 1 |
Eine genaue Beschreibung was welche Angabe
bedeutet findet sich in der Bascom-Hilfe.
Ist der Timer so konfiguriert, kann man den aktuellen Wert eines
PWM-Augangs jederzeit mit der Funktion Pwm(Kanalnummer)
einstellen. Ein PWM Signal wird dann ständig ausgegeben, bis man
den Wert = 0 setzt oder den Timer1 stoppt.
In der Config-Zeile ist der PWM-Ausgang auf 10 Bit Auflösung
eingestellt - also wieder Werte zwischen 0 und 1023. Gibt man dem
PWM-Kanal jetzt den Wert 1023 vor, dann ist der Ausgang für 1023
von 1023 Zeitabschnitten eingeschaltet - also es kommt dauerhaft
+5V aus dem Ausgang. Setzt man den Wert auf 0 (von 1023
Zeitschritten) ist der Ausgang ständig auf 0V. Bei allen anderen
Zahlen ergeben sich Zwischenwerte - gibt man z.B. 10% der
insgesamt 1023 Zählschritte, also 102 als Sollwert vor, dann
bekommt z.B. eine LED am Ausgang auch nur 10% der Zeit Strom und
scheint demnach auch nur mit 10% Ihrer Leuchtkraft zu leuchten.
| Pwm1A = 512 | gibt ein Schaltverhältnis von ca. 50% (512 von 1023) auf OC1A (Pin15) aus |
Der PWM Ausgang lässt sich auch auf andere Auflösungen als 1023 Schritte einstellen. Die 10 Bit aus dem Beispiel haben nicht nur den Vorteil, daß sich so sehr feine Abstufungen einstellen lassen, sondern vorallem, daß mit 0-1023 der selbe Zahlenbereich benutzt wird wie bei den Analogeingängen. Theoretisch könnte man also die Spannung von einem Potentiometer an einem Analogkanal (1ADC ) direkt auf einen PWM-Kanal ausgeben und so z.B. die Drehzahl eines Motors einstellen: Pwm1A = Getadc(1) - führt aber leider zu einer Fehlermeldung. Man muß zuerst den Analogwert in eine Variable einlesen und kann dann die Variable auf den PWM-Ausgang schreiben um das zu realisieren:
| x = Getadc(1) | steuert mit dem Analogwert von ADC1 (Pin24) den PWM-Ausgang OC1A (Pin15) |
| Pwm1A = x |
Man beachte, das x wieder mindestens vom
Typ Word sein muß, um die 10Bit-Werte aufnehmen zu können.
(nach oben)
Normalerweise läuft das Programm des
Mikrocontrollers immer in einer Endlosschleife. Man kann nun z.B.
ständig den Zustand einer Taste abfragen um daraufhin einen
Vorgang auszulösen. Die Programmschleife braucht jedoch eine
gewisse Durchlaufzeit - ist der Tastendruck bzw. das
Eingangssignal sehr kurz, erscheint also erst nach einer Abfrage
des Pin-Zustandes und ist schonwieder vorbei, wenn die
Programmschleife wieder bei der Abfrage angekommen ist, dann
"bemerkt" das Programm garnicht, daß ein Signal da
war.
Dafür gibt es 2 spezielle Anschlüsse: Int0 (Pin4) und Int1
(Pin5).
Diese Pins kann man so konfigurieren, daß sie bei Signalen
sofort das Hauptprogramm anhalten und zuerst einen anderen
Programmteil ausführen, ohne das der Pinzustand in der Schleife
noch abgefragt werden muß. Solche Funktionen, die daß
Hauptprogramm augenblicklich unterbrechen können nennt man
Interrupts und diese sind vor allem für sehr kurze oder
zeitkritische Signale geeignet.
Bei der Einstellung der Parameter kann man auswählen, ob die
Unterbrechung dauerhaft bei 0 Signal (Low Level), oder nur
einmalig beim Umschalten von 0 auf 1 (rising) oder umgekehrt von
1 auf 0 (falling) erfolgen soll:
| Config Int0 = Falling | löst eine Unterbrechung des Hauptprogrammes aus, wenn Pin4 von 1 auf 0 umschaltet |
| Enable Interrupts | aktiviert allgemein die Interruptfunktionen - muß nur einmal angegeben werden |
Für die Beschaltung mit Pullup- oder
Pulldownwiderständen gilt das selbe wie bei den
"normalen" Digitaleingängen. Ein Interrupt-Anschluss
sollte natürlich auch als digitaler "Eingang"
definiert worden sein.
Nun muß man dem Prozessor noch mitteilen, was er machen soll,
wenn der Interrupt ausgelöst wird. Dazu schreibt man vor oder
nach der Hauptschleife ein Unterprogramm. Der Name
desUnterprogrammes wird zuerst angegeben, abgeschlossen mit einem
Doppelpunkt. Damit weiß Bascom, daß dies ein Label, bzw eine
Sprungmarke ist. Beendet wird die Subroutine durch den Befehl
Return, der dazu führt, daß der Prozessor dort wieder mit dem
Hauptprogramm fortfährt, wo er unterbrochen wurde:
| Unterprogramm1: | < Beginn des Unterprogramms |
| (Befehle) | |
| Return | < Rückkehr zum Hauptprogramm |
Im Header muß noch mitgeteilt werden, daß zu dem Unterprogramm gesprungen werden soll, wenn ein Interrupt ausgelöst wird:
| On Int0 Unterprogramm1 | springe bei Interrupt0 zum Unterprogramm1 |
Beim Aufruf eines Unterprogrammes übrigens
nur den dessen Namen angeben, ohne Doppelpunkt. Natürlich kann
ein Unterprogramm, daß automatisch durch einen Interrupt
angesprungen wird auch jederzeit per Programmbefehl mit Gosub
Unterprogrammname angesprungen werden.
Noch ein wichtiger Hinweis, falls die Hardwareinterrupts mit Tasten angesteuert werden:
Das "Prellen" von Kontakten ist ein häufiges Problem in elektronischen Schaltungen. Das bedeutet, daß bei Betätigung einer
Taste nicht nur ein Impuls gesendet wird, sondern mehrere und häufig nochmals, beim Loslassen der Taste.
Dieses Problem kann man entweder elektronisch lösen, indem man kleine Kondensatoren parallel zur Taste anschließt oder per
Software. Die Interrupts im AVR werden meist flankengesteuert genutzt und werden so mehrmals aufgerufen, auch wenn eine
Taste nur einmal betätigt wurde. Beim Aufruf des ersten Interrupts wird zwar die Abarbeitung anderer Interruptroutinen
temporär blockiert, aber der AVR "merkt" sich, daß während dessen ein weiterer Interrupt aufgelaufen ist und führt diesen
unmittelbar nach Beendigung der aktuellen Routine aus. Das Abschalten bzw. Disablen des Interrupts während der
Abarbeitung hilft leider nichts, da er trotzdem gespeichert wird. Die Interruptroutine wird dann eben beim Wiedereinschalten des Interrupts
erneut ausgeführt.
Eine mögliche und relativ einfache Lösung für dieses Problem ist, den Interruptspeicher des AVRs vor dem Verlassen der Routine
zu löschen und dies geschieht indem man in das entsprechende Bit des GIFR-Registers eine logische 1 schreibt.
Für den Int0 macht man das z.B. durch den Befehl Gifr.intf0 = 1 . Signalflanken die während der Abarbeitung der
Interruptroutine z.B. durch Prellen entstanden sind führen so nicht mehr zu einem wiederholten Anspringen der Routine.
(nach oben)
Der Mega8 besitzt zwei Timer: Timer0 und
Timer1
Timer sind nichts anderes als Zähler, die durch den
Prozessortakt selbstständig vorwärts- oder rückwärtszählen
können. Je nachdem wie hoch die Quarzfrequenz ist und wie der
Timer konfiguriert wird kann man bei bekanntem Timer-Takt anhand
des Zählerstandes sehr genau die Zeit, also z.B. die Dauer eines
Eingangs-Impulses messen. Man kann den internen Takt auch
ausschalten (Modus: Counter) und Impulse von externen
Signalquellen zählen lassen oder man kann Ihn als PWM-Ausgang
konfigurieren.
Timer sind binärer Zähler - Timer0 ist 8 Bit "breit"
und kann demnach nur von 0 bis 255 zählen. Kommt ein 256ter
Zählimpuls geht er wieder auf 0 - das ist so, als ob man ein
Display mit 2 Stellen hat, daß nach 99 auch nur wieder 00
anzeigen kann, statt 100. Timer1 ist 16 Bit breit, kann demnach
bis 65355 zählen und geht beim 65356ten Zählschritt wieder auf
0
Wenn der maximale Zählerstand überschritten wird und sich der Zähler auf 0 zurückstellt, dann wird ein Interrupt ausgelöst. Wie bei den Hardwareinterrupts kann man dem ein eigenes Unterprogramm zuweisen, daß dann automatisch in festen Zeitabständen bei jedem Timerüberlauf aufgerufen wird.
Konfiguriert man den Timer als Counter, dann kann man an die entsprechenden Eingänge: Pin 6 für Timer0 und Pin 11 für Timer1 digitale Signale anschließen, die dann gezählt werden. Man kann einstellen ob der Zählschritt bei der ansteigenden Flanke ( 0 -> 1 Wechsel) oder der abfallenden Flanke des Signals erfolgen soll und selbstverständlich kann man auch hier einen Prescaler vorgeben, so daß z.B. nur jeder 1024te Zählimpuls erfasst wird.
Beispiel:
Config Timer1 = Counter , Edge = Rising, , Prescale = 1024 ' Timer1 zählt jedes 1024te Signal an Pin11 in dem Moment, wo dieses von 0 auf 1 umschaltet.
Die Timer bieten noch weitere
Einstellmöglichkeiten, die ich hier beim besten Willen nicht
aufführen kann. Den Timer1 als PWM-Ausgang zu konfigurieren
wurde oben schon teilweise erklärt, über weitere Möglichkeiten
kann man sich hervorragend in der BASCOM-Hilfe belesen.
(nach oben)
Die meisten älteren Computer besitzen noch
serielle Schnittstellen (z.B. Com1) mit denen AVRs dank Ihrer
eingebauten UART spielend kommunizieren können. Neuere Computer
besitzen diese Anschlüsse häufig nicht mehr, hier kann man sich
aber z.B. mit einem USB Adapter behelfen. Diese Adapter sind
entweder kleine Geräte oder sogar im Stecker eines Kabels
eingebaut und melden sich beim Betriebssystem wie ein Com-Port
an. Alle alten Beispiel- und Terminalprogramme kann man damit über USB weiterverwenden und meist noch mit höheren Übertragungsgeschwindigkeiten.
Natürlich kann man die serielle Schnittstelle auch benutzen,
um mehrere AVRs miteinander Daten austauschen zu lassen.
Während man mehrere AVRs einfach zusammenschalten kann muss man
beachten, dass die Spannungen von genormten RS232-Anschlüssen
(wie am PC) zu hoch (+/-12V) und zudem invertiert sind. Will man
nur Daten vom PC empfangen will man aber auch Daten zum PC senden
müsste man sich zuerst die +/-12V erzeugen und das Signal dann
noch invertieren. Dafür gibt es fertige Bausteine wie den Max232, die das alles auf einmal erledigen.
Allerdings ist das Ganze etwas umständlich: USB hat 5V Pegel, für RS232-Kompatibilität werden die Signale im Wandler invertiert und auf +/- 12V umgesetzt.
Diese Spannungen muß der Wandler aus den +5V auch erstmal selbst erzeugen. Auf eurer Platine macht Ihr das ganze mit Max232 o.ä. dann wieder rückwärts -
keine schicke Lösung. Die +/-12V RS232-Pegel haben nur da Vorteile, wo das Übertragungskabel sehr lang sein muß - 20m und mehr.
Die Fa. FTDI-Chip bietet ICs an (z.B. FT232R), die sich am USB-Port auch als Com-Port anmelden - aber direkt 5V-Signale ausgeben die man direkt an den Controller anschließen kann.
Die ganze Hin- und Herwandlung und Invertierung auf 12V-Signale entfällt, man spart sich Bauteile und es sind wesentlich höhere Kommunikationsgeschwindigkeiten möglich.
Meistens kann man so auch gleich die 5V-Versorgungsspannung vom USB-Port für den Controller benutzen und die FTDI-Chips geben meist sogar einen 12MHz-Takt aus, womit man sich den Quarz am Controller auch noch sparen kann.
Leider gibt es diese Wandlerchips nur in SMD-Bauformen, die für Laien nicht einfach zu verbauen sind. Bei der Fa. Elmicro gibt es für weniger als 20,-€ fertige Bausteine mit den Chips von FTDI die sich einfacher
verbauen lassen und es gibt sogar fertige USB-RS232-Wandlerkabel, die direkt nichtinvertierte 5V-Signale ausgeben. Diese Kabel kann man direkt an den Controller anschließen und spart sich die ganze
Zusatzschaltung mit MAX232 o.ä. vollständig.
Daten auszutauschen ist mit Bascom
ausgesprochen simpel. Zunächst gibt man dem AVR im Header eine
Baudrate an und schon kann man mit einem einfachen "Print
X" den Wert einer Variablen an den Rechner senden.
Natürlich können über so eine Datenleitung nur Binärzahlen
(Bytes) gesendet werden. Jeder Zahl ist deshalb ein
Buchstabe/Sonderzeichen (Ascii-Tabelle) zugeordnet, den
PC-Programme anzeigen, wenn der entsprechende Zahlenwert
empfangen wird.
Bascom kümmert sich von selbst um die Umwandlung des
Zahlenwertes in ASCII-Zeichen und sorgt auch dafür, daß nach
der Zahl in die nächste Zeile gesprungen wird, indem es das Sonderzeichen mit der Ascii-Nummer 13 (CR) als Zeilenumbruch anhängt.
Man kann aber
natürlich auch direkt Binärwerte oder Buchstaben/Texte senden:
| $baud = 9600 | 'aktiviert die Schnittstelle und setzt die Baudrate auf 9600 |
Aber Achtung! Vorsicht!! Obacht!!!
Schreibt man das Baud-Statement in den Header wird nicht nur die UART initialisiert, sondern es werden außerdem gleich die endsprechenden RXD / TXD-Pins konfiguriert! D.h. Praktisch: Pin 2 wird als Eingang und Pin 3 als Ausgang gesetzt und die Digital-IOs PD0 und PD1 sind damit nicht mehr verfügbar!
Wenn man die serielle Schnittstelle nicht benutzt sollte also auch auf das $baud-Statement im Header verzichtet werden.
Ein Beispiel für die Ausgabe von Werten über die serielle Schnittstelle:
x = 107
y = 110
| Bascom-Befehl | gesendete Werte | Bildschirmanzeige |
| Print "x" Print "y" |
sendet binär die Werte 120 und 121 für
die Buchstaben x und y und anschließend den Wert 13 für Zeilenwechsel |
x y |
| Print x Print y |
zerlegt die Zahl 107 in 3
Ascii-Zeichen und sendet diese nacheinander + Zeilenwechsel |
107 110 |
| Print x ; Print y ; |
das Semikolon verhindert den Zeilenwechsel | 107110 |
| Printbin x Printbin y |
sendet direkt die Variablenwerte
ohne Umwandlung (das Empfangsprogramm wandelt nach Ascii um) |
k n |
| Print "x = "; Print x Print "y " Printbin 61; Print y |
Beispiel für Kombinationen (Ascii Code 61 entspricht Gleichheitszeichen; Semikolon nach "y" wurde vergessen, Print ohne weitere Angabe sendet nur einen Zeilenumbruch) |
x = 107 y =110 |
Tip: Will man zwischen anderen ASCII-Zeichen ein Sonderzeichen versenden, daß BASCOM nicht kennt, dann kann man den ASCII-Code des Sonderzeichens einfach in geschweiften Klammern in den String hineinschreiben, den man senden will.
Beispiel: Print "Hallo Welt!" sendet genau das selbe wie Print "Hallo{032}Welt{033}" weil die {032} dem Ascii-Code des Leerzeichens und {033} dem Ausrufungszeichen entspricht.
Wichtig: der Ascii-Code in den geschweiften Klammern muß immer dreistellig als Dezimalwert angegeben werden.
Wenn man Daten empfangen will ist das Ganze etwas aufwendiger.
Trifft ein Byte beim AVR ein muß dieses möglichst schnell
ausgelesen werden, weil erst dann der Speicher (Udr) frei ist um
den nächsten Wert zu empfangen. Weil soetwas zeitkritisch ist
ist im AVR wieder ein Interrupt vorgesehen (Urxc), mit dem
automatisch beim Eintreffen eines Bytes ein Unterprogramm
anspringen kann, um das Auslesen sofort zu erledigen.
Den RS232-Empfang im Header vorbereiten:
| On Urxc Datenempfang | ' Unterprogramm für RS232 Empfang angeben |
| Enable Urxc | ' Interrupt aktivieren |
| Dim x As Byte | 'beliebige Variable zum Auslesen des Wertes |
und dann später im Programm:
| Datenempfang: | ' Label für Unterprogramm |
| x = Udr | ' empfangenen Wert (Udr) in die Variable x auslesen |
| ' weitere Befehle | |
| Return | ' Rückkehr zum Ursprungsprogramm |
In der Variablen x steht jetzt der Wert, der zuletzt empfangen wurde. Trifft ein neues Byte ein wird der letzte Wert von x natürlich überschrieben. Deshalb sollte man - wenn mehrere Bytes nacheinander zu erwarten sind - x möglichst sofort im Unterprogramm weiterverarbeiten, oder in einem Array ablegen. Drückt man auf dem PC eine Taste, dann wird der zur Taste gehörige Ascii-Wert empfangen. Diesen kann man entweder direkt auswerten (If-Abfrage o.ä.) oder mit dem Befehl y = chr(x) einer anderen Variablen (String) wieder das Zeichen/den Buchstaben zuweisen. Häufig ist es aber für einzelne Zeichen (Steuertasten) sinvoll sich das zu sparen. Tragen sie in eines beider Felder einen Wert ein und klicken Sie ins andere um das Ergebnis zu erhalten:
Für den Empfang bzw. zum Senden von Daten im Ascii-Format
genügt auf dem PC grundsätzlich jedes Terminalprogramm. Wie die
grunsätzlichen Einstellungen vorzunehmen sind habe ich
exemplarisch für HyperTerminal beschrieben. Sicher gibt es sehr viel
bessere Terminalprogramme (in Bascom ist auch eines enthalten),
allerdings hat Hyperterminal den großen Vorteil, daß es auf vielen
Windowsrechnern bereits vorinstalliert ist, da es zum Programmpaket gehört.
D.h. ohne extra Programminstallation kann man so Daten seiner AVR-Schaltung
an fast jedem Windowsrechner anzeigen lassen und auch senden.
Die Anleitung findet Ihr hier: Hyperterminaleinrichtung
Sofern man eine PC-Programmiersprache beherrscht ist es
natürlich schöner sich eigene Programme zu schreiben, mit denen
man dann auch nicht nur einzelne Buchstaben oder Zahlen, sondern
auch direkt die binären Werte verarbeiten kann.
Und noch etwas: der AVR generiert seine Baudrate indem er den Quarztakt teilt. Wie bei
allen binären Teilen kann es da zu Rundungsfehlern kommen, die zumindest bei höheren
Baudraten, also schnellem Datentransfer, zu Übertragungsfehlern führen können. Deshalb ist es sinnvoll hierbei
keine "glatten" Taktfrequenzen zu verwenden, sondern "krumme" Quarze mit Frequenzen wie 3,6864 7,3728 oder 9,216 MHz,
weil hier die AVR-Interne Taktteilung ohne Fehler aufgeht. Genauere Erklärungen dazu findet Ihr auf der Seite
www.rowalt.de bzw. im AVR-Lehrbuch von Roland Walter.
(nach oben)
Der EEprom ist ein Speicher, in dem man Daten wie Variablen und Texte fest abspeichern kann, so, daß sie auch nach dem Abschalten der Betriebsspannung nicht verloren gehen und beim nächsten Einschalten des Gerätes wieder ausgelesen werden können. Wichtig zum Thema EEprom zu wissen ist, daß man nur einzelne Bytes abspeichern kann. D.h. um einen Text zu speichern muß man z.B. Buchstabe für Buchstabe die jeweiligen Ascii-Zahlen speichern und beim nächsten Programmstart wieder zu einem Text zusammensetzen. Das speichern und auslesen ist denkbar simpel und erfordert keinerlei Konfiguration oder Aktivierung im Header:
| Writeeeprom x , 1 | ' Speichert den Wert der Variablen x in Speicherzelle 1 |
| Readeeprom x , 1 | ' liest Speicherzelle 1 aus und weist den Wert der Variablen x zu |
Es können nur Varablen des Typs Byte direkt abgespeichert werden, bei Variablentypen die mehrere Bytes benötigen (z.B. Word) muß man den Wert zunächst in einzelne Bytes zerlegen, diese einzeln abspeichern und später, beim Auslesen, wieder zusammensetzen:
| Dim x As Byte | ' x als Byte definieren |
| Dim y As Word | ' y als Word definieren |
| y = 899 | ' y beliebigen Wert zuweisen (z.B. ADC Wert o.ä.) |
In einer als Byte vorbereiteten Variablen x
kann man kurzzeitig die einzelnen Bytes vom Word y
zwischenspeichern. Das Kombinieren des Teilens und Speicherns in
einer Befehls-Zeile ist bei Bascom nicht möglich:
| x = High(y) | ' weist x das MSB (High-Byte) des Words y zu |
| Writeeeprom x , 1 | ' speichert x in Zelle 1 |
| x = Low(y) | ' weist x das LSB (Low-Byte) des Words y zu |
| Writeeeprom x , 2 | ' speichert x in Zelle 2 |
Das Word y ist so auf die EEprom-Speicherzellen 1 und 2 aufgeteilt. Wenn man verstanden hat, wie Binäre Zahlen funktionieren (Zahlenscript gelsen?) dann ist es simpel diese 2 Bytes später wieder zu einem Wert des Typs Word zusammenzusetzen:
| Readeeprom x , 1 | ' Speicherzelle 1 (High-Byte) in die Variable x auslesen |
| y = x * 256 | ' bitte selbst nachdenken... |
| Readeeprom x , 2 | ' Speicherzelle 2 (Low-Byte) in die Variable x auslesen |
| y = y + x |
y hat jetzt wieder den ursprünglichen
Wert, der vorher zerlegt abgespeichert wurde. Natürlich muß man
die Adressen der Speicherzellen nicht unbedingt direkt angeben,
sondern kann auch hierfür Variablen verwenden.
Wichtig zu wissen ist, daß das Lesen und Beschreiben des EEproms
für µC-Verhältnisse recht lange benötigt und vorallem, daß
der EEprom nicht beliebig of beschrieben werden kann. Es
empfiehlt sich also das Lesen/Schreiben nicht innerhalb einer
Schleife ständig zu wiederholen, sondern die Daten nur wenn es
nötig ist einmal zu speichern (z.B. auf Tastendruck im
Unterprogramm) und die Daten z.B. nur einmal beim Starten vor der
Hauptschleife auszulesen.
(nach oben)