<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="de">
	<id>https://www.mikrocontroller.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Klaus2m5</id>
	<title>Mikrocontroller.net - Benutzerbeiträge [de]</title>
	<link rel="self" type="application/atom+xml" href="https://www.mikrocontroller.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Klaus2m5"/>
	<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/articles/Spezial:Beitr%C3%A4ge/Klaus2m5"/>
	<updated>2026-04-10T22:27:48Z</updated>
	<subtitle>Benutzerbeiträge</subtitle>
	<generator>MediaWiki 1.39.7</generator>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=89580</id>
		<title>Datei:Sam.zip</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=89580"/>
		<updated>2015-08-17T08:01:51Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: Klaus2m5 lud eine neue Version von Datei:Sam.zip hoch&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Structured Assembly Macros&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=89574</id>
		<title>Datei:Sam.zip</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=89574"/>
		<updated>2015-08-16T06:48:22Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: Klaus2m5 lud eine neue Version von Datei:Sam.zip hoch&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Structured Assembly Macros&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_6502_Emulator&amp;diff=80809</id>
		<title>AVR 6502 Emulator</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_6502_Emulator&amp;diff=80809"/>
		<updated>2014-01-11T14:38:54Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: weitere AVR basierte Emulatoren hinzugefügt&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 6502-Emulation auf Atmel AVR-Controllern ==&lt;br /&gt;
&lt;br /&gt;
Diese Seite faßt Diskussionen aus dem Thread &amp;quot;KIM-1 in AVR?&amp;quot; ([http://www.mikrocontroller.net/topic/193632]) zusammen und soll als Sammelstelle für weitere Informationen und zur Koordination der Entwicklung einer 6502-Emulationsbibliothek für AVR-Controller dienen.&lt;br /&gt;
&lt;br /&gt;
== Warum? ==&lt;br /&gt;
&lt;br /&gt;
In &amp;quot;alten&amp;quot; Homecomputer-Zeiten war der MOS 6502 und seine Derivate - neben dem Z80 von Zilog - die CPU der Wahl für (damals) schnelle, kostengünstige Systeme. Beispiele sind die Commodore-Systeme (C64, VC20, C128, PET, CBM), ATARI XL/XE und Apple II. Aber auch Spielekonsolen der 80er-Jahre verwenden vielmals einen 6502 - Atari 2600 und Nintendo NES sind wohl die bekanntesten Beispiele&lt;br /&gt;
&lt;br /&gt;
Mit einer Taktfrequenz von 1 MHz ist die verfügbare Rechenleistung eher gering. Daraus entstand der Gedanke, ein 8-Bit-System der 80er auf einem modernen, kostengünstigen 8-Bit-Controller in Software zu emulieren. Ein Projekt, das den intel 8080 (Vorgänger des Z80) emuliert und das damalige Standardbetriebssystem CP/M ausführen kann, ist in Ursprüngen [http://spritesmods.com/?art=avrcpm hier] zu finden und wird in [http://www.mikrocontroller.net/topic/177481 diesem Thread] weiterentwickelt (siehe auch die [http://www.mikrocontroller.net/articles/AVR_CP/M Projektseite]).&lt;br /&gt;
&lt;br /&gt;
Was mit einem 8080 möglich ist, sollte für den 6502 auch möglich sein! Das dürfte genug Herausforderung sein - die &amp;quot;Grabenkämpfe&amp;quot; der 80er-Jahre fanden meist zwischen 8080/Z80-Anhängern und 6502-Begeisterten statt...&lt;br /&gt;
&lt;br /&gt;
== Eine neue Idee? ==&lt;br /&gt;
&lt;br /&gt;
Einen 6502 auf AVR zu emulieren ist keine neue Idee. Es existieren bereits einige Projekte, die mehr oder weniger erfolgreich einen 6502 emulieren:&lt;br /&gt;
&lt;br /&gt;
[http://instruct1.cit.cornell.edu/courses/ee476/FinalProjects/s2009/bhp7_teg25/bhp7_teg25/index.html NES-Emulator] - Projekt von Studenten der Cornell University.&lt;br /&gt;
&lt;br /&gt;
[http://instruct1.cit.cornell.edu/courses/ee476/FinalProjects/s2007/bcr22/final%20webpage/final.html Apple II-Emulator], ebenfalls von Cornell.&lt;br /&gt;
&lt;br /&gt;
[http://www.belanger.pwp.blueyonder.co.uk/Projects/Vic%20Emu/vicemu.htm Commodore VC20-Emulator] (6502, VIC, Display, Tastatur) als AVR-Studio Assembler-Projekt.&lt;br /&gt;
&lt;br /&gt;
[http://sites.google.com/site/retroelec/ ATMega based home computer] - ATMega162 emuliert 6502 mit SCART Video Ausgang, PS2 Tastatur und SD Kartenslot.&lt;br /&gt;
&lt;br /&gt;
[http://sbc.rictor.org/avr65c02.html AVR 65C02 Emulator] - ATMega1284 emuliert 65C02 mit on Chip RAM und ROM, erlaubt direkten Zugriff der emulierten CPU auf die AVR IO-Register.&lt;br /&gt;
&lt;br /&gt;
[http://2m5.de/6502_Emu/index.htm Modular 6502 SBC with emulated CPU] - ATMega16 emuliert wahlweise 6502 oder 65C02 mit variabler IO-Konfiguration.&lt;br /&gt;
&lt;br /&gt;
== Projekt: 6502-Emulations-Bibliothek ==&lt;br /&gt;
&lt;br /&gt;
Ein Ziel des Projektes soll es sein, eine universelle 6502-Emulations-Bibliothek für AVR-Mikrocontroller zu erstellen, die dann Basis für weitere Entwicklungen (Systememulatoren für KIM-1, Commodore 1541 usw.) sein soll. &lt;br /&gt;
&lt;br /&gt;
Eine optionale Anforderung an die Emulation ist Zyklenexaktheit der Software. Dies wird z.B. für die Verwendung von Floppy-Speedern für die Emulation des Commodore 1541-Diskettenlaufwerks benötigt. &lt;br /&gt;
&lt;br /&gt;
Wenn Zyklenexaktheit erreicht werden kann, ist die nächste Frage, ob die Software-6502-Emulation auch (mit Hilfe einer Adapterplatine) als pinkompatibler   Ersatz für einen &amp;quot;echten&amp;quot; 6502/6510 dienen kann. Dies ist aktuell unter Diskussion.&lt;br /&gt;
&lt;br /&gt;
== KIM 1-Emulation ==&lt;br /&gt;
&lt;br /&gt;
Der KIM-1 war eine Entwicklungsplattform für den 6502. Die Idee, den KIM-1 als Software-Emulation im AVR zu realisieren, war der Startpunkt für dieses Projekt. Der Fortschritt des Projekts wird [http://t-winkler.net/dokuwiki/doku.php?id=kim:kim1284 hier] beschrieben.&lt;br /&gt;
&lt;br /&gt;
== Commodore 1541-Emulation ==&lt;br /&gt;
&lt;br /&gt;
Bisherige Emulationsprojekte basierten auf einer Reimplementierung des IEC-Bus-Protokolls sowie der Funktionalität der 1541-Firmware (DOS), bekanntestes Projekt hier ist [http://www.c64-wiki.com/index.php/sd2iec_%28firmware%29 sd2iec].&lt;br /&gt;
&lt;br /&gt;
Eine komplette Hardware-Reimplementierung der 1541-Elektronik auf FPGA findet sich bei [http://commodore-gg.hobby.nl/innovatie_1541kaart_eng.htm 1541 &amp;quot;Ultimate&amp;quot;]und [http://www.das-labor.org/wiki/1541_im_FPGA das Labor].&lt;br /&gt;
&lt;br /&gt;
Ideen zur 1541-Realisierung auf AVR mit 6502-Emulator finden sich [http://t-winkler.net/dokuwiki/doku.php?id=1541-emul:start hier].&lt;br /&gt;
&lt;br /&gt;
== Alternativen? ==&lt;br /&gt;
&lt;br /&gt;
Es gibt jede Menge 6502-Implementierungen für FPGAs (in VHDL und Verilog):&lt;br /&gt;
&lt;br /&gt;
[http://www.syntiac.com/fpga64.html Peter Wendrichs C64 für FPGAs]&lt;br /&gt;
&lt;br /&gt;
[http://www.fpgaarcade.com/vic20_main.htm VC20 im FPGA bei fpgaarcade]&lt;br /&gt;
&lt;br /&gt;
[http://alexfreed.com/FPGApple/revisited.html Apple II-Emulator auf Altera DE1 Evaluationsboard]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Projekte bei Opencores:&lt;br /&gt;
&lt;br /&gt;
[http://opencores.org/project,6502vhdl 6502 in VHDL]&lt;br /&gt;
&lt;br /&gt;
[http://opencores.org/project,t65 T65 CPU (VHDL)]&lt;br /&gt;
&lt;br /&gt;
[http://opencores.org/project,cpu6502_true_cycle Zyklengenauer 6502 (VHDL)]&lt;br /&gt;
&lt;br /&gt;
[http://opencores.org/project,lattice6502 6502 in VHDL für Lattice FPGAs]&lt;br /&gt;
&lt;br /&gt;
Software-Emulation des 6502:&lt;br /&gt;
&lt;br /&gt;
[http://www.piumarta.com/software/lib6502/ lib6502 (C) von Ian Piumarta]&lt;br /&gt;
&lt;br /&gt;
[http://www.6502.org/tools/emu/la6502emu.txt 6502-Emulation in 68k-Assembler]&lt;br /&gt;
&lt;br /&gt;
[http://www.6502asm.com/ 6502-Emulation in Javascript]&lt;br /&gt;
&lt;br /&gt;
[http://mdawson.net/vic20beta/vic20.php VC-20 Emulation in Javascript]&lt;br /&gt;
&lt;br /&gt;
Software-Simulation des 6502:&lt;br /&gt;
&lt;br /&gt;
[http://www.visual6502.org/ Simulation des 6502 auf Transistorebene]&lt;br /&gt;
&lt;br /&gt;
[[Kategorie:AVR-Projekte]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=70782</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=70782"/>
		<updated>2013-01-11T14:37:52Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
[[Bild:I2C-slave_USART.png]]&lt;br /&gt;
&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Baudrate, I2C-Adresse und Einstellungen zur Flusskontrolle haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
&lt;br /&gt;
Bugfix Version 1.1a 11.1.2013 - Resolved conflict of the sniffer state machine with the slave I2C state.&lt;br /&gt;
&lt;br /&gt;
[[Media:I2C-slave_USART_11a.zip|I2C-slave_USART.zip]]&lt;br /&gt;
&lt;br /&gt;
[[Kategorie:AVR-Projekte]]&lt;br /&gt;
[[Kategorie:I2C]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:I2C-slave_USART_11a.zip&amp;diff=70781</id>
		<title>Datei:I2C-slave USART 11a.zip</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:I2C-slave_USART_11a.zip&amp;diff=70781"/>
		<updated>2013-01-11T14:35:54Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=70780</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=70780"/>
		<updated>2013-01-11T14:33:06Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
[[Bild:I2C-slave_USART.png]]&lt;br /&gt;
&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Baudrate, I2C-Adresse und Einstellungen zur Flusskontrolle haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
&lt;br /&gt;
Bugfix Version 1.1a 11.1.2013 - Resolved conflict of the sniffer state machine with the slave I2C state.&lt;br /&gt;
&lt;br /&gt;
[[Media:I2C-slave_USART.zip|I2C-slave_USART.zip]]&lt;br /&gt;
&lt;br /&gt;
[[Kategorie:AVR-Projekte]]&lt;br /&gt;
[[Kategorie:I2C]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=70779</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=70779"/>
		<updated>2013-01-11T14:19:43Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Zukünftige Erweiterungen */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
[[Bild:I2C-slave_USART.png]]&lt;br /&gt;
&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Baudrate, I2C-Adresse und Einstellungen zur Flusskontrolle haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
&lt;br /&gt;
[[Media:I2C-slave_USART.zip|I2C-slave_USART.zip]]&lt;br /&gt;
&lt;br /&gt;
[[Kategorie:AVR-Projekte]]&lt;br /&gt;
[[Kategorie:I2C]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=68922</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=68922"/>
		<updated>2012-11-01T18:38:56Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Align */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Location Pointer ==&lt;br /&gt;
=== Align ===&lt;br /&gt;
Manchmal ist es notwendig, Tabellen oder Puffer auf eine bestimmte Byte- oder Wortgrenze zu bringen, z.B. um beim Zugriff einen Überlauf des Index in das obere Adressbyte zu verhindern.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro      align   ;align to 1&amp;lt;&amp;lt;@0&lt;br /&gt;
alignfromhere:&lt;br /&gt;
      .if (alignfromhere &amp;amp; ((1&amp;lt;&amp;lt;@0)-1))   ;if not already aligned&lt;br /&gt;
         .org  (alignfromhere &amp;amp; (0xffff&amp;lt;&amp;lt;@0)) + (1&amp;lt;&amp;lt;@0)&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
@0 bezeichnet die Anzahl rechtsbündiger binärer Nullen.&lt;br /&gt;
Beispiel: align 8 setzt die nächste Adresse auf $xx00.&lt;br /&gt;
Funktioniert in allen Segmenten.&lt;br /&gt;
&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Strukturierte Programmierung ==&lt;br /&gt;
&lt;br /&gt;
===SAM (Structured Assembly Macros)===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
SAM unterstützt strukturiertes Programmieren durch If-Then-Else und Do-Loop Makros. Beliebige Verschachtelung und Mehrfachbedingungen sind möglich. Läuft unter aktuellen Versionen von AVRASM2.&lt;br /&gt;
&lt;br /&gt;
Einige Beispiele:&lt;br /&gt;
 &lt;br /&gt;
Verschachteltes If-Then-Else&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   r16,&#039;a&#039;&lt;br /&gt;
            ifeq  a_chr&lt;br /&gt;
              cpi   r17,&#039;b&#039;&lt;br /&gt;
              ifeq   a_and_b&lt;br /&gt;
                ;...a&amp;amp;b&lt;br /&gt;
              else   a_and_b&lt;br /&gt;
                ;...a&amp;amp;-b&lt;br /&gt;
              end    a_and_b&lt;br /&gt;
            else  a_chr&lt;br /&gt;
              cpi r17,&#039;c&#039;&lt;br /&gt;
              ifeq  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;+c&lt;br /&gt;
              else  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;c&lt;br /&gt;
              end   c_but_no_a&lt;br /&gt;
            end   a_chr&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Mehrere und/oder verknüpfte Bedingungen&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   zh,high(end_buffer)&lt;br /&gt;
            ifeq_and    end_buffer_reached&lt;br /&gt;
            cpi   zl,low(end_buffer)&lt;br /&gt;
            ifeq        end_buffer_reached&lt;br /&gt;
              ldi   zh,high(buffer)       ;wrap buffer&lt;br /&gt;
              ldi   zl,low(buffer)&lt;br /&gt;
            end         end_buffer_reached&lt;br /&gt;
            ld    r0,z+                   ;read buffer&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Das Gleiche als Do-Loop&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; reading a buffer until end&lt;br /&gt;
; similar to: for z = buffer to end_buffer&lt;br /&gt;
            ldi   zh,high(buffer)   ;init buffer pointer&lt;br /&gt;
            ldi   zl,low(buffer)&lt;br /&gt;
            do    read_buf&lt;br /&gt;
              ld    r0,z+&lt;br /&gt;
              cpi   zh,high(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
              cpi   zl,low(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehr Beispiele und das SAM-include als [[Media:sam.zip|Download.]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=68921</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=68921"/>
		<updated>2012-11-01T18:34:26Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Align */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Location Pointer ==&lt;br /&gt;
=== Align ===&lt;br /&gt;
Manchmal ist es notwendig, Tabellen oder Puffer auf eine bestimmte Byte- oder Wortgrenze zu bringen, z.B. um beim Zugriff einen Überlauf des Index in das obere Adressbyte zu verhindern.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;align to 1&amp;lt;&amp;lt;@0&lt;br /&gt;
.macro      align&lt;br /&gt;
alignfromhere:&lt;br /&gt;
      .if (alignfromhere &amp;amp; ((1&amp;lt;&amp;lt;@0)-1))   ;if not already aligned&lt;br /&gt;
         .org  (alignfromhere &amp;amp; (0xffff&amp;lt;&amp;lt;@0)) + (1&amp;lt;&amp;lt;@0)&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
@0 bezeichnet die Anzahl rechtsbündiger binärer Nullen.&lt;br /&gt;
Beispiel: align 8 setzt die nächste Adresse auf $xx00.&lt;br /&gt;
Funktioniert in allen Segmenten.&lt;br /&gt;
&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Strukturierte Programmierung ==&lt;br /&gt;
&lt;br /&gt;
===SAM (Structured Assembly Macros)===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
SAM unterstützt strukturiertes Programmieren durch If-Then-Else und Do-Loop Makros. Beliebige Verschachtelung und Mehrfachbedingungen sind möglich. Läuft unter aktuellen Versionen von AVRASM2.&lt;br /&gt;
&lt;br /&gt;
Einige Beispiele:&lt;br /&gt;
 &lt;br /&gt;
Verschachteltes If-Then-Else&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   r16,&#039;a&#039;&lt;br /&gt;
            ifeq  a_chr&lt;br /&gt;
              cpi   r17,&#039;b&#039;&lt;br /&gt;
              ifeq   a_and_b&lt;br /&gt;
                ;...a&amp;amp;b&lt;br /&gt;
              else   a_and_b&lt;br /&gt;
                ;...a&amp;amp;-b&lt;br /&gt;
              end    a_and_b&lt;br /&gt;
            else  a_chr&lt;br /&gt;
              cpi r17,&#039;c&#039;&lt;br /&gt;
              ifeq  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;+c&lt;br /&gt;
              else  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;c&lt;br /&gt;
              end   c_but_no_a&lt;br /&gt;
            end   a_chr&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Mehrere und/oder verknüpfte Bedingungen&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   zh,high(end_buffer)&lt;br /&gt;
            ifeq_and    end_buffer_reached&lt;br /&gt;
            cpi   zl,low(end_buffer)&lt;br /&gt;
            ifeq        end_buffer_reached&lt;br /&gt;
              ldi   zh,high(buffer)       ;wrap buffer&lt;br /&gt;
              ldi   zl,low(buffer)&lt;br /&gt;
            end         end_buffer_reached&lt;br /&gt;
            ld    r0,z+                   ;read buffer&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Das Gleiche als Do-Loop&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; reading a buffer until end&lt;br /&gt;
; similar to: for z = buffer to end_buffer&lt;br /&gt;
            ldi   zh,high(buffer)   ;init buffer pointer&lt;br /&gt;
            ldi   zl,low(buffer)&lt;br /&gt;
            do    read_buf&lt;br /&gt;
              ld    r0,z+&lt;br /&gt;
              cpi   zh,high(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
              cpi   zl,low(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehr Beispiele und das SAM-include als [[Media:sam.zip|Download.]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=68920</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=68920"/>
		<updated>2012-11-01T18:25:31Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Delay */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Location Pointer ==&lt;br /&gt;
=== Align ===&lt;br /&gt;
Manchmal ist es notwendig, Tabellen oder Puffer auf eine bestimmte Byte- oder Wortgrenze zu bringen, z.B. um beim Zugriff einen Überlauf des Index in das obere Adressbyte zu verhindern.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;align to 1&amp;lt;&amp;lt;@0&lt;br /&gt;
.macro      align&lt;br /&gt;
alignfromhere:&lt;br /&gt;
      .if (alignfromhere &amp;amp; ((1&amp;lt;&amp;lt;@0)-1))   ;if not already aligned&lt;br /&gt;
         .org  (alignfromhere &amp;amp; (0xffff&amp;lt;&amp;lt;@0)) + (1&amp;lt;&amp;lt;@0)&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Beispiel: align 8 setzt die nächste Adresse auf $xx00.&lt;br /&gt;
Funktioniert in allen Segmenten.&lt;br /&gt;
&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Strukturierte Programmierung ==&lt;br /&gt;
&lt;br /&gt;
===SAM (Structured Assembly Macros)===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
SAM unterstützt strukturiertes Programmieren durch If-Then-Else und Do-Loop Makros. Beliebige Verschachtelung und Mehrfachbedingungen sind möglich. Läuft unter aktuellen Versionen von AVRASM2.&lt;br /&gt;
&lt;br /&gt;
Einige Beispiele:&lt;br /&gt;
 &lt;br /&gt;
Verschachteltes If-Then-Else&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   r16,&#039;a&#039;&lt;br /&gt;
            ifeq  a_chr&lt;br /&gt;
              cpi   r17,&#039;b&#039;&lt;br /&gt;
              ifeq   a_and_b&lt;br /&gt;
                ;...a&amp;amp;b&lt;br /&gt;
              else   a_and_b&lt;br /&gt;
                ;...a&amp;amp;-b&lt;br /&gt;
              end    a_and_b&lt;br /&gt;
            else  a_chr&lt;br /&gt;
              cpi r17,&#039;c&#039;&lt;br /&gt;
              ifeq  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;+c&lt;br /&gt;
              else  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;c&lt;br /&gt;
              end   c_but_no_a&lt;br /&gt;
            end   a_chr&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Mehrere und/oder verknüpfte Bedingungen&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   zh,high(end_buffer)&lt;br /&gt;
            ifeq_and    end_buffer_reached&lt;br /&gt;
            cpi   zl,low(end_buffer)&lt;br /&gt;
            ifeq        end_buffer_reached&lt;br /&gt;
              ldi   zh,high(buffer)       ;wrap buffer&lt;br /&gt;
              ldi   zl,low(buffer)&lt;br /&gt;
            end         end_buffer_reached&lt;br /&gt;
            ld    r0,z+                   ;read buffer&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Das Gleiche als Do-Loop&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; reading a buffer until end&lt;br /&gt;
; similar to: for z = buffer to end_buffer&lt;br /&gt;
            ldi   zh,high(buffer)   ;init buffer pointer&lt;br /&gt;
            ldi   zl,low(buffer)&lt;br /&gt;
            do    read_buf&lt;br /&gt;
              ld    r0,z+&lt;br /&gt;
              cpi   zh,high(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
              cpi   zl,low(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehr Beispiele und das SAM-include als [[Media:sam.zip|Download.]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR-Tutorial:_LCD&amp;diff=62479</id>
		<title>AVR-Tutorial: LCD</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR-Tutorial:_LCD&amp;diff=62479"/>
		<updated>2011-12-16T16:40:18Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Benutzerdefinierte Zeichen */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Kaum ein elektronisches Gerät kommt heutzutage noch ohne ein LCD daher. Ist doch auch praktisch, Informationen im Klartext anzeigen zu können, ohne irgendwelche LEDs blinken zu lassen. Kein Wunder also, dass die häufigste Frage in Mikrocontroller-Foren ist: &amp;quot;Wie kann ich ein LCD anschließen?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==Das LCD und sein Controller==&lt;br /&gt;
&lt;br /&gt;
Die meisten Text-LCDs verwenden den Controller [[HD44780|&#039;&#039;&#039;HD44780&#039;&#039;&#039;]] oder einen kompatiblen (z.&amp;amp;nbsp;B. KS0070) und haben 14 oder 16 Pins.&amp;lt;br /&amp;gt;&lt;br /&gt;
Die Pinbelegung ist meist (Ausnahme z.&amp;amp;nbsp;B. TC1602E (Pollin 120420): V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt; und V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; vertauscht) folgendermaßen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;ACHTUNG: Es gibt Displays mit abweichender Anschluss-Belegung, falscher Anschluss kann zur Zerstörung führen! Daher immer das zugehörige Datenblatt zu Rate ziehen!&amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Einzelheiten unter [http://www.mikrocontroller.net/articles/HD44780 Artikel zum Controller HD44780]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
! Pin # || Bezeichnung || Funktion&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; (selten: V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt;)&lt;br /&gt;
||  GND (selten: +5 V)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt; (selten: V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt;)&lt;br /&gt;
||  +5 V (selten: GND)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt;, V0, V5&lt;br /&gt;
||  Kontrastspannung (-5 V / 0 V bis 5 V)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  4&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  RS&lt;br /&gt;
||  Register Select (0=Befehl/Status 1=Daten)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  5&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  RW&lt;br /&gt;
||  1=Read 0=Write&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  6&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  E&lt;br /&gt;
||  0=Disable 1=Enable&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  7&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB0&lt;br /&gt;
||  Datenbit 0&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  8&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB1&lt;br /&gt;
||  Datenbit 1&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  9&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB2&lt;br /&gt;
||  Datenbit 2&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  10&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB3&lt;br /&gt;
||  Datenbit 3&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  11&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB4&lt;br /&gt;
||  Datenbit 4&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  12&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB5&lt;br /&gt;
||  Datenbit 5&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  13&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB6&lt;br /&gt;
||  Datenbit 6&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  14&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB7&lt;br /&gt;
||  Datenbit 7&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  15&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  A&lt;br /&gt;
||  LED-Beleuchtung, meist Anode&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  16&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  K&lt;br /&gt;
||  LED-Beleuchtung, meist Kathode&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Achtung: Unbedingt von der richtigen Seite zu zählen anfangen! Meistens ist das Pin1-Pad eckig oder daneben eine kleine 1 auf der LCD-Platine, ansonsten im Datenblatt nachschauen.&lt;br /&gt;
&lt;br /&gt;
Bei der DIL-Version (2x7, 2x8 Kontakte) auch darauf achten, auf welcher Platinen-Seite der Stecker montiert wird: auf der falschen (meist hinteren) Seite sind dann die Flachbandleitungen 1 und 2, 3 und 4  usw. vertauscht. Das kann man kompensieren, indem man es auf der anderen Kabelseite genauso permutiert oder es auf dem Layout bewusst so legt (Stecker auf der Bottom-Seite plazieren). Man kann es NICHT kompensieren, indem man das Flachbandkabel auf der anderen Seite in den Stecker führt.&lt;br /&gt;
&lt;br /&gt;
Bei LCDs mit 16-poligem Anschluss sind die beiden letzten Pins für die Hintergrundbeleuchtung reserviert. Hier unbedingt das Datenblatt zu Rate ziehen. Die beiden Anschlüsse sind je nach Hersteller verdreht beschaltet. Falls kein Datenblatt vorliegt, kann man mit einem Durchgangsprüfer feststellen, welcher Anschluss mit Masse (GND) verbunden ist.&lt;br /&gt;
&lt;br /&gt;
V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; wird ganz einfach an GND angeschlossen und V&amp;lt;sub&amp;gt;CC&amp;lt;/sub&amp;gt;=V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt; an +5 V. V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt; = V0 = V5 kann man testweise auch an GND legen. Wenn das LCD dann zu dunkel sein sollte, muss man ein 10k&amp;amp;Omega;-Potentiometer zwischen GND und 5 V schalten, mit dem Schleifer an V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt;. Meist kann man den +5 V-Anschluss am Poti weglassen, da im Display ein Pull-up-Widerstand ist:&lt;br /&gt;
&lt;br /&gt;
[[Bild:LCD_Vee.gif|framed|center| Gewinnung der Kontrastspannung]]&lt;br /&gt;
&lt;br /&gt;
Wenn der Kontrast zu schwach sein sollte (z.B. bei tiefen Temperaturen), kann man anstelle von GND eine negative Spannung ans Kontrast-Poti legen. Diese kann bis -5 V gehen und kann leicht aus einem Timerpin des µC, einem Widerstand, zwei Dioden und zwei Kondensatoren erzeugt werden. So wird auch ein digital einstellbarer Kontrast mittels PWM ermöglicht.&lt;br /&gt;
&lt;br /&gt;
Es gibt zwei verschiedene Möglichkeiten zur Ansteuerung eines solchen Displays: den &#039;&#039;&#039;8-Bit-&#039;&#039;&#039; und den &#039;&#039;&#039;4-Bit-&#039;&#039;&#039;Modus.&lt;br /&gt;
* Für den &#039;&#039;&#039;8-Bit-Modus&#039;&#039;&#039; werden (wie der Name schon sagt) alle acht Datenleitungen zur Ansteuerung verwendet, somit kann durch einen Zugriff immer ein ganzes Byte übertragen werden.&lt;br /&gt;
* Der &#039;&#039;&#039;4-Bit-Modus&#039;&#039;&#039; verwendet nur die oberen vier Datenleitungen (&#039;&#039;&#039;DB4-DB7&#039;&#039;&#039;). Um ein Byte zu übertragen, braucht man somit zwei Zugriffe, wobei zuerst das höherwertige &#039;&#039;&#039;&amp;quot;Nibble&amp;quot;&#039;&#039;&#039; (= 4 Bits), also Bit 4 bis Bit 7 übertragen wird und dann das niederwertige, also Bit 0 bis Bit 3. Die unteren Datenleitungen des LCDs, die beim Lesezyklus Ausgänge sind, lässt man offen (siehe Datasheets, z.&amp;amp;nbsp;B. vom KS0070).&lt;br /&gt;
&lt;br /&gt;
Der 4-Bit-Modus hat den Vorteil, dass man 4 IO-Pins weniger benötigt als beim 8-Bit-Modus. 6 bzw. 7 Pins (eines Portes) reichen aus.&lt;br /&gt;
&lt;br /&gt;
Neben den vier Datenleitungen (DB4, DB5, DB6 und DB7) werden noch die Anschlüsse &#039;&#039;&#039;RS&#039;&#039;&#039;, &#039;&#039;&#039;RW&#039;&#039;&#039; und &#039;&#039;&#039;E&#039;&#039;&#039; benötigt. &lt;br /&gt;
&lt;br /&gt;
* Über &#039;&#039;&#039;RS&#039;&#039;&#039; wird ausgewählt, ob man einen Befehl oder ein Datenbyte an das LCD schicken möchte. Beim Schreiben gilt: ist RS Low, dann wird das ankommende Byte als Befehl interpretiert; Ist RS high, wird das Byte auf dem LCD angezeigt (genauer: ins Data-Register geschrieben, kann auch für den CG bestimmt sein). &lt;br /&gt;
* &#039;&#039;&#039;RW&#039;&#039;&#039; legt fest, ob geschrieben oder gelesen werden soll. High bedeutet lesen, low bedeutet schreiben. Wenn man RW auf lesen einstellt und RS auf Befehl, dann kann man das &#039;&#039;&#039;Busy-Flag&#039;&#039;&#039; an DB7 lesen, das anzeigt, ob das LCD den vorhergehenden Befehl fertig verarbeitet hat. Ist RS auf Daten eingestellt, dann kann man z.&amp;amp;nbsp;B. den Inhalt des Displays lesen - was jedoch nur in den wenigsten Fällen Sinn macht. Deshalb kann man RW dauerhaft auf low lassen (= an GND anschließen), so dass man noch ein IO-Pin am Controller einspart. Der Nachteil ist, dass man dann das Busy-Flag nicht lesen kann, weswegen man nach jedem Befehl ca. 50 µs (beim Return Home 2 ms, beim Clear Display 20 ms) warten sollte, um dem LCD Zeit zum Ausführen des Befehls zu geben. Dummerweise schwankt die Ausführungszeit von Display zu Display und ist auch von der Betriebsspannung abhängig. Für professionellere Sachen also lieber den IO-Pin opfern und Busy abfragen.&lt;br /&gt;
* Der &#039;&#039;&#039;E&#039;&#039;&#039; Anschluss schließlich signalisiert dem LCD, dass die übrigen Datenleitungen jetzt korrekte Pegel angenommen haben und es die gewünschten Daten von den Datenleitungen bzw. Kommandos von den Datenleitungen übernehmen kann. Beim Lesen gibt das Display die Daten / Status so lange aus, wie E high ist. Beim Schreiben übernimmt das Display die Daten mit der fallenden Flanke.&lt;br /&gt;
&lt;br /&gt;
== Anschluss an den Controller ==&lt;br /&gt;
&lt;br /&gt;
Jetzt, da wir wissen, welche Anschlüsse das LCD benötigt, können wir das LCD mit dem Mikrocontroller verbinden: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;ACHTUNG: Es gibt Displays mit abweichender Anschluss-Belegung, falscher Anschluss kann zur Zerstörung führen! Daher immer das zugehörige Datenblatt zu Rate ziehen.&amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Einzelheiten unter [http://www.mikrocontroller.net/articles/HD44780 Artikel zum Controller HD44780]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
!Pinnummer&amp;lt;BR&amp;gt;LCD || Bezeichnung || Anschluss&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |1 || V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; || GND&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |2 || V&amp;lt;sub&amp;gt;CC&amp;lt;/sub&amp;gt; || +5 V&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |3 || V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt; || GND oder [[Potentiometer | Poti]] &lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |4 || RS || PD4 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |5 || RW || GND &lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |6 || E || PD5 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |7 || DB0 || nicht angeschlossen &lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |8 || DB1 || nicht angeschlossen&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |9 || DB2 || nicht angeschlossen&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |10 || DB3 || nicht angeschlossen&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |11 || DB4 || PD0 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |12 || DB5 || PD1 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |13 || DB6 || PD2 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |14 || DB7 || PD3 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |15 || A || Vorsicht! Meistens nicht direkt an +5 V abschließbar,&amp;lt;br /&amp;gt; sondern nur über einen Vorwiderstand, der an die Daten&amp;lt;br /&amp;gt;der Hintergrundbeleuchtung angepasst werden muss.&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |16 || K || GND&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ok. Alles ist verbunden. Wenn man jetzt den Strom einschaltet, sollten ein oder zwei schwarze Balken auf dem Display angezeigt werden. &lt;br /&gt;
&lt;br /&gt;
Doch wie bekommt man jetzt die Befehle und Daten in das Display? Dazu muss das LCD initialisiert werden und man muss Befehle (Commands) und seine Daten an das LCD senden. Weil die Initialisierung ein Spezialfall der Übertragung von Befehlen ist, im Folgenden zunächst die Erklärung für die Übertragung von Werten an das LCD.&lt;br /&gt;
&lt;br /&gt;
== Ansteuerung des LCDs im 4-Bit-Modus ==&lt;br /&gt;
&lt;br /&gt;
Um ein Byte zu übertragen, muss man es erstmal in die beiden Nibbles zerlegen, die getrennt übertragen werden. Da das obere Nibble (Bit 4 - Bit 7) als erstes übertragen wird, die 4 Datenleitungen jedoch an die vier unteren Bits des Port D angeschlossen sind, muss man die beiden Nibbles des zu übertragenden Bytes erstmal vertauschen. Der AVR kennt dazu praktischerweise einen eigenen Befehl: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           swap r16               ; vertauscht die beiden Nibbles von r16&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aus 0b00100101 wird so z.&amp;amp;nbsp;B. 0b01010010. &lt;br /&gt;
&lt;br /&gt;
Jetzt sind die Bits für die erste Phase der Übertragung an der richtigen Stelle. Trotzdem wollen wir das Ergebnis nicht einfach so mit &#039;&#039;&#039;out PORTD, r16&#039;&#039;&#039; an den Port geben. Um die Hälfte des Bytes, die jetzt nicht an die Datenleitungen des LCDs gegeben wird auf null zu setzen, verwendet man folgenden Befehl: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           andi r16, 0b00001111   ; Nur die vier unteren (mit 1 markierten)&lt;br /&gt;
                                  ; Bits werden übernommen, alle anderen werden null&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Also: Das obere Nibble wird erst mit dem unteren vertauscht, damit es unten ist. Dann wird das obere (das wir jetzt noch nicht brauchen) auf null gesetzt. &lt;br /&gt;
&lt;br /&gt;
Jetzt müssen wir dem LCD noch mitteilen, ob wir Daten oder Befehle senden wollen. Das machen wir, indem wir das Bit, an dem RS angeschlossen ist (PD4), auf 0 (Befehl senden) oder auf 1 (Daten senden) setzen. Um ein Bit in einem normalen Register zu setzen, gibt es den Befehl sbr (Set Bit in Register). Dieser Befehl unterscheidet sich jedoch von sbi (das nur für IO-Register gilt) dadurch, dass man nicht die Nummer des zu setzenden Bits angibt, sondern eine Bitmaske. Das geht so: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           sbr r16, 0b00010000     ; Bit 4 setzen, alle anderen Bits bleiben gleich&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
RS ist an PD4 angeschlossen. Wenn wir r16 an den Port D ausgeben, ist RS jetzt also high und das LCD erwartet Daten anstatt von Befehlen. &lt;br /&gt;
&lt;br /&gt;
Das Ergebnis können wir jetzt endlich direkt an den Port D übergeben: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           out PORTD, r16&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich muss vorher der Port D auf Ausgang geschaltet werden, indem man 0xFF ins Datenrichtungsregister DDRD schreibt. &lt;br /&gt;
&lt;br /&gt;
Um dem LCD zu signalisieren, dass es das an den Datenleitungen anliegende Nibble übernehmen kann, wird die E-Leitung (Enable, an PD5 angeschlossen) auf high und kurz darauf wieder auf low gesetzt. Ein Puls an dieser Leitung teilt also dem LCD mit, das die restlichen Leitungen jetzt ihren vom Programm gewollten Pegel eingenommen haben und gültig sind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           sbi PORTD, 5              ; Enable high&lt;br /&gt;
           nop                       ; 3 Taktzyklen warten (&amp;quot;nop&amp;quot; = nichts tun)&lt;br /&gt;
           nop&lt;br /&gt;
           nop&lt;br /&gt;
           cbi PORTD, 5              ; Enable wieder low&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die eine Hälfte des Bytes wäre damit geschafft! Die andere Hälfte kommt direkt hinterher: Alles, was an der obenstehenden Vorgehensweise geändert werden muss, ist, das &amp;quot;swap&amp;quot; (Vertauschen der beiden Nibbles) wegzulassen.&lt;br /&gt;
&lt;br /&gt;
== Initialisierung des Displays ==&lt;br /&gt;
&lt;br /&gt;
Allerdings gibt es noch ein Problem. Wenn ein LCD eingeschaltet wird, dann läuft es zunächst im 8 Bit Modus. Irgendwie muss das Display initialisiert und auf den 4 Bit Modus umgeschaltet werden, und zwar nur mit den 4 zur Verfügung stehenden Datenleitungen.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wenn es Probleme gibt, dann meistens an diesem Punkt. Die &amp;quot;kompatiblen&amp;quot; Kontroller sind gelegentlich doch nicht 100% identisch. Es lohnt sich, das Datenblatt (siehe Weblinks im Artikel [[LCD]]) genau zu lesen, in welcher Reihenfolge und mit welchen Abständen (Delays) die Initialisierungbefehle gesendet werden. Eine weitere Hilfe können Ansteuerungsbeispiele in Forenbeiträgen geben z.&amp;amp;nbsp;B.&lt;br /&gt;
&lt;br /&gt;
* [http://www.mikrocontroller.net/topic/79609#664268 (A) KS0066U oder Ähnliche --- LCD Treiber]&lt;br /&gt;
&lt;br /&gt;
=== Initialisierung für 4 Bit Modus ===&lt;br /&gt;
&lt;br /&gt;
Achtung: Im Folgenden sind alle Bytes aus Sicht des LCD-Kontrollers angegeben! Da LCD-seitig nur die Leitungen DB4 - DB7 verwendet werden, ist daher immer nur das höherwertige Nibble gültig. Durch die Art der Verschaltung (DB4 - DB7 wurde auf dem PORT an PD0 bis PD3 angeschlossen) ergibt sich eine Verschiebung, so dass das am Kontroller auszugebende Byte nibblemässig vertauscht ist!&lt;br /&gt;
&lt;br /&gt;
Die Sequenz, aus Sicht des Kontrollers, sieht so aus:&lt;br /&gt;
&lt;br /&gt;
* Nach dem Anlegen der Betriebsspannung muss eine Zeit von mindestens ca. 15ms gewartet werden, um dem LCD-Kontroller Zeit für seine eigene Initialisierung zu geben&lt;br /&gt;
* $3 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 4.1ms warten&lt;br /&gt;
* $3 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 100µs warten&lt;br /&gt;
* $3 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* $2 ins Steuerregister schreiben (RS = 0), dadurch wird auf 4 Bit Daten umgestellt&lt;br /&gt;
* Ab jetzt muss für die Übertragung eines Bytes jeweils zuerst das höherwertige Nibble und dann das niederwertige Nibble übertragen werden, wie oben beschrieben&lt;br /&gt;
* Mit dem Konfigurier-Befehl $20 das Display konfigurieren (4-Bit, 1 oder 2 Zeilen, 5x7 Format)&lt;br /&gt;
* Mit den restlichen Konfigurierbefehlen die Konfiguration vervollständigen: Display ein/aus, Cursor ein/aus, etc.&lt;br /&gt;
&lt;br /&gt;
Eine Begründung, warum die ersten Befehle dreifach geschickt werden sollen, findet sich [http://www.mikrocontroller.net/topic/158983#1508510 im Forum].&lt;br /&gt;
&lt;br /&gt;
=== Initialisierung für 8 Bit Modus ===&lt;br /&gt;
&lt;br /&gt;
Der Vollständigkeit halber hier noch die notwendige Initialiserungssequenz für den 8 Bit Modus. Da hier die Daten komplett als 1 Byte übertragen werden können, sind einige Klimmzüge wie im 4 Bit Modus nicht notwendig. Begründung für die anfänglichen Wiederholungen siehe oben.&lt;br /&gt;
&lt;br /&gt;
* Nach dem Anlegen der Betriebsspannung muss eine Zeit von mindestens ca. 15ms gewartet werden, um dem LCD-Kontroller Zeit für seine eigene Initialisierung zu geben&lt;br /&gt;
* $30 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 4.1ms warten&lt;br /&gt;
* $30 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 100µs warten&lt;br /&gt;
* $30 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mit dem Konfigurier-Befehl 0x30 das Display konfigurieren (8-Bit, 1 oder 2 Zeilen, 5x7 Format)&lt;br /&gt;
* Mit den restlichen Konfigurierbefehlen die Konfiguration vervollständigen: Display ein/aus, Cursor ein/aus, etc.&lt;br /&gt;
&lt;br /&gt;
== Routinen zur LCD-Ansteuerung im 4-Bit-Modus ==&lt;br /&gt;
&lt;br /&gt;
Im Folgenden werden die bisherigen Grundroutinen zur LCD-Ansteuerung im 4-Bit-Modus zusammengefasst und kommentiert. Die darin enthaltenen Symbole (temp1, PORTD,...) müssen in einem dazugehörenden Hauptprogramm definiert werden. Dies wird nächsten Abschnitt &#039;&#039;Anwendung&#039;&#039; weiter erklärt.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
;;                 LCD-Routinen                ;;&lt;br /&gt;
;;                 ============                ;;&lt;br /&gt;
;;              (c)andreas-s@web.de            ;;&lt;br /&gt;
;;                                             ;;&lt;br /&gt;
;; 4bit-Interface                              ;;&lt;br /&gt;
;; DB4-DB7:       PD0-PD3                      ;;&lt;br /&gt;
;; RS:            PD4                          ;;&lt;br /&gt;
;; E:             PD5                          ;;&lt;br /&gt;
;;                                             ;;&lt;br /&gt;
;; Takt:          4 MHz                        ;;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 ;sendet ein Datenbyte an das LCD&lt;br /&gt;
lcd_data:&lt;br /&gt;
           mov temp2, temp1             ; &amp;quot;Sicherungskopie&amp;quot; für&lt;br /&gt;
                                        ; die Übertragung des 2.Nibbles&lt;br /&gt;
           swap temp1                   ; Vertauschen&lt;br /&gt;
           andi temp1, 0b00001111       ; oberes Nibble auf Null setzen&lt;br /&gt;
           sbr temp1, 1&amp;lt;&amp;lt;4              ; entspricht 0b00010000 (Anm.1)&lt;br /&gt;
           out PORTD, temp1             ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
                                        ; 2. Nibble, kein swap da es schon&lt;br /&gt;
                                        ; an der richtigen stelle ist&lt;br /&gt;
           andi temp2, 0b00001111       ; obere Hälfte auf Null setzen &lt;br /&gt;
           sbr temp2, 1&amp;lt;&amp;lt;4              ; entspricht 0b00010000&lt;br /&gt;
           out PORTD, temp2             ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
           rcall delay50us              ; Delay-Routine aufrufen&lt;br /&gt;
           ret                          ; zurück zum Hauptprogramm&lt;br /&gt;
&lt;br /&gt;
 ; sendet einen Befehl an das LCD&lt;br /&gt;
lcd_command:                            ; wie lcd_data, nur RS=0&lt;br /&gt;
           mov temp2, temp1&lt;br /&gt;
           swap temp1&lt;br /&gt;
           andi temp1, 0b00001111&lt;br /&gt;
           out PORTD, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           andi temp2, 0b00001111&lt;br /&gt;
           out PORTD, temp2&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay50us&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; erzeugt den Enable-Puls&lt;br /&gt;
 ;&lt;br /&gt;
 ; Bei höherem Takt (&amp;gt;= 8 MHz) kann es notwendig sein, &lt;br /&gt;
 ; vor dem Enable High 1-2 Wartetakte (nop) einzufügen. &lt;br /&gt;
 ; Siehe dazu http://www.mikrocontroller.net/topic/81974#685882&lt;br /&gt;
lcd_enable:&lt;br /&gt;
           sbi PORTD, 5                 ; Enable high&lt;br /&gt;
           nop                          ; mindestens 3 Taktzyklen warten&lt;br /&gt;
           nop&lt;br /&gt;
           nop&lt;br /&gt;
           cbi PORTD, 5                 ; Enable wieder low&lt;br /&gt;
           ret                          ; Und wieder zurück                     &lt;br /&gt;
&lt;br /&gt;
 ; Pause nach jeder Übertragung&lt;br /&gt;
delay50us:                              ; 50µs Pause (bei 4 MHz)&lt;br /&gt;
           ldi  temp1, $42&lt;br /&gt;
delay50us_:dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause (bei 4 MHz)&lt;br /&gt;
           ldi  temp1, $21&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&lt;br /&gt;
 ; Initialisierung: muss ganz am Anfang des Programms aufgerufen werden&lt;br /&gt;
lcd_init:&lt;br /&gt;
           ldi  temp3,50&lt;br /&gt;
powerupwait:&lt;br /&gt;
           rcall  delay5ms&lt;br /&gt;
           dec  temp3&lt;br /&gt;
           brne powerupwait&lt;br /&gt;
           ldi temp1, 0b00000011        ; muss 3mal hintereinander gesendet&lt;br /&gt;
           out PORTD, temp1             ; werden zur Initialisierung&lt;br /&gt;
           rcall lcd_enable             ; 1&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; 2&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; und 3!&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi temp1, 0b00000010        ; 4bit-Modus einstellen&lt;br /&gt;
           out PORTD, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi temp1, 0b00101000        ; 4Bit / 2 Zeilen / 5x8&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi temp1, 0b00001100        ; Display ein / Cursor aus / kein Blinken&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi temp1, 0b00000100        ; inkrement / kein Scrollen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           ldi temp1, 0b00000001   ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; Sendet den Befehl: Cursor Home&lt;br /&gt;
lcd_home:&lt;br /&gt;
           ldi temp1, 0b00000010   ; Cursor Home&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Anm.1: Siehe [[Bitmanipulation]]&lt;br /&gt;
&lt;br /&gt;
Weitere Funktionen (wie z.&amp;amp;nbsp;B. Cursorposition verändern) sollten mit Hilfe der [[AVR-Tutorial:_LCD#Welche_Befehle_versteht_das_LCD.3F|Befehlscodeliste]] nicht schwer zu realisieren sein. Einfach den Code in temp laden, lcd_command aufrufen und ggf. eine Pause einfügen.&amp;lt;br&amp;gt; &lt;br /&gt;
Natürlich kann man die LCD-Ansteuerung auch an einen anderen Port des Mikrocontrollers &amp;quot;verschieben&amp;quot;: Wenn das LCD z.&amp;amp;nbsp;B. an Port B angeschlossen ist, dann reicht es, im Programm alle &amp;quot;PORTD&amp;quot; durch &amp;quot;PORTB&amp;quot; und &amp;quot;DDRD&amp;quot; durch &amp;quot;DDRB&amp;quot; zu ersetzen.&amp;lt;br&amp;gt; &lt;br /&gt;
Wer eine höhere Taktfrequenz als 4 MHz verwendet, der sollte daran denken, die Dauer der Verzögerungsschleifen anzupassen.&lt;br /&gt;
&lt;br /&gt;
==Anwendung==&lt;br /&gt;
&lt;br /&gt;
Ein Programm, das diese Routinen zur Anzeige von Text verwendet, kann z.&amp;amp;nbsp;B. so aussehen (die Datei lcd-routines.asm muss sich im gleichen Verzeichnis befinden). Nach der Initialisierung wird zuerst der Displayinhalt gelöscht. Um dem LCD ein Zeichen zu schicken, lädt man es in temp1 und ruft die Routine &amp;quot;lcd_data&amp;quot; auf. Das folgende Beispiel zeigt das Wort &amp;quot;Test&amp;quot; auf dem LCD an. &lt;br /&gt;
&lt;br /&gt;
[http://www.mikrocontroller.net/sourcecode/tutorial/lcd-test.asm Download lcd-test.asm] &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.include &amp;quot;m8def.inc&amp;quot;&lt;br /&gt;
&lt;br /&gt;
; .def definiert ein Synonym (Namen) für ein µC Register&lt;br /&gt;
.def temp1 = r16&lt;br /&gt;
.def temp2 = r17&lt;br /&gt;
.def temp3 = r18&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, LOW(RAMEND)      ; LOW-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPL, temp1&lt;br /&gt;
           ldi temp1, HIGH(RAMEND)     ; HIGH-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPH, temp1&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, 0xFF    ; Port D = Ausgang&lt;br /&gt;
           out DDRD, temp1&lt;br /&gt;
&lt;br /&gt;
           rcall lcd_init     ; Display initialisieren&lt;br /&gt;
           rcall lcd_clear    ; Display löschen&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, &#039;T&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, &#039;e&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           &lt;br /&gt;
           ldi temp1, &#039;s&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, &#039;t&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
loop:&lt;br /&gt;
           rjmp loop&lt;br /&gt;
&lt;br /&gt;
.include &amp;quot;lcd-routines.asm&amp;quot;            ; LCD-Routinen werden hier eingefügt&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für längere Texte ist die Methode, jedes Zeichen einzeln in das Register zu laden und &amp;quot;lcd_data&amp;quot; aufzurufen natürlich nicht sehr praktisch. Dazu später aber mehr.&lt;br /&gt;
&lt;br /&gt;
Bisher wurden in Register immer irgendwelche Zahlenwerte geladen, aber in diesem Programm kommt plötzlich die Anweisung&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ldi temp1, &#039;T&#039;&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
vor. Wie ist diese zu verstehen? Passiert hier etwas grundlegend anderes als beim Laden einer Zahl in ein Register?&lt;br /&gt;
&lt;br /&gt;
Die Antwort darauf lautet: Nein. Auch hier wird letztendlich nur eine Zahl in ein Register geladen. Der Schlüssel zum Verständnis beruht darauf, dass zum LCD, so wie zu allen Ausgabegeräten, für die Ausgabe von Texten immer nur Zahlen übertragen werden, sog. Codes. Zum Beispiel könnte man vereinbaren, dass ein LCD, wenn es den Ausgabecode 65 erhält, ein &#039;A&#039; anzeigt, bei einem Ausgabecode von 66 ein &#039;B&#039; usw. Naturgemäß gibt es daher viele verschiedene Code-Buchstaben Zuordnungen. Damit hier etwas Ordnung in das potentielle Chaos kommt, hat man sich bereits in der Steinzeit der Programmierung auf bestimmte Codetabellen geeinigt, von denen die verbreitetste sicherlich die ASCII-Zuordnung ist.&lt;br /&gt;
&lt;br /&gt;
==ASCII==&lt;br /&gt;
&lt;br /&gt;
ASCII steht für &#039;&#039;American Standard Code for Information Interchange&#039;&#039; und ist ein standardisierter Code zur Zeichenumsetzung. Die Codetabelle sieht hexadezimal dabei wie folgt aus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
!   ||x0||x1||x2||x3||x4||x5||x6||x7||x8||x9||xA||xB||xC||xD||xE||xF&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 0x&lt;br /&gt;
|NUL||SOH||STX||ETX||EOT||ENQ||ACK||BEL||BS||HT||LF||VT||FF||CR||SO||SI&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 1x&lt;br /&gt;
|DLE||DC1||DC2||DC3||DC4||NAK||SYN||ETB||CAN||EM||SUB||ESC||FS||GS||RS||US&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 2x&lt;br /&gt;
|SP||!||&amp;quot;||#||$||%||&amp;amp;||&#039;||(||)||*||+||,||-||.||/&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 3x&lt;br /&gt;
|0||1||2||3||4||5||6||7||8||9||:||;||&amp;lt;||=||&amp;gt;||?&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 4x&lt;br /&gt;
|@||A||B||C||D||E||F||G||H||I||J||K||L||M||N||O&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 5x&lt;br /&gt;
|P||Q||R||S||T||U||V||W||X||Y||Z||[||\||]||^||_&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 6x&lt;br /&gt;
|`||a||b||c||d||e||f||g||h||i||j||k||l||m||n||o&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 7x&lt;br /&gt;
|p||q||r||s||t||u||v||w||x||y||z||{|| &amp;amp;#124; ||}||~||DEL&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die ersten beiden Zeilen enthalten die Codes für einige Steuerzeichen, ihre vollständige Beschreibung würde hier zu weit führen. Das Zeichen &#039;&#039;&#039;SP&#039;&#039;&#039; steht für ein &#039;&#039;Space&#039;&#039;, also ein Leerzeichen. &#039;&#039;&#039;BS&#039;&#039;&#039; steht für &#039;&#039;Backspace&#039;&#039;, also ein Zeichen zurück. &#039;&#039;&#039;DEL&#039;&#039;&#039; steht für &#039;&#039;Delete&#039;&#039;, also das Löschen eines Zeichens. &#039;&#039;&#039;CR&#039;&#039;&#039; steht für &#039;&#039;Carriage Return&#039;&#039;, also wörtlich: der Wagenrücklauf (einer Schreibmaschine), während &#039;&#039;&#039;LF&#039;&#039;&#039; für &#039;&#039;Line feed&#039;&#039;, also einen Zeilenvorschub steht.&lt;br /&gt;
&lt;br /&gt;
Der Assembler kennt diese Codetabelle und ersetzt die Zeile&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ldi temp1, &#039;T&#039;&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
durch&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ldi temp1, $54&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
was letztendlich auch der Lesbarkeit des Programmes zugute kommt. Funktional besteht kein Unterschied zwischen den beiden Anweisungen. Beide bewirken, dass das Register temp1 mit dem Bitmuster 01010100 ( = hexadezimal 54, = dezimal 84 oder eben der ASCII Code für &#039;&#039;&#039;T&#039;&#039;&#039;) geladen wird.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Das LCD wiederrum kennt diese Code-Tabelle ebenfalls und wenn es über den Datenbus die Codezahl $54 zur Anzeige empfängt, dann schreibt es ein &#039;&#039;&#039;T&#039;&#039;&#039; an die aktuelle Cursorposition. Genauer gesagt, weiss das LCD nichts von einem &#039;&#039;&#039;T&#039;&#039;&#039;. Es sieht einfach in seinen internen Tabellen nach, welche Pixel beim Empfang der Codezahl $54 auf schwarz zu setzen sind. &#039;Zufällig&#039; sind das genau jene Pixel, die für uns Menschen ein &#039;&#039;&#039;T&#039;&#039;&#039; ergeben.&lt;br /&gt;
&lt;br /&gt;
==Welche Befehle versteht das LCD?==&lt;br /&gt;
&lt;br /&gt;
Auf dem LCD arbeitet ein Controller vom Typ HD44780. Dieser Kontroller versteht eine Reihe von Befehlen, die allesamt mittels lcd_command gesendet werden können. Ein Kommando ist dabei nichts anderes als ein Befehlsbyte, in dem die verschiedenen Bits verschiedene Bedeutungen haben:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
! Bitwert   || Bedeutung&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  0&lt;br /&gt;
||dieses Bit muss 0 sein&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  1&lt;br /&gt;
||dieses Bit muss 1 sein&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  x&lt;br /&gt;
||der Zustand dieses Bits ist egal&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | sonstige Buchstaben&lt;br /&gt;
||das Bit muss je nach gewünschter Funktionalität gesetzt werden.&amp;lt;br /&amp;gt;Die mögliche Funktionalität des jeweiligen Bits geht aus der Befehlsbeschreibung hervor.&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Beispiel: Das Kommando &#039;ON/OFF Control&#039; soll benutzt werden, um das Display einzuschalten, der Cursor soll eingeschaltet werden und der Cursor soll blinken.&lt;br /&gt;
Das Befehlsbyte ist so aufgebaut:&lt;br /&gt;
   0b00001dcb&lt;br /&gt;
Aus der Befehlsbeschreibung entnimmt man:&lt;br /&gt;
* Display ein bedeutet, dass an der Bitposition d eine 1 stehen muss.&amp;lt;br&amp;gt;&lt;br /&gt;
* Cursor ein bedeutet, dass an der Bitposition c ein 1 stehen muss.&amp;lt;br&amp;gt;&lt;br /&gt;
* Cursor blinken bedeutet, dass an der Bitposition b eine 1 stehen muss.&amp;lt;br&amp;gt;&lt;br /&gt;
Das dafür zu übertragende Befehlsbyte hat also die Gestalt 0b00001111 oder in hexadezimaler Schreibweise $0F.&lt;br /&gt;
&lt;br /&gt;
===Clear display: 0b00000001===&lt;br /&gt;
&lt;br /&gt;
Die Anzeige wird gelöscht und der Ausgabecursor kehrt an die Home Position (links, erste Zeile) zurück.&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 1.64ms&lt;br /&gt;
&lt;br /&gt;
===Cursor home: 0b0000001x===&lt;br /&gt;
&lt;br /&gt;
Der Cursor kehrt an die Home Position (links, erste Zeile) zurück. Ein verschobenes Display wird auf die Grundeinstellung zurückgesetzt.&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs bis 1.64ms&lt;br /&gt;
&lt;br /&gt;
===Entry mode: 0b000001is===&lt;br /&gt;
&lt;br /&gt;
Legt die Cursor Richtung sowie eine mögliche Verschiebung des Displays fest&lt;br /&gt;
* i = 1, Cursorposition bei Ausgabe eines Zeichens erhöhen&lt;br /&gt;
* i = 0, Cursorposition bei Ausgabe eines Zeichens vermindern&lt;br /&gt;
* s = 1, Display wird gescrollt, wenn der Cursor das Ende/Anfang, je nach Einstellung von i, erreicht hat.&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===On/off control: 0b00001dcb===&lt;br /&gt;
&lt;br /&gt;
Display insgesamt ein/ausschalten; den Cursor ein/ausschalten; den Cursor auf blinken schalten/blinken aus. Wenn das Display ausgeschaltet wird, geht der Inhalt des Displays nicht verloren. Der vorher angezeigte Text wird nach wiedereinschalten erneut angezeigt.&lt;br /&gt;
Ist der Cursor eingeschaltet, aber Blinken ausgeschaltet, so wird der Cursor als Cursorzeile in Pixelzeile 8 dargestellt. Ist Blinken eingeschaltet, wird der Cursor als blinkendes ausgefülltes Rechteck dargestellt, welches abwechselnd mit dem Buchstaben an dieser Stelle angezeigt wird.&lt;br /&gt;
&lt;br /&gt;
* d = 0, Display aus&lt;br /&gt;
* d = 1, Display ein&lt;br /&gt;
* c = 0, Cursor aus&lt;br /&gt;
* c = 1, Cursor ein&lt;br /&gt;
* b = 0, Cursor blinken aus&lt;br /&gt;
* b = 1, Cursor blinken ein&lt;br /&gt;
 &lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Cursor/Scrollen: 0b0001srxx===&lt;br /&gt;
&lt;br /&gt;
Bewegt den Cursor oder scrollt das Display um eine Position entweder nach rechts oder nach links.&lt;br /&gt;
&lt;br /&gt;
* s = 1, Display scrollen&lt;br /&gt;
* s = 0, Cursor bewegen&lt;br /&gt;
* r = 1, nach rechts&lt;br /&gt;
* r = 0, nach links &lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Konfiguration: 0b001dnfxx===&lt;br /&gt;
&lt;br /&gt;
Einstellen der Interface Art, Modus, Font&lt;br /&gt;
* d = 0, 4-Bit Interface&lt;br /&gt;
* d = 1, 8-Bit Interface&lt;br /&gt;
* n = 0, 1 zeilig&lt;br /&gt;
* n = 1, 2 zeilig&lt;br /&gt;
* f = 0, 5x7 Pixel&lt;br /&gt;
* f = 1, 5x11 Pixel&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Character RAM Address Set: 0b01aaaaaa===&lt;br /&gt;
&lt;br /&gt;
Mit diesem Kommando werden maximal 8 selbst definierte Zeichen definiert. Dazu wird der Character RAM Zeiger auf den Anfang des Character Generator (CG) RAM gesetzt und das Zeichen durch die Ausgabe von 8 Byte definiert. Der Adresszeiger wird nach Ausgabe jeder Pixelspalte (8 Bit) vom LCD selbst erhöht. Nach Beendigung der Zeichendefinition muss die Schreibposition explizit mit dem Kommando &amp;quot;Display RAM Address Set&amp;quot; wieder in den DD-RAM Bereich gesetzt werden.&lt;br /&gt;
&lt;br /&gt;
aaaaaa 6-bit CG RAM Adresse&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Display RAM Address Set: 0b1aaaaaaa===&lt;br /&gt;
&lt;br /&gt;
Den Cursor neu positionieren. Display Data (DD) Ram ist vom Character Generator (CG) Ram unabhängig. Der Adresszeiger wird bei Ausgabe eines Zeichens ins DD Ram automatisch erhöht. Das Display verhält sich so, als ob eine Zeile immer aus 40 logischen Zeichen besteht, von der, je nach konkretem Displaytyp (16 Zeichen, 20 Zeichen) immer nur ein Teil sichtbar ist.&lt;br /&gt;
&lt;br /&gt;
aaaaaaa 7-bit DD RAM Adresse. Auf 2-zeiligen Displays (und den meisten 16x1 Displays), kann die Adressangabe wie folgt interpretiert werden:&lt;br /&gt;
&lt;br /&gt;
1laaaaaa&lt;br /&gt;
* l = Zeilennummer (0 oder 1)&lt;br /&gt;
* a = 6-Bit Spaltennummer&lt;br /&gt;
&lt;br /&gt;
 --------------------------------&lt;br /&gt;
 DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0&lt;br /&gt;
 --- --- --- --- --- --- --- ---&lt;br /&gt;
  1   A   A   A   A   A   A   A &lt;br /&gt;
&lt;br /&gt;
Setzt die DDRAM Adresse:&lt;br /&gt;
&lt;br /&gt;
Wenn N = 0 (1 line display)&lt;br /&gt;
    AAAAAAA = &amp;quot;00h&amp;quot; - &amp;quot;4Fh&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Wenn N = 1 (2 line display) ((1x16))&lt;br /&gt;
    AAAAAAA = &amp;quot;00h&amp;quot; - &amp;quot;27h&amp;quot; Zeile 1. (0x80) &lt;br /&gt;
    AAAAAAA = &amp;quot;40h&amp;quot; - &amp;quot;67h&amp;quot; Zeile 2. (0xC0)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
==Einschub: Code aufräumen==&lt;br /&gt;
&lt;br /&gt;
Es wird Zeit, sich einmal etwas kritisch mit den bisher geschriebenen Funktionen auseinander zu setzen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Portnamen aus dem Code herausziehen===&lt;br /&gt;
&lt;br /&gt;
Wenn wir die LCD-Funktionen einmal genauer betrachten, dann fällt sofort auf, dass über die Funktionen verstreut immer wieder das &#039;&#039;&#039;PORTD&#039;&#039;&#039; sowie einzelne Zahlen für die Pins an diesem Port auftauchen. Wenn das LCD an einem anderen Port betrieben werden soll, oder sich die Pin-Belegung ändert, dann muss an all diesen Stellen eine Anpassung vorgenommen werden. Dabei darf keine einzige Stelle übersehen werden, ansonsten würden die LCD-Funktionen nicht oder nicht vollständig funktionieren.&lt;br /&gt;
&lt;br /&gt;
Eine Möglichkeit, dem vorzubeugen, ist es, diese immer gleichbleibenden Dinge an den Anfang der LCD-Funktionen vorzuziehen. Anstelle von PORTD wird dann im Code ein anderer Name benutzt, den man frei vergeben kann. Dem Assembler wird nur noch mitgeteilt, das dieser Name für PORTD steht. Muss das LCD an einen anderen Port angeschlossen werden, so wird nur diese Zurodnung geändert und der Assembler passt dann im restlichen Code alle davon abhängigen Anweisungen an:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
;;                 LCD-Routinen                ;;&lt;br /&gt;
;;                 ============                ;;&lt;br /&gt;
;;              (c)andreas-s@web.de            ;;&lt;br /&gt;
;;                                             ;;&lt;br /&gt;
;; 4bit-Interface                              ;;&lt;br /&gt;
;; DB4-DB7:       PD0-PD3                      ;;&lt;br /&gt;
;; RS:            PD4                          ;;&lt;br /&gt;
;; E:             PD5                          ;;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
 &lt;br /&gt;
; .equ definiert ein Symbol und dessen Wert&lt;br /&gt;
.equ LCD_PORT = PORTD&lt;br /&gt;
.equ LCD_DDR  = DDRD&lt;br /&gt;
.equ PIN_E    = 5&lt;br /&gt;
.equ PIN_RS   = 4&lt;br /&gt;
&lt;br /&gt;
 ;sendet ein Datenbyte an das LCD&lt;br /&gt;
lcd_data:&lt;br /&gt;
           mov temp2, temp1             ; &amp;quot;Sicherungskopie&amp;quot; für&lt;br /&gt;
                                        ; die Übertragung des 2.Nibbles&lt;br /&gt;
           swap temp1                   ; Vertauschen&lt;br /&gt;
           andi temp1, 0b00001111       ; oberes Nibble auf Null setzen&lt;br /&gt;
           sbr temp1, 1&amp;lt;&amp;lt;PIN_RS         ; entspricht 0b00010000&lt;br /&gt;
           out LCD_PORT, temp1          ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
                                        ; 2. Nibble, kein swap da es schon&lt;br /&gt;
                                        ; an der richtigen stelle ist&lt;br /&gt;
           andi temp2, 0b00001111       ; obere Hälfte auf Null setzen &lt;br /&gt;
           sbr temp2, 1&amp;lt;&amp;lt;PIN_RS         ; entspricht 0b00010000&lt;br /&gt;
           out LCD_PORT, temp2          ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
           rcall delay50us              ; Delay-Routine aufrufen&lt;br /&gt;
           ret                          ; zurück zum Hauptprogramm&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
 ; sendet einen Befehl an das LCD&lt;br /&gt;
&lt;br /&gt;
lcd_command:                            ; wie lcd_data, nur RS=0&lt;br /&gt;
           mov temp2, temp1&lt;br /&gt;
           swap temp1&lt;br /&gt;
           andi temp1, 0b00001111&lt;br /&gt;
           out LCD_PORT, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           andi temp2, 0b00001111&lt;br /&gt;
           out LCD_PORT, temp2&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay50us&lt;br /&gt;
           ret&lt;br /&gt;
 &lt;br /&gt;
 ; erzeugt den Enable-Puls&lt;br /&gt;
lcd_enable:&lt;br /&gt;
           sbi LCD_PORT, PIN_E          ; Enable high&lt;br /&gt;
           nop                          ; 3 Taktzyklen warten&lt;br /&gt;
           nop&lt;br /&gt;
           nop&lt;br /&gt;
           cbi LCD_PORT, PIN_E          ; Enable wieder low&lt;br /&gt;
           ret                          ; Und wieder zurück                     &lt;br /&gt;
 &lt;br /&gt;
 ; Pause nach jeder Übertragung&lt;br /&gt;
delay50us:                              ; 50µs Pause&lt;br /&gt;
           ldi  temp1, $42&lt;br /&gt;
delay50us_:dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
 &lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause&lt;br /&gt;
           ldi  temp1, $21&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
 &lt;br /&gt;
 ; Initialisierung: muss ganz am Anfang des Programms aufgerufen werden&lt;br /&gt;
lcd_init:&lt;br /&gt;
           ldi   temp1, 0xFF            ; alle Pins am Ausgabeport auf Ausgang&lt;br /&gt;
           out   LCD_DDR, temp1&lt;br /&gt;
&lt;br /&gt;
           ldi   temp3,6&lt;br /&gt;
powerupwait:&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           dec   temp3&lt;br /&gt;
           brne  powerupwait&lt;br /&gt;
           ldi   temp1,    0b00000011   ; muss 3mal hintereinander gesendet&lt;br /&gt;
           out   LCD_PORT, temp1        ; werden zur Initialisierung&lt;br /&gt;
           rcall lcd_enable             ; 1&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; 2&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; und 3!&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi   temp1, 0b00000010      ; 4bit-Modus einstellen&lt;br /&gt;
           out   LCD_PORT, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi   temp1, 0b00101000      ; 4 Bit, 2 Zeilen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi   temp1, 0b00001100      ; Display on, Cursor off&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi   temp1, 0b00000100      ; endlich fertig&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ret&lt;br /&gt;
 &lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           ldi   temp1, 0b00000001      ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; Sendet den Befehl: Cursor Home&lt;br /&gt;
lcd_home:&lt;br /&gt;
           ldi   temp1, 0b00000010      ; Cursor Home&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mittels &#039;&#039;&#039;.equ&#039;&#039;&#039; werden mit dem Assembler Textersetzungen vereinbart. Der Assembler ersetzt alle Vorkomnisse des Quelltextes durch den zu ersetzenden Text. Dadurch ist es z.&amp;amp;nbsp;B. möglich, alle Vorkommnisse von &#039;&#039;&#039;PORTD&#039;&#039;&#039; durch &#039;&#039;&#039;LCD_PORT&#039;&#039;&#039; auszutauschen. Wird das LCD an einen anderen Port, z.&amp;amp;nbsp;B. &#039;&#039;&#039;PORTB&#039;&#039;&#039; gelegt, dann genügt es, die Zeilen&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.equ LCD_PORT = PORTD&lt;br /&gt;
.equ LCD_DDR  = DDRD&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
durch&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.equ LCD_PORT = PORTB&lt;br /&gt;
.equ LCD_DDR  = DDRB&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
zu ersetzen. Der Assembler sorgt dann dafür, dass diese Portänderung an den relevanten Stellen im Code über die Textersetzungen einfließt. Selbiges natürlich mit der Pin-Zuordnung.&lt;br /&gt;
&lt;br /&gt;
===Registerbenutzung===&lt;br /&gt;
&lt;br /&gt;
Bei diesen Funktionen mussten einige Register des Prozessors benutzt werden, um darin Zwischenergebnisse zu speichern bzw. zu bearbeiten.&lt;br /&gt;
&lt;br /&gt;
Beachtet werden muss dabei natürlich, dass es zu keinen Überschneidungen kommt. Solange nur jede Funktion jeweils für sich betrachtet wird, ist das kein Problem. In 20 oder 30 Code-Zeilen kann man gut verfolgen, welches Register wofür benutzt wird. Schwieriger wird es, wenn Funktionen wiederum andere Funktionen aufrufen, die ihrerseits wieder Funktionen aufrufen usw. Jede dieser Funktionen benutzt einige Register und mit zunehmender Programmgröße wird es immer schwieriger, zu verfolgen, welches Register zu welchem Zeitpunkt wofür benutzt wird.&lt;br /&gt;
&lt;br /&gt;
Speziell bei Basisfunktionen wie diesen LCD-Funktionen, ist es daher oft ratsam, dafür zu sorgen, dass jede Funktion die Register wieder in dem Zustand hinterlässt, indem sie sie auch vorgefunden hat. Wir benötigen dazu wieder den Stack, auf dem die Registerinhalte bei Betreten einer Funktion zwischengespeichert werden und von dem die Register bei Verlassen einer Funktion wiederhergestellt werden.&lt;br /&gt;
&lt;br /&gt;
Nehmen wir die Funktion&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           ldi   temp1, 0b00000001      ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Diese Funktion verändert das Register temp1. Um das Register abzusichern, schreiben wir die Funktion um:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           push  temp1                  ; temp1 auf dem Stack sichern&lt;br /&gt;
           ldi   temp1, 0b00000001      ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           pop   temp1                  ; temp1 vom Stack wiederherstellen&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am besten hält man sich an die Regel: Jede Funktion ist dafür zuständig, die Register zu sichern und wieder herzustellen, die sie auch selbst verändert. &#039;&#039;&#039;lcd_clear&#039;&#039;&#039; ruft die Funktionen &#039;&#039;&#039;lcd_command&#039;&#039;&#039; und &#039;&#039;&#039;delay5ms&#039;&#039;&#039; auf. Wenn diese Funktionen selbst wieder Register verändern (und das tun sie), so ist es die Aufgabe dieser Funktionen, sich um die Sicherung und das Wiederherstellen der entsprechenden Register zu kümmern. &#039;&#039;&#039;lcd_clear&#039;&#039;&#039; sollte sich nicht darum kümmern müssen. Auf diese Weise ist das Schlimmste, das einem passieren kann, dass ein paar Register unnütz gesichert und wiederhergestellt werden. Das kostet zwar etwas Rechenzeit und etwas Speicherplatz auf dem Stack, ist aber immer noch besser als das andere Extrem: Nach einem Funktionsaufruf haben einige Register nicht mehr den Wert, den sie haben sollten, und das Programm rechnet mit falschen Zahlen weiter.&lt;br /&gt;
&lt;br /&gt;
===Lass den Assembler rechnen===&lt;br /&gt;
Betrachtet man den Code genauer, so fallen einige konstante Zahlenwerte auf (Das vorangestellte $ kennzeichnet die Zahl als Hexadezimalzahl):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
delay50us:                              ; 50µs Pause&lt;br /&gt;
           ldi  temp1, $42&lt;br /&gt;
delay50us_:&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Der Code benötigt eine Warteschleife, die mindestens 50µs dauert. Die beiden Befehle innerhalb der Schleife benötigen 3 Takte: 1 Takt für den &#039;&#039;&#039;dec&#039;&#039;&#039; und der &#039;&#039;&#039;brne&#039;&#039;&#039; benötigt 2 Takte, wenn die Bedingung zutrifft, der Branch also genommen wird. Bei 4 Mhz werden also 4000000 / 3 * 50 / 1000000 = 66.6 Durchläufe durch die Schleife benötigt, um eine Verzögerungszeit von 50µs (0.000050 Sekunden) zu erreichen, hexadezimal ausgedrückt: $42.&lt;br /&gt;
&lt;br /&gt;
Der springende Punkt ist: Bei anderen Taktfrequenzen müsste man nun jedesmal diese Berechnung machen und den entsprechenden Zahlenwert einsetzen. Das kann aber der Assembler genausogut erledigen. Am Anfang des Codes wird ein Eintrag definiert, der die Taktfrequenz festlegt. Traditionell heißt dieser Eintrag &amp;lt;i&amp;gt;XTAL&amp;lt;/i&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.equ XTAL  = 4000000&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
delay50us:                              ; 50µs Pause&lt;br /&gt;
           ldi  temp1, ( XTAL * 50 / 3 ) / 1000000&lt;br /&gt;
delay50us_:&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An einer anderen Codestelle gibt es weitere derartige magische Zahlen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause&lt;br /&gt;
           ldi  temp1, $21&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Was geht hier vor?&lt;br /&gt;
Die innere Schleife benötigt wieder 3 Takte pro Durchlauf. Bei $C9 = 201 Durchläufen werden also 201 * 3 = 603 Takte verbraucht. In der äußeren Schleife werden pro Durchlauf alo 1 + 603 + 1 + 2 = 607 Takte verbraucht. Da die äußere Schleife $21 = 33 mal wiederholt wird, werden 20031 Takte verbraucht. Bei 4Mhz benötigt der Prozessor 20031 / 4000000 = 0.005007 Sekunden, also 5 ms.&lt;br /&gt;
Wird der Wiederholwert für die innere Schleife bei $C9 belassen, so werden 4000000 / 607 * 5 / 1000 Wiederholungen der äusseren Schleife benötigt. Auch diese Berechnung kann wieder der Assembler übernehmen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause&lt;br /&gt;
           ldi  temp1, ( XTAL * 5 / 607 ) / 1000&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ein kleines Problem kann bei der Verwendung dieses Verfahrens entstehen: Bei hohen Taktfrequenzen und großen Wartezeiten kann der berechnete Wert größer als 255 werden und man bekommt die Fehlermeldung &amp;quot;Operand(s) out of range&amp;quot; beim Assemblieren. Dieser Fall tritt zum Beispiel für obige Konstruktion bei einer Taktfrequenz von 16 MHz ein (genauer gesagt ab 15,3 MHz), während darunter XTAL beliebig geändert werden kann. Als einfachste Lösung bietet es sich an, die Zahl der Takte pro Schleifendurchlauf durch das Einfügen von &#039;&#039;&#039;nop&#039;&#039;&#039; zu erhöhen und die Berechnungsvorschrift anzupassen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ausgabe eines konstanten Textes ==&lt;br /&gt;
&lt;br /&gt;
Weiter oben wurde schon einmal ein Text ausgegeben. Dies geschah durch Ausgabe von einzelnen Zeichen. Das können wir auch anders machen. Wir können den Text im Speicher ablegen und eine Funktion schreiben, die die einzelnen Zeichen aus dem Speicher liest und aus gibt. Dabei stellt sich Frage: Woher &#039;weiß&#039; die Funktion eigentlich, wie lang der Text ist? Die Antwort darauf lautet: Sie kann es nicht wissen. Wir müssen irgendwelche Vereinbarungen treffen, woran die Funktion erkennen kann, dass der Text zu Ende ist. Im Wesentlichen werden dazu 2 Methoden benutzt:&lt;br /&gt;
* Der Text enthält ein spezielles Zeichen, welches das Ende des Textes markiert&lt;br /&gt;
* Wir speichern nicht nur den Text selbst, sondern auch die Länge des Textes&lt;br /&gt;
Mit einer der beiden Methoden ist es der Textausgabefunktion dann ein Leichtes, den Text vollständig auszugeben.&lt;br /&gt;
&lt;br /&gt;
Wir werden uns im Weiteren dafür entscheiden, ein spezielles Zeichen, eine 0 (den Wert 0, nicht das Zeichen &#039;0&#039;), dafür zu benutzen. Die Ausgabefunktionen werden dann etwas einfacher, als wenn bei der Ausgabe die Anzahl der bereits ausgegebenen Zeichen mitgezählt werden muss.&lt;br /&gt;
&lt;br /&gt;
Den Text selbst speichern wir im Flash-Speicher, also dort, wo auch das Programm gespeichert ist:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Einen konstanten Text aus dem Flash Speicher&lt;br /&gt;
 ; ausgeben. Der Text wird mit einer 0 beendet&lt;br /&gt;
lcd_flash_string:&lt;br /&gt;
           push  temp1&lt;br /&gt;
           push  ZH&lt;br /&gt;
           push  ZL&lt;br /&gt;
&lt;br /&gt;
lcd_flash_string_1:&lt;br /&gt;
           lpm   temp1, Z+&lt;br /&gt;
           cpi   temp1, 0&lt;br /&gt;
           breq  lcd_flash_string_2&lt;br /&gt;
           rcall  lcd_data&lt;br /&gt;
           rjmp  lcd_flash_string_1&lt;br /&gt;
&lt;br /&gt;
lcd_flash_string_2:&lt;br /&gt;
           pop   ZL&lt;br /&gt;
           pop   ZH&lt;br /&gt;
           pop   temp1&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Diese Funktion benutzt den Befehl &#039;&#039;&#039;lpm&#039;&#039;&#039;, um das jeweils nächste Zeichen aus dem Flash Speicher in ein Register zur Weiterverarbeitung zu laden. Dazu wird der sog. &#039;&#039;&#039;Z-Pointer&#039;&#039;&#039; benutzt. So nennt man das Registerpaar &#039;&#039;&#039;R30&#039;&#039;&#039; und &#039;&#039;&#039;R31&#039;&#039;&#039;. Nach jedem Ladevorgang wird dabei durch den Befehl&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           lpm   temp1, Z+&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
dieser Z-Pointer um 1 erhöht. Mittels &#039;&#039;&#039;cpi&#039;&#039;&#039; wird das in das Register &#039;&#039;&#039;temp1&#039;&#039;&#039; geladene Zeichen mit 0 verglichen. &#039;&#039;&#039;cpi&#039;&#039;&#039; vergleicht die beiden Zahlen und merkt sich das Ergebnis in einem speziellen Register in Form von Status Bits. &#039;&#039;&#039;cpi&#039;&#039;&#039; zieht dabei ganz einfach die beiden Zahlen voneinander ab. Sind sie gleich, so kommt da als Ergebnis 0 heraus und &#039;&#039;&#039;cpi&#039;&#039;&#039; setzt daher konsequenter Weise das Zero-Flag, das anzeigt, dass die vorhergegangene Operation eine 0 als Ergebnis hatte.&#039;&#039;&#039;breq&#039;&#039;&#039; wertet diese Status-Bits aus. Wenn die vorhergegangene Operation ein 0-Ergebnis hatte, das Zero-Flag also gesetzt ist, dann wird ein Sprung zum angegebenen Label durchgeführt. In Summe bewirkt also die Sequenz&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           cpi   temp1, 0&lt;br /&gt;
           breq  lcd_flash_string_2&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
dass das gelesene Zeichen mit 0 verglichen wird und falls das gelesene&lt;br /&gt;
Zeichen tatsächlich 0 war, an der Stelle lcd_flash_string_2 weiter gemacht wird. Im anderen Fall wird die bereits geschriebene Funktion &#039;&#039;&#039;lcd_data&#039;&#039;&#039; aufgerufen, welche das Zeichen ausgibt. &#039;&#039;&#039;lcd_data&#039;&#039;&#039; erwartet dabei das Zeichen im Register &#039;&#039;&#039;temp1&#039;&#039;&#039;, genau in dem Register, in welches wir vorher mittels &#039;&#039;&#039;lpm&#039;&#039;&#039; das Zeichen geladen hatten.&lt;br /&gt;
&lt;br /&gt;
Das verwendende Programm sieht dann so aus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.include &amp;quot;m8def.inc&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
.def temp1 = r16&lt;br /&gt;
.def temp2 = r17&lt;br /&gt;
.def temp3 = r18&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
           ldi temp1, LOW(RAMEND)      ; LOW-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPL, temp1&lt;br /&gt;
           ldi temp1, HIGH(RAMEND)     ; HIGH-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPH, temp1&lt;br /&gt;
 &lt;br /&gt;
           rcall lcd_init              ; Display initialisieren&lt;br /&gt;
           rcall lcd_clear             ; Display löschen&lt;br /&gt;
 &lt;br /&gt;
           ldi ZL, LOW(text*2)         ; Adresse des Strings in den&lt;br /&gt;
           ldi ZH, HIGH(text*2)        ; Z-Pointer laden&lt;br /&gt;
&lt;br /&gt;
           rcall lcd_flash_string      ; Unterprogramm gibt String aus der&lt;br /&gt;
                                       ; durch den Z-Pointer adressiert wird&lt;br /&gt;
loop:&lt;br /&gt;
           rjmp loop&lt;br /&gt;
&lt;br /&gt;
text:&lt;br /&gt;
           .db &amp;quot;Test&amp;quot;,0                ; Stringkonstante, durch eine 0&lt;br /&gt;
                                       ; abgeschlossen  &lt;br /&gt;
&lt;br /&gt;
.include &amp;quot;lcd-routines.asm&amp;quot;            ; LCD Funktionen&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Genaueres über die Verwendung unterschiedlicher Speicher findet sich im Kapitel [[AVR-Tutorial:_Speicher|Speicher]]&lt;br /&gt;
&lt;br /&gt;
==Zahlen ausgeben==&lt;br /&gt;
Um Zahlen, die beispielsweise in einem Register gespeichert sind, ausgeben zu können, ist es notwendig sich eine Textrepräsentierung der Zahl zu generieren. Die Zahl 123 wird also in den Text &amp;quot;123&amp;quot; umgewandelt welcher dann ausgegeben wird. Aus praktischen Gründen wird allerdings der Text nicht vollständig generiert (man müsste ihn ja irgendwo zwischenspeichern) sondern die einzelnen Buchstaben werden sofort ausgegeben, sobald sie bekannt sind.&lt;br /&gt;
&lt;br /&gt;
===Dezimal ausgeben===&lt;br /&gt;
Das Prinzip der Umwandlung ist einfach. Um herauszufinden wieviele Hunderter in der Zahl 123 enthalten sind, genügt es in einer Schleife immer wieder 100 von der Zahl abzuziehen und mitzuzählen wie oft dies gelang, bevor das Ergebnis negativ wurde. In diesem Fall lautet die Antwort: 1 mal, denn 123 - 100 macht 23. Versucht man erneut 100 anzuziehen, so ergibt sich eine negative Zahl.&lt;br /&gt;
Also muss eine &#039;1&#039; ausgeben werden. Die verbleibenden 23 werden weiter behandelt, indem festgestellt wird wieviele Zehner darin enthalten sind. Auch hier wiederum: In einer Schleife solange 10 abziehen, bis das Ergebnis negativ wurde. Konkret geht das 2 mal gut, also muss das nächste auszugebende Zeichen ein &#039;2&#039; sein. Damit verbleiben noch die Einer, welche direkt in das entsprechende Zeichen umgewandelt werden können. In Summe hat man also an das Display die Zeichen &#039;1&#039; &#039;2&#039; &#039;3&#039; ausgegeben.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 8 Bit Zahl ohne Vorzeichen ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_number:&lt;br /&gt;
           push  temp1            ; die Funktion verändert temp1 und temp2,&lt;br /&gt;
           push  temp2            ; also sichern wir den Inhalt, um ihn am Ende&lt;br /&gt;
                                  ; wieder herstellen zu können&lt;br /&gt;
&lt;br /&gt;
           mov   temp2, temp1     ; das Register temp1 frei machen&lt;br /&gt;
                                  ; abzählen wieviele Hunderter&lt;br /&gt;
                                  ; in der Zahl enthalten sind&lt;br /&gt;
;** Hunderter ** &lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1     ; temp1 mit ASCII &#039;0&#039;-1 vorladen&lt;br /&gt;
lcd_number_1:&lt;br /&gt;
           inc   temp1            ; ASCII erhöhen (somit ist nach dem ersten&lt;br /&gt;
                                  ; Durchlauf eine &#039;0&#039; in temp1)&lt;br /&gt;
           subi  temp2, 100       ; 100 abziehen&lt;br /&gt;
           brcc  lcd_number_1     ; ist dadurch kein Unterlauf entstanden?&lt;br /&gt;
                                  ; nein, dann zurück zu lcd_number_1&lt;br /&gt;
           subi  temp2, -100      ; 100 wieder dazuzählen, da die&lt;br /&gt;
                                  ; vorherhgehende Schleife 100 zuviel&lt;br /&gt;
                                  ; abgezogen hat&lt;br /&gt;
           rcall lcd_data         ; die Hunderterstelle ausgeben&lt;br /&gt;
&lt;br /&gt;
;** Zehner  **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1     ; temp1 mit ASCII &#039;0&#039;-1 vorladen&lt;br /&gt;
lcd_number_2:&lt;br /&gt;
           inc   temp1            ; ASCII erhöhen (somit ist nach dem ersten&lt;br /&gt;
                                  ; Durchlauf eine &#039;0&#039; in temp1)&lt;br /&gt;
           subi  temp2, 10        ; 10 abziehen&lt;br /&gt;
           brcc  lcd_number_2     ; ist dadurch kein Unterlauf enstanden?&lt;br /&gt;
                                  ; nein, dann zurück zu lcd_number_2&lt;br /&gt;
           subi  temp2, -10       ; 10 wieder dazuzählen, da die&lt;br /&gt;
                                  ; vorherhgehende Schleife 10 zuviel&lt;br /&gt;
                                  ; abgezogen hat&lt;br /&gt;
           rcall lcd_data         ; die Zehnerstelle ausgeben&lt;br /&gt;
 &lt;br /&gt;
;** Einer **        &lt;br /&gt;
           ldi   temp1, &#039;0&#039;       ; die Zahl in temp2 ist jetzt im Bereich&lt;br /&gt;
           add   temp1, temp2     ; 0 bis 9. Einfach nur den ASCII Code für&lt;br /&gt;
           rcall lcd_data         ; &#039;0&#039; dazu addieren und wir erhalten dierekt&lt;br /&gt;
                                  ; den ASCII Code für die Ziffer&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
           pop   temp2            ; den gesicherten Inhalt von temp2 und temp1&lt;br /&gt;
           pop   temp1            ; wieder herstellen&lt;br /&gt;
           ret                    ; und zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Beachte: Diese Funktion benutzt wiederrum die Funktion &#039;&#039;&#039;lcd_data&#039;&#039;&#039;. Anders als bei den bisherigen Aufrufen ist &#039;&#039;&#039;lcd_number&#039;&#039;&#039; aber darauf angewiesen, dass &#039;&#039;&#039;lcd_data&#039;&#039;&#039; das Register &#039;&#039;&#039;temp2&#039;&#039;&#039; unangetastet lässt. Falls sie es noch nicht getan haben, dann ist das jetzt die perfekte Gelegenheit, &#039;&#039;&#039;lcd_data&#039;&#039;&#039; mit den entsprechenden &#039;&#039;&#039;push&#039;&#039;&#039; und &#039;&#039;&#039;pop&#039;&#039;&#039; Befehlen zu versehen. Sie sollten dies unbedingt zur Übung selbst machen. Am Ende muß die Funktion dann wie diese hier aussehen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ;sendet ein Datenbyte an das LCD&lt;br /&gt;
lcd_data:&lt;br /&gt;
           push  temp2&lt;br /&gt;
           mov   temp2, temp1           ; &amp;quot;Sicherungskopie&amp;quot; für&lt;br /&gt;
                                        ; die Übertragung des 2.Nibbles&lt;br /&gt;
           swap  temp1                  ; Vertauschen&lt;br /&gt;
           andi  temp1, 0b00001111      ; oberes Nibble auf Null setzen&lt;br /&gt;
           sbr   temp1, 1&amp;lt;&amp;lt;PIN_RS       ; entspricht 0b00010000&lt;br /&gt;
           out   LCD_PORT, temp1        ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
                                        ; 2. Nibble, kein swap da es schon&lt;br /&gt;
                                        ; an der richtigen stelle ist&lt;br /&gt;
           andi  temp2, 0b00001111      ; obere Hälfte auf Null setzen &lt;br /&gt;
           sbr   temp2, 1&amp;lt;&amp;lt;PIN_RS       ; entspricht 0b00010000&lt;br /&gt;
           out   LCD_PORT, temp2        ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
           rcall delay50us              ; Delay-Routine aufrufen&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           ret                          ; zurück zum Hauptprogramm&lt;br /&gt;
 &lt;br /&gt;
 ; sendet einen Befehl an das LCD&lt;br /&gt;
lcd_command:                            ; wie lcd_data, nur ohne RS zu setzen&lt;br /&gt;
           push  temp2&lt;br /&gt;
           mov   temp2, temp1&lt;br /&gt;
           swap  temp1&lt;br /&gt;
           andi  temp1, 0b00001111&lt;br /&gt;
           out   LCD_PORT, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           andi  temp2, 0b00001111&lt;br /&gt;
           out   LCD_PORT, temp2&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay50us&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kurz zur Funktionsweise der Funktion &#039;&#039;&#039;lcd_number&#039;&#039;&#039;: Die Zahl in einem Register bewegt sich im Wertebereich 0 bis 255. Um herauszufinden, wie die Hunderterstelle lautet, zieht die Funktion einfach in einer Schleife immer wieder 100 von der Schleife ab, bis bei der Subtraktion ein Unterlauf, angezeigt durch das Setzen des Carry-Bits bei der Subtraktion, entsteht. Die Anzahl wird im Register &#039;&#039;&#039;temp1&#039;&#039;&#039; mitgezählt. Da dieses Register mit dem ASCII Code von &#039;0&#039; initialisiert wurde, und dieser ASCII Code bei jedem Schleifendurchlauf um 1 erhöht wird, können wir das Register &#039;&#039;&#039;temp1&#039;&#039;&#039; direkt zur Ausgabe des Zeichens für die Hunderterstelle durch die Funktion &#039;&#039;&#039;lcd_data&#039;&#039;&#039; benutzen. Völlig analog funktioniert auch die Ausgabe der Zehnerstelle.&lt;br /&gt;
&lt;br /&gt;
===Unterdrückung von führenden Nullen===&lt;br /&gt;
&lt;br /&gt;
Diese Funktion gibt jede Zahl im Register &#039;&#039;&#039;temp1&#039;&#039;&#039; immer mit 3 Stellen aus. Führende Nullen werden nicht unterdrückt. Möchte man dies ändern, so ist das ganz leicht möglich: Vor Ausgabe der Hunderterstelle muss lediglich überprüft werden, ob die Entsprechende Ausgabe eine &#039;0&#039; wäre. Ist sie das, so wird die Ausgabe übersprungen. Ist es allerdings eine Zahl 1..9, so muss sie der Zehner Stelle signalisieren, daß eine Prüfung auf eine &#039;0&#039; nicht stattfinden darf. Und dazu wird das T-Flag im SREG genutzt. Lediglich in der Einerstelle wird jede Ziffer wie errechnet ausgegeben.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ...&lt;br /&gt;
                                  ; die Hunderterstelle ausgeben, wenn&lt;br /&gt;
                                  ; sie nicht &#039;0&#039; ist&lt;br /&gt;
           clt                    ; T-Flag löschen&lt;br /&gt;
           cpi   temp1, &#039;0&#039;&lt;br /&gt;
           breq  lcd_number_1a&lt;br /&gt;
           rcall lcd_data         ; die Hunderterstelle ausgeben&lt;br /&gt;
           set                    ; T-Flag im SREG setzen da 100er Stelle eine&lt;br /&gt;
                                  ; 1..9 war&lt;br /&gt;
&lt;br /&gt;
lcd_number_1a:&lt;br /&gt;
           ...&lt;br /&gt;
&lt;br /&gt;
           ...&lt;br /&gt;
           brts  lcd_number_2a    ; Test auf &#039;0&#039; überspringen, da 100er eine&lt;br /&gt;
                                  ; 1..9 war (unbedingt anzeigen&lt;br /&gt;
                                  ; auch wenn der Zehner eine &#039;0&#039; ist)&lt;br /&gt;
           cpi   temp1, &#039;0&#039;       ; ansonsten Test auf &#039;0&#039;&lt;br /&gt;
           breq  lcd_number_2b&lt;br /&gt;
lcd_number_2a:        &lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
lcd_number_2b:&lt;br /&gt;
           ...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Das Verfahren, die einzelnen Stellen durch Subtraktion zu bestimmen, ist bei kleinen Zahlen eine durchaus gängige Alternative. Vor allem dann, wenn keine hardwaremäßige Unterstützung für Multiplikation und Division zur Verfügung steht. Ansonsten könnte man die die einzelnen Ziffern auch durch Division bestimmen. Das Prinzip ist folgendes (beispielhaft an der Zahl 52783 gezeigt)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   52783 / 10          -&amp;gt; 5278&lt;br /&gt;
   52783 - 5278 * 10   -&amp;gt;          3&lt;br /&gt;
&lt;br /&gt;
   5278 / 10           -&amp;gt; 527&lt;br /&gt;
   5278 - 527 * 10     -&amp;gt;          8&lt;br /&gt;
&lt;br /&gt;
   527 / 10            -&amp;gt; 52&lt;br /&gt;
   527 - 52 * 10       -&amp;gt;          7&lt;br /&gt;
&lt;br /&gt;
   52 / 10             -&amp;gt; 5&lt;br /&gt;
   52 - 5 * 10         -&amp;gt;          2&lt;br /&gt;
&lt;br /&gt;
   5 / 10              -&amp;gt; 0&lt;br /&gt;
   5 - 0 * 10          -&amp;gt;          5&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Prinzip ist also die Restbildung bei einer fortgesetzten Division durch 10, wobei die einzelnen Ziffern in umgekehrter Reihenfolge ihrer Wertigkeit entstehen. Dadurch hat man aber ein Problem: Damit die Zeichen in der richtigen Reihenfolge ausgegeben werden können, muß man sie meistens zwischenspeichern um sie in der richtigen Reihenfole ausgeben zu können. Wird die Zahl in einem Feld von immer gleicher Größe ausgegeben, dann kann man auch die Zahl von rechts nach links ausgeben (bei einem LCD ist das möglich).&lt;br /&gt;
&lt;br /&gt;
===Hexadezimal ausgeben===&lt;br /&gt;
&lt;br /&gt;
Zu guter letzt hier noch eine Funktion, die eine Zahl aus dem Register &#039;&#039;&#039;temp1&#039;&#039;&#039; in hexadezimaler Form ausgibt. Die Funktion weist keine Besonderheiten auf und sollte unmittelbar verständlich sein.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 8 Bit Zahl ohne Vorzeichen hexadezimal ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_number_hex:&lt;br /&gt;
           swap  temp1&lt;br /&gt;
           rcall lcd_number_hex_digit&lt;br /&gt;
           swap  temp1&lt;br /&gt;
&lt;br /&gt;
lcd_number_hex_digit:&lt;br /&gt;
           push  temp1&lt;br /&gt;
&lt;br /&gt;
           andi  temp1, $0F&lt;br /&gt;
           cpi   temp1, 10&lt;br /&gt;
           brlt  lcd_number_hex_digit_1&lt;br /&gt;
           subi  temp1, -( &#039;A&#039; - &#039;9&#039; - 1 ) ; es wird subi mit negativer&lt;br /&gt;
                                           ; Konstante verwendet,&lt;br /&gt;
                                           ; weil es kein addi gibt&lt;br /&gt;
lcd_number_hex_digit_1:&lt;br /&gt;
           subi  temp1, -&#039;0&#039;               ; ditto&lt;br /&gt;
           rcall  lcd_data&lt;br /&gt;
           &lt;br /&gt;
           pop   temp1&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Binär ausgeben===&lt;br /&gt;
Um die Sache komplett zu machen; Hier eine Routine mit der man eine 8 Bit-Zahl binär auf das LC-Display ausgeben kann:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 8 Bit Zahl ohne Vorzeichen binär ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
&lt;br /&gt;
; eine Zahl aus dem Register temp1 binär ausgeben&lt;br /&gt;
lcd_number_bit:&lt;br /&gt;
	   push temp1		  ; temp1 gesichert&lt;br /&gt;
           push temp2&lt;br /&gt;
	   push temp3&lt;br /&gt;
&lt;br /&gt;
	   mov temp2, temp1;&lt;br /&gt;
&lt;br /&gt;
	   ldi temp3, 8;      ; 8 Bits werden ausgelesen&lt;br /&gt;
lcd_number_loop:           &lt;br /&gt;
	   dec temp3;&lt;br /&gt;
	   rol temp2;         ; Datenbits ins Carry geschoben ...&lt;br /&gt;
	   brcc lcd_number_bit_carryset_0; &lt;br /&gt;
	   brcs lcd_number_bit_carryset_1;&lt;br /&gt;
           rjmp lcd_number_loop;&lt;br /&gt;
&lt;br /&gt;
lcd_number_bit_carryset_0:	 &lt;br /&gt;
	   ldi temp1, &#039;0&#039;     ; Bit low ausgeben&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
	   tst temp3;&lt;br /&gt;
	   breq lcd_number_ende;&lt;br /&gt;
	   rjmp lcd_number_loop;&lt;br /&gt;
&lt;br /&gt;
lcd_number_bit_carryset_1:&lt;br /&gt;
           ldi temp1, &#039;1&#039;     ; Bit high ausgeben&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           tst temp3;&lt;br /&gt;
	   breq lcd_number_ende;&lt;br /&gt;
	   rjmp lcd_number_loop;&lt;br /&gt;
&lt;br /&gt;
lcd_number_ende:&lt;br /&gt;
	   pop temp3&lt;br /&gt;
	   pop temp2&lt;br /&gt;
	   pop temp1&lt;br /&gt;
	   ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Eine 16-Bit Zahl aus einem Registerpärchen ausgeben===&lt;br /&gt;
&lt;br /&gt;
Um eine 16 Bit Zahl auszugeben wird wieder das bewährte Schema benutzt die einzelnen Stellen durch Subtraktion abzuzählen. Da es sich hierbei allerdings um eine 16 Bit Zahl handelt, müssen die Subtraktionen als 16-Bit Arithmetik ausgeführt werden.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 16 Bit Zahl ohne Vorzeichen ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp2 (low Byte) / temp3 (high Byte)&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_number16:&lt;br /&gt;
           push  temp1&lt;br /&gt;
           push  temp2&lt;br /&gt;
           push  temp3&lt;br /&gt;
&lt;br /&gt;
; ** Zehntausender **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number1:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, low(10000)&lt;br /&gt;
           sbci  temp3, high(10000)&lt;br /&gt;
           brcc  lcd_number1&lt;br /&gt;
           subi  temp2, low(-10000)&lt;br /&gt;
           sbci  temp3, high(-10000)&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Tausender **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number2:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, low(1000)&lt;br /&gt;
           sbci  temp3, high(1000)&lt;br /&gt;
           brcc  lcd_number2&lt;br /&gt;
           subi  temp2, low(-1000)&lt;br /&gt;
           sbci  temp3, high(-1000)&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Hunderter **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number3:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, low(100)&lt;br /&gt;
           sbci  temp3, high(100)&lt;br /&gt;
           brcc  lcd_number3&lt;br /&gt;
           subi  temp2, -100             ; + 100 High-Byte nicht mehr erforderlich&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Zehner **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number4:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, 10&lt;br /&gt;
           brcc  lcd_number4&lt;br /&gt;
           subi  temp2, -10&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Einer **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;&lt;br /&gt;
           add   temp1, temp2&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Stack aufräumen **&lt;br /&gt;
           pop   temp3&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           pop   temp1&lt;br /&gt;
&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Eine BCD Zahl ausgeben===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            BCD Zahl in temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_bcd:&lt;br /&gt;
           push  temp2&lt;br /&gt;
          &lt;br /&gt;
           mov   temp2, temp1           ; temp1 sichern&lt;br /&gt;
           swap  temp1                  ; oberes mit unterem Nibble tauschen&lt;br /&gt;
           andi  temp1, 0b00001111      ; und &amp;quot;oberes&amp;quot; ausmaskieren&lt;br /&gt;
           subi  temp1, -0x30           ; in ASCII umrechnen&lt;br /&gt;
           rcall lcd_data               ; und ausgeben&lt;br /&gt;
           mov   temp1, temp2           ; ... danach unteres&lt;br /&gt;
           andi  temp1, 0b00001111&lt;br /&gt;
           subi  temp1, -0x30&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           mov   temp1, temp2           ; temp1 rekonstruieren&lt;br /&gt;
&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           ret &lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Benutzerdefinierte Zeichen ==&lt;br /&gt;
[[Bild:LCD_Character_Grid.png | framed | right| Zeichenraster für 1 Zeichen]]&lt;br /&gt;
&lt;br /&gt;
Das LCD erlaubt für spezielle Zeichen, welche sich nicht im Zeichensatz finden, eigene Zeichen zu definieren. Dazu werden die ersten 8 ASCII Codes reserviert, auf denen sich laut ASCII Tabelle spezielle Steuerzeichen befinden, die normalerweise keine sichtbare Anzeige hervorrufen sondern zur Steuerung von angeschlossenen Geräten dienen. Da diese Zeichen auf einem LCD keine Rolle spielen, können diese Zeichen benutzt werden um sich selbst Sonderzeichen zu erzeugen, die für die jeweilige Anwendung massgeschneidert sind.&lt;br /&gt;
&lt;br /&gt;
Das LCD stellt für jedes Zeichen eine 8*5 Matrix zur Verfügung. Um sich selbst massgeschneiderte Zeichen zu erstellen, ist es am einfachsten sich zunächst auf einem Stück karriertem Papier zu erstellen.&lt;br /&gt;
&lt;br /&gt;
[[Bild:BellCharacter.png | framed | right| Zeichenraster für ein Glockensymbol]]&lt;br /&gt;
&lt;br /&gt;
In diesem Raster markiert man sich dann diejenigen Pixel, die im fertigen Zeichen dunkel erscheinen sollen. Als Beispiel sei hier ein Glockensymbol gezeichnet, welches in einer Telefonapplikation zb als Kennzeichnung für einen Anruf dienen könnte.&lt;br /&gt;
&lt;br /&gt;
Eine Zeile in diesem Zeichen repräsentiert ein an das LCD zu übergebendes Byte, wobei nur die Bits 0 bis 4 relevant sind. Gesetzte Pixel stellen ein 1 Bit dar, nicht gesetzte Pixel sind ein 0-Bit. Das niederwertigste Bit einer Zeile befindet sich rechts. Auf diese Art wird jede Zeile in eine Binärzahl übersetzt, und 8 Bytes repräsentieren ein komplettes Zeichen. Am Beispiel des Glockensymboles: Die 8 Bytes, welches das Symbol repräsentiern, lauten: 0x00, 0x04, 0x0A, 0x0A, 0x0A, 0x1F, 0x04, 0x00,&lt;br /&gt;
&lt;br /&gt;
Dem LCD wird die neue Definition übertragen, indem man dem LCD die &#039;Schreibposition&#039; mittels des Kommandos &#039;&#039;Character RAM Address Set&#039;&#039; in den Zeichensatzgenerator verschiebt. Danach werden die 8 Bytes ganz normal als Daten ausgegeben, die das LCD damit in seine Zeichensatztabelle schreibt.&lt;br /&gt;
&lt;br /&gt;
Durch die Wahl der Speicheradresse definiert man, welches Zeichen (0 bis 7) man eigentlich durch eine eigene Definition ersetzen will.&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
! ASCII Code || Zeichensatzadresse&lt;br /&gt;
|-&lt;br /&gt;
| 0 || 0x00&lt;br /&gt;
|-&lt;br /&gt;
| 1 || 0x08&lt;br /&gt;
|-&lt;br /&gt;
| 2 || 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 3 || 0x18&lt;br /&gt;
|-&lt;br /&gt;
| 4 || 0x20&lt;br /&gt;
|-&lt;br /&gt;
| 5 || 0x28&lt;br /&gt;
|-&lt;br /&gt;
| 6 || 0x30&lt;br /&gt;
|-&lt;br /&gt;
| 7 || 0x38&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Nach erfolgter Definition des Zeichens, muss die Schreibposition wieder explizit in den DDRAM-Bereich gesetzt werden.&lt;br /&gt;
Danach kann ein entsprechendes Zeichen mit dem definierten ASCII Code ausgegeben werden, wobei das LCD die von uns definierte Pixelform zur Anzeige benutzt.&lt;br /&gt;
&lt;br /&gt;
Zuerst müssen natürlich erstmal die Zeichen definiert werden.&lt;br /&gt;
Dieses geschieht einmalig durch den Aufruf der Routine &amp;quot;lcd_load_user_chars&amp;quot;&lt;br /&gt;
unmittelbar nach der Initialisierung des LCD-Displays.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
           rcall lcd_init              ; Display initialisieren&lt;br /&gt;
           rcall lcd_load_user_chars   ; User Zeichen in das Display laden&lt;br /&gt;
           rcall lcd_clear             ; Display löschen&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durch diesen Aufruf werden die im Flash definierten Zeichen in den&lt;br /&gt;
GC-Ram übertragen. Diese Zeichen werden ab Adresse 0 im GC-Ram&lt;br /&gt;
gespeichert und sind danach wie jedes andere Zeichen nutzbar.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
           ldi   temp1, 0              ; Ausgabe des User-Char &amp;quot;A&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 6              ; Ausgabe des User-Char &amp;quot;G&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 5              ; Ausgabe des User-Char &amp;quot;E&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 4              ; Ausgabe des User-Char &amp;quot;M&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 3              ; Ausgabe des User-Char &amp;quot;-&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 2              ; Ausgabe des User-Char &amp;quot;R&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 1              ; Ausgabe des User-Char &amp;quot;V&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 0              ; Ausgabe des User-Char &amp;quot;A&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Jetzt sollte der Schriftzug &amp;quot;AVR-MEGA&amp;quot;&lt;br /&gt;
verkehrt herum (180 Grad gedreht) erscheinen.&lt;br /&gt;
&lt;br /&gt;
Es fehlt natürlich noch die Laderoutine:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Lädt User Zeichen in den GC-Ram des LCD bis Tabellenende (0xFF)&lt;br /&gt;
; gelesen wird. (max. 8 Zeichen können geladen werden)&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            -   &lt;br /&gt;
; veränderte Register: temp1, temp2, temp3, zh, zl&lt;br /&gt;
; Bemerkung:           ist einmalig nach lcd_init aufzurufen&lt;br /&gt;
;       &lt;br /&gt;
&lt;br /&gt;
lcd_load_user_chars:&lt;br /&gt;
        ldi    zl, LOW (ldc_user_char * 2) ; Adresse der Zeichentabelle&lt;br /&gt;
        ldi    zh, HIGH(ldc_user_char * 2) ; in den Z-Pointer laden&lt;br /&gt;
        clr    temp3                       ; aktuelles Zeichen = 0 &lt;br /&gt;
&lt;br /&gt;
lcd_load_user_chars_2:&lt;br /&gt;
        clr    temp2                       ; Linienzähler = 0&lt;br /&gt;
&lt;br /&gt;
lcd_load_user_chars_1:&lt;br /&gt;
        ldi    temp1, 0b01000000           ; Kommando:    0b01aaalll&lt;br /&gt;
        add    temp1, temp3                ; + akt. Zeichen  (aaa)&lt;br /&gt;
        add    temp1, temp2                ; + akt. Linie       (lll)&lt;br /&gt;
        rcall  lcd_command                 ; Kommando schreiben&lt;br /&gt;
&lt;br /&gt;
        lpm    temp1, Z+                   ; Zeichenline laden &lt;br /&gt;
        rcall  lcd_data                    ; ... und ausgeben&lt;br /&gt;
&lt;br /&gt;
        ldi    temp1, 0b01001000           ; Kommando:    0b01aa1lll         &lt;br /&gt;
        add    temp1, temp3                ; + akt. Zeichen  (aaa)       &lt;br /&gt;
        add    temp1, temp2                ; + akt. Linie       (lll)&lt;br /&gt;
        rcall  lcd_command&lt;br /&gt;
&lt;br /&gt;
        lpm    temp1, Z+                   ; Zeichenline laden&lt;br /&gt;
        rcall  lcd_data                    ; ... und ausgeben &lt;br /&gt;
        &lt;br /&gt;
        inc    temp2                       ; Linienzähler + 1&lt;br /&gt;
        cpi    temp2, 8                    ; 8 Linien fertig?&lt;br /&gt;
        brne   lcd_load_user_chars_1       ; nein, dann nächste Linie &lt;br /&gt;
		&lt;br /&gt;
        subi   temp3, -0x10                ; zwei Zeichen weiter (addi 0x10)&lt;br /&gt;
        lpm    temp1, Z                    ; nächste Linie laden&lt;br /&gt;
        cpi    temp1, 0xFF                 ; Tabellenende erreicht? &lt;br /&gt;
        brne   lcd_load_user_chars_2       ; nein, dann die nächsten&lt;br /&gt;
                                           ; zwei Zeichen&lt;br /&gt;
        ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... und die Zeichendefinition:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
ldc_user_char:&lt;br /&gt;
                              ;    Zeichen &lt;br /&gt;
                              ;   0       1&lt;br /&gt;
       .db 0b10001, 0b00100   ; @   @ ,   @&lt;br /&gt;
       .db 0b10001, 0b01010   ; @   @ ,  @ @&lt;br /&gt;
       .db 0b11111, 0b10001   ; @@@@@ , @   @&lt;br /&gt;
       .db 0b10001, 0b10001   ; @   @ , @   @&lt;br /&gt;
       .db 0b10001, 0b10001   ; @   @ , @   @&lt;br /&gt;
       .db 0b10001, 0b10001   ; @   @ , @   @&lt;br /&gt;
       .db 0b01110, 0b10001   ;  @@@  , @   @&lt;br /&gt;
       .db 0b00000, 0b00000   ;       , &lt;br /&gt;
&lt;br /&gt;
                              ;    Zeichen&lt;br /&gt;
                              ;   2       3&lt;br /&gt;
       .db 0b10001, 0b00000   ; @   @ , &lt;br /&gt;
       .db 0b01001, 0b00000   ;  @  @ , &lt;br /&gt;
       .db 0b00101, 0b00000   ;   @ @ , &lt;br /&gt;
       .db 0b11111, 0b11111   ; @@@@@ , @@@@@ &lt;br /&gt;
       .db 0b10001, 0b00000   ; @   @ , &lt;br /&gt;
       .db 0b10001, 0b00000   ; @   @ , &lt;br /&gt;
       .db 0b01111, 0b00000   ;  @@@@ , &lt;br /&gt;
       .db 0b00000, 0b00000   ;       ,  &lt;br /&gt;
&lt;br /&gt;
                              ;    Zeichen&lt;br /&gt;
                              ;   4       5&lt;br /&gt;
       .db 0b10001, 0b11111   ; @   @ , @@@@@  &lt;br /&gt;
       .db 0b10001, 0b00001   ; @   @ ,     @&lt;br /&gt;
       .db 0b10001, 0b00001   ; @   @ ,     @&lt;br /&gt;
       .db 0b10001, 0b01111   ; @   @ ,  @@@@ &lt;br /&gt;
       .db 0b10101, 0b00001   ; @ @ @ ,     @&lt;br /&gt;
       .db 0b11011, 0b00001   ; @@ @@ ,     @&lt;br /&gt;
       .db 0b10001, 0b11111   ; @   @ , @@@@@&lt;br /&gt;
       .db 0b00000, 0b00000   ;       ,  &lt;br /&gt;
&lt;br /&gt;
                              ;    Zeichen&lt;br /&gt;
                              ;   6       7&lt;br /&gt;
       .db 0b11110, 0b11111   ; @@@@  , @@@@@  &lt;br /&gt;
       .db 0b10001, 0b01010   ; @   @ ,  @ @ &lt;br /&gt;
       .db 0b10001, 0b00100   ; @   @ ,   @&lt;br /&gt;
       .db 0b11101, 0b01110   ; @@@ @ ,  @@@&lt;br /&gt;
       .db 0b00001, 0b00100   ;     @ ,   @&lt;br /&gt;
       .db 0b10001, 0b01010   ; @   @ ,  @ @&lt;br /&gt;
       .db 0b01110, 0b11111   ;  @@@  , @@@@@&lt;br /&gt;
       .db 0b00000, 0b00000   ;       ,  &lt;br /&gt;
&lt;br /&gt;
       ; End of Tab&lt;br /&gt;
       .db 0xFF, 0xFF&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Der überarbeitete, komplette Code==&lt;br /&gt;
&lt;br /&gt;
Hier also die komplett überarbeitete Version der LCD Funktionen.&lt;br /&gt;
&lt;br /&gt;
Die für die Benutzung relevanten Funktionen&lt;br /&gt;
* &#039;&#039;&#039;lcd_init&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_clear&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_home&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_data&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_command&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_flash_string&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_number&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_number_hex&#039;&#039;&#039;&lt;br /&gt;
sind so ausgeführt, dass sie kein Register (ausser dem Statusregister &#039;&#039;&#039;SREG&#039;&#039;&#039;) verändern. Die bei manchen Funktionen notwendige Argumente werden immer im Register &#039;&#039;&#039;temp1&#039;&#039;&#039; übergeben, wobei &#039;&#039;&#039;temp1&#039;&#039;&#039; vom Usercode definiert werden muss.&lt;br /&gt;
&lt;br /&gt;
[[Media:lcd-routines.asm|Download lcd-routines.asm]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
{{Navigation_zurückhochvor|&lt;br /&gt;
zurücktext=Stack|&lt;br /&gt;
zurücklink=AVR-Tutorial: Stack|&lt;br /&gt;
hochtext=Inhaltsverzeichnis|&lt;br /&gt;
hochlink=AVR-Tutorial|&lt;br /&gt;
vortext=Interrupts|&lt;br /&gt;
vorlink=AVR-Tutorial: Interrupts}}&lt;br /&gt;
&lt;br /&gt;
[[Category:AVR-Tutorial]]&lt;br /&gt;
[[Category:LCD]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR-Tutorial:_LCD&amp;diff=62476</id>
		<title>AVR-Tutorial: LCD</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR-Tutorial:_LCD&amp;diff=62476"/>
		<updated>2011-12-16T16:29:54Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Benutzerdefinierte Zeichen */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Kaum ein elektronisches Gerät kommt heutzutage noch ohne ein LCD daher. Ist doch auch praktisch, Informationen im Klartext anzeigen zu können, ohne irgendwelche LEDs blinken zu lassen. Kein Wunder also, dass die häufigste Frage in Mikrocontroller-Foren ist: &amp;quot;Wie kann ich ein LCD anschließen?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==Das LCD und sein Controller==&lt;br /&gt;
&lt;br /&gt;
Die meisten Text-LCDs verwenden den Controller [[HD44780|&#039;&#039;&#039;HD44780&#039;&#039;&#039;]] oder einen kompatiblen (z.&amp;amp;nbsp;B. KS0070) und haben 14 oder 16 Pins.&amp;lt;br /&amp;gt;&lt;br /&gt;
Die Pinbelegung ist meist (Ausnahme z.&amp;amp;nbsp;B. TC1602E (Pollin 120420): V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt; und V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; vertauscht) folgendermaßen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;ACHTUNG: Es gibt Displays mit abweichender Anschluss-Belegung, falscher Anschluss kann zur Zerstörung führen! Daher immer das zugehörige Datenblatt zu Rate ziehen!&amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Einzelheiten unter [http://www.mikrocontroller.net/articles/HD44780 Artikel zum Controller HD44780]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
! Pin # || Bezeichnung || Funktion&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; (selten: V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt;)&lt;br /&gt;
||  GND (selten: +5 V)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt; (selten: V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt;)&lt;br /&gt;
||  +5 V (selten: GND)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt;, V0, V5&lt;br /&gt;
||  Kontrastspannung (-5 V / 0 V bis 5 V)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  4&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  RS&lt;br /&gt;
||  Register Select (0=Befehl/Status 1=Daten)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  5&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  RW&lt;br /&gt;
||  1=Read 0=Write&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  6&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  E&lt;br /&gt;
||  0=Disable 1=Enable&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  7&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB0&lt;br /&gt;
||  Datenbit 0&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  8&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB1&lt;br /&gt;
||  Datenbit 1&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  9&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB2&lt;br /&gt;
||  Datenbit 2&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  10&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB3&lt;br /&gt;
||  Datenbit 3&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  11&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB4&lt;br /&gt;
||  Datenbit 4&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  12&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB5&lt;br /&gt;
||  Datenbit 5&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  13&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB6&lt;br /&gt;
||  Datenbit 6&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  14&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB7&lt;br /&gt;
||  Datenbit 7&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  15&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  A&lt;br /&gt;
||  LED-Beleuchtung, meist Anode&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  16&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  K&lt;br /&gt;
||  LED-Beleuchtung, meist Kathode&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Achtung: Unbedingt von der richtigen Seite zu zählen anfangen! Meistens ist das Pin1-Pad eckig oder daneben eine kleine 1 auf der LCD-Platine, ansonsten im Datenblatt nachschauen.&lt;br /&gt;
&lt;br /&gt;
Bei der DIL-Version (2x7, 2x8 Kontakte) auch darauf achten, auf welcher Platinen-Seite der Stecker montiert wird: auf der falschen (meist hinteren) Seite sind dann die Flachbandleitungen 1 und 2, 3 und 4  usw. vertauscht. Das kann man kompensieren, indem man es auf der anderen Kabelseite genauso permutiert oder es auf dem Layout bewusst so legt (Stecker auf der Bottom-Seite plazieren). Man kann es NICHT kompensieren, indem man das Flachbandkabel auf der anderen Seite in den Stecker führt.&lt;br /&gt;
&lt;br /&gt;
Bei LCDs mit 16-poligem Anschluss sind die beiden letzten Pins für die Hintergrundbeleuchtung reserviert. Hier unbedingt das Datenblatt zu Rate ziehen. Die beiden Anschlüsse sind je nach Hersteller verdreht beschaltet. Falls kein Datenblatt vorliegt, kann man mit einem Durchgangsprüfer feststellen, welcher Anschluss mit Masse (GND) verbunden ist.&lt;br /&gt;
&lt;br /&gt;
V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; wird ganz einfach an GND angeschlossen und V&amp;lt;sub&amp;gt;CC&amp;lt;/sub&amp;gt;=V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt; an +5 V. V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt; = V0 = V5 kann man testweise auch an GND legen. Wenn das LCD dann zu dunkel sein sollte, muss man ein 10k&amp;amp;Omega;-Potentiometer zwischen GND und 5 V schalten, mit dem Schleifer an V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt;. Meist kann man den +5 V-Anschluss am Poti weglassen, da im Display ein Pull-up-Widerstand ist:&lt;br /&gt;
&lt;br /&gt;
[[Bild:LCD_Vee.gif|framed|center| Gewinnung der Kontrastspannung]]&lt;br /&gt;
&lt;br /&gt;
Wenn der Kontrast zu schwach sein sollte (z.B. bei tiefen Temperaturen), kann man anstelle von GND eine negative Spannung ans Kontrast-Poti legen. Diese kann bis -5 V gehen und kann leicht aus einem Timerpin des µC, einem Widerstand, zwei Dioden und zwei Kondensatoren erzeugt werden. So wird auch ein digital einstellbarer Kontrast mittels PWM ermöglicht.&lt;br /&gt;
&lt;br /&gt;
Es gibt zwei verschiedene Möglichkeiten zur Ansteuerung eines solchen Displays: den &#039;&#039;&#039;8-Bit-&#039;&#039;&#039; und den &#039;&#039;&#039;4-Bit-&#039;&#039;&#039;Modus.&lt;br /&gt;
* Für den &#039;&#039;&#039;8-Bit-Modus&#039;&#039;&#039; werden (wie der Name schon sagt) alle acht Datenleitungen zur Ansteuerung verwendet, somit kann durch einen Zugriff immer ein ganzes Byte übertragen werden.&lt;br /&gt;
* Der &#039;&#039;&#039;4-Bit-Modus&#039;&#039;&#039; verwendet nur die oberen vier Datenleitungen (&#039;&#039;&#039;DB4-DB7&#039;&#039;&#039;). Um ein Byte zu übertragen, braucht man somit zwei Zugriffe, wobei zuerst das höherwertige &#039;&#039;&#039;&amp;quot;Nibble&amp;quot;&#039;&#039;&#039; (= 4 Bits), also Bit 4 bis Bit 7 übertragen wird und dann das niederwertige, also Bit 0 bis Bit 3. Die unteren Datenleitungen des LCDs, die beim Lesezyklus Ausgänge sind, lässt man offen (siehe Datasheets, z.&amp;amp;nbsp;B. vom KS0070).&lt;br /&gt;
&lt;br /&gt;
Der 4-Bit-Modus hat den Vorteil, dass man 4 IO-Pins weniger benötigt als beim 8-Bit-Modus. 6 bzw. 7 Pins (eines Portes) reichen aus.&lt;br /&gt;
&lt;br /&gt;
Neben den vier Datenleitungen (DB4, DB5, DB6 und DB7) werden noch die Anschlüsse &#039;&#039;&#039;RS&#039;&#039;&#039;, &#039;&#039;&#039;RW&#039;&#039;&#039; und &#039;&#039;&#039;E&#039;&#039;&#039; benötigt. &lt;br /&gt;
&lt;br /&gt;
* Über &#039;&#039;&#039;RS&#039;&#039;&#039; wird ausgewählt, ob man einen Befehl oder ein Datenbyte an das LCD schicken möchte. Beim Schreiben gilt: ist RS Low, dann wird das ankommende Byte als Befehl interpretiert; Ist RS high, wird das Byte auf dem LCD angezeigt (genauer: ins Data-Register geschrieben, kann auch für den CG bestimmt sein). &lt;br /&gt;
* &#039;&#039;&#039;RW&#039;&#039;&#039; legt fest, ob geschrieben oder gelesen werden soll. High bedeutet lesen, low bedeutet schreiben. Wenn man RW auf lesen einstellt und RS auf Befehl, dann kann man das &#039;&#039;&#039;Busy-Flag&#039;&#039;&#039; an DB7 lesen, das anzeigt, ob das LCD den vorhergehenden Befehl fertig verarbeitet hat. Ist RS auf Daten eingestellt, dann kann man z.&amp;amp;nbsp;B. den Inhalt des Displays lesen - was jedoch nur in den wenigsten Fällen Sinn macht. Deshalb kann man RW dauerhaft auf low lassen (= an GND anschließen), so dass man noch ein IO-Pin am Controller einspart. Der Nachteil ist, dass man dann das Busy-Flag nicht lesen kann, weswegen man nach jedem Befehl ca. 50 µs (beim Return Home 2 ms, beim Clear Display 20 ms) warten sollte, um dem LCD Zeit zum Ausführen des Befehls zu geben. Dummerweise schwankt die Ausführungszeit von Display zu Display und ist auch von der Betriebsspannung abhängig. Für professionellere Sachen also lieber den IO-Pin opfern und Busy abfragen.&lt;br /&gt;
* Der &#039;&#039;&#039;E&#039;&#039;&#039; Anschluss schließlich signalisiert dem LCD, dass die übrigen Datenleitungen jetzt korrekte Pegel angenommen haben und es die gewünschten Daten von den Datenleitungen bzw. Kommandos von den Datenleitungen übernehmen kann. Beim Lesen gibt das Display die Daten / Status so lange aus, wie E high ist. Beim Schreiben übernimmt das Display die Daten mit der fallenden Flanke.&lt;br /&gt;
&lt;br /&gt;
== Anschluss an den Controller ==&lt;br /&gt;
&lt;br /&gt;
Jetzt, da wir wissen, welche Anschlüsse das LCD benötigt, können wir das LCD mit dem Mikrocontroller verbinden: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;ACHTUNG: Es gibt Displays mit abweichender Anschluss-Belegung, falscher Anschluss kann zur Zerstörung führen! Daher immer das zugehörige Datenblatt zu Rate ziehen.&amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Einzelheiten unter [http://www.mikrocontroller.net/articles/HD44780 Artikel zum Controller HD44780]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
!Pinnummer&amp;lt;BR&amp;gt;LCD || Bezeichnung || Anschluss&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |1 || V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; || GND&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |2 || V&amp;lt;sub&amp;gt;CC&amp;lt;/sub&amp;gt; || +5 V&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |3 || V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt; || GND oder [[Potentiometer | Poti]] &lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |4 || RS || PD4 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |5 || RW || GND &lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |6 || E || PD5 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |7 || DB0 || nicht angeschlossen &lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |8 || DB1 || nicht angeschlossen&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |9 || DB2 || nicht angeschlossen&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |10 || DB3 || nicht angeschlossen&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |11 || DB4 || PD0 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |12 || DB5 || PD1 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |13 || DB6 || PD2 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |14 || DB7 || PD3 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |15 || A || Vorsicht! Meistens nicht direkt an +5 V abschließbar,&amp;lt;br /&amp;gt; sondern nur über einen Vorwiderstand, der an die Daten&amp;lt;br /&amp;gt;der Hintergrundbeleuchtung angepasst werden muss.&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |16 || K || GND&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ok. Alles ist verbunden. Wenn man jetzt den Strom einschaltet, sollten ein oder zwei schwarze Balken auf dem Display angezeigt werden. &lt;br /&gt;
&lt;br /&gt;
Doch wie bekommt man jetzt die Befehle und Daten in das Display? Dazu muss das LCD initialisiert werden und man muss Befehle (Commands) und seine Daten an das LCD senden. Weil die Initialisierung ein Spezialfall der Übertragung von Befehlen ist, im Folgenden zunächst die Erklärung für die Übertragung von Werten an das LCD.&lt;br /&gt;
&lt;br /&gt;
== Ansteuerung des LCDs im 4-Bit-Modus ==&lt;br /&gt;
&lt;br /&gt;
Um ein Byte zu übertragen, muss man es erstmal in die beiden Nibbles zerlegen, die getrennt übertragen werden. Da das obere Nibble (Bit 4 - Bit 7) als erstes übertragen wird, die 4 Datenleitungen jedoch an die vier unteren Bits des Port D angeschlossen sind, muss man die beiden Nibbles des zu übertragenden Bytes erstmal vertauschen. Der AVR kennt dazu praktischerweise einen eigenen Befehl: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           swap r16               ; vertauscht die beiden Nibbles von r16&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aus 0b00100101 wird so z.&amp;amp;nbsp;B. 0b01010010. &lt;br /&gt;
&lt;br /&gt;
Jetzt sind die Bits für die erste Phase der Übertragung an der richtigen Stelle. Trotzdem wollen wir das Ergebnis nicht einfach so mit &#039;&#039;&#039;out PORTD, r16&#039;&#039;&#039; an den Port geben. Um die Hälfte des Bytes, die jetzt nicht an die Datenleitungen des LCDs gegeben wird auf null zu setzen, verwendet man folgenden Befehl: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           andi r16, 0b00001111   ; Nur die vier unteren (mit 1 markierten)&lt;br /&gt;
                                  ; Bits werden übernommen, alle anderen werden null&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Also: Das obere Nibble wird erst mit dem unteren vertauscht, damit es unten ist. Dann wird das obere (das wir jetzt noch nicht brauchen) auf null gesetzt. &lt;br /&gt;
&lt;br /&gt;
Jetzt müssen wir dem LCD noch mitteilen, ob wir Daten oder Befehle senden wollen. Das machen wir, indem wir das Bit, an dem RS angeschlossen ist (PD4), auf 0 (Befehl senden) oder auf 1 (Daten senden) setzen. Um ein Bit in einem normalen Register zu setzen, gibt es den Befehl sbr (Set Bit in Register). Dieser Befehl unterscheidet sich jedoch von sbi (das nur für IO-Register gilt) dadurch, dass man nicht die Nummer des zu setzenden Bits angibt, sondern eine Bitmaske. Das geht so: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           sbr r16, 0b00010000     ; Bit 4 setzen, alle anderen Bits bleiben gleich&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
RS ist an PD4 angeschlossen. Wenn wir r16 an den Port D ausgeben, ist RS jetzt also high und das LCD erwartet Daten anstatt von Befehlen. &lt;br /&gt;
&lt;br /&gt;
Das Ergebnis können wir jetzt endlich direkt an den Port D übergeben: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           out PORTD, r16&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich muss vorher der Port D auf Ausgang geschaltet werden, indem man 0xFF ins Datenrichtungsregister DDRD schreibt. &lt;br /&gt;
&lt;br /&gt;
Um dem LCD zu signalisieren, dass es das an den Datenleitungen anliegende Nibble übernehmen kann, wird die E-Leitung (Enable, an PD5 angeschlossen) auf high und kurz darauf wieder auf low gesetzt. Ein Puls an dieser Leitung teilt also dem LCD mit, das die restlichen Leitungen jetzt ihren vom Programm gewollten Pegel eingenommen haben und gültig sind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           sbi PORTD, 5              ; Enable high&lt;br /&gt;
           nop                       ; 3 Taktzyklen warten (&amp;quot;nop&amp;quot; = nichts tun)&lt;br /&gt;
           nop&lt;br /&gt;
           nop&lt;br /&gt;
           cbi PORTD, 5              ; Enable wieder low&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die eine Hälfte des Bytes wäre damit geschafft! Die andere Hälfte kommt direkt hinterher: Alles, was an der obenstehenden Vorgehensweise geändert werden muss, ist, das &amp;quot;swap&amp;quot; (Vertauschen der beiden Nibbles) wegzulassen.&lt;br /&gt;
&lt;br /&gt;
== Initialisierung des Displays ==&lt;br /&gt;
&lt;br /&gt;
Allerdings gibt es noch ein Problem. Wenn ein LCD eingeschaltet wird, dann läuft es zunächst im 8 Bit Modus. Irgendwie muss das Display initialisiert und auf den 4 Bit Modus umgeschaltet werden, und zwar nur mit den 4 zur Verfügung stehenden Datenleitungen.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wenn es Probleme gibt, dann meistens an diesem Punkt. Die &amp;quot;kompatiblen&amp;quot; Kontroller sind gelegentlich doch nicht 100% identisch. Es lohnt sich, das Datenblatt (siehe Weblinks im Artikel [[LCD]]) genau zu lesen, in welcher Reihenfolge und mit welchen Abständen (Delays) die Initialisierungbefehle gesendet werden. Eine weitere Hilfe können Ansteuerungsbeispiele in Forenbeiträgen geben z.&amp;amp;nbsp;B.&lt;br /&gt;
&lt;br /&gt;
* [http://www.mikrocontroller.net/topic/79609#664268 (A) KS0066U oder Ähnliche --- LCD Treiber]&lt;br /&gt;
&lt;br /&gt;
=== Initialisierung für 4 Bit Modus ===&lt;br /&gt;
&lt;br /&gt;
Achtung: Im Folgenden sind alle Bytes aus Sicht des LCD-Kontrollers angegeben! Da LCD-seitig nur die Leitungen DB4 - DB7 verwendet werden, ist daher immer nur das höherwertige Nibble gültig. Durch die Art der Verschaltung (DB4 - DB7 wurde auf dem PORT an PD0 bis PD3 angeschlossen) ergibt sich eine Verschiebung, so dass das am Kontroller auszugebende Byte nibblemässig vertauscht ist!&lt;br /&gt;
&lt;br /&gt;
Die Sequenz, aus Sicht des Kontrollers, sieht so aus:&lt;br /&gt;
&lt;br /&gt;
* Nach dem Anlegen der Betriebsspannung muss eine Zeit von mindestens ca. 15ms gewartet werden, um dem LCD-Kontroller Zeit für seine eigene Initialisierung zu geben&lt;br /&gt;
* $3 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 4.1ms warten&lt;br /&gt;
* $3 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 100µs warten&lt;br /&gt;
* $3 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* $2 ins Steuerregister schreiben (RS = 0), dadurch wird auf 4 Bit Daten umgestellt&lt;br /&gt;
* Ab jetzt muss für die Übertragung eines Bytes jeweils zuerst das höherwertige Nibble und dann das niederwertige Nibble übertragen werden, wie oben beschrieben&lt;br /&gt;
* Mit dem Konfigurier-Befehl $20 das Display konfigurieren (4-Bit, 1 oder 2 Zeilen, 5x7 Format)&lt;br /&gt;
* Mit den restlichen Konfigurierbefehlen die Konfiguration vervollständigen: Display ein/aus, Cursor ein/aus, etc.&lt;br /&gt;
&lt;br /&gt;
Eine Begründung, warum die ersten Befehle dreifach geschickt werden sollen, findet sich [http://www.mikrocontroller.net/topic/158983#1508510 im Forum].&lt;br /&gt;
&lt;br /&gt;
=== Initialisierung für 8 Bit Modus ===&lt;br /&gt;
&lt;br /&gt;
Der Vollständigkeit halber hier noch die notwendige Initialiserungssequenz für den 8 Bit Modus. Da hier die Daten komplett als 1 Byte übertragen werden können, sind einige Klimmzüge wie im 4 Bit Modus nicht notwendig. Begründung für die anfänglichen Wiederholungen siehe oben.&lt;br /&gt;
&lt;br /&gt;
* Nach dem Anlegen der Betriebsspannung muss eine Zeit von mindestens ca. 15ms gewartet werden, um dem LCD-Kontroller Zeit für seine eigene Initialisierung zu geben&lt;br /&gt;
* $30 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 4.1ms warten&lt;br /&gt;
* $30 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 100µs warten&lt;br /&gt;
* $30 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mit dem Konfigurier-Befehl 0x30 das Display konfigurieren (8-Bit, 1 oder 2 Zeilen, 5x7 Format)&lt;br /&gt;
* Mit den restlichen Konfigurierbefehlen die Konfiguration vervollständigen: Display ein/aus, Cursor ein/aus, etc.&lt;br /&gt;
&lt;br /&gt;
== Routinen zur LCD-Ansteuerung im 4-Bit-Modus ==&lt;br /&gt;
&lt;br /&gt;
Im Folgenden werden die bisherigen Grundroutinen zur LCD-Ansteuerung im 4-Bit-Modus zusammengefasst und kommentiert. Die darin enthaltenen Symbole (temp1, PORTD,...) müssen in einem dazugehörenden Hauptprogramm definiert werden. Dies wird nächsten Abschnitt &#039;&#039;Anwendung&#039;&#039; weiter erklärt.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
;;                 LCD-Routinen                ;;&lt;br /&gt;
;;                 ============                ;;&lt;br /&gt;
;;              (c)andreas-s@web.de            ;;&lt;br /&gt;
;;                                             ;;&lt;br /&gt;
;; 4bit-Interface                              ;;&lt;br /&gt;
;; DB4-DB7:       PD0-PD3                      ;;&lt;br /&gt;
;; RS:            PD4                          ;;&lt;br /&gt;
;; E:             PD5                          ;;&lt;br /&gt;
;;                                             ;;&lt;br /&gt;
;; Takt:          4 MHz                        ;;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 ;sendet ein Datenbyte an das LCD&lt;br /&gt;
lcd_data:&lt;br /&gt;
           mov temp2, temp1             ; &amp;quot;Sicherungskopie&amp;quot; für&lt;br /&gt;
                                        ; die Übertragung des 2.Nibbles&lt;br /&gt;
           swap temp1                   ; Vertauschen&lt;br /&gt;
           andi temp1, 0b00001111       ; oberes Nibble auf Null setzen&lt;br /&gt;
           sbr temp1, 1&amp;lt;&amp;lt;4              ; entspricht 0b00010000 (Anm.1)&lt;br /&gt;
           out PORTD, temp1             ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
                                        ; 2. Nibble, kein swap da es schon&lt;br /&gt;
                                        ; an der richtigen stelle ist&lt;br /&gt;
           andi temp2, 0b00001111       ; obere Hälfte auf Null setzen &lt;br /&gt;
           sbr temp2, 1&amp;lt;&amp;lt;4              ; entspricht 0b00010000&lt;br /&gt;
           out PORTD, temp2             ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
           rcall delay50us              ; Delay-Routine aufrufen&lt;br /&gt;
           ret                          ; zurück zum Hauptprogramm&lt;br /&gt;
&lt;br /&gt;
 ; sendet einen Befehl an das LCD&lt;br /&gt;
lcd_command:                            ; wie lcd_data, nur RS=0&lt;br /&gt;
           mov temp2, temp1&lt;br /&gt;
           swap temp1&lt;br /&gt;
           andi temp1, 0b00001111&lt;br /&gt;
           out PORTD, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           andi temp2, 0b00001111&lt;br /&gt;
           out PORTD, temp2&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay50us&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; erzeugt den Enable-Puls&lt;br /&gt;
 ;&lt;br /&gt;
 ; Bei höherem Takt (&amp;gt;= 8 MHz) kann es notwendig sein, &lt;br /&gt;
 ; vor dem Enable High 1-2 Wartetakte (nop) einzufügen. &lt;br /&gt;
 ; Siehe dazu http://www.mikrocontroller.net/topic/81974#685882&lt;br /&gt;
lcd_enable:&lt;br /&gt;
           sbi PORTD, 5                 ; Enable high&lt;br /&gt;
           nop                          ; mindestens 3 Taktzyklen warten&lt;br /&gt;
           nop&lt;br /&gt;
           nop&lt;br /&gt;
           cbi PORTD, 5                 ; Enable wieder low&lt;br /&gt;
           ret                          ; Und wieder zurück                     &lt;br /&gt;
&lt;br /&gt;
 ; Pause nach jeder Übertragung&lt;br /&gt;
delay50us:                              ; 50µs Pause (bei 4 MHz)&lt;br /&gt;
           ldi  temp1, $42&lt;br /&gt;
delay50us_:dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause (bei 4 MHz)&lt;br /&gt;
           ldi  temp1, $21&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&lt;br /&gt;
 ; Initialisierung: muss ganz am Anfang des Programms aufgerufen werden&lt;br /&gt;
lcd_init:&lt;br /&gt;
           ldi  temp3,50&lt;br /&gt;
powerupwait:&lt;br /&gt;
           rcall  delay5ms&lt;br /&gt;
           dec  temp3&lt;br /&gt;
           brne powerupwait&lt;br /&gt;
           ldi temp1, 0b00000011        ; muss 3mal hintereinander gesendet&lt;br /&gt;
           out PORTD, temp1             ; werden zur Initialisierung&lt;br /&gt;
           rcall lcd_enable             ; 1&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; 2&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; und 3!&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi temp1, 0b00000010        ; 4bit-Modus einstellen&lt;br /&gt;
           out PORTD, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi temp1, 0b00101000        ; 4Bit / 2 Zeilen / 5x8&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi temp1, 0b00001100        ; Display ein / Cursor aus / kein Blinken&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi temp1, 0b00000100        ; inkrement / kein Scrollen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           ldi temp1, 0b00000001   ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; Sendet den Befehl: Cursor Home&lt;br /&gt;
lcd_home:&lt;br /&gt;
           ldi temp1, 0b00000010   ; Cursor Home&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Anm.1: Siehe [[Bitmanipulation]]&lt;br /&gt;
&lt;br /&gt;
Weitere Funktionen (wie z.&amp;amp;nbsp;B. Cursorposition verändern) sollten mit Hilfe der [[AVR-Tutorial:_LCD#Welche_Befehle_versteht_das_LCD.3F|Befehlscodeliste]] nicht schwer zu realisieren sein. Einfach den Code in temp laden, lcd_command aufrufen und ggf. eine Pause einfügen.&amp;lt;br&amp;gt; &lt;br /&gt;
Natürlich kann man die LCD-Ansteuerung auch an einen anderen Port des Mikrocontrollers &amp;quot;verschieben&amp;quot;: Wenn das LCD z.&amp;amp;nbsp;B. an Port B angeschlossen ist, dann reicht es, im Programm alle &amp;quot;PORTD&amp;quot; durch &amp;quot;PORTB&amp;quot; und &amp;quot;DDRD&amp;quot; durch &amp;quot;DDRB&amp;quot; zu ersetzen.&amp;lt;br&amp;gt; &lt;br /&gt;
Wer eine höhere Taktfrequenz als 4 MHz verwendet, der sollte daran denken, die Dauer der Verzögerungsschleifen anzupassen.&lt;br /&gt;
&lt;br /&gt;
==Anwendung==&lt;br /&gt;
&lt;br /&gt;
Ein Programm, das diese Routinen zur Anzeige von Text verwendet, kann z.&amp;amp;nbsp;B. so aussehen (die Datei lcd-routines.asm muss sich im gleichen Verzeichnis befinden). Nach der Initialisierung wird zuerst der Displayinhalt gelöscht. Um dem LCD ein Zeichen zu schicken, lädt man es in temp1 und ruft die Routine &amp;quot;lcd_data&amp;quot; auf. Das folgende Beispiel zeigt das Wort &amp;quot;Test&amp;quot; auf dem LCD an. &lt;br /&gt;
&lt;br /&gt;
[http://www.mikrocontroller.net/sourcecode/tutorial/lcd-test.asm Download lcd-test.asm] &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.include &amp;quot;m8def.inc&amp;quot;&lt;br /&gt;
&lt;br /&gt;
; .def definiert ein Synonym (Namen) für ein µC Register&lt;br /&gt;
.def temp1 = r16&lt;br /&gt;
.def temp2 = r17&lt;br /&gt;
.def temp3 = r18&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, LOW(RAMEND)      ; LOW-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPL, temp1&lt;br /&gt;
           ldi temp1, HIGH(RAMEND)     ; HIGH-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPH, temp1&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, 0xFF    ; Port D = Ausgang&lt;br /&gt;
           out DDRD, temp1&lt;br /&gt;
&lt;br /&gt;
           rcall lcd_init     ; Display initialisieren&lt;br /&gt;
           rcall lcd_clear    ; Display löschen&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, &#039;T&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, &#039;e&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           &lt;br /&gt;
           ldi temp1, &#039;s&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, &#039;t&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
loop:&lt;br /&gt;
           rjmp loop&lt;br /&gt;
&lt;br /&gt;
.include &amp;quot;lcd-routines.asm&amp;quot;            ; LCD-Routinen werden hier eingefügt&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für längere Texte ist die Methode, jedes Zeichen einzeln in das Register zu laden und &amp;quot;lcd_data&amp;quot; aufzurufen natürlich nicht sehr praktisch. Dazu später aber mehr.&lt;br /&gt;
&lt;br /&gt;
Bisher wurden in Register immer irgendwelche Zahlenwerte geladen, aber in diesem Programm kommt plötzlich die Anweisung&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ldi temp1, &#039;T&#039;&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
vor. Wie ist diese zu verstehen? Passiert hier etwas grundlegend anderes als beim Laden einer Zahl in ein Register?&lt;br /&gt;
&lt;br /&gt;
Die Antwort darauf lautet: Nein. Auch hier wird letztendlich nur eine Zahl in ein Register geladen. Der Schlüssel zum Verständnis beruht darauf, dass zum LCD, so wie zu allen Ausgabegeräten, für die Ausgabe von Texten immer nur Zahlen übertragen werden, sog. Codes. Zum Beispiel könnte man vereinbaren, dass ein LCD, wenn es den Ausgabecode 65 erhält, ein &#039;A&#039; anzeigt, bei einem Ausgabecode von 66 ein &#039;B&#039; usw. Naturgemäß gibt es daher viele verschiedene Code-Buchstaben Zuordnungen. Damit hier etwas Ordnung in das potentielle Chaos kommt, hat man sich bereits in der Steinzeit der Programmierung auf bestimmte Codetabellen geeinigt, von denen die verbreitetste sicherlich die ASCII-Zuordnung ist.&lt;br /&gt;
&lt;br /&gt;
==ASCII==&lt;br /&gt;
&lt;br /&gt;
ASCII steht für &#039;&#039;American Standard Code for Information Interchange&#039;&#039; und ist ein standardisierter Code zur Zeichenumsetzung. Die Codetabelle sieht hexadezimal dabei wie folgt aus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
!   ||x0||x1||x2||x3||x4||x5||x6||x7||x8||x9||xA||xB||xC||xD||xE||xF&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 0x&lt;br /&gt;
|NUL||SOH||STX||ETX||EOT||ENQ||ACK||BEL||BS||HT||LF||VT||FF||CR||SO||SI&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 1x&lt;br /&gt;
|DLE||DC1||DC2||DC3||DC4||NAK||SYN||ETB||CAN||EM||SUB||ESC||FS||GS||RS||US&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 2x&lt;br /&gt;
|SP||!||&amp;quot;||#||$||%||&amp;amp;||&#039;||(||)||*||+||,||-||.||/&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 3x&lt;br /&gt;
|0||1||2||3||4||5||6||7||8||9||:||;||&amp;lt;||=||&amp;gt;||?&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 4x&lt;br /&gt;
|@||A||B||C||D||E||F||G||H||I||J||K||L||M||N||O&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 5x&lt;br /&gt;
|P||Q||R||S||T||U||V||W||X||Y||Z||[||\||]||^||_&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 6x&lt;br /&gt;
|`||a||b||c||d||e||f||g||h||i||j||k||l||m||n||o&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 7x&lt;br /&gt;
|p||q||r||s||t||u||v||w||x||y||z||{|| &amp;amp;#124; ||}||~||DEL&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die ersten beiden Zeilen enthalten die Codes für einige Steuerzeichen, ihre vollständige Beschreibung würde hier zu weit führen. Das Zeichen &#039;&#039;&#039;SP&#039;&#039;&#039; steht für ein &#039;&#039;Space&#039;&#039;, also ein Leerzeichen. &#039;&#039;&#039;BS&#039;&#039;&#039; steht für &#039;&#039;Backspace&#039;&#039;, also ein Zeichen zurück. &#039;&#039;&#039;DEL&#039;&#039;&#039; steht für &#039;&#039;Delete&#039;&#039;, also das Löschen eines Zeichens. &#039;&#039;&#039;CR&#039;&#039;&#039; steht für &#039;&#039;Carriage Return&#039;&#039;, also wörtlich: der Wagenrücklauf (einer Schreibmaschine), während &#039;&#039;&#039;LF&#039;&#039;&#039; für &#039;&#039;Line feed&#039;&#039;, also einen Zeilenvorschub steht.&lt;br /&gt;
&lt;br /&gt;
Der Assembler kennt diese Codetabelle und ersetzt die Zeile&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ldi temp1, &#039;T&#039;&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
durch&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ldi temp1, $54&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
was letztendlich auch der Lesbarkeit des Programmes zugute kommt. Funktional besteht kein Unterschied zwischen den beiden Anweisungen. Beide bewirken, dass das Register temp1 mit dem Bitmuster 01010100 ( = hexadezimal 54, = dezimal 84 oder eben der ASCII Code für &#039;&#039;&#039;T&#039;&#039;&#039;) geladen wird.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Das LCD wiederrum kennt diese Code-Tabelle ebenfalls und wenn es über den Datenbus die Codezahl $54 zur Anzeige empfängt, dann schreibt es ein &#039;&#039;&#039;T&#039;&#039;&#039; an die aktuelle Cursorposition. Genauer gesagt, weiss das LCD nichts von einem &#039;&#039;&#039;T&#039;&#039;&#039;. Es sieht einfach in seinen internen Tabellen nach, welche Pixel beim Empfang der Codezahl $54 auf schwarz zu setzen sind. &#039;Zufällig&#039; sind das genau jene Pixel, die für uns Menschen ein &#039;&#039;&#039;T&#039;&#039;&#039; ergeben.&lt;br /&gt;
&lt;br /&gt;
==Welche Befehle versteht das LCD?==&lt;br /&gt;
&lt;br /&gt;
Auf dem LCD arbeitet ein Controller vom Typ HD44780. Dieser Kontroller versteht eine Reihe von Befehlen, die allesamt mittels lcd_command gesendet werden können. Ein Kommando ist dabei nichts anderes als ein Befehlsbyte, in dem die verschiedenen Bits verschiedene Bedeutungen haben:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
! Bitwert   || Bedeutung&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  0&lt;br /&gt;
||dieses Bit muss 0 sein&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  1&lt;br /&gt;
||dieses Bit muss 1 sein&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  x&lt;br /&gt;
||der Zustand dieses Bits ist egal&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | sonstige Buchstaben&lt;br /&gt;
||das Bit muss je nach gewünschter Funktionalität gesetzt werden.&amp;lt;br /&amp;gt;Die mögliche Funktionalität des jeweiligen Bits geht aus der Befehlsbeschreibung hervor.&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Beispiel: Das Kommando &#039;ON/OFF Control&#039; soll benutzt werden, um das Display einzuschalten, der Cursor soll eingeschaltet werden und der Cursor soll blinken.&lt;br /&gt;
Das Befehlsbyte ist so aufgebaut:&lt;br /&gt;
   0b00001dcb&lt;br /&gt;
Aus der Befehlsbeschreibung entnimmt man:&lt;br /&gt;
* Display ein bedeutet, dass an der Bitposition d eine 1 stehen muss.&amp;lt;br&amp;gt;&lt;br /&gt;
* Cursor ein bedeutet, dass an der Bitposition c ein 1 stehen muss.&amp;lt;br&amp;gt;&lt;br /&gt;
* Cursor blinken bedeutet, dass an der Bitposition b eine 1 stehen muss.&amp;lt;br&amp;gt;&lt;br /&gt;
Das dafür zu übertragende Befehlsbyte hat also die Gestalt 0b00001111 oder in hexadezimaler Schreibweise $0F.&lt;br /&gt;
&lt;br /&gt;
===Clear display: 0b00000001===&lt;br /&gt;
&lt;br /&gt;
Die Anzeige wird gelöscht und der Ausgabecursor kehrt an die Home Position (links, erste Zeile) zurück.&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 1.64ms&lt;br /&gt;
&lt;br /&gt;
===Cursor home: 0b0000001x===&lt;br /&gt;
&lt;br /&gt;
Der Cursor kehrt an die Home Position (links, erste Zeile) zurück. Ein verschobenes Display wird auf die Grundeinstellung zurückgesetzt.&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs bis 1.64ms&lt;br /&gt;
&lt;br /&gt;
===Entry mode: 0b000001is===&lt;br /&gt;
&lt;br /&gt;
Legt die Cursor Richtung sowie eine mögliche Verschiebung des Displays fest&lt;br /&gt;
* i = 1, Cursorposition bei Ausgabe eines Zeichens erhöhen&lt;br /&gt;
* i = 0, Cursorposition bei Ausgabe eines Zeichens vermindern&lt;br /&gt;
* s = 1, Display wird gescrollt, wenn der Cursor das Ende/Anfang, je nach Einstellung von i, erreicht hat.&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===On/off control: 0b00001dcb===&lt;br /&gt;
&lt;br /&gt;
Display insgesamt ein/ausschalten; den Cursor ein/ausschalten; den Cursor auf blinken schalten/blinken aus. Wenn das Display ausgeschaltet wird, geht der Inhalt des Displays nicht verloren. Der vorher angezeigte Text wird nach wiedereinschalten erneut angezeigt.&lt;br /&gt;
Ist der Cursor eingeschaltet, aber Blinken ausgeschaltet, so wird der Cursor als Cursorzeile in Pixelzeile 8 dargestellt. Ist Blinken eingeschaltet, wird der Cursor als blinkendes ausgefülltes Rechteck dargestellt, welches abwechselnd mit dem Buchstaben an dieser Stelle angezeigt wird.&lt;br /&gt;
&lt;br /&gt;
* d = 0, Display aus&lt;br /&gt;
* d = 1, Display ein&lt;br /&gt;
* c = 0, Cursor aus&lt;br /&gt;
* c = 1, Cursor ein&lt;br /&gt;
* b = 0, Cursor blinken aus&lt;br /&gt;
* b = 1, Cursor blinken ein&lt;br /&gt;
 &lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Cursor/Scrollen: 0b0001srxx===&lt;br /&gt;
&lt;br /&gt;
Bewegt den Cursor oder scrollt das Display um eine Position entweder nach rechts oder nach links.&lt;br /&gt;
&lt;br /&gt;
* s = 1, Display scrollen&lt;br /&gt;
* s = 0, Cursor bewegen&lt;br /&gt;
* r = 1, nach rechts&lt;br /&gt;
* r = 0, nach links &lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Konfiguration: 0b001dnfxx===&lt;br /&gt;
&lt;br /&gt;
Einstellen der Interface Art, Modus, Font&lt;br /&gt;
* d = 0, 4-Bit Interface&lt;br /&gt;
* d = 1, 8-Bit Interface&lt;br /&gt;
* n = 0, 1 zeilig&lt;br /&gt;
* n = 1, 2 zeilig&lt;br /&gt;
* f = 0, 5x7 Pixel&lt;br /&gt;
* f = 1, 5x11 Pixel&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Character RAM Address Set: 0b01aaaaaa===&lt;br /&gt;
&lt;br /&gt;
Mit diesem Kommando werden maximal 8 selbst definierte Zeichen definiert. Dazu wird der Character RAM Zeiger auf den Anfang des Character Generator (CG) RAM gesetzt und das Zeichen durch die Ausgabe von 8 Byte definiert. Der Adresszeiger wird nach Ausgabe jeder Pixelspalte (8 Bit) vom LCD selbst erhöht. Nach Beendigung der Zeichendefinition muss die Schreibposition explizit mit dem Kommando &amp;quot;Display RAM Address Set&amp;quot; wieder in den DD-RAM Bereich gesetzt werden.&lt;br /&gt;
&lt;br /&gt;
aaaaaa 6-bit CG RAM Adresse&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Display RAM Address Set: 0b1aaaaaaa===&lt;br /&gt;
&lt;br /&gt;
Den Cursor neu positionieren. Display Data (DD) Ram ist vom Character Generator (CG) Ram unabhängig. Der Adresszeiger wird bei Ausgabe eines Zeichens ins DD Ram automatisch erhöht. Das Display verhält sich so, als ob eine Zeile immer aus 40 logischen Zeichen besteht, von der, je nach konkretem Displaytyp (16 Zeichen, 20 Zeichen) immer nur ein Teil sichtbar ist.&lt;br /&gt;
&lt;br /&gt;
aaaaaaa 7-bit DD RAM Adresse. Auf 2-zeiligen Displays (und den meisten 16x1 Displays), kann die Adressangabe wie folgt interpretiert werden:&lt;br /&gt;
&lt;br /&gt;
1laaaaaa&lt;br /&gt;
* l = Zeilennummer (0 oder 1)&lt;br /&gt;
* a = 6-Bit Spaltennummer&lt;br /&gt;
&lt;br /&gt;
 --------------------------------&lt;br /&gt;
 DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0&lt;br /&gt;
 --- --- --- --- --- --- --- ---&lt;br /&gt;
  1   A   A   A   A   A   A   A &lt;br /&gt;
&lt;br /&gt;
Setzt die DDRAM Adresse:&lt;br /&gt;
&lt;br /&gt;
Wenn N = 0 (1 line display)&lt;br /&gt;
    AAAAAAA = &amp;quot;00h&amp;quot; - &amp;quot;4Fh&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Wenn N = 1 (2 line display) ((1x16))&lt;br /&gt;
    AAAAAAA = &amp;quot;00h&amp;quot; - &amp;quot;27h&amp;quot; Zeile 1. (0x80) &lt;br /&gt;
    AAAAAAA = &amp;quot;40h&amp;quot; - &amp;quot;67h&amp;quot; Zeile 2. (0xC0)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
==Einschub: Code aufräumen==&lt;br /&gt;
&lt;br /&gt;
Es wird Zeit, sich einmal etwas kritisch mit den bisher geschriebenen Funktionen auseinander zu setzen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Portnamen aus dem Code herausziehen===&lt;br /&gt;
&lt;br /&gt;
Wenn wir die LCD-Funktionen einmal genauer betrachten, dann fällt sofort auf, dass über die Funktionen verstreut immer wieder das &#039;&#039;&#039;PORTD&#039;&#039;&#039; sowie einzelne Zahlen für die Pins an diesem Port auftauchen. Wenn das LCD an einem anderen Port betrieben werden soll, oder sich die Pin-Belegung ändert, dann muss an all diesen Stellen eine Anpassung vorgenommen werden. Dabei darf keine einzige Stelle übersehen werden, ansonsten würden die LCD-Funktionen nicht oder nicht vollständig funktionieren.&lt;br /&gt;
&lt;br /&gt;
Eine Möglichkeit, dem vorzubeugen, ist es, diese immer gleichbleibenden Dinge an den Anfang der LCD-Funktionen vorzuziehen. Anstelle von PORTD wird dann im Code ein anderer Name benutzt, den man frei vergeben kann. Dem Assembler wird nur noch mitgeteilt, das dieser Name für PORTD steht. Muss das LCD an einen anderen Port angeschlossen werden, so wird nur diese Zurodnung geändert und der Assembler passt dann im restlichen Code alle davon abhängigen Anweisungen an:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
;;                 LCD-Routinen                ;;&lt;br /&gt;
;;                 ============                ;;&lt;br /&gt;
;;              (c)andreas-s@web.de            ;;&lt;br /&gt;
;;                                             ;;&lt;br /&gt;
;; 4bit-Interface                              ;;&lt;br /&gt;
;; DB4-DB7:       PD0-PD3                      ;;&lt;br /&gt;
;; RS:            PD4                          ;;&lt;br /&gt;
;; E:             PD5                          ;;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
 &lt;br /&gt;
; .equ definiert ein Symbol und dessen Wert&lt;br /&gt;
.equ LCD_PORT = PORTD&lt;br /&gt;
.equ LCD_DDR  = DDRD&lt;br /&gt;
.equ PIN_E    = 5&lt;br /&gt;
.equ PIN_RS   = 4&lt;br /&gt;
&lt;br /&gt;
 ;sendet ein Datenbyte an das LCD&lt;br /&gt;
lcd_data:&lt;br /&gt;
           mov temp2, temp1             ; &amp;quot;Sicherungskopie&amp;quot; für&lt;br /&gt;
                                        ; die Übertragung des 2.Nibbles&lt;br /&gt;
           swap temp1                   ; Vertauschen&lt;br /&gt;
           andi temp1, 0b00001111       ; oberes Nibble auf Null setzen&lt;br /&gt;
           sbr temp1, 1&amp;lt;&amp;lt;PIN_RS         ; entspricht 0b00010000&lt;br /&gt;
           out LCD_PORT, temp1          ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
                                        ; 2. Nibble, kein swap da es schon&lt;br /&gt;
                                        ; an der richtigen stelle ist&lt;br /&gt;
           andi temp2, 0b00001111       ; obere Hälfte auf Null setzen &lt;br /&gt;
           sbr temp2, 1&amp;lt;&amp;lt;PIN_RS         ; entspricht 0b00010000&lt;br /&gt;
           out LCD_PORT, temp2          ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
           rcall delay50us              ; Delay-Routine aufrufen&lt;br /&gt;
           ret                          ; zurück zum Hauptprogramm&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
 ; sendet einen Befehl an das LCD&lt;br /&gt;
&lt;br /&gt;
lcd_command:                            ; wie lcd_data, nur RS=0&lt;br /&gt;
           mov temp2, temp1&lt;br /&gt;
           swap temp1&lt;br /&gt;
           andi temp1, 0b00001111&lt;br /&gt;
           out LCD_PORT, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           andi temp2, 0b00001111&lt;br /&gt;
           out LCD_PORT, temp2&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay50us&lt;br /&gt;
           ret&lt;br /&gt;
 &lt;br /&gt;
 ; erzeugt den Enable-Puls&lt;br /&gt;
lcd_enable:&lt;br /&gt;
           sbi LCD_PORT, PIN_E          ; Enable high&lt;br /&gt;
           nop                          ; 3 Taktzyklen warten&lt;br /&gt;
           nop&lt;br /&gt;
           nop&lt;br /&gt;
           cbi LCD_PORT, PIN_E          ; Enable wieder low&lt;br /&gt;
           ret                          ; Und wieder zurück                     &lt;br /&gt;
 &lt;br /&gt;
 ; Pause nach jeder Übertragung&lt;br /&gt;
delay50us:                              ; 50µs Pause&lt;br /&gt;
           ldi  temp1, $42&lt;br /&gt;
delay50us_:dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
 &lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause&lt;br /&gt;
           ldi  temp1, $21&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
 &lt;br /&gt;
 ; Initialisierung: muss ganz am Anfang des Programms aufgerufen werden&lt;br /&gt;
lcd_init:&lt;br /&gt;
           ldi   temp1, 0xFF            ; alle Pins am Ausgabeport auf Ausgang&lt;br /&gt;
           out   LCD_DDR, temp1&lt;br /&gt;
&lt;br /&gt;
           ldi   temp3,6&lt;br /&gt;
powerupwait:&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           dec   temp3&lt;br /&gt;
           brne  powerupwait&lt;br /&gt;
           ldi   temp1,    0b00000011   ; muss 3mal hintereinander gesendet&lt;br /&gt;
           out   LCD_PORT, temp1        ; werden zur Initialisierung&lt;br /&gt;
           rcall lcd_enable             ; 1&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; 2&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; und 3!&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi   temp1, 0b00000010      ; 4bit-Modus einstellen&lt;br /&gt;
           out   LCD_PORT, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi   temp1, 0b00101000      ; 4 Bit, 2 Zeilen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi   temp1, 0b00001100      ; Display on, Cursor off&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi   temp1, 0b00000100      ; endlich fertig&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ret&lt;br /&gt;
 &lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           ldi   temp1, 0b00000001      ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; Sendet den Befehl: Cursor Home&lt;br /&gt;
lcd_home:&lt;br /&gt;
           ldi   temp1, 0b00000010      ; Cursor Home&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mittels &#039;&#039;&#039;.equ&#039;&#039;&#039; werden mit dem Assembler Textersetzungen vereinbart. Der Assembler ersetzt alle Vorkomnisse des Quelltextes durch den zu ersetzenden Text. Dadurch ist es z.&amp;amp;nbsp;B. möglich, alle Vorkommnisse von &#039;&#039;&#039;PORTD&#039;&#039;&#039; durch &#039;&#039;&#039;LCD_PORT&#039;&#039;&#039; auszutauschen. Wird das LCD an einen anderen Port, z.&amp;amp;nbsp;B. &#039;&#039;&#039;PORTB&#039;&#039;&#039; gelegt, dann genügt es, die Zeilen&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.equ LCD_PORT = PORTD&lt;br /&gt;
.equ LCD_DDR  = DDRD&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
durch&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.equ LCD_PORT = PORTB&lt;br /&gt;
.equ LCD_DDR  = DDRB&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
zu ersetzen. Der Assembler sorgt dann dafür, dass diese Portänderung an den relevanten Stellen im Code über die Textersetzungen einfließt. Selbiges natürlich mit der Pin-Zuordnung.&lt;br /&gt;
&lt;br /&gt;
===Registerbenutzung===&lt;br /&gt;
&lt;br /&gt;
Bei diesen Funktionen mussten einige Register des Prozessors benutzt werden, um darin Zwischenergebnisse zu speichern bzw. zu bearbeiten.&lt;br /&gt;
&lt;br /&gt;
Beachtet werden muss dabei natürlich, dass es zu keinen Überschneidungen kommt. Solange nur jede Funktion jeweils für sich betrachtet wird, ist das kein Problem. In 20 oder 30 Code-Zeilen kann man gut verfolgen, welches Register wofür benutzt wird. Schwieriger wird es, wenn Funktionen wiederum andere Funktionen aufrufen, die ihrerseits wieder Funktionen aufrufen usw. Jede dieser Funktionen benutzt einige Register und mit zunehmender Programmgröße wird es immer schwieriger, zu verfolgen, welches Register zu welchem Zeitpunkt wofür benutzt wird.&lt;br /&gt;
&lt;br /&gt;
Speziell bei Basisfunktionen wie diesen LCD-Funktionen, ist es daher oft ratsam, dafür zu sorgen, dass jede Funktion die Register wieder in dem Zustand hinterlässt, indem sie sie auch vorgefunden hat. Wir benötigen dazu wieder den Stack, auf dem die Registerinhalte bei Betreten einer Funktion zwischengespeichert werden und von dem die Register bei Verlassen einer Funktion wiederhergestellt werden.&lt;br /&gt;
&lt;br /&gt;
Nehmen wir die Funktion&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           ldi   temp1, 0b00000001      ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Diese Funktion verändert das Register temp1. Um das Register abzusichern, schreiben wir die Funktion um:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           push  temp1                  ; temp1 auf dem Stack sichern&lt;br /&gt;
           ldi   temp1, 0b00000001      ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           pop   temp1                  ; temp1 vom Stack wiederherstellen&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am besten hält man sich an die Regel: Jede Funktion ist dafür zuständig, die Register zu sichern und wieder herzustellen, die sie auch selbst verändert. &#039;&#039;&#039;lcd_clear&#039;&#039;&#039; ruft die Funktionen &#039;&#039;&#039;lcd_command&#039;&#039;&#039; und &#039;&#039;&#039;delay5ms&#039;&#039;&#039; auf. Wenn diese Funktionen selbst wieder Register verändern (und das tun sie), so ist es die Aufgabe dieser Funktionen, sich um die Sicherung und das Wiederherstellen der entsprechenden Register zu kümmern. &#039;&#039;&#039;lcd_clear&#039;&#039;&#039; sollte sich nicht darum kümmern müssen. Auf diese Weise ist das Schlimmste, das einem passieren kann, dass ein paar Register unnütz gesichert und wiederhergestellt werden. Das kostet zwar etwas Rechenzeit und etwas Speicherplatz auf dem Stack, ist aber immer noch besser als das andere Extrem: Nach einem Funktionsaufruf haben einige Register nicht mehr den Wert, den sie haben sollten, und das Programm rechnet mit falschen Zahlen weiter.&lt;br /&gt;
&lt;br /&gt;
===Lass den Assembler rechnen===&lt;br /&gt;
Betrachtet man den Code genauer, so fallen einige konstante Zahlenwerte auf (Das vorangestellte $ kennzeichnet die Zahl als Hexadezimalzahl):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
delay50us:                              ; 50µs Pause&lt;br /&gt;
           ldi  temp1, $42&lt;br /&gt;
delay50us_:&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Der Code benötigt eine Warteschleife, die mindestens 50µs dauert. Die beiden Befehle innerhalb der Schleife benötigen 3 Takte: 1 Takt für den &#039;&#039;&#039;dec&#039;&#039;&#039; und der &#039;&#039;&#039;brne&#039;&#039;&#039; benötigt 2 Takte, wenn die Bedingung zutrifft, der Branch also genommen wird. Bei 4 Mhz werden also 4000000 / 3 * 50 / 1000000 = 66.6 Durchläufe durch die Schleife benötigt, um eine Verzögerungszeit von 50µs (0.000050 Sekunden) zu erreichen, hexadezimal ausgedrückt: $42.&lt;br /&gt;
&lt;br /&gt;
Der springende Punkt ist: Bei anderen Taktfrequenzen müsste man nun jedesmal diese Berechnung machen und den entsprechenden Zahlenwert einsetzen. Das kann aber der Assembler genausogut erledigen. Am Anfang des Codes wird ein Eintrag definiert, der die Taktfrequenz festlegt. Traditionell heißt dieser Eintrag &amp;lt;i&amp;gt;XTAL&amp;lt;/i&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.equ XTAL  = 4000000&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
delay50us:                              ; 50µs Pause&lt;br /&gt;
           ldi  temp1, ( XTAL * 50 / 3 ) / 1000000&lt;br /&gt;
delay50us_:&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An einer anderen Codestelle gibt es weitere derartige magische Zahlen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause&lt;br /&gt;
           ldi  temp1, $21&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Was geht hier vor?&lt;br /&gt;
Die innere Schleife benötigt wieder 3 Takte pro Durchlauf. Bei $C9 = 201 Durchläufen werden also 201 * 3 = 603 Takte verbraucht. In der äußeren Schleife werden pro Durchlauf alo 1 + 603 + 1 + 2 = 607 Takte verbraucht. Da die äußere Schleife $21 = 33 mal wiederholt wird, werden 20031 Takte verbraucht. Bei 4Mhz benötigt der Prozessor 20031 / 4000000 = 0.005007 Sekunden, also 5 ms.&lt;br /&gt;
Wird der Wiederholwert für die innere Schleife bei $C9 belassen, so werden 4000000 / 607 * 5 / 1000 Wiederholungen der äusseren Schleife benötigt. Auch diese Berechnung kann wieder der Assembler übernehmen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause&lt;br /&gt;
           ldi  temp1, ( XTAL * 5 / 607 ) / 1000&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ein kleines Problem kann bei der Verwendung dieses Verfahrens entstehen: Bei hohen Taktfrequenzen und großen Wartezeiten kann der berechnete Wert größer als 255 werden und man bekommt die Fehlermeldung &amp;quot;Operand(s) out of range&amp;quot; beim Assemblieren. Dieser Fall tritt zum Beispiel für obige Konstruktion bei einer Taktfrequenz von 16 MHz ein (genauer gesagt ab 15,3 MHz), während darunter XTAL beliebig geändert werden kann. Als einfachste Lösung bietet es sich an, die Zahl der Takte pro Schleifendurchlauf durch das Einfügen von &#039;&#039;&#039;nop&#039;&#039;&#039; zu erhöhen und die Berechnungsvorschrift anzupassen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ausgabe eines konstanten Textes ==&lt;br /&gt;
&lt;br /&gt;
Weiter oben wurde schon einmal ein Text ausgegeben. Dies geschah durch Ausgabe von einzelnen Zeichen. Das können wir auch anders machen. Wir können den Text im Speicher ablegen und eine Funktion schreiben, die die einzelnen Zeichen aus dem Speicher liest und aus gibt. Dabei stellt sich Frage: Woher &#039;weiß&#039; die Funktion eigentlich, wie lang der Text ist? Die Antwort darauf lautet: Sie kann es nicht wissen. Wir müssen irgendwelche Vereinbarungen treffen, woran die Funktion erkennen kann, dass der Text zu Ende ist. Im Wesentlichen werden dazu 2 Methoden benutzt:&lt;br /&gt;
* Der Text enthält ein spezielles Zeichen, welches das Ende des Textes markiert&lt;br /&gt;
* Wir speichern nicht nur den Text selbst, sondern auch die Länge des Textes&lt;br /&gt;
Mit einer der beiden Methoden ist es der Textausgabefunktion dann ein Leichtes, den Text vollständig auszugeben.&lt;br /&gt;
&lt;br /&gt;
Wir werden uns im Weiteren dafür entscheiden, ein spezielles Zeichen, eine 0 (den Wert 0, nicht das Zeichen &#039;0&#039;), dafür zu benutzen. Die Ausgabefunktionen werden dann etwas einfacher, als wenn bei der Ausgabe die Anzahl der bereits ausgegebenen Zeichen mitgezählt werden muss.&lt;br /&gt;
&lt;br /&gt;
Den Text selbst speichern wir im Flash-Speicher, also dort, wo auch das Programm gespeichert ist:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Einen konstanten Text aus dem Flash Speicher&lt;br /&gt;
 ; ausgeben. Der Text wird mit einer 0 beendet&lt;br /&gt;
lcd_flash_string:&lt;br /&gt;
           push  temp1&lt;br /&gt;
           push  ZH&lt;br /&gt;
           push  ZL&lt;br /&gt;
&lt;br /&gt;
lcd_flash_string_1:&lt;br /&gt;
           lpm   temp1, Z+&lt;br /&gt;
           cpi   temp1, 0&lt;br /&gt;
           breq  lcd_flash_string_2&lt;br /&gt;
           rcall  lcd_data&lt;br /&gt;
           rjmp  lcd_flash_string_1&lt;br /&gt;
&lt;br /&gt;
lcd_flash_string_2:&lt;br /&gt;
           pop   ZL&lt;br /&gt;
           pop   ZH&lt;br /&gt;
           pop   temp1&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Diese Funktion benutzt den Befehl &#039;&#039;&#039;lpm&#039;&#039;&#039;, um das jeweils nächste Zeichen aus dem Flash Speicher in ein Register zur Weiterverarbeitung zu laden. Dazu wird der sog. &#039;&#039;&#039;Z-Pointer&#039;&#039;&#039; benutzt. So nennt man das Registerpaar &#039;&#039;&#039;R30&#039;&#039;&#039; und &#039;&#039;&#039;R31&#039;&#039;&#039;. Nach jedem Ladevorgang wird dabei durch den Befehl&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           lpm   temp1, Z+&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
dieser Z-Pointer um 1 erhöht. Mittels &#039;&#039;&#039;cpi&#039;&#039;&#039; wird das in das Register &#039;&#039;&#039;temp1&#039;&#039;&#039; geladene Zeichen mit 0 verglichen. &#039;&#039;&#039;cpi&#039;&#039;&#039; vergleicht die beiden Zahlen und merkt sich das Ergebnis in einem speziellen Register in Form von Status Bits. &#039;&#039;&#039;cpi&#039;&#039;&#039; zieht dabei ganz einfach die beiden Zahlen voneinander ab. Sind sie gleich, so kommt da als Ergebnis 0 heraus und &#039;&#039;&#039;cpi&#039;&#039;&#039; setzt daher konsequenter Weise das Zero-Flag, das anzeigt, dass die vorhergegangene Operation eine 0 als Ergebnis hatte.&#039;&#039;&#039;breq&#039;&#039;&#039; wertet diese Status-Bits aus. Wenn die vorhergegangene Operation ein 0-Ergebnis hatte, das Zero-Flag also gesetzt ist, dann wird ein Sprung zum angegebenen Label durchgeführt. In Summe bewirkt also die Sequenz&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           cpi   temp1, 0&lt;br /&gt;
           breq  lcd_flash_string_2&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
dass das gelesene Zeichen mit 0 verglichen wird und falls das gelesene&lt;br /&gt;
Zeichen tatsächlich 0 war, an der Stelle lcd_flash_string_2 weiter gemacht wird. Im anderen Fall wird die bereits geschriebene Funktion &#039;&#039;&#039;lcd_data&#039;&#039;&#039; aufgerufen, welche das Zeichen ausgibt. &#039;&#039;&#039;lcd_data&#039;&#039;&#039; erwartet dabei das Zeichen im Register &#039;&#039;&#039;temp1&#039;&#039;&#039;, genau in dem Register, in welches wir vorher mittels &#039;&#039;&#039;lpm&#039;&#039;&#039; das Zeichen geladen hatten.&lt;br /&gt;
&lt;br /&gt;
Das verwendende Programm sieht dann so aus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.include &amp;quot;m8def.inc&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
.def temp1 = r16&lt;br /&gt;
.def temp2 = r17&lt;br /&gt;
.def temp3 = r18&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
           ldi temp1, LOW(RAMEND)      ; LOW-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPL, temp1&lt;br /&gt;
           ldi temp1, HIGH(RAMEND)     ; HIGH-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPH, temp1&lt;br /&gt;
 &lt;br /&gt;
           rcall lcd_init              ; Display initialisieren&lt;br /&gt;
           rcall lcd_clear             ; Display löschen&lt;br /&gt;
 &lt;br /&gt;
           ldi ZL, LOW(text*2)         ; Adresse des Strings in den&lt;br /&gt;
           ldi ZH, HIGH(text*2)        ; Z-Pointer laden&lt;br /&gt;
&lt;br /&gt;
           rcall lcd_flash_string      ; Unterprogramm gibt String aus der&lt;br /&gt;
                                       ; durch den Z-Pointer adressiert wird&lt;br /&gt;
loop:&lt;br /&gt;
           rjmp loop&lt;br /&gt;
&lt;br /&gt;
text:&lt;br /&gt;
           .db &amp;quot;Test&amp;quot;,0                ; Stringkonstante, durch eine 0&lt;br /&gt;
                                       ; abgeschlossen  &lt;br /&gt;
&lt;br /&gt;
.include &amp;quot;lcd-routines.asm&amp;quot;            ; LCD Funktionen&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Genaueres über die Verwendung unterschiedlicher Speicher findet sich im Kapitel [[AVR-Tutorial:_Speicher|Speicher]]&lt;br /&gt;
&lt;br /&gt;
==Zahlen ausgeben==&lt;br /&gt;
Um Zahlen, die beispielsweise in einem Register gespeichert sind, ausgeben zu können, ist es notwendig sich eine Textrepräsentierung der Zahl zu generieren. Die Zahl 123 wird also in den Text &amp;quot;123&amp;quot; umgewandelt welcher dann ausgegeben wird. Aus praktischen Gründen wird allerdings der Text nicht vollständig generiert (man müsste ihn ja irgendwo zwischenspeichern) sondern die einzelnen Buchstaben werden sofort ausgegeben, sobald sie bekannt sind.&lt;br /&gt;
&lt;br /&gt;
===Dezimal ausgeben===&lt;br /&gt;
Das Prinzip der Umwandlung ist einfach. Um herauszufinden wieviele Hunderter in der Zahl 123 enthalten sind, genügt es in einer Schleife immer wieder 100 von der Zahl abzuziehen und mitzuzählen wie oft dies gelang, bevor das Ergebnis negativ wurde. In diesem Fall lautet die Antwort: 1 mal, denn 123 - 100 macht 23. Versucht man erneut 100 anzuziehen, so ergibt sich eine negative Zahl.&lt;br /&gt;
Also muss eine &#039;1&#039; ausgeben werden. Die verbleibenden 23 werden weiter behandelt, indem festgestellt wird wieviele Zehner darin enthalten sind. Auch hier wiederum: In einer Schleife solange 10 abziehen, bis das Ergebnis negativ wurde. Konkret geht das 2 mal gut, also muss das nächste auszugebende Zeichen ein &#039;2&#039; sein. Damit verbleiben noch die Einer, welche direkt in das entsprechende Zeichen umgewandelt werden können. In Summe hat man also an das Display die Zeichen &#039;1&#039; &#039;2&#039; &#039;3&#039; ausgegeben.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 8 Bit Zahl ohne Vorzeichen ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_number:&lt;br /&gt;
           push  temp1            ; die Funktion verändert temp1 und temp2,&lt;br /&gt;
           push  temp2            ; also sichern wir den Inhalt, um ihn am Ende&lt;br /&gt;
                                  ; wieder herstellen zu können&lt;br /&gt;
&lt;br /&gt;
           mov   temp2, temp1     ; das Register temp1 frei machen&lt;br /&gt;
                                  ; abzählen wieviele Hunderter&lt;br /&gt;
                                  ; in der Zahl enthalten sind&lt;br /&gt;
;** Hunderter ** &lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1     ; temp1 mit ASCII &#039;0&#039;-1 vorladen&lt;br /&gt;
lcd_number_1:&lt;br /&gt;
           inc   temp1            ; ASCII erhöhen (somit ist nach dem ersten&lt;br /&gt;
                                  ; Durchlauf eine &#039;0&#039; in temp1)&lt;br /&gt;
           subi  temp2, 100       ; 100 abziehen&lt;br /&gt;
           brcc  lcd_number_1     ; ist dadurch kein Unterlauf entstanden?&lt;br /&gt;
                                  ; nein, dann zurück zu lcd_number_1&lt;br /&gt;
           subi  temp2, -100      ; 100 wieder dazuzählen, da die&lt;br /&gt;
                                  ; vorherhgehende Schleife 100 zuviel&lt;br /&gt;
                                  ; abgezogen hat&lt;br /&gt;
           rcall lcd_data         ; die Hunderterstelle ausgeben&lt;br /&gt;
&lt;br /&gt;
;** Zehner  **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1     ; temp1 mit ASCII &#039;0&#039;-1 vorladen&lt;br /&gt;
lcd_number_2:&lt;br /&gt;
           inc   temp1            ; ASCII erhöhen (somit ist nach dem ersten&lt;br /&gt;
                                  ; Durchlauf eine &#039;0&#039; in temp1)&lt;br /&gt;
           subi  temp2, 10        ; 10 abziehen&lt;br /&gt;
           brcc  lcd_number_2     ; ist dadurch kein Unterlauf enstanden?&lt;br /&gt;
                                  ; nein, dann zurück zu lcd_number_2&lt;br /&gt;
           subi  temp2, -10       ; 10 wieder dazuzählen, da die&lt;br /&gt;
                                  ; vorherhgehende Schleife 10 zuviel&lt;br /&gt;
                                  ; abgezogen hat&lt;br /&gt;
           rcall lcd_data         ; die Zehnerstelle ausgeben&lt;br /&gt;
 &lt;br /&gt;
;** Einer **        &lt;br /&gt;
           ldi   temp1, &#039;0&#039;       ; die Zahl in temp2 ist jetzt im Bereich&lt;br /&gt;
           add   temp1, temp2     ; 0 bis 9. Einfach nur den ASCII Code für&lt;br /&gt;
           rcall lcd_data         ; &#039;0&#039; dazu addieren und wir erhalten dierekt&lt;br /&gt;
                                  ; den ASCII Code für die Ziffer&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
           pop   temp2            ; den gesicherten Inhalt von temp2 und temp1&lt;br /&gt;
           pop   temp1            ; wieder herstellen&lt;br /&gt;
           ret                    ; und zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Beachte: Diese Funktion benutzt wiederrum die Funktion &#039;&#039;&#039;lcd_data&#039;&#039;&#039;. Anders als bei den bisherigen Aufrufen ist &#039;&#039;&#039;lcd_number&#039;&#039;&#039; aber darauf angewiesen, dass &#039;&#039;&#039;lcd_data&#039;&#039;&#039; das Register &#039;&#039;&#039;temp2&#039;&#039;&#039; unangetastet lässt. Falls sie es noch nicht getan haben, dann ist das jetzt die perfekte Gelegenheit, &#039;&#039;&#039;lcd_data&#039;&#039;&#039; mit den entsprechenden &#039;&#039;&#039;push&#039;&#039;&#039; und &#039;&#039;&#039;pop&#039;&#039;&#039; Befehlen zu versehen. Sie sollten dies unbedingt zur Übung selbst machen. Am Ende muß die Funktion dann wie diese hier aussehen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ;sendet ein Datenbyte an das LCD&lt;br /&gt;
lcd_data:&lt;br /&gt;
           push  temp2&lt;br /&gt;
           mov   temp2, temp1           ; &amp;quot;Sicherungskopie&amp;quot; für&lt;br /&gt;
                                        ; die Übertragung des 2.Nibbles&lt;br /&gt;
           swap  temp1                  ; Vertauschen&lt;br /&gt;
           andi  temp1, 0b00001111      ; oberes Nibble auf Null setzen&lt;br /&gt;
           sbr   temp1, 1&amp;lt;&amp;lt;PIN_RS       ; entspricht 0b00010000&lt;br /&gt;
           out   LCD_PORT, temp1        ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
                                        ; 2. Nibble, kein swap da es schon&lt;br /&gt;
                                        ; an der richtigen stelle ist&lt;br /&gt;
           andi  temp2, 0b00001111      ; obere Hälfte auf Null setzen &lt;br /&gt;
           sbr   temp2, 1&amp;lt;&amp;lt;PIN_RS       ; entspricht 0b00010000&lt;br /&gt;
           out   LCD_PORT, temp2        ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
           rcall delay50us              ; Delay-Routine aufrufen&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           ret                          ; zurück zum Hauptprogramm&lt;br /&gt;
 &lt;br /&gt;
 ; sendet einen Befehl an das LCD&lt;br /&gt;
lcd_command:                            ; wie lcd_data, nur ohne RS zu setzen&lt;br /&gt;
           push  temp2&lt;br /&gt;
           mov   temp2, temp1&lt;br /&gt;
           swap  temp1&lt;br /&gt;
           andi  temp1, 0b00001111&lt;br /&gt;
           out   LCD_PORT, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           andi  temp2, 0b00001111&lt;br /&gt;
           out   LCD_PORT, temp2&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay50us&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kurz zur Funktionsweise der Funktion &#039;&#039;&#039;lcd_number&#039;&#039;&#039;: Die Zahl in einem Register bewegt sich im Wertebereich 0 bis 255. Um herauszufinden, wie die Hunderterstelle lautet, zieht die Funktion einfach in einer Schleife immer wieder 100 von der Schleife ab, bis bei der Subtraktion ein Unterlauf, angezeigt durch das Setzen des Carry-Bits bei der Subtraktion, entsteht. Die Anzahl wird im Register &#039;&#039;&#039;temp1&#039;&#039;&#039; mitgezählt. Da dieses Register mit dem ASCII Code von &#039;0&#039; initialisiert wurde, und dieser ASCII Code bei jedem Schleifendurchlauf um 1 erhöht wird, können wir das Register &#039;&#039;&#039;temp1&#039;&#039;&#039; direkt zur Ausgabe des Zeichens für die Hunderterstelle durch die Funktion &#039;&#039;&#039;lcd_data&#039;&#039;&#039; benutzen. Völlig analog funktioniert auch die Ausgabe der Zehnerstelle.&lt;br /&gt;
&lt;br /&gt;
===Unterdrückung von führenden Nullen===&lt;br /&gt;
&lt;br /&gt;
Diese Funktion gibt jede Zahl im Register &#039;&#039;&#039;temp1&#039;&#039;&#039; immer mit 3 Stellen aus. Führende Nullen werden nicht unterdrückt. Möchte man dies ändern, so ist das ganz leicht möglich: Vor Ausgabe der Hunderterstelle muss lediglich überprüft werden, ob die Entsprechende Ausgabe eine &#039;0&#039; wäre. Ist sie das, so wird die Ausgabe übersprungen. Ist es allerdings eine Zahl 1..9, so muss sie der Zehner Stelle signalisieren, daß eine Prüfung auf eine &#039;0&#039; nicht stattfinden darf. Und dazu wird das T-Flag im SREG genutzt. Lediglich in der Einerstelle wird jede Ziffer wie errechnet ausgegeben.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ...&lt;br /&gt;
                                  ; die Hunderterstelle ausgeben, wenn&lt;br /&gt;
                                  ; sie nicht &#039;0&#039; ist&lt;br /&gt;
           clt                    ; T-Flag löschen&lt;br /&gt;
           cpi   temp1, &#039;0&#039;&lt;br /&gt;
           breq  lcd_number_1a&lt;br /&gt;
           rcall lcd_data         ; die Hunderterstelle ausgeben&lt;br /&gt;
           set                    ; T-Flag im SREG setzen da 100er Stelle eine&lt;br /&gt;
                                  ; 1..9 war&lt;br /&gt;
&lt;br /&gt;
lcd_number_1a:&lt;br /&gt;
           ...&lt;br /&gt;
&lt;br /&gt;
           ...&lt;br /&gt;
           brts  lcd_number_2a    ; Test auf &#039;0&#039; überspringen, da 100er eine&lt;br /&gt;
                                  ; 1..9 war (unbedingt anzeigen&lt;br /&gt;
                                  ; auch wenn der Zehner eine &#039;0&#039; ist)&lt;br /&gt;
           cpi   temp1, &#039;0&#039;       ; ansonsten Test auf &#039;0&#039;&lt;br /&gt;
           breq  lcd_number_2b&lt;br /&gt;
lcd_number_2a:        &lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
lcd_number_2b:&lt;br /&gt;
           ...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Das Verfahren, die einzelnen Stellen durch Subtraktion zu bestimmen, ist bei kleinen Zahlen eine durchaus gängige Alternative. Vor allem dann, wenn keine hardwaremäßige Unterstützung für Multiplikation und Division zur Verfügung steht. Ansonsten könnte man die die einzelnen Ziffern auch durch Division bestimmen. Das Prinzip ist folgendes (beispielhaft an der Zahl 52783 gezeigt)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   52783 / 10          -&amp;gt; 5278&lt;br /&gt;
   52783 - 5278 * 10   -&amp;gt;          3&lt;br /&gt;
&lt;br /&gt;
   5278 / 10           -&amp;gt; 527&lt;br /&gt;
   5278 - 527 * 10     -&amp;gt;          8&lt;br /&gt;
&lt;br /&gt;
   527 / 10            -&amp;gt; 52&lt;br /&gt;
   527 - 52 * 10       -&amp;gt;          7&lt;br /&gt;
&lt;br /&gt;
   52 / 10             -&amp;gt; 5&lt;br /&gt;
   52 - 5 * 10         -&amp;gt;          2&lt;br /&gt;
&lt;br /&gt;
   5 / 10              -&amp;gt; 0&lt;br /&gt;
   5 - 0 * 10          -&amp;gt;          5&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Prinzip ist also die Restbildung bei einer fortgesetzten Division durch 10, wobei die einzelnen Ziffern in umgekehrter Reihenfolge ihrer Wertigkeit entstehen. Dadurch hat man aber ein Problem: Damit die Zeichen in der richtigen Reihenfolge ausgegeben werden können, muß man sie meistens zwischenspeichern um sie in der richtigen Reihenfole ausgeben zu können. Wird die Zahl in einem Feld von immer gleicher Größe ausgegeben, dann kann man auch die Zahl von rechts nach links ausgeben (bei einem LCD ist das möglich).&lt;br /&gt;
&lt;br /&gt;
===Hexadezimal ausgeben===&lt;br /&gt;
&lt;br /&gt;
Zu guter letzt hier noch eine Funktion, die eine Zahl aus dem Register &#039;&#039;&#039;temp1&#039;&#039;&#039; in hexadezimaler Form ausgibt. Die Funktion weist keine Besonderheiten auf und sollte unmittelbar verständlich sein.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 8 Bit Zahl ohne Vorzeichen hexadezimal ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_number_hex:&lt;br /&gt;
           swap  temp1&lt;br /&gt;
           rcall lcd_number_hex_digit&lt;br /&gt;
           swap  temp1&lt;br /&gt;
&lt;br /&gt;
lcd_number_hex_digit:&lt;br /&gt;
           push  temp1&lt;br /&gt;
&lt;br /&gt;
           andi  temp1, $0F&lt;br /&gt;
           cpi   temp1, 10&lt;br /&gt;
           brlt  lcd_number_hex_digit_1&lt;br /&gt;
           subi  temp1, -( &#039;A&#039; - &#039;9&#039; - 1 ) ; es wird subi mit negativer&lt;br /&gt;
                                           ; Konstante verwendet,&lt;br /&gt;
                                           ; weil es kein addi gibt&lt;br /&gt;
lcd_number_hex_digit_1:&lt;br /&gt;
           subi  temp1, -&#039;0&#039;               ; ditto&lt;br /&gt;
           rcall  lcd_data&lt;br /&gt;
           &lt;br /&gt;
           pop   temp1&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Binär ausgeben===&lt;br /&gt;
Um die Sache komplett zu machen; Hier eine Routine mit der man eine 8 Bit-Zahl binär auf das LC-Display ausgeben kann:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 8 Bit Zahl ohne Vorzeichen binär ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
&lt;br /&gt;
; eine Zahl aus dem Register temp1 binär ausgeben&lt;br /&gt;
lcd_number_bit:&lt;br /&gt;
	   push temp1		  ; temp1 gesichert&lt;br /&gt;
           push temp2&lt;br /&gt;
	   push temp3&lt;br /&gt;
&lt;br /&gt;
	   mov temp2, temp1;&lt;br /&gt;
&lt;br /&gt;
	   ldi temp3, 8;      ; 8 Bits werden ausgelesen&lt;br /&gt;
lcd_number_loop:           &lt;br /&gt;
	   dec temp3;&lt;br /&gt;
	   rol temp2;         ; Datenbits ins Carry geschoben ...&lt;br /&gt;
	   brcc lcd_number_bit_carryset_0; &lt;br /&gt;
	   brcs lcd_number_bit_carryset_1;&lt;br /&gt;
           rjmp lcd_number_loop;&lt;br /&gt;
&lt;br /&gt;
lcd_number_bit_carryset_0:	 &lt;br /&gt;
	   ldi temp1, &#039;0&#039;     ; Bit low ausgeben&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
	   tst temp3;&lt;br /&gt;
	   breq lcd_number_ende;&lt;br /&gt;
	   rjmp lcd_number_loop;&lt;br /&gt;
&lt;br /&gt;
lcd_number_bit_carryset_1:&lt;br /&gt;
           ldi temp1, &#039;1&#039;     ; Bit high ausgeben&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           tst temp3;&lt;br /&gt;
	   breq lcd_number_ende;&lt;br /&gt;
	   rjmp lcd_number_loop;&lt;br /&gt;
&lt;br /&gt;
lcd_number_ende:&lt;br /&gt;
	   pop temp3&lt;br /&gt;
	   pop temp2&lt;br /&gt;
	   pop temp1&lt;br /&gt;
	   ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Eine 16-Bit Zahl aus einem Registerpärchen ausgeben===&lt;br /&gt;
&lt;br /&gt;
Um eine 16 Bit Zahl auszugeben wird wieder das bewährte Schema benutzt die einzelnen Stellen durch Subtraktion abzuzählen. Da es sich hierbei allerdings um eine 16 Bit Zahl handelt, müssen die Subtraktionen als 16-Bit Arithmetik ausgeführt werden.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 16 Bit Zahl ohne Vorzeichen ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp2 (low Byte) / temp3 (high Byte)&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_number16:&lt;br /&gt;
           push  temp1&lt;br /&gt;
           push  temp2&lt;br /&gt;
           push  temp3&lt;br /&gt;
&lt;br /&gt;
; ** Zehntausender **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number1:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, low(10000)&lt;br /&gt;
           sbci  temp3, high(10000)&lt;br /&gt;
           brcc  lcd_number1&lt;br /&gt;
           subi  temp2, low(-10000)&lt;br /&gt;
           sbci  temp3, high(-10000)&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Tausender **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number2:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, low(1000)&lt;br /&gt;
           sbci  temp3, high(1000)&lt;br /&gt;
           brcc  lcd_number2&lt;br /&gt;
           subi  temp2, low(-1000)&lt;br /&gt;
           sbci  temp3, high(-1000)&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Hunderter **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number3:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, low(100)&lt;br /&gt;
           sbci  temp3, high(100)&lt;br /&gt;
           brcc  lcd_number3&lt;br /&gt;
           subi  temp2, -100             ; + 100 High-Byte nicht mehr erforderlich&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Zehner **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number4:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, 10&lt;br /&gt;
           brcc  lcd_number4&lt;br /&gt;
           subi  temp2, -10&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Einer **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;&lt;br /&gt;
           add   temp1, temp2&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Stack aufräumen **&lt;br /&gt;
           pop   temp3&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           pop   temp1&lt;br /&gt;
&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Eine BCD Zahl ausgeben===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            BCD Zahl in temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_bcd:&lt;br /&gt;
           push  temp2&lt;br /&gt;
          &lt;br /&gt;
           mov   temp2, temp1           ; temp1 sichern&lt;br /&gt;
           swap  temp1                  ; oberes mit unterem Nibble tauschen&lt;br /&gt;
           andi  temp1, 0b00001111      ; und &amp;quot;oberes&amp;quot; ausmaskieren&lt;br /&gt;
           subi  temp1, -0x30           ; in ASCII umrechnen&lt;br /&gt;
           rcall lcd_data               ; und ausgeben&lt;br /&gt;
           mov   temp1, temp2           ; ... danach unteres&lt;br /&gt;
           andi  temp1, 0b00001111&lt;br /&gt;
           subi  temp1, -0x30&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           mov   temp1, temp2           ; temp1 rekonstruieren&lt;br /&gt;
&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           ret &lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Benutzerdefinierte Zeichen ==&lt;br /&gt;
[[Bild:LCD_Character_Grid.png | framed | right| Zeichenraster für 1 Zeichen]]&lt;br /&gt;
&lt;br /&gt;
Das LCD erlaubt für spezielle Zeichen, welche sich nicht im Zeichensatz finden, eigene Zeichen zu definieren. Dazu werden die ersten 8 ASCII Codes reserviert, auf denen sich laut ASCII Tabelle spezielle Steuerzeichen befinden, die normalerweise keine sichtbare Anzeige hervorrufen sondern zur Steuerung von angeschlossenen Geräten dienen. Da diese Zeichen auf einem LCD keine Rolle spielen, können diese Zeichen benutzt werden um sich selbst Sonderzeichen zu erzeugen, die für die jeweilige Anwendung massgeschneidert sind.&lt;br /&gt;
&lt;br /&gt;
Das LCD stellt für jedes Zeichen eine 8*5 Matrix zur Verfügung. Um sich selbst massgeschneiderte Zeichen zu erstellen, ist es am einfachsten sich zunächst auf einem Stück karriertem Papier zu erstellen.&lt;br /&gt;
&lt;br /&gt;
[[Bild:BellCharacter.png | framed | right| Zeichenraster für ein Glockensymbol]]&lt;br /&gt;
&lt;br /&gt;
In diesem Raster markiert man sich dann diejenigen Pixel, die im fertigen Zeichen dunkel erscheinen sollen. Als Beispiel sei hier ein Glockensymbol gezeichnet, welches in einer Telefonapplikation zb als Kennzeichnung für einen Anruf dienen könnte.&lt;br /&gt;
&lt;br /&gt;
Eine Reihe in diesem Zeichen repräsentiert ein an das LCD zu übergebendes Byte, wobei nur die letzten 5 Bits relevant sind. Gesetzte Pixel stellen ein 1 Bit dar, nicht gesetzte Pixel sind ein 0-Bit. Das niedrigstwertige Bit einer Reihe befindet sich rechts. Auf diese Art wird jede Spalte in eine Binärzahl übersetzt, und 8 Bytes repräsentieren ein komplettes Zeichen. Am Beispiel des Glockensymboles: Die 8 Bytes, welches das Symbol repräsentiern, lauten: 0x00, 0x04, 0x0A, 0x0A, 0x0A, 0x1F, 0x04, 0x00,&lt;br /&gt;
&lt;br /&gt;
Dem LCD wird die neue Definition übertragen, indem man dem LCD die &#039;Schreibposition&#039; mittels des Kommandos &#039;&#039;Character RAM Address Set&#039;&#039; in den Zeichensatzgenerator verschiebt. Danach werden die 8 Bytes ganz normal als Daten ausgegeben, die das LCD damit in seine Zeichensatztabelle schreibt.&lt;br /&gt;
&lt;br /&gt;
Durch die Wahl der Speicheradresse definiert man, welches Zeichen (0 bis 7) man eigentlich durch eine eigene Definition ersetzen will.&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
! ASCII Code || Zeichensatzadresse&lt;br /&gt;
|-&lt;br /&gt;
| 0 || 0x00&lt;br /&gt;
|-&lt;br /&gt;
| 1 || 0x08&lt;br /&gt;
|-&lt;br /&gt;
| 2 || 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 3 || 0x18&lt;br /&gt;
|-&lt;br /&gt;
| 4 || 0x20&lt;br /&gt;
|-&lt;br /&gt;
| 5 || 0x28&lt;br /&gt;
|-&lt;br /&gt;
| 6 || 0x30&lt;br /&gt;
|-&lt;br /&gt;
| 7 || 0x38&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Nach erfolgter Definition des Zeichens, muss die Schreibposition wieder explizit in den DDRAM-Bereich gesetzt werden.&lt;br /&gt;
Danach kann ein entsprechendes Zeichen mit dem definierten ASCII Code ausgegeben werden, wobei das LCD die von uns definierte Pixelform zur Anzeige benutzt.&lt;br /&gt;
&lt;br /&gt;
Zuerst müssen natürlich erstmal die Zeichen definiert werden.&lt;br /&gt;
Dieses geschieht einmalig durch den Aufruf der Routine &amp;quot;lcd_load_user_chars&amp;quot;&lt;br /&gt;
unmittelbar nach der Initialisierung des LCD-Displays.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
           rcall lcd_init              ; Display initialisieren&lt;br /&gt;
           rcall lcd_load_user_chars   ; User Zeichen in das Display laden&lt;br /&gt;
           rcall lcd_clear             ; Display löschen&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durch diesen Aufruf werden die im Flash definierten Zeichen in den&lt;br /&gt;
GC-Ram übertragen. Diese Zeichen werden ab Adresse 0 im GC-Ram&lt;br /&gt;
gespeichert und sind danach wie jedes andere Zeichen nutzbar.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
           ldi   temp1, 0              ; Ausgabe des User-Char &amp;quot;A&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 6              ; Ausgabe des User-Char &amp;quot;G&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 5              ; Ausgabe des User-Char &amp;quot;E&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 4              ; Ausgabe des User-Char &amp;quot;M&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 3              ; Ausgabe des User-Char &amp;quot;-&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 2              ; Ausgabe des User-Char &amp;quot;R&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 1              ; Ausgabe des User-Char &amp;quot;V&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 0              ; Ausgabe des User-Char &amp;quot;A&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Jetzt sollte der Schriftzug &amp;quot;AVR-MEGA&amp;quot;&lt;br /&gt;
verkehrt herum (180 Grad gedreht) erscheinen.&lt;br /&gt;
&lt;br /&gt;
Es fehlt natürlich noch die Laderoutine:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Lädt User Zeichen in den GC-Ram des LCD bis Tabellenende (0xFF)&lt;br /&gt;
; gelesen wird. (max. 8 Zeichen können geladen werden)&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            -   &lt;br /&gt;
; veränderte Register: temp1, temp2, temp3, zh, zl&lt;br /&gt;
; Bemerkung:           ist einmalig nach lcd_init aufzurufen&lt;br /&gt;
;       &lt;br /&gt;
&lt;br /&gt;
lcd_load_user_chars:&lt;br /&gt;
        ldi    zl, LOW (ldc_user_char * 2) ; Adresse der Zeichentabelle&lt;br /&gt;
        ldi    zh, HIGH(ldc_user_char * 2) ; in den Z-Pointer laden&lt;br /&gt;
        clr    temp3                       ; aktuelles Zeichen = 0 &lt;br /&gt;
&lt;br /&gt;
lcd_load_user_chars_2:&lt;br /&gt;
        clr    temp2                       ; Linienzähler = 0&lt;br /&gt;
&lt;br /&gt;
lcd_load_user_chars_1:&lt;br /&gt;
        ldi    temp1, 0b01000000           ; Kommando:    0b01aaalll&lt;br /&gt;
        add    temp1, temp3                ; + akt. Zeichen  (aaa)&lt;br /&gt;
        add    temp1, temp2                ; + akt. Linie       (lll)&lt;br /&gt;
        rcall  lcd_command                 ; Kommando schreiben&lt;br /&gt;
&lt;br /&gt;
        lpm    temp1, Z+                   ; Zeichenline laden &lt;br /&gt;
        rcall  lcd_data                    ; ... und ausgeben&lt;br /&gt;
&lt;br /&gt;
        ldi    temp1, 0b01001000           ; Kommando:    0b01aa1lll         &lt;br /&gt;
        add    temp1, temp3                ; + akt. Zeichen  (aaa)       &lt;br /&gt;
        add    temp1, temp2                ; + akt. Linie       (lll)&lt;br /&gt;
        rcall  lcd_command&lt;br /&gt;
&lt;br /&gt;
        lpm    temp1, Z+                   ; Zeichenline laden&lt;br /&gt;
        rcall  lcd_data                    ; ... und ausgeben &lt;br /&gt;
        &lt;br /&gt;
        inc    temp2                       ; Linienzähler + 1&lt;br /&gt;
        cpi    temp2, 8                    ; 8 Linien fertig?&lt;br /&gt;
        brne   lcd_load_user_chars_1       ; nein, dann nächste Linie &lt;br /&gt;
		&lt;br /&gt;
        subi   temp3, -0x10                ; zwei Zeichen weiter (addi 0x10)&lt;br /&gt;
        lpm    temp1, Z                    ; nächste Linie laden&lt;br /&gt;
        cpi    temp1, 0xFF                 ; Tabellenende erreicht? &lt;br /&gt;
        brne   lcd_load_user_chars_2       ; nein, dann die nächsten&lt;br /&gt;
                                           ; zwei Zeichen&lt;br /&gt;
        ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... und die Zeichendefinition:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
ldc_user_char:&lt;br /&gt;
                              ;    Zeichen &lt;br /&gt;
                              ;   0       1&lt;br /&gt;
       .db 0b10001, 0b00100   ; @   @ ,   @&lt;br /&gt;
       .db 0b10001, 0b01010   ; @   @ ,  @ @&lt;br /&gt;
       .db 0b11111, 0b10001   ; @@@@@ , @   @&lt;br /&gt;
       .db 0b10001, 0b10001   ; @   @ , @   @&lt;br /&gt;
       .db 0b10001, 0b10001   ; @   @ , @   @&lt;br /&gt;
       .db 0b10001, 0b10001   ; @   @ , @   @&lt;br /&gt;
       .db 0b01110, 0b10001   ;  @@@  , @   @&lt;br /&gt;
       .db 0b00000, 0b00000   ;       , &lt;br /&gt;
&lt;br /&gt;
                              ;    Zeichen&lt;br /&gt;
                              ;   2       3&lt;br /&gt;
       .db 0b10001, 0b00000   ; @   @ , &lt;br /&gt;
       .db 0b01001, 0b00000   ;  @  @ , &lt;br /&gt;
       .db 0b00101, 0b00000   ;   @ @ , &lt;br /&gt;
       .db 0b11111, 0b11111   ; @@@@@ , @@@@@ &lt;br /&gt;
       .db 0b10001, 0b00000   ; @   @ , &lt;br /&gt;
       .db 0b10001, 0b00000   ; @   @ , &lt;br /&gt;
       .db 0b01111, 0b00000   ;  @@@@ , &lt;br /&gt;
       .db 0b00000, 0b00000   ;       ,  &lt;br /&gt;
&lt;br /&gt;
                              ;    Zeichen&lt;br /&gt;
                              ;   4       5&lt;br /&gt;
       .db 0b10001, 0b11111   ; @   @ , @@@@@  &lt;br /&gt;
       .db 0b10001, 0b00001   ; @   @ ,     @&lt;br /&gt;
       .db 0b10001, 0b00001   ; @   @ ,     @&lt;br /&gt;
       .db 0b10001, 0b01111   ; @   @ ,  @@@@ &lt;br /&gt;
       .db 0b10101, 0b00001   ; @ @ @ ,     @&lt;br /&gt;
       .db 0b11011, 0b00001   ; @@ @@ ,     @&lt;br /&gt;
       .db 0b10001, 0b11111   ; @   @ , @@@@@&lt;br /&gt;
       .db 0b00000, 0b00000   ;       ,  &lt;br /&gt;
&lt;br /&gt;
                              ;    Zeichen&lt;br /&gt;
                              ;   6       7&lt;br /&gt;
       .db 0b11110, 0b11111   ; @@@@  , @@@@@  &lt;br /&gt;
       .db 0b10001, 0b01010   ; @   @ ,  @ @ &lt;br /&gt;
       .db 0b10001, 0b00100   ; @   @ ,   @&lt;br /&gt;
       .db 0b11101, 0b01110   ; @@@ @ ,  @@@&lt;br /&gt;
       .db 0b00001, 0b00100   ;     @ ,   @&lt;br /&gt;
       .db 0b10001, 0b01010   ; @   @ ,  @ @&lt;br /&gt;
       .db 0b01110, 0b11111   ;  @@@  , @@@@@&lt;br /&gt;
       .db 0b00000, 0b00000   ;       ,  &lt;br /&gt;
&lt;br /&gt;
       ; End of Tab&lt;br /&gt;
       .db 0xFF, 0xFF&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Der überarbeitete, komplette Code==&lt;br /&gt;
&lt;br /&gt;
Hier also die komplett überarbeitete Version der LCD Funktionen.&lt;br /&gt;
&lt;br /&gt;
Die für die Benutzung relevanten Funktionen&lt;br /&gt;
* &#039;&#039;&#039;lcd_init&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_clear&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_home&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_data&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_command&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_flash_string&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_number&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_number_hex&#039;&#039;&#039;&lt;br /&gt;
sind so ausgeführt, dass sie kein Register (ausser dem Statusregister &#039;&#039;&#039;SREG&#039;&#039;&#039;) verändern. Die bei manchen Funktionen notwendige Argumente werden immer im Register &#039;&#039;&#039;temp1&#039;&#039;&#039; übergeben, wobei &#039;&#039;&#039;temp1&#039;&#039;&#039; vom Usercode definiert werden muss.&lt;br /&gt;
&lt;br /&gt;
[[Media:lcd-routines.asm|Download lcd-routines.asm]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
{{Navigation_zurückhochvor|&lt;br /&gt;
zurücktext=Stack|&lt;br /&gt;
zurücklink=AVR-Tutorial: Stack|&lt;br /&gt;
hochtext=Inhaltsverzeichnis|&lt;br /&gt;
hochlink=AVR-Tutorial|&lt;br /&gt;
vortext=Interrupts|&lt;br /&gt;
vorlink=AVR-Tutorial: Interrupts}}&lt;br /&gt;
&lt;br /&gt;
[[Category:AVR-Tutorial]]&lt;br /&gt;
[[Category:LCD]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR-Tutorial:_LCD&amp;diff=62475</id>
		<title>AVR-Tutorial: LCD</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR-Tutorial:_LCD&amp;diff=62475"/>
		<updated>2011-12-16T16:26:34Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Benutzerdefinierte Zeichen */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Kaum ein elektronisches Gerät kommt heutzutage noch ohne ein LCD daher. Ist doch auch praktisch, Informationen im Klartext anzeigen zu können, ohne irgendwelche LEDs blinken zu lassen. Kein Wunder also, dass die häufigste Frage in Mikrocontroller-Foren ist: &amp;quot;Wie kann ich ein LCD anschließen?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==Das LCD und sein Controller==&lt;br /&gt;
&lt;br /&gt;
Die meisten Text-LCDs verwenden den Controller [[HD44780|&#039;&#039;&#039;HD44780&#039;&#039;&#039;]] oder einen kompatiblen (z.&amp;amp;nbsp;B. KS0070) und haben 14 oder 16 Pins.&amp;lt;br /&amp;gt;&lt;br /&gt;
Die Pinbelegung ist meist (Ausnahme z.&amp;amp;nbsp;B. TC1602E (Pollin 120420): V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt; und V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; vertauscht) folgendermaßen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;ACHTUNG: Es gibt Displays mit abweichender Anschluss-Belegung, falscher Anschluss kann zur Zerstörung führen! Daher immer das zugehörige Datenblatt zu Rate ziehen!&amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Einzelheiten unter [http://www.mikrocontroller.net/articles/HD44780 Artikel zum Controller HD44780]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
! Pin # || Bezeichnung || Funktion&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  1&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; (selten: V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt;)&lt;br /&gt;
||  GND (selten: +5 V)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  2&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt; (selten: V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt;)&lt;br /&gt;
||  +5 V (selten: GND)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  3&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt;, V0, V5&lt;br /&gt;
||  Kontrastspannung (-5 V / 0 V bis 5 V)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  4&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  RS&lt;br /&gt;
||  Register Select (0=Befehl/Status 1=Daten)&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  5&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  RW&lt;br /&gt;
||  1=Read 0=Write&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  6&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  E&lt;br /&gt;
||  0=Disable 1=Enable&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  7&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB0&lt;br /&gt;
||  Datenbit 0&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  8&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB1&lt;br /&gt;
||  Datenbit 1&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  9&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB2&lt;br /&gt;
||  Datenbit 2&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  10&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB3&lt;br /&gt;
||  Datenbit 3&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  11&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB4&lt;br /&gt;
||  Datenbit 4&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  12&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB5&lt;br /&gt;
||  Datenbit 5&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  13&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB6&lt;br /&gt;
||  Datenbit 6&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  14&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  DB7&lt;br /&gt;
||  Datenbit 7&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  15&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  A&lt;br /&gt;
||  LED-Beleuchtung, meist Anode&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  16&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  K&lt;br /&gt;
||  LED-Beleuchtung, meist Kathode&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Achtung: Unbedingt von der richtigen Seite zu zählen anfangen! Meistens ist das Pin1-Pad eckig oder daneben eine kleine 1 auf der LCD-Platine, ansonsten im Datenblatt nachschauen.&lt;br /&gt;
&lt;br /&gt;
Bei der DIL-Version (2x7, 2x8 Kontakte) auch darauf achten, auf welcher Platinen-Seite der Stecker montiert wird: auf der falschen (meist hinteren) Seite sind dann die Flachbandleitungen 1 und 2, 3 und 4  usw. vertauscht. Das kann man kompensieren, indem man es auf der anderen Kabelseite genauso permutiert oder es auf dem Layout bewusst so legt (Stecker auf der Bottom-Seite plazieren). Man kann es NICHT kompensieren, indem man das Flachbandkabel auf der anderen Seite in den Stecker führt.&lt;br /&gt;
&lt;br /&gt;
Bei LCDs mit 16-poligem Anschluss sind die beiden letzten Pins für die Hintergrundbeleuchtung reserviert. Hier unbedingt das Datenblatt zu Rate ziehen. Die beiden Anschlüsse sind je nach Hersteller verdreht beschaltet. Falls kein Datenblatt vorliegt, kann man mit einem Durchgangsprüfer feststellen, welcher Anschluss mit Masse (GND) verbunden ist.&lt;br /&gt;
&lt;br /&gt;
V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; wird ganz einfach an GND angeschlossen und V&amp;lt;sub&amp;gt;CC&amp;lt;/sub&amp;gt;=V&amp;lt;sub&amp;gt;DD&amp;lt;/sub&amp;gt; an +5 V. V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt; = V0 = V5 kann man testweise auch an GND legen. Wenn das LCD dann zu dunkel sein sollte, muss man ein 10k&amp;amp;Omega;-Potentiometer zwischen GND und 5 V schalten, mit dem Schleifer an V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt;. Meist kann man den +5 V-Anschluss am Poti weglassen, da im Display ein Pull-up-Widerstand ist:&lt;br /&gt;
&lt;br /&gt;
[[Bild:LCD_Vee.gif|framed|center| Gewinnung der Kontrastspannung]]&lt;br /&gt;
&lt;br /&gt;
Wenn der Kontrast zu schwach sein sollte (z.B. bei tiefen Temperaturen), kann man anstelle von GND eine negative Spannung ans Kontrast-Poti legen. Diese kann bis -5 V gehen und kann leicht aus einem Timerpin des µC, einem Widerstand, zwei Dioden und zwei Kondensatoren erzeugt werden. So wird auch ein digital einstellbarer Kontrast mittels PWM ermöglicht.&lt;br /&gt;
&lt;br /&gt;
Es gibt zwei verschiedene Möglichkeiten zur Ansteuerung eines solchen Displays: den &#039;&#039;&#039;8-Bit-&#039;&#039;&#039; und den &#039;&#039;&#039;4-Bit-&#039;&#039;&#039;Modus.&lt;br /&gt;
* Für den &#039;&#039;&#039;8-Bit-Modus&#039;&#039;&#039; werden (wie der Name schon sagt) alle acht Datenleitungen zur Ansteuerung verwendet, somit kann durch einen Zugriff immer ein ganzes Byte übertragen werden.&lt;br /&gt;
* Der &#039;&#039;&#039;4-Bit-Modus&#039;&#039;&#039; verwendet nur die oberen vier Datenleitungen (&#039;&#039;&#039;DB4-DB7&#039;&#039;&#039;). Um ein Byte zu übertragen, braucht man somit zwei Zugriffe, wobei zuerst das höherwertige &#039;&#039;&#039;&amp;quot;Nibble&amp;quot;&#039;&#039;&#039; (= 4 Bits), also Bit 4 bis Bit 7 übertragen wird und dann das niederwertige, also Bit 0 bis Bit 3. Die unteren Datenleitungen des LCDs, die beim Lesezyklus Ausgänge sind, lässt man offen (siehe Datasheets, z.&amp;amp;nbsp;B. vom KS0070).&lt;br /&gt;
&lt;br /&gt;
Der 4-Bit-Modus hat den Vorteil, dass man 4 IO-Pins weniger benötigt als beim 8-Bit-Modus. 6 bzw. 7 Pins (eines Portes) reichen aus.&lt;br /&gt;
&lt;br /&gt;
Neben den vier Datenleitungen (DB4, DB5, DB6 und DB7) werden noch die Anschlüsse &#039;&#039;&#039;RS&#039;&#039;&#039;, &#039;&#039;&#039;RW&#039;&#039;&#039; und &#039;&#039;&#039;E&#039;&#039;&#039; benötigt. &lt;br /&gt;
&lt;br /&gt;
* Über &#039;&#039;&#039;RS&#039;&#039;&#039; wird ausgewählt, ob man einen Befehl oder ein Datenbyte an das LCD schicken möchte. Beim Schreiben gilt: ist RS Low, dann wird das ankommende Byte als Befehl interpretiert; Ist RS high, wird das Byte auf dem LCD angezeigt (genauer: ins Data-Register geschrieben, kann auch für den CG bestimmt sein). &lt;br /&gt;
* &#039;&#039;&#039;RW&#039;&#039;&#039; legt fest, ob geschrieben oder gelesen werden soll. High bedeutet lesen, low bedeutet schreiben. Wenn man RW auf lesen einstellt und RS auf Befehl, dann kann man das &#039;&#039;&#039;Busy-Flag&#039;&#039;&#039; an DB7 lesen, das anzeigt, ob das LCD den vorhergehenden Befehl fertig verarbeitet hat. Ist RS auf Daten eingestellt, dann kann man z.&amp;amp;nbsp;B. den Inhalt des Displays lesen - was jedoch nur in den wenigsten Fällen Sinn macht. Deshalb kann man RW dauerhaft auf low lassen (= an GND anschließen), so dass man noch ein IO-Pin am Controller einspart. Der Nachteil ist, dass man dann das Busy-Flag nicht lesen kann, weswegen man nach jedem Befehl ca. 50 µs (beim Return Home 2 ms, beim Clear Display 20 ms) warten sollte, um dem LCD Zeit zum Ausführen des Befehls zu geben. Dummerweise schwankt die Ausführungszeit von Display zu Display und ist auch von der Betriebsspannung abhängig. Für professionellere Sachen also lieber den IO-Pin opfern und Busy abfragen.&lt;br /&gt;
* Der &#039;&#039;&#039;E&#039;&#039;&#039; Anschluss schließlich signalisiert dem LCD, dass die übrigen Datenleitungen jetzt korrekte Pegel angenommen haben und es die gewünschten Daten von den Datenleitungen bzw. Kommandos von den Datenleitungen übernehmen kann. Beim Lesen gibt das Display die Daten / Status so lange aus, wie E high ist. Beim Schreiben übernimmt das Display die Daten mit der fallenden Flanke.&lt;br /&gt;
&lt;br /&gt;
== Anschluss an den Controller ==&lt;br /&gt;
&lt;br /&gt;
Jetzt, da wir wissen, welche Anschlüsse das LCD benötigt, können wir das LCD mit dem Mikrocontroller verbinden: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;ACHTUNG: Es gibt Displays mit abweichender Anschluss-Belegung, falscher Anschluss kann zur Zerstörung führen! Daher immer das zugehörige Datenblatt zu Rate ziehen.&amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Einzelheiten unter [http://www.mikrocontroller.net/articles/HD44780 Artikel zum Controller HD44780]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
!Pinnummer&amp;lt;BR&amp;gt;LCD || Bezeichnung || Anschluss&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |1 || V&amp;lt;sub&amp;gt;SS&amp;lt;/sub&amp;gt; || GND&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |2 || V&amp;lt;sub&amp;gt;CC&amp;lt;/sub&amp;gt; || +5 V&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |3 || V&amp;lt;sub&amp;gt;EE&amp;lt;/sub&amp;gt; || GND oder [[Potentiometer | Poti]] &lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |4 || RS || PD4 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |5 || RW || GND &lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |6 || E || PD5 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |7 || DB0 || nicht angeschlossen &lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |8 || DB1 || nicht angeschlossen&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |9 || DB2 || nicht angeschlossen&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |10 || DB3 || nicht angeschlossen&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |11 || DB4 || PD0 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |12 || DB5 || PD1 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |13 || DB6 || PD2 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |14 || DB7 || PD3 am AVR&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |15 || A || Vorsicht! Meistens nicht direkt an +5 V abschließbar,&amp;lt;br /&amp;gt; sondern nur über einen Vorwiderstand, der an die Daten&amp;lt;br /&amp;gt;der Hintergrundbeleuchtung angepasst werden muss.&lt;br /&gt;
|-&lt;br /&gt;
| align=&amp;quot;center&amp;quot; |16 || K || GND&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ok. Alles ist verbunden. Wenn man jetzt den Strom einschaltet, sollten ein oder zwei schwarze Balken auf dem Display angezeigt werden. &lt;br /&gt;
&lt;br /&gt;
Doch wie bekommt man jetzt die Befehle und Daten in das Display? Dazu muss das LCD initialisiert werden und man muss Befehle (Commands) und seine Daten an das LCD senden. Weil die Initialisierung ein Spezialfall der Übertragung von Befehlen ist, im Folgenden zunächst die Erklärung für die Übertragung von Werten an das LCD.&lt;br /&gt;
&lt;br /&gt;
== Ansteuerung des LCDs im 4-Bit-Modus ==&lt;br /&gt;
&lt;br /&gt;
Um ein Byte zu übertragen, muss man es erstmal in die beiden Nibbles zerlegen, die getrennt übertragen werden. Da das obere Nibble (Bit 4 - Bit 7) als erstes übertragen wird, die 4 Datenleitungen jedoch an die vier unteren Bits des Port D angeschlossen sind, muss man die beiden Nibbles des zu übertragenden Bytes erstmal vertauschen. Der AVR kennt dazu praktischerweise einen eigenen Befehl: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           swap r16               ; vertauscht die beiden Nibbles von r16&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aus 0b00100101 wird so z.&amp;amp;nbsp;B. 0b01010010. &lt;br /&gt;
&lt;br /&gt;
Jetzt sind die Bits für die erste Phase der Übertragung an der richtigen Stelle. Trotzdem wollen wir das Ergebnis nicht einfach so mit &#039;&#039;&#039;out PORTD, r16&#039;&#039;&#039; an den Port geben. Um die Hälfte des Bytes, die jetzt nicht an die Datenleitungen des LCDs gegeben wird auf null zu setzen, verwendet man folgenden Befehl: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           andi r16, 0b00001111   ; Nur die vier unteren (mit 1 markierten)&lt;br /&gt;
                                  ; Bits werden übernommen, alle anderen werden null&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Also: Das obere Nibble wird erst mit dem unteren vertauscht, damit es unten ist. Dann wird das obere (das wir jetzt noch nicht brauchen) auf null gesetzt. &lt;br /&gt;
&lt;br /&gt;
Jetzt müssen wir dem LCD noch mitteilen, ob wir Daten oder Befehle senden wollen. Das machen wir, indem wir das Bit, an dem RS angeschlossen ist (PD4), auf 0 (Befehl senden) oder auf 1 (Daten senden) setzen. Um ein Bit in einem normalen Register zu setzen, gibt es den Befehl sbr (Set Bit in Register). Dieser Befehl unterscheidet sich jedoch von sbi (das nur für IO-Register gilt) dadurch, dass man nicht die Nummer des zu setzenden Bits angibt, sondern eine Bitmaske. Das geht so: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           sbr r16, 0b00010000     ; Bit 4 setzen, alle anderen Bits bleiben gleich&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
RS ist an PD4 angeschlossen. Wenn wir r16 an den Port D ausgeben, ist RS jetzt also high und das LCD erwartet Daten anstatt von Befehlen. &lt;br /&gt;
&lt;br /&gt;
Das Ergebnis können wir jetzt endlich direkt an den Port D übergeben: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           out PORTD, r16&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich muss vorher der Port D auf Ausgang geschaltet werden, indem man 0xFF ins Datenrichtungsregister DDRD schreibt. &lt;br /&gt;
&lt;br /&gt;
Um dem LCD zu signalisieren, dass es das an den Datenleitungen anliegende Nibble übernehmen kann, wird die E-Leitung (Enable, an PD5 angeschlossen) auf high und kurz darauf wieder auf low gesetzt. Ein Puls an dieser Leitung teilt also dem LCD mit, das die restlichen Leitungen jetzt ihren vom Programm gewollten Pegel eingenommen haben und gültig sind.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           sbi PORTD, 5              ; Enable high&lt;br /&gt;
           nop                       ; 3 Taktzyklen warten (&amp;quot;nop&amp;quot; = nichts tun)&lt;br /&gt;
           nop&lt;br /&gt;
           nop&lt;br /&gt;
           cbi PORTD, 5              ; Enable wieder low&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die eine Hälfte des Bytes wäre damit geschafft! Die andere Hälfte kommt direkt hinterher: Alles, was an der obenstehenden Vorgehensweise geändert werden muss, ist, das &amp;quot;swap&amp;quot; (Vertauschen der beiden Nibbles) wegzulassen.&lt;br /&gt;
&lt;br /&gt;
== Initialisierung des Displays ==&lt;br /&gt;
&lt;br /&gt;
Allerdings gibt es noch ein Problem. Wenn ein LCD eingeschaltet wird, dann läuft es zunächst im 8 Bit Modus. Irgendwie muss das Display initialisiert und auf den 4 Bit Modus umgeschaltet werden, und zwar nur mit den 4 zur Verfügung stehenden Datenleitungen.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Wenn es Probleme gibt, dann meistens an diesem Punkt. Die &amp;quot;kompatiblen&amp;quot; Kontroller sind gelegentlich doch nicht 100% identisch. Es lohnt sich, das Datenblatt (siehe Weblinks im Artikel [[LCD]]) genau zu lesen, in welcher Reihenfolge und mit welchen Abständen (Delays) die Initialisierungbefehle gesendet werden. Eine weitere Hilfe können Ansteuerungsbeispiele in Forenbeiträgen geben z.&amp;amp;nbsp;B.&lt;br /&gt;
&lt;br /&gt;
* [http://www.mikrocontroller.net/topic/79609#664268 (A) KS0066U oder Ähnliche --- LCD Treiber]&lt;br /&gt;
&lt;br /&gt;
=== Initialisierung für 4 Bit Modus ===&lt;br /&gt;
&lt;br /&gt;
Achtung: Im Folgenden sind alle Bytes aus Sicht des LCD-Kontrollers angegeben! Da LCD-seitig nur die Leitungen DB4 - DB7 verwendet werden, ist daher immer nur das höherwertige Nibble gültig. Durch die Art der Verschaltung (DB4 - DB7 wurde auf dem PORT an PD0 bis PD3 angeschlossen) ergibt sich eine Verschiebung, so dass das am Kontroller auszugebende Byte nibblemässig vertauscht ist!&lt;br /&gt;
&lt;br /&gt;
Die Sequenz, aus Sicht des Kontrollers, sieht so aus:&lt;br /&gt;
&lt;br /&gt;
* Nach dem Anlegen der Betriebsspannung muss eine Zeit von mindestens ca. 15ms gewartet werden, um dem LCD-Kontroller Zeit für seine eigene Initialisierung zu geben&lt;br /&gt;
* $3 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 4.1ms warten&lt;br /&gt;
* $3 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 100µs warten&lt;br /&gt;
* $3 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* $2 ins Steuerregister schreiben (RS = 0), dadurch wird auf 4 Bit Daten umgestellt&lt;br /&gt;
* Ab jetzt muss für die Übertragung eines Bytes jeweils zuerst das höherwertige Nibble und dann das niederwertige Nibble übertragen werden, wie oben beschrieben&lt;br /&gt;
* Mit dem Konfigurier-Befehl $20 das Display konfigurieren (4-Bit, 1 oder 2 Zeilen, 5x7 Format)&lt;br /&gt;
* Mit den restlichen Konfigurierbefehlen die Konfiguration vervollständigen: Display ein/aus, Cursor ein/aus, etc.&lt;br /&gt;
&lt;br /&gt;
Eine Begründung, warum die ersten Befehle dreifach geschickt werden sollen, findet sich [http://www.mikrocontroller.net/topic/158983#1508510 im Forum].&lt;br /&gt;
&lt;br /&gt;
=== Initialisierung für 8 Bit Modus ===&lt;br /&gt;
&lt;br /&gt;
Der Vollständigkeit halber hier noch die notwendige Initialiserungssequenz für den 8 Bit Modus. Da hier die Daten komplett als 1 Byte übertragen werden können, sind einige Klimmzüge wie im 4 Bit Modus nicht notwendig. Begründung für die anfänglichen Wiederholungen siehe oben.&lt;br /&gt;
&lt;br /&gt;
* Nach dem Anlegen der Betriebsspannung muss eine Zeit von mindestens ca. 15ms gewartet werden, um dem LCD-Kontroller Zeit für seine eigene Initialisierung zu geben&lt;br /&gt;
* $30 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 4.1ms warten&lt;br /&gt;
* $30 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mindestens 100µs warten&lt;br /&gt;
* $30 ins Steuerregister schreiben (RS = 0)&lt;br /&gt;
* Mit dem Konfigurier-Befehl 0x30 das Display konfigurieren (8-Bit, 1 oder 2 Zeilen, 5x7 Format)&lt;br /&gt;
* Mit den restlichen Konfigurierbefehlen die Konfiguration vervollständigen: Display ein/aus, Cursor ein/aus, etc.&lt;br /&gt;
&lt;br /&gt;
== Routinen zur LCD-Ansteuerung im 4-Bit-Modus ==&lt;br /&gt;
&lt;br /&gt;
Im Folgenden werden die bisherigen Grundroutinen zur LCD-Ansteuerung im 4-Bit-Modus zusammengefasst und kommentiert. Die darin enthaltenen Symbole (temp1, PORTD,...) müssen in einem dazugehörenden Hauptprogramm definiert werden. Dies wird nächsten Abschnitt &#039;&#039;Anwendung&#039;&#039; weiter erklärt.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
;;                 LCD-Routinen                ;;&lt;br /&gt;
;;                 ============                ;;&lt;br /&gt;
;;              (c)andreas-s@web.de            ;;&lt;br /&gt;
;;                                             ;;&lt;br /&gt;
;; 4bit-Interface                              ;;&lt;br /&gt;
;; DB4-DB7:       PD0-PD3                      ;;&lt;br /&gt;
;; RS:            PD4                          ;;&lt;br /&gt;
;; E:             PD5                          ;;&lt;br /&gt;
;;                                             ;;&lt;br /&gt;
;; Takt:          4 MHz                        ;;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 ;sendet ein Datenbyte an das LCD&lt;br /&gt;
lcd_data:&lt;br /&gt;
           mov temp2, temp1             ; &amp;quot;Sicherungskopie&amp;quot; für&lt;br /&gt;
                                        ; die Übertragung des 2.Nibbles&lt;br /&gt;
           swap temp1                   ; Vertauschen&lt;br /&gt;
           andi temp1, 0b00001111       ; oberes Nibble auf Null setzen&lt;br /&gt;
           sbr temp1, 1&amp;lt;&amp;lt;4              ; entspricht 0b00010000 (Anm.1)&lt;br /&gt;
           out PORTD, temp1             ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
                                        ; 2. Nibble, kein swap da es schon&lt;br /&gt;
                                        ; an der richtigen stelle ist&lt;br /&gt;
           andi temp2, 0b00001111       ; obere Hälfte auf Null setzen &lt;br /&gt;
           sbr temp2, 1&amp;lt;&amp;lt;4              ; entspricht 0b00010000&lt;br /&gt;
           out PORTD, temp2             ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
           rcall delay50us              ; Delay-Routine aufrufen&lt;br /&gt;
           ret                          ; zurück zum Hauptprogramm&lt;br /&gt;
&lt;br /&gt;
 ; sendet einen Befehl an das LCD&lt;br /&gt;
lcd_command:                            ; wie lcd_data, nur RS=0&lt;br /&gt;
           mov temp2, temp1&lt;br /&gt;
           swap temp1&lt;br /&gt;
           andi temp1, 0b00001111&lt;br /&gt;
           out PORTD, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           andi temp2, 0b00001111&lt;br /&gt;
           out PORTD, temp2&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay50us&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; erzeugt den Enable-Puls&lt;br /&gt;
 ;&lt;br /&gt;
 ; Bei höherem Takt (&amp;gt;= 8 MHz) kann es notwendig sein, &lt;br /&gt;
 ; vor dem Enable High 1-2 Wartetakte (nop) einzufügen. &lt;br /&gt;
 ; Siehe dazu http://www.mikrocontroller.net/topic/81974#685882&lt;br /&gt;
lcd_enable:&lt;br /&gt;
           sbi PORTD, 5                 ; Enable high&lt;br /&gt;
           nop                          ; mindestens 3 Taktzyklen warten&lt;br /&gt;
           nop&lt;br /&gt;
           nop&lt;br /&gt;
           cbi PORTD, 5                 ; Enable wieder low&lt;br /&gt;
           ret                          ; Und wieder zurück                     &lt;br /&gt;
&lt;br /&gt;
 ; Pause nach jeder Übertragung&lt;br /&gt;
delay50us:                              ; 50µs Pause (bei 4 MHz)&lt;br /&gt;
           ldi  temp1, $42&lt;br /&gt;
delay50us_:dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause (bei 4 MHz)&lt;br /&gt;
           ldi  temp1, $21&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&lt;br /&gt;
 ; Initialisierung: muss ganz am Anfang des Programms aufgerufen werden&lt;br /&gt;
lcd_init:&lt;br /&gt;
           ldi  temp3,50&lt;br /&gt;
powerupwait:&lt;br /&gt;
           rcall  delay5ms&lt;br /&gt;
           dec  temp3&lt;br /&gt;
           brne powerupwait&lt;br /&gt;
           ldi temp1, 0b00000011        ; muss 3mal hintereinander gesendet&lt;br /&gt;
           out PORTD, temp1             ; werden zur Initialisierung&lt;br /&gt;
           rcall lcd_enable             ; 1&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; 2&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; und 3!&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi temp1, 0b00000010        ; 4bit-Modus einstellen&lt;br /&gt;
           out PORTD, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi temp1, 0b00101000        ; 4Bit / 2 Zeilen / 5x8&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi temp1, 0b00001100        ; Display ein / Cursor aus / kein Blinken&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi temp1, 0b00000100        ; inkrement / kein Scrollen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           ldi temp1, 0b00000001   ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; Sendet den Befehl: Cursor Home&lt;br /&gt;
lcd_home:&lt;br /&gt;
           ldi temp1, 0b00000010   ; Cursor Home&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Anm.1: Siehe [[Bitmanipulation]]&lt;br /&gt;
&lt;br /&gt;
Weitere Funktionen (wie z.&amp;amp;nbsp;B. Cursorposition verändern) sollten mit Hilfe der [[AVR-Tutorial:_LCD#Welche_Befehle_versteht_das_LCD.3F|Befehlscodeliste]] nicht schwer zu realisieren sein. Einfach den Code in temp laden, lcd_command aufrufen und ggf. eine Pause einfügen.&amp;lt;br&amp;gt; &lt;br /&gt;
Natürlich kann man die LCD-Ansteuerung auch an einen anderen Port des Mikrocontrollers &amp;quot;verschieben&amp;quot;: Wenn das LCD z.&amp;amp;nbsp;B. an Port B angeschlossen ist, dann reicht es, im Programm alle &amp;quot;PORTD&amp;quot; durch &amp;quot;PORTB&amp;quot; und &amp;quot;DDRD&amp;quot; durch &amp;quot;DDRB&amp;quot; zu ersetzen.&amp;lt;br&amp;gt; &lt;br /&gt;
Wer eine höhere Taktfrequenz als 4 MHz verwendet, der sollte daran denken, die Dauer der Verzögerungsschleifen anzupassen.&lt;br /&gt;
&lt;br /&gt;
==Anwendung==&lt;br /&gt;
&lt;br /&gt;
Ein Programm, das diese Routinen zur Anzeige von Text verwendet, kann z.&amp;amp;nbsp;B. so aussehen (die Datei lcd-routines.asm muss sich im gleichen Verzeichnis befinden). Nach der Initialisierung wird zuerst der Displayinhalt gelöscht. Um dem LCD ein Zeichen zu schicken, lädt man es in temp1 und ruft die Routine &amp;quot;lcd_data&amp;quot; auf. Das folgende Beispiel zeigt das Wort &amp;quot;Test&amp;quot; auf dem LCD an. &lt;br /&gt;
&lt;br /&gt;
[http://www.mikrocontroller.net/sourcecode/tutorial/lcd-test.asm Download lcd-test.asm] &lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.include &amp;quot;m8def.inc&amp;quot;&lt;br /&gt;
&lt;br /&gt;
; .def definiert ein Synonym (Namen) für ein µC Register&lt;br /&gt;
.def temp1 = r16&lt;br /&gt;
.def temp2 = r17&lt;br /&gt;
.def temp3 = r18&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, LOW(RAMEND)      ; LOW-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPL, temp1&lt;br /&gt;
           ldi temp1, HIGH(RAMEND)     ; HIGH-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPH, temp1&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, 0xFF    ; Port D = Ausgang&lt;br /&gt;
           out DDRD, temp1&lt;br /&gt;
&lt;br /&gt;
           rcall lcd_init     ; Display initialisieren&lt;br /&gt;
           rcall lcd_clear    ; Display löschen&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, &#039;T&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, &#039;e&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           &lt;br /&gt;
           ldi temp1, &#039;s&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
           ldi temp1, &#039;t&#039;     ; Zeichen anzeigen&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
loop:&lt;br /&gt;
           rjmp loop&lt;br /&gt;
&lt;br /&gt;
.include &amp;quot;lcd-routines.asm&amp;quot;            ; LCD-Routinen werden hier eingefügt&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Für längere Texte ist die Methode, jedes Zeichen einzeln in das Register zu laden und &amp;quot;lcd_data&amp;quot; aufzurufen natürlich nicht sehr praktisch. Dazu später aber mehr.&lt;br /&gt;
&lt;br /&gt;
Bisher wurden in Register immer irgendwelche Zahlenwerte geladen, aber in diesem Programm kommt plötzlich die Anweisung&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ldi temp1, &#039;T&#039;&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
vor. Wie ist diese zu verstehen? Passiert hier etwas grundlegend anderes als beim Laden einer Zahl in ein Register?&lt;br /&gt;
&lt;br /&gt;
Die Antwort darauf lautet: Nein. Auch hier wird letztendlich nur eine Zahl in ein Register geladen. Der Schlüssel zum Verständnis beruht darauf, dass zum LCD, so wie zu allen Ausgabegeräten, für die Ausgabe von Texten immer nur Zahlen übertragen werden, sog. Codes. Zum Beispiel könnte man vereinbaren, dass ein LCD, wenn es den Ausgabecode 65 erhält, ein &#039;A&#039; anzeigt, bei einem Ausgabecode von 66 ein &#039;B&#039; usw. Naturgemäß gibt es daher viele verschiedene Code-Buchstaben Zuordnungen. Damit hier etwas Ordnung in das potentielle Chaos kommt, hat man sich bereits in der Steinzeit der Programmierung auf bestimmte Codetabellen geeinigt, von denen die verbreitetste sicherlich die ASCII-Zuordnung ist.&lt;br /&gt;
&lt;br /&gt;
==ASCII==&lt;br /&gt;
&lt;br /&gt;
ASCII steht für &#039;&#039;American Standard Code for Information Interchange&#039;&#039; und ist ein standardisierter Code zur Zeichenumsetzung. Die Codetabelle sieht hexadezimal dabei wie folgt aus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
!   ||x0||x1||x2||x3||x4||x5||x6||x7||x8||x9||xA||xB||xC||xD||xE||xF&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 0x&lt;br /&gt;
|NUL||SOH||STX||ETX||EOT||ENQ||ACK||BEL||BS||HT||LF||VT||FF||CR||SO||SI&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 1x&lt;br /&gt;
|DLE||DC1||DC2||DC3||DC4||NAK||SYN||ETB||CAN||EM||SUB||ESC||FS||GS||RS||US&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 2x&lt;br /&gt;
|SP||!||&amp;quot;||#||$||%||&amp;amp;||&#039;||(||)||*||+||,||-||.||/&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 3x&lt;br /&gt;
|0||1||2||3||4||5||6||7||8||9||:||;||&amp;lt;||=||&amp;gt;||?&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 4x&lt;br /&gt;
|@||A||B||C||D||E||F||G||H||I||J||K||L||M||N||O&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 5x&lt;br /&gt;
|P||Q||R||S||T||U||V||W||X||Y||Z||[||\||]||^||_&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 6x&lt;br /&gt;
|`||a||b||c||d||e||f||g||h||i||j||k||l||m||n||o&lt;br /&gt;
|-&lt;br /&gt;
!style=&amp;quot;background-color:#ffddcc&amp;quot;| 7x&lt;br /&gt;
|p||q||r||s||t||u||v||w||x||y||z||{|| &amp;amp;#124; ||}||~||DEL&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Die ersten beiden Zeilen enthalten die Codes für einige Steuerzeichen, ihre vollständige Beschreibung würde hier zu weit führen. Das Zeichen &#039;&#039;&#039;SP&#039;&#039;&#039; steht für ein &#039;&#039;Space&#039;&#039;, also ein Leerzeichen. &#039;&#039;&#039;BS&#039;&#039;&#039; steht für &#039;&#039;Backspace&#039;&#039;, also ein Zeichen zurück. &#039;&#039;&#039;DEL&#039;&#039;&#039; steht für &#039;&#039;Delete&#039;&#039;, also das Löschen eines Zeichens. &#039;&#039;&#039;CR&#039;&#039;&#039; steht für &#039;&#039;Carriage Return&#039;&#039;, also wörtlich: der Wagenrücklauf (einer Schreibmaschine), während &#039;&#039;&#039;LF&#039;&#039;&#039; für &#039;&#039;Line feed&#039;&#039;, also einen Zeilenvorschub steht.&lt;br /&gt;
&lt;br /&gt;
Der Assembler kennt diese Codetabelle und ersetzt die Zeile&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ldi temp1, &#039;T&#039;&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
durch&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ldi temp1, $54&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
was letztendlich auch der Lesbarkeit des Programmes zugute kommt. Funktional besteht kein Unterschied zwischen den beiden Anweisungen. Beide bewirken, dass das Register temp1 mit dem Bitmuster 01010100 ( = hexadezimal 54, = dezimal 84 oder eben der ASCII Code für &#039;&#039;&#039;T&#039;&#039;&#039;) geladen wird.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Das LCD wiederrum kennt diese Code-Tabelle ebenfalls und wenn es über den Datenbus die Codezahl $54 zur Anzeige empfängt, dann schreibt es ein &#039;&#039;&#039;T&#039;&#039;&#039; an die aktuelle Cursorposition. Genauer gesagt, weiss das LCD nichts von einem &#039;&#039;&#039;T&#039;&#039;&#039;. Es sieht einfach in seinen internen Tabellen nach, welche Pixel beim Empfang der Codezahl $54 auf schwarz zu setzen sind. &#039;Zufällig&#039; sind das genau jene Pixel, die für uns Menschen ein &#039;&#039;&#039;T&#039;&#039;&#039; ergeben.&lt;br /&gt;
&lt;br /&gt;
==Welche Befehle versteht das LCD?==&lt;br /&gt;
&lt;br /&gt;
Auf dem LCD arbeitet ein Controller vom Typ HD44780. Dieser Kontroller versteht eine Reihe von Befehlen, die allesamt mittels lcd_command gesendet werden können. Ein Kommando ist dabei nichts anderes als ein Befehlsbyte, in dem die verschiedenen Bits verschiedene Bedeutungen haben:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
! Bitwert   || Bedeutung&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  0&lt;br /&gt;
||dieses Bit muss 0 sein&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  1&lt;br /&gt;
||dieses Bit muss 1 sein&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; |  x&lt;br /&gt;
||der Zustand dieses Bits ist egal&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot; | sonstige Buchstaben&lt;br /&gt;
||das Bit muss je nach gewünschter Funktionalität gesetzt werden.&amp;lt;br /&amp;gt;Die mögliche Funktionalität des jeweiligen Bits geht aus der Befehlsbeschreibung hervor.&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Beispiel: Das Kommando &#039;ON/OFF Control&#039; soll benutzt werden, um das Display einzuschalten, der Cursor soll eingeschaltet werden und der Cursor soll blinken.&lt;br /&gt;
Das Befehlsbyte ist so aufgebaut:&lt;br /&gt;
   0b00001dcb&lt;br /&gt;
Aus der Befehlsbeschreibung entnimmt man:&lt;br /&gt;
* Display ein bedeutet, dass an der Bitposition d eine 1 stehen muss.&amp;lt;br&amp;gt;&lt;br /&gt;
* Cursor ein bedeutet, dass an der Bitposition c ein 1 stehen muss.&amp;lt;br&amp;gt;&lt;br /&gt;
* Cursor blinken bedeutet, dass an der Bitposition b eine 1 stehen muss.&amp;lt;br&amp;gt;&lt;br /&gt;
Das dafür zu übertragende Befehlsbyte hat also die Gestalt 0b00001111 oder in hexadezimaler Schreibweise $0F.&lt;br /&gt;
&lt;br /&gt;
===Clear display: 0b00000001===&lt;br /&gt;
&lt;br /&gt;
Die Anzeige wird gelöscht und der Ausgabecursor kehrt an die Home Position (links, erste Zeile) zurück.&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 1.64ms&lt;br /&gt;
&lt;br /&gt;
===Cursor home: 0b0000001x===&lt;br /&gt;
&lt;br /&gt;
Der Cursor kehrt an die Home Position (links, erste Zeile) zurück. Ein verschobenes Display wird auf die Grundeinstellung zurückgesetzt.&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs bis 1.64ms&lt;br /&gt;
&lt;br /&gt;
===Entry mode: 0b000001is===&lt;br /&gt;
&lt;br /&gt;
Legt die Cursor Richtung sowie eine mögliche Verschiebung des Displays fest&lt;br /&gt;
* i = 1, Cursorposition bei Ausgabe eines Zeichens erhöhen&lt;br /&gt;
* i = 0, Cursorposition bei Ausgabe eines Zeichens vermindern&lt;br /&gt;
* s = 1, Display wird gescrollt, wenn der Cursor das Ende/Anfang, je nach Einstellung von i, erreicht hat.&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===On/off control: 0b00001dcb===&lt;br /&gt;
&lt;br /&gt;
Display insgesamt ein/ausschalten; den Cursor ein/ausschalten; den Cursor auf blinken schalten/blinken aus. Wenn das Display ausgeschaltet wird, geht der Inhalt des Displays nicht verloren. Der vorher angezeigte Text wird nach wiedereinschalten erneut angezeigt.&lt;br /&gt;
Ist der Cursor eingeschaltet, aber Blinken ausgeschaltet, so wird der Cursor als Cursorzeile in Pixelzeile 8 dargestellt. Ist Blinken eingeschaltet, wird der Cursor als blinkendes ausgefülltes Rechteck dargestellt, welches abwechselnd mit dem Buchstaben an dieser Stelle angezeigt wird.&lt;br /&gt;
&lt;br /&gt;
* d = 0, Display aus&lt;br /&gt;
* d = 1, Display ein&lt;br /&gt;
* c = 0, Cursor aus&lt;br /&gt;
* c = 1, Cursor ein&lt;br /&gt;
* b = 0, Cursor blinken aus&lt;br /&gt;
* b = 1, Cursor blinken ein&lt;br /&gt;
 &lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Cursor/Scrollen: 0b0001srxx===&lt;br /&gt;
&lt;br /&gt;
Bewegt den Cursor oder scrollt das Display um eine Position entweder nach rechts oder nach links.&lt;br /&gt;
&lt;br /&gt;
* s = 1, Display scrollen&lt;br /&gt;
* s = 0, Cursor bewegen&lt;br /&gt;
* r = 1, nach rechts&lt;br /&gt;
* r = 0, nach links &lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Konfiguration: 0b001dnfxx===&lt;br /&gt;
&lt;br /&gt;
Einstellen der Interface Art, Modus, Font&lt;br /&gt;
* d = 0, 4-Bit Interface&lt;br /&gt;
* d = 1, 8-Bit Interface&lt;br /&gt;
* n = 0, 1 zeilig&lt;br /&gt;
* n = 1, 2 zeilig&lt;br /&gt;
* f = 0, 5x7 Pixel&lt;br /&gt;
* f = 1, 5x11 Pixel&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Character RAM Address Set: 0b01aaaaaa===&lt;br /&gt;
&lt;br /&gt;
Mit diesem Kommando werden maximal 8 selbst definierte Zeichen definiert. Dazu wird der Character RAM Zeiger auf den Anfang des Character Generator (CG) RAM gesetzt und das Zeichen durch die Ausgabe von 8 Byte definiert. Der Adresszeiger wird nach Ausgabe jeder Pixelspalte (8 Bit) vom LCD selbst erhöht. Nach Beendigung der Zeichendefinition muss die Schreibposition explizit mit dem Kommando &amp;quot;Display RAM Address Set&amp;quot; wieder in den DD-RAM Bereich gesetzt werden.&lt;br /&gt;
&lt;br /&gt;
aaaaaa 6-bit CG RAM Adresse&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
===Display RAM Address Set: 0b1aaaaaaa===&lt;br /&gt;
&lt;br /&gt;
Den Cursor neu positionieren. Display Data (DD) Ram ist vom Character Generator (CG) Ram unabhängig. Der Adresszeiger wird bei Ausgabe eines Zeichens ins DD Ram automatisch erhöht. Das Display verhält sich so, als ob eine Zeile immer aus 40 logischen Zeichen besteht, von der, je nach konkretem Displaytyp (16 Zeichen, 20 Zeichen) immer nur ein Teil sichtbar ist.&lt;br /&gt;
&lt;br /&gt;
aaaaaaa 7-bit DD RAM Adresse. Auf 2-zeiligen Displays (und den meisten 16x1 Displays), kann die Adressangabe wie folgt interpretiert werden:&lt;br /&gt;
&lt;br /&gt;
1laaaaaa&lt;br /&gt;
* l = Zeilennummer (0 oder 1)&lt;br /&gt;
* a = 6-Bit Spaltennummer&lt;br /&gt;
&lt;br /&gt;
 --------------------------------&lt;br /&gt;
 DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0&lt;br /&gt;
 --- --- --- --- --- --- --- ---&lt;br /&gt;
  1   A   A   A   A   A   A   A &lt;br /&gt;
&lt;br /&gt;
Setzt die DDRAM Adresse:&lt;br /&gt;
&lt;br /&gt;
Wenn N = 0 (1 line display)&lt;br /&gt;
    AAAAAAA = &amp;quot;00h&amp;quot; - &amp;quot;4Fh&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Wenn N = 1 (2 line display) ((1x16))&lt;br /&gt;
    AAAAAAA = &amp;quot;00h&amp;quot; - &amp;quot;27h&amp;quot; Zeile 1. (0x80) &lt;br /&gt;
    AAAAAAA = &amp;quot;40h&amp;quot; - &amp;quot;67h&amp;quot; Zeile 2. (0xC0)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ausführungszeit: 40µs&lt;br /&gt;
&lt;br /&gt;
==Einschub: Code aufräumen==&lt;br /&gt;
&lt;br /&gt;
Es wird Zeit, sich einmal etwas kritisch mit den bisher geschriebenen Funktionen auseinander zu setzen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Portnamen aus dem Code herausziehen===&lt;br /&gt;
&lt;br /&gt;
Wenn wir die LCD-Funktionen einmal genauer betrachten, dann fällt sofort auf, dass über die Funktionen verstreut immer wieder das &#039;&#039;&#039;PORTD&#039;&#039;&#039; sowie einzelne Zahlen für die Pins an diesem Port auftauchen. Wenn das LCD an einem anderen Port betrieben werden soll, oder sich die Pin-Belegung ändert, dann muss an all diesen Stellen eine Anpassung vorgenommen werden. Dabei darf keine einzige Stelle übersehen werden, ansonsten würden die LCD-Funktionen nicht oder nicht vollständig funktionieren.&lt;br /&gt;
&lt;br /&gt;
Eine Möglichkeit, dem vorzubeugen, ist es, diese immer gleichbleibenden Dinge an den Anfang der LCD-Funktionen vorzuziehen. Anstelle von PORTD wird dann im Code ein anderer Name benutzt, den man frei vergeben kann. Dem Assembler wird nur noch mitgeteilt, das dieser Name für PORTD steht. Muss das LCD an einen anderen Port angeschlossen werden, so wird nur diese Zurodnung geändert und der Assembler passt dann im restlichen Code alle davon abhängigen Anweisungen an:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
;;                 LCD-Routinen                ;;&lt;br /&gt;
;;                 ============                ;;&lt;br /&gt;
;;              (c)andreas-s@web.de            ;;&lt;br /&gt;
;;                                             ;;&lt;br /&gt;
;; 4bit-Interface                              ;;&lt;br /&gt;
;; DB4-DB7:       PD0-PD3                      ;;&lt;br /&gt;
;; RS:            PD4                          ;;&lt;br /&gt;
;; E:             PD5                          ;;&lt;br /&gt;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;&lt;br /&gt;
 &lt;br /&gt;
; .equ definiert ein Symbol und dessen Wert&lt;br /&gt;
.equ LCD_PORT = PORTD&lt;br /&gt;
.equ LCD_DDR  = DDRD&lt;br /&gt;
.equ PIN_E    = 5&lt;br /&gt;
.equ PIN_RS   = 4&lt;br /&gt;
&lt;br /&gt;
 ;sendet ein Datenbyte an das LCD&lt;br /&gt;
lcd_data:&lt;br /&gt;
           mov temp2, temp1             ; &amp;quot;Sicherungskopie&amp;quot; für&lt;br /&gt;
                                        ; die Übertragung des 2.Nibbles&lt;br /&gt;
           swap temp1                   ; Vertauschen&lt;br /&gt;
           andi temp1, 0b00001111       ; oberes Nibble auf Null setzen&lt;br /&gt;
           sbr temp1, 1&amp;lt;&amp;lt;PIN_RS         ; entspricht 0b00010000&lt;br /&gt;
           out LCD_PORT, temp1          ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
                                        ; 2. Nibble, kein swap da es schon&lt;br /&gt;
                                        ; an der richtigen stelle ist&lt;br /&gt;
           andi temp2, 0b00001111       ; obere Hälfte auf Null setzen &lt;br /&gt;
           sbr temp2, 1&amp;lt;&amp;lt;PIN_RS         ; entspricht 0b00010000&lt;br /&gt;
           out LCD_PORT, temp2          ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
           rcall delay50us              ; Delay-Routine aufrufen&lt;br /&gt;
           ret                          ; zurück zum Hauptprogramm&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
 ; sendet einen Befehl an das LCD&lt;br /&gt;
&lt;br /&gt;
lcd_command:                            ; wie lcd_data, nur RS=0&lt;br /&gt;
           mov temp2, temp1&lt;br /&gt;
           swap temp1&lt;br /&gt;
           andi temp1, 0b00001111&lt;br /&gt;
           out LCD_PORT, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           andi temp2, 0b00001111&lt;br /&gt;
           out LCD_PORT, temp2&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay50us&lt;br /&gt;
           ret&lt;br /&gt;
 &lt;br /&gt;
 ; erzeugt den Enable-Puls&lt;br /&gt;
lcd_enable:&lt;br /&gt;
           sbi LCD_PORT, PIN_E          ; Enable high&lt;br /&gt;
           nop                          ; 3 Taktzyklen warten&lt;br /&gt;
           nop&lt;br /&gt;
           nop&lt;br /&gt;
           cbi LCD_PORT, PIN_E          ; Enable wieder low&lt;br /&gt;
           ret                          ; Und wieder zurück                     &lt;br /&gt;
 &lt;br /&gt;
 ; Pause nach jeder Übertragung&lt;br /&gt;
delay50us:                              ; 50µs Pause&lt;br /&gt;
           ldi  temp1, $42&lt;br /&gt;
delay50us_:dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
 &lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause&lt;br /&gt;
           ldi  temp1, $21&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
 &lt;br /&gt;
 ; Initialisierung: muss ganz am Anfang des Programms aufgerufen werden&lt;br /&gt;
lcd_init:&lt;br /&gt;
           ldi   temp1, 0xFF            ; alle Pins am Ausgabeport auf Ausgang&lt;br /&gt;
           out   LCD_DDR, temp1&lt;br /&gt;
&lt;br /&gt;
           ldi   temp3,6&lt;br /&gt;
powerupwait:&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           dec   temp3&lt;br /&gt;
           brne  powerupwait&lt;br /&gt;
           ldi   temp1,    0b00000011   ; muss 3mal hintereinander gesendet&lt;br /&gt;
           out   LCD_PORT, temp1        ; werden zur Initialisierung&lt;br /&gt;
           rcall lcd_enable             ; 1&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; 2&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           rcall lcd_enable             ; und 3!&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi   temp1, 0b00000010      ; 4bit-Modus einstellen&lt;br /&gt;
           out   LCD_PORT, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ldi   temp1, 0b00101000      ; 4 Bit, 2 Zeilen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi   temp1, 0b00001100      ; Display on, Cursor off&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ldi   temp1, 0b00000100      ; endlich fertig&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           ret&lt;br /&gt;
 &lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           ldi   temp1, 0b00000001      ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&lt;br /&gt;
 ; Sendet den Befehl: Cursor Home&lt;br /&gt;
lcd_home:&lt;br /&gt;
           ldi   temp1, 0b00000010      ; Cursor Home&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mittels &#039;&#039;&#039;.equ&#039;&#039;&#039; werden mit dem Assembler Textersetzungen vereinbart. Der Assembler ersetzt alle Vorkomnisse des Quelltextes durch den zu ersetzenden Text. Dadurch ist es z.&amp;amp;nbsp;B. möglich, alle Vorkommnisse von &#039;&#039;&#039;PORTD&#039;&#039;&#039; durch &#039;&#039;&#039;LCD_PORT&#039;&#039;&#039; auszutauschen. Wird das LCD an einen anderen Port, z.&amp;amp;nbsp;B. &#039;&#039;&#039;PORTB&#039;&#039;&#039; gelegt, dann genügt es, die Zeilen&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.equ LCD_PORT = PORTD&lt;br /&gt;
.equ LCD_DDR  = DDRD&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
durch&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.equ LCD_PORT = PORTB&lt;br /&gt;
.equ LCD_DDR  = DDRB&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
zu ersetzen. Der Assembler sorgt dann dafür, dass diese Portänderung an den relevanten Stellen im Code über die Textersetzungen einfließt. Selbiges natürlich mit der Pin-Zuordnung.&lt;br /&gt;
&lt;br /&gt;
===Registerbenutzung===&lt;br /&gt;
&lt;br /&gt;
Bei diesen Funktionen mussten einige Register des Prozessors benutzt werden, um darin Zwischenergebnisse zu speichern bzw. zu bearbeiten.&lt;br /&gt;
&lt;br /&gt;
Beachtet werden muss dabei natürlich, dass es zu keinen Überschneidungen kommt. Solange nur jede Funktion jeweils für sich betrachtet wird, ist das kein Problem. In 20 oder 30 Code-Zeilen kann man gut verfolgen, welches Register wofür benutzt wird. Schwieriger wird es, wenn Funktionen wiederum andere Funktionen aufrufen, die ihrerseits wieder Funktionen aufrufen usw. Jede dieser Funktionen benutzt einige Register und mit zunehmender Programmgröße wird es immer schwieriger, zu verfolgen, welches Register zu welchem Zeitpunkt wofür benutzt wird.&lt;br /&gt;
&lt;br /&gt;
Speziell bei Basisfunktionen wie diesen LCD-Funktionen, ist es daher oft ratsam, dafür zu sorgen, dass jede Funktion die Register wieder in dem Zustand hinterlässt, indem sie sie auch vorgefunden hat. Wir benötigen dazu wieder den Stack, auf dem die Registerinhalte bei Betreten einer Funktion zwischengespeichert werden und von dem die Register bei Verlassen einer Funktion wiederhergestellt werden.&lt;br /&gt;
&lt;br /&gt;
Nehmen wir die Funktion&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           ldi   temp1, 0b00000001      ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Diese Funktion verändert das Register temp1. Um das Register abzusichern, schreiben wir die Funktion um:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Sendet den Befehl zur Löschung des Displays&lt;br /&gt;
lcd_clear:&lt;br /&gt;
           push  temp1                  ; temp1 auf dem Stack sichern&lt;br /&gt;
           ldi   temp1, 0b00000001      ; Display löschen&lt;br /&gt;
           rcall lcd_command&lt;br /&gt;
           rcall delay5ms&lt;br /&gt;
           pop   temp1                  ; temp1 vom Stack wiederherstellen&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am besten hält man sich an die Regel: Jede Funktion ist dafür zuständig, die Register zu sichern und wieder herzustellen, die sie auch selbst verändert. &#039;&#039;&#039;lcd_clear&#039;&#039;&#039; ruft die Funktionen &#039;&#039;&#039;lcd_command&#039;&#039;&#039; und &#039;&#039;&#039;delay5ms&#039;&#039;&#039; auf. Wenn diese Funktionen selbst wieder Register verändern (und das tun sie), so ist es die Aufgabe dieser Funktionen, sich um die Sicherung und das Wiederherstellen der entsprechenden Register zu kümmern. &#039;&#039;&#039;lcd_clear&#039;&#039;&#039; sollte sich nicht darum kümmern müssen. Auf diese Weise ist das Schlimmste, das einem passieren kann, dass ein paar Register unnütz gesichert und wiederhergestellt werden. Das kostet zwar etwas Rechenzeit und etwas Speicherplatz auf dem Stack, ist aber immer noch besser als das andere Extrem: Nach einem Funktionsaufruf haben einige Register nicht mehr den Wert, den sie haben sollten, und das Programm rechnet mit falschen Zahlen weiter.&lt;br /&gt;
&lt;br /&gt;
===Lass den Assembler rechnen===&lt;br /&gt;
Betrachtet man den Code genauer, so fallen einige konstante Zahlenwerte auf (Das vorangestellte $ kennzeichnet die Zahl als Hexadezimalzahl):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
delay50us:                              ; 50µs Pause&lt;br /&gt;
           ldi  temp1, $42&lt;br /&gt;
delay50us_:&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Der Code benötigt eine Warteschleife, die mindestens 50µs dauert. Die beiden Befehle innerhalb der Schleife benötigen 3 Takte: 1 Takt für den &#039;&#039;&#039;dec&#039;&#039;&#039; und der &#039;&#039;&#039;brne&#039;&#039;&#039; benötigt 2 Takte, wenn die Bedingung zutrifft, der Branch also genommen wird. Bei 4 Mhz werden also 4000000 / 3 * 50 / 1000000 = 66.6 Durchläufe durch die Schleife benötigt, um eine Verzögerungszeit von 50µs (0.000050 Sekunden) zu erreichen, hexadezimal ausgedrückt: $42.&lt;br /&gt;
&lt;br /&gt;
Der springende Punkt ist: Bei anderen Taktfrequenzen müsste man nun jedesmal diese Berechnung machen und den entsprechenden Zahlenwert einsetzen. Das kann aber der Assembler genausogut erledigen. Am Anfang des Codes wird ein Eintrag definiert, der die Taktfrequenz festlegt. Traditionell heißt dieser Eintrag &amp;lt;i&amp;gt;XTAL&amp;lt;/i&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.equ XTAL  = 4000000&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
delay50us:                              ; 50µs Pause&lt;br /&gt;
           ldi  temp1, ( XTAL * 50 / 3 ) / 1000000&lt;br /&gt;
delay50us_:&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne delay50us_&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An einer anderen Codestelle gibt es weitere derartige magische Zahlen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause&lt;br /&gt;
           ldi  temp1, $21&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Was geht hier vor?&lt;br /&gt;
Die innere Schleife benötigt wieder 3 Takte pro Durchlauf. Bei $C9 = 201 Durchläufen werden also 201 * 3 = 603 Takte verbraucht. In der äußeren Schleife werden pro Durchlauf alo 1 + 603 + 1 + 2 = 607 Takte verbraucht. Da die äußere Schleife $21 = 33 mal wiederholt wird, werden 20031 Takte verbraucht. Bei 4Mhz benötigt der Prozessor 20031 / 4000000 = 0.005007 Sekunden, also 5 ms.&lt;br /&gt;
Wird der Wiederholwert für die innere Schleife bei $C9 belassen, so werden 4000000 / 607 * 5 / 1000 Wiederholungen der äusseren Schleife benötigt. Auch diese Berechnung kann wieder der Assembler übernehmen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Längere Pause für manche Befehle&lt;br /&gt;
delay5ms:                               ; 5ms Pause&lt;br /&gt;
           ldi  temp1, ( XTAL * 5 / 607 ) / 1000&lt;br /&gt;
WGLOOP0:   ldi  temp2, $C9&lt;br /&gt;
WGLOOP1:   dec  temp2&lt;br /&gt;
           brne WGLOOP1&lt;br /&gt;
           dec  temp1&lt;br /&gt;
           brne WGLOOP0&lt;br /&gt;
           ret                          ; wieder zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ein kleines Problem kann bei der Verwendung dieses Verfahrens entstehen: Bei hohen Taktfrequenzen und großen Wartezeiten kann der berechnete Wert größer als 255 werden und man bekommt die Fehlermeldung &amp;quot;Operand(s) out of range&amp;quot; beim Assemblieren. Dieser Fall tritt zum Beispiel für obige Konstruktion bei einer Taktfrequenz von 16 MHz ein (genauer gesagt ab 15,3 MHz), während darunter XTAL beliebig geändert werden kann. Als einfachste Lösung bietet es sich an, die Zahl der Takte pro Schleifendurchlauf durch das Einfügen von &#039;&#039;&#039;nop&#039;&#039;&#039; zu erhöhen und die Berechnungsvorschrift anzupassen.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ausgabe eines konstanten Textes ==&lt;br /&gt;
&lt;br /&gt;
Weiter oben wurde schon einmal ein Text ausgegeben. Dies geschah durch Ausgabe von einzelnen Zeichen. Das können wir auch anders machen. Wir können den Text im Speicher ablegen und eine Funktion schreiben, die die einzelnen Zeichen aus dem Speicher liest und aus gibt. Dabei stellt sich Frage: Woher &#039;weiß&#039; die Funktion eigentlich, wie lang der Text ist? Die Antwort darauf lautet: Sie kann es nicht wissen. Wir müssen irgendwelche Vereinbarungen treffen, woran die Funktion erkennen kann, dass der Text zu Ende ist. Im Wesentlichen werden dazu 2 Methoden benutzt:&lt;br /&gt;
* Der Text enthält ein spezielles Zeichen, welches das Ende des Textes markiert&lt;br /&gt;
* Wir speichern nicht nur den Text selbst, sondern auch die Länge des Textes&lt;br /&gt;
Mit einer der beiden Methoden ist es der Textausgabefunktion dann ein Leichtes, den Text vollständig auszugeben.&lt;br /&gt;
&lt;br /&gt;
Wir werden uns im Weiteren dafür entscheiden, ein spezielles Zeichen, eine 0 (den Wert 0, nicht das Zeichen &#039;0&#039;), dafür zu benutzen. Die Ausgabefunktionen werden dann etwas einfacher, als wenn bei der Ausgabe die Anzahl der bereits ausgegebenen Zeichen mitgezählt werden muss.&lt;br /&gt;
&lt;br /&gt;
Den Text selbst speichern wir im Flash-Speicher, also dort, wo auch das Programm gespeichert ist:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ; Einen konstanten Text aus dem Flash Speicher&lt;br /&gt;
 ; ausgeben. Der Text wird mit einer 0 beendet&lt;br /&gt;
lcd_flash_string:&lt;br /&gt;
           push  temp1&lt;br /&gt;
           push  ZH&lt;br /&gt;
           push  ZL&lt;br /&gt;
&lt;br /&gt;
lcd_flash_string_1:&lt;br /&gt;
           lpm   temp1, Z+&lt;br /&gt;
           cpi   temp1, 0&lt;br /&gt;
           breq  lcd_flash_string_2&lt;br /&gt;
           rcall  lcd_data&lt;br /&gt;
           rjmp  lcd_flash_string_1&lt;br /&gt;
&lt;br /&gt;
lcd_flash_string_2:&lt;br /&gt;
           pop   ZL&lt;br /&gt;
           pop   ZH&lt;br /&gt;
           pop   temp1&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Diese Funktion benutzt den Befehl &#039;&#039;&#039;lpm&#039;&#039;&#039;, um das jeweils nächste Zeichen aus dem Flash Speicher in ein Register zur Weiterverarbeitung zu laden. Dazu wird der sog. &#039;&#039;&#039;Z-Pointer&#039;&#039;&#039; benutzt. So nennt man das Registerpaar &#039;&#039;&#039;R30&#039;&#039;&#039; und &#039;&#039;&#039;R31&#039;&#039;&#039;. Nach jedem Ladevorgang wird dabei durch den Befehl&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           lpm   temp1, Z+&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
dieser Z-Pointer um 1 erhöht. Mittels &#039;&#039;&#039;cpi&#039;&#039;&#039; wird das in das Register &#039;&#039;&#039;temp1&#039;&#039;&#039; geladene Zeichen mit 0 verglichen. &#039;&#039;&#039;cpi&#039;&#039;&#039; vergleicht die beiden Zahlen und merkt sich das Ergebnis in einem speziellen Register in Form von Status Bits. &#039;&#039;&#039;cpi&#039;&#039;&#039; zieht dabei ganz einfach die beiden Zahlen voneinander ab. Sind sie gleich, so kommt da als Ergebnis 0 heraus und &#039;&#039;&#039;cpi&#039;&#039;&#039; setzt daher konsequenter Weise das Zero-Flag, das anzeigt, dass die vorhergegangene Operation eine 0 als Ergebnis hatte.&#039;&#039;&#039;breq&#039;&#039;&#039; wertet diese Status-Bits aus. Wenn die vorhergegangene Operation ein 0-Ergebnis hatte, das Zero-Flag also gesetzt ist, dann wird ein Sprung zum angegebenen Label durchgeführt. In Summe bewirkt also die Sequenz&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           cpi   temp1, 0&lt;br /&gt;
           breq  lcd_flash_string_2&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
dass das gelesene Zeichen mit 0 verglichen wird und falls das gelesene&lt;br /&gt;
Zeichen tatsächlich 0 war, an der Stelle lcd_flash_string_2 weiter gemacht wird. Im anderen Fall wird die bereits geschriebene Funktion &#039;&#039;&#039;lcd_data&#039;&#039;&#039; aufgerufen, welche das Zeichen ausgibt. &#039;&#039;&#039;lcd_data&#039;&#039;&#039; erwartet dabei das Zeichen im Register &#039;&#039;&#039;temp1&#039;&#039;&#039;, genau in dem Register, in welches wir vorher mittels &#039;&#039;&#039;lpm&#039;&#039;&#039; das Zeichen geladen hatten.&lt;br /&gt;
&lt;br /&gt;
Das verwendende Programm sieht dann so aus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.include &amp;quot;m8def.inc&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
.def temp1 = r16&lt;br /&gt;
.def temp2 = r17&lt;br /&gt;
.def temp3 = r18&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
           ldi temp1, LOW(RAMEND)      ; LOW-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPL, temp1&lt;br /&gt;
           ldi temp1, HIGH(RAMEND)     ; HIGH-Byte der obersten RAM-Adresse&lt;br /&gt;
           out SPH, temp1&lt;br /&gt;
 &lt;br /&gt;
           rcall lcd_init              ; Display initialisieren&lt;br /&gt;
           rcall lcd_clear             ; Display löschen&lt;br /&gt;
 &lt;br /&gt;
           ldi ZL, LOW(text*2)         ; Adresse des Strings in den&lt;br /&gt;
           ldi ZH, HIGH(text*2)        ; Z-Pointer laden&lt;br /&gt;
&lt;br /&gt;
           rcall lcd_flash_string      ; Unterprogramm gibt String aus der&lt;br /&gt;
                                       ; durch den Z-Pointer adressiert wird&lt;br /&gt;
loop:&lt;br /&gt;
           rjmp loop&lt;br /&gt;
&lt;br /&gt;
text:&lt;br /&gt;
           .db &amp;quot;Test&amp;quot;,0                ; Stringkonstante, durch eine 0&lt;br /&gt;
                                       ; abgeschlossen  &lt;br /&gt;
&lt;br /&gt;
.include &amp;quot;lcd-routines.asm&amp;quot;            ; LCD Funktionen&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Genaueres über die Verwendung unterschiedlicher Speicher findet sich im Kapitel [[AVR-Tutorial:_Speicher|Speicher]]&lt;br /&gt;
&lt;br /&gt;
==Zahlen ausgeben==&lt;br /&gt;
Um Zahlen, die beispielsweise in einem Register gespeichert sind, ausgeben zu können, ist es notwendig sich eine Textrepräsentierung der Zahl zu generieren. Die Zahl 123 wird also in den Text &amp;quot;123&amp;quot; umgewandelt welcher dann ausgegeben wird. Aus praktischen Gründen wird allerdings der Text nicht vollständig generiert (man müsste ihn ja irgendwo zwischenspeichern) sondern die einzelnen Buchstaben werden sofort ausgegeben, sobald sie bekannt sind.&lt;br /&gt;
&lt;br /&gt;
===Dezimal ausgeben===&lt;br /&gt;
Das Prinzip der Umwandlung ist einfach. Um herauszufinden wieviele Hunderter in der Zahl 123 enthalten sind, genügt es in einer Schleife immer wieder 100 von der Zahl abzuziehen und mitzuzählen wie oft dies gelang, bevor das Ergebnis negativ wurde. In diesem Fall lautet die Antwort: 1 mal, denn 123 - 100 macht 23. Versucht man erneut 100 anzuziehen, so ergibt sich eine negative Zahl.&lt;br /&gt;
Also muss eine &#039;1&#039; ausgeben werden. Die verbleibenden 23 werden weiter behandelt, indem festgestellt wird wieviele Zehner darin enthalten sind. Auch hier wiederum: In einer Schleife solange 10 abziehen, bis das Ergebnis negativ wurde. Konkret geht das 2 mal gut, also muss das nächste auszugebende Zeichen ein &#039;2&#039; sein. Damit verbleiben noch die Einer, welche direkt in das entsprechende Zeichen umgewandelt werden können. In Summe hat man also an das Display die Zeichen &#039;1&#039; &#039;2&#039; &#039;3&#039; ausgegeben.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 8 Bit Zahl ohne Vorzeichen ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_number:&lt;br /&gt;
           push  temp1            ; die Funktion verändert temp1 und temp2,&lt;br /&gt;
           push  temp2            ; also sichern wir den Inhalt, um ihn am Ende&lt;br /&gt;
                                  ; wieder herstellen zu können&lt;br /&gt;
&lt;br /&gt;
           mov   temp2, temp1     ; das Register temp1 frei machen&lt;br /&gt;
                                  ; abzählen wieviele Hunderter&lt;br /&gt;
                                  ; in der Zahl enthalten sind&lt;br /&gt;
;** Hunderter ** &lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1     ; temp1 mit ASCII &#039;0&#039;-1 vorladen&lt;br /&gt;
lcd_number_1:&lt;br /&gt;
           inc   temp1            ; ASCII erhöhen (somit ist nach dem ersten&lt;br /&gt;
                                  ; Durchlauf eine &#039;0&#039; in temp1)&lt;br /&gt;
           subi  temp2, 100       ; 100 abziehen&lt;br /&gt;
           brcc  lcd_number_1     ; ist dadurch kein Unterlauf entstanden?&lt;br /&gt;
                                  ; nein, dann zurück zu lcd_number_1&lt;br /&gt;
           subi  temp2, -100      ; 100 wieder dazuzählen, da die&lt;br /&gt;
                                  ; vorherhgehende Schleife 100 zuviel&lt;br /&gt;
                                  ; abgezogen hat&lt;br /&gt;
           rcall lcd_data         ; die Hunderterstelle ausgeben&lt;br /&gt;
&lt;br /&gt;
;** Zehner  **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1     ; temp1 mit ASCII &#039;0&#039;-1 vorladen&lt;br /&gt;
lcd_number_2:&lt;br /&gt;
           inc   temp1            ; ASCII erhöhen (somit ist nach dem ersten&lt;br /&gt;
                                  ; Durchlauf eine &#039;0&#039; in temp1)&lt;br /&gt;
           subi  temp2, 10        ; 10 abziehen&lt;br /&gt;
           brcc  lcd_number_2     ; ist dadurch kein Unterlauf enstanden?&lt;br /&gt;
                                  ; nein, dann zurück zu lcd_number_2&lt;br /&gt;
           subi  temp2, -10       ; 10 wieder dazuzählen, da die&lt;br /&gt;
                                  ; vorherhgehende Schleife 10 zuviel&lt;br /&gt;
                                  ; abgezogen hat&lt;br /&gt;
           rcall lcd_data         ; die Zehnerstelle ausgeben&lt;br /&gt;
 &lt;br /&gt;
;** Einer **        &lt;br /&gt;
           ldi   temp1, &#039;0&#039;       ; die Zahl in temp2 ist jetzt im Bereich&lt;br /&gt;
           add   temp1, temp2     ; 0 bis 9. Einfach nur den ASCII Code für&lt;br /&gt;
           rcall lcd_data         ; &#039;0&#039; dazu addieren und wir erhalten dierekt&lt;br /&gt;
                                  ; den ASCII Code für die Ziffer&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
           pop   temp2            ; den gesicherten Inhalt von temp2 und temp1&lt;br /&gt;
           pop   temp1            ; wieder herstellen&lt;br /&gt;
           ret                    ; und zurück&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Beachte: Diese Funktion benutzt wiederrum die Funktion &#039;&#039;&#039;lcd_data&#039;&#039;&#039;. Anders als bei den bisherigen Aufrufen ist &#039;&#039;&#039;lcd_number&#039;&#039;&#039; aber darauf angewiesen, dass &#039;&#039;&#039;lcd_data&#039;&#039;&#039; das Register &#039;&#039;&#039;temp2&#039;&#039;&#039; unangetastet lässt. Falls sie es noch nicht getan haben, dann ist das jetzt die perfekte Gelegenheit, &#039;&#039;&#039;lcd_data&#039;&#039;&#039; mit den entsprechenden &#039;&#039;&#039;push&#039;&#039;&#039; und &#039;&#039;&#039;pop&#039;&#039;&#039; Befehlen zu versehen. Sie sollten dies unbedingt zur Übung selbst machen. Am Ende muß die Funktion dann wie diese hier aussehen:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
 ;sendet ein Datenbyte an das LCD&lt;br /&gt;
lcd_data:&lt;br /&gt;
           push  temp2&lt;br /&gt;
           mov   temp2, temp1           ; &amp;quot;Sicherungskopie&amp;quot; für&lt;br /&gt;
                                        ; die Übertragung des 2.Nibbles&lt;br /&gt;
           swap  temp1                  ; Vertauschen&lt;br /&gt;
           andi  temp1, 0b00001111      ; oberes Nibble auf Null setzen&lt;br /&gt;
           sbr   temp1, 1&amp;lt;&amp;lt;PIN_RS       ; entspricht 0b00010000&lt;br /&gt;
           out   LCD_PORT, temp1        ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
                                        ; 2. Nibble, kein swap da es schon&lt;br /&gt;
                                        ; an der richtigen stelle ist&lt;br /&gt;
           andi  temp2, 0b00001111      ; obere Hälfte auf Null setzen &lt;br /&gt;
           sbr   temp2, 1&amp;lt;&amp;lt;PIN_RS       ; entspricht 0b00010000&lt;br /&gt;
           out   LCD_PORT, temp2        ; ausgeben&lt;br /&gt;
           rcall lcd_enable             ; Enable-Routine aufrufen&lt;br /&gt;
           rcall delay50us              ; Delay-Routine aufrufen&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           ret                          ; zurück zum Hauptprogramm&lt;br /&gt;
 &lt;br /&gt;
 ; sendet einen Befehl an das LCD&lt;br /&gt;
lcd_command:                            ; wie lcd_data, nur ohne RS zu setzen&lt;br /&gt;
           push  temp2&lt;br /&gt;
           mov   temp2, temp1&lt;br /&gt;
           swap  temp1&lt;br /&gt;
           andi  temp1, 0b00001111&lt;br /&gt;
           out   LCD_PORT, temp1&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           andi  temp2, 0b00001111&lt;br /&gt;
           out   LCD_PORT, temp2&lt;br /&gt;
           rcall lcd_enable&lt;br /&gt;
           rcall delay50us&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kurz zur Funktionsweise der Funktion &#039;&#039;&#039;lcd_number&#039;&#039;&#039;: Die Zahl in einem Register bewegt sich im Wertebereich 0 bis 255. Um herauszufinden, wie die Hunderterstelle lautet, zieht die Funktion einfach in einer Schleife immer wieder 100 von der Schleife ab, bis bei der Subtraktion ein Unterlauf, angezeigt durch das Setzen des Carry-Bits bei der Subtraktion, entsteht. Die Anzahl wird im Register &#039;&#039;&#039;temp1&#039;&#039;&#039; mitgezählt. Da dieses Register mit dem ASCII Code von &#039;0&#039; initialisiert wurde, und dieser ASCII Code bei jedem Schleifendurchlauf um 1 erhöht wird, können wir das Register &#039;&#039;&#039;temp1&#039;&#039;&#039; direkt zur Ausgabe des Zeichens für die Hunderterstelle durch die Funktion &#039;&#039;&#039;lcd_data&#039;&#039;&#039; benutzen. Völlig analog funktioniert auch die Ausgabe der Zehnerstelle.&lt;br /&gt;
&lt;br /&gt;
===Unterdrückung von führenden Nullen===&lt;br /&gt;
&lt;br /&gt;
Diese Funktion gibt jede Zahl im Register &#039;&#039;&#039;temp1&#039;&#039;&#039; immer mit 3 Stellen aus. Führende Nullen werden nicht unterdrückt. Möchte man dies ändern, so ist das ganz leicht möglich: Vor Ausgabe der Hunderterstelle muss lediglich überprüft werden, ob die Entsprechende Ausgabe eine &#039;0&#039; wäre. Ist sie das, so wird die Ausgabe übersprungen. Ist es allerdings eine Zahl 1..9, so muss sie der Zehner Stelle signalisieren, daß eine Prüfung auf eine &#039;0&#039; nicht stattfinden darf. Und dazu wird das T-Flag im SREG genutzt. Lediglich in der Einerstelle wird jede Ziffer wie errechnet ausgegeben.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           ...&lt;br /&gt;
                                  ; die Hunderterstelle ausgeben, wenn&lt;br /&gt;
                                  ; sie nicht &#039;0&#039; ist&lt;br /&gt;
           clt                    ; T-Flag löschen&lt;br /&gt;
           cpi   temp1, &#039;0&#039;&lt;br /&gt;
           breq  lcd_number_1a&lt;br /&gt;
           rcall lcd_data         ; die Hunderterstelle ausgeben&lt;br /&gt;
           set                    ; T-Flag im SREG setzen da 100er Stelle eine&lt;br /&gt;
                                  ; 1..9 war&lt;br /&gt;
&lt;br /&gt;
lcd_number_1a:&lt;br /&gt;
           ...&lt;br /&gt;
&lt;br /&gt;
           ...&lt;br /&gt;
           brts  lcd_number_2a    ; Test auf &#039;0&#039; überspringen, da 100er eine&lt;br /&gt;
                                  ; 1..9 war (unbedingt anzeigen&lt;br /&gt;
                                  ; auch wenn der Zehner eine &#039;0&#039; ist)&lt;br /&gt;
           cpi   temp1, &#039;0&#039;       ; ansonsten Test auf &#039;0&#039;&lt;br /&gt;
           breq  lcd_number_2b&lt;br /&gt;
lcd_number_2a:        &lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
lcd_number_2b:&lt;br /&gt;
           ...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Das Verfahren, die einzelnen Stellen durch Subtraktion zu bestimmen, ist bei kleinen Zahlen eine durchaus gängige Alternative. Vor allem dann, wenn keine hardwaremäßige Unterstützung für Multiplikation und Division zur Verfügung steht. Ansonsten könnte man die die einzelnen Ziffern auch durch Division bestimmen. Das Prinzip ist folgendes (beispielhaft an der Zahl 52783 gezeigt)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   52783 / 10          -&amp;gt; 5278&lt;br /&gt;
   52783 - 5278 * 10   -&amp;gt;          3&lt;br /&gt;
&lt;br /&gt;
   5278 / 10           -&amp;gt; 527&lt;br /&gt;
   5278 - 527 * 10     -&amp;gt;          8&lt;br /&gt;
&lt;br /&gt;
   527 / 10            -&amp;gt; 52&lt;br /&gt;
   527 - 52 * 10       -&amp;gt;          7&lt;br /&gt;
&lt;br /&gt;
   52 / 10             -&amp;gt; 5&lt;br /&gt;
   52 - 5 * 10         -&amp;gt;          2&lt;br /&gt;
&lt;br /&gt;
   5 / 10              -&amp;gt; 0&lt;br /&gt;
   5 - 0 * 10          -&amp;gt;          5&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Prinzip ist also die Restbildung bei einer fortgesetzten Division durch 10, wobei die einzelnen Ziffern in umgekehrter Reihenfolge ihrer Wertigkeit entstehen. Dadurch hat man aber ein Problem: Damit die Zeichen in der richtigen Reihenfolge ausgegeben werden können, muß man sie meistens zwischenspeichern um sie in der richtigen Reihenfole ausgeben zu können. Wird die Zahl in einem Feld von immer gleicher Größe ausgegeben, dann kann man auch die Zahl von rechts nach links ausgeben (bei einem LCD ist das möglich).&lt;br /&gt;
&lt;br /&gt;
===Hexadezimal ausgeben===&lt;br /&gt;
&lt;br /&gt;
Zu guter letzt hier noch eine Funktion, die eine Zahl aus dem Register &#039;&#039;&#039;temp1&#039;&#039;&#039; in hexadezimaler Form ausgibt. Die Funktion weist keine Besonderheiten auf und sollte unmittelbar verständlich sein.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 8 Bit Zahl ohne Vorzeichen hexadezimal ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_number_hex:&lt;br /&gt;
           swap  temp1&lt;br /&gt;
           rcall lcd_number_hex_digit&lt;br /&gt;
           swap  temp1&lt;br /&gt;
&lt;br /&gt;
lcd_number_hex_digit:&lt;br /&gt;
           push  temp1&lt;br /&gt;
&lt;br /&gt;
           andi  temp1, $0F&lt;br /&gt;
           cpi   temp1, 10&lt;br /&gt;
           brlt  lcd_number_hex_digit_1&lt;br /&gt;
           subi  temp1, -( &#039;A&#039; - &#039;9&#039; - 1 ) ; es wird subi mit negativer&lt;br /&gt;
                                           ; Konstante verwendet,&lt;br /&gt;
                                           ; weil es kein addi gibt&lt;br /&gt;
lcd_number_hex_digit_1:&lt;br /&gt;
           subi  temp1, -&#039;0&#039;               ; ditto&lt;br /&gt;
           rcall  lcd_data&lt;br /&gt;
           &lt;br /&gt;
           pop   temp1&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Binär ausgeben===&lt;br /&gt;
Um die Sache komplett zu machen; Hier eine Routine mit der man eine 8 Bit-Zahl binär auf das LC-Display ausgeben kann:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 8 Bit Zahl ohne Vorzeichen binär ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
&lt;br /&gt;
; eine Zahl aus dem Register temp1 binär ausgeben&lt;br /&gt;
lcd_number_bit:&lt;br /&gt;
	   push temp1		  ; temp1 gesichert&lt;br /&gt;
           push temp2&lt;br /&gt;
	   push temp3&lt;br /&gt;
&lt;br /&gt;
	   mov temp2, temp1;&lt;br /&gt;
&lt;br /&gt;
	   ldi temp3, 8;      ; 8 Bits werden ausgelesen&lt;br /&gt;
lcd_number_loop:           &lt;br /&gt;
	   dec temp3;&lt;br /&gt;
	   rol temp2;         ; Datenbits ins Carry geschoben ...&lt;br /&gt;
	   brcc lcd_number_bit_carryset_0; &lt;br /&gt;
	   brcs lcd_number_bit_carryset_1;&lt;br /&gt;
           rjmp lcd_number_loop;&lt;br /&gt;
&lt;br /&gt;
lcd_number_bit_carryset_0:	 &lt;br /&gt;
	   ldi temp1, &#039;0&#039;     ; Bit low ausgeben&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
	   tst temp3;&lt;br /&gt;
	   breq lcd_number_ende;&lt;br /&gt;
	   rjmp lcd_number_loop;&lt;br /&gt;
&lt;br /&gt;
lcd_number_bit_carryset_1:&lt;br /&gt;
           ldi temp1, &#039;1&#039;     ; Bit high ausgeben&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           tst temp3;&lt;br /&gt;
	   breq lcd_number_ende;&lt;br /&gt;
	   rjmp lcd_number_loop;&lt;br /&gt;
&lt;br /&gt;
lcd_number_ende:&lt;br /&gt;
	   pop temp3&lt;br /&gt;
	   pop temp2&lt;br /&gt;
	   pop temp1&lt;br /&gt;
	   ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Eine 16-Bit Zahl aus einem Registerpärchen ausgeben===&lt;br /&gt;
&lt;br /&gt;
Um eine 16 Bit Zahl auszugeben wird wieder das bewährte Schema benutzt die einzelnen Stellen durch Subtraktion abzuzählen. Da es sich hierbei allerdings um eine 16 Bit Zahl handelt, müssen die Subtraktionen als 16-Bit Arithmetik ausgeführt werden.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Eine 16 Bit Zahl ohne Vorzeichen ausgeben&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            Zahl im Register temp2 (low Byte) / temp3 (high Byte)&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_number16:&lt;br /&gt;
           push  temp1&lt;br /&gt;
           push  temp2&lt;br /&gt;
           push  temp3&lt;br /&gt;
&lt;br /&gt;
; ** Zehntausender **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number1:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, low(10000)&lt;br /&gt;
           sbci  temp3, high(10000)&lt;br /&gt;
           brcc  lcd_number1&lt;br /&gt;
           subi  temp2, low(-10000)&lt;br /&gt;
           sbci  temp3, high(-10000)&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Tausender **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number2:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, low(1000)&lt;br /&gt;
           sbci  temp3, high(1000)&lt;br /&gt;
           brcc  lcd_number2&lt;br /&gt;
           subi  temp2, low(-1000)&lt;br /&gt;
           sbci  temp3, high(-1000)&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Hunderter **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number3:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, low(100)&lt;br /&gt;
           sbci  temp3, high(100)&lt;br /&gt;
           brcc  lcd_number3&lt;br /&gt;
           subi  temp2, -100             ; + 100 High-Byte nicht mehr erforderlich&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Zehner **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;-1&lt;br /&gt;
lcd_number4:&lt;br /&gt;
           inc   temp1&lt;br /&gt;
           subi  temp2, 10&lt;br /&gt;
           brcc  lcd_number4&lt;br /&gt;
           subi  temp2, -10&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Einer **&lt;br /&gt;
           ldi   temp1, &#039;0&#039;&lt;br /&gt;
           add   temp1, temp2&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
&lt;br /&gt;
; ** Stack aufräumen **&lt;br /&gt;
           pop   temp3&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           pop   temp1&lt;br /&gt;
&lt;br /&gt;
           ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Eine BCD Zahl ausgeben===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            BCD Zahl in temp1&lt;br /&gt;
; veränderte Register: keine&lt;br /&gt;
;&lt;br /&gt;
lcd_bcd:&lt;br /&gt;
           push  temp2&lt;br /&gt;
          &lt;br /&gt;
           mov   temp2, temp1           ; temp1 sichern&lt;br /&gt;
           swap  temp1                  ; oberes mit unterem Nibble tauschen&lt;br /&gt;
           andi  temp1, 0b00001111      ; und &amp;quot;oberes&amp;quot; ausmaskieren&lt;br /&gt;
           subi  temp1, -0x30           ; in ASCII umrechnen&lt;br /&gt;
           rcall lcd_data               ; und ausgeben&lt;br /&gt;
           mov   temp1, temp2           ; ... danach unteres&lt;br /&gt;
           andi  temp1, 0b00001111&lt;br /&gt;
           subi  temp1, -0x30&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           mov   temp1, temp2           ; temp1 rekonstruieren&lt;br /&gt;
&lt;br /&gt;
           pop   temp2&lt;br /&gt;
           ret &lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Benutzerdefinierte Zeichen ==&lt;br /&gt;
[[Bild:LCD_Character_Grid.png | framed | right| Zeichenraster für 1 Zeichen]]&lt;br /&gt;
&lt;br /&gt;
Das LCD erlaubt für spezielle Zeichen, welche sich nicht im Zeichensatz finden, eigene Zeichen zu definieren. Dazu werden die ersten 8 ASCII Codes reserviert, auf denen sich laut ASCII Tabelle spezielle Steuerzeichen befinden, die normalerweise keine sichtbare Anzeige hervorrufen sondern zur Steuerung von angeschlossenen Geräten dienen. Da diese Zeichen auf einem LCD keine Rolle spielen, können diese Zeichen benutzt werden um sich selbst Sonderzeichen zu erzeugen, die für die jeweilige Anwendung massgeschneidert sind.&lt;br /&gt;
&lt;br /&gt;
Das LCD stellt für jedes Zeichen eine 8*5 Matrix zur Verfügung. Um sich selbst massgeschneiderte Zeichen zu erstellen, ist es am einfachsten sich zunächst auf einem Stück karriertem Papier zu erstellen.&lt;br /&gt;
&lt;br /&gt;
[[Bild:BellCharacter.png | framed | right| Zeichenraster für ein Glockensymbol]]&lt;br /&gt;
&lt;br /&gt;
In diesem Raster markiert man sich dann diejenigen Pixel, die im fertigen Zeichen dunkel erscheinen sollen. Als Beispiel sei hier ein Glockensymbol gezeichnet, welches in einer Telefonapplikation zb als Kennzeichnung für einen Anruf dienen könnte.&lt;br /&gt;
&lt;br /&gt;
Eine Reihe in diesem Zeichen repräsentiert ein an das LCD zu übergebende Byte, wobei nur die letzten 5 Bits relevant sind. Gesetzte Pixel stellen ein 1 Bit dar, nicht gesetzte Pixel sind ein 0-Bit. Das niedrigstwertige Bit einer Reihe befindet sich rechts. Auf diese Art wird jede Spalte in eine Binärzahl übersetzt, und 8 Bytes repräsentieren ein komplettes Zeichen. Am Beispiel des Glockensymboles: Die 8 Bytes, welches das Symbol repräsentiern, lauten: 0x04, 0x0A, 0x0A, 0x0A, 0x1F, 0x04, 0x00, 0x00&lt;br /&gt;
&lt;br /&gt;
Dem LCD wird die neue Definition übertragen, indem man dem LCD die &#039;Schreibposition&#039; mittels des Kommandos &#039;&#039;Character RAM Address Set&#039;&#039; in den Zeichensatzgenerator verschiebt. Danach werden die 8 Bytes ganz normal als Daten ausgegeben, die das LCD damit in seine Zeichensatztabelle schreibt.&lt;br /&gt;
&lt;br /&gt;
Durch die Wahl der Speicheradresse definiert man, welches Zeichen (0 bis 7) man eigentlich durch eine eigene Definition ersetzen will.&lt;br /&gt;
{| {{Tabelle}}&lt;br /&gt;
|-  style=&amp;quot;background-color:#ffddcc&amp;quot;&lt;br /&gt;
! ASCII Code || Zeichensatzadresse&lt;br /&gt;
|-&lt;br /&gt;
| 0 || 0x00&lt;br /&gt;
|-&lt;br /&gt;
| 1 || 0x08&lt;br /&gt;
|-&lt;br /&gt;
| 2 || 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 3 || 0x18&lt;br /&gt;
|-&lt;br /&gt;
| 4 || 0x20&lt;br /&gt;
|-&lt;br /&gt;
| 5 || 0x28&lt;br /&gt;
|-&lt;br /&gt;
| 6 || 0x30&lt;br /&gt;
|-&lt;br /&gt;
| 7 || 0x38&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Nach erfolgter Definition des Zeichens, muss die Schreibposition wieder explizit in den DDRAM-Bereich gesetzt werden.&lt;br /&gt;
Danach kann ein entsprechendes Zeichen mit dem definierten ASCII Code ausgegeben werden, wobei das LCD die von uns definierte Pixelform zur Anzeige benutzt.&lt;br /&gt;
&lt;br /&gt;
Zuerst müssen natürlich erstmal die Zeichen definiert werden.&lt;br /&gt;
Dieses geschieht einmalig durch den Aufruf der Routine &amp;quot;lcd_load_user_chars&amp;quot;&lt;br /&gt;
unmittelbar nach der Initialisierung des LCD-Displays.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
           rcall lcd_init              ; Display initialisieren&lt;br /&gt;
           rcall lcd_load_user_chars   ; User Zeichen in das Display laden&lt;br /&gt;
           rcall lcd_clear             ; Display löschen&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Durch diesen Aufruf werden die im Flash definierten Zeichen in den&lt;br /&gt;
GC-Ram übertragen. Diese Zeichen werden ab Adresse 0 im GC-Ram&lt;br /&gt;
gespeichert und sind danach wie jedes andere Zeichen nutzbar.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
           ldi   temp1, 0              ; Ausgabe des User-Char &amp;quot;A&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 6              ; Ausgabe des User-Char &amp;quot;G&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 5              ; Ausgabe des User-Char &amp;quot;E&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 4              ; Ausgabe des User-Char &amp;quot;M&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 3              ; Ausgabe des User-Char &amp;quot;-&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 2              ; Ausgabe des User-Char &amp;quot;R&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 1              ; Ausgabe des User-Char &amp;quot;V&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           ldi   temp1, 0              ; Ausgabe des User-Char &amp;quot;A&amp;quot;&lt;br /&gt;
           rcall lcd_data&lt;br /&gt;
           .&lt;br /&gt;
           .&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Jetzt sollte der Schriftzug &amp;quot;AVR-MEGA&amp;quot;&lt;br /&gt;
verkehrt herum (180 Grad gedreht) erscheinen.&lt;br /&gt;
&lt;br /&gt;
Es fehlt natürlich noch die Laderoutine:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;**********************************************************************&lt;br /&gt;
;&lt;br /&gt;
; Lädt User Zeichen in den GC-Ram des LCD bis Tabellenende (0xFF)&lt;br /&gt;
; gelesen wird. (max. 8 Zeichen können geladen werden)&lt;br /&gt;
;&lt;br /&gt;
; Übergabe:            -   &lt;br /&gt;
; veränderte Register: temp1, temp2, temp3, zh, zl&lt;br /&gt;
; Bemerkung:           ist einmalig nach lcd_init aufzurufen&lt;br /&gt;
;       &lt;br /&gt;
&lt;br /&gt;
lcd_load_user_chars:&lt;br /&gt;
        ldi    zl, LOW (ldc_user_char * 2) ; Adresse der Zeichentabelle&lt;br /&gt;
        ldi    zh, HIGH(ldc_user_char * 2) ; in den Z-Pointer laden&lt;br /&gt;
        clr    temp3                       ; aktuelles Zeichen = 0 &lt;br /&gt;
&lt;br /&gt;
lcd_load_user_chars_2:&lt;br /&gt;
        clr    temp2                       ; Linienzähler = 0&lt;br /&gt;
&lt;br /&gt;
lcd_load_user_chars_1:&lt;br /&gt;
        ldi    temp1, 0b01000000           ; Kommando:    0b01aaalll&lt;br /&gt;
        add    temp1, temp3                ; + akt. Zeichen  (aaa)&lt;br /&gt;
        add    temp1, temp2                ; + akt. Linie       (lll)&lt;br /&gt;
        rcall  lcd_command                 ; Kommando schreiben&lt;br /&gt;
&lt;br /&gt;
        lpm    temp1, Z+                   ; Zeichenline laden &lt;br /&gt;
        rcall  lcd_data                    ; ... und ausgeben&lt;br /&gt;
&lt;br /&gt;
        ldi    temp1, 0b01001000           ; Kommando:    0b01aa1lll         &lt;br /&gt;
        add    temp1, temp3                ; + akt. Zeichen  (aaa)       &lt;br /&gt;
        add    temp1, temp2                ; + akt. Linie       (lll)&lt;br /&gt;
        rcall  lcd_command&lt;br /&gt;
&lt;br /&gt;
        lpm    temp1, Z+                   ; Zeichenline laden&lt;br /&gt;
        rcall  lcd_data                    ; ... und ausgeben &lt;br /&gt;
        &lt;br /&gt;
        inc    temp2                       ; Linienzähler + 1&lt;br /&gt;
        cpi    temp2, 8                    ; 8 Linien fertig?&lt;br /&gt;
        brne   lcd_load_user_chars_1       ; nein, dann nächste Linie &lt;br /&gt;
		&lt;br /&gt;
        subi   temp3, -0x10                ; zwei Zeichen weiter (addi 0x10)&lt;br /&gt;
        lpm    temp1, Z                    ; nächste Linie laden&lt;br /&gt;
        cpi    temp1, 0xFF                 ; Tabellenende erreicht? &lt;br /&gt;
        brne   lcd_load_user_chars_2       ; nein, dann die nächsten&lt;br /&gt;
                                           ; zwei Zeichen&lt;br /&gt;
        ret&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... und die Zeichendefinition:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
ldc_user_char:&lt;br /&gt;
                              ;    Zeichen &lt;br /&gt;
                              ;   0       1&lt;br /&gt;
       .db 0b10001, 0b00100   ; @   @ ,   @&lt;br /&gt;
       .db 0b10001, 0b01010   ; @   @ ,  @ @&lt;br /&gt;
       .db 0b11111, 0b10001   ; @@@@@ , @   @&lt;br /&gt;
       .db 0b10001, 0b10001   ; @   @ , @   @&lt;br /&gt;
       .db 0b10001, 0b10001   ; @   @ , @   @&lt;br /&gt;
       .db 0b10001, 0b10001   ; @   @ , @   @&lt;br /&gt;
       .db 0b01110, 0b10001   ;  @@@  , @   @&lt;br /&gt;
       .db 0b00000, 0b00000   ;       , &lt;br /&gt;
&lt;br /&gt;
                              ;    Zeichen&lt;br /&gt;
                              ;   2       3&lt;br /&gt;
       .db 0b10001, 0b00000   ; @   @ , &lt;br /&gt;
       .db 0b01001, 0b00000   ;  @  @ , &lt;br /&gt;
       .db 0b00101, 0b00000   ;   @ @ , &lt;br /&gt;
       .db 0b11111, 0b11111   ; @@@@@ , @@@@@ &lt;br /&gt;
       .db 0b10001, 0b00000   ; @   @ , &lt;br /&gt;
       .db 0b10001, 0b00000   ; @   @ , &lt;br /&gt;
       .db 0b01111, 0b00000   ;  @@@@ , &lt;br /&gt;
       .db 0b00000, 0b00000   ;       ,  &lt;br /&gt;
&lt;br /&gt;
                              ;    Zeichen&lt;br /&gt;
                              ;   4       5&lt;br /&gt;
       .db 0b10001, 0b11111   ; @   @ , @@@@@  &lt;br /&gt;
       .db 0b10001, 0b00001   ; @   @ ,     @&lt;br /&gt;
       .db 0b10001, 0b00001   ; @   @ ,     @&lt;br /&gt;
       .db 0b10001, 0b01111   ; @   @ ,  @@@@ &lt;br /&gt;
       .db 0b10101, 0b00001   ; @ @ @ ,     @&lt;br /&gt;
       .db 0b11011, 0b00001   ; @@ @@ ,     @&lt;br /&gt;
       .db 0b10001, 0b11111   ; @   @ , @@@@@&lt;br /&gt;
       .db 0b00000, 0b00000   ;       ,  &lt;br /&gt;
&lt;br /&gt;
                              ;    Zeichen&lt;br /&gt;
                              ;   6       7&lt;br /&gt;
       .db 0b11110, 0b11111   ; @@@@  , @@@@@  &lt;br /&gt;
       .db 0b10001, 0b01010   ; @   @ ,  @ @ &lt;br /&gt;
       .db 0b10001, 0b00100   ; @   @ ,   @&lt;br /&gt;
       .db 0b11101, 0b01110   ; @@@ @ ,  @@@&lt;br /&gt;
       .db 0b00001, 0b00100   ;     @ ,   @&lt;br /&gt;
       .db 0b10001, 0b01010   ; @   @ ,  @ @&lt;br /&gt;
       .db 0b01110, 0b11111   ;  @@@  , @@@@@&lt;br /&gt;
       .db 0b00000, 0b00000   ;       ,  &lt;br /&gt;
&lt;br /&gt;
       ; End of Tab&lt;br /&gt;
       .db 0xFF, 0xFF&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Der überarbeitete, komplette Code==&lt;br /&gt;
&lt;br /&gt;
Hier also die komplett überarbeitete Version der LCD Funktionen.&lt;br /&gt;
&lt;br /&gt;
Die für die Benutzung relevanten Funktionen&lt;br /&gt;
* &#039;&#039;&#039;lcd_init&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_clear&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_home&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_data&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_command&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_flash_string&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_number&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;lcd_number_hex&#039;&#039;&#039;&lt;br /&gt;
sind so ausgeführt, dass sie kein Register (ausser dem Statusregister &#039;&#039;&#039;SREG&#039;&#039;&#039;) verändern. Die bei manchen Funktionen notwendige Argumente werden immer im Register &#039;&#039;&#039;temp1&#039;&#039;&#039; übergeben, wobei &#039;&#039;&#039;temp1&#039;&#039;&#039; vom Usercode definiert werden muss.&lt;br /&gt;
&lt;br /&gt;
[[Media:lcd-routines.asm|Download lcd-routines.asm]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
{{Navigation_zurückhochvor|&lt;br /&gt;
zurücktext=Stack|&lt;br /&gt;
zurücklink=AVR-Tutorial: Stack|&lt;br /&gt;
hochtext=Inhaltsverzeichnis|&lt;br /&gt;
hochlink=AVR-Tutorial|&lt;br /&gt;
vortext=Interrupts|&lt;br /&gt;
vorlink=AVR-Tutorial: Interrupts}}&lt;br /&gt;
&lt;br /&gt;
[[Category:AVR-Tutorial]]&lt;br /&gt;
[[Category:LCD]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=53785</id>
		<title>Datei:Sam.zip</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=53785"/>
		<updated>2010-12-26T09:53:44Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: hat eine neue Version von „Datei:Sam.zip“ hochgeladen:&amp;amp;#32;added EXITxx&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Structured Assembly Macros&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=53784</id>
		<title>Datei:Sam.zip</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=53784"/>
		<updated>2010-12-26T09:39:20Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: hat eine neue Version von „Datei:Sam.zip“ hochgeladen&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Structured Assembly Macros&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53783</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53783"/>
		<updated>2010-12-26T09:35:00Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* SAM (Structured Assembly Macros) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Strukturierte Programmierung ==&lt;br /&gt;
&lt;br /&gt;
===SAM (Structured Assembly Macros)===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
SAM unterstützt strukturiertes Programmieren durch If-Then-Else und Do-Loop Makros. Beliebige Verschachtelung und Mehrfachbedingungen sind möglich. Läuft unter aktuellen Versionen von AVRASM2.&lt;br /&gt;
&lt;br /&gt;
Einige Beispiele:&lt;br /&gt;
 &lt;br /&gt;
Verschachteltes If-Then-Else&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   r16,&#039;a&#039;&lt;br /&gt;
            ifeq  a_chr&lt;br /&gt;
              cpi   r17,&#039;b&#039;&lt;br /&gt;
              ifeq   a_and_b&lt;br /&gt;
                ;...a&amp;amp;b&lt;br /&gt;
              else   a_and_b&lt;br /&gt;
                ;...a&amp;amp;-b&lt;br /&gt;
              end    a_and_b&lt;br /&gt;
            else  a_chr&lt;br /&gt;
              cpi r17,&#039;c&#039;&lt;br /&gt;
              ifeq  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;+c&lt;br /&gt;
              else  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;c&lt;br /&gt;
              end   c_but_no_a&lt;br /&gt;
            end   a_chr&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Mehrere und/oder verknüpfte Bedingungen&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   zh,high(end_buffer)&lt;br /&gt;
            ifeq_and    end_buffer_reached&lt;br /&gt;
            cpi   zl,low(end_buffer)&lt;br /&gt;
            ifeq        end_buffer_reached&lt;br /&gt;
              ldi   zh,high(buffer)       ;wrap buffer&lt;br /&gt;
              ldi   zl,low(buffer)&lt;br /&gt;
            end         end_buffer_reached&lt;br /&gt;
            ld    r0,z+                   ;read buffer&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Das Gleiche als Do-Loop&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; reading a buffer until end&lt;br /&gt;
; similar to: for z = buffer to end_buffer&lt;br /&gt;
            ldi   zh,high(buffer)   ;init buffer pointer&lt;br /&gt;
            ldi   zl,low(buffer)&lt;br /&gt;
            do    read_buf&lt;br /&gt;
              ld    r0,z+&lt;br /&gt;
              cpi   zh,high(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
              cpi   zl,low(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehr Beispiele und das SAM-include als [[Media:sam.zip|Download.]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53781</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53781"/>
		<updated>2010-12-25T18:02:11Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* SAM (Structured Assembly Macros) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Strukturierte Programmierung ==&lt;br /&gt;
&lt;br /&gt;
===SAM (Structured Assembly Macros)===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
SAM unterstützt strukturiertes Programmieren durch If-Then-Else und Do-Loop Makros. Beliebige Verschachtelung und Mehrfachbedingungen sind möglich. Läuft unter aktuellen Versionen von AVRASM2.&lt;br /&gt;
&lt;br /&gt;
Einige Beispiele:&lt;br /&gt;
 &lt;br /&gt;
Verschachteltes If-Then-Else&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   r16,&#039;a&#039;&lt;br /&gt;
            ifeq  a_chr&lt;br /&gt;
              cpi   r17,&#039;b&#039;&lt;br /&gt;
              ifeq   a_and_b&lt;br /&gt;
                ;...a&amp;amp;b&lt;br /&gt;
              else   a_and_b&lt;br /&gt;
                ;...a&amp;amp;-b&lt;br /&gt;
              end    a_and_b&lt;br /&gt;
            else  a_chr&lt;br /&gt;
              cpi r17,&#039;c&#039;&lt;br /&gt;
              ifeq  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;+c&lt;br /&gt;
              else  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;c&lt;br /&gt;
              end   c_but_no_a&lt;br /&gt;
            end   a_chr&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Mehrere und/oder verknüpfte Bedingungen&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   zh,high(end_buffer)&lt;br /&gt;
            ifeq_and    end_buffer_reached&lt;br /&gt;
            cpi   zl,low(end_buffer)&lt;br /&gt;
            ifeq        end_buffer_reached&lt;br /&gt;
              ldi   zh,high(buffer)       ;wrap buffer&lt;br /&gt;
              ldi   zl,low(buffer)&lt;br /&gt;
            end         end_buffer_reached&lt;br /&gt;
            ld    r0,z+                   ;read buffer&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Das Gleiche als Do-Loop&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; reading a buffer until end&lt;br /&gt;
; similar to: for z = buffer to end_buffer&lt;br /&gt;
            ldi   zh,high(buffer)   ;init buffer pointer&lt;br /&gt;
            ldi   zl,low(buffer)&lt;br /&gt;
            do    read_buf&lt;br /&gt;
              ld    r0,z+&lt;br /&gt;
              cpi   zh,high(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
              cpi   zl,low(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehr Beispiele und das SAM-include als Download.&lt;br /&gt;
&lt;br /&gt;
[[Datei:sam.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53780</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53780"/>
		<updated>2010-12-25T17:54:11Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* SAM (Structured Assembly Macros) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Strukturierte Programmierung ==&lt;br /&gt;
&lt;br /&gt;
===SAM (Structured Assembly Macros)===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
SAM unterstützt strukturiertes Programmieren durch If-Then-Else und Do-Loop Makros. Beliebige Verschachtelung und Mehrfachbedingungen sind möglich.&lt;br /&gt;
&lt;br /&gt;
Einige Beispiele:&lt;br /&gt;
&lt;br /&gt;
Verschachteltes If-Then-Else&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   r16,&#039;a&#039;&lt;br /&gt;
            ifeq  a_chr&lt;br /&gt;
              cpi   r17,&#039;b&#039;&lt;br /&gt;
              ifeq   a_and_b&lt;br /&gt;
                ;...a&amp;amp;b&lt;br /&gt;
              else   a_and_b&lt;br /&gt;
                ;...a&amp;amp;-b&lt;br /&gt;
              end    a_and_b&lt;br /&gt;
            else  a_chr&lt;br /&gt;
              cpi r17,&#039;c&#039;&lt;br /&gt;
              ifeq  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;+c&lt;br /&gt;
              else  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;c&lt;br /&gt;
              end   c_but_no_a&lt;br /&gt;
            end   a_chr&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehrere und/oder verknüpfte Bedingungen&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   zh,high(end_buffer)&lt;br /&gt;
            ifeq_and    end_buffer_reached&lt;br /&gt;
            cpi   zl,low(end_buffer)&lt;br /&gt;
            ifeq        end_buffer_reached&lt;br /&gt;
              ldi   zh,high(buffer)       ;wrap buffer&lt;br /&gt;
              ldi   zl,low(buffer)&lt;br /&gt;
            end         end_buffer_reached&lt;br /&gt;
            ld    r0,z+                   ;read buffer&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Gleiche als Do-Loop&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; reading a buffer until end&lt;br /&gt;
; similar to: for z = buffer to end_buffer&lt;br /&gt;
            ldi   zh,high(buffer)   ;init buffer pointer&lt;br /&gt;
            ldi   zl,low(buffer)&lt;br /&gt;
            do    read_buf&lt;br /&gt;
              ld    r0,z+&lt;br /&gt;
              cpi   zh,high(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
              cpi   zl,low(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehr Beispiele und das SAM-include als Download:&lt;br /&gt;
[[sam.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53779</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53779"/>
		<updated>2010-12-25T17:52:07Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Strukturierte Programmierung */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Strukturierte Programmierung ==&lt;br /&gt;
&lt;br /&gt;
===SAM (Structured Assembly Macros)===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
SAM unterstützt strukturiertes Programmieren durch If-Then-Else und Do-Loop Makros. Beliebige Verschachtelung und Mehrfachbedingungen sind möglich.&lt;br /&gt;
&lt;br /&gt;
Einige Beispiele:&lt;br /&gt;
&lt;br /&gt;
Verschachteltes If-Then-Else&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   r16,&#039;a&#039;&lt;br /&gt;
            ifeq  a_chr&lt;br /&gt;
              cpi   r17,&#039;b&#039;&lt;br /&gt;
              ifeq   a_and_b&lt;br /&gt;
                ;...a&amp;amp;b&lt;br /&gt;
              else   a_and_b&lt;br /&gt;
                ;...a&amp;amp;-b&lt;br /&gt;
              end    a_and_b&lt;br /&gt;
            else  a_chr&lt;br /&gt;
              cpi r17,&#039;c&#039;&lt;br /&gt;
              ifeq  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;+c&lt;br /&gt;
              else  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;c&lt;br /&gt;
              end   c_but_no_a&lt;br /&gt;
            end   a_chr&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehrere und/oder verknüpfte Bedingungen&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   zh,high(end_buffer)&lt;br /&gt;
            ifeq_and    end_buffer_reached&lt;br /&gt;
            cpi   zl,low(end_buffer)&lt;br /&gt;
            ifeq        end_buffer_reached&lt;br /&gt;
              ldi   zh,high(buffer)       ;wrap buffer&lt;br /&gt;
              ldi   zl,low(buffer)&lt;br /&gt;
            end         end_buffer_reached&lt;br /&gt;
            ld    r0,z+                   ;read buffer&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Gleiche als Do-Loop&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; reading a buffer until end&lt;br /&gt;
; similar to: for z = buffer to end_buffer&lt;br /&gt;
            ldi   zh,high(buffer)   ;init buffer pointer&lt;br /&gt;
            ldi   zl,low(buffer)&lt;br /&gt;
            do    read_buf&lt;br /&gt;
              ld    r0,z+&lt;br /&gt;
              cpi   zh,high(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
              cpi   zl,low(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehr Beispiele und das SAM-include als Download:&lt;br /&gt;
[[Datei:sam.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53778</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53778"/>
		<updated>2010-12-25T17:50:15Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Strukturierte Programmierung */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
==== Strukturierte Programmierung ====&lt;br /&gt;
&lt;br /&gt;
===SAM (Structured Assembly Macros)===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
SAM unterstützt strukturiertes Programmieren durch If-Then-Else und Do-Loop Makros. Beliebige Verschachtelung und Mehrfachbedingungen sind möglich.&lt;br /&gt;
&lt;br /&gt;
Einige Beispiele:&lt;br /&gt;
&lt;br /&gt;
Verschachteltes If-Then-Else&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   r16,&#039;a&#039;&lt;br /&gt;
            ifeq  a_chr&lt;br /&gt;
              cpi   r17,&#039;b&#039;&lt;br /&gt;
              ifeq   a_and_b&lt;br /&gt;
                ;...a&amp;amp;b&lt;br /&gt;
              else   a_and_b&lt;br /&gt;
                ;...a&amp;amp;-b&lt;br /&gt;
              end    a_and_b&lt;br /&gt;
            else  a_chr&lt;br /&gt;
              cpi r17,&#039;c&#039;&lt;br /&gt;
              ifeq  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;+c&lt;br /&gt;
              else  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;c&lt;br /&gt;
              end   c_but_no_a&lt;br /&gt;
            end   a_chr&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehrere und/oder verknüpfte Bedingungen&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   zh,high(end_buffer)&lt;br /&gt;
            ifeq_and    end_buffer_reached&lt;br /&gt;
            cpi   zl,low(end_buffer)&lt;br /&gt;
            ifeq        end_buffer_reached&lt;br /&gt;
              ldi   zh,high(buffer)       ;wrap buffer&lt;br /&gt;
              ldi   zl,low(buffer)&lt;br /&gt;
            end         end_buffer_reached&lt;br /&gt;
            ld    r0,z+                   ;read buffer&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Gleiche als Do-Loop&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; reading a buffer until end&lt;br /&gt;
; similar to: for z = buffer to end_buffer&lt;br /&gt;
            ldi   zh,high(buffer)   ;init buffer pointer&lt;br /&gt;
            ldi   zl,low(buffer)&lt;br /&gt;
            do    read_buf&lt;br /&gt;
              ld    r0,z+&lt;br /&gt;
              cpi   zh,high(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
              cpi   zl,low(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehr Beispiele und das SAM-include als Download:&lt;br /&gt;
[[Datei:sam.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53777</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=53777"/>
		<updated>2010-12-25T17:48:01Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Verzögerung um X Nanosekunden */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
====Strukturierte Programmierung====&lt;br /&gt;
===SAM (Structured Assembly Macros)===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
SAM unterstützt strukturiertes Programmieren durch If-Then-Else und Do-Loop Makros. Beliebige Verschachtelung und Mehrfachbedingungen sind möglich.&lt;br /&gt;
&lt;br /&gt;
Einige Beispiele:&lt;br /&gt;
&lt;br /&gt;
Verschachteltes If-Then-Else&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   r16,&#039;a&#039;&lt;br /&gt;
            ifeq  a_chr&lt;br /&gt;
              cpi   r17,&#039;b&#039;&lt;br /&gt;
              ifeq   a_and_b&lt;br /&gt;
                ;...a&amp;amp;b&lt;br /&gt;
              else   a_and_b&lt;br /&gt;
                ;...a&amp;amp;-b&lt;br /&gt;
              end    a_and_b&lt;br /&gt;
            else  a_chr&lt;br /&gt;
              cpi r17,&#039;c&#039;&lt;br /&gt;
              ifeq  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;+c&lt;br /&gt;
              else  c_but_no_a&lt;br /&gt;
                ;...-a&amp;amp;c&lt;br /&gt;
              end   c_but_no_a&lt;br /&gt;
            end   a_chr&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehrere und/oder verknüpfte Bedingungen&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            cpi   zh,high(end_buffer)&lt;br /&gt;
            ifeq_and    end_buffer_reached&lt;br /&gt;
            cpi   zl,low(end_buffer)&lt;br /&gt;
            ifeq        end_buffer_reached&lt;br /&gt;
              ldi   zh,high(buffer)       ;wrap buffer&lt;br /&gt;
              ldi   zl,low(buffer)&lt;br /&gt;
            end         end_buffer_reached&lt;br /&gt;
            ld    r0,z+                   ;read buffer&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Gleiche als Do-Loop&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; reading a buffer until end&lt;br /&gt;
; similar to: for z = buffer to end_buffer&lt;br /&gt;
            ldi   zh,high(buffer)   ;init buffer pointer&lt;br /&gt;
            ldi   zl,low(buffer)&lt;br /&gt;
            do    read_buf&lt;br /&gt;
              ld    r0,z+&lt;br /&gt;
              cpi   zh,high(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
              cpi   zl,low(end_buffer)&lt;br /&gt;
            loopne read_buf&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mehr Beispiele und das SAM-include als Download:&lt;br /&gt;
[[Datei:sam.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Hilfe:Bearbeitungshilfe&amp;diff=53776</id>
		<title>Hilfe:Bearbeitungshilfe</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Hilfe:Bearbeitungshilfe&amp;diff=53776"/>
		<updated>2010-12-25T17:37:04Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Datei/Bild hochladen und einfügen */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Umfangreiche Hilfe ==&lt;br /&gt;
Umfangreiche Hilfe befindet sich auch hier: &amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;[http://de.wikipedia.org/wiki/Hilfe:Bearbeiten de.Wikipedia.org/wiki/Hilfe:Bearbeiten]&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
== Überschriften ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
== Überschrift ==&lt;br /&gt;
=== Unter-Überschrift ===&lt;br /&gt;
==== Unter-Unter-Überschrift ====&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Formatierung ==&lt;br /&gt;
Eine ausführlichere Beschreibung der Gliederungs- und Formatierungsmöglichkeiten findet sich im Wikipedia Online-Lexikon: http://de.wikipedia.org/wiki/Hilfe:Bearbeitungshilfe&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;0&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;kursiv&#039;&#039;&lt;br /&gt;
|&amp;lt;nowiki&amp;gt;&#039;&#039;kursiv&#039;&#039;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;fett&#039;&#039;&#039;&lt;br /&gt;
|&amp;lt;nowiki&amp;gt;&#039;&#039;&#039;fett&#039;&#039;&#039;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;u&amp;gt;unterstrichen&amp;lt;/u&amp;gt;&lt;br /&gt;
|&amp;lt;nowiki&amp;gt;&amp;lt;u&amp;gt;unterstrichen&amp;lt;/u&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;sup&amp;gt;hoch&amp;lt;/sup&amp;gt;gestellt&lt;br /&gt;
|&amp;lt;nowiki&amp;gt;&amp;lt;sup&amp;gt;hoch&amp;lt;/sup&amp;gt;gestellt&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;sub&amp;gt;tief&amp;lt;/sub&amp;gt;gestellt&lt;br /&gt;
|&amp;lt;nowiki&amp;gt;&amp;lt;sub&amp;gt;tief&amp;lt;/sub&amp;gt;gestellt&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Einbinden von Sourcecode ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;c&amp;gt;C-Code&amp;lt;/c&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Beispiel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
int main(void) {&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;avrasm&amp;gt;AVR Assembler-Code&amp;lt;/avrasm&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Beispiel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
; Mach fast nix!&lt;br /&gt;
.org 0x0000&lt;br /&gt;
Start:&lt;br /&gt;
  LDI r16, 42&lt;br /&gt;
  RJMP Start&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;vhdl&amp;gt;VHDL-Code&amp;lt;/vhdl&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vhdl&amp;gt;&lt;br /&gt;
-- Kommentar in VHDL&lt;br /&gt;
process(clk)&lt;br /&gt;
begin&lt;br /&gt;
  if rising_dedge(clk) then&lt;br /&gt;
    data &amp;lt;= data_in;&lt;br /&gt;
  end if; &lt;br /&gt;
end process;&lt;br /&gt;
&amp;lt;/vhdl&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;pre&amp;gt;Sonstige Texte mmit fester Formatierung&amp;lt;/pre&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Hallo Welt!&lt;br /&gt;
    Leerzeichen bleiben, feste Zeichenbreite.&lt;br /&gt;
    Geht auch für ASCII-Art&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Einfügen von Bildern oder Dateien ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Hierfür ist [[Spezial:Anmelden|Anmeldung]] erforderlich!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Bild erstellen ===&lt;br /&gt;
* Fotos müssen nicht von Hand verkleinert werden, sondern können bis 2 MB groß hochgeladen werden.&lt;br /&gt;
* Falls mit einem Vektorzeichenprogramm (z.B. Inkscape) gearbeitet wird, bitte das SVG-Format verwenden (erlaubt nachträgliche Bearbeitung durch andere!)&lt;br /&gt;
* Für Zeichnungen/Schaltpläne ist ansonsten das PNG-Format am besten geeignet, JPEG nur für Fotos/Scans verwenden, siehe [[Bildformate]].&lt;br /&gt;
&lt;br /&gt;
=== Datei/Bild hochladen und einfügen ===&lt;br /&gt;
# Dort, wo das Bild erscheinen (oder die Datei verlinkt werden) soll, den Code &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;[[Bild:AVR-Programmer.png]]&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; in den Text einfügen (beliebiger Dateiname, aber mit richtiger Endung!).&lt;br /&gt;
# In der Vorschau oder der gespeicherten Seite auf den Platzhalter-Link &amp;quot;Bild:AVR-Programmer.png&amp;quot; klicken. Vorsicht, nicht gespeicherte Änderungen gehen verloren!&lt;br /&gt;
# Datei auswählen, ggf. Beschreibung eingeben und hochladen (wird automatisch umbenannt).&lt;br /&gt;
&lt;br /&gt;
=== Größe festlegen ===&lt;br /&gt;
&lt;br /&gt;
Folgender Code skaliert das Bild automatisch auf 200 Pixel Breite: &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;[[Bild:AVR-Programmer.png|&amp;lt;/nowiki&amp;gt;&#039;&#039;&#039;200px&#039;&#039;&#039;&amp;lt;nowiki&amp;gt;]]&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tabellen ==&lt;br /&gt;
&lt;br /&gt;
Für eine ausführlichere Liste und Beschreibung möglicher Features sei auf [http://de.wikipedia.org/wiki/Hilfe:Tabellen Wikipedia Hilfe:Tabellen] und [http://de.wikipedia.org/wiki/Hilfe:Tabellen-Referenz Wikipedia Hilfe:Tabellen-Referenz] verwiesen.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ &#039;&#039;&#039;Tabellen-Überschrift&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
! Überschrift links || Überschrift rechts&lt;br /&gt;
|-&lt;br /&gt;
| links oben  || rechts oben&lt;br /&gt;
|-&lt;br /&gt;
| links mitte || rechts mitte&lt;br /&gt;
|-&lt;br /&gt;
| links unten || rechts unten&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ &#039;&#039;&#039;Tabellen-Überschrift&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
! Überschrift links || Überschrift rechts&lt;br /&gt;
|-&lt;br /&gt;
| links oben  || rechts oben&lt;br /&gt;
|-&lt;br /&gt;
| links mitte || rechts mitte&lt;br /&gt;
|-&lt;br /&gt;
| links unten || rechts unten&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Um sortierbare Tabellen zu erhalten fügt man statt &amp;lt;tt&amp;gt; class=&amp;quot;wikitable&amp;quot; &amp;lt;/tt&amp;gt; folgendes ein: &amp;lt;tt&amp;gt; class=&amp;quot;wikitable sortable&amp;quot; id=&amp;quot;tabellexyz&amp;quot; &amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Richtlinien ==&lt;br /&gt;
&lt;br /&gt;
=== Links zu verwandten Artikeln und externen Seiten ===&lt;br /&gt;
&lt;br /&gt;
Für &#039;&#039;&#039;interne&#039;&#039;&#039; Links zu verwandten Artikeln legt man einen Bereich nach dem folgenden Muster an:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
== Siehe auch ==&lt;br /&gt;
* [[Test]]&lt;br /&gt;
* [[Foobar]]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ähnlich bei &#039;&#039;&#039;externen&#039;&#039;&#039; Links:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
== Weblinks ==&lt;br /&gt;
* http://url1/&lt;br /&gt;
* http://url2/&lt;br /&gt;
* [http://url3/ Seite 3 hat einen Linktext]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Link für Nachricht an eine Person/Autor ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;[http://www.mikrocontroller.net/user/show/Username_aus_Forum Alternativer Text]&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[http://www.mikrocontroller.net/user/show/andreas Alternativer Text]&lt;br /&gt;
&lt;br /&gt;
[[Kategorie:Tipps für Autoren]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=53775</id>
		<title>Datei:Sam.zip</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:Sam.zip&amp;diff=53775"/>
		<updated>2010-12-25T17:28:27Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: Structured Assembly Macros&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Structured Assembly Macros&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=50172</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=50172"/>
		<updated>2010-08-27T11:59:10Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Informationen im Web */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.&amp;amp;nbsp;B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
* [[Media:keyboard_switch.zip|(Assembler Source, HEX-File, dieses Dokument als PDF)]]&lt;br /&gt;
&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
* [http://www.computer-engineering.org PS/2 Keyboard Protocol]&lt;br /&gt;
* [http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;br /&gt;
&lt;br /&gt;
[[Kategorie:AVR-Projekte]]&lt;br /&gt;
[[Category:Datenübertragung]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=49108</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=49108"/>
		<updated>2010-07-10T16:25:15Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
[[Bild:I2C-slave_USART.png]]&lt;br /&gt;
&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Baudrate, I2C-Adresse und Einstellungen zur Flusskontrolle haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
&lt;br /&gt;
[[Media:I2C-slave_USART.zip|I2C-slave_USART.zip]]&lt;br /&gt;
&lt;br /&gt;
==Zukünftige Erweiterungen==&lt;br /&gt;
&lt;br /&gt;
10-bit Adressierung&lt;br /&gt;
&lt;br /&gt;
Zusätzliche gemeinsame Adresse (zusammen mit anderen Slaves) zum Abfragen der Daten nach Aktivierung des -Int Signals. Dabei antworten zunächst alle Slaves, die das -Int Signal aktiviert haben, mit ihrer eigenen Adresse. Durch Slave-Bus-Arbitration gewinnt der Slave mit der niedrigsten Adresse. Danach überträgt dieser Slave seine Daten.&lt;br /&gt;
&lt;br /&gt;
[[Kategorie:AVR-Projekte]]&lt;br /&gt;
[[Kategorie:I2C]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42990</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42990"/>
		<updated>2010-02-08T11:33:16Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
[[Bild:I2C-slave_USART.png]]&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Baudrate, I2C-Adresse und Einstellungen zur Flusskontrolle haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
&lt;br /&gt;
[[Media:I2C-slave_USART.zip|I2C-slave_USART.zip]]&lt;br /&gt;
&lt;br /&gt;
[[Kategorie:AVR-Projekte]]&lt;br /&gt;
[[Kategorie:I2C]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42989</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42989"/>
		<updated>2010-02-08T11:25:27Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Konfiguration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
[[Bild:I2C-slave_USART.png]]&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Baudrate, I2C-Adresse und Einstellungen zur Flusskontrolle haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
&lt;br /&gt;
[[Media:I2C-slave_USART.zip|I2C-slave_USART.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:I2C-slave_USART.png&amp;diff=42988</id>
		<title>Datei:I2C-slave USART.png</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:I2C-slave_USART.png&amp;diff=42988"/>
		<updated>2010-02-08T11:18:00Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42987</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42987"/>
		<updated>2010-02-08T11:17:44Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Die Schaltung */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
[[Bild:I2C-slave_USART.png]]&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Alle einstellbaren Optionen haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
&lt;br /&gt;
[[Media:I2C-slave_USART.zip|I2C-slave_USART.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42985</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42985"/>
		<updated>2010-02-08T11:12:45Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Alle einstellbaren Optionen haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
&lt;br /&gt;
[[Media:I2C-slave_USART.zip|I2C-slave_USART.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42984</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42984"/>
		<updated>2010-02-08T11:08:32Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Alle einstellbaren Optionen haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
&lt;br /&gt;
[[Media:I2C-slave_USART.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42983</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42983"/>
		<updated>2010-02-08T11:07:13Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Alle einstellbaren Optionen haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;br /&gt;
[[Bild:I2C-slave_USART.zip]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:I2C-slave_USART.zip&amp;diff=42982</id>
		<title>Datei:I2C-slave USART.zip</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:I2C-slave_USART.zip&amp;diff=42982"/>
		<updated>2010-02-08T11:06:10Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: Anleitung als PDF, Firmware als HEX, EEP und ASM Dateien&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Anleitung als PDF, Firmware als HEX, EEP und ASM Dateien&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42981</id>
		<title>I2C-slave USART</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=I2C-slave_USART&amp;diff=42981"/>
		<updated>2010-02-08T10:58:52Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: Die Seite wurde neu angelegt: „&amp;#039;&amp;#039;von Klaus2m5&amp;#039;&amp;#039; ==Übersicht== Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert eine…“&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
==Übersicht==&lt;br /&gt;
Ein AVR ATTINY2313 als I2C nach RS232 Konverter. Benutzt USART und USI Hardware des AVR, programmiert in Assembler. Erweitert einen Master um jeweils einen USART, begrenzt nur durch die Anzahl der verfügbaren I2C-Adressen und der Bandbreite des I2C-Bus.&lt;br /&gt;
&lt;br /&gt;
Eine spezielle Anwendung besteht darin, einem beliebigen AVR eine temporäre RS232 Schnittstelle zur Verfügung zu stellen, ohne dass dieser einen Baudratenquartz und Schnittstellenhardware benötigt. Mittels des vorhandenen ISP-Steckers kann I2C-Master Software den USART steuern.&lt;br /&gt;
==Die Schaltung==&lt;br /&gt;
&lt;br /&gt;
Ein Platinenlayout existiert nicht, da man diese Schaltung in vielen Variationen, bis hin zur Integration mit einem I2C-Master und mehreren I2C-Slaves, aufbauen kann.&lt;br /&gt;
==Grundfunktionen==&lt;br /&gt;
* I2C-Slave nach RS232 Konverter&lt;br /&gt;
* Jeweils 48 Bytes Empfangs- und Sendepuffer&lt;br /&gt;
* Optionale Hardware und/oder Xon/Xoff Flusskontrolle&lt;br /&gt;
* Viele Optionen konfigurierbar, ohne neu zu flashen&lt;br /&gt;
* Bei  I2C-Aktivität leuchtet die LED rot, ansonsten grün&lt;br /&gt;
* Wenn -RESET low ist, wird die I2C Schnittstelle inklusive der Pullups abgeschaltet (High-Z)&lt;br /&gt;
==Erweiterungen==&lt;br /&gt;
Drückt man den Taster an PB2 des AVR, kommt man in den Debug- &amp;amp; Konfigurationsmonitor. Die LED leuchtet dauernd rot und die I2C Schnittstelle ist abgeschaltet (Der Master bekommt ein Nak, wenn er die Slave Adresse anspricht). Der Monitor läuft über RS232 und meldet sich dort mit seinem Prompt „I2R&amp;gt;“. Er bietet die Möglichkeit, den Inhalt von GPRs, I/O-Registern, SRAM und EEPROM auszulesen und zu verändern. Man kann einen Reset auslösen (benutzt den Watchdog) oder einen Call oder Jump zu einer Flash-Adresse machen. Ein einfacher I2C-Sniffer erlaubt die Überwachung des angeschlossenen I2C-Buses. Mit den I2C-Master Funktionen kann man I2C-Slaves testen.&lt;br /&gt;
==Konfiguration==&lt;br /&gt;
Alle einstellbaren Optionen haben reservierte EEPROM-Adressen und können mit Hilfe der Monitor-Commands E &amp;amp; P leicht geändert werden. Die Änderungen werden nach einem Reset wirksam.&lt;br /&gt;
==Anwendungsbeispiel==&lt;br /&gt;
Um die Anwendungsmöglichkeiten zu verdeutlichen, befindet sich im Ordner Debugger des Downloads eine Sammlung von „Debug Skeleton *.asm“ Dateien, die an verschiedene AVRs angepasst sind. Diese stellen ein Gerüst für die Anwendungsentwicklung mit I2C-Master Zugriff über die ISP-Schnittstelle auf RS232 Ein-/Ausgabe dar. Sie enthalten einen, dem I2C-Slave USART Debug- und Konfigurationsmonitor ähnlichen Monitor, der unter anderem den Zugriff auf die Speicher und Register des AVR erlaubt.&lt;br /&gt;
==Download==&lt;br /&gt;
Eine Anleitung mit mehr Details und die Firmware mit Assembler Source befinden sich im Download.&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=42705</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=42705"/>
		<updated>2010-01-30T17:05:14Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Verzögerung um X Nanosekunden */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Mikrosekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set     Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set     cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro   wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=42703</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=42703"/>
		<updated>2010-01-30T16:09:44Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Verzögerung um X Nanosekunden */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Dabei werden maximal 4 Instruktionen erzeugt.&lt;br /&gt;
Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Millisekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab (exakt bis +1 Takt).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set	Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set	cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro      wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=42702</id>
		<title>AVR Assembler Makros</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=AVR_Assembler_Makros&amp;diff=42702"/>
		<updated>2010-01-30T16:00:08Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* I/O */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
Hier entsteht eine Sammlung von verschiedenen nützlichen [[Makro|Makros]] für den [[AVR]] [[Assembler]].&lt;br /&gt;
&lt;br /&gt;
=== 16 Bit Konstante in Z-Pointer laden ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SetZPtr ;(Adresse)&lt;br /&gt;
        ldi     ZL, LOW(@0)&lt;br /&gt;
        ldi     ZH, HIGH(@0)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Natürlich auch möglich mit X- und Y-Pointer.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Speicher==&lt;br /&gt;
=== 2 Register ohne Zwischenspeicher vertauschen ===&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO SWAP ;(a, b)&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
        eor     @1, @0&lt;br /&gt;
        eor     @0, @1&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Arithmetik==&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDI ;(a, k)&lt;br /&gt;
        subi    @0, -(@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Konstante addieren (16 Bit) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(RdL:RdH, k)&lt;br /&gt;
        subi    @0L, LOW(-@1)  &lt;br /&gt;
        sbci    @0H, HIGH(-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
oder (sinnlos)&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.MACRO ADDIW ;(Rd, k)&lt;br /&gt;
        sbiw    @0, (-@1)&lt;br /&gt;
.ENDMACRO&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
DAS geht auch ohne Makro&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
        adiw a, b&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
SBIW und ADIW sind aber beide auf die Register(paare) R24, R26, R28, R30 beschränkt UND nehmen nur Zahlen &amp;lt;64 an.&lt;br /&gt;
&lt;br /&gt;
==I/O==&lt;br /&gt;
&lt;br /&gt;
Bei grösseren und neueren AVRs sind etliche I/O-Register nicht mit IN/OUT-Befehlen ansprechbar. LDS/STS erreicht zwar alle, ist aber bei kleineren oder älteren ineffizient.&lt;br /&gt;
&lt;br /&gt;
=== Port lesen ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro input&lt;br /&gt;
  .if @1 &amp;lt; 0x40&lt;br /&gt;
	in	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	lds	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Port schreiben ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro output&lt;br /&gt;
  .if @0 &amp;lt; 0x40&lt;br /&gt;
	out	@0, @1&lt;br /&gt;
  .else&lt;br /&gt;
  	sts	@0, @1&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Portbit abfragen ===&lt;br /&gt;
&lt;br /&gt;
Abfrage eines Bits eines I/O-Ports und Sprung wenn 1/0.&lt;br /&gt;
Überschreibt u.U. ZL.&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Set&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbis ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbic	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrc	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Branch if Bit in I/O-Register is Cleared&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
.macro bbic ;port,bit,target&lt;br /&gt;
  .if @0 &amp;lt; 0x20&lt;br /&gt;
 	sbis	@0, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .elif @0 &amp;lt; 0x40&lt;br /&gt;
	in	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .else&lt;br /&gt;
	lds	zl, @0&lt;br /&gt;
	sbrs	zl, @1&lt;br /&gt;
	rjmp	@2&lt;br /&gt;
  .endif&lt;br /&gt;
.endm&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
== Delay ==&lt;br /&gt;
=== Verzögerung um X Nanosekunden ===&lt;br /&gt;
von Klaus2m5&lt;br /&gt;
&lt;br /&gt;
Taktgenaue Verzögerung der Instruktionsausführung durch Angabe der Verzögerungszeit&lt;br /&gt;
in Nanosekunden. Taktgenau bedeutet, dass auf die nächste volle Anzahl von Takten&lt;br /&gt;
aufgerundet wird. Beispiel: 75ns bei 20MHZ (50ns Taktzeit) bedeutet eine tatsächliche&lt;br /&gt;
Verzögerung von 2 Zyklen und entspricht 100ns. Dabei werden maximal 4 Instruktionen&lt;br /&gt;
erzeugt.&lt;br /&gt;
&lt;br /&gt;
Die Variable Osc_Hz muss der verwendeten Taktquelle angepasst werden und definiert&lt;br /&gt;
die CPU-Taktfrequenz in Hertz.&lt;br /&gt;
&lt;br /&gt;
wait_ns wird mit folgenden Parametern aufgerufen:&lt;br /&gt;
  1. Verzögerungszeit in Nanosekunden&lt;br /&gt;
  2. bereits verbrauchte Takte&lt;br /&gt;
  3. ein Immediate-Register (R16-R31) als Zähler&lt;br /&gt;
Bereits verbrauchte Takte werden aus den Instruktionen errechnet, die zwischen&lt;br /&gt;
den zu verzögernden Ereignissen liegen. Beispiel:&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
            sbi     porta,0&lt;br /&gt;
            wait_ns 1000,2,R16&lt;br /&gt;
            cbi     porta,0&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;br /&gt;
In diesem Fall besteht die Anzahl der verbrauchten Takte lediglich aus den Instruktionen,&lt;br /&gt;
die zum Ereignis führen. Am Ende von SBI wird die steigende Flanke, am Ende von CBI&lt;br /&gt;
die fallende Flanke des Signals erzeugt. Wenn wir also möglichst exakt eine&lt;br /&gt;
Pulsbreite von einer Millisekunde erzeugen wollen, müssen wir die Ausführungszeit&lt;br /&gt;
von CBI von unserer Wartezeit abziehen. Die Ausführung von CBI liegt vor dem Ereignis!&lt;br /&gt;
&lt;br /&gt;
Wenn die Verzögerungszeit kleiner als die Anzahl bereits verbrauchter Taktzyklen ist,&lt;br /&gt;
wird keine weitere Verzögerung erzeugt. Die maximale Verzögerung ist 767 Takte&lt;br /&gt;
entsprechend 38350ns bei 20MHZ. Bei niedrigeren Frequenzen wird eine längere Verzögerung&lt;br /&gt;
erreicht, allerdings nimmt dann auch die Genauigkeit der Verzögerung ab.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;avrasm&amp;gt;&lt;br /&gt;
;&lt;br /&gt;
; wait_ns  waittime in ns , cyles already used , waitcount register &lt;br /&gt;
;&lt;br /&gt;
;     cycles already used will be subtracted from the delay&lt;br /&gt;
;     the waittime resolution is 1 cycle (delay from exact to +1 cycle)&lt;br /&gt;
;     the maximum delay at 20MHz (50ns/clock) is 38350ns&lt;br /&gt;
;     waitcount register must specify an immediate register&lt;br /&gt;
;&lt;br /&gt;
.set	Osc_Hz         = 7372800                    ;7,3728 MHz (Baudrate xtal)&lt;br /&gt;
.set	cycle_time_ns  = (1000000000 / Osc_Hz)      ;clock duration&lt;br /&gt;
.macro      wait_ns&lt;br /&gt;
      .set cycles = ((@0 + cycle_time_ns - 1) / cycle_time_ns - @1)&lt;br /&gt;
      .if (cycles &amp;gt; (255 * 3 + 2))&lt;br /&gt;
        .error &amp;quot;MACRO wait_ns - too many cycles to burn&amp;quot;&lt;br /&gt;
      .else&lt;br /&gt;
        .if (cycles &amp;gt; 6)&lt;br /&gt;
          .set  loop_cycles = (cycles / 3)      &lt;br /&gt;
          ldi   @2,loop_cycles&lt;br /&gt;
          dec   @2&lt;br /&gt;
          brne  pc-1&lt;br /&gt;
          .set  cycles = (cycles - (loop_cycles * 3))&lt;br /&gt;
        .endif&lt;br /&gt;
        .if (cycles &amp;gt; 0)&lt;br /&gt;
          .if   (cycles &amp;amp; 4)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 2)&lt;br /&gt;
            rjmp  pc+1&lt;br /&gt;
          .endif&lt;br /&gt;
          .if   (cycles &amp;amp; 1)&lt;br /&gt;
            nop&lt;br /&gt;
          .endif&lt;br /&gt;
        .endif&lt;br /&gt;
      .endif&lt;br /&gt;
.endmacro&lt;br /&gt;
&amp;lt;/avrasm&amp;gt;&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:Keyboard_switch.png&amp;diff=32914</id>
		<title>Datei:Keyboard switch.png</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:Keyboard_switch.png&amp;diff=32914"/>
		<updated>2008-12-04T12:33:13Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: hat eine neue Version von „Bild:Keyboard switch.png“ hochgeladen&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32912</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32912"/>
		<updated>2008-12-04T12:25:37Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Informationen im Web */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
* [[Media:keyboard_switch.zip|(Assembler Source, HEX-File, dieses Dokument als PDF)]]&lt;br /&gt;
&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
* [http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
* [http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;br /&gt;
&lt;br /&gt;
[[Category:Projekte|P]]&lt;br /&gt;
[[Category:AVR|P]]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32910</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32910"/>
		<updated>2008-12-04T12:16:34Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
* [[Media:keyboard_switch.zip|(Assembler Source, HEX-File, dieses Dokument als PDF)]]&lt;br /&gt;
&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
* [http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
* [http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32909</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32909"/>
		<updated>2008-12-04T12:07:59Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
[[Media:keyboard_switch.zip]]&lt;br /&gt;
(Assembler Source, HEX-File, dieses Dokument als PDF)&lt;br /&gt;
&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
* [http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
* [http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32908</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32908"/>
		<updated>2008-12-04T11:54:18Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
[[Bild:keyboard_switch.zip]]&lt;br /&gt;
(Assembler Source, HEX-File, dieses Dokument als PDF)&lt;br /&gt;
&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
* [http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
* [http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32907</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32907"/>
		<updated>2008-12-04T11:50:41Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
[[Datei:keyboard_switch.zip]]&lt;br /&gt;
(Assembler Source, HEX-File, dieses Dokument als PDF)&lt;br /&gt;
&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
* [http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
* [http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32906</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32906"/>
		<updated>2008-12-04T11:47:03Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
[[keyboard_switch.zip]]&lt;br /&gt;
(Assembler Source, HEX-File, dieses Dokument als PDF)&lt;br /&gt;
&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
* [http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
* [http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:Keyboard_switch.zip&amp;diff=32905</id>
		<title>Datei:Keyboard switch.zip</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:Keyboard_switch.zip&amp;diff=32905"/>
		<updated>2008-12-04T11:46:06Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32904</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32904"/>
		<updated>2008-12-04T11:45:26Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Download */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
[[Bild:keyboard_switch.zip]]&lt;br /&gt;
(Assembler Source, HEX-File, dieses Dokument als PDF)&lt;br /&gt;
&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
* [http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
* [http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32903</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32903"/>
		<updated>2008-12-04T11:34:34Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Informationen im Web */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
(Assembler Source, HEX-File, dieses Dokument als PDF)&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
* [http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
* [http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32902</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32902"/>
		<updated>2008-12-04T11:31:52Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: /* Initialisierung */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
=== Timer und Interrupts ===&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
(Assembler Source, HEX-File, dieses Dokument als PDF)&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
[http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
&lt;br /&gt;
[http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Datei:Keyboard_switch.png&amp;diff=32901</id>
		<title>Datei:Keyboard switch.png</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Datei:Keyboard_switch.png&amp;diff=32901"/>
		<updated>2008-12-04T11:23:55Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: hat eine neue Version von „Bild:Keyboard switch.png“ hochgeladen&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32900</id>
		<title>Aktiver PS/2 Tastatur Umschalter</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Aktiver_PS/2_Tastatur_Umschalter&amp;diff=32900"/>
		<updated>2008-12-04T11:14:44Z</updated>

		<summary type="html">&lt;p&gt;Klaus2m5: Die Seite wurde neu angelegt: &amp;#039;&amp;#039;von Klaus2m5&amp;#039;&amp;#039; == Übersicht == Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2  Tastatur an bis zu 4 PCs.  An inaktiven PC Ports wird...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;von Klaus2m5&#039;&#039;&lt;br /&gt;
== Übersicht ==&lt;br /&gt;
Ein in Assembler programmierter AVR ATTINY2313 erlaubt den Anschluss einer PS/2 &lt;br /&gt;
Tastatur an bis zu 4 PCs.&lt;br /&gt;
&lt;br /&gt;
An inaktiven PC Ports wird die Tastatur emuliert und die Parameter der Tastatur &lt;br /&gt;
(typematic, Lock LEDs) gespeichert. Das Umschalten der Tastatur erfolgt über &lt;br /&gt;
Hotkey (Shift Lock). LEDs und typematic rate werden dabei entsprechend des &lt;br /&gt;
ausgewählten PCs an die Tastatur übertragen. Die Anzeige des aktiven PCs &lt;br /&gt;
erfolgt durch eine gepulste LED (PC = Anzahl Pulse).&lt;br /&gt;
=== Einschränkungen ===&lt;br /&gt;
Es werden nur Tastaturen mit Scancode Set 2 unterstützt (XT Tastatur). Soweit &lt;br /&gt;
bekannt, verwenden alle aktuellen PCs, Betriebssysteme und PS/2 Tastaturen &lt;br /&gt;
diesen Standard.&lt;br /&gt;
== Die Schaltung ==&lt;br /&gt;
[[Bild:keyboard_switch.png]]&lt;br /&gt;
=== Stromversorgung ===&lt;br /&gt;
Die Schaltung wird durch die angeschlossenen PCs über Schottky Dioden versorgt. &lt;br /&gt;
Schaltung und Tastatur sind versorgt, falls einer der angeschlossenen PCs &lt;br /&gt;
eingeschaltet ist oder die Tastatur Schnittstelle durch die Standby Spannung &lt;br /&gt;
des PC Netzteils versorgt wird.&lt;br /&gt;
=== PC Ports ===&lt;br /&gt;
Jeweils 2 Pins des Port B bilden die Clock und Data Anschlüsse für einen PC. &lt;br /&gt;
Das benutzte PS/2 Protokoll erfordert eine Open Collector Ansteuerung mit &lt;br /&gt;
Pullup Widerständen (1,5k) ähnlich I²C. Ursprünglich hatte ich 10k als Pullup &lt;br /&gt;
vorgesehen, mit 1,5m Kabel traten aber Übertragungsfehler auf und das Signal &lt;br /&gt;
war deutlich sichtbar beeinträchtigt (Scope).&lt;br /&gt;
&lt;br /&gt;
Um erkennen zu können, ob ein PC auch tatsächlich eingeschaltet ist, erfolgt &lt;br /&gt;
die Versorgung der Pullups direkt aus der PC Spannung. Ein Pulldown (10k) nebst &lt;br /&gt;
Block Kondensator (100n) pro PC sorgt für definierten low Pegel, falls kein PC &lt;br /&gt;
angeschlossen ist.&lt;br /&gt;
=== Tastatur Port ===&lt;br /&gt;
Die Tastatur ist an Port D3 und D5 angeschlossen. Die Tastatur Clock ist dabei &lt;br /&gt;
mit INT1 verbunden, damit die Data Bits mit der fallenden Flanke des Clock &lt;br /&gt;
Signals ausgewertet werden können.&lt;br /&gt;
=== Status LED ===&lt;br /&gt;
Die Status LED wird vom Port D6 angesteuert und signalisiert den im Augenblick &lt;br /&gt;
aktiven PC und andere Betriebszustände.&lt;br /&gt;
=== Reset ===&lt;br /&gt;
Der Reset Taster ist noch ein Überbleibsel aus der Debug Zeit und nicht &lt;br /&gt;
zwingend erforderlich. Damals war es äußerst lästig, erst die Stromversorgung &lt;br /&gt;
von allen PCs unterbrechen zu müssen, um einen Reset zu erreichen.&lt;br /&gt;
=== Freie Ports ===&lt;br /&gt;
Einige Ports sind noch frei und wurden während der Entwicklung benutzt oder &lt;br /&gt;
sind für zusätzliche Funktionen vorgesehen.&lt;br /&gt;
* RS232 (Port D0:1) – zum Überwachen der Tastatur Scancodes und der Emulation. Gleichzeitig ist ein 7,372 MHz Quarz an Port A0:1 erforderlich. Der Schalter „debug“ muss beim Assemblieren der Firmware gesetzt sein &lt;br /&gt;
&lt;br /&gt;
* ISP (MISO, MOSI, SCK, Reset) – falls eigene Experimente geplant sind. Während des Programmiervorgangs dürfen PC3 &amp;amp; PC4 nicht verbunden sein.&lt;br /&gt;
&lt;br /&gt;
* BCD codierte Ausgabe des aktiven PC (Port A0:1) – zur Erweiterung durch einen externen Maus oder VGA Umschalter. Der Schalter „hst_sel_out“ muss beim Assemblieren der Firmware gesetzt sein.&lt;br /&gt;
&lt;br /&gt;
* Maus (Port D2,4) – Erweiterung um einen zusätzlichen Maus Eingang. Zurzeit ohne Funktion. Die Erweiterung auf eine Maus Umschaltung ist von mir nicht geplant und ist wahrscheinlich nur auf einem größeren AVR sinnvoll.&lt;br /&gt;
=== Aufbau ===&lt;br /&gt;
Ein Layout für eine gedruckte Schaltung existiert nicht. Die Schaltung lässt &lt;br /&gt;
sich jedoch leicht auf einer Lochrasterplatine aufbauen.&lt;br /&gt;
&lt;br /&gt;
Die 6-poligen Mini-DIN Buchsen zu den PC Tastatur Ports können durch in der &lt;br /&gt;
Mitte auseinander geschnittene, und direkt mit der Platine verlötete PS/2 &lt;br /&gt;
Stecker/Stecker Kabel ersetzt werden.&lt;br /&gt;
&lt;br /&gt;
Wenn der Schalter für weniger PCs aufgebaut wird, kann die Hardware für diese &lt;br /&gt;
PCs entfallen. Es wird jedoch empfohlen, die freien Pins des Port B mit 10k &lt;br /&gt;
Pulldowns zu bestücken.&lt;br /&gt;
&lt;br /&gt;
Für eigene Experimente sollte man noch eine externe Stromversorgung, Probe Pins &lt;br /&gt;
fürs Scope, einen RS232 Anschluss nebst Quarz und einen ISP Stecker verbauen &lt;br /&gt;
(nicht Teil des Schaltplans).&lt;br /&gt;
== Die Firmware ==&lt;br /&gt;
Das zum Download angebotene ZIP File enthält sowohl den Sourcecode (Assembler) &lt;br /&gt;
als auch ein HEX File zum flashen des AVR ohne Debug-Funktion und BCD-Ausgabe &lt;br /&gt;
an Port A. Benötigte Fuses:&lt;br /&gt;
* 8MHz internal RC Osc mit Startup 14 CK + 65ms (voreingestellt)&lt;br /&gt;
* BOD enabled – 2,7V&lt;br /&gt;
* CKDIV8 disabled !!!!!!!&lt;br /&gt;
* Finger weg vom Rest der Fuses!&lt;br /&gt;
=== Status ===&lt;br /&gt;
Der momentane Betriebszustand wird durch eine LED dargestellt. Wenn kein &lt;br /&gt;
besonderer Betriebszustand vorliegt, Zeigt die Anzahl der Lichtimpulse den &lt;br /&gt;
aktiven PC an. Wenn nach dem Drücken einer Taste die Kommunikation zum aktiven &lt;br /&gt;
PC nicht möglich ist, z.B. keine Stromversorgung von diesem PC, dann wird dies &lt;br /&gt;
durch 2 Sekunden 8Hz Blinken dargestellt.&lt;br /&gt;
=== Steuerung ===&lt;br /&gt;
Die Umschaltung der Tastatur erfolgt durch Klammergriff. Die Shift-Lock/Caps-&lt;br /&gt;
Lock Taste dient dabei als Hotkey.  Wird der Hotkey betätigt, dann leuchtet die &lt;br /&gt;
LED dauernd bis diese Taste wieder losgelassen wird.  Die Zifferntasten 1-4 im &lt;br /&gt;
Alpha Block (nicht der Nummern Block) schalten auf den entsprechenden PC. &lt;br /&gt;
Selbstverständlich kann man auch noch Caps-Lock erreichen, indem man die [ ^ ] &lt;br /&gt;
Taste drückt ([ ~ ] bei englischer Tastatur). Die Tastenkombination kann im &lt;br /&gt;
Sourcecode  frei gewählt werden. Ein Beispiel ist im Source  enthalten. Dabei &lt;br /&gt;
werden die Tasten F1-F4 zum Umschalter, Caps-Lock über [Esc] erreicht.&lt;br /&gt;
&lt;br /&gt;
Das Umschalten der Tastatur auf einen PC ohne Versorgungsspannung oder mit &lt;br /&gt;
dauerhaft auf low liegendem Data- oder Clock-Signal wird unterdrückt und mit &lt;br /&gt;
8Hz Blinken angezeigt. &lt;br /&gt;
=== Initialisierung ===&lt;br /&gt;
Bei einem Reset durch Einschalten (auch Brown Out) wird der erste PC &lt;br /&gt;
eingestellt, der eine Versorgungsspannung zur Verfügung stellt. Die &lt;br /&gt;
Initialisierung der Tastatur wird dann dem PC an diesem Port überlassen.&lt;br /&gt;
&lt;br /&gt;
Wird der Reset durch den Reset Pin ausgelöst (durch drücken der Reset Taste), &lt;br /&gt;
dann wird die Tastatur durch den AVR initialisiert und die bisherigen &lt;br /&gt;
Einstellungen wieder hergestellt.&lt;br /&gt;
&lt;br /&gt;
Timer und Interrupts&lt;br /&gt;
&lt;br /&gt;
Alle internen Funktionen des Tastatur Umschalters benutzen die Timer des AVR. &lt;br /&gt;
Beide Timer werden mit einem Prescaler von 8 initialisiert, so dass diese mit &lt;br /&gt;
einer Auflösung von 1µs arbeiten (8MHz/8 = 1MHz = 1µs). Natürlich eignet sich &lt;br /&gt;
der 16 Bit Timer in diesem Falle für längere Zeiträume (65ms) und der 8 Bit &lt;br /&gt;
Timer für eher kurze Zeiträume (256 µs).&lt;br /&gt;
&lt;br /&gt;
So wird die LED mit Hilfe des Timer 1 Overflow Interrupts gesteuert. Dieser &lt;br /&gt;
Interrupt wird enabled ausgeführt, da er nicht zu den höchsten Prioritäten des &lt;br /&gt;
Umschalters gehört.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit der Tastatur wird durch das Clock-Signal der Tastatur &lt;br /&gt;
angestoßen. Diese erzeugt über Portpin D3 mit der fallenden Flanke einen &lt;br /&gt;
externen Interrupt über den INT1-Vektor. Laut Spezifikation generiert die &lt;br /&gt;
Tastatur eine Clock mit maximal 16,6kHz. Die Daten sind während der low Phase &lt;br /&gt;
der Clock gültig, entsprechend 30µs des Clock-Zyklus von 60µS. Daten müssen &lt;br /&gt;
also nach 240 AVR Clocks erfasst sein (inklusive aller vorausgehender &lt;br /&gt;
Interrupts). Ein 11 Bit Datenframe von der Tastatur erreicht den AVR in minimal &lt;br /&gt;
660µs, maximal in 1,1ms (minimum Clock 10kHz). Um verlorene Clocks sicher &lt;br /&gt;
erkennen zu können, wird am Anfang eines Frames der Timer 1 Compare A auf Timer &lt;br /&gt;
1 + 2ms gesetzt. Läuft der Timer Compare vor Ende des Frames ab, so wird durch &lt;br /&gt;
den resultierenden Interrupt ein Fehler Bit im Status Register gesetzt (0x40). &lt;br /&gt;
Dies geschieht auch bei Start- und Stop-Bits mit falschem Pegel und bei &lt;br /&gt;
falschem Parity (0x80).&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit den PC Tastatureingängen erfolgt mit Hilfe des Timer 0 &lt;br /&gt;
Compare A. Für jede Clock-Phase wird der Compare auf Timer 0 + 30µs gesetzt &lt;br /&gt;
(240 AVR Clocks + die eventuell von INT1 benötigten AVR Clocks. Der CLock &lt;br /&gt;
Zyklus kann durch verzögerte Auslösung des Compare Interrupts bis auf 50µs &lt;br /&gt;
verlängert werden, er wird aber immer mindestens 30 µs lang sein (10kHz-&lt;br /&gt;
16,6kHz). Mit dem Versenden eines Frames an einen PC wird für diesen PC ein &lt;br /&gt;
Software Timer gestartet, so dass das nächste Frame für diesen PC mindestens &lt;br /&gt;
3,5ms warten muss. Dadurch bleibt genügend Zeit für die Kommunikation mit den &lt;br /&gt;
anderen PCs, und der PC kann mit Hilfe seines 2ms Timeouts mit Sicherheit &lt;br /&gt;
verlorene Clocks erkennen.&lt;br /&gt;
=== Hauptprogramm ===&lt;br /&gt;
Im Hauptprogramm werden permanent die durch die Tastatur- und PC-Kommunikation &lt;br /&gt;
gesetzten Status Register überprüft („looking for work“). Wenn die &lt;br /&gt;
Statusregister die maximale Anzahl von Interrupts erreicht haben, war die &lt;br /&gt;
Übertragung erfolgreich. Wenn zum Beispiel der Tastatur Interrupt 11 erreicht &lt;br /&gt;
hat, ist ein Frame komplett empfangen, und kann an den PC weitergegeben werden. &lt;br /&gt;
Wenn ein Frame nicht vollständig übertragen werden konnte, wird dies durch &lt;br /&gt;
setzen von Bit 7 oder Bit 6 indiziert. In diesem Fall führt das Hauptprogramm &lt;br /&gt;
die Fehlerbehandlung durch (Resend, Resend Request).&lt;br /&gt;
&lt;br /&gt;
Ein Kommando vom aktiven PC wird an die Tastatur weitergegeben. Bei den &lt;br /&gt;
inaktiven PCs sorgt eine Emulation für die Antworten. In jedem Falle werden &lt;br /&gt;
alle wichtigen Parameter in einem Array gespeichert. Aus diesem Array können &lt;br /&gt;
beim Wechsel des aktiven PCs die entsprechenden Parameter an die Tastatur &lt;br /&gt;
gesendet werden.&lt;br /&gt;
&lt;br /&gt;
Die Kommunikation mit einem PC erfordert oft das Senden mehrerer Frames &lt;br /&gt;
hintereinander und ist deshalb durch einen Sequencer realisiert. Das &lt;br /&gt;
Hauptprogramm kann dadurch in den Wartezeiten zwischen den Frames auch mit &lt;br /&gt;
anderen PCs kommunizieren, obwohl nur eine Task existiert. Auch für die &lt;br /&gt;
Tastatur gibt es einen Sequencer, der nach dem Umschalten des aktiven PCs die &lt;br /&gt;
Tastatur enabled und mit den Parametern für diesen PC lädt.&lt;br /&gt;
== Quellen ==&lt;br /&gt;
=== Download ===&lt;br /&gt;
(Assembler Source, HEX-File, dieses Dokument als PDF)&lt;br /&gt;
=== Informationen im Web ===&lt;br /&gt;
[http://www.computer-engineering.org/index.php?title=Main_Page PS/2 Keyboard Protocol]&lt;br /&gt;
&lt;br /&gt;
[http://www.beyondlogic.org/keyboard/keybrd.htm Interfacing the PCs keyboard]&lt;/div&gt;</summary>
		<author><name>Klaus2m5</name></author>
	</entry>
</feed>