Microwebserver
Aus Wiki
Zum Projekt
Das hier vorgestellte Projekt stellt die Entwicklung und den Aufbau einer Programmierumgebung mit Mikrokontroller auf AVR Basis mit integrierter Netzwerkanbindung vor. Basierend auf einem Mikrokontroller vom Typ ATmega 1281/2561 wird vom Entwurf eines Konzeptes, über die Erstellung des Schaltplanes, der Entwicklung der Software bis hin zur fertigen Platine mit einer Vielzahl von genormten Anschlüssen sowie einem Netzwerkanschluss inkl. TCP/IP die Fertigung dieser Entwicklungsumgebung dargelegt. Dieses Entwicklungsboard kann sowohl von Schülern, Auszubildenden oder Studenten im Rahmen ihrer Ausbildung aber auch von Lehrkräften an Schulen und Universtäten sowie im Heim- und Hobbybereich genutzt werden. Die einfache Programmierung in Assembler oder der Hochsprache C/C++ gewährleistet eine leichte Handhabung der Entwicklungsumgebung und schnelle Erfolgserlebnisse. Sie ist für alle gedacht,die sich in die Welt der Mikrokontrollerprogrammierung hinein wagen aber auch für Könner, die mit Hilfe eines AVR Systems schnell und problemlos zum Ziel kommen möchten.
The aim of this project was to develop and produce a computer application with an AVR microcontroller and a network connection. Based on with the help of…)the microcontroller ATmega 1281/2561 you can see the timeline showing all steps of the development from the idea to the concept to the design of the circuit until the construction of the board with all nominal interfaces including the network interface. This development environment platform is not only for learners and students but also for teachers, professionals and for hobbies in domestic use. The platform is easy to program in Assembler or C/C++. It provides easier administration and easy achievement of the planned results. The board we developed is meant to be used by people who have just begun to approach the world of microcontroller programming aswell as providing fast solutions for professionals.
Aufgabenstellung
Zu Beginn der Aufstellung von Anforderungen an das uns gedanklich vorschwebende Projekt war eine Präzisierung der Funktionen des Mikrowebservers notwendig. Hier ging es vor allem um die vielseitige Nutzung des Gerätes, sowohl für Schüler, Studenten und Anfänger auf dem Gebiet der Programmierung als auch für fortgeschrittene Programmierer, Hobbyelektroniker und Dozenten in entsprechenden Unterrichtsfächern oder Kursen. Es sollte eine einfach zu bedienende und anschlussfreudige Entwicklungsumgebung für Schulungszwecke geschaffen werden, die aber auch im Hausgebrauch oder in der Industrie sinnvoll eingesetzt werden kann. Uns war aber auch klar, dass es schon eine Vielzahl von Mikrocontrollersystemen gab. Deshalb konzipierten wir ein System, welches den Ansprüchen an ein vollwertiges Embedded System gerecht wird, aber den Laien nicht mit Komplexität erschlägt. Folgende Funktionen sollte unser System vorweisen:
- leicht zu programmierender Microcontroller
- kein Ausbau des Controllers aus der Hardware zum Programmieren (ISP)
- Programmieren in Assembler und C/C++ möglich
- vielfältige Schnittstellenverfügbarkeit wie RS232, SPI, USART, I2C(Two Wire Interface), Netzwerk und Analogeingänge
- Debuggmodus (Industriestandard JTAG) für Schulungs- und Entwicklungszwecke
- hohe Taktrate des Systems (>10MHz)
- ausreichend Speicher (256kB Flash, 128kB RAM)
- kompakte Abmessungen der Platine
- wenige Bauelemente, geringe Beschaltung der IC`s
- großer Spannungsversorgungsbereich
- kostengünstige und leicht verfügbare Programmiersoftware und Programmiergeräte
Planung
Nach den im Kapitel Aufgabenstellung zu berücksichtigenden Vorgaben und der groben Aufteilung der Funktionen auf die jeweiligen Baugruppen war die Auswahl der Bauteile an der Reihe. Durch die guten Erfahrungen mit dem ATmega 1281/2561 fiel die Wahl auf diesen Controller. Als Schnittstelle zum Netzwerk sollte der Netzwerkcontroller ENC28J60 dienen. Zur Anschaltung der weltweit genormten Schnittstelle RS232 wurde der ST 232 als Spannungswandler gewählt. Um möglichst kurze Prozesszeiten zu erzielen, haben wir für die Speicherbausteine den Typ 74HCT573 als Zwischenspeicher und den HD 618182 (bzw. baugleiche) als Speicherbaustein ausgewählt. Beide Typen sind stromsparende High-CMOS Bausteine mit kurzen Schaltzeiten von 55ns. Auch das Unterbringen aller Bauelemente auf einer möglichst kompakten Platine war eine Anforderung bei der Entwicklung der Hardware, um die Platine auch in vorhandene Systeme zu integrieren bzw. ein kompaktes Stand-Alone-System bereit zustellen. Weiterhin wurde ein recht einfach zu haltender Aufbau mit wenigen Komponenten angestrebt, und die Entwicklung der Platine unter Berücksichtigung eines kostengünstigen Herstellungsprozesses.
Durchführung
Für die Durchführung unseres Projektes teilten wir die Arbeiten in die Bereiche Hard- und Software auf. Dadurch wurde jedem ein Teilgebiet der Gesamtarbeit zugewiesen und man konnte sich auf den jeweiligen Teil konzentrieren. Dennoch war eine ständige Absprache mit dem Teampartner unumgänglich, da viele Sachverhalte nur zusammen zu klären waren.
Hardware
In diesem Kapitel beschäftigen wir uns mit der Hardware, also allen Bauelementen des Entwicklungsboards, im Einzelnen und als Gesamtbetrachtung. Darüber hinaus werden einige wichtige Funktionen und Algorithmen einzelner Bausteine besprochen, um die Funktionalität der Entwicklungumgebung besser zu verstehen.
Danach werden Schaltplan und die Platine vorgestellt, sowie die einzelnen Arbeitsschritte von der Umsetzung des Schaltplanes in ein Layout, bis hin zur Herstellung der fertig bestückten und geprüften Platine.
Spannungsversorgung
Bei der Konzeption der Spannungsversorgung waren ein großer Eingangsspannungsbereich und die verschiedenen Spannungsebenen der eingesetzten Komponenten zu berücksichtigen. So benötigen der ENC28J60 eine Versorgungsspannung von 3,3 Volt , der ATmega1281/2561 und der MAX232 hingegen eine Spannung von 5 Volt (Vcc). Der Atmega1281/2561 kann zwar auch mit 3,3 Volt bis zu einer Frequenz von 8 MHz betrieben werden, aber um die vollen 16 MHz zu erreichen, werden deshalb 5 Volt eingesetzt, was letztendlich auch eine wesentlich bessere Performance bedeutet. Das kann man an nachfolgendem Diagramm gut erkennen.
Diese verschiedenen Spannungsebenen wurden durch Schaltregler-IC´s, dem IC5 LM2940 und dem IC6 LM3940 realisiert (Achtung: Bezeichnungen sind in unterer Grafik vertauscht). Beide Spannungsregler gehören zur Gruppe der Low-Drop Schaltregler. Der Vorteil gegenüber den gerne benutzten LM7805 ist eine mögliche geringere Spannungsdifferenz zwischen Ein- und Ausgang. Beide Arten von Längsreglern werden im Folgenden kurz beschrieben.
Low-Drop-Regler gehören zur Gruppe der Schaltregler. Hierbei wird die Ausgangsspannung mit der Spannung am nachgeschalteten Kondensator verglichen und beim Unterschreiten mit einem kurzen Spannungsimpuls diese wieder nachgeladen. Dieser Vorgang geschieht mit einer sehr hohen Frequenz und sehr kurzen Impulsen. Der Spannungsimpuls hat dabei die volle Spannung vom Eingang des Spannungsreglers. Dadurch ist die eigene Verlustleistung sehr gering, weil die restliche Spannung nicht über den IC in Wärme umgesetzt werden muss. Da diese Impulse sehr kurz sind, werden sie vom Kondensator geschluckt und es ist nur eine kleine Spannungsanhebung am Kondensator zu erkennen. Es findet eine geregelte Energienachladung der Kondensatoren statt. Die Schaltung wird dann von diesen Kondensatoren versorgt. Wichtig ist hierbei, dass die Kondensatoren nicht zu klein dimensioniert werden dürfen, da sonst die Spannungsimpulse nicht mehr von den Kondensatoren aufgenommen werden, sondern direkt in die Schaltung gelangen und so Schäden hervorrufen können. Im ungünstigsten Fall liegt dann für kurze Zeit die volle Eingangsspannung an. Bei digitalen Schaltkreisen kann das sehr verhängnisvoll sein. Umgekehrt darf die Schaltung nicht zu viel Strom beziehen.
Beide Spannungsregler liefern demnach einen genügend hohen Strom zur Versorgung der Schaltung, welcher max. 1A betragen kann. Die Kondensatoren dienen zur Glättung der Gleichspannung und zur Filterung hochfrequenter Anteile.
An die Buchse X2 wird eine unstabilisierte Spannung im Bereich von 8 bis max. 40 Volt angelegt, die durch die Diode D1 gleichgerichtet, durch C10 geglättet und dann der weiteren Spannungsstabilisierung zugeführt wird.
Die beschriebene Spannungsversorgung befindet sich aufgrund des geringen Platzbedarfs und der geringen Wärmeentwicklung ebenfalls auf der Platine unseres Entwicklungsboards.
ATmega1281/2561
Zentraler Baustein des Entwicklungsboards ist der Mikrocontroller ATmega2561 von Atmel. Sein baugleicher Bruder der ATmega1281 unterscheidet sich nur geringfügig in einigen elektrischen Parametern vom ATmega2561. Beide können für das hier vorgestellte Board verwendet werden. In den folgenden Kapiteln wird dieser Controllertyp mit seinen vielfältigen Funktionen und Eigenschaften vorgestellt wobei wir im weiteren Verlauf auf den in unserem Board benutzten ATmega2561 eingehen.
Warum fiel die Wahl auf einen solchen Mikrocontroller? Einige wichtige Gründe dafür sind:
- die kostengünstige Beschaffung des Bausteines
- sogenannter RISC - Reduce Instruction Set Computing (16MIPS @ 16MHz, fast alle Instruktionen singlecycle)
- 8-bit I/O-Datenbus
- Taktsteuerung wahlweise intern bzw. extern
- 32-bit interner Datenbus
- getrennte Speicherbereiche (Flash Rom, SRAM, EEPROM), Harvard Architektur
- In-System-Programmierung (ISP), kein teures Programmiergerät und kein Ausbau aus der Schaltung für die Programmierung notwendig
- programmierbar mit Assembler, Basic und C, welche kostengünstig oder kostenlos zur Verfügung stehen
- Debugging-Modus möglich durch JTAG Anschluss
- hohe Taktfrequenz
Weiterhin besitzt er AD-Wandler, PWM (Pulsweitenmodulator), Analogkomparator, SPI-, UART-, Two-Wire-Serial-Schnittstellen und vieles mehr. Alle diese Funktionen sind vielseitig programmierbar. Der ATmega arbeitet bei einer Versorgungsspannung von 5 Volt mit einer maximalen Taktfrequenz von 16 MHz.
Das Bild 2.1. gibt die Struktur des Controllers mit der Arithmetic Logical Unit ALU, den angrenzenden Registern, der Timer/Counter-Sektion, den Oszillatoren und den vielen anderen Baugruppen wieder. Der ATmega besitzt 8 Ein-Ausgabe Ports (I/O Ports) an denen einige Pins dreifache, jedoch alle mindestens doppelte Funktionen aufweisen. Die I/O Ports A bis F sind jeweils 8-bit Ports, der Port G hingegen ein 5-bit Port. Alle Ports besitzen zugehörige Datenrichtungs- und Datenregister. Nach einem Reset sind alle Ports grundsätzlich als I/O Ports definiert. Durch entsprechende Registereinträge können die Anschlüsse der Ports verschiedene Funktionen erfüllen oder Zustände einnehmen.
Obwohl seine Innenarchitektur recht komplex erscheint, benötigt er abgesehen von den Ports wenige Anschlüsse. Diese beschränken sich auf die Zuführung der Versorgungsspannung Vcc und GND, die Anschaltung eines externen Quarzes an XTAL1 und XTAL2, die Anschlüsse für den internen AD-Wandler AVCC, AGND und AREF, den Anschluss zur Freigabe der SPI-Programmierung PEN und natürlich den RESET-Anschluss des Controllers. Das zeigt, wie vielseitig die einzelnen Pins der 8 I/O Ports beschaltet werden können, da sie ja nicht nur die Funktion von Ein- bzw. Ausgangsdatenports erfüllen. Auf für unser Entwicklungsboard wichtige Ports und deren einzelne Anschlüsse wird im weiteren Verlauf noch eingegangen.
Durch scharfes Hinsehen sind bereits Harvard-Architektur und getrennte Speicherbereiche gut zu erkennen. Vorteil dieser Architektur, die vorwiegend von AVR-Chips , Motorola-Controllern und jetzt auch von Pentium-Prozessoren verwendet wird, ist die schnellere Abarbeitung von Befehlen. Nachteil ist jedoch die dadurch bedingte aufwendigere Programmierung, da ja Programmcode nur im Flash und Daten im SRAM abgelegt wird. Diese Bereiche werden im Kapitel Speicher näher erläutert. Zur weiterführenden Vertiefung der Funktionen und des Aufbaus der einzelnen Baugruppen über den in diesem Projekt beschriebenen Umfang hinaus, wird auf die Internetpräsenz von Atmel verwiesen.
Takt
Für einen Mikrocontroller und auch für jede andere CPU ist der Takt ein wichtiger und fester Bestandteil zur Erfüllung seiner Aufgaben. Der Takt, respektive die Frequenz, gibt die Geschwindigkeit vor, mit der die Befehle in der CPU abgearbeitet und die Daten innerhalb eines Rechnersystems ausgetauscht werden. Man spricht hier auch von einem Befehls-Zyklus (engl. Cycle). Er dient also zur zentralen zeitlichen Steuerung aller Prozesse im Mikrocontroller, einer CPU, bis hin zu einem komplexen System und der angeschlossenen Peripherie.
Beim ATmega1281/2561 gibt es drei Möglichkeiten, den Controller mit einem Systemtakt zu steuern:
- über die internen Oszillatoren
- extern per Quarz oder Keramikresonator
- extern als TTL Takt
Diese drei Varianten werden im Folgenden näher beschrieben.
Interner RC-Oszillator
Im internen RC-Oszillator wird mit Hilfe eines internen Widerstand/Kondensator Gliedes (RC) und in Abhängigkeit des Bitmusters im Register CKSEL eine feste Frequenz von 1, 2, 4 oder 8 MHz generiert. Diese Frequenz kann von außen nicht verändert werden und wird bei einer Versorgungsspannung von 5 Volt und einer Betriebstemperatur von 25° C ausgegeben. Standardauslieferung ist eine Oszillatorfrequenz von 8MHz mit einem Teilerwert von 8 durch den internen Vorteiler, sodaß der Oszillator mit 1 MHz schwingt. Nachteil dieses internen RC Oszillators ist seine hohe Abhängigkeit von Temperatur bzw. Versorgungsspannung. Deshalb wird er für die meisten Anwendungen nicht benutzt. Diese Abhängigkeiten sind in den folgenden Kennlinien dargestellt.
Das Taktsignal dieses Oszillators wird nun über einen Clock-Multiplexer den verschiedenen zeitgesteuerten Bausteinen des Mikrocontrollers zugeführt. Zu beachten ist, dass dieser RC-Oszillator direkt für den FlashROM und den EEPROM taktgebend wirkt, sofern er aktiviert ist. Nach einem Reset des Controllers wird automatisch eine Frequenz von 1 MHz eingestellt, bis die entsprechenden Bits in den Oszillator-Registern ausgelesen werden und eine andere Frequenz vorgeben. Durch Setzen eines bestimmten Bitmusters im Register OSCCAL Value kann die Frequenz in weiten Bereichen kalibriert werden. Dadurch ist eine Taktsynchronisierung des Controllers mit fremdgetakteten angeschlossenen peripheren Geräten möglich. Die folgenden Tabellen stellen das Setzen der benötigten Bits für die entsprechenden Frequenzen in den Registern CKSEL und OSCCAL Value dar.
externer Quarz
Bei Verwendung eines externen Quarzes wird dieser mit den Anschlüssen XTAL1 und XTAL2 des Mikrocontrollers verbunden. Hier kann sowohl ein Quarz als auch ein Keramikresonator angeschlossen werden. Die beiden Kondensatoren können optional eingesetzt werden, was vom Hersteller empfohlen wird aber nicht zwingend notwendig ist. Deren Werte sind abhängig vom verwendeten Schwingungsgeber und der zu erzielenden Frequenz.
Die Pins XTAL1 und XTAL2 können sowohl als Ein- und Ausgänge programmiert werden. Die vom Quarz abgegebene Frequenz gelangt intern auf einen Oszillator, der in drei verschiedenen Modi betrieben werden kann, welche jeweils für einen bestimmten Frequenzbereich zuständig sind. Diese Betriebsmodi sind abhängig von den Werten im Register CKSEL, was nachstehende Tabelle darstellt.
Durch Programmierung des CKOPT Registerwertes kann der Oszillator mit der gesamten Amplitude voll durchschwingen sowie die gesamte Frequenzbandbreite nutzen. Ist CKOPT nicht programmiert, steht nur ein begrenzter Frequenzbereich zur Verfügung, die Stromaufnahme des Oszillatorverstärkers sinkt stark ab und das Taktsignal kann zum Treiben weiterer Clockbausteine nicht mehr genutzt werden.
Für alle, die keinen Quarz in der Kramkiste haben, aber über einen 10 kOhm Widerstand und einen 100nF Kondensator verfügen. soll ergänzend zu den hier besprochenen Taktgebervarianten auch eine einfache, unkonventionelle Methode vorgestellt werden. Die einfache externe RC-Beschaltung ist mit den Pins XTAL1 und XTAL2 möglich. Dabei liegt an XTAL1 der Mittelabgriff des RC-Gliedes und XTAL2 bleibt unbeschaltet.
externer TTL-Takt
Die dritte und letzte Art der Taktgebung beschreibt die externe Anschaltung eines TTL Taktsignals. Hierbei wird an den Pin XTAL1 ein TTL konformes Taktsignal angelegt. Der Pin XTAL2 bleibt in diesem Fall unbeschaltet. Die Anschaltung eines externen Signals ist auf dem Board bereits mit Hilfe des Jumpers J7 realisiert. Dort werden die 6,25 MHz des Netzwerkcontrollers ENC 28j60 herausgeführt, um - gerade wenn man es braucht - ein externes Taktsignal bereitzustellen. Hiermit können schnell versehentlich falsch gesetzte Timerregisterwerte im ATmega korrigiert werden, um ihn wieder zum leben zu erwecken. Das Register CKSEL muss den Wert 0000 erhalten um dem Mikrocontroller mitzuteilen, dass ein externes Taktsignal anliegt. Durch Setzen des Registers CKOPT kann ein interner Kondensator mit dem Wert von 36pF zugeschaltet werden.
Zusammenfassend können wir nun daraus schliessen, dass die einfache Beschaltung des Controllers mit einem Quarz oder Keramikresonator die wohl beliebteste Methode zur Taktgenerierung darstellt. Das hat seinen Vorteil speziell in Embedded-Systemen, wo auf eine geringe Anzahl an Bauelementen aber dennoch auf eine hohe Ganggenauigkeit Wert gelegt wird.
Speicher
In der Einleitung zum ATmega1281/2561 haben wir bereits über dessen Besonderheiten gesprochen. Dort kam auch zur Sprache, dass er stolzer Besitzer der Harvard Architektur ist. Dass bedeutet, er besitzt nicht wie bei der Neumann Architektur einen einzigen Hauptspeicher, sondern verschiedene Speicher und Speicherarten, welche völlig unabhängig voneinander sind. Der Atmega beherbergt drei verschiedene Speichertypen: Flash, SRAM, EEPROM. Diese werden nachfolgend näher beschrieben.
Flash
Der ATmege1281/2561 besitzt einen 128K On-Chip In-System programmierbaren Flash-Speicher für den Programmcode. Dieser Speicher kann bis zu 10000 mal beschrieben und gelöscht werden. Er ist ein sogenannter nichtflüchtiger Speicher, das heisst, er behält seine Informationen auch nach Abschalten der Versorgungsspannung bei. Da in diesem Speicherbereich nur der Programmcode hinterlegt wird, ist ein sehr langer Gebrauchszyklus des ATmega möglich. Seitdem die AVR Familie 16 bzw. 32 bit breite Befehle abarbeitet, ist der Flash Speicherbereich als 64K x 16 bit organisiert. Zum Schutz des Programmcode in diesem Speicherbereich, ist er in zwei Bereiche unterteilt. Einmal in den Programm-Boot-Bereich und den Anwendungsprogrammbereich. Der Programmcounter ist 16 bit breit und adressiert die 64 K Programmspeicherplätze. Auf den Flash-ROM können folgende Programmiermethoden angewendet werden.
- Parallel Programmierung (PM)
- SPI/JTAG serielle Programmierung
Hierbei organisiert der Controller selbstständig das Beschreiben und Auslesen von Programmcode aus dem Boot-Speicherbereich. Für diese Tätigkeiten bedient sich der Controller des Boot Loaders. Diese Routine ist Bestandteil der sogenannten "Read While Write Self-Programming" Methode. Der Bootloader beherrscht folgende Funktionen:
- Lese-während-Schreibe-Selbst-Programmierung
- flexible Speichergröße einstellbar
- Hohe Datensicherheit durch Boot Lock Bits für flexiblen Schutz
- eigene Einstellungen für Reset Vector Auswahl
- optimale Speicherplatzausnutzung
- effizienter Lese-Aktualisiere-Schreibe Algorithmus
Somit ist der Bootloader für die Sicherung des Programmcode im Bootloader-Bereich, die Abtrennung zum Flash-Application-Bereich und den Zugriff durch andere Programmteile oder interne Routinen verantwortlich. Die Größe des Speichers kann in Abhängigkeit der gesetzten Bits im BOOTSZ Register programmiert werden. Diese Verschiebungen der Speicherbereiche im Flash Speicher zeigen folgenden Abbildungen:
Interner RAM
Der interne RAM, auch SRAM genannt, hat eine Speichergröße von 4096 Byte intern und kann optional auf weitere 64 K Speicherbereich erhöht werden. Er wird für das Ablegen von Registerwerten sowie als Stack genutzt. Der externe Speicherbereich wird für die Ablage von Variablen und den Heap genutzt. Die Verwaltung und die Zugriffssteuerung auf diesen Speicher werden im Kapitel Software näher erläutert.
Er kann in zwei verschiedenen Modi betrieben werden. Zum ersten im ATmega128 Modus. Dort wird der gesamte Speicherbereich aufgeteilt, indem die ersten Speicherbereiche beginnend mit $0000 für die 32 Register des ATmega, die 64 I/O-Register, die optional weiteren 160 extended I/O-Register und der Rest als freier Speicher zur Verfügung steht. Bedingt durch die höhere Anzahl an zu adressierenden peripheren Baugruppen im ATmega128, als in der Adressliste vorrätig sind, wird der externe Speicherbereich ausschließlich im ATmega128 Modus verwendet. Folgende Abbildung soll diese Speicherbereichsvergabe verdeutlichen.
Die zweite Variante ist der ATmega103-Modus, bei dem der optionale externe SRAM Speicherbereich von 64 K nicht zur Verfügung steht. Hier werden nur die 32 Atmega128-Register und die I/O-Register adressiert, der restliche verbleibende Speicherbereich wird zur freien Verwendung bereitgestellt. Das soll folgende Abbildung zeigen.
EEProm
Der dritte und letzte Speicher im ATmega1281/2561 ist der EEPROM. Seine Speichergröße beträgt 4096 Byte. Er ist bis zu 100000 Mal wiederbeschreibbar. Er zählt wie der Flash-Speicher zur Klasse der nichtflüchtigen Speicher. Ihn kontrollieren drei Register: das Adressregister, das Datenregister und das eigentliche Kontrollregister. Auf den EEPROM Speicher kann im SPI, JTAG und Parallelmodus zugegriffen werden. Hiefür werden wieder verschiedene Bits in den entsprechenden Registern gesetzt. Er dient hauptsächlich der Speicherung von Messwerten und Einstellungen.
Externe Speicherschnittstelle
Um den optional zu verwendenden externen Speicher auch ansprechen zu können, besitzt unser Mikrocontroller ATmega2561 ein spezielles Interface. Diese Schnittstelle verwaltet alle Daten- und Adressfunktionen, um mit dem externen Speicherbaustein zu kommunizieren. Darüber hinaus können an dieses Interface auch LCD Displays, AD- und DA-Wandler angeschlossen werden. Das Interface besitzt vier verschiedene Wait-State-Einstellungen, voneinander unabhängige Einstellungen des externen Speichers, Einteilfunktion des Speichers in Sektoren mit variablen Sektorgrößen und stromsparende Datenbusschutzfunktionen.
Sofern das XMEM-bit (external memory) gesetzt ist, werden die für die externe Speicherverwaltung benötigten Leitungen und Pins freigeschaltet. Durch die Bitsetzung des XMEM werden die Data-Direction-Registerwerte der entsprechenden Ports überschrieben und sie werden in ihre Alternativzustände gebracht. Zur Belegung der entsprechenden Pins wird auf das Kapitel "Ports" verwiesen. Für das Interface stehen die im Multiplexverfahren getriggerten unteren Adressbusleitungen A0 bis A7 bzw. Datenbusleitungen D0 bis D7, die oberen Adressbusleitungen A8 bis A15, die Adress-Latch-Enable Leitung ALE, sowie die beiden Standardleitungen Write WR und Read RD zur Verfügung.
I/O Ports
Wie schon im einleitenden Kapitel zum ATmega kurz erwähnt, besitzt er 54 I/O-Anschlüsse, aufgeteilt in 6 Ports zu je 8 Anschlüssen und einen Port mit 6 Pins, die teilweise dreifach zumindest aber alle doppelt belegt sind. In der Grundeinstellung dienen sie alle als Digitale I/O-Ports und können optional mit internen Pull up Widerständen versehen werden. Alle I/O-Ports besitzen Schutzdioden gegen Vcc und gegen GND sowie einen Kondensator zur Schwingungsunterdrückung. Die Ausgangstreiber der Anschlusspins geben genügend "Bums", um eine LED zum Leuchten zu bringen (im Normalfall 20mA).
Alle Portanschlüsse werden jeweils über drei Registerbits des betreffenden Ports konfiguriert. Da wären das Data Direction Registerbit, das Portbit und das Pinbit. Das Data Direction Bit im Data Direction Register definiert den Pin als Ein- oder Ausgang. Logisch NULL bedeutet Eingang, logisch EINS bedeutet Ausgang. Die Schreibweise ist hier DDxn, wobei x für den Port steht und n für den Anschluss am Port x. Im Portregister werden durch Setzen einer logischen Eins die Pull Up Widerstände aktiviert. Sind die Pins eines Ports als Ausgänge definiert, verursacht das Setzen des Portxn Bits auf logisch EINS ein High Signal an den entsprechenden Pins. Alle I/O-Pins sind als Tri-State Anschlüsse ausgeführt. Unabhängig davon, ob ein Port als Ein- oder Ausgang geschaltet ist, können die Werte durch das Pinxn Registerbit des entsprechenden Port ausgelesen werden.
In erweiterten Einstellungen können Sie als ADC-Eingang, als UART, SPI, TWI(I²C), Interruptquellen oder als PWM-Ausgänge benutzt werden. Diese zusätzlichen Funktionen werden durch die entsprechenden Registerwerte der Ports gesteuert. Der Übersichtlichkeit halber und um den Rahmen dieses Kapitels nicht zu sprengen, werden wir uns ausschließlich um die in unserem Microwebserver benötigten Pins und Ports kümmern. Diese sollen nun vorgestellt werden.
Vorweg zur Erklärung: Im nachfolgenden Text wird oft von Ports und deren Pins die Rede sein. Diese Bezeichnungen sollen dem Leser ein leichteres Verständnis der in unserem Entwicklungsboard genutzten Pins verhelfen. Bis auf PortA und PortC wurden alle anderen Anschlüsse in ihrer Alternativbeschaltung genutzt und sind somit nicht den eigentlichen I/O Ports zuzuordnen. Dennoch werden wir im Folgenden von den jeweiligen Ports und deren Anschlüsse sprechen.
Wie im Schaltplan ersichtlich, wurden alle Ports, dort aber nicht zwingend jeder einzelne Pin benutzt. Durch die sehr flexible Programmierung der einzelnen Pins des ATmega, konnten wir so die für unser Entwicklungsboard benötigte Anschaltung realisieren. Das ist ein weiterer wichtiger Vorteil dieses Controllers.
Alle Pins des Port A werden sowohl als Adressbusleitungen als auch I/O-Datenleitungen für die Datenbits D0 bis D7 verwendet, um Daten in und aus dem Speicherschaltkreis zu lesen und zu schreiben. Die Besonderheit liegt an der Zwischenschaltung eines Speicher IC's 74HCT573, der als Zwischenspeicher ausgeführt ist (Latch), um die unteren 8 Adressbusleitungen A0 bis A7 für den externen Speicher "zwischenzuparken", um damit einfach die fehlenden Adressbusanschlüsse am ATmega zu simulieren. Näheres dazu ist im Kapitel Speicherbaustein zu lesen.
An Port B sind nur 4 Pins beschaltet, welche für den Datenaustausch mit dem Netzwerkcontroller ENC 28j60 verbunden sind . Hier wird die Datenübertragung im sogenannten SPI (Serial Peripheral Interface) Modus bewältigt. Die vier Pins beinhalten die Datenübertragungsleitung MISO und MOSI, das Synchrontaktsignal SCK sowie die Auswahlleitung für Slave Select SS. Detaillierte Funktionen und Abläufe des SPI Busses sind im Kapitel "SPI Bus" beschrieben. Aufmerksame Leser werden hier bemerkt haben, dass wir es bei den beiden Bausteinen ATmega2561 und ENC28J60 um Bauelemente mit unterschiedlicher Versorgungsspannung zu tun haben. Das stellt kein Problem dar, da der ATmega2561 ein HIGH Signal bereits ab 1,8 Volt zweifelsfrei erkennt. Vom ENC28J60 wird ein Signal mit einem Pegel von 3,3 Volt gesendet. Da sollte genügend Spielraum vorhanden sein.
Der Port C des Controllers ist nur mit den oberen acht Adressbusleitungen A8 bis A15 belegt (oberes Adressbyte).
Port D beinhaltet die Datenleitungen TXD und RXD zur Anbindung der RS232 Schnittstelle. Weiterhin sind hier die Leitungen Serial Data SDA und Serial Clock SCK für die Two Wire Interface- Schnittstelle (TWI=I²C) an Jumper 5 angeschlossen. Diese Schnittstelle kann mit einer einfachen Zweidrahtleitung bis zu 128 periphere Geräte ansprechen, da in diesem Modus nur eine 7 bit Adressierung möglich ist(vergleiche mit ASCII). Es wird nur eine Datenleitung SDA und eine Taktleitung SCK benötigt. Somit hat der Microwebserver eine weitere Schnittstelle nach "außen", um bei entsprechender Programmierung eine Erweiterung durch andere Geräte zu ermöglichen. Zusätzlich liegen an den Pins PD4 bis PD6 jeweils Kontrollleuchtdioden um Zustände bzw. Programmabläufe des Controllers optisch darzustellen. Diese Pins sind LOW aktiv und werden durch setzen der Bits im Data-Direction-Register als Ausgänge programmiert und können je nach Erfordernis im Data-Register programmiert werden. An Pin PD7 wird das Taktsignal des Timer/Counter 2 herausgeführt um ggf. weitere angeschlossene Baugruppen mit dem Controllertakt zu synchronisieren. Somit wird eine taktgenaue synchrone Abarbeitung des Datenaustausches gewährleistet.
An Port E liegen mehrere Interruptleitungen, welche den Prozessablauf des Controllers beeinflussen können und sollen. Da wären an den Pins PE4 bis PE7 die Interruptleitung INT4 bis INT7. INT4 wird sowohl an den Netzwerkscontroller ENC28J60 als auch an Jumper 6 geführt. So ist der ENC28J60 oder ein externes Gerät in der Lage, bei entsprechenden Ereignissen, ein Interruptsignal an den Controller zu senden. Die Interruptleitungen INT5 bis INT7 werden auf den Jumper 3 geführt, um eine Interruptmeldung von weiteren peripheren Geräten zu ermöglichen. Hier kann z. B. ein weiterer Controller oder ein angeschlossener Drucker ein INT Signal an den Controller senden. Die Pins PE0 bis PE3 sind als SPI-Schnittstelle beschaltet und programmiert. Hier sind die Pins PE0 als Empfangsleitung RXD, PE1 als Sendeleitung TXD, PE2 als Taktleitung SCK1 und Pin PE3 als Auswahlleitung für den Slave SS beschaltet. Diese Leitungen werden ebenfalls an Jumper6 geführt, um eine weitere, zweite SPI-Schnittstelle nach außen zu führen.
Port F stellt zwei Schnittstellen bereit. Zum einen die JTAG-Schnittstelle, welche für die hardwarenahe Simulation und das Debuggen im Zuge der Programmentwicklung und Fehlerkorrektur zuständig ist. Jumper 4 stellt dann diese JTAG Schnittstelle, zur Verfügung, mit welcher - bei entsprechender Programmierhardware bzw. einem Entwicklungssystem - die Abläufe des Controllers debugged werden können. Zum anderen sind am Port F die Leitungen des internen AD-Wandlers ADC0 bis ADC3 herausgeführt und werden am Jumper 2 zur Verfügung gestellt. Dieser AD Wandler ist in unserem Fall nicht aktiv, da die anderen Leitungen des Port F , welche auch für den AD-Wandler notwendig sind, für die JTAG Schnittstelle zur Verfügung gestellt werden.
Zum Schluss kommen wir zum Port G. Hier werden die Leitungen zur Steuerung der Speicherbausteine herausgeführt. Das sind die Leitungen Write WR und Read RD sowie die Adress-Latch-Enable ALE Leitung für den Zwischenspeicher.
An Pin 1 des ATmega1281/2561 (PG5) wird das Taktsignal OCB0B des Timer/Counter 0 an Jumper 6 geführt, um externe Geräte mit dem Systemtakt zu synchronisieren. Damit ist die Beschreibung der für unser Entwicklungsboard benötigten Mikrocontrolleranschlüsse abgeschlossen.
Interrupts
Um einen ereignisgesteuerten Programmablauf zu verwirklichen und somit gezielt Prozesse des Systems anzuhalten, zu verändern oder zu beeinflussen, bedarf es einer komplexen Interruptsteuerung. Interrupt bedeutet immer Unterbrechung des gerade ausgeführten Prozesses und Sprung in ein Unterprogramm. Diese Unterprogramme werden auch Interrupt-Service-Routinen ISR oder Interrupt-Handler genannt. Um dem Controller mitzuteilen, wo diese Routinen zu finden sind, gibt es eine Interrupt-Vektor (lat. Zeiger)-Tabelle, in der die Startadressen der Routinen hinterlegt sind. In unserem Fall soll die CPU des Controllers aus ihrem Rechnen-Dörnröschenschlaf geweckt werden. Der ATmega2561 besitzt insgesamt 35 Interruptroutinen, welche durch die Interrupteingänge von außen an INT0 bis INT7 (in unserem Schaltplan an Port E nur INT0 bis INT4) und alle weiteren durch die internen Baugruppen des ATmega initiiert werden. Interne Interrupts werden zum Beispiel durch die Timer/Counter bei Nulldurchgang oder Überlauf, durch den Analogkomparator, durch den Abschluss einer seriellen Datenübertragung (UART, SPI, JTAG), den Analog-Digital-Wandler und durch den internen Speicher (EEPROM, SRAM) hervorgerufen. In allen Fällen wird die CPU im Controller ihre Arbeit unterbrechen und die jeweilige Interruptserviceroutine einlesen und abarbeiten. So wird in unserem Microwebserver der Netzwerkcontroller ENC28J60 auf die INT Leitung 4 gelegt. Vier weitere Interruptleitungen werden durch die Jumperschnittstellen nach außen geführt. Dadurch können periphere Geräte wie z. B. andere Controller, Drucker, USB Sticks u.v.a.m. einen Interrupt auslösen, sofern sie die ungeteilte Aufmerksamkeit der CPU des Controllers benötigen. Wird eine Interrupt-Service-Routine abgearbeitet, sind alle weiteren Interruptanforderungen gesperrt. Der Controller bietet die Möglichkeit die Interruptadressen zu maskieren.
Was passiert nun im Details beim ATmega2561?
- Interruptsignal IRQ wird durch Quelle initiiert
- CPU stoppt aktuellen Prozess
- Registerwerte werden auf dem Stack gesichert
- Interruptvectortabelle wird angesprungen, ISR-Wert wird ggf. maskiert
- Sprung zur Interrupt-Service-Routine (an deren Adresse im Speicher)
- Laden der Adresse der Interrupt-Service-Routine in die CPU, abarbeiten der Befehle der Routine
- nach Abarbeiten der Routine laden der Registerwerte vom Stack in CPU
- CPU fährt mit vorher unterbrochenem Arbeitsablauf fort
Der Wermutstropfen des bisher so glänzend davongekommenen ATmega ist die fehlende Priorität von Interruptanforderungen. Die einzige existierende Priorität ist die der aufsteigenden Rangfolge in der Interrupt-Vektor-Tabelle. So wird z.B. ein Reset immer vor einem externen Interrupt behandelt. Hier ist der Programmierer gefragt, indem er alle in Frage kommenden Interruptleitungen in den Opcode mit aufnimmt und alle nicht benutzten Interruptvektoren mit reti deklariert. Da heißt es also: wachsam sein und selbst die Worst-Case-Liste erarbeiten, um einen programmtechnischen Supergau zu vermeiden. Nicht selten ist es in der Praxis vorgekommen, daß Controllerbausteine sporadisch Fehlfunktionen hatten, die Fehlersuche den Programmierer schier zur Verzweiflung getrieben hat. Er konnte den Fehlcode nicht finden... Eine vollständige Liste aller Interrupts befindet sich im Anhang.
Timer/Counter
Im ATmega2561 befinden sich verschiedene Timer und Counter, die hier näher vorgestellt werden sollen. Ein Timer ist landläufig nichts anderes als eine Uhr, welche durch ein Startsignal zu laufen beginnt und die abgelaufene Zeit als Wert zur Verfügung stellt. Mit dem kleinen Unterschied, das diese Uhr in unserem Controller in Form eines Registers existiert. Dort werden die Registerwerte um 1 erhöht oder erniedrigt. Ein Counter (engl. Zähler) hingegen reagiert auf Ereignisse verschiedenster Art, z.B. ein Taktsignal oder eine Pegelflanke. Timer/Counter dienen dazu, bestimmte Algorithmen zu überwachen, zu steuern und je nach Programmierung ein Interrupt auszulösen, um die CPU über die Ereignisse in Kenntnis zu setzen. Timer und Counter sind eng miteinander verwandt. Ein Timer kann auch Counteraufgaben wahrnehmen, sofern er entsprechend programmiert ist. Die Timer/Counter im ATmega können auch als Signalgeneratoren benutzt werden. Das Praktische an diesen Timern ist Ihre Steuerbarkeit durch den Systemtakt. Somit ist es möglich zeitliche Prozesse immer mit dem gewählten Systemtakt, also der Controllergeschwindigkeit zu realisieren. Auf unserem Entwicklungsboard befindet sich zur Takterzeugung ein 16 MHz Quarz. Dadurch ist es dem Controller möglich, mit diesem Takt die internen Timer und Counter anzusteuern. Schauen wir uns das Prinzipschaltbild der Timer-Counter Sektion im ATmega1281/2561 an:
Dem ATmega stehen vier 8bit und zwei 16bit Timer/Counter zur Verfügung. Alle greifen auf den gleichen Vorteiler zu, der separat und unabhängig zu den Timern seine Aufgabe erfüllt. Dennoch können mehrere Timer verschiedene Werte des Vorteilers abrufen. Diese komplexe Funktionalität wird in den Timer/Counter-Registern eingestellt. Wird einem Timer/Counter kein internes oder externes Taktsignal zugeführt, stoppt er sofort bzw. beginnt gar nicht erst zu zählen. Timer/Counter können hervorragend zur Interruptauslösung benutzt werden. Durch einen Überlauf des maximalen Zählwertes (Overflow) oder eines zuvor festgelegten Wertes, können alle Timer/Counter ein Interruptsignal an die CPU senden. Mit den Timern können auch komplexe Wellenformen erzeugt sowie Pulsweitenmodulation realisiert werden.
Der 8bit Timer besitzt zwei vergleichbare unabhängige Ausgänge, kann Pulseweitenmodulation (PWM) unterstützen und nennt zwei Register sein Eigen, in denen die Timerwerte ständig bereitgehalten werden. Die Kontrolleinheit des Timers überwacht die Taktquelle (intern, extern), gibt die Betriebsart clear, direction und count an den Timer weiter und erkennt, ob der Timer/Counter am obersten bzw. untersten Zählwert steht.
Die 16bit Timer sind als reine 16bit Timer ausgebildet, dass bedeutet, daß diese Timer immer bis 2hoch16 durchzählen um dann einen programmierten Zustand oder Ablauf zu ermöglichen. Es ist nicht möglich z.B. nur die unteren 8 Bit des Timers zu nutzen um ein Ereignis auszulösen. Sie beinhalten drei unabhängige Ausgänge, doppelt gepufferte Ausgangsregister, einen phasenrichtigen Pulsweitenmodulator mit variabel einstellbarer Pulsweitenmodulationsperiode, einen Frequenzgenerator, einen externen Event-Counter und zwanzig voneinander unabhängige Interruptquellen. Für den 16bit Timer sind drei Register zuständig, die entsprechend ihrer gesetzten Bits die vielfältigen Funktionen des Timers aktivieren. Das Timer/Counter-, Output-Compare- und Input-Capture Register sind jeweils 16bit Register. Das Contol-Register ist jedoch nur ein 8bit Register und hat keine CPU Zugriffsberechtigung. Die Interruptanforderungen befinden sich im Timer-Interrupt-Mask- Register. Auch für den 16bit Timer gelten die gleichen Ansteueralgorithmen wie für den 8bit Timer. Ohne externen bzw. internen Takt läuft er nicht an bzw. weiter. Auch besitzen die 16bit Timer jeweils eine Kontrolleinheit, die entsprechend den anliegenden Signalen und den Registerwerten den Timer in den entsprechenden Modus versetzen.
Watchdog
Unter Watchdog versteht man den sogenannten "Wachhund" eines Controllers. Er ist im Regelfall als Timer ausgeführt und soll im Programmfehlerfall ein Reset des Systems auslösen, um den Controller in den Ausgangszustand zu versetzen. Beim ATmega2561 sind ein Watchdog-Oszillator mit einer Frequenz von 128 kHz und ein nachgeschalteter Watchdogtimer implementiert. Der Watchdogtimer kann nun einen Interrupt oder einen Systemreset auslösen. Diese Funktionen und die Werte des Vorteilers des Watchdogtimers werden im Watchdog Timer Control Register eingestellt. Der Vorteiler ermöglicht Timerzyklen zwischen 16 msec und 8 sec. Ein Watchdog-Reset dauert nur einen Taktzyklus, reicht aber aus um die Resetroutine des Controllers zu starten. Ein weiteres Einstellungsbit im Controller entscheidet darüber, ob der Watchdogtimer ständig aktiv ist. Das bedeutet, das er bei Zeitablauf keinen Interrupt sondern gleich ein Systemreset auslöst.
Komperator
Neben den bisher erläuterten Baugruppen des ATmega 2561 besitzt er auch einen Analog-Komparator. Mit diesem Komparator sind zwei grundlegende Modi möglich. Zum Einen der normale Komparatormodus, bei dem zwei Eingänge miteinander verglichen werden und am Ausgang ein entsprechendes logisches Signal anliegt. Zum Anderen der Multiplex-Modus. Dort können bis zu 16 verschiedene Signalwerte am negativen Eingang miteinander verglichen werden. Hierfür wird der negative Eingang des Komparators umgeschaltet um an diesem im Mutliplexverfahren bis zu 16 Eingangssignale abzutasten. Hierdurch können komplexe analaoge Signale (z.B. Sinus) abgetastet und verglichen werden, um eine entsprechende Funktion auszulösen (z.B. Noise, o.ä.). Das Komparatorausgangssignal kann den Timer/Counter1 triggern. Somit ist es möglich abhängig vom Komparatorergebnis zeitlich abhängige Ereignisse zu steuern. Um die Komparatoreingänge zu nutzen sind am Port E die Pins PE2 und PE3 vorgesehen. PE2 ist der negative Eingang, PE3 der positive Eingang. Gesteuert wird der Komparator durch sein Status-und Kontrollregister. Je nach Einstellung der Bitwerte in diesem Register, werden die verschiedenen Komparatormodi aktiviert/deaktiviert. Der Komparator kann auch zum Auslösen eines Interrupt verwendet werden. Er hat dafür eine dirkete Verbindung zur Interrupt Unit. Wann ein Interrupt ausgelöst werden soll kann im Status-Kontroll-Register eingestellt werden. Sofern nur analoge Signale an den Eingängen AI0 und AI1 anliegen, kann das digitale Input Buffer abgeschaltet werden, um so stromsparender zu arbeiten. Das wird im DID-Register (Digital-Input-Disabled) mit den Bits 0 und 1 eingestellt.
Programmierung
Der ATmega kann seinen internen Speicher in folgenden Modi programmieren:
- parallel Programming
- serial Programming
- serial Programming über JTAG-Interface
Die Programmierung des ATmega durch den Anwender erfolgt über die serielle ISP Schnittstelle. ISP bedeutet In-System-Programmierung und hier ist der Name bereits Programm. Diese Schnittstelle ermöglicht die sehr komfortable und schnelle Anbindung des Controllers an ein Programmiergerät ohne den Controller aus einer vertrauten Umgebung herauszureissen. Benötigt werden hierfür nur drei Anschlusspins auf der Platine bzw. ein spezieller Stecker am Gehäuse. Die einzelnen Schnittstellenleitungen werden beim ATmega folgendermaßen herausgeführt:
- Vcc
- GND
- RST Reset
- MISO Master-In / Slave-Out
- MOSI Master-Out /Slave-In
- SCK Clocksignal (serial Clock)
In unserem Board sind die oben aufgeführten Anschlüsse am Jumper 4 herausgeführt. Dieser Jumper ist so beschaltet, daß das Programmiergerät AVRISP mkII von Atmel dort angesteckt werden kann. DIe eigentliche Programmierung erfolgt über das Programmiergerät. Es stellt die Schnittstelle zwischen der Entwicklungssoftware auf einem PC und dem Board dar. Als Programmierschnittstelle im ATmega wird das dort vorhandene Serial-Periphal-Interface (SPI) genutzt. Der Aufbau und die Wirkungsweise dieser Schnittstelle wird im nachfolgenden Kapitel erläutert. In der Tabelle sind die benötigten Anschlusspins und Ihre Bezeichnung aufgeführt.
SPI-BUS
Das Serial Peripheral Interface (kurz SPI) ist ein Bus-System mit einem sehr lockeren Standard für einen synchronen seriellen Datenbus, mit dem digitale Schaltungen nach dem Master-Slave-Prinzip miteinander verbunden werden können.
- Drei gemeinsame Leitungen an denen jeder Teilnehmer angeschlossen ist:
- SDO (Serial Data Out) bzw. MOSI (Master-out Slave-in)
- SDI (Serial Data In) bzw. MISO (Master-in Slave-out)
- SCLK (Serial Clock)
- je eine SS/CS/STE (Slave Select/Chip Select/Slave Transmit Enable)-Leitung für jeden Slave zwischen ihm und dem Master (es gibt aber auch Anwendungen, bei denen sich mehrere Slaves eine Leitung teilen, siehe Bild 1)
- Full-Duplex-Betrieb
- keine Adressierung (erfolgt über Slave-Select Leitung)
- viele Einstellmöglichkeiten (z.B. Phase, Polarität)
- Taktfrequenzen bis in den MHz-Bereich zulässig
- vielfältige Einsatzmöglichkeiten in Audio- und Messanwendungen, zur Datenübertragung zwischen Microcontrollern
Die vielen Einstellmöglichkeiten sind unter anderem deshalb erforderlich, weil die Spezifikation für den SPI-Bus sich in vielen Dingen (Daten bei der steigenden oder fallenden Taktflanke gültig? MSB oder LSB zuerst übertragen? Erlaubte Taktfrequenz?) nicht konkret festgelegt sind und deshalb verschiedene, zueinander inkompatible Geräte existieren. So ist häufig für jeden angeschlossenen Schaltkreis eine eigene Konfiguration des steuernden Microcontrollers (Master des SPI-Busses) erforderlich. Die ATmega-Serie ist darauf bestens vorbereitet. Sie kann die meisten benötigten Einstellungen über Register innerhalb der Hardware erledigen. Im folgenden Bild ist eine typische Übertragung eines Bytes zu sehen. Als Parameter wird das LSB zuerst gesendet und die Übernahme geschieht bei fallender Flanke. Zum Synchronisieren wird das Chip Select Signal CS verwendet, um den Anfang der Übertragung anzuzeigen. Der Slave hat sich dann um das Mitzählen der Bits selbst zu kümmern, denn es können nicht nur 8 Bits, oder besser ein Byte, in einem Rutsch übertragen werden, sondern gleich ganze Blöcke an Daten.

UART
UART ist die Abkürzung für Universal Asynchronous Receiver Transmitter. Die Funktion ist, einen seriellen digitalen Datenstrom mit einem fixen Rahmen aufzubauen, welcher aus einem Start-Bit, fünf bis maximal neun Datenbits, einem optionalen Parity-Bit zur Erkennung von Übertragungsfehlern und einem Stopp-Bit besteht. Das UART dient sowohl zum Senden als auch zum Empfangen von Daten.
Die Besonderheit besteht darin, dass bei der üblichen asynchronen Version kein explizites Taktsignal verwendet wird. Stattdessen synchronisiert sich der Empfänger durch den Rahmen bestehend aus dem Start- und Stopp-Bit und einer bestimmten Bitrate. Optional kann auch ein Paritybit zur Fehlererkennung mitgesendet werden. Da der Beginn einer Übertragung mit dem Start-Bit zu beliebigen Zeitpunkten erfolgen kann, wird diese serielle Schnittstelle als asynchron bezeichnet. Im Gegensatz dazu sind synchrone serielle Schnittstellen durch einen bestimmten Bittakt ohne einen Rahmen aus Start-/Stopp-Bit aufgebaut wie es bei SPI der Fall ist.
Wie man auf den Bild erkennt, kann man mit Hilfe des Startbits genau den Anfang der Übertragung feststellen. Das Stoppbit wird aus diesem Grund auch invertiert zu Startbit übertragen, somit kann der Rahmen der Übertragung genau erfasst werden wenn die richtige Bitrate eingestellt ist. Die Bitrate auf beiden Seiten kann bis zu 5 Prozent von einander abweichen, somit ist gewährleistet, daß das letzte Bit des Rahmen immer noch im richtigen Abstand erfasst wird. Ist die Abweichung größer oder kleiner 5 Prozent kann es zu Übertragungsfehlern kommen, das die entsprechende Bitstelle nicht mehr zu richtigen Zeitpunkt erfasst wird.
MAX232
MAX232 ist die Bezeichnung für einen Levelshifter IC von Maxim. Aber auch andere Hersteller haben die Bezeichnung in ähnlicher Form übernommen und bieten IC`s mit gleichen Funktionen. Nötig ist dieser IC, da die UART-Schnittstelle mit anderen Spannungspegeln arbeitet als der Mikrocontroller. Es wird eine Spannungsanpassung benötigt. Die UART/RS232 arbeitet zwischen -12V und +12V, Mikrocontroller arbeiten meist mit 3.3V oder 5V. Der MAX232 arbeitet ebenfalls im 5 Volt Spannungsbereich. Er beinhaltet Inverter und Spannungsvervielfältiger und stellt so die -12V und +12V zur Verfügung. Er besitzt je zwei Ein- und Ausgänge. Im beigefügten Bild ist seine Standardbeschaltung zu sehen, um eine Pegelanpassung der unterschiedlichen Spannungsebenen zu realisieren. So lässt sich eine RS232 konforme Schnittstelle mit wenigen Bauelementen entwickeln.
D-Sub 9 - Buchse
Um eine weltweit verbreitete standardisierte Schnittstelle in unser Entwicklungsboard zu integrieren, entschieden wir uns für die RS232 Schnittstelle (EIA 232), welche über eine standardisierte D-Sub 9 Buchse herausgeführt wird. Die Bauform der Buchse entspricht den Normen
- DIN 41652 Teil 1
- IEC 807-2
- MIL-C-24308.
Es handelt sich um eine zweireihige trapezförmige Buchse mit 1 mm Kontaktaufnahmen, wobei sich in der unteren Reihe vier Kontakte, in der oberen Reihe fünf Kontakte befinden. Es wurde die Bauform stehend mit 90° abgewinkelten Lötfahnen gewählt, um sie direkt auf der Platine zu integrieren. Befestigt wird die Buchse zusätzlich mit zwei Lötverbindungen auf der Trägerplatine.
ENC28J60
Der ENC28J60
Der Integrierte Baustein ENC28J60 dient zur Ankopplung eines Netzwerkes an einen Mikrocontroller und ist somit das Bindeglied zum Netzwerk. Man bezeichnet ihn auch als Ethernetcontroller, der folgende Aufgaben übernimmt:
- Verbindung zwischen Netzwerk und Controller
- De- und Enkodierung der analogen Signale in der Physical Layer Unit (PHY)
- Erkennung von angeschlossenen Kabeln, deren Polarität inkl. Korrektur
- Wiederversenden nach Kollisionserkennung
- Prüfsummengenerierung
- Verwerfen von fehlerhaften Paketen
- Erkennen von Traffic auf der Verbindung
- Verarbeiten der ein- und ausgehenden Frames
- die Paketverwaltung (TCP, ICMP), unterstützt Uni- , Multi- und Broadcastpakete
- Zwischenspeicherung der Frames im Dual Port RAM
Er arbeitet bei einer quarzgesteuerten Frequenz von 25 MHz die durch einen internen Oszillator erzeugt wird. Obwohl der ENC28J60 eine Versorgungsspannung von 3,3 V benötigt sind seine SPI Schnittstellenanschlüsse 5V kompatibel, um mit einem Mikrocontroller ohne Levelshifting arbeiten zu können. Dieser Umstand war ein weiterer Grund, ihn für dieses Entwicklungsboard einzusetzen. Der Ethernetcontroller ENC28J60 erfüllt die Norm IEEE 802.3 und ist somit für ein 10BaseT-Netzwerk geeignet. Intern verfügt er über eine hardwaregesteuerte Paketverwaltung FIFO und eine DMA Baugruppe für den schnellen Zugriff auf die Daten. In der Standardanschaltung an einen Controller wird er im SPI (Serial Periphal Interface) Modus benutzt. Folgende Abbildung verdeutlicht die prinzipielle Funktionsweise des ENC28J60 .
Eine Besonderheit des ENC28J60 ist sein Dual-Port RAM. Das ist ein 8 kByte großer Speicher der in zwei separate Bereiche, einen Empfangs- und einen Sendespeicher, aufgeteilt ist. Die Größe und Aufteilung dieser Speicherbereiche kann vollständig durch den Mikorcontroller über den SPI Bus gesteuert werden. So ist es möglich in Abhängigkeit der Anwendung mehr oder weniger Speicher für eingehende bzw. ausgehende Pakete zu reservieren. Zusätzlich zu diesem Ethernet-Buffer beinhaltet der ENC 28j60 Speicherbereiche für die Control- und PHY Register. Das PHY-Register belegt einen Speicherbereich von 256 Byte, das Controlregister ist in vier Bänke mit jeweils 32 Byte aufgeteilt. Der Ethernet-Buffer hat eine Größe von 0000h bis 1FFFh.
Kommunikation und Anbindung des ENC28J60
Wie erfolgt nun die Kommunikation des ENC28J60 mit dem Netzwerk und dem Mikrokontroller? Wie oben kurz erwähnt, arbeitet der ENC28J60 mit 3,3 Volt Versorgungsspannung und es entsteht zwischen Host-Controller und Ethernetcontroller eine Spannungsdifferenz. Durch die definierten Logikpegel des ATmega2561 ist eine Levelshifter-Logik zwischen Controller und ENC28J60 nicht notwendig, da der Controller bereits ab 1,8 V einen High-Pegel erkennt. So können neben den bereits erwähnten SPI Leitungen auch die Interruptleitung und die WOL-Leitung ohne besondere Zwischenbeschaltung an den Controller angeschlossen werden. Praktische Versuche und Messungen auf dem aufgebauten Board haben diese Aussage bestätigt. Die Beschaltung des Ethernetcontrollers beschränkt sich auf ein Minimum, so dass er oft für die Anschaltung einer Controllerschaltung an ein Netzwerk verwendet wird. So braucht er nur einen 25 MHz Quarz mit den zugehörigen Kondensatoren, die RJ45 Buchse mit ggf. integrierten LED´s, dem Übertrager sowie den Anpassungswiderständen. Ein Widerstand in der Größe um 2 kOhm wird für den analogen Schaltungsteil des ENC28J60 benötigt. Der 10µF Kondensator am Anschluss VCAP stabilisiert die interne Spannungsversorgung des digitalen Schaltungsteils im ENC28J60. Aufgrund von Messungen am Entwicklungsboardprototyp und der leichten Beschaffung von Bauelementen haben wir den Kondensatorwert am Anschluss VCAP auf 47µF und den Widerstandswert am Anschluss RBIAS auf 2,7 kOhm erhöht. Durch den Einsatz der LED´s sind entsprechendende Vorwiderstände zwischen den ENC28J60 und die LED´s zu schalten.
Kommuniziert wird mit Hilfe des SPI Busses. Diese Schnittstelle stellt die einfachste und schnellste Anbindung des ENC28J60 an einen Host-Controller dar. Hier werden lediglich die drei für SPI üblichen Datenleitungen MISO, MOSI, SCK, die Chip (Slave)-Select-Leitung SS (CS)und die Interruptleitung INT angeschlossen. Mehr Verbindungen bedarf es zur Datenübertragung und zum Informationsaustausch nicht. Durch Einstellungen der entsprechenden Bits im Register ECON1 des ENC28J60 wird der Ethernetcontroller durch den Host-Controller gesteuert. Hier werden grundlegende Betriebsarten, wie der Sende - und Empfangsmodus, die Behandlung der Prüfsummen, der DMA -Zugriff sowie die Selektion der Registerbänke eingestellt. Die Kommunikation wird fast vollständig durch die Software gesteuert, indem der Host-Controller über seine Programmroutinen die Übertragung der Daten mit dem ENC28j60 vereinbart. Diesem Thema widmet sich das Kapitel ENC28J60 im Softwareteil dieser Dokumentation. Durch die separate Interruptleitung kann der Ethernetcontroller einen Interrupt an den Hostcontroller senden, um ihn auf bestimmte Zustände (z.B. RX/TX-Buffer voll, Meldung von Traffic) aufmerksam zu machen. Die Standardbeschaltung des ENC28J60 , zeigt folgendes Bild. Deutlich sind hier auch die erforderlichen Maßnahmen zur Anschaltung des ENC28J60 mit dem externen Netzwerk, wie Anpasungswiderstände und galvanische Trennung, zu erkennen.
Betriebsarten Half/Full-Duplex
Standardmäßig wird der ENC28J60 von anderen Geräten in einem Netzwerk, die mit automatischer Duplex-Erkennung ausgestattet sind, als Halbduplexgerät erkannt. Um die Funktion des Vollduplexbetriebs zu erreichen, muss in den Registern MACON3.FULDPLX und PHCON1.PDPXMD eine 1 gesetzt werden. Während des Umschaltens von Halb- in Vollduplexbetrieb kann auf den ENC28J60 vom Host-Controller nicht zugegriffen werden.
Im Halbduplexmodus arbeitet der Ethernetcontroller in der Art, dass er die vom Hostcontroller übergebenen Daten in Pakete zusammenfügt und an das Netzwerk sendet. Da es im Halbduplex immer nur einen Sender geben kann, kommt es in einem Halbduplexnetzwerk zu Kollisionen. Hier gibt es nun zwei Arten der Behandlung dieser Kollisionen:
- Kollisionserkennung bei noch nicht vollständigem Absenden des Frames
- Kollisionerkennung bei vollständigem Absenden des Frames (entfernte Kollision)
Im Halbduplexverfahren erkennt der ENC28J60 eine Kollision und wartet mit dem erneuten Senden des Datenpaketes. Der Host-Controller braucht solange nicht eingreifen, bis die vorher programmierte maximale Sendewiederholung erreicht ist. Dann wird durch den Host-Controller entschieden, ob das Paket als verworfen oder als erfolgreich gesendet behandelt wird. Bei einer entfernten Kollision wird das Versenden des Paketes sofort abgebrochen. Im Vollduplexverfahren ist ein gleichzeitiges Senden und Empfangen möglich. Speziell für diese Betriebsart ist der Ethernetbuffer in die Bereiche RX und TX aufgeteilt. Die Einstellungen der Register, welche für den Halbduplexbetrieb zuständig waren, werden nun nicht benutzt. Durch die Konfiguration im Vollduplexbetrieb werden keine Datenpakete zurück gesendet. Sollte es für Testzwecke notwendig sein, einen Loopback-Betrieb durchzuführen, ist das Bit PLOOPBK im PHCON1 Register zu setzen. Das schaltet die TwistedPair Treiber im ENC28J60 aus und jeder ankommende Traffic wird ignoniert. Nun werden alle ankommenden Pakete durch die internen Filter als normale Netzwerkpakete erkannt und verarbeitet.
Ethernetübertrager
Durch die Maßgabe unseres Entwicklungsboards, einen Netzwerkanschluß bereit zu stellen, ist eine Schnittstelle notwendig, um es mit mit seiner Außenwelt zu verbinden. Hier bedienen wir uns einer standardisierten RJ 45 Buchse mit integriertem Übertrager vom Typ HFJ11-2450E-L12 "Fast Jack" der Firma HALO. Vorteil dieser Buchse liegt in den bereits eingebauten LED's für die Anzeigen von Link und Traffic sowie dem integrierten Übertrager. Der Übertrager gewährleistet eine galvanische Trennung des Microwebservers vom Netzwerk. Im Sende- und in Empfangsrichtung besitzt der Übertrager ein Übertragungsverhältnis von 1:1. Seine Gleichtaktunterdrückung beträgt typisch -40dB, die Übersprechdämpfung beträgt mindestens -40dB. Die LED werden mit einer Spannung von 2,1 Volt versorgt. Da in unserem Fall die Leuchtdioden durch den ENC 28j60 versorgt werden, sind entsprechende Vorwiderstände mit 470 Ohm vorzusehen.
Speicherbaustein
In unserem Entwicklungsboard kommen zwei Speicherbausteine zur Anwendung. Zum Einen der 74HCT573N bzw. Baugleiche, ein High-Speed-CMOS Standard IC der als Zwischenspeicher (Latch) fungiert. Zum Anderen der HD628182 (bzw. Baugleiche), ein 1 Mbit Statisches RAM für den externen 64K (128K) Speicher.
Wenden wir uns zuerst dem 74HCT573N zu. Dieser IC ist ein 8bit Latch mit TTL kompatiblen Daten-Eingängen und Tri-State Ausgängen zur optimalen Anpassung an verschiedene Datenbusse. Über die Anschlüsse Latch-Enable (LE) und Output-Enable (OE) kann dieses Latch durch den Controller gesteuert werden. Liegt am Anschluss LE ein HIGH an, dann werden die an den 8 Eingängen anliegenden Werte übernommen und gleichzeitg an den Ausgängen zur Verfügung gestellt. Bei LOW am LE Anschluss werden die Werte an den Eingängen im Latch gespeichert, die zur Zeit der HIGH-LOW Flanke angelegen haben. Ist der Anschluss OE auf HIGH werden die Werte im Latch gehalten und nicht an die Ausgänge weitergeschaltet. Diese schalten in den hochohmigen Zustand. Erst wenn OE auf LOW gesetzt wird, stehen die Informationen an den Ausgängen zur Verfügung. Folgendes Bild verdeutlicht diese Arbeitsweise.
Vorraussetzung für eine sichere Datenübertragung zwischen Controller und Speicher ist ein ständiges Anliegen der Adressinformationen am Adressbus. Der Mikrocontroller benötigt den 74HCT573 zur Zwischenspeicherung der unteren acht Adressbits A0 bis A7 da er selbst nicht über 16 Adressleitungen verfügt, die 64K im externen Speicher aber mit 16 bit adressiert werden müssen. Der Ablauf der Übertragung ist stark vereinfacht folgender:
- Adressen A0-A15 ausgeben
- Adressen A0-A7 im Latch speichern
- Daten D0-D7 einlesen / ausgeben
Die Umschaltung zwischen den Adress- und Datenbits geschieht im Multiplexverfahren. Nachfolgend ist beispielhaft für den Schreibvorgang ein Timingdiagramm dargestellt, um die Zwischenspeicherung der Adressbits A0-A7 zu verdeutlichen. Damit wird die bereits oben erwähnte Vorraussetzung erreicht, dass bei der Übertragung der Daten zum/vom externen Speicherbaustein die benötigten Adressinformationen ständig anliegen.
Zur Abspeicherung von Daten und als Erweiterung des internen SRAM im Mikrocontroller, wird der integrierte Baustein HD628182DLP-5, ein 1 Megabit SRAM, organisiert in 128K x 8bit, benutzt. Auch er ist ein High Speed-CMOS Typ und hat eine Schaltzeit von 55ns. Seine Speichermatrix ist in der Form 512 x 2048bit organisiert. Die Adressbusleitungen dienen zur Auswahl der entsprechenden Speicherzellen, indem 8 Adressleitungen die Zeilen und 8 Adressleitungen die Spalten der Speichermatrix über die interne Logik definieren. Der Speicherbaustein benötigt keinerlei Taktsignal und wird ausschließlich durch seine Steuerleitungen CS1, CS2, OE, WE vom Hostcontroller gesteuert. Seine Daten I/O Pins sind als Tri State ausgeführt und werden abhängig von den Zuständen an den Steuerleitungen geschaltet. Durch seinen geringen Ruhestromverbrauch von ca. 10µA ist er auch für mobile batteriebetriebene Minianwendungen geeignet. Um auch die oberen 64 K des externen Speichers zu nutzen, wird eine siebzehnte Adressleitung A16 an Pin 32 vom Controller herausgeführt. Die Aktivierung dieser Adressleitung ist Aufgabe des Programmierers, sofern er auf die noch freien 64K des Speichers zugreifen möchte.
Schaltplan
Nachdem wir die wichtigen aktiven Bauteile und Ihre Funktionen kennen gelernt haben, folgt die Aufgabe, diese in einer Schaltung miteinander zu verbinden. Bei der Entwicklung dieser Schaltung steht an erster Stelle der Entwurf eines Schaltplanes. Durch die Auswahl der aktiven Halbleiterbauelemente
- ATmega1281-16AI oder ATmega2561-16AU,
- ENC28J60,
- 74HT573 und 628182 sowie
- MAX 232
ist es nun möglich, einen Schaltungsentwurf der o.g. Bauelemente zu erstellen, welcher die Funktionen unseres Entwicklungsboards erfüllt. Nach Zusammenstellung der Standardbeschaltungsvorschläge der Hersteller wurden die benötigten Bauteile mit Hilfe einer Schaltungssoftware miteinander verbunden. Wie bereits an der einen oder anderen Stelle erwähnt, wurden einige Bauelemente abweichend von den empfohlenen Werten gewählt. In der nachfolgenden Abbildung des Stromlaufplanes sieht man deutlich die einzelnen Komponenten, ihre periphere Beschaltung und deren Verbindungen untereinander.
Der Schaltplan wurde mit Hilfe der Software "Eagle Ver 4.16" erstellt.Platinendesign
Nach der Erstellung des Schaltplans galt es jetzt, ihn in ein geeignetes Platinenlayout zu verwandeln. Hierbei waren folgende Überlegungen notwendig. Vorgabe war eine einseitig bestückte Platine, da der für die Herstellung zur Verfügung stehende Isolationsgravurfräser eine einseitige Platine ohne Probleme herstellen kann. Weiterhin war zu beachten das die Baugruppen übersichtlich und optimal auf der Platine platziert werden. So wurde ein Platinenlayout realisiert, was nur zwei Drahtbrücken enthält und bei dem die einzelnen Funktionsgruppen in eigene Bereiche auf der Platine gesetzt wurden, was ihr schnelles und unkompliziertes Finden und eine eventuelle Reparatur ermöglicht. Es sind alle Bauelemente, welche für die RS232-Schnittstelle verantwortlich zeichnen, direkt an der Sub D Buchse X1 wiederzufinden. Die Baugruppe Netzwerkanbindung inklusive deren Anpassungswiderstände sind in direkter Nachbarschaft der RJ45 Buchse platziert. Der Mikrocontroller mit den Speicherbausteinen findet seinen Bereich in unmittelbarer Nähe der nach außen geführten Programmier- und Portschnittstellen. Durch den Einsatz der SMD Form des ATmega2561, ist dieser auf der Leiterseite der Platine zu finden. Dadurch konnte auf der Bestückungsseite viel Platz eingespart werden und das Layout insgesamt wesentlich verkleinert werden. Die Spannungsversorgung belegt den vierten und letzten Teil der Platine und grenzt unmittelbar an Buchse X2. So konnten lange Leitungswege vermieden werden. Die Datenleitungen zwischen Controller und den Speicherbausteinen verlaufen kompakt nebeneinander und sind somit nicht anderen Störeinflüssen auf der Platine ausgesetzt. Spannungsversorgung und RS232-Schnittstelle sind ebenfalls auf den erforderlichen Platz minimiert worden und verursachen aufgrund der kurzen Leiterbahnwege keine Störungen. Leiterzüge der Spannungsebenen sind entsprechend den Stromstärken dimensioniert. Ein Ringmasse wurde gemäß den Vorgaben beim Entwurf digitaler Systeme vermieden. Alle Kupfergroßflächen sind massefrei.
Auch das Platinenlayout wurde mit Hilfe der Software "Eagle ver.4.16" erstellt. Nach dem Einstellen der erforderlichen Parameter und dem folgenden Autorouting des Layouts mußten nur kleine Änderungen an einigen "engen" Stellen vorgenommen werden. Durch den Einsatz des Isolationsfräsers zur Platinenherstellung war eine Isolationsbreite von minimal 0,3 mm zu beachten. Die Bestückungs- und die Leiterseite der Platine sind in den nachfolgenden Abbildungen ersichtlich.
Board
Als Trägerplatine wurde 1,5 mm starkes handelsübliches einseitig kupferkaschiertes Platinenmaterial vom Typ FR4 100x160E-35u verwendet, welches nach dem Fräsvorgang auf die Größe einer halben Europlatine mit den Maßen 100x81 mm reduziert wurde. Diese Größe resultiert zum einen aus der durch die Free-Version der Layout-Software Eagle maximal mögliche editierbare Platinengröße und andererseits aus der Vorgabe, die Platine möglichst kompakt zu halten. Auch durch im einschlägigen Fachhandel erhältliche Gehäuse ist diese Größe gewählt worden.
Isolationsfräsen
Für das Isolationsfräsverfahren stand uns eine Isolationsfräsmaschine der Firma isel-automation vom Typ "isel-CNC-Modular" zur Verfügung. Hier wird die sogenannte Gravurfräsung angewendet. Diese Maschine muss man sich wie eine Kreuzung zwischen einem Plotter und einer Bohrmaschinenhalterung vorstellen. Die Bohrmaschine, eine einfache Proxxon Maschine, in die der Fräser eingespannt wird, bewegt sich auf einer Führungsschiene in Abhängigkeit des Layouts mit Hilfe einer Schrittmotorsteuerung in links-rechts-Richtung (x -Achse). Der Tisch mit der Platinenspannvorrichtung bewegt sich in Abhängigkeit des Layouts in Vor-Rück-Richtung (y-Achse). Die Steuerungssoftware "ProLeiSys" ermöglicht es, über eine Schnittstellenkarte vom Typ "isel-Microstepkarte MPK-3" die mit der Platinenentwicklungssoftware Eagle erstellten Layouts zu fräsen. Die Ansteuerkarte wird durch einen DOS Computer gesteuert, der wiederum das Bindeglied zur Eagle-Software darstellt. Die Schnittstellenkarte steuert ihrerseits die Schrittmotoren zur Achsenveränderung des Fräsers. Durch einen definierten Anschlag auf der Platinenspannvorrichtung wird ein genau definierter Koordinatenursprung, auch Nullpunkt genannt, realisiert. Die Ganggenauigkeit der Fräse beträgt 0,02mm. So ist es möglich, Leiterzüge mit einer Genauigkeit von 0,02 mm zu fräsen. Diese 0,02 mm resultieren aus dem Spiel der mechanischen Bauteile der Fräse. Andererseits kann der Fräser selbst nur eine minimale Isolation von 0,3mm fräsen. Die Tiefenkontrolle des Gravurfräsens übernimmt ein Tiefenregler der Firma Thiemig. Mit ihm sind Tiefengravuren bis zu 5mm möglich. Als Fräser werden Gravierstichel 60° und 36° mit 0,10 mm Gravurbreite, 3 mm Schaftdurchmesser und 40 mm Länge verwendet. Diese können über www.carbide-tools.com bezogen werden. Nach dem Fräsen der Leiterzüge werden anschließend alle Löcher der Platine mit einer parallel angebrachten zweiten Proxxon Bohrmaschine gebohrt. Das wird durch einen softwaregesteuerten "Werkzeugwechsel" realisiert, indem die Fräsmaschinenhalterung nach oben gehoben und die Bohrmaschinenhalterung abgesenkt wird. Beide Arbeitsgänge erfolgen unmittelbar nacheinander. Es ist nicht notwendig, die Platine aus der Spannvorrichtung zu nehmen.
Wie erfolgt nun der Weg vom Layout zur fertigen Platine? Nachfolgend wird das in chronologischen Stichpunkten beschrieben:
- Erstellen der Schaltung und des Layouts mit Eagle Ver 4.16
- Generieren einer Bitmap Datei mit Eagle Ver 4.16 -> 1 Pixel entspricht einem 1mm2
- Erzeugen einer HPGL Datei mit Eagle Ver 4.16, Fräserstärke auf 0,3mm einstellen
- Erstellen eines kleinen Programms im Rahmen des Eagle Projektes basierend auf PerlTK (c)
- Per RS 232 Schnittstelle werden die Daten an den DOS Rechner übertragen
- HPGL Datei wird im Menü ausgewählt, Fräser fährt an den Nullpunkt
- Layer wird gewählt (Top oder Bottom) und Fräsvorgang starten
- Im Menü "bohren" aktivieren
- Bohrvorgang starten
Diese Form der Platinenherstellung ist schneller und vor allem umweltfreundlicher wie herkömmliche Ätzverfahren da hierbei keine chemischen Substanzen verwendet werden, deren Restbestände einer besonderen Entsorgung bedürfen. Auch die aufwendige Fotoätztechnik oder ähnliche Verfahren mit mehreren teilweise zeitintensiven Arbeitsgängen müssen hier nicht vorgenommen werden. Als Restbestandteile verbleiben nur das abgefräste Kupfermaterial und etwas Trägermaterial der Platine. Diese Bestandteile werden umweltgerecht entsorgt.
Bestücken und Testen
Nach dem Fräsvorgang wird die Platine sorgfältig gereinigt und einer Sichtprüfung auf fehlerhafte Fräsungen, Kurzschlüsse, nicht gefräste Isolationen und andere Anomalien unterzogen. Nachdem diese Prüfung fehlerfrei durchlaufen wurde, wird die Platine mit einer Handhebelplatinenschere auf das erforderliche Maß von 100x81mm geschnitten. Nun ist mit der Bestückung der Platine beginnend mit dem Microcontroller fortzufahren. Da wir uns für die SMD Variante entschieden, wird der µC nach folgenden Arbeitsgängen auf die Platine gebracht:
- Säubern der Lötanschlüsse für den Controller
- Lötpaste vom Typ Sc170 dünn und gleichmäßig auf die Anschlüsse des µC auftragen
- Controller möglichst exakt auf die Lötfahnen drücken und ggf. ausrichten
- Backofen (E oder Gas) auf min. 270°C vorheizen
- mit Controller bestückte Platine auf mittlerem Blech ca. 3 min im Backofen belassen
- wenn Lötpaste zerlaufen ist und ein metallisch glänzendes Aussehen aufweist, Platine aus Ofen entnehmen und abkühlen lassen
- Sichtkontrolle auf nicht gelötete Anschlüsse und Lötbrücken durchführen
- ggf. entstandene Lötbrücken mit Entlötlitze entfernen, nicht gelötete Anschlüsse werden im gleichen Arbeitsgang verlötet
- Sichtkontrolle durchführen(am besten gegen eine helle Lampe)
- Platinenleiterseite mit Lötlack (z.B. SK10) einsprühen und trocknen lassen
Nun folgte beim Prototyp das Bestücken der Platine mit den beiden Drahtbrücken, allen Bauteilen der Spannungsversorgung, des Quarzes Q2 (16MHz), des Kondensators C16 und des Jumpers J1. Nach Überprüfen der bis hierher eingebauten Bauteile auf ihren korrekten Sitz kann nun eine Spannung von ca. 9-12 Volt an die Buchse X2 angeschlossen werden. Die Spannungswerte der Spannungsregler sind nun zu überprüfen. Sind alle Werte im erforderlichen Bereich, wird ein vorhandenes Programmierinterface (z.B. AVRISPmkII) angeschlossen. Nach Anschluss der Programmiereinheit sollte nun der Controller durch das Aufleuchten der grünen LED am Programmiergerät erkannt werden. In der Programmierentwicklungssoftware (z.B. AVR Studio) können nach Einrichten der Parameter und Festlegen des verwendeten Controllertyps, die Einstellungen des frisch eingebauten Controllers ausgelesen werden. Bis dahin sollte es keine Probleme geben. Wie im Einzelnen die Programmiersoftware benutzt wird, ist in der jeweiligen Version nachzuschlagen. Beim AVR Studio ist lediglich eine Projektdatei zu erstellen und der Typ des Controllers festzulegen.
Es folgen nun alle weiteren Bauelemente der Platine, beginnend mit den niedrigen Bauelementen wie Widerstände, Keramikkondensatoren dem zweiten Quarz usw. Dann werden die Elektrolytkondensatoren, die Leuchtdioden LED1 bis 3, die übrig gebliebenen Jumper und die restlichen Schaltkreise eingelötet. Am Ende folgen die RJ45 Buchse sowie die Sub D9 Buchse X1, welche durch zwei große Lötfahnen zusätzlich mechanisch befestigt wird. Es steht jedem frei für die im DIL Gehäuse befindlichen Schaltkreise entsprechende Sockel auf die Platine zu löten. Zwischen den einzelnen Lötabschnitten ist eine sorgfältige Sichtprüfung empfehlenswert.
Nach dem kompletten Zusammenbau der Schaltung ist ein weiterer Funktionstest, beginnend mit der nochmaligen Prüfung aller Spannungen, vorzusehen. Ist nach erneutem Anschluss die LED am Programmiergerät bis hierhin immer noch grün, ist alles in Ordnung. Kontrolliert wird auch die Versorgungsspannung am Ethernetcontroller ENC28j60 (Pin28). Sie sollte dort 3,3 Volt betragen. Damit ist die Bestückung und Prüfung der Platine abgeschlossen. Jetzt kann die Software mit der Programmierentwicklungsumgebung implementiert werden. Zum Schutz der Platine kann diese schon in ein handelsübliches Gehäuse nach Wahl eingebaut werden. Jedoch sollte man an die Jumper heran kommen, um bequem programmieren zu können. Unsere Wahl fiel auf ein Aluminium-Gehäuse "Euro-Gehäuse 1" der Firma proMa//systro GmbH. Da dieses Gehäuse die Aufnahme einer Europlatine mit den Maßen 160x100 ermöglicht, mußte es für unsere halbe Europlatinengröße entsprechend verkleinert werden. Hierfür wurden die Seitenprofile, die Deckplatten und die Seitenplatten entsprechend gekürzt, entgratet und die Seitendeckel mit Löchern für die Verschraubung versehen. An den Seitenplatten werden die Aussparungen entsprechend den Buchsenanordnungen auf der Platine gebohrt bzw. gefräst und ausgefeilt. Nach sorgfältiger Fertigung sollte unsere Platine leicht in die untere Führungsnut des Gehäuses gleiten, die Buchsen an den entsprechenden Durchbrüchen plan abschliessen und das Gehäuse sich leicht verschrauben lassen. Hiermit ist das Kapitel der Hardware abgeschlossen und wir wenden uns dem Softwareteil des Microwebservers zu.
Software
Nachdem wir in den letzten Kapiteln die Hardware behandelt haben kommen wir zur Software. Das schönste Hardwaredesign nützt gar nichts, wenn nicht eine intelligent geschriebene Software diese auch mit Leben füllt. In den folgenden Kapiteln wird auf die Entwicklungsumgebung in der die Software entwickelt wurde beschrieben, grundlegende Funktionen eingegangen sowie die Softwarearchitektur erklärt.
Es wird die Entstehung einer Entwicklungsumgebung beschrieben, die sich dann zum späteren Entwickeln von Applikationen eignet, die jeder mit guten Grundkenntnissen in C auch umsetzen kann. Als Beispiel werden auch einige kleine Applikationen gezeigt die bewusst einfach gehalten sind, um zu zeigen, wie Funktionen und Techniken eingesetzt werden.
Entwicklungsumgebung
Die Entwicklung der Software für den ATmega geschieht auf dem normalen PC mit Hilfe eines Crosscompilers. Als IDE (Integrated Development Envioment) kann unter Windows zum Beispiel AVR-Studio benutzt werden. Unter Linux eignet sich Anjuta als IDE. Als Programmiersprache wurde C für die Entwicklung gewählt, da diese Sprache sehr verbreitet ist und auch die Portierbarkeit auf andere System gewährleistet. Da in unserem Projekt auf möglichst geringe Kosten geachtet wird, wurde ein freier Compiler gewählt, die angepasste avr-lib c und der AVR-GCC-Port. Dieser Port ist auf nahezu allen gängigen Systemen verfügbar, wird aktiv weiterentwickelt und bringt auch eine Menge AVR-spezifische Funktionen mit. Das erleichtert die Arbeit und das Portieren. Somit es es auch möglich, dass jeder zur Entwicklung sein Lieblingsbetriebssystem (Windows, Linux oder MAC) wählen kann. Um die Programme die auf den PC entwickelt würden in den Controller zu bekommen wird ein sogenannter ISP benutzt der mit Grundvoraussetzung zum entwickeln ist. Im folgendem wird noch mal auf die einzelnen Komponenten die zur Entwicklung benötigt werden eingegangen.
AVR-GCC
Der AVR-GCC ist eine Portierung des GCC4.1 auf den AVR. Er beinhaltet die C Standard Library, angepasst auf den AVR, und weitere grundlegende C-Funktionen. Er ist als Crosscompiler ausgelegt und erzeugt lauffähigen Maschinencode für den AVR. Der Sourcecode wird hierbei auf einem normalen Rechner entwickelt und dann für die jeweilige Zielplattform zu einem lauffähigen Programm übersetzt, welches dann mit einem speziellen Programmer auf die Zielplattform übertragen wird. Ein weiterer großer Vorteil ist die aktive Entwicklergemeinde des AVR-GCC, so ´das er ständig verbessert und erweitert wird und inzwischen für viele Systeme verfügbar ist. So ist der AVR-GCC als Port für Windows (WinAVR) sehr interessant, da er in Verbindung mit AVR-Studio von Atmel eine sehr komfortable Entwicklungsumgebung darstellt, die mit Features wie einen Debugger oder einen JTAG-Debugger aufwarten kann um die Fehlersuche zu erleichtern oder Fehler in der Hardware zu finden.
avrdude / avrISPmkII
Die Übertragung des Programms auf den Mikrocontroller funktioniert wie schon erwähnt mit speziellen Programmern, auch ISP genannt, wie die gleichnamige Schnittstelle. Das hat den Vorteil, dass die Programme auf den Controller innerhalb der Schaltung übertragen werden können, ohne diesen aus der Schaltung entnehmen zu müssen. Das ist ein wichtiges Merkmal, verringert es doch den Aufwand enorm, da schon während der Entwicklung Programmteile getestet werden können und lästiges Hantieren mit der Hardware entfällt.
doxygen
Ein wichtiger Teil beim Programmieren ist die Dokumentation des Sourcecode. Diese erfolgt in unserem Projekt mit doxygen innerhalb des Sourcecode. Das ermöglicht dem Programmierer die Dokumentation nach speziellen Regeln schon während des Programmierens direkt in den Sourcecode zu schreiben, und aus diesem auch zu erzeugen. Danach erhält man eine sehr gute übersichtliche Dokumentation mit Querverweisen, benutzten Bibliotheken und die Funktionsbeschreibung an sich. Voraussetzung ist hierbei, dass diese auch gleich mit eingepflegt wird. So ist es auch möglich, mehrere Programmierer an einem Projekt gleichzeitig arbeiten zu lassen, da jeder, wenn die Dokumentation richtig gepflegt wird, diese sofort zur Verfügung hat und sich so zurechtfindet. Unter hier findet sich die Dokumentation zum diesem Projekt, mit dem derzeitigen Stand Rev. 69 (Stand 10.09.2008). Als Voraussetzung für das Arbeiten mit mehreren Programmierern wird hier natürlich auch ein Revisionsystem gebraucht. In diesem Fall kommt Subversion oder kurz SVN zum Einsatz. SVN verwaltet die verschiedenen Revisionen einer Software und speichert alle Änderungen. So kann jeder Änderungen schnell einpflegen und alle Programmierer haben den gleichen Sourcecodestand und die aktuelle, passende Dokumentation. Bei Fehlern können so auch alte Programmteile wieder hergestellt oder verglichen werden. Ein Feature das jeder Programmierer nicht mehr missen möchte. Es besteht die Möglichkeit sich eine grafische Entwicklung des Sourcecode anzeigen zu lassen. Daran erkennt man sehr gut wie aktiv die Entwicklung stattfindet. Als Beispiel für eine Grafik nahmen wir unser Projekt. Diese stellt den Start der Entwicklung im Juli 2006/2007 bis heute dar.

Grundgedanken zum Aufbau der Software
Bevor man die ersten Zeilen Code schreibt und den ersten lauffähigen Code erhält ist die genaue kenntnis der Hardware Voraussetzung. Daran orientiert sich der Programmaufbau und die Verwaltung. Dazu gehören zum großen Teil der interne Aufbau / Verarbeitung und vor allem die Speicherorganisation. Der interne Aufbau ist für uns weniger von Interesse, da uns hier der Compiler sehr stark unterstützt. Ihm braucht nur die Zielplattform mitgeteilt werden und alle Anpassungen wie Registerzuordnung, Stack- und RAM-Adressen, größe des Flash, EEprom und spezielle Register sind ihm dadurch bekannt. Da dem Mikrocontroller in der Grundversion verhältnissmäßig viel Speicher zur Verfügung steht reichen diese Einstellungen in der Regel. Eine Ausnahme bildet in diesem Projekt der externe RAM. Dieser externe RAM muss dem Compiler mitgeteilt werden, sonst wird der Compiler versuchen den Stack und Variablen in das internen RAM zu hinterlegen, welcher mit 8kByte zwar für Mikrocontrollerverhältnisse recht groß ist, aber für das Projekt nicht genügt. Es besteht zwar die Möglichkeit alles auch im internen RAM zu halten, dies führt aber zu Einschränkungen in der Programmierung mit vielen Variablen. Standardmäßig hat der Controller folgende Speicheraufteilung mit internen RAM:
Der Speicher wird in 3 Teile aufgeteilt. Der erste rote Bereich von Adresse $0000 bis $01FF kennzeichnet die Adressen für Register die zur Steuerung der Hardware benutzt werden. Der grüne Bereich ist für globale Variablen und den Heap gedacht und beginnt bei $0200. Die Variablen sind in diesem Bereich an festen Adressen hinterlegt, nur der Heap ist dynamisch und kann sich aufsteigend vergrößern. Der Heap wird bei der Speicherreservierung zur Laufzeit von malloc() benutzt. Der blaue Bereich ist für den Stack vorgesehen. Dieser kann sich absteigend vom Speicherende beginnend vergrößern. Hier werden Rücksprungadressen und Variablen angelegt die lokal in Funktionen benutzt werden. Wie man auf den Bild erkennen kann, arbeiten der Heap und der Stack auf einander zu. Sollte einer dieser Bereiche sich mit den jeweiligen anderen Bereich überschneiden kann es zu Problemen und Fehlfunktionen im Programm führen. Diese Speicherkollisionen sollten vermieden werden. Der Programmierer hat sich selbst darum zu kümmern wie die Speicherauslastung ist und sollte deshalb sparsam mit globalen Variablen und den Heap umgehen. Für die meisten Projekte ist aber trotzdem der interne Speicher ausreichend, da meistens nicht die Komplexität erreicht wird beziehungsweise so viele Variablen gebraucht werden. Da aber in unserem Projekt sehr viele Daten zwischengespeichert und verarbeitet werden müssen wurde die Möglichkeit genutzt, externes RAM als Erweiterung zu nutzen. Dieser externe RAM hat einen Adressraum von 64kByte. Hier lassen sich schon wesentlich mehr Daten unterbringen. Interessant ist hier die wählbare Speicheraufteilung. Da der der Zugriff auf das interne RAM schneller ist als der auf das externe RAM ergeben sich einige Überlegungen wie die Aufteilung erfolgen sollte, ohne die Verarbeitunggeschwindigkeit zu stark zu beeinflussen. Als sehr gute Möglichkeit bietet sich hier an das interne RAM nur für den Stack und das externe RAM für globale Variablen und den Heap zu nutzen. Mit dieser Lösung geht man auch gleich einem sehr großen Problem aus dem Weg. Denn jetzt arbeiten der Stack und der Heap nicht mehr auf einander zu und es entstehen keine Speicherkollisionen mehr. Ein weiterer Vorteil ist, dass der Stack noch mit der vollen Geschwindigkeit des internen RAM arbeiten kann, was den Programmfluss nicht merklich bremst da C den Stack sehr stark für lokale Variablen und Funktionsparameter benutzt. Da jetzt nur noch globale Variablen und der Heap im externen RAM liegen, ist der Geschwindigkeitsunterschied nicht so stark zu bemerken, da auf diese Daten in der Regel nicht so oft zugegriffen werden muss. Im Folgenden ist die neue Speicheraufteilung dargestellt.
Aufmerksame Leser werden nun festgestellt haben, dass in diesem Projekt aber 128kByte RAM externer Speicher eingebaut wurden jedoch nur 64kByte adressiert werden können. Es fehlen also 64kByte Speicher die ungenutzt bleiben! Bleibt die Frage zu klären wie diese trotzdem genutzt werden können? Um auf die restlichen 64kByte zugreifen zu können, ist ein zusätzliches Adressbit notwendig. Dieses existiert auch und ist am Mikrocontroller verfügbar. Da aber bei der ATmega-Serie nur ein Adressraum von 64kByte für das RAM vorgesehen ist, muss die Steuerung des zusätzlichen Adressbits von Hand vorgenommen werden. Der avr-gcc kann von sich aus dieses Adressbit nicht verwalten. Wie kann dieser Speicher aber trotzdem genutzt werden? Hierzu wurde eine extra Funktion ( in ext_mem.h ab Revision 98 ) geschrieben, mit der man jedes Byte einzeln ansprechen kann. Leider muss sich der Programmierer, der diesen zusätzlichen Speicher nutzen möchte, selbst darum kümmern, wie er die Daten ablegen und organisieren will. Er muss sich diesen Speicher wie ein Array mit 64kByte Größe vorstellen. Mit geringem Programmieraufwand kann man auf diese Art und Weise diesen zusätzlichen Speicher für große Datenmengen nutzen. Zum Beispiel das Puffern von vielen Daten wie sie bei Multimediastreams vorkommen. Die Verwaltung obliegt dem Programmierer.
Trennung von System-, Treiber-funktionen und Anwendungen
Zum wichtigsten Merkmal für eine Entwicklungsumgebung gehört sicherlich die Trennung der Software in einzelne Bereiche. Da das Ziel dieses Projektes die Entwicklung einer Entwicklungsumgebung ist, wenden wir uns den wichtigsten Funktionen der Hardware und deren Schnittstellen zum Entwickler von Applikationen zu. Wichtigstes Merkmal dieser Schnittstellen ist es, eine saubere Trennung von Applikationen und Hardware und deren Treiber zu erhalten, wie im folgenden Bild zu sehen ist.
Somit teilen sich die Bereiche in zwei Hauptbereiche auf. Zum ersten Systemtreiber die nicht oder nur indirekt auf die Hardware zugreifen und zum Zweiten die Hardwaretreiber, die direkt auf die Hardware zugreifen aber für ihre Verwaltung auch Systemtreiber nutzen. Folgendes Organigramm verdeutlicht die Zuordnungen.
Diese Treiber finden sich in eigenen Verzeichnissen wieder, um sie einander zuordnen zu können. Wie vorhin schon angemerkt können sich die System- und Hardwaretreiber auch gegenseitig benutzen. So benutzt zum Beispiel der USART-Treiber noch die FIFO-Treiber aus dem Systemtreiberbereich, um seine Sende- und Empfangpuffer zu verwalten. Der große Vorteil dieser Struktur ist die Übersichtlichkeit, einfachere Verwaltung, Portierbarkeit, Standardisierung und das Änderungen an Treibern global für alle Programme, die diese Treiber benutzen, sofort aktiv werden. Voraussetzung ist, das die Applikationen diese Treiber benutzen und keine Insellösungen implementieren.
Bereitstellung der Grundfunktionen
Um ein funktionierendes Mikrocontrollersystem zu erhalten sind bestimmte Grundfunktionen notwendig. Zudem macht die Zielsetzung der Entwicklung eines TCP/IP-Stack auch bestimmte Funktionen notwendig. Dies sind zum Beispiel Timerfunktionen und Counterfunktionen um den TCP/IP Stack realisieren zu können, oder I/O Funktionen, um mit der Außenwelt zu kommunizieren. Im Folgendem werden die Grundfunktionen vorgstellt. Die meisten Funktionen greifen direkt auf die Hardware zu. Es ist daher wichtig Applikationen von den Hardwarezugriffen zu trennen, um einen reibungslosen Ablauf der Applikationen zu garantieren. Es wäre schlecht, wenn eine Applikation auf eine Hardwarefunktion zugreift, während ein Treiber in einer Interruptroutine dies auch versucht. Die Treiber sollten daher Mechanismen beinhalten um solche Konflikte zu vermeinden, dies kann über Lock-Mechanismen realisiert werden, die, um effektiv zu sein möglichst einfach aber wirkungsvoll gestaltet werden sollten. In den folgenden Kapiteln wird ein Überblick über die wichtigsten Funktionen gegeben. Diese Funktionen bilden den Grundstock um Applikationen entwickeln und schreiben zu können. Grundidee der Trennung von Applikationen und Treibern ist auch die Portierbarkeit dieses System. Auf anderen Systemen die diese Entwicklungsumgebung benutzen lassen sich so einfach auch die Applikationen portieren. Es müssen nur die Treiber an die vorhandene Hardware angepasst werden. Es ist dann im Idealfall nur noch ein Neuübersetzen des Programmcodes notwendig. Auf diese Art und Weise müssen die Applikationen nur noch auf einem System entwickelt werden und können dann fast ohne Anpassung auf anderen Systemen verwendet werden.
TIMER
Wie in den Kapiteln zur Hardware schon erläutert, besitzt der ATmega einige Timer mit verschiedenen zeitlichen Auflösungen. Das Grundprinzip ist bei allen aber dasselbe: Es wird ein 8 oder 16 Bit Register vorgeladen - der sogenannte Counter - und bis zu einen bestimmten Punkt inkrementiert oder dekrementiert. Der Takt, der den Counter verändert, kann voreingestellt werden und wird aus dem Systemtakt erzeugt. Es sind so verschiedene Vorteiler möglich von 1/1 bis zu 1/1024 des Systemtaktes. Ein Event wird durch einen Over- oder Underflow des Counterregisters ausgelöst. Die Auswertung kann dann durch einen entsprechenden Interrupt oder durch die Abfrage eines Statusregisters des Timers erfolgen. Zudem gibt es noch verschiedene Modi, mit denen der Counter betrieben werden kann. So kann der Start des Counters zum Beispiel durch ein anderes Ereignis ausgelöst werden oder vom Programm selber. Es stehen aber noch andere Funktionen bereit, die die Arbeit mit dem Timer erleichtern. Der für uns interessante ist der CTC Modus (Clear Timer/Counter on Compare Match). Er erlaubt das automatische Nachladen des Counter mit einem Wert beim Erreichen des Counters von 0 aus einem anderen Register. Das Register lädt so automatisch seinen Startwert und zählt nach einem Over- oder Underflow nicht stur weiter oder bleibt stehen wie bei anderen Modi. Dieser Modus erzeugt so ohne den Eingriff des Programms in genau definierten zeitlichen Abständen Events wie den CTC Interrupt. Damit kann eine sehr genaue Zeitquelle für andere Programme erzeugt werden.
Taktquelle der Real Time Clock
Viele Funktionen von Programmen erfordern eine genaue Zeitquelle um Steueraufgaben oder Programmabläufe zu erledigen. Das gewährleistet nur eine ausgeklügelte und genaue Zeitquelle. Die Real Time Clock soll diese Aufgaben in unseren fall übernehmen. Normalerweise wird eine Real Time Clock meist in einer extra darauf spezialisierten Hardware aufgebaut. Da das Board aber bewusst einfach gehalten werden soll, wird diese in Software nachgebildet, wobei uns die internen Timer des Mikrocontrollers dabei unterstützen. An diese RTC werden jedoch verschiedene Anforderungen gestellt wie:
- Genauigkeit
- möglichst hohe zeitliche Auflösung und
- Flexibilität
Die Genauigkeit ist in erster Linie von der verwendeten Taktquelle und deren Genauigkeit abhängig. Diese erfordert deshalb einen sorgfältigen Aufbau der Hardware wie sie schon in den Kapitel zuvor beschrieben wurde. Bleiben nur die möglichst hohe zeitliche Auflösung und die Flexibilität. Bei der zeitlichen Auflösung muss indes abgewogen werden, ob eine hohe Auflösung wichtig ist, oder niedrige Rechnenlast. Denn eine hohe Auflösung treibt die Rechenlast in die Höhe, da jeder Event des Timers ja bearbeitet werden muss, wohingegen eine niedrige Auflösung weniger Rechenlast erzeugt, aber für bestimmte Funktionen nicht oder nur eingeschränkt brauchbar ist. Als Kompromiss wird eine Auflösung von 1/100 Sekunde gewählt, da dies für die meisten Anwendungen noch praktikabel ist und keine zu hohe Rechnenlast erzeugt. Für genauere Anwendungen kann seperat kurzfristig einer der anderen Timer genutzt werden. Benutzt wird der Timer0 mit folgendem C-Codebeispiel, die Erläuterung erfolgt danach:
unsigned char i;
#define Hz 100
// Alle Callbackeinträge löschen
for ( i = 0 ; i < MAX_TIMER0_CALLBACKS ; i++ ) TIMER0_CallbackFunc[i] = NULL;
// Timer0 einstellungen setzen
TCCR0A |= ( 1<<WGM01 ); // CTC mode setzen
OCR0A = ( 16000000 / ( 8 * Hz ) ); // Reload für 100Hz bei 16MHz setzen
TCCR0B |= ( 0<<CS02 ) | ( 1<<CS01 ) | ( 0<<CS00 ); // Prescaler 1024
TIMSK0 |= ( 1<<OCIE0A ); // Compare Match A Interupt freigegben
Als erstes werden die Callbackeinträge gelöscht um einen definierten Ausgangszustand zu erhalten. Danach wird TimerCounter0 in den CTC Modus versetzt. Der TimerCounter lädt so seinen Startwert beim Erreichen von 0 neu nach und löst ein Event aus. In der zweiten Zeile wird der Reloadwert berechnet und in das Reloadregister geschrieben aus dem er vom Counter0 nachgeladen wird. Die Berechnung des Reloadwert gestaltet sich nicht ganz einfach, es ist der passende rechnerische Reloadwert mit entsprechenden Prescaler zu finden der einen ganzzahligen Wert ergibt, da das Register 16-Bit breit ist und nur ganze Zahlen verarbeiten kann. Bei ungeraden Werten rundet der Compiler, die RTC wird ungenau und driftet zeitlich. Die Berechnung des Reloadwertes erfolgt nach folgender Formel:
Der Fehler in Prozent läßt sich einfach berechnen mit der Folgenden Formel.
Folgende Tabelle verdeutlicht dies für einen Systemtakt von 16MHz.
| Prescaler | Reloadwert | Abweichung |
| 1 | 160000 | 0% |
| 8 | 20000 | 0% |
| 64 | 2500 | 0% |
| 256 | 625 | 0% |
| 1024 | 156,25 | -0.16% |
Zu sehen ist das sich nur die Prescaler 8,64 und 256 benutzen lassen. Der Prescaler 1 ergibt zwar einen geraden Wert, aber paßt leider nicht in ein 16-Bit breites Register. In diesen Fal liegt die Benutzung des Prescaler 8 nahe, er besitzt eine sehr hohe Auflösung, somit lassen sich bei ungenauigkeit noch kleine Änderungen vornehmen die die drift nur leicht beeinflussen, während bei den größeren Prescaler kleine Änderungen mehr Auswirkung auf die Drift haben, da hier die Prozentuale abweichung für die Werteänderung von +/-1 größer ist.
Jetzt erfolgt die Aktivierung des Counter und die Einstellung des Presaler. Danach erfolgt die Interruptfreigabe für den CTC Mode der bei jeden durchlauf des Counter einen Interrupt erzeugt. Jetzt läuft die Grundlegene Funktion der RTC. Die Routine die bei jeden Interrupt abgearbeitet wird sollte möglichst flexibel sein, deshalb ist sie als CallBack-funktion angelegt.
Callback Funktionen
Die Interruptroutine ist an sich sehr einfach gehalten. Sie durchsucht einfach eine Tabelle mit Einträgen welche die Adresse der Callbackfunktionen die angesprungen werden soll enthält. Einträge mit den Wert NULL werden ignoriert, da sie keine gültige Adresse darstellt. Dabei werden Callbackfunktionen als Adresseinträge auf eine andere Funktion die vorher von einem anderen Programm hinterlegt worden ist bezeichnet. Dies hat den Vorteil das gerade bei Programmen die im Flash abgelegt sind, wie zum Beispiel Interruptroutinen flexibel bleiben und man zur Laufzeit die ausgeführte Funktion änderen kann. Dieses Prinzip kommt häufig zum Einsatz um zum Beispiel zur Laufzeit Änderungen an Funktionen vorzunehmen oder den Programmierer eine Einfache möglichkeit zum ändern zu geben
unsigned char i; for ( i = 0 ; i < MAX_TIMER0_CALLBACKS ; i++ ) if ( TIMER0_CallbackFunc[i] != NULL ) TIMER0_CallbackFunc[i]();
Um Einträge zu hinterlegen werden extra Funktionen bereit gestellt, da hier einige Sache zu beachten sind. Es muss in den Augenblick in der eine Adresse auf eine Funktion hinterlegt wird, dafür gesorgt werden das keine zugriffe auf die Tabelle geschehen, das dies den Programmauflauf stören könnte, und in vielen Fällen zu abstürzen führt. Verhindern kann man dies durch sperren des TimerInterrupts, dabei wird der eigentliche TimerCounter nicht angehalten. Tritt während dieser Zeit ein Interrupt auf, so wird dieser nicht verworfen, sondern in einen Register festgehalten. Ist der Interrupt für den Timer wieder frei gegeben, wird dieser sofort nach der Freigabe ausgeführt. Hierbei ist zu beachten, das der Interrupt nicht zu lange gesperrt wird, also max 1/100 Sekunde, da sich nicht mehrere Interrupts festhalten lassen.
void timer0_init( unsigned char Hz ); void timer0_stop(void); void timer0_free(void); unsigned char timer0_RegisterCallbackFunction( TIMER0_CALLBACK_FUNC pFunc ); unsigned char timer0_RemoveCallbackFunction( TIMER0_CALLBACK_FUNC pFunc );
Clock / RTC
Kommen wir zur eigentlichen RTC. Da jetzt eine relativ genaue Zeitquelle vorhanden ist, beschäftigen wir uns mit den der eigentlichen Routine. Das programmieren dieser gestaltet sich recht einfach. Es werden nur eine Handvoll Variablen benutzt, die die 1/100 Sekunden,Sekunden,Minuten und Stunden zählen. Die Variablen werden global hinterlegt, so das andere Programme diese nutzen können. Bleibt nur diese Routine in die von uns bereits erstellte Timerfunktion per Callback zu hinterlegen damit sie zyklisch aufgerufen werden kann. Die CLOCK/RTC stellt aber noch weitere Funktionen zur Verfügung, so können sich andere Programme hier als wiederrum als Callbackfunktionen hinterlegen. Hier kann zusätzlich unterschieden werden in welchen abständen diese Funktionen aufgerufen werden sollen, mögliche Werte sind: jede 1/100 Sekunde, jede Sekunde ,jede Minute oder jede Stunde. Das erleichtet viele Aufgaben für den Programmierer von Applikationen ernorm.
Counter
Um den Funktionsumfang noch zu erweitern, gibt es die möglichkeit Counter zu benutzten, die von der RTC zur verfügung gestellt werden. Counter sind Variablen der RTC die nach aktivierung durch ein Programm in bestimmten Zeitintervallen und einen Startwert die das Programm vorgibt gegen 0 zählen. So können feste Zeitinterwalle hinterlegt werden, die von der RTC automatisch per Interrupt verringert werden. Hier kann das Programm, das eine bestimmte Zeit warten soll oder muss, einen Counter mit Zeit und Interval X angelegen. So angelegt, kann es ein anderes Ereignis oder Problem bearbeiten und mit hilfe des Counters zwischendurch abfragen ob die Zeit abgelaufen ist und so einen Timeout realisieren. Wie so eine Funktion implementiert werden kann zeigt das folgende Codebeispiel, welche aber nur die vorgehensweise aufzeigen soll.
unsigned char timer, retval;
#define TIMEOUT 1000
timer = CLOCK_RegisterCoundowntimer();
CLOCK_SetCountdownTimer( timer , TIMEOUT, MSEC );
while ( 1 )
{
if ( Get_Key() && ( CLOCK_GetCountdownTimer( timer ) != 0 ) )
{
CLOCK_ReleaseCountdownTimer( timer );
retval = Okay;
break;
}
if ( CLOCK_GetCountdownTimer( timer ) == 0 )
{
CLOCK_ReleaseCountdownTimer( timer );
retval = Error;
break;
}
}
return( retval );
Es wird ein freier Counter abgefragt. Die Funktion gibt die Nummer eines Freien Counters zurück der in timer gespeichert wird. Danach wird der Counter mit den Werten vorgeladen. Anschließend wird die Whileschleife durchlaufen die kontrolliert ob der Counter schon bei 0 ist oder ein anderes Ereigniss geschehen ist vor ablauf des Counters. Im folgendem sind die Funktionen aufgelistet die zur Verfügung stehen, eine genaue Dokumentation findet sich in der Sourcecode-Dokumentation.
void CLOCK_init( void ); void CLOCK_tick( void ); unsigned char CLOCK_RegisterCallbackFunction( CLOCK_CALLBACK_FUNC pFunc, unsigned char Resolution); unsigned char CLOCK_RemoveCallbackFunction( CLOCK_CALLBACK_FUNC pFunc ); unsigned char CLOCK_RegisterCoundowntimer( void ); void CLOCK_SetCountdownTimer( unsigned char counter, unsigned int value, unsigned char Resolution ); unsigned int CLOCK_GetCountdownTimer( unsigned char counter ); void CLOCK_ReleaseCountdownTimer( unsigned char counter ); void CLOCK_GetTime( unsigned char * Time ); void CLOCK_delay(unsigned int us);
FIFO und LIFO
Eine der schwierigsten Aufgaben für den Programmierer ist es Ein- und Aus-gaben zu Puffern um effizent arbeiten zu können und um auch das quasi gleichzeitige ausführen von Programmen zu vereinfachen. Um Daten effizent Puffern zu können wird gerne ein Ringpuffer nach den FIFO oder LIFO-Prinzip benutzt. Hier können Daten abgelegt und gesammelt werden ohne diese gleich verarbeiten zu müssen. Somit kann die eigentliche Verarbeitung einen schritt später erfolgen wenn genug Daten im Puffer sind oder der Puffer voll ist. Die Verwaltung des Puffer übernimmt dabei der Treiber. Ihm übergibt man nur die Adresses eines Buffers im RAM und deren Größe. Als Rückgfabewert erhält man eine Handlenummer über diesen man den Puffer abfragen und befüllen kann, ohne sich über den eigentlichen Aufbau und Verwaltung des Puffer kümmern zu müssen. Genauso kann man diesen Puffer auch wieder Freigeben. Im Angefügten Sourcecode kann man sich einen genauen Überblick über die Funktionen und Arbeitsweise verschaffen. Ein blick in die Dokumentation per Doxygen ist auch interessant da hier alle Informationen enthalten sind.
Funktionsüberlick für fifo.c/.h:
unsigned int Get_FIFO( unsigned char * buffer, unsigned int bufferlenght ); unsigned char Free_FIFO ( unsigned int FIFO ); unsigned int Get_Bytes_in_FIFO( unsigned int FIFO ); unsigned char Put_Byte_in_FIFO( unsigned int FIFO, unsigned char Byte ); unsigned int Get_Block_from_FIFO( unsigned int FIFO, unsigned int bufferlenght, unsigned char * buffer ); unsigned char Get_Byte_from_FIFO( unsigned int FIFO ); unsigned int Flush_FIFO( unsigned int FIFO ); unsigned int Get_FIFOsize( FIFO ); unsigned int Get_FIFOrestsize( FIFO ); unsigned int Get_FIFO_to_FIFO( unsigned int Src_FIFO, unsigned int bufferlenght, unsigned int Dest_FIFO );
UART / RS232
Die einfachste möglichkeit mit der Außenwelt zu interagieren ist in die kommunikation über die Serielle Schnittstelle. Die RS232 ist eine international standardisierte Schnittstelle, die jeder Computer heute unterstützt oder zumindest nachgerüstet werden kann. Der hier vorgestellte Mikrocontroller unterstützt diese Schnittstelle in Hardware. Es muss nur der Modus und die Bitrate eingestellt werden und das zu übertragene Byte der Hardware übergegeben werden. Die Hardware kümmert sich um den Versand und meldet den auch den erfolgreichen Versand. Der Empfang geht genauso vonstatten, er wird per Register und/oder per Interrupt der Empfang signalisiert, anschliessend muss nur das Byte aus einem Register ausgelesen werden und entsprechend ausgewertet werden.
Die Funktionen für den Nutzer der RS232 unterteilen sich in 4 Teile. Initailisierung, Senden, Empfangen und Status. Diese Vorgänge sollten für den Programmierer transparent sein. Er interagiert mit der Hardware nur über diese Treiber. Die eigentliche Arbeit erledigt der Treiber im Hintergrund. Wobei sich das Senden und Empfangen in mehrere Routinen ausspliten, da das Senden und Empfangen im Interruptbetrieb geschieht um mehr Rechenzeit für den Programmierer zu bekommen und die Effizenz zu steigern. Um das Senden und Empfangen im Interruptbetrieb zu ermöglich werden Puffer (Ringpuffer nach dem FIFO-Prinzip) benötigt die zu sendende oder empfangende Bytefolgen zwischenspeichern. Da der Treiber im Hintergrund arbeiten soll wäre ohne solch ein Puffer kein Vorteil zu erreichen, da trotzdem jedes Byte das gesendet oder empfangen werden soll abgefragt werden müsste. Mit ein Hauptgrund für den Interruptbetrieb ist natürlich auch der wegfall der Wartezeit um auf den Versand oder Empfang des nächsten Byte zu warten, da die serielle Übertragung relativ zur Rechengeschwindigkeit sehr langsam ist. Dadurch gestaltet sich die Verwaltung für den Treiber aber aufwendiger. Der Aufwand sich lohnt aber, da sich so gleich nach dem übergeben einer Bytefolge an den Puffer weiterarbeiten lässt im Programm, während im Hintergrund der Puffer gesendet wird. Die Funktionen für den Benutzer gestalten sich recht einfach und können in der Sourcecode-Dokumentaion nachgelesen werden.
Funktionen der Uart (uart.c/h)
void UART_init( void ); void UART_Send_Byte( unsigned char Byte ); unsigned char UART_Get_Byte( void ); unsigned int UART_Get_Bytes_in_Buffer( void );
SPI
Wie in den Kapitel Hardware/SPI schon beschrieben dient der SPI-Bus zur Anbindung von externer Hardware. In diesen Fall zur anbindung des Ethernetbaustein. Um die eine schnelle kommunikation mit den ENC28j60 zu realisieren sind schnelle Übertragungsroutinen notwendig, sowohl Byte als auch Blockweise. Das SPI-Interface des ATmega unterstützt die Programmierung mit einer vielzahl von Register. Es muss nur die Schnittestelle, Art der Übertragung und die Geschwindigkeit eingestellt werden. Das Interface unterstützt den Poll und Interruptberieb des SPI-Interface. Als Übertragungstakt ist maximal 1/2 des Systemtaktes möglich welches bei einen Systemtakt von 16MHz einen SPI-Takt von 8MHz erlaubt. Diese hohe Geschwindigkeit schließt leider die Bearbeitung per Interruptbetrieb aus, da hier die Bearbeitung nicht schnell genug erfolgen würde. Denn es stehen pro übertragenem Byte maximal 16 Takte zur Verfügung um das Nächste Byte für die Übertragung vorzubereiten. Damit wäre eine Interruptroutine viel zu langsam, da bei jeden Interrupt erst alle Register gesichert werden müssten und erst dann das Senden vorbereitet werden kann. Im Pollbetrieb kann die Bearbeitung wesentlich schneller erfolgen. Nachteil ist das leider keine Zeit für andere Aufgaben übrig bleibt. Die Routinen zum Übertragen von ganzen Bytefolgen sind daher stark optimiert, gut zu sehen in der Dokumentation des Sourcecode. Nicht zu vergessen ist die Selektion des Slaves vor jeder Übertragung mit Hilfe des Chipselect-Signals.
Byteübertragung
Die Vorbeireitung der SPI-Schnittstelle gestaltet sich einfach nach folgendem Beispiel.
SPCR0 |= 1<<MSTR0 | 1<<SPE0; // Master mode SPSR0 |= 1<<SPI2X0; // Voller SPI Takt ( 1/2 Systemtakt )
Die SPI-Schnittstelle arbeitet Bidirektional, es wird wenn ein Byte gesendet wird auch immer im Takt ein Byte vom Slave empfangen. Das bedeutet auch wenn wir nur Daten vom Slave empfangen möchten, müssen Dummybytes gesendet werden weil der Master den Takt vorgibt, während wenn wir Daten zum Salve senden dieser auch immer Dummybytes an den Master zurück schickt. Die Übertragung eines einzelnen Byte mit dem Empfang des Byte welches vom Slave empfangen wird sieht dann folgendermassen aus.
unsigned char SPI_ReadWrite( unsigned char Data )
{
// daten senden
SPDR0 = Data;
// auf fertig warten
while(!(SPSR0 & (1<<SPIF0)));
// empfangende daten einlesen
Data = SPDR0;
// daten zurueckgeben
return( Data );
}
Es ist aber nicht zu vergessen vor jeder Übertragung den Slave mit hilfe der SlaveSelect Leitung zu selektieren.
Blockübertragung
Während die Übertragung von einzelen Bytes noch recht unkompliziert von statten geht wird es bei ganzen Blöcken schon schwerer. Das Problen ist nicht die Übertragung der Bytes an sich, sondern das effektive übertragen der Bytes hintereinander. Da der SPI-Bus mit den halben Systemtakt betrieben wird ergeben sich Probleme bei der schnellen Bereitstellung des nächsten zu übertragenen Bytes. Es stört zwar nicht wenn zwischen den einzelen Bytes pausen sind, doch verringert dies den effektiven Durchsatz der Datenübertagung. Hier ist ein Weg zu finden der dieses Problem minimiert. Die beste möglichkeit ist das vorladen der Register die an der Übertragung beteilig sind. Wichtigstes Register ist das Senderegister. Hier ist es sinnvoll bereits während das vorherige Byte noch übertragen wird ein Register in der CPU vorzuladen, um dann in einer Schleife nur noch die Beendung der Übertragung des letzten Byte abzuwarten und sofort das nächste zu senden. Während der Übertragung bleiben dann 16 Taktzyklen Zeit das nächste Byte auf aus dem Puffer zu lesen und die Register vorzubereiten. Für das senden sieht das wie folgt aus.
void SPI_FastMem2Write( unsigned char * buffer, unsigned int Datalenght )
{
unsigned int Counter = 0;
unsigned char data;
// erten Wert senden
SPDR0 = buffer[ Counter++ ];
while( Counter < Datalenght )
{
// Wert schon mal in Register holen, schneller, da der Wert jetzt in einem
// Register steht und nicht mehr aus dem RAM geholt wird,
// nachdem das senden des vorherigen Wertes fertig ist,
data = buffer[ Counter++ ];
// warten auf fertig
while(!(SPSR0 & (1<<SPIF0)));
// Wert aus Register senden
SPDR0 = data;
}
while(!(SPSR0 & (1<<SPIF0)));
return;
}
Interessant ist das empfangen von Byteblocken. Hier sind gute Hardwarekenntnisse Vorraussetzung um eine schnelle Empfangsroutine zu implementieren. Während beim Senden immer gewartet werden muss ob das Senderegister tatsächlich leer ist, kann beim Empfangen bereits wieder ein Dummybyte gesendet werden und dann erst das Register mit dem empfangenden Byte ausgelesen werden, das dieses Register intern erst beschrieben wird wenn das letzt Bit den letzten Bytes komplett empfamgen worden ist. Intern wird beim empfang ein Pufferregister verwendet, so das das auszulesende Register in ruhe ausgelesen werden kann. Die Zeitspanne dafür beträgt die schon bekannten 16 Taktzyklen. Somit ergeben sich maximal 2 Takte Zeitverlust am SPI-Bus, die als kleine Pause wahrgenommen werden. Ein guter Beitrag in einem Forum zu diesen Problem gab es bereits schon 2006, welches von mehreren Forumsmitglieden und mir geführt worden ist. Dort ist auch ein Link hinterlegt der eine sehr gute Timinganalyse beinhaltet. Zu beachten ist das diese Optimierung nicht auf jeden Controller des Typ ATmega funktioniert, da Atmel hier sich in der Dokumentation nicht klar ausdrückt.
void SPI_FastRead2Mem( unsigned char * buffer, unsigned int Datalenght )
{
unsigned int Counter = 0;
unsigned char data;
// dummywrite
SPDR0 = 0x00;
while( Counter < Datalenght )
{
// warten auf fertig
while(!(SPSR0 & (1<<SPIF0)));
/*
// einfache Optimierung
// Daten einlesen
data = SPDR;
// dummy-write
SPDR = 0x00;
*/
// bessere Optimierung, aber nicht auf jeden controller
// dummy-write
SPDR0 = 0x00;
// Daten einlesen
data = SPDR0;
// speichern
buffer[ Counter++ ] = data;
}
while(!(SPSR0 & (1<<SPIF0)));
return;
}
Der ENC28j60
Wie in dem Hardwarekapitel zuvor schon ausführlich erklärt, erhält der ENC28j60 seine Anbindung per SPI zum Mikrocontroller. Die Grundlagen hierfür sind bereits geschaffen worden, sogar mit ausreichender geschwindigkeit. Kommen wir nun zum ENC28j60 an sich. Dieser wird komplett über SPI angesprochen und gesteuert. Dabei wird zwischen Zugriffen auf Register und Speicher unterschieden. Über die Register läßt sich der Ethernet-IC Steuern und Voreinstellen und über die Speicherzugriffe Daten empfangen und versenden. Als Register steht ein ganzer Satz an Einstellungsmöglichkeiten zur Verfügung die sich über die Art der Verbindung, Steuerung der beiden LED, Statusabfragen, Rückmeldungen und Festlegung der Speicheraufteilung erstrecken. Über diese Register wird auch die MAC-Adresse festgelegt, da der Ethnernetcontroller hierfür keinen dauerhaften Speicherplatz vorsieht. Die Register verteilen sich über vier Banke, die nach Funktionen gruppiert sind und müssen beim verändern von Register vorher eingestellt werden. Die Treiber für den ENC28j60 stammen vom Mircochip und wurden auf den AVR-GCC angepasst.
Interruptbetrieb des ENJ28j60
Jetzt kann der Ethernetcontroller seinen Betrieb aufnehmen. Um die Rechnenlast gering zu halten und nicht immer den Status nach einem empfangendem Frame abfragen zu müssen bietet der Controller die möglichkeit bei Empfang eines Frames einen Interrupt auszulösen. Dazu ist eine extra Leitung vom Ethernetcontroller zum Mikrocontroller geführt die durch eine negative Flanke einen Interrupt (externer Interrupt) auslöst. Nach einer signalisierung kann der Mikrocntroller dann im Interrupt ein Frame abfragen, speichern und zur weiteren Verarbeitung den Netzwerkstack zuführen.
Der Netzwerkstack
Ein Computernetzwerk besteht laut Definition aus einen zusammenschluss von Datenverarbeitenden Geräten die über eine Schnittstelle mit Hilfe von speziellen Protokollen kommunizieren. Dabei orientiert sich die kommunikation an das ISO/OSI-Schichtmodell. Das ISO/OSI-Schichtmodell ist in sieben Schichten aufgeteilt. Dabei baut jede Schicht auf die unmittelbar dadrunterliegende Schicht auf. Jede Schicht kümmert sich wiederrum um ein spezielles Problem und sichert auf dieser Schicht die Übertragung. Die Schichten teilen sich wie folgt auf, dabei wird auch die Unterteilung der Probleme jeder Schicht deutlich.
- 1. Bitübertragungsschicht (Physical Layer)
- 2. Sicherungsschicht (Datalink Layer)
- 3. Vermittlungsschicht (Network layer)
- 4. Transportschicht (Transport Layer)
- 5. Sitzungsschicht (Session Layer)
- 6. Darstellungsschicht (Presentation Layer)
- 7. Anwendungsschicht (Application Layer)
Jede Schicht defeniert dabei eigene Protokolle oder Standarts der Übertragung.
- Schicht 1: Die Bitübertragungschicht steuert die Übertragung von Informationen auf physikalischer Ebene, wie zum Beispiel einzelne Bit oder Byte auf einem Medium übertragen werden und interpretiert werden.
- Schicht 2: Die Sicherungsschicht beschreibt die sichere und fehlerfreie Übertragung der Schicht 1. Sie übernimmt den den Zugriff auf das Übertragungsmedium und führt einfache Sicherungen wie Prüfsummen durch.
- Schicht 3: Die Vermittlungschicht sorgt für die Vermittlung und Weiterleitung von Datenpacketen. Ein bekannter vertreter ist IP.
- Schicht 4: Die Transportschicht organisiert die Segmentierung von Daten und stellt die unterste Ebene der vollständigen Ende-zu-Ende-Kommunikation dar. Es ist die erste Schicht die unabhängigt vom darunter liegendem Netzwerk arbeitet.
- Schicht 5: Die Sitzungsschicht organisiert und synchronisiert den Datenaustauscht zwischen den Teilnehmern. Sie kümmert sich um die richtige Reinfolge der Daten und behandelt Fehler bei der Datenübertragung und steuert die logische Verbindung zweiter oder mehrerer Teilnehmer.
- Schicht 6: Die Darstellungsschicht bringt die systemabhängige Darstellung von Daten in eine systemunabhängige Darstellung und umgekehrt, so das Daten syntaktisch korrekt zwischen verschiedenen Systemen ausgetauscht werden können.
- Schicht 7: Die Anwendungsschicht ist die Verarbeitungsschicht und stellt die höchste Schicht im ISO/OSI-Modell dar. Es stellt der Anwendung verschiedene Dienste zur Verfügung wie Email, Dateiübertragung u.s.w. .
Die Schichten 1 und 2 sind die Schichten die sehr starkt von dem verwendeten Übertragungsmedium abhängen, da jedes Medium seine eigenen Eigenschaften hat und deshalb sehr Hardwarenah implementiert ist und je nach Übertragungsmedium eigene mechanismen zur Sicherung mitbringt. Bekanntester Vertreter sind Ethernet, Tokenring, PPP oder X.25. Ab Schicht 3 findet die Übertragungmediumunabhängige Übertragung statt, bekanntester vertreter ist IP. In Schicht 4 bis 7 findet die Kommunikation mit hilfe von virtuellen oder logischen Verbindungen statt, bekannte Vertreter hier sind TCP oder UDP, welches die häufigsten höheren verwendeten IP-Protokolle sind.
Die Schichten 1 und teilweise 2 werden in diesem Projekt vom Ethernetcontroller ENC28j60 bewältigt. Er kümmert sich um den Zugriff auf das Medium und die Fehlerfreie Übertragung. Dabei berechnet und prüft dieser die Checksumme automatisch und überträgt Daten neu oder verwirft sie beim empfang. Die restlichen Schichten des ISO/OSI-Schichtmodels werden in Software bewältigt. Dabei entsteht ein Netzwerkstack der sich um jede Schicht kümmert und die Daten entsprechend der Schicht verarbeitet und aufbereitet um sie dann an die nächste Schicht weiterzureichen, so das diese dann der Anwendung zur Verfügung stehen. In den folgenden Kapitel wird ein kleiner ausflug in die einzelnen Schichten und Protokolle unternommen. Dabei wird als Übertragungsmedium Ethernet nach IEEE802.3 benutzt, worauf dieser Netzwerkstack aufsetzt. Die vorherige Abbildung zeigt die Aufteilung der Schichten in Verbindung mit Ethernet.
Ethernet
Bei Ethernet handelt es sich um einen Standard für Kabelgebundene Packetorientierte Datenübertragung. Standardisiert ist dies in der IEEE802.3 ( 'I'nstitute of 'E'lectrical and 'E'lectronics 'E'nginieers(IEEE)) und dient zur lokalen Vernetzung in einem LAN ('L'ocal 'A'rea 'N'etwork) innerhalb eines Gebäudes. Der IEEE802.3 Standard ist in Schicht 1 und 2 angesiedelt. Hier werden die Art der Übertragung und die Zugriffmechanismen wie CSMA/CD beschrieben nach der jede nach dem IEEE802.3 arbeitende Netzwerkkarte auf das Kabelgebundene Medium zugreift. Desweiteren legt dieser Standard auch das Protokoll von Datenpacketen für Ethernet fest, in folgendem Ethernet-Packet genannt. Das Ethernet-Packet hat folgenden bestimmten Aufbau.
Inhalt eines Ethernet-Packet sind Präambel, Start-Frame-Delimiter (SFD), Source, Destination, Typ-Field, Payload und CRC. Die Abbildung(x.y) veranschaulicht dies. Das Präambel sorgt für die synchronisierung des Empfängers mit dem Sender, dabei werden abwechselnt 0 und 1 gesendet ( 10101010b, LSB zuerst) gefolgt von den Start-Frame-Delimiter (10101011b, LSB zuerst) der den Anfang der Daten des Ethernet-Packetes markiert. Diese Daten des Ethernet-Packetes sind das eigentliche Ethernet-Frame. Es besteht aus den Feldern Source, Destination, Typ-Field, Payload (evtl. PAD) und CRC. Die Source und Destination Felder bestehen aus der 48-Bit (6 Byte) breiten MAC-Adresse (MAC, 'M'edia 'A'ccess 'C'ontrol) die den Sender und Empfänger eindeutig adressieren. Bestimmte MAC-Adressen haben dabei festgelegte Aufgaben wie zum Beispiel die Broadcastadresse, die als Ziel alle angeschlossenden Empfänger hat. Dabei werden alle 48Bit der Empfängeradresse auf 1 gesetzt. Es gibt aber noch spezielle Adressbits, auf die hier aber nicht näher eingegangen werden soll. Gefolgt werden diese Felder von dem 16Bit breiten (2 Byte) Typ-Field. Diese Feld legt den Typ des Payloads fest der die Nutzerdaten für das nächst höhere Protokoll enthält. Gängige Typ-Field Kennungen sind:
- 0x0800 IP Internet Protocol Version 4 (IPv4)
- 0x0806 Address Resolution Protocol (ARP)
- 0x86DD IP Internet Protocol Version 6 (IPv6)
Im danach folgenden Payload befinden sich die Nutzerdaten. Dieses Feld kann bis zu 1500Byte (maximale Framelänge 1518Byte - ( Source (6Byte) + Destination(6Byte) + Type-Field (2Byte) + CRC (4Byte) ) = 1500 ) betragen. Da der IEEE802.3 Standard eine minimale Framelänge von 64 vorsieht, wird der nicht genutzte Payload-bereich aufgefüllt wenn dieser zu klein ist für die minimale Framelänge. Dieses Feld wird PAD-Field genannt und wird mit 0x00 aufgefüllt. Danch folgt das CRC-Feld. Hier wird eine Checksumme für das gesammte gesendete Frame angehangen, welches dem Empfänger die möglichkeit zur Prüfung auf eine Fehlerfreie Übertragung ermöglicht.
IPv4
Das Internet Protokoll Version 4 [RFC 791] stellt die erste Schicht im OSI-Schichtmodel dar die Hardwareunabhängig arbeitet und routingfähig ist. Es befindet sich in Schicht 3 des ISO/OSI-Schichtmodels. Es dient zur Vermittlung und Adressierung im Netzwerk. IPv4 würde bereits schon sehr früh entwickelt und wurde so konzipiert das ein Netzwerkpacket selbstständig seinen Weg durch ein Netzwerk findet und somit unemfindlicher gegen Ausfälle eines Netzwerkteils ist, vorrausgesetzt das es meherere Wege zu einem Ziel gibt. Dafür sieht IPv4 vor das man Netzwerkadressbereiche segmentieren kann und so Adresseräume zuzammenfassen kann zu einem Subnetz. Mit hilfe von IPv4 kann man 32Bit (4 Byte) breite Adressen vergeben, die einen Teilnehmer eindeutig Adressieren. Diese 32Bit breiten Adressen erlauben einen Adressraum von 4.294.967.296 Adressen. Eine solche Adresse könnte beispielsweise so aussehen:
Zur einfacheren lesbarkeit werden diese 32Bit-Adressen zu Blöcken von 8Bit zusammen gefasst. Diese 4 mal 8Bit Blocke werden durch Dezimalzahlen die durch einen Punkt getrennt werden dargestellt. Gelegentlich findet man auch die Hexadezmailschreibeweise, welche aber nur für eingefleischte Profis wirklich lesbar sind.
Mit dem IPv4 Protokoll wurde auch die schon oben genannte Segmentierung (Subneting) eingeführt. Dazu wird eine ebenfalls 32Bit breite Subnetmask eingeführt, die den für ein Subnetz relevanten Teil der IP-Adresse durch eine einfache AND-Verknüpfung erzeugt und so das Routing vereinfacht. Andersherrum kann durch eine Invertierung und anschliessendem AND der Subnetzmask mit der IP-Adresse der relevante Teil der Adresse innerhalb eines Subnetzes gefiltert werden. Auch hier kann die oben benutzte schreibweise eingesetzt werden.
Mit diesen Schema lässt sich leicht der relevante Anteil der IP-Adresse für das Subnetz errechnen. Bei den obrigen Beispiel wurde das bedeuten das die ersten 24Bit der IP-Adresse das Subnetz adressieren und die letzten 8Bit den Rechner im Subnetz. Das bedeutet das in dem Beispiel in diesen Subnetz 2^8 oder 256 Adressen für das Subnetz zur Verfügung stehen, sogenannte Hostadressen, wobei bestimmte Hostdressen besondere Funktionen haben. Wenn alle Bits im Hostteil gesetzt sind entspricht das der Broadcastadresse im Subnetz, und keine gesetzten Bits im Hostteil meint das eigeneliche Subnetz. So bleiben effektiv 2^8 - 2 oder 254 Adressen für dieses Subnetz übrig. Eine gängige Schreibweise für die Subnetmask besteht auch nur in der Angabe der gesetzten Bits in der Subnetmask gelesen von links nach rechts, ein Beispiel wäre 192.168.10.0/24. Hierbei ist das Subnetz 192.168.10.0 mit der Subnetmask 255.255.255.0 gemeint.
Durch die Einführung des Subneting besteht auch die möglichkeit Netzwerke hierachrisch aufzubauen und zusammenzufassen. So können Router die an mehrere Subnetze angeschlossen sind diese Subnetze trennen und zwischen ihnen Vermitteln. Dabei hält jeder Router Routintabellen vor, die ihm auskunft geben wie andere Subnetze zu erreichen sind. Dabei müssen Router nicht einmal direkt an einem Zielnetzwerk angeschlossen sein, sondern brauchen nur zu wissen über welchen anderen Router sie dieses Netzwerk erreichen. Dabei sind auch Querverbindungen möglich, es muss nur ein entsprechender Routingeintrag vorhanden sein. So kann es auch sein das es zu einem Netzwerk mehrere Routen gibt, welches die Ausfallsicherheit des Netzwerkes erhöht. Die Subnetmask ist aber nicht bestandteil eines IP-Packetes, sonder wird nur zum Routing und Zuordnung von IP-Adressen zu Netzwerken benutzt und findet hauptsächlich Anwendung auf Routern.
Kommen wir nun zum aufbau eines IP-Packetes. Folgende Abbildung verdeutlicht den IP-Header und den Payload eines IP-Packetes. Der Payload beinhaltet hierbei die Daten für das nächste höhere Protokoll.
- IP-Version: Die IP-Version gibt an um welche Verion des IP-Protokolls verwendet wird.
- Header Lenght: Dieser Wert gibt die Länge des IP-Header in 32-Bit Blöcken an. Die Minimallänge dei IP-Headers beträgt 20 Byte für die wichtigsten Informationen für den Transport des IP-Packetes. Der Minimalwert ist 5. 20*8 / 32 = 5. die Maxmiallänge beträgt 15. Da dieses Feld 4 Bit breit ist. Damit kann die Maximallänge 60Byte betragen. 60 * 8 / 32 = 15.
- Type of Service: Dieses Feld enthält Angaben zur Verarbeitung des Datenpacketes. Eine Ausführliche Beschreibeung befindet sich in der [RFC 1349].
- Total Lenght: Gibt die Länge des gesamten IP-Packet an mitsamt Header und Payload. Gesamtlänge mit Header darf 2^16 betragen, also 65536 Byte.
- Identivication: Diese Feld ist 16 Bit breit. Dieses und die beiden folgenden Felder Flags und Fragment Offset steuern die Reassembly (Zusammensetzen von zuvor fragmentierten IP-Datenpaketen). Eindeutige Kennung eines Datagramms. Anhand dieses Feldes und der 'Source Address' kann der Empfänger die Zusammengehörigkeit von Fragmenten detektieren und sie wieder reassemblieren.
- Flags: Diese 3Bit breite Feld regelt die Fragmentierung von IP-Packeten.
- Fragment Offset: Bei Framentierten IP-Packeten kann anhand dieses Offset und mit hilfe der Identification das ursprünglich IP-Packet wieder erzeugt werden (reassembling).
- Time to Live: Mit Hilfe dieses Feldes wird die Zeit eines IP-Packetes im Internet begrenzt damit Schleifen die beim Routing evtl. auftreten ein IP-Packet nicht endlos existieren lassen. Das TTL (Time to Live) wird bei jeden Hop (Hop = Router) um ein vermindert. Wenn es 0 wird wird das IP-Packet verworfen. Maximalwert ist 255.
- Protocol: Gibt an um welches höhere Protokoll es sich bei den Daten im Payload handelt. Häufige Werte sind 6 für TCP oder 17 für UDP.
- Header Checksum: Hier wird die Checksumme des gesamten Headers eingetragen, die Daten aus dem Payload werden nicht mit einberechnet.
- Source Address: Die Quell IP-Adresse.
- Destination Address: Die Ziel IP-Adresse.
- Options: Dieses Feld ist optinal und kann weitere Information aufnehmen wie die Verarbeitung beim routing zum Beispiel erfolgen soll.
- PAD: Da der IP-Header in Blöcken zu 32Bit organisiert ist werden nicht genutzte Bits mit 0 aufgefüllt. Das sogenannte Padding.
- Payload: Dieser bereich enthält die eigentlichen Daten die von IP Transportiert werden sollen und für die nächst höhere Protokollschicht gedacht sind.
ARP
Das ARP-Protokoll [RFC 826 - Ethernet Address Resolution Protocol] vermittelt zwischen der 2. und 3. Schicht des OSI-Schichtmodels zählt aber bereits zu Schicht 3. Es realisiert die Zuordnung der IP-Adressen auf IP-Ebene im lokalen LAN zu den Hardwareadressen der MAC-Ebene. Da es keinen zusammenhang zwischen diesen beiden Adressen gibt ist eine Auflösung dieser Adressen im lokalen LAN nötig. Dabei schickt ein Teilnehmer der die MAC-Adresse zu einer IP-Adresse sucht eine Broadcastnachricht an alle Teilnehmer. Diese Nachricht beinhaltet die eigene MAC- und IP-Adresse und die IP-Adresse zu der die passende MAC-Adresse gesucht wird. Das Feld mit der zu suchende MAC-Adresse wird hierbei frei gelassen und wird bei einer erfolgreichen Zuordnung von dem Teilnehmer mit der gesuchten IP aufgefüllt und zum Absender geschickt als Anwort.
Die ersten gelben Felder geben an um welchen Typ von Adressen es sich handelt und wie lang diese sind. Das lila Feld gibt die Operation an, ob das Packet eine Anfrage oder Antwort ist. Danach folgen die Angaben zur Anfrage, die die wichtigen Daten enthalten. Das Schema der Anfrage wurde schon beschrieben und wie die Antwort erfolgt.
ICMP
Bei dem ICMP-Protokoll handel es sich um ein Protokoll zum Austausch von Informationen und Fehlermeldungen über das IP-Protokoll. Definiert ist dieses Protokoll in der [RFC 792 'I'nternet 'C'ontrol 'M'essages 'P'rotocol]. ICMP ist im ISO/OSI-Schichtmodel auf der gleichen Schicht wie IP eingeordnet, auf Schicht 3, da es Informationen und Fehlermeldungen zum IP-Protokoll behandelt. ICMP unterscheidet mehrere verschieden Packettypen, bekanntester Vertreter ist Packettyp 8 "Echo Request" oder allgemein gerne als Ping bezeichnet. In dem hier entwickelten Netzwerkstack wird auch nur Packettyp 8 implementiert, da gerne Ping genommen wird um die erreichbarkeit eines Host zu testen. Deshalb gehen wir nicht weiter dadrauf ein und soll nur der vollständigkeit halber erwähnt werden.
UDP
Mit dem UDP-Protokoll [RFC 768] treffen wir neben TCP einen der wichtigsten Protokolle des Internet auf Grundlage des IP-Protokolls. Mit UDP werden viele Dienste realisiert die uns selbstverständlich erscheinen wie das Domain Name Service System oder diverse Audio und Videodienste. Bei UDP handelt es sich um ein Transportprotokoll und ist deshalb in Schicht 4 des ISO/OSI-Schichtmodels angesiedelt. UDP ist ein Verbindungsloses Protokoll, welches minimale Verzögerung und wenig Overhead bietet. Es ist einfach Aufgebaut und leicht handhabbar. Der größte Nachteil von UDP ist, das es nach dem Fire-and-Forget Prinzip arbeitet. Es ist nicht garantiert das eine gesendete UDP-Nachricht auch bei Empfänger ankommt. Um verlorengegangende UDP-Nachrichten oder UDP-Nachrichten die in der falschen Reinfolge ankommen muss sich die Anwendung selbst kümmern. Dies ist auch so gewollt, da bestimmte Anwendungen die in Echtzeit arbeiten darauf keine Rücksicht nehmen können, weil eine Neuanforderung der verlorengegangenden Daten schlicht zu viel Zeit kostet und nicht erwünscht ist. Ein gutes Beispiel sind hier Audio oder Videoanwendungen, bei dem eine kleine Störung weniger ins gewicht fällt als die Unterbrechung oder Verzögerung des Datenstroms wegen einer Neuanforderung eines verlorengegangenden UDP-Packetes.
Das UDP-Protokoll befindet sich im Datenbereich des IP-Packetes.
Der Aufbau eines UDP-Packetes unterteilt sich in UDP-Header und UDP-Payload, dargestellt in folgender Abbildung.
- Source-Port: Dieses 16Bit Feld gibt den Port auf Senderseite an. Mit Hilfe dieses Ports wird eine Zuordnung von eintreffenden UDP-Packeten getroffen zu einer Anwendung.
- Destination-Port: Dieses 16Bit Feld gibt den Port auf Empfängerseite an. Mit Hilfe dieses Ports wird eine Zuordnung von eintreffenden UDP-Packeten getroffen zu einer Anwendung. Wenn ein Teilenhmer ein UDP-Packet zu einem anderen Teilnehmer per UDP schickt, setzt dieser den Destinationport auf die Portnummer auf welcher auf Empfängerseite ein bestimmter Service läuft. Dabei setzt er den Sourceport mit einer Zufallszahl damit der Empfänger mit dieser Portnummer Antworten kann und das UDP-Packet bei einer Antwort zugeordnet werden kann.
- Lenght: Gibt die Länge des Payload in Bytes an.
- Checksum: Beinhaltet eine 16Bit Checksumme über das gesammte UDP-Packet. Dazu wird das UDP-Packet in Blöcke a 16Bit aufgeteilt um die 16Bit Checksumme zu berechnen. Dieses Feld ist aber nicht Pflicht und kann auf 0 gesetzt werden. Das signalisiert den Router der solch ein Packet überträgt das er die Checksumme nicht berechnen braucht. Das hat den vorteil das auch UDP-Packet übertragen werden die Fehler haben. Da die Fehler meist nur einzelne Bits sind und es z.B. bei Audioanwendungen aber in stört, aber der Datenström nicht abreisst.
- PAD: Da der Payload in 16Bit Felder unterteilt und der Datenbereich nicht immer exakt in diese Felder passt wird der restliche Datenbereich mit 0 aufgefüllt damit die Checksumme gerechnet werden kann.
Wie vorhin schon angesprochen ist UDP ein sehr einfaches Protokoll. Es arbeitet Port-basiert. Mit deren hilfe kann eine Anwendung oder Service direkt angesprochen werden. Um eine zuordnung auch in der Gegenrichtung zu ermöglichen gibt es Ziel und Quellport. Der Zielport ist von der anzusprechenden Anwendung oder Service abhängig, der Quellport wird zufällig festgelegt an Hand einer Zufallszahl und dient zur späteren Zuordnung bei anworten auf ein gesendetes UDP-Packet.
Funktionen die für das arbeiten mit UDP benötigt werden sind:
unsigned int UDP_SendPacket( unsigned int SOCKET, unsigned int Datalenght, unsigned char * UDP_Databuffer ); unsigned int UDP_ListenOnPort( unsigned int Port, unsigned int Bufferlenght, unsigned char * UDP_Recivebuffer); unsigned int UDP_GetSocketState( unsigned int SOCKET ); unsigned int UDP_GetByteInBuffer( unsigned int SOCKET ); void UDP_FreeBuffer( unsigned int SOCKET ); unsigned int UDP_CloseSocket( unsigned int SOCKET );
Genaue Funktion und Benutzung der Funktionen zu UDP kann der Sourcecodedokumentation entnommen werden. Beispiele die die prinzipelle Benutzung zeigen sind dort enthalten.
TCP
TCP (Transmission Control Protocol) [RFC 793 , RFC 1323] ist eines der meistgenutzen Protokolle die auf IP aufsetzen und ist im ISO/OSI-Schichtmodel von Schicht 4-7 angesiedelt.
TCP baut dabei eine logische Verbindung über ein Netzwerk zu einem anderen Comupter auf. Es kümmert sich um die Datenübertragung und die dabei auftretenden Fehler und versucht diese zu korrigieren wenn es zu solchen gekommen ist. TCP sichert zudem das die Reinfolge der Daten, wie sie die Anwendung versendet oder empfängt, richtig ist, da die Protokolle IP oder UDP dies nicht vorsehen und auch nicht garantieren können. Über solch eine logische Verbindung können dann eine vielzahl von Anwendungen arbeiten und miteinander kommunizieren und müssen sich nicht um die richtigkeit der Übertragung der Daten kümmern. Dafür sorgt TCP.Für eine Verbindung zwischen zwei Teilnehmern wird wie schon bei UDP das Portprinzip angewendet um einen bestimmten Service oder eine bestimmte Anwendung anzusprechen. Mit diesen Portnummern können später nach dem Verbindungsaufbau eintreffende TCP-Packete auch einer TCP-Verbindung zugeordnet werden und letztendlich einer Anwendung. Der Aufbau einer Verbindung erfolgt durch einen 3-Wege-Handshake bei dem beide Teilnahmer sich synchronisieren. Zu dieser syncrohnisation gehören der Austausch der Sequenznummern zwischen den Teilnehmern die beim Start zufällig gewählt werden und die menge der Daten die jeder Teilnehmer zwischenspeichern kann. Die Sequenznummer dienen als Zähler der gesendeten Bytes und müssen vom Empfänger quitiert werden. An Hand dieser Quitierung kann der Sender erkennen ob Daten verloren gegangen sind um diese gegebenfalls neu versenden. Das Funktioniert in beide richtungen, vom Sender zum Empfänger und umgekehrt, da beide Teilnehmer über eine eigene Sequenznummer verfügen. Der Teilnehmer der eine Sequenznummer erhält quitiert diese Sequenznummer als Acknowlegenummer an den Sender. Jedes TCP-Packet enthält deshalb eine Sequenznummer und eine Acknowlegenummer. Zusetzlich wird dem Sender vom Empfänger noch mitgeteilt wieviel Daten er noch senden darf, eh der Empfänger diese nicht mehr verarbeiten kann. Als dritte möglichkeit der Steuerung kommen noch TCP-Flags hinzu. Diese Flags geben den Zustand der Verbindung wieder und steuern diese. Mit ihnen wird der Aufbau, Abbau und Datenbehandlung gesteuert. Mit diesen drei möglichkeiten der Steuerung innerhalb von TCP sind die wichtigsten Grundlagen geschaffen für eine logische und Fehlerfreie Verbindung zwischen zwei Teilnehmern. Das folgende Bild zeigt den Aufbau des TCP-Header.
TCP beherbert aber noch mehr Steuer und kontrollmöglichkeiten die im folgendem kurz erläutert werden.
- Source Port Der Sourceport gibt den Quellport des TCP-Packetes an.
- Destination Port Der Destinationport gibt den Zielport des TCP-Packetes an, der einer Anwendung zugeordnet wird.
- Sequence Number Die Sequence Number gibt die Sequenznummer des Payload an im TCP-Packet. Dieser dient zur sortierung der TCP-Packet da die TCP-Packete in unterschiedlicher reinfolge ankommen können.
- Acknowledgment Number Die Acknowledge Number die zum bestätigen der empfangenden TCP-Packete beim Sender. Mit hilfe dieser Acknowledgments kann der Sender feststellen ob und welche TCP-Packete verloren gegangen sind um die wieder zu versenden.
- Data Offset Gibt die länge des TCP-Packetes in 32-Bit worten an ohne Payload.
- Flags Die Flag steuern die Verbindung. Mit Ihnen wird die Art des TCP-Packetes festgelegt und wie die Daten zu behandeln sind. Im folgenden wird eine kurze übersicht über die Flags gegeben.
- FIN Dient zu beenden einer logischen TCP-Verbindung und signalisiert die Anforderung der Beendigung. Wie ein Verbindungsabbau erfolgt wird später noch erläutert.
- SYN Mit hilfe des SYN-Flag wird der Verbindungsaufbau signalisiert, wie dieser abläuft wird später noch erläutert
- RST Das RST-Flag dient zu reseten der Verbindung. Dies Flag wird auch beim ablehnen eines Verbindungsaufbau mit dem SYN-Flag benutzt.
- PSH Dieses Flag zeig an das die Daten die im Payload enthalten sind gesondert behandelt werden müssen und sofort zur Anwendung durchleitert werden soll ohne gepuffert zu werden.
- ACK Das ACK-Flag dient der Bestätigung von gesendeten TCP-Packeten, bestätigen des SYN- oder FIN-Flag für einen Verbindungaufbau oder Abbau.
- URG Wenn dieses Flag gesetzt ist werden Daten auf die der UrgentPointer zeigt sofort zur Anwendung durchgeleutet. Dabei Unterbricht die Anwendung ihre Arbeit und bearbeitet die Daten auf den der Urgentpointer zeigt. Diese Flag wird kaum noch genutzt und hat fast keine bedeuting mehr.
- Windows Size Ihr wird den Empfänger des TCP-Packetes mitgeteilt wieviel platz noch im Puffer für Daten beim Sender vorhanden ist und wieviel der Empfänger an Daten noch zum Senden schicken kann ohne das des zum Datenverlust kommt.
- Checksum Hier wird die Checksumme des über den gesamten Header und Payloadbereich hinterlegt mit dem sich Fehler bei der Übertragung erkennen lassen.
- Urgent Pointer Der Urgent Pointer zeigt auf die Daten im Payload die sofort zur Anwendung weitergeleitet werden sollen. Dieser Pointer ist nur mit dem URG-Flag gültig.
- Options Mit hilfes dieses Feldes können im TCP-Header noch zusetzliche Daten angehangen werden. Das Optionsfeld gibt dabei die größe der Options um die der TCP-Header erweitert wird in 32-Bit Blöcken an.
- PAD Sollte der TCP-Header nicht komplett genutzt werden weil das Optionsfeld nicht vollständig genutzt wird wird der restliche Bereich mit Nullen aufgefüllt.
- Payload Die eigentlichen Daten.
Beim Aufbau einer TCP-Verbindung müssen sich beide Teilnehmer synchronisieren. Dazu werden die Sequenznummern und die Windowsize nach einem festen Schema ausgetauscht. Der Verbindungsaufbau wird mit hilfe der Flags signalisiert. Beim Verbindungsaufbau kommen das SYN und ACK-Flag zum tragen. Die folgende Abbildung verdeutlicht dies.

Der Teilnemher der eine Verbindung aufabuen möchte sendet ein TCP-Packet mit gesetzten SYN-Flag an den anderen Teilnehmer und setzt dabei die Sequenznummer auf einen zufälligen Wert X. Der andere Teilnehmer des TCP-Packets sendet daraufhin ein TCP-Packet mit gesetzem SYN + ACK-Flag, der empfangendem Sequenznummer X + 1 als Acknowledge und seiner eigenen zufälligen Sequenznummer Y und quitiert mit diesem TCP-Packet das empfangende SYN und die Sequenznummer X. Der Teilnehmer der den Verbingungsaufbau gestartet hat bekommt mit der Antwort die Bestätigung seiner Sequenznummer X + 1 und die Sequenznummer Y des anderen Teilnemher. Dieses TCP-Paket gestätig er wiederum um die Sequenznummer des anderen zu bestätigen mit einen ACK und der Sequenznummer Y + 1 als Acknowledge. Nach diesem Vorgang sind beide Teilnehmer synchronisiert und die eigentliche kommunikation kann erfolgen.
Die eigentliche kommunikation erfolgt dann mach einen einfachen Schema. Der Teilnehmer der Daten senden möchte erhöht seine Sequenznummer X um die Anzahl der Daten und schickt ein TCP-Packet mit der neuen Sequenznummer X und den Daten. Der Empfänger quitiert den empfang der Daten mit der Sequenznummer aus dem TCP-Packet als Acknowledgmentnummer und gesetztem ACK-Flag. Unter einsatz der PSH- und URG-Flags kann die kommunikation noch beeinflusst werden, worauf hier aber nicht eingegangen werden soll. Um die kommunikation zu beschleunigen kann der Sender Daten auch im vorraus senden ohne die Quitierung abzuwarten, dabei muss er die größe der Windowsize beachten die der Empfänger hat. Auch hierbei muss der Empfänger jedes TCP-Packt quitieren mit seiner Ackknowledgementnumber um den Sender das erneute senden von nicht empfangenden TCP-Packeten zu ermöglichen.
Wenn die Verbindung nicht mehr gebraucht wird erfolgt der Abbau der Verbindung. Der Abbau der Verbindung erfolgt durch einen 3(eigentlich 4)-Wege-Handshake in welchen beide Teilnehmer kontrolliert die Verbindung abbauen und etvl. letzte Daten senden an den jewals anderen Teilnehmer. Hierbei spielt das FIN-Flag eine besondere Rolle.

Der Teilnehmer der die Verbindung abbauen möchte sendet dazu ein TCP-Packet mit gesetztem FIN-Flag und der Sequenznummer X an den anderen Teilnehmer. Dieser quitiert das FIN mit einen TCP-Packet mit gesetztem ACK-Flag und einer Sequenznummer X + 1. In diesem Zustand ist die TCP-Verbindung noch halb geöffen und es besteht die möglich noch Daten an den Teilnehmer zu senden der die Verbindung schließen will. Wenn alle Daten gesendet worden sind kann der andere Teilnehmer ein TCP-Packet senden mit gesetztem FIN-Flag und seiner Sequenznummer Y. Der Teilnehmer andere Teilnehmer quitiert dieses TCP-Packet mit einem ACK-Flag und der Sequenznummer Y + 1. Erst jetzt ist die Verbindung komplett geschlossen.
Das folgende Schema zeigt sehr gut den Verbindungsaufbau und Abbau für den jewaligen Teilnehmer mit Hilfe der SYN-, FIN- und ACK-Flags.
unsigned int Connect2IP( unsigned long IP, unsigned int Port ); void CloseTCPSocket( unsigned int Socket); unsigned int CheckSocketState( unsigned int Socket ); unsigned int GetSocketData( unsigned int Socket , unsigned int bufferlen, unsigned char * buffer); unsigned char GetByteFromSocketData( unsigned int Socket ); signed int GetBytesInSocketData( unsigned int Socket ); unsigned int PutSocketData( unsigned int Socket, unsigned int Datalenght, unsigned char * Sendbuffer ); unsigned int PutSocketData_P( unsigned int Socket, unsigned int Datalenght, const prog_char * Sendbuffer ); unsigned int PutSocketData_RPE( unsigned int Socket, unsigned int Datalenght, unsigned char * Sendbuffer, unsigned char Mode ); void SendData_RPE( unsigned int Socket, unsigned int Datalenght, unsigned char * Sendbuffer, unsigned char Mode ); signed int FlushSocketData( unsigned int Socket );
Genaue Funktion und Benutzung der Funktionen zu TCP kann der Sourcecodedokumentation entnommen werden. Beispiele die die prinzipelle Benutzung zeigen sind dort enthalten.
Netzwerkfunktionen
NTP
Das Network Time Protocoll dient zu holen der Zeit um die lokale Uhr zu stellen. Die einfachste Implementierung arbeitet nach der [RFC 868] und arbeitet nur Sekundengenau, da die Signallaufzeit nicht berücksichtigt wird. NTP arbeitet auf UDP:37 und TCP:37. NTP liefert einen 32Bit Wert zurück der die Sekunden seid dem 1.1.1900/GMT zählt. Daraus läßt sich leicht die aktuelle Zeit der Zeitzone in der sich der Rechner befindet ermitteln. Die Entwicklungsumgebung ermöglicht das einfache stellen der Uhrzeit mit Hilfe dies Protokolls beim Start. Ein einfacher Aufruf von
unsigned int NTP_GetTime( unsigned long IP, unsigned char * dnsbuffer )
versucht die Zeit zu holen. Dabei sollte entweder die IP gesetzte sein oder ein DNS-Name.
DNS
Der Domain Name Service, kurz DNS, ist ein Service zur Auflösung von Domainnamen nach IP-Adressen. Da es Menschen naturgemäß schwer fällt sich IP-Adressen zu merken und es ihm leichter fällt einprägsame Namen zu benutzen wurde dieser Service eingeführt. Definiert ist dieses Protokoll in der [RFC 1034] und [RFC 1035]. DNS setzt auf UDP Port 53 auf um den Overhead gering zuhalten, da DNS ein sehr stark genutzter Service ist. Jeder der das Internet benutzt, benutzt quasi auch diesen Service. Aufgebaut ist DNS hierarisch. Gelesen werden Domainnamen von rechts nach links getrennt durch einen Punkt. Als erstes Steht die TLD (Top-Level-Domain). Sie gibt das Land (.de, .au, .us um nur einige zu nennen) an oder hat spezielle Namen für Organisationen ( .org, .gow) oder Themen (.tv, .sex). Danach folgen der eigentliche Name (Secondary Domain Name), gefolgt von Subdomains. Für die TLD sind spezielle Root-DNS-Server zuständig die anhand der TLD die Zuordnung für die DNS-Server der jewaligen TLD erledigen, für Deutschland ist das die Denic. Bei der Denic [[1]] sind die zuständigen DNS-Server für den Secondary Domain Name hinderlegt welche für die weitere Auflösung des Namen zuständig sind. Eine DNS-Anfrage kann also einen ganzen Zweig an abfragen bei anderen DNS-Servern auslösen. Um die Netzlast nicht zu groß werden zu lassen, DNS macht ein beträchtlichen Teil des Internetverkehrs aus, richten die meisten ISP (Internet Service Provider) DNS-Cacheserver für ihre Kunden ein, die DNS-Anfragen auflösen und für sich wiederholende Anfragen die Anworten für eine gewisse Zeit zwischenspeichern. Bei Anfragen die ihnen aber unbekannt sind müssen sie aber dennoch besagtest System benutzen zum auflösen. Um diesen Service auf dem Mikrocontroller nutzen zu können gibt es eine Funktion die diese Anfrage erledigt und die IP zum übergebenen Domainnamen zurück liefert. Ihr wird einfach nur ein Pointer auf ein String übergeben der den Domainnamen der aufgelöst werden soll enthält und liefert ein unsigned long mit der IP zurück.
unsigned long DNS_ResolveName( unsigned char * HOSTNAME );
Fazit zur Softwareentwicklung
Anwendungsbeispiele
UDP-Echoserver
Bei einem Echoservice [RFC 862] handelt es sich um einen Dienst der alle auf UDP oder TCP Port 7 ankommenden Daten an den Versender zurück schickt. An diesen Beispiel kann man sehr gut erkennen wie man Services schreibt die nicht so leicht blockieren können. Als erstes wird der Service initialisiert mit UDP_echo_init(). Danach wird die UDP_echo() von der mainloop in main.c in regelmässigen Abständen durchlaufen. Hier wir geschaut ob es noch ein Socket gibt welcher auf Port 7 lauscht, wenn nicht wird ein neues Socket erstellt der auf Port 7 lauscht. Sollte bereits schon ein lauschender Socket bestehen wird nach Daten in diesen Socket gesehen, wenn keine Daten enthalten sind wird die UDP_echo() verlassen. Sollten Daten enthalten sein, werden diese an den Sender zurück geschickt und das Socket danach geschlossen.
unsigned int UDP_Socket;
void UDP_echo_init( void )
{
UDP_Socket = 0xffff;
printf_P( PSTR("UDP-Echo Service initialisiert\r\n"));
return;
}
void UDP_echo( void )
{
// make an static UDPbuffer
// warning, do not use an stack-allocated buffer! it will be damage the udp-packet
static unsigned char UDPBuffer[ UDP_Bufferlen ];
// if an Socket created or opened, if not, create them ?
if ( UDP_Socket == 0xffff )
UDP_Socket = UDP_ListenOnPort( UDPPORT_ECHO, UDP_Bufferlen, UDPBuffer );
else
{
if ( UDP_GetSocketState( UDP_Socket ) == SOCKET_BUSY )
{
LockEthernet();
UDP_SendPacket( UDP_Socket, UDP_GetByteInBuffer( UDP_Socket ), UDPBuffer );
FreeEthernet();
UDP_CloseSocket( UDP_Socket );
UDP_Socket = 0xffff;
}
}
return;
}
Telnet-Server
Telnet [RFC 854] TCP:23 ist ein Service, der die Ausgaben einer entfernten Terminalanwendung auf den Bildschirm eines anderen Rechners per Netzwerk umleitet. Dabei übernimmt Telnet die Aushandlung, wie die Übertragung der Daten vorgenommen wird oder überträgt zum Beispiel Informationen über die Auflösung des Terminals auf dem die Ausgaben erfolgen. Diese Aushandlungen am Anfang einer Telnetverbindung sind aber nicht Pflicht. Dadurch kann man Services auch auf anderen TCP-Ports testen, wie zum Beispiel HTTP. Auf dem Mikrocontroller ist eine sehr einfache Variante implementiert, die es ermöglicht, per Netzwerk direkt mit dem Mikrocontroller zu kommunizieren, je nach dem wie die Funktionen oder Befehle die man überträgt interpretiert werden. In diesem Service findet man eines der besten Beispiele vor, wie man nichtblockierende Programme schreibt, welche mit Netzwerkfunktionen umgehen und zeichenweise Eingaben erwarten. Eine ausführliche Dokumentation zu diesen Programm findet sich in der Sourccodedokumentation.
HTTP-Server
Das Hypertext Transfer Protocol [RFC 2616] [RFC 1945] ist wohl die einfachste möglichkeit das Internet zum benutzen und zu erkunden, auch gerne als World Wide Web genannt. Viele bezeichnen gerne das Internet als WWW und meinen eigentlich nur ein Teil davon, denn das WWW nur ein spezieller Teil des Internet der für viele sichtbar ist. Jeder moderne Rechner, ja sogar inzwischen sogar in jedem einfachen Telefon, liegt ein Programm bei um diesen Teil des Internet zu erkunden. Diese Programme werden in Browser genannt, wobei dieser Begriff dem browsen angelehnt ist. Browsen stammt aus dem engl. und bedeutet soviel wie stöbern, damit erübrigt sich auch schon die weitere Begriffserklärung, da man eigentlich auch nichts anderes macht. Das HTTP Protokoll arbeitet Nachrichtenbasiert. Der Client sendet eine Anfrage und der Server Anwortet. Dabei wird eine Anfrage und Antwort in zwei Bereiche unterteilt, Nachrichtenkopf und Nachrichtenkörper. Der Nachrichtenkopf (Header) beschreibt den Nachrichtenkörper (Body), und wie dieser zu interpretieren ist (Kodierung, Länge und Typ um nur einige Informationen zu nennen). Ein typischer Dialog konnte folgendermaßen aussehen.
GET / HTTP/1.1
Host: 192.168.2.98>
Connection: keep-alive
HTTP/1.0 200
Content-Type: text/html
Keep-Alive: close
<HTML>
<HEAD>
<meta http-equiv="refresh" content="20; ">
<TITLE>Winkewinke</TITLE>
</HEAD>
<BODY>
Zeit: 23:13:06.04, Uptime: 21188 sek
Ethernet: 2906582 Bytes in 32471 Packets
Du bist der 956. Besucher auf Socket 0.
Bearbeitungszeit 0.02 Sekunden.>
</BODY>
</HTML>
Wichtigster bestandteil ist der GET-request des Browsers an den HTTP-Server, der eine bestimmte Datei von Ihm anfordert. Dabei können in der Anfrage des Client noch eine menge andere Informationen enthalten sein wie zum Beispiel über den Browser oder art der Verbindung. Abgeschlossen wird die Anfrage mit einer Leerzeile. Daraufhin Antwortet der Server entsprechend der Anfrage mit einer Codenummer und den eigentlichen Daten die angefordert wurden falls die Anfrage gültig war. In diesem Projekt ist ein einfacher HTTP-Server implementiert der eine statische Seite ausliefert und sonst keine weiteren Anfragen unterstützt.
MP3-Streamingclient
Mit dem MP3-Streamingclient für das Mikrocontrollerboard ist eines der schönsten Beispiele zu sehen wie man Hardware erweitert und viele Softwarefunktionen die implementiert sind zusammenspielen. Grundidee zu dieser Erweiterung war es gewesen eine kleine Box zu schaffen die man versteckt neben die Audioanlage aufstellt und mit ihr verbindet. Die Box wird dann mit dem Netzwerk welches auch Internet bereit stellt verbunden. Danach kann der Mikrocontroller im hintergrund einen MP3-Stream der von einem beliebigen Shoutcast-Server [[2]] geliefert wird abspielen. Dabei wird nur die Quelle des MP3-Stream per Telnet vorgegeben und um den Rest kümmert sich die Software auf dem Mikrocontrollerboard. Dabei kümmert sich die Software um das öffnen des Streames und um das Buffer sowie weiterleiten an den MP3-Decoder. Der MP3-Decoder stellt in diesen Fall auch die schon genannte Hardwareerweiterung dar. Beim dem MP3-Decoder [[3]] handelt es sich um einen DSP mitsamt auf ihm enthaltender Software zum decodieren den man steuern kann und den MP3-Stream aufbereitet übergeben muss. Der eigentliche Schwerpunkt der Software liegt im anfordern und puffern des MP3-Stream. Dies ist keine leichte aufgabe, da der MP3-Decoder selbst keinen großen Puffer bietet und ständig mit neuen Daten versorgt werden will. Hier ist effizentes programmieren angesagt. Folgende Abbildung zeigt den verlauf der Daten.
Wie eben schon erwähnt liegt der Schwerpunkt der Software auf das bereitstellen des MP3-Stream für den MP3-Decoder. Ein MP3-Stream kann Datenraten zwischen 8 und 448Kbit/s haben. Wärend bei kleinen Datenraten von 8kBit/s die Verarbeitung noch leicht fällt, sieht es bei hohen Datenraten die für MP3-Streaming in ausreichender qualität erforderlich sind, erst ab 128kBit/s wird CD-qualität erreicht, schon wesentlich schwerer aus. Bei dieser Datenrate durchlaufen die Daten die Puffer mit einer Datenrate von 16kByte/s. Diese Datenraten muss der Netzwerkstack liefern und die Software verarbeiten können, keine leichte Aufgabe, kann es doch vorkommen das bei der Übertragung des MP3-Stream auf Netzwerkebene es zu Fehlern kommt und die Daten neu angefordert werden müssen und so die Puffer schnell leer laufen, denn trotzdem muss dem MP3-Decoder diese Datenrate aber auch konstant zur Verfügung gestellt werden damit seine Puffer nicht leer laufen und es zu aussetzer bei der Wiedergabe kommt. Einige Test mit der Software haben gezeigt das die Leistungsreserven des Mikrocontrollerboard reichen um einen MP3-Stream über das Internet mit bis zu 256kBit/s abzuspielen ohne das der Datenstrom für den Decoder Abreißt.
Beim entwickeln der Software für den MP3-Decoder und das MP3-Streaming sind eine menge neuer Netzwerkfunktionen und Funktionen für den FIFO-Puffer entstanden die die Arbeit mit den Funktionen extrem beschleunigen. Als Beispiel sei hier das schnelle kopieren zwischen zwei FIFO-Puffer erwähnt. Anstatt Byte für Byte aus jedem FIFO zu lesen und schreiben wurde eine extra Funktion der fifo.c hinzugefügt, die das schnelle kopieren zwischen den FIFO-Puffer ermöglicht in einen Rutsch und mit einem Funktionsaufruf. Das hat die Geschwindigkeit um mehr als 300% vervielfacht. Diese Funktion finden sehr stark Anwendung im TCP-Netzwerkstack, da der TCP-Puffer auch als FIFO realisiert ist. So kann man sehr schnell Daten von einer TCP-Verbindung in einen FIFO kopieren der den MP3-Decoder versorgt, was letztendlich den Datendurchsatz und der freien Rechenzeit auf dem Mikrocontrollerboard zu gute kommt. Auch ist nicht zu vergessen das bei der Entwicklung des MP3-Streamingclient viele Fehler in der Software gefunden worden sind, da viele Funktionen mit extremen Parametern betrieben worden sind und so ihre schwächen zeigten. Ein Blick in die Sourcecodedokumentation sind die neuen Funktionen auf jeden fall wert.
Als zweiten Teil des MP3-Streamingclient kommt die Hardware. Die eigentliche Arbeit des decodierens des MP3-Streams erledigt ein DSP von VLSI [[4]]. Dieser DSP braucht nur eine minimale externe Beschaltung um seiner Aufgabe nach zu kommen. Gesteuert und mit Daten versorgt wird dieser über den SPI-Bus, der Reset-, der CS-, der BSYNC- und der DREQ-Leitung. Da das Mikrocontrollerboard mehrere SPI-Interfaces besitzt wird einer nur für den VS10xx abgestellt damit die verschiedenen SPI-Interfaces ihrer Aufgabe unabhängig voneinander weiter nachgehen können. So reduzieren sich die Datenleitungen auf 6 Stück an der Zahl und die Spannungsversorgung. Die Spannungsversorgung läßt sich dabei Ein oder Aus schalten, da der Stromverbrauch mit fast 50mA gesehen zum rest des Mikrocontrollerboard nicht gering ausfällt. Die folgenden Bilder zeigen den Schematischen aufbau sowie das Leiterplattendesign und das fertige Board.
Vorschau auf Neues
Zum Abschluss dieser Dokumentation über den Aufbau einer Entwicklungsumgebung mit Mikrocontroller auf AVR Basis wollen wir nicht vergessen, auch weitere interessante mögliche Anwendungen des Boards zu erwähnen. Durch die Vielzahl von Schnittstellen ist es möglich, das Board als ferngesteuerten Zustandsüberwacher (z.B. Alarmzentrale) in Haus und Hof mit Hilfe einer Internetverbindung (Browser) zu nutzen oder eine komplexe Steuerung für Maschinen oder andere über Relaiskarten gesteuerte Bauteile zu entwicklen. Auch im Hobbybereich, im Modellbau, im Modellbahnbau oder als Wetterempfangsschnittstelle mit entsprechendem Wetterodul kann unser Board eingesetzt werden.
Schon während der Bau- und Testphase haben wir bereits einige Neuerungen analysiert, wie zum Beispiel einen effizienteren Spannungsregler LM2575 T 5.0 zur Spannungsversorgung sowie einen weiteren Jumper am Eingang XTAL2 des Controllers, um mit einer einfachen Steckbrücke den Takt des ENC28j60 von Jumper 7 für den Controller zu nutzen. Diese Weiterentwicklungen zeigen, dass ein solches Entwicklungsboard selbst ständiger Weiterentwicklung unterliegt. Jedem Anwender ist es freigestellt, dieses Board nach seinen Wünschen zu bauen, zu modifizieren oder eine abgespeckte Version, zum Beispiel ohne Ethernetschnittstelle, zu entwicklen. Wir wünschen allen neugierig gewordenen viel Spass dabei.
Anhang
Bildverzeichnis
Bild A1: Funktionsbaugruppen ATmega 2561
Technische Daten
- Art des Gerätes: Entwicklungsboard mit Mikrocontroller
- Betriebsspannung: 6-25 Volt DC
- Stromverbrauch unter Vollast : 175mA
Schnittstellen:
- IEEE 802.3 Ethernet (RJ 45 Buchse)
- IEEE 232 RS 232 (D-Sub 9 Buchse) bis 57200 bit/s
- EIA-232 UART (Pfostenstecker intern)
- I2C serieller Bus (Pfostenstecker intern)
- SPI Serial Periphere Interface (Pfostenstecker intern)
- JTAG Debug Modus Schnittstelle (Pfostenstecker intern)
- 8xAnalog 8 Analogeingänge(gemultiplext) mit bis zu 10Bit Auflösung
Abmessungen: 103x85x42 (LxBxH)
µC :
- ATmega 2561 16-AI
Datenbus :
- 8 bit
Adressbus:
- 16 bit
Takt:
- 16 MHz
Bauteileliste
Folgende Bauteile werden für das Entwicklungsboard benötigt:
- IC 1..........ATmega 2561-16 AI
- IC 2..........74HCT573
- IC 3..........626128 LP55
- IC 4..........ST232 DIL
- IC 5..........LM 2940 5,0V
- IC 6..........LM 3940 3,3V
- IC 7..........ENC28j60
- D1............1N4004
- Q1............25 MHz Grundwellenquarz
- Q2............16 MHz Grundwellenquarz
- LED1-3......LED beliebig, 1.5 V; 0,02A
- R1,R2.........270 Ohm
- R3-R6.........50 Ohm
- R7............2,7 kOhm
- R8-R10........470 Ohm
- C1,C3,C8,C9...10µF/40V
- C2,C4,C13-C16.10nF ker.
- C5,C6.........18pF ker.
- C7............47µF/25V
- C10...........100µF/40V
- C11,C12.......10µF/25V
- L1............100µH
- L2............330µH
- X1............RJ 45, Fast Jack m. Übertrager
- X2............1,3mm print/mini DC Buchse
- X3............D-Sub 9, print/90°
- Leiterplatte..FR4, 100x160x1,5/0.35µm Cu
- Gehäuse.......Euro Gehäuse 1,Fa. proMa//systro GmbH
Interrupt-Vektor-Tabelle ATmega 2561
Übersicht über alle verfügbaren Interruptroutinen im ATmega 2561:
1 $0000 RESET External Pin, Power-on Reset, Brown-out Reset, Watchdog Reset, and JTAG AVR Reset 2 $0002 INT0 External Interrupt Request 0 3 $0004 INT1 External Interrupt Request 1 4 $0006 INT2 External Interrupt Request 2 5 $0008 INT3 External Interrupt Request 3 6 $000A INT4 External Interrupt Request 4 7 $000C INT5 External Interrupt Request 5 8 $000E INT6 External Interrupt Request 6 9 $0010 INT7 External Interrupt Request 7 10 $0012 PCINT0 Pin Change Interrupt Request 0 11 $0014 PCINT1 Pin Change Interrupt Request 1 12 $0016 PCINT2 Pin Change Interrupt Request 2 13 $0018 WDT Watchdog Time-out Interrupt 14 $001A TIMER2 COMPA Timer/Counter2 Compare Match A 15 $001C TIMER2 COMPB Timer/Counter2 Compare Match B 16 $001E TIMER2 OVF Timer/Counter2 Overflow 17 $0020 TIMER1 CAPT Timer/Counter1 Capture Event 18 $0022 TIMER1 COMPA Timer/Counter1 Compare Match A 19 $0024 TIMER1 COMPB Timer/Counter1 Compare Match B 20 $0026 TIMER1 COMPC Timer/Counter1 Compare Match C 21 $0028 TIMER1 OVF Timer/Counter1 Overflow 22 $002A TIMER0 COMPA Timer/Counter0 Compare Match A 23 $002C TIMER0 COMPB Timer/Counter0 Compare match B 24 $002E TIMER0 OVF Timer/Counter0 Overflow 25 $0030 SPI,STC SPI Serial Transfer Complete 26 $0032 USART0 RX USART0 Rx Complete 27 $0034 USART0 UDRE USART0 Data Register Empty 28 $0036 USART0 TX USART0 Tx Complete 29 $0038 ANALOG COMP Analog Comparator 30 $003A ADC ADC Conversion Complete 31 $003C EE READY EEPROM Ready 32 $003E TIMER3 CAPT Timer/Counter3 Capture Event 33 $0040 TIMER3 COMPA Timer/Counter3 Compare Match A 34 $0042 TIMER3 COMPB Timer/Counter3 Compare Match B 35 $0044 TIMER3 COMPC Timer/Counter3 Compare Match C 36 $0046 TIMER3 OVF Timer/Counter3 Overflow 37 $0048 USART1 RX USART1 Rx Complete 38 $004A USART1 UDRE USART1 Data Register Empty 39 $004C USART1 TX USART1 Tx Complete 40 $004E TWI 2-wire Serial Interface 41 $0050 SPM READY Store Program Memory Ready 42 $0052 TIMER4 CAPT Timer/Counter4 Capture Event 43 $0054 TIMER4 COMPA Timer/Counter4 Compare Match A 44 $0056 TIMER4 COMPB Timer/Counter4 Compare Match B 45 $0058 TIMER4 COMPC Timer/Counter4 Compare Match C 46 $005A TIMER4 OVF Timer/Counter4 Overflow 47 $005C TIMER5 CAPT Timer/Counter5 Capture Event 48 $005E TIMER5 COMPA Timer/Counter5 Compare Match A 49 $0060 TIMER5 COMPB Timer/Counter5 Compare Match B 50 $0062 TIMER5 COMPC Timer/Counter5 Compare Match C 51 $0064 TIMER5 OVF Timer/Counter5 Overflow 52 $0066 USART2 RX USART2 Rx Complete 53 $0068 USART2 UDRE USART2 Data Register Empty 54 $006A USART2 TX USART2 Tx Complete 55 $006C USART3 RX USART3 Rx Complete 56 $006E USART3 UDRE USART3 Data Register Empty 57 $0070 USART3 TX USART3 Tx Complete
Source-Code Referenzen
Nachfolgend werden stellvertretend für den selbst erstellten Source Code die Code Dateien der UART Schnittstelle und des FIFO mit den dazugehörigen Header-Dateien wiedergegeben. Diese Source-Code Darstellung soll die Arbeitsweise und die allgemeinen Programmierrichtlinien des Entwicklungsboards darstellen.
UART-Schnittstelle
uart.c
/*!\file uart.c \brief Stellt die UART-Schnittstelle bereit */
//***************************************************************************
//* uart.c
//*
//* Mon Jul 31 21:46:47 2007
//* Copyright 2007 Dirk Broßwick
//* Email
/// \ingroup hardware
/// \defgroup UART Die UART-Schnittstelle (uart.c)
/// \code #include "uart.h" \endcode
/// \par Uebersicht
/// Die UART-Schnittstelle fuer den AVR-Controller
//****************************************************************************/
//@{
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "uart.h"
#include "system/buffer/fifo.h"
#include <stdio.h>
#include <avr/pgmspace.h>
#include <avr/io.h>
#include <avr/interrupt.h>
// stdout auf UART_Send_Byte verbiegen
static FILE mystdout = FDEV_SETUP_STREAM( UART_Send_Byte , NULL, _FDEV_SETUP_WRITE);
volatile unsigned char TX_state = TX_complete;
unsigned char TX_Buffer[ TX_Bufferlen ];
unsigned char RX_Buffer[ RX_Bufferlen ];
volatile unsigned long TX_Counter = 0;
volatile unsigned long RX_Counter = 0;
unsigned int RX_fifo;
unsigned int TX_fifo;
/* -----------------------------------------------------------------------------------------------------------*/
/*!\brief Initialisiert die Uart1 des ATmega2561.
* \param NONE
* \return NONE
*/
/* -----------------------------------------------------------------------------------------------------------*/
void UART_init( void )
{
// FIFO initialisieren wenn nicht schon passiert
INIT_FIFO ();
// FIFO reservieren für RX und TX
RX_fifo = Get_FIFO( RX_Buffer, RX_Bufferlen );
TX_fifo = Get_FIFO( TX_Buffer, TX_Bufferlen );
// TX_state auf complete setzen, da ja nix gesendet wurde
TX_state = TX_complete;
// Bitrate einstellen
UBRR1H = (unsigned char) ( UBRR_VAL>>8 );
UBRR1L = (unsigned char) UBRR_VAL;
// UART Tx,Rx,RxINT einschalten, UDRIW und RXCIE werden erst wärend der benutzung freigeben nach bedarf
UCSR1B |= ( 1<<TXEN1 ) | ( 1<<RXEN1 ) | ( 1<<RXCIE1 ) ;
// Asynchron 8N1
UCSR1C |= ( 1<< USBS1 ) | ( 1<<UCSZ11 ) ;
// printf auf uart umbiegen
stdout = &mystdout;
return;
}
/* -----------------------------------------------------------------------------------------------------------*/
/*!\brief Interruptroutine zum senden, wenn Transmiter ist fertig und keine weiteren Daten im Puffer
*/
/* -----------------------------------------------------------------------------------------------------------*/
ISR( USART1_TX_vect )
{
// Wenn der TX_puffer leer ist, Interrupt sperren
UCSR1B &= ~( 1<<TXCIE1 );
TX_state = TX_complete;
}
/* -----------------------------------------------------------------------------------------------------------*/
/*!\brief Interruptroutine zum senden, wird aufgerufen wenn UART-Register leer, aber Transmitter noch am senden
*/
/* -----------------------------------------------------------------------------------------------------------*/
ISR( USART1_UDRE_vect )
{
// checken ob noch bytes im Buffer sind
if ( Get_Bytes_in_FIFO ( TX_fifo ) != 0 )
UDR1 = Get_Byte_from_FIFO ( TX_fifo );
else
{
// Wenn nicht was es das letzte Byte im Puffer, dann diesen Interrupt sperren und TX-Interrupt freigeben
UCSR1B |= ( 1<<TXCIE1 );
UCSR1B &= ~( 1<<UDRIE1 );
}
}
/* -----------------------------------------------------------------------------------------------------------*/
/*!\brief Interruptroutine zum empfangen
*/
/* -----------------------------------------------------------------------------------------------------------*/
ISR( USART1_RX_vect )
{
unsigned char Byte;
RX_Counter++;
Byte = UDR1;
if ( Get_Bytes_in_FIFO ( RX_fifo ) < RX_Bufferlen )
Put_Byte_in_FIFO ( RX_fifo, Byte );
}
/* -----------------------------------------------------------------------------------------------------------*/
/*!\brief Sendet ein Byte über die Uart1.
* \param ein Byte vom typ unsigned char
* \return NONE
*/
/* -----------------------------------------------------------------------------------------------------------*/
void UART_Send_Byte( unsigned char Byte )
{
cli();
TX_Counter++;
if ( Get_Bytes_in_FIFO ( TX_fifo ) == 0 )
{
// Wenn Controller noch mit senden eines Byte beschäftig, ab in den Puffer
if ( TX_state == TX_sending )
{
// Byte in Buffer schreiben
Put_Byte_in_FIFO ( TX_fifo, Byte );
// Buffer Emty freigeben, TX complete sperren
UCSR1B &= ~( 1<<TXCIE1 );
UCSR1B |= ( 1<<UDRIE1 );
}
else
{
UDR1 = Byte;
// Buffer Emty sperren, TX_complete freigeben
UCSR1B |= ( 1<<TXCIE1 );
UCSR1B &= ~( 1<<UDRIE1 );
TX_state = TX_sending;
}
}
else
{
// Wenn Puffer voll, warten bis wieder was rein paßt
while ( 1 )
{
cli();
if ( Get_Bytes_in_FIFO ( TX_fifo ) < TX_Bufferlen ) break;
sei();
}
// Byte in Buffer schreiben
Put_Byte_in_FIFO ( TX_fifo, Byte );
}
sei();
}
/* -----------------------------------------------------------------------------------------------------------*/
/*!\brief Empfängt ein Byte über die Uart1.
* \param NONE
* \return Byte aus den Puffer
*/
/* -----------------------------------------------------------------------------------------------------------*/
unsigned char UART_Get_Byte( void )
{
unsigned char Byte=0;
cli();
// check ob überhaupt noch Bytes im Puffer sind, und wenn ja, auslesen
if ( Get_Bytes_in_FIFO ( RX_fifo ) != 0 )
Byte = Get_Byte_from_FIFO ( RX_fifo );
sei();
return(Byte);
}
/* -----------------------------------------------------------------------------------------------------------*/
/*!\brief Liest die Anzahl der Byte die im Empfangsbuffer sind.
* \param NONE
* \return Anzahl der Byte im Enpfangpuffer
*/
/* -----------------------------------------------------------------------------------------------------------*/
unsigned int UART_Get_Bytes_in_Buffer( void )
{
unsigned int BytesInBuffer=1;
cli();
// Anzahl der Bytes holen
BytesInBuffer = Get_Bytes_in_FIFO ( RX_fifo );
sei();
return( BytesInBuffer );
}
uart.h
/*!\file uart.h \brief Stellt die UART-Schnittstelle bereit */
//***************************************************************************
//* uart.h
//*
//* Mon Jul 31 21:46:47 2007
//* Copyright 2007 Dirk Broßwick
//* Email
/// \ingroup hardware
/// \defgroup UART Die UART-Schnittstelle (uart.h)
/// \par Uebersicht
/// Die UART-Schnittstelle fuer den AVR-Controller
//****************************************************************************/
//@{
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef _UART_H
#define _UART_H
#define F_CPU 16000000L
#define BAUD 57600L
#define UBRR_VAL (F_CPU/(BAUD*16)-1)
#define TX_Bufferlen 256
#define RX_Bufferlen 256
#define TX_complete 0
#define TX_sending 1
void UART_init( void );
void UART_Send_Byte( unsigned char Byte );
unsigned char UART_Get_Byte( void );
unsigned int UART_Get_Bytes_in_Buffer( void );
#endif /* _UART_H */
//@}
FIFO
fifo.c
/*! \file fifo.c \brief Stellt die FIFO Funkionalitaet bereit */
//***************************************************************************
//* fifo.c
//*
//* Sat Jun 3 23:01:42 2006
//* Copyright 2006 User
//* Email
//****************************************************************************/
/// \ingroup system
/// \defgroup FIFO Stellt die FIFO Funkionalitaet bereit (fifo.c)
/// \code #include "fifo.h" \endcode
/// \par Uebersicht
/// Stellt Funktionen bereit um einen FIFO-Puffer zu verwalten.
/// \todo Die Lib muss nocg ausgibig geprüft werden, benutzung noch nicht empfohlen<br>
/// \date 04-01-2008: FIFO ist getestet und funktioniert, erste Verwendung in UART
//****************************************************************************/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
//@{
#include <avr/io.h>
#include "fifo.h"
struct FIFO FIFO_Table[ MAX_FIFO_BUFFERS ];
unsigned char FIFO_initstate = 0;
/*-----------------------------------------------------------------------------------------------------------*/
/*!\brief Initialisiert die FIFO-Buffer Strukturen.
* \param NONE
*/
/*------------------------------------------------------------------------------------------------------------*/
void INIT_FIFO( void )
{
unsigned int i;
if ( FIFO_initstate != 0 )
return;
FIFO_initstate = 1;
// löschen aller FIFOs
for ( i = 0 ; i < MAX_FIFO_BUFFERS ; i++ )
FIFO_Table[ i ].buffer = NULL;
return;
}
/*-----------------------------------------------------------------------------------------------------------*/
/*!\brief Reserviert eine FIFO-Buffer verwaltungsstruktur.
* \param buffer Zeiger auf einen Puffer der verwaltet werden soll.
* \param bufferlenght Größe des Puffer.
* \return FIFOnumber Die Nummer des FIFO oder FIFO_ERROR.
*/
/*------------------------------------------------------------------------------------------------------------*/
unsigned int Get_FIFO( unsigned char * buffer, unsigned int bufferlenght )
{
unsigned int i;
for( i = 0; i < MAX_FIFO_BUFFERS ; i++)
{
if ( FIFO_Table[ i ].buffer == NULL )
{
FIFO_Table[ i ].buffer = buffer;
FIFO_Table[ i ].bufferlenght = bufferlenght;
FIFO_Table[ i ].readpointer = 0;
FIFO_Table[ i ].writepointer = 0;
FIFO_Table[ i ].byteinbuffer = 0;
FIFO_Table[ i ].lock = UNLOCK;
return( i );
}
}
return( FIFO_ERROR );
}
/*-----------------------------------------------------------------------------------------------------------*/
/*!\brief Gibt eine reservierte FIFO-Struktur wieder zur Benutzung frei.
* \param FIFO Nummer des FIFO.
* \return Returncode FIFO_OK oder FIFO_ERROR.
*/
/*------------------------------------------------------------------------------------------------------------*/
unsigned char Free_FIFO ( unsigned int FIFO )
{
if ( ( FIFO < MAX_FIFO_BUFFERS ) && ( FIFO_Table[ FIFO ].lock == UNLOCK ) )
{
FIFO_Table[ FIFO ].buffer = NULL;
return( FIFO_OK );
}
return( FIFO_ERROR );
}
/*-----------------------------------------------------------------------------------------------------------*/
/*!\brief Holt die anzahl der Bytes die sich im Puffer befunden.
* \param FIFO Nummer des FIFO.
* \return Returncode Anzahl der Bytes oder FIFO_ERROR.
*/
/*------------------------------------------------------------------------------------------------------------*/
unsigned int Get_Bytes_in_FIFO( unsigned int FIFO )
{
// FIFO auf gültigkeit testen
if ( ( FIFO < MAX_FIFO_BUFFERS ) && ( FIFO_Table[ FIFO ].lock == UNLOCK ) )
{
return( FIFO_Table[ FIFO ].byteinbuffer );
}
return( FIFO_ERROR );
}
/*-----------------------------------------------------------------------------------------------------------*/
/*!\brief Schreibt ein Byte in den FIFO.
* \param FIFO Nummer des FIFO.
* \param Byte Das Byte welches in den FIFO soll.
* \return Returncode FIFO_OK oder FIFO_ERROR.
*/
/*------------------------------------------------------------------------------------------------------------*/
unsigned char Put_Byte_in_FIFO( unsigned int FIFO, unsigned char Byte )
{
// FIFO auf gültigkeit testen
if ( ( FIFO < MAX_FIFO_BUFFERS ) && ( FIFO_Table[ FIFO ].lock == UNLOCK ) )
{
// passen noch byte in den Puffer
if ( FIFO_Table[ FIFO ].byteinbuffer < FIFO_Table[ FIFO ].bufferlenght )
{
// Byte in Puffer schreiben
FIFO_Table[ FIFO ].buffer[ FIFO_Table[ FIFO ].writepointer ] = Byte;
// writepointer schon das ende erreicht ? und setzen
if ( FIFO_Table[ FIFO ].writepointer == FIFO_Table[ FIFO ].bufferlenght )
FIFO_Table[ FIFO ].writepointer = 0;
else
FIFO_Table[ FIFO ].writepointer++;
FIFO_Table[ FIFO ].byteinbuffer++;
return( FIFO_OK );
}
}
return( FIFO_ERROR );
}
/*-----------------------------------------------------------------------------------------------------------*/
/*!\brief Holt ein Byte aus den FIFO.
* \param FIFO Nummer des FIFO.
* \return byte Byte aus dem FIFO.
*/
/*------------------------------------------------------------------------------------------------------------*/
unsigned int Get_FIFO_to_FIFO( unsigned int Src_FIFO, unsigned int bufferlenght, unsigned int Dest_FIFO )
{
unsigned char byte;
unsigned int i=0;
// FIFO auf gültigkeit testen
if ( Src_FIFO < MAX_FIFO_BUFFERS && Dest_FIFO < MAX_FIFO_BUFFERS )
{
// sind noch bytes in den Puffer
if ( FIFO_Table[ Src_FIFO ].byteinbuffer >= bufferlenght && FIFO_Table[ Dest_FIFO ].byteinbuffer >= bufferlenght)
{
for( i = 0; i < bufferlenght ; i++ )
{
byte = FIFO_Table[ Src_FIFO ].buffer[ FIFO_Table[ Src_FIFO ].readpointer ];
// readpointer schon das ende erreicht ? und setzen
if ( FIFO_Table[ Src_FIFO ].readpointer == FIFO_Table[ Src_FIFO ].bufferlenght )
{
FIFO_Table[ Src_FIFO ].readpointer = 0;
}
else
{
FIFO_Table[ Src_FIFO ].readpointer++;
}
FIFO_Table[ Src_FIFO ].byteinbuffer--;
// Byte in Puffer schreiben
FIFO_Table[ Dest_FIFO ].buffer[ FIFO_Table[ Dest_FIFO ].writepointer ] = byte;
// writepointer schon das ende erreicht ? und setzen
if ( FIFO_Table[ Dest_FIFO ].writepointer == FIFO_Table[ Dest_FIFO ].bufferlenght )
FIFO_Table[ Dest_FIFO ].writepointer = 0;
else
FIFO_Table[ Dest_FIFO ].writepointer++;
FIFO_Table[ Dest_FIFO ].byteinbuffer++;
i++;
}
}
else
{
return(1);
}
}
return( i );
}
/*-----------------------------------------------------------------------------------------------------------*/
/*!\brief Holt ein Byte aus den FIFO.
* \param FIFO Nummer des FIFO.
* \return byte Byte aus dem FIFO.
*/
/*------------------------------------------------------------------------------------------------------------*/
unsigned int Get_Block_from_FIFO( unsigned int FIFO, unsigned int bufferlenght, unsigned char * buffer )
{
unsigned char byte;
unsigned int i=0;
// FIFO auf gültigkeit testen
if ( ( FIFO < MAX_FIFO_BUFFERS ) && ( FIFO_Table[ FIFO ].lock == UNLOCK ) )
{
// sind noch bytes in den Puffer
if ( FIFO_Table[ FIFO ].byteinbuffer >= bufferlenght )
{
for( i = 0; i < bufferlenght ; i++ )
{
buffer[ i ] = FIFO_Table[ FIFO ].buffer[ FIFO_Table[ FIFO ].readpointer ];
// readpointer schon das ende erreicht ? und setzen
if ( FIFO_Table[ FIFO ].readpointer == FIFO_Table[ FIFO ].bufferlenght )
{
FIFO_Table[ FIFO ].readpointer = 0;
}
else
{
FIFO_Table[ FIFO ].readpointer++;
}
FIFO_Table[ FIFO ].byteinbuffer--;
}
i++;
}
}
return( i );
}
/*-----------------------------------------------------------------------------------------------------------*/
/*!\brief Holt ein Byte aus den FIFO.
* \param FIFO Nummer des FIFO.
* \return byte Byte aus dem FIFO.
*/
/*------------------------------------------------------------------------------------------------------------*/
unsigned char Get_Byte_from_FIFO( unsigned int FIFO )
{
unsigned char byte;
// FIFO auf gültigkeit testen
if ( ( FIFO < MAX_FIFO_BUFFERS ) && ( FIFO_Table[ FIFO ].lock == UNLOCK ) )
{
// sind noch bytes in den Puffer
if ( FIFO_Table[ FIFO ].byteinbuffer != 0 )
{
// Byte aus Puffer lesen
byte = FIFO_Table[ FIFO ].buffer[ FIFO_Table[ FIFO ].readpointer ];
// readpointer schon das ende erreicht ? und setzen
if ( FIFO_Table[ FIFO ].readpointer == FIFO_Table[ FIFO ].bufferlenght )
{
FIFO_Table[ FIFO ].readpointer = 0;
}
else
{
FIFO_Table[ FIFO ].readpointer++;
}
FIFO_Table[ FIFO ].byteinbuffer--;
return( byte );
}
}
return( 0 );
}
/*-----------------------------------------------------------------------------------------------------------*/
/*!\brief Leert den FIFO.
* \param FIFO Nummer des FIFO.
* \return Returncode FIFO_OK oder FIFO_ERROR.
*/
/*------------------------------------------------------------------------------------------------------------*/
unsigned int Flush_FIFO( unsigned int FIFO )
{
if ( ( FIFO < MAX_FIFO_BUFFERS ) && ( FIFO_Table[ FIFO ].lock == UNLOCK ) )
{
FIFO_Table[ FIFO ].readpointer = 0;
FIFO_Table[ FIFO ].writepointer = 0;
FIFO_Table[ FIFO ].byteinbuffer = 0;
return( FIFO_OK );
}
return( FIFO_ERROR );
}
unsigned int Get_FIFOsize( unsigned int FIFO )
{
if ( FIFO < MAX_FIFO_BUFFERS )
return( FIFO_Table[ FIFO ].bufferlenght );
return( FIFO_ERROR );
}
unsigned int Get_FIFOrestsize( unsigned int FIFO )
{
if ( FIFO < MAX_FIFO_BUFFERS )
return( FIFO_Table[ FIFO ].bufferlenght - FIFO_Table[ FIFO ].byteinbuffer );
return( FIFO_ERROR );
}
//@}
fifo.h
/*! \file fifo.h \brief Stellt die FIFO Funkionalitaet bereit */
//***************************************************************************
//* fifo.h
//*
//* Sat Jun 3 23:01:42 2006
//* Copyright 2006 User
//* Email
//****************************************************************************/
/// \ingroup system
/// \defgroup FIFO Die FIFO-Puffer funktionalität (fifo.c)
//****************************************************************************/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
//@{
#ifndef _FIFO_H
#define FIFO_H
unsigned int Get_FIFO( unsigned char * buffer, unsigned int bufferlenght );
unsigned char Free_FIFO ( unsigned int FIFO );
unsigned int Get_Bytes_in_FIFO( unsigned int FIFO );
unsigned char Put_Byte_in_FIFO( unsigned int FIFO, unsigned char Byte );
unsigned int Get_Block_from_FIFO( unsigned int FIFO, unsigned int bufferlenght, unsigned char * buffer );
unsigned char Get_Byte_from_FIFO( unsigned int FIFO );
unsigned int Flush_FIFO( unsigned int FIFO );
unsigned int Get_FIFOsize( FIFO );
unsigned int Get_FIFOrestsize( FIFO );
unsigned int Get_FIFO_to_FIFO( unsigned int Src_FIFO, unsigned int bufferlenght, unsigned int Dest_FIFO );
#define MAX_FIFO_BUFFERS 16
#define FIFO_ERROR 0xffff
#define FIFO_OK 0x00
#define UNLOCK 0xff
#define LOCK 0x00
#define NULL 0
struct FIFO {
unsigned char * buffer;
unsigned int bufferlenght;
unsigned int writepointer;
unsigned int readpointer;
unsigned int byteinbuffer;
unsigned char lock;
};
#endif /* FIFO_H */
//@}
Abkürzungsverzeichnis
| AD | Ananlog-Digital |
| ADC | Analog-Digital Wandler |
| ALE | Activate Latch-Enable Leitung |
| ALU | Arithmetic -Logical.-Unit |
| ARP | Adress Routing Protocol |
| AVR | "Alf (Egil Bogen) and Vegard (Wollan) 's Risc processor" |
| AVR GCC | AVR GNU C complier |
| CPU | Central Processing Unit |
| CS | Chip Select |
| DA | Digital-Analog |
| DHCP | Dynamic-Host-Control Protocol |
| DIN | Deutsche Industrie Norm |
| DNS | Domain Name Service |
| DOS | Diskette Operating System |
| EEPROM | Electrically Ereasable Programmable Read Only Memory |
| FIFO | First In First Out (Pipe) |
| HPGL | HP Plotter language |
| HTTP | Hyper Text Terminal Protocol |
| I/O | IN/OUT |
| I2C | Inter Ic Communication |
| ICMP | Internet Controll Messages Protocoll |
| IEC | International Engineering Consortium |
| IEE | |
| INT | Interrupt-Leitung |
| IP | Internet Protocol |
| IPv4 | Internet Protocol 4 Standard: 4 Oktette |
| IPv6 | Internet Protocol 6 Standard: 6 Oktette |
| IRQ | Interrupt Request |
| ISP | In System Prorammierung |
| ISR | Interrupt Service Routine |
| JTAG | Joint Test Action Group |
| LCD | Liquid Crystal Display |
| LED | Light Emitting Diode |
| lib | library |
| LIFO | Last In First Out (Stack) |
| MIL | milli Inch |
| MISO | Master In Slave Out |
| MOSI | Master Out Slave In |
| NTP | Net Time Protocoll |
| PWM | Pulse Weiten Modulation |
| RAM | Random Access Memory |
| RD | Read |
| RISC | Reduce Intruction Set Controller |
| ROM | Read Only Memory |
| RS 232 | Schnitstelle |
| RTC | Real Time Clock |
| RX | Recieve - empfangen |
| SCK | Serial Clock Signal |
| SDI | Serial Data In |
| SDO | Serial Data Out |
| SMD | Surface Mount Device |
| SPI | Serial Programming Interface |
| SRAM | Static Random Access Memory |
| SS | Slave Select |
| STE | |
| TCP | Transmission Controll Protocoll |
| TELNET | Terminalprogramm unter Unix |
| TX | Transmitting - Senden |
| UART | Universal Asynchron Remote Transmit |
| UDP | User Data Protocoll |
| WR | Write |
Inhaltsverzeichnis
Inhaltsverzeichnis
1 Zum Projekt
1.1 Aufgabenstellung
1.2 Planung
1.3 Durchführung
2 Hardware
2.1 Spannungsversorgung
2.2 ATmega 1281/2561
2.2.1 Takt
2.2.1.1 Interner RC Oszillator
2.2.1.2 externer Quarz
2.2.1.3 externer TTL-Takt
2.2.2 Speicher
2.2.2.1 Flash
2.2.2.2 Interner RAM
2.2.2.3 EEProm
2.2.2.4 Externe Speicherschnittstelle
2.2.3 I/O Ports
2.2.4 Interrupts
2.2.5 Timer/Counter
2.2.6 Watchdog
2.2.7 Komperator
2.2.8 Programmierung
2.3 SPI-BUS
2.4 UART
2.5 MAX232
2.6 D-Sub 9 - Buchse
2.7 ENC28j60
2.7.1 Der ENC28j60
2.7.2 Kommunikation und Anbindung des ENC28j60
2.7.3 Betriebsarten Half/Full-Duplex
2.8 Ethernetübertrager
2.9 Speicherbaustein
2.10 Schaltplan
2.11 Platinendesign
2.11.1 Board
2.11.2 Isolationsfräsen
2.11.3 Bestücken und Testen
3 Software
3.1 Entwicklungsumgebung
3.1.1 AVR-GCC
3.1.2 avrdude / avrISPmkII
3.1.3 doxygen
3.2 Grundgedanken zum Aufbau der Software
3.3 Trennung von System-, Treiber-funktionen und Anwendungen
3.4 Bereitstellung der Grundfunktionen
3.4.1 TIMER
3.4.1.1 Taktquelle der Real Time Clock
3.4.1.2 Callback Funktionen
3.4.1.3 Clock / RTC
3.4.1.4 Counter
3.4.2 FIFO und LIFO
3.4.3 UART / RS232
3.4.4 SPI
3.4.4.1 Byteübertragung
3.4.4.2 Blockübertragung
3.4.5 Der ENC28j60
3.4.5.1 Interruptbetrieb des ENJ28j60
3.5 Der Netzwerkstack
3.5.1 Ethernet
3.5.2 IPv4
3.5.3 ARP
3.5.3.1 ICMP
3.5.3.2 UDP
3.5.3.3 TCP
3.6 Netzwerkfunktionen
3.6.1 NTP
3.6.2 DNS
4 Anwendungsbeispiele
4.1 UDP-Echoserver
4.2 Telnet-Server
4.3 HTTP-Server
4.4 MP3-Streamingclient
5 Anhang
5.1 Technische Daten
5.2 Bauteileliste
5.3 Interrupt-Vektor-Tabelle ATmega 2561
7 Literaturverzeichnis
8 Danksagung
Literaturverzeichnis
"TCP/IP" (2.Auflage) , Verlag: O'Reilly, Autor: Craig Hund, ISBN: 3-89721-110-6
"Mikrocontroller und Mikroprozessoren", Verlag: Springer, Autor: Brinkschulte & Ungerer, ISBN:3-540-43095-4
"Extreme Programming", Verlag: Addision-Wesley, Autor: Kent Beck, ISBN: 3-8273-2139-5
"Duden Informatik", Verlag: Duden, Autor: / , ISBN: 3-411-04484-5
"make GE-PACKT", Verlag: mitp, Autor: Thomas Peschel-Findeisen, ISBN: 3-8266-1442-9
"Die Psychologie des Programmierers", Verlag: mitp, Autor: Gerald M. Weinberg, ISBN: 3-8266-1465-8
"TCP/IP Illustrated, Volume 1", Verlag: Addision-Wesley, Autor: W.Richard Stevens, ISBN: 0-201-63346-9
"TCP/IP", Verlag: bhv, Autor: Thomas Zwolsky, ISBN: 3-8266-8079-0
"TCP/IP" (2.Auflage), Verlag: bhv, Autor: Dirk Larisch, ISBN: 3-8266-7365-4
Quellenverzeichnis
http://www.atmel.com/dyn/resources/prod_documents/doc2549.pdf ( Stand: 16.Mai 2008 )
http://de.wikipedia.org/wiki/Transmission_Control_Protocol ( Stand: 17.Mai 2008 )
http://www.rvs.uni-bielefeld.de/~heiko/tcpip/tcpip.html ( Stand: 17.Mai 2008 )
http://www.vlsi.fi/en/products/vs1001.html ( Stand: 18.Mai 2008 )
http://www.myplace.nu/mp3/ ( Stand: 18.Mail 2008 )
Danksagung
Selbständigkeitserklärung
Wir erklären hiermit, das wir diese Projektarbeit selbständig verfasst und keine anderen als die angegebenen Quellen benutzt haben. Alle Stellen, die wörtlich oder sinngemäß aus Quellen entnommen wurden, haben wir als solche gekennzeichnet. Uns ist bekannt, dass andernfalls die Staatliche Technikerschule Berlin gemäß dem Gesetz zum Entzug des aufgrund dieser Projektarbeit verliehenen Zeugnisses berechtigt ist.
Berlin, ___.___.200_ Unterschrift Studierender.........................
Berlin, ___.___.200_ Unterschrift Studierender.........................


