This document was uploaded by user and they confirmed that they have the permission to share
it. If you are author or own the copyright of this book, please report to us by using this DMCA
report form. Report DMCA
Overview
Download & View Avr Assembly Tutorial Complete as PDF for free.
Learning AVR Assembler with Zu meiner privaten practical examples Homepage Erlernen der AVRAssemblersprache mit praktischen Beispielen Want to learn how to program AT90S AVR processors in assembler language? This is the page for you! Wollten Sie schon immer Assemblersprache von AT90S AVR-Prozessoren erlernen? Das ist die Seite zum Lernen! Select your language Goto to the english page
Zur deutschen Seite
Sitemap with all pages at this site
Sitemap mit allen Seiten auf dieser Webseite
Send feedback/questions to the author
Feedback/Fragen an den Autor senden
Leave a new note in my guestbook
Neuer Eintrag in mein Gästebuch
Visit my forum, enter a new message
In das Forum gehen, einen neuen Eintrag schreiben
Visit the Download page for a zipped copy of this webpage or the beginner's course in one PDF doc.
Besuchen Sie die Download-Seite um eine Kopie dieser Webseite down zu loaden oder den kompletten Anfängerkurs in einem einzigen PDFDokument.
Have a look at this webpage's visitor statistic and see how popular this page is!
Riskieren Sie einen Blick auf die Serverstatistik dieser Seite!
Inform me about any relevant changes on the Ich möchte über alle wesentlichen Änderungen auf website by EMail dieser Webseite informiert werden. AVR-Webring - More webpages on AVR - Weitere AVR-Webseiten [ Join Now | Ring Hub | Random | << Prev | Next >> ]
AVR-Single-Chip-Processors (AT90S, ATmega and ATtiny) from ATMEL with practical examples.
The Single-Chip-processors of ATMEL are excellent for homebrewing every kind of processordriven electronics. The only problem is that assembly has to be learned in order to program these devices. After having done these first steps the assembly language provides very fast, lean and effective code, by which every task can be accomodated. These pages are for beginners and help in learning the first steps. Sitemap
New on this webpage
Error list
avr-source
AVR-Webring
Index Beginner's introduction to AVR assembler language. Also available as complete PDF-document for printing the whole course (Download, 1.1 MB)
Four simple programming examples with extended comments as first steps of a practical introduction to assembler programming: Sense and requirements, Simple programming examples
A command line assembler with extended error checking and commenting, free for download
For convenient operation of the command-line assembler: a window caller including editing the source and include files, viewing the list file, finding errors and editing erroneous lines, etc., for free download here
Binary multiplication, division, conversion of number formats and fixed decimals in detail, hardware multiplication
Programming and testing of the hardware of the STK200-Board: EEPROM, external RAM, LCDdisplay, SIO-interface
Connecting a two-line-LCD with a four-line connection to the STK500 programming board with base routines for driving the LCD and a small clock application
Converting an analog voltage to digital using the STK500 board, the on-board analog comparator and timer/counter 1 as pulse width generator
Connectinga 4*3 keypad to an AVR and sensing using Port connections or with a resistor matrix and an AD converter
Converting a digital value to an analog voltage using a buffered R/2R network, including wave generation like sawtooth, triangle, sinewave forms and a small tone player application.
Software-Know-How, special assembler commands: LPM, stack jumps, macros
Small applications: a DCF77 synchronized clock, a PCM-toPWG-decoder, a terminalcontrolled frequency generator, a digital signal generator with frequency/pulse-width adjust and LCD, an eggtimer as a gift, a steppermotor controller/driver
Accu loader applying an ATmega16
The whole webpage for download, ca. 2.9 MB packed, ca. 3.9 MB unpacked. After download unzip this file in a separate directory, keeping the pathes.
Top of that page
Index
Error list
avr-source
AVR-Webring
New on this webpage since:
Description and link
Source code
10.01.2009 Frequency counter with ATmega8, nine modes, LCD, 16 MHz xtal
fcountV03
28.12.2008 Updated version of the beginner course in one pdf document
-
New version 2.2 of the command line assembler gavrasm (free 23.12.2008 Assembler for Linux, Dos and Win, in german, englisch and french for 115 AVR device types)
28.06.2007 Steppermotor controller/driver with an ATtiny13
stepper.asm
02.12.2006
New version 2.1 of the command line assembler gavrasm (free Assembler for Linux, Dos and Win)
-
05.11.2006 An eggtimer with a ATtiny2313 as a gift
eggtimer.asm
29.09.2006
New version 2.0 of the command line assembler gavrasm (free Assembler for Linux, Dos and Win)
-
13.08.2006
New version 1.9 of the command line assembler gavrasm (free Assembler for Linux, Dos and Win)
-
New version 1.8 of the command line assembler gavrasm (free 16.07.2006 Assembler for Linux, Dos and Win) Persian version of the beginner's course on the new Download page
-
25.05.2006 An adjustable digital signal generator with LCD
Zipped sources
04.05.2006
New version 1.7 of gavrasm (free AVR assembler for download). Corrects a bug in the ELIF directive.
-
17.04.2006 Added a sitemap of the whole site
-
17.04.2006 Page on connecting a 4*3 keypad to an AVR
-
28.12.2005
New version 1.6 of gavrasm (free AVR assembler for download). Adds support for new CAN-, Tiny- and one new Mega-Type.
-
27.09.2005
New version 1.5 of gavrasm (free AVR assembler for download). Corrected two minor bugs.
-
28.03.2005
Accu cell loader hardware and assembler software applying an ATmega16
akkuload.asm zipped
28.03.2005
Added a description of advanced directives like conditional assembly and on left shift of port bits in the beginner course.
-
New version 1.3 of gavrasm (free AVR assembler for download). 27.03.2005 Corrected false EEPROM capacity of two ATmega and added support for the new types ATmega 640, 1280, 1281, 2560 and 2561. New version 1.2 of gavrasm (free AVR assembler for download). 08.03.2005 Corrected some minor bugs and added support for the new types ATtiny25, 45 and 85.
-
New version 1.1 of gavrasm (free AVR assembler for download). 06.01.2005 Corrected some minor bugs in the MACRO treatment, finally added support for the historic program counter PC.
03.01.2005
sawtooth1 sawtooth2 triangle sinewave and wavetable music
Applying an R/2R resistor network for Digital-to-Analog conversion and generating waveforms
New version 1.0 of gavrasm (free AVR assembler for download. 09.10.2004 Added support for ATmega325 etc., an extra error file and others. New version of the window caller, assimilated to gavrasm 1.0 28.03.2003
-
gavrasm (Free AVR assembler) in improved version 0.9 for download. Added support for ATmega48/88/168 now.
-
gavrasm (Free AVR assembler) in improved version 0.8 for download. Corrects some minor errors. Also, a new version of the 15.02.2004 convenient window caller for the command line assembler for free download.
-
30.12.2003 Beginner course as complete PDF file for download available
-
gavrasm (Free AVR assembler) in improved version 0.7 for download. Corrects an error with AT90S1200, adds the new AVR 20.10.2003 type ATtiny2313, provides IFDEVICE directive for type-specific code.
-
New version of the convenient window caller for the command line 09.09.2003 assembler, improved editor for include files, viewing the list file, error finder, etc. for free download here.
-
gavrasm (Free AVR assembler) in improved version 0.6 for 03.09.2003 download. Corrects an error with negative numbers, adds several AVR types, provides nested IF/ELSE/ENDIF.
-
Convenient window caller for the command line assembler, simple 26.08.2003 editor for the source file, viewing the list file, etc. for free download here. gavrasm (Free AVR assembler) in improved version 0.5 for 16.08.2003 download. Corrects an error in the instruction set of AT90S1200 in previous versions.
-
gavrasm (Free AVR assembler) in improved version 0.4 for 21.07.2003 download. For convenient calling the assembler I have a window caller for free download.
-
14.06.2003
gavrasm (Free AVR assembler) in improved version 0.3 for download.
-
31.05.2003
gavrasm (Free AVR assembler) in improved version 0.2 for download
-
17.05.2003
Analog-to-Digital-Conversion using the analog comparator and timer/counter 1 of the AT90S8515 on board a STK500
ADC8.asm
09.05.2003 Fixed decimal point numbers
8-Bit ADC 10-Bit ADC
24.12.2002 Free AVR assembler for download
-
14.09.2002 Introduction to Studio Version 4
-
23.08.2002 Hardware programming equipment for the beginner
-
11.08.2002 Creating tables in the program flash memory
-
13.04.2002
Lcd4IncE.asm Lcd4IncCE.asm
Connecting a 2-line-LCD to a STK500 port, with a date/time software clock
All instructions and many terms in the assembler source files (HTML format) of the example pages are now linked to the 02.02.2002 description in the beginner course, so you can easily have more explanation on them.
-
02.02.2002 Added a page on assembler directives and expressions
-
Added number format conversion tutorial and routines and 06.01.2002 restructured the calculation pages, removed several minor HTML syntax errors.
CONVERT.asm
Renewed all assembler source files: commands in lower case letters to be more compatible with the editor from ATMEL (which still is source not as advanced - compared to Tan's -, let me know if you need the file 03.01.2002 Linux FPK or Win-Delphi Pascal sources for the self-written index software if you have a similiar job to do), added a new index page to all source files 16.12.2001
Binary math (multiplication and division)
MULT8E.asm DIV8E.asm
01.12.2001
Moved these pages from http://www.dg4fac.de to this new location at http://www.avr-asm-tutorial.net due to elevated traffic.
-
10.07.2001 Structure of asm source code
8515STD.asm
24.09.2001 Intro to the studio version 3.52
-
12.08.2001 Beginner's introduction to AVR assembler language
-
14.01.2001
DCF synchronised clock with serial interface in a 2313
CLOCK.asm
Echoes serial characters back as hex, for the STK200 board
SIOHEX.asm
Small application page
-
23.12.2000 PCM to Analogue Decoder for remote control systems
09.12.2000 Examples for the use of macros in assembler language!
Top of that page
Index
Download
avr-source
AVR-Webring
Known and corrected bugs Date
File(s)
Error description
Status
Thanks
10.01.2009 keyboard.html
Error in source code for keypad
corrected Carl Rheinlaender
26.08.2007 switch_schem.gif
Akkuload-Analog schematic: Error in wiring of channel 3
corrected Jonny Bijlsma
02.05.2005 akkucalc.asm
Akkuload: Caused by a serious bug in the calculation routine, the Corrected. Sebastian Mazur currents are by a factor of roughly two too small, and are displayed false.
06.01.2005
If you use gavrasm for assembling, and if you add more than one ORG directive within the code, and if you use PonyProg for burning the Hex- and Eep-files to the AVR, the shift in adress, caused by the ORG directive, is implemented in INTEL-Hex-format, but ignored by Pony-Prog. Be cautious when using more than one directive.
gavrasm and Pony-Prog
Pending
(Self)
False description of the BLD and Corrected Mark BST instruction
01.10.2004 CALC.html 05.12.2003
Lcd4IncE Lcd4IncCE
Bug in the LCD-Clock asm file prevented compilation
05.07.2003
FP_CONV10, HTML A missing code line caused .ASM calculation errors
Corrected Dan Carroll Corrected
Thilo Mölls
24.12.2002 exp2313.gif
Corrected the pullup resistor in the experimental circuit, due to occasional reset problems reported, to a lower value
Corrected
Andreas Wander
24.12.2002 (several)
Corrected an error in the source code for the 4-Bit-LCD software
Corrected
Jan de Jong
15.07.2002 DIVISION.html
Elapsed processor time with wrong dimension
Corrected Armin Kniesel
Two int vectors missing!
Corrected -
The interrupt routine has a serious bug. The result is an unhandled interrupt, delaying execution of the program by approx. a factor of 30. As this routine requires external SRAM and so doesn't work with STK500, I removed these codes and will not provide a debugged routine instead.
RTS and CTS between the plug and the line driver were exchanged. The plug is to be Wim connected to the PC using a Corrected Korevaar crossed link for RD/TD and RTS/ CTS. PB4 must be set to 0 to activate the CTS line.
bcdmath.inc 25.11.2001 sioint.inc testsint.asm
Download of the inc-files results in an error message from the server. These files were renamed to .asm and the calling asm file corrected.
Corrected
Axel Rühl
24.09.2001 (Several)
Errors using .DEF and .EQU
Corrected
Stefan Beyer
When working with a DCF77 signal the seconds are incorrect (59.th second is already 0).
Open!
-
Error in the calculation of tens of monthes from the DCF77 signals
Corrected
Thomas Baumann
(Several)
Some minor additions and changes in the text
corrected
Frank Dalchow
TEST1.html TEST1.asm
Some translation errors in the text corrected
Brian Tangney
CLOCK.html CLOCK.asm 03.06.2001
TEST1.html 02.12.2000 TEST1.asm
Top of that page
It was stated that the frequency of Timo the LEDs is 800 kHz. In fact it is corrected Engelmann only 667 kHz!
Index
Download
avr-source
Error list
AVR-Webring The AVR webring provides hundreds of links to AVR related webpages. Please have a look at these if you search for more informations on AVRs. This page is member in the AVR-Webring: AVR-Webring [ Join Now | Ring Hub | Random | << Prev | Next >> ]
Pfad: Home => AVR-Übersicht Tutorial für das Erlernen der Assemblersprache von
AVR-Einchip-Prozessoren (AT90S, ATmega, ATtiny) von ATMEL anhand geeigneter praktischer Beispiele. Die Einchip-Prozessoren von ATMEL eignen sich hervorragend für den Eigenbau prozessorgesteuerter Elektronik. Einzige Hürde ist dabei die Assembler-Sprache, mit der die vielseitigen Winzlinge zu programmieren sind. Wenn man die ersten Hürden überwunden hat, wird man allerdings mit den sehr schlanken, sehr schnellen und ereignisgesteuerten Programmen jeden Ablauf in den Griff bekommen. Diese Seite wendet sich an Anfänger und hilft bei den ersten Schritten. Sitemap
Neu auf dieser Seite
Fehlerhinweise
asm-Sourcen
AVR-Webring
Inhalt Ausführliche allgemeine Einführung mit allen Werkzeugen, Befehlsbeschreibungen, Befehls- und Porttabellen, u.v.a.m.! Als komplette PDF-Datei (64 Seiten) zum Ausdrucken (Download ca. 856 kB)
Präsentation der AVRMikroprozessoren im PDFFormat mit praktischen Beispielen für den ATtiny13 und mit Assembler-Quelltext
Vier einfache, ausführlich kommentierte Programmbeispiele, für Anfänger als erste Schritte. Sinn, Zweck und Voraussetzungen der Beispiele, vier einfache Programmierbeispiele zum Erlernen von Assembler
Das Werkzeug: ein Kommandozeilen-Assembler in deutscher Sprache für DOS, Win32 und Linux(i386) zum Download, mit Fehlerkommentierung für Anfänger und mit vielen Extras!
Alles über Zeitschleifen, mit dem beliebten Sekundenblinker
Alles über Interrupts und Interrupt-Programmierung
Ein Windows-Programm zum komfortablen Aufruf des Kommandozeilen-Assemblers, zum Editieren der Source- und IncludeDateien, zum Anzeigen der Listdatei, zur komfortablen Fehlersuche, u.a.m. zum Download
Ausführliche Erklärungen und Routinen zu den Themen Multiplikation, Division, Zahlenumwandlung und Festkommazahlen in Assembler mit Beispielen (binär, dezimal, hex, ASCII), HardwareMultiplikation
Ansteuerung von Peripherie am Beispiel des STK200: Programmierung und Testen der Hardware auf dem STK-Board: EEPROM, externes RAM, LCDDisplay, SIO-Schnittstelle
Anschluss einer 2-Zeilen-LCD an das STK500 mit Basisroutinen und Beispielprogramm für eine Uhr
Aufbau eines 8-Bit-AD-Wandlers mit dem eingebauten Analogkomparator und dem Timer/Counter1 am STK500 Programmierboard, mit ausführlicher Beschreibung und Software in HTMLForm und als Assembler Quellcode
Anschluss einer 12-er-Tastatur an einen AVR und Auslesen mittels Portansteuerung oder mit Widerstandsmatrix und einem AD-Wandler
Umwandlung eines Digitalwerts in eine analoge Spannung mit einem gepufferten R/2R Netzwerk, einschließlich Erzeugung von Wellenformen wie Sägezahn, Dreieck, Sinus (mit Sinustabelle) und einem einfachen Musiknotenspieler
Sammlung von kommentierten Anwendungen: DCF77-Uhr, PCMDecoder, PulsweitenRechteckgenerator mit seriellem Interface, Rechteckgenerator mit Potieinstellung und LCD, Frequenzzähler mit Frequenz-, Perioden-, Periodenanteils-, Umdrehungs- und Spannungsmessung, Eieruhr zum Verschenken, Schrittmotor-Steuerung, etc.
Akkulader mit einem ATmega16
Abdruck einer Artikelserie in der Amateurfunkzeitschrift cq-dl des Deutschen Amateur-Radio Club DARC: ●
●
●
●
Gezipptes Abbild dieser Seite herunterladen (ca. 3,8 MB) und in ein Verzeichnis auf dem eigenen Rechner entpacken (ca. 20 MB). Dabei unbedingt die Verzeichnisse des Pakets beibehalten!
Teil I: Hardware von AVRProzessoren Teil II: Software von AVRProzessoren Teil III: Programmieren der Prozessoren Teil IV: Morse-Ausgabe mit AVR-Prozessor
Seitenanfang
Inhalte
Fehlerhinweise
asm-Sourcen
AVR-Webring
Neu auf dieser Seite seit:
Beschreibung und Link
Sourcecode
Verbesserte Version 3 des Frequenzzählers mit Frequenz-, 09.01.2009 Perioden-, Periodenanteil-, Umdrehungs- und Spannungsmessung mit ATmega8
fcountV03
Aktualisierte und verbesserte Version des Anfängerkurses in 07.01.2009 einem PDF-Dokument Update des gezippten Abbildes der Webseite
-
23.12.2008
gavrasm Assembler in Version 2.2 (deutsch, englisch und französisch, für 115 AVR-Typen) zum kostenlosen Download
-
28.09.2008
Erweiterung der Hardware-Multiplikation um 16-mit-24-BitMultiplikation
-
25.05.2008 Zeitschleifen, Tonausgabe mit Lautsprecher, LED-Blinker
-
25.05.2008 Interrupts und Interrupt-Programmierung
-
20.01.2008 Hardware-Multiplikation mit ATmega
-
28.06.2007 Schrittmotor-Steuerung mit einem ATtiny13
schrittmotor_v1.asm
02.12.2006 gavrasm Assembler in Version 2.1 zum kostenlosen Download
-
29.10.2006 ATtiny2313-Eieruhr
eieruhr.asm
29.09.2006 gavrasm Assembler in Version 2.0 zum kostenlosen Download
-
13.08.2006 gavrasm Assembler in Version 1.9 zum kostenlosen Download
-
gavrasm Assembler in Version 1.8 zum kostenlosen Download 16.07.2006 Persische Version des Anfängerkurses auf der neuen DownloadSeite.
-
17.06.2006
Frequenzzähler mit Frequenz-, Perioden-, Periodenanteil-, Umdrehungs- und Spannungsmessung mit ATmega8
fcountV02 Gezippter Quellcode
25.05.2006 Einstellbarer Rechteckgenerator mit Potieinstellung und LCD 04.05.2006
gavrasm Assembler in Version 1.7 zum kostenlosen Download. Korrigiert einen Fehler bei der Behandlung der ELIF-Direktive.
15.4.2006
Präsentation der AVR-Mikroprozessoren an Beispielen mit dem ATtiny13
(diverse)
23.2.2006
Anschluss einer 12-er-Tastatur an einen AVR und Auslesen mittels I/O-Leitungen oder einen AD-Wandler
-
28.12.2005
gavrasm Assembler in Version 1.6 zum kostenlosen Download. Unterstützt neue CAN, Tiny- und einen neuen Mega-Typ.
-
27.9.2005
gavrasm Assembler in Version 1.5 zum kostenlosen Download. Beseitigt zwei kleine Fehler.
-
28.3.2005
Hardware und Assembler-Software für ein Akkuladegerät für bis zu vier einzelnen Zellen
akkuload.asm, gezippt
Beschreibung der fortgeschrittenen Direktiven zur bedingten 28.3.2005 Assemblierung und des Linksschiebens bei Portbit-Angaben im Anfängerkurs
-
gavrasm Assembler in Version 1.3 zum kostenlosen Download. Beseitigt eine falsche Angabe der EEPROM-Größe bei zwei 27.3.2005 ATmega-Typen und implementiert die neuen ATmega 640, 1280, 1281, 2560 und 2561. 8.3.2005
gavrasm Assembler in Version 1.2 zum kostenlosen Download. Beseitigt einige kleine Fehler und implementiert die neuen AVR- Typen ATtiny25, 45 und 85.
6.1.2005
gavrasm Assembler in Version 1.1 zum kostenlosen Download. Beseitigt einige kleine Fehler und implementiert die Verwendung des Programmcounters PC. Sägezahn1 Sägezahn2 Sinus Sinus-Tabelle Musik
R/2R-Widerstandsnetzwerk als Digital-zu-Analog-Wandler, mit einigen einfachen Anwendungen
4.1.2005
gavrasm Version 1.0 mit einem kleinen Fix 9.10.2004 Neue Version des Windows-Helfers zum Assemblieren, an Version 1.0 von gavrasm angepasst und mit deutscher Hilfe
-
gavrasm Assembler in Version 1.0 zum kostenlosen Download. Unterstützt die neuen Typen ATmega325/3250/645/6450, viele 3.10.2004 neue Direktiven und erzeugt eine zusätzliche Datei mit allen Fehlermeldungen.
-
gavrasm Assembler in Version 0.9 zum kostenlosen Download. Unterstützt die neuen Typen ATmega48/88/168.
-
gavrasm Assembler in Version 0.8 zum kostenlosen Download mit kleinen Korrekturen. Außerdem eine neue Version des 15.02.2004 Window Callers zum komfortablen Umgang mit dem Kommandozeilen-Assembler zum kostenlosen Download
-
Anfängerkurs als komplette Datei im PDF-Format (Download, (78 Seiten, 850 kB)).
-
28.03.2004
30.11.2003
gavrasm Assembler in Version 0.7 zum kostenlosen Download. Korrigiert einen Fehler beim AT90S1200, unterstützt jetzt auch 20.10.2003 den neuen Typ ATtiny2313, IFDEVICE-Direktive für typspezifischen Code.
-
Eine neue Version des Window Callers zum komfortablen 09.09.2003 Umgang mit dem Kommandozeilen-Assembler zum kostenlosen Download
-
gavrasm Assembler in Version 0.6 zum kostenlosen Download. 03.09.2003 Korrigiert einen Fehler beim Rechnen mit negativen Zahlen, unterstützt mehr AVR-Typen, verschachtelte IF/ELSE/ENDIF.
-
26.08.2003
Ein Window Caller zum komfortablen Umgang mit dem Kommandozeilen-Assembler zum kostenlosen Download
-
16.08.2003
gavrasm Assembler in Version 0.5 zum kostenlosen Download. Korrigiert einen Fehler im Instruktionsset von AT90S1200.
-
gavrasm Assembler in Version 0.4 zum kostenlosen Download. 21.07.2003 Ein Windows- Caller zur Vereinfachung des Aufrufs steht ebenfalls zum kostenlosen Download.
-
14.06.2003 gavrasm Assembler in Version 0.3 zum kostenlosen Download.
-
31.05.2003
gavrasm (freier AVR Assembler) in Version 0.2 zum kostenlosen Download.
17.05.2003 8-Bit-AD-Wandler am STK500 Board
adc8.asm
09.05.2003 Rechnen mit Festkommazahlen
8-Bit-ADC 10-Bit-ADC
24.12.2002
Endlich fertig, der Assembler für den Anfänger: zum kostenlosen Download.
14.09.2002 Einführung in den Umgang mit dem Simulator Studio 4 20.08.2002
-
Einfache Hardware zum Programmieren und Experimentieren für den Anfänger
11.08.2002 Erstellen von Tabellen im Programm-Flash-Speicher 11.04.2002
Lcd4Inc.asm Lcd4IncC.asm
Ansteuerung einer 2-Zeilen-LCD-Anzeige mit dem STK500board mit Uhrprogramm
Alle Befehle und Stichwörter der Assembler-Quelltexte in 02.02.2002 HTML bei den Beispielen sind jetzt mit den Erläuterungen im Beginner-Kurs verlinkt.
-
02.02.2002 Liste aller Assemblerdirektiven und Ausdrücke
-
05.01.2002
Routinen zur Zahlenumwandlung Dezimal, BCD, Binär und Hexadezimal
konvert.asm
In allen Quellcode-Dateien wurden Assembler-Instruktionen jetzt in Kleinschreibung umgewandelt, weil der Editor von ATMEL noch immer keine grossbuchstabigen Instruktionen erkennt (ist Index nicht so schlau wie der von Tan, wer die selbst geschriebene aller 03.01.2002 Software in Linux FPK Pascal oder Win-Delphi braucht, um eine Quelldateien ähnliche Ochsentour zu vermeiden, melde sich bei mir). Außerdem gibt es jetzt eine Index-Seite mit Links zu allen Quelldateien. 16.12.2001
Grundrechenarten in Assembler (Multiplikation und Division)
mult8.asm div8d.asm
1.12.2001
Umzug dieser Seiten von http://www.dg4fac.de nach hier: http:// www.avr-asm-tutorial.net, bedingt durch sehr viel Webverkehr.
-
7.10.2001 Struktur eines Assemblerprogrammes mit Vorlage für den 8515
8515std.asm
24.9.2001 Einführung in die Studio Version 3.52
-
10.6.2001 Werkzeuge zur Assemblerprogrammierung
-
25.2.2001 Allgemeine Einführung
-
DCF77-synchronisierbare Uhr mit serieller Schnittstelle im 2313 14.01.2001 SIO-Testprogramm mit Hexadezimalcode-Echo für STK200 Board
siohex.asm
Kleine Anwendungsseite
-
23.12.2000 PCM zu Analog Decoder für Fernsteuerungen
09.12.2000 Beispiele für die Anwendung von Makros in Assembler!
Seitenanfang
dcf77uhr.asm
Inhalte
AVR-Webring
Bekannte und korrigierte Fehler: Datum
Datei(en)
Fehlerbeschreibung
Status
Dank
09.01.2009 HTML-Seite
Fehler beim Auslesen der Zehnertastatur über einen Portanschluss
korrigiert Carl Rheinländer
Beschreibung 09.01.2009 Quellcode html-listing
Fehler bei der AutorangeImplementierung im Frequenzzähler mit ATmega8
in v3 korrigiert
26.08.2007 switch_schem.gif
Akkulader-Analogteil: Fehler im Schaltbild: vertauschen AD-Wandler- korrigiert Jonny Bijlsma Anschlüsse bei Kanal 3
02.02.2005 akkucalc.asm
Akkuload: Fehler in der Umrechnung gemessener Spannungen in Ströme führt zu halbem Ladestrom und falscher Anzeige
korrigiert Sebastian Mazur
Wird gavrasm zum Assemblieren verwendet und darin eine weitere, zweite ORG-Direktive ausgeführt, anschließend mit Pony-Prog die Hexund Eep-Datei eingelesen, dann wertet Pony-Prog die geänderte Adresse im Intel-Hex-Format nicht korrekt aus, Pony-Prog ignoriert das ORG. Vorsicht bei der Verwendung solcher Konstruktionen!
offen
(selbst)
fp_conv10, HTML Fehler bei der 10-bit-AD-WandlerUmrechnung in 4-digit-Fließomma, 05.07.2003 fp_conv_10, ASM verursachte Rechenfehler
Korrigiert
Thilo Mölls
24.12.2002 exp2313.gif
Pullup-Widerstand in der Schaltung verkleinert, weil gelegentlich Probleme beim Reset auftreten
Korrigiert
Andreas Wander
24.12.2002 (diverse)
Einige Link-Fehler im BeginnerTutorial (Portbeschreibungen) sowie Jan de Korrigiert einen Fehler im Uhrenquellcode der 4Jong Bit-LCD-Steuerung beseitigt
15.07.2002 division.html
Fehler bei der Angabe der Prozessorzeit
Behoben Armin Kniesel
29.04.2002 test2.html
Fehler bei der Beschreibung der Datenrichtungsregister
Die Interrupt-Service-Routine enthält einen schweren Bug, der zu dauerhaft unbehandelten Interrupts des UARTs führt, die die weitere Bearbeitung um etwa den Faktor 30 verlangsamt! Da Entfernt die Routinen externes SRAM erfordern und deshalb ohnehin nicht mit dem STK500 zusammen spielen, werde ich vorerst keine ausgebesserte Version dafür schreiben.
06.01.2005
16.02.2002
23.12.2001
gavrasm mit Pony-Prog
8515std.html 8515std.asm
clock.gif clock.pdf
(Selber gemerkt)
RTS und CTS Verbindung zwischen dem 9-poligen Stecker und dem Pegelwandler ist vertauscht eingezeichnet. Die Anschlsse sind mit Wim einem gekreuzten Anschlusskabel Korrigiert Korevaar (RD/TD, RTS/CTS) mit dem PC zu verbinden. Portbit PB4 muss auf 0 gesetzt werden, damit CTS aktiviert wird!
bcdmath.inc 25.11.2001 sioint.inc testsint.asm
Die beiden inc-Dateien lassen sich nicht von der Webseite laden. Die Dateien wurden in .asm umbenannt und die aufrufende Quelldatei korrigiert.
Korrigiert
Axel Rühl
24.9.2001 (Diverse)
Falsche Verwendung von .DEF und . EQU Instruktionen
Korrigiert
Stefan Beyer
1. Falsche Verwendung des LDIBefehls für R1 2. Falsche Angabe der Verzögerung bei Delay10
Korrigiert caswi
sprung.html
03.06.2001
02.12.2000
dcf77uhr.html dcf77uhr.asm
Bei DCF77-Empfang falsche Ausgabe Offen! der Sekunden (59. ist bereits 0)
-
Fehlerhafte Berechnung der Thomas Korrigiert Monatszehner aus dem DCF77-Signal Baumann
(Diverse)
Kleinere Fehler und Ergänzungen im Text
Korrigiert
Frank Dalchow
test1.html test1.asm
Es wurde behauptet, die Blinkfrequenz der LEDs betrüge 800 kHz. Tatsächlich sind es nur 667 kHz.
Korrigiert
Timo Engelmann
Seitenanfang
Inhalte
asm-Sourcen
Fehlerhinweise
AVR-Webring Im AVR-Webring sind hunderte Webseiten versammelt, die sich mit den AVR befassen. Diese Seite ist Mitglied im AVR-Webring: AVR-Webring [ Mitglied werden | Ring Hub | Zufallsauswahl | Vorheriger | Nächster ]
Sitemap der Webseite http://www.avr-asm-tutorial.net
Sitemap der Webseite http://www.avr-asmtutorial.net in deutsch Hauptseite Deutsche Leitseite ●
●
●
●
●
●
●
●
●
● ●
●
●
●
●
●
●
●
Anfänger's Einführung in AVR-Assembler ❍ Gesamter Kurs als PDF beginner_de.pdf ❍ Hardware ISP-Programmier-Interface ❍ Warum in Assembler programmieren? ❍ Werkzeuge für die AVR-Assembler-Programmierung ■ Erste Schritte mit dem Studio 4 ■ Erste Schritte mit dem Studio 3.52 ■ Direktiven in Assembler ■ Struktur von AVR-Assembler-Programmen ■ Standard 8515 Programm-Datei Struktur ■ Programmplanung ❍ Verwendung von Registern in Assembler ❍ Verwendung von Ports in Assembler ■ Port Details im AT90S8515 ❍ Verwendung von SRAM in Assembler ❍ Relative und absolute Sprünge, Reset- und Interrupt-Vektoren ❍ Rechnen in Assembler ❍ Tabellen: Instruktionen, Abkürzungen, Ports, Vektoren, Direktiven, Ausdrücke Binäres Rechnen in AVR Assembler ❍ Binäres Multiplizieren zweier 8-Bit-Zahlen in AVR Assembler ❍ Assembler Quelltext der Multiplikation ❍ Division einer 16-Bit-Zahl durch eine 8-Bit-Zahl ❍ Assembler Quellcode der Division ❍ Zahlenumwandlung in AVR-Assembler ❍ Quelltext der Zahlenumwandlungsroutinen ❍ Umgang mit Festkommazahlen in AVR Assembler ❍ Assembler Quelltext der Umwandlung einer 8-Bit-Zahl in eine dreistellige Festkommazahl ❍ Assembler Quelltext der Umwandlung einer 10-Bit-Zahl in eine vierstellige Festkommazahl ❍ Hardware-Multiplikation mit ATmega Vier einfache Programmierbeispiele als erste Schritte zum Lernen ❍ Test 1: Ausgabe an die Leuchtdioden. ❍ Test 2: Eingabe von einem Port ❍ Test 3: Timer im Polling mode ❍ Test 4: Timer im Interupt mode Zeitschleifen ❍ 8-Bit-Zeitschleifen ■ Hardware für die Tonausgabe über Lautsprecher ❍ 16-Bit-Zeitschleifen ■ Hardware für den LED-Blinker Einführung in die Interrupt-Programmierung ❍ Interrupt-Vektoren ❍ Interruptquellen ❍ Programmablauf bei Interrupts ■ Grafik Ablauf von Interrupt-Programmen ■ Grafik Ablauf bei mehreren Interrupts ❍ Interrupt und Ressourcen AVR-Hardware-Testroutinen für einen AT90S8515 auf dem STK200 ❍ Vorrausetzungen für die Beispiele ❍ Demonstriert den Gebrauch des EEPROMs ❍ TestRam testet exteres RAM auf dem STK-200 board ❍ Tested eine angeschlossene LCD-Anzeige ■ Include-Routinen zur LCD-Ansteuerung ■ Uhrenanwendung zum LCD-Test ❍ Testet die Serielle Schnittstelle auf dem Board ■ Hardware der Verbindung des Boards mit dem PC ■ SIO-Software ■ Hex-Echo über SIO ❍ Anschluss eines Keyboards an Port B Kleine AVR-Anwendungen ❍ DCF77 Uhr mit dem AVR-Controller AT90S2313 ■ Schaltbild clock.pdf ❍ Decoder für Fernsteuersignale mit AT90S2313 ■ Software für PCM-Decoder ■ aBLAUFDIAGRAMM pcm2flow.pdf ■ Schaltbild pcm2pwg4.pdf ❍ ANSI-Terminal programmierbarer Signalgenerator ■ Quellcode für den Pulsweiten-Generator ❍ Einstellbarer Rechteckgenerator mit ATmega8 und LCD-Anzeige ■ Quellcode Hauptprogramm, html ■ Quellcode LCD-Routinen, html ■ Quellcode Frequenztabelle, html ■ Quellcode alles, asm ■ Schaltbild ❍ Frequenzzähler mit ATmega8 und LCD-Anzeige ■ Quellcode Programm, html ■ Quellcode Programm, asm ■ Schaltbild Prozessorteil ■ Schaltbild Vorverstärker, Vorteiler ■ ATmega8 Fuses, Studio, Teil 1 ■ ATmega8 Fuses, Studio, Teil 2 ■ ATmega8 Fuses, PonyProg ❍ ATtiny2313-Eieruhr zum Verschenken ■ Quellcode, html ■ Quellcode, asm ■ Beispiel-Anleitung, Open-Office-Format ■ Beispiel-Anleitung, Rich-Text-Format ■ Schaltbild, GIF, groß ■ Schaltbild, GIF, verkleinert ❍ Steppermotorsteuerung mit ATtiny13 ■ Quellcode im .asm-Format ■ Quellcode im .html-Format ■ Schaltbild Steuerung im .gif-Format ■ Schaltbild Steuerung im .pdf-Format ■ Bild der Steuerung ■ Schaltbild Netzteil im .gif-Format ■ Schaltbild Netzteil im .pdf-Format ■ Bild Netzteil ■ Bild Schrittmotor ■ Bild Steuerung und Schrittmotor, klein Anschluss einer 2-zeiligen LCD-Anzeige an das STK500 ❍ Include-Routinen für LCD-Anzeige am STK500 ❍ Uhrenanwendung für LCD-Anzeige an das STK500 AVR-PWM-ADC für STK500 ❍ Software für PWM-ADC R/2R-Netzwerk als DAC für einen AVR Spezielles Programmier-Know-How ❍ Anwendung der LPM-Instruktion ❍ JUMP über den Stack ❍ Makro-Befehl-Beispiel ❍ Sprungziele in Makros ❍ Parameter-Übergabe an Makro Anschluss einer 12-er-Tastatur an einen AVR ❍ Open-Office Spreadsheet ❍ Excel Spreadsheet Präsentation der AVR-Mikrokontroller mit Anwendungsbeispielen ❍ Teil_1_Prozessoren.pdf ❍ Teil_2_BitsAndBytes.pdf ❍ Teil_3_Befehle.pdf ❍ Teil_4_AufbauTiny13.pdf ❍ Anwendungsbeispiele.pdf ❍ Teil_5b_Beispiel01_02.pdf ❍ Teil_6_Beispiel03_07.pdf ❍ Teil_7_Beispiel08_10.pdf ❍ Teil_8_Beispiel_11.pdf ❍ Teil_5a_UebersichtBeispiele.pdf ❍ Quellcode der Beispiele: ■ bsp01_loop.asm ■ bsp02_led.asm ■ bsp03_blink.asm ■ bsp04_blink_langsam.asm ■ bsp04_blink_langsam_takte.asm ■ bsp04_blink_langsam_tn12.asm ■ bsp05_blink_Timer.asm ■ bsp05_blink_Timer_kurz.asm ■ bsp06_lsp.asm ■ bsp07_keyint.asm ■ bsp08_morsekey.asm ■ bsp09_adcmorsekey.asm ■ bsp10_morsebake.asm ■ bsp11_1750_SinePwm.asm AVR-Einführung für Funkamateure ❍ Teil 1: Eigenschaften von AVR-Mikrokontrollern ■ Schaltbild mit 2313 ■ Anschlüsse eines 2323 ❍ Teil 2: Software der Prozessoren ■ Quellcode: beispiel.asm ❍ Teil 3: Programmieren der Prozessoren ■ ISP-Interface zum Programmieren ❍ Teil 4: Beispielanwendung CW-Geber ■ Schaltbild des CW-Gebers ■ Quellcode für 2313: Cw01.asm ■ Listing für 2313: Cw01.html ■ Quellcode für STK500: Cw01_500.asm ■ Listing der Software für STK500 ■ Quellcode für STK200: Cw01_200.asm ■ Listing für STK200: Cw01_200.html Gerd's AVR Assembler ❍ Deutsche Download-Seite ❍ LiesMich.Txt ❍ gavrasm_lin_de_22.zip ❍ gavrasm_win_de_22.zip ❍ gavrasm_dos_de_22.zip ❍ DosRead.Txt ❍ gavrasm_sources_doswin_22.zip ❍ gavrasm_sources_lin_22.zip ❍ Alle Instruktionen: instr.asm ❍ Alle Instruktionen (DOS/WIN-Format): instr_doswin.asm ❍ All Instruktionen (Linux Format): instr_linux.asm ❍ Einführung in Gerd's AVR Assembler Gerd's AVR Assembler Caller ❍ Software (Win): gavrasmW.zip ❍ LiesMich.txt Akkuload - ein Mikroprozessor-gesteuertes Ladegerät ❍ Akkulader_Beschreibung.pdf ❍ Deutsche Download-Seite ■ Quellcode gesamt: akkuload.zip ■ Quellcode Hauptprogramm: akkuload.asm ■ Quellcode akkuuart.asm ■ Quellcode akkucalc.asm ■ Quellcode akkukey.asm ■ Quellcode akkulcd.asm ❍ Schaltbild Prozessorteil, GIF-Format ❍ Schaltbild Analogteil, GIF-Format ❍ Schaltbild Prozessorteil, PDF-Format Links zu allen Quelldateien dieses Tutorials ❍ 8515std.asm ❍ adc8.asm ❍ avr_pwm1.pas ❍ dcf77uhr.asm ❍ div8d.asm ❍ eieruhr.asm ❍ fp_conv10_de.asm ❍ fp_conv8_de.asm ❍ konvert.asm ❍ lcd_inc.asm ❍ lcd_test.asm ❍ Lcd4Inc.asm ❍ Lcd4IncC.asm ❍ mult8.asm ❍ musik.asm ❍ pcm2pwg4.asm ❍ pwgsio2.asm ❍ r2r.pas ❍ sawtooth1.asm ❍ sawtooth2.asm ❍ sine8_25.txt ❍ sinewave.asm ❍ sinewave.pas ❍ siohex.asm ❍ test1.asm ❍ test2.asm ❍ test3.asm ❍ test4.asm ❍ testeep.asm ❍ testjmp.asm ❍ testkbd.asm ❍ testlcd.asm ❍ testlpm.asm ❍ testmac1.asm ❍ testmac2.asm ❍ testmac3.asm ❍ testram.asm ❍ testsio.asm ❍ triangle.asm
http://www.avr-asm-tutorial.net/sitemap_de.html (1 of 2)1/20/2009 7:29:18 PM
Sitemap der Webseite http://www.avr-asm-tutorial.net
Warum Asm? Werkzeuge Edit Asm ISP Studio3 Studio4 Struktur
Programmiertechnik für Anfänger in AVR Assemblersprache Die folgenden Seiten wenden sich an Menschen, die das erste Mal in Assembler programmieren möchten und mit der Programmierung von ATMEL-AVRs AT90Sxxxx beginnen wollen. Es werden ein paar grundlegende Techniken vorgestellt, die mit der Zeit und mit fortgeschrittener Programmierübung immer wieder benötigt werden und mit denen man sich daher zu Beginn vertraut machen sollte. Hier kann man auch das eine oder andere nachschlagen und in Tabellen wühlen.
Themenüberblick der Programmiertechniken Thema Hardware
Pfad: Home => AVR-Überblick => Programmiertechniken => Menue
Programmiertechnik für Anfänger in AVR Assemblersprache Die folgenden Seiten wenden sich an Menschen, die das erste Mal in Assembler programmieren möchten und mit der Programmierung von ATMEL-AVRs AT90Sxxxx beginnen wollen. Es werden ein paar grundlegende Techniken vorgestellt, die mit der Zeit und mit fortgeschrittener Programmierübung immer wieder benötigt werden und mit denen man sich daher zu Beginn vertraut machen sollte. Hier kann man auch das eine oder andere nachschlagen und in Tabellen wühlen.
Befehle ADC r1,r2 ADD r1,r2 ADIW rd,k63 AND r1,r2 ANDI rh,k255, Register ASR r1 BLD r1,b7 BRCC k127 BRCS k127 BREQ k127 BRGE k127 BRHC k127 BRHS k127 BRID k127 BRIE k127 BRLO k127 BRLT k127 BRMI k127 BRNE k127 BRPL k127 BRSH k127 BRTC k127 BRTS k127 BRVC k127 BRVS k127 BST r1,b7 CBI pl,b7 CBR rh,255, Register CLC CLH CLI CLN CLR r1 CLS CLT, Anwendung CLV CLZ COM r1 CP r1,r2 CPC r1,r2 CPI rh,k255, Register CPSE r1,r2 DEC r1 EOR r1,r2 ICALL IJMP IN r1,p1 INC r1 LD rp,(rp,rp+,-rp) (Register), SRAM-Zugriff, Ports LDD r1,ry+k63 LDI rh,k255 (Register), Pointer LDS r1,k65535 LPM LSL r1 LSR r1 MOV r1,r2 NEG r1 NOP OR r1,r2 ORI rh,k255 OUT p1,r1 POP r1, in Int-Routine PUSH r1, in Int-Routine RCALL k4096 RET, in Int-Routine RETI RJMP k4096 ROL r1 ROR r1 SBC r1,r2 SBCI rh,k255 SBI pl,b7 SBIC pl,b7 SBIS pl,b7 SBIW rd,k63 SBR rh,255, Register SBRC r1,b7 SBRS r1,b7 SEC SEH SEI, in Int-Routine SEN SER rh SES SET, Anwendung SEV SEZ SLEEP ST (rp/rp+/-rp),r1 (Register), SRAM-Zugriff, Ports STD ry+k63,r1 STS k65535,r1 SUB r1,r2 SUBI rh,k255 SWAP r1 TST r1 WDR Zum Seitenanfang
Ports, alphabetisch ACSR, Analog Comparator Control and Status Register DDRx, Port x Data Direction Register EEAR, EEPROM Adress Register EECR, EEPROM Control Register EEDR, EEPROM Data Register GIFR, General Interrupt Flag Register GIMSK, General Interrupt Mask Register ICR1L/H, Input Capture Register 1 MCUCR, MCU General Control Register OCR1A, Output Compare Register 1 A OCR1B, Output Compare Register 1 B PINx, Port Input Access PORTx, Port x Output Register SPL/SPH, Stackpointer SPCR, Sreial Peripheral Control Register SPDR, Serial Peripheral Data Register SPSR, Serial Peripheral Status Register SREG, Status Register TCCR0, Timer/Counter Control Register, Timer 0 TCCR1A, Timer/Counter Control Register 1 A TCCR1B, Timer/Counter Control Register 1 B TCNT0, Timer/Counter Register, Counter 0 TCNT1, Timer/Counter Register, Counter 1 TIFR, Timer Interrupt Flag Register TIMSK, Timer Interrupt Mask Register UBRR, UART Baud Rate Register UCR, UART Control Register UDR, UART Data Register WDTCR, Watchdog Timer Control Register
Zum Seitenanfang
Verwendete Abkürzungen Die in diesen Listen verwendeten Abkürzungen geben den zulässigen Wertebereich mit an. Bei Doppelregistern ist das niederwertige Byte-Register angegeben. Konstanten bei der Angabe von Sprungzielen werden dabei automatisch vom Assembler aus den Labels errechnet. Kategorie Abk.
Pfad: Home => AVR-Überblick => Programmiertechniken => Register
Programmiertechnik für Anfänger in AVR Assemblersprache Was ist ein Register? Register sind besondere Speicher mit je 8 Bit Kapazität. Sie sehen bitmäßig daher etwa so aus: 7
6
5
4
3
2
1
0
Man merke sich die Nummerierung der Bits: sie beginnt immer bei Null. In einen solchen Speicher passen Zahlen von 0 bis 255 (Ganzzahl ohne Vorzeichen), von -128 bis +127 (Ganzzahl mit Vorzeichen in Bit 7), ein Acht-Bit- ASCII-Zeichen wie z.B. 'A' oder auch acht einzelne Bits, die sonst nix miteinander zu tun haben (z.B. einzelne Flaggen oder Flags). Das Besondere an diesen Registern (im Gegensatz zu anderen Speichern) ist, dass sie ●
● ●
direkt in Befehlen verwendet werden können, da sie direkt an das Rechenwerk, den Akkumulator, angeschlossen sind, Operationen mit ihrem Inhalt mit nur einem Befehlswort ausgeführt werden können, sowohl Quelle von Daten als auch Ziel des Ergebnisses der Operation sein können.
Es gibt 32 davon in jedem AVR. Auch der kleinste Typ hat schon so viele davon. Das macht die AVR ziemlich einzigartig, da dadurch viele Kopieraktionen und der langsamere Zugriff auf andere Speicherarten oft nicht nötig ist. Die Register werden mit R0 bis R31 bezeichnet, man kann ihnen mit einer Assemblerdirektive aber auch einen etwas wohlklingenderen Namen verpassen, wie z.B. .DEF MeinLieblingsregister = R16 Assemblerdirektiven gibt es einige (mehr Assemblerdirektiven gibt es hier), sie stellen RegieAnweisungen an den Assembler dar und erzeugen selbst keinen ausführbaren Code. Sie beginnen immer mit einem Punkt. Statt des Registernamens R16 wird dann fürderhin immer der neue Name verwendet. Das könnte also ein schreibintensives Programm werden. Mit dem Befehl LDI MeinLieblingsRegister, 150 was in etwa bedeutet: Lade die Zahl 150 in das Register R16, aber hurtig, (in englisch: LoaD Immediate) wird ein fester Wert oder eine Konstante in mein Lieblingsregister geladen. Nach dem Übersetzen (Assemblieren) ergibt das im Programmspeicher etwa folgendes Bild: 000000 E906 In E906 steckt sowohl der Load-Befehl als auch das Zielregister (R16) als auch die Konstante 150, auch wenn man das auf den ersten Blick nicht sieht. Auch dies macht Assembler bei den AVR zu einer höchst effektiven Angelegenheit: Instruktion und Konstante in einem einzigen Befehlswort und schnell und effektiv ausgeführt. Zum Glück müssen wir uns um diese Übersetzung des Befehlsworts nicht kümmern, das macht der Assembler für uns. In einem Befehl können auch zwei Register vorkommen. Der einfachste Befehl dieser Art ist der Kopierbefehl MOV. Er kopiert den Inhalt des einen Registers in ein anderes Register. Also etwa so: .DEF MeinLieblingsregister = R16 .DEF NochEinRegister = R15 LDI MeinLieblingsregister, 150 MOV NochEinRegister, MeinLieblingsregister Die ersten beiden Zeilen dieses großartigen Programmes sind Direktiven, die ausschließlich dem Assembler mitteilen, dass wir anstelle der beiden Registernamen R16 und R15 andere Benennungen zu verwenden wünschen. Sie erzeugen keinen Code! Die beiden Programmzeilen mit LDI und MOV erzeugen Code, nämlich: 000000 E906 000001 2F01 Der zweite Befehl schiebt die 150 im Register R16 in das Rechenwerk und kopiert dessen Inhalt in das Zielregister R15. MERKE: Das erstgenannte Register im Assemblerbefehl ist immer das Zielregister, das das Ergebnis aufnimmt! (Also so ziemlich umgekehrt wie man erwarten würde und wie man es ausspricht. Deshalb sagen viele, Assembler sei schwer zu lernen!) Zum Seitenanfang
Unterschiede der Register Schlaumeier würden das obige Programm vielleicht eher so schreiben: .DEF NochEinRegister = R15 LDI NochEinRegister, 150 Und sind reingefallen: Nur die Register R16 bis R31 lassen sich hurtig mit einer Konstante laden, die Register R0 bis R15 nicht! Diese Einschränkung ist ärgerlich, ließ sich aber bei der Konstruktion der Assemblersprache für die AVRs wohl kaum vermeiden. Es gibt eine Ausnahme, das ist das Nullsetzen eines Registers. Dieser Befehl CLR MeinLieblingsRegister ist für alle Register zulässig. Diese zwei Klassen von Registern gibt es ausser bei LDI noch bei folgenden Befehlen: ● ●
● ●
●
● ●
ANDI Rx,K ; Bit-Und eines Registers Rx mit einer Konstante K, CBR Rx,M ; Lösche alle Bits im Register Rx, die in der Maske M (eine Konstante) gesetzt sind, CPI Rx,K ; Vergleiche das Register Rx mit der Konstante K, SBCI Rx,K ; Subtrahiere die Konstante K und das Carry-Flag vom Wert des Registers Rx und speichere das Ergebnis im Register Rx, SBR Rx,M ; Setze alle Bits im Register Rx, die auch in der Maske M (eine Konstante) gesetzt sind, SER Rx ; Setze alle Bits im Register Rx (entspricht LDI Rx,255), SUBI Rx,K ; Subtrahiere die Konstante K vom Inhalt des Registers Rx und speichere das Ergebnis in Register Rx.
Rx muss bei diesen Befehlen ein Register zwischen R16 und R31 sein! Wer also vorhat, solche Befehle zu verwenden, sollte ein Register oberhalb von R15 dafür auswählen. Das programmiert sich dann leichter. Noch ein Grund, die Register mittels .DEF umzubenennen: in größeren Programmen wechselt sich leichter ein Register, wenn man ihm einen besonderen Namen gegeben hat. Zum Seitenanfang
Pointer-Register Noch wichtigere Sonderrollen spielen die Registerpaare R26/R27, R28/R29 und R30/R31. Diese Pärchen sind so wichtig, dass man ihnen in der AVR-Assemblersprache extra Namen gegeben hat: X, Y und Z. Diese Doppelregister sind als 16-Bit-Pointerregister definiert. Sie werden gerne bei Adressierungen für internes oder externes RAM verwendet (X, Y und Z) oder als Zeiger in den Programmspeicher (Z). Bei den 16-Bit-Pointern befindet sich das niedrigere Byte der Adresse im niedrigeren Register, das höherwertige Byte im höheren Register. Die beiden Teile haben wieder eigene Namen, nämlich ZH (höherwertig, R31) und ZL (niederwertig, R30). Die Aufteilung in High und Low geht dann etwa folgendermaßen: .EQU Adresse = RAMEND ; In RAMEND steht die höchste SRAM-Adresse des Chips LDI YH,HIGH(Adresse) LDI YL,LOW(Adresse) Für die Pointerzugriffe selbst gibt es eine Reihe von Spezial-Zugriffs-Kommandos zum Lesen (LD=Load) und Schreiben (ST=Store), hier am Beispiel des X-Zeigers: Zeiger
Vorgang
Beispiele
X
Lese/Schreibe von der Adresse X und lasse den Zeiger unverändert
LD R1,X ST X,R1
X+
Lese/Schreibe von der Adresse X und erhöhe den Zeiger anschließend um Eins
LD R1,X+ ST X+,R1
-X
Vermindere den Zeiger um Eins und lese/schreibe dann erst von der neuen Adresse
LD R1,-X ST -X,R1
Analog geht das mit Y und Z ebenso. Für das Lesen aus dem Programmspeicher gibt es nur den Zeiger Z und den Befehl LPM. Er lädt das Byte an der Adresse Z in das Register R0. Da im Programmspeicher jeweils Worte, also zwei Bytes stehen, wird die Adresse mit zwei multipliziert und das unterste Bit gibt jeweils an, ob das untere oder obere Byte des Wortes im Programmspeicher geladen werden soll. Also etwa so: LDI ZH,HIGH(2*Adresse) LDI ZL,LOW(2*Adresse) LPM Nach Erhöhen des Zeigers um Eins wird das zweite Byte des Wortes im Programmspeicher gelesen. Da die Erhöhung des 16-Bit-Speichers um Eins auch oft vorkommt, gibt es auch hierfür einen Spezialbefehl für Zeiger: ADIW ZL,1 LPM ADIW heisst soviel wie ADdiere Immediate Word und kann bis maximal 63 zu dem Wort addieren. Als Register wird dabei immer das untere Zeigerregister angegeben (hier: ZL). Der analoge Befehl zum Zeiger vermindern heißt SBIW (SuBtract Immediate Word). Anwendbar sind die beiden Befehle auf die Registerpaare X, Y und Z sowie auf das Doppelregister R24/R25, das keinen eigenen Namen hat und auch keinen Zugriff auf RAM- oder sonstige Speicher erm•glicht. Es kann als 16-Bit-Wert optimal verwendet werden. Wie bekommt man aber nun die Werte, die ausgelesen werden sollen, in den Programmspeicher? Dazu gibt es die DB- und DW-Anweisungen für den Assembler. Byteweise Listen werden so erzeugt: .DB 123,45,67,78 ; eine Liste mit vier Bytes .DB "Das ist ein Text. " ; eine Liste mit einem Text Auf jeden Fall ist darauf achten, dass die Anzahl der einzufügenden Bytes pro Zeile geradzahlig sein muss. Sonst fügt der Assembler ein Nullbyte am Ende hinzu, das vielleicht gar nicht erwünscht ist. Das Problem gibt es bei wortweise organisierten Tabellen nicht. Die sehen so aus: .DW 12345,6789 ; zwei Worte Statt der Konstanten können selbstverständlich auch Labels (Sprungadressen) eingefügt werden, also z.B. so: Label1: [... hier kommen irgendwelche Befehle...] Label2: [... hier kommen noch irgendwelche Befehle...] Sprungtabelle: .DW Label1,Label2 Beim Lesen per LPM erscheint übrigens das niedrigere Byte der 16-Bit-Zahl zuerst! Und noch was für Exoten, die gerne von hinten durch die Brust ins Auge programmieren: Die Register sind auch mit Zeigern lesbar und beschreibbar. Sie liegen an der Adresse 0000 bis 001F. Das kann man nur gebrauchen, wenn man auf einen Rutsch eine Reihe von Registern in das RAM kopieren will oder aus dem RAM laden will. Lohnt sich aber erst ab 5 Registern. Zum Seitenanfang
Empfehlungen zur Registerwahl 1. 2. 3. 4. 5.
Register immer mit der .DEF-Anweisung festlegen, nie direkt verwenden. Werden Pointer-Register für RAM u.a. benötigt, R26 bis R31 dafür reservieren. 16-Bit-Zähler oder ähnliches realisiert man am besten in R24/R25. Soll aus dem Programmspeicher gelesen werden, Z (R30/31) und R0 dafür reservieren. Werden oft konstante Werte oder Zugriffe auf einzelne Bits in einem Register verwendet, dann die Register R16 bis R23 dafür vorzugsweise reservieren. 6. Für alle anderen Anwendungsfälle vorzugsweise R1 bis R15 verwenden.
Pfad: Home => AVR-Überblick => Programmiertechniken => Direktiven/Ausdrücke
Programmiertechniken in AVR Assemblersprache Assembler-Direktiven Assembler-Direktiven steuern den Assembler, sie erzeugen für sich betrachtet keinen eigenen Code. Der einleitende Punkt muss in Spalte 1 beginnen. Segment
Direktive
Kopf
Beschreibung
.DEVICE
Definiert den Typ des Zielprozessors und legt verfügbaren Satz an Instruktionen fest (ungültige Instruktionen für den Typ werden mit Fehlermeldung quittiert, Syntax .DEVICE AT90S8515)
.DEF
Legt ein Synonym für ein Register fest (z.B. .DEF MeinReg = R16)
.EQU
Definiert ein Symbol und legt seinen Wert fest (kann später umdefiniert werden, Syntax .EQU test = 1234567, interne Speicherung des Wertes erfolgt als 4-Byte-Integer-Zahl)
.SET
Fixiert den Wert eines Symboles (keine spätere Neudefinition möglich)
.INCLUDE
Fügt eine externe Datei ein, als ob deren Inhalt an dieser Stelle stünde (Typisch z.B. Einbinden der Headerdatei: .INCLUDE "C:\avrtools \appnotes\8515def.inc")
.CSEG
Beginn des Codesegmentes (alles was danach folgt, wird als Code übersetzt und in den Programmraum gespeichert)
.DB
Fügt ein oder mehrere konstante Bytes in das Programm (kann eine Zahl von 0..255 sein, ein ASCII-Zeichen 'c', eine Zeichenkette "abcde" oder ein Gemisch wie z.B. 1,2,3,'a',"abc". Im Programmraum muss die Anzahl der eingefügten Bytes geradzahlig sein, weil der Programmspeicher immer nur ganze 16-Bit-Worte enthalten kann, andernfalls wird vom Assembler ein Nullbyte angefügt.)
.DW
Fügt ein binäres Wort in den Programmraum ein (produziert z.B. eine Tabelle im Code!)
.LISTMAC
Makros werden in die Listdatei aufgenommen (ohne diese Angabe wird Code aus Makros nicht im .LST-file ausgegeben)
.MACRO
Beginn eines Makros (Code wird nicht erzeugt, erst bei Aufruf des Makros mit seinem Name (Syntax: .MACRO makroname parameter, Aufruf mit: makroname parameter)
Code
.ENDMACRO Ende des Makros Beginn des EEPROM-Speichers (die erzeugten Inhalte landen beim Programmieren im EEPROM-Segment)
.ESEG
Fügt ein oder mehrere konstante Bytes in das EEPROM ein (kann eine Zahl von 0..255 sein, ein ASCII-Zeichen 'c', eine Zeichenkette "abcde" oder ein Gemisch wie z.B. 1,2,3,'a',"abc".)
EEPROM .DB
.DW
Fügt ein binäres Wort in den EEPROM-Raum ein (Im EEPROMSegment wird erst das niedrigere, dann das höhere Byte eingefügt)
.DSEG
Beginn des Datensegments (hier dürfen dann nur BYTE-Direktiven und Labels stehen, bei der Übersetzung werden nur die Labels entsprechend ausgewertet)
.BYTE
Reserviert ein oder mehrere Bytes im Datensegment (fügt den Bytewert im Unterschied zu .DB nicht wirklich ein!)
.ORG
Legt die Anfangsadresse im jeweiligen Segment fest (z.B. .ORG 0x0000)
.LIST
Schaltet die Listing-Ausgabe ein (der produzierte Code wird menschenlesbar in einer .LST-Datei ausgegeben)
.NOLIST
Schaltet die Ausgabe in die .LST-Datei aus
.INCLUDE
Fügt eine externe Datei ein, als ob deren Inhalt an dieser Stelle stünde (Typisch z.B. Einbinden der Headerdatei: .INCLUDE "C:\avrtools \appnotes\8515def.inc")
.EXIT
Ende des Assembler-Codes (stellt weitere Übersetzung ein)
bedingtes Assemblieren: ein Code wird nur dann übersetzt, wenn eine Bedingung gegeben oder nicht gegeben ist, andernfalls wird ein anderer Code übersetzt, Ausgaben von Nachrichten oder erzwungenen Fehlern: dient zur Benachrichtigung während der Übersetzung bzw. erzwingt einen Abbruch der Übersetzung.
Es ist wichtig zu beachten, dass sich die Direktiven nur an den Assembler richten und sein Verhalten steuern, im erzeugten Programm selbst finden keine Verzweigungen statt! Nicht alle Assembler kennen diese Direktiven. Neuere Assembler von ATMEL, die neueren Studioversionen sowie gavrasm kennen diese Direktiven. Die Syntax dieser Direktiven sieht so aus: ●
●
●
●
●
●
●
.IF , dann Codezeilen, dann .ENDIF. Bedingung ist ein Ausdruck, der entweder wahr oder falsch sein kann, also z.B. ein Vergleich wie ab oder a==b. .IF , dann Codezeilen, dann .ELSE, dann Codezeilen, dann .ENDIF. Wenn die Bedingung erfüllt ist, werden die ersten Codezeilen übersetzt, wenn nicht die zweiten hinter ELSE. .IF , dann Codezeilen, dann .ELIF , dann Codezeilen, dann . ENDIF. Wenn Bedingung 1 wahr ist, wird der erste Code erzeugt, wenn nicht wird die Bedingung 2 geprüft. Ist diese erfüllt, wird der zweite Code erzeugt. Wenn weder Bedingung 1 noch 2 erfüllt sind, wird gar kein Code erzeugt. .IFDEF <Symbol>. Prüft, ob ein Symbol definiert ist. Wenn ja, dann wird der Code übersetzt, wenn nein, dann nicht. Kann mit den ELSE- und ELIF- Direktiven kombiniert werden und ist mit der ENDIF-Direktive abzuschließen. .IFNDEF <Symbol>. Bewirkt das Umgekehrte: wenn das Symbol nicht definiert ist, wird übersetzt. .MESSAGE "". Gibt den Nachrichtentext beim Übersetzen aus. Kann in Kombination mit der bedingten Assemblierung dazu benutzt werden, um einen Hinweis auf das Ergebnis der Verzweigung zu geben. .ERROR "". Gibt den Nachrichtentext aus und erzeugt eine Fehlermeldung. Kann benutzt werden, um unter bestimmten Bedingungen die Assemblierung zu beenden, z.B. wenn ein Wert zu groß oder zu klein ist.
Assembler-Ausdrücke Ausdrücke werden für Berechnungen im Assembler-Quelltext verwendet und werden bei der Übersetzung des Codes ausgewertet. Sie erzeugen keinen ausführbaren Code. Typ
Gerd's AVR Assembler Ein Kommandozeilen-Assembler für alle AT90S-, AT90CAN-, AT90USB-, ATtiny-, ATmega- und ATxmegaTypen der Mikroprozessoren von ATMEL mit vielen erweiterten und neuen Eigenschaften. Arbeitet auf der DOS-, Win32- und Linux(i386)-Kommandozeile. Durch erweiterte Fehlerprüfung und -kommentierung besonders auch für Anfänger geeignet!
Sprachen Erster AVR-Assembler vollständig in deutscher Sprache. Leicht in andere Sprachen übersetzbar (sende mir die übersetzte Dateien gavrlang_xx.pas und LiesMich.Txt, dann gibt es auch andere Sprachversionen). Auch in englischer Sprache erhältlich, siehe die englische Version dieser Seite.
Eigenschaften Siehe die LiesMich.Txt für mehr Informationen über die Eigenschaften. Eine kurze Einführung in die Nutzung der speziellen Features von gavrasm bietet die Einführung.
Benachrichtigungsdienst bei neuen Versionen Bitte das Mail-Formular ausfüllen und abschicken, dann gibt es Benachrichtigungen per Mail.
Neu in Version 2.2 ● ● ●
Auf 115 Typen erweitert, alle Eigenschaften intern gespeichert. Französische Sprachversion (danke Hervé) Verwendung interner Symbole abschaltbar mit Schalter x
Download Version 2.2 ist verfügbar zum Download als fertig compilierte Version ●
●
●
Deutsch: ❍ Linux (i386) ❍ Dos ❍ Win Englisch: ❍ Linux (i386) ❍ Dos ❍ Win Französisch: ❍ Linux (i386) ❍ Dos ❍ Win
Packe die gezippte ausführbare Datei und die LiesMich-Datei aus.
Quellcode Quellcode zum Download, geschrieben für und getestet mit FreePascal, ist verfügbar für ● ●
Linux und für DOS und Win.
Packe die gezippten Dateien aus, kopiere die sprachspezifische Version der Datei gavrlang_xx.pas und füge sie als gavrlang.pas ein. Compiliere mit einem Pascal-Compiler (fpc, http://www.freepascal.org).
Status Diese Software wurde ausgiebig getestet, kann aber noch Fehler enthalten. Daher Vorsicht bei der Benutzung. Berichte über Bugs und vermisste Features bitte an gavrasm (at) avr-asm-tutorial.net, Subject=gavrasm-bug 2.2.
Frühere Versionen Links zu älteren Versionen: ●
●
●
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
Version 2.1 (Dezember 2006) ❍ Linux (i386) ❍ Dos ❍ Win Version 2.0 (September 2006) ❍ Linux (i386) ❍ Dos ❍ Win Version 1.9 (August 2006) ❍ Linux (i386) ❍ Dos ❍ Win Version 1.8 (Juli 2006) Version 1.7 (Mai 2006) Version 1.6 (Dezember 2005) Version 1.5 (September 2005) Version 1.4 (Juli 2005) Version 1.3 (Mai 2005) Version 1.2 (März 2005) Version 1.1 (Januar 2005) Version 1.0 (Oktober 2004) Version 0.9 (März 2004) Version 0.8 (Februar 2004) Version 0.7 (November 2003) Version 0.6 (September 2003) Version 0.5 (August 2003) Version 0.4 (Juli 2003) Version 0.3 (Juni 2003) Version 0.2 (Mai 2003) Version 0.1 (Dezember 2002)
Gerd's AVR assembler A command line assembler for all AT90S-, AT90CAN-, AT90USB-, ATtiny-, ATmega and ATxmega-Types of microcontrollers of ATMEL, with many extended and new features. Works on DOS, Win32 and Linux(i386) command lines. Extended error checking and commenting, especially suited for the beginner in assembler programming!
Languages AVR-Assembler in an english, french and a german version, can easily be translated to other languages (send me your edited gavrlang_xx.pas and the ReadMe.Txt file for further translation to other languages). See the german page for all versions.
Features View the ReadMe.Txt for more informations on features. See also the introduction to the use and some notes on special features of gavrasm.
Mail on new version releases Please fill in the form and mail it to me. You'll get a mail whenever a new version of gavrasm is released.
Download Version 2.2 is available for download as already-compiled-version for ● ● ●
Linux (i386) Dos Win
Unpack the executable and a readme file. See the readme for command line options and use.
Source-Code Source code, written for FreePascal, is available for ● ●
Linux and for DOS and Win.
Unpack the zipped files, copy your desired language file gavrlang_xx.pas, rename it to gavrlang.pas and compile with the Pascal compiler (fpc, see http://www.freepascal.org).
Status This software was tested intensively, but may still have some bugs, so be careful with its use. Reports on bugs and missed features to gavrasm (at) avr-asm-tutorial.net, subject=gavrasm-bug 2.2.
Earlier versions Links to older versions: ●
●
●
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
Version 2.1 (December 2006) ❍ Linux (i386) ❍ Dos ❍ Win Version 2.0 (September 2006) ❍ Linux (i386) ❍ Dos ❍ Win Version 1.9 (August 2006) ❍ Linux (i386) ❍ Dos ❍ Win Version 1.8 (July 2006) Version 1.7 (May 2006) Version 1.6 (December 2005) Version 1.5 (September 2005) Version 1.4 (July 2005) Version 1.3 (May 2005) Version 1.2 (March 2005) Version 1.1 (January 2005) Version 1.0 (October 2004) Version 0.9 (March 2004) Version 0.8 (February 2004) Version 0.7 (September 2003) Version 0.6 (September 2003) Version 0.5 (August 2003) Version 0.4 (June 2003) Version 0.3 (June 2003) Version 0.2 (May 2003) Version 0.1 (December 2002)
Program gavrasm AVR Assembler for ATMEL AVR-Processors ====================================== Command line Assembler for ATMEL AVR-Processors of the types - AT90S - AT90CAN - ATtiny - ATmega and - ATXmega. For Linux i386 pre-compiled executable : gavrasm For DOS resp. Win32 compiled executable: gavrasm.exe Sourcecode for fpc-Pascal Calls and options ----------------Call: gavrasm [-abelmqsw] SourceFile[.asm] Parameters: a: Switch output of ANSI-chars on/off default is on (Linux) resp. off (Dos, Win) b: Extended error comments for beginners default is off e: Longer error messages default is off (short error messages) l: Suppress output of the LIST-file default is LIST-file output on m: Suppress listing of expanded macros default is listing on q: No output of messages on the command line (quiet) (except: headers and forced messages!) default is output on s: Output a list of symbols in the LIST-file default is no symbol list w: Enable wrap-around default is wrap-around off x: Disable using internal def.inc information default is internal information Output files: SourceFile.lst ... Assembler-Listing, Textfile SourceFile.hex ... Code in Intel-Hex-format, Textfile SourceFile.eep ... EEPROM-content in Intel-Hex-format, Textfile, (deleted if no .ESEG content was generated) SourceFile.err ... Error messages (if any), Textfile Call: gavrasm [-?hdt] Parameters: ?: List of options h: List of options d: List of supported directives t: List of supported AVR types Remark for the option a: The Linux version outputs ANSI code sequences by default, with other OS's this option is disabled by default. The option a inverts this switch under all operating systems.
Specialties of this Assembler ----------------------------Following only the special properties of this assembler are discussed. All properties not mentioned explicitly, work like in other assemblers, so these are compatible with others (e.g. with the ATMEL(R)-assembler). a) Main differences resulting in error and warning messages for code that assembles correct (without any errors and warnings) with other assemblers: * Header files for the AVR type (*def.inc) are not necessary, gavrasm knows them all by itself. Header files for known AVR types, included using the INCLUDE directive, are ignored, the file is not read. A warning is given. Instead of reading the header file, gavrasm identifies the AVR type from that line and, if recognized, defines its own AVR type-specific symbol set. Be aware of that in the default mode if you change the header file for an AVR, because these changes don't come into effect under gavrasm. In contrast to other assemblers, the symbols of the AVR type are already defined with the DEVICE directive, including header files is no longer necessary. If you want to use self-written def.inc, then avoid using the term "def.inc" in the filename. That prevents gavrasm from recognizing the header file and includes it normally. From version 2.2 on this property can be forced by using the parameter x on the command line. * Filenames (e.g. in the INCLUDE directive, usually do not need to be in quotes. Under Linux always use quotes ("...") for the filename! * Constant displacements in relative jump- or branchinstructions, usually written like " brcc PC+10" (where PC stands for PROGRAM COUNTER), are already recognized if the displacement is preceeded by a '+' or '-' character, like " brcc +10". The use of the notation PC+10 is optional. * The extra features in interpreting text and character constants, implemented in gavrasm, may cause error messages and incompatibilites with other assemblers. The differences in detail: - Introducing a " within a text constant by using a double "" is misinterpreted by other assemblers. This causes an error message saying "garbage at end of line", the text behind the double "" is not interpreted. - A ; within a text string ("...;...") or as an ASCII character constant (';') is misinterpreted by other assemblers as beginning of a comment, they ignore all behind the ; and produce an error message. - If non-printable ASCII code characters are part of a text constant, these are in gavrasm written like "...\m\j\@". Other assemblers that do not have this feature misinterpret the \ as a normal ASCII character. No error message results. If the number of control characters is odd, a warning results saying that the number of characters is odd and a Null-char is added. * The .ERROR directive in gavrasm expects that a text is given as parameter, to be displayed as error message. Other assembler don't know that. The text makes sense because one needs to know what exactly caused the error. To get the same effect, code for other assemblers require the use of an additional .MESSAGE directive. b) Source code is available and documented: * FPK-Pascal, written and tested for Linux-(i386), DOS-(go32v2) and Win32-FPK (use latest version, older versions have problems with some of the Pascal instructions) (FPK-Compiler available at http://www.freepascal.org). Several warnings during compilation can be ignored. * Source code files provided: gavrasm.pas: Main program file gavrdev.pas: Unit that provides all hardware characteristics and symbols of all supported AVR types, *def.inc Include files are not needed gavrif.pas: Unit providing nested if/else/endif support gavrlang.pas: Unit for language support (currently available in an english (gavrlang_en.pas) and german gavrlang_de.pas) version gavrline.pas: Unit to split the asm lines into pieces gavrmacr.pas: Unit for Macro-processing gavrout.pas: Unit for Hex output gavrsymb.pas: Unit for symbol-processing * Prior to compilation: copy the language file gavrlang_en.pas and rename it to gavrlang.pas! For the french version use gavrlang_fr.pas! * Test file for checking of the assembler: instr.asm: Test file with all AVR instructions c) Extra directives (Pseudo opcodes): * DEVICE now automatically provides all symbols of the respective AVR type, forget the *def.inc-files, you'll never need them any more. * The EXIT-directive without conditions works normal (interrupts further assembling process of the currently processed file) and is compatible with other assemblers. If an additional parameter is given, usually a comparison, this is interpreted as a boolean value. If this is 1 (TRUE) the EXIT directive is executed, providing a conditional stop of the assembling process (stops the assembling process completely). This conditional EXIT directive also works within INCLUDE files. This feature can be used to check for error conditions during the assembly process (e.g. stop if limits for certain symbol values are exceeded, as an overflow detection). This directive can, in part, be exchanged with the directive .ERROR, that produces an error message. * Additional .IF, .ELSE, .ELIF and .ENDIF directive: .IF (condition) assembles the following code only if the condition is true (1), otherwise branches either to the code after .ELSE, .ELIF or .ENDIF. Please use the ELIF directive as an alternative to ELSE, and do not mix these, the result is not defined. Allows nested .IF, .ELSE, .ELIF and .ENDIF directives of any depth. * The .IFDEVICE parameter directive compiles the following code, if the type specified by the parameter is equal to the one specified in the .DEVICE statement. * .IFDEF and .IFNDEF tests for defined and unsdefined symbols and works like the .IF directive. * .MESSAGE displays a message (in "..." as parameter) on the list output, which is not suppressed in the quiet-mode. * .ERROR forces an error with a message (in "..." as parameter) on the list output and in the error textfile. * Additional .SETGLOBAL p1,p2,[...] directive to export local macro symbols. Otherwise symbols defined within a macro are completely local and cannot be used outside of the macro. Be careful with that feature, it might be source for errors. * Recursive .INCLUDE-directive with unlimited depth. Under Linux, always use quotes for the filename ("..."). * List of supported directives available by gavrasm -d d) Makros: * Extended macro capabilities: - recursive macro calls without limitations - extended parameter checking within the macro definition, not only at the macro call itself - all labels and symbols in macros are defined locally, they are not accessible from outside the macro, improves error recognition - export of local symbols by use of the SETGLOBAL directive - labels in macros are correct even during forward use and if used in recursive calls - optional use of .ENDM or .ENDMACRO to close the macro definition - List of all defined and used macros in the listfile, if the symbol list is switched on with -s (and by not suppressing list output on the command line by not using the -l option or by .LIST in the source code) e) Improved error detection, commenting and syntax checking: * Extended symbol checking: - exact symbol type checking (cases R for registers, C for constants, V for variables, T for AVR type definitions, M for local macro labels, L for global macro labels) - the use of mnemonics as symbol names is prohibited, excludes those used by ATMEL within some older header files (accepts OR and BRTS as a symbol name) - extended recognition of undefined symbols * Extended error commenting and warnings (more than 100 error types, 8 warning types) * Enhanced syntax commenting for beginners (option b) for instructions and directives in the list file * Extended calculation check (Over- and Underrun of internal 32-bit-Integer values) * Extended characters in literal constants and strings: ';', '''', '\n', '\\', "abc\n\j", "abc;def" * Separator character (dots, '.') in binary- and hex-numbers for improved readability of the source code (e.g. 0b0001.0010, $124A.035F, 0x82.FABC) * BYTE1-function implemented (similiar to BYTE2 etc.) * Allows constant displacement in relative jumps, e.g. rjmp +10 or brcc -3 (displacement must start with + or - in that case) f) Supported AVR-types: * ATtiny: 10, 11, 12, 13, 13A, 15, 22, 24, 25, 26, 28, 43U, 44, 45, 48, 84, 85, 88, 167, 261, 461, 861, 2313 * AT90CAN: 32, 64, 128 * AT90S: 1200, 2313, 2323, 4343, 4414, 4433, 4434, 8515, 8535 * ATmega: 8, 8A, 16, 16HVA, 16U4, 32, 32A, 32C1, 32HVB, 32M1, 32U4, 32U6, 48, 48P, 64, 64A, 88, 88P, 88PA, 103, 128, 128A, 161, 162, 163, 164P, 165, 165P, 168, 168P, 168PA, 169, 169P, 323, 324P, 324PA, 325, 325P, 328P, 329, 329P, 406, 640, 644, 644P, 645, 649, 1280, 1281, 1284P, 2560, 2561, 3250, 3250P, 3290, 3290P, 3290P, 6450, 6490, 8515, 8535 * ATXmega: 64A1, 64A3, 128A1, 128A3, 256A3, 256A3B * AT90PWM: 2, 2B, 3, 3B, 216, 316 * AT90USB: 82, 162, 646, 647, 1286, 1287 * AT86RF401 * List of supported AVR types with gavrasm -t g) Language versions currently supported: * English, German, French (just copy the file avrlang_xx.pas to avrlang.pas by overwriting it and recompile) h) Open issues and unresolved errors: * The def.inc-files for the ATXmega types have extra #define directives and use, so-far undefined, strange rules. So including these header files from ATMEL will result in error messages. If you use the gavrasm-internal def.inc-information (by default, no x parameter) no errors result and the correct labels are defined. Versions and Changes -------------------Latest versions at http://www.avr-asm-tutorial.net/gavrasm/index_en.html December 2008: Version 2.2 - Added: Support for french version (thanks to Herve) - Added: 65 additional AVR types - Changed: The whole internal storage and processing of hardwarespecific properties of the AVRs, the def.inc files and the typespecific internal symbols was re-worked and changed. - Removed: The support for AVR types AT90C8534, AT90S2343, ATmega104 and ATmega603 was removed (these types are no more supported by ATMEL - obsolete devices). Added: SPM Z+ support. November 2006: Version 2.1 - Corrected: Bug in CALL and JUMP opcodes - Corrected: Recognition of illegal characters in instructions September 2006: Version 2.0 - Corrected: Bug in handling variables with .SET August 2006: Version 1.9 - Corrected: Recognition of already defined symbols corrected - Corrected: Incorrect opcode of STD without displacement July 2006: Version 1.8 - Corrected: False memory adresses of internal SRAM in some ATmega May 2006: Version 1.7 - Corrected: ADIW/SBIW check changed - Corrected: Re-worked the ELIF directive December 2005: Version 1.6 - Added: Support for types AT90CAN32 and 64, ATtiny24, 44 and 84, ATmega 644 September 2005: Version 1.5 - Corrected: Double list output in directives removed. - Corrected: Problem with nested IF/IFDEF/IFNDEF/IFDEVICE directives and undefined variables resolved. July 2005: Version 1.4 - Added: Support for types AT90CAN128, ATmega329, 3290, 406, 649, 6490, AT90PWM2, 3 - Changed: Unlike in earlier versions, directives are allowed in columns >1 and instruction may start in column 1 of a line. - Changed: Within the D- and E-segment, now all directives are allowed. - Corrected: An error while working with the constant ','. - Corrected: Device instruction did not recognize some AVR types with longer names. - Corrected: Port ADCSRA in ATmega8 was only recognized under its former name. April 2005: Version 1.3 - Corrected: EEPROM capacity of the ATmega104 and 128 - Added: Support for types ATmega 1280, 1281, 2560, 2561 and 640 March 2005: Version 1.2 - Corrected: Error in bit WGM02 of port TCCR0B in the ATtiny13 constants - Corrected: Missing parameters in a number of instructions were accepted without an error - Added: Support for the new types ATtiny25, 45 and 85 January 2005: Version 1.1 - Corrected: Error in the number of warnings - Corrected: Error in using SETGLOBAL - Corrected: Error in using underscore in macro names - Added: Support for using the currect program counter PC as a variable, e.g. in RJMP PC+3 or brcc PC-2. October 2004: Version 1.0 - Added: Support for new types ATmega325/3259/645/6450 - Changed: Complete rewriting of the directives - Added: New directives .ERROR, .MESSAGE, .ELIF, .IFDEF and .IFNDEF for enhanced compatibility with the ATMEL assembler - Added: Error output to a separate error file source.err. March 2004: Version 0.9 - Added: Support for the new types ATmega48/88/168 Febrary 2004: Version 0.8 - Corrected: Error with setting the symbol RAMEND removed - Corrected: Some SRam settings for older ATmega types were false - Corrected: Assembler did not run in the short error message mode by default October 2003: Version 0.7 - Added: Support for new type ATtiny2313 - Added: .IFDEVICE directive - Changed: Reliability of ATtiny22 instruction set now fine September 2003: Version 0.6 - Corrected: Error when using negative numbers in binary operations - Corrected: Error with type checking in .DEVICE statement - Added: def.inc include files now trigger the internal symbol definitions, no error message any more, a warning is given and the def.incfile is not read - Changed: Complete instruction set reworked - Added: Several older device types, warning for undocumented types - Added: Nested .IF, .ELSE and .ENDIF directives August 2003: Version 0.5 - Corrected: LD/ST instruction for type AT90S1200 added, resulted in an error message in previous versions July 2003: Version 0.4 - Corrected: Misleading error message when using an undefined register - Added: DEVICE now adds all predefined symbols, as listed in the data sheet of the respective processor (these were previously provided by def.inc-files). Attempting to include a *def.inc-file results in an error message! - Added: Support for the new type AVRtiny13 (even before ATMEL has released its def.inc!) June 2003: Version 0.3 - Corrected: Detection of double labels improved (ran into internal compiler error) - Corrected: Constant expressions starting and ending with a bracket lead to a strange error message, therefore removed the support for macro calls with parameters in brackets May 2003: Version 0.2 - Added: SETGLOBAL directive for export of local symbols in macros - Added: Wrap-around allowed in relative jumps/calls. Wrap is also possible in relative (conditional) branches (BRCC etc.), difference to the ATMEL assembler! - Fixed: Addressing in hex-file now compatible with Studio/Wavrasm (byte-oriented, not word-oriented, also handles multiple .ORG directives correct - Fixed: LSL and LSR instruction for AT90S1200 are valid, invalid error message in version 0.1 - Fixed: Labels, variables and symbols starting with an underscore are now accepted - Fixed: Double division x/y/z yielded false result, corrected - Fixed: Instructions starting in column 1 of a line produced a misleading error message, added hint - Fixed: directives that don't start in column 1 of the line but are preceded by a label were not recognized, error message added - Added: Command line option E for shorter (default) or longer error messages December 2002: Version 0.1 Beta (first release) Terms of use of this software ----------------------------- Copyright for all versions: (C)2002..2005 by Gerhard Schmidt - Free use of the source code and the compiled versions for noncommercial purposes. Distribution allowed if the copyright information is included. - No warranty for correct functioning of the software. - Report errors (especially any compiler errors) and your most-wanted features to [email protected] (Subject "gavrasm 2.2"). If errors occur, please include the source code, any include files, the list and the error file. http://www.avr-asm-tutorial.net/gavrasm/v22/ReadMe.Txt1/20/2009 7:29:48 PM
Introduction to gavrasm AVR Assembler
Pfad: Home => AVR-Assembler gavrasm => Introduction
Introduction to Gerd's AVR Assembler Here I introduce some special features of the gavrasm Assembler. These examples do not compile on other assemblers. The features discussed: 1. 2. 3. 4. 5. 6. 7. 8.
Calling the assembler on a command line Calling the assembler within a batch or a shell Window caller as assembling tool Using the IF-ELSE-ENDIF directive Use of the EXIT directive Using macro labels Using special literal constants Using a separator in constants
Calling the assembler on the command line Assembling with gavrasm on the command line is as follows. 1. Open a command line (Win: select start/programs/addons/MSDOS window) or a shell (Linux-KDE) and change the directory, typing cd [path], to the one that has the source code file. 2. Assembling starts with typing [gavrasm-path]gavrasm [-options] sourcefile[.asm]. If you have all used INCLUDE-files in the same directory where the source code file resides, you can just add the name of these files (e.g. .INCLUDE "8515def.inc"). Otherwise add the whole path to the included files (e.g. . INCLUDE "C:\avrtools\appnotes\8515def.inc" or "/home/gerd/avrasm/def/8515def.inc"). The parameter options are completely listed in the file ReadMe.Txt. Here are some additional explanations: ●
●
●
●
●
●
●
Option -a makes sense in Linux, if you have no ANSI chars in your command line shell or if you do not like displaying line numbers in that shell. -a switches the output of linenumbers off (as is default in the win version of the assembler). Option -b makes sense for beginners to get extended error messages. The error message then comments the required parameters of an instruction. This works only if you have chosen longer error messages with the -e option (e.g. -eb). Option -l switches the generation of the list file off. That makes sense only if you don't have enough disk space. Option -q suppresses the output of error messages on the command line. The only messages then are INCLUDEs, the pass information and the result of the compilation (number of errors). Option -s switches the output of the symbol list in the list file on, as far as listing is not switched off. All symbols are displayed at the end of the list file, sorted by the type of the symbol: ❍ R: Register, ❍ L: Label, ❍ C: EQU-constant, ❍ V: SET-variable, ❍ M: Local macro-symbol (label in a macro), ❍ G: Globalized macro-label Additional informations listed are how often the symbol was redefined (nDef), how often the symbol is used (nUsed) and the last value of then symbol in decimal and hex form. Option -w allows wrap-around. These are jumps or branches forward (over the end of the adress space to its beginning) or backward (over the beginning of the adress space at its end. This option makes sense, if the AVR has a 4k flash, which isn't accessible by relative jumps or branches. Wrap-Around in gavrasm does also work with the relative brach instructions like BRCC or BREQ. Because other assemblers do not allow this, this source code will only compile correct with gavrasm. The options -?, -h, -d and -t will set the assembler to only output the required information (-? and -h: list of options; -d: list of implemented directives; -t: valid AVR types and their properties). An added source code file name on the command line is ignored!
To the top of that page
Calling the assembler in a batch or a shell If you're tired of typing in the path of the assembler, you can place that call in a batch file. If win is used: create a new textfile in the same directory, where your source resides, and add the following lines: REM Batch calling assembler [drive]: CD [Path-to-your-sourcefile] [gavrasm-path]\gavrasm [-options] sourcefile[.asm] PAUSE Rename this text file with the extension ".bat". If you like, you can place a reference to that batch file on your desktop. Assembling is just started by clicking on that file or its reference. PAUSE leaves the window open, until you type in a character. In Linux we use the follwing shell script: cd [Path-to-your-sourcefile] [Path-to-gavrasm]/gavrasm [-options] sourcefile[.asm] read key and place it somewhere with the extension .sh. Those who like it: place a reference to that shell on the KDE desktop (Create new, reference to program, execute in a terminal program). The line with "read key" leaves the shell open. To the top of that page
Window caller - a tool for assembling Tired of writing batch files for the command line assembler, if you work in a window-orientated environment? Here's something for you! By request I designed a windows program that creates batch files, calls the assembler, and allows views/editing for the different textfiles that play a role in assembling. Some new features are: ● ● ● ●
●
●
It is menue-driven and allows working with whole projects. Settings can be saved and loaded, for a quick change of the project. It includes an editor for the source files. In its current form the editor displays row and column information (unlike most simple windows editors) and allows tab characters in the files. Recognizes any include files automatically and allows editing. Includes can be nested, a maximum of five include files can be handled. If you open the list file after assembling, you can quickly find errors, load the responsible files and edit the line directly.
The Read-Me file has more informations on its features and how to work with that small helper. This executable is only available for windows operating systems (sorry linuxers). The zipped executable and the ReadMe-file is available for downloaded here.
Use of IF-ELSE-ENDIF The additional directives .IF, .ELSE and .ENDIF add the opportunity to compile or not compile certain code depending on conditions. During compilation the IF condition is checked. If it is true, the following source code is compiled. If not, the compilation is restarted behind the .ELSE or the .ENDIF directive. Be careful: If those two directives are missing, the whole remaining source code might not be compiled! As application example the following source code: .EQU clock=40000000 .IF clock>4000000 .EQU divider=4 .ELSE .EQU divider=2 .ENDIF A short look to the symbol list in the list file shows, that divider has been set to 2. If you change the value of clock e.g. to 10,000,000, divider will be set to 4. Generally speaking: you can avoid further changes in the source code, if you anticipate these changes under certain conditions. A typical application is, if you like to write and use the same code for different processor types. With other types, interrupt vectors are different. Using .IF, the vector area of the processor is compiled specifically for each AVR type. ; ; Define processor type on top of the source code ; .EQU aType=2313 ; Processor is a 2313 ;.EQU aType=2323 ; Processor would be a 2323 ;.EQU aType=8515 ; Processor would be a 8515 ; ; Int-Vector area ; .CSEG .ORG $0000 rjmp Main ; for all types rjmp IntVecInt0 ; External Int Vector, is used ; Int-Vector area for 2313 .IF aType == 2313 reti ; IntVecInt1 ; External Int Vector, not used reti ; Timer1Capt, not used reti ; Timer1/Comp, not used reti ; Timer1/Ovf, not used rjmp IntVecTC0Ovf ; TC0-Overflow, used reti ; UartRX, not used reti ; UartUdre, not used reti ; UartTx, not used .ENDIF ; Int-Vector area for 2323 .IF aType == 2323 rjmp IntVecTC0Ovf ; TC0-Overflow, used .ENDIF ; Int-Vector area for 8515 .IF aType == 8515 reti ; IntVecInt1 ; External Int Vector, not used reti ; Timer1Capt, not used reti ; Timer1/CompA, not used reti ; Timer1/CompB, not used reti ; Timer1/Ovf, not used rjmp IntVecTC0Ovf ; TC0-Overflow, used reti ; SpiStc, not used reti ; UartRX, not used reti ; UartUdre, not used reti ; UartTx, not used reti ; AnaComp, not used .ENDIF ; ; Interrupt-Service-Routine INT0 ; IntVecInt0: [...] reti ; ; Interrupt-Service-Routine TC0-Overflow ; IntVecTC0Ovf: [...] reti ; ; Main program start ; Main: [...] You see, that just changing the processor type is easy, if you have once designed the vector area for this type. Otherwise you'd have to go through your whole source code and redesign. If you forget the difference in the int vector area, you run into a real nice design bug. The conditions of the .IF directive can be more complex, if you like, e.g.: .IF (aType == 2313) || (aType == 8515) Nested .IF directives are currently not implemented in order to keep the thing simple. To the top of that page
Use of the EXIT directive If a certain number exceeds its defined value range, one likes to stop assembling and issuing an error message. So, if you missed this opportunity for extended range check in other assemblers, you'll be a friend of gavrasm. . EXIT checks the following condition and stops assembling, if it's true: .EQU clock=4000000 .EQU divider=64 .EXIT (clock/divider)>65535 With this range check you make sure that no overflow of your 16-bit timer/counter will occur, before you run into a debugger problem. gavrasm notifies you of such a condition and refuses compilation of the buggy code. To the top of that page
Use of macros Macros are code sequences stored by the assembler, which are only added to the code, if the macro is called. E. g.: .MACRO mtest .nop .ENDM ; ; Here we place the macro's code ; mtest The code within the macro can be changed by calling the macro with paqrameters. Parameters can be numbers, but can also be register names or any other symbol. The parameters are referenced within the macro as @0 bis @9. E.g. like this: ; ; Register global definition ; .DEF rmp,R16 ; Here's the macro .MACRO mtest ldi @0,@1 ; Expects a register as first param, a constant as second clr @0 .ENDM ; ; Macro call with parameters ; mtest rmp,50 The use of macros in gavrasm is enhanced, because you can call macros within a macro whenever you need it. Nesting of macros is only limited by memory storage space. Labels in macros are in gavrasm clearly protected. Labels can only be used within the macro, using them outside the macro yields an error message. This prevents bugs, if you call a macro more than one time in your source code. Globally defined symbols are accessible within the macro, so don't try to use a symbol name in a macro, that is already defined outside. If you'd like to export a local label within a macro to the outside, use the special directive .SETGLOBAL label1[, label2, ...] to export its value. Whenever you call such a macro, the value of label1 is redefined to its current value. This works exact, even if the label is used before it is defined. With this instrument I added the opportunity to write extensive code for different purposes in macros, and place these code sequences into the source whenever it is required. As the code is only placed there if the macro is called, you can include all your favoured macros, without wasting place if you don't need some of them. To the top of that page
Use of special literal constants gavrasm allows the use of ASCII control codes within a literal constant, e.g. .DB "This is the first line.\m\jThis is the second line." A '\'-char is inserted by two backspaces, e.g. "path is C:\\dos". Contrary to other assemblers, a ';' in a string is recognized and treated correct. To the top of that page
Program GAVRASMW ---------------Gerd's little helper to assemble AVR assembler source files with the command-line assembler GAVRASM in a window-oriented environment. Freeware for Windows, last changed October 2004 Free download at http://www.avr-asm-tutorial.net/gavrasm/GAVRASMI.html#caller Features: - Creates convenient batch files for calling the assembler. - Once configured, all necessary informations are stored in a project file, and assembling is just a one-click task. - Configuration of parameters for assembling is included. - View the list file output and the created batch files. - Edit your assembler source file and any include files (the editor displays line- and column information for convenient error editing). - If you view the list file, and if it reports any errors, you can easily open the source/include file and correct the code on the erroneous line. Installation: - Unpack the zip-archive to a directory, where you have write-access-rights to. - Place a reference to the exe-file on the desktop or in the start menue. Start a new project: - Start gavrasmw. - Select "Setup" from the menue. In the "gavrasm" section, push "Select" and navigate to the gavrasm.exe executable. Select the desired command-line parameters for the assembler. In the "Source Code File" section push "Select", navigate to the desired path (create a new directory, if so desired) and give the source file a name. Either push "Save as default" or "Save as Project File" (or both). If you save the informations as a new project file, give the project a name. Push the "Close" button. - Select "Edit" and "Source" from the menue. The editor window opens with a standard assembler source code frame. Type in your source code, select "File" and "Save". - If you have a line with an include directive in your source file, and if the include file is not a "*def.inc" file, you can edit the include file by selecting "Edit" and the appropriate include file. If that include file does not yet exist, it opens with a standard file header. Include files can be nested, a maximum of five include files can be handled in this version. - Select "Assemble" from the main window to start the assembler in a command line window. Actually, this creates a batch file and starts it. - View the list or error output of the assembler by selecting "View" and "Listfile" or "Errorfile". If there are errors reported, use "Error" and "FindNext" to display the error lines. If you're at such a line, select "Error" and "Open file" to open that file in a new window. The cursor will be in the line where this error occurred. Correct it, save the file and close this window. Proceed with the next error, if any. - View the batch file content by selecting "View" and "Batchfile". Any changes to that file, with an external editor, will be overwritten by the next "Assemble" operation! Start an existing project: - Start gavrasmw. - Select "Setup". If you'd like to work with other than the default settings, select "Load Project File" and navigate to the desired project settings file (*.asw). Report any bugs in this software to [email protected] with the subject "gavrasm-caller, October version". http://www.avr-asm-tutorial.net/gavrasm/gavrasmw/ReadMe.txt1/20/2009 7:29:52 PM
Mail form AVR-Assembler-Tutorial
Tutorial-Home ==> gavrasm-Home-EN/DE => Mailform
Inform me about new versions of gavrasm by mail By filling this form and sending it to me via EMail, you will be notified by EMail whenever a new version of gavrasm is released. Traffic will be about once every three monthes. Note that gavrasm is strictly private and noncommercial, your EMail address will only be used for this purpose, and will not be given to somebody else. Your EMail address will also not be visible to other persons receiving these mails.
Informieren Sie mich über neue Versionen von gavrasm per Mail Durch Ausfüllen des Formblattes und Versenden per EMail werden sie per Mail benachrichtigt, wann immer eine neue Version von gavrasm verfügbar ist. Das ist etwa alle drei Monate der Fall. Beachten sie, dass gavrasm strikt privat und nicht-kommerziell ist, ihre EMail-Adresse nur für diesen Zweck verwendet wird und nicht an irgendjemanden anderen weiter gegeben wird. Auch die anderen Empfänger dieser Mails werden ihre MailAdresse nicht sehen. Your Name/Ihr Name:
Your Prename/Ihr Vorname:
Your Mail-address/Ihre EMail-Adresse:
Select your language/Sprachauswahl: English Deutsch mail to webmaster/Mail an Webmaster
Programm gavrasm AVR Assembler fuer ATMEL AVR-Prozessoren ======================================== Kommandozeilen Assembler fuer ATMEL AVR-Prozessoren der Typenreihen - AT90S, - AT90CAN, - AT90USB, - ATtiny, - ATmega, und - ATXmega. Fuer Linux i386 compilierte ausfuehrbare Datei: gavrasm Fuer Dos bzw. Win32 compilierte ausfuehrbare Datei: gavrasm.exe Quellcode in fpc-Pascal Aufrufe und Optionen -------------------Aufruf: gavrasm [-abelmqsw] quelldatei[.asm] Parameter: a: Ausgabe von ANSI-Zeichen umschalten (An/Aus) Default ist An (Linux) bzw. Aus (Dos, Win) b: Erweiterte Syntaxkommentierung fuer Anfaenger (nur bei e!) Default ist Aus (kein zusaetzlicher Kommentar) e: Laengeres, ausfuehrlicheres Format fuer Fehlermeldungen Default ist Aus (kurze Fehlermeldungen) l: Ausgabe der LIST-Datei unterdruecken Default ist LIST-Datei-Ausgabe an m: Expansion von Makros nicht listen Default ist List ein q: Keine Ausgabe von Meldungen auf der Kommandozeile (quiet) Ausnahme: Erzwungene Meldungen (.MESSAGE) Default ist Ausgabe eingeschaltet s: Ausgabe der Liste der Symbole im Listing an Default ist keine Symbolliste w: Ermoegliche Wrap-around Default ist Wrap-Around ausgeschaltet x: Benutze nicht die internen def.inc-Informationen Default ist die Verwendung interner def.inc-Informationen Ausgabedateien: quelldatei.lst ... Assembler-Listing, Textdatei quelldatei.hex ... Code im Format Intel-Hex, Textdatei quelldatei.eep ... EEPROM-Inhalt im Format Intel-Hex, Textdatei (Datei wird wieder gelöscht, wenn kein .ESEGInhalt erzeugt wurde) quelldatei.err ... Fehlermeldungen falls zutreffend, Textdatei Aufruf: gavrasm [-?hdt] Parameter: ?: Liste der Optionen h: Liste der Optionen d: Liste der unterstuetzten Direktiven t: Liste der unterstuetzten AVRTypen Erlaeuterung zur A-Option: Unter Linux ist die Ausgabe von ANSI-Codesequenzen per Default eingeschaltet, bei anderen OS abgeschaltet. Mit der A-Option wird diese Voreinstellung bei allen Betriebssystemen invertiert.
Eigenschaften des Assemblers ---------------------------Im folgenden sind nur die Besonderheiten dieses Assemblers beschrieben. In der Regel ist dieser Assembler kompatibel mit anderen AVR-Assemblern (z.B. dem ATMEL(R)-Assembler). a) Unterschiede dieses Assemblers, die zu Fehlermeldungen und Warnungen bei Code fuehren, der unter anderen Assemblern fehlerfrei assembliert wird * Die Header-Dateien von ATMEL(R), die die Symbole des verwendeten AVR-Typs enthalten, koennen mit der .INCLUDEDirektive angegeben werden. Die Angaben zu der Datei werden aber in der Default-Einstellung von gavrasm ignoriert, die Datei wird nicht gelesen. Stattdessen wird aus dem angegebenen Dateinamen der AVR-Typ extrahiert und die intern im gavrasm gespeicherten Symbole fuer diesen Typ gesetzt. Dies sollte man beachten fuer den Fall, dass die HeaderDateien vom User manuell geaendert oder ergaenzt wurden. gavrasm ignoriert wegen dieses Verhaltens die in der Headerdatei vorgenommenen Aenderungen. Im Gegensatz zu anderen Assemblern werden die Symbole des betreffenden Typs bereits mit der DEVICE-Direktive festgelegt, ein Einlesen von Header-Dateien ist ueberfluessig. Die Headerdatei muss auch nicht vorhanden sein. Diese Automatik kann umgangen werden, indem der Namensbestandteil "def.inc" der Headerdatei veraendert wird und damit die Erkennung der Headerdatei unterbunden wird. Ab Version 2.3 kann diese Eigenschaft auch einfach mit dem Schalter x abgeschaltet werden. Die Angabe von x auf der Kommandozeile führt dazu, dass alle Include-Dateien ausgewertet werden. * Dateinamen (z.B. in der INCLUDE-Direktive) muessen nicht in Gaensefueszen angegeben werden. Dies gilt nicht fuer die Linux-Version, hier muessen Dateinamen in Gaensefueszen eingeschlossen werden ("..."). * Absolutwerte z.B. bei Sprungdistanzen werden bereits dann erkannt, wenn die Sprungdistanz durch ein Vorzeichen eingeleitet wird, wie z.B. " brcc +10". Die Verwendung der Schreibweise PC+10 ist optional. * Ein wichtiger Unterschied zu anderen Assemblern ist bei gavrasm die Verarbeitung von Texten. Die Verwendung dieser Features bedingt gegebenenfalls Inkompatibilitaet des Quellcodes zu anderen Assemblern. Die Unterschiede im Einzelnen: - In Textpassagen kann ein doppeltes Anfuehrungszeichen "" verwendet werden, um ein Anfuehrungszeichen in den Text einzufuegen. Andere Assembler quittieren dies mit "garbage at end of line" und ignorieren den Text nach dem doppelten Anfuehrungszeichen. - Beim Auftreten des Semikolons ; in einer ASCII-Konstante (';') oder einem Text ("...;...") bemaengeln andere Assembler dies mit einer Fehlermeldung, da das Semikolon faelschlich als Kommentarzeichen interpretiert wird. In gavrasm ist das Semikolon in Konstanten und Texten zulaessig und wird korrekt verarbeitet. - Beim Einfuegen von nicht druckbaren ASCII-Steuerzeichen in Texte mittels \ (z.B. "...\m\j\@") interpretieren andere Assembler dieses Steuerzeichen nicht wie gavrasm. Diese uebersetzen \ stattdessen als ASCII-Zeichen. Nur bei einer ungeradzahligen Anzahl macht sich dies mit einer Warnung bemerkbar, die Anzahl Zeichen in der Textkonstante sei ungerade und es werde ein Nullzeichen eingefuegt. * Die .ERROR-Direktive verlangt bei gavrasm einen Text als Parameter, bei dem ATMEL-Assembler nicht. Der Text macht Sinn, weil man ja wissen moechte, was genau den Error verursacht hat. Beim ATMEL-Assembler ist dazu eine zusaetzliche .MESSAGE-Direktive notwendig. b) Verfuegbarer Quellcode: * FPK-Pascal, fuer Linux(i386), DOS (go32v2) und Win32 Zum Kompilieren die neuesten Versionen des Compilers verwenden, aeltere Versionen haben Probleme mit einigen Pascal-Instruktionen (FPK-Compiler ueber http://www.freepascal.org erhaeltlich). Die zahlreichen Warnungen beim Kompilieren koennen ignoriert werden. * Dateien: gavrasm.pas: Hauptdatei gavrdev.pas: Unit mit allen Hardware-Eigenschaften und vordefinierten Symbolen der unterstuetzten AVR-Typen (ersetzt alle bekannten *def.inc Dateien) gavrif.pas: Unit zur Implementierung geschachtelter IF/ELSE/ELIF/ENDIF-Konstruktionen gavrlang.pas: Unit zur Sprachauswahl (deutsch, englisch und franzoesisch verfuegbar: gavrlang_de.pas, gavrlang_en.pas, gavrlang_fr.pas), deutsche Version durch Kopieren von gavrlang_de.pas, Einfuegen als gavrlang.pas und compilieren. gavrline.pas: Unit zum Splitten der asm-Zeilen in ihre Bestandteile gavrmacr.pas: Unit fuer die Makro-Verwaltung gavrout.pas: Unit fuer die Hexausgabe gavrsymb.pas: Unit fuer die Symbole-Verwaltung * Vor dem Kompilieren die gewuenschte Sprachdatei (gavrlang_de.pas) kopieren und als gavrlang.pas umbenennen! * Testdatei zur Ueberpruefung des Assemblers: instr.asm: Testdatei mit allen AVR-Instruktionen testdir.asm: Testdatei mit allen Direktiven des Assemblers c) Direktiven: * DEVICE-Direktive bindet automatisch alle Symbole des betreffenden AVR-Typs ein, das Include von *def.inc-Dateien ist damit jetzt ueberfluessig. * Die EXIT-Direktive ohne Bedingung bricht die weitere Verarbeitung der Quelldatei ab und ist kompatibel mit anderen AVR-Assemblern. Zusaetzlich kann aber auch eine Bedingung als Parameter angegeben werden. Ist die Bedingung erfuellt (TRUE, 1), dann bricht gavrasm den gesamten Assembliervorgang hier ab. Dies funktioniert auch in INCLUDE-Dateien. Dieses Feature kann verwendet werden, um z.B. Bereichspruefungen waehrend der Assemblierung durchzufuehren und bei Ueber- oder Unterschreitung bestimmter Grenzen mit einer entsprechenden Fehlermeldung abzubrechen. Diese Direktive kann teilweise durch die .ERROR-Direktive ersetzt werden. Die .ERROR-Direktive bricht die weitere Assemblierung nicht ab. * Zusaetzliche .IF, .ELSE, ELIF und .ENDIF Direktiven: Code nach .IF (Bedingung) wird nur assembliert, wenn die Bedingung wahr (1) ist, sonst wird zum Code nach der .ELSE-, .ELIF oder .ENDIF-Direktive verzweigt. Mit .ELIF kann im Unterschied zu .ELSE eine zusaetzliche Bedingung abgefragt werden. Bitte die ELIF-Direktive nur alternativ zu ELSE verwenden, eine gemischte Verwendung ist nicht definiert! Beliebig tief verschachtelte .IF, .ELSE/ELIF und .ENDIF Direktiven sind zulaessig. * .IFDEVICE ermoeglicht Verzweigungen abhaengig vom AVR-Typ, andernfalls wird nach .ELSE, .ELIF oder .ENDIF weiter assembliert. * .IFDEF und .IFNDEF Direktive zur Abfrage, ob ein Symbol definiert ist oder nicht. Die Definition des Symbols MUSS vor der ersten Abfrage erfolgen, andernfalls resultieren Uebersetzungsfehler! * .MESSAGE gibt eine erzwungene Meldung (in "..." als Parameter) aus und kann zur Benachrichtigung waehrend des Uebersetzens verwendet werden. * .ERROR erzwingt einen Fehler mit einem definierbaren Fehlertext (in "..." als Parameter). * Zusaetzliche Direktive .SETGLOBAL p1,p2,[...] zum Export lokaler Symbole aus Makros. Normalerweise sind Symbole (Labels) in Makros lokal, d.h. auszerhalb des Makros nicht verwendbar. SETGLOBAL stellt die gelisteten Symbole auch auszerhalb zur Verfuegung. Vorsicht bei der Anwendung dieses Features, es kann zu Fehlern fuehren. * Rekursive .INCLUDE-Direktive mit beliebig tiefer Schachtelung, In der Linux-Version Dateiname auf jeden Fall in Gaensefuesze einschlieszen! * Die Liste der unterstuetzten Direktiven wird durch Aufruf mit gavrasm -d ausgegeben. d) Makros: * Erweiterte Makro-Aufrufe: - verschachtelte Aufrufe ohne Begrenzung zulaessig - erweiterte Parameterueberpruefung bereits innerhalb der Definition, nicht erst beim Aufruf - alle Labels in Makros sind lokal definiert und von auszerhalb des Makros zwecks verbesserter Fehlererkennung unzugaenglich - Export lokaler Symbole mit der .SETGLOBAL-Direktive - Labels in Makros sind auch vorwaerts und bei verschachtelten Makrodefinitionen gueltig - Optionale Verwendung von .ENDM oder .ENDMACRO zum Abschluss des Makros - Liste aller definierten und verwendeten Makros im Listing, wenn die Ausgabe der Symbolliste eingeschaltet ist (Aufruf mit Option -s und ohne Option -l bzw. mit .LIST im Quellcode d) Fehlererkennung und Syntaxpruefung: * Erweiterte Symbolpruefung auf Zulaessigkeit: - exakte Unterscheidung nach Symboltypen (Typen: R fuer Register, C fuer Konstanten, V fuer Variablen, T fuer AVR-TypDefinitionen, M fuer lokale Makro-Sprungmarken, L fuer Sprungmarken, G fuer globalisierte Lokalvariablen in Makros) - erweitert kompatibel mit den meisten gaengigen ATMEL-HeaderDateien (akzeptiert OR und BRTS als Symbolname, weil diese in aelteren *def.inc-Dateien von ATMEL(R) verwendet wurden) - erweiterte Erkennung undefinierter Symbole, keine DefaultSetzung auf Null * Erweiterte Fehlerkommentierung und Warnungen ( mehr als 100 Fehlertypen, 8 Warnungstypen) * Ausfuehrliche Syntaxdarstellung im Anfaengermodus (Option -eb) zu fehlerhaften Instruktionen und Direktiven in der Listdatei * Erweiterte Rechenpruefung (Ueber-, Unterlauf von Rechenschritten bei internen 32-Bit-Integerwerten) * Erweiterte zulaessige Zeichen in Literalkonstanten: ';', '''', '\n', '\\', "abc\n\j", "abc;def" * Zulaessiges Trennzeichen (Punkt, '.') in Binaer- und Hexadezimalzahlen fuer verbesserte Lesbarkeit des Quellcodes (z.B. 0b0001.0010, $124A.035F, 0x82.FABC) * BYTE1-Funktion (analog zu BYTE2 etc.) * Ermoeglicht bei relativen Spruengen die Angabe des Displacements, z.B. rjmp +10 oder brcc -3 (Displacement MUSS in diesem Fall mit einem Vorzeichen beginnen) e) Unterstuetzte AVR-Typen: * ATtiny: 11, 12, 13, 15, 22, 24, 25, 26, 28, 43U, 44, 45, 48, 84, 85, 88, 167, 261, 461, 861, 2313 * AT90CAN: 32, 64, 128 * AT90S: 1200, 2313, 2323, 4343, 4414, 4433, 4434, 8515, 8535 * ATmega: 8, 16, 16HVA, 32, 32U4, 32M1, 32C1, 48, 48P, 64, 83, 88, 88P, 103, 128, 161, 162, 163, 164P, 165, 165P, 168, 168P, 169, 169P, 323, 324P, 325, 325P, 328P, 329, 329P, 406, 640, 644, 644P, 645, 649, 1280, 1281, 1284P, 2560, 2561, 3250, 3290, 3250P, 3290, 3290P, 6450, 6490, 8515, 8535 * ATXmega: 64A1, 128A1 (siehe offene Punkte!) * AT90PWM: 2, 2B, 3, 3B, 216, 316 * AT90USB: 162, 646, 647, 1286, 1287 * AT86RF401 * Liste der unterstuetzten AVR-Typen durch Aufruf mit gavrasm -t f) Sprachversionen: * Deutsche, englische und franzoesische Sprachversion verfuegbar g) Offene Punkte und bekannte Fehler: * Mit den ATXmega Typen hat ATMEL #DEFINE-Anweisungen in die Headerdateien eingefuehrt. Deren Format ist nicht definiert. Diese Defines werden von gavrasm als Symbole definiert und können verwendet werden. Versionen und Aenderungen ------------------------Neueste Versionen unter http://www.avr-asm-tutorial.net/gavrasm/index_de.html Dezember 2008: Version 2.2 - Hinzugefuegt: Unterstuetzung fuer franzoesische Sprachversion (danke an Herve) - Hinzugefuegt: 50 neue AVR-Typen - Geaendert: Die gesamte interne Speicherung und Verarbeitung der Hardware-Eigenschaften der AVR, der def.inc-Dateien und der prozessorspezifischen internen Symbole wurde ueberarbeitet und geaendert. - Entfernt: AVR-Typen AT90C8534, AT90S2343, ATmega104, ATmega603, (die entfernten Typen werden von ATMEL nicht mehr supported obsolet) -Hinzugefuegt: SPM Z+ Unterstuetzung November 2006: Version 2.1 - Korrigiert: Fehler beim Opcode von CALL und JUMP - Korrigiert: Erkennung von ungueltigen Zeichen in Instruktionen September 2006: Version 2.0 - Korrigiert: Fehler bei der Behandlung von Variablen mit SET August 2006: Version 1.9 - Korrigiert: Fehler bei der Erkennung benutzter Symbole - Korrigiert: STD ohne Displacement ergab falschen Opcode July 2006: Version 1.8 - Korrigiert: Fehler bei Memory-Adressen bei einigen ATmega Mai 2006: Version 1.7 - Korrigiert: Pruefung der ADIW/SBIW-Instruktionen. - Korrigiert: ELIF-Direktive ueberarbeitet. Dezember 2005: Version 1.6 - Hinzugefuegt: Unterstuetzung fuer die AVR-Typen AT90CAN32, AT90CAN64, ATtiny24, 44, 84, ATmega644. September 2005: Version 1.5 - Korrigiert: Doppelte Ausgabe von Direktiven beseitigt. - Korrigiert: Problem mit geschachtelten IF/IFDEF/IFNDEF/IFDEVICE Direktiven beseitigt. Juli 2005: Version 1.4 - Hinzugefuegt: Unterstuetzung fir die AVR-Typen AT90CAN128, ATmega329, 3290, 406, 649, 6490, AT90PWM2, 3 - Geaendert: Anders als in frueheren Versionen sind Direktiven in allen Spalten der Zeile erlaubt, Instruktionen duerfen in Spalte 1 der Zeile beginnen. - Geaendert: In D- und E-Segmenten sind alle Direktiven zulaessig. - Korrigiert: Ein Fehler bei der Verarbeitung der Konstante ',' wurde behoben. - Korrigiert: DEVICE Direktive erkannte einige Typen mit laengeren Namen nicht korrekt. - Korrigiert: Port ADCSRA bei ATmega8 wurde nur unter altem Namen erkannt. April 2005: Version 1.3 - Korrigiert: falsche EEPROM-Groesse bei ATmega104 und 128 - Hinzugefuegt: Unterstuetzung fuer die Typen ATmega 1280, 1281, 2560, 2561 und 640 Maerz 2005: Version 1.2 - Korrigiert: Fehler beim Registerbit WGM02 im Port TCCR0B des ATtiny13 war falsch - Korrigiert: Fehlende Parameter bei einer Reihe von Instruktionen wurden faelschlicherweise ohne Kommentar akzeptiert. - Hinzugefuegt: Unterstuetzung fuer die Typen ATtiny25, 45 und 85 Januar 2005: Version 1.1 - Korrigiert: Fehler bei der Anzahl Warnungen - Korrigiert: Fehler bei der Verwendung von SETGLOBAL - Korrigiert: Fehler bei der Verwendung von underscore bei Macronamen - Hinzugefuegt: Unterstuetzung fuer die optionale Angabe von PC bei relativen Sprungdistanzen. Oktober 2004: Version 1.0 - Hinzugefuegt: Unterstuetzung fuer neue Typen ATmega325/3250/ 645/6450 - Geaendert: Gesamte Auswertung von Direktiven ueberarbeitet - Hinzugefuegt: Neue Direktiven .ERROR, .MESSAGE, .ELIF, .IFDEF, IFNDEF fuer verbesserte Kompatibilitaet mit neueren ATMEL(R)Assemblern - Hinzugefuegt: Ausgabe aller Fehlermeldungen in eine separate Fehlerdatei source.err. Maerz 2004: Version 0.9 - Hinzugefuegt: Unterstuetzung fuer neue Typen ATmega48/88/168 Februar 2004: Version 0.8 - Korrigiert: Fehler beim Setzen des RAMEND-Symbols beseitigt - Korrigiert: Falsche SRam-Angaben bei drei aelteren ATmega-Typen - Korrigiert: Assembler startete nicht mit kurzen Fehlermeldungen als Voreinstellung, wenn keine Parameter angegeben wurden Oktober 2003: Version 0.7 - Hinzugefuegt: Unterstuetzung fuer neuen Typ ATtiny2313 - Hinzugefuegt: Direktive .IFDEVICE fuer Verzweigungen abhaengig vom AVR-Typ - Geaendert: Einstufung von ATtiny22 als zuverlaessig, da jetzt Datenblatt verfuegbar September 2003: Version 0.6 - Korrigiert: Fehler bei der Verarbeitung negativer Zahlen in Funktionen behoben - Korrigiert: Fehler bei der Verarbeitung von .DEVICE behoben - Geaendert: Der Aufruf von def.inc-Dateien mit .INCLUDE bewirkt jetzt, dass die entsprechenden internen Symbol-Definitionen geladen werden und eine Warnung ausgegeben wird, die def.inc-Datei wird nicht verarbeitet - Geaendert: Gesamten Instruktionsset ueberarbeitet - Hinzugefuegt: Support fuer einige aeltere Typen, Warnung bei undokumentierten Typen - Hinzugefuegt: Verschachtelte IF-Konstruktionen August 2003: Version 0.5 - Korrigiert: LD/ST-Instruktion fuer AT90S1200 zulaessig, ergab Fehlermeldung! Juli 2003: Version 0.4 - Korrigiert: Missverstaendliche Fehlermeldung bei der Verwendung undefinierter Registersymbole - Hinzugefuegt: DEVICE-Direktive bindet alle Symbole des betreffenden AVR-Typs ein und ersetzt die *def.inc-Dateien. Ausgabe einer Fehlermeldung beim INCLUDE von *def.inc-Dateien! - Unterstuetzung fuer den neuen Typ ATtiny13. Juni 2003: Version 0.3 - Korrigiert: Erkennung doppelter Marken korrigiert (ergab faelschlich internen Compiler-Fehler) - Korrigiert: Konstantenausdruecke, die mit einer Klammer beginnen und enden, wurden nicht richtig erkannt. Daher Unterstuetzung fuer Makro-Aufrufe mit geklammerten Parametern entfernt! Mai 2003: Version 0.2 - Hinzugefuegt: Export lokaler Makrosymbole nach global implementiert - Wrap-Around bei relativen Verzweigungen/Spruengen implementiert, funktioniert bei allen Verzweigungsinstruktionen (RJMP, RCALL) und bei allen bedingten Branch-Instruktionen (Unterschied zu aelteren ATMEL-Assemblern!) - Korrigiert: Mit Studio/Wavrasm kompatible Adressierung in der Hex-Datei (byte-orientiert, nicht wort-orientiert), funktioniert auch bei mehreren .ORG-Direktiven korrekt - Korrigiert: Instruktionen LSL und LSR fuer Typ AT90S1200 gueltig, ergab Fehlermeldung in Version 0.1 - Korrigiert: Marken/Variable/Konstanten mit Unterstrich als erstem Zeichen werden jetzt akzeptiert - Korrigiert: Doppelte Division x/y/z rechnete falsch! - Korrigiert: Instruktionen in Spalte 1 werden nicht akzeptiert, missverstaendliche Fehlermeldung verbessert - Korrigiert: Direktiven nach einer Marke in der gleichen Zeile werden ueberlesen und nicht ausgefuehrt, Fehlermeldung hinzugefuegt - Hinzugefuegt: Kommandozeilenoption E fuer kurze (Default-Einstellung) oder laengere Fehlermeldungen Dezember 2002: Version 0.1 Beta (Erstveroeffentlichung)
Nutzungsbedingungen ------------------- Copyright fuer alle Versionen: (C)2002/2003/2004/2005/2006/2008 by Gerhard Schmidt - Nutzung des Quellcodes und der kompilierten Versionen fuer nichtkommerzielle Zwecke frei. Weiterverbreitung nur unter Beibehaltung der enthaltenen Copyright-Angaben zulaessig. - Keine Garantie fuer korrekte Funktion der Software. - Fehlermeldungen (mit Quellcode, allen Include-Dateien und Listfile) sowie Featurewuensche mit Betreff "gavrasm 2.2" bitte an [email protected]. http://www.avr-asm-tutorial.net/gavrasm/v22/LiesMich.Txt1/20/2009 7:30:33 PM
Einführung in den gavrasm AVR Assembler
Pfad: Home => AVR-Assembler gavrasm => Einführung
Einführung in Gerd's AVR Assembler Hier werden die besonderen Features des gavrasm-Assemblers vorgestellt. Die Beispiele compilieren nicht unbedingt auch auf anderen Assemblern. Im einzelnen werden vorgestellt: 1. 2. 3. 4. 5. 6. 7. 8.
Aufruf des Assemblers auf der Kommandozeile Aufruf des Assemblers mit einer Batch oder in einer Shell Hilfsprogramm zum Aufruf unter Windows Verwendung von IF-ELSE-ENDIF Verwendung von EXIT Verwendung von Makros Verwendung von besonderen literalen Konstanten Verwendung von Trennzeichen in Konstanten
Aufruf des Assemblers auf der Kommandozeile Beim Aufruf in einer Kommandozeile geht man am besten so vor: 1. Man öffnet eine Kommandozeile (Win: meistens unter Start/Programme/Zubehör/Eingabeaufforderung) oder Shell (Linux-KDE) und wechselt mit cd [Pfad] in das Verzeichnis mit dem Quelltext. 2. Das Assemblieren wird mit [gavrasm-pfad]gavrasm -optionen quelltext[.asm] gestartet. Befinden sich alle verwendeten INCLUDE-Dateien im Pfad des Quelltextes, dann braucht man nur den Namen der Include-Dateien (z.B. "8515def.inc") anzugeben. Andernfalls muss dem Namen der korrekte Pfad hinzugefgt werden (z.B. "C:\avrtools\appnotes\8515def.inc" oder "/home/gerd/avrasm/def/8515def.inc"). Die Parameteroptionen sind in der Datei Liesmich.Txt generell aufgelistet. Hier einige ausführlichere Erläuterungen: ●
●
●
●
●
●
●
Die Option -a ist nur unter Linux sinnvoll zu verwenden. Sie schaltet die Ausgabe der einzelnen Zeilennummern während des Kompiliervorganges aus. Die Option -b ist sinnvoll, um bei Fehlermeldungen eine ausführlichere Kommentierung der Parameter einer Instruktion zusätzlich zu erhalten. Sie funktioniert nur, wenn gleichzeitig die Option -e gewählt wird (z.B. -eb). Die Option -l schaltet die Erzeugung der List-Datei generell aus. Nur für Puristen sinnvoll, die unbedingt knappen Plattenplatz sparen wollen. Die Option -q sperrt generell die Ausgabe von Meldungen auf der Kommandozeile. Die einzigen Nachrichten sind INCLUDEs, die Pass-Information und der Erfolg/Misserfolg des Kompiliervorganges (Anzahl Fehler). Die Option -s schaltet die Ausgabe der verwendeten Symbole in der List-Datei an, sofern die Listausgabe nicht mit der Direktive .NOLIST im Quellcode ausgeschaltet ist. Alle Symbole werden am Ende der Datei sortiert nach ihrem Typ ❍ R: Register, ❍ L: Label oder Marke, ❍ C: EQU-Konstante, ❍ V: SET-Variable, ❍ M: Lokales Makro-Symbol (Label/Marke in einem Makro), ❍ G: Globalisiertes Makro-Symbol geordnet ausgegeben. Dabei erhält man als zusätzliche Information, wie oft das Symbol definiert wurde (nDef, sinnvoll bei Variablen), verwendet wird (nUsed) und den letzten Wert des Symbols in dezimaler und hexadezimaler Form. Die Option -w erlaubt das Wrap-Around, also Sprünge vorwärts über das Ende des FlashProgrammspeichers hinaus (an den Anfang des Adressraums) bzw. rückwärts vor den Beginn des Speichers (an das Ende des Adressraums). Die Option ist nur sinnvoll, wenn der AVR einen über 4k großen Flash verfügt und damit einen sehr langen Adressraum hat, der mit relativen Sprüngen nicht mehr direkt überbrückt werden kann. Das Wrap-Around funktioniert in gavrasm auch mit den relativen BranchInstruktionen (z.B. BRCC oder BREQ). Weil das andere Assembler (z.B. der ATMEL-Assembler) aber nicht beherrschen, sind solche Quelltexte nicht mit anderen Assemblern kompilierbar! Die Optionen -?, -h, -d und -t veranlassen gavrasm ausschließlich zur Ausgabe der gewünschten Informationen (-? und -h: zulässige Optionen, -d: implementierte Direktiven, -t: zulässige AVR-Typen und deren Eigenschaften). Eine gleichzeitig angegebene Quelldatei wird dabei ignoriert und nicht assembliert!
An den Seitenanfang
Aufruf des Assemblers mit einer Batch oder in einer Shell Um sich die wiederholte Prozedur der Eingabe von Pfadnamen zu ersparen, kann der Assembler-Aufruf in einer Batchdatei erfolgen. Unter Win wird dazu eine neue Textdatei erzeugt, mit folgenden Zeilen versehen REM Batch-Aufrufdatei [Laufwerk]: CD [Pfad-zur-Sourcedatei] [gavrasm-pfad]\gavrasm [-optionen] quelldatei[.asm] PAUSE Die Textdatei wird mit der Endung ".bat" in einem beliebigen Verzeichnis gespeichert. Wer mag, kann eine Verknüpfung auf diese Batchdatei auf den Desktop legen. Sie kann durch Anklicken gestartet werden und steuert den Kompilierprozess. Die Pause-Anweisung bewirkt, dass sich das Fenster erst nach einer Taste schließt. Unter Linux schreiben wir folgendes Shell-Script: cd [Pfad-zur-Sourcedatei] [Pfad-zu-gavrasm]/gavrasm [-optionen] Quelldatei[.asm] read key und speichern es mit der Endung .sh irgendwo ab. Wer mag, kann sich eine Verknüpfung zu dieser Shelldatei auf dem KDE-Desktop erzeugen (Neu erstellen/ Verknüpfung mit Programm, dabei Ausführung in einem Terminalprogramm wählen) und anschließend die Ausführungrechte setzen. Die Zeile mit read key bewirkt, dass man die Ausgabe im Terminal verfolgen kann, bevor sich das Terminal schließt. An den Seitenanfang
Window Caller - Werkzeug für den Aufruf unter Windows Wenn Du in einer Fensterumgebung arbeitest, könntest Du müde werden beim Schreiben von Batchdateien. Hier ist was einfaches als Ersatz. Auf vielfachen Wunsch habe ich ein Windows-Programm geschrieben, das menuegesteuert Batchdateien erzeugt, den Assembler aufruft und in einem Editorfenster Dateien anzeigen kann. Einige neue Schmankerl sind: ●
● ●
●
●
Die Source- und Include-Dateien lassen sich auch editieren, dabei werden Zeile und Spalte angezeigt (anders als bei den anderen simplen Windows-Editoren). Der Editor arbeitet auch mit Tab-Zeichen. Mit Include eingebundene Dateien werden automatisch erkannt und können editiert werden. Das funktioniert auch rekursiv mit bis zu maximal fünf Include-Dateien. Wird nach dem Assemblieren die List-Datei angezeigt, können Fehler im Quelltext komfortabel gesucht werden. Auf Wunsch werden die Dateien, in denen der Fehler auftrat, in den Editor geladen und direkt die fehlerhafte Stelle angesteuert. Es können jetzt beliebig viele Dateien gleichzeitig angeschaut und editiert werden.
Mehr über die Features und über die Bedienung gibt es in der Lies-Mich-Datei. Das Programm läuft nur unter Windows (sorry, Linuxer) und steht gepackt unter diesem Link zum Download zur Verfügung. An den Seitenanfang
Verwendung von IF-ELSE-ENDIF Die zusätzlichen Direktiven .IF, .ELSE und .ENDIF ermöglichen es, je nach den Einstellungen im Kopf einer Quelldatei oder nach festgestellten Rechenergebnissen Programmteile zu kompilieren oder nicht. Die IFBedingung wird während der Kompilation geprüft. Ist sie erfüllt, wird der folgende Quellcode assembliert. Wenn nicht, wird die Kompilierung des Quellcodes erst nach der .ELSE- oder der .ENDIF-Direktive wieder fortgesetzt. Achtung: Fehlen beide Anweisungen, wird der Rest des gesamten Quelltextes nicht mehr kompiliert! Als Anwendungsbeispiel: .EQU taktfrequenz=40000000 .IF taktfrequenz>4000000 .EQU teiler=4 .ELSE .EQU teiler=2 .ENDIF Ein Blick auf die Symbolliste zeigt, dass Teiler nach dem Kompilieren auf 2 gesetzt ist. Wird die Taktfrequenz im Kopf z.B. auf 10.000.000 umdefiniert, wird der Teiler auf 4 gesetzt. Allgemein vermeidet man dadurch, dass für jede Änderung von minimalen Parametern eigene Änderungen im Quelltext, außer im Kopf, vorgenommen werden müssen. Als typische Anwendung kann ein Quelltext z.B für verschiedene Typen von AVRs geschrieben werden. Weil sich die Interruptvektoren bei jedem Typ an einer anderen Stelle befinden, kann der Vektorbereich spezifisch kompiliert werden. ; ; Definiere Prozessor im Kopf ; .EQU aTyp=2313 ; Prozessor ist ein 2313 ;.EQU aTyp=2323 ; Prozessor wäre ein 2323 ;.EQU aTyp=8515 ; Prozessor wäre ein 8515 ; ; Abschnitt mit den Int-Vektoren ; .CSEG .ORG $0000 rjmp Main ; für alle Typen gleich rjmp IntVecInt0 ; External Int Vector, wird verwendet ; Int-Vektoren für 2313 .IF aTyp == 2313 reti ; IntVecInt1 ; External Int Vector, nicht verwendet reti ; Timer1Capt, nicht verwendet reti ; Timer1/Comp, nicht benutzt reti ; Timer1/Ovf, nicht benutzt rjmp IntVecTC0Ovf ; TC0-Overflow, verwendet reti ; UartRX, nicht benutzt reti ; UartUdre, nicht benutzt reti ; UartTx, nicht verwendet .ENDIF ; Int-Vektoren für 2323 .IF aTyp == 2323 rjmp IntVecTC0Ovf ; TC0-Overflow, verwendet .ENDIF ; Int-Vektoren für 8515 .IF aTyp == 8515 reti ; IntVecInt1 ; External Int Vector, nicht verwendet reti ; Timer1Capt, nicht verwendet reti ; Timer1/CompA, nicht benutzt reti ; Timer1/CompB, nicht benutzt reti ; Timer1/Ovf, nicht benutzt rjmp IntVecTC0Ovf ; TC0-Overflow, verwendet reti ; SpiStc, nicht verwendet reti ; UartRX, nicht benutzt reti ; UartUdre, nicht benutzt reti ; UartTx, nicht verwendet reti ; AnaComp, nicht verwendet .ENDIF ; ; Interrupt-Service-Routine INT0 ; IntVecInt0: [...] reti ; ; Interrupt-Service-Routine TC0-Overflow ; IntVecTC0Ovf: [...] reti ; ; Hauptprogramm ; Main: [...] Es ist klar ersichtlich, dass bei einer Umstellung des Quellcodes für einen anderen Prozessor alle Änderungen sehr umfangreich und deshalb fehlerträchtig sind. Vergisst man die Sache mit den unterschiedlichen Interruptvektoren, ergibt sich ein nettes Fehlersuch-Problem. Mit dem dargestellten Quellcode ist die Umstellung des Programmes für einen anderen Typ ein Leichtes. Selbstverständlich können die Bedingungen der .IF-Direktive auch komplexer formuliert sein, wie z.B. hier: .IF (aTyp == 2313) || (aTyp == 8515) Verschaltelte IF-Direktiven sind wegen der Unübersichtlichkeit und wegen möglicher Fehlerquellen allerdings nicht implementiert. An den Seitenanfang
Verwendung von EXIT Überschreitet eine Größe einen zulässigen Wertebereich, dann bricht man die Assemblierung mit einer Fehlermeldung gerne ab, damit kein Unsinn auf den AVR losgelassen wird. Mit der Direktive .EXIT kann man das erreichen: .EQU taktfrequenz=4000000 .EQU teiler=64 .EXIT (taktfrequenz/teiler)>65535 Damit ist z.B. sichergestellt, dass auch bei anderen Taktfrequenzen kein 16-Bit-Zählerüberlauf zu befürchten ist, ohne dass es der Assemblerprogrammierer merkt. An den Seitenanfang
Verwendung von Makros Makros werden vom Assembler gespeichert und erst beim Aufruf ihres Namens in den Code eingefügt, z.B. so .MACRO mtest .nop .ENDM ; ; Hier wird das Makro eingefügt ; mtest Der Code im Makro ist mittels übergebenen Parametern variierbar. Die Parameter heißen @0 bis @9. Z.B. so ; ; Register global definieren ; .DEF rmp,R16 .MACRO mtest ldi @0,@1 ; Erwartet Register als ersten Param, Konstante als zweiten clr @0 .ENDM ; ; Makro einfügen ; mtest rmp,50 Die Verwendung von Makros weist bei diesem Assembler die Besonderheit auf, dass alle im Makro definierten Marken nur innerhalb des entsprechenden Makros gültig sind. Das verhindert, dass bei mehrfacher Verwendung des Makros Konfusion auftritt. Global definierte Symbole sind von innerhalb des Makros zugänglich, daher darf auch bei allen Makro-internen Symbolen kein Namenskonflikt auftreten. Wegen der lokalen Definition von Marken innerhalb des Makros ist es normalerweise nicht möglich, interne Marken außerhalb des Makros zu verwenden. Um dennoch eine Exportmöglichkeit zu haben, kann man mit der Direktive .SETGLOBAL Marke1[, Marke2, ...] den Wert nach außerhalb des Makros exportieren. Die so gebildete Variable wird zu Beginn jedes Aufrufs des Makros jeweils auf den aktuellen Wert gesetzt. Mit diesem Instrumentarium ist die Möglichkeit gegeben, umfangreiche Bibliotheken für immer wiederkehrende Aufgaben zu schreiben. Weil der Code nur dann eingefügt wird, wenn das Makro auch tatsächlich im Code aufgerufen wird, wird kein Platz verschwendet. An den Seitenanfang
Verwendung von besonderen literalen Konstanten gavrasm ermölicht die Verwendung von ASCII-Kontrollzeichen in Zeichenkonstanten, also z.B. .DB "Dies ist die erste Zeile.\m\jDies ist die zweite Zeile." Ein '\'-Zeichen wird mit zwei '\' eingefügt. Im Gegensatz zu anderen Assemblern werden ';' in Zeichenketten richtig erkannt und behandelt. An den Seitenanfang
Programm GAVRASMW ----------------Gerd's kleiner Helfer zum Assemblieren von AVR Quellcode mit dem Kommandozeilen-Assembler GAVRASM in einer fensterorientierten Umgebung. Freeware fr Windows, letzte Aenderung Oktober 2004 Freier Download unter http://www.avr-asm-tutorial.net/gavrasm/GAVRASMI.html#caller Features: - Erzeugt bequeme Batchdateien zum Aufruf des Assemblers. - Einmal konfiguriert werden alle notwendigen Informationen in einer Projektdatei gespeichert, und zum Assemblieren ist nur ein Mausklick noetig. - Die Aufrufparameter beim Assemblieren sind bequem einstellbar. - Die Listdatei und die erzeugte Batchdatei lassen sich bequem betrachten. - Editieren der Quelldatei sowie aller Include-Dateien mit Anzeige der Zeilen- und Spalteninformationen zum einfachen Entwanzen. - Wenn die Listdatei betrachtet wird und dort auf Fehler hingewiesen wird, laeszst sich direkt die zugehoerige Quelldatei oeffnen und die fehlerhafte Zeile kann editiert werden. Installation: - Entpacken des Zip-Archivs in einen Ordner, in dem Sie Schreibrechte haben. - Erstellen Sie eine Referenz auf die ausfuehrbare Datei auf der Arbeitsoberflaeche oder im Startmenu. Ein neues Projekt erzeugen: - gavrasmw starten. - "Setup" aus dem Menue waehlen. Im Fensterbereich "gavrasm" waehlen Sie "Select" und navigieren sie zu der ausfuehrbaren Datei "gavrasm.exe". Im Fensterbereich "Parameter" waehlen Sie die gewuenschten Parameter fuer den Assembler-Aufruf aus. Im Fensterbereich "Source Code File" waehlen sie "Select" und navigieren Sie zu der gewuenschten Quellcodedatei (dabei koennen Sie auch neue Ordner und eine neue Hauptdatei erzeugen). Entweder mit "Save as default" (gavrasmw startet dann immer mit diesen Einstellungen) oder "Save as Project file" schlieszen. Wenn die Projektdatei als neues Projekt gespeichert werden soll, vergeben Sie einen neuen Dateinamen. Mit dem Knopf "Close" das Fenster schlieszen. - "Edit" und "Source" aus dem Menue waehlen. Ist die HauptQuelldatei noch nicht vorhanden, dann oeffnet sich der Editor mit mit einem Standardinhalt. Editieren Sie die Quelldatei und schlieszen den Editor mit "File" und "Save". - Wenn Sie eine Zeile mit einer Include-Direktive im Quellcode haben und diese Include-Datei keine Standard "*def.inc"-Datei ist, dann koennen Sie mit im Menue mit "Edit" und dem entsprechenden Menue-Eintrag diese Include-Datei editieren. Wenn die Include-Datei noch nicht existiert, dann oeffnet sich diese mit einem standardisierten Header-Eintrag. Zur Zeit koennen maximal fuenf Include-Dateien verwaltet werden. Die Include-Dateien koennen beliebig tief verschachtelt sein. - Waehle "Assemble" im Hauptfenster um den Assembler in einer Kommandozeile zu starten. Dies erzeugt die Batchdatei und startet diese. - Um die List-Ausgabe oder die Error-Ausgabe des Assemblers zu betrachten, waehlen Sie "View" und "Listfile" resp. "View" und "Errorfile". Falls hierin Fehler berichtet werden, verwenden Sie "Error" und "FindNext", um zu den fehlerhaften Zeilen zu navigieren. Befinden Sie sich bei einer Zeile mit einer Fehlermeldung, waehlen Sie "Error" und "Open file" um die Quelldatei in einem separaten Editorfenster zu oeffnen. Korrigieren Sie den Fehler, speichern Sie die Datei und schlieszen Sie das Fenster. Navigieren Sie dann zum naechsten Fehler, falls noch welche enthalten sind. - Betrachten Sie die Batchdatei mit "View" und "Batchfile". Aenderungen in dieser Datei mit einem externen Editor werden bei der naechsten "Assemble"Operation ueberschrieben! Ein bestehendes Projekt oeffnen: - Starte gavrasmw. - Waehle im Menue "Setup". Wenn Sie mit einem anderen als dem Standardprojekt arbeiten wollen, waehlen Sie "Load Project File" und navigieren Sie zu der gewuenschten Projektdatei (Endung: *.asw"). Berichte ueber Bugs in dieser Software an [email protected] mit dem Betreff "gavrasm-caller, Oktober version". http://www.avr-asm-tutorial.net/gavrasm/gavrasmw/LiesMich.txt1/20/2009 7:30:37 PM
SRAM im AVR
Pfad: Home => AVR-Überblick => Programmiertechniken => SRAM-Verwendung
Programmiertechnik für Anfänger in AVR Assemblersprache Verwendung von SRAM in AVR Assembler Alle AVR-Typen verfügen in gewissem Umfang über Statisches RAM (SRAM) an Bord. Bei sehr einfachen Assemblerprogrammen kann man es sich im Allgemeinen leisten, auf die Verwendung dieser Hardware zu verzichten und alles in Registern unterzubringen. Wenn es aber eng wird im Registerbereich, dann sollte man die folgenden Kenntnisse haben, um einen Ausweg aus der Speicherenge zu nehmen.
Was ist SRAM?
SRAM sind Speicherstellen, die im Gegensatz zu Registern nicht direkt in die Recheneinheit (Arithmetic and Logical Unit ALU, manchmal aus historischen Gründen auch Akkumulator genannt) geladen und verarbeitet werden können. Ihre Verwendung ist daher auf den Umweg über ein Register angewiesen. Im dargestellten Beispiel wird ein Wert von der Adresse im SRAM in das Register R2 geholt (1. Instruktion), irgendwie mit dem Inhalt von Register R3 verknüpft und das Ergebnis in Register R3 gespeichert (Instruktion 2). Im letzten Schritt kann der geänderte Wert auch wieder in das SRAM geschrieben werden (3.Instruktion). Es ist daher klar, dass SRAM-Daten langsamer zu verarbeiten sind als Daten in Registern. Dafür besitzt schon der zweitkleinste AVR immerhin 128 Bytes an SRAM-Speicher. Da passt schon einiges mehr rein als in 32 popelige Register. Die größeren AVR ab AT90S8515 aufwärts bieten neben den eingebauten 512 Bytes zusätzlich die Möglichkeit, noch externes SRAM anzuschließen. Die Ansteuerung in Assembler erfolgt dabei in identischer Weise wie internes RAM. Allerdings belegt das externe SRAM eine Vielzahl von Portpins für Adress- und Datenleitungen und hebt den Vorteil der internen Bus-Gestaltung bei den AVR wieder auf.
Zum Seitenanfang
Wozu kann man SRAM verwenden? SRAM bietet über das reine Speichern von Bytes an festen Speicherplätzen noch ein wenig mehr. Der Zugriff kann nicht nur mit festen Adressen, sondern auch mit Zeigervariablen erfolgen, so dass eine fließende Adressierung der Zellen möglich ist. So können z.B. Ringpuffer zur Zwischenspeicherung oder berechnete Tabellen verwendet werden. Das geht mit Registern nicht, weil die immer eine feste Adresse benötigen. Noch relativer ist die Speicherung über einen Offset. Dabei steht die Adresse in einem Pointerregister, es wird aber noch ein konstanter Wert zu dieser Adresse addiert und dann erst gespeichert oder gelesen. Damit lassen sich Tabellen noch raffinierter verwenden. Die wichtigste Anwendung für SRAM ist aber der sogenannte Stack oder Stapel, auf dem man Werte zeitweise ablegen kann, seien es Rücksprungadressen beim Aufruf von Unterprogrammen, bei der Unterbrechung des Programmablaufes mittels Interrupt oder irgendwelche Zwischenwerte, die man später wieder braucht und für die ein extra Register zu schade ist. Zum Seitenanfang
Wie verwendet man SRAM? Um einen Wert in eine Speicherstelle im SRAM abzulegen, muss man seine Adresse festlegen. Das verwendbare SRAM reicht von Adresse 0x0060 bis zum jeweiligen Ende des SRAM-Speichers (beim AT90S8515 ist das ohne externes SRAM z.B. 0x025F). Mit dem Befehl STS 0x0060, R1 wird der Inhalt des Registers R1 in die Speicherzelle im SRAM kopiert. Mit LDS R1, 0x0060 wird vom SRAM in das Register kopiert. Das ist der direkte Weg mit einer festen Adresse, die vom Programmierer festgelegt wird. Um das Hantieren mit festen Adressen und deren möglicherweisen späteren Veränderung bei fortgeschrittener Programmierkunst sowie das Merken der Adresse zu erleichtern empfiehlt der erfahrene Programmierer wieder die Namensvergabe, wie im folgenden Beispiel: .EQU MeineLieblingsSpeicherzelle = 0x0060 STS MeineLieblingsSpeicherzelle, R1 Aber auch das ist noch nicht allgemein genug. Mit .EQU MeineLieblingsSpeicherzelle = SRAM_START .EQU MeineZweiteLieblingsSpeicherzelle = SRAM_START ist die in der Include-Datei eingetragene Adresse der SRAM-Speicherzellen noch allgemeingültiger angegeben. Zugegeben, kürzer ist das alles nicht, aber viel leichter zu merken.
Organisation als Datensegment Bei etwas komplexeren Datenstrukturen empfiehlt sich das Anlegen in einem Datensegment. Eine solche Struktur sieht dann z. B. so aus: .DSEG ; das ist der Beginn des Datensegments, die folgenden Einträge organisieren SRAM .ORG SRAM_START ; an den Beginn des SRAM legen. ; EinByte: ; ein Label als Symbol für die Adresse einer Speicherzelle .BYTE 1 ; ein Byte dafuer reservieren ; ZweiBytes: ; ein Label als Symbol für die Adresse zweier aufeinander folgender Speicherzellen .BYTE 2 ; zwei Bytes reservieren ; .EQU Pufferlaenge = 32 ; definiert die Laenge eines Datenpuffers Buffer_Start: ; ein Label fuer den Anfang des Datenpuffers .BYTE Pufferlaenge ; die folgenden 32 Speicherzellen als Datenpuffer reservieren Buffer_End: ; ein Label fuer das Ende des Datenpuffers ; .CSEG ; Ende des Datensegments, Beginn des Programmsegments Das Datensegment enthält also ausschließlich Labels, über die die entsprechenden reservierten Speicherzellen später adressiert werden können, und .BYTE-Direktiven, die die Anzahl der zu reservierenden Zellen angeben. Ziel der ganzen Angelegenheit ist das Anlegen einer flexibel änderbaren Struktur für den Zugriff über Adresssymbole. Inhalte für diese Speicherzellen werden dabei nicht erzeugt, es gibt auch keine Möglichkeit, den Inhalt von SRAM-Speicherzellen beim Brennen des Chips zu manipulieren.
Zugriffe auf das SRAM über Pointer Eine weitere Adressierungsart für SRAM-Zugriffe ist die Verwendung von Pointern, auch Zeiger genannt. Dazu braucht es zwei Register, die die Adresse enthalten. Wie bereits in der Pointer-RegisterAbteilung erläutert sind das die Registerpaare X mit XL/XH (R26, R27), Y mit YL/YH (R28, R29) und Z mit ZL und ZH (R30, R31). Sie erlauben den Zugriff auf die jeweils addressierte Speicherzelle direkt (z.B. ST X, R1), nach vorherigem Vermindern der Adresse um Eins (z.B. ST -X,R1) oder mit anschliessendem Erhöhen um Eins (z.B. ST X+, R1). Ein vollständiger Zugriff auf drei Zellen sieht also etwa so aus: .EQU MeineLieblingsZelle = 0x0060 .DEF MeinLieblingsRegister = R1 .DEF NochEinRegister = R2 .DEF UndNochEinRegister = R3 LDI XH, HIGH(MeineLieblingszelle) LDI XL, LOW(MeineLieblingszelle) LD MeinLieblingsregister, X+ LD NochEinRegister, X+ LD UndNochEinRegister, X Sehr einfach zu bedienen, diese Pointer. Und nach meinem Dafürhalten genauso einfach (oder schwer) zu verstehen wie die Konstruktion mit dem Dach in gewissen Hochsprachen.
Indizierte Zugriffe über Pointer Die dritte Konstruktion ist etwas exotischer und nur erfahrene Programmierer greifen in ihrer unermesslichen Not danach. Nehmen wir mal an, wir müssen sehr oft und an verschiedenen Stellen eines Programmes auf die drei Positionen im SRAM zugreifen, weil dort irgendwelche wertvollen Informationen stehen. Nehmen wir ferner an, wir hätten gerade eines der Pointerregister so frei, dass wir es dauerhaft für diesen Zweck opfern könnten. Dann bleibt bei dem Zugriff nach ST/LD-Muster immer noch das Problem, dass wir das Pointerregister immer anpassen und nach dem Zugriff wieder in einen definierten Zustand versetzen müssten. Das ist eklig. Zur Vermeidung (und zur Verwirrung von Anfängern) hat man sich den Zugriff mit Offset einfallen lassen (deutsch etwa: Ablage). Bei diesem Zugriff wird das eigentliche Zeiger-Register nicht verändert, der Zugriff erfolgt mit temporärer Addition eines festen Wertes. Im obigen Beispiel würde also folgende Konstruktion beim Zugriff auf die Speicherzelle 0x0062 erfolgen. Zuerst wäre irgendwann das Pointer-Register zu setzen: .EQU MeineLieblingsZelle = 0x0060 .DEF MeinLieblingsRegister = R1 LDI YH, HIGH(MeineLieblingszelle) LDI YL, LOW(MeineLieblingszelle) Irgendwo später im Programm will ich dann auf Zelle 0x0062 zugreifen: STD Y+2, MeinLieblingsRegister Obacht! Die zwei werden nur für den Zugriff addiert, der Registerinhalt von Y wird nicht verändert. Zur weiteren Verwirrung des Publikums geht diese Konstruktion nur mit dem Y- und dem Z-Pointer, nicht aber mit dem X-Pointer! Der korrespondierende Befehl für das indizierte Lesen eines SRAM-Bytes LDD MeinLieblingsRegister, Y+2 ist ebenfalls vorhanden. Das war es schon mit dem SRAM, wenn da nicht der Stack noch wäre. Zum Seitenanfang
Verwendung von SRAM als Stack Die häufigste und bequemste Art der Nutzung des SRAM ist der Stapel, englisch stack genannt. Der Stapel ist ein Türmchen aus Holzklötzen. Jedes neu aufgelegte Klötzchen kommt auf den schon vorhandenen Stapel obenauf, jede Entnahme vom Turm kann immer nur auf das jeweils oberste Klötzchen zugreifen, weil sonst der ganze schöne Stapel hin wäre. Das kluge Wort für diese Struktur ist Last-In-First-Out (LIFO) oder schlichter: die Letzten werden die Ersten sein. Zur Verwirrung des Publikums wächst der Stapel bei fast allen Mikroprozessoren aber nicht von der niedrigsten zu höheren Adressen hin, sondern genau umgekehrt. Wir könnten sonst am Anfang des SRAMs unsere schönen Datenstrukturen nicht anlegen. Einrichten des Stapels Um vorhandenes SRAM für die Anwendung als Stapel herzurichten ist zu allererst der Stapelzeiger einzurichten. Der Stapelzeiger ist ein 16-Bit-Zeiger, der als Port ansprechbar ist. Das Doppelregister heißt SPH:SPL. SPH nimmt das obere Byte der Adresse, SPL das niederwertige Byte auf. Das gilt aber nur dann, wenn der Chip über mehr als 256 Byte SRAM verfügt. Andernfalls fehlt SPH und kann/muss nicht verwendet werden. Wir tun im nächsten Beispiel so, als ob wir mehr als 256 Bytes SRAM haben. Zum Einrichten des Stapels wird der Stapelzeiger mit der höchsten verfügbaren SRAM-Adresse bestückt. (Der Stapel oder Turm wächst nach unten, d.h. zu niedrigeren Adressen hin.) .DEF MeinLieblingsRegister = R16 LDI MeinLieblingsRegister, HIGH(RAMEND) ; Oberes Byte OUT SPH,MeinLieblingsRegister ; an Stapelzeiger LDI MeinLieblingsRegister, LOW(RAMEND) ; Unteres Byte OUT SPL,MeinLieblingsRegister ; an Stapelzeiger Die Größe RAMEND ist natürlich prozessorspezifisch und steht in der Include-Datei für den Prozessor. So steht z.B. in der Datei 8515def.inc die Zeile .equ RAMEND =$25F ;Last On-Chip SRAM Location Die Datei 8515def.inc kommt mit der Assembler-Direktive .INCLUDE "C:\irgendwo\8515def.inc" irgendwo am Anfang des Assemblerprogrammes hinzu. Damit ist der Stapelzeiger eingerichtet und wir brauchen uns im weiteren nicht mehr weiter um diesen Zeiger kümmern, weil er ziemlich automatisch manipuliert wird. Verwendung des Stapels Die Verwendung des Stapels ist unproblematisch. So lassen sich Werte von Registern auf den Stapel legen: PUSH MeinLieblingsregister ; Ablegen des Wertes Wo der Registerinhalt abgelegt wird, interessiert uns nicht weiter. Dass dabei der Zeiger automatisch erniedrigt wird, interessiert uns auch nicht weiter. Wenn wir den abgelegten Wert wieder brauchen, geht das einfach mit: POP MeinLieblingsregister ; Rücklesen des Wertes Mit POP kriegen wir natürlich immer nur den Wert, der als letztes mit PUSH auf den Stapel abgelegt wurde. Wichtig: Selbst wenn der Wert vielleicht gar nicht mehr benötigt wird, muss er mit Pop wieder vom Stapel! Das Ablegen des Registers auf den Stapel lohnt also programmtechnisch immer nur dann, wenn ● ● ●
der Wert in Kürze, d.h. ein paar Befehle weiter im Ablauf, wieder gebraucht wird, alle Register in Benutzung sind und, keine Möglichkeit zur Zwischenspeicherung woanders besteht.
Pfad: Home => AVR-Überblick => Programmiertechniken => Ports
Programmiertechnik für Anfänger in AVR Assemblersprache Was ist ein Port? Ports sind eigentlich ein Sammelsurium verschiedener Speicher. In der Regel dienen sie der Kommunikation mit irgendeiner internen Gerätschaft wie z.B. den Timern oder der Seriellen Schnittstelle oder der Bedienung von äußeren Anschlüssen wie den Parallel-Schnittstellen des AVR. Der wichtigste Port wird weiter unten besprochen: das Status-Register, das an das wichtigste interne Gerät, nämlich den Akkumulator, angeschlossen ist.
Port-Organisation Es gibt insgesamt 64 direkt adressierbare Ports, die aber nicht bei allen AVR-Typen auch tatsächlich physikalisch vorhanden sind. Bei größeren ATmega gibt es neben den direkt adressierbaren Ports noch indirekt adressierbare Ports, doch dazu später mehr. Je nach Größe und Ausstattung des Typs sind eine Reihe von Ports sinnvoll ansprechbar. Welche der Ports in welchem Typ tatsächlich vorhanden sind, ist letztlich aus den Datenblättern zu erfahren. Hier ein Ausschnitt aus den Ports des ATmega8 (von ATMEL zur allgemeinen Verwirrung auch "Register" genannt:
Ports haben eine feste Adresse (in dem oben gezeigten Ausschnitt z. B. 0x3F für den Port SREG, 0x steht dabei für hexadezimal!), über die sie angesprochen werden können. Die Adresse gilt teilweise unabhängig vom AVR-Typ, teilweise aber auch nicht. So befindet sich das Statusregister SREG immer an der Adresse 0x3F, der Ausgabeport der Parallelschnittstelle B immer an der Portadresse 0x18. Ports nehmen oft ganze Zahlen auf, sie können aber auch aus einer Reihe einzelner Steuerbits bestehen. Diese einzelnen Bits haben dann eigene Namen, so dass sie mit Befehlen zur Bitmanipulation angesteuert werden können. In der Portliste hat auch jedes Bit in dem jeweiligen Port seinen speziellen symbolischen Namen, z. B. hat das Bit 7 im Port "GICR" den symbolischen Namen "INT1".
Port-Symbole, Include Diese Adressen und Bit-Nummern muss man sich aber nicht merken. In den Include-Dateien zu den einzelnen AVR-Typen, die der Hersteller zur Verfügung stellt, sind die jeweiligen verfügbaren Ports und ihre Bits mit wohlklingenden Namen belegt. So ist in den Include-Dateien die Assemblerdirektive .EQU PORTB, 0x18 angegeben und wir müssen uns fürderhin nur noch merken, dass der Port B PORTB heißt. Fü das Bit INT1 im Port GICR ist in der Include-Datei definiert: .EQU INT1, 7 Die Include-Datei des AVR ATmega8515 heißt "m8515def.inc" und kommt mit folgender Direktive in die Quellcode-Datei: .INCLUDE "m8515def.inc" oder, wenn man nicht mit dem Studio arbeitet, .INCLUDE "C:\PfadNachIrgendwo\m8515def.inc und alle für diesen Typ bekannten Portregister und Steuerbits sind jetzt mit ihren symbolischen Alias-Namen leichter ansprechbar.
Ports setzen In Ports kann und muss man Werte schreiben, um die betreffende Hardware zur Mitarbeit zu bewegen. So enthält z.B. das MCU General Control Register, genannt MCUCR, eine Reihe von Steuerbits, die das generelle Verhalten des Chips beeinflussen (siehe im Ausschnitt oben bzw. die Beschreibung des MCUCR im Detail). MCUCR ist ein mit Einzelbits vollgepackter Port, in dem jedes Bit noch mal einen eigenen Namen hat (ISC00, ISC01, ...). Wer den Port benötigt, um den AVR in den Tiefschlaf zu versetzen, muss sich im Typenblatt die Wirkung dieser Sleep-Bits heraussuchen und durch eine Folge von Instruktionen die entsprechende einschläfernde Wirkung programmieren, für den ATmega8 also z.B. so: ... .DEF MeinLieblingsregister = R16 LDI MeinLieblingsregister, 0b10000000 OUT MCUCR, MeinLieblingsregister SLEEP Der Out-Befehl bringt den Inhalt meines Lieblingsregisters, nämlich ein gesetztes Sleep-Enable-Bit SE, zum Port MCUCR und versetzt den AVR gleich und sofort in den Schlaf, wenn er im ausgeführten Code auf eine SLEEP-Instruktion trifft. Da gleichzeitig alle anderen Bits mitgesetzt werden und mit Sleep-Mode SM=0 als Modus der Halbschlaf eingestellt wurde, geht der Chip nicht völlig auf Tauchstation. In diesem Zustand wird die Befehlsausführung eingestellt, die Timer und andere Quellen von Interrupts bleiben aber aktiv und können den Halbschlaf jederzeit unterbrechen, wenn sich was Wichtiges tut.
Ports transparenter setzen Weil "LDI MeinLieblingsregister, 0b10000000" eine ziemlich intransparente Angelegenheit ist, weil zum Verständnis dafür, was eigentlich hier gemacht wird, ein Blick in die Portbits im Datenblatt nötig ist, schreibt man dies besser so: LDI MeinLieblingsRegister, 1<<SE "1<<SE" nimmt eine binäre Eins (= 0b00000001) und schiebt diese SE mal links (<<). SE ist im Falle des ATmega8 als 7 definiert, also die 1 sieben mal links schieben. Das Ergebnis (= 0b10000000) ist gleichbedeutend mit dem oben eingefügten Bitmuster und setzt das Bit SE im Port MCUCR auf Eins. Wenn wir gleichzeitig auch noch das Bit SM0 auf 1 setzen wollen (was einen der acht möglichen Tiefschlafmodi einschaltet, dann wird das so formuliert: LDI MeinLieblingsRegister, (1<<SE) | (1<<SM0) Der vertikale Strich zwischen beiden Teilergebnissen ist ein binäres ODER, d. h. dass alle auf Eins gesetzten Bits in einem der beiden Teilausdrücke in Klammern Eins werden, also sowohl das Bit SE (= 7) als auch das Bit SM0 (= 4) gesetzt sind, woraus sich 0b10010000 ergibt. Noch mal zum Merken: die Linksschieberei wird nicht im Prozessor vollzogen, nur beim Assemblieren. Die Instruktion LDI wird auch nicht anders übersetzt, wenn wir die Schieberei verwenden, und ist auch im Prozessor nur eine einzige Instruktion. Von den Symbolen SE und SM0 hat der Prozessor selbst sowieso keine Ahnung, das ist alles nur für den Quellcode-Schreiber von Bedeutung. Die Linksschieberei hat den immensen Vorteil, dass nun für jeden aufgeklärten Menschen sofort erkennbar ist, dass hier die Bits SE und SM0 manipuliert werden. Nicht dass jeder sofort wüsste, dass damit der Schlafmodus eingestellt wird, aber es liegt wenigstens in der Assoziation näher als 0b10010000. Noch ein Vorteil: das SE-Bit liegt bei anderen Prozessoren woanders im Port MCUCR als in Bit 7, z. B. im AT90S8515 lag es in Bit 5. SM0 ist bei diesem Typ gar nicht bekannt, bei einer Portierung unseres Quellcodes für den ATmega8 auf diesen Typ kriegen wir dann eine Fehlermeldung (SM0 ist dort nicht definiert) und wir wissen dann sofort, wo wir suchen und nacharbeiten müssen.
Ports lesen Umgekehrt lassen sich die Portinhalte mit dem IN-Befehl in beliebige Register einlesen und dort weiterverarbeiten. So lädt .DEF MeinLieblingsregister = R16 IN MeinLieblingsregister, MCUCR den lesbaren Teil des Ports MCUCR in das Register R16. Den lesbaren Teil deswegen, weil es bei vielen Ports auch nicht belegte Bits gibt, die dann immer als Null eingelesen werden. Oft will man nur bestimmte Portbits setzen und die Porteinstellung ansonsten so lassen. Das geht mit Lesen-Ändern-Schreiben (Read-Modify-Write) z. B. so: IN MeinLieblingsregister, MCUCR SBR MeinLieblingsRegister, (1<<SE) | (1<<SM0) OUT MCUCR,MeinLieblingsRegister Braucht aber halt drei Instruktionen.
Auf Portbits reagieren Noch öfter als ganze Ports einlesen muss man auf Änderungen bestimmter Bits der Ports prüfen. Dazu muss nicht der ganze Port gelesen und verarbeitet werden. Es gibt hierfür spezielle Sprungbefehle, die aber im Kapitel Springen vorgestellt werden. Umgekehrt kommt es oft vor, dass ein bestimmtes Portbit gesetzt oder rückgesetzt werden muss. Auch dazu braucht man nicht den ganzen Port lesen und nach der Änderung im Register dann den neuen Wert wieder zurückschreiben. Die beiden Befehle heissen SBI (Set Bit I/O-Register) und CBI (Clear Bit I/O-Register). Ihre Anwendung geht z.B. so: .EQU Aktivbit=0 ; Das zu manipulierende Bit des Ports SBI PortB, Aktivbit ; Das Bit wird Eins CBI PortB, Aktivbit ; Das Bit wird Null Die beiden Instruktionen haben einen gravierenden Nachteil: sie lassen sich nur auf Ports bis zur Adresse 0x1F anwenden, für Ports darüber sind sie leider unzulässig. Für Ports oberhalb des mit IN und OUT beschreibbaren Adressraums geht das natürlich schon gar nicht.
Porteinblendung im Speicherraum Für den Zugang zu den Ports, die im nicht direkt zugänglichen Adressraum liegen (z. B. bei einigen großen ATmega und ATxmega) und für den Exotenprogrammierer gibt es wie bei den Registern auch hier die Möglichkeit, die Ports wie ein SRAM zu lesen und zu schreiben, also mit dem LD- bzw. der ST-Instruktion. Da die ersten 32 Adressen im SRAM-Speicherraum schon mit den Registern belegt sind, werden die Ports mit ihrer um 32 erhöhten Adresse angesprochen, wie z.B. bei .DEF MeinLieblingsregister = R16 LDI ZH,HIGH(PORTB+32) LDI ZL,LOW(PORTB+32) LD MeinLieblingsregister,Z Das macht nur im Ausnahmefall einen Sinn, geht aber halt auch. Es ist der Grund dafür, warum das SRAM erst ab Adresse 0x60 beginnt (0x20 für die Register, 0x40 für die Ports reserviert), bei großen ATmega erst bei 0x100. Zum Seitenanfang
Details wichtiger Ports in den AVR Die folgende Tabelle kann als Nachschlagewerk für die wichtigsten gebräuchlichsten Ports im AT90S8515 dienen. Sie enthält nicht alle möglichen Ports. Insbesondere die Ports der MEGA-Typen und der AT90S4434/8535 sind der Übersichtlichkeit halber nicht darin enthalten! Bei Zweifeln immer die Originaldokumentation befragen! Gerät
Link
Register
Link
Akkumulator
SREG
Stack
SPL/SPH Stackpointer
SPL/SPH
Ext.SRAM/ Ext.Interrupt
MCUCR
MCU General Control Register
MCUCR
Interrupt Mask Register
GIMSK
Flag Register
GIFR
Timer Int Mask Register
TIMSK
Timer Interrupt Flag Register
TIFR
Timer/Counter 0 Control Register
TCCR0
Timer/Counter 0
TCNT0
Timer/Counter Control Register 1 A
TCCR1A
Timer/Counter Control Register 1 B
TCCR1B
Timer/Counter 1
TCNT1
Output Compare Register 1 A
OCR1A
Output Compare Register 1 B
OCR1B
Input Capture Register
ICR1L/H
Watchdog Timer Control Register
WDTCR
EEPROM Adress Register
EEAR
Ext.Int.
INT
Timer Interrupts
Timer Int.
Timer 0
Timer 0
Timer 1
Watchdog Timer
EEPROM
Timer 1
WDT
EEPROM EEPROM Data Register
SPI
SPI
UART
SREG
Status Register
UART
EEDR
EEPROM Control Register
EECR
Serial Peripheral Control Register
SPCR
Serial Peripheral Status Register
SPSR
Serial Peripheral Data Register
SPDR
UART Data Register
UDR
UART Status Register
USR
UART Control Register
UCR
UART Baud Rate Register
UBRR
Analog Comparator ANALOG Analog Comparator Control and Status Register ACSR I/O-Ports
IO-Ports
Zum Seitenanfang
Das Statusregister als wichtigster Port Der am häufigste verwendete Port für den Assemblerprogrammierer ist das Statusregister mit den darin enthaltenen acht Bits. In der Regel wird auf diese Bits vom Programm aus nur lesend/auswertend zugegriffen, selten werden Bits explizit gesetzt (mit dem Assembler-Befehl SEx) oder zurückgesetzt (mit dem Befehl CLx). Die meisten Statusbits werden von Bit-Test, Vergleichs- und Rechenoperationen gesetzt oder rückgesetzt und anschliessend für Entscheidungen und Verzweigungen im Programm verwendet. Die folgende Tabelle enthält eine Liste der Assembler-Instruktionen, die die jeweiligen Status-Bits beeinflussen. Bit
Pfad: Home => AVR-Überblick => Programmiertechniken => Ports => Details
Programmiertechnik für Anfänger in AVR Assemblersprache Die Tabelle der wichtigsten Ports in den ATMELAVR-Typen AT90S2313, 2323 und 8515. Byteweise ansprechbare Ports oder Doppelregister sind nicht im Detail dargestellt. Keine Gewähr für Richtigkeit der Angaben!
Status-Register, Akkumulatorflags Port
Funktion
Port-Adresse RAM-Adresse
SREG Status Register Akkumulator 0x3F
0x5F
7
6
5
4
3
2
1
0
I
T
H
S
V
N
Z
C
Bit Name
Bedeutung
Möglichkeiten
7
I
Globales Interrupt Flag
6
T
Bitspeicher
5
H
Halbübertrags-Flag
4
S
Vorzeichen-Flag
3
V
Zweierkomplement-Übertrags-Flag
2
N
Negativ-Flag
1
Z
Null-Flag
0
C
Übertrags-Flag
Befehl
0: Interrupts ausgeschaltet
CLI
1: Interrupts erlaubt/eingeschaltet
SEI
0: Gespeichertes Bit ist 0
CLT
1: Gespeichertes Bit ist 1
SET
0: Kein Halbübertrag aufgetreten
CLH
1: Halbübertrag aufgetreten
SEH
0: Vorzeichen positiv
CLS
1: Vorzeichen negativ
SES
0: Kein Übertrag aufgetreten
CLV
1: Übertrag aufgetreten
SEV
0: Ergebnis war nicht negativ/kleiner CLN 1: Ergebnis war negativ/kleiner
SEN
0: Ergebnis war nicht Null/ungleich CLZ 1: Ergebnis war Null/gleich
SEZ
0: Kein Übertrag aufgetreten
CLC
1: Übertrag aufgetreten
SEC
Zum Seitenanfang
Stackpointer Port
Funktion Port-Adresse RAM-Adresse
SPL/SPH Stackpointer 003D/0x3E Name
0x5D/0x5E
Bedeutung
Verfügbarkeit
SPL
Low-Byte des Stackpointers Ab AT90S2313 aufwärts, nicht bei 1200
SPH
High-Byte des Stackpointers
Ab AT90S8515 aufwärts, nur bei Devices mit >256 Byte internem SRAM
Zum Seitenanfang
SRAM und External Interrupt Steuerung Port
Funktion
Port-Adresse RAM-Adresse
MCUCR MCU General Control Register 0x35
0x55
7
6
5
4
3
2
1
0
SRE
SRW
SE
SM
ISC11
ISC10
ISC01
ISC00
Bit Name
Bedeutung
Möglichkeiten 0=Kein externes SRAM angeschlossen
7
SRE
Ext.SRAM Enable
6
SRW Ext.SRAM Wait States
5
SE
Sleep Enable
4
SM
Sleep Mode
3
ISC11
2
ISC10
1
ISC01
0
ISC00
1=Externes SRAM ansprechen 0=Kein extra Wait Zyklus bei externem SRAM 1=Zusätzlicher Wait State bei externem SRAM 0=Ignoriere SLEEP Befehl 1=SLEEP-Befehle befolgen 0=Idle Mode (Halbschlaf) 1=Power Down Mode (Tiefschlaf) 00: Low-Pegel löst Interrupt aus
Interruptsteuerung Pin INT1 01: Undefiniert (Verknüpft mit GIMSK) 10: Fallende Flanke löst Interrupt aus 11: Ansteigende Flanke löst Interrupt aus 00: Low-Pegel löst Interrupt aus Interruptsteuerung Pin INT0 01: Undefiniert (Verknüpft mit GIMSK) 10: Fallende Flanke löst Interrupt aus 11: Ansteigende Flanke löst Interrupt aus
Zum Seitenanfang
Externe Interrupt-Steuerung Port
Funktion
Port-Adresse RAM-Adresse
GIMSK General Interrupt Maskenregister 0x3B
0x5B
7
6
5
4
3
2
1
0
INT1
INT0
-
-
-
-
-
-
Bit Name
Bedeutung
Möglichkeiten
7
INT1
Interrupt durch externen Pin INT1 0: Externer INT1 ausgeschaltet (Verknüpft mit Modus in MCUCR) 1: Externer INT1 eingeschaltet
6
INT0
Interrupt durch externen Pin INT0 0: Externer INT0 ausgeschaltet (Verknüpft mit Modus in MCUCR) 1: Externer INT0 eingeschaltet
0...5
(Nicht benutzt)
Port
Funktion
Port-Adresse RAM-Adresse
GIFR General Interrupt Flag Register 0x3A
0x5A
7
6
5
4
3
2
1
0
INTF1
INTF0
-
-
-
-
-
-
Bit Name
Bedeutung
Möglichkeiten
7
INTF1
Interrupt durch externen Pin INT1 aufgetreten
6
INTF0
Interrupt durch externen Pin INT0 aufgetreten
0...5
Automatisch Rücksetzen durch Bearbeitung der Int-Routine oder Rücksetzen per Befehl
(Nicht benutzt)
Zum Seitenanfang
Timer Interrupt-Steuerung Port
Funktion
Port-Adresse RAM-Adresse
TIMSK Timer Interrupt Maskenregister 0x39
0x59
7
6
5
4
3
2
1
0
TOIE1
OCIE1A
OCIE1B
-
TICIE1
-
TOIE0
-
Bit Name
Bedeutung
Möglichkeiten 0: Kein Int bei Überlauf
7
TOIE1
Timer/Counter 1 Überlauf-Interrupt
6
OCIE1A Timer/Counter 1 Vergleichszahl A Interrupt
5
OCIE1B Timer/Counter 1 Vergleichszahl B Interrupt
4
1: Int bei Überlauf 0: Kein Int bei Erreichen Stand A 1: Int bei Erreichen Stand in A 0: Kein Int bei Erreichen Stand B 1: Int bei Erreichen Stand in B
(Nicht benutzt)
3
0: Kein Int bei Capture
TICIE1 Timer/Counter 1 Capture-Ereignis Interrupt
2
1: Int bei Capture
(Nicht benutzt)
1
TOIE0
0: Kein Int bei Überlauf
Timer/Counter 0 Überlauf-Interrupt
0
1: Int bei Überlauf
(Nicht benutzt)
Port
Funktion
Port-Adresse RAM-Adresse
TIFR Timer Interrupt Flag Register 0x38
0x58
7
6
5
4
3
2
1
0
TOV1
OCF1A
OCF1B
-
ICF1
-
TOV0
-
Bit Name
Bedeutung
Möglichkeiten
7
TOV1
Timer/Counter 1 Überlauf erreicht
6
OCF1A Timer/Counter 1 Vergleichszahl A erreicht
5
OCF1B Timer/Counter 1 Vergleichszahl B erreicht
4
Interrupt-Modus: Automatisches Rücksetzen bei Bearbeitung der zugehörigen Int-Routine
Compare Ausgang A 00: OC1A/B nicht verbunden 01: OC1A/B wechselt Polarität 10: OC1A/B auf Null setzen COM1B1 Compare Ausgang A 11: OC1A/B auf Eins setzen COM1B0
Pfad: Home => AVR-Überblick => Programmiertechniken => Programmablauf
Programmiertechnik für Anfänger in AVR Assemblersprache Steuerung des Programmablaufes in AVR Assembler Hier werden alle Vorgänge erläutert, die mit dem Programmablauf zu tun haben und diesen beeinflussen, also die Vorgänge beim Starten des Prozessors, Sprünge und Verzweigungen, Unterbrechungen, etc.
Was passiert beim Reset? Beim Anlegen der Betriebsspannung, also beim Start des Prozessors, wird über die Hardware des Prozessors ein sogenannter Reset ausgelöst. Dabei wird der Zähler für die Programmschritte auf Null gesetzt. An dieser Stelle des Programmes wird die Verarbeitung also immer begonnen. Ab hier muss der Programm-Code stehen, der ausgeführt werden soll. Aber nicht nur beim Start wird der Zähler auf diese Adresse zurückgesetzt, sondern auch bei 1. externem Rücksetzen am Eingangs-Pin Reset durch die Hardware, 2. Ablauf der Wachhund-Zeit (Watchdog-Reset), einer internen Überwachungsuhr, 3. direkten Sprüngen an die Adresse Null (Sprünge siehe unten). Die dritte Möglichkeit ist aber gar kein richtiger Reset, denn das beim Reset automatisch ablaufende Rücksetzen von Register- und Port-Werten auf den jeweils definierten Standardwert (Default) wird hierbei nicht durchgeführt. Vergessen wir also besser die 3.Möglichkeit, sie ist unzuverlässig. Die zweite Möglichkeit, nämlich der eingebaute Wachhund, muss erst explizit von der Leine gelassen werden, durch Setzen seiner entsprechenden Port-Bits. Wenn er dann nicht gelegentlich mit Hilfe der Instruktion WDR zurückgepfiffen wird, dann geht er davon aus, dass Herrchen AVR eingeschlafen ist und weckt ihn mit einem brutalen Reset (Kaltstart). Ab dem Reset wird also der an Adresse 0x000000 stehende Code wortweise in die Ausführungsmimik des Prozessors geladen und ausgeführt. Während der Ausführung wird die Adresse um 1 erhöht und schon mal die nächste Instruktion aus dem Programmspeicher geholt (Fetch during Execution). Wenn die erste Instruktion keine Verzweigung des Programmes auslöst, kann die zweite Instruktion also direkt nach dem ersten ausgeführt werden. Jeder Taktzyklus am Takteingang des Prozessors entspricht daher einer ausgef&uum;hrten Instruktion (wenn nichts dazwischenkommt). Die erste Instruktion des ausführbaren Programmes muss immer bei Adresse 0x000000 stehen. Um dem Assembler mitzuteilen, dass er nach irgendwelchen Vorwörtern wie Definitionen oder Konstanten nun bitte mit der ersten Zeile Programmcode beginnen möge, gibt es folgende Direktiven: .CSEG .ORG 0000 Die erste Direktive teilt dem Assembler mit, dass ab jetzt in das Code-Segment zu assemblieren ist. Ein anderes Segment wäre z.B. das EEPROM, das ebenfalls im Assembler-Quelltext auf bestimmte Werte eingestellt werden könnte. Die entsprechende Assembler-Direktive hierfür würde dann natürlich lauten: .ESEG Die zweite Assembler-Direktive oben stellt den Programmzähler beim Assemblieren auf die Adresse 0000 ein. ORG ist die Abkürzung für Origin, also Ursprung. Wir könnten auch bei 0100 mit dem Programm beginnen, aber das wäre ziemlich sinnlos (siehe oben). Da die beiden Angaben CSEG und ORG trivial sind, können sie auch weggelassen werden und der Assembler macht das dann automatisch so. Wer aber den Beginn des Codes nach zwei Seiten Konstantendefinitionen eindeutig markieren will, kann das mit den beiden Direktiven tun. Auf das erste ominöse erste Wort Programmcode folgen fast ebenso wichtige Spezialbefehle, die Interrupt-Vektoren. Dies sind festgelegte Stellen mit bestimmten Adressen. Diese Adressen werden bei Auslösung eines Interrupts durch die Hardware angesprungen, wobei der normale Programmablauf unterbrochen wird. Diese Vektoren sind spezifisch für jeden Prozessortyp (siehe unten bei der Erläuterung der Interrupts). Die Instruktionen, mit denen auf eine Unterbrechung reagiert werden soll, müssen an dieser Stelle stehen. Werden also Interrupts verwendet, dann kann die erste Instruktion nur eine Sprunginstruktion sein, damit diese ominösen Vektoren übersprungen werden. Der typische Programmablauf nach dem Reset sieht also so aus: .CSEG .ORG 0000 RJMP Start [...] hier kommen dann die Interrupt-Vektoren [...] und danach nach Belieben noch alles mögliche andere Zeug Start: ; Das hier ist der Programmbeginn [...] Hier geht das Hauptprogramm dann erst richtig los. Die Instruktion RJMP bewirkt einen Sprung an die Stelle im Programm mit der Kennzeichnung Start:, auch ein Label genannt. Die Stelle Start: folgt weiter unten im Programm und ist dem Assembler hiermit entsprechend mitgeteilt: Labels beginnen immer in Spalte 1 einer Zeile und enden mit einem Doppelpunkt. Labels, die diese beiden Bedingungen nicht einhalten, werden vom Assembler nicht ernstgenommen. Fehlende Labels aber lassen den Assembler sinnierend innehalten und mit einer "Undefined label"-Fehlermeldung die weitere Zusammenarbeit einstellen. Zum Seitenanfang
Linearer Programmablauf und Verzweigungen Nachdem die ersten Schritte im Programm gemacht sind, noch etwas triviales: Programme laufen linear ab. Das heißt: Instruktion für Instruktion wird nacheinander aus dem Programmspeicher geholt und abgearbeitet. Dabei zählt der Programmzähler immer eins hoch. Ausnahmen von dieser Regel werden nur vom Programmierer durch gewollte Verzweigungen oder mittels Interrupt herbeigeführt. Da sind Prozessoren ganz stur immer geradeaus, es sei denn, sie werden gezwungen. Und zwingen geht am einfachsten folgendermaßen. Oft kommt es vor, dass in Abhängigkeit von bestimmten Bedingungen gesprungen werden soll. Dann benötigen wir bedingte Verzweigungen. Nehmen wir an, wir wollen einen 32-Bit-Zähler mit den Registern R1, R2, R3 und R4 realisieren. Dann muss das niederwertigste Byte in R1 um Eins erhöht werden. Wenn es dabei überläuft, muss auch R2 um Eins erhöht werden usw. bis R4. Die Erhöhung um Eins wird mit der Instruktion INC erledigt. Wenn dabei ein Überlauf auftritt, also 255 zu 0 wird, dann ist das im Anschluss an die Durchführung am gesetzten Z-Bit im Statusregister zu bemerken. Das eigentliche Übertragsbit C des Statusregisters wird bei der INCInstruktion nicht verändert, weil es woanders dringender gebraucht wird und das Z-Bit völlig ausreicht. Wenn der Übertrag nicht auftritt, also das Z-Bit nicht gesetzt ist, können wir die Erhöherei beenden. Wenn aber das Z-Bit gesetzt ist, darf die Erhöherei beim nächsten Register weitergehen. Wir müssen also springen, wenn das Z-Bit nicht gesetzt ist. Der entsprechende Sprungbefehl heißt aber nicht BRNZ (BRanch on Not Zero), sondern BRNE (BRanch if Not Equal). Na ja, Geschmackssache. Das ganze Räderwerk des 32-Bit langen Zählers sieht damit so aus: INC R1 BRNE Weiter INC R2 BRNE Weiter INC R3 BRNE Weiter INC R4 Weiter: Das war es schon. Eine einfache Sache.Das Gegenteil von BRNE ist übrigens BREQ oder BRanch EQual. Welche der Statusbits durch welche Instruktionen und Bedingungen gesetzt oder rückgesetzt werden (auch Prozessorflags genannt), geht aus den einzelnen Instruktionsbeschreibungen in der Befehlsliste hervor. Entsprechend kann mit den bedingten Sprunginstruktionen BRCC/BRCS ; Carry-Flag 0 oder gesetzt BRSH ; Gleich oder größer BRLO ; Kleiner BRMI ; Minus BRPL ; Plus BRGE ; Größer oder gleich (mit Vorzeichen) BRLT ; Kleiner (mit Vorzeichen) BRHC/BRHS ; Halbübertrag 0 oder 1 BRTC/BRTS ; T-Bit 0 oder 1 BRVC/BRVS ; Zweierkomplement-Übertrag 0 oder 1 BRIE/BRID ; Interrupt an- oder abgeschaltet auf die verschiedenen Bedingungen reagiert werden. Gesprungen wird immer dann, wenn die entsprechende Bedingung erfüllt ist. Keine Angst, die meisten dieser Instruktionen braucht man sehr selten. Nur Zero und Carry sind für den Anfang wichtig. Zum Seitenanfang
Zeitzusammenhänge beim Programmablauf Wie oben schon erwähnt entspricht die Zeitdauer zur Bearbeitung einer Instruktion in der Regel exakt einem Prozessortakt. Läuft der Prozessor mit 4 MHz Takt, dann dauert die Bearbeitung einer Instruktion 1/4 µs oder 250 ns, bei 10 MHz Takt nur 100 ns. Die Dauer ist quarzgenau vorhersagbar und Anwendungen, die ein genaues Timing erfordern, sind durch entsprechend exakte Gestaltung des Programmablaufes erreichbar. Es gibt aber eine Reihe von Instruktionen, z.B. die Sprungbefehle oder die Lese- und Schreibbefehle für das SRAM, die zwei oder mehr Taktzyklen erfordern. Hier hilft nur die Instruktionstabelle weiter. Um genaue Zeitbeziehungen herzustellen, muss man manchmal den Programmablauf um eine bestimmte Anzahl Taktzyklen verzögern. Dazu gibt es die sinnloseste Instruktion des Prozessors, den Tu-nix-Befehl: NOP Diese Instruktion heißt "No Operation" und tut nichts außer Prozessorzeit zu verschwenden. Bei 4 MHz Takt braucht es genau vier solcher NOPs, um eine exakte Verzögerung um 1 µs zu erreichen. Zu viel anderem ist diese Instruktion nicht zu gebrauchen. Für einen Rechteckgenerator von 1 kHz müssen aber nicht 1000 solcher NOPs kopiert werden, das geht auch anders! Dazu braucht es die Sprunginstruktionen. Mit ihrer Hilfe können Schleifen programmiert werden, die für eine festgelegte Anzahl von Läufen den Programmablauf aufhalten und verzögern. Das können 8-Bit-Register sein, die mit der DEC-Instruktion heruntergezählt werden, wie z.B. bei CLR R1 Zaehl: DEC R1 BRNE Zaehl Es können aber auch 16-Bit-Zähler sein, wie z.B. bei LDI ZH,HIGH(65535) LDI ZL,LOW(65535) Zaehl: SBIW ZL,1 BRNE Zaehl Mit mehr Registern lassen sich mehr Verzögerungen erreichen. Jeder dieser Verzögerer kann auf den jeweiligen Bedarf eingestellt werden und funktioniert dann quarzgenau, auch ohne Hardware-Timer. Zum Seitenanfang
Makros im Programmablauf Es kommt vor, dass in einem Programm identische oder ähnliche Code-Sequenzen an mehreren Stellen benötigt werden. Will oder kann man den Programmteil nicht mittels Unterprogrammen bewältigen, dann kommt für diese Aufgabe auch ein Makro in Frage. Makros sind Codesequenzen, die nur einmal entworfen werden und durch Aufruf des Makronamens in den Programmablauf eingefügt werden. Nehmen wir an, es soll die folgende Codesequenz (Verzögerung um 1 µs bei 4 MHz Takt) an mehreren Programmstellen benötigt werden. Dann erfolgt irgendwo im Quellcode die Definition des folgenden Makros: .MACRO Delay1 NOP NOP NOP NOP .ENDMACRO Diese Definition des Makros erzeugt keinen Code, es werden also keine vier NOPs in den Code eingefügt. Erst der Aufruf des Makros [...] irgendwo im Code Delay1 [...] weiter mit Code bewirkt, dass an dieser Stelle vier NOPs eingebaut werden. Der zweimalige Aufruf des Makros baut acht NOPs in den Code ein. Handelt es sich um größere Codesequenzen, hat man etwas Zeit im Programm oder ist man knapp mit Programmspeicher, sollte man auf den Code-extensiven Einsatz von Makros verzichten und lieber Unterprogramme verwenden. Zum Seitenanfang
Unterprogramme Im Gegensatz zum Makro geht ein Unterprogramm sparsamer mit dem Programmspeicher um. Irgendwo im Code steht die Sequenz ein einziges Mal herum und kann von verschiedenen Programmteilen her aufgerufen werden. Damit die Verarbeitung des Programmes wieder an der aufrufenden Stelle weitergeht, folgt am Ende des Unterprogrammes ein Return. Das sieht dann für 10 Takte Verzögerung so aus: Delay10: NOP NOP NOP RET Unterprogramme beginnen immer mit einem Label, hier Delay10:, weil sie sonst nicht angesprungen werden könnten. Es folgen drei Nix-Tun-Instruktionen und die abschließende Return-Instruktion. Schlaumeier könnten jetzt einwenden, das seien ja bloß 7 Takte (RET braucht 4) und keine 10. Gemach, es kömmt noch der Aufruf dazu und der schlägt mit 3 Takten zu Buche: [...] irgendwo im Programm: RCALL Delay10 [...] weiter im Programm RCALL ist eine Verzweigung zu einer relativen Adresse, nämlich zum Unterprogramm. Mit RET wird wieder der lineare Programmablauf abgebrochen und zu der Instruktion nach dem RCALL verzweigt. Es sei noch dringend daran erinnert, dass die Verwendung von Unterprogrammen das vorherige Setzen des Stackpointers oder Stapelzeigers voraussetzt (siehe Stapel), weil die Rücksprungadresse beim RCALL auf dem Stapel abgelegt wird. Wer das obige Beispiel ohne Stapel programmieren möchte, verwendet den absoluten Sprungbefehl: [...] irgendwo im Programm RJMP Delay10 Zurueck: [...] weiter im Programm Jetzt muss das "Unterprogramm" allerdings anstelle des RET natürlich ebenfalls eine RJMP-Instruktion bekommen und zum Label Zurueck: verzweigen. Da der Programmcode dann aber nicht mehr von anderen Stellen im Programmcode aufgerufen werden kann (die Rückkehr funktioniert jetzt nicht mehr), ist die Springerei ziemlich sinnlos. Auf jeden Fall haben wir jetzt das relative Springen mit RJMP gelernt. RCALL und RJMP sind auf jeden Fall unbedingte Verzweigungen. Ob sie ausgeführt werden hängt nicht von irgendwelchen Bedingungen ab. Zum bedingten Ausführen eines Unterprogrammes muss das Unterprogramm mit einem bedingten Verzweigungsbefehl kombiniert werden, der unter bestimmten Bedingungen das Unterprogramm ausführt oder den Aufruf eben überspringt. Für solche bedingten Verweigungen zu einem Unterprogramm eignen sich die beiden folgenden Instruktionen ideal. Soll in Abhängigkeit vom Zustand eines Bits in einem Register zu einem Unterprgramm oder einem anderen Programmteil verzweigt werden, dann geht das so: SBRC R1,7 ; Überspringe wenn Bit 7 Null ist RCALL UpLabel ; Rufe Unterprogramm auf Hier wird der RCALL zum Unterprogramm UpLabel: nur ausgeführt, wenn das Bit 7 im Register R1 eine Eins ist, weil die Instruktion bei einer Null (Clear) übersprungen wird. Will man auf die umgekehrte Bedingung hin ausführen, so kommt statt SBRC die analoge Instruktion SBRS zum Einsatz. Die auf SBRC/SBRS folgende Instruktion kann sowohl eine Ein-Wort- als auch eine Zwei-Wort-Instruktion sein, der Prozessor weiß die Länge der Instruktion korrekt Bescheid und überspringt dann eben um die richtige Anzahl Instruktionen. Zum Überspringen von mehr als einer Instruktion kann dieser bedingte Sprung natürlich nicht benutzt werden. Soll ein Überspringen nur dann erfolgen, wenn zwei Registerinhalte gleich sind, dann bietet sich die etwas exotische Instruktion CPSE R1,R2 ; Vergleiche R1 und R2, springe wenn gleich RCALL UpIrgendwas ; Rufe irgendwas Der wird selten gebraucht und ist ein Mauerblümchen. Man kann aber auch in Abhängigkeit von bestimmten Port-Bits die nächsten Instruktion überspringen. Die entsprechenden Instruktionen heißen SBIC und SBIS, also etwa so: SBIC PINB,0 ; Überspringe wenn Bit 0 Null ist RJMP EinZiel ; Springe zum Label EinZiel Hier wird die RJMP-Instruktion nur übersprungen, wenn das Bit 0 im Eingabeport PINB gerade Null ist. Also wird das Programm an dieser Stelle nur dann zum Programmteil EinZiel: verzweigen, wenn das Portbit 0 Eins ist. Das ist etwas verwirrend, da die Kombination von SBIC und RJMP etwas umgekehrt wirkt als sprachlich naheliegend ist. Das umgekehrte Sprungverhalten kann anstelle von SBIC mit SBIS erreicht werden. Leider kommen als Ports bei den beiden Instruktionen nur die unteren 32 infrage, für die oberen 32 Ports können diese Instruktionen nicht verwendet werden. Nun noch die Exotenanwendung für den absoluten Spezialisten. Nehmen wir mal an, sie hätten vier Eingänge am AVR, an denen ein Bitklavier angeschlossen sei (vier kleine Schalterchen). Je nachdem, welches der vier Tasten eingestellt sei, soll der AVR 16 verschiedene Tätigkeiten vollführen. Nun könnten Sie selbstverständlich den Porteingang lesen und mit jede Menge Branch-Anweisungen schon herausfinden, welches der 16 Programmteile denn heute gewünscht wird. Sie können aber auch eine Tabelle mit je einem Sprungbefehl auf die sechzehn Routinen machen, etwa so: MeinTab: RJMP Routine1 RJMP Routine2 [...] RJMP Routine16 Jetzt laden Sie den Anfang der Tabelle in das Z-Register mit LDI ZH,HIGH(MeinTab) LDI ZL,LOW(MeinTab) und addieren den heutigen Pegelstand am Portklavier in R16 zu dieser Adresse. ADD ZL,R16 BRCC NixUeberlauf INC ZH NixUeberlauf: Jetzt können Sie nach Herzenslust und voller Wucht in die Tabelle springen, entweder nur mal als Unterprogramm mit ICALL oder auf Nimmerwiederkehr mit IJMP Der Prozessor lädt daraufhin den Inhalt seines Z-Registerpaares in den Programmzähler und macht dort weiter. Manche finden das eleganter als unendlich verschachtelte bedingte Sprünge. Mag sein. Zum Seitenanfang
Interrupts im Programmablauf Sehr oft kommt es vor, dass in Abhängigkeit von irgendwelchen Änderungen in der Hardware oder zu bestimmten Gelegenheiten auf dieses Ereignis reagiert wird. Ein Beispiel ist die Pegeländerung an einem Eingang. Man kann das lösen, indem das Programm im Kreis herum läuft und immer den Eingang befragt, ob er sich nun geändert hat. Die Methode heisst Polling, weil es wie bei die Bienchen darum geht, jede Blüte immer mal wieder zu besuchen und deren neue Pollen einzusammeln. Gibt es ausser diesem Eingang noch anderes wichtiges für den Prozessor zu tun, dann kann das dauernde zwischendurch Anfliegen der Blüte ganz schön nerven und sogar versagen: Kurze Pulse werden nicht erkannt, wenn Bienchen nicht gerade vorbei kam und nachsah, der Pegel aber wieder weg ist. Für diesen Fall hat man den Interrupt erfunden. Der Interrupt ist eine von der Hardware ausgelöste Unterbrechung des Programmablaufes. Dazu kriegt das Gerät zunächst mitgeteilt, dass es unterbrechen darf. Dazu ist bei dem entsprechenden Gerät ein oder mehr Bits in bestimmten Ports zu setzen. Dem Prozessor ist durch ein gesetztes Interrupt Enable Bit in seinem Status-Register mitzuteilen, dass er unterbrochen werden darf. Irgendwann nach den Anfangsfeierlichkeiten des Programmes muss dazu das I-Flag im Statusregister gesetzt werden, sonst macht der Prozessor beim Unterbrechen nicht mit. SEI ; Setze Int-Enable Wenn jetzt die Bedingung eintritt, also z.B. ein Pegel von Null auf Eins wechselt, dann legt der Prozessor seine aktuelle Programmadresse erst mal auf den Stapel ab (Obacht! Der muss vorher eingerichtet sein! Wie das geht siehe Stapel). Er muss ja das unterbrochene Programm exakt an der Stelle wieder aufnehmen, an dem es unterbrochen wurde, dafür der Stapel. Nun springt der Prozessor an eine vordefinierte Stelle des Programmes und setzt die Verarbeitung erst mal dort fort. Die Stelle nennt man Interrupt Vektor. Sie ist prozessorspezifisch festgelegt. Da es viele Geräte gibt, die auf unterschiedliche Ereignisse reagieren können sollen, gibt es auch viele solcher Vektoren. So hat jede Unterbrechung ihre bestimmte Stelle im Programm, die angesprungen wird. Die entsprechenden Programmstellen der wichtigsten Prozessoren sind in der folgenden Tabelle aufgelistet. (Der erste Vektor ist aber kein IntVektor, weil er keine Rücksprung-Adresse auf dem Stapel ablegt!) Name
Pfad: Home => AVR-Überblick => Programmiertechniken => Rechnen
Programmiertechnik für Anfänger in AVR Assemblersprache Rechnen in Assemblersprache Hier gibt es alles Wichtige zum Rechnen in Assembler. Dazu gehören die gebräuchlichen Zahlensysteme, das Setzen und Rücksetzen von Bits, das Schieben und Rotieren, das Addieren/ Subtrahieren/Vergleichen und die Umwandlung von Zahlen.
Zahlenarten in Assembler An Darstellungsarten für Zahlen in Assembler kommen infrage: ● ● ● ● ●
Positive Ganzzahlen Die kleinste handhabbare positive Ganzzahl in Assembler ist das Byte zu je acht Bits. Damit sind Zahlen zwischen 0 und 255 darstellbar. Sie passen genau in ein Register des Prozessors. Alle größeren Ganzzahlen müssen auf dieser Einheit aufbauen und sich aus mehreren solcher Einheiten zusammensetzen. So bilden zwei Bytes ein Wort (Bereich 0 .. 65.535), drei Bytes ein längeres Wort (Bereich 0 .. 16.777.215) und vier Bytes ein Doppelwort (Bereich 0 .. 4.294.967.295). Die einzelnen Bytes eines Wortes oder Doppelwortes können über Register verstreut liegen, da zur Manipulation ohnehin jedes einzelne Register in seiner Lage angegeben sein muss. Damit wir den Überblick nicht verlieren, könnte z.B. ein Doppelwort so angeordnet sein: .DEF dw0 = r16 .DEF dw1 = r17 .DEF dw2 = r18 .DEF dw3 = r19 dw0 bis dw3 liegen jetzt in einer Registerreihe. Soll dieses Doppelwort z.B. zu Programmbeginn auf einen festen Wert (hier: 4.000.000) gesetzt werden, dann sieht das in Assembler so aus: .EQU dwi = 4000000 ; Definieren der Konstanten LDI dw0,LOW(dwi) ; Die untersten 8 Bits in R16 LDI dw1,BYTE2(dwi) ; Bits 8 .. 15 in R17 LDI dw2,BYTE3(dwi) ; Bits 16 .. 23 in R18 LDI dw3,BYTE4(dwi) ; Bits 24 .. 31 in R19 Damit ist die Zahl in verdauliche Brocken auf die Register aufgeteilt und es darf mit dieser Zahl gerechnet werden. Zum Seitenanfang Vorzeichenbehaftete Zahlen Manchmal, aber sehr selten, braucht man auch negative Zahlen zum Rechnen. Die kriegt man definiert, indem das höchstwertigste Bit eines Bytes als Vorzeichen interpretiert wird. Ist es Eins, dann ist die Zahl negativ. In diesem Fall werden alle Zahlenbits mit ihrem invertierten Wert dargestellt. Invertiert heißt, dass -1 zu binär 1111.1111 wird, die 1 also als von binär 1.0000.0000 abgezogen dargestellt wird. Das vorderste Bit ist dabei aber das Vorzeichen, das signalisiert, dass die Zahl negativ ist und die folgenden Bits die Zahl invertiert darstellen. Einstweilen genügt es zu verstehen, dass beim binären Addieren von +1 (0000.0001) und -1 (1111.1111) ziemlich exakt Null herauskommt, wenn man von dem gesetzten Übertragsbit Carry mal absieht. In einem Byte sind mit dieser Methode die Ganzzahlen von +127 (binär: 0111.1111) bis -128 (binär: 1000.000) darstellbar. In Hochsprachen spricht man von Short-Integer. Benötigt man größere Zahlenbereiche, dann kann man weitere Bytes hinzufügen. Dabei enthält nur das jeweils höchstwertigste Byte das Vorzeichenbit für die gesamte Zahl. Mit zwei Bytes ist damit der Wertebereich von +32767 bis -32768 (Hochsprachen: Integer), mit vier Bytes der Wertebereich von +2.147.483.647 bis -2.147.483.648 darstellbar (Hochsprachen: LongInt). Zum Seitenanfang Binary Coded Digit, BCD Die beiden vorgenannten Zahlenarten nutzen die Bits der Register optimal aus, indem sie die Zahlen in binärer Form behandeln. Man kann Zahlen aber auch so darstellen, dass auf ein Byte nur jeweils eine dezimale Ziffer kommt. Eine dezimale Ziffer wird dazu in binärer Form gespeichert. Da die Ziffern von 0 bis 9 mit vier Bits darstellbar sind und selbst in den vier Bits noch Luft ist (in vier Bits würde dezimal 0 .. 15 passen), bleibt dabei ziemlich viel Raum leer. Für das Speichern der Zahl 250 werden schon drei Register gebraucht, also z.B. so: Bit
7
6
5
4
3 2 1 0
Wertigkeit 128 64 32 16 8 4 2 1 R16, Ziffer 1
0
0
0
0 0 0 1 0
R17, Ziffer 2
0
0
0
0 0 1 0 1
R18, Ziffer 3
0
0
0
0 0 0 0 0
Instruktionen zum Setzen: LDI R16,2 LDI R17,5 LDI R18,0
Auch mit solchen Zahlenformaten lässt sich rechnen, nur ist es aufwendiger als bei den binären Formen. Der Vorteil ist, dass solche Zahlen mit fast beliebiger Größe (soweit das SRAM reicht ...) gehandhabt werden können und dass sie leicht in Zeichenketten umwandelbar sind. Zum Seitenanfang Gepackte BCD-Ziffern Nicht ganz so verschwenderisch geht gepacktes BCD mit den Ziffern um. Hier wird jede binär kodierte Ziffer in jeweils vier Bits eines Bytes gepackt, so dass ein Byte zwei Ziffern aufnehmen kann. Die beiden Teile des Bytes werden oberes und unteres Nibble genannt. Packt man die höherwertige Ziffer in die oberen vier Bits des Bytes (Bit 4 bis 7), dann hat das beim Rechnen Vorteile (es gibt spezielle Einrichtungen im AVR zum Rechnen mit gepackten BCD-Ziffern). Die schon erwähnte Zahl 250 würde im gepackten BCD-Format folgendermaäßen aussehen: Byte Ziffern Wert 8 4 2 1 8 4 2 1 2
Zum Setzen ist nun die binäre (0b...) oder die headezimale (0x...) Schreibweise erforderlich, damit die Bits an die richtigen Stellen im oberen Nibble kommen. Das Rechnen mit gepackten BCD ist etwas umständlicher im Vergleich zum Binär-Format, die Zahlenumwandlung in darstellbare Zeichenketten aber fast so einfach wie im ungepackten BCDFormat. Auch hier lassen sich fast beliebig lange Zahlen handhaben. Zum Seitenanfang Zahlen im ASCII-Format Sehr eng verwandt mit dem ungepackten BCD-Format ist die Speicherung von Zahlen im ASCIIFormat. Dabei werden die Ziffern 0 bis 9 mit ihrer ASCII-Kodierung gespeichert (ASCII = American Standard Code for Information Interchange). Das ist ein uraltes, aus Zeiten des mechanischen Fernschreibers stammendes, sehr umständliches, äußerst beschränktes und von höchst innovativen Betriebssystem-Herstellern in das Computer-Zeitalter herüber gerettetes sieben-bittiges Format, mit dem zusätzlich zu irgendwelchen Befehlen der Übertragungssteuerung beim Fernschreiber (z.B. EOT = End Of Transmission) auch Buchstaben und Zahlen darstellbar sind. Es wird in seiner Altertümlichkeit nur noch durch den (ähnlichen) fünfbittigen Baudot-Code für deutsche Fernschreiber und durch den Morse-Code für ergraute Marinefunker übertroffen. In diesem CodeSystem wird die 0 durch die dezimale Ziffer 48 (hexadezimal: 30, binär: 0011.0000), die 9 durch die dezimale Ziffer 57 (hexadezimal: 39, binär: 0011.1001) repräsentiert. Auf die Idee, diese Ziffern ganz vorne im ASCII hinzulegen, hätte man schon kommen können, aber da waren schon die wichtigen Verkehrs-Steuerzeichen für den Fernschreiber. So müssen wir uns noch immer damit herumschlagen, 48 zu einer BCD-kodierten Ziffer hinzu zu zählen oder die Bits 4 und 5 auf eins zu setzen, wenn wir den ASCII-Code über die serielle Schnittstelle senden wollen. Zur Platzverschwendung gilt das schon zu BCD geschriebene. Zum Laden der Zahl 250 kommt diesmal der folgende Quelltext zum Tragen: LDI R18,'2' LDI R17,'5' LDI R16,'0' Das speichert direkt die ASCII-Kodierung in das jeweilige Register. Zum Seitenanfang
Bitmanipulationen Um eine BCD-kodierte Ziffer in ihr ASCII-Pendant zu verwandeln, müssen die Bits 4 und 5 der Zahl auf Eins gesetzt werden. Das verlangt nach einer binären Oder-Verknüpfung und ist eine leichte Aufgabe. Die geht so (ORI geht nur mit Registern ab R16 aufwärts): ORI R16,0x30 Steht ein Register zur Verfügung, in dem bereits 0x30 steht, hier R2, dann kann man das Oder auch mit diesem Register durchführen: OR R1,R2 Zurück ist es schon schwieriger, weil die naheliegende, umgekehrt wirkende Instruktion, ANDI R1,0x0F die die oberen vier Bits des Registers auf Null setzt und die unteren vier Bits beibeh•lt, nur für Register oberhalb R15 möglich ist. Eventuell also in einem solchen Register durchführen! Hat man die 0x0F schon in Register R2, kann man mit diesem Register Und-verknüpfen: AND R1,R2 Auch die beiden anderen Instruktionen zur Bitmanipulation, CBR und SBR, lassen sich nur in Registern oberhalb von R15 durchführen. Sie würden entsprechend korrekt lauten: SBR R16,0b00110000 ; Bits 4 und 5 setzen CBR R16,0b00110000 ; Bits 4 und 5 löschen Sollen eins oder mehrere Bits einer Zahl invertiert werden, bedient man sich gerne des ExklusivOder-Verfahrens, das nur für Register, nicht für Konstanten, zulässig ist: LDI R16,0b10101010 ; Invertieren aller geraden Bits EOR R1,R16 ; in Register R1 und speichern in R1 Sollen alle Bits eines Bytes invertiert werden, kommt das Einer-Komplement ins Spiel. Mit COM R1 wird der Inhalt eines Registers bitweise invertiert, aus Einsen werden Nullen und umgekehrt. So wird aus 1 die Zahl 254, aus 2 wird 253, usw. Anders ist die Erzeugung einer negativen Zahl aus einer Positiven. Hierbei wird das Vorzeichenbit (Bit 7) umgedreht bzw. der Inhalt von Null subtrahiert. Dieses erledigt die Instruktion NEG R1 So wird aus +1 (dezimal: 1) -1 (binär 1111.1111), aus +2 wird -2 (binär 1111.1110), usw. Neben der Manipulation gleich mehrerer Bits in einem Register gibt es das Kopieren eines einzelnen Bits aus dem eigens für diesen Zweck eingerichteten T-Bit des Status-Registers. Mit BLD R1,0 wird das T-Bit im Statusregister in das Bit 0 des Registers R1 kopiert und das dortige Bit überschrieben. Das T-Bit kann vorher auf Null oder Eins gesetzt oder aus einem beliebigen anderen Bit-Lagerplatz in einem Register geladen werden: CLT ; T-Bit auf Null setzen, oder SET ; T-Bit auf Eins setzen, oder BST R2,2 ; T-Bit aus Register R2, Bit 2, laden
Zum Seitenanfang
Schieben und Rotieren Das Schieben von binären Zahlen entspricht dem Multiplizieren und Dividieren mit 2. Beim Schieben gibt es unterschiedliche Mechanismen. Die Multiplikation einer Zahl mit 2 geht einfach so vor sich, dass alle Bits einer binären Zahl um eine Stelle nach links geschoben werden. In das freie Bit 0 kommt eine Null. Das überzählige ehemalige Bit 7 wird dabei in das Carry-Bit im Status-Register abgeschoben. Der Vorgang wird logisches LinksSchieben genannt. LSL R1 Das umgekehrte Dividieren durch 2 heißt Dividieren oder logisches Rechts-Schieben. LSR R1 Dabei wird das frei werdende Bit 7 mit einer 0 gefüllt, während das Bit 0 in das Carry geschoben wird. Dieses Carry kann dann zum Runden der Zahl verwendet werden. Als Beispiel wird eine Zahl durch vier dividiert und dabei gerundet. LSR R1 ; Division durch 2 BRCC Div2 ; Springe wenn kein Runden INC R1 ; Aufrunden Div2: LSR R1 ; Noch mal durch 2 BRCC DivE ; Springe wenn kein Runden INC R1 ; Aufrunden DivE: Teilen ist also eine einfache Angelegenheit bei Binärzahlen (aber nicht durch 3)! Bei Vorzeichen behafteten Zahlen würde das Rechtsschieben das Vorzeichen in Bit 7 übel verändern. Das darf nicht sein. Desdewegen gibt es neben dem logischen Rechtsschieben auch das arithmetische Rechtsschieben. Dabei bleibt das Vorzeichenbit 7 erhalten und die Null wird in Bit 6 eingeschoben. ASR R1 Wie beim logischen Schieben landet das Bit 0 im Carry. Wie nun, wenn wir 16-Bit-Zahlen mit 2 multiplizieren wollen? Dann muss das links aus dem untersten Byte herausgeschobene Bit von rechts in das oberste Byte hineingeschoben werden. Das erledigt man durch Rollen. Dabei landet keine Null im Bit 0 des verschobenen Registers, sondern der Inhalt des Carry-Bits. LSL R1 ; Logische Schieben unteres Byte ROL R2 ; Linksrollen des oberen Bytes Bei der ersten Instruktion gelangt Bit 7 des unteren Bytes in das Carry-Bit, bei der zweiten Instruktion dann in Bit 0 des oberen Bytes. Nach der zweiten Instruktion hängt Bit 7 des oberen Bytes im Carry-Bit herum und wir könnten es ins dritte Byte schieben, usw. Natürlich gibt es das Rollen auch nach rechts, zum Dividieren von 16-Bit-Zahlen gut geeignet. Hier nun alles Rückwärts: LSR R2 ; Oberes Byte durch 2, Bit 0 ins Carry ROR R1 ; Carry in unteres Byte und dabei rollen So einfach ist das mit dem Dividieren bei großen Zahlen. Man sieht sofort, dass AssemblerDividieren viel schwieriger zu erlernen ist als Hochsprachen-Dividieren, oder? Gleich vier mal Spezial-schieben kommt jetzt. Es geht um die Nibble gepackter BCD-Zahlen. Wenn man nun mal das obere Nibble anstelle des unteren Nibble braucht, dann kommt man um vier mal Rollen ROR R1 ROR R1 ROR R1 ROR R1 mit einem einzigen SWAP R1 herum. Das vertauscht mal eben die oberen vier mit den unteren vier Bits. Zum Seitenanfang
Addition, Subtraktion und Vergleich Ungeheuer schwierig in Assembler ist Addieren, Dividieren und Vergleichen. Zart-besaitete Anfänger sollten sich an dieses Kapitel nicht herantrauen. Wer es trotzdem liest, ist übermütig und jedenfalls selbst schuld. Um es gleich ganz schwierig zu machen, addieren wir die 16-Bit-Zahlen in den Registern R1:R2 zu den Inhalten von R3:R4 (Das ":" heißt nicht Division! Das erste Register gibt das High-Byte, das zweite nach dem ":" das Low-Byte an). ADD R2,R4 ; zuerst die beiden Low-Bytes ADC R1,R3 ; dann die beiden High-Bytes Anstelle von ADD wird beim zweiten Addieren ADC verwendet. Das addiert auch noch das Carry-Bit dazu, falls beim ersten Addieren ein Übertrag stattgefunden hat. Sind sie schon dem Herzkasper nahe? Wenn nicht, dann kommt jetzt das Subtrahieren. Also alles wieder rückwärts und R3:R4 von R1:R2 subtrahiert. SUB R2,R4 ; Zuerst das Low-Byte SBC R1,R3 ; dann das High-Byte Wieder derselbe Trick: Anstelle des SUB das SBC, das zusätzlich zum Register R3 auch gleich noch das Carry-Bit von R1 abzieht. Kriegen Sie noch Luft? Wenn ja, dann leisten sie sich das folgende: Abziehen ohne Ernst! Jetzt kommt es knüppeldick: Ist die Zahl in R1:R2 nun größer als die in R3:R4 oder nicht? Also nicht SUB, sondern CP, (für ComPare) und nicht SBC, sondern CPC: CP R2,R4 ; Vergleiche untere Bytes CPC R1,R3 ; Vergleiche obere Bytes Wenn jetzt das Carry-Flag gesetzt ist, kann das nur heißen, dass R3:R4 größer ist als R1:R2. Wenn nicht, dann eben nicht. Jetzt setzen wir noch einen drauf! Wir vergleichen Register R1 und eine Konstante miteinander: Ist in Register R16 ein binäres Wechselbad gespeichert? CPI R16,0xAA Wenn jetzt das Z-Bit gesetzt ist, dann ist das aber so was von gleich! Und jetzt kommt die Sockenauszieher-Hammer-Instruktion! Wir vergleichen, ob das Register R1 kleiner oder gleich Null ist. TST R1 Wenn jetzt das Z-Flag gesetzt ist, ist das Register ziemlich leer und wir können mit BREQ, BRNE, BRMI, BRPL, BRLO, BRSH, BRGE, BRLT, BRVC oder auch BRVS ziemlich lustig springen. Sie sind ja immer noch dabei! Assembler ist schwer, gelle? Na dann, kriegen sie noch ein wenig gepacktes BCD-Rechnen draufgepackt. Beim Addieren von gepackten BCD's kann sowohl die unterste der beiden Ziffern als auch die oberste überlaufen. Addieren wir im Geiste die BCD-Zahlen 49 (=hex 49) und 99 (=hex 99). Beim Addieren in hex kommt hex E2 heraus und es kommt kein ByteÜberlauf zustande. Die untersten beiden Ziffern sind beim Addieren übergelaufen (9+9=18 = hex 12). Folglich ist die oberste Ziffer korrekt um eins erhöht worden, aber die unterste stimmt nicht, sie müsste 8 statt 2 lauten. Also könnten wir unten 6 addieren, dann stimmt es wieder. Die oberste Ziffer stimmt überhaupt nicht, weil hex E keine zulässige BCD-Ziffer ist. Sie müsste richtigerweise 4 lauten (4+9+1=14) und ein Überlauf sollte auftreten. Also, wenn zu E noch 6 addiert werden, kommt dezimal 20 bzw. hex 14 heraus. Alles ganz easy: Einfach zum Ergebnis noch hex 66 addieren und schon stimmt alles. Aber gemach! Das wäre nur korrekt, wenn bei der hintersten Ziffer, wie in unserem Fall, entweder schon beim ersten Addieren oder später beim Addieren der 6 tatsächlich ein Überlauf in die nächste Ziffer auftrat. Wenn das nicht so ist, dann darf die 6 nicht addiert werden. Woran ist zu merken, ob dabei ein Übertrag von der unteren in die höhere Ziffer auftrat? Am Halbübertrags-Bit im Status-Register. Dieses H-Bit zeigt für einige Instruktionen an, ob ein solcher Übertrag aus dem unteren in das obere Nibble auftrat. Dasselbe gilt analog für das obere Nibble, nur zeigt hier das Carry-Bit den Überlauf an. Die folgenden Tabellen zeigen die verschiedenen Möglichkeiten an. ADD R1,R2 ADD Nibble,6 (Half)Carry-Bit (Half)Carry-Bit
Korrektur
0
0
6 wieder abziehen
1
0
keine
0
1
keine
1
1
(geht gar nicht)
Nehmen wir an, die beiden gepackten BCD-Zahlen seien in R2 und R3 gespeichert, R1 soll den Überlauf aufnehmen und R16 und R17 stehen zur freien Verfügung. R16 soll zur Addition von 0x66 dienen (das Register R2 kann keine Konstanten addieren), R17 zur Subtraktion der Korrekturen am Ergebnis. Dann geht das Addieren von R2 und R3 so: LDI R16,0x66 LDI R17,0x66 ADD R2,R3 BRCC NoCy1 INC R1 ANDI R17,0x0F NoCy1: BRHC NoHc1 ANDI R17,0xF0 NoHc1: ADD R2,R16 BRCC NoCy2 INC R1 ANDI R17,0x0F NoCy2: BRHC NoHc2 ANDI R17,0x0F NoHc2: SUB R2,R17 Die einzelnen Schritte: Im ersten Schritt werden die beiden Zahlen addiert. Tritt dabei schon ein Carry auf, dann wird das Ergebnisregister R1 erhöht und eine Korrektur des oberen Nibbles ist nicht nötig (die obere 6 im Korrekturspeicher R16 wird gelöscht). INC und ANDI beeinflussen das H-Bit nicht. War nach der ersten Addition das H-Bit schon gesetzt, dann kann auch die Korrektur des unteren Nibble entfallen (das untere Nibble wird Null gesetzt). Dann wird 0x66 addiert. Tritt dabei nun ein Carry auf, dann wird wie oben verfahren. Trat dabei ein Half-Carry auf, dann wird ebenfalls wie oben verfahren. Schließlich wird das Korrektur-Register vom Ergebnis abgezogen und die Berechnung ist fertig. Kürzer geht es so. LDI R16,0x66 ADD R2,R16 ADD R2,R3 BRCC NoCy INC R1 ANDI R16,0x0F NoCy: BRHC NoHc ANDI R16,0xF0 NoCy: SUB R2,R16 Ich überlasse es dem Leser zu ergründen, warum das so geht. Zum Seitenanfang
Umwandlung von Zahlen Alle Zahlenformate sind natürlich umwandelbar. Die Umwandlung von BCD in ASCII und zurück war oben schon besprochen (Bitmanipulationen). Die Umwandlung von gepackten BCD's ist auch nicht schwer. Zuerst ist die gepackte BCD mit einem SWAP umzuwandeln, so dass das erste Digit ganz rechts liegt. Mit einem ANDI kann dann das obere (ehemals untere) Nibble gelöscht werden, so dass das obere Digit als reine BCD blank liegt. Das zweite Digit ist direkt zugänglich, es ist nur das obere Nibble zu löschen. Von einer BCD zu einer gepackten BCD kommt man, indem man die höherwertige BCD mit SWAP in das obere Nibble verfrachtet und anschließend die niederwertige BCD damit verODERt. Etwas schwieriger ist die Umwandlung von BCD-Zahlen in eine Binärzahl. Dazu macht man zuerst die benötigten Bits im Ergebnisspeicher frei. Man beginnt mit der niedrigstwertigen BCD-Ziffer. Bevor man diese zum Ergebnis addiert, wird das Ergebnis erstmal mit 10 multipliziert. Dazu speichert man das Ergebnis irgendwo zwischen, multipliziert es mit 4 (zweimal links schieben/ rotieren), addiert das alte Ergebnis (mal 5) und schiebt noch einmal nach links (mal 10). Jetzt erst wird die BCD-Ziffer addiert. Tritt bei irgendeinem Schieben oder Addieren des obersten Bytes ein Carry auf, dann passt die BCD-Zahl nicht in die verfügbaren binären Bytes. Die Verwandlung einer Binärzahl in BCD-Ziffern ist noch etwas schwieriger. Handelt es sich um 16Bit-Zahlen, kann man solange 10.000 subtrahieren, bis ein Überlauf auftritt, das ergibt die erste BCDZiffer. Anschließend subtrahiert man 1.000 bis zum Überlauf und erhält die zweite Ziffer, usw. bis 10. Der Rest ist die letzte Ziffer.Die Ziffern 10.000 bis 10 kann man im Programmspeicher in einer wortweise organisierten Tabelle verewigen, z.B. so DezTab: .DW 10000, 1000, 100, 10 und wortweise mit der LPM-Instruktion aus der Tabelle herauslesen in zwei Register. Eine Alternative ist eine Tabelle mit der Wertigkeit jedes einzelnen Bits einer 16-Bit-Zahl, also z.B. .DB 0,3,2,7,6,8 .DB 0,1,6,3,8,4 .DB 0,0,8,1,9,2 .DB 0,0,4,0,9,6 .DB 0,0,2,0,4,8 ; und so weiter bis .DB 0,0,0,0,0,1 Dann wären die einzelnen Bits der 16-Bit-Zahl nach links herauszuschieben und, wenn es sich um eine 1 handelt, der entsprechende Tabellenwert per LPM zu lesen und zu den Ergebnisbytes zu addieren. Ein vergleichsweise aufwendigeres und langsameres Verfahren. Eine dritte Möglichkeit wäre es, die fünf zu addierenden BCD-Ziffern beginnend mit 00001 durch Multiplikation mit 2 bei jedem Durchlauf zu erzeugen und mit dem Schieben der umzuwandelnden Zahl beim untersten Bit zu beginnen. Es gibt viele Wege nach Rom, und manche sind mühsamer.
Hardware zum Erlernen der Assembler-Programmierung
Pfad: Home => AVR-deutsch => Programmiertechniken => Hardware
Hardware für die AVR-Assembler-Programmierung
Damit es beim Lernen von Assembler nicht zu trocken zugeht, braucht es etwas Hardware zum Ausprobieren. Gerade wenn man die ersten Schritte macht, muss der Lernerfolg schnell sichtbar sein. Hier werden mit wenigen einfachen Schaltungen im Eigenbau die ersten Hardware-Grundlagen beschrieben. Um es vorweg zu nehmen: es gibt von der Hardware her nichts einfacheres als einen AVR mit den eigenen Ideen zu bestücken. Dafür wird ein Programmiergerät beschrieben, das einfacher und billiger nicht sein kann. Wer dann größeres vorhat, kann die einfache Schaltung stückweise erweitern. Wer sich mit Löten nicht herumschlagen will und nicht jeden Euro umdrehen muss, kann ein fertiges Programmierboard erstehen. Die Eigenschaften solcher Boards werden hier ebenfalls beschrieben. Zum Seitenanfang
Das ISP-Interface der AVR-Prozessoren Bevor es ins Praktische geht, zunächst ein paar grundlegende Informationen zum Programmieren der Prozessoren. Nein, man braucht keine drei verschiedenen Spannungen, um das Flash-EEPROM eines AVR zu beschreiben und zu lesen. Nein, man braucht keinen weiteren Mikroprozessor, um ein Programmiergerät für einfache Zwecke zu bauen. Nein, man braucht keine 10 I/O-Ports, um so einem Chip zu sagen, was man von ihm will. Nein, man muss den Chip nicht aus der Schaltung auslöten, in eine andere Fassung stecken, ihn dann dort programmieren und alles wieder rückwärts. Geht alles viel einfacher. Für all das sorgt ein in allen Chips eingebautes Interface, über das der Inhalt des Flash-Programmspeichers sowie des eingebauten EEPROM's beschrieben und gelesen werden kann. Das Interface arbeitet seriell und braucht genau drei Leitungen: ● ● ●
SCK: Ein Taktsignal, das die zu schreibenden Bits in ein Schieberegister im AVR eintaktet und zu lesende Bits aus einem weiteren Schieberegister austaktet, MOSI: Das Datensignal, das die einzutaktenden Bits vorgibt, MISO: Das Datensignal, das die auszutaktenden Bits zum Lesen durch die Programmiersoftware ausgibt.
Damit die drei Pins nicht nur zum Programmieren genutzt werden können, wechseln sie nur dann in den Programmiermodus, wenn das RESET-Signal am AVR (auch: RST oder Restart genannt) auf logisch Null liegt. Ist das nicht der Fall, können die drei Pins als beliebige I/O-Signalleitungen dienen. Wer die drei Pins mit dieser Doppelbedeutung benutzen möchte und das Programmieren des AVR in der Schaltung selbst vornehmen möchte, muss z.B. einen Multiplexer verwenden oder Schaltung und Programmieranschluss durch Widerstände voneinander entkoppeln. Was nötig ist, richtet sich nach dem, was die wilden Programmierimpulse mit dem Rest der Schaltung anstellen können. Nicht notwendig, aber bequem ist es, die Versorgungsspannung von Schaltung und Programmier-Interface gemeinsam zu beziehen und dafür zwei weitere Leitungen vorzusehen. GND versteht sich von selbst, VTG bedeutet Voltage Target und ist die Betriebsspannung des Zielsystems. Damit wären wir bei der 6-Draht-ISPProgrammierleitung. Die ISP6-Verbinder haben die nebenstehende, von ATMEL standardisierte Pinbelegung.
Und wie das so ist mit Standards: immer gab es schon welche, die früher da waren, die alle verwenden und an die sich immer noch (fast) alle halten. Hier ist das der 10-polige Steckverbinder. Er hat noch zusätzlich einen LED-Anschluss, über den die Programmiersoftware mitteilen kann, dass sie fertig mit dem Programmieren ist. Auch nicht schlecht, mit einer roten LED über einen Widerstand gegen die Versorgungsspannung ein deutliches Zeichen dafür zu setzen, dass die Programmiersoftware ihren Dienst versieht.
Zum Seitenanfang
Programmierer für den PC-Parallel-Port So, Lötkolben anwerfen und ein Programmiergerät bauen. Es ist denkbar einfach und dürfte mit Standardteilen aus der gut sortierten Bastelkiste schnell aufgebaut sein.
Ja, das ist alles, was es zum Programmieren braucht. Den 25-poligen Stecker steckt man in den Parallelport des PC's, den 10-poligen ISP-Stecker an die AVR-Experimentierschaltung. Wer gerade keinen 74LS245 zur Hand hat, kann auch einen 74HC245 verwenden. Allerdings sollten dann die unbenutzten Eingänge an Pin 11, 12 und 13 einem definierten Pegel zugeführt werden, damit sie nicht herumklappern, unnütz Strom verbraten und HF erzeugen.
Den Rest erledigt die alte ISP-Software, die es auf der ATMEL-Seite kostenlos gibt, PonyProg2000 oder andere Brenn-Software. Allerdings ist bei der Brennsoftware auf die Unterstützung neuerer AVR-Typen zu achten. Wer eine serielle Schnittstelle hat (oder einen USB-Seriell-Wandler) kann sich zum Programmieren aus dem Studio oder anderer Brenner-Software auch den kleinen, süßen AVR910-Programmer bauen (Bauanleitung und Schaltbild siehe die Webseite von Klaus Leidinger). Zum Seitenanfang
Experimentalschaltung mit ATtiny13 Dies ist ein sehr kleines Experimentierboard, das Tests mit den vielseitigen Innereien des ATtiny13 ermöglicht.
D
Bild zeigt das ISP10-Programmier-Interface auf der linken Seite, mit einer Programmier-LED, die über einen Widerstand von 390 Ω an die Betriebsspannung angeschlossen ist, ● den ATtiny13, dessen Reset-Eingang an Pin 1 mit einem Widerstand von 10 kΩ an die Betriebsspannung führt, ● den Stromversorgungsteil mit einem Brückengleichrichter, der mit 9..15 V aus einem ungeregelten Netzteil oder einem Trafo gespeist werden kann, und einem kleinen 5 V-Spannungsregler. ●
Der ATtiny13 braucht keinen externen Quarz oder Taktgenerator, weil er einen internen 9,6 MHz-RCOszillator hat und von der Werksausstattung her mit einem Vorteiler von 8 bei 1,2 MHz arbeitet. Die Hardware kann auf einem kleinen Experimentierboard aufgebaut werden, wie auf dem Bild zu sehen. Alle Pins des ATtiny13 sind hier über Lötnägel zugänglich und können mit einfachen Steckverbindern mit externer Hardware verbunden werden (im Bild eine LED mit Vorwiderstand).
Experimentalschaltung mit AT90S2313 Damit es was mehr zum Programmieren gibt, hier eine einfache Schaltung mit einem schon etwas größeren AVR-Typ. Verwendbar ist der veraltete AT90S2313 oder sein neuerer Ersatztyp ATtiny2313. Die Schaltung hat ● ● ● ●
ein kleines geregeltes Netzteil für den Trafoanschluss (für künftige Experimente mit einem 1A-Regler ausgestattet), einen Quarz-Taktgenerator (hier mit einem 10 MHz-Quarz, es gehen aber auch langsamere), die Teile für einen sicheren Reset beim Einschalten, das ISP-Programmier-Interface (hier mit einem ISP10PIN-Anschluss).
Damit kann man im Prinzip loslegen und an die vielen freien I/O-Pins des 2313 jede Menge Peripherie dranstricken. Das einfachste Ausgabegerät dürfte für den Anfang eine LED sein, die über einen Widerstand gegen die Versorgungsspannung geschaltet wird und die man an einem Portbit zum Blinken animieren kann.
Zum Seitenanfang
Fertige Programmierboards für die AVR-Familie Wer nicht selber löten will oder gerade einige Euros übrig hat und nicht weiss, was er damit anstellen soll, kauft sich ein fertiges Programmierboard.
ATMEL STK500 Leicht erhältlich ist das STK500 von ATMEL. Es bietet u.a.: ● ● ● ● ● ● ● ●
Sockel für die Programmierung der meisten AVR-Typen, serielle und parallele Low- und High-Voltage-Programmierung, ISP6PIN- und ISP10PIN-Anschluss für externe Programmierung, programmierbare Oszillatorfrequenz und Versorgungsspannungen, steckbare Tasten und LEDs, einen steckbaren RS232C-Anschluss (UART), ein serielles Flash-EEPROM, Zugang zu allen Ports über 10-polige Pfostenstecker.
Die Experimente können mit dem mitgelieferten AT90S8515 oder ATmega8515 sofort beginnen. Das Board wird über eine serielle Schnittstelle (COMx) an den Rechner gekoppelt und von den neueren Versionen des Studio's von ATMEL bedient. Für die Programmierung externer Schaltungen besitzt das Board einen ISP6-Anschluss. Damit dürften alle Hardware-Bedürfnisse für den Anfang abgedeckt sein. Für den Anschluss an eine USB-Schnittstelle am PC braucht man noch einen handelsüblichen USB-Seriell-Wandler. Für eine gute automatische Erkennung durch das Studio ist im Gerätemanager eine der Schnittstellen COM2 bis COM4 für den Wandler und eine Geschwindigkeit von 115 kBaud einzustellen. Damit dürften alle Hardware-Bedürfnisse für den Anfang abgedeckt sein.
AVR Dragon Wer an seinem PC oder Notebook keine RS232-Schnittstelle mehr hat, ist mit dem preiswerten AVR Dragon gut bedient. An das kleine schmucke Board kann man eine ISP6- oder ISP10-Schnittstelle anbringen und damit externe Hardware programmieren. Das Board hat auch Schnittstellen für die Hochspannungsprogrammierung.
Pfad: Home => AVR-Überblick => Programmiertechniken => Warum Assembler?
Programmiertechnik für Anfänger in AVR Assemblersprache Assembler oder Hochsprache, das ist hier die Frage. Warum soll man noch eine neue Sprache lernen, wenn man schon welche kann? Das beste Argument: Wer in Frankreich lebt und nur Englisch kann, kann sich zwar durchschlagen, aber so richtig heimisch und unkompliziert ist das Leben dann nicht. Mit verquasten Sprachkonstruktionen kann man sich zwar durchschlagen, aber elegant hört sich das meistens nicht an. Und wenn es schnell gehen muss, geht es eben öfter schief.
In der Kürze liegt die Würze Assemblerbefehle übersetzen sich 1 zu 1 in Maschinenbefehle. Auf diese Weise macht der Prozessor wirklich nur das, was für den angepeilten Zweck tatsächlich erforderlich ist und was der Programmierer auch gerade will. Keine extra Schleifen und nicht benötigten Features stören und blasen den ausgeführten Code auf. Wenn es bei begrenztem Programmspeicher und komplexerem Programm auf jedes Byte ankommt, dann ist Assembler sowieso Pflicht. Kürzere Programme lassen sich wegen schlankerem Maschinencode leichter entwanzen, weil jeder einzelne Schritt Sinn macht und zu Aufmerksamkeit zwingt. zum Seitenanfang
Schnell wie Hund Da kein unnötiger Code ausgeführt wird, sind Assembler-Programme maximal schnell. Jeder Schritt ist von voraussehbarer Dauer. Bei zeitkritischen Anwendungen, wie z.B. bei Zeitmessungen ohne Hardware-Timer, die bis an die Grenzen der Leistungsfähigkeit des Prozessors gehen sollen, ist Assembler ebenfalls zwingend. Soll es gemütlich zugehen, können Sie programmieren wie Sie wollen.
Assembler ist leicht erlernbar Es stimmt nicht, dass Assembler komplizierter und schwerer erlernbar ist als Hochsprachen. Das Erlernen einer einzigen Assemblersprache macht Sie mit den wichtigsten Grundkonzepten vertraut, das Erlernen von anderen Assembler-Dialekten ist dann ein Leichtes. Der erste Code sieht nicht sehr elegant aus, mit jedem Hunderter an Quellcode sieht das schon schöner aus. Schönheitspreise kriegt man erst ab einigen Tausend Zeilen Quellcode. Da viele Features prozessorabhängig sind, ist Optimierung eine reine Übungsangelegenheit und nur von der Vertrautheit mit der Hardware und dem Dialekt abhängig. Die ersten Schritte fallen in jeder neu erlernten Sprache nicht leicht und nach wenigen Wochen lächelt man über die Holprigkeit und Umständlichkeit seiner ersten Gehversuche. Manche Assembler-Befehle lernt man eben erst nach Monaten richtig nutzen. zum Seitenanfang
AVR sind ideal zum Lernen Assemblerprogramme sind gnadenlos, weil sie davon ausgehen, dass der Programmierer jeden Schritt mit Absicht so und nicht anders macht. Alle Schutzmechanismen muss man sich selber ausdenken und auch programmieren, die Maschine macht bedenkenlos jeden Unsinn mit. Kein Fensterchen warnt vor ominösen Schutzverletzungen, es sei denn man hat das Fenster selber programmiert. Denkfehler beim Konstruieren sind aber genauso schwer aufzudecken wie bei Hochsprachen. Das Ausprobieren ist bei den ATMEL-AVR aber sehr leicht, da der Code rasch um einige wenige Diagnostikzeilen ergänzt und mal eben in den Chip programmiert werden kann. Vorbei die Zeiten mit EPROM löschen, programmieren, einsetzen, versagen und wieder von vorne nachdenken. Änderungen sind schnell gemacht, kompiliert und entweder im Studio simuliert, auf dem STK-Board ausprobiert oder in der realen Schaltung einprogrammiert, ohne dass sich ein IC-Fuß verbogen oder die UV-Lampe gerade im letzten Moment vor der großen Erleuchtung den Geist aufgegeben hat. zum Seitenanfang
Pfad: Home => AVR-deutsch => Programmiertechniken => Werkzeuge
Werkzeuge für die AVR-Assembler-Programmierung In diesem Abschnitt werden die Werkzeuge vorgestellt, die zum Assembler-Programmieren nötig sind. Dabei werden zunächst Werkzeuge besprochen, die jeden Arbeitsschritt separat erledigen, damit die zugrunde liegenden Schritte einzeln nachvollzogen werden können. Erst danach wird eine integrierte Werkzeugumgebung vorgestellt. Für die Programmierung werden vier Teilschritte und Werkzeuge benötigt. Im einzelnen handelt es sich um 1. 2. 3. 4.
den Editor, den Assembler, das Programmier-Interface, und den Simulator.
Der Editor Assemblerprogramme schreibt man mit einem Editor. Der braucht im Prinzip nicht mehr können als ASCII-Zeichen zu schreiben und zu speichern. Im Prinzip täte es ein sehr einfaches Schreibgerät. Wir zeigen hier ein etwas veraltetes Gerät, den WAVRASM von ATMEL. Der WAVRASM sieht nach der Installation und nach dem Start eines neuen Projektes etwa so aus:
Im Editor schreiben wir einfach die Assemblerbefehle und Befehlszeilen drauf los, gespickt mit Kommentaren, die alle mit einem Semikolon beginnen. Das sollte dann etwa so aussehen:
Nun wird das Assembler-Programm mit dem File-Menue in irgendein Verzeichnis, am besten in ein eigens dazu errichtetes, abgespeichert. Fertig ist der Assembler-Quellcode. Manche Editoren erkennen Instruktionen und Symbole und färben diese Worte entsprechend ein, so dass man Tippfehler schnell erkennt und ausbessern kann (Syntax-Highlighting genannt). In einem solchen Editor sieht unser Programm so aus:
Auch wenn die im Editor eingegebenen Worte noch ein wenig kryptisch aussehen, sie sind von Menschen lesbar und für den Mikroprozessor noch völlig unbrauchbar. Zum Seitenanfang
Der Assembler Nun muss das ganze Programm von der Textform in die Maschinen-sprachliche Form gebracht werden. Den Vorgang heisst man Assemblieren, was in etwa "Zusammenbauen", "Auftürmen" oder auch "zusammenschrauben" bedeutet. Das erledigt ein Programm, das auch so heißt: Assembler. Derer gibt es sehr viele. Für AVR heißen die zum Beispiel "AvrAssembler" oder "AvrAssembler2" (von ATMEL, Bestandteil der Studio-Entwicklungsumgebung), "TAvrAsm" (Tom's AVR Assembler) oder mein eigener "GAvrAsm" (Gerd's AVR Assembler). Welchen man nimmt, ist weitgehend egal, jeder hat da so seine Stärken, Schwächen und Besonderheiten. Beim WAVRASM klickt man dazu einfach auf den Menuepunkt mit der Aufschrift "Assemble". Das Ergebnis ist in diesem Bild zu sehen. Der Assembler geruht uns damit mitzuteilen, dass er das Programm übersetzt hat. Andernfalls würfe er mit dem Schlagwort Error um sich. Immerhin ein Wort Code ist dabei erzeugt worden. Und er hat aus unserer einfachen Textdatei gleich vier neue Dateien erzeugt. In der ersten der vier neuen Dateien, TEST.EEP, befindet sich der Inhalt, der in das EEPROM geschrieben werden soll. Er ist hier ziemlich uninteressant, weil wir nichts ins EEPROM programmieren wollten. Hat er gemerkt und die Datei auch gleich wieder gelöscht.
Die zweite Datei, TEST.HEX, ist schon wichtiger, weil hier die Befehlsworte untergebracht sind. Diese Datei brauchen wir zum Programmieren des Prozessors. Sie enthält die nebenstehenden Hieroglyphen. Die hexadezimalen Zahlen sind als ASCII-Zeichen aufgelöst und werden mit Adressangaben und Prüfsummen zusammen abgelegt. Dieses Format heisst Intel-Hex-Format und ist uralt. Jedenfalls versteht diesen Salat jede Programmier-Software recht gut.
Die dritte Datei, TEST.OBJ, kriegen wir später, sie wird zum Simulieren gebraucht. Ihr Format ist hexadezimal und von ATMEL speziell zu diesem Zweck definiert. Sie sieht im Hex-Editor wie abgebildet aus. Merke: Diese Datei wird vom Programmiergerät nicht verstanden!
Die vierte Datei, TEST.LST, können wir uns mit einem Editor anschauen. Sie enthält das Nebenstehende. Wir sehen das Programm mit allen Adressen (hier: "000000"), Maschinenbefehlen (hier: "cfff") und Fehlermeldungen (hier: keine) des Assemblers. Die List-Datei braucht man selten, aber gelegentlich.
Zum Seitenanfang
Das Programmieren des Chips Nun muss der in der Hex-Datei abgelegte Inhalt dem AVR-Chip beigebracht werden. Das erledigt Brenner-Software. Üblich sind die Brenn-Tools im Studio von ATMEL, das vielseitige PonyProg 2000 und andere mehr (konsultiere die Lieblings-Suchmaschine). Das Brennen wird hier am Beispiel des ISP gezeigt. Das gibt es nicht mehr, arbeitet aber sehr viel anschaulicher als moderne Software, weil es einen Blick in das FlashMemory des AVR ermöglicht. Wir starten das Programm ISP, erzeugen ein neues Projekt und laden die gerade erzeugte Hex-Datei mit LOAD PROGRAM. Das sieht dann wie im Bild aus. Wenn wir nun mit dem Menue Program den Chip programmieren, dann legt dieser gleich los. Beim Brennen gibt eine Reihe von weiteren Voraussetzungen (richtige Schnittstelle auswählen, Adapter an Schnittstelle angeschlossen, Chip auf dem Programmierboard vorhanden, Stromversorgung auf dem Board eingeschaltet, ...), ohne die das natürlich nicht geht.
Zum Seitenanfang
Das Simulieren im Studio In einigen Fällen hat selbst geschriebener Code nicht bloß Tippfehler, sondern hartnäckige logische Fehler. Die Software macht einfach nicht das, was sie soll, wenn der Chip damit gebrannt wird. Tests auf dem Chip selbst können kompliziert sein, speziell wenn die Hardware aus einem Minimum besteht und keine Möglichkeit besteht, Zwischenergebnisse auszugeben oder wenigstens Hardware-Signale zur Fehlersuche zu benutzen. In diesen Fällen hat das Studio-Software-Paket von ATMEL einen Simulator, der für die Entwanzung ideale Möglichkeiten bietet. Das Programm kann Schritt für Schritt abgearbeitet werden, die Zwischenergebnisse in Prozessorregistern sind überwachbar, etc. Die folgenden Bilder sind der Version 4 entnommen, die ältere Version 3 sieht anders aus, macht aber in etwa dasselbe. Die Studio Software enthält alles, was man zur Entwicklung, zur Fehlersuche, zur Simulation, zum Brennen der Programme und an Hilfen braucht. Nach der Installation und dem Start des Riesenpakets (z. Zt. ca. 100 MB) sieht das erste Bild wie folgt aus: Der erste Dialog fragt, ob wir ein neues oder bereits bestehendes Projekt öffnen wollen. Im Falle einer Erstinstallation ist "New Project" die korrekte Antwort. Der Knopf "Next>>" bringt uns zum Einstellungsdialog für das neue Projekt:
Hier wird die Plattform "Simulator" ausgewählt.
Ferner wird hier der Prozessor-Zieltyp (hier: ATmega8) ausgewählt und der Dialog mit "Finish" abgeschlossen. Das öffnet ein ziemlich großes Fenster mit ziemlich vielen Bestandteilen:
● ● ● ● ●
links oben das Fenster mit der Projektverwaltung, in dem Ein- und Ausgabedateien verwaltet werden können, in der Mitte oben ist das Editorfenster zu sehen, das den Inhalt der Datei "test1.asm" anzeigt und in das der Quelltext eingegeben werden kann, rechts oben die Hardware-Ansicht des Zielprozessors (dazu später mehr), links unten das "Build"-Fensters, in dem Diagnose-Ausgaben erscheinen, und rechts unten ein weiteres Feld, in dem diverse Werte beim Entwanzen angezeigt werden.
Alle Fenster sind in Größe und Lage verschiebbar. Für den Nachvollzug der nächsten Schritte im Studio ist es notwendig, das im Editorfenster sichtbare Programm abzutippen (zu den Bestandteilen des Programmes später mehr) und mit "Build" und "Build" zu übersetzen. Das bringt die Ausgabe im unteren Build-Fenster zu folgender Ausgabe:
D
Fenster teilt uns mit, dass das Programm fehlerfrei übersetzt wurde und wieviel Code dabei erzeugt wurde (14 Worte, 0,2% des verfügbaren Speicherraums. Nun wechseln wir in das Menü "Debug", was unsere Fensterlandschaft ein wenig verändert: Im linken Editor-Fenster taucht nun ein gelber Pfeil auf, der auf die erste ausführbare Instruktion des Programmes zeigt. Mit "View", "Toolbars" und "Processor" bringt man das Prozessorfenster rechts zur Anzeige. Es bringt uns Informationen über die aktuelle Ausführungsadresse, die Anzahl der verarbeiteten Instruktionen, die verstrichene Zeit und nach dem Klicken auf das kleine "+" neben "Registers" den Inhalt der Register. Mit "Debug" und "Step into" oder der Taste F11 wird nun ein Einzelschritt vorgenommen.
Der Programmzähler hat sich nun verändert, er steht auf "0x000001". Der Schrittzähler hat einen Zyklus gezählt und im Register R16 steht nun hexadezimal "0xFF" oder dezimal 255. LDI lädt also einen Hexadezimalwert in ein Register. Nach einem weiteren Schritt mit F11 und Aufklappen von PORTB im HardwareFenster "I/O-View" ist die Wirkung der gerade abgearbeiteten Instruktion "out PORTB,rmp" zu sehen:
Das Richtungs-Port-Register Data Direction PortB (DDRB) hat nun 0xFF und in der Bitanzeige unten acht schwarze Kästchen. Mit zwei weiteren Einzelschritten (F11) hat dann der Ausgabeport PORTB den Hexadezimalwert "0x55".
Zwei weitere Schritte klappen die vier schwarzen und die vier weißen Kästchen um.
Erstaunlicherweise ist nun der Port PINB dem vorhergehenden Bitmuster gefolgt.
Neues Projekt beginnen Nach dem Starten von Studio 4 und dem Anlegen eines neuen Projektes sollte sich etwa folgendes Bild zeigen.
Nach der Eingabe des Projektnamens und der Auswahl eines Ordners zum Speichern der Projektdateien werden wir zur Auswahl eines AVR-Typs genötigt:
In dem eingebauten Editor öffnet sich gleich eine Textdatei, in der das Assembler-Programm, der Quellcode, eingetippt werden kann.
Das war es denn auch schon mit dem Editor. Zum Seitenanfang
Simulator ist reine Ansichtssache Um dieses Programm im Simulator zu testen, wird im Menu Build-And-Run gewählt. Im unteren Fenster, das Output-Window heißt, wird die Ausgabe des Assemblers angezeigt. Der Simulator zeigt dann mit einem gelben Pfeil an der entsprechenden Zeile an, welchen Befehl des Programmes er als nächstes auszuführen gedenkt. Damit wir sehen, was passiert, öffnen wir im linken Fenster einen Blick auf die Register R16..R31.
Mit View können wir beim Simulieren auch noch weitere Anzeigearten anstelle des Output-Fensters aktivieren, z.B. die Register. Mit F11 geht es zeilenweise im Einzelschritt durch die Simulation. Nach zwei Einzelschritten sieht das im Register-Fenster so aus:
Nach der Durchführung der EOR-Instruktion hat sich der Registerinhalt von R17 geändert, im Registerfenster erscheint der Wert nun in rot.
Die Flags aus dem Statusregister SREG können im linken Fenster unter I/O, CPU und SREG während der Simulation überwacht werden.
Das gleiche Fenster zeigt auch die Inhalte von Ports an, hier der Port B.
Zum Seitenanfang
Programmieren des AVRs Um das fertige Programm in den AVR zu übertragen, kann man im Studio 4 das eingebaute Interface zum Programmierboard STK500 einsetzen. Das findet sich unter Tools und kommuniziert dann mit dem Studio.
Damit kann man das eben im Studio gerade editierte, assemblierte und simulierte Programm oder auch ein extern assembliertes Programm als .hex-Datei in den gewählten AVR übertragen.
Pfad: Home => AVR-deutsch => Programmiertechniken => Struktur
Struktur von AVR-Assembler-Programmen In diesem Abschnitt werden die Strukturen vorgestellt, die für Assembler-Programme typisch sind und sich immer wieder wiederholen. Dazu gehören Kommentare, die Angaben im Kopf des Programmes, der Code zu Beginn des eigentlichen Programmes und der Aufbau von Programmen.
Kommentare Das wichtigste an Assemblerprogrammen sind die Kommentare. Ohne Kommentierung des geschriebenen Codes blickt man schon nach wenigen Tagen oft nicht mehr durch, wofür das Programm gut war oder was an dieser Stelle des Programmes eigentlich warum getan wird. Man kann natürlich auch ohne Kommentare Programme schreiben, vor allem wenn man sie vor anderen und vor sich geheim halten will. Ein Kommentar beginnt mit einem Semikolon. Alles, was danach in dieser Zeile folgt, wird vom Übersetzungsprogramm, dem Assembler, einfach ignoriert. Wenn man mehrere Zeilen lange Kommentare schreiben möchte, muss man eben jede weitere Zeile mit einem Semikolon beginnen. So sieht dann z.B. der Anfang eines Assemblerprogrammes z.B. so aus: ; ; Klick.asm, Programm zum Ein- und Ausschalten eines Relais alle zwei Sekunden ; Geschrieben von G.Schmidt, letzte Änderung am 6.10.2001 ;
Kommentieren kann und soll man aber auch einzelne Abschnitte eines Programmes, wie z.B. eine abgeschlossene Routine oder eine Tabelle. Randbedingungen wie z.B. die dabei verwendeten Register, ihre erwarteten Inhalte oder das Ergebnis nach der Bearbeitung des Teilabschnittes erleichtern das spätere Aufsuchen von Fehlern und vereinfachen nachträgliche Änderungen. Man kann aber auch einzelne Zeilen mit Befehlen kommentieren, indem man den Rest der Zeile mit einem Semikolon vor dem Assembler abschirmt und dahinter alles mögliche anmerkt: LDI R16,0x0A ; Hier wird was geladen MOV R17,R16 ; und woanders hinkopiert
Zum Seitenanfang
Angaben im Kopf des Programmes Den Sinn und Zweck des Programmes, sein Autor, der Revisionsstand und andere Kommentare haben wir schon als Bestandteil des Kopfes identifiziert. Weitere Angaben, die hier hin gehören, sind der Prozessortyp, für den die Software geschrieben ist, die wichtigsten Konstanten (zur übersichtlichen Änderung) und die Festlegung von errinnerungsfördernden Registernamen. Der Prozessortyp hat dabei eine besondere Bedeutung. Programme laufen nicht ohne Änderungen auf jedem Prozessortyp. Nicht alle Prozessoren haben den gleichen Befehlssatz, jeder Typ hat seine typische Menge an EEPROM und SRAM, usw. Alle diese Besonderheiten werden in einer besonderen Kopfdatei (header file) festgelegt, die in den Code importiert wird. Diese Dateien heissen je nach Typ z.B. 2323def.inc, 8515def.inc, etc. und werden vom Hersteller zur Verfügung gestellt. Es ist guter Stil, mit dieser Datei sofort nach dem Kommentar im Kopf zu beginnen. Sie wird folgendermaßen eingelesen: .NOLIST ; Damit wird das Auflisten der Datei abgestellt .INCLUDE "C:\avrtools\appnotes\8515def.inc" ; Import der Datei .LIST ; Auflisten wieder anschalten
Der Pfad, an dem sich die Header-Datei befindet, kann natürlich weggelassen werden, wenn sie sich im gleichen Verzeichnis wie die Assemblerdatei befindet. Andernfalls ist der Pfad entsprechend den eigenen Verhältnissen anzupassen. Das Auflisten der Datei beim Übersetzen kann nervig sein, weil solche Header-Dateien sehr lang sind, beim Auflisten des übersetzten Codes (entstehende .lst-Datei) entsprechend lange Listen von meist uninteressanten (weil trivialen) Informationen produzieren. Das Abschalten vor dem Einlesen der Header-Datei spart jede Menge Papier beim Ausdrucken der List-Datei. Es lohnt sich, einen kurzen Blick in die Include-Datei zu werfen. Zu Beginn der Datei wird mit .DEVICE AT90S8515 ; Festlegung des Zieldevices
der Zielchip definiert. Das wiederum bewirkt, dass Befehle, die auf dem Zielchip nicht definiert sind, vom Assembler mit einer Fehlermeldung zurückgewiesen werden. Die Device-Anweisung an den Assembler braucht also beim Einlesen der Header-Datei nicht noch einmal in den Quellcode eingegeben werden (ergäbe eine Fehlermeldung). Hier sind z.B. auch die Register XH, XL, YH, YL, ZH und ZL definiert, die beim byteweisen Zugriff auf die Doppelregister X, Y und Z benötigt werden. Ferner sind darin alle Port-Speicherstellen definiert, z.B. erfolgt hier die Übersetzung von PORTB in hex 18. Schließlich sind hier auch alle PortBits mit denjenigen Namen registriert, die im Datenblatt des jeweiligen Chips verwendet werden. So wird hier z.B. das Portbit 3 beim Einlesen von Port B als PINB3 übersetzt, exakt so wie es auch im Datenblatt heißt. Mit anderen Worten: vergisst man die Einbindung der Include-Datei des Chips zu Beginn des Programmes, dann hagelt es Fehlermeldungen, weil der Assembler nur Bahnhof versteht. Die resultierenden Fehlermeldungen sind nicht immer sehr aussagekräftig, weil fehlende Labels und Konstanten vom ATMEL-Assembler nicht mit einer Fehlermeldung quittiert werden. Stattdessen nimmt der Assembler einfach an, die fehlende Konstante sei Null und übersetzt einfach weiter. Man kann sich leicht vorstellen, welches Chaos dabei herauskommt. Der arglose Programmierer denkt: alles in Ordnung. In Wirklichkeit wird ein ziemlicher Käse im Chip ausgeführt. In den Kopf des Programmes gehören insbesondere auch die Register-Definitionen, also z.B. .DEF mpr = R16 ; Das Register R16 mit einem Namen belegen
Das hat den Vorteil, dass man eine vollständige Liste der Register erhält und sofort sehen kann, welche Register verwendet werden und welche noch frei sind. Das Umbenennen vermeidet nicht nur Verwendungskonflikte, die Namen sind auch aussagekräftiger. Ferner gehört in den Kopf die Definition von Konstanten, die den gesamten Programmablauf beeinflussen können. So eine Konstante wäre z.B. die Oszillatorfrequenz des Chips, wenn im Programm später die serielle Schnittstelle verwendet werden soll. Mit .EQU fq = 4000000 ; Quarzfrequenz festlegen
zu Beginn der Assemblerdatei sieht man sofort, für welchen Takt das Programm geschrieben ist. Beim Umschreiben auf eine andere Frequenz muss nur diese Zahl geändert werden und man braucht nicht den gesamten Quelltext nach dem Auftauchen von 4000000 zu durchsuchen. Zum Seitenanfang
Angaben zum Programmbeginn Nach dem Kopf sollte der Programmcode beginnen. Am Beginn jedes Codes stehen die Reset- und Interrupt-Vektoren (zur Funktion siehe Sprung). Da diese relative Sprünge enthalten müssen, folgen darauf am besten die Interrupt-Service-Routinen. Danach ist ein guter Platz für abgeschlossene Unterprogramme. Danach sollte das Hauptprogramm stehen. Das Hauptprogramm beginnt mit immer mit dem Einstellen der Register-Startwerte, dem Initialisieren des Stackpointers und der verwendeten Hardware. Danach geht es programmspezifisch weiter. Zum Seitenanfang
Der Editor Ein Assembler-Programm ist eine einfache Textdatei. Damit mit der Datei auch alle anderen Einstellungen zu diesem Programm gleichzeitig verwaltet werden können, ist das gesamte Softwareprojekt im Studio 3 als Projekt verwaltet. Als erstes ist daher ein neues Projekt anzulegen. Das geht mit Project-New. Hier geben wir den Projektnamen, den Ordner und den gewünschten Assembler an.
Das öffnet nun die Projektübersicht.
Mit rechtem Mausklick auf Assembler Files und Create New File eröffnen wir eine Textdatei, die das Assemblerprogramm aufnehmen kann.
In das Editorfenster geben wir nun unser Programm ein. Hier ist eines, das wechselnd die Lampen an Port B an und aus macht.
Man beachte, dass der Simulator die eingegebenen Befehlsworte für den Prozessor nur dann erkennt und blau einfärbt, wenn sie in Kleinbuchstaben geschrieben sind! Labels und Defs werden nach wie vor nicht erkannt. Man beachte ferner, dass das Verzeichnis, in dem sich die Include-Dateien befinden, mit den neueren Studio-Versionen geändert hat.
Assemblieren der Quelldateien Nach der Eingabe des Programmes im Editor wird assembliert. In der Projektansicht reicht ein Klick mit der rechten Maustaste und die Auswahl von Assemble. Das Ergebnis des Vorganges öffnet ein weiteres Fenster mit den Meldungen.
Tauchen hier Fehlermeldungen auf, dann ist Entwanzen angesagt. Das Programm ist nun fertig assembliert und kann in den Zielchip programmiert werden. Das ist hier nicht beschrieben. Zum Seitenanfang
Simulieren des Programmablaufes Bei hartnäckigen Wanzen oder komplexeren Abläufen lohnt sich das Simulieren des Programmes oder seiner Teile im Simulator. In diesem Fall wählen wir Build and Run. In unserem Quelltext erscheint dann ein kleiner gelber Marker, der auf die nächste auszuführende Quelltextzeile zeigt.
Mit der Taste F11 starten wir schrittweise das Abarbeiten des Quelltextes. Damit wir den Forschritt erkennen können, öffnen wir eine Registeransicht. Der LDI-Befehl schreibt hex-FF in das Register R16.
Der nächste Befehl schreibt in das Datenrichtungsregister von Port B. Diesen Port können wir uns anzeigen lassen. Er zeigt nach dem OUT-Befehl den Inhalt des DDRB so an:
Nach dem zweiten OUT-Befehl (hex-AA an Port D) tut sich auch am Port-Datenregister etwas.
Pfad: Home => AVR-Überblick => Programmiertechniken => Projektplanung
Programmiertechnik für Anfänger in AVR Assemblersprache Wie plane ich ein Projekt in Assembler? Hier wird erklärt, wie man ein einfaches Projekt plant, das in Assembler programmiert werden soll. Weil die verwendeten Hardwarekmponenten eines Prozessors sehr vieles vorweg bestimmen, was und wie die Hard- und Software aufgebaut werden muss, zunächst die Überlegungen zur Hardware. Dann folgt ein Kapitel zur Entscheidung über den Einsatz von Interrupts und schließlich Einiges über Timing.
Überlegungen zur Hardware In die Entscheidung, welchen AVR-Typ man verwendet, gehen eine Vielzahl an Anforderungen ein. Hier einige häufiger vorkommende Forderungen zur Auswahl: 1. Welche festen Portanschlüsse werden gebraucht? Feste Portanschlüsse sind Ein- oder Ausgänge von internen Komponenten, die an ganz bestimmten Anschlüssen liegen müssen und nicht frei wählbar sind. Sie werden zuerst zugeordnet. Komponenten und Anschlüsse dieser Art sind: 1. Soll der Prozessor in der Schaltung programmiert werden können (ISP-Interface), dann werden die Anschlüsse SCK, MOSI und MISO diesem Zweck fest zugeordnet. Bei entsprechender Hardwaregestaltung können diese als Eingänge (SCK, MOSI) oder als Ausgang doppelt verwendet werden (Entkopplung über Widerstände oder Multiplexer empfohlen). 2. Wird eine serielle Schnittstelle benötigt, sind RXD und TXD dafür zu reservieren. Soll zusätzlich das RTS/CTS-Hardware-Protokoll implementiert werden, sind zusätzlich zwei weitere Portbits dafür zu reservieren, die aber frei platziert werden können. 3. Soll der Analogkomparator verwendet werden, dann sind AIN0 und AIN1 dafür zu reservieren. 4. Sollen externe Signale auf Flanken überwacht werden, dann sind INT0 bzw. INT1 dafür zu reservieren. 5. Sollen AD-Wandler verwendet werden, müssen entsprechend der Anzahl benötigter Kanäle die Eingänge dafür vorgesehen werden. Verfügt der AD-Wandler über die externen Anschlüsse AVCC und AREF, sollten sie entsprechend extern beschaltet werden. 6. Sollen externe Impulse gezählt werden, sind dafür die Timer-Input-Anschlüsse T0, T1 bzw. T2 zu reservieren. Soll die Dauer externer Impulse exakt gemessen werden, ist dafür der ICP-Eingang zu verwenden. Sollen Timer-Ausgangsimpulse mit definierter Pulsdauer ausgegeben werden, sind die entsprechenden OCx-Ausgänge dafür zu reservieren. 7. Wird externer SRAM-Speicher benötigt, müssen alle nötigen Address- und Datenports sowie ALE, RD und WR dafür reserviert werden. 8. Soll der Takt des Prozessors aus einem externen Oszillatorsignal bezogen werden, ist XTAL1 dafür zu reservieren. Soll ein externer Quarz den Takt bestimmen, sind die Anschlüsse XTAL1 und XTAL2 dafür zu verwenden. 2. Welche zusammenhängenden Portanschlüsse werden gebraucht? Zusammenhängende Portanschlüsse sind solche, bei denen zwei oder mehr Bits in einer bestimmten Reihenfolge angeordnet sein sollten, um die Software zu vereinfachen. 1. Erfordert die Ansteuerung eines externen Gerätes das Schreiben oder Lesen von mehr als einem Bit gleichzeitig, z.B. eine vier- oder achtbittige LCD-Anzeige, sollten die nötigen Portbits in dieser Reihenfolge platziert werden. Ist das z.B. bei achtbittigen Interfaces nicht möglich, können auch zwei vierbittige Interfaces vorgesehen werden. Die Software wird vereinfacht, wenn diese 4-Bit-Interfaces links- bzw. rechtsbündig im Port angeordnet sind. 2. Werden zwei oder mehr ADC-Kanäle benötigt, sollten diese in einer direkten Abfolge (z.B. ADC2/ADC3/ADC4) platziert werden, um die Ansteuerungssoftware zu vereinfachen. 3. Welche frei platzierbaren Portbits werden noch gebraucht? Jetzt wird alles zugeordnet, was keinen bestimmten Platz braucht. 1. Wenn es jetzt wegen eines einzigen Portbits eng wird, kann der RESET-Pin bei einigen Typen als Eingang verwendet werden, indem die entsprechende Fuse gesetzt wird. Da der Chip anschließend nur noch über Hochvolt-Programmierung zugänglich ist, ist dies für fertig getestete Software akzeptabel. Für Prototypen in der Testphase ist für ISP ein Hochvolt-Programmier-Interface am umdefinierten RESET-Pin und eine Schutzschaltung aus Widerstand und Zenerdiode gegenüber der Signalquelle vonnöten (beim HV-Programmieren treten +12 Volt am RESET-Pin auf). In die Entscheidung, welcher Prozessortyp für die Aufgabe geeignet ist, gehen ferner noch ein: ● ● ● ●
●
●
●
Wieviele und welche Timer werden benötigt? Welche Werte sind beim Abschalten des Prozessors zu erhalten (EEPROM dafür vorsehen)? Wieviel Speicher wird im laufenden Betrieb benötigt (entsprechend SRAM dafür vorsehen)? Platzbedarf? Bei manchen Projekten mag der Platzbedarf des Prozessors ein wichtiges Entscheidungskriterium sein. Strombedarf? Bei Batterie-/Akku-betriebenen Projekten sollte der Strombedarf ein wichtiges Auswahlkriterium sein. Preis? Spielt nur bei Großprojekten eine wichtige Rolle. Ansonsten sind Preisrelationen recht kurzlebig und nicht unbedingt von der Prozessorausstattung abhängig. Verfügbarkeit? Wer heute noch ein Projekt mit dem AT90S1200 startet, hat ihn vielleicht als Restposten aus der Grabbelecke billig gekriegt. Nachhaltig ist so eine Entscheidung nicht. Es macht wesentlich mehr Mühe, so ein Projekt auf einen Tiny- oder Mega-Typen zu portieren als der Preisvorteil heute wert ist. Das "Portieren" endet daher meist mit einer kompletten Neuentwicklung, die dann auch schöner aussieht, besser funktioniert und mit einem Bruchteil des Codes auskommt.
Zum Seitenanfang
Überlegungen zum Interrupt-Betrieb Einfachste Projekte kommen ohne Interrupt aus. Wenn allerdings der Strombedarf minimiert werden soll, dann auch dann nicht. Es ist daher bei fast allen Projekten die Regel, dass eine Interruptsteuerung nötig ist. Und die will sorgfältig geplant sein. Grundanforderungen des Interrupt-Betriebs Falls es nicht mehr parat ist, hier ein paar Grundregeln: ●
●
●
●
●
Interrupts ermöglichen: ❍ Interruptbetrieb erfordert einen eingerichteten SRAM-Stapel! ==> SPH:SPL sind zu Beginn auf RAMEND gesetzt, der obere Teil des SRAM (je nach Komplexität und anderweitigem Stapeleinsatz 8 bis x Byte) bleibt dafür freigehalten! ❍ Jede Komponente (z.B. Timer) und jede Bedingung (z.B. ein Overflow), die einen Interrupt auslösen soll, wird durch Setzen des entsprechenden Interrupt-Enable-Bits in seinen Steuerregistern dazu ermutigt, dies zu tun. ❍ Das I-Flag im Statusregister SREG wird zu Beginn gesetzt und bleibt möglichst während des gesamten Betriebs gesetzt. Ist es bei einer Operation nötig, Interrupts zu unterbinden, wird das I-Flag kurz zurückgesetzt und binnen weniger Befehlsworte wieder gesetzt. Interrupt-Service-Tabelle: ❍ Jeder Komponente und jeder gesetzten Interruptbedingung ist eine Interrupt-ServiceRoutine zugeordnet, die an einer ganz bestimmten Addresse im Flash-Speicher beginnt. Die Speicherstelle erfordert an dieser Stelle einen Ein-Wort-Sprung in die eigentliche Service-Routine (RJMP; bei sehr großen ATmega sind Zwei-Wort-Sprünge - JMP vorgesehen). ❍ Die ISR-Addressen sind prozessortyp-spezifisch angeordnet! Beim Portieren zu einem anderen Prozessortyp sind diese Addressen entsprechend anzupassen. ❍ Jede im Programm nicht verwendete ISR-Adresse wird mit einem RETI abgeschlossen, damit versehentlich eingeschaltete Enable-Bits von Komponenten definiert abgeschlossen sind und keinen Schaden anrichten könen. Die Verwendung der .ORGDirektive zum Einstellen der ISR-Addresse ist KEIN definierter Abschluss! ❍ Beim Vorliegen der Interrupt-Bedingung wird in den Steuerregistern der entsprechenden Komponente ein Flag gesetzt, das im Allgemeinen nach dem Anspringen der Interrupt-Service-Tabelle automatisch wieder gelöscht wird. In wenigen Ausnahmefällen kann es nötig sein (z.B. beim TX-Buffer-Empty-Interrupt der SIO, wenn kein weiteres Zeichen gesendet werden soll), den Interrupt-Enable abzuschalten und das bereits erneut gesetzte Flag zu löschen. ❍ Bei gleichzeitig eintreffenden Interrupt-Service-Anforderungen sind die ISRAddressen nach Prioritäten geordnet: die ISR mit der niedrigsten Addresse in der Tabelle wird bevorzugt ausgeführt. Interrupt-Service-Routinen: ❍ Jede Service-Routine beginnt mit der Sicherung des Prozessor- Statusregisters in einem für diesen Zweck reservierten Register und endet mit der Wiederherstellung des Status. Da die Unterbrechung zu jeder Zeit erfolgen kann, - also auch zu einer Zeit, in der der Prozessor mit Routinen des Hauptprogramms beschäftigt ist, die das Statusregister verwenden, - kann eine Störung dieses Registers unvorhersehbare Folgen haben. ❍ Beim Ansprung der ISR wird die Rücksprungaddresse auf dem Stapel abgelegt, der dadurch nach niedrigeren Addressen hin wächst. Der Interrupt und das Anspringen einer Interrupt-Service-Tabelle schaltet die Ausführung weiterer anstehender Interrupts zunächst ab. Jede Interrupt-Service-Routine endet daher mit der Instruktion RETI, die den Stapel wieder in Ordnung bringt und die Interrupts wieder zulässt. ❍ Da jede Interrupt-Service-Routine anstehende weitere Interrupts solange blockiert, wie sie selbst zu ihrer Ausführung benötigt, hat jede Interrupt-Service-Routine so kurz wie nur irgend möglich zu sein und sich auf die zeitkritischen Operationen zu beschränken. ❍ Da auch Interrupts mit höherer Priorität blockiert werden, sollte bei zeitkritischen Operationen niedriger prioritäre Ints besonders kurz sein. ❍ Da eine erneute Unterbrechung während der Verarbeitung einer Service-Routine nicht vorkommen kann, können in den verschiedenen ISR-Routinen die gleichen temporären Register verwendet werden. Schnittstelle Interrupt-Routine und Hauptprogramm: ❍ Die Kommunikation zwischen der Interruptroutine und dem Hauptprogramm erfolgt über einzelne Flaggen, die in der ISR gesetzt und im Hauptprogramm wieder zurückgesetzt werden. Zum Rücksetzen der Flaggen kommen ausschließlich Ein-WortInstruktionen zum Einsatz oder Interrupts werden vorübergehend blockiert, damit während des Rücksetzvorgangs diese oder andere Flaggen im Register bzw. im SRAM nicht fälschlich überschrieben werden. ❍ Werte aus der Service-Routine werden in dezidierten Registern oder SRAMSpeicherzellen übergeben. Jede Änderung von Register- oder SRAM-Werten innerhalb der Service-Routine, die außerhalb des Interrupts weiterverarbeitet werden, ist daraufhin zu prüfen, ob bei der Übergabe durch weitere Interrupts Fehler möglich sind. Die Übergabe und Weiterverabeitung von Ein-Byte-Werten ist unproblematisch, bei Übergabe von zwei und mehr Bytes ist ein eindeutiger Übergabemechanismus zwingend (Interrupt Disable beim Kopieren der Daten in der Hauptprogramm-Schleife, Flag-Setzen/-Auswerten/-Rücksetzen, o.ä.)! Als Beispiel sei der Übergabemechanismus eines 16-Bit-Wertes von einem Timer/Counter an die Auswerteroutine beschrieben. Der Timer schreibt die beiden Bytes in zwei Register und setzt ein Flag in einem anderen Register, dass Werte zur Weiterverarbeitung bereitstehen. Im Hauptprogramm wird dieses Flag ausgewertet, zurückgesetzt und die Übergabe des Wertes gestartet. Wenn nun das erste Byte kopiert ist und erneut ein Interrupt des Timers/Counters zuschlägt, gehören anschließend Byte 1 und Byte 2 nicht zum gleichen Wertepaar. Das gilt es durch definierte Übergabemechanismen zu verhindern! Die Hauptprogramm-Routinen: ❍ Im Hauptprogramm legt ein Loop den Prozessor schlafen, wobei der Schlafmodus "Idle" eingestellt sein muss. Jeder Interrupt weckt den Prozessor auf, verzweigt zur Service-Routine und setzt nach deren Beendigung die Verarbeitung fort. Es macht Sinn, nun die Flags darauf zu überprüfen, ob eine oder mehrere der Service-Routinen Bedarf an Weiterverarbeitung signalisiert hat. Wenn ja, wird entsprechend dorthin verzweigt. Nachdem alle Wünsche der ISRs erfüllt sind, wird der Prozessor wieder schlafen gelegt.
Grundaufbau im Interrupt-Betrieb Aus dem Dargestellten ergibt sich die folgende Grundstruktur eines Interrupt- getriebenen Programmes an einem Beispiel:
; ; Registerdefinitionen ; .EQU rsreg = R15 ; Status-Sicherungs-Register bei Interrupts .EQU rmp = R16 ; Temporäres Register auäerhalb von Interrupts .EQU rimp = R17 ; Temporäres Register innerhalb von Interrupts .EQU rflg = R18 ; Flaggenregister zur Kommunikation .EQU bint0 = 0 ; Flaggenbit zur Signalisierung INT0-Service .EQU btc0 = 1 ; Flaggenbit zur Signalisierung TC0-Overflow ; ... ; ISR-Tabelle ; .CSEG .ORG $0000 rjmp main ; Reset-Vektor, wird beim Start ausgeführt rjmp isr_int0 ; INT0-Vektor, wird bei eine Pegeländerung am INT0-Eingang ausgefürt reti ; nicht belegter Interrupt reti ; nicht belegter Interrupt rjmp isr_tc0_Overflow ; TC0-Overflow-Vektor, wird bei Überlauf TC0 ausgeführt reti ; nicht belegter Interrupt reti ; nicht belegter Interrupt ; ... gegebenenfalls weitere ISR ; ; Interrupt-Service-Routinen ; isr_int0: ; INT0-Service Routine in rsreg,SREG ; sichere Status in rimp,PINB ; lese Port B in Temp Register out PORTC,rimp ; schreibe Temp Register in Port C ; ... mache irgendwas weiteres sbr rflg,1<
Zum Seitenanfang
Überlegungen zum Timing Geht ein Projekt darüber hinaus, ein Portbit abzufragen und daraus abgeleitet irgendwas anderes zu veranlassen, dann sind Überlegungen zum Timing des Projektes zwinged. Timing ● ●
● ●
beginnt mit der Wahl der Taktfrequenz des Prozessors, geht weiter mit der Frage, was wie häufig und mit welcher Präzision vom Prozesser erledigt werden muss, über die Frage, welche Timing-Steuerungsmöglichkeiten bestehen, bis zu wie diese kombiniert werden können.
Wahl der Taktfrequenz des Prozessors Die oberste Grundfrage ist die nach der nötigen Präzision des Taktgebers. Reicht es in der Anwendung, wenn die Zeiten im Prozentbereich ungenau sind, dann ist der interne RC-Oszillator in vielen AVR-Typen v&oml;llig ausreichend. Bei den neueren ATtiny und ATmega hat sich die selbsttätige Oszillator-Kalibration eingebürgert, so dass die Abweichungen vom Nominalwert des RC-Oszillators nicht mehr so arg ins Gewicht fallen. Wenn allerdings die Betriebsspannung stark schwankt, kann der Fehler zu groß sein. Wem der interne RC-Takt zu langsam oder zu schnell ist, kann bei einigen neueren Typen (z.B. dem ATtiny13) einen Vorteiler bemühen. Der Vorteiler geht beim Anlegen der Betriebsspannung auf einen voreingestellten Wert (z.B. auf 8) und teilt den internen RC-Oszillator entsprechend vor. Entweder per Fuse-Einstellung oder auch per Software-Einstellung kann der Vorteiler auf niedrigere Teilerverhältnisse (höhere Taktfrequenz) oder auf einen höheren Wert umgestellt werden (z.B. 128), um durch niedrigere Taktfrequenz z.B. verst&aum;rkt Strom zu sparen. Obacht bei V-Typen! Wer dem Prozessor einen zu hohen Takt zumutet, hat es nicht anders verdient als dass der Prozessor nicht tut, was er soll. Wem der interne RC-Oszillator zu ungenau ist, kann per Fuse eine externe RC-Kombination, einen externen Oszillator, einen Quarz oder einen Keramikschwinger auswählen. Gegen falsch gesetzte Fuses ist kein Kraut gewachsen, es muss dann schon ein Board mit eigenem Oszillator sein, um doch noch was zu retten. Die Höhe der Taktfrequenz sollte der Aufgabe angemessen sein. Dazu kann grob die Wiederholfrequenz der Tätigkeiten dienen. Wenn eine Taste z.B. alle 2 ms abgefragt werden soll und nach 20 Mal als entprellt ausgeführt werden soll, dann ist bei einer Taktfrequenz von 1 MHz für 2000 Takte zwischen zwei Abfragen Platz, 20.000 Takte für die wiederholte Ausführung des Tastenbefehls. Weit ausreichend für eine gemütliche Abfrage und Ausführung. Eng wird es, wenn eine Pulsweitenmodulation mit hoher Auflösung und hoher PWM-Taktfrequenz erreicht werden soll. Bei einer PWM-Taktfrequenz von 10 kHz und 8 Bit Auflösung sind 2,56 MHz schon zu langsam für eine software-gesteuerte Lösung. Wenn das ein Timer mit Interrupt-Steuerung übernimmt, um so besser. Was ist wann und wie zu tun? Mit welchen Methoden lässt sich das machen? Zum Seitenanfang
Binäres Rechnen in AVR Assembler Diese Seite zeigt, wie Rechnen mit Binärzahlen in AVR Assembler funktioniert. An einfachen Beispielen werden die Grundrechenarten Multiplikation und Division sowie die Zahlenumwandlung erläutert. An zwei Beispielen wird der Umgang mit Fließkommazahlen erläutert. Die Hardware-Multiplikation mit ATmega wird gezeigt. 1. Multiplikation zweier 8-Bit-Zahlen ❍ Dezimales Multiplizieren als Vorlage ❍ Binäres Multiplizieren ❍ Assemblerprogramm Multiplikation ❍ Rotieren in Assembler ❍ Multiplikation im Studio 2. Division einer 16-Bit-Zahl durch eine 8-Bit-Zahl ❍ Dezimale Division als Vorlage ❍ Binäre Division ❍ Binäre Division im Simulator 3. Umwandlungen von und zu Binärzahlen ❍ Allgemeines zur Formatumwandlung ❍ Zahlenformate und allgemeine Programmierregeln ❍ ASCII nach Binär ❍ BCD nach Binär ❍ Binärzahl mit 10 multiplizieren ❍ Binär nach ASCII ❍ Binär nach BCD ❍ Binär nach Hex-ASCII ❍ Hex-ASCII nach Binär 4. Rechnen mit Festkommazahlen ❍ Sinn und Unsinn von Fließkommazahlen ❍ Lineare Umrechnungen ❍ Beispiel 1: 8-Bit-AD-Wandler zu 0,00 bis 5,00 Volt ❍ Beispiel 2: 10-Bit-AD-Wandler zu 0,000 bis 5,000 Volt 5. Hardware Multiplikation mit ATmega ❍ 8- mal 8-Bit-Multiplikation ❍ 16- mal 8-Bit-Multiplikation ❍ 16- mal 16-Bit-Multiplikation ❍ 16- mal 24-Bit-Multiplikation
Pfad: Home => AVR-Übersicht => Binäres Rechnen => Multiplikation
Binäres Multiplizieren zweier 8-BitZahlen in AVR Assembler Dezimales Multiplizieren Zwei 8-Bit-Zahlen sollen multipliziert werden. Man erinnere sich, wie das mit Dezimalzahlen geht: 1234 * 567 = ? -----------------------1234 * 7 = 8638 + 12340 * 6 = 74040 + 123400 * 5 = 617000 -----------------------1234 * 567 = 699678 ========================
Also in Einzelschritten dezimal: 1. Wir nehmen die Zahl mit der kleinsten Ziffer mal und addieren sie zum Ergebnis. 2. Wir nehmen die Zahl mit 10 mal, dann mit der nächsthöheren Ziffer, und addieren sie zum Ergebnis. 3. Wir nehmen die Zahl mit 100 mal, dann mit der dritthöchsten Ziffer, und addieren sie zum Ergebnis.
Binäres Multiplizieren Jetzt in Binär: Das Malnehmen mit den Ziffern entfällt, weil es ja nur Null oder Eins gibt, also entweder wird die Zahl addiert oder eben nicht. Das Malnehmen mit 10 wird in binär zum Malnehmen mit 2, weil wir ja nicht mit Basis 10 sondern mit Basis 2 rechnen. Malnehmen mit 2 ist aber ganz einfach: man kann die Zahl einfach mit sich selbst addieren oder binär nach links schieben und rechts eine binäre Null dazu schreiben. Man sieht schon, dass das binäre Multiplizieren viel einfacher ist als das dezimale Multiplizieren. Man fragt sich, warum die Menschheit so ein schrecklich kompliziertes Dezimalsystem erfinden musste und nicht beim binären System verweilt ist.
AVR-Assemblerprogramm Die rechentechnische Umsetzung in AVR-Assembler-Sprache zeigt der Quelltext in HTML. Wer den Quelltext direkt haben möchte, findet ihn in der Datei mult8.asm. Der Quelltext kann mit einem Assembler übersetzt werden, der Object-Code im Studio durchgespielt werden. Zu Beginn der Berechnung liegen die folgenden Bedingungen vor (wechseln Sie Ihren Browser, wenn diese Tabelle nicht korrekt angezeigt wird!): .
rmh = R1 = 0x00
rm1 = R0 = 0xAA
Z1 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 *
.
Z2 =
rm2 = R2 = 0x55 0 1 0 1 0 1 0 1
reh = R4 = 0x00
rel = R3 = 0x00
Erg 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Binäres Rotieren Für das Verständnis der Berechnung ist die Kenntnis des Assembler-Befehles ROL bzw ROR wichtig. Der Befehl verschiebt die Bits eines Registers nach links (ROL) bzw. rechts (ROR), schiebt das Carry-Bit aus dem Statusregister in die leer werdende Position im Register und schiebt dafür das beim Rotieren herausfallende Bit in das Carry-Flag. Dieser Vorgang wird für das Linksschieben mit dem Inhalt des Registers von 0xAA, für das Rechtsschieben mit 0x55 gezeigt:
Pfad: Home => AVR-Übersicht => Binäres Rechnen => Multiplikation
Assembler Quelltext der Multiplikation ; Mult8.asm multipliziert zwei 8-Bit-Zahlen ; zu einem 16-Bit-Ergebnis ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Ablauf des Multiplizierens: ; ; 1.Multiplikator 2 wird bitweise nach rechts in das ; Carry-Bit geschoben. Wenn es eine Eins ist, dann ; wird die Zahl in Multiplikator 1 zum Ergebnis dazu ; gezählt, wenn es eine Null ist, dann nicht. ; 2.Nach dem Addieren wird Multiplikator 1 durch Links; schieben mit 2 multipliziert. ; 3.Wenn Multiplikator 2 nach dem Schieben nicht Null ; ist, dann wird wie oben weiter gemacht. Wenn er Null ; ist, ist die Multplikation beendet. ; ; Benutzte Register ; .DEF rm1 = R0 ; Multiplikator 1 (8 Bit) .DEF rmh = R1 ; Hilfsregister für Multiplikation .DEF rm2 = R2 ; Multiplikator 2 (8 Bit) .DEF rel = R3 ; Ergebnis, LSB (16 Bit) .DEF reh = R4 ; Ergebnis, MSB .DEF rmp = R16 ; Hilfsregister zum Laden ; .CSEG .ORG 0000 ; rjmp START ; START: ldi rmp,0xAA ; Beispielzahl 1010.1010 mov rm1,rmp ; in erstes Multiplikationsreg ldi rmp,0x55 ; Beispielzahl 0101.0101 mov rm2,rmp ; in zweites Multiplikationsreg ; ; Hier beginnt die Multiplikation der beiden Zahlen ; in rm1 und rm2, das Ergebnis ist in reh:rel (16 Bit) ; MULT8: ; ; Anfangswerte auf Null setzen clr rmh ; Hilfsregister leeren clr rel ; Ergebnis auf Null setzen clr reh ; ; Hier beginnt die Multiplikationsschleife ; MULT8a: ; ; Erster Schritt: Niedrigstes Bit von Multplikator 2 ; in das Carry-Bit schieben (von links Nullen nachschieben) ; clc ; Carry-Bit auf Null setzen ror rm2 ; Null links rein, alle Bits eins rechts, ; niedrigstes Bit in Carry schieben ; ; Zweiter Schritt: Verzweigen je nachdem ob eine Null oder ; eine Eins im Carry steht ; brcc MULT8b ; springe, wenn niedrigstes Bit eine ; Null ist, über das Addieren hinweg ; ; Dritter Schritt: Addiere 16 Bits in rmh:rm1 zum Ergebnis ; in reh:rel (mit Überlauf der unteren 8 Bits!) ; add rel,rm1 ; addiere LSB von rm1 zum Ergebnis adc reh,rmh ; addiere Carry und MSB von rm1 ; MULT8b: ; ; Vierter Schritt: Multipliziere Multiplikator rmh:rm1 mit ; Zwei (16-Bit, links schieben) ; clc ; Carry bit auf Null setzen rol rm1 ; LSB links schieben (multiplik. mit 2) rol rmh ; Carry in MSB und MSB links schieben ; ; Fünfter Schritt: Prüfen, ob noch Einsen im Multi; plikator 2 enthalten sind, wenn ja, dann weitermachen ; tst rm2 ; alle bits Null? brne MULT8a ; wenn nicht null, dann weitermachen ; ; Ende der Multiplikation, Ergebnis in reh:rel ; ; Endlosschleife ; LOOP: rjmp loop
; Mult8.asm multipliziert zwei 8-Bit-Zahlen ; zu einem 16-Bit-Ergebnis ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Ablauf des Multiplizierens: ; ; 1.Multiplikator 2 wird bitweise nach rechts in das ; Carry-Bit geschoben. Wenn es eine Eins ist, dann ; wird die Zahl in Multiplikator 1 zum Ergebnis dazu ; gezählt, wenn es eine Null ist, dann nicht. ; 2.Nach dem Addieren wird Multiplikator 1 durch Links; schieben mit 2 multipliziert. ; 3.Wenn Multiplikator 2 nach dem Schieben nicht Null ; ist, dann wird wie oben weiter gemacht. Wenn er Null ; ist, ist die Multplikation beendet. ; ; Benutzte Register ; .DEF rm1 = R0 ; Multiplikator 1 (8 Bit) .DEF rmh = R1 ; Hilfsregister für Multiplikation .DEF rm2 = R2 ; Multiplikator 2 (8 Bit) .DEF rel = R3 ; Ergebnis, LSB (16 Bit) .DEF reh = R4 ; Ergebnis, MSB .DEF rmp = R16 ; Hilfsregister zum Laden ; .CSEG .ORG 0000 ; rjmp START ; START: ldi rmp,0xAA ; Beispielzahl 1010.1010 mov rm1,rmp ; in erstes Multiplikationsreg ldi rmp,0x55 ; Beispielzahl 0101.0101 mov rm2,rmp ; in zweites Multiplikationsreg ; ; Hier beginnt die Multiplikation der beiden Zahlen ; in rm1 und rm2, das Ergebnis ist in reh:rel (16 Bit) ; MULT8: ; ; Anfangswerte auf Null setzen clr rmh ; Hilfsregister leeren clr rel ; Ergebnis auf Null setzen clr reh ; ; Hier beginnt die Multiplikationsschleife ; MULT8a: ; ; Erster Schritt: Niedrigstes Bit von Multplikator 2 ; in das Carry-Bit schieben (von links Nullen nachschieben) ; clc ; Carry-Bit auf Null setzen ror rm2 ; Null links rein, alle Bits eins rechts, ; niedrigstes Bit in Carry schieben ; ; Zweiter Schritt: Verzweigen je nachdem ob eine Null oder ; eine Eins im Carry steht ; brcc MULT8b ; springe, wenn niedrigstes Bit eine ; Null ist, über das Addieren hinweg ; ; Dritter Schritt: Addiere 16 Bits in rmh:rm1 zum Ergebnis ; in reh:rel (mit Überlauf der unteren 8 Bits!) ; add rel,rm1 ; addiere LSB von rm1 zum Ergebnis adc reh,rmh ; addiere Carry und MSB von rm1 ; MULT8b: ; ; Vierter Schritt: Multipliziere Multiplikator rmh:rm1 mit ; Zwei (16-Bit, links schieben) ; clc ; Carry bit auf Null setzen rol rm1 ; LSB links schieben (multiplik. mit 2) rol rmh ; Carry in MSB und MSB links schieben ; ; Fünfter Schritt: Prüfen, ob noch Einsen im Multi; plikator 2 enthalten sind, wenn ja, dann weitermachen ; tst rm2 ; alle bits Null? brne MULT8a ; wenn nicht null, dann weitermachen ; ; Ende der Multiplikation, Ergebnis in reh:rel ; ; Endlosschleife ; LOOP: rjmp loop
Pfad: Home => AVR-Übersicht => Binäres Rechnen => Division
Division einer 16-Bit-Zahl durch eine 8-Bit-Zahl Dezimales Dividieren Zunächst wieder eine dezimale Division, damit das besser verständlich wird. Nehmen wir an, wir wollen 5678 durch 12 teilen. Das geht dann etwa so: 5678 : 12 = ? -------------------------- 4 * 1200 = 4800 ---878 - 7 * 120 = 840 --38 - 3 * 12 = 36 -2 Ergebnis: 5678 : 12 = 473 Rest 2 ================================
Binäres Dividieren Binär entfällt wieder das Multiplizieren des Divisors (4 * 1200, etc.), weil es nur Einsen und Nullen gibt. Dafür haben binäre Zahlen leider sehr viel mehr Stellen als dezimale. Die direkte Analogie wäre in diesem Fall etwas aufwändig, weshalb die rechentechnische Lösung etwas anders aussieht. Die Division einer 16-Bit-Zahl durch eine 8-Bit-Zahl in AVR Assembler ist in HTML-Format hier und als Quellcode hier gezeigt.
Programmschritte beim Dividieren Das Programm gliedert sich in folgende Teilschritte: 1. Definieren und Vorbelegen der Register mit den Testzahlen, 2. das Vorbelegen von Hilfsregistern (die beiden Ergebnisregister werden mit 0x0001 vorbelegt, um das Ende der Division nach 16 Schritten elegant zu markieren!), 3. die 16-Bit-Zahl in rd1h:rd1l wird bitweise in ein Hilfsregister rd1u geschoben (mit 2 multipliziert), rollt dabei eine 1 links heraus, dann wird auf jeden Fall zur Subtraktion im vierten Schritt verzweigt, 4. der Inhalt des Hilfsregisters wird mit der 8-Bit-Zahl in rd2 verglichen, ist das Hilfsregister größer wird die 8-Bit-Zahl subtrahiert und eine Eins in das Carry-Bit gepackt, ist es kleiner dann wird nicht subtrahiert und eine Null in das Carry-Bit gepackt, 5. der Inhalt des Carry-Bit wird in die Ergebnisregister reh:rel von rechts einrotiert, 6. rotiert aus dem Ergebnisregister eine Null links heraus, dann muss weiter dividiert werden und ab Schritt 3 wird wiederholt (insgesamt 16 mal), rollt eine 1 heraus, dann sind wir fertig. Für das Rotieren gilt das oben dargestellte Procedere (siehe oben, zum Nachlesen).
Das Dividieren im Simulator Die folgenden Bilder zeigen die Vorgänge im Simulator, dem Studio. Dazu wird der Quellcode assembliert und die Object-Datei in das Studio geladen. 1. DIV8D1.gif: Der Object-Code ist gestartet, der Cursor steht auf dem ersten Befehl. Mit F11 machen wir Einzelschritte. 2. DIV8D2.gif: In die Register R0, R1 und R3 werden die beiden Werte 0xAAAA und 0x55 geschrieben, die wir dividieren wollen. 3. DIV8D3.gif: Die Startwerte für das Hilfsregister werden gesetzt, das Ergebnisregisterpaar wurde auf 0x0001 gesetzt. 4. DIV8D4.gif: R1:R0 wurde nach links in Hilfsregister R2 geschoben, aus 0xAAAA ist 0x015554 entstanden. 5. DIV8D5.gif: Weil 0x01 in R2 kleiner als 0x55 in R3 ist, wurde das Subtrahieren übersprungen, eine Null in das Carry gepackt und in R5:R4 geschoben. Aus der ursprünglichen 1 im Ergebnisregister R5:R4 ist durch das Linksrotieren 0x0002 geworden. Da eine Null in das Carry herausgeschoben wurde, geht der nächste Befehl (BRCC) mit einem Sprung zur Marke div8a und die Scleife wird wiederholt. 6. DIV8D6.gif: Nach dem 16-maligen Durchlaufen der Schleife gelangen wir schließlich an die Endlosschleife am Ende der Division. Im Ergebnisregister R5:R4 steht nun das Ergebnis der Division von 0xAAAA durch 0x55, nämlich 0x0202. Die Register R2:R1:R0 sind leer, es ist also kein Rest geblieben. Bliebe ein Rest, könnten wir ihn mit zwei multiplizieren und mit der 8-Bit-Zahl vergleichen, um das Ergebnis vielleicht noch aufzurunden. Aber das ist hier nicht programmiert. 7. DIV8D7.gif: Im Prozessor-View sehen wir nach Ablauf des gesamten Divisionsprozesses immerhin 60 Mikrosekunden Prozessorzeit verbraucht.
Pfad: Home => AVR-Übersicht => Binäres Rechnen => Zahlenumwandlung
Zahlenumwandlung in AVRAssembler Das Umwandeln von Zahlen kommt in Assembler recht häufig vor, weil der Prozessor am liebsten (und schnellsten) in binär rechnet, der dumme Mensch aber nur das Zehnersystem kann. Wer also aus einem Assemblerprogramm heraus mit Menschen an einer Tastatur kommunizieren möchte, kommt um Zahlenumwandlung nicht herum. Diese Seite befasst sich daher mit diesen Wandlungen zwischen den Zahlenwelten, und zwar etwas detaillierter und genauer. Wer gleich in die Vollen gehen möchte, kann sich direkt in den üppig kommentierten Quelltext stürzen. Den gibt es über diesen Link im HTML-Format oder über diesen Link im Assemblerformat.
Allgemeine Bedingungen der Zahlenumwandlung Die hier behandelten Zahlensysteme sind: ●
●
●
●
Dezimal: Jedes Byte zu je acht Bit enthält eine Ziffer, die in ASCII formatiert ist. So repräsentiert der dezimale Wert 48, in binär $30, die Ziffer Null, 49 die Eins, usw. bis 57 die Neun. Die anderen Zahlen, mit denen ein Byte gefüllt werden kann, also 0 bis 47 und 58 bis 255, sind keine gültigen Dezimalziffern. (Warum gerade 48 die Null ist, hat mit amerikanischen Militärfernschreibern zu tun, aber das ist eine andere lange Geschichte.) BCD-Zahlen: BCD bedeutet Binary Coded Decimal. Es ist ähnlich wie dezimale ASCIIZahlen, nur entspricht die BCD-dezimale Null jetzt tatsächlich dem Zahlenwert Null (und nicht 48). Dementsprechend gehen die BCDs von 0 bis 9. Alles weitere, was noch in ein Byte passen würde (10 .. 255) ist keine gültige Ziffer und gehört sich bei BCDs verboten. Binärzahlen: Hier gibt es nur die Ziffern 0 und 1. Von hinten gelesen besitzen sie jeweils die Wertigkeit der Potenzen von 2, also ist die Binärzahl 1011 soviel wie 1* (2 hoch 0) + 1*(2 hoch 1) + 0*(2 hoch 2) + 1*(2 hoch 3), so ähnlich wie dezimal 1234 gleich 4*(10 hoch 0) + 3* (10 hoch 1) + 2*(10 hoch 2) + 1*(10 hoch 3) ist (jede Zahl hoch 0 ist übrigens 1, nur nicht 0 hoch 0, da weiss man es nicht so genau!). Binärzahlen werden in Paketen zu je acht (als Byte bezeichnet) oder 16 (als Wort bezeichnet) Binärziffern gehandhabt, weil die einzelnen Bits kaum was wert sind. Hexadezimal: Hexadezimalzahlen sind eigentlich Viererpäckchen von Bits, denen man zur Vereinfachung die Ziffern 0 bis 9 und A bis F (oder a bis f) gibt und als solche meistens ASCII-verschlüsselt. A bis F deswegen, weil vier Bits Zahlen von 0 bis 15 sein können. So ist binär 1010 soviel wie 1*(2 hoch 3) + 1*(2 hoch 1), also dezimal 10, kriegt dafür den Buchstaben A. Der Buchstabe A liegt aber in der ASCII-Codetabelle beim dezimalen Wert 65, als Kleinbuchstabe sogar bei 97. Das alles ist beim Umwandeln wichtig und beim Codieren zu bedenken.
Soweit der Zahlenformatsalat. Die zum Umwandeln geschriebene Software soll einigermaßen brauchbar für verschiedene Zwecke sein. Es lohnt sich daher, vor dem Schreiben und Verwenden ein wenig Grütze zu investieren. Ich habe daher folgende Regeln ausgedacht und beim Schreiben eingehalten: ●
●
●
●
●
Binärzahlen: Alle Binärzahlen sind auf 16 Bit ausgelegt (Wertebereich 0..65.535). Sie sind in den beiden Registern rBin1H (obere 8 Bit, MSB) und rBin1L (untere 8 Bit, LSB) untergebracht. Dieses binäre Wort wird mit rBin1H:L abgekürzt. Bevorzugter Ort beim AVR für die beiden ist z.B. R1 und R2, die Reihenfolge ist dabei egal. Für manche Umwandlungen wird ein zweites binäres Registerpaar gebraucht, das hier als rBin2H:L bezeichnet wird. Es kann z.B. in R3 und R4 gelegt werden. Es wird nach dem Gebrauch wieder in den Normalzustand versetzt, deshalb kann es unbesehen auch noch für andere Zwecke dienen. BCD- und ASCII-Zahlen: Diese Zahlen werden generell mit dem Zeiger Z angesteuert (Zeigerregister ZH:ZL oder R31:R30), weil solche Zahlen meistens irgendwo im SRAMSpeicher herumstehen. Sie sind so angeordnet, dass die höherwertigste Ziffer die niedrigste Adresse hat. Die Zahl 12345 würde also im SRAM so stehen: $0060: 1, $0061: 2, $0062: 3, usw. Der Zeiger Z kann aber auch in den Bereich der Register gestellt werden, also z.B. auf $0005. Dann läge die 1 in R5, die 2 in R6, die 3 in R7, usw. Die Software kann also die Dezimalzahlen sowohl aus dem SRAM als auch aus den Registern verarbeiten. Aber Obacht: es wird im Registerraum unbesehen alles überschrieben, was dort herumstehen könnte. Sorgfältige Planung der Register ist dann heftig angesagt! Paketeinteilung: Weil man nicht immer alle Umwandlungsroutinen braucht, ist das Gesamtpaket in vier Teilpakete eingeteilt. Die beiden letzten Pakete braucht man nur für Hexzahlen, die beiden ersten zur Umrechnung von ASCII- oder BCD- zu Binär bzw. von Binär in ASCII- und BCD. Jedes Paket ist mit den darin befindlichen Unterprogrammen separat lauffähig, es braucht nicht alles eingebunden werden. Beim Entfernen von Teilen der Pakete die Aufrufe untereinander beachten! Das Gesamtpaket umfasst 217 Worte Programm. Fehler: Tritt bei den Zahlenumwandlungen ein Fehler auf, dann wird bei allen fehlerträchtigen Routinen das T-Flag gesetzt. Das kann mit BRTS oder BRTC bequem abgefragt werden, um Fehler in der Zahl zu erkennen, abzufangen und zu behandeln. Wer das T-Flag im Statusregister SREG noch für andere Zwecke braucht, muss alle "set"-, "clt"-, "brts"- und "brtc"-Anweisungen auf ein anderes Bit in irgendeinem Register umkodieren. Bei Fehlern bleibt der Zeiger Z einheitlich auf der Ziffer stehen, bei der der Fehler auftrat. Weiteres: Weitere Bedingungen gibt es im allgemeinen Teil des Quelltextes. Dort gibt es auch einen Überblick zur Zahlenumwandlung, der alle Funktionen, Aufrufbedingungen und Fehlerarten enthält.
Von ASCII nach Binär Die Routine AscToBin2 eignet sich besonders gut für die Ermittlung von Zahlen aus Puffern. Sie überliest beliebig viele führende Leerzeichen und Nullen und bricht die Zahlenumwandlung beim ersten Zeichen ab, das keine gültige Dezimalziffer repräsentiert. Die Zahlenlänge muss deshalb nicht vorher bekannt sein, der Zahlenwert darf nur den 16-Bit-Wertebereich der Binärzahl nicht überschreiten. Auch dieser Fehler wird erkannt und mit dem T-Flag signalisiert. Soll die Länge der Zahl exakt fünf Zeichen ASCII umfassen, kann die Routine Asc5ToBin2 verwendet werden. Hier wird jedes ungültige Zeichen, bis auf führende Nullen und Leerzeichen, angemahnt. Die Umrechnung erfolgt von links nach rechts, d.h. bei jeder weiteren Stelle der Zahl wird das bisherige Ergebnis mit 10 multipliziert und die dazu kommende Stelle hinzugezählt. Das geht etwas langsam, weil die Multiplikation mit 10 etwas rechenaufwendig ist. Es gibt sicher schnellere Arten der Wandlung.
Von BCD zu Binär Die Umwandlung von BCD zu Binär funktioniert ähnlich. Auf eine eingehende Beschreibung der Eigenschaften dieses Quellcodes wird daher verzichtet.
Binärzahl mit 10 multiplizieren Diese Routine Bin1Mul10 nimmt eine 16-Bit-Binärzahl mit 10 mal, indem sie diese kopiert und durch Additionen vervielfacht. Aufwändig daran ist, dass praktisch bei jedem Addieren ein Überlauf denkbar ist und abgefangen werden muss. Wer keine zu langen Zahlen zulässt oder wem es egal ist, ob Unsinn rauskommt, kann die Branch-Anweisungen alle rauswerfen und das Verfahren damit etwas beschleunigen.
Von binär nach ASCII Die Wandlung von Binär nach ASCII erfolgt in der Routine Bin2ToAsc5. Das Ergebnis ist generell fünfstellig. Die eigentliche Umwandlung erfolgt durch Aufruf von Bin2ToBcd5. Wie das funktioniert, wird weiter unten beschrieben. Wird statt Bin2ToAsc5 die Routine Bin2ToAsc aufgerufen, kriegt man den Zeiger Z auf die erste Nicht-Null in der Zahl gesetzt und die Anzahl der Stellen im Register rBin2L zurück. Das ist bequem, wenn man das Ergebnis über die serielle Schnittstelle senden will und unnötigen Leerzeichen-Verkehr vermeiden will.
Von binär nach BCD Bin2ToBcd5 rechnet die Binärzahl in rBin1H:L in dezimal um. Auch dieses Verfahren ist etwas zeitaufwändig, aber sicher leicht zu verstehen. Die Umwandlung erfolgt durch fortgesetztes Abziehen immer kleiner werdender Binärzahlen, die die dezimalen Stellen repräsentieren, also 10.000, 1.000, 100 und 10. Nur bei den Einern wird von diesem Schema abgewichen, hi.
Von binär nach Hex Die Routine Bin2ToHex4 produziert aus der Binärzahl eine vierstellige Hex-ASCII-Zahl, die etwas leichter zu lesen ist als das Original in binär. Oder mit welcher Wahrscheinlichkeit verschreiben Sie sich beim Abtippen der Zahl 1011011001011110? Da ist B65E doch etwas bequemer und leichter zu memorieren, fast so wie 46686 in dezimal. Die Routine produziert die Hex-Ziffern A bis F in Großbuchstaben. Wer es lieber in a .. f mag: bitte schön, Quelltext ändern.
******************************************************** * Routinen zur Zahlumwandlung, Version 0.1 Januar 2002 * * (C)2002 by info!at!avr-asm-tutorial.net * ******************************************************** Die folgenden Regeln gelten für alle Routinen zur Zahlumwandlung: - Fehler während der Umwandlung werden durch ein gesetztes T-Bit im Status-Register signalisiert. - Der Z Zeiger zeigt entweder in das SRAM (Adresse >= $0060) oder auf einen Registerbereich (Adressen $0000 bis $001D), die Register R0, R16 und R30/31 dürfen nicht in dem benutzten Bereich liegen! - ASCII- und BCD-kodierte mehrstellige Zahlen sind absteigend geordnet, d.h. die höherwertigen Ziffern haben die niedrigerwertigen Adressen. - 16-bit-Binärzahlen sind generell in den Registern rBin1H:rBin1L lokalisiert, bei einigen Routinen wird zusätzlich rBin2H:rBin2L verwendet. Diese müssen im Hauptprogramm definiert werden. - Bei Binärzahlen ist die Lage im Registerbereich nicht maßgebend, sie können auf- oder absteigend geordnet sein oder getrennt im Registerraum liegen. Zu verneiden ist eine Zuordnung zu R0, rmp, ZH oder ZL. - Register rmp (Bereich: R16..R29) wird innerhalb der Rechenroutinen benutzt, sein Inhalt ist nach Rückkehr nicht definiert. - Das Registerpaar Z wird innerhalb von Routinen verwendet. Bei der Rückkehr ist sein Inhalt abhängig vom Fehlerstatus definiert. - Einige Routinen verwenden zeitweise Register R0. Sein Inhalt wird vor der Rückkehr wieder hergestellt. - Wegen der Verwendung des Z-Registers ist in jedem Fall die Headerdatei des Prozessors einzubinden oder ZL (R30) und ZH (R31) sind manuell zu definieren. Wird die Headerdatei oder die manuelle Definition nicht vorgenommen, gibt es beim Assemblieren eine Fehlermeldung oder es geschehen rätselhafte Dinge. - Wegen der Verwendung von Unterroutinen muss der Stapelzeiger (SPH:SPL bzw. SPL bei mehr als 256 Byte SRAM) initiiert sein.
************* Überblick über die Routinen ************** Routine Aufruf Bedingungen Rückkehr, Fehler -------------------------------------------------------AscToBin2 Z zeigt auf Beendet beim ersten 16-bit-Bin erstes Zeichen, das nicht rBin1H:L, ASCIIeiner Dezimalziffer ÜberlaufZeichen entspricht, überfehler liest Leerzeichen und führende Nullen Asc5ToBin2 Z zeigt auf Benötigt exakt 5 16-bit-Bin erstes gültige Ziffern, rBin1H:L, ASCIIüberliest LeerzeiÜberlauf Zeichen chen und Nullen oder ungültige Ziffern Bcd5ToBin2 Z zeigt auf Benötigt exakt 5 16-bit-Bin 5-stellige gültige Ziffern rBin1H:L BCD-Zahl Überlauf oder ungültige Ziffern Bin2ToBcd5 16-bit-Bin Z zeigt auf erste 5-digit-BCD in rBin1H:L BCD-Ziffer (auch ab Z, keine nach Rückkehr) Fehler Bin2ToHex4 16-bit-Bin Z zeigt auf erste 4-stellige in rBin1H:L Hex-ASCII-Stelle, Hex-Zahl ab Ausgabe A...F Z, keine Fehler Hex4ToBin2 4-digit-Hex Benötigt exakt vier 16-bit-Bin Z zeigt auf Stellen Hex-ASCII, rBin1H:L, erste Stelle akzeptiert A...F und ungültige a...f Hex-Ziffer ******************* Umwandlungscode ******************** Paket I: Von ASCII bzw. BCD nach Binär
Von ASCII nach Binär ; AscToBin2 ; ========= ; wandelt eine ASCII-kodierte Zahl in eine 2-Byte-/16-Bit; Binärzahl um. ; Aufruf: Z zeigt auf erste Stelle der umzuwandelnden ; Zahl, die Umwandlung wird bei der ersten nicht dezima; len Ziffer beendet. ; Stellen: Zulässig sind alle Zahlen, die innerhalb des ; Wertebereiches von 16 Bit binär liegen (0..65535). ; Rückkehr: Gesetztes T-Flag im Statusregister zeigt Feh; ler an. ; T=0: Zahl in rBin1H:L ist gültig, Z zeigt auf erstes ; Zeichen, das keiner Dezimalziffer entsprach ; T=1: Überlauffehler (Zahl zu groß), rBin1H:L undefi; niert, Z zeigt auf Zeichen, bei dessen Verarbeitung ; der Fehler auftrat. ; Benötigte Register: rBin1H:L (Ergebnis), rBin2H:L (wieder ; hergestellt), rmp ; Benötigte Unterroutinen: Bin1Mul10 ; AscToBin2: clr rBin1H ; Ergebnis auf Null setzen clr rBin1L clt ; Fehlerflagge zurücksetzen AscToBin2a: ld rmp,Z+ ; lese Zeichen cpi rmp,' ' ; ignoriere führende Leerzeichen ... breq AscToBin2a cpi rmp,'0' ; ... und Nullen breq AscToBin2a AscToBin2b: subi rmp,'0' ; Subtrahiere ASCII-Null brcs AscToBin2d ; Ende der Zahl erkannt cpi rmp,10 ; prüfe Ziffer brcc AscToBin2d ; Ende der Umwandlung rcall Bin1Mul10 ; Binärzahl mit 10 malnehmen brts AscToBin2c ; Überlauf, gesetztes T-Flag add rBin1L,rmp ; Addiere Ziffer zur Binärzahl ld rmp,Z+ ; Lese schon mal nächstes Zeichen brcc AscToBin2b ; Kein Überlauf ins MSB inc rBin1H ; Überlauf ins nächste Byte brne AscToBin2b ; Kein Überlauf ins nächste Byte set ; Setze Überlauffehler-Flagge AscToBin2c: sbiw ZL,1 ; Überlauf trat bei letztem Zeichen auf AscToBin2d: ret ; fertig, Rückkehr
Fünfstellige ASCII-Zahl nach Binär ; ; Asc5ToBin2 ; ========== ; wandelt eine fünfstellige ASCII-kodierte Zahl in 2-Byte; Binärzahl um. ; Aufruf: Z zeigt auf erste Stelle der ASCII-kodierten ; Zahl, führende Leerzeichen und Nullen sind erlaubt. ; Stellen: Die Zahl muss exakt 5 gültige Stellen haben. ; Rückkehr: T-Flag zeigt Fehlerbedingung an: ; T=0: Binärzahl in rBin1H:L ist gültig, Z zeigt auf ; erste Stelle der ASCII-kodierten Zahl. ; T=1: Fehler bei der Umwandlung. Entweder war die Zahl ; zu groß (0..65535, Z zeigt auf die Ziffer, bei der ; der Überlauf auftrat) oder sie enthilt ein ungülti; ges Zeichen (Z zeigt auf das ungültige Zeichen). ; Benötigte Register: rBin1H:L (Ergebnis), R0 (wiederher; gestellt), rBin2H:L (wieder hergestellt), rmp ; Aufgerufene Unterroutinen: Bin1Mul10 ; Asc5ToBin2: push R0 ; R0 wird als Zähler verwendet, retten ldi rmp,6 ; Fünf Ziffern, einer zu viel mov R0,rmp ; in Zählerregister R0 clr rBin1H ; Ergebnis auf Null setzen clr rBin1L clt ; Fehlerflagge T-Bit zurücksetzen Asc5ToBin2a: dec R0 ; Alle Zeichen leer oder Null? breq Asc5ToBin2d ; Ja, beenden ld rmp,Z+ ; Lese nächstes Zeichen cpi rmp,' ' ; überlese Leerzeichen breq Asc5ToBin2a ; geh zum nächsten Zeichen cpi rmp,'0' ; überlese führende Nullen breq Asc5ToBin2a ; geh zum nächsten Zeichen Asc5ToBin2b: subi rmp,'0' ; Behandle Ziffer, ziehe ASCII-0 ab brcs Asc5ToBin2e ; Ziffer ist ungültig, raus cpi rmp,10 ; Ziffer größer als neun? brcc Asc5ToBin2e ; Ziffer ist ungültig, raus rcall Bin1Mul10 ; Multipliziere Binärzahl mit 10 brts Asc5ToBin2e ; Überlauf, raus add rBin1L,rmp ; addiere die Ziffer zur Binärzahl ld rmp,z+ ; lese schon mal das nächste Zeichen brcc Asc5ToBin2c ; Kein Überlauf in das nächste Byte inc rBin1H ; Überlauf in das nächste Byte breq Asc5ToBin2e ; Überlauf auch ins übernächste Byte Asc5ToBin2c: dec R0 ; Verringere Zähler für Anzahl Zeichen brne Asc5ToBin2b ; Wandle weitere Zeichen um Asc5ToBin2d: ; Ende der ASCII-kodierten Zahl erreicht sbiw ZL,5 ; Stelle die Startposition in Z wieder her pop R0 ; Stelle Register R0 wieder her ret ; Kehre zurück Asc5ToBin2e: ; Letztes Zeichen war ungültig sbiw ZL,1 ; Zeige mit Z auf ungültiges Zeichen pop R0 ; Stelle Register R0 wieder her set ; Setze T-Flag für Fehler ret ; und kejre zurück ;
Von BCD zu Binär ; Bcd5ToBin2 ; ========== ; wandelt eine 5-bit-BCD-Zahl in eine 16-Bit-Binärzahl um ; Aufruf: Z zeigt auf erste Stelle der BCD-kodierten ; Zahl ; Stellen: Die Zahl muss exakt 5 gültige Stellen haben. ; Rückkehr: T-Flag zeigt Fehlerbedingung an: ; T=0: Binärzahl in rBin1H:L ist gültig, Z zeigt auf ; erste Stelle der BCD-kodierten Zahl. ; T=1: Fehler bei der Umwandlung. Entweder war die Zahl ; zu groß (0..65535, Z zeigt auf die Ziffer, bei der ; der Überlauf auftrat) oder sie enthielt ein ungülti; ges Zeichen (Z zeigt auf das ungültige Zeichen). ; Benötigte Register: rBin1H:L (Ergebnis), R0 (wiederher; gestellt), rBin2H:L (wieder hergestellt), rmp ; Aufgerufene Unterroutinen: Bin1Mul10 ; Bcd5ToBin2: push R0 ; Rette Register R0 clr rBin1H ; Setze Ergebnis Null clr rBin1L ldi rmp,5 ; Setze Zähler auf 5 Ziffern mov R0,rmp ; R0 ist Zähler clt ; Setze Fehlerflagge zurück Bcd5ToBin2a: ld rmp,Z+ ; Lese BCD-Ziffer cpi rmp,10 ; prüfe ob Ziffer korrekt brcc Bcd5ToBin2c ; ungültige BCD-Ziffer rcall Bin1Mul10 ; Multipliziere Ergebnis mit 10 brts Bcd5ToBin2c ; Überlauf aufgetreten add rBin1L,rmp ; Addiere Ziffer brcc Bcd5ToBin2b ; Kein Überlauf ins nächste Byte inc rBin1H ; Überlauf ins nächste Byte breq Bcd5ToBin2c ; Überlauf ins übernächste Byte Bcd5ToBin2b: dec R0 ; weitere Ziffer? brne Bcd5ToBin2a ; Ja pop R0 ; Stelle Register wieder her sbiw ZL,5 ; Setze Zeiger auf erste Stelle der BCD-Zahl ret ; Kehre zurück Bcd5ToBin2c: sbiw ZL,1 ; Eine Ziffer zurück pop R0 ; Stelle Register wieder her set ; Setze T-flag, Fehler ret ; Kehre zurück
Binärzahl mit 10 multiplizieren ; ; Bin1Mul10 ; ========= ; Multipliziert die 16-Bit-Binärzahl mit 10 ; Unterroutine benutzt von AscToBin2, Asc5ToBin2, Bcd5ToBin2 ; Aufruf: 16-Bit-Binärzahl in rBin1H:L ; Rückkehr: T-Flag zeigt gültiges Ergebnis an. ; T=0: rBin1H:L enthält gültiges Ergebnis. ; T=1: Überlauf bei der Multiplikation, rBin1H:L undefiniert ; Benutzte Register: rBin1H:L (Ergebnis), rBin2H:L (wird wieder ; hergestellt) ; Bin1Mul10: push rBin2H ; Rette die Register rBin2H:L push rBin2L mov rBin2H,rBin1H ; Kopiere die Zahl dort hin mov rBin2L,rBin1L add rBin1L,rBin1L ; Multipliziere Zahl mit 2 adc rBin1H,rBin1H brcs Bin1Mul10b ; Überlauf, raus hier! Bin1Mul10a: add rBin1L,rbin1L ; Noch mal mit 2 malnehmen (=4*Zahl) adc rBin1H,rBin1H brcs Bin1Mul10b ; Überlauf, raus hier! add rBin1L,rBin2L ; Addiere die Kopie (=5*Zahl) adc rBin1H,rBin2H brcs Bin1Mul10b ;Überlauf, raus hier! add rBin1L,rBin1L ; Noch mal mit 2 malnehmen (=10*Zahl) adc rBin1H,rBin1H brcc Bin1Mul10c ; Kein Überlauf, überspringe Bin1Mul10b: set ; Überlauf, setze T-Flag Bin1Mul10c: pop rBin2L ; Stelle die geretteten Register wieder her pop rBin2H ret ; Kehre zurück ; ; ************************************************ ; ; Paket II: Von Binär nach ASCII bzw. BCD ;
Von binär nach ASCII ; Bin2ToAsc5 ; ========== ; wandelt eine 16-Bit-Binärzahl in eine fünfstellige ASCII; kodierte Dezimalzahl um ; Aufruf: 16-Bit-Binärzahl in rBin1H:L, Z zeigt auf Anfang ; der Zahl ; Rückkehr: Z zeigt auf Anfang der Zahl, führende Nullen sind ; mit Leerzeichen überschrieben ; Benutzte Register: rBin1H:L (bleibt erhalten), rBin2H:L ; (wird überschrieben), rmp ; Aufgerufene Unterroutinen: Bin2ToBcd5 ; Bin2ToAsc5: rcall Bin2ToBcd5 ; wandle Binärzahl in BCD um ldi rmp,4 ; Zähler auf 4 mov rBin2L,rmp Bin2ToAsc5a: ld rmp,z ; Lese eine BCD-Ziffer tst rmp ; prüfe ob Null brne Bin2ToAsc5b ; Nein, erste Ziffer ungleich 0 gefunden ldi rmp,' ' ; mit Leerzeichen überschreiben st z+,rmp ; und ablegen dec rBin2L ; Zähler um eins senken brne Bin2ToAsc5a ; weitere führende Leerzeichen ld rmp,z ; Lese das letzte Zeichen Bin2ToAsc5b: inc rBin2L ; Ein Zeichen mehr Bin2ToAsc5c: subi rmp,-'0' ; Addiere ASCII-0 st z+,rmp ; und speichere ab, erhöhe Zeiger ld rmp,z ; nächstes Zeichen lesen dec rBin2L ; noch Zeichen behandeln? brne Bin2ToAsc5c ; ja, weitermachen sbiw ZL,5 ; Zeiger an Anfang ret ; fertig
Binär in ASCII ohne führende Leerzeichen ; ; Bin2ToAsc ; ========= ; wandelt eine 16-Bit-Binärzahl in eine fünfstellige ASCII; kodierte Dezimalzahl um, Zeiger zeigt auf die erste signi; fikante Ziffer der Zahl, und gibt Anzahl der Ziffern zu; rück ; Aufruf: 16-Bit-Binärzahl in rBin1H:L, Z zeigt auf Anfang ; der Zahl (5 Stellen erforderlich, auch bei kleineren Zah; len!) ; Rückkehr: Z zeigt auf erste signifikante Ziffer der ASCII; kodierten Zahl, rBin2L enthält Länge der Zahl (1..5) ; Benutzte Register: rBin1H:L (bleibt erhalten), rBin2H:L ; (wird überschrieben), rmp ; Aufgerufene Unterroutinen: Bin2ToBcd5, Bin2ToAsc5 ; Bin2ToAsc: rcall Bin2ToAsc5 ; Wandle Binärzahl in ASCII ldi rmp,6 ; Zähler auf 6 mov rBin2L,rmp Bin2ToAsca: dec rBin2L ; verringere Zähler ld rmp,z+ ; Lese Zeichen und erhöhe Zeiger cpi rmp,' ' ; war Leerzeichen? breq Bin2ToAsca ; Nein, war nicht sbiw ZL,1 ; ein Zeichen rückwärts ret ; fertig
Von binär nach BCD ; ; Bin2ToBcd5 ; ========== ; wandelt 16-Bit-Binärzahl in 5-stellige BCD-Zahl um ; Aufruf: 16-Bit-Binärzahl in rBin1H:L, Z zeigt auf die ; erste Stelle der BCD-kodierten Resultats ; Stellen: Die BCD-Zahl hat exakt 5 gültige Stellen. ; Rückkehr: Z zeigt auf die höchste BCD-Stelle ; Benötigte Register: rBin1H:L (wird erhalten), rBin2H:L ; (wird nicht wieder hergestellt), rmp ; Aufgerufene Unterroutinen: Bin2ToDigit ; Bin2ToBcd5: push rBin1H ; Rette Inhalt der Register rBin1H:L push rBin1L ldi rmp,HIGH(10000) ; Lade 10.000 in rBin2H:L mov rBin2H,rmp ldi rmp,LOW(10000) mov rBin2L,rmp rcall Bin2ToDigit ; Ermittle 5.Stelle durch Abziehen ldi rmp,HIGH(1000) ; Lade 1.000 in rBin2H:L mov rBin2H,rmp ldi rmp,LOW(1000) mov rBin2L,rmp rcall Bin2ToDigit ; Ermittle 4.Stelle durch Abziehen ldi rmp,HIGH(100) ; Lade 100 in rBin2H:L mov rBin2H,rmp ldi rmp,LOW(100) mov rBin2L,rmp rcall Bin2ToDigit ; Ermittle 3.Stelle durch Abziehen ldi rmp,HIGH(10) ; Lade 10 in rBin2H:L mov rBin2H,rmp ldi rmp,LOW(10) mov rBin2L,rmp rcall Bin2ToDigit ; Ermittle 2.Stelle durch Abziehen st z,rBin1L ; Rest sind Einer sbiw ZL,4 ; Setze Zeiger Z auf 5.Stelle (erste Ziffer) pop rBin1L ; Stelle den Originalwert wieder her pop rBin1H ret ; und kehre zurück ; ; Bin2ToDigit ; =========== ; ermittelt eine dezimale Ziffer durch fortgesetztes Abziehen ; einer binär kodierten Dezimalstelle ; Unterroutine benutzt von: Bin2ToBcd5, Bin2ToAsc5, Bin2ToAsc ; Aufruf: Binärzahl in rBin1H:L, binär kodierte Dezimalzahl ; in rBin2H:L, Z zeigt auf bearbeitete BCD-Ziffer ; Rückkehr: Ergebis in Z (bei Aufruf), Z um eine Stelle er; höht, keine Fehlerbehandlung ; Benutzte Register: rBin1H:L (enthält Rest der Binärzahl), ; rBin2H (bleibt erhalten), rmp ; Aufgerufene Unterroutinen: ; Bin2ToDigit: clr rmp ; Zähler auf Null Bin2ToDigita: cp rBin1H,rBin2H ; Vergleiche MSBs miteinander brcs Bin2ToDigitc ; MSB Binärzahl kleiner, fertig brne Bin2ToDigitb ; MSB Binärzahl größer, subtrahiere cp rBin1L,rBin2L ; MSB gleich, vergleiche LSBs brcs Bin2ToDigitc ; LSB Binärzahl kleiner, fertig Bin2ToDigitb: sub rBin1L,rBin2L ; Subtrahiere LSB Dezimalzahl sbc rBin1H,rBin2H ; Subtrahiere Carry und MSB inc rmp ; Erhöhe den Zähler rjmp Bin2ToDigita ; Weiter vergleichen/subtrahieren Bin2ToDigitc: st z+,rmp ; Speichere das Ergebnis und erhöhe Zeiger ret ; zurück ; ; *********************************************** ; ; Paket III: Von Binär nach Hex-ASCII ;
Von binär nach Hex ; Bin2ToHex4 ; ========== ; wandelt eine 16-Bit-Binärzahl in Hex-ASCII ; Aufruf: Binärzahl in rBin1H:L, Z zeigt auf erste Position ; des vierstelligen ASCII-Hex ; Rückkehr: Z zeigt auf erste Position des vierstelligen ; ASCII-Hex, ASCII-Ziffern A..F in Großbuchstaben ; Benutzte Register: rBin1H:L (bleibt erhalten), rmp ; Aufgerufene Unterroutinen: Bin1ToHex2, Bin1ToHex1 ; Bin2ToHex4: mov rmp,rBin1H ; MSB in rmp kopieren rcall Bin1ToHex2 ; in Hex-ASCII umwandeln mov rmp,rBin1L ; LSB in rmp kopieren rcall Bin1ToHex2 ; in Hex-ASCII umwandeln sbiw ZL,4 ; Zeiger auf Anfang Hex-ASCII ret ; fertig ; ; Bin1ToHex2 wandelt die 8-Bit-Binärzahl in rmp in Hex-ASCII ; gehört zu: Bin2ToHex4 ; Bin1ToHex2: push rmp ; Rette Byte auf dem Stapel swap rmp ; Vertausche die oberen und unteren 4 Bit rcall Bin1ToHex1 ; wandle untere 4 Bits in Hex-ASCII pop rmp ; Stelle das Byte wieder her Bin1ToHex1: andi rmp,$0F ; Maskiere die oberen vier Bits subi rmp,-'0' ; Addiere ASCII-0 cpi rmp,'9'+1 ; Ziffern A..F? brcs Bin1ToHex1a ; Nein subi rmp,-7 ; Addiere 7 für A..F Bin1ToHex1a: st z+,rmp ; abspeichern und Zeiger erhöhen ret ; fertig ; ; *********************************************** ; ; Paket IV: Von Hex-ASCII nach Binär ;
Von Hex nach Binär ; Hex4ToBin2 ; ========== ; wandelt eine vierstellige Hex-ASCII-Zahl in eine 16-Bit; Binärzahl um ; Aufruf: Z zeigt auf die erste Stelle der Hex-ASCII-Zahl ; Rückkehr: T-Flag zeigt Fehler an: ; T=0: rBin1H:L enthält die 16-Bit-Binärzahl, Z zeigt ; auf die erste Hex-ASCII-Ziffer wie beim Aufruf ; T=1: ungültige Hex-ASCII-Ziffer, Z zeigt auf ungültige ; Ziffer ; Benutzte Register: rBin1H:L (enthält Ergebnis), R0 (wie; der hergestellt), rmp ; Aufgerufene Unterroutinen: Hex2ToBin1, Hex1ToBin1 ; Hex4ToBin2: clt ; Lösche Fehlerflag rcall Hex2ToBin1 ; Wandle zwei Hex-ASCII-Ziffern brts Hex4ToBin2a ; Fehler, beende hier mov rBin1H,rmp ; kopiere nach MSB Ergebnis rcall Hex2ToBin1 ; Wandle zwei Hex-ASCII-Ziffern brts Hex4ToBin2a ; Fehler, beende hier mov rBin1L,rmp ; kopiere nach LSB Ergebnis sbiw ZL,4 ; Ergebis ok, Zeiger auf Anfang Hex4ToBin2a: ret ; zurück ; ; Hex2ToBin1 wandelt 2-stellig-Hex-ASCII nach 8-Bit-Binär ; Hex2ToBin1: push R0 ; Rette Register R0 rcall Hex1ToBin1 ; Wandle nächstes Zeichen in Byte brts Hex2ToBin1a ; Fehler, stop hier swap rmp; untere vier Bits in obere vier Bits mov R0,rmp ; zwischenspeichern rcall Hex1ToBin1 ; Nächstes Zeichen umwandeln brts Hex2ToBin1a ; Fehler, raus hier or rmp,R0 ; untere und obere vier Bits zusammen Hex2ToBin1a: pop R0 ; Stelle R0 wieder her ret ; zurück ; ; Hex1ToBin1 liest ein Zeichen und wandelt es in Binär um ; Hex1ToBin1: ld rmp,z+ ; Lese Zeichen subi rmp,'0' ; Ziehe ASCII-0 ab brcs Hex1ToBin1b ; Fehler, kleiner als 0 cpi rmp,10 ; A..F ; Ziffer größer als 9? brcs Hex1ToBin1c ; nein, fertig cpi rmp,$30 ; Kleinbuchstaben? brcs Hex1ToBin1a ; Nein subi rmp,$20 ; Klein- in Grossbuchstaben Hex1ToBin1a: subi rmp,7 ; Ziehe 7 ab, A..F ergibt $0A..$0F cpi rmp,10 ; Ziffer kleiner $0A? brcs Hex1ToBin1b ; Ja, Fehler cpi rmp,16 ; Ziffer größer als $0F brcs Hex1ToBin1c ; Nein, Ziffer in Ordnung Hex1ToBin1b: ; Error sbiw ZL,1 ; Ein Zeichen zurück set ; Setze Fehlerflagge Hex1ToBin1c: ret ; Zurück
; ******************************************************** ; * Routinen zur Zahlumwandlung, Version 0.1 Januar 2002 * ; * (C)2002 by [email protected] * ; ******************************************************** ; ; Die folgenden Regeln gelten für alle Routinen zur Zahl; umwandlung: ; - Fehler während der Umwandlung werden durch ein gesetz; tes T-Bit im Status-Register signalisiert. ; - Der Z Zeiger zeigt entweder in das SRAM (Adresse >= ; $0060) oder auf einen Registerbereich (Adressen $0000 ; bis $001D), die Register R0, R16 und R30/31 dürfen ; nicht in dem benutzten Bereich liegen! ; - ASCII- und BCD-kodierte mehrstellige Zahlen sind ab; steigend geordnet, d.h. die höherwertigen Ziffern ha; ben die niedrigerwertigen Adressen. ; - 16-bit-Binärzahlen sind generell in den Registern ; rBin1H:rBin1L lokalisiert, bei einigen Routinen wird ; zusätzlich rBin2H:rBin2L verwendet. Diese müssen im ; Hauptprogramm definiert werden. ; - Bei Binärzahlen ist die Lage im Registerbereich nicht ; maßgebend, sie können auf- oder absteigend geordnet ; sein oder getrennt im Registerraum liegen. Zu vernei; den ist eine Zuordnung zu R0, rmp, ZH oder ZL. ; - Register rmp (Bereich: R16..R29) wird innerhalb der ; Rechenroutinen benutzt, sein Inhalt ist nach Rückkehr ; nicht definiert. ; - Das Registerpaar Z wird innerhalb von Routinen verwen; det. Bei der Rückkehr ist sein Inhalt abhängig vom ; Fehlerstatus definiert. ; - Einige Routinen verwenden zeitweise Register R0. Sein ; Inhalt wird vor der Rückkehr wieder hergestellt. ; - Wegen der Verwendung des Z-Registers ist in jedem Fall ; die Headerdatei des Prozessors einzubinden oder ZL ; (R30) und ZH (R31) sind manuell zu definieren. Wird ; die Headerdatei oder die manuelle Definition nicht ; vorgenommen, gibt es beim Assemblieren eine Fehlermel; dung oder es geschehen rätselhafte Dinge. ; - Wegen der Verwendung von Unterroutinen muss der Sta; pelzeiger (SPH:SPL bzw. SPL bei <256 Byte SRAM) ; initiiert sein. ; ; ************* Überblick über die Routinen ************** ; Routine Aufruf Bedingungen Rückkehr, ; Fehler ; -------------------------------------------------------; AscToBin2 Z zeigt auf Beendet beim ersten 16-bit-Bin ; erstes Zeichen, das nicht rBin1H:L, ; ASCIIeiner Dezimalziffer Überlauf; Zeichen entspricht, über- fehler ; liest Leerzeichen ; und führende Nullen ; Asc5ToBin2 Z zeigt auf Benötigt exakt 5 16-bit-Bin ; erstes gültige Ziffern, rBin1H:L, ; ASCIIüberliest Leerzei- Überlauf ; Zeichen chen und Nullen oder ungül; tige Ziffern ; Bcd5ToBin2 Z zeigt auf Benötigt exakt 5 16-bit-Bin ; 5-stellige gültige Ziffern rBin1H:L ; BCD-Zahl Überlauf ; oder ungül; tige Ziffern ; Bin2ToBcd5 16-bit-Bin Z zeigt auf erste 5-digit-BCD ; in rBin1H:L BCD-Ziffer (auch ab Z, keine ; nach Rückkehr) Fehler ; Bin2ToHex4 16-bit-Bin Z zeigt auf erste 4-stellige ; in rBin1H:L Hex-ASCII-Stelle, Hex-Zahl ab ; Ausgabe A...F Z, keine ; Fehler ; Hex4ToBin2 4-digit-Hex Benötigt exakt vier 16-bit-Bin ; Z zeigt auf Stellen Hex-ASCII, rBin1H:L, ; erste Stelle akzeptiert A...F und ungültige ; a...f Hex-Ziffer ; ; ******************* Umwandlungscode ******************** ; ; Paket I: Von ASCII bzw. BCD nach Binär ; ; AscToBin2 ; ========= ; wandelt eine ASCII-kodierte Zahl in eine 2-Byte-/16-Bit; Binärzahl um. ; Aufruf: Z zeigt auf erste Stelle der umzuwandelnden ; Zahl, die Umwandlung wird bei der ersten nicht dezima; len Ziffer beendet. ; Stellen: Zulässig sind alle Zahlen, die innerhalb des ; Wertebereiches von 16 Bit binär liegen (0..65535). ; Rückkehr: Gesetztes T-Flag im Statusregister zeigt Feh; ler an. ; T=0: Zahl in rBin1H:L ist gültig, Z zeigt auf erstes ; Zeichen, das keiner Dezimalziffer entsprach ; T=1: Überlauffehler (Zahl zu groß), rBin1H:L undefi; niert, Z zeigt auf Zeichen, bei dessen Verarbeitung ; der Fehler auftrat. ; Benötigte Register: rBin1H:L (Ergebnis), rBin2H:L (wieder ; hergestellt), rmp ; Benötigte Unterroutinen: Bin1Mul10 ; AscToBin2: clr rBin1H ; Ergebnis auf Null setzen clr rBin1L clt ; Fehlerflagge zurücksetzen AscToBin2a: ld rmp,Z+ ; lese Zeichen cpi rmp,' ' ; ignoriere führende Leerzeichen ... breq AscToBin2a cpi rmp,'0' ; ... und Nullen breq AscToBin2a AscToBin2b: subi rmp,'0' ; Subtrahiere ASCII-Null brcs AscToBin2d ; Ende der Zahl erkannt cpi rmp,10 ; prüfe Ziffer brcc AscToBin2d ; Ende der Umwandlung rcall Bin1Mul10 ; Binärzahl mit 10 malnehmen brts AscToBin2c ; Überlauf, gesetztes T-Flag add rBin1L,rmp ; Addiere Ziffer zur Binärzahl ld rmp,Z+ ; Lese schon mal nächstes Zeichen brcc AscToBin2b ; Kein Überlauf ins MSB inc rBin1H ; Überlauf ins nächste Byte brne AscToBin2b ; Kein Überlauf ins nächste Byte set ; Setze Überlauffehler-Flagge AscToBin2c: sbiw ZL,1 ; Überlauf trat bei letztem Zeichen auf AscToBin2d: ret ; fertig, Rückkehr ; ; Asc5ToBin2 ; ========== ; wandelt eine fünfstellige ASCII-kodierte Zahl in 2-Byte; Binärzahl um. ; Aufruf: Z zeigt auf erste Stelle der ASCII-kodierten ; Zahl, führende Leerzeichen und Nullen sind erlaubt. ; Stellen: Die Zahl muss exakt 5 gültige Stellen haben. ; Rückkehr: T-Flag zeigt Fehlerbedingung an: ; T=0: Binärzahl in rBin1H:L ist gültig, Z zeigt auf ; erste Stelle der ASCII-kodierten Zahl. ; T=1: Fehler bei der Umwandlung. Entweder war die Zahl ; zu groß (0..65535, Z zeigt auf die Ziffer, bei der ; der Überlauf auftrat) oder sie enthilt ein ungülti; ges Zeichen (Z zeigt auf das ungültige Zeichen). ; Benötigte Register: rBin1H:L (Ergebnis), R0 (wiederher; gestellt), rBin2H:L (wieder hergestellt), rmp ; Aufgerufene Unterroutinen: Bin1Mul10 ; Asc5ToBin2: push R0 ; R0 wird als Zähler verwendet, retten ldi rmp,6 ; Fünf Ziffern, einer zu viel mov R0,rmp ; in Zählerregister R0 clr rBin1H ; Ergebnis auf Null setzen clr rBin1L clt ; Fehlerflagge T-Bit zurücksetzen Asc5ToBin2a: dec R0 ; Alle Zeichen leer oder Null? breq Asc5ToBin2d ; Ja, beenden ld rmp,Z+ ; Lese nächstes Zeichen cpi rmp,' ' ; überlese Leerzeichen breq Asc5ToBin2a ; geh zum nächsten Zeichen cpi rmp,'0' ; überlese führende Nullen breq Asc5ToBin2a ; geh zum nächsten Zeichen Asc5ToBin2b: subi rmp,'0' ; Behandle Ziffer, ziehe ASCII-0 ab brcs Asc5ToBin2e ; Ziffer ist ungültig, raus cpi rmp,10 ; Ziffer größer als neun? brcc Asc5ToBin2e ; Ziffer ist ungültig, raus rcall Bin1Mul10 ; Multipliziere Binärzahl mit 10 brts Asc5ToBin2e ; Überlauf, raus add rBin1L,rmp ; addiere die Ziffer zur Binärzahl ld rmp,z+ ; lese schon mal das nächste Zeichen brcc Asc5ToBin2c ; Kein Überlauf in das nächste Byte inc rBin1H ; Überlauf in das nächste Byte breq Asc5ToBin2e ; Überlauf auch ins übernächste Byte Asc5ToBin2c: dec R0 ; Verringere Zähler für Anzahl Zeichen brne Asc5ToBin2b ; Wandle weitere Zeichen um Asc5ToBin2d: ; Ende der ASCII-kodierten Zahl erreicht sbiw ZL,5 ; Stelle die Startposition in Z wieder her pop R0 ; Stelle Register R0 wieder her ret ; Kehre zurück Asc5ToBin2e: ; Letztes Zeichen war ungültig sbiw ZL,1 ; Zeige mit Z auf ungültiges Zeichen pop R0 ; Stelle Register R0 wieder her set ; Setze T-Flag für Fehler ret ; und kejre zurück ; ; Bcd5ToBin2 ; ========== ; wandelt eine 5-bit-BCD-Zahl in eine 16-Bit-Binärzahl um ; Aufruf: Z zeigt auf erste Stelle der BCD-kodierten ; Zahl ; Stellen: Die Zahl muss exakt 5 gültige Stellen haben. ; Rückkehr: T-Flag zeigt Fehlerbedingung an: ; T=0: Binärzahl in rBin1H:L ist gültig, Z zeigt auf ; erste Stelle der BCD-kodierten Zahl. ; T=1: Fehler bei der Umwandlung. Entweder war die Zahl ; zu groß (0..65535, Z zeigt auf die Ziffer, bei der ; der Überlauf auftrat) oder sie enthielt ein ungülti; ges Zeichen (Z zeigt auf das ungültige Zeichen). ; Benötigte Register: rBin1H:L (Ergebnis), R0 (wiederher; gestellt), rBin2H:L (wieder hergestellt), rmp ; Aufgerufene Unterroutinen: Bin1Mul10 ; Bcd5ToBin2: push R0 ; Rette Register R0 clr rBin1H ; Setze Ergebnis Null clr rBin1L ldi rmp,5 ; Setze Zähler auf 5 Ziffern mov R0,rmp ; R0 ist Zähler clt ; Setze Fehlerflagge zurück Bcd5ToBin2a: ld rmp,Z+ ; Lese BCD-Ziffer cpi rmp,10 ; prüfe ob Ziffer korrekt brcc Bcd5ToBin2c ; ungültige BCD-Ziffer rcall Bin1Mul10 ; Multipliziere Ergebnis mit 10 brts Bcd5ToBin2c ; Überlauf aufgetreten add rBin1L,rmp ; Addiere Ziffer brcc Bcd5ToBin2b ; Kein Überlauf ins nächste Byte inc rBin1H ; Überlauf ins nächste Byte breq Bcd5ToBin2c ; Überlauf ins übernächste Byte Bcd5ToBin2b: dec R0 ; weitere Ziffer? brne Bcd5ToBin2a ; Ja pop R0 ; Stelle Register wieder her sbiw ZL,5 ; Setze Zeiger auf erste Stelle der BCD-Zahl ret ; Kehre zurück Bcd5ToBin2c: sbiw ZL,1 ; Eine Ziffer zurück pop R0 ; Stelle Register wieder her set ; Setze T-flag, Fehler ret ; Kehre zurück ; ; Bin1Mul10 ; ========= ; Multipliziert die 16-Bit-Binärzahl mit 10 ; Unterroutine benutzt von AscToBin2, Asc5ToBin2, Bcd5ToBin2 ; Aufruf: 16-Bit-Binärzahl in rBin1H:L ; Rückkehr: T-Flag zeigt gültiges Ergebnis an. ; T=0: rBin1H:L enthält gültiges Ergebnis. ; T=1: Überlauf bei der Multiplikation, rBin1H:L undefiniert ; Benutzte Register: rBin1H:L (Ergebnis), rBin2H:L (wird wieder ; hergestellt) ; Bin1Mul10: push rBin2H ; Rette die Register rBin2H:L push rBin2L mov rBin2H,rBin1H ; Kopiere die Zahl dort hin mov rBin2L,rBin1L add rBin1L,rBin1L ; Multipliziere Zahl mit 2 adc rBin1H,rBin1H brcs Bin1Mul10b ; Überlauf, raus hier! Bin1Mul10a: add rBin1L,rbin1L ; Noch mal mit 2 malnehmen (=4*Zahl) adc rBin1H,rBin1H brcs Bin1Mul10b ; Überlauf, raus hier! add rBin1L,rBin2L ; Addiere die Kopie (=5*Zahl) adc rBin1H,rBin2H brcs Bin1Mul10b ;Überlauf, raus hier! add rBin1L,rBin1L ; Noch mal mit 2 malnehmen (=10*Zahl) adc rBin1H,rBin1H brcc Bin1Mul10c ; Kein Überlauf, überspringe Bin1Mul10b: set ; Überlauf, setze T-Flag Bin1Mul10c: pop rBin2L ; Stelle die geretteten Register wieder her pop rBin2H ret ; Kehre zurück ; ; ************************************************ ; ; Paket II: Von Binär nach ASCII bzw. BCD ; ; Bin2ToAsc5 ; ========== ; wandelt eine 16-Bit-Binärzahl in eine fünfstellige ASCII; kodierte Dezimalzahl um ; Aufruf: 16-Bit-Binärzahl in rBin1H:L, Z zeigt auf Anfang ; der Zahl ; Rückkehr: Z zeigt auf Anfang der Zahl, führende Nullen sind ; mit Leerzeichen überschrieben ; Benutzte Register: rBin1H:L (bleibt erhalten), rBin2H:L ; (wird überschrieben), rmp ; Aufgerufene Unterroutinen: Bin2ToBcd5 ; Bin2ToAsc5: rcall Bin2ToBcd5 ; wandle Binärzahl in BCD um ldi rmp,4 ; Zähler auf 4 mov rBin2L,rmp Bin2ToAsc5a: ld rmp,z ; Lese eine BCD-Ziffer tst rmp ; prüfe ob Null brne Bin2ToAsc5b ; Nein, erste Ziffer <> 0 gefunden ldi rmp,' ' ; mit Leerzeichen überschreiben st z+,rmp ; und ablegen dec rBin2L ; Zähler um eins senken brne Bin2ToAsc5a ; weitere führende Leerzeichen ld rmp,z ; Lese das letzte Zeichen Bin2ToAsc5b: inc rBin2L ; Ein Zeichen mehr Bin2ToAsc5c: subi rmp,-'0' ; Addiere ASCII-0 st z+,rmp ; und speichere ab, erhöhe Zeiger ld rmp,z ; nächstes Zeichen lesen dec rBin2L ; noch Zeichen behandeln? brne Bin2ToAsc5c ; ja, weitermachen sbiw ZL,5 ; Zeiger an Anfang ret ; fertig ; ; Bin2ToAsc ; ========= ; wandelt eine 16-Bit-Binärzahl in eine fünfstellige ASCII; kodierte Dezimalzahl um, Zeiger zeigt auf die erste signi; fikante Ziffer der Zahl, und gibt Anzahl der Ziffern zu; rück ; Aufruf: 16-Bit-Binärzahl in rBin1H:L, Z zeigt auf Anfang ; der Zahl (5 Stellen erforderlich, auch bei kleineren Zah; len!) ; Rückkehr: Z zeigt auf erste signifikante Ziffer der ASCII; kodierten Zahl, rBin2L enthält Länge der Zahl (1..5) ; Benutzte Register: rBin1H:L (bleibt erhalten), rBin2H:L ; (wird überschrieben), rmp ; Aufgerufene Unterroutinen: Bin2ToBcd5, Bin2ToAsc5 ; Bin2ToAsc: rcall Bin2ToAsc5 ; Wandle Binärzahl in ASCII ldi rmp,6 ; Zähler auf 6 mov rBin2L,rmp Bin2ToAsca: dec rBin2L ; verringere Zähler ld rmp,z+ ; Lese Zeichen und erhöhe Zeiger cpi rmp,' ' ; war Leerzeichen? breq Bin2ToAsca ; Nein, war nicht sbiw ZL,1 ; ein Zeichen rückwärts ret ; fertig ; ; Bin2ToBcd5 ; ========== ; wandelt 16-Bit-Binärzahl in 5-stellige BCD-Zahl um ; Aufruf: 16-Bit-Binärzahl in rBin1H:L, Z zeigt auf die ; erste Stelle der BCD-kodierten Resultats ; Stellen: Die BCD-Zahl hat exakt 5 gültige Stellen. ; Rückkehr: Z zeigt auf die höchste BCD-Stelle ; Benötigte Register: rBin1H:L (wird erhalten), rBin2H:L ; (wird nicht wieder hergestellt), rmp ; Aufgerufene Unterroutinen: Bin2ToDigit ; Bin2ToBcd5: push rBin1H ; Rette Inhalt der Register rBin1H:L push rBin1L ldi rmp,HIGH(10000) ; Lade 10.000 in rBin2H:L mov rBin2H,rmp ldi rmp,LOW(10000) mov rBin2L,rmp rcall Bin2ToDigit ; Ermittle 5.Stelle durch Abziehen ldi rmp,HIGH(1000) ; Lade 1.000 in rBin2H:L mov rBin2H,rmp ldi rmp,LOW(1000) mov rBin2L,rmp rcall Bin2ToDigit ; Ermittle 4.Stelle durch Abziehen ldi rmp,HIGH(100) ; Lade 100 in rBin2H:L mov rBin2H,rmp ldi rmp,LOW(100) mov rBin2L,rmp rcall Bin2ToDigit ; Ermittle 3.Stelle durch Abziehen ldi rmp,HIGH(10) ; Lade 10 in rBin2H:L mov rBin2H,rmp ldi rmp,LOW(10) mov rBin2L,rmp rcall Bin2ToDigit ; Ermittle 2.Stelle durch Abziehen st z,rBin1L ; Rest sind Einer sbiw ZL,4 ; Setze Zeiger Z auf 5.Stelle (erste Ziffer) pop rBin1L ; Stelle den Originalwert wieder her pop rBin1H ret ; und kehre zurück ; ; Bin2ToDigit ; =========== ; ermittelt eine dezimale Ziffer durch fortgesetztes Abziehen ; einer binär kodierten Dezimalstelle ; Unterroutine benutzt von: Bin2ToBcd5, Bin2ToAsc5, Bin2ToAsc ; Aufruf: Binärzahl in rBin1H:L, binär kodierte Dezimalzahl ; in rBin2H:L, Z zeigt auf bearbeitete BCD-Ziffer ; Rückkehr: Ergebis in Z (bei Aufruf), Z um eine Stelle er; höht, keine Fehlerbehandlung ; Benutzte Register: rBin1H:L (enthält Rest der Binärzahl), ; rBin2H (bleibt erhalten), rmp ; Aufgerufene Unterroutinen: ; Bin2ToDigit: clr rmp ; Zähler auf Null Bin2ToDigita: cp rBin1H,rBin2H ; Vergleiche MSBs miteinander brcs Bin2ToDigitc ; MSB Binärzahl kleiner, fertig brne Bin2ToDigitb ; MSB Binärzahl größer, subtrahiere cp rBin1L,rBin2L ; MSB gleich, vergleiche LSBs brcs Bin2ToDigitc ; LSB Binärzahl kleiner, fertig Bin2ToDigitb: sub rBin1L,rBin2L ; Subtrahiere LSB Dezimalzahl sbc rBin1H,rBin2H ; Subtrahiere Carry und MSB inc rmp ; Erhöhe den Zähler rjmp Bin2ToDigita ; Weiter vergleichen/subtrahieren Bin2ToDigitc: st z+,rmp ; Speichere das Ergebnis und erhöhe Zeiger ret ; zurück ; ; *********************************************** ; ; Paket III: Von Binär nach Hex-ASCII ; ; Bin2ToHex4 ; ========== ; wandelt eine 16-Bit-Binärzahl in Hex-ASCII ; Aufruf: Binärzahl in rBin1H:L, Z zeigt auf erste Position ; des vierstelligen ASCII-Hex ; Rückkehr: Z zeigt auf erste Position des vierstelligen ; ASCII-Hex, ASCII-Ziffern A..F in Großbuchstaben ; Benutzte Register: rBin1H:L (bleibt erhalten), rmp ; Aufgerufene Unterroutinen: Bin1ToHex2, Bin1ToHex1 ; Bin2ToHex4: mov rmp,rBin1H ; MSB in rmp kopieren rcall Bin1ToHex2 ; in Hex-ASCII umwandeln mov rmp,rBin1L ; LSB in rmp kopieren rcall Bin1ToHex2 ; in Hex-ASCII umwandeln sbiw ZL,4 ; Zeiger auf Anfang Hex-ASCII ret ; fertig ; ; Bin1ToHex2 wandelt die 8-Bit-Binärzahl in rmp in Hex-ASCII ; gehört zu: Bin2ToHex4 ; Bin1ToHex2: push rmp ; Rette Byte auf dem Stapel swap rmp ; Vertausche die oberen und unteren 4 Bit rcall Bin1ToHex1 ; wandle untere 4 Bits in Hex-ASCII pop rmp ; Stelle das Byte wieder her Bin1ToHex1: andi rmp,$0F ; Maskiere die oberen vier Bits subi rmp,-'0' ; Addiere ASCII-0 cpi rmp,'9'+1 ; Ziffern A..F? brcs Bin1ToHex1a ; Nein subi rmp,-7 ; Addiere 7 für A..F Bin1ToHex1a: st z+,rmp ; abspeichern und Zeiger erhöhen ret ; fertig ; ; *********************************************** ; ; Paket IV: Von Hex-ASCII nach Binär ; ; Hex4ToBin2 ; ========== ; wandelt eine vierstellige Hex-ASCII-Zahl in eine 16-Bit; Binärzahl um ; Aufruf: Z zeigt auf die erste Stelle der Hex-ASCII-Zahl ; Rückkehr: T-Flag zeigt Fehler an: ; T=0: rBin1H:L enthält die 16-Bit-Binärzahl, Z zeigt ; auf die erste Hex-ASCII-Ziffer wie beim Aufruf ; T=1: ungültige Hex-ASCII-Ziffer, Z zeigt auf ungültige ; Ziffer ; Benutzte Register: rBin1H:L (enthält Ergebnis), R0 (wie; der hergestellt), rmp ; Aufgerufene Unterroutinen: Hex2ToBin1, Hex1ToBin1 ; Hex4ToBin2: clt ; Lösche Fehlerflag rcall Hex2ToBin1 ; Wandle zwei Hex-ASCII-Ziffern brts Hex4ToBin2a ; Fehler, beende hier mov rBin1H,rmp ; kopiere nach MSB Ergebnis rcall Hex2ToBin1 ; Wandle zwei Hex-ASCII-Ziffern brts Hex4ToBin2a ; Fehler, beende hier mov rBin1L,rmp ; kopiere nach LSB Ergebnis sbiw ZL,4 ; Ergebis ok, Zeiger auf Anfang Hex4ToBin2a: ret ; zurück ; ; Hex2ToBin1 wandelt 2-stellig-Hex-ASCII nach 8-Bit-Binär ; Hex2ToBin1: push R0 ; Rette Register R0 rcall Hex1ToBin1 ; Wandle nächstes Zeichen in Byte brts Hex2ToBin1a ; Fehler, stop hier swap rmp; untere vier Bits in obere vier Bits mov R0,rmp ; zwischenspeichern rcall Hex1ToBin1 ; Nächstes Zeichen umwandeln brts Hex2ToBin1a ; Fehler, raus hier or rmp,R0 ; untere und obere vier Bits zusammen Hex2ToBin1a: pop R0 ; Stelle R0 wieder her ret ; zurück ; ; Hex1ToBin1 liest ein Zeichen und wandelt es in Binär um ; Hex1ToBin1: ld rmp,z+ ; Lese Zeichen subi rmp,'0' ; Ziehe ASCII-0 ab brcs Hex1ToBin1b ; Fehler, kleiner als 0 cpi rmp,10 ; A..F ; Ziffer größer als 9? brcs Hex1ToBin1c ; nein, fertig cpi rmp,$30 ; Kleinbuchstaben? brcs Hex1ToBin1a ; Nein subi rmp,$20 ; Klein- in Grossbuchstaben Hex1ToBin1a: subi rmp,7 ; Ziehe 7 ab, A..F ergibt $0A..$0F cpi rmp,10 ; Ziffer kleiner $0A? brcs Hex1ToBin1b ; Ja, Fehler cpi rmp,16 ; Ziffer größer als $0F brcs Hex1ToBin1c ; Nein, Ziffer in Ordnung Hex1ToBin1b: ; Error sbiw ZL,1 ; Ein Zeichen zurück set ; Setze Fehlerflagge Hex1ToBin1c: ret ; Zurück http://www.avr-asm-tutorial.net/avr_de/quellen/konvert.asm1/20/2009 7:35:32 PM
Binäre Fließkommazahlen in AVR Assembler
Pfad: Home => AVR-Übersicht => Binäres Rechnen => Fließkomma
Umgang mit Festkommazahlen in AVR Assembler Sinn und Unsinn von Fließkommazahlen Oberster Grundsatz: Verwende keine Fließkommazahlen, es sei denn, Du brauchst sie wirklich. Fließkommazahlen sind beim AVR Ressourcenfresser, lahme Enten und brauchen wahnsinnige Verarbeitungszeiten. So oder ähnlich geht es einem, der glaubt, Assembler sei schwierig und macht lieber mit Basic und seinen höheren Genossen C und Pascal herum. Nicht so in Assembler. Hier kriegst Du gezeigt, wie man bei 4 MHz Takt in gerade mal weniger als 60 Mikrosekunden, im günstigsten Fall in 18 Mikrosekunden, eine Multiplikation einer Kommmazahl abziehen kann. Ohne extra Fließkommazahlen-Prozessor oder ähnlichem Schnickschnack für Denkfaule. Wie das geht? Zurück zu den Wurzeln! Die meisten Aufgaben mit Fließkommazahlen sind eigentlich auch mit Festkommazahlen gut zu erledigen. Und die kann man nämlich mit einfachen Ganzzahlen erledigen. Und die sind wiederum in Assembler leicht zu programmieren und sauschnell zu verarbeiten. Das Komma denkt sich der Programmierer einfach dazu und schmuggelt es an einem festem Platz einfach in die Strom von Ganzzahlen-Ziffern rein. Und keiner merkt, dass hier eigentlich gemogelt wird. Zum Seitenanfang
Lineare Umrechnungen Als Beispiel folgende Aufgabe: ein 8-Bit-AD-Wandler misst ein Eingangssignal von 0,00 bis 2,55 Volt und liefert als Ergebnis eine Binärzahl zwischen $00 und $FF ab. Das Ergebnis, die Spannung, soll aber als ASCII-Zeichenfolge auf einem LCD-Display angezeigt werden. Doofes Beispiel, weil es so einfach ist: Die Hexzahl wird in eine BCD-kodierte Dezimalzahl zwischen 000 und 255 umgewandelt und nach der ersten Ziffer einfach das Komma eingeschmuggelt. Fertig. Leider ist die Elektronikwelt manchmal nicht so einfach und stellt schwerere Aufgaben. Der ADWandler tut uns nicht den Gefallen und liefert für Eingangsspannungen zwischen 0,00 und 5,00 Volt die 8-Bit-Hexzahl $00 bis $FF. Jetzt stehen wir blöd da und wissen nicht weiter, weil wir die Hexzahl eigentlich mit 500/255, also mit 1,9608 malnehmen müssten. Das ist eine doofe Zahl, weil sie fast zwei ist, aber nicht so ganz. Und so ungenau wollten wir es halt doch nicht haben, wenn schon der AD-Wandler mit einem Viertel Prozent Genauigkeit glänzt. Immerhin zwei Prozent Ungenauigkeit beim höchsten Wert ist da doch zuviel des Guten. Um uns aus der Affäre zu ziehen, multiplizieren wir das Ganze dann eben mit 500*256/255 oder 501,96 und teilen es anschließend wieder durch 256. Wenn wir jetzt anstelle 501,96 mit aufgerundet 502 multiplizieren (es lebe die Ganzzahl!), beträgt der Fehler noch ganze 0,008%. Mit dem können wir einstweilen leben. Und das Teilen durch 256 ist dann auch ganz einfach, weil es eine bekannte Potenz von Zwei ist, und weil der AVR sich beim Teilen durch Potenzen von Zwei so richtig pudelwohl fühlt und abgeht wie Hund. Beim Teilen einer Ganzzahl durch 256 geht er noch schneller ab, weil wir dann nämlich einfach das letzte Byte der Binärzahl weglassen können. Nix mit Schieben und Rotieren wie beim richtigen Teilen. Die Multiplikation einer 8-Bit-Zahl mit der 9-Bit-Zahl 502 (hex 01F6) kann natürlich ein Ergebnis haben, das nicht mehr in 16 Bit passt. Hier müssen deshalb 3 Bytes oder 24 Bits für das Ergebnis vorbereitet sein, damit nix überläuft. Während der Multiplikation der 8-Bit-Zahl wird die 9-Bit-Zahl 502 immer eine Stelle nach links geschoben (mit 2 multipliziert), passt also auch nicht mehr in 2 Bytes und braucht also auch drei Bytes. Damit erhalten wir als Beispiel folgende Belegung von Registern beim Multiplizieren: Zahl
Wert (Beispiel) Register
Eingangswert 255
R1
Multiplikator 502
R4:R3:R2
Ergebnis
R7:R6:R5
128.010
Nach der Vorbelegung von R4:R3:R2 mit dem Wert 502 (Hex 00.01.F6) und dem Leeren der Ergebnisregister R7:R6:R5 geht die Multiplikation jetzt nach folgendem Schema ab: 1. Testen, ob die Zahl schon Null ist. Wenn ja, sind wir fertig mit der Multiplikation. 2. Wenn nein, ein Bit aus der 8-Bit-Zahl nach rechts heraus in das Carry-Flag schieben und gleichzeitig eine Null von links hereinschieben. Der Befehl heißt Logical-Shift-Right oder LSR. 3. Wenn das herausgeschobene Bit im Carry eine Eins ist, wird der Inhalt des Multiplikators (beim ersten Schritt 502) zum Ergebnis hinzu addiert. Beim Addieren auf Überläufe achten (Addition von R5 mit R2 mit ADD, von R6 mit R3 sowie von R7 mit R4 mit ADC!). Ist es eine Null, unterlassen wir das Addieren und gehen sofort zum nächsten Schritt. 4. Jetzt wird der Multiplikator mit zwei multipliziert, denn das nächste Bit der Zahl ist doppelt so viel wert. Also R2 mit LSL links schieben (Bit 7 in das Carry herausschieben, eine Null in Bit 0 hineinschieben), dann das Carry mit ROL in R3 hineinrotieren (Bit 7 von R3 rutscht jetzt in das Carry), und dieses Carry mit ROL in R4 rotieren. 5. Jetzt ist eine binäre Stelle der Zahl erledigt und es geht bei Schritt 1 weiter. Das Ergebnis der Multiplikation mit 502 steht dann in den Ergebnisregistern R7:R6:R5. Wenn wir jetzt das Register R5 ignorieren, steht in R7:R6 das Ergebnis der Division durch 256. Zur Verbesserung der Genauigkeit können wir noch das Bit 7 von R5 dazu heranziehen, um die Zahl in R7:R6 zu runden. Und unser Endergebnis in binärer Form braucht dann nur noch in dezimale ASCIIForm umgewandelt werden (siehe Umwandlung binär zu Dezimal-ASCII). Setzen wir dem Ganzen dann noch einen Schuss Komma an der richtigen Stelle zu, ist die Spannung fertig für die Anzeige im Display. Der gesamte Vorgang von der Ausgangszahl bis zum Endergebnis in ASCII dauert zwischen 79 und 228 Taktzyklen, je nachdem wieviel Nullen und Einsen die Ausgangszahl aufweist. Wer das mit der Fließkommaroutine einer Hochsprache schlagen will, soll sich bei mir melden. Zum Seitenanfang
Beispiel 1: 8-Bit-AD-Wandler mit Festkommaausgabe Das bisher beschriebene Programm ist, noch ein wenig optimiert, in HTML-Form oder als AssemblerQuelltext zugänglich. Der Quelltext enthält alle nötigen Routinen der Umrechnung in kompakter Form, zum leichteren Import in andere Programme. Als Kopf ist ein Testabschnitt hinzugefügt, so dass der Programmablauf im Studio simuliert werden kann. Zum Seitenanfang
Assembler Quelltext der Umwandlung einer 8Bit-Zahl in eine dreistellige Festkommazahl ; Demonstriert Fließkomma-Umwandlung in ; Assembler, (C)2003 www.avr-asm-tutorial.net ; ; Die Aufgabe: Ein 8-Bit-Analog-Wandler-Signal ; in Binärform wird eingelesen, die Zahlen ; reichen von hex 00 bis FF. ; Diese Zahlen sind umzurechnen in eine ; Fließkommazahl zwischen 0,00 bis 5,00 ; Volt. ; ; Der Programmablauf: ; 1. Multiplikation mit 502 (hex 01F6). ; Dieser Schritt multipliziert die Zahl mit ; den Faktoren 500 und 256 und dividiert ; mit 255 in einem Schritt. ; 2. Das Ergebnis wird gerundet und das letzte ; Byte abgeschnitten. ; Dieser Schritt dividiert durch 256, indem ; das letzte Byte des Ergebnisses ignoriert ; wird. Davor wird das Bit 7 des Ergebnisses ; abgefragt und zum Runden des Ergebnisses ; verwendet. ; 3. Die erhaltene Zahl wird in ASCII-Zeichen ; umgewandelt und mit einem Dezimalpunkt ; versehen. ; Das Ergebnis, eine Ganzzahl zwischen 0 und ; und 500 wird in eine Dezimalzahl verwan; delt und in der Form 5,00 als Fließkomma; zahl in ASCII-Zeichen dargestellt. ; ; Die verwendeten Register: ; Die Routinen benutzen die Register R8 bis R1, ; ohne diese vorher zu sichern. Zusätzlich wird ; das Vielzweckregister rmp verwendet, das in ; der oberen Registerhälfte liegen muss. Bitte ; beachten, dass diese verwendeten Register ; nicht mit anderen Verwendungen in Konflikt ; kommen können. ; ; Zu Beginn wird die 8-Bit-Zahl im Register ; R1 erwartet. ; ; Die Multiplikation verwendet R4:R3:R2 zur ; Speicherung des Multiplikators 502 (der ; bei der Berechnung maximal 8 mal links ge; schoben wird - Multiplikation mit 2). Das ; Ergebnis der Multiplikation wird in den Re; gistern R7:R6:R5 berechnet. ; Das Ergebnis der sogenannten Division durch ; 256 durch Ignorieren von R5 des Ergebnisses ; ist in R7:R6 gespeichert. Abhängig von Bit ; 7 in R5 wird zum Registerpaar R7:R6 eine ; Eins addiert. Das Ergebnis in R7:R6 wird ; in das Registerpaar R2:R1 kopiert. ; Die Umwandlung der Binärzahl in R2:R1 in ; eine ASCII-Zeichenfolge verwendet das Paar ; R4:R3 als Divisor für die Umwandlung. Das ; Ergebnis, der ASCII-String, ist in den Re; gistern R5:R6:R7:R8 abgelegt. ; ; Weitere Bedingungen: ; Die Umwandlung benutzt Unterprogramme, daher ; muss der Stapel funktionieren. Es werden ; maximal drei Ebenen Unterprogrammaufrufe ; verschachtelt (benötigt sechs Byte SRAM). ; ; Umwandlungszeiten: ; Die gesamte Umwandlung benötigt 228 Taktzyklen ; maximal (Umwandlung von $FF) bzw. 79 Takt; zyklen (Umwandlung von $00). Bei 4 MHz Takt ; entspricht dies 56,75 Mikrosekunden bzw. 17,75 ; Mikrosekunden. ; ; Definitionen: ; Register .DEF rmp = R16 ; als Vielzweckregister verwendet ; ; AVR-Typ ; Getestet für den Typ AT90S8515, die Angabe ; wird nur für den Stapel benötigt. Die Routinen ; arbeiten auf anderen AT90S-Prozessoren genauso ; gut. .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Start des Testprogramms ; ; Schreibt eine Zahl in R1 und startet die Wand; lungsroutine, nur für Testzwecke. ; .CSEG .ORG $0000 rjmp main ; main: ldi rmp,HIGH(RAMEND) ; Richte den Stapel ein out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,$FF ; Wandle FF um mov R1,rmp rcall fpconv8 ; Rufe die Umwandlungsroutine no_end: ; unendliche Schleife, wenn fertig rjmp no_end ; ; Ablaufssteuerung der Umwandlungsroutine, ruft die ; verschiedenen Teilschritte auf ; fpconv8: rcall fpconv8m ; Multipliziere mit 502 rcall fpconv8r ; Runden und Division mit 256 rcall fpconv8a ; Umwandlung in ASCII-String ldi rmp,'.' ; Setze Dezimalpunkt mov R6,rmp ret ; Alles fertig ; ; Unterprogramm Multiplikation mit 502 ; ; Startbedingung: ; +--+ ; |R1| Eingabezahl, im Beispiel $FF ; |FF| ; +--+ ; +--+--+--+ ; |R4|R3|R2| Multiplikant 502 = $00 01 F6 ; |00|01|F6| ; +--+--+--+ ; +--+--+--+ ; |R7|R6|R5| Resultat, im Beispiel 128.010 ; |01|F4|0A| ; +--+--+--+ ; fpconv8m: clr R4 ; Setze den Multiplikant auf 502 ldi rmp,$01 mov R3,rmp ldi rmp,$F6 mov R2,rmp clr R7 ; leere Ergebnisregister clr R6 clr R5 fpconv8m1: or r1,R1 ; Prüfe ob noch Bits 1 sind brne fpconv8m2 ; Noch Einsen, mach weiter ret ; fertig, kehre zurück fpconv8m2: lsr R1 ; Schiebe Zahl nach rechts (teilen durch 2) brcc fpconv8m3 ; Wenn das niedrigste Bit eine 0 war, ; dann überspringe den Additionsschritt add R5,R2 ; Addiere die Zahl in R4:R3:R2 zum Ergebnis adc R6,R3 adc R7,R4 fpconv8m3: lsl R2 ; Multipliziere R4:R3:R2 mit 2 rol R3 rol R4 rjmp fpconv8m1 ; Wiederhole für das nächste Bit ; ; Runde die Zahl in R7:R6 mit dem Wert von Bit 7 von R5 ; fpconv8r: clr rmp ; Null nach rmp lsl R5 ; Rotiere Bit 7 ins Carry adc R6,rmp ; Addiere LSB mit Übertrag adc R7,rmp ; Addiere MSB mit Übertrag mov R2,R7 ; Kopiere den Wert nach R2:R1 (durch 256 teilen) mov R1,R6 ret ; ; Wandle das Wort in R2:R1 in einen ASCII-Text in R5:R6:R7:R8 ; ; +---+---+ ; + R2| R1| Eingangswert 0..500 ; +---+---+ ; +---+---+ ; | R4| R3| Dezimalteiler ; +---+---+ ; +---+---+---+---+ ; | R5| R6| R7| R8| Ergebnistext (für Eingangswert 500) ; |'5'|'.'|'0'|'0'| ; +---+---+---+---+ ; fpconv8a: clr R4 ; Setze Dezimalteiler auf 100 ldi rmp,100 mov R3,rmp rcall fpconv8d ; Hole ASCII-Ziffer durch wiederholtes Abziehen mov R5,rmp ; Setze Hunderter Zeichen ldi rmp,10 ; Setze Dezimalteiler auf 10 mov R3,rmp rcall fpconv8d ; Hole die nächste Ziffer mov R7,rmp ldi rmp,'0' ; Wandle Rest in ASCII-Ziffer um add rmp,R1 mov R8,rmp ; Setze Einer Zeichen ret ; ; Wandle Binärwort in R2:R1 in eine Dezimalziffer durch fortgesetztes ; Abziehen des Dezimalteilers in R4:R3 (100, 10) ; fpconv8d: ldi rmp,'0' ; Beginne mit ASCII-0 fpconv8d1: cp R1,R3 ; Vergleiche Wort mit Teiler cpc R2,R4 brcc fpconv8d2 ; Carry nicht gesetzt, subtrahiere Teiler ret ; fertig fpconv8d2: sub R1,R3 ; Subtrahiere Teilerwert sbc R2,R4 inc rmp ; Ziffer um eins erhöhen rjmp fpconv8d1 ; und noch einmal von vorne ; ; Ende der Fließkomma-Umwandlungsroutinen ; ; ; Ende des Umwandlungstestprogramms ;
; Demonstriert Fließkomma-Umwandlung in ; Assembler, (C)2003 www.avr-asm-tutorial.net ; ; Die Aufgabe: Ein 8-Bit-Analog-Wandler-Signal ; in Binärform wird eingelesen, die Zahlen ; reichen von hex 00 bis FF. ; Diese Zahlen sind umzurechnen in eine ; Fließkommazahl zwischen 0,00 bis 5,00 ; Volt. ; ; Der Programmablauf: ; 1. Multiplikation mit 502 (hex 01F6). ; Dieser Schritt multipliziert die Zahl mit ; den Faktoren 500 und 256 und dividiert ; mit 255 in einem Schritt. ; 2. Das Ergebnis wird gerundet und das letzte ; Byte abgeschnitten. ; Dieser Schritt dividiert durch 256, indem ; das letzte Byte des Ergebnisses ignoriert ; wird. Davor wird das Bit 7 des Ergebnisses ; abgefragt und zum Runden des Ergebnisses ; verwendet. ; 3. Die erhaltene Zahl wird in ASCII-Zeichen ; umgewandelt und mit einem Dezimalpunkt ; versehen. ; Das Ergebnis, eine Ganzzahl zwischen 0 und ; und 500 wird in eine Dezimalzahl verwan; delt und in der Form 5,00 als Fließkomma; zahl in ASCII-Zeichen dargestellt. ; ; Die verwendeten Register: ; Die Routinen benutzen die Register R8 bis R1, ; ohne diese vorher zu sichern. Zusätzlich wird ; das Vielzweckregister rmp verwendet, das in ; der oberen Registerhälfte liegen muss. Bitte ; beachten, dass diese verwendeten Register ; nicht mit anderen Verwendungen in Konflikt ; kommen können. ; ; Zu Beginn wird die 8-Bit-Zahl im Register ; R1 erwartet. ; ; Die Multiplikation verwendet R4:R3:R2 zur ; Speicherung des Multiplikators 502 (der ; bei der Berechnung maximal 8 mal links ge; schoben wird - Multiplikation mit 2). Das ; Ergebnis der Multiplikation wird in den Re; gistern R7:R6:R5 berechnet. ; Das Ergebnis der sogenannten Division durch ; 256 durch Ignorieren von R5 des Ergebnisses ; ist in R7:R6 gespeichert. Abhängig von Bit ; 7 in R5 wird zum Registerpaar R7:R6 eine ; Eins addiert. Das Ergebnis in R7:R6 wird ; in das Registerpaar R2:R1 kopiert. ; Die Umwandlung der Binärzahl in R2:R1 in ; eine ASCII-Zeichenfolge verwendet das Paar ; R4:R3 als Divisor für die Umwandlung. Das ; Ergebnis, der ASCII-String, ist in den Re; gistern R5:R6:R7:R8 abgelegt. ; ; Weitere Bedingungen: ; Die Umwandlung benutzt Unterprogramme, daher ; muss der Stapel funktionieren. Es werden ; maximal drei Ebenen Unterprogrammaufrufe ; verschachtelt (benötigt sechs Byte SRAM). ; ; Umwandlungszeiten: ; Die gesamte Umwandlung benötigt 228 Taktzyklen ; maximal (Umwandlung von $FF) bzw. 79 Takt; zyklen (Umwandlung von $00). Bei 4 MHz Takt ; entspricht dies 56,75 Mikrosekunden bzw. 17,75 ; Mikrosekunden. ; ; Definitionen: ; Register .DEF rmp = R16 ; als Vielzweckregister verwendet ; ; AVR-Typ ; Getestet für den Typ AT90S8515, die Angabe ; wird nur für den Stapel benötigt. Die Routinen ; arbeiten auf anderen AT90S-Prozessoren genauso ; gut. .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Start des Testprogramms ; ; Schreibt eine Zahl in R1 und startet die Wand; lungsroutine, nur für Testzwecke. ; .CSEG .ORG $0000 rjmp main ; main: ldi rmp,HIGH(RAMEND) ; Richte den Stapel ein out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,$FF ; Wandle FF um mov R1,rmp rcall fpconv8 ; Rufe die Umwandlungsroutine no_end: ; unendliche Schleife, wenn fertig rjmp no_end ; ; Ablaufssteuerung der Umwandlungsroutine, ruft die ; verschiedenen Teilschritte auf ; fpconv8: rcall fpconv8m ; Multipliziere mit 502 rcall fpconv8r ; Runden und Division mit 256 rcall fpconv8a ; Umwandlung in ASCII-String ldi rmp,'.' ; Setze Dezimalpunkt mov R6,rmp ret ; Alles fertig ; ; Unterprogramm Multiplikation mit 502 ; ; Startbedingung: ; +--+ ; |R1| Eingabezahl, im Beispiel $FF ; |FF| ; +--+ ; +--+--+--+ ; |R4|R3|R2| Multiplikant 502 = $00 01 F6 ; |00|01|F6| ; +--+--+--+ ; +--+--+--+ ; |R7|R6|R5| Resultat, im Beispiel 128.010 ; |01|F4|0A| ; +--+--+--+ ; fpconv8m: clr R4 ; Setze den Multiplikant auf 502 ldi rmp,$01 mov R3,rmp ldi rmp,$F6 mov R2,rmp clr R7 ; leere Ergebnisregister clr R6 clr R5 fpconv8m1: or r1,R1 ; Prüfe ob noch Bits 1 sind brne fpconv8m2 ; Noch Einsen, mach weiter ret ; fertig, kehre zurück fpconv8m2: lsr R1 ; Schiebe Zahl nach rechts (teilen durch 2) brcc fpconv8m3 ; Wenn das niedrigste Bit eine 0 war, ; dann überspringe den Additionsschritt add R5,R2 ; Addiere die Zahl in R4:R3:R2 zum Ergebnis adc R6,R3 adc R7,R4 fpconv8m3: lsl R2 ; Multipliziere R4:R3:R2 mit 2 rol R3 rol R4 rjmp fpconv8m1 ; Wiederhole für das nächste Bit ; ; Runde die Zahl in R7:R6 mit dem Wert von Bit 7 von R5 ; fpconv8r: clr rmp ; Null nach rmp lsl R5 ; Rotiere Bit 7 ins Carry adc R6,rmp ; Addiere LSB mit Übertrag adc R7,rmp ; Addiere MSB mit Übertrag mov R2,R7 ; Kopiere den Wert nach R2:R1 (durch 256 teilen) mov R1,R6 ret ; ; Wandle das Wort in R2:R1 in einen ASCII-Text in R5:R6:R7:R8 ; ; +---+---+ ; + R2| R1| Eingangswert 0..500 ; +---+---+ ; +---+---+ ; | R4| R3| Dezimalteiler ; +---+---+ ; +---+---+---+---+ ; | R5| R6| R7| R8| Ergebnistext (für Eingangswert 500) ; |'5'|'.'|'0'|'0'| ; +---+---+---+---+ ; fpconv8a: clr R4 ; Setze Dezimalteiler auf 100 ldi rmp,100 mov R3,rmp rcall fpconv8d ; Hole ASCII-Ziffer durch wiederholtes Abziehen mov R5,rmp ; Setze Hunderter Zeichen ldi rmp,10 ; Setze Dezimalteiler auf 10 mov R3,rmp rcall fpconv8d ; Hole die nächste Ziffer mov R7,rmp ldi rmp,'0' ; Wandle Rest in ASCII-Ziffer um add rmp,R1 mov R8,rmp ; Setze Einer Zeichen ret ; ; Wandle Binärwort in R2:R1 in eine Dezimalziffer durch fortgesetztes ; Abziehen des Dezimalteilers in R4:R3 (100, 10) ; fpconv8d: ldi rmp,'0' ; Beginne mit ASCII-0 fpconv8d1: cp R1,R3 ; Vergleiche Wort mit Teiler cpc R2,R4 brcc fpconv8d2 ; Carry nicht gesetzt, subtrahiere Teiler ret ; fertig fpconv8d2: sub R1,R3 ; Subtrahiere Teilerwert sbc R2,R4 inc rmp ; Ziffer um eins erhöhen rjmp fpconv8d1 ; und noch einmal von vorne ; ; Ende der Fließkomma-Umwandlungsroutinen ; ; ; Ende des Umwandlungstestprogramms ; http://www.avr-asm-tutorial.net/avr_de/quellen/fp_conv8_de.asm1/20/2009 7:35:37 PM
Assembler Quelltext der Umwandlung einer 10Bit-Zahl in eine vierstellige Festkommazahl ; Demonstriert Fließkomma-Umwandlung in ; Assembler, (C)2003 www.avr-asm-tutorial.net ; ; Die Aufgabe: Ein 10-Bit-Analog-Wandler-Signal ; in Binärform wird eingelesen, die Zahlen ; reichen von hex 0000 bis 03FF. ; Diese Zahlen sind umzurechnen in eine ; Fließkommazahl zwischen 0,000 bis 5,000 ; Volt. ; ; Der Programmablauf: ; 1. Die Eingabezahl wird geprüft, ob sie ; kleiner als $0400 ist. ; Das vermeidet Überläufe bei der anschlie; ßenden Multiplikation. ; 2. Multiplikation mit 320.313 (hex 04E338). ; Dieser Schritt multipliziert die Zahl mit ; den Faktoren 5.000 und 65.536 und divi; diert mit 1.023 in einem Schritt. ; 3. Das Ergebnis wird gerundet und die letzten ; beiden Bytes abgeschnitten. ; Dieser Schritt dividiert durch 65.536, ; indem die beiden letzten Bytes des Ergeb; nisses ignoriert werden. Davor wird das ; Bit 15 des Ergebnisse abgefragt und zum ; Runden des Ergebnisses verwendet. ; 4. Die erhaltene Zahl wird in ASCII-Zeichen ; umgewandelt und mit einem Dezimalpunkt ; versehen. ; Das Ergebnis, eine Zahl zwischen 0 und ; 5.000 wird in ASCII-Zeichen umgewandelt. ; ; Die verwendeten Register: ; Die Routinen benutzen die Register R10 bis R1, ; ohne diese vorher zu sichern. Zusätzlich wird ; das Vielzweckregister rmp verwendet, das in ; der oberen Registerhälfte liegen muss. Bitte ; beachten, dass diese verwendeten Register ; nicht mit anderen Verwendungen in Konflikt ; kommen können. ; ; Zu Beginn wird die 10-Bit-Zahl im Register; paar R2:R1 erwartet. Wenn die Zahl größer ; ist als $03FF, dann kehrt die Prüfroutine ; mit einem gesetzten Carry-Flag zurück und ; der Ergebnistext in R5:R6:R7:R8:R9:R10 wird ; auf den null-terminierten Ausdruck "E.EEEE" ; gesetzt. ; Die Multiplikation verwendet R6:R5:R4:R3 zur ; Speicherung des Multiplikators 320.313 (der ; bei der Berechnung maximal 10 mal links ge; schoben wird - Multiplikation mit 2). Das ; Ergebnis der Multiplikation wird in den Re; gistern R10:R9:R8:R7 berechnet. ; Das Ergebnis der sogenannten Division durch ; 65.536 durch Ignorieren von R8:R7 des Ergeb; nisses ist in R10:R9 gespeichert. Abhängig ; von Bit 7 in R8 wird zum Registerpaar R10:R9 ; eine Eins addiert. Das Ergebnis in R10:R9 ; wird in das Registerpaar R2:R1 kopiert. ; Die Umwandlung der Binärzahl in R2:R1 in ; eine ASCII-Zeichenfolge verwendet das Paar ; R4:R3 als Divisor für die Umwandlung. Das ; Ergebnis, der ASCII-String, ist in den Re; gistern R5:R6:R7:R8:R9:R10 (Null-terminiert) ; abgelegt. ; ; Weitere Bedingungen: ; Die Umwandlung benutzt Unterprogramme, daher ; muss der Stapel funktionieren. Es werden ; maximal drei Ebenen Unterprogrammaufrufe ; verschachtelt (benötigt sechs Byte SRAM). ; ; Umwandlungszeiten: ; Die gesamte Umwandlung benötigt 326 Taktzyklen ; maximal (Umwandlung von $03FF) bzw. 111 Takt; zyklen (Umwandlung von $0000). Bei 4 MHz Takt ; entspricht dies 81,25 Mikrosekunden bzw. 27,5 ; Mikrosekunden. ; ; Definitionen: ; Register .DEF rmp = R16 ; als Vielzweckregister verwendet ; ; AVR-Typ ; Getestet für den Typ AT90S8515, die Angabe ; wird nur für den Stapel benötigt. Die Routinen ; arbeiten auf anderen AT90S-Prozessoren genauso ; gut. .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Start des Testprogramms ; ; Schreibt eine Zahl in R2:R1 und startet die Wand; lungsroutine, nur für Testzwecke. ; .CSEG .ORG $0000 rjmp main ; main: ldi rmp,HIGH(RAMEND) ; Richte den Stapel ein out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,$03 ; Wandle $03FF um mov R2,rmp ldi rmp,$FF mov R1,rmp rcall fpconv10 ; Rufe die Umwandlungsroutine no_end: ; unendliche Schleife, wenn fertig rjmp no_end ; ; Ablaufssteuerung der Umwandlungsroutine, ruft die ; verschiedenen Teilschritte auf ; fpconv10: rcall fpconv10c ; Prüfe die Eingabezahl in R2:R1 brcs fpconv10e ; Wenn Carry, dann Ergebnis="E.EEE" rcall fpconv10m ; Multipliziere mit 320.313 rcall fpconv10r ; Runden und Division mit 65536 rcall fpconv10a ; Umwandlung in ASCII-String rjmp fpconv10f ; Setze Dezimalpunkt und Nullabschluss fpconv10e: ldi rmp,'E' ; Fehlermeldung in Ergebnistext mov R5,rmp mov R7,rmp mov R8,rmp mov R9, rmp fpconv10f: ldi rmp,'.' ; Setze Dezimalpunkt mov R6,rmp clr rmp ; Null-Abschluss setzen mov R10,rmp ret ; Alles fertig ; ; Unterprogramm Eingabeprüfung ; fpconv10c: ldi rmp,$03 ; Vergleiche MSB mit 03 cp rmp,R2 ; wenn R2>$03, setze carry bei Rückkehr ret ; ; Unterprogramm Multiplikation mit 320.313 ; ; Startbedingung: ; +---+---+ ; | R2+ R1| Eingabezahl ; +---+---+ ; +---+---+---+---+ ; | R6| R5| R4| R3| Multiplikant 320.313 = $00 04 E3 38 ; | 00| 04| E3| 38| ; +---+---+---+---+ ; +---+---+---+---+ ; |R10| R9| R8| R7| Resultat ; | 00| 00| 00| 00| ; +---+---+---+---+ ; fpconv10m: clr R6 ; Setze den Multiplikant auf 320.313 ldi rmp,$04 mov R5,rmp ldi rmp,$E3 mov R4,rmp ldi rmp,$38 mov R3,rmp clr R10 ; leere Ergebnisregister clr R9 clr R8 clr R7 fpconv10m1: mov rmp,R1 ; Prüfe ob noch Bits zu multiplizieren or rmp,R2 ; Irgendein Bit Eins? brne fpconv10m2 ; Noch Einsen, mach weiter ret ; fertig, kehre zurück fpconv10m2: lsr R2 ; Schiebe MSB nach rechts (teilen durch 2) ror R1 ; Rotiere LSB rechts und setze Bit 7 brcc fpconv10m3 ; Wenn das niedrigste Bit eine 0 war, ; dann überspringe den Additionsschritt add R7,R3 ; Addiere die Zahl in R6:R5:R4:R3 zum Ergebnis adc R8,R4 adc R9,R5 adc R10,R6 fpconv10m3: lsl R3 ; Multipliziere R6:R5:R4:R3 mit 2 rol R4 rol R5 rol R6 rjmp fpconv10m1 ; Wiederhole für das nächste Bit ; ; Runde die Zahl in R10:R9 mit dem Wert von Bit 7 von R8 ; fpconv10r: clr rmp ; Null nach rmp lsl R8 ; Rotiere Bit 7 ins Carry adc R9,rmp ; Addiere LSB mit Übertrag adc R10,rmp ; Addiere MSB mit Übertrag mov R2,R10 ; Kopiere den Wert nach R2:R1 (durch 65536 teilen) mov R1,R9 ret ; ; Wandle das Wort in R2:R1 in einen ASCII-Text in R5:R6:R7:R8:R9:R10 ; ; +---+---+ ; + R2| R1| Eingangswert 0..5.000 ; +---+---+ ; +---+---+ ; | R4| R3| Dezimalteiler ; +---+---+ ; +---+---+---+---+---+---+ ; | R5| R6| R7| R8| R9|R10| Ergebnistext (für Einmgangswert 5,000) ; |'5'|'.'|'0'|'0'|'0'|$00| mit Null-Abschluss ; +---+---+---+---+---+---+ ; fpconv10a: ldi rmp,HIGH(1000) ; Setze Dezimalteiler auf 1.000 mov R4,rmp ldi rmp,LOW(1000) mov R3,rmp rcall fpconv10d ; Hole ASCII-Ziffer durch wiederholtes Abziehen mov R5,rmp ; Setze Tausender Zeichen clr R4 ; Setze Dezimalteiler auf 100 ldi rmp,100 mov R3,rmp rcall fpconv10d ; Hole die nächste Ziffer mov R7,rmp ; Setze Hunderter Zeichen ldi rmp,10 ; Setze Dezimalteiler auf 10 mov R3,rmp rcall fpconv10d ; Hole die nächste Ziffer mov R8,rmp ; Setze Zehner Zeichen ldi rmp,'0' ; Wandle Rest in ASCII-Ziffer um add rmp,R1 mov R9,rmp ; Setze Einer Zeichen ret ; ; Wandle Binärwort in R2:R1 in eine Dezimalziffer durch fortgesetztes ; Abziehen des Dezimalteilers in R4:R3 (1000, 100, 10) ; fpconv10d: ldi rmp,'0' ; Beginne mit ASCII-0 fpconv10d1: cp R1,R3 ; Vergleiche Wort mit Teiler cpc R2,R4 brcc fpconv10d2 ; Carry nicht gesetzt, subtrahiere Teiler ret ; fertig fpconv10d2: sub R1,R3 ; Subtrahiere Teilerwert sbc R2,R4 inc rmp ; Ziffer um eins erhöhen rjmp fpconv10d1 ; und noch einmal von vorne ; ; Ende der Fließkomma-Umwandlungsroutinen ; ; ; Ende des Umwandlungstestprogramms ;
; Demonstriert Fließkomma-Umwandlung in ; Assembler, (C)2003 www.avr-asm-tutorial.net ; ; Die Aufgabe: Ein 10-Bit-Analog-Wandler-Signal ; in Binärform wird eingelesen, die Zahlen ; reichen von hex 0000 bis 03FF. ; Diese Zahlen sind umzurechnen in eine ; Fließkommazahl zwischen 0,000 bis 5,000 ; Volt. ; ; Der Programmablauf: ; 1. Die Eingabezahl wird geprüft, ob sie ; kleiner als $0400 ist. ; Das vermeidet Überläufe bei der anschlie; ßenden Multiplikation. ; 2. Multiplikation mit 320.313 (hex 04E338). ; Dieser Schritt multipliziert die Zahl mit ; den Faktoren 5.000 und 65.536 und divi; diert mit 1.023 in einem Schritt. ; 3. Das Ergebnis wird gerundet und die letzten ; beiden Bytes abgeschnitten. ; Dieser Schritt dividiert durch 65.536, ; indem die beiden letzten Bytes des Ergeb; nisses ignoriert werden. Davor wird das ; Bit 15 des Ergebnisse abgefragt und zum ; Runden des Ergebnisses verwendet. ; 4. Die erhaltene Zahl wird in ASCII-Zeichen ; umgewandelt und mit einem Dezimalpunkt ; versehen. ; Das Ergebnis, eine Zahl zwischen 0 und ; 5.000 wird in ASCII-Zeichen umgewandelt. ; ; Die verwendeten Register: ; Die Routinen benutzen die Register R10 bis R1, ; ohne diese vorher zu sichern. Zusätzlich wird ; das Vielzweckregister rmp verwendet, das in ; der oberen Registerhälfte liegen muss. Bitte ; beachten, dass diese verwendeten Register ; nicht mit anderen Verwendungen in Konflikt ; kommen können. ; ; Zu Beginn wird die 10-Bit-Zahl im Register; paar R2:R1 erwartet. Wenn die Zahl größer ; ist als $03FF, dann kehrt die Prüfroutine ; mit einem gesetzten Carry-Flag zurück und ; der Ergebnistext in R5:R6:R7:R8:R9:R10 wird ; auf den null-terminierten Ausdruck "E.EEEE" ; gesetzt. ; Die Multiplikation verwendet R6:R5:R4:R3 zur ; Speicherung des Multiplikators 320.313 (der ; bei der Berechnung maximal 10 mal links ge; schoben wird - Multiplikation mit 2). Das ; Ergebnis der Multiplikation wird in den Re; gistern R10:R9:R8:R7 berechnet. ; Das Ergebnis der sogenannten Division durch ; 65.536 durch Ignorieren von R8:R7 des Ergeb; nisses ist in R10:R9 gespeichert. Abhängig ; von Bit 7 in R8 wird zum Registerpaar R10:R9 ; eine Eins addiert. Das Ergebnis in R10:R9 ; wird in das Registerpaar R2:R1 kopiert. ; Die Umwandlung der Binärzahl in R2:R1 in ; eine ASCII-Zeichenfolge verwendet das Paar ; R4:R3 als Divisor für die Umwandlung. Das ; Ergebnis, der ASCII-String, ist in den Re; gistern R5:R6:R7:R8:R9:R10 (Null-terminiert) ; abgelegt. ; ; Weitere Bedingungen: ; Die Umwandlung benutzt Unterprogramme, daher ; muss der Stapel funktionieren. Es werden ; maximal drei Ebenen Unterprogrammaufrufe ; verschachtelt (benötigt sechs Byte SRAM). ; ; Umwandlungszeiten: ; Die gesamte Umwandlung benötigt 326 Taktzyklen ; maximal (Umwandlung von $03FF) bzw. 111 Takt; zyklen (Umwandlung von $0000). Bei 4 MHz Takt ; entspricht dies 81,25 Mikrosekunden bzw. 27,5 ; Mikrosekunden. ; ; Definitionen: ; Register .DEF rmp = R16 ; als Vielzweckregister verwendet ; ; AVR-Typ ; Getestet für den Typ AT90S8515, die Angabe ; wird nur für den Stapel benötigt. Die Routinen ; arbeiten auf anderen AT90S-Prozessoren genauso ; gut. .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Start des Testprogramms ; ; Schreibt eine Zahl in R2:R1 und startet die Wand; lungsroutine, nur für Testzwecke. ; .CSEG .ORG $0000 rjmp main ; main: ldi rmp,HIGH(RAMEND) ; Richte den Stapel ein out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,$03 ; Wandle $03FF um mov R2,rmp ldi rmp,$FF mov R1,rmp rcall fpconv10 ; Rufe die Umwandlungsroutine no_end: ; unendliche Schleife, wenn fertig rjmp no_end ; ; Ablaufssteuerung der Umwandlungsroutine, ruft die ; verschiedenen Teilschritte auf ; fpconv10: rcall fpconv10c ; Prüfe die Eingabezahl in R2:R1 brcs fpconv10e ; Wenn Carry, dann Ergebnis="E.EEE" rcall fpconv10m ; Multipliziere mit 320.313 rcall fpconv10r ; Runden und Division mit 65536 rcall fpconv10a ; Umwandlung in ASCII-String rjmp fpconv10f ; Setze Dezimalpunkt und Nullabschluss fpconv10e: ldi rmp,'E' ; Fehlermeldung in Ergebnistext mov R5,rmp mov R7,rmp mov R8,rmp mov R9, rmp fpconv10f: ldi rmp,'.' ; Setze Dezimalpunkt mov R6,rmp clr rmp ; Null-Abschluss setzen mov R10,rmp ret ; Alles fertig ; ; Unterprogramm Eingabeprüfung ; fpconv10c: ldi rmp,$03 ; Vergleiche MSB mit 03 cp rmp,R2 ; wenn R2>$03, setze carry bei Rückkehr ret ; ; Unterprogramm Multiplikation mit 320.313 ; ; Startbedingung: ; +---+---+ ; | R2+ R1| Eingabezahl ; +---+---+ ; +---+---+---+---+ ; | R6| R5| R4| R3| Multiplikant 320.313 = $00 04 E3 38 ; | 00| 04| E3| 38| ; +---+---+---+---+ ; +---+---+---+---+ ; |R10| R9| R8| R7| Resultat ; | 00| 00| 00| 00| ; +---+---+---+---+ ; fpconv10m: clr R6 ; Setze den Multiplikant auf 320.313 ldi rmp,$04 mov R5,rmp ldi rmp,$E3 mov R4,rmp ldi rmp,$38 mov R3,rmp clr R10 ; leere Ergebnisregister clr R9 clr R8 clr R7 fpconv10m1: mov rmp,R1 ; Prüfe ob noch Bits zu multiplizieren or rmp,R2 ; Irgendein Bit Eins? brne fpconv10m2 ; Noch Einsen, mach weiter ret ; fertig, kehre zurück fpconv10m2: lsr R2 ; Schiebe MSB nach rechts (teilen durch 2) ror R1 ; Rotiere LSB rechts und setze Bit 7 brcc fpconv10m3 ; Wenn das niedrigste Bit eine 0 war, ; dann überspringe den Additionsschritt add R7,R3 ; Addiere die Zahl in R6:R5:R4:R3 zum Ergebnis adc R8,R4 adc R9,R5 adc R10,R6 fpconv10m3: lsl R3 ; Multipliziere R6:R5:R4:R3 mit 2 rol R4 rol R5 rol R6 rjmp fpconv10m1 ; Wiederhole für das nächste Bit ; ; Runde die Zahl in R10:R9 mit dem Wert von Bit 7 von R8 ; fpconv10r: clr rmp ; Null nach rmp lsl R8 ; Rotiere Bit 7 ins Carry adc R9,rmp ; Addiere LSB mit Übertrag adc R10,rmp ; Addiere MSB mit Übertrag mov R2,R10 ; Kopiere den Wert nach R2:R1 (durch 65536 teilen) mov R1,R9 ret ; ; Wandle das Wort in R2:R1 in einen ASCII-Text in R5:R6:R7:R8:R9:R10 ; ; +---+---+ ; + R2| R1| Eingangswert 0..5.000 ; +---+---+ ; +---+---+ ; | R4| R3| Dezimalteiler ; +---+---+ ; +---+---+---+---+---+---+ ; | R5| R6| R7| R8| R9|R10| Ergebnistext (für Einmgangswert 5,000) ; |'5'|'.'|'0'|'0'|'0'|$00| mit Null-Abschluss ; +---+---+---+---+---+---+ ; fpconv10a: ldi rmp,HIGH(1000) ; Setze Dezimalteiler auf 1.000 mov R4,rmp ldi rmp,LOW(1000) mov R3,rmp rcall fpconv10d ; Hole ASCII-Ziffer durch wiederholtes Abziehen mov R5,rmp ; Setze Tausender Zeichen clr R4 ; Setze Dezimalteiler auf 100 ldi rmp,100 mov R3,rmp rcall fpconv10d ; Hole die nächste Ziffer mov R7,rmp ; Setze Hunderter Zeichen ldi rmp,10 ; Setze Dezimalteiler auf 10 mov R3,rmp rcall fpconv10d ; Hole die nächste Ziffer mov R8,rmp ; Setze Zehner Zeichen ldi rmp,'0' ; Wandle Rest in ASCII-Ziffer um add rmp,R1 mov R9,rmp ; Setze Einer Zeichen ret ; ; Wandle Binärwort in R2:R1 in eine Dezimalziffer durch fortgesetztes ; Abziehen des Dezimalteilers in R4:R3 (1000, 100, 10) ; fpconv10d: ldi rmp,'0' ; Beginne mit ASCII-0 fpconv10d1: cp R1,R3 ; Vergleiche Wort mit Teiler cpc R2,R4 brcc fpconv10d2 ; Carry nicht gesetzt, subtrahiere Teiler ret ; fertig fpconv10d2: sub R1,R3 ; Subtrahiere Teilerwert sbc R2,R4 inc rmp ; Ziffer um eins erhöhen rjmp fpconv10d1 ; und noch einmal von vorne ; ; Ende der Fließkomma-Umwandlungsroutinen ; ; ; Ende des Umwandlungstestprogramms ; http://www.avr-asm-tutorial.net/avr_de/quellen/fp_conv10_de.asm1/20/2009 7:35:40 PM
Hardware Multiplikation in AVR Assembler
Path: Home => AVR-Überblick => Binäre Berechnungen => Hardware Multiplikation
Binäre Hardware Multiplikation in AVR Assembler Alle ATmega, AT90CAN and AT90PWM haben einen Multiplikator an Bord, der 8- mal 8-Bit-Multiplikationen in nur zwei Taktzyklen durchführt. Wenn also Multiplikationen durchgeführt werden müssen und man sicher ist, dass die Software niemals in einem AT90S- oder ATtiny-Prozessor laufen werden muss, sollte man diese schnelle HardwareMöglichkeit nutzen. Diese Seite zeigt, wie man das macht. Es gibt hier folgende Abteilungen: 1. 2. 3. 4.
Hardware Multiplikation von 8-mal-8-Bit Binärzahlen Diese Aufgabe ist einfach und direkt. Wenn die zwei Binärzahlen in den Registern R16 und R17 stehen, dann muss man nur folgende Instruktion eingeben: mul R16,R17 Weil das Ergebnis der Multiplikation bis zu 16 Bit lange Zahlen ergibt, ist das Ergebnis in den Registern R1 (höherwertiges Byte) und R0 (niedrigerwertiges Byte) untergebracht. Das war es auch schon. Das Programm demonstriert die Simulation im Studio. Es multipliziert dezimal 250 (hex FA) mit dezimal 100 (hex 64) in den Registern R16 und R17.
Die Register R0 (LSB) und R1 (MSB) enthalten das Ergebnis hex 61A8 oder dezimal 25,000.
Und: ja, die Multiplikation braucht bloß zwei Zyklen, oder 2 Mikrosekunden bei einem Takt von einem MHz.
Zum Seitenanfang
Hardware Multiplikation von 16- mit 8-Bit-Zahl Sind größere Zahlen zu multiplizieren? Die Hardware ist auf 8 Bit beschränkt, also müssen wir ersatzweise einige geniale Ideen anwenden. Das ist an diesem Beispiel 16 mal 8 gezeigt. Verstehen des Konzepts hilft bei der Anwendung der Methode und es ist ein leichtes, das später auf die 32- mit 64-Bit-Multiplikation zu übertragen. Zuerst die Mathematik: eine 16-Bit-Zahl lässt sich in zwei 8-Bit-Zahlen auftrennen, wobei die höherwertige der beiden Zahlen mit dezimal 256 oder hex 100 mal genommen wird. Für die, die eine Erinnerung brauchen: die Dezimalzahl 1234 kann man auch als die Summe aus (12*100) und 34 betrachten, oder auch als die Summe aus (1*1000), (2*100), (2*10) und 4. Die 16-Bit-Binärzahl m1 ist also gleich 256*m1M plus m1L, wobei m1M das MSB und m1L das LSB ist. Multiplikation dieser Zahl mit der 8-Bit-Zahl m2 ist also mathematisch ausgedrückt: m1 * m2 = (256*m1M + m1L) * m2 = 256*m1M*m2 + m1L*m2 Wir müssen also lediglich zwei Multiplikationen durchführen und die Ergebnisse addieren. Zwei Multiplikationen? Es sind doch drei * zu sehen! Für die Multiplikation mit 256 braucht man in der Binärwelt keinen HardwareMultiplikator, weil es ausreicht, die Zahl einfach um ein Byte nach links zu rücken. Genauso wie in der Dezimalwelt eine Multiplikation mit 10 einfach ein Linksrücken um eine Stelle ist und die frei werdende leere Ziffer mit Null gefüllt wird. Beginnen wir mit einem praktischen Beispiel. Zuerst brauchen wir einige Register, um 1. die Zahlen m1 und m2 zu laden, 2. für das Ergebnis Raum zu haben, das bis zu 24 Bit lang werden kann.
; ; Testet Hardware Multiplikation 16-mit-8-Bit ; ; Register Definitionen: ; .def Res1 = R2 ; Byte 1 des Ergebnisses .def Res2 = R3 ; Byte 2 des Ergebnisses .def Res3 = R4 ; Byte 3 des Ergebnisses .def m1L = R16 ; LSB der Zahl m1 .def m1M = R17 ; MSB der Zahl m1 .def m2 = R18 ; die Zahl m2
Zuerst werden die Zahlen in die Register geladen:
; ; Lade Register ; .equ m1 = 10000 ; ldi m1M,HIGH(m1) ; obere 8 Bits von m1 in m1M ldi m1L,LOW(m1) ; niedrigere 8 Bits von m1 in m1L ldi m2,250 ; 8-Bit Konstante in m2
Die beiden Zahlen sind in R17:R16 (dez 10000 = hex 2710) und R18 (dez 250 = hex FA) geladen.
Dann multiplizieren wir zuerst das niedrigerwertige Byte:
; ; Multiplikation ; mul m1L,m2 ; Multipliziere LSB mov Res1,R0 ; kopiere Ergebnis in Ergebnisregister mov Res2,R1
Die LSB-Multiplikation von hex 27 mit hex FA ergibt hex 0F0A, das in die Register R0 (LSB, hex A0) und R1 (MSB, hex 0F) geschrieben wurde. Das Ergebnis wird in die beiden untersten Bytes der Ergebnisregister, R3:R2, kopiert.
Nun folgt die Multiplikation des MSB mit m2:
mul m1M,m2 ; Multiplizere MSB
Die Multiplikation des MSB von m1, hex 10, mit m2, hex FA, ergibt hex 2616 in R1:R0.
Nun werden zwei Schritte auf einmal gemacht: die Multiplikation mit 256 und die Addition des Ergebnisses zum bisherigen Ergebnis. Das wird erledigt durch Addition von R1:R0 zu Res3:Res2 anstelle von Res2:Res1. R1 wird zunächst schlicht nach Res3 kopiert. Dann wird R0 zu Res2 addiert. Falls dabei das Übertragsflag Carry nach der Addition gesetzt ist, muss noch das nächsthöre Byte Res3 um Eins erhöht werden.
mov Res3,R1 ; Kopiere MSB des Ergebnisses zum Ergebnis-Byte 3 add Res2,R0 ; Addiere LSB des Ergebnisses zum Ergebnis-Byte 2 brcc NoInc ; Wenn Carry nicht gesetzt, springe inc Res3 ; erhoehe Ergebnis-Byte 3 NoInc:
Das Ergebnis in R4:R3:R2 ist hex 2625A9, was dezimal 2500000 entspricht (wie jeder sofort weiß), und das ist korrekt.
Der Zykluszähler zeigt nach der Multiplikation auf 10, bei 1 MHz Takt sind gerade mal 10 Mikrosekunden vergangen. Sehr viel schneller als die Software-Multiplikation!
Zum Seitenanfang
Hardware Multiplikation einer 16- mit einer 16-bit-Binärzahl Nun, da wir das Prinzip verstanden haben, sollte es einfach sein die 16-mal-16Multiplikation zu erledigen. Das Ergebnis benötigt jetzt vier Bytes (Res4:Res3:Res2: Res1, untergebracht in R5:R4:R3:R2). Die Formel lautet: m1 * m2 = (256*m1M + m1L) * (256*m2M + m2L) = 65536*m1M*m2M + 256*m1M*m2L + 256*m1L*m2M + m1L*m2L Offensichtlich sind jetzt vier Multiplikationen zu erledigen. Wir beginnen mit den beiden einfachsten, der ersten und der letzten: ihre Ergebnisse können einfach in die Ergebnisregister kopiert werden. Die beiden mittleren Multiplikationen in der Formel müssen zu den beiden mittleren Ergebnisregistern addiert werden. Mögliche Überläufe müssen in das Ergebnisregister Res4 übertragen werden, wofür hier ein einfacher Trick angewendet wird, der einfach zu verstehen sein dürfte. Die Software:
Die Simulation im Studio zeigt die folgenden Schritte. Das Laden der beiden Konstanten 10000 (hex 2710) und 25000 (hex 61A8) in die Register im oberen Registerraum ...
Multiplikation der beiden MSBs (hex 27 und 61) und kopieren des Ergebnisses in R1:R0 in die beiden oberen Ergebnisregister R5:R4 (Multiplikation mit 65536 eingeschlossen) ...
Multiplikation der beiden LSBs (hex 10 und A8) und kopieren des Ergebnisses in R1:R0 in die beiden niedrigeren Ergebnisregister R3:R2 ...
Multiplikation des MSB von m1 mit dem LSB von m2, und Addition des Ergebnisses in R1:R0 zu den beiden mittleren Ergebnisregistern, kein Übertrag ist erfolgt ...
Multiplikation des LSB von m1 mit dem MSB von m2, sowie Addition des Ergebnisses in R1:R0 mit den beiden mittleren Ergebnisbytes, kein Übertrag. Das Ergebnis ist hex 0EE6B280, was dezimal 250000000 ist und offenbar korrekt ...
Die Multiplikation hat 19 Taktzyklen gebraucht, das ist sehr viel schneller als die Software-Multiplikation. Ein weiterer Vorteil: die benötigte Zeit ist IMMER genau 19 Taktzyklen lang, nicht abhängig von den Zahlenwerten (wie es bei der Software-Multiplikation der Fall ist) und vom Auftreten von Überläufen (deshalb der Trick mit der Addition von Null mit Carry). Darauf kann man sich verlassen ...
Zum Seitenanfang
4. Hardware Multiplikation einer 16- mit einer 24-Bit-Binärzahl Die
Multiplikation einer 16-Bit-Binärzahl "a" mit einer 24-Bit-Binärzahl "b" führt im Ergebnis zu einem maximal 40 Bit breiten Ergebnis. Das Multiplizier-Schema erfordert sechs 8-mit-8-Bit-Multiplikationen und das Addieren der Ergebnisse an der richtigen Position zum Gesamtergebnis. Der Assembler-Code dafür:
Tutorial für das Erlernen der Assemblersprache von AVR-Einchip-Prozessoren AT90Sxxxx von ATMEL anhand geeigneter praktischer Beispiele.
Einfache Einführungsbeispiele, Tutorial
(Die Links auf die *.asm-Dateien zum Herunterladen mit gedrückter Shift-Taste anklicken.) HTML- ASMFormat Format Test1
Test2
Test3
Test4
Erläuterung zum Inhalt
Test1
Umgang mit dem Board kennenlernen, Ausgabe über den Port an die angeschlossenen Leuchtdioden, Grundstruktur von Assembler, -direktiven und befehlen kennenlernen. Die Lampen blinken mit 800 Hz Takt.
Test2
Eingabe von einem Port lesen, das Aufrufen von Unterprogrammen, Stack(Stapel-Befehle), Binäre Rechen- Operationen (AND, OR, ROL, etc.), Bedingte Verzweigungen (SBIx, BRxx). Das Drücken der Schalter 0 und 1 soll die Lampen 0 und 1 anmachen, das Drücken eines der Schalter 2 bis 6 die anderen Lampen. Das Drücken von Schalter 7 macht alle Lampen aus.
Test3
Timer im Polling modus ansteuern, MOV-Befehl. Ein Loop im Hauptprogramm fragt den Hardware-Zähler ab, bis dieser Null erreicht, dann wird der Softwarezähler um Eins erhöht und auf den LEDs ausgegeben.
Test4
Timer im Interrupt modus, Interrupts, Interrupt-Vektoren, BCD-Arithmetik. Hauptprogramm-Loop fragt oberes Byte des Software-Zählers ab, bis dieser hex 3D erreicht hat. Dann den Timer, bis dieser 09 erreicht hat (eine Sekunde = dez 15625 = hex 3D09 Zählerimpulse). Die Zähler werden auf Null gesetzt und eine Sekunde weitergezählt. Die Sekunden werden als gepackte BCD-Zahl behandelt (eine Ziffer zu vier Bit, 1 Byte entspricht zwei Ziffern), Die Sekunden werden bei Erreichen von 60 wieder auf Null gesetzt. Der Sekundenstand wird auf den LEDs ausgegeben.
; Test 1: Board kennenlernen, Ausgabe an die Leuchtdioden. ; Was es hier zu lernen gibt: ; - Einen Ausgabe-Port ansteuern (Port B mit den LEDs) ; - Aus welchen Teilen ein Programm besteht.
; Von mir verwendete Konventionen: ; - Gross geschriebene Wörter kennzeichnen vordefinierte Befehlsworte ; oder mit dem Prozessortyp vordefinierte Ports. ; - Klein geschriebene Worte sind von mir definiert und willkürlich ; gewählt.
; Prozessor definieren ; .NOLIST .INCLUDE "8515def.inc" .LIST ; Die Befehle NOLIST und LIST schalten das Auflisten der INCLUDE-Datei ; in der Datei TEST1.LST aus.
; Register definieren ; ; Dieses Register wird für die Zwischenspeicherung der Zahlen ; verwendet. Mit dem Befehl .DEF kriegt eins der 32 8-Bit-Register ; einen Namen (hier: mp), den man sich einfacher merken kann als ; R16. Ausserdem kann man einfacher Register verlegen, wenn man sie ; über einen Namen anspricht. .DEF mp = R16
; Test 1: Board kennenlernen, Ausgabe an die Leuchtdioden. ; Was es hier zu lernen gibt: ; - Einen Ausgabe-Port ansteuern (Port B mit den LEDs) ; - Aus welchen Teilen ein Programm besteht. ; Von mir verwendete Konventionen: ; - Gross geschriebene Wörter kennzeichnen vordefinierte Befehlsworte ; oder mit dem Prozessortyp vordefinierte Ports. ; - Klein geschriebene Worte sind von mir definiert und willkürlich ; gewählt. ; Prozessor definieren ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; Die Befehle NOLIST und LIST schalten das Auflisten der INCLUDE-Datei ; in der Datei TEST1.LST aus. ; Register definieren ; ; Dieses Register wird für die Zwischenspeicherung der Zahlen ; verwendet. Mit dem Befehl .DEF kriegt eins der 32 8-Bit-Register ; einen Namen (hier: mp), den man sich einfacher merken kann als ; R16. Ausserdem kann man einfacher Register verlegen, wenn man sie ; über einen Namen anspricht. .DEF mp = R16 ; Restart ; ; Bis jetzt war alles Vorgeplänkel. Hier beginnt nun das Programm. ; mit dem Reset- und Interrupt-Sprung-Teil. Dieser Teil ; produziert die ersten Programm-Bytes an der Adresse 0000 und ; folgende. ; Vorerst besteht dieser Teil nur aus einem Sprungbefehl in das ; Hauptprogramm. Dieser Befehl wird bei jedem Neustart des Pro; zessors ausgeführt. Ein Neustart wird bei Power-On, bei einem ; Hardware-Reset am Reset-Pin oder durch den Watchdog-Timer ; ausgelöst. Der Watchdog ist in diesem Programm nicht aktiv. ; In allen Fällen wird ein Sprung zum Programm mit dem Namen ; "main" ausgeführt. ; RJMP heisst "Relative Jump" oder relativer Sprung. Bei dem rela; tiven Sprung wird in dem Befehlswort eine relative Distanz mit ; angegeben, die der Assembler aus der Differenz zwischen der Adresse ; des Ziels (main) und der aktuellen Adresse ausrechnet. Die Sprung; Distanz darf 2 kB vorwärts oder rückwärts nicht überschreiten und ; es gibt Fehlermeldungen. rjmp main ; Hier beginnt das Hauptprogramm mit dem Namen "main". Als erstes ; muss deshalb ein sogenanntes Label gesetzt werden. Ein Label ist ; ein frei definierter Name, gefolgt von einem Doppelpunkt. Zur ; besseren Übersicht beginnen alle Label in Spalte 1 einer Zeile, ; alle Programmbefehle jedoch mit einem oder mehr Leerzeichen oder TAB. ; Hinter dem Label kann schon ein Programmbefehl stehen, getrennt ; mittels Leerzeichen oder TAB. Habe ich aber hier nicht gemacht. main: ; Als erstes muss der Port B, an den die LEDs angeschlossen sind, als ; Ausgang definiert werden. Dies macht man, indem man acht Einsen in ; das Datenrichtungs-Register des Ports B schreibt. Das Datenrichtungs; Register von Port B heisst DDRB (Data Direction Register B). Das er; fordert zwei Schritte: Erstens wird der Binärwert 1111.1111 in ein ; Register geschrieben: ldi mp,0b11111111 ; Der Befehl LDI (LoaD Immediate) lädt einen 8-Bit-Wert in das Register ; mp. Dieser Befehl ist nur für die Register R16 bis R31 zulässig, des; halb ist mp am Programmanfang als Register R16 definiert worden. Die ; Befehle mit zwei Parametern sind durchgängig so aufgebaut, dass der ; erste Parameter (Register mp) immer das Ziel angibt, in dem das Ergeb; nis gespeichert wird. Nach der Durchführung dieses Befehls enthält ; das Register 16 den Binärwert 11111111, hexadezimal FF oder dezimal ; 255. ; Die Schreibweise "0b..." kennzeichnet immer eine Binärzahl, mit "0x..." ; wird eine Hexadezimalzahl angegeben. Die führende Null lässt den ; Assembler eine gültige Zahl erwarten. Zahlen ohne diesen Vorsatz sind ; automatisch dezimal ( LDI mp,255 wäre gleichwertig). ; Dieser Wert muss jetzt in das Datenrichtungs-Register gegeben ; werden, damit der Port B in ganzer Breite zu Ausgängen wird. Eine 1 ; im Datenrichtungsregister macht den zugehörigen Pin zum Ausgang, eine ; 0 zum Eingang. out DDRB,mp ; Der Befehl OUT schreibt Registerinhalte (hier: mp oder R16) auf einen ; Port (hier DDRB). DDRB ist in der Datei "8515def.inc" definiert, die ; über den .DEVICE-Befehl oder wie hier über den .INCLUDE-Befehl in den ; Assembler-Text eingebunden ist. Dadurch muss man sich die Portnummer ; des Datenrichtungsregisters nicht merken. ; Dieser Programmteil gibt nun abwechselnd Nullen und Einsen auf die ; Portausgänge aus. Die LEDs leuchten in einem sehr schnellen Takt. ; Da dieser Programmteil unendlich lang wiederholt ist, kriegt er das ; Label loop, da nach dem Klappern der Bits wieder dorthin gesprungen ; werden muss. loop: ldi mp,0x00 out PORTB,mp ; LDI lädt erst mal acht Bits Nullen in das Universalregister mp. ; OUT gibt diese Nullen auf dem Port B aus. Diesmal kommen sie in das ; Datenausgaberegister von Port B (PORTB). ; Die Nullen lassen die LEDs leuchten, da sie über 1 k an die Versor; gungsspannung geführt sind (0=an, 1=aus). ldi mp,0xFF out PORTB,mp ; Anschliessend kommen acht Einsen in das Register und von da aus in ; den Datenausgabe-Port. Das macht die Lämpchen wieder aus. rjmp loop ; Mit diesem relativen Sprung geht es wieder zurück an den Anfang der ; Schleife, und das Ganze dauert ewiglich. ; Bei 4 MHz Quarzfrequenz dauert jeder LDI- und OUT-Befehl in der Schleife ; 250 ns, der RJMP braucht zwei Takte und dauert 500 ns. Macht zusammen ; 1500 ns, die LEDs werden folglich mit 667 kHz angesteuert. ; ; Nach dem Assemblieren sollte das Programm acht Worte haben. In der ; Datei TEST1.LST kann man sich das Ergebnis der Assemblierung an; schauen. ; Dieses war der erste Streich ... http://www.avr-asm-tutorial.net/avr_de/quellen/test1.asm1/20/2009 7:36:04 PM
AVR-Tutorium, Teil 2
Pfad: Home => AVR-Übersicht => Tutorium => Teil 2
; Test 2: Board kennenlernen: Eingabe von einem Port ; Was man hier dazu lernen kann: ; - Eingabe von einem Port lesen ; - Das Aufrufen von Unterprogrammen, Stack- (Stapel-Befehle) ; - Binäre Rechen Operationen (AND, OR, ROL, etc.) ; - Bedingte Verzweigungen (SBIx, BRxx) .NOLIST .INCLUDE "8515def.inc" .LIST
; Ein Universalregister definieren: .DEF mp = R16
; Der Reset-Sprungbefehl an Adresse 0: RJMP main
; Hier beginnt das Hauptprogramm main: OUT LDI OUT
; Diese Befehle richten einen Stack im SRAM ein. Ein Stack wird immer ; dann gebraucht, wenn Unterprogramme oder Interrupts aufgerufen werden. ; Beim Aufruf muss der aktuelle Zählerstand der Bearbeitung in einem ; Speicher abgelegt werden, damit nach Beendigung wieder an die auf; rufende Stelle zurückgekehrt werden kann. Der Stack wird immer am ; oberen Speicherende des SRAM angelegt. Das Speicherende des jeweili; gen Chips ist in der Datei "xxxxdef.inc" mit dem Namen RAMEND defi; niert. ; Wird ein Byte auf dem Stapel oder Stack abgelegt, dann wird ein ; Byte in das SRAM geschrieben (an die Adresse SPH:SPL), dann wird der ; Stapelzähler SPH:SPL um Eins verringert. Weiteres Ablegen bringt den ; Zeiger näher an den Anfang des SRAMs. Wird ein Wert aus dem Stapel ; entnommen, dann wird der Zeiger um eins erhöht und der Wert ausgelesen. ; Der jeweils zuletzt im SRAM abgelegte Wert kommt bei der Entnahme ; wieder als erster heraus (Last-in-First-Out-Struktur). ; Da der Programmzähler und die weitere Adressverwaltung 16 Bits ; Breite hat, alle Register und das SRAM aber 8 Bits, muss man jeweils ; mit 2 Bytes zu 8 Bits oder einem Wort zu 16 Bits arbeiten. Der ; SRAM-Speicher hat 16 Bit Adresse, also gibt es den Port SPL für die ; unteren 8 Bits, den Port SPH für die oberen 8 Bits der Adresse. Zusam; men ist SPH:SPL ein 16-Bit-Zeiger in das SRAM. ; Die Rechenoperationen LOW und HIGH veranlassen den Assembler, das ; 16-Bit-Wort RAMEND jeweils zu je 8-Bits aufzuteilen, damit es in die ; Ports SPL und SPH übertragen werden kann. ; An Port D sind die Schalter angeschlossen. Der Port D wird gelesen. ; Sein Datenrichtungsregister muss also acht Nullen kriegen: LDI mp,0x00 ; 8 Nullen in Universalregister OUT DDRD,mp ; an Datenrichtungsregister ; Die Schalter verbinden die Eingänge des Ports D mit GND. Damit bei ; offenem Schalter ein definierter Pegel herrscht, können die Pull-Up; Widerstände eingeschaltet werden. Das ist an sich auf dem STK200 nicht ; erforderlich, da diese extern vorhanden sind. Aus pädagischen Gründen ; schalten wir sie trotzdem ein. Das erreicht man durch das Schreiben ; von Einsen in das Datenausgangsregister: LDI mp,0xFF ; 8 Einsen in Universalregister OUT PORTD,mp ; und an Port D (das sind jetzt die Pull-Ups!) ; Port B mit den LEDs soll wieder als Ausgang dienen (siehe TEST1), die ; Lampen sollen zu Beginn alle aus sein. LDI mp,0xFF ; 8 Einsen in Universalregister OUT DDRB,mp ; und in Datenrichtungsregister OUT PORTB,mp ; und an alle Ausgänge ; Das Drücken der Schalter 0 und 1 soll die Lampen 0 und 1 anmachen, ; das Drücken eines der Schalter 2 bis 6 die anderen Lampen. Das ; Drücken von Schalter 7 macht alle Lampen aus. ; Im Hauptloop erfolgt die Abfrage der Schalter, wenn die Bedingungen ; erfüllt sind, wird zu den jeweiligen Unterprogrammen verzeigt. loop: ; Abfrage von Schalter 0 (einfachst) ; Überspringe den nächsten Befehl (Aufruf des Unterprogrammes zum ; Anmachen der Lampe 0), wenn das 0-te Bit im Port D eine Eins ist ; (Schalter aus, Pull-Up erzeugt eine Eins). Das Unterprogramm ; Lampe 0 steht weiter hinten. Beim RCALL wird die aktuelle Adresse ; auf den Stapel abgelegt, beim Rücksprung wird diese wieder vom Stapel ; genommen und das Programm mit dem Befehl nach RCALL fortgesetzt. Weil ; mit dem SBIS-Befehl immer nur ein Befehl übersprungen wird, muss es ; sich um einen Ein-Byte-Befehl handeln. RCALL ist ein solcher, der ; absolute CALL-Befehl wäre ein 2-Byte-Befehl und ginge nicht! SBIS PIND,0 ; Springe, wenn Bit 0 im Port D Eins ist RCALL Lampe0 ; Führe ein relativ angegebenes Unterprogramm aus ; Nach Abarbeiten des Unterprogrammes und beim Überspringen des Unter; programmes wird nun nächste Befehl durchgeführt. ; Abfrage von Schalter 1 (exotisch) ; Die Ports sind in den Adressraum des SRAMs gespiegelt, die Adresse ; ergibt sich durch Addition von 20 hex. Anstelle der IN/OUT-Befehle ; können auch die Lade-/Speicher-Befehle mit Zeigern verwendet werden. .EQU d_in_spiegel=PIND + $20 ; Mit dem Registerpaar R27:R26 kann ein Zeiger X in das SRAM konstruiert ; werden, mit dem LD-Befehl kann der Inhalt des Ports gelesen werden, als ; wenn es ein SRAM-Byte wäre. LDI R26,LOW(d_in_spiegel) ; unteres Byte Zeiger LDI R27,HIGH(d_in_spiegel) ; oberes Byte Zeiger LD mp,X ; Lade Register aus Zeiger auf PIND ; Isoliere Pin1 mit AND-Befehl und teste auf Null ANDI mp,0b00000010 ; AND Bit 1 ; Überspringe die folgenden Befehle, wenn nicht Null bei AND herauskam ; (= Bit 1 war Eins, Schalter 1 ist aus). Der Sprungbefehl ist für eine ; größere Anzahl Befehle geeignet, da zu einem Label verzweigt wird. BRNE weiter ; Verzweige nach weiter, falls nicht Null RCALL Lampe1 ; Führe Unterprogramm Lampe1 aus ; Schalter 2 bis 6 ; Einlesen des Ports D in ein Register, Maskieren der anderen Schalter, ; Vergleich mit Schalter 2 bis 6, ; Vergleich mit LEDs 2 bis 7 anmachen weiter: IN mp,PIND ; Lese Port D ORI mp,0b10000011 ; Maskiere Schalter 0, 1 und 7 CPI mp,0b11111111 ; Irgendein Schalter gedrückt? BREQ sw7 ; Verzweige nach sw7, falls = $FF IN mp,PINB ; Lese LED-Port ANDI mp,0b00000011 ; Lampen 2 bis 7 anmachen OUT PORTB,mp ; an LED-Port sw7: IN mp,PIND ; Lese Schalter-Port ROL mp ; Schiebe siebtes Bit in das Carry-Flag BRCS endloop ; Siebtes Bit ist 1 (BRanch if Carry is Set) LDI mp,0xFF ; Alle Lampen aus OUT PORTB,mp endloop: RJMP loop ;
; Unterprogramm Lampe 0 ; schaltet Lampe 0 ein. Lampe0: IN mp,PINB ; Lese aktuellen Zustand von Port B ANDI mp,0b11111110 ; Lösche Bit 0 mit UND-Befehl OUT PORTB,mp ; Schreibe Ergebnis zurück RET ; Hole Rücksprungadresse vom Stapel und kehre zurück
; Test 2: Board kennenlernen: Eingabe von einem Port ; Was man hier dazu lernen kann: ; - Eingabe von einem Port lesen ; - Das Aufrufen von Unterprogrammen, Stack- (Stapel-Befehle) ; - Binäre Rechen Operationen (AND, OR, ROL, etc.) ; - Bedingte Verzweigungen (SBIx, BRxx) .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; Ein Universalregister definieren: .DEF mp = R16 ; Der Rest-Sprungbefehl an Adresse 0: rjmp main ; Hier beginnt das Hauptprogramm main: ldi mp,LOW(RAMEND) ;Initiate Stackpointer out SPL,mp ldi mp,HIGH(RAMEND) out SPH,mp ; Diese Befehle richten einen Stack im SRAM ein. Ein Stack wird immer ; dann gebraucht, wenn Unterprogramme oder Interrupts aufgerufen werden. ; Beim Aufruf muss der aktuelle Zählerstand der Bearbeitung in einem ; Speicher abgelegt werden, damit nach Beendigung wieder an die auf; rufende Stelle zurückgekehrt werden kann. Der Stack wird immer am ; oberen Speicherende des SRAM angelegt. Das Speicherende des jeweili; gen Chips ist in der Datei "xxxxdef.inc" mit dem Namen RAMEND defi; niert. ; Wird ein Byte auf dem Stapel oder Stack abgelegt, dann wird ein ; Byte in das SRAM geschrieben (an die Adresse SPH:SPL), dann wird der ; Stapelzähler SPH:SPL um Eins verringert. Weiteres Ablegen bringt den ; Zeiger näher an den Anfang des SRAMs. Wird ein Wert aus dem Stapel ; entnommen, dann wird der Zeiger um eins erhöht und der Wert ausgelesen. ; Der jeweils zuletzt im SRAM abgelegte Wert kommt bei der Entnahme ; wieder als erster heraus (Last-in-First-Out-Struktur). ; Da der Programmzähler und die weitere Adressverwaltung 16 Bits ; Breite hat, alle Register und das SRAM aber 8 Bits, muss man jeweils ; mit 2 Bytes zu 8 Bits oder einem Wort zu 16 Bits arbeiten. Der ; SRAM-Speicher hat 16 Bit Adresse, also gibt es den Port SPL für die ; unteren 8 Bits, den Port SPH für die oberen 8 Bits der Adresse. Zusam; men ist SPH:SPL ein 16-Bit-Zeiger in das SRAM. ; Die Rechenoperationen LOW und HIGH veranlassen den Assembler, das ; 16-Bit-Wort RAMEND jeweils zu je 8-Bits aufzuteilen, damit es in die ; Ports SPL und SPH übertragen werden kann. ; An Port D sind die Schalter angeschlossen. Der Port D wird gelesen. ; Sein Datenrichtungsregister muss also acht Nullen kriegen: ldi mp,0x00 ; 8 Nullen in Universalregister out DDRD,mp ; an Datenrichtungsregister ; Die Schalter verbinden die Eingänge des Ports D mit GND. Damit bei ; offenem Schalter ein definierter Pegel herrscht, können die Pull-Up; Widerstände eingeschaltet werden. Das ist beim STK200 nicht nötig, ; weil diese extern schon auf dem Board vorhanden sind. Aus pädagischen ; Gründen schalten wir sie trotzdem ein. Das erreicht man durch das ; Schreiben von Einsen in das Datenausgangsregister: ldi mp,0xFF ; 8 Einsen in Universalregister out PORTD,mp ; und an Port D (das sind jetzt die Pull-Ups!) ; Port B mit den LEDs soll wieder als Ausgang dienen (siehe TEST1), die ; Lampen sollen zu Beginn alle aus sein. ldi mp,0xFF ; 8 Einsen in Universalregister out DDRB,mp ; und in Datenrichtungsregister out PORTB,mp ; und an alle Ausgänge ; Das Drücken der Schalter 0 und 1 soll die Lampen 0 und 1 anmachen, ; das Drücken eines der Schalter 2 bis 6 die anderen Lampen. Das ; Drücken von Schalter 7 macht alle Lampen aus. ; Im Hauptloop erfolgt die Abfrage der Schalter, wenn die Bedingungen ; erfüllt sind, wird zu den jeweiligen Unterprogrammen verzeigt. loop: ; Abfrage von Schalter 0 (einfachst) ; Überspringe den nächsten Befehl (Aufruf des Unterprogrammes zum ; Anmachen der Lampe 0), wenn das 0-te Bit im Port D eine Eins ist ; (Schalter aus, Pull-Up erzeugt eine Eins). Das Unterprogramm ; Lampe 0 steht weiter hinten. Beim RCALL wird die aktuelle Adresse ; auf den Stapel abgelegt, beim Rücksprung wird diese wieder vom Stapel ; genommen und das Programm mit dem Befehl nach RCALL fortgesetzt. Weil ; mit dem SBIS-Befehl immer nur ein Befehl übersprungen wird, muss es ; sich um einen Ein-Byte-Befehl handeln. RCALL ist ein solcher, der ; absolute CALL-Befehl wäre ein 2-Byte-Befehl und ginge nicht! sbis PIND,0 ; Springe, wenn Bit 0 im Port D Eins ist rcall Lampe0 ; Führe ein relativ angegebenes Unterprogramm aus ; Nach Abarbeiten des Unterprogrammes und beim Überspringen des Unter; programmes wird nun nächste Befehl durchgeführt. ; Abfrage von Schalter 1 (exotisch) ; Die Ports sind in den Adressraum des SRAMs gespiegelt, die Adresse ; ergibt sich durch Addition von 20 hex. Anstelle der IN/OUT-Befehle ; können auch die Lade-/Speicher-Befehle mit Zeigern verwendet werden. .EQU d_in_spiegel=PIND + $20 ; Mit dem Registerpaar R27:R26 kann ein Zeiger X in das SRAM konstruiert ; werden, mit dem LD-Befehl kann der Inhalt des Ports gelesen werden, als ; wenn es ein SRAM-Byte wäre. ldi R26,LOW(d_in_spiegel) ; unteres Byte Zeiger ldi R27,HIGH(d_in_spiegel) ; oberes Byte Zeiger ld mp,X ; Lade Register aus Zeiger auf PIND ; Isoliere Pin1 mit AND-Befehl und teste auf Null andi mp,0b00000010 ; AND Bit 1 ; Überspringe die folgenden Befehle, wenn nicht Null bei AND herauskam ; (= Bit 1 war Eins, Schalter 1 ist aus). Der Sprungbefehl ist für eine ; größere Anzahl Befehle geeignet, da zu einem Label verzweigt wird. brne weiter ; Verzweige nach weiter, falls nicht Null rcall Lampe1 ; Führe Unterprogramm Lampe1 aus ; Schalter 2 bis 6 ; Einlesen des Ports D in ein Register, Maskieren der anderen Schalter, ; Vergleich mit Schalter 2 bis 6, ; Vergleich mit LEDs 2 bis 7 anmachen weiter: in mp,PIND ; Lese Port D ori mp,0b10000011 ; Maskiere Schalter 0, 1 und 7 cpi mp,0b11111111 ; Irgendein Schalter gedrückt? breq sw7 ; Verzweige nach sw7, falls = $FF in mp,PINB ; Lese LED-Port andi mp,0b00000011 ; Lampen 2 bis 7 anmachen out PORTB,mp ; an LED-Port sw7: in mp,PIND ; Lese Schalter-Port rol mp ; Schiebe siebtes Bit in das Carry-Flag brcs endloop ; Siebtes Bit ist 1 (BRanch if Carry is Set) ldi mp,0xFF ; Alle Lampen aus out PORTB,mp endloop: rjmp loop ; ; Unterprogramm Lampe 0 ; schaltet Lampe 0 ein. Lampe0: in mp,PINB ; Lese aktuellen Zustand von Port B andi mp,0b11111110 ; Lösche Bit 0 mit UND-Befehl out PORTB,mp ; Schreibe Ergebnis zurück ret ; Hole Rücksprungadresse vom Stapel und kehre zurück ; Unterprogramm Lampe 1 ; schaltet Lampe 1 ein Lampe1: in mp,PINB ; Lese Zustand von Port B cbr mp,0b00000010 ; Lösche Bit 2 mit CBR-Befehl out PORTB,mp ; Schreibe Ergebnis zurück ret ; Adresse vom Stapel und zurück http://www.avr-asm-tutorial.net/avr_de/quellen/test2.asm1/20/2009 7:36:12 PM
AVR-Tutorium, Teil 3
Pfad: Home => AVR-Übersicht => Tutorium => Teil 3
; Test 3: Board kennenlernen, Timer im Polling mode ; Was hier Neues zu lernen ist: ; - Timer im Polling modus ; - MOV-Befehl .NOLIST .INCLUDE "8515def.inc" .LIST
; Test 3: Board kennenlernen, Timer im Polling mode ; Was hier Neues zu lernen ist: ; - Timer im Polling modus ; - MOV-Befehl .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; Universalregister definieren .DEF mp = R16 ; Zähler für Anzahl Nulldurchgänge .DEF z1 = R0 ; Reset-Vektor auf Adresse 0000 rjmp main ; Hauptprogramm beginnt hier main: ldi mp,LOW(RAMEND) ;Initiate Stackpointer (Unterprogramme!) out SPL,mp ldi mp,HIGH(RAMEND) out SPH,mp ; Software-Zähler-Register auf Null setzen ldi mp,0 ; z1 kann nicht direkt gesetzt werden, da R0 mov z1,mp ; Kopieren von mp in das Register z1 ; Vorteiler des Zählers = 1024, 4 MHz/1024 = 3906,25 Hz, ; ergibt alle 256 µs einen Zählimpuls. ; ldi mp,0x05 ;Initiate Timer/Counter 0 Vorteiler out TCCR0,mp ; an Timer 0 Control Register ; Port B ist LED-Port ldi mp,0xFF ; alle Bits als Ausgang out DDRB,mp ; in Datenrichtungsregister ; Hauptprogramm-Loop fragt Zähler ab, bis dieser Null erreicht, ; dann wird der Softwarezähler um Eins erhöht und auf den LEDs ; ausgegeben. ; Dadurch werden die 3906,25 noch einmal durch 256 geteilt, ergibt ; 15,25878906 Hz. Wer es lieber in Zeiten mag: 65,536 ms. ; loop: in mp,TCNT0 ; Inhalt des 8-Bit-Zählers lesen cpi mp,0 ; auf Null testen brne loop ; Wenn nicht null, dann wieder loop rcall IncZ1 ; Unterprogramm Software-Zähler erhöhen rcall Display ; Unterprogramm Zählerstand ausgeben warte: in mp,TCNT0 ; Inhalt Zähler 0 lesen cpi mp,0 ; auf Null testen breq warte ; Warte bis nicht mehr Null rjmp loop ; Nächste Runde IncZ1: inc z1 ; Erhöhe Software-Zähler ret ; Kehre zurück ins Hauptprogramm Display: mov mp,z1 ; Zählerstand nach mp kopieren com mp ; Einer-Komplement XOR(FF) wg. Lampen out PORTB,mp ; Software-Zählerstand an LEDs ret ; Zurück zum Hauptprogramm http://www.avr-asm-tutorial.net/avr_de/quellen/test3.asm1/20/2009 7:36:15 PM
AVR-Tutorium, Teil 4
Pfad: Home => AVR-Übersicht => Tutorium => Teil 4
; Test 4: Board kennenlernen, Timer im Interupt mode ; Was hier Neues zu lernen ist: ; - Timer im Interrupt modus ; - Interrupts, Interrupt-Vektoren ; - BCD-Arithmetik .NOLIST .INCLUDE "8515def.inc" .LIST
; Arbeitsregister für die Interrupt-Service-Routine .DEF ri = R1
; Register zum Zählen der Sekunden, gepackte BCD-Ziffern .DEF sec = R2
; Reset-Vektor auf Adresse 0000 RJMP main ; Interrupt-Vektoren, fast alle blindgesetzt ausser dem Timer-Overflow ; RETI ist Rückkehr vom Interrupt mit Wiederherstellung des Interrupt; Flags im Status-Register. Wichtig: Sprung zur Interrupt-Service; Routine tc0i muss an der Adresse 0007 stehen. ; Mechanismus des Interrupts: Ist der Timer von 255 auf Null überge; laufen, dann wird das Programm unterbrochen, der Programmzähler auf ; dem Stapel abgelegt, der Befehl an der Adresse 0007 ausgeführt. Nach ; Beendigung des Interrupts wird der Programmzähler wieder hergestellt ; und mit dem unterbrochenen Programm fortgefahren.
; Die Interrupt-Vektoren zu je 1 Byte: RETI ; Int0-Interrupt RETI ; Int1-Interrupt RETI ; TC1-Capture RETI ; TC1-Compare A RETI ; TC1-Compare B RETI ; TC1-Overflow RJMP tc0i ; Timer/Counter 0 Overflow, mein Sprung-Vektor RETI ; Serial Transfer complete RETI ; UART Rx complete RETI ; UART Data register empty RETI ; UART Tx complete RETI ; Analog Comparator
; Interrupt-Service-Routine für den Zähler tc0i: IN ri,SREG ; Rette den Inhalt des Flag-Registers INC z1 ; Erhöhe Software-Zähler mit Bit 8-15 OUT SREG,ri ; Stelle Flag-Register wieder her RETI ; Kehre vom Interrupt zurück
; Hauptprogramm beginnt hier main: OUT LDI OUT
LDI mp,LOW(RAMEND) ;Initiate Stackpointer SPL,mp ; wegen Interrupts und Unterprogr. mp,HIGH(RAMEND) SPH,mp
; Software-Zähler-Register auf Null setzen LDI mp,0 ; z1 kann nicht direkt gesetzt werden, da R0 MOV z1,mp ; Kopieren von 0 in den Software-Zähler MOV sec,mp ; und Sekundenzähler auf Null ; Vorteiler des Zählers = 256, 4 MHz/256 = 15625 Hz = $3D09 LDI mp,0x04 ;Initiate Timer/Counter 0 Vorteiler OUT TCCR0,mp ; an Timer 0 Control Register
; Port B ist LED-Port LDI mp,0xFF ; alle Bits als Ausgang OUT DDRB,mp ; in Datenrichtungsregister
; Interrupts bei Timer 0 einschalten LDI mp,$02 ; Bit 1 setzen OUT TIMSK,mp ; in Timer Interupt Mask Register ; Einfacher wäre dieser Befehl einfacher: ; SBI TIMSK,TOIE0 ; Timer Interrupt Mask Flag setzen ; Dieser Befehl geht aber nicht, weil mit SBI nur Ports bis 0x1F angesprochen werden ; können, und TIMSK liegt darüber.
; Alle Interrupts allgemein freigeben SEI ; Gib Interrupts im Status-Register frei ; Hauptprogramm-Loop fragt oberes Byte des Zählers ab, bis dieser ; hex 3D erreicht hat. Dann den Timer, bis dieser 09 erreicht hat ; (eine Sekunde = dez 15625 = hex 3D09 Zählerimpulse). Die Zähler ; werden auf Null gesetzt und eine Sekunde weitergezählt. Die Se; kunden werden als gepackte BCD-Zahl behandelt (eine Ziffer zu ; vier Bit, 1 Byte entspricht zwei Ziffern), Die Sekunden werden ; bei Erreichen von 60 wieder auf Null gesetzt. Der Sekundenstand ; wird auf den LEDs ausgegeben. loop: LDI mp,$3D ; Vergleichswert MSB loop1: CP z1,mp ; Vergleiche mit MSB Zählerstand BRLT loop1 ; z1 < mp, weiter warten loop2: IN mp,TCNT0 ; Zähler LSB lesen CPI mp,$09 ; mit LSB vergleichen BRLT loop2 ; TCNT0 < 09, weiter warten LDI mp,0 ; Null OUT TCNT0,mp ; in Hardware-Zähler LSB MOV z1,mp ; und in Software-Zähler MSB RCALL IncSec ; Unterprogramm Sekunden-Zähler erhöhen RCALL Display ; Unterprogramm Sekunden an LED ausgeben RJMP loop ; Das Ganze von Vorne
; Unterprogramm eine Sekunde zum Sekundenzähler ; in BCD-Arithmetik! Unteres Nibble = Bit 0..3, Oberes N. = 4..7 IncSec: SEC ; Setze Carry-Flag für Addition LDI mp,6 ; Provoziere Überlauf unteres Nibble ADC sec,mp ; beim Addieren von 6 + 1 (Carry) BRHS Chk60 ; Springe zum 60-Check, wenn Überlauf SUB sec,mp ; Ziehe die 6 wieder ab Chk60: LDI mp,$60 ; Vergleiche mit 60 CP sec,mp BRLT SecRet ; Springe, wenn kleiner LDI mp,256-$60 ; Lade Rest auf Null ADD sec,mp ; Addiere auf Null SecRet: RET ; Kehre zum Hauptprogramm zurück
; Test 4: Board kennenlernen, Timer im Interupt mode ; Was hier Neues zu lernen ist: ; - Timer im Interrupt modus ; - Interrupts, Interrupt-Vektoren ; - BCD-Arithmetik .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; Universalregister definieren .DEF mp = R16 ; Zähler Anzahl Nulldurchgänge, MSB Zähler, Software-Zähler .DEF z1 = R0 ; Arbeitsregister für die Interrupt-Service-Routine .DEF ri = R1 ; Register zum Zählen der Sekunden, gepackte BCD-Ziffern .DEF sec = R2 ; Reset-Vektor auf Adresse 0000 rjmp main ; Interrupt-Vektoren, fast alle blindgesetzt ausser dem Timer-Overflow ; RETI ist Rückkehr vom Interrupt mit Wiederherstellung des Interrupt; Flags im Status-Register. Wichtig: Sprung zur Interrupt-Service; Routine tc0i muss an der Adresse 0007 stehen. ; Mechanismus des Interrupts: Ist der Timer von 255 auf Null überge; laufen, dann wird das Programm unterbrochen, der Programmzähler auf ; dem Stapel abgelegt, der Befehl an der Adresse 0007 ausgeführt. Nach ; Beendigung des Interrupts wird der Programmzähler wieder hergestellt ; und mit dem unterbrochenen Programm fortgefahren. ; Die Interrupt-Vektoren zu je 1 Byte: reti ; Int0-Interrupt reti ; Int1-Interrupt reti ; TC1-Capture reti ; TC1-Compare A reti ; TC1-Compare B reti ; TC1-Overflow rjmp tc0i ; Timer/Counter 0 Overflow, mein Sprung-Vektor reti ; Serial Transfer complete reti ; UART Rx complete reti ; UART Data register empty reti ; UART Tx complete reti ; Analog Comparator ; Interrupt-Service-Routine für den Zähler tc0i: in ri,SREG ; Rette den Inhalt des Flag-Registers inc z1 ; Erhöhe Software-Zähler mit Bit 8-15 out SREG,ri ; Stelle Flag-Register wieder her reti ; Kehre vom Interrupt zurück ; Hauptprogramm beginnt hier main: ldi mp,LOW(RAMEND) ;Initiate Stackpointer out SPL,mp ; wegen Interrupts und Unterprogr. ldi mp,HIGH(RAMEND) out SPH,mp ; Software-Zähler-Register auf Null setzen ldi mp,0 ; z1 kann nicht direkt gesetzt werden, da R0 mov z1,mp ; Kopieren von 0 in den Software-Zähler mov sec,mp ; und Sekundenzähler auf Null ; Vorteiler des Zählers = 256, 4 MHz/256 = 15625 Hz = $3D09 ldi mp,0x04 ;Initiate Timer/Counter 0 Vorteiler out TCCR0,mp ; an Timer 0 Control Register ; Port B ist LED-Port ldi mp,0xFF ; alle Bits als Ausgang out DDRB,mp ; in Datenrichtungsregister ; Interrupts bei Timer 0 einschalten ldi mp,$02 ; Bit 1 setzen out TIMSK,mp ; in Timer Interupt Mask Register ; Eigentlich könnte dieser Befehl folgendermassen aussehen: ; SBI TIMSK,TOIE0 ; Timer Interrupt Mask Flag setzen ; Das geht aber nicht, weil SBI nur bei Portnummern bis 0x1F ; geht, TIMSK liegt aber darüber. Deshalb der Umweg. ; Alle Interrupts allgemein freigeben sei ; Gib Interrupts im Status-Register frei ; Hauptprogramm-Loop fragt oberes Byte des Zählers ab, bis dieser ; hex 3D erreicht hat. Dann den Timer, bis dieser 09 erreicht hat ; (eine Sekunde = dez 15625 = hex 3D09 Zählerimpulse). Die Zähler ; werden auf Null gesetzt und eine Sekunde weitergezählt. Die Se; kunden werden als gepackte BCD-Zahl behandelt (eine Ziffer zu ; vier Bit, 1 Byte entspricht zwei Ziffern), Die Sekunden werden ; bei Erreichen von 60 wieder auf Null gesetzt. Der Sekundenstand ; wird auf den LEDs ausgegeben. loop: ldi mp,$3D ; Vergleichswert MSB loop1: cp z1,mp ; Vergleiche mit MSB Zählerstand brlt loop1 ; z1 < mp, weiter warten loop2: in mp,TCNT0 ; Zähler LSB lesen cpi mp,$09 ; mit LSB vergleichen brlt loop2 ; TCNT0 < 09, weiter warten ldi mp,0 ; Null out TCNT0,mp ; in Hardware-Zähler LSB mov z1,mp ; und in Software-Zähler MSB rcall IncSec ; Unterprogramm Sekunden-Zähler erhöhen rcall Display ; Unterprogramm Sekunden an LED ausgeben rjmp loop ; Das Ganze von Vorne ; Unterprogramm eine Sekunde zum Sekundenzähler ; in BCD-Arithmetik! Unteres Nibble = Bit 0..3, Oberes N. = 4..7 IncSec: sec ; Setze Carry-Flag für Addition ldi mp,6 ; Provoziere Überlauf unteres Nibble adc sec,mp ; beim Addieren von 6 + 1 (Carry) brhs Chk60 ; Springe zum 60-Check, wenn Überlauf sub sec,mp ; Ziehe die 6 wieder ab Chk60: ldi mp,$60 ; Vergleiche mit 60 cp sec,mp brlt SecRet ; Springe, wenn kleiner ldi mp,256-$60 ; Lade Rest auf Null add sec,mp ; Addiere auf Null SecRet: ret ; Kehre zum Hauptprogramm zurück ; Unterprogramm zur Ausgabe des Sekundenzählers an die LEDs Display: mov mp,sec ; Zählerstand nach mp kopieren com mp ; Einer-Komplement = XOR(FF) wg. Lampen out PORTB,mp ; Software-Zählerstand an LEDs ret ; Zurück zum Hauptprogramm http://www.avr-asm-tutorial.net/avr_de/quellen/test4.asm1/20/2009 7:36:19 PM
Zeitschleifen in AVR Assembler
Path: Home => AVR-Überblick => Zeitschleifen
Zeitschleifenprogrammierung in AVR Assembler Anfänger verzweifeln oft schon bei der Aufgabe, eine an einem Ausgangsport angeschlossene LED zum Blinken zu bewegen. Das "Hello-World"-Programm der AVR-Welt ist schon einigermaßen schwierig. Deshalb hier alles über Zeitschleifen, wie man sie entwirft, einsetzt und näherungsweise und korrekt berechnet.
Path: Home => AVR-Überblick => Zeitschleifen => 8-Bit-Register
Zeitschleife mit 8-Bit-Register in AVR Assembler Hier wird eine 8-Bit-Zeitschleife erklärt. Wenn Sie das alles schon wissen, blättern Sie es trotzdem kurz durch. Die Zeitberechnungen und die Taktfrequenzen könnten einiges Neues bringen, das bei den etwas komplizierteren Zeitschleifen behilflich sein könnte.
Code einer 8-Bit-Schleife Eine Zeitschleife mit einem 8-Bit-Register sieht in Assembler folgendermaßen aus:
.equ c1 = 200 ; Anzahl Durchläufe der Schleife ldi R16,c1 ; Lade Register mit Schleifenwert Loop: ; Schleifenbeginn dec R16 ; Registerwert um Eins verringern brne Loop ; wenn nicht Null dann Schleifenbeginn Praktischerweise gibt die Konstante c1 die Anzahl Schleifendurchläufe direkt an. Mit ihr kann die Anzahl der Durchläufe und damit die Verzögerung variiert werden. Da in ein 8-Bit-Register nur Zahlen bis 255 passen, ist die Leistungsfähigkeit arg eng beschränkt.
Prozessortakte Für die Zeitverzögerung mit einer solchen Schleife sind zwei Größen maßgebend: ● ●
die Anzahl Prozessortakte, die die Schleife benötigt, und die Zeitdauer eines Prozessortakts.
Die Anzahl Prozessortakte, die die einzelnen Instruktionen benötigen, stehen in den Datenbüchern der AVRs. Und zwar ziemlich weit hinten, unter "Instruction Set Summary", in der Spalte "#Clocks". Demnach ergibt sich folgendes Bild:
.equ c1 = 200 ; 0 Takte, macht der Assembler alleine ldi R16,c1 ; 1 Takt Loop: ; Schleifenbeginn dec R16 ; 1 Takt brne Loop ; 2 Takte wenn nicht Null, 1 Takt bei Null Damit setzt sich die Anzahl Takte folgendermaßen zusammen: 1. Laden: 1 Takt, Anzahl Durchläufe: 1 mal 2. Schleife mit Verzweigung an den Schleifenbeginn: 3 Takte, Anzahl Durchläufe: c1 - 1 mal 3. Schleife ohne Verzweigung an den Schleifenbeginn: 2 Takte, Anzahl Durchläufe: 1 mal Damit ergibt sich die Anzahl Takte nt zu: nt = 1 + 3*(c1-1) + 2 oder mit aufgelöster Klammer zu: nt = 1 + 3*c1 - 3 + 2 oder halt noch einfacher zu: nt = 3*c1 Jetzt haben wir die Anzahl Takte.
Taktfrequenz des AVR Die Dauer eines Takts ergibt sich aus der Taktfrequenz des AVR. Die ist gar nicht so einfach zu ermitteln, weil es so viele Möglichkeiten gibt, sie auszuwählen oder zu verstellen: ●
●
●
●
●
AVRs ohne interne Oszillatoren: diese brauchen entweder ❍ einen externen Quarz, ❍ einen externen Keramikresonator, oder ❍ einen externen Oszillator Die Taktfrequenz wird in diesem Fall von den externen Komponenten bestimmt. AVRs mit einem oder mehreren internen Oszillatoren: diese haben eine voreingestellte Taktfrequenz, die im Kapitel "System Clock and Clock Options" bzw. im Unterkapitel "Default Clock Source" steht. Jeder AVR-Typ hat da so seine besondere Vorliebe. AVRs mit internen RC-Oszillatoren bieten ferner die Möglichkeit, diesen zu verstellen. Dazu werden Werte in den Port OSCCAL geschrieben. Bei älteren AVR geschieht das durch den Programmierer, bei moderneren schreibt ein automatischer Mechanismus den OSCCAL-Wert zu Beginn in den Port. Auch die Automatik benötigt den Handeingriff, wenn höhere Genauigkeit gefordert ist und der AVR abseits der Spannung oder Betriebstemperatur betrieben wird, für die er kalibriert ist (beim ATtiny13 z.B. 3 V und 25°C). Im Kapitel "System Clock and Clock Options" wird auch erklärt, wie man durch Verstellen von Fuses ❍ zwischen internen und externen Taktquellen auswählt, ❍ einen internen Vorteiler zwischen Taktquelle und Prozessortakt schalten kann (Clock Prescaler, siehe unten), ❍ eine Wartezeit beim Start des Prozessors einfügt, bis der Oszillator stabil schwingt ("Delay from Reset"). Die Fuses werden mittels Programmiergerät eingestellt. Obacht! Beim Verstellen von Fuses, z. B. auf eine externe Taktquelle, muss diese auch angeschlossen sein. Sonst verabschiedet sich der Prozessor absolut taktlos vom Programmiergerät und gibt diesem keine sinnvollen Antworten mehr. Um das Publikum zu verwirren, haben einige AVR noch einen Vorteiler für den Prozessortakt (Clock Prescaler). Dieser lässt sich entweder per Fuse (siehe oben) auf 1 oder 8 einstellen, bei manchen AVR aber auch per Software auf Teilerwerte von 1, 2, 4, 8, bis 256 einstellen (Port CLKPR).
Haben Sie aus all dem Durcheinander jetzt herausgefunden, mit wieviel MHz der AVR nun eigentlich läuft (fc), dann ergibt sich die Dauer eines Prozessortakts (tc) zu: tc [µs] = 1 / fc [MHz] Bei 1,2 MHz Takt (ATtiny13, interner RC-Oszillator mit 9,6 MHz, CLKPR = 8) sind das z.B. 0,83 µs. Die restlichen Stellen können Sie getrost vergessen, der interne RC-Oszillator ist so arg ungenau, dass weitere Stellen eher dem Glauben an Zauberei ähneln.
Zeitverzögerung Mit der Anzahl Prozessortakte von oben (nc=3*c1, c1=200, nc=600) und 1,2 MHz Takt ergibt sich eine Verzögerung von 500 µs. Mit maximal 640 µs ist die Maximalbegrenzung dieser Konstruktion erreicht.
Verlängerung! Mit folgendem Trick können Sie noch etwas Verlängerung herausholen:
.equ c1 = 200 ; 0 Takte, macht der Assembler alleine ldi R16,c1 ; 1 Takt Loop: ; Schleifenbeginn nop ; tue nichts, 1 Takt nop ; tue nichts, 1 Takt nop ; tue nichts, 1 Takt nop ; tue nichts, 1 Takt nop ; tue nichts, 1 Takt dec R16 ; 1 Takt brne Loop ; 2 Takte wenn nicht Null, 1 Takt bei Null Jetzt braucht jeder Schleifendurchlauf (bis auf den letzten) acht Takte und es gelten folgende Formeln: nc = 1 + 8*(c1 - 1) + 7 oder nc = 8 * c1 Das verlängert die 8-Bit-Registerschleife immerhin auf 256 * 8 = 2048 Takte oder - bei 1,2 MHz auf ganze 1,7 ms.
Das Summprogramm Immer noch nicht für die Blink-LED geeignet, aber immerhin für ein angenehmes Brummen mit 586 Hz im Lautsprecher. Schließen Sie einen Lautsprecher an Port B, Bit 0, an, fairerweise über einen Elko von einigen µF, und lassen Sie das folgende Programm auf den ATtiny13 los:
Path: Home => AVR-Überblick => Zeitschleifen => 16-Bit-Doppelregister
Zeitschleife mit 16-Bit-Doppelregister in AVR Assembler Hier wird eine 16-Bit-Zeitschleife mit einem Doppelregister erklärt. Mit dieser können Verzögerungen um ca. eine halbe Sekunde realisiert werden. Damit wird eine Blinkschaltung mit einer LED realisiert.
Code einer 16-Bit-Schleife Eine Zeitschleife mit einem 16-Bit-Doppelregister sieht in Assembler folgendermaßen aus:
.equ c1 = 50000 ; Anzahl Durchläufe der Schleife ldi R25,HIGH(c1) ; Lade MSB-Register mit Schleifenwert ldi R24,LOW(c1) ; Lade LSB-Register mit Schleifenwert Loop: ; Schleifenbeginn sbiw R24,1 ; Doppelregisterwert um Eins verringern brne Loop ; wenn nicht Null dann wieder Schleifenbeginn Die Konstante c1 gibt wieder die Anzahl Schleifendurchläufe direkt an. Da in ein 16-Bit-Register Zahlen bis 65535 passen, ist die Schleife 256 mal leistungsfähiger als ein einzelnes 8-Bit-Register. Die Instruktion "SBIW R24,1" verringert das Doppelregister wortweise, d.h. nicht nur der Inhalt von R24 wird um eins verringert, bei einem Unterlauf von R24 wird auch das nächsthöhere Register R25 um Eins verringert. Das Doppelregister aus R24 und R25 (besser als R25:R24 benannt) eignet sich für solche Schleifen besonders gut, weil die Doppelregister X (R27:R26), Y (R29:R28) und Z (R31:R30) neben den 16Bit-Operationen ADIW und SBIW auch noch andere Instruktionen kennen, und daher für den schnöden Zweck einer Schleife zu schade sind und R25:R24 eben nur ADIW und SBIW können.
Prozessortakte Die Anzahl Prozessortakte, die die einzelnen Instruktionen benötigen, steht wieder in den Datenbüchern der AVRs. Demnach ergibt sich für die 16-Bit-Schleife folgendes Bild:
.equ c1 = 50000 ; 0 Takte, macht der Assembler alleine ldi R25,HIGH(c1) ; 1 Takt ldi R24,LOW(c1) ; 1 Takt Loop: ; Schleifenbeginn sbiw R24,1 ; 2 Takte brne Loop ; 2 Takte wenn nicht Null, 1 Takt bei Null Damit setzt sich die Anzahl Takte folgendermaßen zusammen: 1. Laden: 2 Takte, Anzahl Durchläufe: 1 mal 2. Schleife mit Verzweigung an den Schleifenbeginn: 4 Takte, Anzahl Durchläufe: c1 - 1 mal 3. Schleife ohne Verzweigung an den Schleifenbeginn: 3 Takte, Anzahl Durchläufe: 1 mal Damit ergibt sich die Anzahl Takte nt zu: nt = 2 + 4*(c1-1) + 3 oder mit aufgelöster Klammer zu: nt = 2 + 4*c1 - 4 + 3 oder noch einfacher zu: nt = 4*c1 + 1
Zeitverzögerung Die Maximalzahl an Takten ergibt sich, wenn c1 zu Beginn auf 0 gesetzt wird, dann wird die Schleife 65536 mal durchlaufen. Maximal sind also 4*65536+1 = 262145 Takte Verzögerung möglich. Mit der Anzahl Prozessortakte von oben (c1=50000, nc=4*c1+1) und 1,2 MHz Takt ergibt sich eine Verzögerung von 166,7 ms. Variable Zeitverzögerungen unterschiedlicher Länge sind manchmal nötig. Dies kann z.B. der Fall sein, wenn die gleiche Software bei verschiedenen Taktfrequenzen laufen soll. Dann sollte die Software so flexibel gestrickt sein, dass nur die Taktfrequenz geändert wird und sich die Zeitschleifen automatisch anpassen. So ein Stück Software ist im Folgenden gezeigt. Zwei verschiedene Zeitverzögerungen sind als Rechenbeispiele angegeben (1 ms, 100 ms).
Hinweise: Die Konstanten c1ms und c100ms werden auf unterschiedliche Weise berechnet, um bei der Ganzzahlen-Verarbeitung durch den Assembler einerseits zu große Rundungsfehler und andererseits Überläufe zu vermeiden.
Verlängerung! Mit folgendem Trick können Sie wieder etwas Verlängerung herausholen:
Das Blinkprogramm Damit sind wir beim beliebten "Hello World" für AVRs: der im Sekundenrhytmus blinkenden LED an einem AVR-Port. Die Hardware, die es dazu braucht, ist bescheiden. Die Software steht da:
Path: Home => AVR-Überblick => Interrupt-Programmierung
Interruptprogrammierung in AVR Assembler Bevor man seine ersten Programme mit Interrupt-Steuerung programmiert, sollte man das hier alles gelesen, verstanden und verinnerlicht haben. Auch wenn das einigen Aufwand bedeutet: es zahlt sich schnell aus, weil es viel Frust vermeidet.
Überblick 1. 2. 3. 4.
Interrupt-Vektoren, Interruptquellen, Ablauf im Programm, Interrupts und Ressourcen.
Path: Home => AVR-Überblick => Interrupt-Programmierung => Vektortabelle
Die Interrupt-VektorenTabelle
Hier kommt alles über die Reset- und Interrupt-Vektoren-Tabelle, und was man bei ihr richtig und falsch machen kann.
Wat is ene Vektortabelle? Vergessen Sie für eine Weile mal die Worte Vektor und Tabelle, sie haben hier erst mal nix zu sagen und dienen nur der Verwirrung des unkundigen Publikums. Wir kommen auf die zwei Worte aber noch ausführlich zurück. Stellen Sie sich einfach vor, dass jedes Gerät im AVR für jede Aufgabe, die einer Unterbrechung des Prozessors würdig ist, eine feste Adresse hat, zu der sie den Prozessor zwingt hinzuspringen, wenn das entsprechende Ereignis eintritt (also z.B. der Pegel an einem INT0-Eingang wechselt oder wenn der Timer gerade eben übergelaufen ist). Der Sprung an genau diese eine Adresse ist in jedem AVR für jedes Gerät und jedes Ereignis festgenagelt. Welche Adresse mit welchem Gerät und Ereignis verknüpft ist, steht im Handbuch für den jeweiligen AVR-Typ. Oder ist in Tabellen hier aufgelistet. Alle Interrupt-Sprungziele sind am Anfang des Programmspeichers, beginned bei der Adresse 0000 (mit dem Reset), einfach aufgereiht. Es sieht fast aus wie eine Tabelle, es ist aber gar keine wirkliche. Eine Tabelle wäre, wenn an dieser Adresse tatsächlich das eigentliche Sprungziel selbst stünde, z.B. eine Adresse, zu der jetzt weiter verzweigt werden soll. Der Prozessor täte diese dort abholen und den aus der Tabelle ausgelesenen Wert in seinen Programmzähler laden. Wir hätten dann eine echte Liste mit Adressen vor uns, eine echte Tabelle. Beim AVR gibt es aber gar keine solche Tabelle mit Adresswerten. Stattdessen stehen in unserer sogenannten Tabelle Sprungbefehle wie RJMP herum. Der AVR ist also noch viel einfacher gestrickt: wenn ein INT0-Interrupt auftritt, lädt er einfach die Adresse 0001 in seinen Programmspeicher und führt den dort stehenden Befehl aus. Und das MUSS dann eben ein Sprungbefehl an die echte Adresse sein, an der auf den Interrupt reagiert wird, die Interrupt-ServiceRoutine (ISR). Also nix Tabelle, sondern Auflistung der Sprungbefehle zu den Serviceroutinen. Jetzt haben wir noch die Sache mit dem Vektor zu klären. Auch dieses Wort ist Käse und hat mit den AVRs rein gar nix zu tun. Als man noch Riesen-Mikrocomputer baute mit etlichen 40-poligen ICs, die als Timer, UARTs und I/O-Ports nach außen hin die Schnittstellenarbeit verrichteten, fand man es eine gute Idee, wenn diese selbst darüber bestimmen könnten, wo ihre Service-Routine im Programmspeicher zu finden ist. Sie gaben dazu bei einem Interrupt einen Wert an den Prozessor, der dann diesen zu einer Tabellen-Anfangsadresse addierte und die Adresse der Service-Routine von dieser Adresse holte. Das war sehr flexibel, weil man sowohl die Tabelle als auch die vom Schnittstellenbaustein zurückgegebenen Werte jederzeit im Programm manipulieren konnte und so flugs die ganze Tabelle oder die Interrupt-Service-Routine wechseln konnte. Der zu der Tabellenadresse zu zählende Wert wurde als Vektor oder Displacement (Verschiebung) bezeichnet. Und jetzt wissen wir, wie es zu dem Wort Vektortabelle kam, und dass es mit den AVR rein gar nix zu tun hat, weil wir es weder mit einer Tabelle noch mit Vektoren zu tun haben. Unsere Sprungziele sind im AVR fest verdrahtet, es wird auch nix zu einer Anfangsadresse einer Tabelle addiert und schon gar nicht sind irgendwelche Service-Routinen austauschbar. Warum verwenden wir diese Worte überhaupt? Gute Frage. Weil es alle tun, weil es sich doll anhört und weil es Anfänger eine Weile davon abhält zu kapieren worum es wirlich geht.
Aussehen bei kleinen AVR Eine Reset- und Interrupt-Vektor-Tabelle sieht bei einem kleineren AVR folgendermaßen aus: rjmp rjmp Routine reti rjmp reti reti
Main ; Reset, Sprung zur Initiierung Int0Sr ; Externer Interrupt an INT0, Sprung zur Service; Irgendein anderer Interrupt, nicht benutzt IntTc0OvflwSr ; Überlauf Timer 0, Behandlungsroutine ; Irgendwelche anderen Interrupts, nicht benutzt ; Und noch mehr Interrupts, auch nicht benutzt
Merke: ●
●
Die Anzahl der Instruktionen (RJMP, RETI) hat exakt der Anzahl der im jeweiligen AVR-Typ möglichen Interrupts zu entsprechen. Alle benutzten Interrupts verzweigen mit RJMP zu einer spezifischen Interrupt-ServiceRoutine.
● ●
Alle nicht benutzten Interrupt-Sprungadressen werden mit der Instruktion RETI abgeschlossen.
Alles andere hat in dieser Sprungliste rein gar nix zu suchen. Das hat damit zu tun, dass die Sprungadressen dann genau stimmen, kein Vertun beim Springen erfolgt und die korrekte Anzahl und Abfolge mit dem Handbuch verglichen werden kann. Die Instruktion RETI sorgt dafür, dass der Stapel in Ordnung gebracht wird und Interrupts auf jeden Fall wieder zugelassen werden. Schlaumeier glauben, sie könnten auf die lästigen RETI-Instruktionen verzichten. Z.B. indem sie das oben stehende wie folgt formulieren: rjmp Main ; Reset, Sprung zur Initiierung .org $0001 rjmp Int0Sr ; Externer Interrupt an INT0, Sprung zur ServiceRoutine .org $0003 rjmp IntTc0OvflwSr ; Überlauf Timer 0, Behandlungsroutine Das geht, alle Sprungbefehle stehen an der korrekten Position. Es funktioniert auch, solange nicht absichtlich oder aus Versehen ein weiterer Interrupt zugelassen wird. Der ungeplante Interrupt findet an der mit .org übersprungenen Stelle den auszuführenden Opcode $FFFF vor, da alle unprogrammierten Speicherstellen des Programmspeichers beim Löschen mit diesem befüllt werden. Dieser Opcode ist nirgendwo definiert, er macht auch nix, er bewirkt nichts und die Bearbeitung wird einfach an der nächsten Stelle im Speicher fortgesetzt. Wo der versehentliche Interrupt landet, ist dann recht zufällig und jedenfalls ungeplant. Folgt auf die Einsprungstelle irgendwo noch ein weiterer Sprung zu einer anderen Serviceroutine, dann wird eben die fälschlich ausgeführt. Folgt keine mehr, dann läuft das Programm in die nächstfolgende Unterroutine. Das wäre fatal, weil die garantiert nicht mit RETI endet und daher die Interrupts nie wieder zugelassen werden. Oder, wenn keine Unterroutinen zwischen der Sprungtabelle und dem Hauptprogramm stehen, der weitere Ablauf läuft in das Hauptprogramm, und alles wird wieder von vorne initiiert. Ein weiteres Scheinargument, damit liefe die Software auf jedem anderen AVR-Typ auch korrekt, ist ebenfalls Käse. Ein Blick auf die Tabellen mit den Interrupt-Sprungzielen zeigt, dass sich ATMEL mitnichten immer an irgendwelche Reihenfolgen gehalten hat und dass es munter durcheinander geht. Die Scheinkompatibilitäet kann also zur Nachlässigkeit verführen, eine fatale Fehlerquelle. Daher sollte gelten: ● ● ●
In einer Sprungtabelle haben .org-Direktiven nix verloren. Jeder (noch) nicht verwendete Eintrag in der Sprungtabelle wird mit RETI abgeschlossen. Die Länge der Sprungtabelle entspricht exakt der für den Prozessor definierten Anzahl Interruptsprünge.
Path: Home => AVR-Überblick => Interrupt-Programmierung => Quellen
Interruptprogrammierung in AVR Assembler
Hier wird erläutert, von welchen Quellen aus Interrupts ausgelöst werden können.
Quellen von Interrupts Generell gilt: ●
Jeder AVR-Typ verfügt über unterschiedliche Hardware und kann daher auch unterschiedliche Arten an Interrupts auslösen.
● ● ●
Ob und welche Interrupts implementiert sind, ist aus dem jeweiligen Datenbuch für den AVR-Typ zu entnehmen. Die Interruptquellen bei jedem Typ sind priorisiert, d.h. bei gleichzeitig auftretender Interruptanforderung wird der Interrupt mit der höheren Priorität zuerst bearbeitet.
Arten Hardware-Interrupts Bei den folgenden Tabellen bedeutet ein kleines n eine Ziffer, die Zählung beginnt immer mit 0. Die Benennung der Interrupts in den Handbüchern sind uneinheitlich, die Abkürzungen für die Benennung der Interrupts wurden für eine übersichtliche Darstellung gewählt und sind nicht offiziell. Folgende hauptsächlichen Interrupts sind zu unterscheiden. Aufgelistet ist die Hardware, wann ein Interrupt erfolgt, Erläuterungen dazu, die Verfügbarkeit der Interruptart bei den AVR-Typen und der Name, unter dem der Interrupt in den folgenden Tabellen gefunden wird. Gerät
Interrupt bei ...
Erläuterung
Verfügbarkeit Name(n)
Pegelwechseln an einem bestimmten Portanschluss Externe Interrupts (INTn)
Wählbar, ob jeder Pegelwechsel, nur solche von Low auf high oder INT0 bei allen nur solche von High auf Low einen Interrupt auslösen können (ISC- viele haben INT1 INTn Bits). wenige INT2
Port Interrupts
Pegelwechsel an einem Port (PCIn)
Wählbar mit Maske, welche Bits bei Pegelwechsel einen Interrupt auslösen
bei vielen tiny/mega
Überlauf
Timer überschreitet seinen Maximalwert und beginnt bei Null; bei 8-Bit-Timern 256, bei 16-Bit-Timern 65536, bei CTC auch der im Compare-Register A eingestellte Maximalwert
bei allen Timern TCnO
Vergleichswert
bei Timern mit nur einem Vergleichswert: Übereinstimmung mit COMP-Wert erreicht
viele
TCnC
Vergleichswerte A, B, C
bei Timern mit mehreren Vergleichswerten: Übereinstimmung mit COMP-Wert erreicht
A,B: viele C wenige
TCnA... TCnC
Fangwert
bei Timer im Zählmodus: Übereinstimmung der gezählten Impulse mit CAPT-Wert erreicht
viele
TCnP
Zeichen empfangen
ein vollständiges Zeichen wurde empfangen und liegt im Eingangspuffer
viele
UnRX
Senderegister frei
Zeichen in Sendepuffer übernommen, frei für nächstes Zeichen
Schreibvorgang im EEPROM ist beendet, der nächste Schreibvorgang kann begonnen werden
fast alle
EERY
ADC
Wandlung komplett
eine Umwandlung der Eingangsspannung ist erfolgt, der Ergebniswert kann vom Datenregister abgeholt werden
alle mit ADC
ADC
Analog Komparator
Wechsel bei Vergleich
je nach Einstellung der ACIS-Bits erfolgt ein Interrupt bei jedem Polaritätätswechsel oder nur bei positiven bzw. negativen Flanken
alle
ANA ANAn
Weitere
(diverse)
(weitere Interrupt-Arten sind den Handbüchern zu entnehmen)
einige
Timer Interrupts
UART
PCIn
Jeder dieser Interrupts ist per Software ein- und ausschaltbar, auch während des Programmablaufs. Die Voreinstellung beim RESET ist, dass keiner dieser Interrupts ermöglicht ist. To the top of that page
AVR-Typen mit Ein-Wort-Vektoren Bedingt durch die unterschiedliche Ausstattung der AVR-Typen mit internem SRAM ist bei den AVR-Typen die Interrupt-Vektoren-Tabelle entweder auf Ein-Wort(RJMP) oder Zwei-Wort-Instruktionen (JMP) ausgelegt. AVR-Typen mit wenig SRAM kommen mit einem relativen Sprung aus und benötigen daher ein Wort pro Vektor. Jedes Programmwort in der Tabelle entspricht genau einem Interrupt. In der folgenden Tabelle sind die AVR-Typen mit ihren Interrupts aufgelistet. Die Adressen geben an, an welcher Stelle der Tabelle der Vektor liegt. Sie geben gleichzeitig die Priorität an: je niedriger die Adresse desto höher der Vorrang des Interrupts. Leere Kästen geben an, dass dieser Vektor nicht verfügbar ist. Typ/Adr
0000 0001 0002
0003 0004 0005 0006
0007
0008
0009 000A 000B 000C 000D 000E 000F
0010
0011
AT90S1200
RES INT0 TC0O ANA
AT90S2313
RES INT0 INT1
AT90S2323
RES INT0 TC0O
AT90S2343
RES INT0 TC0O
AT90S4433
RES INT0 INT1
TC1P TC1C TC1O TC0O SPIS
AT90S4434
RES INT0 INT1
TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS
AT90S8515
RES INT0 INT1
TC1P TC1A TC1B TC1O TC0O SPIS
AT90S8535
RES INT0 INT1
TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS
U0RX U0UD U0TX ADC EERY ANA
ATmega8
RES INT0 INT1
TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS
U0RX U0UD U0TX ADC EERY ANA TWI
0012
0013 0014
TC1P TC1C TC1O TC0O U0RX U0UD U0TX ANA
U0RX U0UD U0TX ADC EERY ANA U0RX U0UD U0TX ADC EERY ANA
U0RX U0UD U0TX ANA
ATmega8515 RES INT0 INT1
TC1P TC1A TC1B TC1O TC0O SPIS
ATmega8535 RES INT0 INT1
TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS
SPMR
U0RX U0UD U0TX ANA INT2 TC0C EERY SPMR U0RX U0UD U0TX ADC EERY ANA TWI
Arten und Reihenfolgen der Interrupts nur bei einigen wenigen Typen gleich sind, bei Umstellung von einem auf einen anderen Prozessor in der Regel die gesamte Vektortabelle umgestellt werden muss.
To the top of that page
AVR-Typen mit Zwei-Wort-Vektoren Bei AVR-Typen mit größerem SRAM kann ein Teil des verfügbaren Programmraums mit relativen Sprungbefehlen wegen der begrenzten Sprungdistanz nicht mehr erreicht werden. Diese Typen verwenden daher pro Vektor zwei Speicherworte, so dass der absolute Sprungbefehl JMP verwendet werden kann, der zwei Worte einnimmt. Die folgenden Tabellen listen die bei diesen AVR-Typen verfügbaren Interrupts auf. Da die Anzahl an Vektoren sehr groß ist, ist die Tabelle in jeweils 16 Vektoren unterteilt. AVR-Typ/Adr 0000 0002 0004 0006 0008 000A 000C 000E 0010
Path: Home => AVR-Überblick => Interrupt-Programmierung => Ablauf
Interruptprogrammierung in AVR Assembler Hier wird erläutert, wie interrupt-gesteuerte Programme grundsätzlich ablaufen müssen. Um das Prinzip zu verdeutlichen, wird zuerst ein einfacher Ablauf demonstriert. Dann wird gezeigt, was sich bei mehreren Interrupts ändert.
Warnung! Hier gibt es ein Konzept zu erlernen! Für alle, die bisher nur in Hochsprachen oder, in Assembler, nur schleifengesteuerte Programme gebaut haben, hier eine wichtige Warnung. Das Folgende wirft einiges Gewohntes über Programmierung über den Haufen. Mit den gewohnten Schemata im Kopf ist das Folgende schlicht nicht zu kapieren. Lösen Sie sich von dem Gewohnten und stellen Sie ausschließlich die Interrupts in den Vordergrund. Um diese dreht sich bei dieser Programmierung nun alles. Das heißt, er ist unser Ausgangspunkt für so ziemlich alles: ● ● ● ●
mit ihm beginnt jeder Ablauf, er steuert alle Folgeprozesse, sein Wohlergehen (Funktionieren, Eintreten, rechtzeitige Bearbeitung, etc.) ist alleroberste Maxime, alle anderen Programmteile horchen auf seinen Eintritt und sind reine Sklaven und Zulieferanten des Masters Interrupt.
Wenn Sie Schwierigkeiten und Unwohlsein beim Kapieren des Konzepts verspüren, versuchen Sie aus dem ungewohnten Dilemma auch nicht den Ausweg, in alte Strukturen zu verfallen. Ich garantiere Ihnen, dass eine Mischung aus Interrupts und gewohntem linearen Programmieren am Ende viel komplizierter ist als das neue Konzept in seiner Reinform anzuwenden: sie verheddern sich in diversen Schleifen, der nächste Interrupt kommt bestimmt, aber ob Ihr Hauptprogramm auf ihn hört, bleibt ein Rätsel. Wenn Sie das Interrupt-Konzept verinnerlicht haben, geht alles viel leichter und eleganter. Und garantiert noch schneller und eleganter als in der Hochsprache, die solche netten Erfindungen vor Ihnen verheimlicht, sie in vorgefertigte und oft unpassende Korsette einschnürt, so dass Sie die wahre Vielfalt des Möglichen gar nicht erst zu Gesicht bekommen.
Ablauf eines interruptgesteuerten Programms Den Standardablauf eines interrupt-gesteuerten Propgrammes in allgemeiner Form zeigt das folgende Bild.
Reset, Init Nach dem Einschalten der Betriebsspannung beginnt der Prozessorstart immer bei der Adresse 0000. An dieser Adresse MUSS ein Sprungbefehl an die Adresse des Hauptprogrammes stehen (in der Regel RJMP, bei großen ATmega kann auch JMP verwendet werden). Das Hauptprogramm setzt zu allererst die Stapeladresse in den bzw. die Stapelzeiger, weil alle interruptgesteuerten Programme den Stapel ZWINGEND benötigen. Dann initiiert das Hauptprogramm die Hardware, schaltet also Timer, ADWandler, Serielle Schnittstelle und was auch immer gebraucht wird ein und ermöglicht die entsprechenden Interrupts. Außerdem werden dann noch Register mit ihren richtigen Startwerten zu laden, damit alles seinen geregelten Gang geht. Dann ist noch wichtig, den Schlafmodus so zu setzen, dass der Prozessor bei Interrupts auch wieder aufwacht. Am Ende wird das Interrupt-Flag des Prozessors gesetzt, damit er auch auf ausgelöste Interrupts reagieren kann. Damit ist alles erledigt und der Prozessor wird mit der Instruktion SLEEP schlafen gelegt.
Interrupt Irgendwann schlägt nun Interrupt 3 zu. Der Prozessor ● ● ● ● ● ● ●
●
● ●
●
wacht auf, schaltet die Interrupts ab, legt den derzeitigen Wert des Programmzählers (die nächste Instruktion hinter SLEEP) auf dem Stapel ab, schreibt die Adresse 0003 in den Programmzähler, und setzt die Verarbeitung an dieser Adresse fort, dort steht ein Sprungbefehl zur Interrupt-Service-Routine an Adresse ISR3:, ISR3: wird bearbeitet und signalisiert durch Setzen von Bit 4 in einem Flaggenregister, dass eine Weiterverarbeitung des Ereignisses notwendig wird, ISR3 wird beendet, indem mit der Instruktion RETI die Ausgangsadresse vom Stapel geholt und in den Programmzähler geladen wird und schließlich die Interrupts wieder zugelassen werden, der nun aufgeweckte Prozessor setzt die Verarbeitung an der Instruktion hinter SLEEP fort, falls ISR3 das Flaggenbit gesetzt hat, kann jetzt die Nachbearbeitung des Ereignisses erfolgen und das Flaggenbit wieder auf Null gesetzt werden, ohne oder mit Nachbearbeitung wird der Prozessor auf jeden Fall wieder schlafen gelegt.
Der Ablauf zeigt, wie die Interrupt-Service-Routine mit dem Rest des Programmablaufs kommuniziert: über das Flaggenbit wird mitgeteilt, dass etwas Weiteres zu tun ist, was nicht innerhalb der Service-Routine erledigt wurde (z.B. weil es zu lange dauern würde, zu viele Resourcen benötigt, etc.).
Path: Home => AVR-Überblick => Interrupt-Programmierung => Interrupts und Ressourcen
Interrupts und Ressourcen Interrupts haben den Vorteil, dass sie nur dann auftreten und bearbeitet werden müssen, wenn ein bestimmtes Ereignis tatsächlich eintritt. Selten ist vorhersagbar, wann das Ereignis genau eintreten wird. Sogar mehrere unterschiedliche Ereignisse können gleichzeitig oder kurz hintereinander eintreten. Zum Beispiel kann der Timer gerade einen Overflow haben und der AD-Wandler ist gerade mit einer Messumwandlung fertig. Diese Eigenart, dass sie jederzeit auftreten können, macht einige besondere Überlegungen beim Programmieren erforderlich, die in Hochsprachen entweder nicht vorkommen oder vom Compiler eigenständig erledigt werden. In Assembler müssen wir selber denken, und werden dafüfür mit einem schlanken, sauschnellen und zuverlässigen Programm belohnt. Die folgenden Überlegungen nimmt uns AssemblerProgrammierern keiner ab.
Klare Ressourcenzuordnung Das Statusregister als sensible Ressource Das Zuschlagen eines Interrupts kann jederzeit und überall, nur nicht innerhalb von Interrupt-ServiceRoutinen erfolgen. Erfolgt innerhalb der Service-Routine eine Beeinflussung von Flaggen des Statusregisters, was nahezu immer der Fall ist, dann kann das im Hauptprogramm-Loop üble Folgen haben. Plötzlich ist das Zero- oder Carry-Flag im Statusregister gesetzt, nur weil zwischendurch der Interrupt zuschlug. Nichts funktioniert mehr so, wie man es erwartet hat. Aber das nur machmal und nicht immer. Ein solches Programm verhält sich eher wie ein Zufallsgenerator als wie ein zuverlässiges Stück Software. AVR-Prozessoren haben keinen zweiten Satz Statusregister, auf den sie bei einem Interrupt umschalten können. Deshalb muss das Statusregister vor einer Veränderung in der Interrupt-ServiceRoutine gesichert und vor deren Beendigung wieder in seinen Originalzustand versetzt werden. Dazu gibt es drei Möglichkeiten: Nr. Sicherung in
1
2
3
Code
Zeitbedarf Takte
Register
in R15,SREG 2 [...] out SREG,R15
Stapel
push R0 in R0,SREG [...] 6 out SREG,R0 pop R0
SRAM
sts $0060,R0 in R0,SREG [...] 6 out SREG,R0 lds R0,$0060
Vorteile
Schnell
Nachteile Registerverbrauch
Kein RegisterLahm verbrauch
Damit ist die Auswahl und Priorität klar. Alles entscheidend ist, ob man Register satt verfügbar hat oder machen kann. Oft verwendet man für eine einzelne Flagge gerne das T-Bit im Statusregister. Da gibt es bei allen drei Methoden einen üblen Fallstrick: Jede Veränderung am T-Bit wird wieder überschrieben, wenn am Ende der Routine der Originalstatus des Registers wieder hergestellt wird. Bei Methode 1 muss also Bit 7 des R15 gesetzt werden, damit das T-Bit nach der Beendigung der Routine als gesetzt resultiert. Obacht: Kann einige Stunden Fehlersuche verursachen!
Verwendung von Registern Angenommen, eine Interrupt-Service-Routine mache nichts anderes als das Herunterzählen eines Zählers. Dann ist klar, dass das Register, in dem der Zähler untergebracht ist (z.B. mit .DEF rCnt = R17 definiert), zu nichts anderem eingesetzt werden kann. Jede andere Verwendung von R17 würde den Zählrhythmus empfindlich stören. Dabei hülfe es bei einer Verwendung in der Hauptprogramm-Schleife auch nicht, wenn wir den Inhalt des Registers mit PUSH rCnt auf dem Stapel ablegen würden und nach seiner Verwendung den alten Zustand mit POP rCnt wieder herstellen würden. Irgendwann schlägt der Interrupt genau zwischen dem PUSH und dem POP zu, und dann haben wir den Salat. Das passiert vielleicht nur alle paar Minuten mal, ein schwer zu diagnostizierender Fehler. Wir müssten dann schon vor der Ablage auf dem Stapel alle Interrupts mit CLI abschalten und nach der Verwendung und Wiederherstellung des Registers die Interrupts wieder mit SEI zulassen. Liegen zwischen Ab- und Anschalten der Interrupts viele Hundert Instruktionen, dann stauen sich die zwischenzeitlich aufgelaufenen Interrupts und können verhungern. Wenn zwischendurch der Zähler zwei mal übergelaufen ist, dann geht unsere Uhr danach etwas verkehrt, weil aus den zwei Ereignissen nur ein Interrupt geworden ist. Innerhalb einer anderen Interrupt-Service-Routine können wir rCnt auf diese Weise (also mit PUSH und POP) verwenden, weil diese nicht durch andere Interrupts unterbrochen werden kann. Allerdings sollte man sich bewusst sein, dass jedes PUSH- und POP-Pärchen vier weitere Taktimpulse verschwendet. Wer also satt Zeit hat, hat auch Register en masse. Wer keine Register übrig hat, kommt um so was vielleicht nicht herum. Manchmal MUSS auf ein Register oder auf einen Teil eines Registers (z.B. ein Bit) sowohl von innerhalb als auch von außerhalb einer Interrupt-Service-Routine zugegriffen werden, z.B. weil die ISR dem Hauptprogramm-Loop etwas mitzuteilen hat. In diesen Fällen muss man sich ein klares Bild vom Ablauf verschaffen. Es muss klar sein, dass Schreibzugriffe sich nicht gegenseitig stören oder blockieren. Es muss dann auch klar sein, dass dasselbe Register bei zwei nacheinderfolgenden Lesevorgängen bereits von einem weiteren Interrupt verändert worden sein kann. Liegen die zwei Zeitpunkte längere Zeit auseinander, dann ist es je nach dem Timing des Gesamtablaufes fast zwingend, dass irgendwann ein Konflikt auftritt. An einem Beispiel: sowohl der Timer als auch der ADC haben dem Hauptprogramm etwas mitzuteilen und setzen jeweils ein Bit eines Flaggenregisters rFlag. Also etwa so: Isr1: [...] sbr rFlag,1<
●
●
Register möglichst klar der alleinigen Verwendung innerhalb und außerhalb von InterruptService-Routinen zuordnen. Jede gemeinsame Nutzung von Registern intensiv auf mögliche Konflikte hin durchdenken. Vorher denken vermeidet die Fehlersuche hinterher. Vorsicht bei der gemeinsamen Nutzung von Doppelregistern. Interrupts blockieren! Und hinterher nicht vergessen, sie wieder zuzulassen.
Tutorial für das Erlernen der Assemblersprache von
AVR-Einchip-Prozessoren AT90Sxxxx von ATMEL anhand geeigneter praktischer Beispiele.
Hardware auf dem STK board
(Die Links auf die *.asm-Dateien zum Herunterladen mit gedrückter Shift-Taste anklicken.) HTMLFormat
ASMFormat
Erläuterung zum Inhalt
Das EEPROM lesen und beschreiben. Beim Programmieren wird ein im EEPROM definierter Zähler auf Null gesetzt. Mit jedem Restart wird dieser um Eins erhöht und sein Inhalt auf den LEDs in Hex ausgegeben. Siehe EEPROM EEPROM unbedingt die Hinweise am Ende dieses Programmes, da beim Programmieren und beim Verifizieren auf dem Board Fehlermeldungen auftreten werden! RAM
Testet exteres RAM auf dem STK-200 board zählt RAM-Positionen durch Schreiben von AA und 55 in die einzelnen Positionen, verifiziert den Inhalt und gibt bei erfolgreichem Test das MSB der obersten RAM-Adresse auf den LEDs aus
LCD
Löscht das LCD-Display und gibt in Zeile 1 und 2 einen Test-Text aus. Die nötigen LCD-Grund-Routinen sind sinnigerweise in einer eigenen IncludeDatei untergebracht, die für alle möglichen Zwecke weiterverwendet werden kann.
SIO
SIO
Sendet auf der Schnittstelle mit 9k6 8N1 einen Text und echot dann eingehende Zeichen zurück. Für die Hardware (Kabel, Stecker, etc.) und das Terminal- Programm bitte noch diesen Text lesen!
SIO
SIO
Ähnlich: Sendet die Hexadezimalverschlüsselung der empfangenen Zeichen zurück. Die gleichen Hinweise wie oben treffen zu (siehe Text!)
; Demonstriert den Gebrauch des EEPROMs ; ; Beim Programmieren wird ein im EEPROM definierter Zähler ; auf Null gesetzt. Mit jedem Restart wird dieser um Eins ; erhöht und sein Inhalt auf den LEDs in Hex ausgegeben. ; Siehe unbedingt die Hinweise am Ende dieses Programmes! ; .NOLIST .INCLUDE "8515def.inc" .LIST ;
; Konstanten definieren ; .equ cnt=$0000 ; Adresse des Zählers im EEPROM ;
; Reset-/Interrupt-Vektoren RJMP main ; Sprung zum Hauptprogramm ; main: LDI mpr,$FF ; alle Bits von Port B sind Output OUT DDRB,mpr
; Programm holt ein Byte aus dem Epromspeicher LDI mpr,LOW(cnt) ; Übergib die zu lesende Adresse OUT EEARL,mpr ; im EEPROM an den EEPROM-Port LDI mpr,HIGH(cnt) ; Low/High-Byte wird separat OUT EEARH,mpr ; übergeben, da 512 Byte verfügbar SBI EECR,EERE ; Setze des Read-Enable-Bit EERE im ; EEPROM-Control-Register EECR IN neu,EEDR ; Lese das Byte aus dem EEPROM-Speicher ; Erhöhe den Zähler und gebe ihn in das EEPROM zurück INC neu wart:
; Wenn das EEPROM nicht fertig ist, muss erst gewartet werden SBIC EECR,1 ; Frage Bit 1 im EEPROM-Control-Register RJMP wart ; ab und wiederhole bis EEPROM ready meldet ; Da die EEPROM-Adresse nicht geändert werden muss, entfällt hier ; die Übergabe der EEPROM-Schreibadresse in EEARL/EEARH OUT EEDR,neu ; Neuen Wert an das EEPROM-Datenregister ; Die beiden Schreibbefehle dürfen nicht unterbrochen werden, da ; die beiden Schreibbefehle sicherheitshalber nach vier Befehlen ; von der Hardware abgebrochen werden. Daher müssen die Interrupts ; (hier nicht eingeschaltet) hier abgeschaltet werden! CLI
; Jetzt kommen die beiden Schreibbefehle: SBI EECR,EEMWE ; Schaltet EEPROM Master Write Enable ein SBI EECR,EEWE ; Löst Schreibvorgang im EEPROM aus ; In den folgenden ca. 1,5 Millisekunden wird das Byte ins EEPROM ; geschrieben. Das stört uns aber nur dann, wenn wieder das EEPROM ; verwendet werden soll. Hier nicht: wir schreiben den invertierten ; Inhalt des Zählers in den Port B an die LEDs und beenden das ; Programm mit einer unendlichen Schleife: COM neu ; invertieren OUT PORTB,neu ; an Port B loop: RJMP loop ; unendlich warten
; Hier beginnt nun das Nullsetzen des EEPROMs beim Programmieren ; Zuerst wird dem Assembler mitgeteilt, dass der folgende Code in ; das EEPROM gehört: .ESEG
; Demonstriert den Gebrauch des EEPROMs ; ; Beim Programmieren wird ein im EEPROM definierter Zähler ; auf Null gesetzt. Mit jedem Restart wird dieser um Eins ; erhöht und sein Inhalt auf den LEDs in Hex ausgegeben. ; Siehe unbedingt die Hinweise am Ende dieses Programmes! ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Konstanten definieren ; .equ cnt=$0000 ; Adresse des Zählers im EEPROM ; ; Register definieren .def mpr=R16 ; Universalregister .def neu=R17 ; Zählerwert-Zwischenspeicher ; ; Reset-/Interrupt-Vektoren rjmp main ; Sprung zum Hauptprogramm ; main: ldi mpr,$FF ; alle Bits von Port B sind Output out DDRB,mpr ; Programm holt ein Byte aus dem Epromspeicher ldi mpr,LOW(cnt) ; Übergib die zu lesende Adresse out EEARL,mpr ; im EEPROM an den EEPROM-Port ldi mpr,HIGH(cnt) ; Low/High-Byte wird separat out EEARH,mpr ; übergeben, da 512 Byte verfügbar sbi EECR,EERE ; Setze des Read-Enable-Bit EERE im ; EEPROM-Control-Register EECR in neu,EEDR ; Lese das Byte aus dem EEPROM-Speicher ; Erhöhe den Zähler und gebe ihn in das EEPROM zurück inc neu wart: ; Wenn das EEPROM nicht fertig ist, muss erst gewartet werden sbic EECR,1 ; Frage Bit 1 im EEPROM-Control-Register rjmp wart ; ab und wiederhole bis EEPROM ready meldet ; Da die EEPROM-Adresse nicht geändert werden muss, entfällt hier ; die Übergabe der EEPROM-Schreibadresse in EEARL/EEARH out EEDR,neu ; Neuen Wert an das EEPROM-Datenregister ; Die beiden Schreibbefehle dürfen nicht unterbrochen werden, da ; die beiden Schreibbefehle sicherheitshalber nach vier Befehlen ; von der Hardware abgebrochen werden. Daher müssen die Interrupts ; (hier nicht eingeschaltet) hier abgeschaltet werden! cli ; Jetzt kommen die beiden Schreibbefehle: sbi EECR,EEMWE ; Schaltet EEPROM Master Write Enable ein sbi EECR,EEWE ; Löst Schreibvorgang im EEPROM aus ; In den folgenden ca. 1,5 Millisekunden wird das Byte ins EEPROM ; geschrieben. Das stört uns aber nur dann, wenn wieder das EEPROM ; verwendet werden soll. Hier nicht: wir schreiben den invertierten ; Inhalt des Zählers in den Port B an die LEDs und beenden das ; Programm mit einer unendlichen Schleife: com neu ; invertieren out PORTB,neu ; an Port B loop: rjmp loop ; unendlich warten ; Hier beginnt nun das Nullsetzen des EEPROMs beim Programmieren ; Zuerst wird dem Assembler mitgeteilt, dass der folgende Code in ; das EEPROM gehört: .ESEG ; Jetzt kommt der EEPROM-Inhalt: .DB $00 ; Ein Byte mit Null ; Das war es. ; Beim Programmieren muss der Inhalt des EEPROM-Files TESTEEP.EEP ; separat geladen und mitprogrammiert werden! Nicht vergessen! ; Da beim Verifizieren nach dem Programmieren zwischendurch der ; Reset aufgehoben wird und der Prozessor schon einmal durch das ; Programm läuft, geht das Verifizieren des EEPROM-Inhaltes in ; jedem Fall schief. Das gilt auch für jeden Lesezugriff auf das ; EEPROM mit dem Programmiergerät! ; Der Befehl im ISP, den Prozessor neu zu starten, löst ebenfalls ; Mehrfachstarts aus. Durch Aus- und Einschalten vom Board aus kann ; wirklich getestet werden, ob die Zählerei exakt funktioniert. http://www.avr-asm-tutorial.net/avr_de/quellen/testeep.asm1/20/2009 7:37:09 PM
AVR-Hardware, Externes RAM testen
Pfad: Home => AVR-Übersicht => Hardware => RAM ; ************************************************
; TestRam testet exteres RAM auf dem STK-200 board ; zählt RAM-Positionen durch Schreiben von AA und ; 55 in die einzelnen Positionen, verifiziert den ; Inhalt und gibt bei erfolgreichem Test das MSB ; der obersten RAM-Adresse auf den LEDs aus ; ************************************************
; Erläuterungen zum RAM-Zugriff ; Der MUX-IC 74HC573 und das RAM 62256-70 müssen bestückt sein. ; Port A ist abwechselnd Adress-Bus für das LSB der RAM-Adresse ; und den Datenbus (Multiplex) ; Port C ist der obere Adress-Bus ; Port D Bit 6 ist /RD (Read) am SRAM ; Bit 7 ist /WR (Write) am SRAM ; Die Leitung ALE (Adress Latch Enable) wird verwendet, Pin 30 ; Wenn das gesamte externe RAM ok ist, dann müssen die LEDs am ; Ende alle an sein bis auf das achte. Die höchste Adresse ; ist dann bei einem 32k-SRAM 7FFF.
; ************************************************ ; TestRam testet exteres RAM auf dem STK-200 board ; zählt RAM-Positionen durch Schreiben von AA und ; 55 in die einzelnen Positionen, verifiziert den ; Inhalt und gibt bei erfolgreichem Test das MSB ; der obersten RAM-Adresse auf den LEDs aus ; ************************************************ ; Erläuterungen zum RAM-Zugriff ; Der MUX-IC 74HC573 und das RAM 62256-70 müssen bestückt sein. ; Port A ist abwechselnd Adress-Bus für das LSB der RAM-Adresse ; und den Datenbus (Multiplex) ; Port C ist der obere Adress-Bus ; Port D Bit 6 ist /RD (Read) am SRAM ; Bit 7 ist /WR (Write) am SRAM ; Die Leitung ALE (Adress Latch Enable) wird verwendet, Pin 30 ; Wenn das gesamte externe RAM ok ist, dann müssen die LEDs am ; Ende alle an sein bis auf das achte. Die höchste Adresse ; ist dann bei einem 32k-SRAM 7FFF. ; 8515-Bibliothek laden .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; Register .def mp = R16 ; Multi-Purpose .def soll = R17 ; enthält abwechselnd AA und 55 ; Reset-/Interrupt-Vektor rjmp main ; Unterprogramme ; Hauptprogramm main: ldi mp,LOW(RAMEND) ;Initiate Stackpointer out SPL,mp ; wegen verwendeten Unterprogrammen ldi mp,HIGH(RAMEND) out SPH,mp ; Port B steuert die LEDs an ldi mp,0xFF ; Alles Ausgänge out DDRB,mp ; an Datenrichtungsregister Port B ; Geht wieder nicht complilieren: ; SBI MCUCR,SRE ; Setze das SRAM-Bit im MCU-Register ; Ersatz: in mp,MCUCR ; Lese MCU-Control-Register ori mp,0x80 ; Setze Bit 7 out MCUCR,mp ; Wenn das verwendete SRAM langsamer als 70 ns ist und deshalb ; einen zusätzlichen WAIT-state haben muss, dann muss ; auch das Bit 6 im Port MCUCR gesetzt sein, d.h. der ORI-Befehl ; lautet dann: ORI mp,0xC0 ; Setze Bit 7 und Bit 6 ldi XL,LOW(RAMEND) ; Register XL ist R26, LSB RAM-Adresse ldi XH,HIGH(RAMEND) ; Register XH ist R27, MSB RAM-Adresse ldi soll,0b10101010 ; Bitmuster für Test loop: inc XL ; Erhöhe Adresszähler um 1 brne check ; Nicht Null, MSB ok inc XH ; Erhöhe MSB der Adresse breq check ; Null, MSB übergelaufen, raus check: st X,soll ; schreibe Bitmuster in SRAM ld mp,X ; Lese die gleiche SRAM-Adresse cp mp,soll ; Vergleiche gelesen mit geschrieben brne Zurueck ; Nicht gleich, raus com soll ; Drehe alle Bits im Bitmuster um (XOR FF) st X,soll ; Noch mal mit 0101.0101 ld mp,X ; Auslesen cp mp,soll ; Vergleichen brne Zurueck ; Nicht gleich, raus com soll ; wieder umdrehen rjmp loop ; Weitermachen Zurueck: ld mp,-X ; Pointer X um eins zurücksetzen, Ergebnis egal Ungleich: com XH ; XOR FF der obersten RAM-Adresse out PORTB,XH ; auf die LEDs ende: rjmp ende ; Loop für immer http://www.avr-asm-tutorial.net/avr_de/quellen/testram.asm1/20/2009 7:37:13 PM
AVR-Hardware, LCD-Anzeige testen
Pfad: Home => AVR-Übersicht => Hardware => LCD ; ************************************************
; TestLcd löscht das LCD-Display und gibt in Zeile 1 und 2 einen Test-Text aus. ; ************************************************
; Erläuterungen zum LCD-Zugriff ; Hardware: ; Die LCD-Routinen setzen voraus, dass eine entsprechende ; LCD angeschlossen ist. Das LCD-Board kann mit einer ; Buchsenleiste nach unten hin bestückt werden, so ; dass sie direkt auf das STK200 aufgesetzt werden kann.
; ;Der aufgelötete Connector ...
; ; ... kann direkt auf das Board aufgesteckt werden und ...
; ; ... passt von der Anschlussfolge her schon genau. ; Die 2-Zeilen-LCD-Anzeige muss auf das Board ; aufgesteckt sein, alle erforderliche Hardware ist ; schon auf dem Board vorhanden. Es genügt eine 14; polige einreihige Buchsenleiste, die von der Löt; seite her auf die LCD aufgelötet und in die 14; polige Steckerleiste auf dem STK-200-Board gesteckt ; wird. Das war's an Hardware. ; Ach ja: der Kontrastregler muss noch justiert werden ; sonst ist nichts zu sehen. Also ohne Ansteuerung so ; einstellen, dass die schwarzen Kästchen gerade ; verschwinden. ; Software: Die Ansteuerung erfolgt in diesem Beispiel ; wie Memory (memory-mapped). Das hat den Vorteil, ; dass die gleichen Befehle wie für Memory benutzt ; werden können, das Herumeiern mit den verschiedenen ; Ports entfällt und gleichzeitig SRAM am Board be; trieben werden kann. (Die Beispiel-Programme auf ; dem Internet-Server sind alle I/O-mapped und ver; tragen sich nicht mit gleichzeitigem Memory-Betrieb!). ; Die Adresse der LCD-Steuerung ist $8000 für die ; Befehle und $C000 für den Zugriff auf den Zeichen; generator und die Displayzeilen (jeweils Lesen und ; Schreiben. ; Da bei Memory-Betrieb der Zugriff sehr schnell er; folgen würde, muss das WAIT-Bit im MCUCR-Register ; gesetzt werden. Das macht zwar den RAM-Zugriff auch ; langsamer, aber das kann verschmerzt werden. Für ; schnelle RAM-Zugriffe kann das Bit zeitweise wieder ; Null gesetzt werden, dann ist der Nachteil weg. ; Verwendete Ports: Es werden die gleichen Ports verwendet ; wie beim RAM-Zugriff, also: ; Port A ist abwechselnd Adress-Bus für das LSB der RAM; Adresse und den Datenbus (Multiplex) des SRAM, ; für die LCD werden diese nicht verwendet, sind ; aber bei Memory-Mapping blockiert! ; Port C ist der obere Adress-Bus beim SRAM, verwendet ; von der LCD werden Bit 7 (Adresse) und 6 (RS; Signal an der LCD). ; Port D Bit 6 ist /RD (Read) am SRAM, nicht benutzt von LCD ; Bit 7 ist /WR (Write) am SRAM und an der LCD ; ; Ablauf des Testprogrammes: Nacheinander werden folgende ; Schritte durchlaufen: ; 0. Warten, bis die LCD nicht mehr das BUSY-Flag gesetzt hat ; 1. Löschen des Displays ; 2. Setzen der Übertragungsart (8-Bit), des festen Anzeige; fensters und anderer Eigenschaften des Displays ; 3. Ausgabe von zwei Testzeilen mit Text. ; Bei jedem Schritt wird vorher eine Leuchtdiode angemacht, ; so dass bei einem Scheitern an der Nummer der Lampe der ; fehlerhafte Schritt erkannt werden kann. Wenn alles durch; laufen wird, sollte die LED 4 an sein und der Text auf der ; LCD zu lesen sein (eventuell Kontrast justieren). ; ; Aufbau der Software: Alle Ansteuerungen der LCD erfolgen ; als Unterprogramme, die leicht in andere Programme über; tragen werden können. Verwendete Register sind: ; mp: Allround-Register zur Übergabe von Werten ; R26/R27: XL/XH, 16-Bit-Adresse für den ST X und LD X-Befehl ; ;
; Register .def mp = R16 ; Multi-Purpose .def test = R17 ; Zählt die einzelnen Testphasen
; Reset-/Interrupt-Vektor RJMP main
; Unterprogramme zur LCD-Ansteuerung LcdWt: ; Warte bis das LCD-Busy-Flag gelöscht ist LDI XH,0x80 ; Oberes Byte der RAM-Adresse der LCD LDI XL,0x00 ; Unteres Byte der RAM-Adresse der LCD LD mp,X ; Lese Busy flag und AC-Adresse ROL mp ; Schiebe Bit 7 in das Carry-Flag BRCS LcdWt ; Wenn Eins, dann noch busy, weiter RET LcdCl: ; Lösche die LCD LDI mp,0x01 ; Der Löschbefehl für die LCD lautet 01h LcdBef: ; Gib den Befehl in mp an die LCD, wenn diese bereit ist PUSH mp ; Der Befehl in mp wird noch gebraucht, auf Stapel RCALL LcdWt ; Warte, bis Anzeige Befehle entgegen nimmt POP mp ; Nehme Befehl wieder vom Stapel ST X,mp ; Gib ihn an die LCD weiter RET ; Ende des Unterprogrammes LcdInit: ; Initiiert die Arbeitsweise der LCD LDI mp,0b00111000 ; 8-Bit-Übertragung, nicht vier Bit RCALL LcdBef ; an LCD geben LDI mp,0b00000110 ; Increment, Display freeze RCALL LcdBef ; an LCD geben LDI mp,0b00010000 ; Cursor move, nicht shift RCALL LcdBef ; an LCD geben RET ; Zurück LcdBu: ; Schreibe den Buchstaben in mp in die LCD PUSH mp ; Der Buchstabe wird noch gebraucht, auf Stapel RCALL LcdWt ; Warte bis die LCD wieder kann POP mp ; Hole Buchstaben wieder vom Stapel LDI XH,0xC0 ; Der Speicher der LCD ist auf Adresse 0C00h ST X,mp ; Schreibe Buchstabe auf LCD RET ; Zurück aus dem Unterprogramm LcdTs: ; Schreibe das Wort "Test" in die aktuelle Zeile PUSH mp ; Rette die Zeilenadresse in mp RCALL LcdWt ; Warte, bis Display verfügbar ist POP mp ; Stelle Zeilenadresse wieder her ORI mp,0x80 ; Setze Bit 7 der Zeilenadresse RCALL LcdBef ; Gib den Befehl an die LCD weiter LDI mp,'T' ; Buchstabe T RCALL LcdBu ; Schreibe Buchstabe in mp auf LCD LDI mp,'e' ; Buchstabe e RCALL LcdBu ; Schreibe auf LCD LDI mp,'s' ; Buchstabe s RCALL LcdBu ; Schreibe auf LCD LDI mp,'t' ; Buchstabe t RCALL LcdBu ; Schreibe auf LCD RET ; Fertig, Zurück LcdTst: ; Schreibe das Wort "Test" in Zeile 1 und 2 LDI mp,0x00 ; Zeile 1 beginnt an der Adresse 00h im Display RCALL LcdTs ; Schreibe Test in Zeile 1 LDI mp,0x40 ; Zeile 2 beginnt an der Adresse 40h im Display RCALL LcdTs ; Schreibe Test in Zeile 2 RCALL LcdWt ; Warten bis LCD fertig ist LDI mp,0b00001111 ; Befehl für Display On, Cursor On und Blink RCALL LcdBef ; Gib Befehl an die Anzeige weiter RET ; Zurück aus dem Unterprogramm
; Port B steuert die LEDs an LDI mp,0xFF ; Alles Ausgänge OUT DDRB,mp ; an Datenrichtungsregister Port B ; Geht wieder nicht complilieren: ; SBI MCUCR,SRE ; Setze das SRAM-Bit im MCU-Register ; Ersatz: IN mp,MCUCR ; Lese MCU-Control-Register ORI mp,0xC0 ; Setze Bit 7 (SRAM) und Bit 6 (WAIT-STATE) OUT MCUCR,mp
; ************************************************ ; TestLcd löscht das LCD-Display und gibt in Zeile ; 1 und 2 einen Test-Text aus. ; ************************************************ ; Erläuterungen zum LCD-Zugriff ; Hardware: Die 2-Zeilen-LCD-Anzeige muss auf das Board ; aufgesteckt sein, alle erforderliche Hardware ist ; schon auf dem Board vorhanden. Es genügt eine 14; polige einreihige Buchsenleiste, die von der Löt; seite her auf die LCD aufgelötet und in die 14; polige Steckerleiste auf dem STK-200-Board gesteckt ; wird. Das war's an Hardware. ; Software: Die Ansteuerung erfolgt in diesem Beispiel ; wie Memory (memory-mapped). Das hat den Vorteil, ; dass die gleichen Befehle wie für Memory benutzt ; werden können, das Herumeiern mit den verschiedenen ; Ports entfällt und gleichzeitig SRAM am Board be; trieben werden kann. (Die Beispiel-Programme auf ; dem Internet-Server sind alle I/O-mapped und ver; tragen sich nicht mit gleichzeitigem Memory-Betrieb!). ; Die Adresse der LCD-Steuerung ist $8000 für die ; Befehle und $C000 für den Zugriff auf den Zeichen; generator und die Displayzeilen (jeweils Lesen und ; Schreiben. ; Da bei Memory-Betrieb der Zugriff sehr schnell er; folgen würde, muss das WAIT-Bit im MCUCR-Register ; gesetzt werden. Das macht zwar den RAM-Zugriff auch ; langsamer, aber das kann verschmerzt werden. Für ; schnelle RAM-Zugriffe kann das Bit zeitweise wieder ; Null gesetzt werden, dann ist der Nachteil weg. ; Verwendete Ports: Es werden die gleichen Ports verwendet ; wie beim RAM-Zugriff, also: ; Port A ist abwechselnd Adress-Bus für das LSB der RAM; Adresse und den Datenbus (Multiplex) des SRAM, ; für die LCD werden diese nicht verwendet, sind ; aber bei Memory-Mapping blockiert! ; Port C ist der obere Adress-Bus beim SRAM, verwendet ; von der LCD werden Bit 7 (Adresse) und 6 (RS; Signal an der LCD). ; Port D Bit 6 ist /RD (Read) am SRAM, nicht benutzt von LCD ; Bit 7 ist /WR (Write) am SRAM und an der LCD ; ; Ablauf des Testprogrammes: Nacheinander werden folgende ; Schritte durchlaufen: ; 0. Warten, bis die LCD nicht mehr das BUSY-Flag gesetzt hat ; 1. Löschen des Displays ; 2. Setzen der Übertragungsart (8-Bit), des festen Anzeige; fensters und anderer Eigenschaften des Displays ; 3. Ausgabe von zwei Testzeilen mit Text. ; Bei jedem Schritt wird vorher eine Leuchtdiode angemacht, ; so dass bei einem Scheitern an der Nummer der Lampe der ; fehlerhafte Schritt erkannt werden kann. Wenn alles durch; laufen wird, sollte die LED 4 an sein und der Text auf der ; LCD zu lesen sein (eventuell Kontrast justieren). ; ; Aufbau der Software: Alle Ansteuerungen der LCD erfolgen ; als Unterprogramme, die leicht in andere Programme über; tragen werden können. Verwendete Register sind: ; mp: Allround-Register zur Übergabe von Werten ; R26/R27: XL/XH, 16-Bit-Adresse für den ST X und LD X-Befehl ; ; ; 8515-Bibliothek laden .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; Register .def mp = R16 ; Multi-Purpose .def test = R17 ; Zählt die einzelnen Testphasen ; Reset-/Interrupt-Vektor rjmp main ; Unterprogramme zur LCD-Ansteuerung LcdWt: ; Warte bis das LCD-Busy-Flag gelöscht ist ldi XH,0x80 ; Oberes Byte der RAM-Adresse der LCD ldi XL,0x00 ; Unteres Byte der RAM-Adresse der LCD ld mp,X ; Lese Busy flag und AC-Adresse rol mp ; Schiebe Bit 7 in das Carry-Flag brcs LcdWt ; Wenn Eins, dann noch busy, weiter ret LcdCl: ; Lösche die LCD ldi mp,0x01 ; Der Löschbefehl für die LCD lautet 01h LcdBef: ; Gib den Befehl in mp an die LCD, wenn diese bereit ist push mp ; Der Befehl in mp wird noch gebraucht, auf Stapel rcall LcdWt ; Warte, bis Anzeige Befehle entgegen nimmt pop mp ; Nehme Befehl wieder vom Stapel st X,mp ; Gib ihn an die LCD weiter ret ; Ende des Unterprogrammes LcdInit: ; Initiiert die Arbeitsweise der LCD ldi mp,0b00111000 ; 8-Bit-Übertragung, nicht vier Bit rcall LcdBef ; an LCD geben ldi mp,0b00000110 ; Increment, Display freeze rcall LcdBef ; an LCD geben ldi mp,0b00010000 ; Cursor move, nicht shift rcall LcdBef ; an LCD geben ret ; Zurück LcdBu: ; Schreibe den Buchstaben in mp in die LCD push mp ; Der Buchstabe wird noch gebraucht, auf Stapel rcall LcdWt ; Warte bis die LCD wieder kann pop mp ; Hole Buchstaben wieder vom Stapel ldi XH,0xC0 ; Der Speicher der LCD ist auf Adresse 0C00h st X,mp ; Schreibe Buchstabe auf LCD ret ; Zurück aus dem Unterprogramm LcdTs: ; Schreibe das Wort "Test" in die aktuelle Zeile push mp ; Rette die Zeilenadresse in mp rcall LcdWt ; Warte, bis Display verfügbar ist pop mp ; Stelle Zeilenadresse wieder her ori mp,0x80 ; Setze Bit 7 der Zeilenadresse rcall LcdBef ; Gib den Befehl an die LCD weiter ldi mp,'T' ; Buchstabe T rcall LcdBu ; Schreibe Buchstabe in mp auf LCD ldi mp,'e' ; Buchstabe e rcall LcdBu ; Schreibe auf LCD ldi mp,'s' ; Buchstabe s rcall LcdBu ; Schreibe auf LCD ldi mp,'t' ; Buchstabe t rcall LcdBu ; Schreibe auf LCD ret ; Fertig, Zurück LcdTst: ; Schreibe das Wort "Test" in Zeile 1 und 2 ldi mp,0x00 ; Zeile 1 beginnt an der Adresse 00h im Display rcall LcdTs ; Schreibe Test in Zeile 1 ldi mp,0x40 ; Zeile 2 beginnt an der Adresse 40h im Display rcall LcdTs ; Schreibe Test in Zeile 2 ldi mp,0b00001111 ; Befehl für Display On, Cursor On und Blink rcall LcdBef ; Gib Befehl an die Anzeige weiter ret ; Zurück aus dem Unterprogramm ; Hauptprogramm main: ldi mp,LOW(RAMEND) ;Initiate Stackpointer out SPL,mp ; wegen verwendeten Unterprogrammen ldi mp,HIGH(RAMEND) out SPH,mp ; Port B steuert die LEDs an ldi mp,0xFF ; Alles Ausgänge out DDRB,mp ; an Datenrichtungsregister Port B ; Geht wieder nicht complilieren: ; SBI MCUCR,SRE ; Setze das SRAM-Bit im MCU-Register ; Ersatz: in mp,MCUCR ; Lese MCU-Control-Register ori mp,0xC0 ; Setze Bit 7 (SRAM) und Bit 6 (WAIT-STATE) out MCUCR,mp ; Hier startet der eigentliche Test der LCD ldi test,0xFE ; Setze Bit 0 auf 0 = Lampe 0 an out PORTB,test ; Lampe 0 an rcall LcdWt ; Einfach nur warten, ;falls ein Fehler passiert, bleibt er hier stecken sec ; Setze Carry Flag 1 rol test ; Schiebe die Null eins links, Lampe 1 an out PORTB,test rcall LcdCl ; Lösche die LCD sec ; Carry wieder an für Lampe 2 an rol test ; Und reinschieben in test out PORTB,test rcall LcdInit ; Einige Initiierungen der Arbeitsweise sec ; Setze Carry Flag 1 rol test ; Schiebe weiter, Lampe 3 an out PORTB,test rcall LcdTst ; Schreibe die beiden Testzeilen sec ; Carry wieder 1 rol test ; Test ist zu Ende, Lampe 4 an out PORTB,test ende: rjmp ende ; Loop für immer http://www.avr-asm-tutorial.net/avr_de/quellen/testlcd.asm1/20/2009 7:37:20 PM
; ******************************************** ; * LCD-Interface routines for multi-purpose * ; * use with the ATMEL STK200 board, Version * ; * 0.1 Beta, (C) 1999 Gerhard Schmidt * ; * Report bugs to [email protected] * ; ******************************************** ; ; Purpose: ; Include file for the AVR assembler ; Supplies the common routines to drive an ; LCD connected to the ATMEL STK200 board ; Works best and is compatible to external ; SRAM on that board (up to 32 kB) ; ; Requires: ; mpr ... Multipurpose register, anything ; between R16 and R31, unchanged ; after all routines ; Memory-mapped operation of the LCD ; Setup of the software stack due to the use ; of relative subroutines and PUSH/POP ; LCD properly connected to the board, otherwise ; processor hangs around and waits for ; the busy flag to go down! (no timeout!) ; 39 words of program space ; ; Interfaces: ; lcd_wt Waits indefinately until the busy ; flag of the LCD is off ; lcd_fc Sends the command byte in register ; mpr to the LCD after busy is off ; lcd_cl Clears the LCD and sets cursor to ; the home position, after busy is off ; lcd_st Sets the LCD to 8-bit-transfer, ; freezes the display window and sets ; cursor to increment after busy is off ; lcd_sc Sets cursor to the display position ; in register mpr (Line 1: 00 .. 0F hex, ; Line 2: 40 .. 4F hex) ; lcd_ch Outputs the character in register ; mpr to the LCD after busy is off ; lcd_on Sets display on, cursor on and blink ; ; Adress definitions: .equ lcd_rs = 0x8000 ; Register select = 0 adress .equ lcd_ds = 0xC000 ; Register select = 1 adress ; ; Subroutines ; ; Wait until LCD is not busy any more ; lcd_wt: push mpr ; save register lcd_wt1: lds mpr,lcd_rs ; read busy flag rol mpr ; Busy = Bit 7 into Carry brcs lcd_wt1 ; still busy, repeat pop mpr ; restore register ret ; ; Outputs the function command in mpr to the LCD ; lcd_fc: rcall lcd_wt ; Wait until not busy any more sts lcd_rs,mpr ; Command byte to LCD ret ; ; Clears LCD and sets cursor to home position ; lcd_cl: push mpr ; save register ldi mpr,0x01 ; the clear command rcall lcd_fc ; output to LCD command pop mpr ; restore register ret ; ; Sets LCD to 8-bit-mode, display window freeze and ; cursor incrementation (standard mode) ; lcd_st: push mpr ; save register ldi mpr,0b00111000 ; 8-Bit-transfer rcall lcd_fc ; to LCD command ldi mpr,0b00000110 ; Increment, display freeze rcall lcd_fc ; to LCD ldi mpr,0b00010000 ; Cursor move, not shift rcall lcd_fc ; to LCD pop mpr ; restore register ret ; ; Sets cursor on the LCD to a certain display position in mpr ; lcd_sc: push mpr ; save position ori mpr,0x80 ; set bit 7 of the position rcall lcd_fc ; position to LCD pop mpr ; restore register ret ; ; Sends a character in mpr to the display at the current ; position, position is incremented after write ; lcd_ch: rcall lcd_wt ; wait for not busy sts lcd_ds,mpr ; transfer character to LCD-Display ret ; ; Sets LCD display on, cursor on and blink on ; lcd_on: push mpr ; save register ldi mpr,0b00001111 ; command byte rcall lcd_fc ; to LCD pop mpr ; restore register ret http://www.avr-asm-tutorial.net/avr_de/quellen/lcd_inc.asm1/20/2009 7:37:21 PM
AVR-Hardware-Tutorium: SIO ansteuern
Pfad: Home => AVR-Übersicht => Hardware => SIO
; Testet die Serielle Schnittstelle ; ; Sendet auf der Schnittstelle mit 9k6 8N1 einen ; Text und echot dann eingehende Zeichen zurück. ;
; Hardware: Verbindung zwischen Schnittstellen ; Win-Ansteuerung: mit HyperTerminal (siehe Anleitung) ; .NOLIST .INCLUDE "8515def.inc" .LIST ;
;Reset-/Interrupt-Vektoren RJMP main ; main: OUT LDI OUT ;
LDI mpr,bdteiler ; Baudgenerator UBRR,mpr ; Teiler setzen mpr,0b00011000 ; Enable TX und RX UCR,mpr ; an UART Control Register
; Sende alle Grossbuchstaben ; LDI c,'A' ; erster Buchstabe LDI nc,90-65+1 ; Anzahl Buchstaben tloop: SBIS USR,UDRE ; Springe, wenn Senderegister leer RJMP tloop ; Warte noch ein Weilchen OUT UDR,c ; Buchstabe an Senderegister übergeben INC c ; nächster Buchstabe DEC nc ; Zähler Anzahl Buchstaben zu senden abwärts BRNE tloop ; nächster Buchstabe ;
; Warte bis Zeichen empfangen, Echo zurück, für immer ; rloop: SBIS USR,RXC ; Teste RXC-bit auf vorliegendes Zeichen RJMP rloop ; Kein Zeichen vorhanden, warte IN c,UDR ; Hole Zeichen vom UART ab rwait: SBIS USR,UDRE ; Warte bis Sender bereit RJMP rwait ; Sender noch nicht frei OUT UDR,c ; Sende Zeichen aus CPI c,0x0D ; Return-Zeichen? BRNE rloop ; Kein Return, einfach weiter LDI c,0x0A ; Lade Linefeed RJMP rwait ; Sende noch Linefeed hinterher
Hard- und Software für die Kommunikation mit dem STK-200 über die SIO/UART ========================================================================== 1. Hardware Benötigt wird ein 9-poliger Stecker für das STK200-Board und entweder eine 25-polige Buchse oder ein weiterer 9-poliger Stecker. Im neunpoligen Stecker sowie in der 25-poligen Buchse werden jeweils folgende Pins innerhalb des jeweiligen Steckers miteinander verbunden: 9-polig: 25-polig: Name der Leitung ---------------------------------------------Pin 4 = Pin 20 = Data Terminal Ready DTR Pin 8 = Pin 5 = Clear To Send CTS Pin 6 = Pin 6 = Data Set Ready DSR Mittels dreiadrigem Kabel werden folgende Leitungen zwischen den beiden Steckern/Buchsen verbunden: 9-polig: 25-polig: Name der Leitung ---------------------------------------------Pin 2 = Pin 3 = Read Data RD Pin 3 = Pin 2 = Transmit Data TD Pin 5 = Pin 7 = Signal Ground SG Das war es.
2. Software Grundsätzlich ist jedes Terminalprogramm geeignet. Am einfachsten unter Windows ist HyperTerminal, da es bei Win95 dabei ist. Die Installation der Verbindung geht so: a) Im Startmenu unter PROGRAMME-ZUBEHÖR-HYPERTERMINAL anklicken. b) Im offenen Ordner auf HYPERTRM.EXE doppelklicken. c) Einen Namen für die Verbindung eingeben z.B. STK200Sio und Ok klicken. d) In dem Rufnummer-Fenster keine Rufnummer eingeben. Das Klappfenster VERBINDEN_ÜBER aufklappen und DIREKTVERBINDUNG_ÜBER_COMX anwählen. (X ist in der Regel COM2, wenn die Maus an COM1 hängt.) Ok wählen. e) Im EIGENSCHAFTEN-Fenster Baudrate 9600 wählen, 8 Datenbits, keine Parität, 1 Stopbit und Protokoll HARDWARE auswählen. f) In dem weissen Fenster kann nun beliebig mit dem STK200 kommuniziert werden. Das Programm sollte beim Einschalten des Boards am Anfang den Text ausgeben und anschliessend die eingebenen Zeichen per Echo zurücksenden. Für einen Härtetest z.B. kann man diese Datei senden lassen (Menuepunkt ÜBERTRAGUNG-TEXTDATEI_SENDEN) und damit die Geschwindigkeit der Übertragung austesten. g) Beim Schliessen des Fensters die lästige Frage mit JA beantworten. Wenn man anschliessend auch die Frage nach dem Speichern der Sitzung mit JA beantwortet, kann man die gleichen Einstellungen später wieder verwenden, um schnell Kontakt aufzunehmen. Einfach im HyperTerminalOrdner die entsprechende Ikone anklicken.
3. Erfahrungen mit Hyperterminal Bei Baudraten von 19200 ist Schluss. Darüber geht keine vernünftige Verbindung mehr. Die Umlaute kommen verkehrt rüber. Das liegt am Windoof. Am Zeilenanfang geht bei schneller Übertragung immer ein Zeichen verloren, weil das Carriage-Return beim ECHO im Testprogramm des STK200 um einen Linefeed ergänzt wird. Dann bleibt nicht genügend Zeit und ein anderer Buchstabe wird verstümmelt ankommen. http://www.avr-asm-tutorial.net/avr_de/TestSio.Txt1/20/2009 7:37:26 PM
Empfang und Echo von SIO-Zeichen auf dem STK200 Board
Pfad: Home => AVR-Übersicht => Hardware => SIO-hex
Assembler-Quellcode für das Testen der Seriellen Schnittstelle ; ; Testet die Serielle Schnittstelle ; ; Empfängt auf der SIO-Schnittstelle mit 9k6 8N1 Zeichen und ; sendet die Hex-Werte dieser Zeichen zurück. ; ; Hardware: Verbindung zwischen Schnittstellen ; Ansteuerung: Terminalprogramm, z.B. HyperTerminal (siehe Anleitung) ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Konstanten ; .EQU fq=4000000 ; Quarzfrequenz .EQU baud=9600 ; Baudrate .EQU bdteiler=(fq/(16*baud))-1 ; Baud-Teiler .EQU RamStart = 0x0060 ; ; Register ; .DEF mpr=R16 ; Universalregister .DEF cc=R17 ; Zeichenkopie .DEF h=R18 ; Hilfsregister ; ; XL/XH = R26/R27 werden als Pointer in das SRAM verwendet ; YL/YH = R28/R29 werden als Pointer in das SRAM verwendet ; ; Programmcode beginnt hier ; .CSEG ; ; Reset-/Interrupt-Vektoren ; RJMP main ; Reset-Vektor ; main: LDI XH,HIGH(RamStart) LDI XL,LOW(RamStart) LDI YH,HIGH(RamStart) LDI YL,LOW(RamStart) LDI mpr,0x0D ; Neue Zeile beginnen ST X+,mpr ; im SRAM ablegen und Pointer erhöhen LDI mpr,0x0A ; Linefeed dazu ST X+,mpr LDI mpr,bdteiler ; Baudgenerator OUT UBRR,mpr ; Teiler setzen LDI mpr,0b00011000 ; Enable TX und RX OUT UCR,mpr ; an UART Control Register ; ; Hauptprogrammschleife fragt SIO-Schnittstelle ab und sendet die im SRAM ; gespeicherten Zeichen aus ; tloop: SBIC USR,RXC ; Springe, wenn das Empfangsregister leer ist RJMP rx ; Empfange nächstes Zeichen SBIC USR,UDRE ; Springe, wenn das Senderegister nicht verfügbar ist RJMP tx ; Sende nächstes Zeichen RJMP tloop ; Alles wieder von vorne ; ; Empfange ein Zeichen und speichere es im SRAM ; rx: LDI mpr,' ' ; Sende ein Leerzeichen als Trennzeichen ST X+,mpr ; Speichern im SRAM und Eingabeadresse in X erhöhen IN mpr,UDR ; Hole Zeichen von der SIO-Schnittstelle ab MOV cc,mpr ; Lege Kopie an SWAP mpr ; Oberes und unteres Nibble vertauschen ANDI mpr,0x0F ; Oberes Nibble löschen CPI mpr,10 ; Nibble > 9? BRCS rx1 ; Nein LDI h,7 ; Addiere 7 für hex A bis F ADD mpr,h rx1: LDI h,'0' ; von 0 nach '0' ADD mpr,h ST X+,mpr ; und in das SRAM legen ANDI cc,0x0F ; Unteres Nibble gleich behandeln CPI cc,10 BRCS rx2 LDI h,7 ADD cc,h rx2: LDI h,'0' ADD cc,h ST X+,cc LDI cc,'h' ; hex-Kennzeichnung mitsenden ST X+,cc ; auch ins SRAM ablegen RJMP tloop ; wieder zurück zur Hauptprogrammschleife ; ; Sende Zeichen aus dem SRAM-Puffer, wenn welche dort liegen ; tx: CP XL,YL ; Zu sendende Zeichen im Puffer vorhanden? BREQ tx1 ; Keine Zeichen zu senden LD mpr,Y+ ; Hole Zeichen aus dem SRAM und erhöhe Y-Pointer OUT UDR,mpr ; Zeichen an Senderegister übergeben RJMP tloop ; und wieder zurück in die Hauptprogrammschleife tx1: LDI XH,HIGH(RamStart) ; Setze Pointer wieder an den Anfang des SRAM LDI XL,LOW(RamStart) LDI YH,HIGH(RamStart) LDI YL,LOW(RamStart) RJMP tloop ; und zurück in die Hauptprogrammschleife ; ; Code Ende ;
; ; Testet die Serielle Schnittstelle ; ; Empfängt auf der SIO-Schnittstelle mit 9k6 8N1 Zeichen und ; sendet die Hex-Werte dieser Zeichen zurück. ; ; Hardware: Verbindung zwischen Schnittstellen ; Ansteuerung: Terminalprogramm, z.B. HyperTerminal (siehe Anleitung) ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Konstanten ; .EQU fq=4000000 ; Quarzfrequenz .EQU baud=9600 ; Baudrate .EQU bdteiler=(fq/(16*baud))-1 ; Baud-Teiler .EQU RamStart = 0x0060 ; ; Register ; .DEF mpr=R16 ; Universalregister .DEF cc=R17 ; Zeichenkopie .DEF h=R18 ; Hilfsregister ; ; XL/XH = R26/R27 werden als Pointer in das SRAM verwendet ; YL/YH = R28/R29 werden als Pointer in das SRAM verwendet ; ; Programmcode beginnt hier ; .CSEG ; ; Reset-/Interrupt-Vektoren ; rjmp main ; Reset-Vektor ; main: ldi XH,HIGH(RamStart) ldi XL,LOW(RamStart) ldi YH,HIGH(RamStart) ldi YL,LOW(RamStart) ldi mpr,0x0D ; Neue Zeile beginnen st X+,mpr ; im SRAM ablegen und Pointer erhöhen ldi mpr,0x0A ; Linefeed dazu st X+,mpr ldi mpr,bdteiler ; Baudgenerator out UBRR,mpr ; Teiler setzen ldi mpr,0b00011000 ; Enable TX und RX out UCR,mpr ; an UART Control Register ; ; Hauptprogrammschleife fragt SIO-Schnittstelle ab und sendet die im SRAM ; gespeicherten Zeichen aus ; tloop: sbic USR,RXC ; Springe, wenn das Empfangsregister leer ist rjmp rx ; Empfange nächstes Zeichen sbic USR,UDRE ; Springe, wenn das Senderegister nicht verfügbar ist rjmp tx ; Sende nächstes Zeichen rjmp tloop ; Alles wieder von vorne ; ; Empfange ein Zeichen und speichere es im SRAM ; rx: ldi mpr,' ' ; Sende ein Leerzeichen als Trennzeichen st X+,mpr ; Speichern im SRAM und Eingabeadresse in X erhöhen in mpr,UDR ; Hole Zeichen von der SIO-Schnittstelle ab mov cc,mpr ; Lege Kopie an swap mpr ; Oberes und unteres Nibble vertauschen andi mpr,0x0F ; Oberes Nibble löschen cpi mpr,10 ; Nibble > 9? brcs rx1 ; Nein ldi h,7 ; Addiere 7 für hex A bis F add mpr,h rx1: ldi h,'0' ; von 0 nach '0' add mpr,h st X+,mpr ; und in das SRAM legen andi cc,0x0F ; Unteres Nibble gleich behandeln cpi cc,10 brcs rx2 ldi h,7 add cc,h rx2: ldi h,'0' add cc,h st X+,cc ldi cc,'h' ; hex-Kennzeichnung mitsenden st X+,cc ; auch ins SRAM ablegen rjmp tloop ; wieder zurück zur Hauptprogrammschleife ; ; Sende Zeichen aus dem SRAM-Puffer, wenn welche dort liegen ; tx: cp XL,YL ; Zu sendende Zeichen im Puffer vorhanden? breq tx1 ; Keine Zeichen zu senden ld mpr,Y+ ; Hole Zeichen aus dem SRAM und erhöhe Y-Pointer out UDR,mpr ; Zeichen an Senderegister übergeben rjmp tloop ; und wieder zurück in die Hauptprogrammschleife tx1: ldi XH,HIGH(RamStart) ; Setze Pointer wieder an den Anfang des SRAM ldi XL,LOW(RamStart) ldi YH,HIGH(RamStart) ldi YL,LOW(RamStart) rjmp tloop ; und zurück in die Hauptprogrammschleife ; ; Code Ende ; http://www.avr-asm-tutorial.net/avr_de/quellen/siohex.asm1/20/2009 7:37:29 PM
AVR-Tutorial, Voraussetzungen
Pfad: Home => AVR-Übersicht => Tutorial
Tutorial für das Erlernen der Assemblersprache von
AVR-Einchip-Prozessoren AT90Sxxxx von ATMEL anhand geeigneter praktischer Beispiele.
Sinn, Zweck und Voraussetzungen
Sinn und Zweck Die folgenden Lektionen führen in die Programmierung von AVR-Einchip-Prozessoren der Serien AT90Sxxxx der Firma ATMEL ein. Nach Durcharbeiten der Beispiele sollte jeder auch ohne Vorkenntnisse in der Lage sein, einfache Programme für diese Prozessoren zu erstellen. Die Beispiele können direkt im Assembler kompiliert, in den Chip übertragen werden und gestartet werden. Jeder Programmierschritt ist ausführlich kommentiert.
Voraussetzungen Alle Beispiele sind auf den ATMEL-Assembler ausgelegt. Bei Verwendung anderer Assembler müssen eventuell Anpassungen an einzelnen Befehlen in Bezug auf die Syntax vorgenommen werden. Die Beispiele laufen direkt auf dem STK-200-Programmierboard, das von der Firma ATMEL vertrieben wird und im Versandhandel zu beschaffen ist. Für alle Hardware-Komponenten auf diesem Board werden Beispiele zu deren Programmierung und Ansteuerung angegeben. ACHTUNG! Alle Beispiele setzen voraus, dass sich die Datei "8515def.inc" im gleichen Verzeichnis wie der Quellcode befindet, sonst hagelt es Fehlermeldungen vom Assembler. Die Datei findet sich bei installiertem Assembler im Verzeichnis X:\avrtools\appnotes\ und kann von dort in das Quellcode- Verzeichnis kopiert werden.
AVR-Einchip-Prozessoren AT90Sxxxx von ATMEL in praktischen Beispielen. Die folgenden Beispiele sind kleine Anwendungen zum Ausprobieren und für ernsthafte Anwendungen. Sie wurden zwar erprobt und angewendet, eine Garantie für ihr korrektes Funktionieren kann aber verständlicherweise nicht übernommen werden. HTMLFormat
DCF77Uhr
PcmDec
PwgSio
ASMFormat
Kurzbeschreibung
DCF77-synchronisierbare Digitaluhr mit seriellem Interface zur Steuerung und Diagnose mit einem Terminalprogramm mit ANSI-Darstellung. Arbeitet mit DCF77Uhr einem 10MHz-AT90S2313 in einer speziellen Schaltung (Schaltplan siehe unter Links). (Quelltext bisher nur in englischer Version verfügbar! 1211 Zeilen, übersetze ich nur auf mehrfache Anfragen.)
Link
GIF PDF
PcmDec
PCM-kodierte Fernsteuersignale von 0,8 bis 2,2 ms werden mittels eines AT90S2323 in einer sehr kleinen Schaltung in einen Analogwert von 0 bis 5 Volt umgewandelt.
PwgSio
Rechteckgenerator, erzeugt beliebig lange, exakte Signale und ist über den SIO-Eingang des STK200 boards mit PwgSio einem Terminalprogramm mit ANSI-Darstellung frei einstellbar und bedienbar.
Rechteckgenerator mit einstellbarer Frequenz und Pulsweite, normalem und invertiertem Digitalausgang, Frequenz/Zeit/UPM/Pulsweite-Anzeige auf LCD, Anwendung eines ATmega8 mit ADC, Quarzoszillator, etc.
RectGen
fcount_m8_v3
fcountV2
Frequenzzähler, misst Frequenzen bis zu 40 MHz und zeigt Frequenz, Umdrehungszahl, Periodendauer, Periodenanteile und eine Spannung an, mit SIO-Interface
fcount
eieruhr
eieruhr
ATtiny2313-Eieruhr zum Angeben, Vielzweck-Geschenk Eieruhr in Dutzenden Varianten zum individuellen Beschenken des gesamten Bekanntenkreises
schrittmotor
ATtiny13-Schrittmotorsteuerung, Einstellung eines schrittmotor Schrittmotors mit einer Analogspannung von 0..5V, einstellbar bis 65535 Einzelschritte Vollausschlag
Pfad: Home => AVR-Übersicht => Anwendungen => DCF77 Uhr ; *************************************************************** ; * DCF-synchronized Digital Clock for RS232 communication on * ; * a 2313-Experimental-Board, Version 0.2 as of 12.01.2001 * ; * Features: XTal driven digital clock for exact date and time * * ; * information, adjusted and read over a SIO-I/O 9k6 8N1 * ; * connection, Self-adjusting date- and time-synchronisation ; * to a connected receiver for the Frankfurt/Germany based * ; * official clock reference DCF77 (optional) * ; * (C)2001 by Gerhard Schmidt * ; * report bugs to info!at!avr-asm-tutorial.net * ; *************************************************************** ; ; Hardware requirements: ; - 2313 board (see extra doc) ; - RS232 compatible terminal, e.g. PC+Win+HyperTerminal to adjust/read ; the date and time informationen ; - (RXD/TXD, RTS/CTS)-crosswired RS232 cable connection between the ; 2313-board and the terminal ; - Optional: DCF77 clock with active low receiver signal (second ticks) ; ; Software features: ; - Interrupt-driven and buffered SIO-I/O with RTS/CTS hardware protocol ; - Interrupt-driven clock signals with exact timing ; - SLEEP mode for reduced power consumption of the MPU ; - Exact date calculation from 2001 up to the year 2099 ; - Transmits ANSI color codes for terminal control ; - DCF synchronisation: self-adjusting to unexact signal lengthes and ; spurious signals by software, loss-of-signal-detection, full parity bit ; checking, convenient hardware debugging opportunities by terminal display ; of the signal length and all detected errors during the sampling process ; .NOLIST .INCLUDE "C:\avrtools\appnotes\2313def.inc" .LIST ; ; Constants ; ; Constants for Sio properties .EQU fq=10000000; Xtal frequency on board in Hz .EQU baud=9600; Baudrate for SIO communication .EQU bddiv=(fq/(16*baud))-1; Baudrate divider .EQU ticks=fq/16000; ticks per second for the timing functions ; Constants for Sio communications .EQU ccr=0x0D; Carriage return character .EQU clf=0x0A; Line feed character .EQU cnul=0x00; NUL character .EQU cesc=0x1B; ESCAPE character .EQU cBs=0x08; Backspace character ; Bit assignment of the RTS and CTS pins on Port B .EQU bIRts = 2 .EQU bOCts = 4 ; Locations in the Internal SRAM .EQU sDTF = 0x60 ; Date/Time information, first location in SRAM .EQU dDDT = 0 ; relative distance, Tens of Days .EQU dDD = 1 ; relative distance, Days .EQU dDMT = 3 ; relative distance, Tens of Monthes .EQU dDM = 4 ; relative distance, Month .EQU dDY = 9 ; relative distance, Years .EQU dTHT = 12 ; relative disance, Tens of Hours .EQU dTH = 13 ; relative distance, Hours .EQU dTMT = 15 ; relative distance, Tens of Minutes .EQU dTM = 16 ; relative distance, Minutes .EQU dTST = 18 ; relative distance, Tens of Seconds .EQU dTS = 19 ; relative distance, Seconds .EQU sDTL = 0x74 ; Date/Time information, last location in SRAM .EQU sDTT = 0x6C ; Position of Time .EQU sSec = 0x73 ; Adress of seconds .EQU sSecT = 0x72 ; Adress of tens of seconds .EQU sMin = 0x70 ; Adress of minutes ; Constants for Sio Rx- and Tx-Buffers .EQU RxBuF = 0x75 ; Sio Rx-Buffer, first location in SRAM .EQU RxBuL = 0x84 ; Sio Rx-Buffer, last location in SRAM (16 Bytes) .EQU TxBuF = 0x85 ; Sio Tx-Buffer, first location in SRAM .EQU TxBuL = 0xA4 ; Sio Tx-Buffer, last location in SRAM (32 Bytes) ; ; Used registers ; ; Register mainly for Program Memory Read Operations .DEF rlpm = R0 ; Used for read operations with LPM ; SIO Tx Buffer In pointer .DEF rsiotxin = R1 ; Registers for the DCF77-Receiver option .DEF rdcfp = R2 ; Parity bit counter for DCF signals .DEF rdcf1 = R3 ; Last Receiver Shift Register .DEF rdcf2 = R4 ; Receiver Shift register .DEF rdcf3 = R5 ; Receiver Shift register .DEF rdcf4 = R6 ; Receiver Shift register .DEF rdcf5 = R7 ; First Receiver Shift Register .DEF rDcfCmp = R8 ; Compare length of DCF pulse (self-adjusted) .DEF rDcfLc = R9 ; Last count length detected .EQU cDcfCmpDflt = 125 ; Default length for DCF signals (selfadjusted) .DEF rDcfCnt = R10 ; DCF length count, interrupt driven .DEF rDcfL0 = R11 ; Distance of last short pulse from medium count length .DEF rDcfL1 = R12 ; Distance of last long pulse from medium count length ; For all purposes .DEF rmpr = R16 ; Multi-purpose register ; Low level ticker for seconds counting by interrupt (1.6 ms) .DEF rtckh = R17 ; MSB .DEF rtckl = R18 ; LSB ; DCF77 Tick register for signal length, driven by timer interrupt (1.6 ms) .DEF rDcfl = R19 ; Timer 0 flag register with the following bits: .DEF rdtf = R20 ; Date/Time Flag register .EQU bEos = 0 ; Bit 0: End of second reached .EQU mEos = 1 .EQU bMin = 1 ; Bit 1: Echo minutes only .EQU mMin = 2 .EQU bDTm = 2 ; Bit 2: DT mode active .EQU mDTm = 4 ; Bits used by DCF77: .EQU bDcfm = 3 ; Bit 3: DCF mode active .EQU mDcfm = 8 .EQU bDcfCtm = 4 ; Bit 4: DCF echo counters .EQU mDcfCtm = 16 .EQU bDcfSync = 5 ; Bit 5: DCF synch tickers at next second .EQU mDcfSync = 32 .EQU bDcfRdy = 6 ; Bit 6: DCF bit ready .EQU mDcfRdy = 64 .EQU bDcfOk = 7 ; Bit 7: DCF Signal is ok .EQU mDcfOk = 128 ; SIO flag register Bit 5: Unused ; Bit 6: Unused ; Bit 7: Unused ; .DEF rsioflg = R21 ; SIO flag register .EQU bEcho = 0 ; Bit 0: Echo all incoming characters .EQU mEcho = 1 .EQU bAddLf = 1 ; Bit 1: Insert LF after CR .EQU mAddLf = 2 .EQU bTxBAct = 2 ; Bit 2: Transmitter buffer active .EQU mTxBAct = 4 .EQU bTxAct = 3 ; Bit 3: Character transmission active .EQU mTxAct = 8 .EQU bRxCmp = 4 ; Bit 4: Rx-Line complete .EQU mRxCmp = 16 ; DCF error flag register .DEF rDcfErr = R22 ; Last DCF-error .EQU bDcfPem = 0 ; Bit 0: Parity error minute .EQU mDcfPem = 1 .EQU bDcfPeh = 1 ; Bit 1: Parity error hour .EQU mDcfPeh = 2 .EQU bDcfPed = 2 ; Bit 2: Parity error date .EQU mDcfPed = 4 .EQU bDcfCts = 3 ; Bit 3: Count too short .EQU mDcfCts = 8 .EQU bDcfCtl = 4 ; Bit 4: Count too long .EQU mDcfCtl = 16 .EQU bDcfSok = 5 ; Bit 5: Synchronisation ( not an error!) .EQU mDcfSOk = 32 .EQU bDcfEtr = 6 ; Bit 6: Error to be reported .EQU mDcfEtr = 64 .EQU bDcfAny = 7 ; Bit 7: Any error .EQU mDcfAny = 128 ; DCF shift register counter .DEF rDcfs = R23 ; Shift Register Bit Counter ; ; Code starts here ; .CSEG .ORG $0000 ; ; Reset- and Interrupt-vectors ; RJMP Start ; Reset-vector RJMP IInt0 ; External Interrupt Request 0 RJMP IInt1 ; External Interrupt Request 1 RJMP TCpt1 ; Timer/Counter1 Capture event RJMP TCmp1 ; Timer/Counter1 Compare match RJMP TOvf1 ; Timer/Counter1 Overflow RJMP TOvf0 ; Timer/Counter0 Overflow RJMP URxAv ; Uart Rx char available RJMP UTxDe ; Uart Tx data register empty RJMP UTxCp ; Uart Tx complete RJMP AnaCp ; Analog comparator ; ; ************** Interrupt service routines ******** ; ; External Interrupt 0: Started by a negative edge on the DCF input ; IInt0: PUSH rmpr IN rmpr,SREG SBRS rdtf,bDcfSync RJMP IInt01 CBR rdtf,mDcfSync ; Synchronize DCF and internal tickers CLR rtckl CLR rtckh IInt01: CLR rDcfl OUT SREG,rmpr POP rmpr RETI ; ; External Interrupt 1 : Started by a positive edge on the DCF input ; IInt1: PUSH rmpr IN rmpr,SREG CPI rDcfl,10 ; Exclude short signals BRCS IInt1r MOV rDcfCnt,rDcfl ; Store count length INC rDcfs SBR rdtf,mDcfRdy ; Flag received bit ready IInt1r: OUT SREG,rmpr POP rmpr RETI ; ; Timer/Counter 1, Capture event interrupt, not used ; TCpt1: RETI ; ; Timer/Counter 1, Compare match interrupt, not used ; TCmp1: RETI ; ; Timer/Counter 1, Overflow interrupt, not used ; TOvf1: RETI ; ; Timer/Counter 0, Overflow interrupt, used to count times ; TOvf0: PUSH rmpr ; Save Register rmpr LDI rmpr,6 ; Set Counter to 6 to int after 250 ticks OUT TCNT0,rmpr IN rmpr,SREG ; Save status register INC rtckl BRNE TOvf0a INC rtckh TOvf0a: CPI rtckl,LOW(ticks) ; End of second reached? BRNE TOvf0b CPI rtckh,HIGH(ticks) BRNE TOvf0b SBR rdtf,mEos ; Set End of second flag CLR rtckl CLR rtckh TOvf0b: INC rDcfl ; DCF77 counter tick BRNE TOvf0c DEC rDcfl TOvf0c: OUT SREG,rmpr ; Restore anything POP rmpr RETI ; ; Uart Rx Complete Interrupt ; URxAv: PUSH rmpr ; Save mpr register IN rmpr,SREG ; Save SREG PUSH rmpr SBIC USR,FE ; Framing error? RJMP URxAv2 IN rmpr,UDR ; Read Char SBRC rsioflg,bEcho RCALL siotxch ; Echo character CPI rmpr,cBs ; Backspace? BRNE URxAv0 CPI XL,RxBuF+1 ; Backspace BRCS URxAv3 DEC XL RJMP URxAv3 URxAv0: ST X+,rmpr ; Store in RX buffer CPI XL,RxBuL+1 ; End of buffer reached? BRCS URxAv1 LDI XL,RxBuF URxAv1: CPI rmpr,cCr ; End of input line? BRNE URxAv3 SBR rSioFlg,mRxCmp ; Set Line complete flag RJMP URxAv3 URxAv2: IN rmpr,UDR ; Clear Framing error bit URxAv3: POP rmpr ; Restore SREG OUT SREG,rmpr POP rmpr ; Restore rmpr RETI ; ; Uart Data register empty interrupt ; UTxDe: PUSH rmpr ; Save register IN rmpr,SREG ; Save status register PUSH rmpr CP YL,rsiotxin ; Compare Buffer In and Out BRNE UTxDeCh UTxDeOff: CBR rSioFlg,mTxBAct ; No more chars to send RJMP UTxDeRet UTxDeCh: SBIC PortB,bIRts ; RTS input ready? RJMP UTxDeOff LD rmpr,Y+ ; Read char OUT UDR,rmpr CPI YL,TxBuL+1 ; Check end of buffer BRCS UTxDeRet LDI YL,TxBuF ; Point to buffer start UTxDeRet: POP rmpr ; Restore status register OUT SREG,rmpr POP rmpr ; Restore register RETI ; ; Uart Tx complete interrupt ; UTxCp: PUSH rmpr IN rmpr,SREG CBR rsioflg,mTxAct ; Clear the flag (not used here) OUT SREG,rmpr POP rmpr RETI ; ; Analog comparator interrupt ; AnaCp: RETI ; ; ******* End of interrupt service routines *********** ; ; Sio service subroutines to be called from various sources ; ; ; Char in rmpr to Tx-Buffer ; siotxch: SBIC PortB,bIRts ; Send only, if RTS is active RET PUSH ZH PUSH ZL CLR ZH ; Point to TX buffer input position MOV ZL,rSioTxIn ST Z+,rmpr CPI ZL,TxBuL+1 ; End of buffer reached? BRCS siotxchx LDI ZL,TxBuF siotxchx: ; Wait here to avoid buffer overrun CP ZL,YL BREQ siotxchx CLI ; Enter critical situation, disable interrupts SBRC rsioflg,bTxBAct RJMP sioTxChy SBR rsioflg,mTxBAct OUT UDR,rmpr RJMP sioTxChn sioTxChy: MOV rsioTxIn,ZL sioTxChn: SEI ; End of critical situation POP ZL POP ZH CPI rmpr,cCr ; Add linefeeds after carriage return? BRNE sioTxChz SBRS rsioflg,bAddLf RJMP sioTxChz LDI rmpr,cLf RCALL sioTxCh LDI rmpr,cCr sioTxChz: RET ; ; Transmits a null-terminated text from memory that Z points to ; TxTxt: PUSH rlpm PUSH rmpr TxTxt1: LPM ; Read a char from the program memory at Z MOV rmpr,rlpm CPI rmpr,cnul ; End of text? BREQ TxTxtz RCALL siotxch ADIW ZL,1 RJMP TxTxt1 TxTxtz: POP rmpr POP rlpm RET ; ; Send date/time to SIO ; DispDT: RCALL DcfErr CLR ZH ; Send time info in SRAM to SIO LDI ZL,sDTF DispDT1: LD rmpr,Z+ ; Read from SRAM RCALL siotxch ; Transmit CPI rmpr,cCr ; Last char? BRNE DispDT1 RET ; ; Send a byte as decimal number to the SIO ; DispByte: RCALL siotxch ; preface LDI rmpr,' ' RCALL siotxch LDI rmpr,'=' RCALL siotxch LDI rmpr,' ' RCALL siotxch LDI ZH,100 ; send 100's SUB ZL,ZH BRCS DispByte1 LDI rmpr,'1' SUB ZL,ZH BRCS DispByte1 SUB ZL,ZH INC rmpr DispByte1: RCALL siotxch ADD ZL,ZH LDI ZH,10 ; send 10's SUB ZL,ZH BRCS DispByte3 LDI rmpr,'0' DispByte2: INC rmpr SUB ZL,ZH BRCC DispByte2 RJMP DispByte4 DispByte3: CPI rmpr,' ' BREQ DispByte4 LDI rmpr,'0' DispByte4: ADD ZL,ZH RCALL siotxch LDI rmpr,'0' ; send 1's ADD rmpr,ZL RCALL siotxch LDI rmpr,' ' RCALL siotxch RJMP siotxch ; ************** End of SIO subrourines ******************* ; ; ***************** Various subroutines ******************* ; ; DT mode active, display date/time ; DTModeX: SBRS rdtf,bMin ; Minutes only? RJMP DTModeX1 LDS rmpr,sSec ; End of minute? CPI rmpr,'0' BRNE DTModeX2 LDS rmpr,sSecT CPI rmpr,'0' BRNE DTModeX2 DTModeX1: RCALL DispDT ; Display date and time DTModeX2: RET ; Return to loop ; ; DCF mode active, display DCF characteristics ; DCFModeX: RCALL DcfErr ; Report any DCF77 errors first SBRC rdtf,bDcfCtm RJMP DCFModeX2 OR rDcfs,rDcfs ; Report DCF signals bitwise LDI rmpr,cCr BREQ DCFModeX1 DEC rDcfLc LDI rmpr,'1' CP rDcfLc,rDcfCmp BRCC DCFModeX1 DEC rmpr DCFModeX1: RJMP siotxch DCFModeX2: LDI rmpr,'b' ; Report signal number MOV ZL,rDcfs RCALL DispByte LDI rmpr,'c' ; Report detected signal length in ticks MOV ZL,rDcfLc RCALL DispByte LDI rmpr,'m' ; Report current discriminating value MOV ZL,rDcfCmp RCALL DispByte LDI rmpr,cCr RJMP siotxch ; ; Reports any DCF errors ; DcfErr: SBRS rDcfErr,bDcfEtr ; Any unreported errors? RET CBR rDcfErr,mDcfEtr LDI ZH,HIGH(2*TxtDcfErr) ; Error text intro LDI ZL,LOW(2*TxtDcfErr) RCALL TxTxt MOV rmpr,rDcfErr ANDI rmpr,0x3F DcfErr1: ADIW ZL,1 CLC ; Identify next error bit ROR rmpr BRCC DcfErr3 RCALL TxTxt DcfErr2: OR rmpr,rmpr ; No more bits set? BRNE DcfErr1 ANDI rDcfErr,0x80 LDI rmpr,cCr RJMP siotxch DcfErr3: ADIW ZL,1 ; Point to next text sequence LPM OR rlpm,rlpm BRNE DcfErr3 RJMP DcfErr2 ; ; DCF synchronisation ; Dcf: CLR rDcfs ; End of minute, clear bit counter SBR rDcfErr,(mDcfSOk | mDcfEtr) ; Set synch to be reported LDI rmpr,'0' CLR ZH LDI ZL,sSec ; Second ST Z,rmpr ST -Z,rmpr DEC ZL DEC ZL MOV rmpr,rDcf1 ; Minute ROR rmpr ROR rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST Z,rmpr MOV rmpr,rDcf2 ; Tens of minutes ROR rmpr MOV rmpr,rDcf1 ROR rmpr ROR rmpr SWAP rmpr ANDI rmpr,0x07 ORI rmpr,0x30 ST -Z,rmpr DEC ZL ; Hour MOV rmpr,rDcf2 ROR rmpr ROR rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST -Z,rmpr MOV rmpr,rDcf2 ; Tens of hours ROL rmpr ROL rmpr ROL rmpr ANDI rmpr,0x03 ORI rmpr,0x30 ST -Z,rmpr LDI ZL,sDTF+dDD ; Day MOV rmpr,rDcf3 ROR rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST Z,rmpr MOV rmpr,rDcf3 ; Tens of Days ROR rmpr SWAP rmpr ANDI rmpr,0x03 ORI rmpr,0x30 ST -Z,rmpr ADIW ZL,4 ; Month MOV rmpr,rDcf4 ROR rmpr ROR rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST Z,rmpr MOV rmpr,rDcf4 ; Tens of monthes SWAP rmpr ROR rmpr ROR rmpr ANDI rmpr,0x01 ORI rmpr,0x30 ST -Z,rmpr ADIW ZL,6 ; Years MOV rmpr,rDcf4 ROL rmpr MOV rmpr,rDcf5 ROL rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST Z,rmpr MOV rmpr,rDcf5 ; Tens of years ROL rmpr SWAP rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST -Z,rmpr RET ; ; Next second ; ChkDcf: ; Check DCF77 info complete CBR rdtf,mEos ; Clear seconds flag bit SBRC rdtf,bDcfOk ; Last DCF tick was ok? RJMP NewDcfSec SBR rdtf,mDcfSync ; Minute is over MOV rmpr,rDcfs ; Have all 59 DCF ticks been received? CPI rmpr,59 BRCS CntTooShort ; Less than 59 ticks BRNE CountTooLong ; More than 59 ticks SBRS rDcfErr,bDcfAny ; Any errors in parity? RJMP Dcf RJMP CntReset ; No DCF synch, clear all CountTooLong: SBRS rdtf,bDcfm ; DCF echo mode on? RJMP CntReset SBR rDcfErr,(mDcfCtl | mDcfEtr | mDcfAny) ; Set DCF error type RJMP CntReset CntTooShort: SBR rDcfErr,mDcfCts ; Set DCF error type OR rDcfs,rDcfs ; DCF shift register totally empty? BREQ CntReset SBR rDcfErr,(mDcfEtr | mDcfAny) ; Set error to report CntReset: CLR rDcfs ; Clear the DCF shift counter CBR rDcfErr,mDcfAny ; Clear the global DCF error bit NewDcfSec: CBR rdtf,mDcfOk ; Clear the DCF tick ok bit IncSec: CLR ZH ; Point to Date/Time info in SRAM LDI ZL,sDTF+dTS ; Second RCALL IncNmbr ; Next second and handle overflow CPI rmpr,60 ; end of minute? BRCC IncMin IncRet: RET IncMin: LDI rmpr,'0' ; Clear seconds ST Z,rmpr ST -Z,rmpr LDI ZL,sDTF+dTM ; Next minute RCALL IncNmbr CPI rmpr,60 ; End of the hour? BRCS IncRet IncHour: LDI rmpr,'0' ; Clear minutes ST Z,rmpr ST -Z,rmpr LDI ZL,sDTF+dTH ; Next hour RCALL IncNmbr CPI rmpr,24 ; End of the day? BRCS IncRet LDI rmpr,'0' ; Clear hours ST Z,rmpr ST -Z,rmpr IncDay: LDI ZL,sDTF+dDD ; Next day RCALL IncNmbr CPI rmpr,32 ; End of month? BRCC IncMonth CPI rmpr,31 ; End of month for short monthes BRNE ChkFeb LDI ZL,sDTF+dDM ; Get days RCALL GetByte SUBI rmpr,4 ; Before April? BRCS IncRet CPI rmpr,3 ; April or June? BRCS IncDay1 INC rmpr ; Later than June IncDay1: SBRC rmpr,0 ; Even month? RET RJMP IncMonth ; End of a short month ChkFeb: LDI ZL,sDTF+dDM ; Get current month RCALL GetByte CPI rmpr,2 ; February? BRNE IncRet LDI ZL,sDTF+dDY ; Get year RCALL GetByte ANDI rmpr,0x03 ; February with 29 days? BRNE ChkFeb1 LDI ZL,sDTF+dDD ; Get current day RCALL GetByte CPI rmpr,30 ; Long February ends with 29 RJMP ChkFeb2 ChkFeb1: LDI ZL,sDTF+dDD ; Short February, get actual day RCALL GetByte CPI rmpr,29 ; End of month? ChkFeb2: BRCS IncRet IncMonth: LDI ZL,sDTF+dDD ; Next month, clear days LDI rmpr,'1' ST Z,rmpr LDI rmpr,'0' ST -Z,rmpr LDI ZL,sDTF+dDM ; Next month RCALL IncNmbr CPI rmpr,13 ; End of the year? BRCS IncRet IncYear: LDI rmpr,'1' ; next year, clear month ST Z,rmpr LDI rmpr,'0' ST -Z,rmpr LDI ZL,sDTF+dDY ; Inc years by running into the following ; ; Inc Number at Z and Z-1 and return with the number in one byte ; IncNmbr: LD rmpr,Z ; Inc's a number in SRAM and its tens, if necessary INC rmpr CPI rmpr,'9'+1 BRCS IncNmbr1 LD rmpr,-Z INC rmpr ST Z+,rmpr LDI rmpr,'0' IncNmbr1: ST Z,rmpr ; ; Get byte from Z and Z-1 ; GetByte: LD rmpr,-Z ; Two digit number to binary, load first digit SUBI rmpr,'0' MOV rlpm,rmpr ; Multiply by 10 ADD rmpr,rmpr ADD rmpr,rmpr ADD rmpr,rlpm ADD rmpr,rmpr MOV rlpm,rmpr ; Store result in rlpm INC ZL ; Add second digit LD rmpr,Z SUBI rmpr,'0' ADD rmpr,rlpm RET ; **************** End of the subroutine section *************** ; ; ******************** Main program loop *********************** ; ; Main program routine starts here ; Start: CLI ; Disable interrupts LDI rmpr,RAMEND ; Set stack pointer OUT SPL,rmpr RCALL InitDT ; Init Date/Time-Info in SRAM RCALL InitIo ; Init the I/O properties RCALL InitSio ; Init the SIO properties RCALL InitDcf RCALL InitTimer0 ; Init the timer 0 RCALL InitAna ; Init the Analog comparator ; General Interrupt Mask Register ; External Interrupt Request 1 Enable ; External Interrupt Request 0 Enable LDI rmpr,0b11000000 http://www.avr-asm-tutorial.net/avr_de/dcf77uhr.html (1 of 2)1/20/2009 7:37:53 PM
; *************************************************************** ; * DCF-synchronized Digital Clock for RS232 communication on * ; * a 2313-Experimental-Board, Version 0.2 as of 12.01.2001 * ; * Features: XTal driven digital clock for exact date and time * ; * information, adjusted and read over a SIO-I/O 9k6 8N1 * ; * connection, Self-adjusting date- and time-synchronisation * ; * to a connected receiver for the Frankfurt/Germany based * ; * official clock reference DCF77 (optional) * ; * (C)2001 by Gerhard Schmidt * ; * report bugs to [email protected] * ; *************************************************************** ; ; Hardware requirements: ; - 2313 board (see extra doc) ; - RS232 compatible terminal, e.g. PC+Win+HyperTerminal to adjust/read ; the date and time informationen ; - (RXD/TXD, RTS/CTS)-crosswired RS232 cable connection between the ; 2313-board and the terminal ; - Optional: DCF77 clock with active low receiver signal (second ticks) ; ; Software features: ; - Interrupt-driven and buffered SIO-I/O with RTS/CTS hardware protocol ; - Interrupt-driven clock signals with exact timing ; - SLEEP mode for reduced power consumption of the MPU ; - Exact date calculation from 2001 up to the year 2099 ; - Transmits ANSI color codes for terminal control ; - DCF synchronisation: self-adjusting to unexact signal lengthes and ; spurious signals by software, loss-of-signal-detection, full parity bit ; checking, convenient hardware debugging opportunities by terminal display ; of the signal length and all detected errors during the sampling process ; .NOLIST .INCLUDE "C:\avrtools\appnotes\2313def.inc" .LIST ; ; Constants ; ; Constants for Sio properties .EQU fq=10000000; Xtal frequency on board in Hz .EQU baud=9600; Baudrate for SIO communication .EQU bddiv=(fq/(16*baud))-1; Baudrate divider .EQU ticks=fq/16000; ticks per second for the timing functions ; Constants for Sio communications .EQU ccr=0x0D; Carriage return character .EQU clf=0x0A; Line feed character .EQU cnul=0x00; NUL character .EQU cesc=0x1B; ESCAPE character .EQU cBs=0x08; Backspace character ; Bit assignment of the RTS and CTS pins on Port B .EQU bIRts = 2 .EQU bOCts = 4 ; Locations in the Internal SRAM .EQU sDTF = 0x60 ; Date/Time information, first location in SRAM .EQU dDDT = 0 ; relative distance, Tens of Days .EQU dDD = 1 ; relative distance, Days .EQU dDMT = 3 ; relative distance, Tens of Monthes .EQU dDM = 4 ; relative distance, Month .EQU dDY = 9 ; relative distance, Years .EQU dTHT = 12 ; relative disance, Tens of Hours .EQU dTH = 13 ; relative distance, Hours .EQU dTMT = 15 ; relative distance, Tens of Minutes .EQU dTM = 16 ; relative distance, Minutes .EQU dTST = 18 ; relative distance, Tens of Seconds .EQU dTS = 19 ; relative distance, Seconds .EQU sDTL = 0x74 ; Date/Time information, last location in SRAM .EQU sDTT = 0x6C ; Position of Time .EQU sSec = 0x73 ; Adress of seconds .EQU sSecT = 0x72 ; Adress of tens of seconds .EQU sMin = 0x70 ; Adress of minutes ; Constants for Sio Rx- and Tx-Buffers .EQU RxBuF = 0x75 ; Sio Rx-Buffer, first location in SRAM .EQU RxBuL = 0x84 ; Sio Rx-Buffer, last location in SRAM (16 Bytes) .EQU TxBuF = 0x85 ; Sio Tx-Buffer, first location in SRAM .EQU TxBuL = 0xA4 ; Sio Tx-Buffer, last location in SRAM (32 Bytes) ; ; Used registers ; ; Register mainly for Program Memory Read Operations .DEF rlpm = R0 ; Used for read operations with LPM ; SIO Tx Buffer In pointer .DEF rsiotxin = R1 ; Registers for the DCF77-Receiver option .DEF rdcfp = R2 ; Parity bit counter for DCF signals .DEF rdcf1 = R3 ; Last Receiver Shift Register .DEF rdcf2 = R4 ; Receiver Shift register .DEF rdcf3 = R5 ; Receiver Shift register .DEF rdcf4 = R6 ; Receiver Shift register .DEF rdcf5 = R7 ; First Receiver Shift Register .DEF rDcfCmp = R8 ; Compare length of DCF pulse (self-adjusted) .DEF rDcfLc = R9 ; Last count length detected .EQU cDcfCmpDflt = 125 ; Default length for DCF signals (self-adjusted) .DEF rDcfCnt = R10 ; DCF length count, interrupt driven .DEF rDcfL0 = R11 ; Distance of last short pulse from medium count length .DEF rDcfL1 = R12 ; Distance of last long pulse from medium count length ; For all purposes .DEF rmpr = R16 ; Multi-purpose register ; Low level ticker for seconds counting by interrupt (1.6 ms) .DEF rtckh = R17 ; MSB .DEF rtckl = R18 ; LSB ; DCF77 Tick register for signal length, driven by timer interrupt (1.6 ms) .DEF rDcfl = R19 ; Timer 0 flag register with the following bits: .DEF rdtf = R20 ; Date/Time Flag register .EQU bEos = 0 ; Bit 0: End of second reached .EQU mEos = 1 .EQU bMin = 1 ; Bit 1: Echo minutes only .EQU mMin = 2 .EQU bDTm = 2 ; Bit 2: DT mode active .EQU mDTm = 4 ; Bits used by DCF77: .EQU bDcfm = 3 ; Bit 3: DCF mode active .EQU mDcfm = 8 .EQU bDcfCtm = 4 ; Bit 4: DCF echo counters .EQU mDcfCtm = 16 .EQU bDcfSync = 5 ; Bit 5: DCF synch tickers at next second .EQU mDcfSync = 32 .EQU bDcfRdy = 6 ; Bit 6: DCF bit ready .EQU mDcfRdy = 64 .EQU bDcfOk = 7 ; Bit 7: DCF Signal is ok .EQU mDcfOk = 128 ; SIO flag register ; Bit 5: Unused ; Bit 6: Unused ; Bit 7: Unused .DEF rsioflg = R21 ; SIO flag register .EQU bEcho = 0 ; Bit 0: Echo all incoming characters .EQU mEcho = 1 .EQU bAddLf = 1 ; Bit 1: Insert LF after CR .EQU mAddLf = 2 .EQU bTxBAct = 2 ; Bit 2: Transmitter buffer active .EQU mTxBAct = 4 .EQU bTxAct = 3 ; Bit 3: Character transmission active .EQU mTxAct = 8 .EQU bRxCmp = 4 ; Bit 4: Rx-Line complete .EQU mRxCmp = 16 ; DCF error flag register .DEF rDcfErr = R22 ; Last DCF-error .EQU bDcfPem = 0 ; Bit 0: Parity error minute .EQU mDcfPem = 1 .EQU bDcfPeh = 1 ; Bit 1: Parity error hour .EQU mDcfPeh = 2 .EQU bDcfPed = 2 ; Bit 2: Parity error date .EQU mDcfPed = 4 .EQU bDcfCts = 3 ; Bit 3: Count too short .EQU mDcfCts = 8 .EQU bDcfCtl = 4 ; Bit 4: Count too long .EQU mDcfCtl = 16 .EQU bDcfSok = 5 ; Bit 5: Synchronisation ( not an error!) .EQU mDcfSOk = 32 .EQU bDcfEtr = 6 ; Bit 6: Error to be reported .EQU mDcfEtr = 64 .EQU bDcfAny = 7 ; Bit 7: Any error .EQU mDcfAny = 128 ; DCF shift register counter .DEF rDcfs = R23 ; Shift Register Bit Counter ; ; Code starts here ; .CSEG .ORG $0000 ; ; Reset- and Interrupt-vectors ; rjmp Start ; Reset-vector rjmp IInt0 ; External Interrupt Request 0 rjmp IInt1 ; External Interrupt Request 1 rjmp TCpt1 ; Timer/Counter1 Capture event rjmp TCmp1 ; Timer/Counter1 Compare match rjmp TOvf1 ; Timer/Counter1 Overflow rjmp TOvf0 ; Timer/Counter0 Overflow rjmp URxAv ; Uart Rx char available rjmp UTxDe ; Uart Tx data register empty rjmp UTxCp ; Uart Tx complete rjmp AnaCp ; Analog comparator ; ; ************** Interrupt service routines ******** ; ; External Interrupt 0: Started by a negative edge on the DCF input ; IInt0: push rmpr in rmpr,SREG sbrs rdtf,bDcfSync rjmp IInt01 cbr rdtf,mDcfSync ; Synchronize DCF and internal tickers clr rtckl clr rtckh IInt01: clr rDcfl out SREG,rmpr pop rmpr reti ; ; External Interrupt 1 : Started by a positive edge on the DCF input ; IInt1: push rmpr in rmpr,SREG cpi rDcfl,10 ; Exclude short signals brcs IInt1r mov rDcfCnt,rDcfl ; Store count length inc rDcfs sbr rdtf,mDcfRdy ; Flag received bit ready IInt1r: out SREG,rmpr pop rmpr reti ; ; Timer/Counter 1, Capture event interrupt, not used ; TCpt1: reti ; ; Timer/Counter 1, Compare match interrupt, not used ; TCmp1: reti ; ; Timer/Counter 1, Overflow interrupt, not used ; TOvf1: reti ; ; Timer/Counter 0, Overflow interrupt, used to count times ; TOvf0: push rmpr ; Save Register rmpr ldi rmpr,6 ; Set Counter to 6 to int after 250 ticks out TCNT0,rmpr in rmpr,SREG ; Save status register inc rtckl brne TOvf0a inc rtckh TOvf0a: cpi rtckl,LOW(ticks) ; End of second reached? brne TOvf0b cpi rtckh,HIGH(ticks) brne TOvf0b sbr rdtf,mEos ; Set End of second flag clr rtckl clr rtckh TOvf0b: inc rDcfl ; DCF77 counter tick brne TOvf0c dec rDcfl TOvf0c: out SREG,rmpr ; Restore anything pop rmpr reti ; ; Uart Rx Complete Interrupt ; URxAv: push rmpr ; Save mpr register in rmpr,SREG ; Save SREG push rmpr sbic USR,FE ; Framing error? rjmp URxAv2 in rmpr,UDR ; Read Char sbrc rsioflg,bEcho rcall siotxch ; Echo character cpi rmpr,cBs ; Backspace? brne URxAv0 cpi XL,RxBuF+1 ; Backspace brcs URxAv3 dec XL rjmp URxAv3 URxAv0: st X+,rmpr ; Store in RX buffer cpi XL,RxBuL+1 ; End of buffer reached? brcs URxAv1 ldi XL,RxBuF URxAv1: cpi rmpr,cCr ; End of input line? brne URxAv3 sbr rSioFlg,mRxCmp ; Set Line complete flag rjmp URxAv3 URxAv2: in rmpr,UDR ; Clear Framing error bit URxAv3: pop rmpr ; Restore SREG out SREG,rmpr pop rmpr ; Restore rmpr reti ; ; Uart Data register empty interrupt ; UTxDe: push rmpr ; Save register in rmpr,SREG ; Save status register push rmpr cp YL,rsiotxin ; Compare Buffer In and Out brne UTxDeCh UTxDeOff: cbr rSioFlg,mTxBAct ; No more chars to send rjmp UTxDeRet UTxDeCh: sbic PortB,bIRts ; RTS input ready? rjmp UTxDeOff ld rmpr,Y+ ; Read char out UDR,rmpr cpi YL,TxBuL+1 ; Check end of buffer brcs UTxDeRet ldi YL,TxBuF ; Point to buffer start UTxDeRet: pop rmpr ; Restore status register out SREG,rmpr pop rmpr ; Restore register reti ; ; Uart Tx complete interrupt ; UTxCp: push rmpr in rmpr,SREG cbr rsioflg,mTxAct ; Clear the flag (not used here) out SREG,rmpr pop rmpr reti ; ; Analog comparator interrupt ; AnaCp: reti ; ; ******* End of interrupt service routines *********** ; ; Sio service subroutines to be called from various sources ; ; ; Char in rmpr to Tx-Buffer ; siotxch: sbic PortB,bIRts ; Send only, if RTS is active ret push ZH push ZL clr ZH ; Point to TX buffer input position mov ZL,rSioTxIn st Z+,rmpr cpi ZL,TxBuL+1 ; End of buffer reached? brcs siotxchx ldi ZL,TxBuF siotxchx: ; Wait here to avoid buffer overrun cp ZL,YL breq siotxchx cli ; Enter critical situation, disable interrupts sbrc rsioflg,bTxBAct rjmp sioTxChy sbr rsioflg,mTxBAct out UDR,rmpr rjmp sioTxChn sioTxChy: mov rsioTxIn,ZL sioTxChn: sei ; End of critical situation pop ZL pop ZH cpi rmpr,cCr ; Add linefeeds after carriage return? brne sioTxChz sbrs rsioflg,bAddLf rjmp sioTxChz ldi rmpr,cLf rcall sioTxCh ldi rmpr,cCr sioTxChz: ret ; ; Transmits a null-terminated text from memory that Z points to ; TxTxt: push rlpm push rmpr TxTxt1: lpm ; Read a char from the program memory at Z mov rmpr,rlpm cpi rmpr,cnul ; End of text? breq TxTxtz rcall siotxch adiw ZL,1 rjmp TxTxt1 TxTxtz: pop rmpr pop rlpm ret ; ; Send date/time to SIO ; DispDT: rcall DcfErr clr ZH ; Send time info in SRAM to SIO ldi ZL,sDTF DispDT1: ld rmpr,Z+ ; Read from SRAM rcall siotxch ; Transmit cpi rmpr,cCr ; Last char? brne DispDT1 ret ; ; Send a byte as decimal number to the SIO ; DispByte: rcall siotxch ; preface ldi rmpr,' ' rcall siotxch ldi rmpr,'=' rcall siotxch ldi rmpr,' ' rcall siotxch ldi ZH,100 ; send 100's sub ZL,ZH brcs DispByte1 ldi rmpr,'1' sub ZL,ZH brcs DispByte1 sub ZL,ZH inc rmpr DispByte1: rcall siotxch add ZL,ZH ldi ZH,10 ; send 10's sub ZL,ZH brcs DispByte3 ldi rmpr,'0' DispByte2: inc rmpr sub ZL,ZH brcc DispByte2 rjmp DispByte4 DispByte3: cpi rmpr,' ' breq DispByte4 ldi rmpr,'0' DispByte4: add ZL,ZH rcall siotxch ldi rmpr,'0' ; send 1's add rmpr,ZL rcall siotxch ldi rmpr,' ' rcall siotxch rjmp siotxch ; ************** End of SIO subrourines ******************* ; ; ***************** Various subroutines ******************* ; ; DT mode active, display date/time ; DTModeX: sbrs rdtf,bMin ; Minutes only? rjmp DTModeX1 lds rmpr,sSec ; End of minute? cpi rmpr,'0' brne DTModeX2 lds rmpr,sSecT cpi rmpr,'0' brne DTModeX2 DTModeX1: rcall DispDT ; Display date and time DTModeX2: ret ; Return to loop ; ; DCF mode active, display DCF characteristics ; DCFModeX: rcall DcfErr ; Report any DCF77 errors first sbrc rdtf,bDcfCtm rjmp DCFModeX2 or rDcfs,rDcfs ; Report DCF signals bitwise ldi rmpr,cCr breq DCFModeX1 dec rDcfLc ldi rmpr,'1' cp rDcfLc,rDcfCmp brcc DCFModeX1 dec rmpr DCFModeX1: rjmp siotxch DCFModeX2: ldi rmpr,'b' ; Report signal number mov ZL,rDcfs rcall DispByte ldi rmpr,'c' ; Report detected signal length in ticks mov ZL,rDcfLc rcall DispByte ldi rmpr,'m' ; Report current discriminating value mov ZL,rDcfCmp rcall DispByte ldi rmpr,cCr rjmp siotxch ; ; Reports any DCF errors ; DcfErr: sbrs rDcfErr,bDcfEtr ; Any unreported errors? ret cbr rDcfErr,mDcfEtr ldi ZH,HIGH(2*TxtDcfErr) ; Error text intro ldi ZL,LOW(2*TxtDcfErr) rcall TxTxt mov rmpr,rDcfErr andi rmpr,0x3F DcfErr1: adiw ZL,1 clc ; Identify next error bit ror rmpr brcc DcfErr3 rcall TxTxt DcfErr2: or rmpr,rmpr ; No more bits set? brne DcfErr1 andi rDcfErr,0x80 ldi rmpr,cCr rjmp siotxch DcfErr3: adiw ZL,1 ; Point to next text sequence lpm or rlpm,rlpm brne DcfErr3 rjmp DcfErr2 ; ; DCF synchronisation ; Dcf: clr rDcfs ; End of minute, clear bit counter sbr rDcfErr,(mDcfSOk | mDcfEtr) ; Set synch to be reported ldi rmpr,'0' clr ZH ldi ZL,sSec ; Second st Z,rmpr st -Z,rmpr dec ZL dec ZL mov rmpr,rDcf1 ; Minute ror rmpr ror rmpr andi rmpr,0x0F ori rmpr,0x30 st Z,rmpr mov rmpr,rDcf2 ; Tens of minutes ror rmpr mov rmpr,rDcf1 ror rmpr ror rmpr swap rmpr andi rmpr,0x07 ori rmpr,0x30 st -Z,rmpr dec ZL ; Hour mov rmpr,rDcf2 ror rmpr ror rmpr andi rmpr,0x0F ori rmpr,0x30 st -Z,rmpr mov rmpr,rDcf2 ; Tens of hours rol rmpr rol rmpr rol rmpr andi rmpr,0x03 ori rmpr,0x30 st -Z,rmpr ldi ZL,sDTF+dDD ; Day mov rmpr,rDcf3 ror rmpr andi rmpr,0x0F ori rmpr,0x30 st Z,rmpr mov rmpr,rDcf3 ; Tens of Days ror rmpr swap rmpr andi rmpr,0x03 ori rmpr,0x30 st -Z,rmpr adiw ZL,4 ; Month mov rmpr,rDcf4 ror rmpr ror rmpr andi rmpr,0x0F ori rmpr,0x30 st Z,rmpr mov rmpr,rDcf4 ; Tens of monthes swap rmpr ror rmpr ror rmpr andi rmpr,0x01 ori rmpr,0x30 st -Z,rmpr adiw ZL,6 ; Years mov rmpr,rDcf4 rol rmpr mov rmpr,rDcf5 rol rmpr andi rmpr,0x0F ori rmpr,0x30 st Z,rmpr mov rmpr,rDcf5 ; Tens of years rol rmpr swap rmpr andi rmpr,0x0F ori rmpr,0x30 st -Z,rmpr ret ; ; Next second ; ChkDcf: ; Check DCF77 info complete cbr rdtf,mEos ; Clear seconds flag bit sbrc rdtf,bDcfOk ; Last DCF tick was ok? rjmp NewDcfSec sbr rdtf,mDcfSync ; Minute is over mov rmpr,rDcfs ; Have all 59 DCF ticks been received? cpi rmpr,59 brcs CntTooShort ; Less than 59 ticks brne CountTooLong ; More than 59 ticks sbrs rDcfErr,bDcfAny ; Any errors in parity? rjmp Dcf rjmp CntReset ; No DCF synch, clear all CountTooLong: sbrs rdtf,bDcfm ; DCF echo mode on? rjmp CntReset sbr rDcfErr,(mDcfCtl | mDcfEtr | mDcfAny) ; Set DCF error type rjmp CntReset CntTooShort: sbr rDcfErr,mDcfCts ; Set DCF error type or rDcfs,rDcfs ; DCF shift register totally empty? breq CntReset sbr rDcfErr,(mDcfEtr | mDcfAny) ; Set error to report CntReset: clr rDcfs ; Clear the DCF shift counter cbr rDcfErr,mDcfAny ; Clear the global DCF error bit NewDcfSec: cbr rdtf,mDcfOk ; Clear the DCF tick ok bit IncSec: clr ZH ; Point to Date/Time info in SRAM ldi ZL,sDTF+dTS ; Second rcall IncNmbr ; Next second and handle overflow cpi rmpr,60 ; end of minute? brcc IncMin IncRet: ret IncMin: ldi rmpr,'0' ; Clear seconds st Z,rmpr st -Z,rmpr ldi ZL,sDTF+dTM ; Next minute rcall IncNmbr cpi rmpr,60 ; End of the hour? brcs IncRet IncHour: ldi rmpr,'0' ; Clear minutes st Z,rmpr st -Z,rmpr ldi ZL,sDTF+dTH ; Next hour rcall IncNmbr cpi rmpr,24 ; End of the day? brcs IncRet ldi rmpr,'0' ; Clear hours st Z,rmpr st -Z,rmpr IncDay: ldi ZL,sDTF+dDD ; Next day rcall IncNmbr cpi rmpr,32 ; End of month? brcc IncMonth cpi rmpr,31 ; End of month for short monthes brne ChkFeb ldi ZL,sDTF+dDM ; Get days rcall GetByte subi rmpr,4 ; Before April? brcs IncRet cpi rmpr,3 ; April or June? brcs IncDay1 inc rmpr ; Later than June IncDay1: sbrc rmpr,0 ; Even month? ret rjmp IncMonth ; End of a short month ChkFeb: ldi ZL,sDTF+dDM ; Get current month rcall GetByte cpi rmpr,2 ; February? brne IncRet ldi ZL,sDTF+dDY ; Get year rcall GetByte andi rmpr,0x03 ; February with 29 days? brne ChkFeb1 ldi ZL,sDTF+dDD ; Get current day rcall GetByte cpi rmpr,30 ; Long February ends with 29 rjmp ChkFeb2 ChkFeb1: ldi ZL,sDTF+dDD ; Short February, get actual day rcall GetByte cpi rmpr,29 ; End of month? ChkFeb2: brcs IncRet IncMonth: ldi ZL,sDTF+dDD ; Next month, clear days ldi rmpr,'1' st Z,rmpr ldi rmpr,'0' st -Z,rmpr ldi ZL,sDTF+dDM ; Next month rcall IncNmbr cpi rmpr,13 ; End of the year? brcs IncRet IncYear: ldi rmpr,'1' ; next year, clear month st Z,rmpr ldi rmpr,'0' st -Z,rmpr ldi ZL,sDTF+dDY ; Inc years by running into the following ; ; Inc Number at Z and Z-1 and return with the number in one byte ; IncNmbr: ld rmpr,Z ; Inc's a number in SRAM and its tens, if necessary inc rmpr cpi rmpr,'9'+1 brcs IncNmbr1 ld rmpr,-Z inc rmpr st Z+,rmpr ldi rmpr,'0' IncNmbr1: st Z,rmpr ; ; Get byte from Z and Z-1 ; GetByte: ld rmpr,-Z ; Two digit number to binary, load first digit subi rmpr,'0' mov rlpm,rmpr ; Multiply by 10 add rmpr,rmpr add rmpr,rmpr add rmpr,rlpm add rmpr,rmpr mov rlpm,rmpr ; Store result in rlpm inc ZL ; Add second digit ld rmpr,Z subi rmpr,'0' add rmpr,rlpm ret ; **************** End of the subroutine section *************** ; ; ******************** Main program loop *********************** ; ; Main program routine starts here ; Start: cli ; Disable interrupts ldi rmpr,RAMEND ; Set stack pointer out SPL,rmpr rcall InitDT ; Init Date/Time-Info in SRAM rcall InitIo ; Init the I/O properties rcall InitSio ; Init the SIO properties rcall InitDcf rcall InitTimer0 ; Init the timer 0 rcall InitAna ; Init the Analog comparator ; General Interrupt Mask Register ; External Interrupt Request 1 Enable ; External Interrupt Request 0 Enable ldi rmpr,0b11000000 out GIMSK,rmpr ; Timer/Counter Interrupt register ; Disable all TC1 Ints ; Enable TC0 Ints ldi rmpr,0b00000010 out TIMSK,rmpr ; Enable interrupts (Master Int Enable) sei ; Enable all interrupts ; Master Control register settings ; Sleep Enable, Sleep Mode = Idle ; Ext Int 1 on rising edges ; Ext Int 0 on falling edges ldi rmpr,0b00101110 out MCUCR,rmpr Loop: sleep ; Sleep until interrupt occurs nop ; needed to wakeup sbrs rdtf,bDcfRdy ; Check if DCF signal has ended rjmp ChkEos ; no, inc the seconds cbr rdtf,mDcfRdy ; DCF: clear active signal ended mov rDcfLc,rDcfCnt cp rDcfCmp,rDcfLc ; Count parity information, is it a 1 or a 0? brcc DcfPar inc rDcfp ; Count 1's DcfPar: cpi rDcfs,21 ; Start of minute information? brcs DcfCrct brne DcfPar1 clr rDcfp ; Clear parity counter rjmp DcfCrct DcfPar1: cpi rDcfs,29 ; Minute parity to check? brne DcfPar2 sbrc rDcfp,0 ; Parity even? sbr rDcfErr,(mDcfPem | mDcfEtr | mDcfAny) ; Parity error clr rDcfp ; Clear parity counter rjmp DcfCrct DcfPar2: cpi rDcfs,36 ; Hour parity to check? brne DcfPar3 sbrc rDcfp,0 ; Even? sbr rDcfErr,(mDcfPeh | mDcfEtr | mDcfAny) ; Parity error clr rDcfp ; Clear parity counter rjmp DcfCrct DcfPar3: cpi rDcfs,59 ; Date parity to check? brne DcfCrct sbrc rDcfp,0 ; Even? sbr rDcfErr,(mDcfPed | mDcfEtr | mDcfAny) ; Parity error clr rDcfp ; Clear Parity counter DcfCrct: cp rDcfCmp,rDcfLc ; Compare DCF signal length with medium value ror rDcf5 ; Shift the result into the DCF 40-bit storage ror rDcf4 ror rDcf3 ror rDcf2 ror rDcf1 sbr rdtf,mDcfOk ; Set the DCF signal ok bit sub rDcfCnt,rDcfCmp ; Calc distance of signal length from medium value brcs DcfCrct0 ; Underflow = short pulse? mov rDcfL1,rDcfCnt ; Store this difference in the 1-length-byte mov rmpr,rDcfL0 ; Has ever a 0-signal been received? cpi rmpr,0 brne DcfCrct2 DcfCrctUp: inc rDcfCmp ; Only 1-signals received so far, adjust higher medium rjmp Loop DcfCrct0: com rDcfCnt ; Underflow = Signal 0, negative to positive distance mov rDcfL0,rDcfCnt ; Store the difference in the 0-length-byte or rDcfl1,rDcfL1 ; Has ever been received a 1-signal? brne DcfCrCt2 DcfCrctDwn: dec rDcfCmp ; All 0's, reduce medium value rjmp Loop DcfCrct2: cp rDcfL1,rDcfL0 ; Compare the differences of the last 0 and 1 received breq Loop brcs DcfCrctDwn ; Adjust the medium value according to the difference rjmp DcfCrctUp ChkEos: sbrs rdtf,bEos ; End of a timer second? rjmp Loop2 rcall ChkDcf ; Check if DCF is to sychronize sbrs rDTf,bDTm ; DT mode active? rjmp Loop1 http://www.avr-asm-tutorial.net/avr_de/quellen/dcf77uhr.asm (1 of 2)1/20/2009 7:38:01 PM
rcall DTModeX ; Output the results ModeOff: cpi XL,RxBuF ; In the time- and dcf-echo-mode only blanks are to be breq Loop ; reacted upon. Has a char been sent over the SIO? clr XH ; Reset RX-buffer to the start ldi XL,RxBuF ld rmpr,X ; Read character cpi rmpr,' ' ; Check for BLANK brne StartLoop bld rsioflg,bEcho ; Restore the echo bit cbr rDTf,(mDTm | mDcfm) ; Clear the mode bits rjmp CursorOn ; send cursor on the SIO Loop1: sbrs rdtf,bDcfm ; DCF mode active? rjmp Loop2 rcall DCFModeX ; DCF mode execution rjmp ModeOff Loop2: sbrs rSioFlg,bRxCmp ; Line of chars from the SIO complete? rjmp Loop cbr rSioFlg,mRxCmp ; Clear line available bit sbi PortB,bOCts ; Set hardware CTS off cpi XL,RxBuF+1 ; Has only a carriage return been sent? brne Cmnd ldi ZH,HIGH(2*TxtIntro) ; Empty line, transmit help text ldi ZL,LOW(2*TxtIntro) rcall TxTxt CursorOn: ldi ZH,HIGH(2*TxtCursor) ; Display cursor line again ldi ZL,LOW(2*TxtCursor) rcall TxTxt clr XH ; Set SIO-RX buffer to start ldi XL,RxBuF cbi PortB,bOCts ; Set hardware clear to send active StartLoop: rjmp Loop Cmnd: ldi ZH,HIGH(2*CmdTab) ; Line received, search for command in table ldi ZL,LOW(2*CmdTab) Cmnd1: clr XH ; Receive buffer to start ldi XL,RxBuF Cmnd2: lpm ; Read a char from the command table adiw ZL,1 ldi rmpr,cCr ; end of the command? cp rmpr,rlpm brne Cmnd4 lpm ; next byte in command table a Carriage return char? cp rmpr,rlpm brne Cmnd3 adiw ZL,1 ; Jump over that Cmnd3: lpm ; Load the command adress from the tabel and push it to the stack adiw ZL,1 push rlpm lpm adiw ZL,1 push rlpm ldi ZH,HIGH(2*TxtYellow) ; Set terminal to different color ldi ZL,LOW(2*TxtYellow) rjmp TxTxt ; after transmit the return jumps to the command adress! Cmnd4: ld rmpr,X+ ; compare the next char in the RX buffer cp rmpr,rlpm breq Cmnd2 ; equal, compare next Cmnd5: ldi rmpr,cCr ; not equal, search for next command in table cp rmpr,rlpm ; search end of the current command name breq Cmnd6 lpm ; read until a carriage return in the command table is found adiw ZL,1 rjmp Cmnd5 Cmnd6: lpm ; read over additional carriage returns cp rmpr,rlpm brne Cmnd7 adiw ZL,1 rjmp Cmnd6 Cmnd7: adiw ZL,2 ; ignore the following word (command adress) lpm ldi rmpr,cnul ; check if the end of tabel is reached cp rmpr,rlpm brne Cmnd1 ldi ZH,HIGH(2*TxtError) ; end of table, command is unknown ldi ZL,LOW(2*TxtError) rcall TxTxt rjmp CursorOn ; receive next command line ; ************************* End of program loop ***************** ; ; ********************** Initialisation routines **************** ; ; Init the Date/Time-Info in the SRAM ; InitDT: ldi ZH,HIGH(2*DfltDT) ; Point Z to default value in program memory ldi ZL,LOW(2*DfltDT) clr XH ; Point X to date/time info in SRAM ldi XL,sDTF clr rmpr ; Test for NUL = end of default? InitDT1: lpm ; Read from program memory adiw ZL,1 ; Inc Z pointer cp rmpr,rlpm ; Test for end brne InitDT2 ret InitDT2: st X+,rlpm ; Copy to SRAM location and inc X pointer rjmp InitDT1 ; Next location ; ; Initialize the IO-properties ; InitIo: ; Pins on Port D used: ; Bit 0: Input RxD Sio ; 1: Output TxD Sio ; 2: Input External interrupt 0, DCF signal ; 3: Input External Interrupt 1, DCF signal ; 4: Output TO, not used ; 5: Output T1, not used ; 6: ICP Input Capture Pin, not used ldi rmpr,0b00110010 ; Port D Control out DDRD,rmpr ; Data direction register ldi rmpr,0b00001100 ; Pullup resistors on DCF input on to avoid glitches out PortD,rmpr ; on open inputs ; Pins on Port B: ; Bit 0: Input AIN2, not used ; 1: Input AIN1, not used ; 2: Input SIO incoming RTS signal ; 3: Output OC1, not used ; 4: Output SIO outgoing CTS signal ; 5: Input MOSI, programming interface ; 6: Input MISO, programming interface ; 7: Input SCK, programming interface ldi rmpr,0b00011000 ; Port B Control out DDRB,rmpr ; Data Direction Register ldi rmpr,0b00000000 ; No pullup resistors out PortB,rmpr ret ; ; Initialize the SIO properties ; InitSio: ldi rsioflg,0b00000011 ; Echo on, insert LF after CR on clr XH ; Set Rx Buffer ldi XL,RxBuF ldi rmpr,TxBuF ; Set Tx Buffer In mov rsiotxin,rmpr clr YH ; Set Tx Buffer Out ldi YL,TxBuF ldi rmpr,bddiv ; Init baud generator out UBRR,rmpr ; set divider in UART baud rate register ldi rmpr,0b00011000 ; Enable TX and RX, disable Ints ldi ZL,0 ; Wait for 65 ms ldi ZH,0 InitSio1: sbiw ZL,1 brne InitSio1 ldi rmpr,0b11111000 ; Enable TX und RX 8 Bit and Ints out UCR,rmpr ; in UART Control Register cbi PortB,bOCts ; Set Clear to sent active ret ; ; Init the Timer 0 ; InitTimer0: clr rmpr ; Stop the Timer out TCCR0,rmpr ldi rmpr,6 ; Start with 6 to get 250 steps @ 10 MHz and div=64 out TCNT0,rmpr ; to Timer register clr rtckl ; Clear the low-level-ticker clr rtckh clr rdtf ; Clear the flags ldi rmpr,3 ; Divide Clock by 64 out TCCR0,rmpr ; to Timer 0 Control register ret ; ; Init the Analog comparator ; InitAna: ; Analog comparator is disabled and switched off ldi rmpr,0b00000000 out ACSR,rmpr ret ; ; Init DCF ; InitDcf: ldi rmpr,cDcfCmpDflt ; Set medium value to default value mov rDcfCmp,rmpr clr rDcfL0 ; Clear the distances clr rDcfL1 clr rDcfErr ; Clear the error flags ret ; *************** End of init routines ********************* ; ; ********************* Commands *************************** ; ; Command Table, holds the command text and the command adress ; CmdTab: .DB '?',cCr ; Display list of commands .DW Help .DB 'd','?',cCr,cCr .DW DateOut .DB 'd','s',cCr,cCr ; Mode echo seconds .DW DS .DB 'd','m',cCr,cCr ; Mode echo minutes .DW DM .DB 'd','a','t','e','=',cCr ; Set Date .DW Date .DB 't','i','m','e','=',cCr ; Set Time .DW Time .DB 'd','c','f','b',cCr,cCr ; Enter DCF bit pattern mode .DW DCFMode .DB 'd','c','f','c',cCr,cCr ; Enter DCF counter mode .DW DCFCntMode .DB cnul,cnul,cnul,cnul ; ; Display list of commands Help: ldi ZH,HIGH(2*TxtHelp) ldi ZL,LOW(2*TxtHelp) rcall TxTxt rjmp CursorOn ; Display date and time DateOut: rcall DispDT rjmp CursorOn ; Set mode to echo date and time every second DS: cbr rdtf,mMin rcall DTMode rjmp CursorOn ; Set mode to echo date and time every minute only DM: sbr rdtf,mMin ; Enter Date/Time-Echo-Mode DTMode: sbr rdtf,mDTm ; Set the mode bit ldi ZH,HIGH(2*TxtDtm) ; Display the info text ldi ZL,LOW(2*TxtDtm) SetMode: rcall TxTxt bst rsioflg,bEcho ; Store the echo bit and switch it off cbr rsioflg,mEcho cbi PortB,bOCts ; Clear to send active to receive characters clr XH ; Set RX-buffer to start ldi XL,RxBuF rjmp Loop ; ; Enter DCFMode ; DCFMode: cbr rdtf,mDcfCtm ; clear the mode bit DCFMode1: sbr rdtf,mDcfm ; set echo mode ldi ZH,HIGH(2*TxtDcf) ; Display the info text ldi ZL,LOW(2*TxtDcf) rjmp SetMode ; ; Enter DCF counter echo mode ; DCFCntMode: sbr rdtf,mDcfCtm ; set the DCF echo mode bit rjmp DCFMode1 ; ; Set date ; Date: clr ZH ; Point Z to Date in SRAM ldi ZL,sDTF Date1: ld rmpr,X+ ; Take over char from Rx-buffer cpi rmpr,cCr breq Date2 st Z+,rmpr ld rmpr,Z ; End reached? cpi rmpr,',' breq Date2 cpi rmpr,cCr brne Date1 Date2: rcall DispDT ; send date and time to SIO rjmp CursorOn ; Cursor on and back to loop ; ; Set time ; Time: clr ZH ; Point Z to Time in SRAM ldi ZL,sDTT rjmp Date1 ; ; Texts to be displayed ; TxtIntro: .DB cesc,'[','0',59,'1',59,'3','7',59,'4','0','m'; Set screen colours .DB cesc,'[','H',cesc,'[','J' ; ANSI Clear screen .DB "Hello world!" .DB ccr,'T' .DB "his is the experimental 2313 board (C)DG4FAC at work. ? for help. " .DB cnul,cnul TxtCursor: .DB ccr,cesc,'[','3','2','m','*','>',cesc,'[','3','6','m',cnul TxtYellow: .DB cesc,'[','3','3','m',cnul TxtError: .DB ccr,cesc,'[','3','1','m' .DB "Error! Unknown command! " .DB cCr,cnul TxtHelp: .DB "List of commands" .DB ':',cCr .DB " d?: Display date/time." .DB cCr,' ' .DB " date=dd.yy.yyyy: Set date" .DB '.',cCr .DB " time=hh:mm:ss: Set time." .DB cCr,' ' .DB " ds: Display seconds" .DB '.',cCr .DB " dm: Display minutes." .DB cCr,' ' .DB " dcfb: DCF77 bit pattern echo." .DB cCr,' ' .DB " dcfc: DCF77 counter echo." .DB cCr,' ' .DB " ?: Display this list." .DB cCr,cNul TxtDtm: .DB "Date/Time echo. SPACE to leave" .DB '.',cesc,'[','3','5','m',cCr,cnul TxtDcf: .DB "DCF echo mode. SPACE to leave." .DB cesc,'[','3','4','m',cCr,cnul,cnul TxtDcfErr: .DB ' ',' ','D','C','F',':',' ',cNul .DB "Minutes wrong!" .DB ' ',cNul .DB "Hours wrong!" .DB ' ',cNul .DB "Date wrong" .DB '!',cNul .DB "Count too short!" .DB ' ',cNul .DB "Count too long" .DB '!',cNul .DB "Synchronisation." .DB cNul,cNul ; ; Default date and time ; DfltDT: .DB "11.01.2001, 23:12:58" .DB cCr,cNul ; ; End of code segment ; ; 1015 words, only a few left in the 2313. Enough for a copyright. ; Copyright: .DB " 2C00 1GDF4CA"
http://www.avr-asm-tutorial.net/avr_de/quellen/dcf77uhr.asm (2 of 2)1/20/2009 7:38:01 PM
Pfad: Home => AVR-Übersicht => Anwendungen => PCM-Decoder
AVR-Einchip-Prozessoren AT90Sxxxx von ATMEL in praktischen Beispielen.
Decoder für Fernsteuersignale 1. 2. 3. 4. 5.
Kurzfassung für Eilige Aufbau von Fernsteuersignalen Umwandlung von Fernsteuersignalen in eine analoge Stellgröße Erforderliche Hardware Programmaufbau
Kurzfassung für Eilige Der Decoder wandelt pulslängenkodierte Fernsteuersignale am Eingang des AT90S2323-Prozessors (Port-Bit 0, Pin 5) in ein pulsweiten-moduliertes Signal am Ausgang (Port-Bit 1, Pin 6) um. Durch Filtern des pulsweiten-modulierten Ausgangssignals mit einem einfachen RC-Glied entsteht ein Analogsignal, dessen Höhe linear mit der Pulsdauer des Eingangssignales ansteigt. Das Analogsignal kann als Stellgröße für eine Rudermaschine dienen. Gehen falsche Eingangssignale (z.B. Störsignale) am Eingang ein oder bleibt das Signal aus, dann wird das Ausgangssignal auf einen vordefinierbaren Default-Wert gebracht. Am Port-Bit 2, Pin 7) kann zu Testzwecken, z.B. durch Anschließen einer LED, festgestellt werden, ob das Eingangssignal korrekt erkannt wird. Die vorliegende Fassung des Programmes ist für Signale von 0,8 bis 2,2 ms aktiver Dauer und 25 ms Gesamtdauer eingestellt. Der Default-Wert wird schon beim ersten fehlerhaften Signal eingestellt. Alle Parameter des Programmes können den jeweiligen Bedürfnissen angepasst werden. Dazu sind nur die entsprechenden Zahlen im Quelltext zu ändern. HTML-Format Quellcode-Format Flußdiagramm in GIF-Format Flußdigramm in PDF-Format Schaltbild in PDF-Format
Schaltbild in GIF-Format
Zum Anfang des Dokumentes
Aufbau von Fernsteuersignalen Ein Fernsteuersignal muss eine analoge Größe (z.B. die Stellung eines Steuerknüppels) in ein digitales Signal verschlüsseln, das dann beim Empfänger wieder in das analoge Signal zurückgewandelt und mittels einer Rudermaschine ein anderes Gerät, z.B. ein Seitenruder, in die richtige Stellung dreht. Dazu wird die analoge Stellung beim Absender in eine Spannung umgewandelt (z.B. durch ein Potentiometer) und diese in ein Rechtecksignal umgesetzt. Das Rechtecksignal variiert je nach Höhe der Spannung seine Zeitdauer. Ist die Spannung Null, so dauert das Signal genau 0,8 Millisekunden (ms). Ist die Spannung an der Vollaussteuerung, so dauert es 2,0 ms lang. Die Pulsdauer enthält also die Information über die Stellgröße, die Information ist der Pulsdauer "aufmoduliert". Allgemein nennt man solche Signale Puls-codierte Modulation oder PCM. Recht verschwenderisch ist die Zeit zwischen zwei solchen aufeinanderfolgenden Pulsen bemessen: Zwei Pulsanfänge sind immer genau 25 ms auseinander, so dass beim längsten Puls von 2,2 ms genau 22,8 ms lang Pause ist. Ein kontinuierlich anstehendes Fernsteuersignal hat deshalb die konstante Frequenz von 40 Hertz, also 40 Pulse pro Sekunde. Das puls-codierte Signal, das entweder den Zustand Null (Pause) oder Eins (Puls) einnehmen kann, wird dem Sender zugeführt. Dieser Sender sendet entweder auf einer Frequenz, wenn gerade Pause ist, oder auf einer anderen Frequenz, wenn gerade Puls ist. Das PCM-Signal wird also im Sender in ein frequenzmoduliertes Signal mit den beiden Sendefrequenzen f0 (Null) und f0+fm (Puls) umgesetzt und als solches dann im Wechsel ausgesendet. Die beiden Frequenzen liegen dabei sehr nah zusammen, so dass im Empfänger beide empfangen und gleichzeitig weiterverarbeitet werden. Am Ende der Empfängs- und Hochfrequenz-Mimik im Empfänger unterscheidet dann doch eine bestimmte Filterstufe, Demodulator genannt, ob die empfangene Frequenz f0 oder f0+fm ist und gibt nach aussen ein entsprechendes digitales Signal zum Besten. Zum Anfang des Dokumentes
Umwandlung in eine analoge Stellgröße Damit kann natürlich noch keine Rudermaschine etwas anfangen, weil die Stellinformation ja in der Länge des immer noch digitalen Pulses steckt und die Rudermaschine ein analoges Stellsignal erwartet. Die Stufe dazwischen wäre also ein Puls-Code-Modulationssignal-zu-Analog-StellsignalWandler oder vielleicht etwas kürzer ein PCM-Demodulator. Solche Wandler hat man früher in analoger Schaltungstechnik erbaut. Mehr oder weniger zuverlässig und mehr oder weniger aufwendig, was die Zahl der Bauteile betrifft. Diese Zeiten sind vorbei, heute verwendet man dazu einen ganzen Computer. Keinen Intel-PC, aber einen richtigen Computer mit Programmsteuerung, Ein- und Ausgangsleitungen, RAM-Speicher, einem nichtflüchtigen EEPROM-Speicher und vielem anderen mehr. Bloss halt keine Festplatte, aber das kommt später auch noch in das kleine achtpolige IC hinein. So ein Kleinst-Kleinst-Rechner im achtpoliger IC-Fassung, AT90S2323 genannt, hat einen Eingang, an dem das Fernsteuersignal anliegt. Der mit einem Quarz oder einem Keramikschwinger getaktete Rechner braucht nun nur noch mitzählen, für welchen Zeitraum das Pulssignal anliegt. Dann ist rechnerintern klar, auf welche Größe die Rudermaschine eingestellt werden sollte. Die braucht aber die Information in einer anderen Form, nämlich analog. Damit wir uns den entsprechenden DAWandler sparen können, wird ein Ausgangspin dergestalt abwechselnd Null und Eins geschaltet, dass an dem Pin im Mittel die richtige analoge Spannung anliegen würde. Das Signal wird mittels eines Widerstandes auf einen Kondensator geleitet, der diese Mittelung vornimmt und ein wunderschönes analoges Signal abgibt. Dies deshalb, weil er mit dem ständigen Aufladen und Entladen nicht mitkommt und letztlich nur noch das Zeitverhältnis zwischen Nullen und Einsen anzeigt, das Pulsverhältnis. Die beiden Vorgänge, das Ausmessen des Pulses am Eingang und das Takten des Ausganges im richtigen Pulsverhältnis, müssen sehr rasch und gleichmässig parallel erfolgen. Jede Verzögerung oder Unterbrechung ist zu vermeiden, deshalb besteht die ganze Programmierkunst ausschließlich darin, die entsprechenden Zählschleifen vom Timing her hinzukriegen. Jeder normale Rechner wäre damit überfordert, weil er ständig unterbrochen und durch irgendwelchen User-Unsinn wie Mausklicke aus dem Tritt kommt. Nicht so ein entsprechend nur mit diesen beiden Aufgaben programmierter Rechner, der sonst nichts anderes zeitlich koordinieren muss. Zum Anfang des Dokumentes
Erforderliche Hardware Das Schaltbild des Decoders (auch als PDF-Datei verfügbar) sieht so aufgeräumt aus wie ein Feinschmecker-Abendessen.
Der Quarz übernimmt die Zeitsteuerung des Rechners, die beiden Kondensatoren sorgen für das zuverlässige Anschwingen des internen Taktgenerators. An Pin 1 des Rechners wird durch eine Kombination von Widerstand und Kondensator ein zuverlässiges Reset-Signal für den Rechner erzeugt, so dass auch nach Spikes auf der Versorgungsleitung der ganze Rechner sein Programm mit Null beginnt und sich selbst in einem solchen üblen Fall von Einwirkung wieder fängt. Das Eingangssignal wird über einen Widerstand von 1 k (hier nicht eingezeichnet) zugeführt, damit es bei Überspannung nicht gleich das IC zerschießen kann. Das Ausgangssignal geht über den bereits besprochenen Widerstand auf den Mittwertbildner-Kondensator. Der noch freie dritte Ein-/Ausgang ist über einen Widerstand mit einer Leuchtdiode verbunden, die im Fehlerfall (wie z.B. zu kurze oder zu lange Impulsdauer, mehrfaches Fehlen des Fernsteuersignals) anzeigt und nur für Testzwecke dient. Im echten Betrieb kann die Beschaltung dieses Ausganges entfallen. Zum Anfang des Dokumentes
Programmaufbau, Software Die gesamte Software baut auf zeitsensitiven Schleifen auf und verwendet keine Interrupts. HTMLFormat
Quellcode-Format
Flußdiagramm in GIFFormat
Flußdigramm in PDFFormat
Die Dauer des Pulses wird im vorliegenden Fall mit 233 Einzelstufen zu je 6 µs Dauer ermittelt, die den Zeitraum von 0,8 bis 2 ms überdecken. Pulse, die geringfügig kürzer oder länger sind, werden ohne Fehleranzeige noch als korrekt eingeordnet. Pulse, die deutlich kürzer als 0,8 ms sind (>10% kürzer) oder deutlich länger als 2,2 ms sind, werden als fehlerhaft übergangen. Pausen, die deutlich kürzer als vorgeschrieben dauern oder das Ausbleiben des Signales werden als Fehler interpretiert. Nach einem mehrfachen Auftreten des Fehlers in nicht unterbrochener Folge über eine einstellbare Zahl von Perioden hinaus wird ein vorher definierter Default-Wert (z.B. Null oder im vorliegenden Fall die halbe Versorgungsspannung) eingestellt, so dass bei Signalfehlern schnell der sichere Wert eingestellt wird. Das Impuls-/Pausen-Verhältnis am Ausgang ist mit (2,2 - 0,8) = 1,4 ms entsprechend 714 Hz getaktet. Lediglich bei bei Vollaussteuerung ist keine Frequenz messbar, da der Ausgang dauerhaft auf High-Pegel liegt. Die Auflösung des Verhältnisses beträgt ebenfalls 233 Schritte, so dass ein Fehler von max. 0,43% bei der Messung und bei der Analogwandlung eingehalten wird. Das dürfte auch für High-Precision-Anwendungen tolerabel sein.
Pfad: Home => AVR-Überblick => Anwendungen => Signal generator
AVR-Einchip-Prozessor AT90Sxxxx von ATMEL in praktischen Beispielen.
ANSI-Terminal programmierbarer Signalgenerator Diese Anwendung stellt einen Rechteckgenerator für Signale von 5 bis 65.535 Mikrosekunden Dauer und einstellbarer aktiver Dauer zur Verfügung. Die Angaben über die Dauer des Signals und die aktive Dauer sind über ein ANSI-kompatibles Terminalprogramm über die serielle Schnittstelle des STK200-Boards einstellbar. Diese Anwendung erfordert: ●
●
●
das STK200-Board mit einem AT90S8515 an Bord; oder alternativ: ein 2313 mit SIO interface (Neueinstellung von Teilen des Quellcodes sind dann ebenfalls erforderlich!), ein Rechner mit einer freien COM-Schnittstelle und einem ANSI-kompatiblen Terminalprogram (wie z.B. HyperTerminal für Windows), Kabelverbindung zwischen dem Rechner und dem STK200-Board über mindestens eine Zweidrahtverbindung.
Anwendung: 1. Starte das Terminalprogram and stelle die notwendigen Parameter ein: Direktverbindung über COM x, 9600 Baud, 8 Bit Daten, kein Paritätsbit, 1 Stop-Bit 2. Schalte das programmierte STK200-Board ein; das Terminal sollte auf schwarzen Hintergrund wechseln und die Willkommen-Meldung ausgeben, 3. Gib die Dauer des Gesamtimpulses in Mikrosekunden ein (5 bis 65535) und schliesse mit der Eingabetaste ab, 4. Gib die Dauer des aktiven Signals in Mikrosekunden ein (5 to 65534) und schliesse mit der Eingabetaste ab, 5. Beobachte das Signal an Port D, Bit 2, des STK200-Boards! Quellcode in HTML-Format Quellcode in asm-Format
Warnung: ●
● ● ●
●
●
●
●
Das Ändern des Quellcodes im Bereich der Textausgabe am Ende (vom Label 'ErrorStr' abwärts) kann wegen zweier ernster Compiler-Bugs ziemlich üble Folgen haben. Strings müssen immer eine gerade Anzahl von Zeichen haben! Die Anzahl an Byte oder Char Konstanten pro Zeile muss ebenfalls geradzahlig sein! Keine Strings und Konstanten auf einer Zeile mischen! Immer eine extra Zeile für beide Sorten! Durchsuche nach dem Assemblieren das Listing nach der Meldung "Garbage at end of line!". Dies ist kein fataler Fehler, also wird er nicht gemeldet und die Assemblierung abgebrochen! Alle nachfolgenden Labels sind dann aber fehlerhaft. Vergleiche das Label 'Check' am Ende des Compiler-Listings. Wenn es exakt auf sich selbst zeigt, ist alles mit den Strings in Ordnung. Zeigt es nicht auf sich selbst, dann ist ein Fehler in den Strings und in den Labeln. Das Programm tut dann nicht das, was es soll. Die genannten Punkte sind durch einen Fehler des Compilers bei der Label-Adressierung bedingt. Eine weitere, etwas seltenere Fehlerquelle, tritt bei der Verwendung von Semikolon in Textstrings oder in Zeichenkonstanten auf. Fälschlicherweise fasst der Compiler dieses Zeichen auch innerhalb von Strings oder Zeichenkonstanten als "Ende der Programmzeile" auf und ignoriert den Rest der Zeile. Zur Umgehung des schwer zu findenden Fehlers das Semikolon als Dezimal- oder Hex-Konstante auf einer Extrazeile, zusammen mit einem weiteren Buchstaben oder Zeichen, unterbringen. Die goldene Regel der String-Programmierung: Strings IMMER hinter das Ende des ausführbaren Codes platzieren. Bei Nichtbeachtung dieser Regel drohen verkehrte Programmlabels und der AVR macht ziemlich interessante Dinge mit dem gefundenen falschen Code.
http://www.avr-asm-tutorial.net/avr_de/rechteckgen/rectgen_m8_v1_main.html (2 of 2)1/20/2009 7:38:51 PM
Rechteckgenerator ATmega8
Pfad: Home => AVR-Übersicht => Anwendungen => Rechteckgenerator
Rechteckgenerator mit ATmega8 Diese Anwendung eines AVR beschreibt einen Rechteckgenerator mit dem ATMEL ATmega8 mit folgenden Eigenschaften:
● ● ● ● ● ● ● ● ● ● ●
Einstellbarer Frequenzbereich: 0,25 Hz bis 8 MHz in 1024 Stufen Weiter Dynamikbereich der Frequenz ohne mechanisches Umschalten Umprogrammierung des Frequenzverlaufs möglich Einstellbare Pulsweite von 0,00 bis 100,00% in 1024 Stufen Pulsweiteneinstellung und Frequenz unabhängig voneinander Quarzbasis 16 MHz für stabile Frequenz Normaler und invertierter Ausgang Umschaltbare Polarität der Ausgänge (aktiv high, aktiv low) LCD-Anzeige mit umschaltbarer Anzeige von Frequenz, Zeit, Runden pro Minute, Pulsweite Anpassbar an ein- und zweizeilige LCD-Anzeigen mit 16 bis 40 Zeichen pro Zeile Englische oder deutsche Anzeigeversion wählbar
Hardware besteht aus dem AVR-Prozessor ATmega8, einem In-System-Programmieranschluss (ISP), einer LCD-Anzeige und diversen externen Anschlüssen.
1.1 Prozessorteil Der Prozessor ist mit folgenden externen Komponenten beschaltet. Die Betriebsspannung von 5 V wird über die Pins 7 (+5 V) und 8 (0 V) zugeführt und mit einem Keramikkondensator abgeblockt. Pin 1 (= RESET-Eingang) liegt über einen Widerstand an der positiven Betriebsspannung. An den beiden Anschlüssen Pin 9 und 10 (XT1, XT2) ist ein Quarz mit 16 Mhz angeschlossen. Die Umschaltung des Prozessortakts auf den externen Quarz erfolgt durch Setzen der entsprechenden Fuses. Die beiden Quarzanschlüsse sind mit Keramikkondensatoren von 22 pF zur Verbesserung des Anschwingverhaltens des internen Oszillators versehen. Die Betriebsspannung für den AD-Wandler wird über eine Drossel von 22 µH und einen Folienkondensator von 100 nF an Pin 20 (AVCC) zugeführt. Als Referenzspannung dient eine softwareseitige Zuschaltung von AVCC, daher ist der AREFPin 21 mit einem Folienkondensator gegen Masse geblockt.
1.2 ISP-Interface Das ISP-Interface dient der Programmierung des AVR in der fertig aufgebauten Schaltung. Für ISP verwendet werden die Portbits 5 (SCK), 4 (MISO), und 3 (MOSI) sowie der RESET an Pin 1. MISO dient auch der Ansteuerung der LCD. Die Belegung am 10-poligen ISP-Pfostenstecker entspricht dem ATMEL-/KANDA-Standard. Die am Pfostenstecker angeschlossene LED zeigt den aktiven Programmiervorgang an.
1.3 LCD-Anzeige Angeschlossen ist eine Standard-LCD-Anzeige. Für leichtes Wechseln der Anzeige ist ein 14-poliger Pfostenstecker vorgesehen, der der 14-poligen Anschlussleiste der LCD entspricht. Der Kontrollport der LCD ist an die Portbits PB0 (LCD-Enable) und PB4 (LCD-RS-Eingang) angeschlossen. LCD-Enable ist über einen Widerstand mit 0 V verbunden. Solange der Port B nicht initialisiert ist (nach dem Einschalten, beim Programmieren über ISP), ist dadurch sichergestellt, dass die LCD nicht durch Fehlsignale angesteuert wird. Der Eingang LCD-R/W ist dauerhaft mit 0 V verbunden, weil die Anzeige ausschließlich beschrieben wird und nicht ausgelesen werden muss. Der Datenport der LCD ist mit allen acht Bits des Ports D verbunden. Die Ansteuerung der LCD erfolgt also 8-bittig.
1.4 Externe Anschlässe Bis auf die LCD und die Stromversorgung, die über eigene Steckverbinder angeschlossen sind, sind alle weiteren externen Komponenten über einen 14-poligen Steckverbinder angeschlossen. Dadurch ist sichergestellt, dass die Platine ausgebaut und ohne diese Komponenten betrieben und getestet werden kann. Durch Auftrennung des Flachbandkabels können die externen Komponenten systematisch angeschlossen werden. 1.4.1 Einstellpotis Die ersten vier Adern des Flachbandkabels sind mit der Betriebsspannung und den AD-Kanälen verbunden. Die beiden AD-Wandlerkanäle ADC0 (Frequenz) und ADC1 (Pulsweite) sind an die Schleifer von 10-Gang-Potentiometern angeschlossen. Die Potentiometer sind an die Betriebsspannung angeschlossen. Ihr nominaler Widerstandswert und ihre Linearität sind unkritisch, weil die Einstellungen über die Anzeige kontrolliert werden können. Direkt am Potentiometer sind die eingestellten Spannungen mit Folienkondensatoren abgeblockt, um eingestreute Wechselspannungen zu vermeiden. 1.4.2 Schalter Die nächsten fünf Adern verbinden die Schalter mit den Porteingängen PC2 bis PC5. Der Schalter an PC5 ist nur notwendig, wenn eine einzeilige LCD-Anzeige verwendet wird. Bei Anschluss einer zweizeiligen LCD ist dieser Schalter wirkungslos. Alle Schalter verbinden im geschlossenen Zustand den entsprechenden Porteingang mit 0 V, da die inaktiven Eingänge mittels der Software mit Pullup-Widerständen auf die Betriebsspannung gezogen werden. 1.4.3 Ausgangsbuchsen Die folgenden drei Adern führen die Ausgänge des Timers (normal, invertiert) an die CINCH-Ausgangsbuchsen. Die Ausgänge liefern Rechteckspannungen mit den Ausgangscharakteristika der AVR-Treiber und liegen gleichspannunsgekoppelt an den Buchsen. Die Ausgänge sind kurzschlusssicher, aber gegen extern anliegende Spannungen nicht geschützt.
1.5 Netzteil Das Netzteil liefert eine stabilisierte Betriebsspannung von 5 V bei einem Strom von etwa 20 mA. Der Stromverbrauch ist vom Widerstandswert der Potentiometer abhängig. Prozessor und LCD-Anzeige bleiben unter 10 mA Verbrauch. Um einen Trafo mit 6 V Sekundärspannung verwenden zu können, ist die Gleichrichterbrücke mit Schottky-Dioden aufgebaut und ein Low-Drop-Spannungsregler eingesetzt. Bei Trafos mit 7,5 V Sekundärspannung und mehr können diese Komponenten gegen Standardwerte ausgetauscht werden. Der Ladekondensator ist großzügig dimensioniert, bei dem geringen Strom kann er auch kleiner gewählt werden. Die beiden Tantal-Elkos blocken Schwingneigungen des Spannungsreglers ab.
1.6 Aufbauhinweise
Der Aufbau ist mit einer Lochrasterpla recht unkritisch. Der 14polige Anschluss der LCD ist links angeordnet, der ebenfalls 14-polige Anschluss zu den externen Bedieneleme (Potis, Schalter, Ausgangsbuc rechts auf der Platine. Das Flachbandkab ist so belegt, dass es zu vier, fünf, drei und zwei Adern aufgetrennt werden kann und übersichtlich den verschiedenen Sektionen zugeführt werden kann. Wichtig sind die beiden Folienkondensatoren direkt an den Schleifern der beiden Potis, die eingestreute HF/NF abblocken. Der 10-polige ISP-Programmieranschluss wird nicht oft benötigt und ist daher nicht außen am Gehäuse zugänglich angebracht. Das kleine Netzteil sitzt links oben an der Gehäusewand.
2 Bedienung Nach dem Einschalten zeigt die LCD-Anzeige für etwa 2,5 Sekunden eine Meldung zur Gerätefunktion, die Softwareversion und das Copyright der Software an. Danach ist das Gerät betriebsbereit.
2.1 Schalter Mit dem Schalter Time wird zwischen der LCD-Ausgabe der Frequenz (Schalter offen) und der Zeit (Schalter geschlossen) umgeschaltet. Die Ausgabe der Frequenz erfolgt in Hz mit zwei Dezimalstellen, die Ausgabe der Zeit in Mikrosekunden. Beide Größen werden gerundet und mit Tausenderzeichen getrennt dargestellt. Ist die Frequenzausgabe ausgewählt, dann kann mit dem Schalter Rpm die Ausgabe von Umdrehungen pro Minute (= 60 * f) ausgewählt werden. Ist die Ausgabe der Zeit gewählt, bleibt dieser Schalter unwirksam. Mit dem Schalter Inv werden beide Ausgangssignale invertiert. Bei einzeiligen LCD-Anzeigen bewirkt der Schalter Pulse, dass anstelle der Frequenz/Zeit die Pulsweite in % angezeigt wird. Bei zweizeiligen LCDs beibt dieser Schalter unwirksam, die Pulsweite wird dauernd in der zweiten Zeile angezeigt.
2.2 Ausgangssignale Die Ausgangssignale stehen an den beiden CINCH-Buchsen in positiver und invertierter Form zur Verfügung. Um kapazitive Effekte auf die Flankensteilheit zu vermeiden, sollten kurze und unabgeschirmte Leitungen verwendet werden.
3 Funktionsweise Dieser Abschnitt erläutert die Funktionsweise des Prozessorteils, des ISP-Interfaces und der LCD-Anzeige.
3.1 Prozessorteil Der Prozessor ATmega8 arbeitet mit einem extern angeschlossenen Quarz als Taktgenerator. Da der Prozessor als Werkseinstellung mit dem internen RC-Oszillator bei 1 MHz arbeitet, müssen zuerst folgende Fuses des Mega8 umgestellt werden: ● ● ●
Das Umprogrammieren dieser Fuses kann entweder extern in einem Programmierboard (z.B. mit einem STK500 und dem ATMEL-Studio) erfolgen oder in der fertig aufgebauten Schaltung mit dem ISP-Interface (z.B. mit PonyProg2000). Beim Programmieren mit einem STK500 muss der Quarz zugeschaltet werden, da der Mega8 sonst nach dem Umstellen der Fuses ohne Quarz nicht mehr ansprechbar ist. Die Fuses sind richtig eingestellt, wenn eine der beiden letzten Optionen gewählt wird.
Bei PonyProg ist zu beachten, dass die Fuses invertiert dargestellt sind. Zur Orientierung: per Werkseinstellung sind CKSEL3..CKSEL0 auf 0001 und SUT1.. SUT0 auf 10 eingestellt. Mit dem Read-Button sollten die Fuses vorher ausgelesen werden. Natürlich muss der Quarz in der Schaltung vor dem Umprogrammieren der Fuses schon angeschlossen sein. Bei SUT0 kann ebenfalls der Haken ebenfalls entfernt werden (SUT1:SUT0 = 11). Die angeschlossenen Schalter werden zu Beginn des Programms mittels Software mit den Pull-Up-Widerständen auf positives Betriebsspannungs-Niveau gezogen. Sind die Schalter eingeschaltet (aktiviert), werden diese Eingangsleitungen auf logisch Null gesetzt. Der Schalter Pulse ist nur relevant, wenn eine einzeilige LCD-Anzeige verwendet wird. Die Frequenzerzeugung erfolgt mit dem internen 16-Bit-Zähler TC1 des ATmega8 im Fast-PWM-Modus. Das Schema zeigt die Funktionsweise des TC1 im Fast-PWM-Modus und die die Funktionen beeinflussenden Parameter (blau). Die aus dem Quarz
abgeleitete 16MHz-Taktfrequenz wird dazu im Vorteiler entweder durch 1, 8, 64, 256 oder 1024 geteilt und dem Zähler zugeführt. Bei Erreichen des eingestellten TOP-Wertes in dem Doppelregister ICR1 setzt der Zähler zurück und aktiviert die Compare-Ausgänge OC1A (Portbit PB1, Pin 15) und OC1B (Portbit PB2, Pin 16). Die Frequenz des Generators wird über den TOP-Wert in ICR1 festgelegt. Abhängig vom eingestellten Wert der Pulsweite werden die Vergleichswerte in den COMPA- und COMPB-Doppelregistern eingestellt. Erreicht der Zähler diese eingestellten Werte, werden die Ausgänge OC1A und OC1B deaktiviert und bleiben bis zum Erreichen von TOP inaktiv. Die beiden Ausgänge OC1A und OC1B sind von der Software komplementär eingestellt, d.h. sie erzeugen invertierte Signale gleicher Pulsdauer. Mit dem Schalter "Invert" wird diese Polarität an den Ausgängen software-mäßig invertiert. Die Frequenzeinstellung erfolgt mit dem Potentiometer P1. Da 1024 verschiedene diskrete Frequenzen eingestellt werden können, muss dafür ein Zehngang-Poti verwendet werden. Das Poti gibt eine Spannung zwischen 0 und 5 V ab, die mit dem AD-Wandler ADC0 (Portpin PC0, Pin 23) gemessen und in einen Hex-Wert zwischen 0x00 und 0x1F umgewandelt wird. Dieser Wert wird verwendet, um aus der einprogrammierten Tabelle (Lookup-Tabelle, Include-Datei rectgen_m8_table.inc) den zugehörigen TOP-Wert auszulesen, der beim nächsten Update in ICR1 geschrieben wird. Abhängig von der eingestellten Frequenz wird aus dem ADC0-Wert auch noch der Vorteilerwert des Zählers ermittelt und gespeichert. Die Pulsweiteneinstellung wird mit dem zweiten Zehngang-Potentiometer aufgenommen und über ADC1 (Portbit PC1, Pin 24) in einen Wert zwischen 0x00 und 0x1F umgewandelt. Der TOP-Wert wird mit diesem gemessenen Wert multipliziert und durch 1024 geteilt. Das Ergebnis wird beim nächsten Update in die Compare-Register COMPA und COMPB geschrieben. Im gleichen Zyklus wird noch der Schalter Invert eingelesen und die Ausgangspolarität an OC1A und OC1B eingestellt. Der TC1-Zähler läuft ohne Software-Eingriff freilaufend. Zum Aktualisieren der Einstellungen wird der 8-Bit-Zähler TC0 verwendet. Er teilt den Systemtakt durch 1024 (Vorteiler) und läft bei Erreichen eines Zählerstandes von 256 über (@16MHz: alle 16,384 ms). Er unterbricht den Programmablauf und zählt ein Register von 30 abwärts. Wird das Zählerregister Null (nach jeweils 492 ms), wird der AD-Wandler auf Kanal 0 eingestellt und die erste Messung auf ADC0 gestartet. Der AD-Wandler arbeitet mit einer durch 128 geteilten Taktfrequenz. Das Vorliegen des ersten Resultats löst einen ADCConversion-Complete-Interrupt aus. Nach Setzen eines Flagbits wird der ADC auf Kanal 1 umgestellt und erneut eine Messung gestartet. Liegt auch dessen Ergebnis vor, wird der AD-Wandler abgeschaltet und ein Behandlungsflag gesetzt. Die Umwandlung und Aktualisierung der TC1-Parameter erfolgt danach asynchron in der Hauptprogrammschleife. Die eingelesenen Werte werden umgewandelt und der Zähler auf die neuen Sollwerte gesetzt. Nach dem Programmieren von TC1 wird die LCD aktualisiert und der Hauptprogrammloop bis zum nächsten TC0-Interrupt mit Schlafen der CPU abgeschlossen.
3.2 LCD-Anzeige Die LCD-Anzeige ist mit dem 8-Bit-Datenport und den beiden Kontrollleitungen E(nable) und R(egister)S(elect) an den Prozessor angeschlossen. Die R(ead)/W(rite)-Leitung liegt dauernd auf Write, da die gesamte Steuerung über zeitgenaue Schleifen vorgenommen wird. Beim Programmstart wird nach einer Wartezeit für die interne Initialisierung die LCD-Anzeige auf folgende Modi eingestellt: ● ● ● ● ●
8-Bit-Interface Ein- oder zweizeilige LCD (je nach Voreinstellung) keine Display-Shift Cursor aus Blinken aus
Dann wird für 2,5 Sekunden eine Eröffnungsmeldung ausgegeben. Nach jeder Aktualisierung des TC1-Timers wird auch die Anzeige der LCD aktualisiert. Dazu wird zunächst der CTC-Wert aus ICR1 mit dem Vorteilerwert (1, 8, 64, 256, 1024) multipliziert. Ist die Anzeige der Frequenz ausgewählt (Schalter Time aus), dann wird die mit 100 multiplizierte Taktfrequenz (100*16.000.000) durch diesen Wert geteilt, um die Frequenz in Hz mit einer Auflösung von 0,01 Hz als Ganzzahl zu erhalten. Ist die Zeit ausgewählt (Schalter Time an), wird die mit dem Vorteilerwert multiplizierte CTC-Rate mit dem Faktor 25.600.000.000/clock (@16MHz: 1.600) multipliziert, um die Zeit in Mikrosekunden mal 100 als Ganzzahl zu erhalten. Bei einer zweizeiligen LCD wird der sich ergebende Wert für die Frequenz bzw. die Zeit in Zeile 1 ausgegeben, bei einer einzeiligen LCD nur dann, wenn der Schalter für die Ausgabe der Pulsweite (Schalter Pulse) nicht aktiviert ist. Die Pulsweite wird ermittelt, indem der Wert für COMPA bzw. COMPB mit 10.000 multipliziert und dann durch den CTCWert geteilt wird. Die erhaltene Ganzzahl gibt die Pulsweite mal 100 in % an und wird bei einer zweizeiligen LCD in Zeile 2, bei einer einzeiligen LCD bei eingeschaltetem Schalter Pulse in Zeile 1 mit einer Auflösung von 0,01% angezeigt. Die Anzeige wird ca. zwei Mal pro Sekunde aktualisiert. Bei höheren Frequenzen können aufgrund der auf 16 Bit begrenzten Auflösung Frequenzen und Pulsweiten nicht mehr exakt eingestellt werden. Dies ist dadurch erkennbar, dass Nachkommastellen auftreten. Durch die Berechnung der beiden Größen aus den tatsächlich verwendeten Werten wird sichergestellt, dass die angezeigten Größen auf jeden Fall zuverlässig sind.
3.3 ISP-Interface Das ISP-Interface dient der Aktualisierung der Software innerhalb der fertig betriebenen Schaltung. Die Daten- bzw. Taktsignale MOSI, MISO und SCK sind an einem 10-poligen Standard-Pfostenstecker angelegt. Über die RESET-Leitung (Portbit PC6, Pin 1) schaltet das ISP-Programmierinterface den Mega8 in den Programmiermodus, nach dem Ende des Programmierens wird der RESET-Eingang wieder freigegeben und ein Neustart des Prozessor ausgelöst. Die Programmier-LED zeigt einen aktiven Programmierzyklus an, dient ausschließlich diesem Zweck und kann ersatzlos entfallen, wenn diese Funktion nicht erforderlich ist oder wenn ein sechspoliger Programmieranschluss verwendet wird.
4 Software Die Software ist ausschließlich in Assembler geschrieben und in drei funktionelle Pakete aufgeteilt. Vor dem Assemblieren müssen eine Reihe von Einstellungen vorgenommen werden, um die Software an die vorhandene Hardware optimal anzupassen.
4.1 Einstellungen vor dem Assemblieren 4.1.1 Frequenztabelle Die Frequenztabelle in der Datei rectgen_m8_table.inc umfasst 1024 Worte für die Umsetzung der eingestellten Spannung in den CTC-Wert. Die Werte wurden mit einem Spreadsheet errechnet und als Include-Text-Datei exportiert. Bei Änderungen an dieser Tabelle ist zu beachten, dass die Übergänge zwischen den verschiedenen Vorteilerwerten in der Routine Convert in der Datei rectgen_m8_v1.asm ebenfalls an die geänderte Tabelle angepasst werden müssen (aktuelle Werte: 392, 225, 60 und 3). Wird die Taktfrequenz (Konstante clock) geändert, müssen auch die zugehörigen 5-Byte-Konstanten cDivFx und cDivUx entsprechend angepasst werden, da bei der automatischen Berechnung Überläufe auftreten würden. Die Konstanten cLcdLw und cLcdLn definieren die angeschlossene LCD. Die Konstante cLcdMicro definiert das Mikrozeichen auf der angeschlossenen LCD. Der Default ist auf ein u eingestellt, da der häufige Wert 0xE4 nicht auf allen LCDs funktioniert. Die Konstante cEn stellt die Tausender- und Dezimaltrennzeichen auf englisches Format um. 4.1.2 Diverse Schalter Die Anschlussfolge der Schalter Time, Rpm, Inv und Pulse lässt sich bei der Definition der Ports umstellen (Konstanten pbTime, pbRpm, pbInv und pbPwm). Die Schalter dbgon und dbghx dienen dem Debugging. Sie müssen für ordnungsgemäße Funktion des Progamms auf Null gesetzt sein.
4.2 Kommentierter Quellcode Der kommentierte Quellcode steht im HTML-Format ● ● ●
Rechteckgenerator mit ATmega8 - Quellcode LCD-Routinen
; ; ******************************************************* ; * Include-Routinen zur LCD-Ansteuerung, stellt die * ; * Basisroutinen fuer die Initiierung einer LCD und die* ; * Textausgabe zur Verfuegung, ausgelegt fuer ein- und * ; * mehrzeilige LCDs, 8-bit-interface, HD44780-kompati- * ; * ble Displays * ; * (C)2006 by g.schmidt, info!at!avr-asm-tutorial.net * ; ******************************************************* ; ; ; Charakteristiken der Hardware: ; .EQU pLcdDataOut = PORTD ; Daten-Port mit dem LCD verbunden ist .EQU pLcdDataIn = PIND ; dto., der Eingabe-Port .EQU pLcdDataDir = DDRD ; dto., Port-Richtung .EQU pLcdEOut = PORTB ; Kontroll-Port Bit E der LCD .EQU pLcdEDir = DDRB ; dto., Richtungs-Bit .EQU pLcdRsOut = PORTB ; dto., Bit LCD-RS der LCD .EQU pLcdRsDir = DDRB ; dto., Richtungs-Bit .EQU bLcdEOut = 0 ; Port-Bit E der LCD .EQU bLcdRSOut = 4 ; Port-Bit RS der LCD ; ; Weitere Bedingungen zum Assemblieren: ; ; Typ Name Erlaeuterung ; ----------------------------------------------------------; Konstante Clock Prozessorfrequenz in Hz, Bereich 1..16 MHz ; cLcdLw Anzahl Zeichen pro Zeile der LCD ; cLcdLn Anzahl Zeilen der LCD (1 oder 2) ; Register rmp Multi-Purpose Register (R16..R31) ; ----------------------------------------------------------; ; Stellt folgende Routinen zur Verfuegung: ; ; Name Parameter Erfuellt folgende Aufgaben ; --------------------------------------------------------; LcdInit Z:Text1 im Flash Initiiert die LCD and zeigt ; Speicher (null- den Text1 an auf den Z zeigt, ; terminiert), wartet fuer 1.5 Sekunden und ; Zeilen durch ; getrennt ; X:Text2 im Flash zeigt Text2 an, auf den X ; Verwendet R25:R24 zeigt ; LcdFlash Z: Text im Flash Zeigt den Text im Flash; speicher an, auf den Z zeigt ; LcdHome -Setzt die Anzeigenposition ; den Beginn von Zeile 1 ; LcdChar R0 = Zeichen Schreibt das Zeichen in R0 ; an die aktuelle LCD-Position ; LcdLine1 Z:Text im SRAM Zeigt den Text im SRAM auf ; Zeile 1 der LCD an ; LcdLine2 Z:Text im SRAM Zeigt den Text im SRAM auf ; Zeile 2 der LCD an ; ----------------------------------------------------; ; ----------------------------------------------; L C D I N I T I I E R U N G R O U T I N E ; ----------------------------------------------; LcdInit: cbi pLcdEOut,bLcdEOut ; loesche LCD Enable Bit sbi pLcdEDir,bLcdEOut ; E-Bit ist Ausgang sbi pLcdRsDir,bLcdRSOut ; dto., RS-Bit rcall Delay360ms ; warte auf die LCD-interne Initiierung ldi rmp,0xFF ; setze LCD-Datenport auf Ausgang out pLcdDataDir,rmp rcall Delay15ms ; warte 15 ms fuer interne Initiierung .IF cLcdLn == 1 ldi rmp,0x30 ; setze 8-Bit-Interface, einzeilige LCD .ELSE ldi rmp,0x38 ; setze 8-Bit-Interface, zweizeilige LCD .ENDIF rcall LcdStrobeC ; schreibe Kontrollwort an LCD rcall Delay40us ; warte 40 us .IF cLcdLn == 1 ldi rmp,0x30 ; setze 8-Bit-Interface, einzeilige LCD .ELSE ldi rmp,0x38 ; setze 8-Bit-Interface, zweizeilige LCD .ENDIF rcall LcdStrobeC ; schreibe Kontrollwort an LCD rcall Delay40us ; warte 40 us .IF cLcdLn == 1 ldi rmp,0x30 ; setze 8-Bit-Interface, einzeilige LCD .ELSE ldi rmp,0x38 ; setze 8-Bit-Interface, zweizeilige LCD .ENDIF rcall LcdStrobeC ; schreibe Kontrollwort an LCD rcall Delay40us ; warte 40 us ldi rmp,0x08 ; Display abschalten rcall LcdStrobeC ; schreibe Kontrollwort an LCD rcall Delay40us ; warte 40 us ldi rmp,0x01 ; Display loeschen rcall LcdStrobeC ; schreibe Kontrollwort an LCD rcall Delay1m64s ; warte 1.64 ms ldi rmp,0x06 ; Increment ein, Shift aus rcall LcdStrobeC ; schreibe Kontrollwort an LCD ldi rmp,0x0C ; activiere Display, kein Cursor, kein Blinken rcall LcdStrobeC ; schreibe Kontrollwort an LCD rcall LcdHome ; setze Cursorposition auf Anfang Zeile 1 rcall LcdFlash ; Schreibe Text im Flash ab Z auf Display rjmp Delay2s5 ; warte fuer 2.5 Sekunden ; ; ------------------------------------------------; L C D S C H R E I B - S U B R O U T I N E N ; ------------------------------------------------; ; Schreibe Text ab Position Z im Flash auf Display ; LcdFlash: lpm ; lese naechstes Zeichen aus dem Flashspeicher tst R0 ; Ende des Textes erreicht? breq LcdFlash2 .IF cLcdLn>1 ldi rmp,0x0D ; Wagenruecklauf-Zeichen? cp rmp,R0 brne LcdFlash1 ; zeige Zeichen an ldi rmp,0xC0 ; Adresse von Zeile 2 rcall LcdStrobeC ; an Kontrollport der LCD rcall Delay40us ; 40 us Verzoegerung adiw ZL,1 ; naechstes Zeichen rjmp LcdFlash ; und weiter LcdFlash1: .ENDIF out pLcdDataOut,R0 ; Zeichen in R0 an Datenport sbi pLcdRsOut,bLcdRSOut ; setze RS-Bit rcall LcdStrobeE ; E-Bit pulsen cbi pLcdRsOut,bLcdRSOut ; loesche RS-Bit rcall Delay40us ; 40 us Verzoegerung adiw ZL,1 rjmp LcdFlash ; naechtes Zeichen LcdFlash2: ret ; ; Setzt die LCD-Ausgabeposition auf den Anfang von Zeile 1 ; LcdHome: ldi rmp,0x02 ; setze Kontrollwort fuer Home rcall LcdStrobeC ; schreibe Kontrollwort rjmp Delay1m64s ; warte 1,64 ms ; ; gibt das Zeichen in rmp als Kontrollwort aus und pulst das E-Bit ; LcdStrobeC: out pLcdDataOut,rmp ; schreibe Kontrollwort auf Datenport cbi pLcdRsOut,bLcdRSOut ; loesche RS-Bit ; ; Pulst E-Bit mit korrekter Dauer ; LcdStrobeE: sbi pLcdEOut,bLcdEOut ; setze E-Bit Eins nop ; warte mindestens 1 Zyklus .IF Clock>2 ; mehr als 2 MHz Takt nop ; zwei NOP .ENDIF .IF Clock>4 ; mehr als 4 MHz Takt nop ; drei NOP .ENDIF .IF Clock>6 ; mehr als 6 MHz Takt nop ; vier NOP .ENDIF .IF Clock>8 ; mehr als 8 MHz Takt nop ; fuenf NOP .ENDIF .IF Clock>10 ; mehr als 10 MHz Takt nop ; sechs NOP .ENDIF .IF Clock>12 ; mehr als 12 MHz Takt nop ; sieben NOP .ENDIF .IF Clock>14 ; mehr als 14 MHz Takt nop ; acht NOP .ENDIF cbi pLcdEOut,bLcdEOut ; loesche E-Bit rjmp Delay40us ; Verzoegerung 40 us ; ; Zeige Zeichen in R0 an ; LcdChar: out pLcdDataOut,R0 ; Ausgabe des Zeichens in R0 sbi pLcdRsOut,bLcdRSOut ; setze RS-Bit rjmp LcdStrobeE ; pulse E-Bit ; ; Zeigt den Text ab Z im SRAM auf Zeile 1 der LCD an ; LcdLine1: ldi rmp,0x80 ; setze Cursor auf Zeile 1 rcall LcdStrobeC ldi rmp,cLcdLw LcdLine1a: ld R0,Z+ ; lese naechstes Zeichen aus SRAM rcall LcdChar dec rmp brne LcdLine1a ret ; ; Zeigt den Text ab Z im SRAM auf Zeile 2 der LCD an ; LcdLine2: ldi rmp,0xC0 ; setze Cursor auf Zeile 2 rcall LcdStrobeC ldi rmp,cLcdLw LcdLine2a: ld R0,Z+ ; lese naechstes Zeichen aus SRAM rcall LcdChar dec rmp brne LcdLine2a ret ; ; ------------------------------------------------------------; V E R Z O E G E R U N G S R O U T I N E N F U E R L C D ; ------------------------------------------------------------; .SET clockus = clock/1000000 .IF clockus == 0 ; Clock kleiner als 1 MHz .SET clockus = 1 .ENDIF ; ; Verzoegerung fuer 2.5 Sekunden ; Delay2s5: push rmp ldi rmp,2500/15+1 ; Anzahl 15ms-Verzoegerungen Delay2s5a: rcall Delay15ms dec rmp brne Delay2s5a pop rmp ret ; ; Verzoegerung fuer 360 Millisekunden ; Delay360ms: push rmp ldi rmp,360/15+1 ; Anzahl der 15ms-Verzoegerungen Delay360ms1: rcall Delay15ms dec rmp brne Delay360ms1 pop rmp ret ; ; Verzoegerung fuer 15 Millisekunden ; Delay15ms: push ZH ; sichere Z push ZL ldi ZH,HIGH((15000*clockus-16)/4) ldi ZL,LOW((15000*clockus-16)/4) Delay15ms1: sbiw ZL,1 brne Delay15ms1 pop ZL pop ZH ret ; ; Verzoegerung fuer 1.64 Millisekunden ; Delay1m64s: push ZH ; sichere Z push ZL ldi ZH,HIGH((1640*clockus-16)/4) ldi ZL,LOW((1640*clockus-16)/4) Delay1m64s1: sbiw ZL,1 brne Delay1m64s1 pop ZL pop ZH ret ; ; Verzoegerung 40 Mikrosekunden ; Delay40us: push rmp ldi rmp,(40*clockus-11)/3 Delay40us1: dec rmp brne Delay40us1 pop rmp ret ; ; Ende der LCD-Routinen ;
http://www.avr-asm-tutorial.net/avr_de/fcount/fcount_m8_v3.html (4 of 4)1/20/2009 7:39:38 PM
40MHz-Frequenzzähler mit ATmega8
Pfad: Home => AVR-Übersicht => Anwendungen => Frequenzzähler
40MHz-Frequenzzähler mit ATmega8 Diese Anwendung eines AVR beschreibt einen Frequenzzähler mit dem ATMEL ATmega8 mit folgenden Eigenschaften:
● ● ● ●
● ● ●
Digital- und Analogeingang, Spannungsmess-Eingang Eingangsstufe mit Vorverstärker und Vorteiler durch 16 Anzeige mit ein- oder zweizeiliger LCD-Anzeige Neun Mess- und Anzeigemodi, mit Potentiometer wählbar: ❍ 0: Frequenzmessung mit Vorteiler, Gatezeit 0,25 s, Ergebnis in Hz ❍ 1: Frequenzmessung ohne Vorteiler, Gatezeit 0,25 s, Ergebnis in Hz ❍ 2: Frequenzmessung als Periodenmessung mit Frequenz- umrechnung, Ergebnis in 0,01 Hz bzw. 0,001 Hz ❍ 3: Umdrehungsmessung, ohne Vorteiler, ueber Periodenmessung mit Umrechnung, Ergebnis in Upm ❍ 4: Periodendauer gesamte Schwingung, Ergebnis in Mikrosekunden ❍ 5: Periodendauer High-Periode, Ergebnis in Mikrosekunden ❍ 6: Periodendauer Low-Periode, Ergebnis in Mikrosekunden ❍ 7: Periodenanteil High-Periode, Ergebnis in 0,1% ❍ 8: Periodenanteil Low-Periode, Ergebnis in 0,1% ❍ 9: Spannungsmessung, Ergebnis in 0,001 Volt Die Auswahl von Modus 9 (rechter Poti-Anschlag) schaltet die Frequenz-/Zeit-/Periodenmessungen aus. SIO-Interface zum Anschluss an PC Quarzoszillator 16 MHz
Änderungen gegenüber älteren Versionen ● ● ●
Autorange wurde entfernt, da die Bedienung sich als holprig erwies. Der Vorteiler im Analogteil wurde auf 16 umgestellt (Ausgang von QC auf QD umlöten), damit bis 40 MHz gemessen werden kann. Der Zähler zählt ohne Vorteiler nur Frequenzen bis zu ca. 5 MHz. Das serielle Interface wurde um viele Funktionen erweitert (Spannungs- und Frequenzausgabe, Intervall parametergesteuert, Ausgabe der Parameter).
1 Hardware Der Frequenzzähler besteht aus einem Eingangsteil (Vorverstärker und Vorteiler) und dem Prozessor- und Anzeigeteil.
1.1 Eingangsteil
Das Eingangsteil hat einen Analog- und einen Digitaleingang. Das Signal am Analogeingang wird mit dem schnellen Operationsverstärker NE592 verstärkt. Das Ausgangssignal wird dem Pegel angepasst und dem Schmitt-Trigger-NAND 74HCT132 zugeführt. Der Digitaleingang wird direkt dem Schmitt-Trigger-NAND zugeführt. Das Ausgangssignal des Schmitt-Triggers wird, abhängig vom Steuerungssignal des Prozessors, entweder direkt dem Prozessor zum Zählen zugeführt oder vorher im Binärzähler 74HCT93 durch 16 geteilt.
1.2 Prozessorteil
Der Prozessorteil ist um den ATmega8 herum aufgebaut. Der Prozessor wird mit einem Quarz von 16 MHz an den Anschlüssen XTAL1 und XTAL2 getaktet. Die beiden Keramikkondensatoren von 22 pF dienen dem besseren Anschwingen des Quarzoszillators. Die Versorgung erfolgt über den GND-Anschluss an Pin 8 und VCC an Pin 7, die mit einem Keramikkondensator von 100 nF abgeblockt sind. Die Versorgung für den AD-Wandler wird über den GND-Anschluss Pin 22 und über eine Drossel von 22 µH am AVCC-Pin 20 zugeführt, der ebenfalls mit 100 nF geblockt ist. Die interne Referenzspannung wird am AREF-Pin 21 mit einem Folienkondensator von 100 nF geglättet. Der AD-Wandler-Eingang PC1 ist mit dem Schleifer des Potentiometers verbunden, an dem der Mess- und Anzeigemodus eingestellt wird. Der Widerstand von 100 k begrenzt dessen Ausgangsspannung auf 2,5 V. Wird nur ein bestimmter Messmode benötigt, kann das Potentiometer auch gegen einen Trimmer oder einen festen Spannungsteiler aus Widerstäden ersetzt werden. Am AD-Wandler-Kanal ADC0 (PC0) wird über einen Spannungsteiler das Analogsignal für die Spannungsmessung zugeführt. Im dargestellten Fall ist die Messung auf einen Vollausschlag von 5,12 V eingestellt. Durch Ändern der beiden Teiler-Widerstände kann die Auflösung der Spannungsmessung in weiten Bereichen verändert werden. Der AD-Wandler-Eingang ist wegen des hochohmigen Eingangs gegen Einstreuungen abgeblockt. Die Signale TXD und RXD stellen das serielle Interface dar und sind mit dem Treiber-IC MAX232 verbunden. Die am MAX232 angeschlossenen Elkos dienen der Spannungserzeugung für die RS232-Signale. Diese liegen über einen 10-poligen Pfostenstecker an der neunpoligen Buchse an. Die RS232-Signale CTS, DSR und CD sind über die Widerstände 2k2 dauernd aktiviert. Der I/O-Pin PC5 steuert den Vorteiler. Bei High erfolgt keine Vorteilung, bei Low eine Vorteilung durch 16. Der Signaleingang aus dem Vorverstärker/Vorteiler wird sowohl dem Eingang INT0, für die Flankenmessung, als auch dem Eingang T0, für die Zählmessung, zugeführt. Die Portbits PB0 bis PB3 dienen der vierbittigen Ansteuerung des Datenports der LCD-Anzeige. Der E(nable)-Eingang der LCD wird über PB5, der RS-Eingang über PB4 an gesteuert. Der E-Eingang ist bei inaktivem Portbit über 100 k auf Masse gelegt, um unbeabsichtigte Signale an der LCD zu vermeiden. Am VO-Eingang der LCD ist der Kontrast der Anzeige mit einem Trimmer von 10 k einstellbar. Die Portbits MOSI, SCK und MISO sowie das RESET-Signal liegen am 10-poligen ISP-Port an, über den der Prozessor in der Schaltung programmiert werden kann. Die rote LED dient der Anzeige, dass der Programmiervorgang aktiv ist. Über die anliegende Betriebsspannung wird das Programmierinterface mit Strom versorgt. Der RESET-Eingang ist über ein Widerstand von 10 k mit der Betriebsspannung verbunden.
1.3 LCD-Anzeige
Die LCD-Anzeige ist über den 14-poligen Standardstecker an die Prozessorplatine angeschlossen. Verwendet werden können ein- und zweizeilige LCD-Anzeigen mit 16 bis 40 Zeichen pro Zeile (einstellbar per Software).
1.4 SIO-Anschluss Der Zähler verfügt über einen SIO-Anschluss. Über dieses Interface kann das Messergebnis verfolgt werden. Darüber können auch Ausgabeparameter eingestellt werden.
1.5 Aufbauhinweise Die gesamte Schaltung wurde auf einer Lochrasterplatine von 10 * 5 cm aufgebaut und mit Kupferlackdraht verdrahtet.
Zwei der Befestigungsschrauben der LCD-Anzeige dienen auch der Befestigung der Lochrasterplatine.
Alle Komponenten, einschließlich einer kleinen Netzteilversorgung und einer 9 V-Batterie für netzunabhängigen Betrieb passen in ein kleines Gehäuse.
2. Bedienung Die Bedienung ist sehr einfach. Die Auswahl zwischen dem Digital- und dem Analog-Eingang ist nicht erforderlich: angezeigt werden stets beide zusammen. Dazu wird bei offenem Eingang der Trimmer am Analogverstärker gerade so weit heruntergedreht, dass keine Fehlsignale resultieren.
2.1 Potentiometer für Mode-Auswahl Am Mode-Potentiometer wird der Anzeige-Modus eingestellt. Bei der Frequenzmessung erfolgt die Auswahl, ob Impulse gezählt werden oder ob die Dauer der Flanken gemessen wird, durch die Modus-Auswahl. Die Darstellung der Ergebnisse erfolgt bei zweizeiligen LCD-Anzeigen mit 16 Zeichen und mehr folgendermaßen: Mode
Messgröße
Messmethode
Anzeigenformat
0
Frequenz
Zählung, Vorteiler=16 F=99.999.999 Hz
1
Frequenz
Zählung, Vorteiler=1 f= 9.999.999 Hz
2
Frequenz
Periodenmessung
v= 9.999,999 Hz
3
Umdrehungszahl
Periodenmessung
u= 9.999.999 rpm
4
Periode
Periodenmessung
t=99.999.999 us
5
High-Periode
Periodenmessung
h=99.999.999 us
6
Low-Periode
Periodenmessung
l=99.999.999.us
7
High-Periodenanteil Periodenmessung
P=100,0%
8
Low-Periodenanteil Periodenmessung
p=100,0%
9
Spannung
U=9,999V
AD-Wandlung
Bei einzeiligen LCD-Anzeigen wird die Spannung nur angezeigt, wenn Mode 9 eingestellt ist. Bei weniger als 16 Zeichen pro Zeile werden die Tausender-Trennzeichen und die Dimensionen nicht angezeigt. Die Messgrößen werden nur angezeigt, wenn der Messwert die entsprechenden Positionen nicht benötigt.
2.2 Spannungsmessung Die am Spannungs-Messeingang anliegende Spannung wird vier Mal pro Sekunde gemessen und angezeigt.
3. Software Die Software ist vollständig in Assembler geschrieben. Vor dem Assemblieren des Quellcodes sind unbedingt wichtige Einstellungen (siehe 3.1) vorzunehmen. Beim Programmieren in den ATmega8 sind unbedingt noch dessen Fuses zu verändern (siehe 3.2).
3.1 Einstellungen vor dem Assemblieren Die folgenden Einstellungen sind vor dem Assemblieren in der Datei fcountV03.asm zu kontrollieren und ggfs. zu ändern: ● ● ● ● ● ● ● ● ●
Die Schalter debug und debugpulse muessen auf 0 stehen. Wenn ein LCD-Display angeschlossen ist, muss cDisplay auf 1 stehen, sonst auf 0. Wenn das angeschlossene LCD-Display 8 Zeichen pro Zeile darstellen kann, muss cDisplay8 auf 1 stehen. Wenn es 16 oder 20 Zeichen pro Zeile darstellen kann, muss cDisplay8 auf 0 stehen. Wenn das angeschlossene LCD-Display eine einzige Zeile darstellen kann, muss cDisplay2 auf 0 stehen. Wenn es zwei oder mehr Zeilen darstellen kann, muss cDisplay2 auf 1 stehen. Wenn die serielle Schnittstelle angeschlossen ist und bedient werden soll, muss cUart auf 1 stehen. Ist keine serielle Schnittstelle angeschlossen oder soll sie nicht verwendet werden, wird cUart auf 0 gesetzt. Ist der Vorteiler durch 16 an einem anderen Portbit als PC5 angeschlossen, sind die Ports pPresc und pPrescD sowie das Portbit bPresc entsprechend zu aendern. Wird der Prozessortakt mit einer anderen Frequenz als 16 MHz betrieben, ist diese in cFreq anzugeben. Soll die serielle Schnittstelle mit einer anderen Baudrate als 9600 arbeiten, ist cBaud entsprechend zu ändern. Ist der Vorteiler fuer die Spannungsmessung mit zwei anderen Widerstaenden bestueckt als mit 1M, sind die beiden Werte cR1 und cR2 entsprechend zu aendern. Wenn die angezeigte Spannung wesentlich von der Eingangsspannung abweicht, ist cRin zu aendern: kleinere Werte fuer cRin ergeben eine hoehere angezeigte Spannung, groeszere eine niedrigere angezeigte Spannung.
3.2 Fuses Im gelieferten Zustand sind ATmega8-Prozessoren auf den internen RC-Oszillator eingestellt. Damit der ATmega8 mit dem externen Quarz als Oszillator arbeitet, müssen die Fuses umgestellt werden. Die Einstellungen sind mit dem ATMEL Studio folgendermaßen zu ändern:
Die Einstellungen sind mit PonyProg 2000 sind folgendermaßen zu ändern:
Bitte beachten: Nach dem Ändern der Fuses ist der ATmega8 nur noch ansprechbar, wenn ein Quarz angeschlossen ist oder der Takt extern zugeführt wird! Beim Studio ist daher der XTAL Jumper zu stecken.
; **************************************************** ; * Frequenzzaehler, Drehzahlmesser und Voltmeter * ; * fuer ATmega8 bei 16 MHz Taktfrequenz (Quarzosz.) * ; * ohne Autorange, mit Vorteiler /1 oder /16 * ; * Version 0.3 (C)2009 by [email protected] * ; **************************************************** ; .INCLUDE "m8def.inc" ; .EQU debug = 0 .EQU debugpulse = 0 ; ; Switches for connected hardware ; .EQU cDisplay = 1 ; LCD display connected .EQU cDisplay8 = 0 ; displays 8 characters per line instead of 16 .EQU cDisplay2 = 1 ; two line LCD display connected .EQU cUart = 1 ; Uart active ; attached prescaler on port C .EQU pPresc = PORTC ; prescaler by 16 output attached to port C .EQU pPrescD = DDRC ; data direction of prescaler .EQU bPresc = 5 ; bit 5 enables prescaler by 16 ; ; ================================================ ; Other hardware depending stuff ; ================================================ ; .EQU cFreq = 16000000 ; Clock frequency processor in cycles/s .IF cUart .EQU cBaud = 9600 ; If Uart active, define Baudrate .ENDIF .EQU bLcdE = 5 ; LCD E port bit on Port B .EQU bLcdRs = 4 ; Lcd RS port bit on Port B ; ; ================================================ ; Constants for voltage measurement ; ================================================ ; ; Resistor network as pre-divider for the ADC ; -------------------------------------; R1 R2(k) Meas Accur. MaxVoltage ; kOhm kOhm Volt mV/dig Volt ; -------------------------------------; 1000 1000 5,12 5 10 ; 1000 820 5,68 6 11 ; 1000 680 6,32 6 12 ; 1000 560 7,13 7 14 ; 1000 470 8,01 8 15 ; 1000 330 10,32 10 20 ; 1000 270 12,04 12 23 ; 1000 220 14,20 14 27 ; 1000 180 16,78 16 32 ; 1000 150 19,63 19 38 ; 1000 120 23,98 23 46 ; 1000 100 28,16 28 55 ; .EQU cR1 = 1000 ; Resistor between ADC input and measured voltage .EQU cR2 = 1000 ; Resistor between ADC input and ground .EQU cRin = 8250 ; Input resistance ADC, experimental ; ; Other sSoft switches ; .EQU cNMode = 3 ; number o0f measurements before mode changes .EQU cDecSep = ',' ; decimal separator for numbers displayed .EQU c1kSep = '.' ; thousands separator .EQU nMeasm = 4 ; number of measurements per second .IF (nMeasm<4)||(nMeasm>7) .ERROR "Number of measurements outside acceptable range" .ENDIF ; ; ================================================ ; Hardware connections ; ================================================ ; ___ ___ ; RESET |1 |_| 28| Prescaler divide by 16 output ; RXD |2 A 27| ; TXD |3 T 26| ; Time inp |4 M 25| ; |5 E 24| Mode select input, 0..2.56 V ; Count in |6 L 23| Voltage input, 0..2.56 V ; VCC |7 22| GND ; GND |8 A 21| AREF (+2.56 V, output) ; XTAL1 |9 T 20| AVCC input ; XTAL2 |10 m 19| SCK/LCD-E ; |11 e 18| MISO/LCD-RS ; |12 g 17| MOSI/LCD-D7 ; |13 a 16| LCD-D6 ; LCD-D4 |14 8 15| LCD-D5 ; |_________| ; ; ; ================================================ ; Derived constants ; ================================================ ; .EQU cR2c = (cR2 * cRin) / (cR2+cRin) .EQU cMultiplier = (641 * (cR1+cR2c))/cR2c ; used for voltage multiplication .EQU cMaxVoltage = 1024*cMultiplier/256 ; in mV .EQU cSafeVoltage = (cMaxVoltage * 5000) / 2560 .EQU cTDiv = 1000/nMeasm ; interval per measurement update ; calculating the CTC and prescaler values for TC1 (frequency measurement) .SET cCmp1F = cFreq/32 ; CTC compare value with counter prescaler = 8 .SET cPre1F = (1<<WGM12)|(1<2097120 .SET cCmp1F = cFreq/256 ; CTC compare value with counter prescaler = 64 .SET cPre1F = (1<<WGM12)|(1<16776960 .SET cCmp1F = cFreq/1024 ; CTC compare value with counter prescaler = 256 .SET cPre1F = (1<<WGM12)|(1<2040000 .SET cCmp2 = cFreq/32000 .SET cPre2 = (1<8160000 .SET cCmp2 = cFreq/64000 .SET cPre2 = (1<16320000 .SET cCmp2 = cFreq/128000 ; counter prescaler = 128 .SET cPre2 = (1< Presc2 => TC2 => CTC => rTDiv => ; ADC0 conversion => ADC1 conversion => bAdc-flag ; ; Main interval timer TC2 ; - uses TC2 as 8-bit-CTC, with compare interrupt ; - starts a ADC conversion ; - on ADC conversion complete: ; * store ADC result ; * convert ADC result ; * if a new counter result: convert this ; * if Uart connected and monitoring f/U: display on Uart ; * if LCD connected and display mode: display f/U result ; ; Operation at 16 MHz clock: ; cFreq => Prescaler/128 => CTC(125) => rTDiv(250) ; 16MHz => 125 kHz => 1 kHz => 4 Hz ; ; Frequeny counting modes (Mode = 0 and 1) ; - uses TC0 as 8-bit-counter to count positive edges ; - uses TC1 as 16-bit-counter to time-out the counter after 250 ms ; ; Timer modes (Mode = 2 to 8) ; - uses edge detection on external INT0 for timeout ; - uses TC1 as 16-bit-counter to time-out from edge to edge ; ; Voltage only (Mode = 9) ; - Timers TC0 and TC1 off ; - Timer TC2 times interval ; ; ============================================== ; Reset and Interrupt Vectors starting here ; ============================================== ; .CSEG .ORG $0000 ; ; Reset/Intvectors ; rjmp Main ; Reset rjmp Int0Int; Int0 reti ; Int1 rjmp TC2CmpInt ; TC2 Comp reti ; TC2 Ovf reti ; TC1 Capt rjmp Tc1CmpAInt ; TC1 Comp A reti ; TC1 Comp B rjmp Tc1OvfInt ; TC1 Ovf rjmp TC0OvfInt ; TC0 Ovf reti ; SPI STC .IF cUart rjmp SioRxcIsr ; USART RX .ELSE reti ; USART RX .ENDIF reti ; USART UDRE reti ; USART TXC rjmp AdcCcInt ; ADC Conv Compl reti ; EERDY reti ; ANA_COMP reti ; TWI reti ; SPM_RDY ; ; ============================================= ; ; Interrupt Service Routines ; ; ============================================= ; ; TC2 Compare Match Interrupt ; counts rTDiv down, if zero: starts an AD conversion ; TC2CmpInt: in rSreg,SREG ; save SREG dec rTDiv ; count down brne TC2CmpInt1 ; not zero, interval not ended ldi rimp,(1<
http://www.avr-asm-tutorial.net/avr_de/fcount/fcountV03.asm
rol XL rol XH sub rmp,rRes1 ; * 1000 sbc R0,rRes2 sbc rDelL,rRes3 sbc rDelH,rRes4 sbc XL,ZH sbc XH,ZH cp XL,rDiv1 ; overflow? cpc XH,rDiv2 cpc ZL,rDiv3 cpc ZH,rDiv4 brcc CalcPwO clr rRes1 ; clear result inc rRes1 clr rRes2 clr rRes3 clr rRes4 CalcPw1: ; dividing loop lsl rmp ; multiply by 2 rol R0 rol rDelL rol rDelH rol XL rol XH rol ZL rol ZH cp XL,rDiv1 ; compare with divisor cpc XH,rDiv2 cpc ZL,rDiv3 cpc ZH,rDiv4 brcs CalcPw2 ; smaller, roll zero in sub XL,rDiv1 ; subtract divisor sbc XH,rDiv2 sbc ZL,rDiv3 sbc ZH,rDiv4 sec ; roll one in rjmp CalcPw3 CalcPw2: clc CalcPw3: ; roll result rol rRes1 rol rRes2 rol rRes3 rol rRes4 brcc CalcPw1 ; roll on lsl rDelL ; round result rol XL rol XH rol ZL rol ZH cp XL,rDiv1 cpc XH,rDiv2 cpc ZL,rDiv3 cpc ZH,rDiv4 brcs CalcPw4 ldi rmp,1 ; round up add rRes1,rmp ldi rmp,0 adc rRes2,rmp adc rRes3,rmp adc rRes4,rmp CalcPw4: tst rRes4 ; check rel="nofollow"> 1000 brne CalcPwE tst rRes3 brne CalcPwE ldi rmp,LOW(1001) cp rRes1,rmp ldi rmp,HIGH(1001) cpc rRes2,rmp brcc CalcPwE clc ; no error ret CalcPwE: ; error sec ret ; ; Display the binary in R2:R1 in the form " 100,0%" ; DisplPw: ldi XH,HIGH(sResult) ldi XL,LOW(sResult) ldi rmp,' ' st X+,rmp st X+,rmp clr R0 ldi ZH,HIGH(1000) ldi ZL,LOW(1000) rcall DisplDecX2 ldi ZH,HIGH(100) ldi ZL,LOW(100) rcall DisplDecX2 ldi ZL,10 inc R0 rcall DisplDecX2 ldi rmp,cDecSep st X+,rmp ldi rmp,'0' add rmp,rRes1 st X+,rmp ldi rmp,'%' st X+,rmp .IF ! cDisplay8 ldi rmp,8 ldi ZL,' ' DisplPw1: st X+,ZL dec rmp brne DisplPw1 .ENDIF ret ; ; If the first characters in the result buffer are empty, ; place the character in ZL here and add equal, if possible ; DisplMode: ldi XH,HIGH(sResult+1) ; point to result buffer ldi XL,LOW(sResult+1) ld rmp,X ; read second char cpi rmp,' ' brne DisplMode1 ldi rmp,'=' st X,rmp DisplMode1: sbiw XL,1 ld rmp,X ; read first char cpi rmp,' ' brne DisplMode2 st X,ZL DisplMode2: ret ; ;================================================= ; Display binary numbers as decimal ;================================================= ; ; Converts a binary in R2:R1 to a digit in X ; binary in Z ; DecConv: clr rmp DecConv1: cp R1,ZL ; smaller than binary digit? cpc R2,ZH brcs DecConv2 ; ended subtraction sub R1,ZL sbc R2,ZH inc rmp rjmp DecConv1 DecConv2: tst rmp brne DecConv3 tst R0 brne DecConv3 ldi rmp,' ' ; suppress leading zero rjmp DecConv4 DecConv3: subi rmp,-'0' DecConv4: st X+,rmp ret ; ; Display fractional number in R3:R2:(Fract)R1 ; DisplFrac: ldi XH,HIGH(sResult) ldi XL,LOW(sResult) .IF ! cDisplay8 ldi rmp,' ' st X+,rmp st X+,rmp .ENDIF clr R0 ldi ZH,HIGH(10000) ldi ZL,LOW(10000) rcall DisplDecY2 ldi ZH,HIGH(1000) ldi ZL,LOW(1000) rcall DisplDecY2 .IF ! cDisplay8 ldi rmp,c1kSep tst R0 brne DisplFrac0 ldi rmp,' ' DisplFrac0: st X+,rmp .ENDIF ldi ZL,100 rcall DisplDecY1 ldi ZL,10 rcall DisplDecY1 ldi rmp,'0' add rmp,R2 st X+,rmp tst R1 ; fraction = 0? brne DisplFrac1 ldi rmp,' ' st X+,rmp ldi rmp,'H' st X+,rmp ldi rmp,'z' st X+,rmp .IF ! cDisplay8 ldi rmp,' ' st X+,rmp st X+,rmp st X+,rmp st X+,rmp .ENDIF ret DisplFrac1: ldi rmp,cDecSep st X+,rmp .IF cDisplay8 ldi ZL,2 .ELSE ldi ZL,3 .ENDIF DisplFrac2: clr rRes3 clr rRes2 mov R0,rRes1 ; * 1 lsl rRes1 ; * 2 adc rRes2,rRes3 lsl rRes1 ; * 4 rol rRes2 add rRes1,R0 ; * 5 adc rRes2,rRes3 lsl rRes1 ; * 10 rol rRes2 ldi rmp,'0' add rmp,rRes2 st X+,rmp dec ZL brne DisplFrac2 .IF ! cDisplay8 ldi rmp,' ' st X+,rmp ldi rmp,'H' st X+,rmp ldi rmp,'z' st X+,rmp ldi rmp,' ' st X+,rmp .ENDIF ret ; ; Convert a decimal in R4:R3:R2, decimal in ZH:ZL ; DisplDecY2: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; overflow byte DisplDecY2a: cp rRes2,ZL cpc rRes3,ZH cpc rRes4,rDiv2 brcs DisplDecY2b ; ended sub rRes2,ZL ; subtract sbc rRes3,ZH sbc rRes4,rDiv2 inc rDiv1 rjmp DisplDecY2a DisplDecY2b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecY2c ldi rmp,' ' DisplDecY2c: st X+,rmp ret ; ; Convert a decimal decimal in R:R2, decimal in ZL ; DisplDecY1: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; overflow byte DisplDecY1a: cp rRes2,ZL cpc rRes3,rDiv2 brcs DisplDecY1b ; ended sub rRes2,ZL ; subtract sbc rRes3,rDiv2 inc rDiv1 rjmp DisplDecY1a DisplDecY1b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecY1c ldi rmp,' ' DisplDecY1c: st X+,rmp ret ; ; Display a 4-byte-binary in decimal format on result line 1 ; 8-bit-display: "12345678" ; 16-bit-display: " 12.345.678 Hz " ; Displ4Dec: ldi rmp,BYTE1(100000000) ; check overflow cp rRes1,rmp ldi rmp,BYTE2(100000000) cpc rRes2,rmp ldi rmp,BYTE3(100000000) cpc rRes3,rmp ldi rmp,BYTE4(100000000) cpc rRes4,rmp brcs Displ4Dec1 rjmp CycleOvf Displ4Dec1: clr R0 ; suppress leading zeroes ldi XH,HIGH(sResult) ; X to result buffer ldi XL,LOW(sResult) .IF ! cDisplay8 ldi rmp,' ' ; clear the first two digits st X+,rmp st X+,rmp .ENDIF ldi ZH,BYTE3(10000000) ; 10 mio ldi ZL,BYTE2(10000000) ldi rmp,BYTE1(10000000) rcall DisplDecX3 ldi ZH,BYTE3(1000000) ; 1 mio ldi ZL,BYTE2(1000000) ldi rmp,BYTE1(1000000) rcall DisplDecX3 .IF ! cDisplay8 ldi rmp,c1kSep ; set separator tst R0 brne Displ4Dec2 ldi rmp,' ' Displ4Dec2: st X+,rmp .ENDIF ldi ZH,BYTE3(100000) ; 100 k ldi ZL,BYTE2(100000) ldi rmp,BYTE1(100000) rcall DisplDecX3 ldi ZH,HIGH(10000) ; 10 k ldi ZL,LOW(10000) rcall DisplDecX2 ldi ZH,HIGH(1000) ; 1 k ldi ZL,LOW(1000) rcall DisplDecX2 .IF ! cDisplay8 ldi rmp,c1kSep ; set separator tst R0 brne Displ4Dec3 ldi rmp,' ' Displ4Dec3: st X+,rmp .ENDIF ldi ZL,100 ; 100 rcall DisplDecX1 ldi ZL,10 rcall DisplDecX1 ldi rmp,'0' ; 1 add rmp,R1 st X+,rmp ret ; ; Convert a decimal in R3:R2:R1, decimal in ZH:ZL:rmp ; DisplDecX3: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; subtractor for byte 4 DisplDecX3a: cp rRes1,rmp ; compare cpc rRes2,ZL cpc rRes3,ZH cpc rRes4,rDiv2 brcs DisplDecX3b ; ended sub rRes1,rmp ; subtract sbc rRes2,ZL sbc rRes3,ZH sbc rRes4,rDiv2 inc rDiv1 rjmp DisplDecX3a DisplDecX3b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecX3c ldi rmp,' ' DisplDecX3c: st X+,rmp ret ; ; Convert a decimal in R3:R2:R1, decimal in ZH:ZL ; DisplDecX2: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; next byte overflow DisplDecX2a: cp rRes1,ZL cpc rRes2,ZH cpc rRes3,rDiv2 brcs DisplDecX2b ; ended sub rRes1,ZL ; subtract sbc rRes2,ZH sbc rRes3,rDiv2 inc rDiv1 rjmp DisplDecX2a DisplDecX2b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecX2c ldi rmp,' ' DisplDecX2c: st X+,rmp ret ; ; Convert a decimal in R2:R1, decimal in ZL ; DisplDecX1: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; next byte overflow DisplDecX1a: cp rRes1,ZL cpc rRes2,rDiv2 brcs DisplDecX1b ; ended sub rRes1,ZL ; subtract sbc rRes2,rDiv2 inc rDiv1 rjmp DisplDecX1a DisplDecX1b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecX1c ldi rmp,' ' DisplDecX1c: st X+,rmp ret ; ;================================================= ; Delay routines ;================================================= ; Delay10ms: ldi rDelH,HIGH(10000) ldi rDelL,LOW(10000) rjmp DelayZ Delay15ms: ldi rDelH,HIGH(15000) ldi rDelL,LOW(15000) rjmp DelayZ Delay4_1ms: ldi rDelH,HIGH(4100) ldi rDelL,LOW(4100) rjmp DelayZ Delay1_64ms: ldi rDelH,HIGH(1640) ldi rDelL,LOW(1640) rjmp DelayZ Delay100us: clr rDelH ldi rDelL,100 rjmp DelayZ Delay40us: clr rDelH ldi rDelL,40 rjmp DelayZ ; ; Delays execution for Z microseconds ; DelayZ: .IF cFreq>18000000 nop nop .ENDIF .IF cFreq>16000000 nop nop .ENDIF .IF cFreq>14000000 nop nop .ENDIF .IF cFreq>12000000 nop nop .ENDIF .IF cFreq>10000000 nop nop .ENDIF .IF cFreq>8000000 nop nop .ENDIF .IF cFreq>6000000 nop nop .ENDIF .IF cFreq>4000000 nop nop .ENDIF sbiw rDelL,1 ; 2 brne DelayZ ; 2 ret ; ; ========================================= ; Main Program Start ; ========================================= ; main: ldi rmp,HIGH(RAMEND) ; set stack pointer out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp clr rFlg ; set flags to default ; .IF debug .EQU number = 100000000 ldi rmp,BYTE4(number) mov rRes4,rmp mov rDiv4,rmp ldi rmp,BYTE3(number) mov rRes3,rmp mov rDiv3,rmp ldi rmp,BYTE2(number) mov rRes2,rmp mov rDiv2,rmp ldi rmp,BYTE1(number) mov rRes1,rmp mov rDiv1,rmp rcall CycleM6 beloop: rjmp beloop .ENDIF .IF debugpulse .EQU nhigh = 100000000 .EQU nlow = 15000 ldi rmp,BYTE4(nhigh) sts sCtr+3,rmp ldi rmp,BYTE3(nhigh) sts sCtr+2,rmp ldi rmp,BYTE2(nhigh) sts sCtr+1,rmp ldi rmp,BYTE1(nhigh) sts sCtr,rmp ldi rmp,BYTE4(nlow) mov rRes4,rmp mov rDiv4,rmp ldi rmp,BYTE3(nlow) mov rRes3,rmp mov rDiv3,rmp ldi rmp,BYTE2(nlow) mov rRes2,rmp mov rDiv2,rmp ldi rmp,BYTE1(nlow) mov rRes1,rmp mov rDiv1,rmp sbr rFlg,1<
http://www.avr-asm-tutorial.net/avr_de/fcount/fcountV03.asm
mov rRes2,rRes3 mov rRes3,rRes4 clr rRes4 rcall Displ4Dec .IF ! cDisplay8 ldi rmp,' ' st X+,rmp ldi rmp,'H' st X+,rmp ldi rmp,'z' st X+,rmp ldi rmp,' ' st X,rmp .ENDIF ldi ZL,'v' rcall DisplMode ret ; ; Measure time, display rounds per minute ; CycleM3: rcall Divide clr R0 ; overflow detection clr rmp lsl rRes1 ; * 2 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp lsl rRes1 ; * 4 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp mov rDiv1,rRes1 ; copy mov rDiv2,rRes2 mov rDiv3,rRes3 mov rDiv4,rRes4 lsl rRes1 ; * 8 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp lsl rRes1 ; * 16 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp lsl rRes1 ; * 32 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp lsl rRes1 ; * 64 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp tst R0 ; overflow? breq CycleM3a rjmp CycleOvf CycleM3a: sub rRes1,rDiv1 sbc rRes2,rDiv2 sbc rRes3,rDiv3 sbc rRes4,rDiv4 mov rRes1,rRes2 mov rRes2,rRes3 mov rRes3,rRes4 clr rRes4 rcall Displ4Dec .IF ! cDisplay8 ldi rmp,' ' st X+,rmp ldi rmp,'r' st X+,rmp ldi rmp,'p' st X+,rmp ldi rmp,'m' st X+,rmp .ENDIF ldi ZL,'u' rcall DisplMode ret ; ; Measure time high+low, display time ; CycleM4: rcall Multiply rcall Displ4Dec rcall DisplSec ldi ZL,'t' rcall DisplMode ret ; ; Measure time high, display time ; CycleM5: sbrs rFlg,bEdge rjmp CycleM5a rcall Multiply rcall Displ4Dec rcall DisplSec ldi ZL,'h' rcall DisplMode CycleM5a: ret ; ; Measure time low, display time ; CycleM6: sbrc rFlg,bEdge rjmp CycleM6a rcall Multiply rcall Displ4Dec rcall DisplSec ldi ZL,'l' rcall DisplMode CycleM6a: ret ; ; Measure time high and low, display pulse width ratio high in % ; if the edge was negative, store the measured time, if positive calculate ; rRes and rDiv hold the active low time, sCtr the last active high time ; to CalcPw: rDelH:rDelL:R0:rmp = active high time ; CycleM7: sbrs rFlg,bEdge rjmp CycleM7a ldi ZH,HIGH(sCtr) ; edge is high, calculate ldi ZL,LOW(sCtr) ld rRes1,Z+ ; copy counter value ld rRes2,Z+ ld rRes3,Z+ ld rRes4,Z+ add rDiv1,rRes1 ; add to total time adc rDiv2,rRes2 adc rDiv3,rRes3 adc rDiv4,rRes4 brcs CycleM7b mov rmp,rRes1 ; copy high value to divisor mov R0,rRes2 mov rDelL,rRes3 mov rDelH,rRes4 rcall CalcPw ; calculate the ratio brcs CycleM7b ; error rcall DisplPw ; display the ratio ldi ZL,'P' rjmp DisplMode CycleM7a: ldi ZH,HIGH(sCtr) ldi ZL,LOW(sCtr) st Z+,rRes1 ; copy counter value st Z+,rRes2 st Z+,rRes3 st Z+,rRes4 ret CycleM7b: ; overflow ldi rmp,'P' rjmp PulseOvFlw ; ; Measure time high and low, display pulse width ratio low in % ; if the edge was negative, store the measured time, if positive calculate ; rRes and rDiv hold the active low time, sCtr the last active high time ; to CalcPw: rDelH:rDelL:R0:rmp = active low time ; CycleM8: sbrs rFlg,bEdge rjmp CycleM8a ldi ZH,HIGH(sCtr) ; edge is high, calculate ldi ZL,LOW(sCtr) ld rmp,Z+ ; read high-time ld R0,Z+ ld rDelL,Z+ ld rDelH,Z add rDiv1,rmp ; add to total time adc rDiv2,R0 adc rDiv3,rDelL adc rDiv4,rDelH mov rmp,rRes1 ; copy the active low time mov R0,rRes2 mov rDelL,rRes3 mov rDelH,rRes4 rcall CalcPw ; calculate the ratio brcs CycleM8b ; error rcall DisplPw ; display the ratio ldi ZL,'p' rjmp DisplMode CycleM8a: ldi ZH,HIGH(sCtr) ldi ZL,LOW(sCtr) st Z+,rRes1 ; copy counter value st Z+,rRes2 st Z+,rRes3 st Z+,rRes4 ret CycleM8b: ; overflow ldi rmp,'p' rjmp PulseOvFlw ; ; Converts an ADC value in R1:R0 to a voltage for display ; cAdc2U input: ADC value, output: Voltage in V for display ; cAdc2U: clr R2 ; clear the registers for left shift in R3:R2 clr R3 ldi rmp,HIGH(cMultiplier) ; Multiplier to R5:R4 mov R5,rmp ldi rmp,LOW(cMultiplier) mov R4,rmp clr XL ; clear result in ZH:ZL:XH:XL clr XH clr ZL clr ZH cAdc2U1: lsr R5 ; shift Multiplier right ror R4 brcc cAdc2U2 ; bit is zero, don't add add XL,R0 ; add to result adc XH,R1 adc ZL,R2 adc ZH,R3 cAdc2U2: mov rmp,R4 ; check zero or rmp,R5 breq cAdc2U3 ; end of multiplication lsl R0 ; multiply by 2 rol R1 rol R2 rol R3 rjmp cAdc2U1 ; go on multipying cAdc2U3: ldi rmp,$80 ; round up add XL,rmp ldi rmp,$00 adc XH,rmp adc ZL,rmp adc ZH,rmp tst ZH ; check overflow mov R1,XH ; copy result to R2:R1 mov R2,ZL ldi XH,HIGH(sResult+16) ; point to result ldi XL,LOW(sResult+16) ldi rmp,'U' st X+,rmp breq cAdc2U5 ldi ZH,HIGH(2*AdcErrTxt) ldi ZL,LOW(2*AdcErrTxt) cAdc2U4: lpm tst R0 breq cAdc2U6 sbiw ZL,1 st X+,R0 rjmp cAdc2U4 cAdc2U5: clr R0 ldi ZH,HIGH(10000) ldi ZL,LOW(10000) rcall DecConv inc R0 ldi ZH,HIGH(1000) ldi ZL,LOW(1000) rcall DecConv ldi rmp,cDecSep st X+,rmp clr ZH ldi ZL,100 rcall DecConv ldi ZL,10 rcall DecConv ldi rmp,'0' add rmp,R1 st X+,rmp ldi rmp,'V' st X,rmp lds rmp,sResult+17 cpi rmp,' ' brne cAdc2U6 ldi rmp,'=' sts sResult+17,rmp cAdc2U6: ret ; AdcErrTxt: .DB "overflw",$00 ; ; =========================================== ; Lcd display routines ; =========================================== ; .IF cDisplay ; if display connected ; ; LcdE pulses the E output for at least 1 us ; LcdE: sbi PORTB,bLcdE .IF cFreq rel="nofollow">14000000 nop nop .ENDIF .IF cFreq>12000000 nop nop .ENDIF .IF cFreq>10000000 nop nop .ENDIF .IF cFreq>8000000 nop nop .ENDIF .IF cFreq>6000000 nop nop .ENDIF .IF cFreq>4000000 nop nop .ENDIF .IF cFreq>2000000 nop nop .ENDIF nop nop cbi PORTB,bLcdE ret ; ; outputs the content of rmp (temporary ; 8-Bit-Interface during startup) ; LcdRs8: out PORTB,rmp rcall LcdE ret ; ; write rmp as 4-bit-command to the LCD ; LcdRs4: mov R0,rmp ; copy rmp swap rmp ; upper nibble to lower nibble andi rmp,0x0F ; clear upper nibble out PORTB,rmp ; write to display interface rcall LcdE ; pulse E mov rmp,R0 ; copy original back andi rmp,0x0F ; clear upper nibble out PORTB,rmp ; write to display interface rcall LcdE mov rmp,R0 ; restore rmp ret ; ; write rmp as data over 4-bit-interface to the LCD ; LcdData4: push rmp ; save rmp mov rmp,R0 ; copy rmp swap rmp ; upper nibble to lower nibble andi rmp,0x0F ; clear upper nibble sbr rmp,1<elp",cCr,cLf txtUartCursor: .DB cCr,cLf,"i> ",cNul http://www.avr-asm-tutorial.net/avr_de/fcount/fcountV03.asm (3 of 4)1/20/2009 7:40:33 PM
**************************************************** * Anleitung fuer Frequenzzaehler v0.3 vom 9.1.2009 * **************************************************** Abschnitte: ----------1. Einstellungen vor dem Assemblieren 2. Bei der Programmierung zu beachten 3. Messmodus 3.1 Messmodi 3.2 Anzeige des Messmodus 4. Darstellung auf dem Display 4.1 Darstellung bei zweizeiligen LCD-Displays 4.2 Darstellung bei einzeiligen LCD-Displays 4.3 Darstellung bei 16-/20-Zeichen-Displays 4.4 Darstellung bei 8 Zeichen-Displays 5. Darstellung auf der seriellen Schnittstelle 5.1 Befehle der seriellen Schnittstelle 5.2 Format von Spannungs- und Messausgaben
1. Einstellungen vor dem Assemblieren ------------------------------------Die folgenden Einstellungen sind vor dem Assemblieren in der Datei fcountV03.asm zu kontrollieren und ggfs. zu aendern: - Die Schalter debug und debugpulse muessen auf 0 stehen. - Wenn ein LCD-Display angeschlossen ist, muss cDisplay auf 1 stehen, sonst auf 0. - Wenn das angeschlossene LCD-Display 8 Zeichen pro Zeile darstellen kann, muss cDisplay8 auf 1 stehen. Wenn es 16 oder 20 Zeichen pro Zeile darstellen kann, muss cDisplay8 auf 0 stehen. - Wenn das angeschlossene LCD-Display eine einzige Zeile darstellen kann, muss cDisplay2 auf 0 stehen. Wenn es zwei oder mehr Zeilen darstellen kann, muss cDisplay2 auf 1 stehen. - Wenn die serielle Schnittstelle angeschlossen ist und bedient werden soll, muss cUart auf 1 stehen. Ist keine serielle Schnittstelle angeschlossen oder soll sie nicht verwendet werden, wird cUart auf 0 gesetzt. - Ist der Vorteiler durch 8 an einem anderen Portbit als PC5 angeschlossen, sind die Ports pPresc und pPrescD sowie das Portbit bPresc entsprechend zu aendern. - Wird der Prozessortakt mit einer anderen Frequenz als 16 MHz betrieben, ist diese in cFreq anzugeben. - Soll die serielle Schnittstelle mit einer anderen Baudrate als 9600 arbeiten, ist cBaud entsprechend zu aendern. - Ist der Vorteiler fuer die Spannungsmessung mit zwei anderen Widerstaenden bestueckt als mit 1M, sind die beiden Werte cR1 und cR2 entsprechend zu aendern. Wenn die angezeigte Spannung wesentlich von der Eingangsspannung abweicht, ist cRin zu aendern: kleinere Werte fuer cRin ergeben eine hoehere angezeigte Spannung, groeszere eine niedrigere angezeigte Spannung.
2. Bei der Programmierung des ATmega8 zu beachten ------------------------------------------------- Die Fuses "Ext.Crystal/Resonator HighFreq.; Start-up time: 16K CK + 64 ms "CKSEL=1111 SUT=11]" und CKOPT muessen gesetzt werden, alle anderen internen und externen clock fuses muessen geloescht werden. Die Screenshots zeigen die Fuses (FusesPony.gif bzw. FusesStudio1.gif und FusesStudio2.gif).
3. Messmodus -----------3.1 Messmodi Der Messmodus wird mit dem Potentiometer ausgewaehlt. Die folgenden Messmodi sind einstellbar: 0: Frequenzmessung mit Vorteiler, Gatezeit 0,25 s, Ergebnis in Hz 1: Frequenzmessung ohne Vorteiler, Gatezeit 0,25 s, Ergebnis in Hz 2: Frequenzmessung als Periodenmessung mit Frequenzumrechnung, Ergebnis in 0,01 Hz bzw. 0,001 Hz 3: Umdrehungsmessung, ohne Vorteiler, ueber Periodenmessung mit Umrechnung, Ergebnis in Upm 4: Periodendauer gesamte Schwingung, Ergebnis in Mikrosekunden 5: Periodendauer High-Periode, Ergebnis in Mikrosekunden 6: Periodendauer Low-Periode, Ergebnis in Mikrosekunden 7: Periodenanteil High-Periode, Ergebnis in 0,1% 8: Periodenanteil Low-Periode, Ergebnis in 0,1% 9: Spannungsmessung, Ergebnis in 0,001 Volt Die Auswahl von Modus 9 (rechter Poti-Anschlag) schaltet die Frequenz-/Zeit-/Periodenmessungen aus.
3.2 Darstellung des Messmodus Der Messmodus wird am Anfang der LCD-Zeile mit einem Buchstaben und dem Gleichheitszeichen angezeigt (Format "X="). Bei 8-Zeichen-LCDs wird das Gleichheitszeichen bzw. die Messmoduskennung nur dann angezeigt, wenn der anzuzeigende Messwert genuegend klein ist. Bei der Anzeige von Messwerten werden folgende Abkuerzungen fuer den Messmodus verwendet: - "F=": Frequenz in Hz, gemessen mit Vorteiler, Aufloesung +/- 32 Hz - "f=": Frequenz in Hz, gemessen ohne Vorteiler, Aufloesung +/- 4 Hz - "v=": Frequenz in Hz mit Dezimalstellen, gemessen ueber eine Zeitmessung, Aufloesung +/- 0,01 Hz - "u=": Umdrehungszahl in Upm, gemessen ueber eine Zeitmessung, Aufloesung +/- 1 Upm - "t=": Schwingungsdauer in Mikrosekunden, Aufloesung +/- 1 Mikrosekunde - "h=": Dauer einer 1-Periode am Eingang, Aufloesung +/- 1 Mikrosekunde - "l=": Dauer einer 0-Periode am Eingang, Aufloesung +/- 1 Mikrosekunde - "P=": Periodenanteil einer 1-Periode am Eingang in %, Aufloesung +/- 0,1% - "p=": Periodenanteil einer 0-Periode am Eingang in %, Aufloesung +/- 0,1% - "U=": Spannung in Volt am Spannungseingang, Aufloesung abhaengig vom Spannungsteiler am Eingang, bei 1M+1M ca. 4 Millivolt.
4. Darstellung auf dem Display -----------------------------Die Aktualisierung des Dislays erfolgt vier mal pro Sekunde bzw. bei laengeren Schwingungsdauern nach dem Ablauf der Messperiode. 4.1 Darstellung bei zweizeiligen LCD-Displays Die erste Zeile stellt je nach eingestelltem Messmodus die Frequenz, die Zeit oder den Periodenanteil dar. Die zweite Zeile stellt die Spannung dar. Bei zweizeiligen LCDs mit mehr als 8 Zeichen pro Zeile wird der Messmodus (0..9) am Ende der zweiten Zeile dargestellt. 4.2 Darstellung bei einzeiligen LCD-Displays Das Display stellt die Ergebnisse der Frequenz-, Zeitund Periodenanteil-Messung in den Modi 0 .. 8 dar. Bei Modus 9 (rechter Poti-Anschlag) wird die Spannung angezeigt. 4.3 Darstellung bei 16-/20-Zeichen-Displays Die Anzeige erfolgt vollstaendig mit Moduskennung, Tausender- und Dezimaltrennzeichen sowie mit Dimension. Mode Format ----------------------0 "F=99.999.999 Hz " 1 "f= 9.999.999 Hz " 2 "v= 9.999,999 Hz " 3 "u= 9.999.999 rpm" 4 "t=99.999.999 us " 5 "h=99.999.999 us " 6 "l=99.999.999.us " 7 "P=100,0% " 8 "p=100,0% " 9 "U=9,999V " 4.4 Darstellung bei 8 Zeichen-Displays Periodenanteile und die Spannung werden vollstaendig angezeigt. Die Darstellung der anderen Messgroeszen erfolgt verkuerzt. Unterdrueckt werden: - die Ausgabe von Tausender-Trennzeichen, - die Ausgabe der Dimension, - erforderlichenfalls das Gleichheitszeichen und der Buchstabe der Moduskennung. Die Ausgabe erfolgt bei - Frequenzen in Hz - Umdrehungen in Upm - Zeiten in Mikrosekunden. Mode Format kurz Format lang ---------------------------0 "F=999999" "99999999" 1 "f=999999" "99999999" 2 "v=999999" "99999999" 3 "u=999999" "99999999" 4 "t=999999" "99999999" 5 "h=999999" "99999999" 6 "l=999999" "99999999" 7 "P=100,0%" "P=100,0%" 8 "p=100,0%" "p=100,0%" 9 "U=9,999V" "U=9,999V"
5. Darstellung auf der seriellen Schnittstelle ---------------------------------------------Die serielle Schnittstelle sendet beim Prozessorstart den folgenden Text ueber die Schnittstelle:
************************************************* * Frequency- and voltmeter (C)2005 by g.schmidt * ************************************************* Commands: elp i> 5.1 Befehle der seriellen Schnittstelle Folgende Kommandos sind implementiert: u : Spannungsausgabe ausschalten U : Spannungsausgabe einschalten U=123 : Intervall fuer Spannungsausgabe 123 Zyklen f : Frequenzausgabe ausschalten F : Frequenzausgabe einschalten F=123 : Intervall fuer Frequenzausgabe 123 Zyklen p : Zeige eingestellte Intervalle an h, ? : Hilfe-Ausgabe
5.2 Format von Spannungs- und Messausgaben Die Ausgabe von Spannungen erfolgt im Format "U=4,958V" Die Ausgabe von Frequenzen ist vom eingestellten Modus abhaengig, also z. B. in Modus 0 "F=1.234.567 Hz" http://www.avr-asm-tutorial.net/avr_de/fcount/LiesMich3.txt1/20/2009 7:40:44 PM
Geschenkprojekt AVR-Eieruhr - Assembler-Quellcode
Pfad: Home => AVR-Übersicht => Anwendungen => Eieruhr => Assembler-Quellcode
Pfad: Home => AVR-Übersicht => Anwendungen => Eieruhr Als kleines Bastelprojekt - Zum Spaß machen - Fingerübung in Assembler
Die ATtiny2313V-Eieruhr 1. Beschreibung 2. Aufbau 3. Funktionsweise
1. Beschreibung Wer kennt das nicht: der oder die X.Y. hat bald Geburtstag, man ist geladen, und X.Y. hat schon alles. Jetzt kann man phantasielos in irgendein Geschenkgeschäft gehen, irgendetwas völlig Überflüssiges kaufen, einpacken lassen und nur noch hoffen, dass nicht vier bis fünf der anderen Geladenen dasselbe Geschäft aufgesucht, den gleichen schlechten Geschmack haben wie man selbst und mit demselben Ramsch antraben ("Bestimmt kann man die vier anderen umtauschen!" Gegen was bloß?). Wer löten kann, ist jetzt klar im Vorteil: ab sofort gibt es selbstgemachte Bastelware auf den Gabentisch. Das Bibbern ist jetzt vorbei, denn unsere Verwandten und Bekannten kriegen jetzt nach und nach alle einen schicken, individuellen und geschmackssicher gestalteten Eierwecker. Natürlich bestückt mit einem hochmodernen Schicki-Micki-Mikroprofessor, schließlich sind wir eine technologisch führende Nation, auf unseren Erfindungsreichtum stolz, und arbeiten nicht mehr mit diesem säuregereinigten Quarzsand in einem Glaskolben mit Verengung. Den kann keiner so schnell kopieren, das bleibt ein individuelles Geschenk. Rieselsand war vorgestern, heute ist Piepsen und Blinken. Das Prinzip ist einfach erklärt: eine Reihe von LEDs leuchtet wie bei einer Bandanzeige grün und rot auf, immer wenn eine Minute um ist, wird eine weitere LED des Bands rot, eine weniger grün. Damit die Kiste aus einer Batterie betrieben werden kann und nicht nach ein Mal Eierkochen die Batterie alle ist, leuchtet immer nur eine LED gleichzeitig. Das Band läuft aber schnell durch, damit auch was los ist in der Kiste und der Rotstand mit kurzem Blick erfasst werden kann. Damit der geneigte Leser einer Tageszeitung beim Eierkochen nicht die Augen gebannt auf den Eierkocher richten muss, gibt die Eieruhr bei jeder vollen Minute die Anzahl der Kochminuten auch durch vorgezählte Piepser aus. Ist also im Gegensatz zur Sandvariante auch für sehbehinderte Zeitgenossen geeignet. Sind die 10 Minuten voll, zeigt eine gelbe LED und ein ganz langes Piepsen an, dass der Dotter jetzt grün ist und das Ei getrost weggeworfen werden kann. Hier wird jetzt nur die Elektronik beschrieben, den Einbau in ein Gehäuse müssen Sie ganz von selbst kreativ lösen. Für die Anordnung der LEDs gilt: 1. Ein gerades Band ist mit seiner strengen Geometrie für geradlinige Zeitgenossen besser geeignet (z. B. Mathematiker, Hausmeister), Modell "straight forward", 2. Kreis geht aber auch, ist eher für körperlich rundlich gebaute Beschenkte angebracht, Modell "Bierbauch". 3. Für den verspielten Charakter ist am passendsten die eierförmige Anordnung, auch für den Vergesslichen ist das am schönsten (erinnert unmittelbar an die vorgesehene Verwendung der Schachtel), Modell "forget it". 4. Für den eher chaotischen, die Unordnung genießenden und die Komplexität liebenden Bekannten ordnen sie alle LEDs wahllos verteilt und durcheinander auf einer völlig unregelmässigen Grundfläche an, Modell "Hundertwasser". 5. Für den Spielsüchtigen: Vielleicht vertauschen sie auch noch mit jeder Minute ein wenig die Reihenfolge der LEDs per Software. Er wird es lieben, zu raten, wo das nächste Lämpchen rot werden wird, Modell "vote&surprise". Aus eigener Erfahrung hat es sich sehr bewährt, den Beschenkten auch noch mit einer individuell gestalteten Bedienungsanleitung auszustatten. Witzige Formulierungen, wie z.B. deutsche Wörter mit taiwanesischer Grammatik, bringen Lacherfolge. Oder schreiben Sie die Anleitung in Englisch, füttern sie in den Babelfish mit der Anweisung, sie in Niederländisch oder Flämisch zu übersetzen und ersetzen alle Hauptwörter und einen zufällig ausgewählten Anteil der anderen Wörter wieder durch den deutschen Begriff. Als Vorlage hier was im RTF-Format oder als Open-Office-Format. An den Seitenanfang
Aufbau
D
Schaltbild ist recht einfach: die beiden Batterien (2*AA, 2*AAA geht aber auch) speisen das Ganze mit 3 V, mehr Spannung (z.B. 5 V) tötet die LEDs sehr schnell. Die Reihe der Duo-LEDs rot/grün ist mit Strombrems-Widerständen als Kaskade aufgebaut, jede der LEDs lässt sich von den beiden angeschlossenen Portpins aus entweder links rum oder rechts rum mit Strom versorgen und ändert so ihre Farbe. Beim Einbau der LEDs kommt es nur darauf an, alle LEDs in gleicher Farbrichtung einzubauen, die Farbe kann in der Software leicht vertauscht werden. Alle nicht angesteuerten Portpins sind per Software auf inaktiven Tristate gesetzt und stören daher nicht. Der ATtiny2313V (ohne V geht auch) treibt immer nur eine LED, der Spannungsabfall an den Ausgängen unter Last ist bei der Bemessung der Widerstände berücksichtigt. Die gelbe Eierwegwurf-LED ist an einen eigenen Portpin angeschlossen. Ein kleiner Piezo-Lautsprecher ist direkt an den OC0A-Ausgang angeschlossen. Wer gerade keinen Piezo da hat, kann auch einen kleinen 32-Ohm-Lautsprecher anschließen, dann aber über einen 100 µF-Elko gleichstrommäßig entkoppeln! An den Seitenanfang
Funktionsweise Prozessorsteuerung
Die Schaltung kann wegen der niedrigen Versorgungsspannung nicht per ISP programmiert werden, die LEDs würden kaputt gehen! Also den Prozessor auf einem externen Board programmieren und fertig programmiert in die Schaltung einsetzen. Der Prozessor wird zuerst per Fuse-Programmierung auf den internen RC-Oszillator von 4 MHz umgestellt, wir wollen ja möglichst Strom sparen. Die DIV8-Fuse bleibt aktiviert, so dass er mit 500 kHz Takt läuft. Langsam genug für die V-Version. Der Assembler-Quellcode ist hier zum Download verfügbar, in html-Format hier. Im Quellcode werden zuerst die Portpins zugeordnet. D.h., dass alle LEDs in beliebiger Reihenfolge montiert und den Portpins zugeordnet werden können. Die Reihenfolge muss dann dem richtigen Port, Richtungsport und Portbit zugeordnet werden. Die etwas aufwändige Programmierung der einzelnen Ein- und Ausschaltroutinen für jede einzelne LED am Ende des Codes ist dieser Schaltfreiheit zu verdanken. Die Zeitmessung und die LED-Laufanzeige erfolgt mit dem 16-BitTimer 1, der im CTC-Mode läuft. Beim Erreichen des im CompareRegisters A eingestellten Werts wird alle 0,2 Sekunden ein Interrupt ausgelöst und die T-Flagge im Status-Register gesetzt. Der Interrupt weckt den schlafenden Prozessor auf, der danach das T-Flag prüft. Bei gesetztem Flag wird dieses zurückgesetzt und das niedrigste Bit des LED-Zählers abgefragt. Ist dieses Eins, wird der Ton des Lautsprechers abgeschaltet. Bei Null wird geprüft, ob noch ein Piepton auszugeben ist. Wenn ja, wird der Lautsprecherton eingeschaltet (Timer 0 aktiviert) und die Anzahl noch auszugebender Piepser um einen verringert. Danach wird die zuletzt eingeschaltete LED ausgeschaltet. Der LED-Zähler wird erhöht und geprüft, ob er die 10 überschritten hat. Wenn ja, wird der Sekundenzähler erhöht und geprüft, ob dieser 30 erreicht hat. Wenn ja, ist eine Minute um, der Minutenzähler wird erhöht und geprüft, ob er 10 überschritten hat. Wenn ja, wird die gelbe LED eingeschaltet und der Dauerpieps eingeschaltet. Abschließend wird die nächste LED ermittelt und je nach Stand des Minutenzählers entweder auf rot oder auf grün geschaltet. Die einzuschaltende Lampe wird dazu zur Anfangsadresse der Sprungtabelle (rot bzw. grün) im Registerpaar ZH:ZL addiert und mit IJMP in die Tabelle gesprungen. Alle Schaltroutinen verzweigen am Ende wieder zum Schlafbefehl der Hauptprogrammschleife. Varianten
Es gibt eine breite Palette an Varianten: 1. Bandgeschwindigkeit: ❍ für Hektiker: ihm geht das alles zu langsam, für ihn verdoppeln wir die Laufgeschwindigkeit der LEDs auf das Doppelte, indem wir den Wert von 12500 auf 6250 herabsetzen und die 30 aus der Minutenerkennung auf 60 verstellen. Das gibt Speed. ❍ für den Nachdenklichen: ihm kann es nicht langsam genug gehen, bis er die Farbe einer LED zuverlässig einschätzen kann, braucht er fast eine halbe Sekunde. Also machen wir aus 12500 mal eben 25000 und aus 30 wird 15. 2. LEDs verkehrt herum montiert? Kein Problem: die Zeilen ldi ZH,HIGH(TabRot) ; versuche es mit rot, und ldi ZL,LOW(TabRot) gegen die Zeilen ldi ZH,HIGH(TabGruen) ; nein, es ist gruen, und ldi ZL,LOW(TabGruen) vertauschen und schon läuft es mit umgekehrten Farben. 3. Lieber hochtönend? Kein Problem, in der Zeile ldi rmp,63 ; CTC-Wert die 63 gegen einen niedrigeren CTC-Wert für Timer 0 auswählen. 4. Eieruhr für Musikliebhaber? Geht! Tabelle mit den CTC-Werten für die Tonleiter im Flash anlegen, nach jeder beendeten Minute den zugehörigen CTC-Wert aus der Tabelle lesen und in das CompareRegister A des Timers 0 schreiben. 5. Wer lieber ganz, ganz harte Eier vom Typus "Grünkern-Golfball" mag: vier weitere Duo-LEDs an die noch freien Portpins klemmen und die Software um die weiteren vier LEDs erweitern. 6. ... Der Variantenreichtum macht es unmöglich, den Quellcode für alle Varianten hier fertig anzubieten. Die Anzahl dürfte auch bei großem Bekanntenkreis ausreichen, dass keine zwei Uhren mit demselben Design verschenkt werden müssen, jedem sein eigenes Spielzeug. Viel Erfolg beim Basteln.
Pfad: Home = rel="nofollow"> AVR-Übersicht => Anwendungen => Steppermotor-Steuerung
Schrittmotor-Steuerung mit ATtiny13 Diese Anwendung eines AVR beschreibt die Steuerung eines Schrittmotors mit einem ATMEL ATtiny13 mit folgenden Eigenschaften:
● ●
● ● ● ●
Konzipiert für die Fernsteuerung von Schrittmotoren mit Getriebe. Eine Eingangsspannung von 0..5 V mit einer Auflösung von 10 Bits (1024 auflösbare Schritte, Schrittweite: 4,88 mV) steuert die Stellung des Schrittmotors. Anzahl der gesamten Schrittanzahl bis 65.535 einstellbar, daher nahezu beliebige Getriebeübersetzungen per Software einstellbar. Einfachste Ansteuerung des Schrittmotors. Sehr schnelle Einstellung durch optimale Anpassbarkeit an die maximale Drehgeschwindigkeit des Motors. Optimale Reduzierung des Strombedarfs durch Abschaltung der Spulen nach Verstellung.
1. Hardware Die Hardware besteht aus dem AVR-Prozessor ATtiny13, einem sechspoligen Standard-In-System-Programmieranschluss (ISP), einem 7-BitTreiber ULN2003, der Stromversorgung für den Prozessor und der Filterung der Eingangsspannung. Das Schaltbild (Anklicken für höher aufgelöstes PDFFormat):
1.1 Prozessorteil Der Prozessor ATtiny13 hat folgende Funktionen. Die Betriebsspannung von 5 V wird über die Pins 8 (+5 V) und 4 (0 V) zugeführt und mit einem Keramikkondensator von 100 nF abgeblockt. Pin 1 (= RESET-Eingang) liegt über einen Widerstand von 10 kOhm an der positiven Betriebsspannung. Der Eingang PB4 (Pin 3) misst über einen internen AD-Wandler die anliegende Analogspannung durch Vergleich mit der Betriebsspannung. Die Software errechnet daraus die Soll-Stellung des Schrittmotors. Die Ausgänge PB0 bis PB3 (Pins 5, 6, 7 und 2) steuern die Treiber für die Schrittmotor-Magnete an.
1.2 ISP-Interface Das ISP-Interface dient der Programmierung des AVR in der fertig aufgebauten Schaltung. Die Beschaltung entspricht dem ATMEL-Standard. Für ISP verwendet werden die Portbits PB2 (SCK, Pin 7), PB1 (MISO, Pin 6), PB0 (MOSI, Pin 5) sowie der RESET an Pin 1 verwendet. Die Betriebsspannungsanschl sse VTG und GND versorgen eventuell das externe ISP-Interface, hat dies eine eigene Versorgung ist VTG offen zu lassen.
1.3 Spulentreiber ULN2003A Die Antriebsströme für die Magnete des Schrittmotors werden vom Treiberbaustein ULN2003A gesteuert. Die Ausgänge mit Open-CollectorTreibertransistoren vertragen hohe Spannungen von 50 V, Ströme bis 500 mA und schalten die einzelnen Magnete des Motors ein und aus. Induktive Überspannungen an den Kollektoren werden über die eingebauten Dioden am Anschluss CD kurzgeschlossen. Der hier verwendete Motor wird mit 12 V Betriebsspannung betrieben und zieht pro Magnet ca. 150 mA Strom (da im Betrieb immer zwei Magnete angesteuert werden zusammen 300 mA). Die Eingänge I7..I4 des Treiberbausteins werden vom Prozessor angesteuert (aktiv high, logisch 1 schaltet Magnet an).
1.4 Stromversorgung Bei der Stromversorgung auf der Steuerung wurde auf eine hohe Festigkeit gegenüber den Schaltströmen der Magnete gelegt. Die Versorgung der Magnete erfolgt gegen Verpolung über eine Diode 1N4007 mit einem Glättungskondensator von 100 µF. Der Prozessor wird über eine eigene 5 V-Versorgung betrieben, die mit einer Diode 1N4007 und einem Glättungskondensator von 100 µF aus der 12 VVersorgung abgeleitet ist. Der Spannungsregler 78L05 ist mit Tantalkondensatoren von 1 µF bzw. 2,2 µF gegen Schwingungen gesichert. Die Versorgung der Steuerung erfolgt über ein vieradriges Kabel aus einem 12 V-Netzteil (Klick auf Bild fürt zu höher aufgelöstem PDF-Dokument).
Die Stromversorgung ist auf einer kleinen Leiterplatte aufgebaut.
2. Software Die Software für den ATtiny13 ist in Assembler geschrieben, der Quellcode ist hier erhältlich.
2.1 Funktionsweise Die Software besteht aus folgenden Grundelementen: ● ● ● ● ●
der Reset- und Interruptvektor-Tabelle, der Initiierung der Anfangswerte und der Hardwarekomponenten, der AD-Wandler-Messung der Eingangsspannung, der Umrechnung des Ergebnisses in den Sollwert, der Schrittsteuerung und der Ausgabe an den Schrittmotor.
Reset- und Interruptvektor-Tabelle Die Vektortabelle verzweigt bei einem Reset zum Hauptprogramm, bei den beiden Interrupts des Timer/Counters und des AD-Wandlers zu den entsprechenden Behandlungsroutinen. Nicht verwendete Vektoren sind mit RETI abgeschlossen. Iniitierung Anfangswerte Die Initiierung der Anfangswerte erfolgt ab dem Label "Main:". Hier wird ● ● ● ●
der Stapel initiiert, da mit Interrupts und Unterprogrammen gearbeitet wird, das Flaggenregister gelöscht (Funktion siehe unten), der Soll- und Ist-Wert des Schrittmotors auf Null gesetzt, der Abschaltzähler gesetzt.
Initiierung Hardware Die Initiierung der Hardware umfasst: ● ●
●
●
die Richtung der vier Portbits PB0 bis PB3 als Ausgang und die Bits auf den ersten Schritt des Steppermotors setzen, den Zähler für den AD-Wandler auf 64 setzen, das Summenergebnis der Wandlungen löschen, den Digitaleingang von PB4=ADC2 abschalten (wird nicht benötigt, PB4 dient ausschließlich der AD-Messung), die AD-Wandler-Mux fest auf Kanal ADC2 einstellen, und den AD-Wandler mit folgenden Einstellungen starten: ❍ Referenzspannung ist die Betriebsspannung des ATtiny13, ❍ Taktteiler=128, bei 1,2 MHz internem Takt und 13 Taktzyklen pro Messung braucht jede Messung 1,387 ms, Aufsummieren von jeweils 64 Messungen ergibt einen fertigen Messwert alle 88,75 ms oder 11,3 Messwerte pro Sekunde. ❍ Interrupt nach jeder abgeschlossenen Messung, ❍ kein automatischer Neustart der nächsten Messung (wird beim Interrupt neu gestartet). der Timer/Counter 0 wird auf normalen CTC-Modus (Löschen des Zählers bei Erreichen des Vergleichswerts) eingestellt, mit folgenden Eigenschaften: ❍ die Dauer eines CTC-Zyklus ist so lange wie die Ausgabe für einen Schritt des Motors dauert und wird durch die Konstante cCmpA bestimmt, die in das Compare-Register A des Counters geschrieben wird, ❍ nach jedem CTC-Zyklus wird ein Interrupt ausgelöst, der Compare-Match-Interrupt A wird ermöglicht, ❍ der Vorteiler wird auf 1024 eingestellt, der Counter-Takt beträgt daher 1,2 MHz/1024 = 1,172 kHz, mit CTC-Werten zwischen 1 und 255 ergeben sich Frequenzen zwischen 1172 und 4,6 Hz für die Ansteuerung des Schrittmotors. der Prozessor wird auf Schlafmodus eingestellt, d.h. zwischen den Interrupts des Counters und des AD-Wandlers ist der Programmablauf unterbrochen.
AD-Wandler-Messung der Eingangsspannung Der AD-Wandler wandelt die Eingangsspannung an Pin 3 (PB4, ADC2) in einen Wert zwischen 0..1023 um und löst nach jedem Abschluss der Wandlung einen Interrupt aus. Die Interrupt-Behandlungsroutine ab dem Label "AdcInt:" holt das Ergebnis von den Ports ADCL und ADCH ab und summiert es 16-bittig zu dem Registerpaar rAdcH:rAdcL. Der Zähler rAdc wird um eins herangezählt. Erreicht er Null, dann wird die bis dahin erreichte Summe in das Registerpaar rAdcRH:rAdcRL kopiert, die Summe wieder auf Null gesetzt, der Zähler wieder auf den Anfangswert 64 und die Flagge bAdc im Flaggenregister gesetzt. Abschließend wird der AD-Wandler erneut gestartet. Der Summiervorgang bewirkt, dass jeweils 64 Messwerte gemittelt werden, wodurch absichtlich der Ablauf verlangsamt und die Messung von zufälligen und durch Einstreuung verursachten Schwankungen unabhängiger wird. Der resultierende Summenwert liegt zwischen Null und 65535 (0x0000..0xFFFF). Umrechnung des Messergebnisses in den Sollwert Ist nach einem Interrupt die Flagge bAdc im Flaggenregister gesetzt, wird die Umrechnungsroutine ab dem Label "AdcRdy:" aufgerufen. Diese ● ● ●
● ● ●
löscht das Flaggenbit wieder, kopiert den Summenwert in das Registerpaar rAdcCH:rAdcL, multipliziert den Summenwert mit der Konstanten cSmSteps (der Anzahl Schritte, die der Schrittmotor bei Vollausschlag in Vorwärtsrichtung machen soll), zu einem 32-Bit-Ergebnis, rundet die untersten 16 Bits, teilt das Ergebnis durch 65536, so dass sich die Sollschritte zu einem 16-Bit-Ergebnis ergeben, und schreibt das Ergebnis in das Soll-Registerpaar rSmSH:rSmSL (wobei Interrupts zeitweilig abgeschaltet werden, damit es nicht zu Fehlansteuerungen des Schrittmotors kommt).
Schrittsteuerung und Ausgabe an den Schrittmotor Die Schrittsteuerung und die Ausgabe an den Schrittmotor erfolgt in der Interrupt-Behandlungsroutine des Counters ab dem Label "Tc0IntCA:". Zunächst wird der Ist-Wert mit dem Soll-Wert des Schrittmotors 16-bittig verglichen. Sind beide gleich, dann wird nach dem Label "Tc0IntCA0:" verzweigt. Hier wird der Verzögerungszähler im Registerpaar X um eins verringert. Ist er Null, werden die Magnete des Schrittmotors durch Schreiben von Null auf den Ausgabeport abgeschaltet, der Verzögerungszähler wieder auf seinen Anfangswert gesetzt und die Behandlungsroutine beendet. Ist der Ist- und Sollwert nicht gleich, wird der Ist-Wert um einen Schritt vor- bzw. rückwärts verändert. Aus dem Ist-Wert wird der nächste Schritt des Schrittmotors ermittelt: ● ● ● ●
die beiden untersten Bits des Ist-Wertes werden isoliert, und zur Anfangsadresse "SmTab:" im Flash-Speicher addiert, mit LPM wird der Inhalt des Bytes in das Register R0 eingelesen, und an den Ausgabeport geschrieben, der die Treiber der Magnetspulen des Schrittmotors ansteuert.
Die Tabelle "SmTab:" mit den beiden Worten 0x0605 und 0x090A enthält die Schrittfolge des Schrittmotors in der Reihenfolge ● ● ● ●
Anmerkung: Sind die Spulen Q1..Q4 des Schrittmotors in anderer Reihenfolge an die Treiberausgänge angeschlossen, genügt es, diese Tabelle entsprechend umzustellen (siehe unten). In der Behandlungsroutine wird abschließend der Verzögerungszähler wieder auf seinen Anfangswert gesetzt, um die Magnete für die die voreingestellte Zeit im aktiven Zustand zu halten.
2.2 Einstellungen vor dem Assemblieren Im Assembler-Quelltext sind folgende Einstellungen vor dem Assemblieren vorzunehmen: ● ●
●
●
Die drei Debug-Schalter debug_calc, debug_const und debug_out müssen auf Null gesetzt sein! Die Konstante cSmSteps muss auf die Anzahl Schritte eingestellt werden, die der Schrittmotor von Null bis Vollausschlag zurücklegen soll (Maximalwert: 65535). Die Konstante cSmFreq muss auf die Frequenz eingestellt werden, mit der der Schrittmotor angesteuert werden soll und die der verwendete Schrittmotor noch gut bewältigt (minimal 5 Hz, maximal 1171 Hz). Die Konstante cSmDelay gibt die Anzahl Takte an, nach denen bei Erreichen des Sollwertes die Magnete des Schrittmotors weiter aktiv sein sollen. Ist cSmDelay gleich groß wie cSmFreq, beträgt der Verzögerungszeitraum genau eine Sekunde.
Die Anschlussreihenfolge der vier Magnete an der Buchse J2 ist nicht bei allen Schrittmotoren gleich. Wird eine andere Anschlussfolge der Magnete des Schrittmotors Q1 bis Q4 verwendet oder soll die Drehrichtung des Motors umgekehrt werden, muss nur die Tabelle SmTab: angepasst werden. Die bestehende Tabelle für den KP4M4-001 ist folgendermaßen aufgebaut: Magnet Farbe Portbit Step 1 Step 2 Step3 Step 4 Q1
Pfad: Home = rel="nofollow"> AVR-Übersicht => 4-Bit-LCD am STK500
Anschluss einer 2-zeiligen LCD-Anzeige an das STK500 Beim Entwicklerboard STK500 ist keine LCD-Schnittstelle dabei, über die eine handelsübliche Anzeige angeschlossen werden kann. Eine solche war beim STK200 mit dabei, aber auch die hat ihre Macken: Sie lässt sich nicht zusammen mit externem SRAM betreiben (wegen memory mapping der Anzeigeschnittstelle). Man kann aber eine LCD-Anzeige auch an jeden AVR-Port anschließen, an dem man 6 Portbits noch frei und verfügbar hat. Die Anzeige wird dabei im 4-Bit-Modus angesteuert, damit man Portbits sparen kann. Nimmt man ferner in Kauf, dass man aus der LCD nichts auslesen kann (z.B. den Status oder den Zeichengenerator), dann kann man die Hard- und Software einfach gestalten. Der Nachteil ist, dass man das Schreib-Timing über Verzögerungsschleifen durchführen muss. Aber das ist nicht sooo schlimm.
Hardware Die Hardware ist im folgenden Bild beschrieben.
Es kommt also nur darauf an, das richtige Port-Bit mit dem richtigen LCD-Pin, ● Stromversorgungspin mit dem an der LCD, und das richtige ● Poti mit dem Helligkeitsregler-Eingang der LCD (nein, es geht nicht ohne das Poti!) ●
zu verbinden und schon ist die Hardware gelaufen. Fast! Steht das Poti falsch, sieht man nix. Ohne Prozessoransteuerung sollten beim Drehen am Poti schwarze Kästchen sichtbar werden. Den Kontrastregler soweit drehen, dass die Kästchen gerade nicht mehr zu erkennen sind.
Software Die Software zur Ansteuerung der LCD wird in der Datei Lcd4Inc im HTML-Format und in der Datei Lcd4Inc im Quelltext-Format zur Verfügung gestellt. Sie besteht aus den Routinen 1. 2. 3. 4. 5.
Lcd4Init: Die Routine setzt die LCD zurück, Lcd4Chr: Sie gibt den Buchstaben in rmp auf der LCD aus, Lcd4PBcd: Sie gibt die gepackte BCD-Zahl in rmp auf der LCD aus, Lcd4ZTxt: Sie gibt den nullterminierten Text aus dem Flash ab Z aus, Lcd4RTxt: Sie gibt rmp Buchstaben aus dem SRAM ab Z aus.
Die Software kann als Include-Datei in ein bestehendes Programm eingebunden werden. Im Kopf der Datei sind die Bedingungen angegeben, die die Quelldatei einhalten muss.
Pfad: Home => AVR-Übersicht => Board-Hardware => 4-Bit-LCD am STK500 => Quellcode Basisroutinen
; ******************************************** ; * LCD-Basisroutinen für 4-Bit-Anschluss * ; * einer zweizeiligen LCD an einen ATMEL* * ; * Port, Bits 4..7=Data, Bit 1=RS, Bit0=E ; * Version 1, Februar 2002, (C) 2002 by * * ; * Gerhard Schmidt, bug reports and sug; * gestions to info!at!avr-asm-tutorial.net * ; ******************************************** ; ; Hardware: LCD-Anzeige am aktiven Port ; ; Definitionen, die in dem aufrufenden Assembler; Programm enthalten sein müssen: ; - Stackoperationen ; - Register rmp (R16..R31) ; - Taktfrequenz ftakt ; - pLcdPort Aktiver LCD-Port ; - pLcdDdr Datenrichtungsregister des aktiven Port ; Subroutinen: ; - Lcd4Init: Setzt die LCD zurück ; - Lcd4Chr: Gibt den Character in rmp aus ; - Lcd4PBcd: Gibt die gepackte BCD-Zahl in rmp aus ; - Lcd4ZTxt: Gib nullterminierten Text aus Flash ab Z ; - Lcd4RTxt: Gib rmp chars aus SRAM ab Z aus ; ; Festlegungen für den LCD-Port .EQU cLcdWrite=0b11111111 ; Datenrichtung Schreibe LCD .EQU cLcdDummy=0b00111000 ; Dummy-Function-Word .EQU mLcdRs=0b00000010 ; RS-Bit Maske .EQU bLcdEn=0 ; Enable Bit .EQU c1s=200 ; Warten zu Beginn (200 * 5 ms) .EQU c5ms=ftakt/800 ; 5 ms Warten nach jedem Kontrollwort .EQU c50us=ftakt/80000 ; 50 us Warten nach jedem Zeichen ; ; Makro für Enable active time ; ; Version für 10 MHz Takt ;.MACRO enactive nop ; ; nop ; nop ; nop ; nop ;.ENDMACRO ; ; Version für 4 MHz Takt ; .MACRO enactive nop nop .ENDMACRO ; Lcd4Init: rcall LcdDelay1s ; Warte eine Sekunde auf LCD ldi rmp,cLcdWrite ; Datenrichtung auf Ausgang out pLcdDdr,rmp ldi rmp,cLcdDummy ; Dummy zum Abfangen der LCD rcall Lcd4Set ; drei Mal senden mit Delay je 5 ms rcall LcdDelay5ms ldi rmp,cLcdDummy rcall Lcd4Set rcall LcdDelay5ms ldi rmp,cLcdDummy rcall Lcd4Set rcall LcdDelay5ms ldi rmp,0b00101000 ; Function Set auf 4 Bit rcall Lcd4Ctrl ; Ausgabe auf Control Port LCD ldi rmp,0b00010100 ; Cursor display shift rcall Lcd4Ctrl ldi rmp,0b00001100 ; LCD on rcall Lcd4Ctrl ldi rmp,0b00000110 ; Entry mode rcall Lcd4Ctrl Lcd4Clear: ldi rmp,0b00000001 ; Set Lcd Clear rcall Lcd4Ctrl Lcd4Home: ldi rmp,0b00000010 ; Set LCD Home Position ; ; Ausgabe von rmp an den Control-Port der LCD ; Lcd4Ctrl: push rmp ; Rette Byte andi rmp,0xF0 ; Lösche unteres Nibble rcall Lcd4Set ; Gib oberes Nibble aus pop rmp ; Stelle Byte wieder her swap rmp ; Vertausche Nibbles andi rmp,0xF0 ; Lösche unteres Nibble rcall Lcd4Set ; Gib unteres Nibble aus rjmp LcdDelay5ms ; Fertig ; ; Gib die gepackte BCD-Zahl in rmp auf dem LCD aus ; Lcd4PBcd: push rmp ; Save on stack swap rmp ; Higher to lower nibble rcall Lcd4PBcd1 ; Output nibble pop rmp ; Restore from stack Lcd4PBcd1: andi rmp,0x0F ; Mask upper nibble ori rmp,0x30 ; Nibble to ASCII ; ; Gib char in rmp auf LCD aus ; Lcd4Chr: push rmp ; Rette char auf Stapel andi rmp,0xF0 ; Lösche unteres Nibble sbr rmp,mLcdRs ; Setze RS-Bit rcall Lcd4Set ; Gib Nibble aus pop rmp ; Hole Char vom Stapel swap rmp ; Vertausche Nibble andi rmp,0xF0 ; Lösche unteres Nibble sbr rmp,mLcdRs ; Setze RS-Bit rcall Lcd4Set ; Gib Nibble aus rjmp LcdDelay50us ; Fertig ; ; Gib Nibble in rmp an LCD aus ; Lcd4Set: out pLcdPort,rmp ; Byte auf Ausgabeport nop sbi pLcdPort,bLcdEn ; Setze Enable-Bit enactive ; Delay macro cbi pLcdPort,bLcdEn ; Enable Bit löschen nop ret ; ; Verzögerung um 1 Sekunde bei Init der LCD ; LcdDelay1s: ldi rmp,c1s ; 200 * 5 ms warten LcdDelay1s1: rcall LcdDelay5ms dec rmp brne LcdDelay1s1 ret ; ; Verzögerung um 5 ms nach jedem Control Word ; LcdDelay5ms: push ZH push ZL ldi ZH,HIGH(c5ms) ldi ZL,LOW(c5ms) LcdDelay5ms1: sbiw ZL,1 brne LcdDelay5ms1 pop ZL pop ZH ret ; ; Delay um 50 Mikrosekunden nach jedem Char ; LcdDelay50us: ldi rmp,c50us LcdDelay50us1: nop dec rmp brne LcdDelay50us1 ret ; ; Gib an der Position in rmp den Text ab Z aus (null-term.) ; Lcd4ZTxt: sbr rmp,0b10000000 ; Setze DD-RAM-Adresse rcall Lcd4Ctrl Lcd4ZTxt1: lpm ; Get a char tst R0 ; Null-Char? breq Lcd4ZTxtR mov rmp,R0 rcall Lcd4Chr adiw ZL,1 rjmp Lcd4ZTxt1 Lcd4ZTxtR: ret ; ; Gib rmp chars Text im SRAM ab Z aus ; Lcd4RTxt: mov R0,rmp ; R0 ist Zähler Lcd4RTxt1: ld rmp,Z+ ; Lese char rcall Lcd4Chr dec R0 brne Lcd4RTxt1 ret
; ***************************************** ; * LCD-Basisroutinen für 4-Bit-Anschluss * ; * einer zweizeiligen LCD an einen ATMEL-* ; * Port, Bits 4..7=Data, Bit 1=RS, Bit0=E* ; * Version 1, Februar 2002, (C) 2002 by * ; * Gerhard Schmidt, bug reports and sug- * ; * gestions to [email protected] * ; ***************************************** ; ; Hardware: LCD-Anzeige am aktiven Port ; ; Definitionen, die in dem aufrufenden Assembler; Programm enthalten sein müssen: ; - Stackoperationen ; - Register rmp (R16..R31) ; - Taktfrequenz ftakt ; - pLcdPort Aktiver LCD-Port ; - pLcdDdr Datenrichtungsregister des aktiven Port ; Subroutinen: ; - Lcd4Init: Setzt die LCD zurück ; - Lcd4Chr: Gibt den Character in rmp aus ; - Lcd4PBcd: Gibt die gepackte BCD-Zahl in rmp aus ; - Lcd4ZTxt: Gib nullterminierten Text aus Flash ab Z ; - Lcd4RTxt: Gib rmp chars aus SRAM ab Z aus ; ; Festlegungen für den LCD-Port .EQU cLcdWrite=0b11111111 ; Datenrichtung Schreibe LCD .EQU cLcdDummy=0b00111000 ; Dummy-Function-Word .EQU mLcdRs=0b00000010 ; RS-Bit Maske .EQU bLcdEn=0 ; Enable Bit .EQU c1s=200 ; Warten zu Beginn (200 * 5 ms) .EQU c5ms=ftakt/800 ; 5 ms Warten nach jedem Kontrollwort .EQU c50us=ftakt/80000 ; 50 us Warten nach jedem Zeichen ; ; Makro für Enable active time ; ; Version für 10 MHz Takt ;.MACRO enactive ; nop ; nop ; nop ; nop ; nop ;.ENDMACRO ; ; Version für 4 MHz Takt ; .MACRO enactive nop nop .ENDMACRO ; Lcd4Init: rcall LcdDelay1s ; Warte eine Sekunde auf LCD ldi rmp,cLcdWrite ; Datenrichtung auf Ausgang out pLcdDdr,rmp ldi rmp,cLcdDummy ; Dummy zum Abfangen der LCD rcall Lcd4Set ; drei Mal senden mit Delay je 5 ms rcall LcdDelay5ms ldi rmp,cLcdDummy rcall Lcd4Set rcall LcdDelay5ms ldi rmp,cLcdDummy rcall Lcd4Set rcall LcdDelay5ms ldi rmp,0b00101000 ; Function Set auf 4 Bit rcall Lcd4Ctrl ; Ausgabe auf Control Port LCD ldi rmp,0b00010100 ; Cursor display shift rcall Lcd4Ctrl ldi rmp,0b00001100 ; LCD on rcall Lcd4Ctrl ldi rmp,0b00000110 ; Entry mode rcall Lcd4Ctrl Lcd4Clear: ldi rmp,0b00000001 ; Set Lcd Clear rcall Lcd4Ctrl Lcd4Home: ldi rmp,0b00000010 ; Set LCD Home Position ; ; Ausgabe von rmp an den Control-Port der LCD ; Lcd4Ctrl: push rmp ; Rette Byte andi rmp,0xF0 ; Lösche unteres Nibble rcall Lcd4Set ; Gib oberes Nibble aus pop rmp ; Stelle Byte wieder her swap rmp ; Vertausche Nibbles andi rmp,0xF0 ; Lösche unteres Nibble rcall Lcd4Set ; Gib unteres Nibble aus rjmp LcdDelay5ms ; Fertig ; ; Gib die gepackte BCD-Zahl in rmp auf dem LCD aus ; Lcd4PBcd: push rmp ; Save on stack swap rmp ; Higher to lower nibble rcall Lcd4PBcd1 ; Output nibble pop rmp ; Restore from stack Lcd4PBcd1: andi rmp,0x0F ; Mask upper nibble ori rmp,0x30 ; Nibble to ASCII ; ; Gib char in rmp auf LCD aus ; Lcd4Chr: push rmp ; Rette char auf Stapel andi rmp,0xF0 ; Lösche unteres Nibble sbr rmp,mLcdRs ; Setze RS-Bit rcall Lcd4Set ; Gib Nibble aus pop rmp ; Hole Char vom Stapel swap rmp ; Vertausche Nibble andi rmp,0xF0 ; Lösche unteres Nibble sbr rmp,mLcdRs ; Setze RS-Bit rcall Lcd4Set ; Gib Nibble aus rjmp LcdDelay50us ; Fertig ; ; Gib Nibble in rmp an LCD aus ; Lcd4Set: out pLcdPort,rmp ; Byte auf Ausgabeport nop sbi pLcdPort,bLcdEn ; Setze Enable-Bit enactive ; Delay macro cbi pLcdPort,bLcdEn ; Enable Bit löschen nop ret ; ; Verzögerung um 1 Sekunde bei Init der LCD ; LcdDelay1s: ldi rmp,c1s ; 200 * 5 ms warten LcdDelay1s1: rcall LcdDelay5ms dec rmp brne LcdDelay1s1 ret ; ; Verzögerung um 5 ms nach jedem Control Word ; LcdDelay5ms: push ZH push ZL ldi ZH,HIGH(c5ms) ldi ZL,LOW(c5ms) LcdDelay5ms1: sbiw ZL,1 brne LcdDelay5ms1 pop ZL pop ZH ret ; ; Delay um 50 Mikrosekunden nach jedem Char ; LcdDelay50us: ldi rmp,c50us LcdDelay50us1: nop dec rmp brne LcdDelay50us1 ret ; ; Gib an der Position in rmp den Text ab Z aus (null-term.) ; Lcd4ZTxt: sbr rmp,0b10000000 ; Setze DD-RAM-Adresse rcall Lcd4Ctrl Lcd4ZTxt1: lpm ; Get a char tst R0 ; Null-Char? breq Lcd4ZTxtR mov rmp,R0 rcall Lcd4Chr adiw ZL,1 rjmp Lcd4ZTxt1 Lcd4ZTxtR: ret ; ; Gib rmp chars Text im SRAM ab Z aus ; Lcd4RTxt: mov R0,rmp ; R0 ist Zähler Lcd4RTxt1: ld rmp,Z+ ; Lese char rcall Lcd4Chr dec R0 brne Lcd4RTxt1 ret
Pfad: Home => AVR-Übersicht => 4-Bit-LCD am STK500 => Quellcode Uhr
; *************************************************************** ; * Uhr mit 2-Zeilen-LCD-Anzeige für STK500 mit Timer/Counter 1 * ; * Anschluss der LCD über 4-Bit-Kabel an Port des STK500 * ; * Bit0=E, Bit1=RS, Bit4..7:D4..D7 * ; * Benötigt die LCD-Basisroutinen Lcd4Inc.asm * ; * Eingestellt auf Taktfrequenz 3,685 MHz des STK500 * ; * (C)2002 by info!at!avr-asm-tutorial.net * ; * Erstellt: 16.2.2002, Letzte Änderung: 17.2.2002 * ; *************************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Timing-Schema zur Erzeugung des 1-Sekunden-Taktes ; ;3.685.000Hz--> 460.625Hz --> 67 Hz --> 1 Hz ; ;+---------+ +----------+ +----------+ +--------+ ;|CPU-Takt | |TC1-Teiler| |TC1-Compa-| |Register| ;|3,685 MHz|-->|Prescaler |-->|re Match A|-->| rdiv1s |--> 1 s ;|7,37MHz/2| |Teiler /8 | | /6875 | | /67 | ;+---------+ +----------+ +----------+ +--------+ ; ; Konstanten ; .EQU ftakt = 3685000 ; Frequenz STK500 interner Takt .EQU cdivtc1 = 6875 ; Teiler für TC1 .EQU cdiv1s = 67 ; Teiler für 1 s ; ; Aktive Ports für LCD-Ausgabe ; .EQU pLcdPort=PORTA ; LCD an PORT A angeschlossen .EQU pLcdDdr=DDRA ; Datenrichtungsregister LCD-Port ; ; Benutzte Register ; .DEF rint= R15 ; Interrupt temp register .DEF rmp = R16 ; Multi-Purpose Register .DEF rdiv1s = R17 ; Teiler durch 67 ; ; Datensegment ; ; SRAM-Speicher für Datum und Uhrzeit ; ; Packed BCD: Z=Bit7..4, E=Bit3..0 ; ; $0060 +1 +2 +3 +4 +5 ; +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ ; | Tag | |Monat| | Jahr| |Stund| |Minut| |Sekun| ; | Z E | | Z E | | Z E | | Z E | | Z E | | Z E | ; +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ ; .DSEG .ORG $0060 ; Datensegment beginnt bei $0060 ramdt: .BYTE 6 ; dd mm yy hh mm ss, packed BCD reservieren ; ; Code beginnt hier ; .CSEG .ORG $0000 ; ; Reset- und Interrupt-Vektoren ; rjmp Start ; Reset-vector reti ; External Interrupt Request 0 reti ; External Interrupt Request 1 reti ; Timer/Counter1 Capture event rjmp TCmp1A ; Timer/Counter1 Compare match A reti ; Timer/Counter1 Compare match B reti ; Timer/Counter1 Overflow reti ; Timer/Counter0 Overflow reti ; SPI Serial Transfer complete reti ; Uart Rx char available reti ; Uart Tx data register empty reti ; Uart Tx complete reti ; Analog comparator ; ; ************** Interrupt service routines ******** ; ; ; Timer/Counter 1, Compare match A interrupt ; TCmp1A: in rint,SREG ; Status-Register retten inc rdiv1s ; Teiler durch 67 erhöhen out SREG,rint ; Status vor Int wieder herstellen reti ; ; **************** Ende der Interrupt Service Routinen ********* ; ; **************** Verschiedene Unterprogramme ************* ; ; LCD-4-Bit-Routinen einbinden ; .NOLIST .INCLUDE "Lcd4IncE.asm" .LIST ; ; Init date-time default ; initdt: ldi ZH,HIGH(2*initdtt) ; Set date/time to default ldi ZL,LOW(2*initdtt) ldi XH,HIGH(ramdt) ldi XL,LOW(ramdt) ldi rmp,6 initdt1: lpm st X+,R0 adiw ZL,1 dec rmp brne initdt1 ret initdtt: ; Default date and time table .DB 0x17,0x02,0x02,0x14,0x05,0x00 ; ; Add 1 to BCD number that Z points to ; R0 must be 0x06, R1 is used temporarily, R2 is restart ; value, rmp is maximum value for overflow ; return with carry set if no overflow occurs ; inct: ld R1,Z ; Read Packed BCD to R1 sec ; Set carry adc R1,R0 ; add 06 and carry brhs inct1 ; If half carry: don't sub 06 sub R1,R0 ; no half carry inct1: cp R1,rmp ; max value reached? brcs inct2 ; no overflow mov R1,R2 ; set to restart value inct2: st Z,R1 ; Write to RAM ret ; and return ; ; Display date on LCD ; dispdate: clr rmp ; Set LCD home position ldi ZH,HIGH(2*datet) ; display Date-Text ldi ZL,LOW(2*datet) rcall Lcd4ZTxt ; Gib nullterminierten Text aus ldi rmp,'.' ; Separator für Datum mov R0,rmp ldi ZH,HIGH(ramdt) ; Zeige auf Datum ldi ZL,LOW(ramdt) rcall disp3 ; Gib drei PBCD-Zahlen mit Separator aus ; ; Display time on LCD ; disptime: ldi rmp,0x40 ; LCD Cursor Beginn 2. Zeile ldi ZH,HIGH(2*timet) ; Display Time-Text ldi ZL,LOW(2*timet) rcall Lcd4ZTxt ; Gib nullterminierten String aus ldi rmp,':' ; Separator für Zeit mov R0,rmp ldi ZH,HIGH(ramdt+3) ; Zeige auf Zeit ldi ZL,LOW(ramdt+3) rcall disp3 ; Gib die nächsten drei PBCD aus lds rmp,ramdt+5 ; Lese Sekunden com rmp ; Invertiere out PORTB,rmp ; und gib auf LEDs aus ret ; Fertig ; ; Text, nullterminiert, für Datum und Zeit auf LCD ; datet: .DB "Date: ",0x00,0x00 timet: .DB "Time: ",0x00,0x00 ; ; Gib die drei PBCD ab Z auf die LCD aus ; Separator (: oder .) in R0 ; disp3: ld rmp,Z+ ; Lese Zahl rcall Lcd4PBcd ; Gib Packed BCD aus mov rmp,R0 ; Gib Separator aus rcall Lcd4Chr ld rmp,Z+ ; Lese nächste Zahl rcall Lcd4PBcd mov rmp,R0 ; Gib Separator aus rcall Lcd4Chr ld rmp,Z ; Lese dritte Zahl rjmp Lcd4PBcd ; ; **************** Ende der Unterprogramme ********************* ; ; ******************** Hauptprogram **************************** ; ; Hauptprogramm beginnt hier ; Start: ldi rmp,HIGH(RAMEND) ; Initiate stack pointer out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,0b00100000 ; Sleep Mode Idle out MCUCR,rmp ser rmp ; Port B auf Ausgang out DDRB,rmp rcall Lcd4Init ; Init LCD output rcall initdt ; Init date and time default rcall dispdate ; Default date/time to LCD clr rmp out TCNT1H,rmp ; High Byte TC1 clear out TCNT1L,rmp ; Low Byte TC1 clear ldi rmp,HIGH(cdivtc1) ; Compare Match, MSB first out OCR1AH,rmp ldi rmp,LOW(cdivtc1) ; Compare Match A, LSB last out OCR1AL,rmp ldi rmp,0b01000000 ; Toggle Output A on Comp Match out TCCR1A,rmp ldi rmp,0b00001010 ; Clear on Comp Match A, Div 8 out TCCR1B,rmp ; Start Timer ldi rmp,0b01000000 ; TC1 CompA Int Enable out TIMSK,rmp sei loop: sleep ; Schlafenlegen nop ; Aufwachen cpi rdiv1s,cdiv1s ; 67 erreicht? brcs loop ; not nicht clr rdiv1s ; Neustart, Sekunde zu Ende ldi rmp,0x06 ; Addiere Packed BCD Sekunden mov R0,rmp ; Konstante wird für Packed BCD benötigt clr R2 ; Startwert für Überlauf auf höherwertige Zahl ldi rmp,0x60 ; Überlaufwert für Sekunden ldi ZH,HIGH(ramdt+5) ; Auf SRAM-Adresse der Sekunden ldi ZL,LOW(ramdt+5) rcall inct ; inc seconds brcs tok ; Time is ok sbiw ZL,1 ; set pointer to minutes rcall inct ; inc minutes brcs tok ; time is ok ldi rmp,0x24 ; maximum value for hours sbiw ZL,1 ; Point to hours rcall inct ; inc hours brcs tok ; time is ok inc R2 ; Set Day/Month default to 1 ldi ZH,HIGH(ramdt+1) ; Point to month ldi ZL,LOW(ramdt+1) ld rmp,Z ; Read month cpi rmp,0x02 ; February? brne nonfeb ; Not February adiw ZL,1 ; Point to year ld rmp,Z ; Read year tst rmp ; Year=00? breq m28 ; February 2000 is 28 days only andi rmp,0x10 ; Tens of Years=odd? ld rmp,Z ; Read year again brne yodd ; Tens of years odd andi rmp,0x03 ; lower nibble year=0,4,8? brne m28 ; February has 28 days m29: ldi rmp,0x30 ; February has 29 days rjmp mok ; month is ok yodd: andi rmp,0x03 ; Tens of years are odd cpi rmp,0x02 ; Lower nibble year=2 or 6? breq m29 ; Yes, February has 29 days m28: ldi rmp,0x29 ; February has 28 days rjmp mok nonfeb: cpi rmp,0x07 ; Month > June? brcs monthok ; No, don't inc dec rmp ; back one month monthok: andi rmp,0x01 ; Odd month? brne m31 ; Month has 31 days ldi rmp,0x31 ; Month has 30 days rjmp mok m31: ldi rmp,0x32 ; Month has 31 days mok: ldi ZH,HIGH(ramdt) ; Point to day ldi ZL,LOW(ramdt) rcall inct ; add 1 day brcs dok ; Date is ok adiw ZL,1 ; Point to month ldi rmp,0x13 ; Max monthes rcall inct ; next month brcs dok ; Date is ok adiw ZL,1 ; Point to year clr R2 ; Default year=00 rcall inct ; Next year ; ; Refresh date on LCD ; dok: rcall dispdate ; Display date ; ; Refresh time on LCD ; tok: rcall disptime ; Display time rjmp loop ; ; Ende des Programmes ;
; *************************************************************** ; * Uhr mit 2-Zeilen-LCD-Anzeige für STK500 mit Timer/Counter 1 * ; * Anschluss der LCD über 4-Bit-Kabel an Port des STK500 * ; * Bit0=E, Bit1=RS, Bit4..7:D4..D7 * ; * Benötigt die LCD-Basisroutinen Lcd4Inc.asm * ; * Eingestellt auf Taktfrequenz 3,685 MHz des STK500 * ; * (C)2002 by [email protected] * ; * Erstellt: 16.2.2002, Letzte Änderung: 17.2.2002 * ; *************************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Timing-Schema zur Erzeugung des 1-Sekunden-Taktes ; ;3.685.000Hz--> 460.625Hz --> 67 Hz --> 1 Hz ; ;+---------+ +----------+ +----------+ +--------+ ;|CPU-Takt | |TC1-Teiler| |TC1-Compa-| |Register| ;|3,685 MHz|-->|Prescaler |-->|re Match A|-->| rdiv1s |--> 1 s ;|7,37MHz/2| |Teiler /8 | | /6875 | | /67 | ;+---------+ +----------+ +----------+ +--------+ ; ; Konstanten ; .EQU ftakt = 3685000 ; Frequenz STK500 interner Takt .EQU cdivtc1 = 6875 ; Teiler für TC1 .EQU cdiv1s = 67 ; Teiler für 1 s ; ; Aktive Ports für LCD-Ausgabe ; .EQU pLcdPort=PORTA ; LCD an PORT A angeschlossen .EQU pLcdDdr=DDRA ; Datenrichtungsregister LCD-Port ; ; Benutzte Register ; .DEF rint= R15 ; Interrupt temp register .DEF rmp = R16 ; Multi-Purpose Register .DEF rdiv1s = R17 ; Teiler durch 67 ; ; Datensegment ; ; SRAM-Speicher für Datum und Uhrzeit ; ; Packed BCD: Z=Bit7..4, E=Bit3..0 ; ; $0060 +1 +2 +3 +4 +5 ; +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ ; | Tag | |Monat| | Jahr| |Stund| |Minut| |Sekun| ;|ZE||ZE||ZE||ZE||ZE||ZE| ; +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ ; .DSEG .ORG $0060 ; Datensegment beginnt bei $0060 ramdt: .BYTE 6 ; dd mm yy hh mm ss, packed BCD reservieren ; ; Code beginnt hier ; .CSEG .ORG $0000 ; ; Reset- und Interrupt-Vektoren ; rjmp Start ; Reset-vector reti ; External Interrupt Request 0 reti ; External Interrupt Request 1 reti ; Timer/Counter1 Capture event rjmp TCmp1A ; Timer/Counter1 Compare match A reti ; Timer/Counter1 Compare match B reti ; Timer/Counter1 Overflow reti ; Timer/Counter0 Overflow reti ; SPI Serial Transfer complete reti ; Uart Rx char available reti ; Uart Tx data register empty reti ; Uart Tx complete reti ; Analog comparator ; ; ************** Interrupt service routines ******** ; ; ; Timer/Counter 1, Compare match A interrupt ; TCmp1A: in rint,SREG ; Status-Register retten inc rdiv1s ; Teiler durch 67 erhöhen out SREG,rint ; Status vor Int wieder herstellen reti ; ; **************** Ende der Interrupt Service Routinen ********* ; ; **************** Verschiedene Unterprogramme ************* ; ; LCD-4-Bit-Routinen einbinden ; .NOLIST .INCLUDE "Lcd4IncE.asm" .LIST ; ; Init date-time default ; initdt: ldi ZH,HIGH(2*initdtt) ; Set date/time to default ldi ZL,LOW(2*initdtt) ldi XH,HIGH(ramdt) ldi XL,LOW(ramdt) ldi rmp,6 initdt1: lpm st X+,R0 adiw ZL,1 dec rmp brne initdt1 ret initdtt: ; Default date and time table .DB 0x17,0x02,0x02,0x14,0x05,0x00 ; ; Add 1 to BCD number that Z points to ; R0 must be 0x06, R1 is used temporarily, R2 is restart ; value, rmp is maximum value for overflow ; return with carry set if no overflow occurs ; inct: ld R1,Z ; Read Packed BCD to R1 sec ; Set carry adc R1,R0 ; add 06 and carry brhs inct1 ; If half carry: don't sub 06 sub R1,R0 ; no half carry inct1: cp R1,rmp ; max value reached? brcs inct2 ; no overflow mov R1,R2 ; set to restart value inct2: st Z,R1 ; Write to RAM ret ; and return ; ; Display date on LCD ; dispdate: clr rmp ; Set LCD home position ldi ZH,HIGH(2*datet) ; display Date-Text ldi ZL,LOW(2*datet) rcall Lcd4ZTxt ; Gib nullterminierten Text aus ldi rmp,'.' ; Separator für Datum mov R0,rmp ldi ZH,HIGH(ramdt) ; Zeige auf Datum ldi ZL,LOW(ramdt) rcall disp3 ; Gib drei PBCD-Zahlen mit Separator aus ; ; Display time on LCD ; disptime: ldi rmp,0x40 ; LCD Cursor Beginn 2. Zeile ldi ZH,HIGH(2*timet) ; Display Time-Text ldi ZL,LOW(2*timet) rcall Lcd4ZTxt ; Gib nullterminierten String aus ldi rmp,':' ; Separator für Zeit mov R0,rmp ldi ZH,HIGH(ramdt+3) ; Zeige auf Zeit ldi ZL,LOW(ramdt+3) rcall disp3 ; Gib die nächsten drei PBCD aus lds rmp,ramdt+5 ; Lese Sekunden com rmp ; Invertiere out PORTB,rmp ; und gib auf LEDs aus ret ; Fertig ; ; Text, nullterminiert, für Datum und Zeit auf LCD ; datet: .DB "Date: ",0x00,0x00 timet: .DB "Time: ",0x00,0x00 ; ; Gib die drei PBCD ab Z auf die LCD aus ; Separator (: oder .) in R0 ; disp3: ld rmp,Z+ ; Lese Zahl rcall Lcd4PBcd ; Gib Packed BCD aus mov rmp,R0 ; Gib Separator aus rcall Lcd4Chr ld rmp,Z+ ; Lese nächste Zahl rcall Lcd4PBcd mov rmp,R0 ; Gib Separator aus rcall Lcd4Chr ld rmp,Z ; Lese dritte Zahl rjmp Lcd4PBcd ; ; **************** Ende der Unterprogramme ********************* ; ; ******************** Hauptprogram **************************** ; ; Hauptprogramm beginnt hier ; Start: ldi rmp,HIGH(RAMEND) ; Initiate stack pointer out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,0b00100000 ; Sleep Mode Idle out MCUCR,rmp ser rmp ; Port B auf Ausgang out DDRB,rmp rcall Lcd4Init ; Init LCD output rcall initdt ; Init date and time default rcall dispdate ; Default date/time to LCD clr rmp out TCNT1H,rmp ; High Byte TC1 clear out TCNT1L,rmp ; Low Byte TC1 clear ldi rmp,HIGH(cdivtc1) ; Compare Match, MSB first out OCR1AH,rmp ldi rmp,LOW(cdivtc1) ; Compare Match A, LSB last out OCR1AL,rmp ldi rmp,0b01000000 ; Toggle Output A on Comp Match out TCCR1A,rmp ldi rmp,0b00001010 ; Clear on Comp Match A, Div 8 out TCCR1B,rmp ; Start Timer ldi rmp,0b01000000 ; TC1 CompA Int Enable out TIMSK,rmp sei loop: sleep ; Schlafenlegen nop ; Aufwachen cpi rdiv1s,cdiv1s ; 67 erreicht? brcs loop ; not nicht clr rdiv1s ; Neustart, Sekunde zu Ende ldi rmp,0x06 ; Addiere Packed BCD Sekunden mov R0,rmp ; Konstante wird für Packed BCD benötigt clr R2 ; Startwert für Überlauf auf höherwertige Zahl ldi rmp,0x60 ; Überlaufwert für Sekunden ldi ZH,HIGH(ramdt+5) ; Auf SRAM-Adresse der Sekunden ldi ZL,LOW(ramdt+5) rcall inct ; inc seconds brcs tok ; Time is ok sbiw ZL,1 ; set pointer to minutes rcall inct ; inc minutes brcs tok ; time is ok ldi rmp,0x24 ; maximum value for hours sbiw ZL,1 ; Point to hours rcall inct ; inc hours brcs tok ; time is ok inc R2 ; Set Day/Month default to 1 ldi ZH,HIGH(ramdt+1) ; Point to month ldi ZL,LOW(ramdt+1) ld rmp,Z ; Read month cpi rmp,0x02 ; February? brne nonfeb ; Not February adiw ZL,1 ; Point to year ld rmp,Z ; Read year tst rmp ; Year=00? breq m28 ; February 2000 is 28 days only andi rmp,0x10 ; Tens of Years=odd? ld rmp,Z ; Read year again brne yodd ; Tens of years odd andi rmp,0x03 ; lower nibble year=0,4,8? brne m28 ; February has 28 days m29: ldi rmp,0x30 ; February has 29 days rjmp mok ; month is ok yodd: andi rmp,0x03 ; Tens of years are odd cpi rmp,0x02 ; Lower nibble year=2 or 6? breq m29 ; Yes, February has 29 days m28: ldi rmp,0x29 ; February has 28 days rjmp mok nonfeb: cpi rmp,0x07 ; Month > June? brcs monthok ; No, don't inc dec rmp ; back one month monthok: andi rmp,0x01 ; Odd month? brne m31 ; Month has 31 days ldi rmp,0x31 ; Month has 30 days rjmp mok m31: ldi rmp,0x32 ; Month has 31 days mok: ldi ZH,HIGH(ramdt) ; Point to day ldi ZL,LOW(ramdt) rcall inct ; add 1 day brcs dok ; Date is ok adiw ZL,1 ; Point to month ldi rmp,0x13 ; Max monthes rcall inct ; next month brcs dok ; Date is ok adiw ZL,1 ; Point to year clr R2 ; Default year=00 rcall inct ; Next year ; ; Refresh date on LCD ; dok: rcall dispdate ; Display date ; ; Refresh time on LCD ; tok: rcall disptime ; Display time rjmp loop ; ; Ende des Programmes ; http://www.avr-asm-tutorial.net/avr_de/quellen/Lcd4IncC.asm1/20/2009 7:42:50 PM
AVR-PWM-ADC-Test für STK500
Pfad: Home => AVR-Übersicht => PWM-ADC Tutorial für das Erlernen der Assemblersprache von
AVR-Einchip-Prozessoren AT90Sxxxx von ATMEL anhand geeigneter praktischer Beispiele.
Einfacher 8-Bit-Analog-Digital-Wandler mit PWM auf dem STK board Aufgabe Mit Hilfe der Analogeingänge AIN0 und AIN1 läßt sich ohne größeren zusätzlichen Aufwand an Hardware ein einfacher AnalogDigital-Wandler erstellen. Der Analogvergleicher wird dabei benutzt, um eine mit dem Timer/Counter 1 (TC1) erzeugte Vergleichsspannung mit der Eingangsspannung zu vergleichen und diese so lange nachzujustieren, bis das Ergebnis auf 8 Bit genau feststeht. TC1 arbeitet dabei als Pulsweitenmodulator (PWM), das Tastverhältnis bestimmt die Ausgangsspannung. Das Ergebnis der Wandlung wird auf den LEDs des STK500-Boards ausgegeben.
Erforderliche Hardware Das Bild links zeigt die gesamte Testhardware zum Anschluss an das STK500-Board. Die Portanschlüsse sind von oben gezeichnet. Die Messspannung von 0,00 bis 5,00 Volt wird mit dem 10kPoti aus der Betriebsspannung des Boards erzeugt und mit einem Kondensator gefiltert. Sie wird an den Eingang des Analogkomparators AIN0 (entspricht beim AT90S8515 dem Port PB2) gelegt. Die Vergleichsspannung wird aus dem PWM-Ausgang OC1A (entspricht beim AT90S8515 dem Port PD5) erzeugt und über drei RC-Filter mit 10k/100n an den analogen Vergleichseingang AIN1 (entspricht beim AT90S8515 dem Port PB3) geführt. Das ist schon alles.
Noch ein paar Hinweise zum Testaufbau am STK500. Das RC-Filter und das Teststpannungspoti passen auf ein kleines Lochplatinchen. Der Anschluss an das Board kann mit den beim STK500 mitgelieferten zweipoligen Anschlusskabeln vorgenommen werden, wenn man entsprechende Pfostenstecker auf der Lochplatine vorsieht. Nicht vergessen, dass die LEDs in dieser Schaltung leider an Port C angeschlossen werden müssen, weil Port B für die Analogeingänge gebraucht wird. Deshalb ist auch die Schaltung und die Software für das STK200 nicht geeignet (feste Verdrahtung der LEDs an Port B). Nach Abschluss der Arbeiten nicht vergessen, die LEDs über das Parallelkabel wieder an Port B anzuschließen, wo sie standardmäßig hingehören.
An den Anfang dieser Seite
Funktionsweise Erzeugung der Vergleichsspannung mit Pulsweitenmodulation Ein Pulsweitenmodulator erzeugt ein Rechtecksignal mit einer festgelegten Pulsweite. Der Modulator erzeugt ein 1-Signal für eine bestimmte Zeit lang, der Rest der Zeit ist der Ausgang auf Null. Filtert man diese Rechteckspannung, dann kann man über die Pulsweite eine Analogspannung erzeugen. Stellt man ein Impuls-/Pausen- Verhältnis von 50% ein, dann ergibt sich am Ausgang des Filters die halbe Betriebsspannung, entsprechend bei 25% ein Viertel, etc.
D
Lade- und Entladeverhältnisse an den RC-Filtern zeigt vereinfacht die Grafik. Die Rechteckspannung U(PWM) bildet sich noch deutlich auf der Spannung am Kondensator C1 ab. Die Glättung am Kondensator C2 ist sehr deutlich zu erkennen. Allerdings ist das Nachlaufen zu Beginn des Einschwingvorganges auch deutlich zu erkennen. Noch deutlicher ist dieses Nachlaufen am Kondensator C3 zu sehen. Weil sich auch im eingeschwungenen Zustand noch Lade- und Entladevorgänge auf das Ausgangssignal auswirken, muss das Filter so dimensioniert sein, dass diese Restwelligkeit niedriger ist als das Auflösungsvermögen des AD-Wandlers. Bei 8 Bit Auflösung und 5,0 Volt Betriebsspannung muss die Restwelligkeit deutlich unter 5/256 = 19,5 mV liegen. Vergrößerung der Widerstände und Kondensatoren des RC-Filters oder das Hinzufügen weiterer RC-Glieder bewirkt eine Verminderung der Restwelligkeit. Allerdings wird dadurch die Zeit, die der PWM-Modulator bis zum Einstellen der Analogspannung benötigt, ebenfalls länger. Daher ist ein PWM-basierter ADC nicht sehr schnell (im vorliegenden Fall maximal etwa 5 Messungen pro Sekunde). In der Praxis wird man einen Kompromiss zwischen Einschwingzeit und Restwelligkeit schließen. Die Einschwingzeit wird durch die Anzahl PWMZyklen gewählt, die der AVR abwartet, bevor er den Analogvergleich am Port abliest. Sie ist daher durch Software einstellbar. Im vorliegenden Fall wurden dafür 128 Zyklen gewählt, was für eine stabile Einschwingzeit bei der vorliegenden Dimensionierung völlig ausreicht. Ohne Softwareänderung sind bis zu 65536 Zyklen möglich. Für die Dimensionierung des RC wurde ein kleines Pascal-Programm entwickelt, das die Vorgänge beim PWM simuliert und die Ermittlung der Restwelligkeit und Einschwingzeit ermöglicht. Es läuft auf der Kommandozeile. Der Quellcode avr_pwm1.pas kann auf jedem gängigen Pascal-Compiler zu einem lauffähigen Programm kompiliert werden. Die Frequenz, mit der der PWM arbeitet, ergibt sich bei 8-Bit-Auflösung zu f (PWM) = Taktfrequenz / 510 (9 Bit: 1022, 10 Bit: 2046) Bei einer Taktfrequenz von 3,685 MHz auf dem STK500 Board ergeben sich krumme 7.225,5 Hz PWM-Frequenz oder 138,4 Mikrosekunden pro PWM-Zyklus. Bei 128 Zyklen pro Bit ergeben sich Umwandlungszeiten von 142 Millisekunden pro Messung oder 7 Messungen pro Sekunde. Nicht sehr schnell, aber immer noch schneller als das menschliche Auge. Referenzspannung dieser Schaltung ist die Betriebsspannung des Prozessors. Diese kann eventuell mit dem Studio im STK500 verstellt werden. Die Genauigkeit des PWM-Signals ist im untersten Bereich (0,00 bis 0,10 V), aber mehr noch im oberen Spannungsbereich nicht sehr genau, weil die MOS-Ausgangstreiber nicht ganz bis an die obere Betriebsspannung herankommen. Das bedingt im mittleren Bereich zwar auch schon Ungenauigkeiten, wirkt sich aber in diesen Extrembereichen in Nichtlinearität aus. Es hat deshalb auch keinen großen Sinn, 9 oder 10 Bits Auflösung anzustreben. Wer es genauer haben möchte, greift zu einem Prozessor mit eingebauten AD-Wandlern. An den Anfang dieser Seite
Methode der sukzessiven Approximation Zum Messen der Eingangsspannung könnte man schrittweise die Pulsweite erhöhen und jeweils prüfen, ob die Spannung am Vergleichseingang schon größer ist als die Eingangsspannung. Das erfordert bei 8 Bit Genauigkeit aber schon 255 Einzelschritte, bei 10 Bit Genauigkeit stolze 1023 Schritte. Als Wandelmethode wird deshalb die sukzessive Approximation verwendet. Im ersten Schritt wird die Vergleichsspannung auf die halbe Betriebsspannung eingestellt und festgestellt, ob die Messspannung darüber oder darunter liegt. Liegt sie darunter, wird die Vergleichsspannung im nächsten Schritt auf ein Viertel eingestellt, liegt sie darüber, dann auf dreiviertel. Diese Schritte setzt man so lange fort, bis man die Messspannung mit der nötigen Genauigkeit festgestellt hat. Bei 8 Bit Genauigkeit sind demnach acht Schritte, bei 10 Bit zehn Schritte nötig. Das ist um den Faktor 30 (8 Bit) bzw. 100 schneller als die Methode mit der schrittweisen Erhöhung der Vergleichsspannung. Das Verfahren lässt sich leicht per Software umsetzen. Die ersten fünf Schritte sind für eine 8-Bit-Wandlung in der Tabelle angegeben. Angegeben ist die Vergleichsspannung beim n-ten Schritt und die korrespondierende binäre Ausgabe an den 8-BitPWM. Ist die Vergleichsspannung größer als die Messspannung, geht es beim oberen Kasten weiter. Umgekehrt beim unteren Kasten. 1 2 3 4 1000.0000 0100.0000 0010.0000 0001.0000
Das Muster ist leicht erkennbar: Zu Beginn jedes Schrittes ist das entsprechende Bit auf 1 zu setzen. Ist das zuviel an Spannung, wird es beim nächsten Schritt wieder auf Null gesetzt. Ideal einfach zu programmieren!
Software Die Software ist in HTML-Form als adc8.html und als ASM-Quellcode-Datei adc8.asm zugänglich. Das Programm besteht aus sehr langen Wartezeiträmen, die der PWM braucht, bis sich die erzeugte Spannung stabilisiert hat. Das ist ideal für den Einsatz von Interrupts, da wir sonst komplizierte und vom Timing her ausgeklügelte Verzögerungsschleifen programmieren müssten. Weil der TC1 sowieso mit nichts anderem befasst ist als mit dem Hoch- und Runterzählen des PWM, kann er das gesamte Timing der Messung mit übernehmen. Der Prozessor selbst ruht die meiste Zeit im Schlafmodus und wird alle 142 Mikrosekunden durch den PWM-Interrupt aufgeweckt. Nach der Feststellung, dass die Fertig- Flagge nicht gesetzt ist, kann er weiter ruhen. Ist er fertig, muss das Ergebnis auf den Port und die Flagge wieder gelöscht werden. An den Anfang dieser Seite
Hauptprogramm Das Hauptprogramm durchläuft folgende Schritte: 1. Der Stapel wird eingerichtet. Das ist nötig, weil das Programm über Interrupts des Timers gesteuert abläuft. Interrupts brauchen immer den Stapel, um die Rücksprungadresse dort ablegen zu können. Der Stapel wird auf dem obersten internen SRAM angelegt. 2. Der Zyklenzähler wird gesetzt. Er bestimmt, nach wievielen Zyklen des PWM der Vergleich zwischen der erzeugten PWMAnalogspannung mit dem analogen Eingangssignal erfolgt. Im Beispiel sind das 128 Zyklen. Es ist durch Ändern der Konstanten CycLen0 einstellbar (z.B. um zu erreichen, dass jede Sekunde eine Messung beginnt). 3. Der Bitzähler rBlc wird zu Beginn auf 0x80 gesetzt. Das entspricht der halben Betriebsspannung als Vergleichsspannung. Das Messergebnis in rTmp wird zu Beginn ebenfalls auf diesen Wert gesetzt. 4. Die Portausgänge von Port C zum Treiben der Leuchtdioden werden als Ausgänge geschaltet. 5. Das Portbit PD5 von Port D ist als Ausgang einzustellen, damit das PWM-Ausgangssignal außen am Portpin ausgegeben werden kann. 6. Der Schlafmodus Idle muss eingestellt werden, damit die CPU auf die SLEEP-Instruction reagiert und beim Interrupt des TC1 wieder korrekt aufwacht. 7. Timer/Counter 1 wird als 8-bit-PWM eingestellt und bezieht seinen Zähltakt direkt aus dem CPU-Takt (möglichst große PWM-Frequenz). 8. Die PWM-Pulsweite wird auf 50% (0x0080) eingestellt. 9. Im Timer Int Mask Register wird der Overflow-Interrupt ermöglicht, damit TC1 nach jedem PWM-Zyklus einen Interrupt auslöst. 10. Die Annahme von Interruptanforderungen durch die CPU wird ermöglicht. Ab jetzt beginnt die Schleife, die nach jedem Aufwecken durch TC1 durchlaufen wird. Nachdem der Interrupt bearbeitet ist, wird die Ready-Flagge befragt, ob der ADC-Vorgang schon durchlaufen und der gemessene Wert gültig ist. Ist das der Fall, wird die Flagge wieder gelöscht, der gemessene Wert bitweise invertiert und an die Leuchtdioden ausgegeben. Dann versinkt die CPU wieder in den Schlaf, bis ein neuer Messvorgang beendet ist und die Flagge nach dem Wecken gesetzt ist.
Interruptsteuerung Zentrales Herzstück des AD-Wandlers ist der Pulsweitenmodulator mit dem Timer/Counter TC1. Immer wenn TC1 einen PWMZyklus vollendet hat, wird der Overflow-Interrupt ausgelöst, der Programmzähler auf den Stapel abgelegt und der Programmablauf zur Interruptroutine verzweigt. Diese Interruptroutine übernimmt selbstständig die gesamte PWM-ADC-Steuerung und teilt dem Hauptprogramm über eine Flagge mit, wann die Umwandlung beendet und das Ergebnis gültig ist. Sie startet nach Ende einer Messung automatisch wieder von vorne. Durch Einstellung der PWM-Zyklusanzahl zu Beginn einer Messung und der Anzahl PWMZyklen für die Messung der Einzelbits ergibt sich die Wiederholzeit für die Messungen. Im linken Bild ist der Algorithmus für die gesamte Steuerung des ADWandlers abgebildet. Es beginnt damit, dass der Inhalt des Statusregisters gesichert wird. Vor der Rückkehr vom Interrupt wird dessen Originalinhalt wieder hergestellt. Im nächsten Schritt wird der Zähler für die PWM-Zyklen um Eins erniedrigt und festgestellt, ob schon genügend PWM-Zyklen durchlaufen sind, dass sich die vom PWM erzeugte und vom RC-Filter geglättete Vergleichsspannung am Analog-Eingang AIN1 stabilisiert hat. Ist der Zykluszähler noch nicht Null, werden weitere Zyklen abgewartet und der Interrupt ist vorerst beendet. Ist die vorgegebene Anzahl PWM-Zyklen durchlaufen, wird zunächst der PWM-Zykluszähler auf seinen Voreinstellungswert gesetzt. Dann wird der Analog-Komparator-Ausgang abgefragt, ob die Vergleichsspannung an AIN1 größer oder kleiner als die zu messende Spannung am Eingang AIN0 ist. War die Vergleichsspannung zu hoch, wird das zuletzt gesetzte Bit wieder auf Null gesetzt. Wenn nicht, bleibt es auf Eins. Nun wird der Bitzähler mit dem aktiven Bit um eine Stelle nach rechts geschoben. Dabei wird von links eine Null in Bit 7 des Bitzählers hineingeschoben, nach rechts rutscht Bit 0 des Bitzählers in das CarryFlag des Statusregisters. Rollt dabei eine Null heraus, dann sind wir noch nicht fertig und müssen uns weiter sukzessiv approximieren. Rollt bei dem Vorgang eine Eins aus dem Bitzähler heraus, dann sind wir fertig. Das Ergebnis im Temporärregister wird in das Ergebnisregister kopiert, die Fertig-Flagge im Flaggenregister gesetzt, der PWM-Zyklenzähler auf den längeren Anfangswert für den Neuanfang gesetzt, das Temporärregister entleert und der Bitzähler auf 0x80 gesetzt (acht Näherungsschritte). Schließlich wird das aktive Bit im Bitzähler auf das Temporärregister übertragen (aktives Bit wird auf Eins gesetzt) und das Zwischenergebnis dem PWM mitgeteilt, der damit die Vergleichsspannung am Analogeingang AIN1 füttert. Am Ende wird nur noch das Statusregister wieder in den Originalzustand versetzt und vom Interrupt zurückgekehrt.
Die gesamte Interruptroutine ist mit maximal 25 Taktzyklen oder etwa 6 Mikrosekunden (bei 4 MHz) sehr kurz. Auch wenn noch andere Tasks laufen, dürfte dies immer zu verkraften sein.
Pfad: Home => AVR-Übersicht => 8-Bit-ADC am STK500 => adc8 software
Assembler Quelltext der Umwandlung einer Analogspannung in eine 8-Bit-Zahl ; +-----------------------------------------------------+ ; | 8-Bit-Analog-Digital-Converter mit ATMEL AT90S8515 | ; | auf dem STK500 board, Ausgabe über LEDs | ; | (C)2003 by http://www.avr-asm-tutorial.net | ; | Verwendete Anschlüsse: Ausgang PWM-Rechteckspannung | ; | OC1A an Port D, Bit 5; über dreifach RC-Filter an| ; | invertierienden Analogeingang AIN1 entsprechend | Port B Bit 3; Anschluss der zu messenden Analog- | ; | ; | spannung an nichtinvertierenden Analogeingang | Port B Bit 2; Ausgabe für die LEDs an Port C | ; | ; +-----------------------------------------------------+ ; ; Geschrieben für und getestet mit AT90S8515 auf STK500 ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Konstanten ; .EQU CycLen = 128 ; Anzahl PWM-Zyklen zum Einschwingen .EQU CycLen0 = 128 ; Anzahl PWM-Zyklen vor der ersten Messung ; ; Register ; .DEF rRes = R1 ; Endwert des ADC .DEF rTmp = R14 ; Temporärwert für ADC .DEF rSrg = R15 ; Temporärregister für SREG .DEF rmp = R16 ; Universalregister ohne Int .DEF rimp = R17 ; Universalregister bei Int .DEF rFlg = R18 ; Flagregister, Bit 0=ADC fertig .DEF rBlc = R19 ; Bitlevel-Counter für ADC .DEF rCcL = R24 ; Cycle Counter für ADC, LSB .DEF rCcH = R25 ; dto., LSB ; ; Reset- und Interrupt-Vektoren ; rjmp main ; Reset reti ; INT0 reti ; INT1 reti ; TC1-Capt reti ; TC1-CompA reti ; TC1-CompB rjmp Tc1Ovflw ; TC1-Ovflw reti ; TC0-Ovflw reti ; SPI STC reti ; UART RX reti ; UART UDRE reti ; UART TX reti ; Ana-Comp ; ; TC1-Overflow am PWM-Zyklusende ; Tc1Ovflw: in rSrg,SREG ; Sichern von SREG sbiw rCcL,1 ; Erniedrige Zykluszähler brne Tc1OvflwR ; bei ><0 fertig ldi rCcH,HIGH(CycLen) ; Setze Zyklenzähler ldi rCcL,LOW(CycLen) sbis ACSR,ACO ; prüfe Analogcomp-Ausgang eor rTmp,rBlc ; U zu hoch, lösche letztes Bit lsr rBlc ; Aktives Bit eins rechts brcc Tc1OvflwA ; noch nicht fertig mov rRes,rTmp ; Kopiere das Ergebnis sbr rFlg,0x01 ; Setze Fertig-Flag ldi rCcH,HIGH(CycLen0) ; Setze Dauer vor Messbeginn ldi rCcL,LOW(CycLen0) clr rTmp ; Leeres Ergebnis ldi rBlc,0x80 ; Setze Bitzähler Tc1OvflwA: or rTmp,rBlc ; setze nächstes Bit clr rimp ; Setze TC1-PWM-Zyklus out OCR1AH,rimp out OCR1AL,rTmp Tc1OvflwR: ; Rückkehr vom Interupt out SREG,rSrg ; SREG wieder herstellen reti ; ; Hauptprogramm-Schleife ; main: ldi rmp,HIGH(RAMEND) ; Definiere Stack out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rCcH,HIGH(CycLen0) ; Setze Dauer vor Messbeginn ldi rCcL,LOW(CycLen0) ldi rBlc,0x80 ; Setze Bitzähler mov rTmp,rBlc ; Leeres Ergebnis ldi rmp,0xFF ; Port C auf Output, treibt die LEDs out DDRC,rmp ; alle Datenrichtungsregister = 1 sbi DDRD,PD5 ; PWM-Ausgang OC1A auf Ausgang setzen ldi rmp,0b00100000 ; Sleep-mode idle einstellen out MCUCR,rmp ldi rmp,0b10000001 ; TC1 auf PWM8 non-invertiert out TCCR1A,rmp ; in TC1-Kontrollregister A ldi rmp,0b00000001 ; TC1-clock=System clock out TCCR1B,rmp ; in TC1-Kontrollregister B clr rmp ; Pulsweite Rechtecksignal einstellen out OCR1AH,rmp ; erst das HIGH Byte! ldi rmp,0x80 ; dann das LOW Byte! out OCR1AL,rmp ldi rmp,0b10000000 ; TC1 Overflow Int einschalten out TIMSK,rmp ; in Timer Int Mask Register sei ; Reaktion auf Interrupts durch CPU ermöglichen ; ; Hauptprogramm-Loop ; main1: sleep ; CPU schlafen legen nop sbrs rFlg,0 ; Ready flag Bit 0 abfragen rjmp main1 ; Nicht ready, schlaf weiter cbr rFlg,0x01 ; Setze Bit 0 zurück mov rmp,rRes ; Kopiere Ergebnis in Universalregister com rmp ; Invertiere alle bits (Lampe invers!) out PortC,rmp ; an LED Port ausgeben rjmp main1 ; fertig, schlafen legen ; ; Ende des Programms ;
; +-----------------------------------------------------+ ; | 8-Bit-Analog-Digital-Converter mit ATMEL AT90S8515 | ; | auf dem STK500 board, Ausgabe über LEDs | ; | (C)2003 by http://www.avr-asm-tutorial.net | ; | Verwendete Anschlüsse: Ausgang PWM-Rechteckspannung | ; | OC1A an Port D, Bit 5; über dreifach RC-Filter an| ; | invertierienden Analogeingang AIN1 entsprechend | ; | Port B Bit 3; Anschluss der zu messenden Analog- | ; | spannung an nichtinvertierenden Analogeingang | ; | Port B Bit 2; Ausgabe für die LEDs an Port C | ; +-----------------------------------------------------+ ; ; Geschrieben für und getestet mit AT90S8515 auf STK500 ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Konstanten ; .EQU CycLen = 128 ; Anzahl PWM-Zyklen zum Einschwingen .EQU CycLen0 = 128 ; Anzahl PWM-Zyklen vor der ersten Messung ; ; Register ; .DEF rRes = R1 ; Endwert des ADC .DEF rTmp = R14 ; Temporärwert für ADC .DEF rSrg = R15 ; Temporärregister für SREG .DEF rmp = R16 ; Universalregister ohne Int .DEF rimp = R17 ; Universalregister bei Int .DEF rFlg = R18 ; Flagregister, Bit 0=ADC fertig .DEF rBlc = R19 ; Bitlevel-Counter für ADC .DEF rCcL = R24 ; Cycle Counter für ADC, LSB .DEF rCcH = R25 ; dto., LSB ; ; Reset- und Interrupt-Vektoren ; rjmp main ; Reset reti ; INT0 reti ; INT1 reti ; TC1-Capt reti ; TC1-CompA reti ; TC1-CompB rjmp Tc1Ovflw ; TC1-Ovflw reti ; TC0-Ovflw reti ; SPI STC reti ; UART RX reti ; UART UDRE reti ; UART TX reti ; Ana-Comp ; ; TC1-Overflow am PWM-Zyklusende ; Tc1Ovflw: in rSrg,SREG ; Sichern von SREG sbiw rCcL,1 ; Erniedrige Zykluszähler brne Tc1OvflwR ; bei <>0 fertig ldi rCcH,HIGH(CycLen) ; Setze Zyklenzähler ldi rCcL,LOW(CycLen) sbis ACSR,ACO ; prüfe Analogcomp-Ausgang eor rTmp,rBlc ; U zu hoch, lösche letztes Bit lsr rBlc ; Aktives Bit eins rechts brcc Tc1OvflwA ; noch nicht fertig mov rRes,rTmp ; Kopiere das Ergebnis sbr rFlg,0x01 ; Setze Fertig-Flag ldi rCcH,HIGH(CycLen0) ; Setze Dauer vor Messbeginn ldi rCcL,LOW(CycLen0) clr rTmp ; Leeres Ergebnis ldi rBlc,0x80 ; Setze Bitzähler Tc1OvflwA: or rTmp,rBlc ; setze nächstes Bit clr rimp ; Setze TC1-PWM-Zyklus out OCR1AH,rimp out OCR1AL,rTmp Tc1OvflwR: ; Rückkehr vom Interupt out SREG,rSrg ; SREG wieder herstellen reti ; ; Hauptprogramm-Schleife ; main: ldi rmp,HIGH(RAMEND) ; Definiere Stack out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rCcH,HIGH(CycLen0) ; Setze Dauer vor Messbeginn ldi rCcL,LOW(CycLen0) ldi rBlc,0x80 ; Setze Bitzähler mov rTmp,rBlc ; Leeres Ergebnis ldi rmp,0xFF ; Port C auf Output, treibt die LEDs out DDRC,rmp ; alle Datenrichtungsregister = 1 sbi DDRD,PD5 ; PWM-Ausgang OC1A auf Ausgang setzen ldi rmp,0b00100000 ; Sleep-mode idle einstellen out MCUCR,rmp ldi rmp,0b10000001 ; TC1 auf PWM8 non-invertiert out TCCR1A,rmp ; in TC1-Kontrollregister A ldi rmp,0b00000001 ; TC1-clock=System clock out TCCR1B,rmp ; in TC1-Kontrollregister B clr rmp ; Pulsweite Rechtecksignal einstellen out OCR1AH,rmp ; erst das HIGH Byte! ldi rmp,0x80 ; dann das LOW Byte! out OCR1AL,rmp ldi rmp,0b10000000 ; TC1 Overflow Int einschalten out TIMSK,rmp ; in Timer Int Mask Register sei ; Reaktion auf Interrupts durch CPU ermöglichen ; ; Hauptprogramm-Loop ; main1: sleep ; CPU schlafen legen nop sbrs rFlg,0 ; Ready flag Bit 0 abfragen rjmp main1 ; Nicht ready, schlaf weiter cbr rFlg,0x01 ; Setze Bit 0 zurück mov rmp,rRes ; Kopiere Ergebnis in Universalregister com rmp ; Invertiere alle bits (Lampe invers!) out PortC,rmp ; an LED Port ausgeben rjmp main1 ; fertig, schlafen legen ; ; Ende des Programms ; http://www.avr-asm-tutorial.net/avr_de/quellen/adc8.asm1/20/2009 7:43:10 PM
R/2R-Netzwerk als DAC für einen AVR
Pfad: Home => AVR-Überblick => R/2R-DAC
Tutorial zum Erlernen der AVR Assembler-Sprache von
AVR-EinchipProzessoren AT90Sxxxx von ATMEL anhand praktischer Beispiele.
Einfacher 8-BitDigital-zu-AnalogWandler mit einem R/2R-Netzwerk
Zweck Die Umwandlung von digitalen Werten in eine Analogspannung kann durch integrierte Schaltungen bewerkstelligt werden. Eine billigere und weniger anspruchsvolle Lösung ist ein selbstgebautes R/2R-Widerstandsnetzwerk, gefolgt von einem Operationsverstärker. Ein R/2R-Netzwerk ist wie im Bild gezeigt aus Widerständen aufgebaut. Die einzelnen Eingangsbits liegen entweder auf Null Volt oder auf der Betriebsspannung und speisen über doppelt so große Widerstände ein wie der vertikale Teil des Netzwerks. Jedes Bit trägt so seinen spezifischen Teil zur resultierenden Ausgangsspannung bei. Das funktioniert wirklich, und ziemlich gut! Kommerzielle Digital-Analog-Wandler haben solche R/2RNetzwerke im IC integriert. Der Ausgang eines AVR liefert nicht sehr viel Strom an seinen Portausgängen, wenn die Spannungen in der Nähe der Versorgungsspannungen bleiben sollen. Daher sollten die Widerstände des R/2R-Netzwerks größer als einige 10 Kiloohm sein. Um das Netzwerk möglichst gering zu belasten entkoppelt ein Operationsverstärker das Netzwerk vom weiteren Verbraucher. Die Widerstandswerte sollten so genau eingehalten werden wie es vom gesamten Netzwerk erwartet wird. Abweichungen von Widerstandswerten sind besonders bei den höherwertigen Bits relevant. Die folgende Tabelle zeigt einige Beispiele für die schrittweise Spannungssteigerung eines R/2R-Netzwerks mit einer 51k/100k-Kombination. (Die Berechnungen wurden mit einem FreePascal-Programm durchgeführt, der freie Quellcode kann hier gedowngeloaded) werden.
Man beachte den Sprung, wenn Bit 7 High wird! Die Spannung springt dann um mehr als zwei Digits. Das ist für ein 8Bit-Netzwerk zu groß, aber akzeptabel für ein 4-Bit-Netzwerk.
Benötigte Hardware Die Hardware ist einfach zu bauen, es ist ein wahres Widerstands-Grab.
Der CA3140 ist ein Operationsverstärker mit einer FET-Eingangsstufe. Er arbeitet auch bei Eingangsspannungen in der Nähe der negativen Versorgungsspannung. Man kann auch einen 741 verwenden, aber das hat Konsequenzen (siehe unten). Die Betriebsspannung von 5 Volt wird hier über den zehnpoligen Steckverbinder bezogen. Dieser passt direkt zu einem STK200- oder STK500-Entwicklungsboard. Es ist auch möglich, die Betriebsspannung des Operationsverstärkers aus einer externen Spannungsquelle zu beziehen. Das hat einige Vorteile, ist aber nicht zwingend. Anstelle der Parallelschaltung zweier gleich großer Widerstände kann man natürlich auch ähnliche Paare verwenden, aber das verringert die Genauigkeit bei einem 8-Bit-Netzwerk immens (siehe oben). Zum Anfang dieser Seite
Anwendung des R/2R-Netzwerks Einen Sägezahn erzeugen Das folgende Programm erzeugt eine Sägezahnspannung am R/2R-Netzwerk-Ausgang. Den Quellcode gibt es zum Download here. ; ************************************************************* ; * R/2R-Netzwerk erzeugt eine Saegezahnspannung ueber Port D * ; * (C)2005 by info!at!avr-asm-tutorial.net * ; ************************************************************* ; .INCLUDE "8515def.inc" ; ; Register Definitionen ; .DEF rmp = R16 ; Multipurpose Register ; ldi rmp,0xFF; Alle Pins von Port D als Ausgang out DDRD,rmp ; in Datenrichtungsregister sawtooth: out PORTD,rmp ; Inhalt von rmp an Port D ausgeben inc rmp ; erhöhen rjmp sawtooth ; und weiter fuer immer
Das Ergebnis ist etwas enttäuschend. Sieht nicht wie ein Sägezahn aus, eher wie eine Holzsäge, mit der Stahl gesägt worden ist. Der Grund dafür liegt nicht beim R/2R-Netzwerk sondern beim Operationsverstärker. Er arbeitet nicht so ganz gut in der Nähe der positiven Betriebsspannung.
Die maximale Ausgangsspannung des R/2R-Netzwerks muss also auf etwa 2.5 Volt begrenzt werden. Das wird per Software erledigt (Quellcode steht unter diesem Link zum Download). ; ***************************************************** ; * R/2R-Netzwerk als Saegezahn ueber Port D * ; * (C)2005 by info!at!avr-asm-tutorial.net * ; ***************************************************** ; .INCLUDE "8515def.inc" ; ; Register Definitionen ; .DEF rmp = R16 ; Multipurpose Register ; ldi rmp,0xFF; Alle Pins von Port D auf Ausgang out DDRD,rmp sawtooth: out PORTD,rmp ; Inhalt des Registers an Port ausgeben inc rmp ; um Eins erhoehen andi rmp,0x7F ; Bit 7 auf Null setzen rjmp sawtooth ; und so weiter Das sieht etwas besser aus. Man beachten, dass wir jetzt Bit 7 des Ports eigentlich nicht mehr benötigen, er kann fest auf Null gezogen werden, weil er sowieso Null ist.
Hier das Ergebnis, wenn der Operationsverstärker CA3140 mit einem billigeren 741 ersetzt wird. Der 741 arbeitet weder in der Nähe der negativen noch in der Nähe der positiven Betriebsspannung. Der Spannungsbereich des R/2R-Netzwerks müsste entweder weiter eingeschränkt werden (auf ca. 2 bis 4 Volt) oder es muss eine symmetrische Versorgung des Opamp her.
Zum Anfang dieser Seite
Eine Dreieckspannung Eine Dreieckspannung ist ähnlich einfach zu erzeugen: nur hoch und runter zählen. Der Quellcode kann wieder unter diesem Link gedowngeloaded werden. Die Software gestattet die Einstellung der Frequenz durch Änderung der Konstanten "delay" und die maximale Amplitude durch "maxAmp". ; *********************************************************** ; * R/2R-Netzwerk produziert eine Dreieckspannung an Port D * ; * (C)2005 by info!at!avr-asm-tutorial.net * ; *********************************************************** ; .INCLUDE "8515def.inc" ; ; Register Definitionen ; .DEF rmp = R16 ; Multipurpose Register .DEF rdl = R17 ; Verzoegerungszaehler ; ; Konstanten ; .EQU maxAmp = 127 ; Maximum Amplitude .EQU delay = 1 ; Verzoegerung, hoehere Werte machen niedrigere Frequenz ; ldi rmp,0xFF; Alle Port-D-Pins als Ausgang out DDRD,rmp triangle: clr rmp ; bei Null anfangen loopup: out PORTD,rmp ; an Port ausgeben ldi rdl,delay ; Verzoegerung einstellen delayup: dec rdl ; Zaehler herunterzaehlen brne delayup ; weiter mit zaehlen inc rmp ; naechstgroesserer Wert cpi rmp,maxAmp ; mit maximaler Amplitude vergleichen brcs loopup ; wenn noch nicht erreicht, weiter hoch loopdwn: out PORTD,rmp ; Ausgabe rueckwaerts ldi rdl,delay ; wieder verzoegern delaydwn: dec rdl ; herunterzaehlen brne delaydwn ; weiter verzoegern dec rmp ; naechstniedrigeren Wert einstellen brne loopdwn ; wenn noch nicht Null, dann weiter rjmp triangle ; und wieder von vorne fuer immer
Links wurde die maximale Amplitude auf 2,5 V begrenzt, rechts sind die vollen 5 Volt ausgesteuert.
Zum Anfang dieser Seite
Einen Sinus erzeugen Mindestens ein Sinus muss jetzt her. Wer denkt, ich schreibe jetzt in Assembler ein Programm zur Sinusberechnung, den muss ich enttäuschen. Ich mache das auf dem PC in einem kleinen Programm in Free-Pascal (Download hier) und importiere die resultierende Tabelle (Download hier) in mein Programm (Download hier). ; ********************************************************** ; * Produziert einen Sinus an einem R/2R-Netzwerk an PORTD * ; * (C)2005 by avr-asm-tutorial.net * ; ********************************************************** ; .INCLUDE "8515def.inc" ; ; Register Definition ; .DEF rmp = R16 ; Multipurpose Register ; ; Beginn des Programms ; ldi rmp,0xFF ; Alle Pins von Port D sind Ausgang out DDRD,rmp ldi ZH,HIGH(2*SineTable) ; Z auf Tabelle im Flash ldi ZL,LOW(2*SineTable) clr rmp loop1: nop nop nop loop2: lpm ; Lesen aus der Tabelle out PORTD,R0 ; Tabellenwert an Port D adiw ZL,1 ; naechster Tabellenwert dec rmp ; Ende der Tabelle erreicht? brne loop1 ; nein ldi ZH,HIGH(2*SineTable) ; Z wieder auf Tabellenanfang ldi ZL,LOW(2*SineTable) rjmp loop2 ; weiter so ; ; Ende Instruktionen ; ; Include der Sinustabelle ; .INCLUDE "sine8_25.txt" ; ; Ende Programm ; ; ; Sinustabelle fuer 8 Bits D/A ; Tabellenlaenge = 256 Werte ; VCC=5.000V, uLow=0.000V, uHigh=2.500V ; (mit sinewave.pas erzeugt) ; Sinetable: .DB 64,65,67,68,70,72,73,75 .DB 76,78,79,81,82,84,85,87 .DB 88,90,91,92,94,95,97,98 .DB 99,100,102,103,104,105,107,108 .DB 109,110,111,112,113,114,115,116 .DB 117,118,118,119,120,121,121,122 .DB 123,123,124,124,125,125,126,126 .DB 126,127,127,127,127,127,127,127 .DB 128,127,127,127,127,127,127,127 .DB 126,126,126,125,125,124,124,123 .DB 123,122,121,121,120,119,118,118 .DB 117,116,115,114,113,112,111,110 .DB 109,108,107,105,104,103,102,100 .DB 99,98,97,95,94,92,91,90 .DB 88,87,85,84,82,81,79,78 .DB 76,75,73,72,70,68,67,65 .DB 64,62,61,59,58,56,54,53 .DB 51,50,48,47,45,44,42,41 .DB 39,38,36,35,34,32,31,30 .DB 28,27,26,25,23,22,21,20 .DB 19,18,17,15,14,13,13,12 .DB 11,10,9,8,8,7,6,5 .DB 5,4,4,3,3,2,2,2 .DB 1,1,1,0,0,0,0,0 .DB 0,0,0,0,0,0,1,1 .DB 1,2,2,2,3,3,4,4 .DB 5,5,6,7,8,8,9,10 .DB 11,12,13,13,14,15,17,18 .DB 19,20,21,22,23,25,26,27 .DB 28,30,31,32,34,35,36,38 .DB 39,41,42,44,45,47,48,50 .DB 51,53,54,56,58,59,61,62
Das war es schon. Macht einen schönen Sinus. Man glaubt kaum, dass hier eine digitale Maschinerie am Werk ist und kein sauberer LC-Oszillator. Unglücklicherweise kann man mit dieser Methode keinen Sinus mit mehr als 1800 Hz machen, weil vier MHz Takt durch 256 schon nur noch 15.625 ergeben. Und zum Tabellelesen auch der eine oder andere Takt gebraucht wird.
Zum Anfang dieser Seite
Musiknoten mit dem R/2R-Netzwerk spielen Das folgende Programm benutzt das R/2R-Netzwerk zum Spielen von Musiknoten mit den Tasten des STK200-Boards. Es arbeitet ohne Änderung mit einem AT90S8515 mit 4 MHz Takt, den acht Tastenschaltern am Port D und dem R/2RNetzwerk an Port B angeschlossen (Download hier). ; ****************************************************************** ; * Musik mit dem STK200 und einem R/2R-Netzwerk * ; * PortD hat acht Tasten (active low), PortB generiert die Ausgabe* ; * fuer das R/2R-Netzwerk, spielt Noten wenn die Tasten betaetigt * ; * werden, fuer ATMEL AT90S8515 bei 4 MHz * ; * (C)2005 by info!at!avr-asm-tutorial.net ; ****************************************************************** ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Konstanten ; .EQU clock = 4000000 ; Processortakt .EQU cNSine = 32 ; Tabellelaenge Sinustabelle ; .DEF rLen = R1 ; Register fuer Dauer der Ausgabe .DEF rCnt = R2 ; Zaehler fuer die Verzoegerung .DEF rmp = R16 ; Multipurpose Register .DEF rTab = R17 ; Zaehler fuer Tabellenlaenge ; ldi rmp,0xFF ; Alle Bits von Port B Ausgang => R/2R Netzwerk out DDRB,rmp ; an Datenrichtungsregister wtloop: in rmp,PIND ; lese die Tasten cpi rmp,0xFF ; alle Tasten inaktiv? breq wtloop ; je, weiter warten bis aktiv ldi ZH,HIGH(2*MusicTable) ; Z auf Tonhoehentabelle setzen ldi ZL,LOW(2*MusicTable) tabloop: rol rmp ; Rotiere naechstes Bit in Carry brcc tabfound ; gedrueckte Taste gefunden adiw ZL,1 ; Z auf nachsten Tabellenwert rjmp tabloop ; Pruefe naechstes Bit tabfound: lpm ; Lese Tonhoehenwert aus Tabelle in R0 mov rlen,R0 ; Kopiere in delay, R0 wird anderweitig benutzt ; ; Spiele einen Ton, bis die Tasten alle inaktiv sind ; startsine: ldi ZH,HIGH(2*SineTable) ; Z auf die Sinustabelle setzen ldi ZL,LOW(2*SineTable) ldi rTab,cNSine ; Laenge der Sinustabelle ; ; Der folgende Code ist timing-maessig optimiert, damit alle ; Teilschritte gleich lang dauern, die benoetigten Taktzyklen ; sind angegeben, wenn die Instruktion abgearbeitet ist, ; Taktzyklen waehrend Tabellenlesen / Taktzyklen am Tabellenende ; loopsine: in rmp,PIND ; 1/- Pruefe ob Tasten noch aktiv ist cpi rmp,0xFF ; 2/breq wtloop ; 3/- Taste nicht mehr aktiv, gehe zurueck nop ; 4/- verzoegern fuer Synchronisation loopnext: lpm ; 7/3 Lese Sinustabelle in R0 out PORTB,R0 ; 8/4 Kopiere zum R/2R-Netzwerk mov rCnt,rLen ; 9/5 Setze Verzoegerungszaehler ; Verzoegerungsschleife, braucht 3*rLen-1 Taktzyklen loopdelay: dec rCnt ; (1) naechster Zaehlerwert brne loopdelay ; (2/1) noch nicht Null ; 3*rLen+8/3*rLen+4 am Ende der Verzoegerung adiw ZL,1 ; 3*rLen+10/3*rLen+6 naechster Tabellenwert dec rTab ; 3*rLen+11/3*rLen+7 Anzahl Tabellenwerte brne loopsine ; 3*rLen+13/3*rLen+8 naechster Tabellenwert ldi ZH,HIGH(2*SineTable) ; -/3*rLen+9 Neuanfang Tabelle ldi ZL,LOW(2*SineTable) ; -/3*rLen+10 ldi rTab,cNSine ; -/3*rLen+11 Laenge der Sinustabelle rjmp loopnext ; -/3*rLen+13 Neustart (ohne Tasten!) ; ; Tabelle fuer Verzoegerung zur Tonerzeugung der 8 Frequenzen ; ; Frequenz = clock / Tabellenlaenge / ( 3 * rLen + 13 ) ; rLen = ( clock /Tabellenlaenge / Frequenz - 13 ) / 3 ; MusicTable: ; f=261.6 293.7 329.6 349.2 392.0 440.0 493.9 523.2 (Sollwert) .DB 155, 138, 122, 115, 102, 90, 80, 75 ; f=261.5 292.7 329.8 349.2 391.9 441.7 494.1 525.2 (Istwert) ; Unterschiede zwischen Soll und Ist wegen Rundung und Aufloesung) ; ; Sinustabelle fuer 8 Bits D/A ; Tabellenlaenge = 32 Werte ; VCC=5.000V, uLow=0.000V, uHigh=2.500V ; (mit sinewave.pas erzeugt) ; Sinetable: .DB 64,76,88,99,109,117,123,126 .DB 128,126,123,117,109,99,88,76 .DB 64,51,39,28,19,11,5,1 .DB 0,1,5,11,19,28,39,51 ; ; Ende des Programms ;
; ************************************************************* ; * R/2R-Netzwerk erzeugt eine Saegezahnspannung ueber Port D * ; * (C)2005 by [email protected] * ; ************************************************************* ; .INCLUDE "8515def.inc" ; ; Register Definitionen ; .DEF rmp = R16 ; Multipurpose Register ; ldi rmp,0xFF; Alle Pins von Port D als Ausgang out DDRD,rmp ; in Datenrichtungsregister sawtooth: out PORTD,rmp ; Inhalt von rmp an Port D ausgeben inc rmp ; erhöhen rjmp sawtooth ; und weiter fuer immer
; ***************************************************** ; * R/2R-Netzwerk als Saegezahn ueber Port D * ; * (C)2005 by [email protected] * ; ***************************************************** ; .INCLUDE "8515def.inc" ; ; Register Definitionen ; .DEF rmp = R16 ; Multipurpose Register ; ldi rmp,0xFF; Alle Pins von Port D auf Ausgang out DDRD,rmp sawtooth: out PORTD,rmp ; Inhalt des Registers an Port ausgeben inc rmp ; um Eins erhoehen andi rmp,0x7F ; Bit 7 auf Null setzen rjmp sawtooth ; und so weiter
; *********************************************************** ; * R/2R-Netzwerk produziert eine Dreieckspannung an Port D * ; * (C)2005 by [email protected] * ; *********************************************************** ; .INCLUDE "8515def.inc" ; ; Register Definitionen ; .DEF rmp = R16 ; Multipurpose Register .DEF rdl = R17 ; Verzoegerungszaehler ; ; Konstanten ; .EQU maxAmp = 127 ; Maximum Amplitude .EQU delay = 1 ; Verzoegerung, hoehere Werte machen niedrigere Frequenz ; ldi rmp,0xFF; Alle Port-D-Pins als Ausgang out DDRD,rmp triangle: clr rmp ; bei Null anfangen loopup: out PORTD,rmp ; an Port ausgeben ldi rdl,delay ; Verzoegerung einstellen delayup: dec rdl ; Zaehler herunterzaehlen brne delayup ; weiter mit zaehlen inc rmp ; naechstgroesserer Wert cpi rmp,maxAmp ; mit maximaler Amplitude vergleichen brcs loopup ; wenn noch nicht erreicht, weiter hoch loopdwn: out PORTD,rmp ; Ausgabe rueckwaerts ldi rdl,delay ; wieder verzoegern delaydwn: dec rdl ; herunterzaehlen brne delaydwn ; weiter verzoegern dec rmp ; naechstniedrigeren Wert einstellen brne loopdwn ; wenn noch nicht Null, dann weiter rjmp triangle ; und wieder von vorne fuer immer
; ********************************************************** ; * Produziert einen Sinus an einem R/2R-Netzwerk an PORTD * ; * (C)2005 by avr-asm-tutorial.net * ; ********************************************************** ; .INCLUDE "8515def.inc" ; ; Register Definition ; .DEF rmp = R16 ; Multipurpose Register ; ; Beginn des Programms ; ldi rmp,0xFF ; Alle Pins von Port D sind Ausgang out DDRD,rmp ldi ZH,HIGH(2*SineTable) ; Z auf Tabelle im Flash ldi ZL,LOW(2*SineTable) clr rmp loop1: nop nop nop loop2: lpm ; Lesen aus der Tabelle out PORTD,R0 ; Tabellenwert an Port D adiw ZL,1 ; naechster Tabellenwert dec rmp ; Ende der Tabelle erreicht? brne loop1 ; nein ldi ZH,HIGH(2*SineTable) ; Z wieder auf Tabellenanfang ldi ZL,LOW(2*SineTable) rjmp loop2 ; weiter so ; ; Ende Instruktionen ; ; Include der Sinustabelle ; .INCLUDE "sine8_25.txt" ; ; Ende Programm ;
; ****************************************************************** ; * Musik mit dem STK200 und einem R/2R-Netzwerk * ; * PortD hat acht Tasten (active low), PortB generiert die Ausgabe* ; * fuer das R/2R-Netzwerk, spielt Noten wenn die Tasten betaetigt * ; * werden, fuer ATMEL AT90S8515 bei 4 MHz * ; * (C)2005 by [email protected] * ; ****************************************************************** ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Konstanten ; .EQU clock = 4000000 ; Processortakt .EQU cNSine = 32 ; Tabellelaenge Sinustabelle ; .DEF rLen = R1 ; Register fuer Dauer der Ausgabe .DEF rCnt = R2 ; Zaehler fuer die Verzoegerung .DEF rmp = R16 ; Multipurpose Register .DEF rTab = R17 ; Zaehler fuer Tabellenlaenge ; ldi rmp,0xFF ; Alle Bits von Port B Ausgang => R/2R Netzwerk out DDRB,rmp ; an Datenrichtungsregister wtloop: in rmp,PIND ; lese die Tasten cpi rmp,0xFF ; alle Tasten inaktiv? breq wtloop ; je, weiter warten bis aktiv ldi ZH,HIGH(2*MusicTable) ; Z auf Tonhoehentabelle setzen ldi ZL,LOW(2*MusicTable) tabloop: rol rmp ; Rotiere naechstes Bit in Carry brcc tabfound ; gedrueckte Taste gefunden adiw ZL,1 ; Z auf nachsten Tabellenwert rjmp tabloop ; Pruefe naechstes Bit tabfound: lpm ; Lese Tonhoehenwert aus Tabelle in R0 mov rlen,R0 ; Kopiere in delay, R0 wird anderweitig benutzt ; ; Spiele einen Ton, bis die Tasten alle inaktiv sind ; startsine: ldi ZH,HIGH(2*SineTable) ; Z auf die Sinustabelle setzen ldi ZL,LOW(2*SineTable) ldi rTab,cNSine ; Laenge der Sinustabelle ; ; Der folgende Code ist timing-maessig optimiert, damit alle ; Teilschritte gleich lang dauern, die benoetigten Taktzyklen ; sind angegeben, wenn die Instruktion abgearbeitet ist, ; Taktzyklen waehrend Tabellenlesen / Taktzyklen am Tabellenende ; loopsine: in rmp,PIND ; 1/- Pruefe ob Tasten noch aktiv ist cpi rmp,0xFF ; 2/breq wtloop ; 3/- Taste nicht mehr aktiv, gehe zurueck nop ; 4/- verzoegern fuer Synchronisation loopnext: lpm ; 7/3 Lese Sinustabelle in R0 out PORTB,R0 ; 8/4 Kopiere zum R/2R-Netzwerk mov rCnt,rLen ; 9/5 Setze Verzoegerungszaehler ; Verzoegerungsschleife, braucht 3*rLen-1 Taktzyklen loopdelay: dec rCnt ; (1) naechster Zaehlerwert brne loopdelay ; (2/1) noch nicht Null ; 3*rLen+8/3*rLen+4 am Ende der Verzoegerung adiw ZL,1 ; 3*rLen+10/3*rLen+6 naechster Tabellenwert dec rTab ; 3*rLen+11/3*rLen+7 Anzahl Tabellenwerte brne loopsine ; 3*rLen+13/3*rLen+8 naechster Tabellenwert ldi ZH,HIGH(2*SineTable) ; -/3*rLen+9 Neuanfang Tabelle ldi ZL,LOW(2*SineTable) ; -/3*rLen+10 ldi rTab,cNSine ; -/3*rLen+11 Laenge der Sinustabelle rjmp loopnext ; -/3*rLen+13 Neustart (ohne Tasten!) ; ; Tabelle fuer Verzoegerung zur Tonerzeugung der 8 Frequenzen ; ; Frequenz = clock / Tabellenlaenge / ( 3 * rLen + 13 ) ; rLen = ( clock /Tabellenlaenge / Frequenz - 13 ) / 3 ; MusicTable: ; f=261.6 293.7 329.6 349.2 392.0 440.0 493.9 523.2 (Sollwert) .DB 155, 138, 122, 115, 102, 90, 80, 75 ; f=261.5 292.7 329.8 349.2 391.9 441.7 494.1 525.2 (Istwert) ; Unterschiede zwischen Soll und Ist wegen Rundung und Aufloesung) ; ; Sinustabelle fuer 8 Bits D/A ; Tabellenlaenge = 32 Werte ; VCC=5.000V, uLow=0.000V, uHigh=2.500V ; (mit sinewave.pas erzeugt) ; Sinetable: .DB 64,76,88,99,109,117,123,126 .DB 128,126,123,117,109,99,88,76 .DB 64,51,39,28,19,11,5,1 .DB 0,1,5,11,19,28,39,51 ; ; Ende des Programms ; http://www.avr-asm-tutorial.net/avr_de/quellen/musik.asm1/20/2009 7:43:35 PM
AVR-Hardware-Testroutinen
Pfad: Home => AVR-Übersicht => Software
Tutorial für das Erlernen der Assemblersprache von
AVR-Einchip-Prozessoren AT90Sxxxx von ATMEL anhand geeigneter praktischer Beispiele.
Spezielles Software-KnowHow
(Die Links auf die *.asm-Dateien zum Herunterladen mit gedrückter Shift-Taste anklicken.) HTML- ASMFormat Format
Erläuterung zum Inhalt
LPM
Liest die Tasten und wandelt die Nummer der Taste über eine Liste im Programmspeicher in die Anzahl an LEDs um und beleuchtet diese. (Taste 0 = 8 Stück). Ein ziemlich unnützes Programm, aber es demonstriert neben dem LPMBefehl auch das Rollen und Springen.
JUMP
JUMP
Sprünge aus dem Stack heraus vornehmen. Die Unterprogrammaufrufe erfolgen nicht über den RCALL-Befehl, sondern über den Stack. D.h. zuerst wird die Rücksprungadresse auf dem Stack abgelegt, dann die Adresse des Unterprogrammes, das aufgerufen werden soll. Der Sprung und der Rücksprung erfolgen dann über den RET- Befehl. Etwas trickreiche Programmierkunst!
MAC1
MAC1
Makros verwenden. Sinnloses Beispiel zum Erlernen, wie ein Makro in den Assembler-Quelltext gelangt und was beim Assemblieren passiert.
MAC2
MAC2
Makros verwenden. Sprungziele in Makros nach innerhalb und ausserhalb des Makros anwenden. Sinnloses Testprogramm.
MAC3
MAC3
Makros verwenden. Makro mit Übergabe eines Parameters an das Makro. Sinnloses Testprogramm zum Lernen.
Pfad: Home => AVR-Übersicht => Software => LPM-Befehl
; Testet den LPM-Befehl zum Auslesen von Bytes aus dem Programmspeicher ; Liest die Tasten und wandelt die Nummer der Taste ; über eine Liste im Programmspeicher in die Anzahl ; an LEDs um und beleuchtet diese. (Taste 0 = 8 Stück) ; Ein ziemlich unnützes Programm, aber es demonstriert ; neben dem LPM-Befehl auch das Rollen und Springen. ; .NOLIST .INCLUDE "8515def.inc" .LIST ;
; Register ; .DEF erg=R0 ; Der LPM-Befehl wirkt ausschliesslich auf R0 .DEF mpr=R16 ; Multifunktionsregister ; ; Verwendet werden auch die Register ZL (R30) und ZH (R31). ; Dies wird im 8515def.inc definiert, daher braucht es hier nicht. ;
; Reset-/Interrupt-Vektor ; RJMP main ; main: OUT DEC OUT OUT
CLR mpr ; Lade 0 in Register mpr DDRD,mpr ; alle D-Ports sind Eingang Schalter mpr ; Lade FF in Register B DDRB,mpr ; Alle B-Ports sind Ausgang LEDs PORTD,mpr ; Alle Pullups auf D einschalten
; Testet den LPM-Befehl zum Auslesen von Bytes ; aus dem Programmspeicher ; ; Liest die Tasten und wandelt die Nummer der Taste ; über eine Liste im Programmspeicher in die Anzahl ; an LEDs um und beleuchtet diese. (Taste 0 = 8 Stück) ; Ein ziemlich unnützes Programm, aber es demonstriert ; neben dem LPM-Befehl auch das Rollen und Springen. ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Register ; .DEF erg=R0 ; Der LPM-Befehl wirkt ausschliesslich auf R0 .DEF mpr=R16 ; Multifunktionsregister ; ; Verwendet werden auch die Register ZL (R30) und ZH (R31). ; Dies wird im 8515def.inc definiert, daher braucht es hier nicht. ; ; Reset-/Interrupt-Vektor ; rjmp main ; main: clr mpr ; Lade 0 in Register mpr out DDRD,mpr ; alle D-Ports sind Eingang Schalter dec mpr ; Lade FF in Register B out DDRB,mpr ; Alle B-Ports sind Ausgang LEDs out PORTD,mpr ; Alle Pullups auf D einschalten loop: ldi ZL,LOW(liste2) ; Registerpaar Z zeigt auf das ldi ZH,HIGH(liste2) ; erste Byte (FF) in der Liste in mpr,PIND ; Lese Schalter aus cpi mpr,0xFF ; Alle Schalter aus? Alle LEDs aus! breq lesen incp: inc ZL ; Zeige mit LSB auf nächstes Byte der Liste brne rolle ; Kein Überlauf des MSB, weiter inc ZH ; MSB übergelaufen, eins weiter rolle: ror mpr ; Schiebe Bit 0 in das Carry-Flag brlo incp ; Carry=1, nicht gedrückt, weiter in der Liste lesen: lpm ; Lese das Byte, auf das Zeiger Z zeigt in Register R0 out PORTB,erg ; Gib Byte auf LEDs aus rjmp loop ; Im Kreis drehen ; ; Die Liste mit den Lampenkombinationen, jedes Byte entspricht einem ; Schalter. ; Die Werte müssen jeweils wortweise angegeben werden, weil bei der ; Verwendung von .DB xx immer automatisch ein Null-Byte mitgespeichert ; würde! Es geht aber auch .DB xx,yy! Die werden zu einem Wort ver; knüpft. Dasselbe gilt bei Texten mit .DB "abcd...": Geradzahlige An; zahlen gehen, ungeradzahlige kriegen ein Nullbyte zusätzlich! ; liste: .DW 0xFEFF ; 1 Lampe, 0 Lampen (0 ist hinten, 1 vorne!) .DW 0xF8FC ; 3 Lampen, 2 Lampen .DW 0xE0F0 ; 5 Lampen, 4 Lampen .DW 0x80C0 ; 7 Lampen, 6 Lampen ; .EQU liste2=liste*2 ; Wird gebraucht, weil die Adresse ; wortweise organisiert ist, die Werte aber ; byteweise gelesen werden sollen ; http://www.avr-asm-tutorial.net/avr_de/quellen/testlpm.asm1/20/2009 7:43:40 PM
AVR-Hardware-Tutorium, RAM-JUMP
Pfad: Home => AVR-Übersicht => Software => JUMP-Befehl
; Testet Unterprogrammaufrufe über den Stack ; ; Die Unterprogrammaufrufe erfolgen nicht über den RCALL; Befehl, sondern über den Stack. D.h. zuerst wird die ; Rücksprungadresse auf dem Stack abgelegt, dann die Adresse ; des Unterprogrammes, das aufgerufen werden soll. Der ; Sprung und der Rücksprung erfolgen dann über den RET; Befehl. ; Diese Adressierungsart ist für Unterprogrammaufrufe aus ; Tabellen günstig, weil man die Adressen dann aus einer ; Tabellenspalte auslesen kann. ; .NOLIST .INCLUDE "8515def.inc" .LIST ; .DEF mpr=R16 ; Wie immer ein allgemeines Arbeitsregister ;
; Reset-/Interrupt-Vektortabelle ; RJMP main ; main: OUT LDI OUT
LDI mpr,LOW(retret) ; Lege Rücksprungadresse auf stack PUSH mpr LDI mpr,HIGH(retret) PUSH mpr LDI mpr,LOW(testup) ; Lege Sprungadresse auf stack PUSH mpr LDI mpr,HIGH(testup) PUSH mpr RET ; Springe zum Unterprogramm ;
; Das anzuspringende Rücksprungprogramm macht alle Lampen an ; retret: LDI mpr,0x00 ; Mache alle Lampen an, wenn erfolgreich OUT PORTB,mpr loop: RJMP loop ; Anhalten ; ;
; Testet Unterprogrammaufrufe über den Stack ; ; Die Unterprogrammaufrufe erfolgen nicht über den RCALL; Befehl, sondern über den Stack. D.h. zuerst wird die ; Rücksprungadresse auf dem Stack abgelegt, dann die Adresse ; des Unterprogrammes, das aufgerufen werden soll. Der ; Sprung und der Rücksprung erfolgen dann über den RET; Befehl. ; Diese Adressierungsart ist für Unterprogrammaufrufe aus ; Tabellen günstig, weil man die Adressen dann aus einer ; Tabellenspalte auslesen kann. ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; .DEF mpr=R16 ; Wie immer ein allgemeines Arbeitsregister ; ; Reset-/Interrupt-Vektortabelle ; rjmp main ; main: ldi mpr,HIGH(RAMEND) ; Stack einrichten out SPH,mpr ldi mpr,LOW(RAMEND) out SPL,mpr ldi mpr,LOW(retret) ; Lege Rücksprungadresse auf stack push mpr ldi mpr,HIGH(retret) push mpr ldi mpr,LOW(testup) ; Lege Sprungadresse auf stack push mpr ldi mpr,HIGH(testup) push mpr ret ; Springe zum Unterprogramm ; ; Das anzuspringende Rücksprungprogramm macht alle Lampen an ; retret: ldi mpr,0x00 ; Mache alle Lampen an, wenn erfolgreich out PORTB,mpr loop: rjmp loop ; Anhalten ; ; ; Testprogramm, das angesprungen werden soll ; testup: ldi mpr,0xFF ; Alle Lampentreiber auf Ausgabe out DDRB,mpr ret ; Rücksprung an die Aufrufadresse http://www.avr-asm-tutorial.net/avr_de/quellen/testjmp.asm1/20/2009 7:43:43 PM
; ***************************************************** ; * Demonstriert die Verwendung von Makros mit ATMEL * ; * AVR Assembler, nur ein Testprogramm für das * ; * ATMEL STK200 Board, (C) 2000 Gerhard Schmidt * ; * Fehlerberichte an [email protected] * ; ***************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Benutzte Register ; .DEF mpr=R16 ; Vielzweckregister ; ; Das folgende Codestück ist ein Makro. Es kann in ; das Programm aufgenommen werden, so oft man es ; benötigt. Immer wenn die gleiche Sequenz benötigt ; wird, läßt sich ein Makro dafür einsetzen. Anders als ; bei einem Unterprogramm wird der gleiche Code mehrfach ; eingefügt, immer wenn das Makro aufgerufen wird. ; .MACRO TestMacro inc mpr inc mpr inc mpr .ENDMACRO ; ; ; Beginn des Hauptprogrammes ; ldi mpr,0xFF ; PortB (LEDs) ist Ausgang out DDRB,mpr ; an Datenrichtungsregister clr mpr ; Setze Register Null testmacro ; Füge das Makro ein (drei mal INC)(three INCs) testmacro ; Noch einmal (noch drei INC)here (another 3) com mpr ; Invertiere das Ergebnisdisplay it out PORTB,mpr ; und zeige es auf den LEDs anLEDs loop: RJMP loop ; und ab in die Endlosschleife ; ; Nach dem Assemblieren sollte ein 11 Worte langes ; Binärprogramm resultieren, da die zwei Makros sechs ; INC-Befehle in den Code einfügen. ; ; Nach der Ausführung sollten die LEDs PB.1 and PB.2 an, ; alle anderen aus sein (Ergebnis = 6 ist Binär 0000.0110)! ; http://www.avr-asm-tutorial.net/avr_de/quellen/testmac1.asm1/20/2009 7:43:46 PM
; ***************************************************** ; * Sprungziele innerhalb eines Makros: Es geht! * ; * Zeigt die Anwendung von Makros mit dem ATMEL AVR * ; * Assembler, nur ein Testprogramm für das * ; * ATMEL STK200 Board, (C) 2000 Gerhard Schmidt * ; * Fehler bitte an [email protected] * ; ***************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Verwendete Register ; .DEF mpr=R16 ; Vielzweckregister ; ; Der folgende Code ist ein Makro, das einen Sprung ; zu einem Ziel innerhalb und einen Sprung zu einem ; Ziel ausserhalb des Makros enthält. Beide Befehle ; werden vom Assembler korrekt übersetzt. ; .MACRO TestMacro inc mpr ; Erhöhe das Register mpr brne mjmp ; Wenn kein Überlauf, dann überspringe rjmp ovf ; Springe zu Ziel ausserhalb des Makros mjmp: .ENDMACRO ; .LISTMAC ; ; Beginn des Hauptprogrammes ; ldi mpr,0xFF ; PortB (LEDs) ist Ausgang out DDRB,mpr ; an Datenrichtungsregister ; ldi mpr,0xFE ; Setze Register auf 254 testmacro ; Füge das Makro einmal ein (1xINC) testmacro ; Füge es noch einmal ein (+1xINC) ; Da bei der Ausführung bis hier ein Überlauf passiert sein muss, ; wird der nun folgende Code nicht ausgeführt. Wenn er ausgeführt ; würde, würden alle LEDs an sein. ; outp: out PORTB,mpr loop: rjmp loop ; und Ende in einer Schleife ; ; Der Überlauf ist passiertund dieser Code wird ausgeführt: ; ovf: ldi mpr,0xFF ; Alle LEDs ausschalten out PORTB,mpr ; auf Port B rjmp loop ; Springe zur Endlosschleife ; ; Nach der Ausführung sollten alle LEDs PB.0 to PB.7 aus sein. http://www.avr-asm-tutorial.net/avr_de/quellen/testmac2.asm1/20/2009 7:43:48 PM
; ***************************************************** ; * Einen Parameter an ein Makro übergeben: geht! * ; * Zeigt den Gebrauch von Makros beim ATMEL AVR- * ; * Assembler, einfach ein Testprogramm für das * ; * ATMEL STK200 Board, (C) 2000 Gerhard Schmidt * ; * Fehlerberichte an [email protected] * ; ***************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Verwendete Register ; .DEF mpr=R16 ; Vielzweckregister ; ; Das folgende Makro enthält ein Sprungziel innerhalb des ; Makros und erhält den Zählerstand mit dem ersten Parameter ; übergeben (@0). ; .MACRO TestMacro ldi mpr,@0 ; Setze mpr auf den Wert des ersten Parameters inc mpr ; Erhöhe um eins brne mjmp ; Wenn kein Überlauf, überspringe nächsten Befehl rjmp ovf ; Springe, wenn Überlauf erfolgt. mjmp: .ENDMACRO ; .LISTMAC ; ; Beginn des Hauptprogrammes ; ldi mpr,0xFF ; PortB (LEDs) ist Ausgang out DDRB,mpr ; an Datenrichtungsregister ; ldi mpr,0xFE ; Setze Register auf 254 ; Hier kommt das Makro: 0xFF wird übergeben, dann um Eins erhöht. ; Wenn dann Null rauskommt, springt die Ausführung nach ovf. ; testmacro(0xff) ; Füge das Makro mit dem Parameter FF ein ; ; Weil ein Überlauf passiert sein muss bei der letzten Erhöhung ; sollte der folgende Code niemals ausgeführt werden. Wenn er ; doch ausgeführt würde, wären alle LEDs an. ; outp: out PORTB,mpr loop: rjmp loop ; und in eine Endlaosschleife ; ; Der Überlauf sollte passiert sein und dieser Code wird ausgeführt: ; ovf: ldi mpr,0xFF ; Alle LEDs aus out PORTB,mpr ; und an Port B rjmp loop ; Springe zur Endlosschleife ; ; Nach der Ausführung sind alle LEDs PB.0 bis PB.7 aus. http://www.avr-asm-tutorial.net/avr_de/quellen/testmac3.asm1/20/2009 7:43:51 PM
Anschluss einer Tastatur an einen AVR
Pfad: Home => AVR-Übersicht => Keyboard
Anschluss einer 12-er-Tastatur an einen AVR Diese Seite zeigt, wie eine handelsübliche 12-er-Tastatur an einen AVR angeschlossen und per Assembler-Software ausgelesen werden kann. Die Abschnitte: 1. Funktionsweise der Tastatur 2. AVR: I/O-Anschlussmatrix einzeln 3. AVR: Anschluss an einen ADC mit Widerstands-Matrix
1. Funktionsweise der Tastatur 12-er-Tastaturen sind Schalter, die über eine Matrix von Zeilen (Rows) und Spalten (Columns) miteinander verbunden sind. Wird die Taste "1" gedrückt, dann ist die Spalte 1 mit der Zeile 1 verbunden, ist "2" gedrückt, dann Spalte 2 mit Reihe 1, usw..
Um herauszufinden, ob irgendeine der 12 Tasten gedrückt ist, würde es reichen, die drei Spalten mit Null Volt zu verbinden und die vier Zeilen zu verbinden und über einen Pull-Up-Widerstand von z.B. 10 kΩ mit Plus zu verbinden. Der Output hat ohne gedrückte Taste Plus-Potential. Jede gedrückte Taste bewirkt dann, dass der Output auf Null Volt gezogen wird.
Um auch noch fest zu stellen, welche der 12 Tasten gedrückt ist, wären z.B. nacheinander die drei Spaltenanschlüsse auf Null Volt zu bringen (die beiden anderen jeweils auf Plus) und das Ergebnis an den vier Zeilenanschlüssen abzulesen. Ist einer der vier Zeilenanschlüsse auf Null, muss die Maschinerie anhalten und den aktuellen Spaltenanschluss sowie das Ergebnis der Zeilenanchlüsse in den Code einer gedrückten Taste umwandeln. Etwa so: Column
Um eine solche Tastatur mit diskreten Bauteilen auslesbar zu machen, braucht es mindestens: ● ● ●
einen Oszillator mit Schieberegister und Start/Stop zur Erzeugung der Spaltensignale, Feststellung, ob einer der vier Zeilenanschlüsse Null ist, Umkodierer für die Auswertung der sieben Signale.
Oder ein fertiges IC, das das alles macht. Oder eben einen Mikrokontroller. An den Seitenanfang
2. AVR: I/O-Anschlüssmatrix einzeln Eine Tastaturmatrix kann direkt und ohne weitere Bauteile an einen Mikrokontroller angeschaltet werden. Im Beispiel sind dies die unteren sieben I/O-Pins des Ports B. Andere Ports lassen sich ebenso verwenden. Die Ports PB4..PB6 werden als Ausgänge definiert und liefern die Spalten-Nullen. Die Ports PB0..PB3 dienen zum Einlesen der Zeilenergebnisse. Die Pull-Up-Widerstände der Ports PB0.. PB3 werden per Software zuschaltet, externe Widerstände sind unnötig. Das folgende Software-Beispiel zeigt zunächst das Initiieren der Ports. Sie wird nur ein Mal zu Beginn des AVR-Programms ausgeführt.
Init-Routine ; ; Init Keypad-I/O ; .DEF rmp = R16 ; ein Hilfsregister definieren ; Ports definieren .EQU pKeyOut = PORTB ; Ausgabe und Pull-Ups .EQU pKeyInp = PINB ; Tastatur lesen .EQU pKeyDdr = DDRB ; Datenrichtungsregister ; Init-Routine InitKey: ldi rmp,0b01110000 ; Datenrichtungsregister out pKeyDdr,rmp ; des Keyports setzen ldi rmp,0b00001111 ; Pull-Up-Widerstände out pKeyOut,rmp ; an den Eingängen
Tastendruck feststellen Die folgende Routine stellt zunächst fest, ob irgendeine Taste gedrückt ist. Sie wird im Programmverlauf regelmäßig wiederholt, z.B. in einer Verzögerungsschleife oder Timer-gesteuert. ; ; Check any key pressed ; AnyKey: ldi rmp,0b00001111 out pKeyOut,rmp in rmp,pKeyInp ori rmp,0b11110000 cpi rmp,0b11111111 breq NoKey
; ; ; ; ; ;
PB4..PB6=Null, pull-Up-Widerstände an den Eingängen PB0..PB3 Tastaturport lesen alle oberen Bits auf Eins alle Bits = Eins? ja, keine Taste gedrückt
Gedrückte Taste feststellen Jetzt ist der Auslesevorgang dran. Nacheinander werden PB6, PB5 und PB4 Null gesetzt und PB0.. PB3 auf Nullen geprüft. Das Registerpaar Z (ZH:ZL) zeigt dabei auf eine Tabelle mit den Tastencodes. Es zeigt am Ende auf den identifizierten Tastencode, der mit der Instruktion LPM aus dem Flash-Memory in das Register R0 gelesen wird. ; ; Identifiziere gedrueckte Taste ; ReadKey: ldi ZH,HIGH(2*KeyTable) ; Z ist Zeiger auf Tastencode ldi ZL,LOW(2*KeyTable) ; read column 1 ldi rmp,0b00111111 ; PB6 = 0 out pKeyOut,rmp in rmp,pKeyInp ; lese Zeile ori rmp,0b11110000 ; obere Bits maskieren cpi rmp,0b11111111 ; ein Key in dieser Spalte? brne KeyRowFound ; Spalte gefunden adiw ZL,4 ; Spalte nicht gefunden, Z vier Keys weiter ldi rmp,0b01011111 ; PB5 = 0 out pKeyOut,rmp in rmp,pKeyInp ; wieder Zeile lesen ori rmp,0b11110000 ; obere Bits maskieren cpi rmp,0b11111111 ; ein Key in dieser Spalte? brne KeyRowFound ; Spalte gefunden adiw ZL,4 ; Spalte nicht gefunden, Z vier Keys weiter ldi rmp,0b01101111 ; PB4 = 0 out pKeyOut,rmp in rmp,pKeyInp ; letzte Zeile lesen ori rmp,0b11110000 ; obere Bits maskieren cpi rmp,0b11111111 ; ein Key in dieser Spalte? breq NoKey ; wider Erwarten auch hier nicht gefunden KeyRowFound: ; Spalte ist gefunden, identifiziere Zeile lsr rmp ; schiebe Bit 0 in das Carry-Flag brcc KeyFound adiw ZL,1 ; zeige auf naechsten Tastencode rjmp KeyRowFound ; weiter schieben KeyFound: lpm ; lese keycode nach R0 rjmp KeyProc ; hier weiter mit Key-Verarbeitung NoKey: rjmp NoKeyPressed ; keine Taste gedrueckt ; ; Tabelle fuer Code Umwandlung ; KeyTable: .DB 0x0A,0x07,0x04,0x01 ; Erste Spalte, Tasten *, 7, 4 und 1 .DB 0x00,0x08,0x05,0x02 ; Zweite Spalte, Tasten 0, 8, 5 und 2 .DB 0x0B,0x09,0x06,0x03 ; Dritte Spalte, Tasten #, 9, 6 und 3
Entprellen In den Routinen KeyProc und NoKeyPressed muss natürlich noch ein Entprellen der Tasten erfolgen. Also z.B. muss in der KeyProc-Routine eine Tastenoperation erst dann ausgeführt werden, wenn die gleiche Taste 50 Millisekunden lang gedrückt ist. In der NoKeyPressed-Routine kann der dazu verwendete Zähler zurück gesetzt werden. Da das Timing der Entprellung auch noch von anderen Bedürfnissen abhängig sein kann, ist es hier nicht eingearbeitet.
Hinweise, Nachteile In den Software-Beispielen ist zwischen der Ausgabe der Column-Adresse und dem Einlesen der Row-Information nur ein Takt Zeit gelassen. Bei hohen Taktfrequenzen und/oder langen Leitungen zwischen Tastatur und Prozessor ist es notwendig, zwischen den Out- und In-Instruktionen mehr Zeit zu lassen (z.B. durch Einfügen von NOP-Instruktionen). Die internen Pull-Ups liegen bei Werten um 50 kΩ. Bei langen Leitungen und in hoch feldverseuchter Umgebung kann es unter Umständen zum Fehlansprechen der Tastatur kommen. Wer es weniger sensibel haben will, kann noch externe Pull-Ups dazu schalten. Der Nachteil der Schaltung ist, dass sie sieben Port-Leitungen exklusiv benötigt. Die Lösung über einen AD-Wandler-Kanal und ein Widerstands-Netzwerk (Abschnitt 3) ist da viel sparsamer. An den Seitenanfang
3. Anschluss an einen ADC mit Widerstands-Matrix Die meisten Tiny- und Mega-AVR-Typen haben heutzutage AD-Wandler an Bord. Sie sind daher ohne größere Klimmzüge dazu in der Lage, Analogspannungen zu messen und mit 10 Bits Genauigkeit aufzulösen. Wer also I/O-Ports sparen will, muss die Tastatur nur dazu bringen, ein Analogsignal zu liefern. Das macht z.B. eine Widerstandsmatrix. Widerstandsmatrix
Hier ist eine solche Widerstandsmatrix abgebildet. Die Spalten sind über drei Widerstände auf Masse geführt, die Zeilen über vier Widerstände auf die Betriebsspannung (z.B. 5V). Der AD-WandlerEingang ist noch mit einem Folienkondensator von 1 nF abgeblockt, da der ADC absolut keine Hochfrequenz mag, die da über die Tasten, die Widerstände und die Zuleitungen eingestreut werden könnte. Wird jetzt z.B. die Taste "5" gedrückt, dann entsteht ein Spannungsteiler: a) 1 k + 820 Ω = 1,82k nach Masse, b) 3,3 k + 680 Ω + 180 Ω = 4,16k nach Plus. Bei 5 Volt Betriebsspannung gelangen dann 5 * 1,82 / (1,82 + 4,16) = 1,522 Volt an den AD-Wandler-Eingang. Rechnen wir noch 5% Toleranz der Widerstände mit ein, dann liegt die Spannung irgendwo zwischen 1,468 und 1,627 Volt. Der AD-Wandler macht daraus bei 5 V Referenzspannung einen Wert zwischen 300 bis 333. Verwenden wir nur die oberen 8 Bit des AD-WandlerErgebnisses (Teilen durch vier oder Linksjustieren des Wandlers), gibt das 74 bis 78. Spannungswerte und Auswertung
Die anderen Kombinationen von Widerständen ergeben die in der Tabelle gegebenen Werte für die Spannungen, die 8-BitAD-Wandler-Werte und die optimale Erkennung, ob der Spannungswert der Taste erreicht wurde. Taste
Spannungen
8-Bit-AD-Werte Detektion
U(min.) U(typ.) U(max.) min. typ. max. (ab AD-W.)
1
0,225
0,248
0,272
11
13
14
7
2
0,396
0,434
0,474
20
22
25
18
3
0,588
0,641
0,698
29
33
36
28
4
0,930
0,969
1,048
47
49
54
42
5
1,468
1,522
1,627
74
78
84
64
6
1,959
2,020
2,139
99 103
110
91
7
2,563
2,688
2,809
130 137
144
121
8
3,285
3,396
3,500
167 173
180
156
9
3,740
3,832
3,917
190 195
201
185
*
4,170
4,237
4,298
212 216
221
207
0
4,507
4,550
4,588
229 232
235
225
#
4,671
4,700
4,726
238 240
242
237
Wie zu erkennen ist, gibt es bei der Verwendung von 5%-Widerständen und der dargestellten Widerstandskombination keine Überlappungen der Spannungsbereiche der einzelnen Tasten. Wer andere Widerstandskombinationen ausprobieren möchte, kann mit der zugehöigen Tabelle herumspielen (im Open-Office-Format, im Excel-XP-Format).
Hinweise zur AD-Wandler-Hardware ATtiny-Typen bieten meist nur die Möglichkeit, eine intern erzeugte Konstantspannung oder die Betriebsspannung als Referenzspannung des AD-Wandlers zu wählen. Für die Tastaturschaltung kommt nur die Betriebsspannung als Referenzspannung infrage. Diese Option ist beim Initiieren des AD-Wandlers einzustellen. Bei vielen ATmega-Typen kann auch eine extern erzeugte Referenzspannung verwendet werden, die am Pin AREF zugeführt wird. Verwendet man diese Möglichkeit, wäre auch die Tastaturschaltung aus dieser Referenz zu speisen. Verwendet man keine externe Referenzspannung, dann kommt für den Betrieb der Tastatur nur die Möglichkeit der Verwendung der Betriebsspannung als Referenzspannung infrage. In diesem Fall wird die Betriebsspannung per Software-Option intern an den AREF-Pin geführt, der externe AREF-Pin wird mit einem Folienkondensator von ca.10 nF abgeblockt. ATmega-Typen bieten zur Erhöhung der Stabilität des AD-Wandlers ferner die Möglichkeit, diesen über den AVCC-Pin separat zu bespeisen. Für die Tastaturschaltung alleine kann dieser Pin direkt an die Betriebsspannung angeschlossen werden. Sollen auch noch andere Messungen veranstaltet werden, bei denen genauer gemessen werden soll, wird der AVCC-Pin über eine Drossel von 22 µH an die Betriebsspannung geführt und mit einem Keramikkondensator von 100 nF gegen Masse abgeblockt.
Initiieren und Lesen des AD-Wandlers Zum Auslesen der Tastatur wird ein AD-Wandler-Kanal gebraucht. Der AD-Wandler wird zu Beginn eingestellt. Die beiden Beispiele zeigen den manuellen Einzelstart eines ATmega8 und den interruptgesteuerten Dauerstart bei einem ATtiny13. ATmega8: manuell starten Als erstes Beispiel ein ATmega8, ohne Interrupts, mit manuellem Start und Stop des AD-Wandlers. Tastatursignal am AD-Kanal ADC0. .DEF rKey = R15 ; Register für AD-Wert .DEF rmp = R16 ; Vielzweck-Register ; setze MUX auf Kanal 0, Linksjustieren, AREF auf AVCC ldi rmp,(1<
Umwandeln des AD-Wandler-Werts in einen Tastencode Die Spannung alleine ist noch nicht sehr verwendungsfähig. Da die Spannungen aufgrund der eigenwilligen Gestaltung von Standard-Widerstandswerten (wer hat sich die Reihe 4,7 - 5,6 - 6,8 8,2) bloss ausgedacht? Muss entweder ziemlich sturzbetrunken oder ein Mathematiker gewesen sein!) und der arg krummen Formel U = R1 / (R1 + R2) kommt hierfür nur eine Tabelle in Betracht. Die Tabelle kann nicht primitiv sein, da wir ja 256 mögliche ADC-Zustände haben und keine unnötige Platzverschwendung betreiben wollen. Wir hangeln uns mit dem ADC-Wert in rKey durch folgende Tabelle: KeyTable: .DB 7, 255, 18, 1, 28, 2, 42, 3, 64, 4, 91, 5 .DB 121, 6, 156, 7, 185, 8, 207, 9, 225, 10, 237, 0, 255, 11 Das niedrige erste Byte jedes Worts sind jeweils die AD-Werte: von 0 bis <7: keine Taste gedrückt (Tastencode=255), von 7 bis <18: Tastencode 1, etc.. Oder wer lieber gleich ASCII mag: KeyTable: .DB 7, 0 , 18, '1', 28, '2', 42, '3', 64, '4', 91, '5' .DB 121, '6', 156, '7', 185, '8', 207, '9', 225, '*', 237, '0', 255, '#' Der Code zur Auswertung sieht dann so aus: ; ; Umwandlung des AD-Werts in einen Keycode ; GetKeyCode: ; falls der AD-Wert zwischendurch wechselt, vorher kopieren! mov R1,rKey ; kopiere AD-Wandler-Wert nach R1 ldi ZH,HIGH(2*KeyTable) ; Z zeigt auf Tabelle ldi ZL,LOW(2*KeyTable) GetKeyCode1: lpm ; Lese Wert aus Tabelle cp R1,R0 ; vergleiche AD-Wert mit Tabellenwert brcs GetKeyCode2 ; kleiner als Tabellenwert, Taste gefunden inc R0 ; teste, ob am Tabellenende breq GetKeyCode2 ; Tabellenende erreicht adiw ZL,2 ; huepfe ein Wort weiter in der Tabelle rjmp GetKeyCode1 GetKeyCode2: adiw ZL,1 ; zeige auf MSB = Tastencode lpm ; lese Tastencode aus Tabelle in R0 ; Natürlich ist jetzt noch zu prüfen, ob keine Taste gedrückt ist (R0 = 0xFF bzw. bei ASCII R0 = 0) und es sind Anti-Prell-Aktionen zu basteln (wenn 20 mal hintereinander die gleiche Taste herauskommt, nehme ich sie ernst, etc.).
Erfahrungen Die Schaltung und die Software sind sehr stabil. In der ersten Version waren die Widerstände 10 mal so groß. Das hatte höhere Störanfälligkeit zur Folge, z.B. wenn in der Nähe mit einer 2 W-Handfunke gerade gesendet wurde.
Präsentation der AVRMikrokontroller mit Anwendungsbeispielen Die folgende Serie von Präsentationen gibt eine Einführung in die Anwendung von AVRMikrokontrollern und demonstriert an einfachen Beispielen mit dem AVR ATtiny13 die Verwendung der Hardware und die Programmierung. Der Quellcode der Anwendungsbeispiele ist zugänglich und ausführlich kommentiert. Die Präsentation liegt im PDF-Format vor, auf Anfrage stelle ich die Dateien auch im OpenOffice- oder M$-Powerpoint-Format zur Verfügung. Teil Datei Inhalt Größe Anwendungsbeispiele.pdf
Zeigt anhand von neun praktisch gebauten Geräten die Anwendungsvielfalt von AVR-Mikrokontrollern
763 kB
1
Teil_1_Prozessoren.pdf
Erläutert den Begriff Computer, vergleicht PC, Mikrocomputer und Mikrocontroller, erläutert am Beispiel eines Ruftonauswerters die Vorteile von Mikrokontrollern
ATtiny13, Aufbau, interne Hardware, AD-Wandler, Programmierinterface, eine externe Beispielbeschaltung
127 kB
5a
Teil_5a_UebersichtBeispiele.pdf Übersicht der Programmierbeispiele
0
5b
6
7
8
48 kB
Teil_5b_Beispiel01_02.pdf
Ein einfaches Testboard mit dem ATtiny13, ParallelportInterface für die ISP-Programmierung, Programm mit Editor schreiben, Beispiel 01: unendliche Schleife, Assemblieren mit 329 kB gavrasm, Assemblerausgabe, Listing, Hexdatei, Brennen mit Pony-Prog, Beispiel 02: LED anschalten, Portausgaben
Teil_6_Beispiel03_07.pdf
Beispiel 03: LED an- und ausschalten, Beispiel 04: LED anund ausschalten mit Verzögerung, DSchleifenverzögerungen, Einführung in Timer, Taktauswahl, Vergleicher, TimerInterrupts, Beispiel 05: Timer-LED-Steuerung, Beispiel 06: NF-Erzeugung mit Timer, Beispiel 07: Externer Interrupt, Interrupt-Vektoren, Init Timer/Interrupt, Testaufbau
Teil_7_Beispiel08_10.pdf
Kommandozeilen-Aufruf mit gavrasmW, Assembleraufruf mit Batch-Datei, Beispiel 08: Tonerzeugung mit Timer und Taster mit externem Interrupt, Beispiel 09: Tonhöen einstellen mit Poti am AD-Wandler, Interruptvektoren, Beispiel 10: 398 kB Morsebake, Parameter für Tonhöhe und Gebegeschwindigkeit, Morsetabelle im Speicher, Morsetext im Speicher,
Teil_8_Beispiel_11.pdf
Beispiel 11: Sinusgenerator mit AVR-Timer, Timerausgang Rechteckgenerator und RC-Filter zur Sinus-Erzeugung, PWM245 kB Generator mit Timer zur Sinuserzeugung, Berechnung der optimalen R- und C-Göße, Transistor-Emitterfolger, Aufbau, Programm, Schirmbilder
Messung der Umdrehungszahl mit zwei Sensoren, innerhalb eines bestimmten Umdrehungsbereichs Verstellung des Zündzeitpunktes
ATmega8
Simulation von Vorzündungsimpulsen für Motortests
Messung der Drehzahl mit einem Sensor, Erzeugung eines einstellbaren Vorzündsignals zur Ermittlung der optimalen Vorzündverstellung
ATmega8
Simulation von Motorimpulsen zur Prüfung der Zündsteuerung
Erzeugen von Testsignalen zum Testen der Zündsteuerung, einstellbare Drehzahl und Vorzündungsverstellung
Motortester
Zündtester
Messung der Anzahl Impulse oder der Impulsdauer, wahlweise Anzeige der Frequenz, der Pulsweite (High+Low, High, Low) in µs, der Drehzahl in upm Vierkanal, jeder Kanal individuell einstellbar, AkkuDaten gespeichert, Tastensteuerung oder seriell über Rechner bedienbar
Frequenzzähler
ATmega8
Frequenzzähler für 0,01 Hz bis 100 MHz
Akkulader
ATmega16
Ladegerät für vier Akkus
Fernsteuerungsauswerter
ATtiny12 ATtiny13
DreikanalFernsteuerung und – PWM-Ausgabe
Dekodiert PCM-Fernsteuersignale, Ausgabe pulsweitenmodulierter Antriebssignale für einen Linearkanal und Links/Rechts-Steuerung
Wat isse ne Computer? • Ein Computer ist eine programmgesteuerte Rechenmaschine (von eng. „to compute“ = rechnen) • Mechanische Rechenmaschine: fest verdrahtetes Programm => Rechenablauf ist durch die Mechanik festgelegt • Computer: Der Rechenablauf wird durch das Programm bestimmt. Wechselt das Programm, wird ein anderer Rechenablauf ausgeführt. • Jeder Computer braucht daher: – eine zentrale Recheneinheit (engl. „Central Processing Unit“, CPU), die Daten holt, die Programmschritte holt (Ablaufsteuerung), rechnet und die Ein- und Ausgabedaten verteilen kann, – einen Programmspeicher, in dem die Rechenanweisungen (Befehle, Instruktionen) gespeichert sind, die von der CPU abgeholt und ausgeführt werden können, und – Ein- und Ausgabe-Einheiten (engl. „Input/Output-Units“, I/O), über die Eingangsdaten eingelesen werden können und über die Ergebnisse (Resultate) der verarbeiteten Daten ausgegeben werden können.
Vergleich PC – Mikrocomputer - Mikrocontroller • PC – CPU: Intel oder AMD, Takt: x GHz, 32/64 Bit Busbreite = 32/64 Leitungen für Befehlsadressen plus 32/64 Leitungen für Daten-Kommunikation – Befehlsspeicher: extern, x Gigabyte, gelesen von externen Speichern (z.B. Festplatte, CD, DVD) – I/O-Einheiten: Festplatten und andere Speichermedien, Tastatur, parallele und serielle Schnittstellen, Graphikausgabe, u.v.a.m.
• = Elefant!
• Mikrocomputer – CPU: Zilog etc., Takt: x MHz, 8/16 Bit Busbreite = 8/16 Leitungen für Befehlsadressen plus 8/16 Leitungen für Daten-Kommunikation – Befehlsspeicher: extern, x kB, Festspeicher (EPROM), – RAM-Speicher: extern, x kB, nach Bedarf – I/O-Einheiten: parallele und serielle Schnittstellen, Graphikschnittstelle, u.v.a.m. extern zubaubar
• = Maus!
• Mikro-controller –CPU: diverse, Takt: 1..20 MHz, 8/16 Bit Busbreite, alle intern –Befehlsspeicher: intern x kB, Festspeicher (EPROM/EEPROM), –RAM-Speicher: intern x Bytes, Statisches RAM, zusätzlich nichtflüchtiges EEPROM x Byte –I/O-Einheiten: programmierbare Pins nach extern
Aufwand an Hard- und Software bei PC, MC und µC •PC –sehr aufwändige Hardware, x*100 € –Abspecken praktisch kaum möglich –hoher Energiebedarf, x*100 W –hoher Verschleiß durch Temperatur und mechanische Teile –Betriebssystem erforderlich –hochkomplexe Software –deshalb sehr hohe Fehleranfälligkeit
•MC –aufwändige Hardware, x*10 € –flexibel im Aufbau (Stecksysteme) –mittlerer Energiebedarf, x*10 W –kein Verschleiß durch Temperatur, geringer Verschleiß bei mechanischen Teilen (Stecker etc.) –gering komplexe Software, standardisierbar –daher geringe Fehleranfälligkeit
•µC –kaum HardwareAufwand, x*1 € –ersetzt auf einfache Weise komplizierte Elektronik/Logik –speziell auf Bedarf abgestimmt, kein Hardware-Ballast –geringster Energiebedarf, x*1 mW –kein Verschleiß –einfachste Software, genau auf Bedarf zugeschnitten –daher kaum Fehleranfälligkeit
Beispiel Ersatz von komplizierter Elektronik: Ruftonauswerter Analog/Mikrokontroller •Analog –Hardware: Tonerkennung (16poliges IC), Zeitverzögerung (8 bis 14 poliges IC), viel Peripherie (Widerstände, Kondensatoren, Potis, etc.) –unflexibel: Umbau auf andere Frequenzen, Toleranzen und Zeiten macht erheblichen Umbau erforderlich, Umbau nur in engen Grenzen –anfällig: Alterung erfordert Nachjustieren, spannungsabhängig
•µC –Hardware: alles in einem 8-poligen IC, wenig Peripherie –hochflexibel: Umbau auf andere Frequenzen, Toleranzen und Zeiten macht nur SoftwareÄnderung erforderlich, Umprogrammierung in weiten Grenzen möglich –unanfällig: kein Nachjustieren, nicht spannungsabhängig
Erkennung: Der Ruftonauswerter überwacht einen Lautsprecherausgang auf Töne um 1750 Hz (genau: 1700 bis 1800 Hz). Bei einem reinen Ton dieser Frequenz über eine vorwählbare Zeit (derzeit 3 Sekunden) schaltet der Ruftonauswerter das Signal für eine bestimmte Zeit lang (derzeit 30 Sekunden) zum angeschlossenen Lautsprecher durch.
•
Manuelle Schaltung: Mit einem Taster kann das Signal dauerhaft durchgeschaltet oder wieder auf Ruftonerkennung geschaltet werden.
•
Inaktiv: Ohne Betriebsspannung des Ruftonauswerters ist das Eingangssignal dauernd auf den Lautsprecher durchgeschaltet.
•
Abschluss: Im nicht aktivierten Zustand ist der Signaleingang mit 33 Ohm abgeschlossen.
•
Zustandsanzeige: Eine grüne LED zeigt an, ob 1. 2. 3. 4.
•
kein NF-Signal vorhanden ist: 100% Intensität, ein NF-Signal vorhanden ist, aber kein Rufton vorliegt: 25% Intensität, ein Rufton mit Störsignalen vorliegt: 12,5% Intensität, ein reiner Rufton vorliegt: 6% Intensität.
Ausgangsanzeige: Eine rote LED zeigt bei vorhandener Betriebsspannung an, ob der Lautsprecher abgeschaltet (LED an) oder zum Signaleingang durchgeschaltet ist (LED aus).
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13 Eine Einführung in Aufbau, Funktionsweise, Programmierung und Nutzen von Mikroprozessoren
Teil II: Wat iss ene Bit, Byte un Word?
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Lampe: an und aus, Schalter: geschlossen und offen, Logik: wahr und falsch, Digitallogik: low und high, Ladung: Minus und Plus, MOSFET-Kondensator/SRAM: geladen und entladen, Serieller Loop: Strom und kein Strom, Lochstreifen/Lochkarte: Loch und kein Loch, Digitalelektronik/SRAM: Flipflop nach links und rechts gekippt, Kernspeicher/Tape: magnetischer Nord- und Südpol, CD/DVD: verkokelt und nicht verkokelt, u.v.a.m. Ist jetzt klar, warum Computer nur 0 und 1 sprechen oder muss ich noch weiter darüber philosophieren, wie man 10 verschiedene Abstufungen von „verkokelt“ oder „gelocht“ in Null-Komma-Nix erzeugt und erkennt?
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Dezimal-, Binär- und Hexadezimalzahlen •
•
•
•
•
Dezimalzahlen: haben 10 Ziffern von 0..9, bei Zahlen die größer als 9 sind, werden Stellen davor angegeben Stellen davor sind 10, 100, 1000, ... , soviel wert wie ihre jeweilige Ziffer sagt Kennzeichung in Assembler: Keine Binärzahlen: haben 2 Ziffern: 0 und 1 bei Zahlen die größer als 1 sind, werden Stellen davor angegeben Stellen davor sind 2, 4, 8, 16, ... , soviel wert wie ihre Ziffer sagt Kennzeichnung in Assembler: 0b.... Hexadezimalzahlen: haben 16 Ziffern: von 0..9 und A..F (10..15), bei Zahlen die größer sind als 15, werden Stellen davor angegeben Stellen davor sind 16, 256, 4.096, 65.536, ... , mal so viel wert wie die Ziffer sagt Kennzeichnung in Assembler: 0x... oder $.. Alphabetimalsystem: hat 26 Ziffern: von A..Z bei Zahlen die größer sind als 25, werden Stellen davor angegeben Stellen davor sind 26, 676, 17.576, 456.976, ... , mal so viel wert wie die Ziffer sagt Kennzeichnung in Assembler: besser nicht, wenn dann 0a... Das Alphabetimalsystem wird im Folgenden nicht weiter behandelt, weil Computer einfach zu blöd sind, um das Alphabet zu kapieren.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Ähnlichkeiten zwischen Dezimal- und Binärzahlen
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Verwandtschaft zwischen Dezimal-, Binär- und Hexadezimalzahlen
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Bytes: acht Binärziffern • •
•
•
Eine Binärzahl mit acht binären Ziffern wird ein Byte genannt. Der darstellbare Wertebereich liegt zwischen Null (0b00000000) und (dezimal) 255 (0b11111111). Bezeichnung: vorzeichenlose Ganzzahl, engl. „unsigned integer byte“ Die vier vordersten und die vier hintersten Bits können zu je einer Hexadezimalzahlziffer zusammengefasst werden (z.B. binär 0b10110111 ist hexadezimal 0xD7). Die vier vordersten Bits werden zusammen als „oberes Nibble“ und die vier hintersten zusammen als „unteres Nibble“ bezeichnet. In einem Nibble kann eine Dezimalziffer untergebracht werden (gepackte Binär Codierte DezimalZahlen, packed BCD), die Zustände A bis F sind dann verboten. Unteres und oberes Nibble eines Bytes können z.B. mit der Instruktion SWAP vertauscht werden. Beispiel in Assemblersprache: ; Oberes und unteres Nibble in Register R16 vertauschen swap R16 ; tausche oberste und unterste vier Bits gegeneinander aus
•
Wird das vorderste Bit nicht als Binärziffer, sondern als Vorzeichen-Bit interpretiert (bei negativen Zahlen ist das vorderste Bit gleich Eins), steht der Wertebereich von –128 (0b11111111) über 0 (0b00000000) bis +127 (0b01111111) zur Verfügung. Bezeichnung: vorzeichenbehaftete Ganzzahl, engl. „signed integer byte“.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Bytes in 8-Bit-Prozessoren • •
Alle 8-Bit-Prozessoren verarbeiten 8 Bits gleichzeitig! Die Recheneinheit kann jeweils 8 Bit eines Registers (R0..R31) mit jeweils 8 Bit eines zweiten Registers (R0..R31) oder einer Konstanten (0..255) behandeln und speichert das 8 Bit breite Ergebnis in einem Register (R0..R31). Z.B. in Assemblersprache: ; Addiere R1 und R0, speichere Ergebnis in R1 add R1,R0 ; Zeitbedarf: 1 Takt, @4MHz: 0,25 µs
•
Überschreitet eine Operation (z.B. eine Addition oder Subtraktion) den zulässigen Wertebereich nach oben hin (Ergebnis ist größer als dezimal 255) oder nach unten hin (Ergebnis kleiner als Null), wird ein Status-Bit (engl. „flag“) gesetzt (Carry-Bit, C). Ist das Ergebnis Null, wird das Status-Bit Z gesetzt (Zero). Die Status-Bits können in Sprungbefehlen abgefragt und dazu benutzt werden, die Verarbeitung in verschiedenen Programmteilen fortzusetzen (bedingte Sprünge, engl. „conditional branch“). Z.B. in Assemblersprache: ; Subtrahiere R1 und R0 sub R1,R0 ; ziehe R0 von R1 ab, Ergebnis in R1 breq gleich ; Ergebnis war Null, springe nach gleich: brcs kleiner ; Ergebnis kleiner Null, springe nach kleiner: ... ; Ergebnis war größer Null, mache weiter gleich: ... ; Ergebnis war gleich, mache was anderes kleiner: ... ; Ergebnis war kleiner, mache ganz was anderes
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Hast Du da noch Worte? • • • • • •
•
16 Bit breite Binärzahlen werden als Worte (eng. word) bezeichnet. Der Wertebereich reicht von 0 bis 65.535 (unsigned integer word). Jeweils vier Bits eines Wortes können als Hexadezimalziffer zusammengefasst werden (z.B. 0b1001.0101.1110.1010 ist gleich 0x95EA). Das obere Byte eines 16-Bit-Wortes wird als höherwertiges Byte oder „most significant byte“ (MSB), das untere Byte als niederwertiges Byte oder „least significant byte“ (LSB) bezeichnet. Wird das vorderste Bit als Vorzeichen interpretiert, reicht der Wertebereich von – 32768 bis +32767 (signed integer word). 8-Bit-Prozessoren können auch 16-Bit-Zahlen verarbeiten. Dazu werden zwei 8Bit-Register nacheinander verarbeitet. Im Prinzip können Paare aus beliebigen Registern gebildet werden, z.B. R5:R6 (R5 ist dann MSB, R6 ist LSB, „:“ signalisiert den Zusammenhang beider) und nacheinander behandelt werden. Einige Instruktionen erlauben die direkte Verarbeitung bestimmter Doppelregister. Bei den AVR sind dies die drei Registerpaare R27:26, R29:R28, R31:R30, sie werden als X (XH:XL), Y (YH:YL) und Z (ZH:ZL) bezeichnet. In sehr eingeschränktem Umfang ist auch R25:R24 für bestimmte Operationen zulässig, das Paar hat keinen eigenen Namen. Beispiele in Assembler: adiw R24,1 ; erhöhe Wort R25:R24 um Eins sbiw XL,5 ; erniedrige Wort X um fünf movw ZL,XL ; kopiere Inhalt von XH:XL nach ZH:ZL st Z+,R0 ; kopiere Inhalt von Register R0 zur Speicheradresse in Z und erhöhe die Adresse um Eins ldd R16,Y+14 ; kopiere den Inhalt der Speicherzelle, auf die Y+14 zeigt, in das Register R16
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Wo braucht man so was? • • • • • • • • •
Beispiel Frequenzzähler: Eingangsfrequenz für die Messung: max. 20 MHz (50 ns) Messdauer: 0,25 Sekunden Zählung mit eingebautem 8-Bit-Counter: Überlauf des Zählers alle 256*50 ns = 12,8 µs Zählung der Zählerüberläufe, maximale Anzahl = 20.000.000 / 4 / 256 = 19.531 Anzahl Überläufe ist größer als 255 (Fassungsvermögen 1 Byte), nicht größer als 65.535 (Fassungsvermögen 2 Bytes), benötigt zwei Bytes für Überläufe Zusammen mit 1 Byte letzter Zählerstand: 3 Byte lange Zahl Speicherung z.B. in R3:R2:R1 Multiplikation der Zahl mit 4 ergibt Frequenz in Hz, kann dabei ein Überlauf auftreten? 20.000.000 ist hexadezimal 0x01.31.2D.00, benötigt also vier Bytes! Beispiel für Multiplikation mit vier in Assembler: ; R3:R2:R1 mit vier multiplizieren = Binärziffern zwei Mal links schieben clr R4 ; wird wegen Überlauf gebraucht, Null setzen lsl R1 ; niedrigstes Byte links schieben, Überlauf in Carry rol R2 ; zweites Byte links rotieren, Carry einschieben rol R3 ; drittes Byte links rotieren, Carry einschieben rol R4 ; allerhöchstes Byte links rotieren, Carry einschieben lsl R1 ; niedrigstes Byte noch mal links schieben rol R2 ; zweites Byte noch mal links rotieren rol R3 ; drittes Byte noch mal links rotieren rol R4 ; viertes Byte noch mal links rotieren, und fertig ; Zeitbedarf: 9 Takte, bei 4 MHz Taktfrequenz: 2,25 µs
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Darf‘s ein bisschen mehr sein? • •
•
•
Zahlen können fast beliebig groß sein. Dazu werden drei oder mehr Register zusammengefasst und als eine Zahl interpretiert und verarbeitet. Drei Bytes (z.B. R2:R1:R0) ergeben dann einen Wertebereich von 0 bis 16.777.215, vier Bytes (z.B. R15:R14:R13:R12) einen Wertebereich bis 4.294.967.295, usw. Die Verarbeitung solcher Zahlen (z.B. Erhöhen, Erniedrigen, Addition, Subtraktion) erfolgt dann Byte für Byte in einzelnen Schritten, wobei das Übertragsbit Carry C für Überträge aus niedrigeren Bytes und das Zerobit Z mit verwendet werden. Beispiel in Assemblersprache: ; Erhöhen von R32:R1:R0 um Eins inc R0 ; erhöhe niedrigstes Byte um Eins brne fertig ; wenn kein Überlauf zu Null springe nach fertig inc R1 ; Überlauf, erhöhe nächsthöheres Byte um Eins brne fertig ; wenn kein Überlauf zu Null springe nach fertig inc R2 ; Überlauf, erhöhe höchstes Byte um Eins fertig: ; hier landen alle Fälle letztendlich
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Schlussfolgerungen •
• • • • • •
Es gibt beliebig viele Zahlensysteme, und man wundert sich, weshalb unsere Vorfahren ausgerechnet das Zehnersystem ausgewählt haben und nicht das Zweier-, Dreier-, Vierer-, Siebener-, Zwölfer-, Sechsundzwanziger- oder Sechziger-System. Wo wir doch zwei Füsse und zwei Hände haben (= vier), das Bier im Sixpack handhaben (=6), der Sieben eine gewisse Magie zugeschrieben wird (=7), ein Bierkasten 12 Flaschen fasst (=12), das SechsundzwanzigerSystem mit dem Alphabet abzuwickeln wäre (Ziffern wären überflüssig) und Zahlen im 60-er System so schön kurz sind. Tradition ist manchmal arg ineffektiv. Computer sprechen binär, manchmal nibbelig und eher nebenbei etwas hexadezimal, keinesfalls aber dezimal! Die Zahlenumwandlung in binär/hexadezimal ist für‘s Denken etwas mühsam, aber aus Computersicht höchst effektiv! Auch im kleinsten Mikrokontroller lassen sich noch größte Zahlen mühelos verarbeiten! Für‘s Handling von 32- und 64-Bit-Zahlen braucht es keinen High-End-PC, es reicht ein 8-beiniges Mikrokontrollerchen! So eine Multiplikation mit vier geht auch bei nur 4 MHz Takt noch sauschnell. GHz-CPU-Takt ist dagegen total out, weil der PC mehr mit Fensterln und Platten beschäftigt ist als mit Rechnen!
Befehlsablauf im Einzelnen • __________ __________ _______ • Taktsignal: ____| |__________| |________| |_ • Befehl bearbeiten: | Programmzähler an Programmspeicher ausgeben | Befehlswort aus Programmspeicher auslesen | Befehlswort dekodieren | Befehl ADD ausführen | Ergebnis speichern Fertig | • Befehl braucht zwei Takte zur Bearbeitung: Takt 1 liest aus Programmspeicher, Takt 2 führt den Befehl aus, @4 MHz: 0,5 µs pro Befehl • Pre-Fetch bei ATMEL-AVRs: Während der Ausführung des vorausgehenden Befehls wird bereits der nächste Befehl geholt; falls kein Sprung erfolgt, kann der nächste Befehl direkt bearbeitet werden; Konsequenzen: doppelte Ausführungsgeschwindigkeit, @4 MHz: 0,25 µs pro Befehl, Befehle ohne Sprung brauchen nur einen Takt, mit Sprung zwei Takte • Der Zeitbedarf für jeden Befehl ist exakt vorhersehbar • Alle Abläufe lassen sich exakt zeitlich planen und ausführen • Im PC nahezu unmöglich, da Multitasking-Betriebssysteme darüber entscheiden, welches Programm wann wieviel Zeit bekommt!
Befehle, Instruktionen (Assembler Mnemomonic) • Die wichtigsten Befehle/Instruktionen beim ATtiny13 –Rechnen: Addieren (Add, Adc) und Subtrahieren (Sub, Sbc) ohne und mit Übertrag,
Vergleichen mit Konstante (Cpi) / Register (Cp) / Register und Übertrag (Cpc), Um eins erhöhen (Inc) oder vermindern (Dec), Logisches Und (And) / Oder (Or) / Exklusiv-Oder (Eor), Bits auf 0 (Cbr) oder 1 (Sbr) setzen, Register auf Festwert setzen (Ldi) / auf Null setzen (Clr) / auf 255 (Ser) setzen –Bit und Bit-Test: Bit im Port auf Eins (Sbi) oder Null (Cbi) setzen, Logisches LinksSchieben (Lsl) oder Rechtsschieben (Lsr), Links- (Rol) oder Rechts- (Ror) Schieben über Carry, Arithmetisches Rechtsschieben (Asr), Unteres und oberes Nibble tauschen (Swap), Flaggbit setzen (Set) / rücksetzen (Clt) / auf Registerbit setzen (Bst) / in Register laden (Bld), Flags im Statusregister setzen (Sex) oder löschen (Clx) mit x={Z,C,N,V,S,H,T,I}, Einer(Com) und Zweier-(Neg) Komplement –Sprünge: Relativer (Rjmp) / Indirekter (Ijmp) Sprung, Relativer (Rcall) / Indirekter (Icall) Unterprogrammaufruf, Relativsprung bei gesetztem (Brxs) / gelöschtem (Brxc) Statusbit mit x={Z,C,N,V,S,H,T,I}, Überspringen bei gesetztem (Sbxs) oder gelöschtem (Sbxc) Bit mit {x=r Register, x=i I/O-Port) –Datentransfer: Register zu Register (Mov), Registerwort zu Registerwort (Movw), Speicher zu Register direkt (Ld) / indirekt (Ldd) / Festadresse (Lds), Register in Speicher direkt (St) / indirekt (Std) / Festadresse (Sts), Programmspeicher in Register (Lpm), Register in I/O-Port (Out), I/O-Port in Register (In), Register auf Stapel (Push) und vom Stapel (Pop) – CPU-Kontrolle: Nichtstun (Nop), Schlafen (Sleep), Wachhund rücksetzen (Wdr), DebugUnterbrechung (Break)
• Jeder Befehl belegt ein Wort (=2 Byte) im Programmspeicher. • Die meisten Instruktionen benötigen einen einzigen Takt für die Ausführung, Sprungbefehle zwei Takte.
Zusammenfassung • Beim Abarbeiten von Befehlen/Instruktionen im Mikrokontroller geht es streng geordnet und überaus berechenbar zu. • Scheffe ist CPU; Hoflieferant der Programmspeicher; Höflinge mit Vitamin B und direktem Zugang zum Chef sind die 32 Register; Indianer sind die Speicher und diverse interne Gerätschaften. Für die Außenpolitik sorgen externe Pins, die geruhen, etwas ein- oder auszugeben. • Alle Befehle/Instruktionen werden nacheinander bearbeitet (konsekutiv), ein Nebeneinander verschiedener Aufgaben kann und darf es auch nicht geben. • Den Takt der Verarbeitungsmusik gibt der Taktgeber vor, die aktuelle Bearbeitungsadresse steht immer im Programmzähler (program counter). • Nach außen hin herrscht absolute Ruhe! Kein Pin gibt interne Adressen oder Daten nach außen, es sei denn, es wird so angewiesen! Unterschied zu PC und MC! • AVRs holen schon mal den nächsten Befehl, während sie den letzten noch gar nicht richtig bearbeitet haben (Pre-Fetch). • Wildes Umherspringen im Programmablauf wird deshalb mit Strafzeiten von mindestens einer Taktlänge geahndet. • Es gibt mehr als 100 verschiedene Befehle, die alle was anderes bewirken. Kein Mensch kann die auswendig lernen und sich merken, was die anstellen, wie lange sie brauchen, welche Flaggen sie wann setzen oder in Ruhe lassen. Für vergessliche Menschen gibt es das vollständige Instruction Set Summary am Ende jedes Datenblattes, für umme bei http://www.atmel.com, Microprozessors, 8-Bit-RISC. Da steht alles drin.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13 Eine Einführung in Aufbau, Funktionsweise, Programmierung und Nutzen von Mikroprozessoren Teil IV: Wat iss ene ATtiny13?
Pin-Out des ATtiny13 • Betriebsspannungsanschlüsse festgelegt (Pins 4 und 8; 1,8 bis 5,5 V Betriebsspannung) • Je nach Programmierung haben die anderen Pins unterschiedliche Funktionen:
Aufbau des ATtiny13 – Taktgenerator – 8-Bit-Zähler • Taktgenerator: – Interner Taktgenerator 9,6 MHz, Auto-Kalibrierung werksseitig, programmierbarer Vorteiler durch 1..256 (ergibt 9,6 MHz bis 37,5 kHz Taktfrequenz) – Externer (Quarz-)Oszillator zuschaltbar an Pin 2 (PB3)
• Ein 8-Bit-Timer/Zähler: – Quelle der Zählimpulse: Timer = vom Taktoszillator über Vorteiler durch 1/8/64/256/1024, Zähler = Taktung durch ansteigende oder abfallende Signale am T0-Pin – 2 Vergleichsregister für Pulsweiten-Generatoren (PWM-Modi) – Programmierbare Zählweite (1..256) im CTC-Modus – Zwei Ausgabe-Pins (OC0A, OC0B) programmierbar für externe Hardware-Signale (für Signalgeneratoren etc.) – Drei Unterbrechungskanäle (Interrupt-Vektoren: Überlauf, Vergleich A, Vergleich B) für halbautomatische Abläufe
Aufbau des ATtiny13 – I/O-Pins, Analogvergleicher • max. 6 Ein-/Ausgabe-Pins (I/O-Pins): – Richtung manipulierbar (Ein- oder Ausgang) – bei Eingängen sind Pull-Up-Widerstände zuschaltbar – Unterbrechung (Interrupt) bei Eingang INT0 (PB1) programmierbar (Int bei fallender oder steigender Flanke oder bei Nullpegel) – Unterbrechung (Interrupt) bei Pegelwechsel bei allen Pins wählbar, mit Maskierung der auslösenden Pins)
• Analog-Vergleicher – zuschaltbarer Analogvergleich an den Pins AIN0 und AIN1 (PB0 und PB1) – Unterbrechung (Interrupt) bei Wechsel
Aufbau des ATtiny13 – Serielle Programmierung • Serielles Programmierinterface: – Programmierung in der Schaltung möglich (ISP = In-System-Programming), – PB0, 1 und 2 sowie Reset und die zwei Betriebsspannungsanschlüsse wechseln beim Programmieren automatisch ihre Funktion und Richtung (einfach 6-Pin oder 10-PinProgrammier-Stecker anstecken) – Wahl zwischen Hochspannungs- und Niederspannungsprogrammierung möglich (im Hochspannungsmodus 12V am Reset-Eingang, besondere Einstellungen zugänglich)
Die Innereien eines ATtiny13 hätten vor 15 Jahren eine Z80-Kiste mit mindestens 19 Zoll, drei Höheneinheiten sowie 10 bis 15 EuropaSteckkarten im Format 160*100 samt 64-poligem Bussystem und lüftergekühltem Netzteil gefüllt. Ein Tiny13 ist für schlappe 2 € bei Ebay zu kriegen, wieviel würde die obige Kiste kosten? (Schon das Gehäuse von Isel kostet locker das 50fache.) Mit einem Strombedarf von maximal 10 mA (bei 5 V und 9,6 MHz Takt, ohne Schlafmodus) kann die ganze Maschinerie aus einer Batterie gespeist werden. Für den Saft einer einzigen Intel-4GHz-CPU kann man deutlich mehr als 1000 von diesen Tiny‘s gleichzeitig betrieben. Ein doch recht komplexes Gerät wie eine Morsebake schrumpft vom TTL/CMOS-Grab-Niveau zu einem achtpoligen Etwas, dessen Sockel noch den größten Raum einnimmt. Robert macht‘s mit SOICFliegenschiss-Gehäuse und Mikroskop noch um den Faktor 20 kleiner und es passt zusammen mit dem Akku in einen Fingerhut. Die Intellenz steckt nur noch im Programm und nicht mehr in der Hardware.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13 Eine Einführung in Aufbau, Funktionsweise, Programmierung und Nutzen von Mikroprozessoren
Teil 5a: Übersicht über die Programmierbeispiele
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Übersicht über die Programmierbeispiele I •
•
Beispiel 01: Unendliche Schleife – Schreiben eines Programms mit Texteditor – Übersetzen in Maschinensprache – Einprogrammieren in den Flash-Speicher des Tiny13 – Unendliche Schleife Beispiel 02: Ausgang und LED-Treiber – Einen I/O-Pin als Ausgang definieren und auf Null setzen – Eine LED an diesem Ausgang antreiben
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Übersicht über die Programmierbeispiele II • •
• • •
Beispiel 03: Ausgang aus- und einschalten, schnell – Schleife mit Ein- und Ausschalten eines I/O-Pins Beispiel 04: LED langsam An und Aus – Ein- und Ausschalten mit Verzögerungsschleife – Herunterzählen eines Zählers und bedingte Sprünge – Ausführungszeiten für Instruktionen Beispiel 05: Timer schaltet LED – Programmieren des Timers als selbstständiger Ein- und Ausschalter Beispiel 06: Timer macht Töne – Schnelles Ein- und Ausschalten als Tongenerator Beispiel 07: Taster schaltet LED ein – Nutzung eines I/O-Pins als Interruptquelle zum Schalten
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Übersicht über die Programmierbeispiele III •
•
•
Beispiel 08: Morsegenerator – Interrupt-gesteuertes Schalten eines NF-Tongenerators mit Timer mittels Taster Beispiel 09: Tonhöhen-Einstellung mit AD-Wandler – Spannungsmessung mit AD-Wandler und Tonhöhenverstellung beim Morsegenerator Beispiel 10: Morseausgabe eines Texts – Interrupt-gesteuerte komplexe Abläufe – Ausgabe eines Bakentextes mit Morsegenerator
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13 Eine Einführung in Aufbau, Funktionsweise, Programmierung und Nutzen von Mikroprozessoren
Teil IV: Programmieren an Beispielen
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Die Test-Hardware
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Das Parallelport-Interface
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Testhardware
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Überblick: Programmieren für Anfänger 1. Schreiben des Programmcodes mit einem Editor ergibt eine Textdatei 2. Übersetzen des Programmcodes mit einem Assembler ergibt ein Programmlisting und eine Hexdatei mit den Maschinenbefehlen 3. Einlesen der Hexdatei und Einbrennen der Maschinenbefehle in den Programmspeicher mittels eines Brennprogramms und eines Programmier-Interfaces 4. AVR startet neu und führt die Maschinenbefehle aus
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Programmierbeispiel 01: Die unendliche Schleife Man nehme: einen Editor (z.B. M$: Notepad, Linux: KEdit) und tippe ein:
• Zeilen mit einem Semikolon werden von da an ignoriert (Kommentare) • „loop:“ ist eine Sprungmarke (engl. Label), es dient der Kennzeichnung für den Assembler, Benennung beliebig, immer Doppelpunkt am Ende • „rjmp“ ist eine Instruktion, sie erzeugt beim Übersetzen ein Befehlswort • „rjmp loop“ bewirkt, dass der Prozessor um einen Befehl rückwärts springt (unbedingter Sprung) • Das macht er (und nichts anderes), solange er unter Spannung steht
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Übersetzen des Programms mit gavrasm • Man nehme: einen Assembler (wie z.B. meinen eigenen, genannt gavrasm) • Man öffne: ein Befehlszeilenfenster (M$: Eingabeaufforderung, Linux: bash) und tippe ein (Pfade natürlich entsprechend der eigenen Pfade):
• Dann wird der Assembler aufgerufen mit:
• „Pfad\gavrasm“ ruft den Assembler auf, „beispiel01[.asm]“ ist die Quelldatei • -seb bedeutet: s=erzeuge eine Symbolliste, e=gib erweiterte Errormeldungen aus, b=gib Kommentare für Beginner aus
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Hier spricht der Assembler
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Was will er uns sagen? • Dabei bedeutet: • „gavrasm gerd's AVR assembler Version 1.4 (C)2005 by DG4FAC“: die Version des Assemblers • „Kompiliere Quelldatei: beispiel01.asm“: die assemblierte Textdatei • „Durchgang: 1, 5 Zeilen verarbeitet.“: der Assembler hat alle Instruktionen verstanden, kein Fehler ist aufgetreten. • „Durchgang: 2, 5 Zeilen verarbeitet.“: der Assembler hat beim zweiten Durchgang auch alle Sprungmarken, Konstanten und Definitionen komplett verstanden und gefunden. • „Warnung 006: Keine DEVICE-Direktive, keine Typprüfung!“: es wurde kein spezifischer AVR-Typ mit der DEVICE-Direktive festgelegt, deshalb wurde nicht überprüft, ob die verwendeten Instruktionen bei diesem AVR-Typ zulässig sind. • „1 Worte Code, 0 Worte Konstanten, Gesamt=1 = 0,0%“: Es wurde eine 16-BitInstruktion (ein Befehlswort) erzeugt, im Programm werden keine Tabellen mit Konstanten verwendet, und insgesamt ist der verfügbare Flash-Speicher zu 0,0% durch den Code belegt.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Wer‘s schriftlich lieber mag ... • In dem Verzeichnis befinden sich jetzt zwei neue Dateien: „beispiel01.lst“ und „beispiel01.hex“. • beispiel01.lst“: Das Listing beim Übersetzen:
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Kryptisch: Maschinencode in Hex • Der einzige Maschinenbefehl an Adresse 000000 lautet also „CFFF“ • Die Hex-Datei enthält die Maschinenbefehle in folgendem Format:
• Diese Datei enthält alle nötigen Daten für die Programmierung in den Flash-Speicher des Chips plus Adressangaben und Prüfbytes. • Sie wird vom Brennprogramm eingelesen, ausgewertet und der enthaltene Maschinencode (hier: CFFF) in den Flash-Speicher übertragen. • Als Beispiel für ein Brennprogramm wird PonyProg2000 verwendet.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Burn it in ...
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 02: Die Test-Hardware
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 02: LED an
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 02: LED an, Programmschritte •
„.INCLUDE „tn13def.inc““: Liest die Prozessor-spezifischen Definitionen des ATtiny13 ein (zulässige Instruktionen, Nummern der I/O-Ports, spezielle Labels, etc.)
•
„sbi DDRB,1“: setze das Bit 1 im Datenrichtungsregister von Port B auf Eins, das macht dieses Portbit (PB1, Pin 6) zu einem Ausgang.
•
„cbi PORTB,1“: setze das Portbit PB1 auf Null, der Ausgang wird damit auf Null Volt gezogen und die LED zieht aus der Versorgungsspannung über den Widerstand Strom und leuchtet.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13 Eine Einführung in Aufbau, Funktionsweise, Programmierung und Nutzen von Mikroprozessoren
Teil VI: Programmieren an weiteren Beispielen
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Programmierbeispiel 03: LED an und ausschalten •„sbi DDRB,1“ schaltet Richtungsregister von Pin 6 auf Ausgang •„cbi PORTB,1“ schaltet Ausgang auf Null, die LED leuchtet •„sbi PORTB,1“ schaltet Ausgang auf Eins, die LED leuchtet nicht •„rjmp loop“ springt wieder zurück du beginnt beim zweiten Befehl neu
•„cbi“ und „sbi“ brauchen je einen Takt, „rjmp“ braucht zwei Takte, macht zusammen vier Takte •1,2 MHz interner Takt durch vier ergibt eine Schaltfrequenz von
300 kHz •Da ist beim besten Willen das An und Aus der LED nicht mehr zu erkennen.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Programmierbeispiel 04: LED an und aus mit Verzögerung •„sbi DDRB,1“ schaltet Richtungsregister von Pin 6 auf Ausgang •„sbi PORTB,1“ schaltet Ausgang auf Eins, die LED ist aus •„sbiw ZL,1“, „brne loop1“ zieht vom Inhalt des Registerpaars ZH:ZL so lange eins ab, bis es Null wird, das wiederholt sich 65535 mal •cbi PORTB,1“ schaltet Ausgang auf Null, die LED ist an •„sbiw ZL,1“, „brne loop1“ zieht vom Inhalt des Registerpaars ZH:ZL so lange eins ab, bis es Null wird, das wiederholt sich 65535 mal •„rjmp loop“ springt wieder zurück und beginnt beim zweiten Befehl neu •„cbi“ und „sbi“ brauchen je einen Takt, „rjmp“ braucht zwei Takte, die beiden Verzögerungsschleifen brauchen zusammen 65535*4*2 = 524.280 Takte, macht zusammen 524.284 Takte •1,2 MHz interner Takt durch 524.284 ergibt eine LED-Schaltfrequenz von
2,29 Hz
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Nachteile der Schleifenverzögerung • • •
Die CPU wird fast ausschließlich mit Unsinn beschäftigt (Abziehen, Prüfen, Rücksprung) Die CPU kann nichts anderes tun als die Schleife zu bearbeiten, sonst würde sich das Schleifen-Timing und die LED-Frequenz drastisch ändern Die CPU verbraucht unnötig viel Strom für das Lesen aus dem Programmspeicher und das Zählen in der Schleife
Daher: • Verzögerungen besser mit dem eingebauten Timer. • Möglichkeiten der Hardware soweit als möglich ausschöpfen. • CPU schlafen legen, verbraucht dann nur noch wenig Strom. • Timer läuft unabhängig von CPU, die kann währenddessen was anderes tun oder eben die meiste Zeit schlafen, bis es wieder Arbeit gibt.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Funktionsweise von Timer/Countern • • • •
• • •
Ein Timer ist ein Binärzähler, der von einem festen Zeittakt angetrieben wird. Der fester Zeittakt kann mittels eines programmierbaren Vorteilers (engl. Prescaler) vom Prozessortakt abgeleitet werden. Dient als Taktgeber das Signal an einem externen Pin, heißt der Timer Counter. Die Taktquelle, die den Timer/Counter antreibt, wird durch Schreiben eines Kontrollbytes an einen bestimmten I/O-Port ausgewählt. Der Zähler kann damit auch jederzeit von der CPU angehalten werden. Der Stand des Timers/Counters kann von der CPU aus jederzeit gelesen und beschrieben werden. Ein Überlaufen des Zählers kann für eine Unterbrechungsanforderung an die CPU verwendet werden (Timer Overflow Interrupt). Um die CPU von der Aufgabe zu entlasten, den Zählerstand laufend zu überwachen, sind an den Timer eine oder mehrere Vergleicher angegliedert. Sie vergleichen den Zählerstand mit einem programmierten Wert im Compare-Register und lösen bei Gleichheit (Compare Match) voreingestellte Ereignisse aus, wie z.B.: - Unterbrechungsanforderung an die CPU (Compare Match Interrupt), - Null-Setzen, Eins-Setzen oder Umkehr an einem externen Pin, - Rücksetzen des Timers auf Null.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Aufbau des 8-Bit-Timers - Taktauswahl
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Aufbau des 8-Bit-Timers - Vergleicher
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Aufbau des 8-Bit-Timers - Interrupts
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 05: Timer steuert LED Zuerst wird der interne Vorteiler für den Prozessortakt auf 32 gesetzt. Dadurch läuft die CPU langsamer. Das wird durch zwei Ausgaben auf den Port CLKPR bewirkt. Dann wird die Timerlänge auf 147 gesetzt (Schreiben in Compare-APort OCR0A). Der Timer setzt sich im CTC-Mode dann automatisch auf Null zurück und beginnt neu. Durch Schreiben von 74 in den Compare-B-Port und durch das Kontrollwort in TCCR0A wird erreicht, dass bei halbem Timerwert der Ausgangspin OC0B seine Polarität wechselt (Toggle). In Kontrollwort TCCR0B wird der Vorteiler des Zählers auf 1024 eingestellt, der Zähler zählt jetzt aufwärts. Nach Setzen des SLEEP ENABLE Bits wird der Prozessor mit dem Befehl SLEEP schlafen gelegt, er wacht nicht wieder auf.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 06: NFErzeugung Dasselbe Prinzip, aber anderes Timing: der Prozessortakt wird bei den voreingestellten 1,2 MHz belassen, statt der LED wird ein Piezo- oder 32-Ohm-Lautsprecher angeschlossen. Die Timer-Werte werden so eingestellt, dass der Ausgang OC0B (PB1 an Pin 6) mit einer Frequenz von 1000 Hz seine Polarität wechselt (Vorteiler = 8, CTC bei 75, 1,2MHz / 8 / 75 = 2kHz, Polaritätswechsel am Ausgang bei Zählerstand 38, zwei Durchläufe ergeben eine volle NF-Schwingung, 2000 Hz / 2 = 1000 Hz). Auch in diesem Beispiel wird die CPU schlafen gelegt und nicht wieder aufgeweckt.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 07: Unterbrechung/Interrupt • In diesem Beispiel wird demonstriert, wie ein Pegelwechsel an einem Eingangspin dazu verwendet werden kann, die schlafende CPU aufzuwecken und eine Aktion auszuführen (hier: Ein- bzw. Ausschalten einer LED). • Der Eingangspin wird dazu mit einem internen Pull-Up-Widerstand auf +5V gezogen. Das entsprechende Portbit wird maskiert, so dass nur Pegelwechsel an diesem Pin Interrupts auslösen können. • Wenn die Taste den Pin auf 0V zieht, wird ein Interrupt ausgelöst. Wird die Taste wieder losgelassen, löst der Pegelwechsel erneut einen Interrupt aus. • Bei einem Interrupt legt die CPU die aktuelle Adresse auf dem Stapel im SRAM ab, verzweigt zu einer festen Adresse (Interrupt-Vektor) und bearbeitet die dortigen Befehle. Bei der Rückkehr wird die auf dem Stapel abgelegte Adresse wieder geholt und die Verarbeitung an der Adresse fortgesetzt, bei der die Unterbrechung auftrat. • Jede Unterbrechungsmöglichkeit (beim ATtiny13 gibt es 10) hat ihren eigenen Platz in der Vektortabelle, so dass sehr schnell auf ein Ereignis reagiert werden kann. • Weitere Unterbrechungen werden so lange unterbunden, bis die Service-Routine beendet ist. Danach kann dann die nächste Unterbrechung bearbeitet werden. Die Bearbeitung mehrer gleichzeitiger Interrupts erfolgt nach einer Prioritätsreihenfolge: je höher in der Tabelle de Vektor steht (niedrigere Adresse), desto vorrangiger.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 07: Schaltbild
Schalter an PB4 angeschlossen,
LED an PB1 angeschlossen,
PB4 mit internem Pull-Up auf +5V
PB1 auf Null: LED ist an
zieht Eingang auf Null Volt
PB1 auf Eins: LED ist aus
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 07: Interrupt Vektoren Register definieren: symbolische Namen für bestimmte Register festlegen.
Die Interrupt-Vektor-Tabelle des ATtiny13: Der Befehl an der Adresse 0000 wird beim Start ausgeführt (Sprung zum Label main:). Der Interrupt INT0 wird hier nicht benutzt, an Adresse 0001 erfolgt ein einfacher Rücksprung vom Interrupt (reti). Bei einem Pegelwechsel an Pin 3 wird der PCINT0 Interrupt an Adresse 0002 angesprungen, der nach Label intpcint: verzweigt. Alle anderen Vektoren werden nicht benutzt.
Die Interupt-Service-Routine intpcint: stellt als erstes fest, ob der Tasteneingang Null der eins ist. Bei Null wird der rjmp nicht ausgeführt. Ist er Null, wird die LED angeschaltet und die Routine beendet (reti). Ist er Eins, wird die LED ausgeschaltet und die Routine beendet (reti).
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 07: Anstoßen des Ganzen (Init) Hauptprogramm, wird beim Reset ausgeführt (Adresse 0000: Sprungbefehl nach main:). Der „Stapel“ im SRAM wird dazu benutzt, um beim Interrupt die Rücksprungadresse dort abzulegen und bei reti wieder dorthin zu springen. Der LED-Ausgang wird initiiert. Der Tasten-Eingang wird definiert und der Pull-Up-Widerstand eingeschaltet. Die Maske in PCMSK wählt den Eingang aus, bei dem Interrupt erfolgen soll (Bit 4). Im Int-Maskenregister GIMSK wird der PCINT0-Interrupt ermöglicht. Im Statusregister der CPU wird das Interrupt-Bit gesetzt, die CPU lässt Ints zu. Das SLEEP ENABLE Bit wird gesetzt. Die CPU wird mit SLEEP schlafen gelegt, wacht beim Interrupt auf, führt ihn aus, und wird anschließend wieder zu Bett gebracht.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 07: Testaufbau
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13 Eine Einführung in Aufbau, Funktionsweise, Programmierung und Nutzen von Mikroprozessoren
Teil 8: gavrasmw und weitere Beispiele
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
gavrasmw – Leichter fensterln
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
gavrasmw - Setup Einmal einstellen: wo ist das Assembler-Programm gavrasm, wo ist meine Quellcode-Datei, welche Einstellungen beim Assemblieren hätten‘s denn gern?
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
gavrasmw – Kleiner Editor auf Knopfdruck
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
gavrasmw macht eine Batch-Datei Das mühsame Eintippen von Pfaden wird von gavrasmw erledigt und in einer Batch-Datei abgelegt.
Zum Assemblieren einfach die Batch-Datei ausführen lassen oder den Menuepunkt „Assemble“ drücken, schon geht gavrasm voll ab.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 08: Taster macht Töne • Morsetongenerator • Kombiniert Beispiel 6 (NF-Erzeugung mit Timer) und Beispiel 7 (Interrupt-gesteuerte Tastenüberwachung) zu einem praktischen Gerät • Minimaler Stromverbrauch wegen CPUSchlafmodus und Timer-Automatik • Minimale Außenbeschaltung (keine Kondensatoren, ein Widerstand) • Genauso groß wie ein 555, aber viel flexibler (beliebige NF-Frequenz ohne Hardwareänderung und alleine durch Programmieren einstellbar)
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 08: Taster macht Töne II • Ändern der NF-Frequenz durch Ändern einer Zahl im Quellcode
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 09: Tonhöhen-Einstellung • •
•
•
•
•
In diesem Beispiel wird die Tonhöhe der NF-Frequenz mit einem Potentiometer eingestellt. Die Poti-Stellung wird mit einem AD-Wandler von Analog (0..5 Volt) in eine digitale Zahl verwandelt (0..1023) und stellt die Timer-Auflösung durch Teilen durch vier auf Werte zwischen 0 und 255 ein. Der AD-Wandler wird einmalig gestartet, nach Ende der Umwandlung unterbricht der AD-Wandler die CPU, schreibt das Ergebnis in ein Register und startet sich automatisch wieder selbst. Die angeschlossene Taste unterbricht beim Schließen und Öffnen die CPU, schreibt den aktuellen Tonhöhenwert in den Timer und startet (Taster geschlossen) oder stoppt (Taster offen) die Ausgabe der NF über den Ausgangs-Pin. Die CPU wird schlafen gelegt und nur durch die beiden Interrupts aufgeweckt. Der Interrupt wird in der Interrupt-Service-Routine bearbeitet, anschließend wird die CPU wieder schlafen gelegt. Beide Interrupts sind voneinander völlig entkoppelt und können zu beliebigen Zeiten auftreten, ohne sich gegenseitig zu stören.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 09: Hardware Der Kondensator am Poti (10 nF) dient zum Abblocken von Wechselspannungsund HF-Einstreuungen. ADC-Wandler sind da sehr empfindlich.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 09: Quellcode Im Schaltbild ist das Poti an Pin2 (ADCKanal 3) angeschlossen.
In den Registern rTonA und rTonB legt der AD-Wandler sein Ergebnis ab.
Die Tastatur löst wieder den PCINT0 Interrupt aus.
Der Interrupt-Vektor „ADC conversion complete“ wird angesteuert, wenn der ADC mit einer Wandlung fertig ist.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 09: Quellcode II Der Tasten-Interrupt stellt fest, ob die Taste gedrückt ist. Die Tonhöhe aus dem Register in den Timer übernehmen. Bei gedrückter Taste NF an OC0A und OC0B einschalten. Bei losgelassener Taste NF ausschalten. Der ADC-Interrupt liest das Umwandlungsergebnis. Teilt den Wert durch vier und schreibt den Wert für CompA in rTonA. Teilt diesen Wert durch 2 und schreibt ihn in rTonB.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 09: Quellcode III Der Rest des Quellcodes ist ähnlich wie bei anderen Beispielen (Stapel einrichten, Einstellen und starten des Timers, Interrupts ermöglichen, Schlafmodus einstellen Schlaf-Loop. Nur der Start des AD-Wandlers zu Beginn im Hauptprogramm ist neu. Zuerst wird der Eingangstreiber von PB3 abgeschaltet, beim Betrieb als AD-Kanal wird der nicht gebraucht (Strom und Noise ein sparen). Dann wird die Referenzspannung (Betriebsspannung) und der zu messende Kanal ADC3 ausgewählt. Im Kontrollport ADCSRB wird als Triggerquelle der Freilauf gewählt. Im Kontrollport ADCSRA wird der ADC gestartet, die Triggerung eingeschaltet, der Interrupt ermöglicht und der Teiler für die Wandlung aus 128 eingestellt (langsam).
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 10: Morsebake • In dieser Anwendung wird von einem ATtiny13 auf Knopfdruck ein voreingestellter Morsetext ausgegeben. • Text, Gebegeschwindigkeit und Tonhöhe werden vor dem Programmieren im Quelltext eingestellt. • Die Tastenüberwachung und die NF-Ausgabe erfolgt rein Timer- und Interrupt-gesteuert. • Bei der Timer-Ausgabe von Tönen (NF an) und Pausen (NF aus) wird die Anzahl der TimerDurchläufe gezählt (Anzahl der NF-Halbwellen). Dadurch wird die Dauer des Tons bzw. der Pause überwacht und nach Ablauf des Zählers die nächstfolgende Aktion eingeleitet.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 10: Einstellung der Parameter
Schaltung: Die LED diesmal an PB2, der Lautsprecher an PB1 und die Taste wieder an PB4. Die Gebegeschwindigkeit und die Tonhöhe werden als Konstanten vorgegeben. Daraus abgeleitete Konstanten errechnet, die bei der Ablaufsteuerung eingesetzt werden. Die sich ergebenden Werte werden überprüft (Überläufe, Unterläufe), damit beim Assemblieren schon erkannt wird, ob etwas aus dem Ruder läuft.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 10: Auszug Morsetabelle Die Morsetabelle ist im Programmspeicher in Binärform abgelegt. Hier die ASCII-Zeichen von 30 bis 4F hex. Jedes Morsezeichen benötigt zwei Byte: im ersten ist die Abfolge von kurzen und langen Tönen kodiert, (bei einer „0“: 0b11111000 = 5 mal lang), im zweiten die Anzahl kurzer und langer Töne zusammen (bei einer „0“: 5). Verkehrszeichen sind auf ASCIIZeichen gelegt, für die es kein MorseÄquivalent gibt (z.B. Verkehrsanfang ist ASCII-Zeichen <).
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Beispiel 10: Textablage Der auszugebende Text ist auch im Programmspeicher abgelegt. Er wird aus dem Programmspeicher ab dem Label MorseText: ausgelesen, in Morsezeichen übersetzt und ausgegeben. Wenn das Zeichen 0 erreicht wird, ist der Text zu Ende (Null-terminierter String) und die Ausgabe wird beendet.
Aufnahme abspielen: hier klicken =>
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13 Eine Einführung in Aufbau, Funktionsweise, Programmierung und Nutzen von Mikroprozessoren
Teil 8: Ein Sinusgenerator mit AVR-Timer
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Sinus mit Timer und RC-Filter Aufgabe: Einen Sinus erzeugen, hier: 1750 Hz Einfache Lösung: Timer mit CTC, toggeln des OCR0A- oder OR0B-Ausgangs und RC-Filter zum Sieben der Oberwellen nachschalten
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Nachteile des Verfahrens • Schaltfrequenz des Ausgangs = 2 * Ausgangsfrequenz • Erste Oberwelle des Rechteck-Signals = 3 * Grundwelle • Schlechte Dämpfung von RC-Filtern bei 3 * Grundfrequenz • Für ausreichende Dämpfung ist größeres RC-Glied notwendig • Dadurch schon hohe Dämpfung bei der Nutzfrequenz
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Besser: Sinusgenerator mit PWM • Prinzip der Pulsweitenmodulation: Die Zeitdauer im High-Zustand und die Zeitdauer im Low-Zustand bei einem Rechteckgenerator sind variabel. Sind beide gleich, ergibt sich am Ausgang im Mittel die halbe Betriebsspannung. _____ _|
_____ |_____|
_____ |_____|
__ |_____|
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Prinzip der Pulsweitenmodulation • Ist der High-Zustand kürzer als der Low-Zustand, ergibt sich eine niedrigere mittlere Spannung: _ _ _ _ _| |________| |________| |_________| |_ • Ist der High-Zustand länger als der Low-Zustand, ergibt sich eine höhere mittlere Spannung: ________ ________ _________ ___ _| |_| |_| |_| • Mit Pulsweitenmodulation lassen sich analoge Spannungen zwischen Null und Betriebsspannung erzeugen. • Die Auflösungsgenauigkeit der „Analogspannung“ ist nur von der Schrittzahl abhängig.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
Timer als PWM-Generator • Die Anzahl Schritte der PWM ergibt sich aus dem Taktgenerator (hier: 9,6MHz), der gewünschten Auflösung (hier: 32) und der gewünschten Frequenz (hier: 1750). n = 9.600.000 / 32 / 1750 = 171,43 • Anzahl Schritte der PWM wird mit dem CTC-Wert festgelegt (CTC = Clear Timer on Compare). Hier erfolgt das bei 171. Dieser Wert kommt nach Compare Match A. • Die PWM-Werte müssen zwischen 0 und 171 variieren. Diese Werte kommen nach Compare Match B. • Compare Match A setzt den Zähler auf Null und den Ausgang auf High. Bei Erreichen von Compare Match B wird der Ausgang Low. Je größer Compare Match B, desto höher die Spannung.
Programmierung von ATMEL AVR Mikroprozessoren am Beispiel des ATtiny13, http://www.avr-asm-tutorial.net
PWM-Werte für einen Sinus Sinus-PWM mit Schrittweite 171 180 160 140
; ********************************************************** ; * Mehr Action: LED-Blinker mit Verzoegerung * ; * (C)2005 by [email protected] * ; ********************************************************** ; .INCLUDE "tn12def.inc" ; ; Schaltbild: ; ATMEL ATtiny13 ; ___ ____ ; ___ 1/ |_| |8 ;+5 Volt O--|___|----|Res Vcc|----O + 5 Volt ; | | ; |PB3 PB2| ; | |6 ___ ; |PB4 PB1|--|___|--|<|--O + 5 Volt ; 4| | 330 LED ; 0 Volt O----|Gnd PB0| ; |__________| ; ; Benutzte Register ; ; ZH:ZL (R31:R30) ist Zaehlregister fuer Verzoegerung ; ; Programm ; sbi DDRB,0 ; PB1 ist Ausgang loop: sbi PORTB,0 ; Ausgang auf Eins (LED ist aus) loop1: sbiw ZL,1 ; Ziehe von ZH:ZL eine 1 ab brne loop1 ; wenn noch nicht Null, wiederhole cbi PORTB,0 ; Ausgang auf Null (LED ist an) loop2: sbiw ZL,1 ; Ziehe von ZH:ZL eine 1 ab brne loop2 ; wenn noch nicht Null, wiederhole rjmp loop ; und das Ganze von vorne ; ; End of source code ;
; ********************************************************** ; * Mehr Action: LED-Blinker mit Verzoegerung * ; * Original fuer ATtiny13, fuer ATtiny12 umgeschrieben * ; * (C)2005 by [email protected] * ; ********************************************************** ; .INCLUDE "tn12def.inc" ; ; ; Anmerkung zur Umstellung des Programms von ATtiny13 auf ATtiny12: ; ; Der ATtiny12 beherrscht die Wort-Instruktionen adiw und sbiw nicht. ; Sie muessen daher ersetzt werden durch entsprechende Byte-Operationen. ; gavrasm hat hier einen Bug und erkennt die ungueltige Operation nicht! ; ; Der Chip mit der Nummer 2 ist fertig mit diesem Programm gebrannt. ; ; Schaltbild: ; ATMEL ATtiny12 ; ___ ____ ; ___ 1/ |_| |8 ;+5 Volt O--|___|----|Res Vcc|----O + 5 Volt ; | | ; |PB3 PB2| ; | |6 ___ ; |PB4 PB1|--|___|--|<|--O + 5 Volt ; 4| | 330 LED ; 0 Volt O----|Gnd PB0| ; |__________| ; ; Benutzte Register ; ; ZH:ZL (R31:R30) ist Zaehlregister fuer Verzoegerung ; ; Konstanten ; .EQU bBlink = 1 ; blinkendes Portbit ; ; Programm ; sbi DDRB,bBlink ; PB1 ist Ausgang loop: sbi PORTB,bBlink ; Ausgang auf Eins (LED ist aus) loop1: subi ZL,1 ; Ziehe von ZL eine 1 ab breq loop1a ; Null geworden, teste auf MSB=Null brcc loop1 ; kein Carry, weiter Loop1 bis Null dec ZH ; MSB eins abziehen rjmp loop1 ; weiter mit loop1 bis Null loop1a: tst ZH ; teste MSB auf Null brne loop1 ; noch nicht Null, weiter mit loop1 bis Null cbi PORTB,bBlink ; Ausgang auf Null (LED ist an) loop2: subi ZL,1 ; Ziehe von ZL eine 1 ab breq loop2a ; Null, pruefe MSB auf Null brcc loop2 ; kein Carry, weiter loop2 bis Null dec ZH ; MBN eins abziehen rjmp loop2 ; weiter mit loop2 bis Null loop2a: tst ZH ; teste MSB auf Null brne loop2 ; noch nicht Null, weiter mit loop2 bis Null rjmp loop ; und das Ganze von vorne ; ; End of source code ; http://www.avr-asm-tutorial.net/avr_de/praesentation/sourcecode/bsp04_blink_langsam_tn12.asm1/20/2009 7:48:21 PM
; ************************************************************** ; * Sinus-Generator mit einem AVR-Timer/Counter als Pulsweiten-* ; * Modulator, einem 3-fach-RC-Filter und einem Emitterfolger * ; * Geschrieben fuer einen ATtiny13 mit 9,6MHz internem Takt- * ; * oszillator, Ausgang ist OC0B (PB1, DIL-Pin 6), Version 1, * ; * Sinus eingestellt auf 1754,4 Hz, Aufloesung 32 Stufen, * ; * CTC=171, RC-Filter: R=2k2, C=2n2, Emitterfolger: Re=1k, * ; * BC183B, hFE=260 * ; * (C)2005 by http://www.avr-asm-tutorial.net * ; ************************************************************** ; .NOLIST .INCLUDE "tn13def.inc" .LIST ; ; *********************************************** ; SCHALTBILD ; *********************************************** ; ; 10k ___________ ; ___ 1/ |8 ;+5V O-|___|-|RES VCC|--O+5V +5V ;T-Kurz __ 2| ATMEL |7 O ; |--O O--|PB3 PB2|--O PTT-Ausgang |C ;T-Lang __ 3| ATtiny13 |6 ___ ___ ___ B|/ BC ; |--O O--|PB4 PB1|--|___|-O-|___|-O-|___|-O--| 183B ; 4| PDIP |5 R | R | R | |\ E ; |--O--|GND PB0|-- --- --- --- O--O NF; |____________| C--- C--- C--- | aus ; | | | +-+ ; --- --- --- | |Re ; +-+ ; | ; --; ; *********************************************** ; EINSTELLUNGEN ; *********************************************** ; ; Anderbare Einstellungen, maximum = 37 Sekunden! ; .EQU cKurz = 2 ; 2 Sekunden Dauer fuer kurzes Signal .EQU cLang = 10 ; 10 Sekunden Dauer fuer langes Signal .EQU bKurz = 3 ; Portbit fuer Taster kurze Signale .EQU bLang = 4 ; Portbit fuer Taster lange Signale .EQU bTx = 2 ; Portbit fuer PTT-Ausgang aktiv high .EQU bPwm = 1 ; Portbit fuer PWM-Ausgang, nicht aenderbar ; ; *********************************************** ; REGISTER-DEFINITIONEN ; *********************************************** ; ; verwendet: R0 fuer Lesen aus Sinustabelle ; frei: R1..R14 .DEF rSreg = R15 ; Status retten bei Interrupts .DEF rmp = R16 ; Multipurpose Register ausserhalb Int .DEF rimp = R17 ; Multipurpose Register innerhalb Int ; frei: R18..R23 .DEF rDauerL = R24 ; Signaldauer-Zaehler, LSB .DEF rDauerM = R25 ; dto., MSB ; frei: X, Y = R26..R29 ; verwendet: Z = R31:R30 fuer Tabelle-Lesen ; ; *********************************************** ; KONSTANTEN ; *********************************************** ; ; Nicht veraenderbare Konstanten ; .EQU cClock = 9600 ; Prozessortaktfrequenz in kHz .EQU cCtc = 171 ; TC0-CTC .EQU cKSin = cKurz * 1754 ; Anzahl Sinus bei kurzem Signal .EQU cLSin = cLang * 1754 ; Anzahl Sinus bei langem Signal .IF (cKSin>65536)||(cLSin>65536) ; Falsche Parameter .ERROR "Zeit zu lang!" .ENDIF ; ; Reset- and interrupt vectors ; .CSEG .ORG $0000 rjmp main ; Reset Vektor reti ; INT0-Interrupt, nicht verwendet rjmp PcInt ; PIN-Change-Interrupt rjmp Tc0OvfInt ; TC0-Overflow-Interrupt reti ; EERDY-Interrupt, nicht benutzt reti ; ANACOMP-Interrupt, nicht benutzt reti ; TC0COMPA-Interrupt, nicht benutzt reti ; TC0COMPB-Interrupt, nicht benutzt reti ; WDT-Interrupt, nicht benutzt reti ; ADC-Interrupt, nicht benutzt ; ; ***************************************************** ; INTERRUPT-SERVICE-ROUTINEN ; ***************************************************** ; ; Pin-Change-Interrupt, startet Signal ; PcInt: in rSreg,SREG ; Status sichern tst rDauerL ; Signal schon gestartet? brne PcIntR ; ja, ignoriere Signal tst rDauerM ; teste auch MSB brne PcIntR ; schon gestartet sbis PINB,bKurz ; Kurzes Signal auf 1? rjmp PcIntK ; nein, starte kurze Signaldauer sbic PINB,bLang ; Langes Signal auf 0? rjmp PcIntR ; kein aktives Signal, ignoriere Int ldi rDauerM,HIGH(cLSin) ; setze Zaehler auf Lang ldi rDauerL,LOW(cLSin) rjmp PcIntS ; Starte Signalausgabe PcIntK: ldi rDauerM,HIGH(cKSin) ; starte kurzes Signal ldi rDauerL,LOW(cKSin) PcIntS: ldi rimp,1<
*
cq-dl-Beiträge zu AVR-Mikrocontrollern
Pfad: Home => cq-dl-Beiträge
cq-dl-Beiträge zu ATMEL-AVRMikrocontrollern Zielsetzung der Beiträge In den letzten Monaten kommen in vielen Bastelanleitungen Prozessoren des Typs AT90S von ATMEL (http://www.atmel.com) vor. Sie erfreuen sich also wachsender Beliebtheit insbesondere auch in Amateur-Kreisen. Woher kommt diese Popularität? Was muss man beachten, wenn man diese Prozessoren in eigenen Schaltungen nutzen will? Was steckt in diesen schwarzen Kästchen? In der Serie wird für den Anfänger erklärt, was es mit diesen Chips auf sich hat und wozu sie zu gebrauchen sind.
Überblick über die Beiträge Die folgenden Seiten enthalten die Beiträge, die für die Mitgliederzeitschrift des Deutschen AmateurRadio-Clubs DARC geschrieben wurden. Hier der Überblick: 1. Teil I: Hardware der AVR-Mikrcontroller ❍ Was ist ein AVR-Prozessor? ❍ Allgemeine elektrische Eigenschaften ❍ Welche AVR-Typen gibt es? ❍ Multifunktionale Pins ❍ Quellen 2. Teil II: Software der AVR-Mikrocontroller ❍ Software selbstgemacht ❍ Assembler mit Editor ❍ Symbole und Instruktionen ❍ Assemblieren ❍ Simulieren ❍ Hilfen zum Lernen 3. Teil III: Programmieren von AVR-Mikrocontrollern ❍ Serielles Programmieren ❍ Programmieren in der Schaltung ❍ Programmieren auf Boards ❍ Programmieren ohne Boards 4. Teil IV: Beispielanwendung AVR-CW-Geber ❍ Aufgabe: Demonstration der Leistungsfähigkeit ❍ Funktionen der Schaltung ❍ Aufbau der Hardware ❍ Aufbau der Software ❍ Bedienung über Einstellmenue ❍ Anhang: Quellcode der Software Wer die gesamten vier Teile mit allen Grafiken von dieser Webseite auf seinen Rechner laden will, findet bei diesem Link eine gezippte Datei. Diese sollte mit einem Entpackprogramm ausgepackt werden. Dabei müssen unbedingt die Pfade im gezippten Archiv beibehalten werden, da sonst Dateien überschrieben werden. Besucher auf dieser Seite seit 06.01.2002:
cq-dl-Beiträge zu ATMEL-AVRMikrocontrollern Teil I: Hardware von ATMEL-AVR-Mikrocontrollern Was ist ein AVR-Prozessor? AVR-Prozessoren sind kleine, fertig aufgebaute Rechner, die schon alles haben, was einen erwachsenen Computer ausmacht: eine Recheneinheit (CPU), nur-lesbaren-Programmspeicher, statischer Speicher, nichtflüchtiger und wiederbeschreibbarer Speicher (EEPROM), eine Unterbrechungssteuerung (Interrupts), den Taktoszillator sowie zahlreiche weitere Hardwarekomponenten wie Parallelports, Timer/Zähler, asynchrone und synchrone serielle Schnittstellen, Pulsweiten-Modulatoren (Digital-Analog-Wandler), Analog-Digital-Wandler, Echtzeituhren. Weil alles schon drin ist im Chip und nur noch ein paar wenige externe Pins zu beschalten sind, sind die externen Beschaltungen sehr einfach. Typisch für AVR-Prozessoren ist der eingebaute wiederbeschreibbare Programmspeicher. Da kein externes EPROM nötig ist und der Flash-Speicher im seriellen Übertragungsmodus ohne besondere Programmierspannung beschreibbar ist, kann der Chip sogar innerhalb der fertigen Schaltung programmiert werden. Besonders bequem also für den Gelegenheitsprogrammierer, der sich langsam an die Programmierung herantraut und viele Versuche bis zum Erfolg braucht. Weil Programmspeicher und etwas RAM-Speicher schon im Chip eingebaut sind, wird nach außen hin kein Adress- und Datenbus gebraucht. Das macht es möglich, die kleinsten Prozessoren in einem nur achtpoligen Gehäuse unter zu bringen und stellt die meisten Pins für Ein- und Ausgaben zur Verfügung. In der Schaltung entfällt die Verdrahtung der Busanschlüsse, das macht die Schaltung einfach und hält die extern erforderlichen Komponenten in engen Grenzen. Trotz der 8-bittigen internen Architektur sind die Prozessoren auch bei 8 bis 10 MHz Takt sehr schnell. Dazu trägt der eigens optimierte Befehlssatz (Reduced Instruction Set, RISC), die 16-bittige Befehlsarchitektur und der von Speicherzugriffen getrennte interne Zugriff auf den Programmspeicher bei. Bis auf wenige Ausnahmen braucht jeder Befehl nur einen Taktzyklus und der Zugriff auf den Programmspeicher erfolgt schon während der Abarbeitung des vorausgegangenen Befehls (Fetch during execution). Zeitkritische Anwendungen können die Möglichkeit der Unterbrechung nutzen, so dass die Anforderung z. B. durch ein Hardware-Ereignis nach optimal kurzer Zeit bearbeitet wird. Zahlreiche Interrupts sind konfigurierbar, die Bearbeitung gleichzeitig eintreffender Interrupts erfolgt nach einer vorgegebenen Priorität. Zur Geschwindkeitserhöhung trägt auch die Möglichkeit bei, das interne RAM als Zwischenspeicher (Stapel, Stack) zu nutzen. Rein interruptgesteuerte Programme können den Chip in den Schlafmodus versetzen, mit gut halbiertem Strombedarf, und werden auf Anforderung schnell wieder geweckt.
Allgemeine elektrische Eigenschaften Die Chips lassen sich mit einer Betriebsspannung zwischen 4 und 6 Volt betreiben, manche Ausführungsformen auch mit 2,7 bis 6 Volt. Sie brauchen bei 8 MHz Takt und 5 Volt Betriebsspannung zwischen 10 und 12 mA. Im Schlafmodus mit Weckfunktion sinkt der Verbrauch auf weniger als die Hälfte. Auch eine niedrigere Betriebsspannung und eine niedrigere Taktrate spart Strom. Sie sind also recht sparsam zu betreiben. Beim Hochfahren der Betriebsspannung wird ein Power-On-Reset durchgeführt, die Hardware wird auf Anfangswerte zurückgesetzt und die Programmbearbeitung beginnt bei Adresse 0000. über einen externen Resetpin kann ein Neustart ebenfalls ausgelöst werden. Der Takt kann an zwei Pins extern über einen Quarz oder Keramikschwinger erzeugt werden. Er kann aber auch aus einem eigenständigen externen Generator zugeführt werden. Die Taktung mit einem internen RCGenerator ist ebenfalls möglich, allerdings ist die Taktfrequenz dann sehr abhängig von der Betriebspannung und der Temperatur. Pins, die als Ausgabeports konfiguriert sind, können je nach Betriebsspannung zwischen 10 und 20 mA treiben, so dass z.B. LEDs über einen Strombegrenzungswiderstand direkt gegen die Betriebsspannung getrieben werden können. Die Abbildung 1 (Anzeige als PDF-Datei) zeigt einen der einfachsten AVRs in seiner Außenbeschaltung.
Die RC-Kombination am Reseteingang verzögert den Reset beim Hochfahren der Betriebsspannung, der Widerstand vor dem Reseteingang begrenzt den Strom aus dem Elko in den Pin insbesondere beim Abschalten der Betriebsspannung, die Diode sorgt für einen sicheren Reset beim kurzen Absenken der Betriebsspannung. Der Kondensator am Betriebsspannungsanschluss glättet die Schaltspitzen, die durch die Taktung im Chip entstehen und sorgt für weniger HF auf der Versorgungsleitung. Der Taktoszillator ist mit einem Standardquarz bestückt, die beiden Kondensatoren helfen beim Anschwingen des internen Oszillators.
Welche AVR-Typen gibt es? Die AVR-Typen unterscheiden sich hinsichtlich des verfügbaren Speichers (z.B. von 2k bis 8k), der nach außen hin verfügbaren Ein- und Ausgabepins (z.B. 3 bis 48 Pins) und der eingebauten Hardwarekomponenten. Drei beliebte Typen sind in der Tabelle 1 charakterisiert. Tabelle 1: Ausgewählte AVR-Typen und ihre Charakteristika AVR-Typ
AT90S2323 AT90S2313 AT90S8515
Anschlusspins
8
20
40
max. Taktrate MHz
8
10
8
Programmspeicher K Words 2
2
8
Statisches RAM Bytes
128
128
512
EEPROM-Speicher Bytes
128
128
512
Ein-/Ausgabe-Pins
3
15
32
Wer noch mehr I/O-Pins oder Speicher braucht, wird bei den AT-MEGA-Typen fündig, die es allerdings nur in besonderen Gehäuseformen gibt. Die eingebauten Hardwarekomponenten dieser drei vorgestellten Typen sind in der Tabelle 2 aufgelistet. Tabelle 2: Die Hardwarekomponenten ausgewählter AVR-Typen AVR-Typ
AT90S2323 AT90S2313 AT90S8515
8-Bit-Timer
1
1
1
16-Bit-Timer/Counter
-
1
1
Async. (UART)
-
1
1
Pulsweitenmodulator (PWM) -
1
2
Analogvergleicher(AnaComp) -
1
1
Wer synchrone serielle Schnittstellen, Analog-Digital-Wandler oder Real-Time-Clocks braucht, wird bei den anderen Typen der Serie fündig.
Multifunktionale Pins Typisch ist, dass die Ein- und Ausgabepins verschiedene Aufgaben übernehmen können, je nachdem welche Hardwarekomponenten per Software aktiviert werden. Sie können einfache Ein- oder AusgabePorts bilden. Wenn sie als Eingabepins geschaltet sind, kann ein Pull-Up-Widerstand softwaremäßig zugeschaltet werden. über einige Ein- und Ausgabepins erfolgt die serielle Programmierung des ProgrammFlash-Speichers (SCK, MOSI und MISO genannt), wenn der Reset-Pin aktiviert ist. Die Doppelfunktion der Pins ist in Abbildung 2 ( Anzeige als PDF-Dokument) am Beispiel des 20-poligen 2313 gezeigt.
Pins, die für die interne Funktionalität benötigt werden, stehen als allgemeine Ein- und Ausgabe-Pins nicht mehr zur Verfügung. Die Planung der Beschaltung des AVR sollte daher von Anfang an diese benötigten Funktionen berücksichtigen. Zur Not muss der nächstgrößere Typ herhalten, wenn die Funktionalität oder die Zahl der I/O-Pins nicht mehr ausreicht.
cq-dl-Beiträge zu ATMEL-AVRMikrocontrollern Teil II: Software von ATMEL-AVR-Mikrocontrollern Software selbstgemacht Weil AVR-Prozessoren für eine ganz bestimmte Hardware eingesetzt werden sollen, ist die Software auf die Hardware-Umgebung eng anzupassen. Das setzt voraus, dass die Software speziell für die gewählte Schaltung geschrieben wird. Wer so hardware-nah noch nie programmiert hat, wird zu Beginn eine größere Mühe haben, wenn er sich an die Programmierung erst langsam gewöhnen muss. Das wird auch kaum einfacher, wenn man statt der Assemblersprache in C oder in Basic programmiert. Das hat zwar den Vorteil, dass man ein paar Sprachelemente schon kennt, aber so richtig hardware-nah kommen Hochsprachen nicht ran. Wer die Fähigkeiten optimal ausreizen will, sollte sich der Mühe unterziehen, Assembler zu lernen. Mit den AVR ist das besonders erleichtert, weil man den Prozessor in der Schaltung selbst erproben kann und weil es einige einfache Werkzeuge gibt, die Abläufe auf dem Trockenen zu testen. Hier werden nur einige Grundbegriffe der Softwareentwicklung für AVRs behandelt. Es gibt zahlreiche umfangreiche Hilfen, vom Buch bis zum Internet. Die werden am Ende kurz besprochen. Wie das fertig assemblierte Programm in den Chip kommt, erklärt der nächste Teil dieser Serie.
Assembler mit Editor Assembler programmieren ist zunächst das Schreiben von reinem Text in eine reine Textdatei. Die folgenden Zeilen versetzen die acht Anschlusspins von Port B in einem AVR vom Typ AT90S2313 in den lesenden Zustand (alle Pins sind Eingänge) und schalten 10k-Pull-Up-Widerstände an diese Eingänge. Beim Lesen sollten also alle Eingänge eine logische Eins zeigen. (=> Screenshot mit dem Beispielprogramm im AVR-Editor wavrasm von ATMEL) Dieser lesbare Text wird in einer Textdatei gespeichert. Er ist als solches noch nicht brauchbar für den AVR. Erst nach dem Übersetzen mit einem Assembler wird daraus ein maschinen-verständliches Programm, das in den Chip gefüttert werden kann. Als Editoren eignen sich sehr einfache Programme (z.B. Notepad für Windows oder KWrite für Linux) oder spezielle Editoren wie der von ATMEL verfügbare Assemblereditor wavrasm, der Editor im Studio von ATMEL bzw. der sehr schön gestaltete AVR-Assembler-Editor von Tan Silliksaar. Spezielle Editoren für AVR-Assembler erkennen z.B. die Instruktionen oder prüfen während der Eingabe nach, ob ein Symbol schon definiert ist und färben die Wörter dann anders ein. Auf diese Weise findet man schneller Schreibfehler (das sind ca. 95% der Fehler in Assembler; die verbleibenden 5% sind Denkfehler und viel schwerer zu finden). (=> Screenshot eines Programmes mit dem ATMEL Studio-Editor) (=> Screenshot mit dem Beispielprogramm im AVR-Editor von Tan Silliksar)
Symbole und Instruktionen Der Text ist trotz der Erläuterungen, die immer mit einem Semikolon beginnen, aber auch so kaum lesbar. Deshalb gibt es die Möglichkeit, statt der hexadezimalen Zahlen und der Konstruktion R16 auch zu schreiben: ; Beispielprogramm in Assembler, leserlicher ; ; Definitionen .EQU PORTB = $18 .EQU DDRB = $17 .EQU PINB = $16 .DEF MeinRegister = R16 ; Programm clr MeinRegister out DDRB,MeinRegister ser MeinRegister out PORTB,MeinRegister in MeinRegister,PINB ; ; Ende des leserlichen Programmes
Der Assembler versteht also Symbole. Symbole (PORTB) sind für Menschen etwas leichter zu merken als Hexadezimalzahlen ($18). Allerdings muss dem Assembler das alles erst mitgeteilt werden (mit .EQU), von selbst weiss er das nicht. Dass PORTB die Ein- und Ausgabepins PB0 bis PB7 am AVR meint, muss der hardwarenahe Programmierer erst mal wissen. Dass DDRB das Datenrichtungsregister von Port B meint, auch. Wenn man dort Nullen (clr MeinRegister steht für CLEAR und bedeutet, dass alle acht Bits im Register zu Null gesetzt werden) hinschreibt, dann sind alle Ausgangstreiber an diesen Pins ausgeschaltet. Wenn man dann noch Einsen nach PORTB schreibt (ser MeinRegister steht für "SET Register" und setzt alle Bits im Register auf 1), sind die Pull-Up-Widerstände eingeschaltet und ziehen die Eingänge auf logisch 1. Wenn nun noch die Eingänge des Ports (PINB) eingelesen werden (in), dann hat MeinRegister anschliessend den Wert 1111.1111 binär, $FF in hexadezimal oder 255 in dezimal. Mit diesem großartigen Programm, das man im Quelltext über diesen Link downloaden kann, kann man bis dahin nur wenig anfangen, es zeigt aber folgendes: 1. Ob ein Pin sich als Eingang oder als Ausgang verhält, hängt alleine davon ab, was das Programm gerade einstellt. Das kann auch im Programmablauf wechseln, wenn man bidirektionale Ports braucht. 2. Alle Hardware wird in sogenannten Ports kontrolliert. Das sind Speicher, die in Abhängigkeit von ihrem Inhalt das gesamte Verhalten der Hardware bestimmen. 3. ähnlich wie die Eingangs- und Ausgangspins lassen sich asynchrone Schnittstellen, Analogvergleicher, u.v.a.m. vom Programmierer ein- und ausschalten oder sonstwie kontrollieren. 4. Ein Assemblerprogramm kann mit Symbolen zwar etwas leserlicher gestaltet werden, aber die Prozessorinstruktionen (hier: clr, out, ser und in) lassen sich nicht ersetzen. Sie sind Symbole derjenigen Befehlsabläufe, die im Prozessor fest vorgegeben sind. Die muss man halt nachschlagen oder auswendig lernen.
Assemblieren Das Übersetzen von Symbolen in hexadezimale Zahlenwerte und das Übersetzen von Instruktionen in binäre Bitfolgen erledigt der Assembler. (=> Screenshot: Aufruf des Assemblers mit dem Beispielprogramm) Der Assembler ist ein Programm, das unsere Textdatei BEISPIEL.ASM als Futter kriegt und dafür eine Reihe neuer Dateien ausspuckt: 1. BEISPIEL.HEX: Diese Datei enthält die Bitfolge für den Prozessor, codiert im sogenannten Intel-Hex-Format. Das ist auch mit einem Texteditor lesbar, aber darf unverstanden bleiben. Damit wird anschließend die Programmiersoftware gefüttert, die versteht das. (=> Screenshot: Assembliertes Assemblerprogramm im Intel-Hex-Format) 2. BEISPIEL.LST: Diese Datei enthält zusätzlich zu dem eingegebenen Text auch die hexadezimale Übersetzung desselben in Instruktionen für den Prozessor. Die kann man ansehen, muss man aber nicht. (=> Screenshot: Gelistetes Assemblerprogramm nach dem Assemblieren) 3. BEISPIEL.OBJ: Diese Datei wird benötigt, um das Programm zu simulieren. Das kommt jetzt gleich dran. Sie ist nur maschinenlesbar.
Simulieren Oft muss man einen komplexen Ablauf programmieren. Dann lohnt es sich, diesen Ablauf in Päckchen zu programmieren und die einzelnen Päckchen auf dem Trockenen zu erproben. Das Trockendock für AVR Assembler heißt Studio und wird von ATMEL kostenlos zur Verfügung gestellt. Das Studio wird mit der Object-Datei gefüttert, ein Zielchip ausgewählt und seine Taktfrequenz vorgewählt. In dem erscheinenden Quelltext von Beipiel.asm lassen sich sogenannte Breakpoints setzen. Dann wird der Ablauf entweder im Einzelschrittverfahren oder mit "Go" gestartet. Nach jedem Schritt bzw. bei Erreichen eines Breakpoints bleibt der Ablauf stehen. Jetzt können in Ruhe Register oder Ports auf ihren Inhalt hin untersucht werden. Im Prozessor-View kann die abgelaufene Zeit abgelesen werden, der Inhalt der Prozessorflags kann inspiziert werden, usw. (=> Screenshot: Das Beispielprogramm im Studio nach Bearbeiten der ersten beiden Instruktionen) Nach Bearbeiten der ersten vier Befehle hat sich am Registerinhalt von R16 und beim Port B etwas getan. Im Studio wird mit Rot die Aufmerksamkeit des Simulierers erregt. (=> Screenshot: Das Beispielprogramm nach Bearbeiten der ersten vier Instruktionen) Selbstverständlich kann der Inhalt von Registern oder von Port-Bits manuell geändert werden, wenn man bestimmte Reaktionen der Software provozieren will. Es gibt noch viele weitere Manipulationsmöglichkeiten, auf die hier nicht im Detail eingegangen werden soll. Damit stehen mächtige Werkzeuge zur Verfügung, mit denen sich erste Schritte machen lassen.
; Beispielprogramm in Assembler, fast unleserlich ; clr R16 out $17,R16 ser R16 out $18,R16 in R16,$16 ; ; Ende des fast unleserlichen Beispielprogrammes
cq-dl-Beiträge zu ATMEL-AVRMikrocontrollern Teil III: Programmieren von ATMEL-AVR-Prozessoren Nach dem Entwurf der Schaltung und nach dem Schreiben und Testen der Software im Simulator muss nun der binäre Code in den AVR chip. Jeder AVR verfügt als Programmspeicher über eine gewisse Menge Flash Memory, das sich programmieren und jederzeit wieder elektrisch löschen und wiederbeschreiben lässt. Es gibt grundsätzlich zwei Möglichkeiten zur Programmübertragung in den Flash-Speicher: serielles oder paralleles Programmieren.
Serielles Programmieren Für professionelle Herstellung ist paralleles Programmieren von Vorteil. Die einfachste Form des Programmierens, und damit die erste Wahl des Amateurs, ist das serielle Übertragen in den Programmspeicher. Serielles Programmieren bedeutet, dass die Programmbits nacheinander, gesteuert durch einen von außen angelegten Takt am Pin SCK, über den Pin MOSI in ein internes Schiebregister eingeschoben und von dort in das Flash Memory kopiert werden. Damit der Programmiervorgang beobachtet, nach Abschluss verifiziert und der Inhalt des Flash gelesen werden kann, ist ein Datenausgang MISO vorhanden. Beim Programmieren werden also drei Signalleitungen benötigt: der Takt SCK, die Eingabedaten MOSI und die Ausgabedaten MISO. Damit dafür nicht extra drei Pins verloren gehen, wechseln drei Pins beim Reset ihre Funktion. Ist Reset aktiv, werden sie mit dem seriellen Programmier-Interface verbunden. Der Programmiermodus wird aber erst dann aktiv, wenn ein entsprechendes Befehlswort eingeschoben wird.
Programmieren in der Schaltung Bemerkenswert ist dabei, dass das serielle Programmieren weder für das Löschen noch für den Programmiervorgang erhöhte Spannungen benötigt. Man kann daher das Programmieren in der Schaltung selbst durchführen. Allerdings ist dabei zu beachten, dass die dafür drei benötigten Pins entweder a. nur für diesen Zweck vorbehalten sind, b. über Widerstände von mindestens 1 k von der Restschaltung abgetrennt werden, oder c. über eine Multiplexschaltung nur bei aktivem Reset mit dem Programmiersignal verbunden werden. Diese Beschaltung wird als ISP (In-System-Programming) bezeichnet. Die zwei Möglichkeiten b) ohne Multiplexer und c) mit Multiplexer werden im Schaltbild dargestellt (PDF-Version des Schaltbildes).
Die Möglichkeit b. ist einfach zu verstehen: die Programmierimpulse liegen parallel zur Schaltungselektrik an. Das geht natürlich nur, wenn über die Schutzwiderstände nicht allzu hohe Ströme angefordert werden und wenn die Schaltung selbst auf die wilden Programmierimpulse nicht allergisch reagiert. Bei der dargestellten Möglichkeit c) ist der AVR mit den auch zur Programmierung verwendeten Pins mit der Schaltung verbunden, wenn /Reset auf High liegt. Aktiviert das Programmiergerät den / Reset-Eingang, werden die Programmierpins mit dem Programmierstecker verbunden und die Programmierung kann erfolgen. Hierbei sind die Ströme auf die entsprechenden Grenzbelastungen des Multiplexers begrenzt. Die Programmierpulse können hier nicht stören.
Programmieren auf Boards Der dargestellte ISP-Header ist identisch mit der Beschaltung des 10-poligen ISP-Headers am STK200 und am STK500 von ATMEL. Der vom STK200 bekannte Stecker vom Parallelport des Rechners kann hier direkt aufgesetzt werden, vom STK500 gibt es eine ähnliche Verbindung. Für den Anfänger ist es am einfachsten, sich ein solches Programmierboard zu besorgen und damit zu beginnen. Man braucht sich dann nicht erst mit Hardware herum zu beißen und kann sofort mit dem Programmieren und Testen loslegen. Die Boards STK500 und STK200 haben eine LED-Reihe und Taster, die eine einfache Ausgabe von Zwischenergebnissen oder eine Eingabe von den Tastern ermöglichen. Beim STK200, das nicht mehr im Handel zu bekommen ist, erfolgte der Anschluss an den Rechner über einen freien Parallelport. Das stößt in der Regel auf Probleme, weil da schon der Drucker dranhängt. Echte Freaks besorgten sich für ein paar Mark eine Zusatzkarte mit einem Parallelport für den PC, aber die kostete auch wieder einen wertvollen Interrupt. Das ist beim STK500 anders, weil das Board einen intelligenten Prozessor mit Speicher und seriellem Interface hat. Dieses Interface wird mit einem freien seriellen Anschluss am PC verbunden. Die Software zum Übertragen des Programmes zum Board ist in den neueren Studio-Versionen enthalten. Auch die Programmierung erfolgt über das Studio. ( => Screenshot: Das Programmierinterface im Studio bedient ein STK500 board) Mit dem STK500 ist es neben dem seriellen Programmieren auch möglich, die Chips parallel und mit einer erhöhten Spannung (12 Volt) zu programmieren. Dadurch kommt man an speziellere Einstellungen in den Chips heran. die beim seriellen Programmieren nicht zugänglich sind. Bei bestimmten AVR-Typen lassen sich Oszillator-Optionen oder Schutzeinstellungen auf diese Art brennen. Wir legen das erst mal in die Ecke für Fortgeschrittene ab. Die Software zum Programmieren über die ISP-Verbindung bzw. des STK200- und STK500-Boards gibt es kostenlos auf der Webseite von ATMEL.
cq-dl-Beiträge zu ATMEL-AVRMikrocontrollern Teil IV: Applikation CW-Geber Dieser Beitrag beschreibt einen CW-Geber mit einem AVR Mikroprozessor des Typs AT90S2313. Damit sollen die vielfältigen Hardware- und Software-Fähigkeiten demonstriert werden, die diese Prozessoren für den anspruchsvollen Bastler haben können. Wer ein STK200- oder STK500-Programmierboard besitzt, kann auf den Bau der Hardware weigehend verzichten und die Software für das entsprechende Board direkt verwenden (siehe Betrieb mit Board).
Aufgabe: Demonstration der Leistungsfähigkeit von Hard- und Software Die Beschaltung des Prozessors demonstriert, wie die interne Hardware mit wenigen externen Komponenten für die Lösung einer recht komplexen Aufgabe genutzt werden kann. Der interne 16Bit-Timer wird zur exakten Erzeugung der Niederfrequenz verwendet. Die Hardware lehrt ferner, wie ein RS232-Interface zur Kommunikation mit der Mikroprozessor-Software realisiert werden kann. Diese Kommunikation erfolgt zudem mit einem Hardware-Protokoll gesteuert. Das Beispiel zeigt auch, wie der Mikroprozessor in der fertigen Hardwareschaltung programmiert werden kann. Softwareseitig kann an dem Beispiel demonstriert werden, wie die interrupt-gesteuerte Kommunikation mit der RS232-Schnittstelle und der Timer-gesteuerten NF-Erzeugung funktioniert. Ferner wird in Rechenpausen der stromsparende SLEEP-Betrieb des Prozessors verwendet. Die programminterne Ablaufsteuerung und die Kommunikation der internen Abläufe miteinander erfolgt im wesentlichen über ein Flagregister. Als weitere Software-Besonderheiten sind die Multiplikation und Division von binären Ganzzahlen zu nennen. Schließlich ist in der kleinen Steuerung auch ein komfortables User-Interface enthalten, das die Einstellung der wichtigsten Eigenschaften der CWAusgabe erlaubt. Insgesamt fördert dieses Stück Software wegen der Komplexität und Unübersichtlichkeit kaum das Verständnis beim blutigen Anfänger. Einzelne Teile können aber gerne von dieser Vorlage abgekupfert und für den eigenen Gebrauch umgebogen werden oder als Anregung dienen. Natürlich soll das Ding für den eigentlichen Zweck, das Geben von Morsezeichen, brauchbar sein. Irgendwann lerne auch ich die damit mal.
Funktionen der Schaltung Die fertig aufgebaute und programmierte Schaltung gibt ohne angeschlossenen PC beim Einschalten einen Text in CW mit festgelegter Geschwindigkeit und Tonhöhe aus. Mit einem über die serielle Schnittstelle angeschlossenen PC und einem dort laufenden Terminalprogramm (z.B. Hyperterminal für Windows) kann nach Ausgabe des gespeicherten Textes zeilenweise Text eingegeben und in CW wieder ausgegeben werden. In Maßen kann während der Ausgabe schon weiter Text eingegeben werden. Mit dem Terminalprogramm kann auch eine vorgefertigte Textdatei an die Schaltung geschickt werden, die mehrzeilige Texte enthält. Dabei sorgt das Hardware-gesteuerte RS232-Protokoll dafür, dass keine Zeichen verloren gehen, da die Textübergabe immer schneller erfolgt als die Ausgabe der Zeichen in CW. Mit dem Terminalprogramm kann man nach Eingabe von ESCAPE ferner die eingestellten Werte für die erzeugte NF-Frequenz, die Gebegeschwindigkeit und den beim Reset auszugebenden Text ändern und die verstellten Werte im internen EEPROM ablegen. Die Einstellung erfolgt komfortabel menuegesteuert.
Aufbau der Hardware Der gesamte Hardware-Aufbau ist recht einfach. Der Aufbau ist im Schaltbild zu sehen. Schaltbild im GIF-Format Schaltbild im PDF-Format Im Zentrum steht der beliebte, leicht erhältliche und preiswerte 20-polige AVR-Prozessor vom Typ AT90S2313-10P. Als Taktgeber wurde ein vorhandener Quarz mit 10 MHz gewählt, die Hard- und Software funktioniert aber auch noch mit wesentlich geringerer Taktfrequenz, ab 1 MHz aufwärts. (Bei anderen Taktfrequenzen muss nur ein Parameter in der Software geändert werden.) Die Hardware für den Prozessor-Reset ist ebenfalls einfach gehalten, weil mit der Schaltung ja keine Bremsen für eine Straßenbahn gesteuert werden sollen, bei der Spikes auf der Versorgungsspannung sicherheitshalber zur Auslegungsgrundlage gehören würden. Die Ausgabe der NF erfolgt am Port-Pin PB3. Das ist mit Bedacht gewählt, da dieser Pin softwaregesteuert auch als Ausgabe-Pin OC1 des 16-Bit-Timers verwendet werden kann. Die Anschaltung des Lautsprechers über eine einfache Gleichstromtrennung ist sehr einfach, aber etwas hart. Zur Lautstärkeeinstellung wäre statt des 470-Ohm-Widerstandes noch ein Poti angebracht, für echte Anwendungen wie z.B. die Ansteuerung einer AM/FM/SSB-Sendeanlage noch geeignete RCNetzwerke zur Abrundung der Rechteckflanken. Das RS232-Interface zur Kommunikation mit dem PC ist mit einem MAX232 aufgebaut, der zu den vier V24-Treibern auch noch die Spannungserzeugung aus 5V enthält. Die Beschaltung des 9-poligen Steckers ist zur Verwendung eines Nullmodem-Kabels ausgelegt, bei dem die Sende-/Empfangs- und die RTS-/CTS-Leitung zur Verbindung zweier PCs über die COM-Schnittstelle gekreuzt sind. Verwendet man stattdessen ein Parallel- oder 1:1-Kabel, muss die Vertauschung schon in der Schaltung erfolgen (vertauschen von RD/TD und RTS/CTS). Die beiden Jumper Data und Control ermöglichen in der Stellung Loop den Test von Verbindungskabeln, Steckern und MAX232 ohne jede Mitwirkung des Prozessors und sind für den manuellen Aufbau hilfreich. Sie können auch entfallen, wenn man diese Diagnosemöglichkeit nicht braucht. Für das Experimentieren mit der Software im 2313 ist das ISP-Interface ideal geeignet, da man damit den Chip mit immer neuen Softwareversionen beschicken kann, ohne ihn aus der Schaltung heraus nehmen zu müssen. Da an Port-Bits in dieser Schaltung kein Mangel besteht, sind die Portbits PB5 (MOSI), PB6 (MISO) und PB7 (SCK) ausschließlich für diesen Zweck reserviert und Maßnahmen zur Umschaltung zwischen Normalbetrieb und In-System-Programmierbetrieb können entfallen. Das Programmier-Interface eignet sich zur Anschaltung an die entsprechende 10-polige Schnittstelle auf dem ATMEL Board STK500, zur direkten Verbindung mit der Parallel-Schnittstelle über das KANDA-Interface, das mit dem STK200 mitgeliefert wird, oder auch einem selbst gebauten ISPInterface. Die Programmier-LED kann zur Not auch weggelassen werden. Die Stromversorgung mit 5V ist einfach und kann zur Not auch mit einer kleinen Batterie erfolgen. Wenn keine weitere Hardware versorgt werden soll, reicht auch ein 78L05 völlig aus.
Aufbau der Software Die Software für die Selbstbauversion (ohne Programmierboard) kann in verschiedenen Versionen angezeigt und heruntergeladen werden, sie ist in AVR-Assembler geschrieben. Sie umfasst ausführlich kommentiert ca. 1100 Textzeilen und ergibt assembliert ca. 650 Worte Programm und ca. 360 Worte an Konstanten (Morsecode-Tabelle, Texte zur menuegesteuerten Kommunikation). Insgesamt ist der verfügbare Programmspeicher im 2313 (1 k Worte) damit zu 97% ausgelastet. Zur korrekten Übersetzung ist die von ATMEL verfügbare Headerdatei 2313def.inc erforderlich. Alle wichtigen Parameter können im Kopf der Software frei eingestellt werden, so dass die Anpassung bei geänderter Hardware keine großen Probleme verursachen dürfte. Beim Umschreiben der Software für größere Prozessoren (z.B. AT90S8515) ist darauf zu achten, dass einige Zeigeroperationen wegen des größeren verfügbaren SRAM- und EEPROM-Bereichs zusätzliche Änderungen im Code brauchen, damit die 16-Bit-Zeiger richtig funktionieren (z.B. beim Lesen und Schreiben des EEPROM-Speichers). Das betrifft auch andere Taktraten, Interrupt-Vektoren, den SRAM-Stapel, etc. An den 8515 angepasste Versionen sind Anhang enthalten. Morsecodes Alle Morsecodes der ASCII-Zeichen 32 bis 95 sind in einer Tabelle im Programm abgelegt. Kleinbuchstaben werden durch Abziehen von 32 in Großbuchstaben umgewandelt. Jedes Zeichen benötigt ein Byte mit dem Code (Punkt=0, Strich=1), von höherwertigen zu niederwertigen Bits organisiert, und ein Byte mit der Anzahl Punkte/Striche des jeweiligen Zeichens. Undefinierte Zeichen werden mit einem Fragezeichen ausgegeben, sie haben die Anzahl Punkte/Striche Null. Beim Leerzeichen sorgt ein gesetztes siebtes Bit für eine Stummschaltung des NF-Ausganges. In dieser Tabelle kann der CW-Purist beliebig herumtoben und umbelegen. Das kommt für einige Sonderzeichen infrage, insbesondere für Belegung der folgenden Codes. ASCII Morse ----------! Warten # ñ $ á, å % é & (frei) * (frei) + Spruchende < Verkehrsanfang > Verkehrsende @ ch [,{ ä \,~ ö ],} ü ^ Irrung Interruptsteuerung Besonderheiten des Programmes liegen in dem überwiegend interruptgesteuerten Ablauf. Über eine entsprechende Vektortabelle wird der Empfang von Zeichen über die serielle Schnittstelle (nicht bei Menuebetrieb!) und die Timer-Interrupts zur Erzeugung der korrekten Anzahl NF-Pegelwechsel in Abhängigkeit von der Gebegeschwindigkeit auf die beiden entsprechenden Interrupt-ServiceRoutinen verteilt. Der Prozessor selbst befindet sich beim Interrupt-Betrieb die meiste Zeit im stromsparenden SLEEP-Modus und wird nur durch Hardware-Interrupts aufgeweckt. Die Übergabe von Senden und Empfang erfolgt über entsprechende Registerflag, das nach jedem Aufwecken überprüft wird, und den Ablauf in die entsprechenden Routinen verzweigt. Wegen des erheblichen Zeitbedarfs und der Komplexität des Ablaufes ist der Menuebetrieb nicht innerhalb der Interruptsteuerung angesiedelt, er erfolgt durch Polling an der UART-Schnittstelle. Rechenroutinen Wichtig sind die eingebauten Rechenroutinen zur Multiplikation und Division von 32-BitBinärzahlen. Sie dienen der Umrechnung vom Prozessor- und Timer-Takt in die der NF-Frequenz angepassten 16-bittigen Compare-Match-Zahl des NF-Timers und der Berechnung der Anzahl von Interrupts zur Erzielung der richtigen Dauer von Punkten und Strichen, abhängig von der eingestellten NF-Frequenz und der Gebegeschwindigkeit. Da diese beiden Eingangsgrößen verstellbar sind, müssen nach jeder Verstellung diese Rechenroutinen bemüht werden, um die Steuerung anzupassen. Achtung: diese Routinen funktionieren nur innerhalb von besonderen Wertebereichen und sind nicht ohne weiteres auf andere Wertebereiche von Binärzahlen anwendbar. Der Rechenmechanismus der Multiplikation (16-bit mal 16-bit binär) und der Division (32-bit durch 16-bit binär, 32-bit durch 8-bit binär) ist aber erkennbar und leicht auf andere Rechenaufgaben übertragbar. Steuerung des Hardware-Protokolles Ungewöhnlich ist die Steuerung der seriellen Schnittstelle zum PC mit dem RTS/CTS-HardwareProtokoll. Die Schaltung signalisiert dem PC über die CTS-Leitung, ob sie empfangsbereit für weitere Zeichen ist. Diese Steuerung ist notwendig, wenn ganze Textdateien übertragen und ausgesendet werden sollen. Dabei reichen die intern vorhandenen 128 Bytes SRAM zur Zwischenspeicherung der gesamten Datei nicht aus (ein Teil des SRAM wird zudem für den Stack benötigt), die Übertragung muss entsprechend dem Ausgabefortschritt abgebremst werden. Deshalb enthält sowohl die Timer-Ausgaberoutine als auch die UART-Empfangsroutine entsprechende Prüfungen, ob der verfügbare Pufferbereich zum Empfang weiterer Zeichen ausreicht und setzt die CTS-Leitung dementsprechend auf aktiv oder inaktiv. Auf diesen Teil (und auf die entsprechenden Leitungen und andere Beschaltungen) kann verzichtet werden, wenn ein entsprechender Senderhythmus der Zeichen eingehalten wird. Aber wir wollten ja unbedingt lernen, wie so was funktioniert!
Einstellmenue über Terminalprogramm Auf dem PC muss zur Kommunikation mit dem CW-Geber ein Terminalprogramm laufen, das die verwendete serielle Schnittstelle mit der voreingestellten Baudrate von 9.600 Baud und mit 8N1 bedient. Dazu ist jedes primitive Terminalprogramm (z.B. Hyperterminal) geeignet. (=> Screenshot: Einstellungen im Hyperterminal) Man muss das Programm nur davon abhalten, ein Modem am anderen Ende der seriellen Leitung zu erwarten, weil sonst die AT-Befehle gemorst werden und keine korrekte Rückmeldung vom "Modem" (OK) kommt. Also wird z.B. Hyperterminal in der Einstellung "Direktverbindung über COMx" betrieben. (=> Screenshot: Nach dem Einschalten meldet sich der 2313 und morst vor sich hin) Damit man das Einstellmenue im 2313 herankommt, muss das Terminalprogramm beim Betätigen der ESCAPE-Taste tatsächlich das ESC-Zeichen (hex: 1B, dezimal: 27) über die Schnittstelle senden (das macht Hyperterminal so). Verwendet man ein Terminalprogramm, das die ESCAPE-Taste für andere Zwecke verwendet, muss man die Software des 2313 ändern und irgend ein anderes Kontrollzeichen auswerten. (=> Screenshot: Der 2313 im Verstellmodus) Hat man sich vertippt, reagiert die Eingabezeile für Morsezeichen auf die Rück- oder BackspaceTaste, falls diese das Kontrollzeichen 8 aussendet. Die Cursortasten und andere ANSI- oder VT100Zeichen werden in dieser Version nicht unterstützt. Hat man es im Terminalprogramm lieber buntig, muss man entsprechende ANSI-Code-Zeichen zu den Menuetexten im Assemblerprogramm hinzufügen. Aber Vorsicht dabei: der ATMEL-Assembler hat seit langem einen üblen Bug und übersetzt bestimmte Mischungen aus Byte-Konstanten und Texten, die in " eingeschlossen sind, nicht korrekt. Der Programmzähler kann dabei um ein oder mehrere Worte vor- oder nachgehen, die Übersetzung richtet ein Chaos bei allen nachfolgenden Programm-Labels an. Nach dem Ändern von .DB-Menuezeilen sollte man sich anschließend im Listing davon überzeugen, dass die eingefügten chk-Prüfwerte adressmäßig übereinstimmen. Sonst kommt es zu weggelassenen oder verstümmelten Zeichen oder Zeilen. Viel Erfolg beim Experimentieren!
Betrieb der Software auf einem Board Wer ein STK200- oder STK500-board sein Eigen nennt, kann auf den Selbstbau des 2313Experimentierboards auch verzichten, den Lautsprecher an das Board anschließen und die Software auf dem Board verwenden. Dazu ist gegenüber der Eigenbau-Schaltung ein AT90S8515 auf dem Board erforderlich, was einige Umbauten in der Software bedingt. Im Anhang können die verschiedenen Software-Versionen heruntergeladen werden. Beim Board STK200 ist der erforderliche Zusatzanbau eine 10-polige, zweireihige Buchse, die an den Portausgang von Port D angeschlossen wird (ähnlich wie der Lautsprecheranschluss beim Experimentierboard). Das NF-Ausgangsignal liegt im Gegensatz zum Experimentierboard an Bit 5 des Ports D an und wird über einen Elko von 10µF und einen Widerstand von ca. 470 Ohm an den Lautsprecher angeschlossen. Die zweite Leitung vom Lautprecher wird auf GND der 10-poligen Buchse gelegt. Die Software ist auf den boardeigenen Taktoszillator von 4 MHz eingestellt. Beim STK500 muss der AT90S8515 bestückt sein. Die Anschlüsse PD0 und PD1 des 10-poligen Postensteckers von Port D müssen an RXD bzw. TXD der RS232-SPARE-Buchse angeschlossen werden (über mit dem Board mitgelieferte zweipolige Steckverbinder). Der Lautsprecher wird wie beim Experimentierboard mit dem Elko und dem Widerstand beschaltet und kommt an Pin PD5 und GND von Port D des Boards. Die Software ist auf die boardeigene Taktung mit 3,96 MHz eingestellt. Wer per Studio-Software den Teilerfaktor anders einstellt, muss den Parameter fq in der Software entsprechend ändern. Allerdings dürfte es bei Taktfrequenzen unterhalb von 1 MHz zu Problemen kommen, wenn extreme NF-Frequenzen oder Gebegeschwindigkeiten eingestellt werden. Bei der Software für beide STK Boards ist noch ein zusätzlicher Menuepunkt ansteuerbar, weil der 8515 über mehr Programmspeicher verfügt (Option x). Beim Betrieb mit einem Board ist ferner zu berücksichtigen, dass ●
●
●
die RS-232-Anschlüsse nicht über eine Beschaltung der Signale RTS und CTS verfügen, das Hardware-Protokoll steht daher nicht zur Verfügung und muss im Terminalprogramm abgeschaltet werden (keine Ausgabe ganzer Textdateien in einem Rutsch möglich!), in den RS232-Buchsen der Boards RD und TD bereits gekreuzt sind und 1 zu 1 verkabelt werden müssen (ein Nullmodem-Kabel funktioniert hierfür nicht), beim STK500 beide RS232-Schnittstellen benötigt werden (zum Programmieren mittels STUDIO und zur Kommunikation mit einem Terminalprogramm) und dass beide unterschiedliche Baudraten verwenden (115 k bzw. 9k6).
Anhang: Software zum Download Mit diesen Links kann die Assemblersoftware für die verschiedenen Versionen heruntergeladen werden: Version
Pfad: Home => cq-dl-Beiträge => Teil 4 => Cw01.asm
cq-dl-Beiträge zu ATMEL-AVRMikrocontrollern Assembler-Quellcode des CW-Programmes ; ********************************************************** ; * CW.asm gibt Morsezeichen aus, die im EEPROM-Speicher * ; * gespeichert sind und die über die serielle Schnitt* ; * stelle des 2313-Experimentier-Boards eingegeben wer* ; * den. Ausgabe NF an PB3. Baudrate 9k6. Takt 10 MHz. * ; * AVR AT90S2313. Programm (C)2002 by DG4FAC G.Schmidt * ; * Version 0.1 vom 28.12.2001 * ; * Bugs und Dankschreiben an info!at!avr-asm-tutorial.net * ; ********************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\2313def.inc" .LIST ; ; Benutzte Register ; ; Register R0 wird für Lesen im Programmspeicher benutzt ; Register R0..R9 werden für Berechnungen benutzt ; .DEF rcml = R10 ; Compare Match-Zahl, LSB .DEF rcmh = R11 ; dto., MSB .DEF rikl = R12 ; Interrupt-Anzahl kurz, LSB .DEF rikh = R13 ; dto., MSB .DEF rill = R14 ; Interrupt-Anzahl lang, LSB .DEF rilh = R15 ; dto., MSB .DEF rmp = R16 ; Multipurpose register, nicht bei Ints .DEF rim = R17 ; Interrupt multi-purpose .DEF rfl = R18 ; Flag Register, bei Ints und Normal .DEF rst = R19 ; Statusregister bei Interrupts .DEF rnsc = R20 ; Anzahl Punkte/Striche beim Senden .DEF rmcd = R21 ; Morsecode beim Senden .DEF x22 = R22 ; unbenutzt .DEF x23 = R23 ; unbenutzt .DEF rctl = R24 ; Zähler für Interrupt Tonerzeugung .DEF rcth = R25 ; (wird als Doppelregister verwendet) ; ; Register XH:XL R27:R26 Zeiger in den Empfangspuffer ; Register YH:YL R29:R28 Zeiger beim Senden ; Register ZH:ZL R31:R30 Zeiger für Lesen im Programmspeicher ; ; Bits im Flagregister ; .EQU besc = 7 ; ESCape-Sequenz, hole Parameter .EQU bmesc = 0x80 ; Maskenbyte für ESC .EQU bstx = 6 ; Starte Sendeprozess .EQU bmstx = 0x40 ; Maskenbyte für bstx .EQU bctx = 5 ; Beende Sendeprozess nach Leerzeichen .EQU bmctx = 0x20 ; Maskenbyte für bctx .EQU btxa = 4 ; Sendeprozess aktiv .EQU bmtxa = 0x10 ; Maskenbyte für btxc .EQU btxe = 3 ; Abschluss des Sendens .EQU bmtxe = 0x08 ; Maskenbyte für btxe .EQU bpl = 2 ; Lange Pause zur Buchstabentrennung .EQU bmpl = 0x04 ; Maskenbyte für bpl .EQU bpk = 1 ; Kurze Pause zur Punkt/Strich-Trennung .EQU bmpk = 0x02 ; Maskenbyte für bpk .EQU bquiet = 0 ; Dauerhaft inaktiviert bei Leerzeichen .EQU bmquiet = 0x01 ; Maskenbyte für bquiet ; ; Default-Werte ; .EQU cfrq=1000 ; NF-Frequenz .EQU cbpm=60 ; Gebegeschwindigkeit ; ; Basisdefinitionen variabel nach Schaltung ; .EQU fqu = 10000000 ; Quarzfrequenz des AVR .EQU fbd = 9600 ; Baudrate des UART .EQU pctrl = PORTB ; Control-Port für RTS/CTS .EQU pdrr = DDRB ; Datenrichtungsregister Controlport .EQU brts = PB2 ; RTS bit Input .EQU bcts = PB4 ; CTS bit Output .EQU boc1 = PB3 ; NF-Ausgabe über OC1-Pin ; ; Umrechnungen in Timer- und Counterwerte ; .EQU nps = 8 ; Prescaler-Einstellung von Timer 1 .EQU ccm = fqu/nps/2; Konstante für Compare Match .EQU cint =2941 ; Konstante für Int-Berechnung, ; experimentell ermittelt ; ; Definitionen fix ; .EQU bddv = (fqu/(16*fbd))-1 ; Baudraten-Teiler .EQU cnul = 0x00 ; Ende für nullterminierte Strings .EQU chbsp = 0x08 ; Backspace character .EQU chcr = 0x0D ; Carriage Return character .EQU chlf = 0x0A ; Line Feed character .EQU chff = 0x0C ; Form Feed character .EQU chesc= 0x1B ; ESCape-Zeichen ; ; Definitionen I/O ; ; Definitionen für Timer-Controlregister TCCR1A .EQU t1aus = 0b10000000 ; Schaltet den NF-Ausgang aus .EQU t1ein = 0b01000000 ; Schaltet den NF-Ausgang ein ; Definition für Timer-Interrupt-Mask-Register TIMSK .EQU t1CompInt=0b01000000 ; Interrupt bei Compare Match ; Definition für Timer-Controlregister TCCR1B .EQU t1TaktInt=0b00001010 ; Timer-Takt CLK / 8, Clear Comp ; Definitionen für UART-Betrieb in UCR .EQU siorxtx = 0b00011000 ; Betrieb RX und TX ohne Int .EQU siorxint= 0b10011000 ; Betrieb RX mit Int, TX ohne Int ; Definition für den SLEEP-Mode in MCUCR .EQU sleepmode=0b00100000 ; Aufwachen bei Interrupt ; ; Positionen im SRAM ; .EQU sfrq = 0x0060 ; Eingestellte NF-Frequenz, Wort .EQU sbpm = 0x0062 ; Eingestellte Geschwindigkeit, Byte .EQU srtx = 0x0063 ; RAM-Puffer für Ein-/Ausgabezeile .EQU srte = 0x00B3 ; Ende des nutzbaren Puffers, benutzt werden ; auch zwei weitere Folgebytes ; ; Programm beginnt hier ; .CSEG .ORG 0x0000 ; ; Reset- und Interrupt-Sprungtabelle ; rjmp start ; Reset-Vector reti ; Ext Int 0, nicht benutzt reti ; Ext Int 1, nicht benutzt reti ; Timer 1 Capture Event, nicht benutzt rjmp itc1m ; Timer 1 Compare Match reti ; Timer 1 Overflow, nicht benutzt reti ; Timer 0 Overflow, nicht benutzt rjmp iurxc ; UART Rx Complete reti ; UART Tx data register empty, nicht benutzt reti ; UART Tx All sent, nicht benutzt ; ; Interrupt-Service-Routinen ; itc1m: ; Timer 1 Compare Match Interrupt in rst,SREG ; Rette Statusregister sbiw rctl,1 ; Zähler eins abzählen brne itc1mz ; Raus, wenn nicht Null mov rcth,rikh ; Setze Zähler auf kurze Dauer mov rctl,rikl ldi rim,t1aus ; Ton auf aus tst rnsc ; Zeichen fertig gesendet? breq itc1m0 ; Verzweige, wenn Zeichen fertig sbrc rfl,bpk ; kurze Pausen-Flag? rjmp itc1ms ; Pause zwischen Punkt/Strich war schon sbr rfl,bmpk ; Setze kurze Pause-Flag rjmp itc1my ; und Ausgabe auf inaktiv, fertig itc1ms: ; Sende nächsten Punkt/Strich cbr rfl,bmpk ; Lösche kurze Pause-Flag ldi rim,t1ein; Ton an = Toggle dec rnsc ; Weiteren Punkt/Strich gesendet rol rmcd ; Punkt oder Strich senden? brcc itc1my ; Punkt senden mov rcth,rilh ; Lange Dauer einstellen mov rctl,rill rjmp itc1my itc1m0: ; Zeichen fertig gesendet sbrc rfl,bctx ; Sendung beenden? rjmp itc1mx sbrc rfl,bpl ; Lange Pause senden? rjmp itc1mn ; Nächsten Buchstaben beginnen sbr rfl,bmpl ; Setze langes Pausen-Flag mov rcth,rilh ; Dauer auf lang stellen mov rctl,rill itc1my: ; Stelle Modus inaktiv/toggle ein sbrc rfl,bquiet ; bei Leerzeichen Ton aus ldi rim,t1aus ; Ton auf aus out TCCR1A,rim itc1mz: out SREG,rst ; Stelle Status wieder her reti itc1mn: cbr rfl,bmpl ; Langes Pausen-Flag aus ld rim,y+ ; Nächsten Buchstaben lesen tst rim ; Null-Terminator breq itc1mn1 cpi rim,chcr ; Ende der Zeile? brne itc1mn2 itc1mn1: sbr rfl,bmctx ; Setze beenden-Flag ldi rim,' ' ; Sende noch ein Leerzeichen itc1mn2: out UDR,rim ; Debug subi rim,0x20 ; ASCII-Control-Zeichen weg brcc itc1mn3 ldi rim,'?'-0x20 ; Sende Fragezeichen itc1mn3: cpi rim,0x40 ; Kleinbuchstabe? brcs itc1mn4 subi rim,0x20 ; in Grossbuchstaben wandeln cpi rim,0x40 brcs itc1mn4 ldi rim,'?'-0x20 itc1mn4: add rim,rim ; Mal 2 für Tabelle push ZH ; Register retten push ZL push R0 ldi ZH,HIGH(2*morse) ; Zeichentabelle laden ldi ZL,LOW(2*morse) add ZL,rim ; Zeichen dazu zählen brcc itc1mn5 ; Kein Übertrag? inc ZH ; Übertrag itc1mn5: lpm ; Lese Zeichencode aus Tabelle mov rmcd,R0 ; in Zeichencode-Register adiw ZL,1 ; Zeiger auf nächstes Byte lpm ; aus Tabelle lesen mov rnsc,R0 ; Anzahl Striche/Punkte pop R0 ; Wieder herstellen der Register pop ZL pop ZH tst rnsc ; Undefiniertes Zeichen? breq itc1mn ; Überspringe Zeichen sbr rfl,bmquiet ; Leerzeichen sbrs rnsc,7 ; Leerzeichen? cbr rfl,bmquiet ; Kein Leerzeichen cbr rnsc,0x80 ; Lösche höchstes Bit mov rim,YL ; CTS einschalten? sub rim,XL brcs itc1mn6 ; Ausschalten cpi rim,3 brcs itc1mn6 cbi pctrl,bcts ; CTS einschalten rjmp itc1ms itc1mn6: sbi pctrl,bcts ; CTS ausschalten rjmp itc1ms ; Sende ein Zeichen itc1mx: ; Sendung einstellen clr rim ; Timer 1 abschalten out TCCR1B,rim ; Timer-Takt aus out TCNT1H,rim ; Timer auf Null stellen out TCNT1L,rim out TIMSK,rim ; Timer Interrupts aus out TCCR1A,rim ; Timer Compare Mode aus cbr rfl,bmctx+bmtxa ; Ende-Flag und aktiv ausschalten sbr rfl,bmtxe ; Beenden-Flag einschalten ldi YH,HIGH(srte) ; Buffer auf Ende ldi YL,LOW(srte) st Y,rim ; Null-terminieren rjmp itc1mz ; ; UART Rx Complete Interrupt ; iurxc: in rst,SREG ; Rette Statusregister in rim,UDR ; Lese Zeichen von SIO cpi rim,chesc ; ESCape-Sequenz? brne iurx1 sbr rfl,bmesc ; Setze ESCape-Bit rjmp iurxz iurx1: cpi rim,chbsp ; Backspace-Char? brne iurx2 cpi XL,LOW(srtx) ; Schon auf Anfang? breq iurxz sbiw XL,1 ; Eine Position zurück ldi rim,chcr ; Zeilenabschluss st x+,rim clr rim ; Null-terminieren st x,rim sbiw XL,1 ; zurück ldi rim,chbsp ; Backspace zurücksenden rjmp iurxe ; Echo character iurx2: cpi XL,low(srte) ; Pufferüberlauf? brcs iurx3 ; Nein, weiter ldi rim,chcr ; Character überschreiben iurx3: cpi rim,chcr ; Zeilenende? brne iurxw ; Nein, in Puffer und fertig sbrs rfl,btxa ; Überspringe Ausgabe wenn aktiv out UDR,rim ; Carriage Return schon mal ausgeben st x+,rim ; CR-Zeichen anhängen clr rim ; Null terminieren st x,rim ldi XH,HIGH(srtx) ; Puffer auf Anfang ldi XL,LOW(srtx) sbr rfl,bmstx ; Über Flag Sender starten ldi rim,chlf ; Sende Zeilenvorschub rjmp iurxe ; Echo Line-Feed iurxw: st x+,rim ; Speichere character ldi rim,chcr ; Abschluss terminieren st x+,rim ; mit Carriage return clr rim ; Null-terminieren st x,rim sbrs rfl,btxa ; Sender aktiv? rjmp iurxs ; Nein, CTS nicht prüfen mov rim,YL ; CTS ein oder aus? sub rim,XL ; Distanz zwischen Ein- und Ausgabe brcs iurxo ; Eingabe > Ausgabe: CTS aus cpi rim,3 ; mindestens zwei Zeichen Vorlauf? brcs iurxo ; Nein, dann CTS ausschalten cbi pctrl,bcts ; CTS einschalten rjmp iurxs ; Zeichen herstellen und raus iurxo: sbi pctrl,bcts ; CTS ausschalten iurxs: sbiw XL,2 ; Char wieder herstellen ld rim,x+ ; durch Lesen im SRAM iurxe: sbrs rfl,btxa ; Keine Ausgabe, wenn aktiv out UDR,rim ; Echo character an SIO zurück iurxz: out SREG,rst ; Stelle Status wieder her reti ; Ende des Interrupts ; ; Diverse eigenständige Unterprogramme ; ; Kopiert den EEPROM-Inhalt in das SRAM ; ecopy: sbic EECR,EEWE ; Warte, bis EEPROM bereit rjmp ecopy ; Noch nicht bereit clr YH ; Startadresse im EEPROM auf Null clr YL ldi ZH,HIGH(sfrq) ; Startadresse im SRAM ldi ZL,LOW(sfrq) ; auf ersten Parameter ecopy1: out EEAR,YL ; Leseadresse im EEPROM ; 2313 hat nur 128 Bytes, daher nur unteres Register sbi EECR,EERE ; Lese-Strobe setzen cbi EECR,EERE ; und wieder ausschalten in rmp,EEDR ; Byte aus EEPROM-Datenregister lesen st Z+,rmp ; und in SRAM schreiben adiw YL,1 ; nächste EEPROM-Adresse anwählen tst rmp ; Null-Terminator? brne ecopy1 ; Nein: weiter kopieren ret ; ; Schreibe Parameter in das EEPROM zurück ; ewrite: ldi ZH,HIGH(sfrq) ; Zeiger auf SRAM ldi ZL,LOW(sfrq) clr XH ; Zeiger in das EEPROM clr XL ewrite1: sbic EECR,EEWE ; Frage Write-Bit im EEPROM rjmp ewrite1 ; ab und wiederhole bis EEPROM ready out EEARL,XL ; Schreibadresse im EEPROM einstellen ; Hat nur 7 Bit, daher nur LSB ld rmp,Z+ ; Lese Byte aus SRAM in Register out EEDR,rmp ; in das EEPROM-Datenregister cli ; Keine Interrupts mehr beim Schreibvorgang sbi EECR,EEMWE ; Setze EEPROM Master Write Enable sbi EECR,EEWE ; Löse Schreibvorgang im EEPROM aus sei ; Jetzt Interrupts wieder zulassen adiw XL,1 ; Nächste EEPROM-Adresse in X-Zeiger cpi XL,128 ; Ende EEPROM erreicht? brcc ewrite2 ; Überlänge! Null fehlt! Abbrechen! tst rmp ; Nullterminiert? brne ewrite1 ; Noch weitere Bytes schreiben ewrite2: ret ; ; Lese16 wandelt eine ASCII-Zahl im Puffer in binär ; in R1:R0, bei Fehler Rückkehr mit gesetztem Carry-Bit ; lese16: clr R0 ; Leeren Resultat-Register R1:R0 clr R1 ldi ZH,HIGH(srtx) ; Zeige auf Pufferanfang ldi ZL,LOW(srtx) lese16a: ld rmp,Z+ ; Lese ASCII-Ziffer aus SRAM cpi rmp,chcr ; Ende der Zahl mit Carriage Return? breq lese16ok ; Zahl mit Carriage Return beendet cpi rmp,cnul ; Ende der Zahl mit Nullterminiert? breq lese16ok ; Ende mit Nullterminierung cpi rmp,'9'+1 ; Ziffer > ASCII-9? brcc lese16no ; Ja, Fehler! cpi rmp,'0' ; Ziffer < ASCII-0 brcs lese16no ; Ja, auch Fehler subi rmp,'0' ; Wandle Ziffer in binär ; Ab hier wird das bisherige Resultat mit 10 multi; pliziert mov R2,R0 ; Kopiere Binärzahl in Hilfsregister R3:R2 mov R3,R1 add R0,R0 ; Multipliziere mit 2 durch Addieren 16 Bit adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R2 ; Addiere die Kopie der Zahl adc R1,R3 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! ; Hier ist das Multiplizieren mit 10 beendet. add R0,rmp ; Addiere Ziffer zum Resultat hinzu brcc lese16a ; Kein Überlauf des unteren Bytes inc R1 ; Überlauf, oberes Byte erhöhen brne lese16a ; Kein Überlauf des oberen Bytes lese16no: sec ; Setze Carry-Bit bei Fehler und kehre zurück ret lese16ok: clc ; Clear Carry bei fehlerfrei ret ; ; Wandle 16-Bit-Zahl in R1:R0 in ASCII in R4..R9 (nullterm.) ; unterdrücke führende Nullen, sende Ergebnis ohne führende ; Nullen über die SIO ; b16asc: clr ZH ; Z zeigt auf höchste ASCII-Ziffer in ldi ZL,4 ; Register R4 (Zeiger in Register!) ldi rmp,HIGH(10000) ; Beginne mit Zehntausendern mov R3,rmp ; oberes Byte in R3 ldi rmp,LOW(10000) mov R2,rmp ; unteres Byte in R2 rcall b16sub ; Ermittle ASCII-code der Zehntausender ; Stelle durch fortgesetztes Subtrahieren von 10000 ldi rmp,HIGH(1000) ; Weiter mit Tausendern mov R3,rmp ldi rmp,LOW(1000) mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Tausender clr R3 ; Weiter mit Hunderten ldi rmp,100 mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Hunderter ldi rmp,10 ; Weiter mit Zehnern mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Zehner ldi rmp,'0' ; Rest sind Einer add rmp,R0 mov R8,rmp ; R8 kriegt die ASCII-Einer clr R9 ; Nullterminieren in R9 ldi ZL,4 ; Z auf 10000-er Zeichen b16asc1: cpi ZL,9 ; Ende der Zeichenkette erreicht? breq b16asc2 ; Ja, raus ld rmp,z+ ; Zeichen aus Register kopieren cpi rmp,'0' ; Führende Null? breq b16asc1 ; Ja, weitermachen b16asc2: dec ZL ; auf vorheriges Zeichen setzen ; ; Sende nullterminierten Text aus SRAM an SIO ; Z zeigt nicht ins SRAM, sondern in die Register mit ; dem Ergebnis! ; Das Registerpaar Z zeigt auf das erste Zeichen ; txstxt: ld rmp,z+ ; Lese Zeichen aus SRAM/Register tst rmp ; Nullterminator erreicht? breq txstxt1 ; Ja, raus und fertig rcall txch ; Sende character über SIO rjmp txstxt ; Weitermachen mit nächstem Zeichen txstxt1: ret ; ; Ermittle ASCII-Code einer Ziffer der 16-Bit-Binärzahl in ; R1:R0 durch fortgesetztes Subtrahieren einer 16-Bit; Hilfszahl (10000, 1000, 100, 10) in R3:R2. Tritt ein ; Überlauf ab, dann ist die Ziffer gefunden ; (Unterroutine für b16asc) ; b16sub: ldi rmp,'0' ; Setze ASCII-Null b16sub1: sub R0,R2 ; Subtrahiere die Hilfszahl sbc R1,R3 ; in 16-bit brcs b16sub2 ; Ende subtrahieren erreicht? inc rmp ; Einer geht noch! rjmp b16sub1 ; Weiter mit subtrahieren! b16sub2: add R0,R2 ; Zu weit, addiere wieder zurück adc R1,R3 st Z+,rmp ; ASCII-Ziffer in Register schreiben ret ; und zurück ; ; Sende nullterminierten Text aus dem Programmspeicher ; über die SIO aus ; txtext: lpm ; Lese Zeichen aus Programmspeicher mit Zeiger Z tst R0 ; Ergebnis im Register R0 hat Null erreicht? breq txtend ; Ja, raus aus der Routine mov rmp,R0 ; Kopiere Zeichen in Senderegister rcall txch ; Sende character mit Prüfung adiw zl,1 ; Zeiger auf nächstes Zeichen rjmp txtext ; Weitermachen bis Null txtend: ret ; Kehre zurück ; ; Liste alle Parameter über die SIO-Schnittstelle auf ; txpar: ldi ZH,HIGH(2*txtpar1) ; Sende Parametervorspann ldi ZL,LOW(2*txtpar1) ; für NF-Frequenz rcall txtext lds R0,sfrq ; Zeige eingestellte Frequenz an lds R1,sfrq+1 rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar2) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar2) ; Geschwindigkeit an rcall txtext lds R0,sbpm ; Zeige Geschwindigkeit an clr R1 ; nur 8 Bit! rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar3) ; Zeige Vorspann für Counterldi ZL,LOW(2*txtpar3) ; Compare-Match-Zahl ccm an rcall txtext mov R1,rcmh ; Compare-Match-Zahl in R1:R0 mov R0,rcml rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar4) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar4) ; Anzahl NF-Ints bei Punkt an rcall txtext mov R1,rikh ; Anzahl Ints bei Punkt in R1:R0 mov R0,rikl rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar5) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar5) ; Anzahl NF-Ints bei Strich an rcall txtext mov R1,rilh ; Anzahl Ints bei Strich in R1:R0 mov R0,rill rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar6) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar6) ; Ausgabetext an rcall txtext ldi ZH,HIGH(srtx) ; Zeiger Z auf Ausgabetext im SRAM ldi ZL,LOW(srtx) rjmp txstxt ; Sende Inhalt SRAM nullterminiert ; ; 32-Bit durch 16-Bit-Division ; ; Dividiert 32-Bit-Zahl in R3:R2:R1:R0 durch R5:R4 ; Ergebnis in R7:R6 (Ergebnis darf nicht > 16 Bit sein!) ; div32: clr R7 ; Clear Ergebnis-Register R7:R6 clr R6 inc R6 ; Stopbit setzen für 16 Divisionsschritte div32a: clc ; Null in Carry schieben rol R0 ; Multipliziere Divident mit 2 rol R1 rol R2 rol R3 brcs div32e ; Carry ist herausgerollt? Dann 1! cp R3,R5 ; Vergleiche oberes Byte brcs div32n ; Ergebnis ist kleiner, also eine 0 brne div32e ; Ergebnis ist größer, also eine 1 cp R2,R4 ; Vergleich MSB gleich, vergleiche unteres ; Byte brcs div32n ; Unteres Byte kleiner, also eine 0 div32e: sub R2,R4 ; Ziehe den Divisor von den oberen 16 Bit ab sbc R3,R5 sec ; Setze das Carry-Bit, Ergebnis ist eine 1 rjmp div32s ; Zum Reinschieben in das Ergebnis div32n: clc ; Lösche Carry-Bit, Ergebnis ist eine 0 div32s: rol R6 ; Schiebe Carry-Bit von rechts in Ergebnis rol R7 brcc div32a ; Ende, wenn eine 1 links herausrollt ret ; ; Multipliziert 16-Bit-Zahl in R1:R0 mit 16-Bit-Zahl in R5:R4 ; Ergebnis 32 Bit in R9:R8:R7:R6, für jeden Zahlenbereich ; Mul16: clr R3 ; Leere obere zwei Bytes der 16-Bit-Zahl clr R2 clr R9 ; Leere Ergebnis-Register R9:R8:R7:R6 clr R8 clr R7 clr R6 Mul16a: clc ; Null ins Carry-Bit ror R5 ; Schiebe unterstes Bit Divisor in Carry ror R4 ; und dividiere Multiplikant durch 2 brcc Mul16b ; Bit war eine 0, Addition überspringen add R6,R0 ; addiere Multiplikator 32 Bit zum adc R7,R1 ; bisherigen Ergebnis, jeweils mit adc R8,R2 ; Überlauf adc R9,R3 Mul16b: tst R4 ; Schon alle Bits ausmultipliziert? brne Mul16c ; Nein, LSB nicht Null, weiter tst R5 ; Teste auch oberes Byte brne Mul16c ; Nein, MSB nicht Null, weiter ret ; Fertig Mul16c: clc ; Null in Carry-Bit schieben rol R0 ; Zahl durch Linksschieben mit 2 rol R1 ; multiplizieren rol R2 rol R3 rjmp Mul16a ; und weiter dividieren ; ; Dividiert 32 Bit-Zahl in R3:R2:R1:R0 durch eine ; 8-Bit-Zahl in R4 und durch 256, Ergebnis gerundet ; in R8:R7, Wertebereich beachten! ; Div32_8: clr R8 ; Ergebnisspeicher R8:R7:R6 leeren clr R7 clr R6 inc R6 ; Stopbit nach 24 Schritten setzen Div32_8a: clc ; Carry-Bit leeren rol R0 ; Divident mit 2 multiplizieren rol R1 ; durch Linksschieben rol R2 rol R3 brcs Div32_8e ; 1 herausgerollt, Ergebnis=1 cp R3,R4 ; Vergleiche oberstes Byte mit Divisor brcs Div32_8n ; Kleiner, Ergebnis = 0 Div32_8e: sub R3,R4 ; Ziehe Divisor von oberstem Byte ab sec ; Setze Carry für Ergebnis = 1 rjmp Div32_8b ; Ins Ergebnis schieben Div32_8n: clc ; Clear Carry für Ergebnis = 0 Div32_8b: rol R6 ; Ergebnis-Bit von rechts her hineinrotieren rol R7 rol R8 brcc Div32_8a ; Weiter, wenn eine Null herausrollt rol R6 ; Binäre Kommastelle eine 1, aufrunden? brcc Div32_8z ; Nein, wenn Null inc R7 ; Aufrunden LSB Ergebnis brne Div32_8z ; Kein Überlauf bei LSB-Erhöhung inc R8 ; Aufrunden MSB Div32_8z: ret ; ; Rechne Parameter um in Timer- und Zählerwerte ; Oben definiert: ccm = Taktfrequenz / Prescale 8 / 2 ; cint = Konstante ; Compare-Match-Zahl für Timer 1 = ccm / NF-Frequenz ; Anzahl Ints bei Punkt = cint * NF-Frequenz / Speed /256 ; Anzahl Ints bei Punkt = Anzahl Ints bei Strich * 3 ; calctc: ldi rmp,BYTE4(ccm) ; Taktabhängige Konstante ccm mov R3,rmp ; in R3:R2:R1:R0, bei 10 MHz: 625.000 ldi rmp,BYTE3(ccm) ; oder 0x00098968 mov R2,rmp ldi rmp,BYTE2(ccm) mov R1,rmp ldi rmp,BYTE1(ccm) mov R0,rmp lds R4,sfrq ; durch NF-Frequenz in R5:R4 lds R5,sfrq+1 rcall div32 ; ergibt Compare Match-Zahl 16-Bit mov rcmh,R7 ; Ergebnis in Speicher rcmh:rcml mov rcml,R6 ldi rmp,HIGH(cint) ; Konstante für Anzahl Ints mov R1,rmp ; bei 10 MHz und Teiler 200: 3125 ldi rmp,LOW(cint) mov R0,rmp lds R4,sfrq ; Mal NF-Frequenz in Hz lds R5,sfrq+1 ; rcall txreg ; Debug info rcall mul16 ; Multplizieren 16 Bit * 16 Bit ; rcall txreg ; Debug info mov R3,R9 ; Ergebnis in R9..R6 nach R3..R0 kopieren mov R2,R8 mov R1,R7 mov R0,R6 lds R4,sbpm ; durch Gebegeschwindigkeit teilen rcall div32_8 ; teilen 32-Bit durch 8-Bit ; rcall txreg ; Debug info mov rikh,R8 ; in Kurzspeicher kopieren mov rikl,R7 mov rilh,R8 ; und in Langspeicher kopieren mov rill,R7 add rill,rill ; Langspeicher mit 2 malnehmen adc rilh,rilh ; durch Addieren add rill,rikl ; und noch einmal dazu zählen adc rilh,rikh ; macht 3 mal so lang ret ; ; Debug code ; ; Display Byte in rmp in Hex an die SIO ; ;txbhx: ; push rmp ; swap rmp ; rcall txnhx1 ; pop rmp ;txnhx1: ; cbr rmp,0b11110000 ; subi rmp,-'0' ; cpi rmp,'9'+1 ; brcs txnhx2 ; subi rmp,-7 ;txnhx2: ; rcall txch ; ret ; ; Display Register R0..R9 in Hex an SIO ; ;txreg: ; ldi rmp,chcr ; rcall txch ; clr ZH ; clr ZL ;txreg1: ; ld rmp,Z+ ; rcall txbhx ; ldi rmp,' ' ; rcall txch ; cpi ZL,11 ; brcs txreg1 ; ldi rmp,chcr ; rcall txch ; ret ; ; Hauptprogramm-Loop, Restart-Vektor ; start: ldi rmp,low(RAMEND) ; Init stack pointer im SRAM out SPL,rmp ; nur 8-Bit-Pointer bei 2313 ldi rmp,bddv ; Baudrate des UART einstellen out UBRR,rmp ldi rmp,siorxtx ; UART Tx und Rx ein, Ints aus out UCR,rmp cbi pdrr,brts ; Bit Richtung ist RTS-Eingang sbi pdrr,bcts ; Bit Richtung ist CTS-Ausgang sbi pctrl,bcts ; CTS ausschalten (invertiert!) rcall ecopy ; Kopiere EEPROM-Inhalt nach RAM ldi ZH,high(2*txtid) ; Sende ID-Text an SIO ldi ZL,low (2*txtid) rcall txtext clr rfl ; Flag auf Anfangswert leer ; ; Bedingungen für Interrupt-Betrieb herstellen ; start1: rcall calctc ; Rechne Timerwerte und Ints aus rcall txpar ; Gib die eingestellten Werte über SIO ldi ZH,HIGH(2*txtcur) ; Sende Cursor-String ldi ZL,LOW(2*txtcur) rcall txtext ldi rmp,sleepmode ; Mode Idle für Sleep einstellen out MCUCR,rmp ; Aufwachen durch Interrupts ldi rmp,siorxint ; Enable RX mit Interrupt out UCR,rmp cbi pctrl,bcts ; Aktiviere CTS-Leitung sei ; Enable General Interrupt Flag ldi XH,HIGH(srtx) ; Puffer auf Anfang stellen ldi XL,LOW(srtx) rcall starttx ; und Text in CW aussenden ldi YH,HIGH(srte+3) ; Ausgabepointer hinter Ende ldi YL,LOW(srte+3) ; des Puffers stellen ldi XH,high(srtx+1) ; Puffer wieder auf Anfang ldi XL,low(srtx+1) ; und überschreiben des Textes clr rmp ; Null-Terminierung schreiben st -x,rmp ldi rmp,chcr ; Setze leeres Textfeld mit CR st -x,rmp ; Jetzt zeigt X-Zeiger auf Anfang ; ; Interrupt loop, ab jetzt praktisch nur Kreisverkehr ; mit Ausbruch bei gesetzten Flagbits ; loop: sleep ; CPU schlafen schicken nop ; Dummy-Befehl, bleibt bei Schlaf hier stehen nop ; nach dem Aufwachen ausgeführt sbrc rfl,bstx ; Sendeflag von Int-Routine gesetzt? rjmp starttx ; Ja: Starte Sendevorgang sbrc rfl,btxe ; Senden-beenden von Int-Routine? rjmp stoptx ; Beende Sendevorgang sbrc rfl,btxa ; Ist die Sendeausgabe aktiv? rjmp loop ; (Während Aussendung keine Parameter!) sbrc rfl,besc ; Parameter mit Menue holen? rjmp getpar ; Hole Parameter über SIO rjmp loop ; weiter im Kreis herum ; ; Startet Sendeprozess ; starttx: sbrc rfl,btxa ; Nicht neu starten, wenn schon aktiv rjmp loop ; Wieder raus! cbr rfl,bmstx ; Setze Flag bit zurück sbi pctrl,bcts ; Stop CTS-Leitung ldi YH,HIGH(srtx) ; Sende-Pointer auf Pufferanfang ldi YL,LOW(srtx) mov rcth,rikh ; Kurze Verzögerung bis zum Start mov rctl,rikl clr rnsc ; Auszugebendes Zeichen beendet, provoziert ; Laden des nächsten Zeichens bei der Senderoutine sbi pdrr,boc1 ; Ausgabe Pin OC1=PB3 auf Ausgang clr rmp ; Setze Timer-Werte out TCCR1A,rmp ; OC1 inaktivieren out TCNT1H,rmp ; Timer-Register auf Null setzen out TCNT1L,rmp out OCR1AH,rcmh ; Compare Match auf Dauer entout OCR1AL,rcml ; sprechend der NF-Frequenz ldi rmp,t1CompInt ; Ermögliche Compare Int out TIMSK,rmp ldi rmp,t1TaktInt ; Clear Timer on Compare ; Match und Prescaler CK/8 out TCCR1B,rmp ; Timer-Takt starten rjmp loop ; und CPU bis zum Timer-Int schlafen legen ; ab jetzt läuft wieder alles automatisch ab ; ; Beendet Sendevorgang ; stoptx: cbr rfl,bmtxe ; Setze Beenden-Flag zurück cbi pctrl,bcts ; CTS wieder einschalten ldi ZH,HIGH(2*txtcur) ; Gib Cursor an SIO aus ldi ZL,LOW(2*txtcur) rcall txtext cpi XL,LOW(srtx) ; Schon Zeichen eingegeben? breq loop ; Nein, schlafen legen bis SIO-RX kommt ldi ZH,HIGH(srtx) ; Gepufferte Zeichen auf Cursorldi ZL,LOW(srtx) ; zeile ausgeben an SIO-TX stoptx1: ld rmp,z+ ; Zeichen aus Puffer lesen rcall txch ; an SIO senden cp ZL,XL ; Schon alle ausgegeben? brcs stoptx1 ; Weiter ausgeben rjmp loop ; Alles ausgegeben, schlafen legen ; ; Getpar holt menuegesteuert Parameter vom User ; getpar: ldi rmp,siorxtx ; Rx-Interrupts abschalten out UCR,rmp ; reiner Polling-Betrieb getpar0: rcall calctc ; Rechne aktuelle Parameter um getpara: rcall txpar ; Gib die Parameter aus getparb: ldi ZH,HIGH(2*txtmenue) ; Gib Menue aus ldi ZL,LOW(2*txtmenue) rcall txtext rcall rxch ; Hole ein Zeichen von SIO http://www.avr-asm-tutorial.net/cq-dl/teil4/Cw01.html (1 of 2)1/20/2009 7:49:44 PM
cq-dl-Beiträge zu AVR-Mikrocontrollern
cpi rmp,chesc ; Ende-Zeichen ESCAPE? brne getpar1 ; Nein, mach weiter im Menue cbr rfl,bmesc ; Setze Menue-Flag zurück rjmp start1 ; Ende, starte fast alles neu getpar1: cpi rmp,'s' ; Speichern gewählt? breq getpars ; geh zum Speichern cpi rmp,'l' ; Lesen gewählt? breq getparl ; geh zum Lesen cpi rmp,'d' ; Default-Werte gewählt? breq getpard ; Setze Default Werte ldi ZH,HIGH(2*txtf); Zeiger auf Frequenzzeile ldi ZL,LOW(2*txtf) ; im Menue cpi rmp,'f' ; Frequenzeingabe gewählt? breq getpar2 ; ja, hole Zahl ldi ZH,HIGH(2*txtg); Geschwindigkeitseingabe ldi ZL,LOW(2*txtg) ; Zeiger setzen cpi rmp,'g' ; Geschwindigkeitseingabe gewählt? breq getpar2 ; ja, hole Zahl ldi ZH,HIGH(2*txtt) ; Zeiger auf Texteingabe ldi ZL,LOW(2*txtt) cpi rmp,'t' ; Texteingabe gewählt? brne getpar0 ; Nein, gib dem User noch mal das Menue getpar2: ; Hole eine Zahlen- oder Texteingabe in den Puffer push rmp ; wird noch gebraucht (gewählter Menuepunkt) rcall txch ; Echo char an SIO zurück ldi rmp,chcr ; Mache neue Zeile rcall txch rcall txtext ; Gib den ausgewählten Menuetext aus ldi XH,HIGH(srtx) ; Empfangspuffer auf Anfang ldi XL,LOW(srtx) getpar3: rcall rxch ; Hole char von SIO st x+,rmp ; in Puffer out UDR,rmp ; Echo an SIO cpi rmp,chcr ; Ende der Eingabe? brne getpar3 ; Weiter mit Eingabe clr rmp ; String Nullterminieren st x,rmp pop rmp ; Menuepunkt wieder vom Stapel holen cpi rmp,'t' ; Texteingabe gewählt? breq getpara ; Ja, schon fertig, gib die Parameter aus push rmp ; Wird weiterhin gebraucht (Menuepunkt) rcall lese16 ; ASCII-Zahl im Puffer in binär ; in R1:R0 umwandeln pop rmp ; Menuepunkt wieder herstellen brcs getpare ; Fehler in Zahl, Fehlermeldung ausgeben cpi rmp,'f' ; Frequenz gewählt? brne getparg ; Nein, Geschwindigkeit gewählt! mov rmp,R1 ; Zahl zu groß? cpi rmp,0x10 ; Frequenz > 4095 Hz? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,0x01 ; Frequenz < 256 Hz? brcs getpark ; Fehlermeldung Zahl zu niedrig sts sfrq,R0 ; Zahl ok, übertragen sts sfrq+1,R1 rjmp getpar0 ; Rechne Parameter neu aus und gebe aus getparg: ; Neue Geschwindigkeit eingegeben tst R1 ; Zahl <256? brne getparh ; Nein, Fehlermeldung Zahl zu groß mov rmp,R0 cpi rmp,201 ; Zahl >200? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,10 ; Zahl <10? brcs getpark ; Zahl zu niedrig sts sbpm,R0 ; Zahl ok, übertragen rjmp getpar0 ; Beginne Neu mit Berechnung und Ausgabe getpars: ; Speichern der eingestellten Werte im EEPROM rcall ewrite ; Alles übertragen ldi ZH,HIGH(2*txteepw); Meldung ausgeben ldi ZL,LOW(2*txteepw) rcall txtext rjmp getparb ; Menuepunkte ausgeben und weiter getparl: ; Lesen der Werte aus dem EEPROM rcall ecopy ; Alles ins SRAM übertragen rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpard: ; Default-Werte setzen ldi ZH,HIGH(sfrq) ; Zeiger auf Frequenz ldi ZL,LOW(sfrq) ldi rmp,LOW(cfrq) ; LSB Default-Frequenz setzen st z+,rmp ; in SRRAM-Speicher ldi rmp,HIGH(cfrq) ; MSB Default-Frequenz setzen st z+,rmp ; in SRAM-Speicher ldi rmp,cbpm ; Default-Geschwindigkeit st z+,rmp ; in SRAM-Speicher rcall getdeftext ; rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpare: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtzahl) ; Fehler in Zahl ldi ZL,LOW(2*txtzahl) rcall txtext rjmp getpara getpark: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtklein) ; Zahl zu niedrig ldi ZL,LOW(2*txtklein) rcall txtext rjmp getpara getparh: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txthoch) ; Zahl zu hoch ldi ZL,LOW(2*txthoch) rcall txtext rjmp getpara getdeftext: ; Default text in Speicher ab Z ldi rmp,'<' ; Verkehrsanfang, eingefügt 2313=>8515 st z+,rmp ldi rmp,10 ; Testtext in Speicher mov R0,rmp ; in Zähler getdeftext1: ldi rmp,'P' ; Paris st z+,rmp ldi rmp,'A' st z+,rmp ldi rmp,'R' st z+,rmp ldi rmp,'I' st z+,rmp ldi rmp,'S' st z+,rmp ldi rmp,' ' st z+,rmp dec R0 brne getdeftext1 sbiw ZL,1 ; Eins zurück ldi rmp,'>' st z+,rmp ldi rmp,chcr ; Textpuffer mit Carriage Return st z+,rmp clr rmp ; und Nullterminator st z,rmp ; entleeren ret ; ; Warte auf char von der SIO mit Echo ; rxch: in rmp,USR ; Lese Control register sbrs rmp,RXC ; Character vorhanden? rjmp rxch ; Noch nicht, weiter warten in rmp,UDR ; Lese character aud Datenregister ret ; und zurück ; ; Sende text character in rmp an SIO mit Prüfung ; txch: push rmp ; Rette Zeichen auf Stapel txch1: in rmp,USR ; Senderegister leer? sbrs rmp,UDRE ; Ist das UDRE-Bit gesetzt rjmp txch1 ; Nein, weiter warten pop rmp ; Hole Zeichen vom Stapel out UDR,rmp ; Character senden cpi rmp,chcr ; Nach Carriage Return noch ein brne txch2 ; Linefeed? ldi rmp,chlf ; auch den noch senden! rcall txch ldi rmp,chcr ; und Zeichen wieder herstellen txch2: ret ; ; Morsecode der ASCII-Zeichen 0x20 bis 0x5F ; unteres Byte = Code (0=Punkt, 1=Strich) ; oberes Byte = Anzahl Punkte/Striche ; Bit 7 = 1: Leerzeichen ; morse: ; Zeichen 20 .. 2F .DB 0b11100000,0b10000011 ; Blank .DB 0b01000000,5 ; ! = Warten .DB 0b01001000,6 ; " .DB 0b11011000,5 ; # = ñ .DB 0b01101000,5 ; $ = á, å .DB 0b01000000,5 ; % = é .DB 0b00000000,0 ; & = nicht benutzt .DB 0b01111000,6 ; ' .DB 0b10110000,5 ; ( .DB 0b10110100,6 ; ) .DB 0b00000000,0 ; * = nicht benutzt .DB 0b00010100,6 ; + = Spruchende .DB 0b11001100,6 ; , .DB 0b10000100,6 ; .DB 0b01010100,6 ; . .DB 0b10010000,5 ; / ;Zeichen 30 .. 3F .DB 0b11111000,5 ; 0 .DB 0b01111000,5 ; 1 .DB 0b00111000,5 ; 2 .DB 0b00011000,5 ; 3 .DB 0b00001000,5 ; 4 .DB 0b00000000,5 ; 5 .DB 0b10000000,5 ; 6 .DB 0b11000000,5 ; 7 .DB 0b11100000,5 ; 8 .DB 0b11110000,5 ; 9 .DB 0b11100000,6 ; : .DB 0b10101000,6 ; ; .DB 0b10101000,5 ; < = Verkehrsanfang .DB 0b10001000,5 ; = .DB 0b01010000,5 ; > = Verkehrsende .DB 0b00110000,6 ; ? ;Zeichen 40 .. 4F .DB 0b11110000,4 ; @ = ch .DB 0b01000000,2 ; A .DB 0b10000000,4 ; B .DB 0b10100000,4 ; C .DB 0b10000000,3 ; D .DB 0b00000000,1 ; E .DB 0b00100000,4 ; F .DB 0b11000000,3 ; G .DB 0b00000000,4 ; H .DB 0b00000000,2 ; I .DB 0b01110000,4 ; J .DB 0b10100000,3 ; K .DB 0b01000000,4 ; L .DB 0b11000000,2 ; M .DB 0b10000000,2 ; N .DB 0b11100000,3 ; O ;Zeichen 50 .. 5F .DB 0b01100000,4 ; P .DB 0b11010000,4 ; Q .DB 0b01000000,3 ; R .DB 0b00000000,3 ; S .DB 0b10000000,1 ; T .DB 0b00100000,3 ; U .DB 0b00010000,4 ; V .DB 0b01100000,3 ; W .DB 0b10010000,4 ; X .DB 0b10110000,4 ; Y .DB 0b11000000,4 ; Z .DB 0b01010000,4 ; [ = Ä .DB 0b11100000,4 ; \ = Ö .DB 0b00110000,4 ; ] = Ü .DB 0b00000000,8 ; ^ = Irrung .DB 0b00110100,6 ; _ morseende: .DW morseende-morse ; Prüfzahl, muss 0x0040 sein ; ; Texte für die serielle Schnittstelle ; Hinweis: Die chk-Werte sind zum Überprüfen im Listing ; eingefügt. Es gibt einen Bug im AVR-Assembler von ATMEL, ; der zu falschen Adressen führt, wenn bestimmte Kombina; tionen von Byte-Konstanten verwendet werden. Dieser Bug ; ist seit zwei Jahren gemeldet und noch immer nicht besei; tigt! Teilweise sind die abenteuerlichen Konstruktionen ; in dieser Liste zur Umgehung dieses Bugs verwendet. ; ; Eingangsmeldung zu Beginn txtid: .DB chff,chcr .DB "+---------------------------------+",chcr .DB "| Morseprogramm (C)2002 by DG4FAC |",chcr .DB "+---------------------------------+",chcr .DB cnul,cnul chk1: .DW chk1 ; für Assembler Bug ; Text für Parameterliste txtpar1: .DB chcr,'E',"ingestellte Parameter:",chcr,'*' txtf: .DB "NF-Frequenz (256..4095 Hz) = ",cnul chk2: ; Bug-Check .DW chk2 txtpar2: .DB " Hz ",chcr,'*' txtg: .DB "Geschwindigkeit (10..200 BpM) = ",cnul chk3: ; Bug-Check .DW chk3 txtpar3: .DB " BpM",chcr,' ',"ccm = ",cnul,cnul chk4: ; Bug-Check .DW chk4 txtpar4: .DB ", Ints (kurz) = ",cnul,cnul chk5: ; Bug-Check .DW chk5 txtpar5: .DB ", Ints (lang) = ",cnul,cnul chk6: ; Bug-Check .DW chk6 txtpar6: .DB chcr,'*' txtt: .DB "Text = ",cnul chk7: ; Bug-Check .DW chk7 txtcur: .DB chcr,'=','>',cnul chk8: ; Bug-Check .DW chk8 txtmenue: .DB "Einstellungen: f=NF-Frequenz, g=Geschwindigkeit," .DB " t=Text, s=Speichern,",chcr .DB " l=Lesen, d=Default, ESC=Ende! ",chcr .DB "(f,g,t,s,l,d,ESC) => ",cnul chk9: ; Bug-Check .DW chk9 ; Prüfzahl für Assembler-Bug txtzahl: .DB chcr,'F',"ehler in Ziffer oder Zahl bzw. Zahl zu gross!",cnul chk10: ; Bug-Check .DW chk10 ; Prüfzahl für Assembler-Bug txtklein: .DB chcr,'*',"* Zahl zu niedrig! **",cnul,cnul chk11: ; Bug-Check .DW chk11 ; Prüfzahl für Assembler-Bug txthoch: .DB chcr,'*',"* Zahl zu hoch! **",cnul,cnul chk12: ; Bug-Check .DW chk12 ; Prüfzahl für Assembler-Bug txteepw: .DB chcr,'P',"arameter ins EEPROM geschrieben.",chcr,cnul chk13: ; Bug-Check .DW chk13 ; ; Copyright-Info .DB "C(2)00 2ybD 4GAF C" ; ; Programm-Code Ende ; ; ****************************************** ; * EEPROM-Inhalt mit Default beginnt hier * ; ****************************************** ; .ESEG .ORG 0x0000 ; efrq: ; Die Default-NF-Frequenz .DW cfrq ebpm: ; Die Default-Geschwindigkeit in BpM .DB cbpm etxt: ; Dieser Text wird zu Beginn ausgegeben .DB "hello!" .DB chcr,cnul etxte: ; ; Copyright-Info ; .DB "(C)2002 by DG4FAC" ; ; Ende des EEPROM-Segmentes ;
; ******************************************************** ; * CW.asm gibt Morsezeichen aus, die im EEPROM-Speicher * ; * gespeichert sind und die über die serielle Schnitt- * ; * stelle des 2313-Experimentier-Boards eingegeben wer- * ; * den. Ausgabe NF an PB3. Baudrate 9k6. Takt 10 MHz. * ; * AVR AT90S2313. Programm (C)2002 by DG4FAC G.Schmidt * ; * Version 0.1 vom 28.12.2001 * ; * Bugs und Dankschreiben an [email protected] * ; ******************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\2313def.inc" .LIST ; ; Benutzte Register ; ; Register R0 wird für Lesen im Programmspeicher benutzt ; Register R0..R9 werden für Berechnungen benutzt ; .DEF rcml = R10 ; Compare Match-Zahl, LSB .DEF rcmh = R11 ; dto., MSB .DEF rikl = R12 ; Interrupt-Anzahl kurz, LSB .DEF rikh = R13 ; dto., MSB .DEF rill = R14 ; Interrupt-Anzahl lang, LSB .DEF rilh = R15 ; dto., MSB .DEF rmp = R16 ; Multipurpose register, nicht bei Ints .DEF rim = R17 ; Interrupt multi-purpose .DEF rfl = R18 ; Flag Register, bei Ints und Normal .DEF rst = R19 ; Statusregister bei Interrupts .DEF rnsc = R20 ; Anzahl Punkte/Striche beim Senden .DEF rmcd = R21 ; Morsecode beim Senden .DEF x22 = R22 ; unbenutzt .DEF x23 = R23 ; unbenutzt .DEF rctl = R24 ; Zähler für Interrupt Tonerzeugung .DEF rcth = R25 ; (wird als Doppelregister verwendet) ; ; Register XH:XL R27:R26 Zeiger in den Empfangspuffer ; Register YH:YL R29:R28 Zeiger beim Senden ; Register ZH:ZL R31:R30 Zeiger für Lesen im Programmspeicher ; ; Bits im Flagregister ; .EQU besc = 7 ; ESCape-Sequenz, hole Parameter .EQU bmesc = 0x80 ; Maskenbyte für ESC .EQU bstx = 6 ; Starte Sendeprozess .EQU bmstx = 0x40 ; Maskenbyte für bstx .EQU bctx = 5 ; Beende Sendeprozess nach Leerzeichen .EQU bmctx = 0x20 ; Maskenbyte für bctx .EQU btxa = 4 ; Sendeprozess aktiv .EQU bmtxa = 0x10 ; Maskenbyte für btxc .EQU btxe = 3 ; Abschluss des Sendens .EQU bmtxe = 0x08 ; Maskenbyte für btxe .EQU bpl = 2 ; Lange Pause zur Buchstabentrennung .EQU bmpl = 0x04 ; Maskenbyte für bpl .EQU bpk = 1 ; Kurze Pause zur Punkt/Strich-Trennung .EQU bmpk = 0x02 ; Maskenbyte für bpk .EQU bquiet = 0 ; Dauerhaft inaktiviert bei Leerzeichen .EQU bmquiet = 0x01 ; Maskenbyte für bquiet ; ; Default-Werte ; .EQU cfrq=1000 ; NF-Frequenz .EQU cbpm=60 ; Gebegeschwindigkeit ; ; Basisdefinitionen variabel nach Schaltung ; .EQU fqu = 10000000 ; Quarzfrequenz des AVR .EQU fbd = 9600 ; Baudrate des UART .EQU pctrl = PORTB ; Control-Port für RTS/CTS .EQU pdrr = DDRB ; Datenrichtungsregister Controlport .EQU brts = PB2 ; RTS bit Input .EQU bcts = PB4 ; CTS bit Output .EQU boc1 = PB3 ; NF-Ausgabe über OC1-Pin ; ; Umrechnungen in Timer- und Counterwerte ; .EQU nps = 8 ; Prescaler-Einstellung von Timer 1 .EQU ccm = fqu/nps/2; Konstante für Compare Match .EQU cint =2941 ; Konstante für Int-Berechnung, ; experimentell ermittelt ; ; Definitionen fix ; .EQU bddv = (fqu/(16*fbd))-1 ; Baudraten-Teiler .EQU cnul = 0x00 ; Ende für nullterminierte Strings .EQU chbsp = 0x08 ; Backspace character .EQU chcr = 0x0D ; Carriage Return character .EQU chlf = 0x0A ; Line Feed character .EQU chff = 0x0C ; Form Feed character .EQU chesc= 0x1B ; ESCape-Zeichen ; ; Definitionen I/O ; ; Definitionen für Timer-Controlregister TCCR1A .EQU t1aus = 0b10000000 ; Schaltet den NF-Ausgang aus .EQU t1ein = 0b01000000 ; Schaltet den NF-Ausgang ein ; Definition für Timer-Interrupt-Mask-Register TIMSK .EQU t1CompInt=0b01000000 ; Interrupt bei Compare Match ; Definition für Timer-Controlregister TCCR1B .EQU t1TaktInt=0b00001010 ; Timer-Takt CLK / 8, Clear Comp ; Definitionen für UART-Betrieb in UCR .EQU siorxtx = 0b00011000 ; Betrieb RX und TX ohne Int .EQU siorxint= 0b10011000 ; Betrieb RX mit Int, TX ohne Int ; Definition für den SLEEP-Mode in MCUCR .EQU sleepmode=0b00100000 ; Aufwachen bei Interrupt ; ; Positionen im SRAM ; .EQU sfrq = 0x0060 ; Eingestellte NF-Frequenz, Wort .EQU sbpm = 0x0062 ; Eingestellte Geschwindigkeit, Byte .EQU srtx = 0x0063 ; RAM-Puffer für Ein-/Ausgabezeile .EQU srte = 0x00B3 ; Ende des nutzbaren Puffers, benutzt werden ; auch zwei weitere Folgebytes ; ; Programm beginnt hier ; .CSEG .ORG 0x0000 ; ; Reset- und Interrupt-Sprungtabelle ; rjmp start ; Reset-Vector reti ; Ext Int 0, nicht benutzt reti ; Ext Int 1, nicht benutzt reti ; Timer 1 Capture Event, nicht benutzt rjmp itc1m ; Timer 1 Compare Match reti ; Timer 1 Overflow, nicht benutzt reti ; Timer 0 Overflow, nicht benutzt rjmp iurxc ; UART Rx Complete reti ; UART Tx data register empty, nicht benutzt reti ; UART Tx All sent, nicht benutzt ; ; Interrupt-Service-Routinen ; itc1m: ; Timer 1 Compare Match Interrupt in rst,SREG ; Rette Statusregister sbiw rctl,1 ; Zähler eins abzählen brne itc1mz ; Raus, wenn nicht Null mov rcth,rikh ; Setze Zähler auf kurze Dauer mov rctl,rikl ldi rim,t1aus ; Ton auf aus tst rnsc ; Zeichen fertig gesendet? breq itc1m0 ; Verzweige, wenn Zeichen fertig sbrc rfl,bpk ; kurze Pausen-Flag? rjmp itc1ms ; Pause zwischen Punkt/Strich war schon sbr rfl,bmpk ; Setze kurze Pause-Flag rjmp itc1my ; und Ausgabe auf inaktiv, fertig itc1ms: ; Sende nächsten Punkt/Strich cbr rfl,bmpk ; Lösche kurze Pause-Flag ldi rim,t1ein; Ton an = Toggle dec rnsc ; Weiteren Punkt/Strich gesendet rol rmcd ; Punkt oder Strich senden? brcc itc1my ; Punkt senden mov rcth,rilh ; Lange Dauer einstellen mov rctl,rill rjmp itc1my itc1m0: ; Zeichen fertig gesendet sbrc rfl,bctx ; Sendung beenden? rjmp itc1mx sbrc rfl,bpl ; Lange Pause senden? rjmp itc1mn ; Nächsten Buchstaben beginnen sbr rfl,bmpl ; Setze langes Pausen-Flag mov rcth,rilh ; Dauer auf lang stellen mov rctl,rill itc1my: ; Stelle Modus inaktiv/toggle ein sbrc rfl,bquiet ; bei Leerzeichen Ton aus ldi rim,t1aus ; Ton auf aus out TCCR1A,rim itc1mz: out SREG,rst ; Stelle Status wieder her reti itc1mn: cbr rfl,bmpl ; Langes Pausen-Flag aus ld rim,y+ ; Nächsten Buchstaben lesen tst rim ; Null-Terminator breq itc1mn1 cpi rim,chcr ; Ende der Zeile? brne itc1mn2 itc1mn1: sbr rfl,bmctx ; Setze beenden-Flag ldi rim,' ' ; Sende noch ein Leerzeichen itc1mn2: out UDR,rim ; Debug subi rim,0x20 ; ASCII-Control-Zeichen weg brcc itc1mn3 ldi rim,'?'-0x20 ; Sende Fragezeichen itc1mn3: cpi rim,0x40 ; Kleinbuchstabe? brcs itc1mn4 subi rim,0x20 ; in Grossbuchstaben wandeln cpi rim,0x40 brcs itc1mn4 ldi rim,'?'-0x20 itc1mn4: add rim,rim ; Mal 2 für Tabelle push ZH ; Register retten push ZL push R0 ldi ZH,HIGH(2*morse) ; Zeichentabelle laden ldi ZL,LOW(2*morse) add ZL,rim ; Zeichen dazu zählen brcc itc1mn5 ; Kein Übertrag? inc ZH ; Übertrag itc1mn5: lpm ; Lese Zeichencode aus Tabelle mov rmcd,R0 ; in Zeichencode-Register adiw ZL,1 ; Zeiger auf nächstes Byte lpm ; aus Tabelle lesen mov rnsc,R0 ; Anzahl Striche/Punkte pop R0 ; Wieder herstellen der Register pop ZL pop ZH tst rnsc ; Undefiniertes Zeichen? breq itc1mn ; Überspringe Zeichen sbr rfl,bmquiet ; Leerzeichen sbrs rnsc,7 ; Leerzeichen? cbr rfl,bmquiet ; Kein Leerzeichen cbr rnsc,0x80 ; Lösche höchstes Bit mov rim,YL ; CTS einschalten? sub rim,XL brcs itc1mn6 ; Ausschalten cpi rim,3 brcs itc1mn6 cbi pctrl,bcts ; CTS einschalten rjmp itc1ms itc1mn6: sbi pctrl,bcts ; CTS ausschalten rjmp itc1ms ; Sende ein Zeichen itc1mx: ; Sendung einstellen clr rim ; Timer 1 abschalten out TCCR1B,rim ; Timer-Takt aus out TCNT1H,rim ; Timer auf Null stellen out TCNT1L,rim out TIMSK,rim ; Timer Interrupts aus out TCCR1A,rim ; Timer Compare Mode aus cbr rfl,bmctx+bmtxa ; Ende-Flag und aktiv ausschalten sbr rfl,bmtxe ; Beenden-Flag einschalten ldi YH,HIGH(srte) ; Buffer auf Ende ldi YL,LOW(srte) st Y,rim ; Null-terminieren rjmp itc1mz ; ; UART Rx Complete Interrupt ; iurxc: in rst,SREG ; Rette Statusregister in rim,UDR ; Lese Zeichen von SIO cpi rim,chesc ; ESCape-Sequenz? brne iurx1 sbr rfl,bmesc ; Setze ESCape-Bit rjmp iurxz iurx1: cpi rim,chbsp ; Backspace-Char? brne iurx2 cpi XL,LOW(srtx) ; Schon auf Anfang? breq iurxz sbiw XL,1 ; Eine Position zurück ldi rim,chcr ; Zeilenabschluss st x+,rim clr rim ; Null-terminieren st x,rim sbiw XL,1 ; zurück ldi rim,chbsp ; Backspace zurücksenden rjmp iurxe ; Echo character iurx2: cpi XL,low(srte) ; Pufferüberlauf? brcs iurx3 ; Nein, weiter ldi rim,chcr ; Character überschreiben iurx3: cpi rim,chcr ; Zeilenende? brne iurxw ; Nein, in Puffer und fertig sbrs rfl,btxa ; Überspringe Ausgabe wenn aktiv out UDR,rim ; Carriage Return schon mal ausgeben st x+,rim ; CR-Zeichen anhängen clr rim ; Null terminieren st x,rim ldi XH,HIGH(srtx) ; Puffer auf Anfang ldi XL,LOW(srtx) sbr rfl,bmstx ; Über Flag Sender starten ldi rim,chlf ; Sende Zeilenvorschub rjmp iurxe ; Echo Line-Feed iurxw: st x+,rim ; Speichere character ldi rim,chcr ; Abschluss terminieren st x+,rim ; mit Carriage return clr rim ; Null-terminieren st x,rim sbrs rfl,btxa ; Sender aktiv? rjmp iurxs ; Nein, CTS nicht prüfen mov rim,YL ; CTS ein oder aus? sub rim,XL ; Distanz zwischen Ein- und Ausgabe brcs iurxo ; Eingabe > Ausgabe: CTS aus cpi rim,3 ; mindestens zwei Zeichen Vorlauf? brcs iurxo ; Nein, dann CTS ausschalten cbi pctrl,bcts ; CTS einschalten rjmp iurxs ; Zeichen herstellen und raus iurxo: sbi pctrl,bcts ; CTS ausschalten iurxs: sbiw XL,2 ; Char wieder herstellen ld rim,x+ ; durch Lesen im SRAM iurxe: sbrs rfl,btxa ; Keine Ausgabe, wenn aktiv out UDR,rim ; Echo character an SIO zurück iurxz: out SREG,rst ; Stelle Status wieder her reti ; Ende des Interrupts ; ; Diverse eigenständige Unterprogramme ; ; Kopiert den EEPROM-Inhalt in das SRAM ; ecopy: sbic EECR,EEWE ; Warte, bis EEPROM bereit rjmp ecopy ; Noch nicht bereit clr YH ; Startadresse im EEPROM auf Null clr YL ldi ZH,HIGH(sfrq) ; Startadresse im SRAM ldi ZL,LOW(sfrq) ; auf ersten Parameter ecopy1: out EEAR,YL ; Leseadresse im EEPROM ; 2313 hat nur 128 Bytes, daher nur unteres Register sbi EECR,EERE ; Lese-Strobe setzen cbi EECR,EERE ; und wieder ausschalten in rmp,EEDR ; Byte aus EEPROM-Datenregister lesen st Z+,rmp ; und in SRAM schreiben adiw YL,1 ; nächste EEPROM-Adresse anwählen tst rmp ; Null-Terminator? brne ecopy1 ; Nein: weiter kopieren ret ; ; Schreibe Parameter in das EEPROM zurück ; ewrite: ldi ZH,HIGH(sfrq) ; Zeiger auf SRAM ldi ZL,LOW(sfrq) clr XH ; Zeiger in das EEPROM clr XL ewrite1: sbic EECR,EEWE ; Frage Write-Bit im EEPROM rjmp ewrite1 ; ab und wiederhole bis EEPROM ready out EEARL,XL ; Schreibadresse im EEPROM einstellen ; Hat nur 7 Bit, daher nur LSB ld rmp,Z+ ; Lese Byte aus SRAM in Register out EEDR,rmp ; in das EEPROM-Datenregister cli ; Keine Interrupts mehr beim Schreibvorgang sbi EECR,EEMWE ; Setze EEPROM Master Write Enable sbi EECR,EEWE ; Löse Schreibvorgang im EEPROM aus sei ; Jetzt Interrupts wieder zulassen adiw XL,1 ; Nächste EEPROM-Adresse in X-Zeiger cpi XL,128 ; Ende EEPROM erreicht? brcc ewrite2 ; Überlänge! Null fehlt! Abbrechen! tst rmp ; Nullterminiert? brne ewrite1 ; Noch weitere Bytes schreiben ewrite2: ret ; ; Lese16 wandelt eine ASCII-Zahl im Puffer in binär ; in R1:R0, bei Fehler Rückkehr mit gesetztem Carry-Bit ; lese16: clr R0 ; Leeren Resultat-Register R1:R0 clr R1 ldi ZH,HIGH(srtx) ; Zeige auf Pufferanfang ldi ZL,LOW(srtx) lese16a: ld rmp,Z+ ; Lese ASCII-Ziffer aus SRAM cpi rmp,chcr ; Ende der Zahl mit Carriage Return? breq lese16ok ; Zahl mit Carriage Return beendet cpi rmp,cnul ; Ende der Zahl mit Nullterminiert? breq lese16ok ; Ende mit Nullterminierung cpi rmp,'9'+1 ; Ziffer > ASCII-9? brcc lese16no ; Ja, Fehler! cpi rmp,'0' ; Ziffer < ASCII-0 brcs lese16no ; Ja, auch Fehler subi rmp,'0' ; Wandle Ziffer in binär ; Ab hier wird das bisherige Resultat mit 10 multi; pliziert mov R2,R0 ; Kopiere Binärzahl in Hilfsregister R3:R2 mov R3,R1 add R0,R0 ; Multipliziere mit 2 durch Addieren 16 Bit adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R2 ; Addiere die Kopie der Zahl adc R1,R3 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! ; Hier ist das Multiplizieren mit 10 beendet. add R0,rmp ; Addiere Ziffer zum Resultat hinzu brcc lese16a ; Kein Überlauf des unteren Bytes inc R1 ; Überlauf, oberes Byte erhöhen brne lese16a ; Kein Überlauf des oberen Bytes lese16no: sec ; Setze Carry-Bit bei Fehler und kehre zurück ret lese16ok: clc ; Clear Carry bei fehlerfrei ret ; ; Wandle 16-Bit-Zahl in R1:R0 in ASCII in R4..R9 (nullterm.) ; unterdrücke führende Nullen, sende Ergebnis ohne führende ; Nullen über die SIO ; b16asc: clr ZH ; Z zeigt auf höchste ASCII-Ziffer in ldi ZL,4 ; Register R4 (Zeiger in Register!) ldi rmp,HIGH(10000) ; Beginne mit Zehntausendern mov R3,rmp ; oberes Byte in R3 ldi rmp,LOW(10000) mov R2,rmp ; unteres Byte in R2 rcall b16sub ; Ermittle ASCII-code der Zehntausender ; Stelle durch fortgesetztes Subtrahieren von 10000 ldi rmp,HIGH(1000) ; Weiter mit Tausendern mov R3,rmp ldi rmp,LOW(1000) mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Tausender clr R3 ; Weiter mit Hunderten ldi rmp,100 mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Hunderter ldi rmp,10 ; Weiter mit Zehnern mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Zehner ldi rmp,'0' ; Rest sind Einer add rmp,R0 mov R8,rmp ; R8 kriegt die ASCII-Einer clr R9 ; Nullterminieren in R9 ldi ZL,4 ; Z auf 10000-er Zeichen b16asc1: cpi ZL,9 ; Ende der Zeichenkette erreicht? breq b16asc2 ; Ja, raus ld rmp,z+ ; Zeichen aus Register kopieren cpi rmp,'0' ; Führende Null? breq b16asc1 ; Ja, weitermachen b16asc2: dec ZL ; auf vorheriges Zeichen setzen ; ; Sende nullterminierten Text aus SRAM an SIO ; Z zeigt nicht ins SRAM, sondern in die Register mit ; dem Ergebnis! ; Das Registerpaar Z zeigt auf das erste Zeichen ; txstxt: ld rmp,z+ ; Lese Zeichen aus SRAM/Register tst rmp ; Nullterminator erreicht? breq txstxt1 ; Ja, raus und fertig rcall txch ; Sende character über SIO rjmp txstxt ; Weitermachen mit nächstem Zeichen txstxt1: ret ; ; Ermittle ASCII-Code einer Ziffer der 16-Bit-Binärzahl in ; R1:R0 durch fortgesetztes Subtrahieren einer 16-Bit; Hilfszahl (10000, 1000, 100, 10) in R3:R2. Tritt ein ; Überlauf ab, dann ist die Ziffer gefunden ; (Unterroutine für b16asc) ; b16sub: ldi rmp,'0' ; Setze ASCII-Null b16sub1: sub R0,R2 ; Subtrahiere die Hilfszahl sbc R1,R3 ; in 16-bit brcs b16sub2 ; Ende subtrahieren erreicht? inc rmp ; Einer geht noch! rjmp b16sub1 ; Weiter mit subtrahieren! b16sub2: add R0,R2 ; Zu weit, addiere wieder zurück adc R1,R3 st Z+,rmp ; ASCII-Ziffer in Register schreiben ret ; und zurück ; ; Sende nullterminierten Text aus dem Programmspeicher ; über die SIO aus ; txtext: lpm ; Lese Zeichen aus Programmspeicher mit Zeiger Z tst R0 ; Ergebnis im Register R0 hat Null erreicht? breq txtend ; Ja, raus aus der Routine mov rmp,R0 ; Kopiere Zeichen in Senderegister rcall txch ; Sende character mit Prüfung adiw zl,1 ; Zeiger auf nächstes Zeichen rjmp txtext ; Weitermachen bis Null txtend: ret ; Kehre zurück ; ; Liste alle Parameter über die SIO-Schnittstelle auf ; txpar: ldi ZH,HIGH(2*txtpar1) ; Sende Parametervorspann ldi ZL,LOW(2*txtpar1) ; für NF-Frequenz rcall txtext lds R0,sfrq ; Zeige eingestellte Frequenz an lds R1,sfrq+1 rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar2) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar2) ; Geschwindigkeit an rcall txtext lds R0,sbpm ; Zeige Geschwindigkeit an clr R1 ; nur 8 Bit! rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar3) ; Zeige Vorspann für Counterldi ZL,LOW(2*txtpar3) ; Compare-Match-Zahl ccm an rcall txtext mov R1,rcmh ; Compare-Match-Zahl in R1:R0 mov R0,rcml rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar4) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar4) ; Anzahl NF-Ints bei Punkt an rcall txtext mov R1,rikh ; Anzahl Ints bei Punkt in R1:R0 mov R0,rikl rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar5) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar5) ; Anzahl NF-Ints bei Strich an rcall txtext mov R1,rilh ; Anzahl Ints bei Strich in R1:R0 mov R0,rill rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar6) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar6) ; Ausgabetext an rcall txtext ldi ZH,HIGH(srtx) ; Zeiger Z auf Ausgabetext im SRAM ldi ZL,LOW(srtx) rjmp txstxt ; Sende Inhalt SRAM nullterminiert ; ; 32-Bit durch 16-Bit-Division ; ; Dividiert 32-Bit-Zahl in R3:R2:R1:R0 durch R5:R4 ; Ergebnis in R7:R6 (Ergebnis darf nicht > 16 Bit sein!) ; div32: clr R7 ; Clear Ergebnis-Register R7:R6 clr R6 inc R6 ; Stopbit setzen für 16 Divisionsschritte div32a: clc ; Null in Carry schieben rol R0 ; Multipliziere Divident mit 2 rol R1 rol R2 rol R3 brcs div32e ; Carry ist herausgerollt? Dann 1! cp R3,R5 ; Vergleiche oberes Byte brcs div32n ; Ergebnis ist kleiner, also eine 0 brne div32e ; Ergebnis ist größer, also eine 1 cp R2,R4 ; Vergleich MSB gleich, vergleiche unteres ; Byte brcs div32n ; Unteres Byte kleiner, also eine 0 div32e: sub R2,R4 ; Ziehe den Divisor von den oberen 16 Bit ab sbc R3,R5 sec ; Setze das Carry-Bit, Ergebnis ist eine 1 rjmp div32s ; Zum Reinschieben in das Ergebnis div32n: clc ; Lösche Carry-Bit, Ergebnis ist eine 0 div32s: rol R6 ; Schiebe Carry-Bit von rechts in Ergebnis rol R7 brcc div32a ; Ende, wenn eine 1 links herausrollt ret ; ; Multipliziert 16-Bit-Zahl in R1:R0 mit 16-Bit-Zahl in R5:R4 ; Ergebnis 32 Bit in R9:R8:R7:R6, für jeden Zahlenbereich ; Mul16: clr R3 ; Leere obere zwei Bytes der 16-Bit-Zahl clr R2 clr R9 ; Leere Ergebnis-Register R9:R8:R7:R6 clr R8 clr R7 clr R6 Mul16a: clc ; Null ins Carry-Bit ror R5 ; Schiebe unterstes Bit Divisor in Carry ror R4 ; und dividiere Multiplikant durch 2 brcc Mul16b ; Bit war eine 0, Addition überspringen add R6,R0 ; addiere Multiplikator 32 Bit zum adc R7,R1 ; bisherigen Ergebnis, jeweils mit adc R8,R2 ; Überlauf adc R9,R3 Mul16b: tst R4 ; Schon alle Bits ausmultipliziert? brne Mul16c ; Nein, LSB nicht Null, weiter tst R5 ; Teste auch oberes Byte brne Mul16c ; Nein, MSB nicht Null, weiter ret ; Fertig Mul16c: clc ; Null in Carry-Bit schieben rol R0 ; Zahl durch Linksschieben mit 2 rol R1 ; multiplizieren rol R2 rol R3 rjmp Mul16a ; und weiter dividieren ; ; Dividiert 32 Bit-Zahl in R3:R2:R1:R0 durch eine ; 8-Bit-Zahl in R4 und durch 256, Ergebnis gerundet ; in R8:R7, Wertebereich beachten! ; Div32_8: clr R8 ; Ergebnisspeicher R8:R7:R6 leeren clr R7 clr R6 inc R6 ; Stopbit nach 24 Schritten setzen Div32_8a: clc ; Carry-Bit leeren rol R0 ; Divident mit 2 multiplizieren rol R1 ; durch Linksschieben rol R2 rol R3 brcs Div32_8e ; 1 herausgerollt, Ergebnis=1 cp R3,R4 ; Vergleiche oberstes Byte mit Divisor brcs Div32_8n ; Kleiner, Ergebnis = 0 Div32_8e: sub R3,R4 ; Ziehe Divisor von oberstem Byte ab sec ; Setze Carry für Ergebnis = 1 rjmp Div32_8b ; Ins Ergebnis schieben Div32_8n: clc ; Clear Carry für Ergebnis = 0 Div32_8b: rol R6 ; Ergebnis-Bit von rechts her hineinrotieren rol R7 rol R8 brcc Div32_8a ; Weiter, wenn eine Null herausrollt rol R6 ; Binäre Kommastelle eine 1, aufrunden? brcc Div32_8z ; Nein, wenn Null inc R7 ; Aufrunden LSB Ergebnis brne Div32_8z ; Kein Überlauf bei LSB-Erhöhung inc R8 ; Aufrunden MSB Div32_8z: ret ; ; Rechne Parameter um in Timer- und Zählerwerte ; Oben definiert: ccm = Taktfrequenz / Prescale 8 / 2 ; cint = ccm / 200 ; Compare-Match-Zahl für Timer 1 = ccm / NF-Frequenz ; Anzahl Ints bei Punkt = cint * NF-Frequenz / Speed /256 ; Anzahl Ints bei Punkt = Anzahl Ints bei Strich * 3 ; calctc: ldi rmp,BYTE4(ccm) ; Taktabhängige Konstante ccm mov R3,rmp ; in R3:R2:R1:R0, bei 10 MHz: 625.000 ldi rmp,BYTE3(ccm) ; oder 0x00098968 mov R2,rmp ldi rmp,BYTE2(ccm) mov R1,rmp ldi rmp,BYTE1(ccm) mov R0,rmp lds R4,sfrq ; durch NF-Frequenz in R5:R4 lds R5,sfrq+1 rcall div32 ; ergibt Compare Match-Zahl 16-Bit mov rcmh,R7 ; Ergebnis in Speicher rcmh:rcml mov rcml,R6 ldi rmp,HIGH(cint) ; Konstante für Anzahl Ints mov R1,rmp ; bei 10 MHz und Teiler 200: 3125 ldi rmp,LOW(cint) mov R0,rmp lds R4,sfrq ; Mal NF-Frequenz in Hz lds R5,sfrq+1 ; rcall txreg ; Debug info rcall mul16 ; Multplizieren 16 Bit * 16 Bit ; rcall txreg ; Debug info mov R3,R9 ; Ergebnis in R9..R6 nach R3..R0 kopieren mov R2,R8 mov R1,R7 mov R0,R6 lds R4,sbpm ; durch Gebegeschwindigkeit teilen rcall div32_8 ; teilen 32-Bit durch 8-Bit ; rcall txreg ; Debug info mov rikh,R8 ; in Kurzspeicher kopieren mov rikl,R7 mov rilh,R8 ; und in Langspeicher kopieren mov rill,R7 add rill,rill ; Langspeicher mit 2 malnehmen adc rilh,rilh ; durch Addieren add rill,rikl ; und noch einmal dazu zählen adc rilh,rikh ; macht 3 mal so lang ret ; ; Debug code ; ; Display Byte in rmp in Hex an die SIO ; ;txbhx: ; push rmp ; swap rmp ; rcall txnhx1 ; pop rmp ;txnhx1: ; cbr rmp,0b11110000 ; subi rmp,-'0' ; cpi rmp,'9'+1 ; brcs txnhx2 ; subi rmp,-7 ;txnhx2: ; rcall txch ; ret ; ; Display Register R0..R9 in Hex an SIO ; ;txreg: ; ldi rmp,chcr ; rcall txch ; clr ZH ; clr ZL ;txreg1: ; ld rmp,Z+ ; rcall txbhx ; ldi rmp,' ' ; rcall txch ; cpi ZL,11 ; brcs txreg1 ; ldi rmp,chcr ; rcall txch ; ret ; ; Hauptprogramm-Loop, Restart-Vektor ; start: ldi rmp,low(RAMEND) ; Init stack pointer im SRAM out SPL,rmp ; nur 8-Bit-Pointer bei 2313 ldi rmp,bddv ; Baudrate des UART einstellen out UBRR,rmp ldi rmp,siorxtx ; UART Tx und Rx ein, Ints aus out UCR,rmp cbi pdrr,brts ; Bit Richtung ist RTS-Eingang sbi pdrr,bcts ; Bit Richtung ist CTS-Ausgang sbi pctrl,bcts ; CTS ausschalten (invertiert!) rcall ecopy ; Kopiere EEPROM-Inhalt nach RAM ldi ZH,high(2*txtid) ; Sende ID-Text an SIO ldi ZL,low (2*txtid) rcall txtext clr rfl ; Flag auf Anfangswert leer ; ; Bedingungen für Interrupt-Betrieb herstellen ; start1: rcall calctc ; Rechne Timerwerte und Ints aus rcall txpar ; Gib die eingestellten Werte über SIO ldi ZH,HIGH(2*txtcur) ; Sende Cursor-String ldi ZL,LOW(2*txtcur) rcall txtext ldi rmp,sleepmode ; Mode Idle für Sleep einstellen out MCUCR,rmp ; Aufwachen durch Interrupts ldi rmp,siorxint ; Enable RX mit Interrupt out UCR,rmp cbi pctrl,bcts ; Aktiviere CTS-Leitung sei ; Enable General Interrupt Flag ldi XH,HIGH(srtx) ; Puffer auf Anfang stellen ldi XL,LOW(srtx) rcall starttx ; und Text in CW aussenden ldi YH,HIGH(srte+3) ; Ausgabepointer hinter Ende ldi YL,LOW(srte+3) ; des Puffers stellen ldi XH,high(srtx+1) ; Puffer wieder auf Anfang ldi XL,low(srtx+1) ; und überschreiben des Textes clr rmp ; Null-Terminierung schreiben st -x,rmp ldi rmp,chcr ; Setze leeres Textfeld mit CR st -x,rmp ; Jetzt zeigt X-Zeiger auf Anfang ; ; Interrupt loop, ab jetzt praktisch nur Kreisverkehr ; mit Ausbruch bei gesetzten Flagbits ; loop: sleep ; CPU schlafen schicken nop ; Dummy-Befehl, bleibt bei Schlaf hier stehen nop ; nach dem Aufwachen ausgeführt sbrc rfl,bstx ; Sendeflag von Int-Routine gesetzt? rjmp starttx ; Ja: Starte Sendevorgang sbrc rfl,btxe ; Senden-beenden von Int-Routine? rjmp stoptx ; Beende Sendevorgang sbrc rfl,btxa ; Ist die Sendeausgabe aktiv? rjmp loop ; (Während Aussendung keine Parameter!) sbrc rfl,besc ; Parameter mit Menue holen? rjmp getpar ; Hole Parameter über SIO rjmp loop ; weiter im Kreis herum ; ; Startet Sendeprozess ; starttx: sbrc rfl,btxa ; Nicht neu starten, wenn schon aktiv rjmp loop ; Wieder raus! cbr rfl,bmstx ; Setze Flag bit zurück sbi pctrl,bcts ; Stop CTS-Leitung ldi YH,HIGH(srtx) ; Sende-Pointer auf Pufferanfang ldi YL,LOW(srtx) mov rcth,rikh ; Kurze Verzögerung bis zum Start mov rctl,rikl clr rnsc ; Auszugebendes Zeichen beendet, provoziert ; Laden des nächsten Zeichens bei der Senderoutine sbi pdrr,boc1 ; Ausgabe Pin OC1=PB3 auf Ausgang clr rmp ; Setze Timer-Werte out TCCR1A,rmp ; OC1 inaktivieren out TCNT1H,rmp ; Timer-Register auf Null setzen out TCNT1L,rmp out OCR1AH,rcmh ; Compare Match auf Dauer entout OCR1AL,rcml ; sprechend der NF-Frequenz ldi rmp,t1CompInt ; Ermögliche Compare Int out TIMSK,rmp ldi rmp,t1TaktInt ; Clear Timer on Compare ; Match und Prescaler CK/8 out TCCR1B,rmp ; Timer-Takt starten rjmp loop ; und CPU bis zum Timer-Int schlafen legen ; ab jetzt läuft wieder alles automatisch ab ; ; Beendet Sendevorgang ; stoptx: cbr rfl,bmtxe ; Setze Beenden-Flag zurück cbi pctrl,bcts ; CTS wieder einschalten ldi ZH,HIGH(2*txtcur) ; Gib Cursor an SIO aus ldi ZL,LOW(2*txtcur) rcall txtext cpi XL,LOW(srtx) ; Schon Zeichen eingegeben? breq loop ; Nein, schlafen legen bis SIO-RX kommt ldi ZH,HIGH(srtx) ; Gepufferte Zeichen auf Cursorldi ZL,LOW(srtx) ; zeile ausgeben an SIO-TX stoptx1: ld rmp,z+ ; Zeichen aus Puffer lesen rcall txch ; an SIO senden cp ZL,XL ; Schon alle ausgegeben? brcs stoptx1 ; Weiter ausgeben rjmp loop ; Alles ausgegeben, schlafen legen ; ; Getpar holt menuegesteuert Parameter vom User ; getpar: ldi rmp,siorxtx ; Rx-Interrupts abschalten out UCR,rmp ; reiner Polling-Betrieb getpar0: rcall calctc ; Rechne aktuelle Parameter um getpara: rcall txpar ; Gib die Parameter aus getparb: ldi ZH,HIGH(2*txtmenue) ; Gib Menue aus ldi ZL,LOW(2*txtmenue) rcall txtext rcall rxch ; Hole ein Zeichen von SIO cpi rmp,chesc ; Ende-Zeichen ESCAPE? brne getpar1 ; Nein, mach weiter im Menue cbr rfl,bmesc ; Setze Menue-Flag zurück rjmp start1 ; Ende, starte fast alles neu getpar1: cpi rmp,'s' ; Speichern gewählt? breq getpars ; geh zum Speichern cpi rmp,'l' ; Lesen gewählt? breq getparl ; geh zum Lesen cpi rmp,'d' ; Default-Werte gewählt? http://www.avr-asm-tutorial.net/cq-dl/teil4/Cw01.asm (1 of 2)1/20/2009 7:49:51 PM
breq getpard ; Setze Default Werte ldi ZH,HIGH(2*txtf); Zeiger auf Frequenzzeile ldi ZL,LOW(2*txtf) ; im Menue cpi rmp,'f' ; Frequenzeingabe gewählt? breq getpar2 ; ja, hole Zahl ldi ZH,HIGH(2*txtg); Geschwindigkeitseingabe ldi ZL,LOW(2*txtg) ; Zeiger setzen cpi rmp,'g' ; Geschwindigkeitseingabe gewählt? breq getpar2 ; ja, hole Zahl ldi ZH,HIGH(2*txtt) ; Zeiger auf Texteingabe ldi ZL,LOW(2*txtt) cpi rmp,'t' ; Texteingabe gewählt? brne getpar0 ; Nein, gib dem User noch mal das Menue getpar2: ; Hole eine Zahlen- oder Texteingabe in den Puffer push rmp ; wird noch gebraucht (gewählter Menuepunkt) rcall txch ; Echo char an SIO zurück ldi rmp,chcr ; Mache neue Zeile rcall txch rcall txtext ; Gib den ausgewählten Menuetext aus ldi XH,HIGH(srtx) ; Empfangspuffer auf Anfang ldi XL,LOW(srtx) getpar3: rcall rxch ; Hole char von SIO st x+,rmp ; in Puffer out UDR,rmp ; Echo an SIO cpi rmp,chcr ; Ende der Eingabe? brne getpar3 ; Weiter mit Eingabe clr rmp ; String Nullterminieren st x,rmp pop rmp ; Menuepunkt wieder vom Stapel holen cpi rmp,'t' ; Texteingabe gewählt? breq getpara ; Ja, schon fertig, gib die Parameter aus push rmp ; Wird weiterhin gebraucht (Menuepunkt) rcall lese16 ; ASCII-Zahl im Puffer in binär ; in R1:R0 umwandeln pop rmp ; Menuepunkt wieder herstellen brcs getpare ; Fehler in Zahl, Fehlermeldung ausgeben cpi rmp,'f' ; Frequenz gewählt? brne getparg ; Nein, Geschwindigkeit gewählt! mov rmp,R1 ; Zahl zu groß? cpi rmp,0x10 ; Frequenz > 4095 Hz? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,0x01 ; Frequenz < 256 Hz? brcs getpark ; Fehlermeldung Zahl zu niedrig sts sfrq,R0 ; Zahl ok, übertragen sts sfrq+1,R1 rjmp getpar0 ; Rechne Parameter neu aus und gebe aus getparg: ; Neue Geschwindigkeit eingegeben tst R1 ; Zahl <256? brne getparh ; Nein, Fehlermeldung Zahl zu groß mov rmp,R0 cpi rmp,201 ; Zahl >200? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,10 ; Zahl <10? brcs getpark ; Zahl zu niedrig sts sbpm,R0 ; Zahl ok, übertragen rjmp getpar0 ; Beginne Neu mit Berechnung und Ausgabe getpars: ; Speichern der eingestellten Werte im EEPROM rcall ewrite ; Alles übertragen ldi ZH,HIGH(2*txteepw); Meldung ausgeben ldi ZL,LOW(2*txteepw) rcall txtext rjmp getparb ; Menuepunkte ausgeben und weiter getparl: ; Lesen der Werte aus dem EEPROM rcall ecopy ; Alles ins SRAM übertragen rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpard: ; Default-Werte setzen ldi ZH,HIGH(sfrq) ; Zeiger auf Frequenz ldi ZL,LOW(sfrq) ldi rmp,LOW(cfrq) ; LSB Default-Frequenz setzen st z+,rmp ; in SRRAM-Speicher ldi rmp,HIGH(cfrq) ; MSB Default-Frequenz setzen st z+,rmp ; in SRAM-Speicher ldi rmp,cbpm ; Default-Geschwindigkeit st z+,rmp ; in SRAM-Speicher rcall getdeftext ; rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpare: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtzahl) ; Fehler in Zahl ldi ZL,LOW(2*txtzahl) rcall txtext rjmp getpara getpark: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtklein) ; Zahl zu niedrig ldi ZL,LOW(2*txtklein) rcall txtext rjmp getpara getparh: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txthoch) ; Zahl zu hoch ldi ZL,LOW(2*txthoch) rcall txtext rjmp getpara getdeftext: ; Default text in Speicher ab Z ldi rmp,'<' ; Verkehrsanfang, eingefügt 2313=>8515 st z+,rmp ldi rmp,10 ; Testtext in Speicher mov R0,rmp ; in Zähler getdeftext1: ldi rmp,'P' ; Paris st z+,rmp ldi rmp,'A' st z+,rmp ldi rmp,'R' st z+,rmp ldi rmp,'I' st z+,rmp ldi rmp,'S' st z+,rmp ldi rmp,' ' st z+,rmp dec R0 brne getdeftext1 sbiw ZL,1 ; Eins zurück ldi rmp,'>' st z+,rmp ldi rmp,chcr ; Textpuffer mit Carriage Return st z+,rmp clr rmp ; und Nullterminator st z,rmp ; entleeren ret ; ; Warte auf char von der SIO mit Echo ; rxch: in rmp,USR ; Lese Control register sbrs rmp,RXC ; Character vorhanden? rjmp rxch ; Noch nicht, weiter warten in rmp,UDR ; Lese character aud Datenregister ret ; und zurück ; ; Sende text character in rmp an SIO mit Prüfung ; txch: push rmp ; Rette Zeichen auf Stapel txch1: in rmp,USR ; Senderegister leer? sbrs rmp,UDRE ; Ist das UDRE-Bit gesetzt rjmp txch1 ; Nein, weiter warten pop rmp ; Hole Zeichen vom Stapel out UDR,rmp ; Character senden cpi rmp,chcr ; Nach Carriage Return noch ein brne txch2 ; Linefeed? ldi rmp,chlf ; auch den noch senden! rcall txch ldi rmp,chcr ; und Zeichen wieder herstellen txch2: ret ; ; Morsecode der ASCII-Zeichen 0x20 bis 0x5F ; unteres Byte = Code (0=Punkt, 1=Strich) ; oberes Byte = Anzahl Punkte/Striche ; Bit 7 = 1: Leerzeichen ; morse: ; Zeichen 20 .. 2F .DB 0b11100000,0b10000011 ; Blank .DB 0b01000000,5 ; ! = Warten .DB 0b01001000,6 ; " .DB 0b11011000,5 ; # = ~n .DB 0b01101000,5 ; $ = á, °a .DB 0b01000000,5 ; % = é .DB 0b00000000,0 ; & = nicht benutzt .DB 0b01111000,6 ; ' .DB 0b10110000,5 ; ( .DB 0b10110100,6 ; ) .DB 0b00000000,0 ; * = nicht benutzt .DB 0b00010100,6 ; + = Spruchende .DB 0b11001100,6 ; , .DB 0b10000100,6 ; .DB 0b01010100,6 ; . .DB 0b10010000,5 ; / ;Zeichen 30 .. 3F .DB 0b11111000,5 ; 0 .DB 0b01111000,5 ; 1 .DB 0b00111000,5 ; 2 .DB 0b00011000,5 ; 3 .DB 0b00001000,5 ; 4 .DB 0b00000000,5 ; 5 .DB 0b10000000,5 ; 6 .DB 0b11000000,5 ; 7 .DB 0b11100000,5 ; 8 .DB 0b11110000,5 ; 9 .DB 0b11100000,6 ; : .DB 0b10101000,6 ; ; .DB 0b10101000,5 ; < = Verkehrsanfang .DB 0b10001000,5 ; = .DB 0b01010000,5 ; > = Verkehrsende .DB 0b00110000,6 ; ? ;Zeichen 40 .. 4F .DB 0b11110000,4 ; @ = ch .DB 0b01000000,2 ; A .DB 0b10000000,4 ; B .DB 0b10100000,4 ; C .DB 0b10000000,3 ; D .DB 0b00000000,1 ; E .DB 0b00100000,4 ; F .DB 0b11000000,3 ; G .DB 0b00000000,4 ; H .DB 0b00000000,2 ; I .DB 0b01110000,4 ; J .DB 0b10100000,3 ; K .DB 0b01000000,4 ; L .DB 0b11000000,2 ; M .DB 0b10000000,2 ; N .DB 0b11100000,3 ; O ;Zeichen 50 .. 5F .DB 0b01100000,4 ; P .DB 0b11010000,4 ; Q .DB 0b01000000,3 ; R .DB 0b00000000,3 ; S .DB 0b10000000,1 ; T .DB 0b00100000,3 ; U .DB 0b00010000,4 ; V .DB 0b01100000,3 ; W .DB 0b10010000,4 ; X .DB 0b10110000,4 ; Y .DB 0b11000000,4 ; Z .DB 0b01010000,4 ; [ = Ä .DB 0b11100000,4 ; \ = Ö .DB 0b00110000,4 ; ] = Ü .DB 0b00000000,8 ; ^ = Irrung .DB 0b00110100,6 ; _ morseende: .DW morseende-morse ; Prüfzahl, muss 0x0040 sein ; ; Texte für die serielle Schnittstelle ; Hinweis: Die chk-Werte sind zum Überprüfen im Listing ; eingefügt. Es gibt einen Bug im AVR-Assembler von ATMEL, ; der zu falschen Adressen führt, wenn bestimmte Kombina; tionen von Byte-Konstanten verwendet werden. Dieser Bug ; ist seit zwei Jahren gemeldet und noch immer nicht besei; tigt! Teilweise sind die abenteuerlichen Konstruktionen ; in dieser Liste zur Umgehung dieses Bugs verwendet. ; ; Eingangsmeldung zu Beginn txtid: .DB chff,chcr .DB "+---------------------------------+",chcr .DB "| Morseprogramm (C)2002 by DG4FAC |",chcr .DB "+---------------------------------+",chcr .DB cnul,cnul chk1: .DW chk1 ; für Assembler Bug ; Text für Parameterliste txtpar1: .DB chcr,'E',"ingestellte Parameter:",chcr,'*' txtf: .DB "NF-Frequenz (256..4095 Hz) = ",cnul chk2: ; Bug-Check .DW chk2 txtpar2: .DB " Hz ",chcr,'*' txtg: .DB "Geschwindigkeit (10..200 BpM) = ",cnul chk3: ; Bug-Check .DW chk3 txtpar3: .DB " BpM",chcr,' ',"ccm = ",cnul,cnul chk4: ; Bug-Check .DW chk4 txtpar4: .DB ", Ints (kurz) = ",cnul,cnul chk5: ; Bug-Check .DW chk5 txtpar5: .DB ", Ints (lang) = ",cnul,cnul chk6: ; Bug-Check .DW chk6 txtpar6: .DB chcr,'*' txtt: .DB "Text = ",cnul chk7: ; Bug-Check .DW chk7 txtcur: .DB chcr,'=','>',cnul chk8: ; Bug-Check .DW chk8 txtmenue: .DB "Einstellungen: f=NF-Frequenz, g=Geschwindigkeit," .DB " t=Text, s=Speichern,",chcr .DB " l=Lesen, d=Default, ESC=Ende! ",chcr .DB "(f,g,t,s,l,d,ESC) => ",cnul chk9: ; Bug-Check .DW chk9 ; Prüfzahl für Assembler-Bug txtzahl: .DB chcr,'F',"ehler in Ziffer oder Zahl bzw. Zahl zu gross!",cnul chk10: ; Bug-Check .DW chk10 ; Prüfzahl für Assembler-Bug txtklein: .DB chcr,'*',"* Zahl zu niedrig! **",cnul,cnul chk11: ; Bug-Check .DW chk11 ; Prüfzahl für Assembler-Bug txthoch: .DB chcr,'*',"* Zahl zu hoch! **",cnul,cnul chk12: ; Bug-Check .DW chk12 ; Prüfzahl für Assembler-Bug txteepw: .DB chcr,'P',"arameter ins EEPROM geschrieben.",chcr,cnul chk13: ; Bug-Check .DW chk13 ; ; Copyright-Info .DB "C(2)00 2ybD 4GAF C" ; ; Programm-Code Ende ; ; ****************************************** ; * EEPROM-Inhalt mit Default beginnt hier * ; ****************************************** ; .ESEG .ORG 0x0000 ; efrq: ; Die Default-NF-Frequenz .DW cfrq ebpm: ; Die Default-Geschwindigkeit in BpM .DB cbpm etxt: ; Dieser Text wird zu Beginn ausgegeben .DB "hello!" .DB chcr,cnul etxte: ; ; Copyright-Info ; .DB "(C)2002 by DG4FAC" ; ; Ende des EEPROM-Segmentes ;
http://www.avr-asm-tutorial.net/cq-dl/teil4/Cw01.asm (2 of 2)1/20/2009 7:49:51 PM
cq-dl-Beiträge zu AVR-Mikrocontrollern
Pfad: Home => cq-dl-Beiträge => Teil 4 => Cw01_200.asm
cq-dl-Beiträge zu ATMEL-AVRMikrocontrollern Assembler-Quellcode des CW-Programmes ; ********************************************************** ; * CW.asm gibt Morsezeichen aus, die im EEPROM-Speicher * ; * gespeichert sind und die über die serielle Schnitt* ; * stelle des STK200-Boards eingegeben werden. Die Aus* ; * gabe der NF erfolgt an PD5. Baudrate 9k6. Takt 4 MHz. * ; * AVR AT90S8515. Programm (C)2002 by DG4FAC G.Schmidt * ; * Version 0.1-200 vom 12.1.2002 * ; * Bugs und Dankschreiben an info!at!avr-asm-tutorial.net * ; ********************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Benutzte Register ; ; Register R0 wird für Lesen im Programmspeicher benutzt ; Register R0..R9 werden für Berechnungen benutzt ; .DEF rcml = R10 ; Compare Match-Zahl, LSB .DEF rcmh = R11 ; dto., MSB .DEF rikl = R12 ; Interrupt-Anzahl kurz, LSB .DEF rikh = R13 ; dto., MSB .DEF rill = R14 ; Interrupt-Anzahl lang, LSB .DEF rilh = R15 ; dto., MSB .DEF rmp = R16 ; Multipurpose register, nicht bei Ints .DEF rim = R17 ; Interrupt multi-purpose .DEF rfl = R18 ; Flag Register, bei Ints und Normal .DEF rst = R19 ; Statusregister bei Interrupts .DEF rnsc = R20 ; Anzahl Punkte/Striche beim Senden .DEF rmcd = R21 ; Morsecode beim Senden .DEF x22 = R22 ; unbenutzt .DEF x23 = R23 ; unbenutzt .DEF rctl = R24 ; Zähler für Interrupt Tonerzeugung .DEF rcth = R25 ; (wird als Doppelregister verwendet) ; ; Register XH:XL R27:R26 Zeiger in den Empfangspuffer ; Register YH:YL R29:R28 Zeiger beim Senden ; Register ZH:ZL R31:R30 Zeiger für Lesen im Programmspeicher ; ; Bits im Flagregister ; .EQU besc = 7 ; ESCape-Sequenz, hole Parameter .EQU bmesc = 0x80 ; Maskenbyte für ESC .EQU bstx = 6 ; Starte Sendeprozess .EQU bmstx = 0x40 ; Maskenbyte für bstx .EQU bctx = 5 ; Beende Sendeprozess nach Leerzeichen .EQU bmctx = 0x20 ; Maskenbyte für bctx .EQU btxa = 4 ; Sendeprozess aktiv .EQU bmtxa = 0x10 ; Maskenbyte für btxc .EQU btxe = 3 ; Abschluss des Sendens .EQU bmtxe = 0x08 ; Maskenbyte für btxe .EQU bpl = 2 ; Lange Pause zur Buchstabentrennung .EQU bmpl = 0x04 ; Maskenbyte für bpl .EQU bpk = 1 ; Kurze Pause zur Punkt/Strich-Trennung .EQU bmpk = 0x02 ; Maskenbyte für bpk .EQU bquiet = 0 ; Dauerhaft inaktiviert bei Leerzeichen .EQU bmquiet = 0x01 ; Maskenbyte für bquiet ; ; Default-Werte ; .EQU cfrq=1000 ; NF-Frequenz .EQU cbpm=60 ; Gebegeschwindigkeit ; ; Basisdefinitionen variabel nach Schaltung ; .EQU fqu = 4000000 ; Quarzfrequenz des AVR .EQU fbd = 9600 ; Baudrate des UART .EQU pctrl = PORTB ; Control-Port für RTS/CTS (nicht benutzt) .EQU pdrr = DDRB ; Datenrichtungsregister Controlport .EQU brts = PB2 ; RTS bit Input (beim STK nicht benutzt) .EQU bcts = PB4 ; CTS bit Output (beim STK nicht benutzt) .EQU pnfd = DDRD ; Port für Richtung NF-Ausgabe (8515) .EQU doc1 = PD5 ; NF-Ausgabe über OC1B-Pin (8515) ; ; Umrechnungen in Timer- und Counterwerte ; .EQU nps = 8 ; Prescaler-Einstellung von Timer 1 .EQU ccm = fqu/nps/2; Konstante für Compare Match .EQU cint =2941 ; Konstante für Int-Berechnung, ; experimentell ermittelt ; ; Definitionen fix ; .EQU bddv = (fqu/(16*fbd))-1 ; Baudraten-Teiler .EQU cnul = 0x00 ; Ende für nullterminierte Strings .EQU chbsp = 0x08 ; Backspace character .EQU chcr = 0x0D ; Carriage Return character .EQU chlf = 0x0A ; Line Feed character .EQU chff = 0x0C ; Form Feed character .EQU chesc= 0x1B ; ESCape-Zeichen ; ; Definitionen I/O ; ; Definitionen für Timer-Controlregister TCCR1A .EQU t1aus = 0b10000000 ; Schaltet den NF-Ausgang aus .EQU t1ein = 0b01000000 ; Schaltet den NF-Ausgang ein ; Definition für Timer-Interrupt-Mask-Register TIMSK .EQU t1CompInt=0b01000000 ; Interrupt bei Compare Match ; Definition für Timer-Controlregister TCCR1B .EQU t1TaktInt=0b00001010 ; Timer-Takt CLK / 8, Clear Comp ; Definitionen für UART-Betrieb in UCR .EQU siorxtx = 0b00011000 ; Betrieb RX und TX ohne Int .EQU siorxint= 0b10011000 ; Betrieb RX mit Int, TX ohne Int ; Definition für den SLEEP-Mode in MCUCR .EQU sleepmode=0b00100000 ; Aufwachen bei Interrupt ; ; Positionen im SRAM ; .EQU sfrq = 0x0060 ; Eingestellte NF-Frequenz, Wort .EQU sbpm = 0x0062 ; Eingestellte Geschwindigkeit, Byte .EQU srtx = 0x0063 ; RAM-Puffer für Ein-/Ausgabezeile .EQU srte = 0x00B3 ; Ende des nutzbaren Puffers, benutzt werden ; auch zwei weitere Folgebytes ; ; Programm beginnt hier ; .CSEG .ORG 0x0000 ; ; Reset- und Interrupt-Sprungtabelle 8515 ; rjmp start ; Reset-Vector reti ; Ext Int 0, nicht benutzt reti ; Ext Int 1, nicht benutzt reti ; Timer 1 Capture Event, nicht benutzt rjmp itc1m ; Timer 1 Compare Match A reti ; Timer 1 Compare Match B reti ; Timer 1 Overflow, nicht benutzt reti ; Timer 0 Overflow, nicht benutzt reti ; SPI STC, nicht benutzt rjmp iurxc ; UART Rx Complete reti ; UART Tx data register empty, nicht benutzt reti ; UART Tx All sent, nicht benutzt reti ; ANA_COMP, nicht benutzt ; ; Interrupt-Service-Routinen ; itc1m: ; Timer 1 Compare Match Interrupt in rst,SREG ; Rette Statusregister sbiw rctl,1 ; Zähler eins abzählen brne itc1mz ; Raus, wenn nicht Null mov rcth,rikh ; Setze Zähler auf kurze Dauer mov rctl,rikl ldi rim,t1aus ; Ton auf aus tst rnsc ; Zeichen fertig gesendet? breq itc1m0 ; Verzweige, wenn Zeichen fertig sbrc rfl,bpk ; kurze Pausen-Flag? rjmp itc1ms ; Pause zwischen Punkt/Strich war schon sbr rfl,bmpk ; Setze kurze Pause-Flag rjmp itc1my ; und Ausgabe auf inaktiv, fertig itc1ms: ; Sende nächsten Punkt/Strich cbr rfl,bmpk ; Lösche kurze Pause-Flag ldi rim,t1ein; Ton an = Toggle dec rnsc ; Weiteren Punkt/Strich gesendet rol rmcd ; Punkt oder Strich senden? brcc itc1my ; Punkt senden mov rcth,rilh ; Lange Dauer einstellen mov rctl,rill rjmp itc1my itc1m0: ; Zeichen fertig gesendet sbrc rfl,bctx ; Sendung beenden? rjmp itc1mx sbrc rfl,bpl ; Lange Pause senden? rjmp itc1mn ; Nächsten Buchstaben beginnen sbr rfl,bmpl ; Setze langes Pausen-Flag mov rcth,rilh ; Dauer auf lang stellen mov rctl,rill itc1my: ; Stelle Modus inaktiv/toggle ein sbrc rfl,bquiet ; bei Leerzeichen Ton aus ldi rim,t1aus ; Ton auf aus out TCCR1A,rim itc1mz: out SREG,rst ; Stelle Status wieder her reti itc1mn: cbr rfl,bmpl ; Langes Pausen-Flag aus ld rim,y+ ; Nächsten Buchstaben lesen tst rim ; Null-Terminator breq itc1mn1 cpi rim,chcr ; Ende der Zeile? brne itc1mn2 itc1mn1: sbr rfl,bmctx ; Setze beenden-Flag ldi rim,' ' ; Sende noch ein Leerzeichen itc1mn2: out UDR,rim ; Debug subi rim,0x20 ; ASCII-Control-Zeichen weg brcc itc1mn3 ldi rim,'?'-0x20 ; Sende Fragezeichen itc1mn3: cpi rim,0x40 ; Kleinbuchstabe? brcs itc1mn4 subi rim,0x20 ; in Grossbuchstaben wandeln cpi rim,0x40 brcs itc1mn4 ldi rim,'?'-0x20 itc1mn4: add rim,rim ; Mal 2 für Tabelle push ZH ; Register retten push ZL push R0 ldi ZH,HIGH(2*morse) ; Zeichentabelle laden ldi ZL,LOW(2*morse) add ZL,rim ; Zeichen dazu zählen brcc itc1mn5 ; Kein Übertrag? inc ZH ; Übertrag itc1mn5: lpm ; Lese Zeichencode aus Tabelle mov rmcd,R0 ; in Zeichencode-Register adiw ZL,1 ; Zeiger auf nächstes Byte lpm ; aus Tabelle lesen mov rnsc,R0 ; Anzahl Striche/Punkte pop R0 ; Wieder herstellen der Register pop ZL pop ZH tst rnsc ; Undefiniertes Zeichen? breq itc1mn ; Überspringe Zeichen sbr rfl,bmquiet ; Leerzeichen sbrs rnsc,7 ; Leerzeichen? cbr rfl,bmquiet ; Kein Leerzeichen cbr rnsc,0x80 ; Lösche höchstes Bit mov rim,YL ; CTS einschalten? sub rim,XL brcs itc1mn6 ; Ausschalten cpi rim,3 brcs itc1mn6 cbi pctrl,bcts ; CTS einschalten rjmp itc1ms itc1mn6: sbi pctrl,bcts ; CTS ausschalten rjmp itc1ms ; Sende ein Zeichen itc1mx: ; Sendung einstellen clr rim ; Timer 1 abschalten out TCCR1B,rim ; Timer-Takt aus out TCNT1H,rim ; Timer auf Null stellen out TCNT1L,rim out TIMSK,rim ; Timer Interrupts aus out TCCR1A,rim ; Timer Compare Mode aus cbr rfl,bmctx+bmtxa ; Ende-Flag und aktiv ausschalten sbr rfl,bmtxe ; Beenden-Flag einschalten ldi YH,HIGH(srte) ; Buffer auf Ende ldi YL,LOW(srte) st Y,rim ; Null-terminieren rjmp itc1mz ; ; UART Rx Complete Interrupt ; iurxc: in rst,SREG ; Rette Statusregister in rim,UDR ; Lese Zeichen von SIO cpi rim,chesc ; ESCape-Sequenz? brne iurx1 sbr rfl,bmesc ; Setze ESCape-Bit rjmp iurxz iurx1: cpi rim,chbsp ; Backspace-Char? brne iurx2 cpi XL,LOW(srtx) ; Schon auf Anfang? breq iurxz sbiw XL,1 ; Eine Position zurück ldi rim,chcr ; Zeilenabschluss st x+,rim clr rim ; Null-terminieren st x,rim sbiw XL,1 ; zurück ldi rim,chbsp ; Backspace zurücksenden rjmp iurxe ; Echo character iurx2: cpi XL,low(srte) ; Pufferüberlauf? brcs iurx3 ; Nein, weiter ldi rim,chcr ; Character überschreiben iurx3: cpi rim,chcr ; Zeilenende? brne iurxw ; Nein, in Puffer und fertig sbrs rfl,btxa ; Überspringe Ausgabe wenn aktiv out UDR,rim ; Carriage Return schon mal ausgeben st x+,rim ; CR-Zeichen anhängen clr rim ; Null terminieren st x,rim ldi XH,HIGH(srtx) ; Puffer auf Anfang ldi XL,LOW(srtx) sbr rfl,bmstx ; Über Flag Sender starten ldi rim,chlf ; Sende Zeilenvorschub rjmp iurxe ; Echo Line-Feed iurxw: st x+,rim ; Speichere character ldi rim,chcr ; Abschluss terminieren st x+,rim ; mit Carriage return clr rim ; Null-terminieren st x,rim sbrs rfl,btxa ; Sender aktiv? rjmp iurxs ; Nein, CTS nicht prüfen mov rim,YL ; CTS ein oder aus? sub rim,XL ; Distanz zwischen Ein- und Ausgabe brcs iurxo ; Eingabe > Ausgabe: CTS aus cpi rim,3 ; mindestens zwei Zeichen Vorlauf? brcs iurxo ; Nein, dann CTS ausschalten cbi pctrl,bcts ; CTS einschalten rjmp iurxs ; Zeichen herstellen und raus iurxo: sbi pctrl,bcts ; CTS ausschalten iurxs: sbiw XL,2 ; Char wieder herstellen ld rim,x+ ; durch Lesen im SRAM iurxe: sbrs rfl,btxa ; Keine Ausgabe, wenn aktiv out UDR,rim ; Echo character an SIO zurück iurxz: out SREG,rst ; Stelle Status wieder her reti ; Ende des Interrupts ; ; Diverse eigenständige Unterprogramme ; ; Kopiert den EEPROM-Inhalt in das SRAM ; ecopy: sbic EECR,EEWE ; Warte, bis EEPROM bereit rjmp ecopy ; Noch nicht bereit clr YH ; Startadresse im EEPROM auf Null clr YL ldi ZH,HIGH(sfrq) ; Startadresse im SRAM ldi ZL,LOW(sfrq) ; auf ersten Parameter ecopy1: out EEARH,YH ; Leseadresse im EEPROM out EEARL,YL ; 2313 hat nur 128 Bytes, daher nur unteres Register sbi EECR,EERE ; Lese-Strobe setzen cbi EECR,EERE ; und wieder ausschalten in rmp,EEDR ; Byte aus EEPROM-Datenregister lesen st Z+,rmp ; und in SRAM schreiben adiw YL,1 ; nächste EEPROM-Adresse anwählen tst rmp ; Null-Terminator? brne ecopy1 ; Nein: weiter kopieren ret ; ; Schreibe Parameter in das EEPROM zurück ; ewrite: ldi ZH,HIGH(sfrq) ; Zeiger auf SRAM ldi ZL,LOW(sfrq) clr XH ; Zeiger in das EEPROM clr XL ewrite1: sbic EECR,EEWE ; Frage Write-Bit im EEPROM rjmp ewrite1 ; ab und wiederhole bis EEPROM ready out EEARH,XH ; Schreibadresse im EEPROM einstellen out EEARL,XL ld rmp,Z+ ; Lese Byte aus SRAM in Register out EEDR,rmp ; in das EEPROM-Datenregister cli ; Keine Interrupts mehr beim Schreibvorgang sbi EECR,EEMWE ; Setze EEPROM Master Write Enable sbi EECR,EEWE ; Löse Schreibvorgang im EEPROM aus sei ; Jetzt Interrupts wieder zulassen adiw XL,1 ; Nächste EEPROM-Adresse in X-Zeiger cpi XH,$02 ; Ende EEPROM erreicht? brcc ewrite2 ; Überlänge! Null fehlt! Abbrechen! tst rmp ; Nullterminiert? brne ewrite1 ; Noch weitere Bytes schreiben ewrite2: ret ; ; Lese16 wandelt eine ASCII-Zahl im Puffer in binär ; in R1:R0, bei Fehler Rückkehr mit gesetztem Carry-Bit ; lese16: clr R0 ; Leeren Resultat-Register R1:R0 clr R1 ldi ZH,HIGH(srtx) ; Zeige auf Pufferanfang ldi ZL,LOW(srtx) lese16a: ld rmp,Z+ ; Lese ASCII-Ziffer aus SRAM cpi rmp,chcr ; Ende der Zahl mit Carriage Return? breq lese16ok ; Zahl mit Carriage Return beendet cpi rmp,cnul ; Ende der Zahl mit Nullterminiert? breq lese16ok ; Ende mit Nullterminierung cpi rmp,'9'+1 ; Ziffer > ASCII-9? brcc lese16no ; Ja, Fehler! cpi rmp,'0' ; Ziffer < ASCII-0 brcs lese16no ; Ja, auch Fehler subi rmp,'0' ; Wandle Ziffer in binär ; Ab hier wird das bisherige Resultat mit 10 multi; pliziert mov R2,R0 ; Kopiere Binärzahl in Hilfsregister R3:R2 mov R3,R1 add R0,R0 ; Multipliziere mit 2 durch Addieren 16 Bit adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R2 ; Addiere die Kopie der Zahl adc R1,R3 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! ; Hier ist das Multiplizieren mit 10 beendet. add R0,rmp ; Addiere Ziffer zum Resultat hinzu brcc lese16a ; Kein Überlauf des unteren Bytes inc R1 ; Überlauf, oberes Byte erhöhen brne lese16a ; Kein Überlauf des oberen Bytes lese16no: sec ; Setze Carry-Bit bei Fehler und kehre zurück ret lese16ok: clc ; Clear Carry bei fehlerfrei ret ; ; Wandle 16-Bit-Zahl in R1:R0 in ASCII in R4..R9 (nullterm.) ; unterdrücke führende Nullen, sende Ergebnis ohne führende ; Nullen über die SIO ; b16asc: clr ZH ; Z zeigt auf höchste ASCII-Ziffer in ldi ZL,4 ; Register R4 (Zeiger in Register!) ldi rmp,HIGH(10000) ; Beginne mit Zehntausendern mov R3,rmp ; oberes Byte in R3 ldi rmp,LOW(10000) mov R2,rmp ; unteres Byte in R2 rcall b16sub ; Ermittle ASCII-code der Zehntausender ; Stelle durch fortgesetztes Subtrahieren von 10000 ldi rmp,HIGH(1000) ; Weiter mit Tausendern mov R3,rmp ldi rmp,LOW(1000) mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Tausender clr R3 ; Weiter mit Hunderten ldi rmp,100 mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Hunderter ldi rmp,10 ; Weiter mit Zehnern mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Zehner ldi rmp,'0' ; Rest sind Einer add rmp,R0 mov R8,rmp ; R8 kriegt die ASCII-Einer clr R9 ; Nullterminieren in R9 ldi ZL,4 ; Z auf 10000-er Zeichen b16asc1: cpi ZL,9 ; Ende der Zeichenkette erreicht? breq b16asc2 ; Ja, raus ld rmp,z+ ; Zeichen aus Register kopieren cpi rmp,'0' ; Führende Null? breq b16asc1 ; Ja, weitermachen b16asc2: dec ZL ; auf vorheriges Zeichen setzen ; ; Sende nullterminierten Text aus SRAM an SIO ; Z zeigt nicht ins SRAM, sondern in die Register mit ; dem Ergebnis! ; Das Registerpaar Z zeigt auf das erste Zeichen ; txstxt: ld rmp,z+ ; Lese Zeichen aus SRAM/Register tst rmp ; Nullterminator erreicht? breq txstxt1 ; Ja, raus und fertig rcall txch ; Sende character über SIO rjmp txstxt ; Weitermachen mit nächstem Zeichen txstxt1: ret ; ; Ermittle ASCII-Code einer Ziffer der 16-Bit-Binärzahl in ; R1:R0 durch fortgesetztes Subtrahieren einer 16-Bit; Hilfszahl (10000, 1000, 100, 10) in R3:R2. Tritt ein ; Überlauf ab, dann ist die Ziffer gefunden ; (Unterroutine für b16asc) ; b16sub: ldi rmp,'0' ; Setze ASCII-Null b16sub1: sub R0,R2 ; Subtrahiere die Hilfszahl sbc R1,R3 ; in 16-bit brcs b16sub2 ; Ende subtrahieren erreicht? inc rmp ; Einer geht noch! rjmp b16sub1 ; Weiter mit subtrahieren! b16sub2: add R0,R2 ; Zu weit, addiere wieder zurück adc R1,R3 st Z+,rmp ; ASCII-Ziffer in Register schreiben ret ; und zurück ; ; Sende nullterminierten Text aus dem Programmspeicher ; über die SIO aus ; txtext: lpm ; Lese Zeichen aus Programmspeicher mit Zeiger Z tst R0 ; Ergebnis im Register R0 hat Null erreicht? breq txtend ; Ja, raus aus der Routine mov rmp,R0 ; Kopiere Zeichen in Senderegister rcall txch ; Sende character mit Prüfung adiw zl,1 ; Zeiger auf nächstes Zeichen rjmp txtext ; Weitermachen bis Null txtend: ret ; Kehre zurück ; ; Liste alle Parameter über die SIO-Schnittstelle auf ; txpar: ldi ZH,HIGH(2*txtpar1) ; Sende Parametervorspann ldi ZL,LOW(2*txtpar1) ; für NF-Frequenz rcall txtext lds R0,sfrq ; Zeige eingestellte Frequenz an lds R1,sfrq+1 rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar2) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar2) ; Geschwindigkeit an rcall txtext lds R0,sbpm ; Zeige Geschwindigkeit an clr R1 ; nur 8 Bit! rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar3) ; Zeige Vorspann für Counterldi ZL,LOW(2*txtpar3) ; Compare-Match-Zahl ccm an rcall txtext mov R1,rcmh ; Compare-Match-Zahl in R1:R0 mov R0,rcml rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar4) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar4) ; Anzahl NF-Ints bei Punkt an rcall txtext mov R1,rikh ; Anzahl Ints bei Punkt in R1:R0 mov R0,rikl rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar5) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar5) ; Anzahl NF-Ints bei Strich an rcall txtext mov R1,rilh ; Anzahl Ints bei Strich in R1:R0 mov R0,rill rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar6) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar6) ; Ausgabetext an rcall txtext ldi ZH,HIGH(srtx) ; Zeiger Z auf Ausgabetext im SRAM ldi ZL,LOW(srtx) rjmp txstxt ; Sende Inhalt SRAM nullterminiert ; ; 32-Bit durch 16-Bit-Division ; ; Dividiert 32-Bit-Zahl in R3:R2:R1:R0 durch R5:R4 ; Ergebnis in R7:R6 (Ergebnis darf nicht > 16 Bit sein!) ; div32: clr R7 ; Clear Ergebnis-Register R7:R6 clr R6 inc R6 ; Stopbit setzen für 16 Divisionsschritte div32a: clc ; Null in Carry schieben rol R0 ; Multipliziere Divident mit 2 rol R1 rol R2 rol R3 brcs div32e ; Carry ist herausgerollt? Dann 1! cp R3,R5 ; Vergleiche oberes Byte brcs div32n ; Ergebnis ist kleiner, also eine 0 brne div32e ; Ergebnis ist größer, also eine 1 cp R2,R4 ; Vergleich MSB gleich, vergleiche unteres ; Byte brcs div32n ; Unteres Byte kleiner, also eine 0 div32e: sub R2,R4 ; Ziehe den Divisor von den oberen 16 Bit ab sbc R3,R5 sec ; Setze das Carry-Bit, Ergebnis ist eine 1 rjmp div32s ; Zum Reinschieben in das Ergebnis div32n: clc ; Lösche Carry-Bit, Ergebnis ist eine 0 div32s: rol R6 ; Schiebe Carry-Bit von rechts in Ergebnis rol R7 brcc div32a ; Ende, wenn eine 1 links herausrollt ret ; ; Multipliziert 16-Bit-Zahl in R1:R0 mit 16-Bit-Zahl in R5:R4 ; Ergebnis 32 Bit in R9:R8:R7:R6, für jeden Zahlenbereich ; Mul16: clr R3 ; Leere obere zwei Bytes der 16-Bit-Zahl clr R2 clr R9 ; Leere Ergebnis-Register R9:R8:R7:R6 clr R8 clr R7 clr R6 Mul16a: clc ; Null ins Carry-Bit ror R5 ; Schiebe unterstes Bit Divisor in Carry ror R4 ; und dividiere Multiplikant durch 2 brcc Mul16b ; Bit war eine 0, Addition überspringen add R6,R0 ; addiere Multiplikator 32 Bit zum adc R7,R1 ; bisherigen Ergebnis, jeweils mit adc R8,R2 ; Überlauf adc R9,R3 Mul16b: tst R4 ; Schon alle Bits ausmultipliziert? brne Mul16c ; Nein, LSB nicht Null, weiter tst R5 ; Teste auch oberes Byte brne Mul16c ; Nein, MSB nicht Null, weiter ret ; Fertig Mul16c: clc ; Null in Carry-Bit schieben rol R0 ; Zahl durch Linksschieben mit 2 rol R1 ; multiplizieren rol R2 rol R3 rjmp Mul16a ; und weiter dividieren ; ; Dividiert 32 Bit-Zahl in R3:R2:R1:R0 durch eine ; 8-Bit-Zahl in R4 und durch 256, Ergebnis gerundet ; in R8:R7, Wertebereich beachten! ; Div32_8: clr R8 ; Ergebnisspeicher R8:R7:R6 leeren clr R7 clr R6 inc R6 ; Stopbit nach 24 Schritten setzen Div32_8a: clc ; Carry-Bit leeren rol R0 ; Divident mit 2 multiplizieren rol R1 ; durch Linksschieben rol R2 rol R3 brcs Div32_8e ; 1 herausgerollt, Ergebnis=1 cp R3,R4 ; Vergleiche oberstes Byte mit Divisor brcs Div32_8n ; Kleiner, Ergebnis = 0 Div32_8e: sub R3,R4 ; Ziehe Divisor von oberstem Byte ab sec ; Setze Carry für Ergebnis = 1 rjmp Div32_8b ; Ins Ergebnis schieben Div32_8n: clc ; Clear Carry für Ergebnis = 0 Div32_8b: rol R6 ; Ergebnis-Bit von rechts her hineinrotieren rol R7 rol R8 brcc Div32_8a ; Weiter, wenn eine Null herausrollt rol R6 ; Binäre Kommastelle eine 1, aufrunden? brcc Div32_8z ; Nein, wenn Null inc R7 ; Aufrunden LSB Ergebnis brne Div32_8z ; Kein Überlauf bei LSB-Erhöhung inc R8 ; Aufrunden MSB Div32_8z: ret ; ; Rechne Parameter um in Timer- und Zählerwerte ; Oben definiert: ccm = Taktfrequenz / Prescale 8 / 2 ; cint = Konstante ; Compare-Match-Zahl für Timer 1 = ccm / NF-Frequenz ; Anzahl Ints bei Punkt = cint * NF-Frequenz / Speed /256 ; Anzahl Ints bei Punkt = Anzahl Ints bei Strich * 3 ; calctc: ldi rmp,BYTE4(ccm) ; Taktabhängige Konstante ccm mov R3,rmp ; in R3:R2:R1:R0, bei 4 MHz: 625.000 ldi rmp,BYTE3(ccm) ; oder 0x00098968 mov R2,rmp ldi rmp,BYTE2(ccm) mov R1,rmp ldi rmp,BYTE1(ccm) mov R0,rmp lds R4,sfrq ; durch NF-Frequenz in R5:R4 lds R5,sfrq+1 rcall div32 ; ergibt Compare Match-Zahl 16-Bit ; rcall txreg ; Debug-Code!!! mov rcmh,R7 ; Ergebnis in Speicher rcmh:rcml mov rcml,R6 ldi rmp,HIGH(cint) ; Konstante für Anzahl Ints mov R1,rmp ; bei 4 MHz: 1250 ldi rmp,LOW(cint) mov R0,rmp lds R4,sfrq ; Mal NF-Frequenz in Hz lds R5,sfrq+1 rcall mul16 ; Multplizieren 16 Bit * 16 Bit ; rcall txreg ; Debug code!!! mov R3,R9 ; Ergebnis in R9..R6 nach R3..R0 kopieren mov R2,R8 mov R1,R7 mov R0,R6 lds R4,sbpm ; durch Gebegeschwindigkeit teilen rcall div32_8 ; teilen 32-Bit durch 8-Bit ; rcall txreg ; Debug code!!! mov rikh,R8 ; in Kurzspeicher kopieren mov rikl,R7 mov rilh,R8 ; und in Langspeicher kopieren mov rill,R7 add rill,rill ; Langspeicher mit 2 malnehmen adc rilh,rilh ; durch Addieren add rill,rikl ; und noch einmal dazu zählen adc rilh,rikh ; macht 3 mal so lang ret ; ; Debug code ; ; Display Byte in rmp in Hex an die SIO ; ;txbhx: ; push rmp ; swap rmp ; rcall txnhx1 ; pop rmp ;txnhx1: ; cbr rmp,0b11110000 ; subi rmp,-'0' ; cpi rmp,'9'+1 ; brcs txnhx2 ; subi rmp,-7 ;txnhx2: ; rcall txch ; ret ; ; Display Register R0..R9 in Hex an SIO ; ;txreg: ; ldi rmp,chcr ; rcall txch ; clr ZH ; clr ZL ;txreg1: ; ld rmp,Z+ ; rcall txbhx ; ldi rmp,' ' ; rcall txch ; cpi ZL,11 ; brcs txreg1 ; ldi rmp,chcr ; rcall txch ; ret ; ; Hauptprogramm-Loop, Restart-Vektor ; start: ldi rmp,HIGH(RAMEND) ; Init stack pointer im SRAM out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,bddv ; Baudrate des UART einstellen out UBRR,rmp ldi rmp,siorxtx ; UART Tx und Rx ein, Ints aus out UCR,rmp cbi pdrr,brts ; Bit Richtung ist RTS-Eingang sbi pdrr,bcts ; Bit Richtung ist CTS-Ausgang sbi pctrl,bcts ; CTS ausschalten (invertiert!) rcall ecopy ; Kopiere EEPROM-Inhalt nach RAM ldi ZH,high(2*txtid) ; Sende ID-Text an SIO ldi ZL,low (2*txtid) rcall txtext clr rfl ; Flag auf Anfangswert leer ; ; Bedingungen für Interrupt-Betrieb herstellen ; start1: rcall calctc ; Rechne Timerwerte und Ints aus rcall txpar ; Gib die eingestellten Werte über SIO ldi ZH,HIGH(2*txtcur) ; Sende Cursor-String ldi ZL,LOW(2*txtcur) rcall txtext ldi rmp,sleepmode ; Mode Idle für Sleep einstellen out MCUCR,rmp ; Aufwachen durch Interrupts ldi rmp,siorxint ; Enable RX mit Interrupt out UCR,rmp cbi pctrl,bcts ; Aktiviere CTS-Leitung sei ; Enable General Interrupt Flag ldi XH,HIGH(srtx) ; Puffer auf Anfang stellen ldi XL,LOW(srtx) rcall starttx ; und Text in CW aussenden ldi YH,HIGH(srte+3) ; Ausgabepointer hinter Ende ldi YL,LOW(srte+3) ; des Puffers stellen ldi XH,high(srtx+1) ; Puffer wieder auf Anfang ldi XL,low(srtx+1) ; und überschreiben des Textes clr rmp ; Null-Terminierung schreiben st -x,rmp ldi rmp,chcr ; Setze leeres Textfeld mit CR st -x,rmp ; Jetzt zeigt X-Zeiger auf Anfang ; ; Interrupt loop, ab jetzt praktisch nur Kreisverkehr ; mit Ausbruch bei gesetzten Flagbits ; loop: sleep ; CPU schlafen schicken nop ; Dummy-Befehl, bleibt bei Schlaf hier stehen nop ; nach dem Aufwachen ausgeführt sbrc rfl,bstx ; Sendeflag von Int-Routine gesetzt? rjmp starttx ; Ja: Starte Sendevorgang sbrc rfl,btxe ; Senden-beenden von Int-Routine? rjmp stoptx ; Beende Sendevorgang sbrc rfl,btxa ; Ist die Sendeausgabe aktiv? rjmp loop ; (Während Aussendung keine Parameter!) sbrc rfl,besc ; Parameter mit Menue holen? rjmp getpar ; Hole Parameter über SIO rjmp loop ; weiter im Kreis herum ; ; Startet Sendeprozess ; starttx: sbrc rfl,btxa ; Nicht neu starten, wenn schon aktiv rjmp loop ; Wieder raus! cbr rfl,bmstx ; Setze Flag bit zurück sbi pctrl,bcts ; Stop CTS-Leitung ldi YH,HIGH(srtx) ; Sende-Pointer auf Pufferanfang ldi YL,LOW(srtx) mov rcth,rikh ; Kurze Verzögerung bis zum Start mov rctl,rikl clr rnsc ; Auszugebendes Zeichen beendet, provoziert ; Laden des nächsten Zeichens bei der Senderoutine sbi pnfd,doc1 ; Ausgabe Pin OC1B=PD5 auf Ausgang (8515) clr rmp ; Setze Timer-Werte out TCCR1A,rmp ; OC1 inaktivieren out TCNT1H,rmp ; Timer-Register auf Null setzen out TCNT1L,rmp out OCR1AH,rcmh ; Compare Match auf Dauer entout OCR1AL,rcml ; sprechend der NF-Frequenz ldi rmp,t1CompInt ; Ermögliche Compare Int out TIMSK,rmp ldi rmp,t1TaktInt ; Clear Timer on Compare ; Match und Prescaler CK/8 out TCCR1B,rmp ; Timer-Takt starten rjmp loop ; und CPU bis zum Timer-Int schlafen legen ; ab jetzt läuft wieder alles automatisch ab ; ; Beendet Sendevorgang ; stoptx: cbr rfl,bmtxe ; Setze Beenden-Flag zurück cbi pctrl,bcts ; CTS wieder einschalten ldi ZH,HIGH(2*txtcur) ; Gib Cursor an SIO aus ldi ZL,LOW(2*txtcur) rcall txtext cpi XL,LOW(srtx) ; Schon Zeichen eingegeben? breq loop ; Nein, schlafen legen bis SIO-RX kommt ldi ZH,HIGH(srtx) ; Gepufferte Zeichen auf Cursorldi ZL,LOW(srtx) ; zeile ausgeben an SIO-TX stoptx1: ld rmp,z+ ; Zeichen aus Puffer lesen rcall txch ; an SIO senden cp ZL,XL ; Schon alle ausgegeben? brcs stoptx1 ; Weiter ausgeben rjmp loop ; Alles ausgegeben, schlafen legen ; ; Getpar holt menuegesteuert Parameter vom User ; getpar: ldi rmp,siorxtx ; Rx-Interrupts abschalten out UCR,rmp ; reiner Polling-Betrieb getpar0: rcall calctc ; Rechne aktuelle Parameter um http://www.avr-asm-tutorial.net/cq-dl/teil4/Cw01_200.html (1 of 2)1/20/2009 7:49:59 PM
cq-dl-Beiträge zu AVR-Mikrocontrollern
getpara: rcall txpar ; Gib die Parameter aus getparb: ldi ZH,HIGH(2*txtmenue) ; Gib Menue aus ldi ZL,LOW(2*txtmenue) rcall txtext rcall rxch ; Hole ein Zeichen von SIO cpi rmp,chesc ; Ende-Zeichen ESCAPE? brne getpar1 ; Nein, mach weiter im Menue cbr rfl,bmesc ; Setze Menue-Flag zurück rjmp start1 ; Ende, starte fast alles neu getpar1: cpi rmp,'s' ; Speichern gewählt? brne getpar1a rjmp getpars ; geh zum Speichern getpar1a: cpi rmp,'l' ; Lesen gewählt? brne getpar1b rjmp getparl ; geh zum Lesen getpar1b: cpi rmp,'d' ; Default-Werte gewählt? brne getpar1c rjmp getpard ; Setze Default Werte getpar1c: cpi rmp,'x' ; Testtext gewählt? brne getpar1d rjmp getparx getpar1d: ldi ZH,HIGH(2*txtf); Zeiger auf Frequenzzeile ldi ZL,LOW(2*txtf) ; im Menue cpi rmp,'f' ; Frequenzeingabe gewählt? brne getpar1e rjmp getpar2 ; ja, hole Zahl getpar1e: ldi ZH,HIGH(2*txtg); Geschwindigkeitseingabe ldi ZL,LOW(2*txtg) ; Zeiger setzen cpi rmp,'g' ; Geschwindigkeitseingabe gewählt? brne getpar1f rjmp getpar2 ; ja, hole Zahl getpar1f: ldi ZH,HIGH(2*txtt) ; Zeiger auf Texteingabe ldi ZL,LOW(2*txtt) cpi rmp,'t' ; Texteingabe gewählt? brne getpar0 ; Nein, gib dem User noch mal das Menue getpar2: ; Hole eine Zahlen- oder Texteingabe in den Puffer push rmp ; wird noch gebraucht (gewählter Menuepunkt) rcall txch ; Echo char an SIO zurück ldi rmp,chcr ; Mache neue Zeile rcall txch rcall txtext ; Gib den ausgewählten Menuetext aus ldi XH,HIGH(srtx) ; Empfangspuffer auf Anfang ldi XL,LOW(srtx) getpar3: rcall rxch ; Hole char von SIO st x+,rmp ; in Puffer out UDR,rmp ; Echo an SIO cpi rmp,chcr ; Ende der Eingabe? brne getpar3 ; Weiter mit Eingabe clr rmp ; String Nullterminieren st x,rmp pop rmp ; Menuepunkt wieder vom Stapel holen cpi rmp,'t' ; Texteingabe gewählt? breq getpara ; Ja, schon fertig, gib die Parameter aus push rmp ; Wird weiterhin gebraucht (Menuepunkt) rcall lese16 ; ASCII-Zahl im Puffer in binär ; in R1:R0 umwandeln pop rmp ; Menuepunkt wieder herstellen brcs getpare ; Fehler in Zahl, Fehlermeldung ausgeben cpi rmp,'f' ; Frequenz gewählt? brne getparg ; Nein, Geschwindigkeit gewählt! mov rmp,R1 ; Zahl zu groß? cpi rmp,0x10 ; Frequenz > 4095 Hz? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,0x01 ; Frequenz < 256 Hz? brcs getpark ; Fehlermeldung Zahl zu niedrig sts sfrq,R0 ; Zahl ok, übertragen sts sfrq+1,R1 rjmp getpar0 ; Rechne Parameter neu aus und gebe aus getparg: ; Neue Geschwindigkeit eingegeben tst R1 ; Zahl <256? brne getparh ; Nein, Fehlermeldung Zahl zu groß mov rmp,R0 cpi rmp,201 ; Zahl >200? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,10 ; Zahl <10? brcs getpark ; Zahl zu niedrig sts sbpm,R0 ; Zahl ok, übertragen rjmp getpar0 ; Beginne Neu mit Berechnung und Ausgabe getpars: ; Speichern der eingestellten Werte im EEPROM rcall ewrite ; Alles übertragen ldi ZH,HIGH(2*txteepw); Meldung ausgeben ldi ZL,LOW(2*txteepw) rcall txtext rjmp getparb ; Menuepunkte ausgeben und weiter getparl: ; Lesen der Werte aus dem EEPROM rcall ecopy ; Alles ins SRAM übertragen rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpard: ; Default-Werte setzen ldi ZH,HIGH(sfrq) ; Zeiger auf Frequenz ldi ZL,LOW(sfrq) ldi rmp,LOW(cfrq) ; LSB Default-Frequenz setzen st z+,rmp ; in SRRAM-Speicher ldi rmp,HIGH(cfrq) ; MSB Default-Frequenz setzen st z+,rmp ; in SRAM-Speicher ldi rmp,cbpm ; Default-Geschwindigkeit st z+,rmp ; in SRAM-Speicher rcall getdeftext ; Default-text in Speicher rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpare: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtzahl) ; Fehler in Zahl ldi ZL,LOW(2*txtzahl) rcall txtext rjmp getpara getpark: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtklein) ; Zahl zu niedrig ldi ZL,LOW(2*txtklein) rcall txtext rjmp getpara getparh: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txthoch) ; Zahl zu hoch ldi ZL,LOW(2*txthoch) rcall txtext rjmp getpara getparx: ; Test-Text ausgeben (nur in der STK-Version!) ldi XH,HIGH(srtx) ; Zeiger X auf SRAM-Text ldi XL,LOW(srtx) ldi ZH,HIGH(2*tsttext) ; Zeiger Z auf Testtext ldi ZL,LOW(2*tsttext) getparx1: lpm ; Lese Zeichen st x+,R0 ; Zeichen in SRAM-Puffer adiw ZL,1 ; nächstes Zeichen tst R0 ; Zeichen eine Null? brne getparx1 ; weiter mit Zeichen rjmp getpara ; fertig getdeftext: ; Default text in Speicher ab Z ldi rmp,'<' ; Verkehrsanfang, eingefügt 2313=>8515 st z+,rmp ldi rmp,10 ; Testtext in Speicher mov R0,rmp ; in Zähler getdeftext1: ldi rmp,'P' ; Paris st z+,rmp ldi rmp,'A' st z+,rmp ldi rmp,'R' st z+,rmp ldi rmp,'I' st z+,rmp ldi rmp,'S' st z+,rmp ldi rmp,' ' st z+,rmp dec R0 brne getdeftext1 sbiw ZL,1 ; Eins zurück ldi rmp,'>' st z+,rmp ldi rmp,chcr ; Textpuffer mit Carriage Return st z+,rmp clr rmp ; und Nullterminator st z,rmp ; entleeren ret ; eingefügt bis hier 2313=>8515 ; ; Warte auf char von der SIO mit Echo ; rxch: in rmp,USR ; Lese Control register sbrs rmp,RXC ; Character vorhanden? rjmp rxch ; Noch nicht, weiter warten in rmp,UDR ; Lese character aud Datenregister ret ; und zurück ; ; Sende text character in rmp an SIO mit Prüfung ; txch: push rmp ; Rette Zeichen auf Stapel txch1: in rmp,USR ; Senderegister leer? sbrs rmp,UDRE ; Ist das UDRE-Bit gesetzt rjmp txch1 ; Nein, weiter warten pop rmp ; Hole Zeichen vom Stapel out UDR,rmp ; Character senden cpi rmp,chcr ; Nach Carriage Return noch ein brne txch2 ; Linefeed? ldi rmp,chlf ; auch den noch senden! rcall txch ldi rmp,chcr ; und Zeichen wieder herstellen txch2: ret ; ; Morsecode der ASCII-Zeichen 0x20 bis 0x5F ; unteres Byte = Code (0=Punkt, 1=Strich) ; oberes Byte = Anzahl Punkte/Striche ; Bit 7 = 1: Leerzeichen ; morse: ; Zeichen 20 .. 2F .DB 0b11100000,0b10000011 ; Blank .DB 0b01000000,5 ; ! = Warten .DB 0b01001000,6 ; " .DB 0b11011000,5 ; # = ñ .DB 0b01101000,5 ; $ = á, å .DB 0b01000000,5 ; % = é .DB 0b00000000,0 ; & = nicht benutzt .DB 0b01111000,6 ; ' .DB 0b10110000,5 ; ( .DB 0b10110100,6 ; ) .DB 0b00000000,0 ; * = nicht benutzt .DB 0b00010100,6 ; + = Spruchende .DB 0b11001100,6 ; , .DB 0b10000100,6 ; .DB 0b01010100,6 ; . .DB 0b10010000,5 ; / ;Zeichen 30 .. 3F .DB 0b11111000,5 ; 0 .DB 0b01111000,5 ; 1 .DB 0b00111000,5 ; 2 .DB 0b00011000,5 ; 3 .DB 0b00001000,5 ; 4 .DB 0b00000000,5 ; 5 .DB 0b10000000,5 ; 6 .DB 0b11000000,5 ; 7 .DB 0b11100000,5 ; 8 .DB 0b11110000,5 ; 9 .DB 0b11100000,6 ; : .DB 0b10101000,6 ; ; .DB 0b10101000,5 ; < = Verkehrsanfang .DB 0b10001000,5 ; = .DB 0b01010000,5 ; > = Verkehrsende .DB 0b00110000,6 ; ? ;Zeichen 40 .. 4F .DB 0b11110000,4 ; @ = ch .DB 0b01000000,2 ; A .DB 0b10000000,4 ; B .DB 0b10100000,4 ; C .DB 0b10000000,3 ; D .DB 0b00000000,1 ; E .DB 0b00100000,4 ; F .DB 0b11000000,3 ; G .DB 0b00000000,4 ; H .DB 0b00000000,2 ; I .DB 0b01110000,4 ; J .DB 0b10100000,3 ; K .DB 0b01000000,4 ; L .DB 0b11000000,2 ; M .DB 0b10000000,2 ; N .DB 0b11100000,3 ; O ;Zeichen 50 .. 5F .DB 0b01100000,4 ; P .DB 0b11010000,4 ; Q .DB 0b01000000,3 ; R .DB 0b00000000,3 ; S .DB 0b10000000,1 ; T .DB 0b00100000,3 ; U .DB 0b00010000,4 ; V .DB 0b01100000,3 ; W .DB 0b10010000,4 ; X .DB 0b10110000,4 ; Y .DB 0b11000000,4 ; Z .DB 0b01010000,4 ; [ = Ä .DB 0b11100000,4 ; \ = Ö .DB 0b00110000,4 ; ] = Ü .DB 0b00000000,8 ; ^ = Irrung .DB 0b00110100,6 ; _ morseende: .DW morseende-morse ; Prüfzahl, muss 0x0040 sein ; ; Testtext, nur in der STK-Version implementiert! tsttext: .DB "",chcr, cnul ChkT: .DW ChkT ; ; Texte für die serielle Schnittstelle ; Hinweis: Die chk-Werte sind zum Überprüfen im Listing ; eingefügt. Es gibt einen Bug im AVR-Assembler von ATMEL, ; der zu falschen Adressen führt, wenn bestimmte Kombina; tionen von Byte-Konstanten verwendet werden. Dieser Bug ; ist seit zwei Jahren gemeldet und noch immer nicht besei; tigt! Teilweise sind die abenteuerlichen Konstruktionen ; in dieser Liste zur Umgehung dieses Bugs verwendet. ; ; Eingangsmeldung zu Beginn txtid: .DB chff,chcr .DB "+---------------------------------+",chcr .DB "| Morseprogramm (C)2002 by DG4FAC |",chcr .DB "+---------------------------------+",chcr .DB cnul,cnul chk1: .DW chk1 ; für Assembler Bug ; Text für Parameterliste txtpar1: .DB chcr,'E',"ingestellte Parameter:",chcr,'*' txtf: .DB "NF-Frequenz (256..4095 Hz) = ",cnul chk2: ; Bug-Check .DW chk2 txtpar2: .DB " Hz ",chcr,'*' txtg: .DB "Geschwindigkeit (10..200 BpM) = ",cnul chk3: ; Bug-Check .DW chk3 txtpar3: .DB " BpM",chcr,' ',"ccm = ",cnul,cnul chk4: ; Bug-Check .DW chk4 txtpar4: .DB ", Ints (kurz) = ",cnul,cnul chk5: ; Bug-Check .DW chk5 txtpar5: .DB ", Ints (lang) = ",cnul,cnul chk6: ; Bug-Check .DW chk6 txtpar6: .DB chcr,'*' txtt: .DB "Text = ",cnul chk7: ; Bug-Check .DW chk7 txtcur: .DB chcr,'=','>',cnul chk8: ; Bug-Check .DW chk8 txtmenue: .DB "Einstellungen: f=NF-Frequenz, g=Geschwindigkeit," .DB " t=Text, s=Speichern,",chcr .DB " l=Lesen, d=Default, x=Test, ESC=Ende! ",chcr .DB "(f,g,t,s,l,d,x,ESC) => ",cnul chk9: ; Bug-Check .DW chk9 ; Prüfzahl für Assembler-Bug txtzahl: .DB chcr,'F',"ehler in Ziffer oder Zahl bzw. Zahl zu gross!",cnul chk10: ; Bug-Check .DW chk10 ; Prüfzahl für Assembler-Bug txtklein: .DB chcr,'*',"* Zahl zu niedrig! **",cnul,cnul chk11: ; Bug-Check .DW chk11 ; Prüfzahl für Assembler-Bug txthoch: .DB chcr,'*',"* Zahl zu hoch! **",cnul,cnul chk12: ; Bug-Check .DW chk12 ; Prüfzahl für Assembler-Bug txteepw: .DB chcr,'P',"arameter ins EEPROM geschrieben.",chcr,cnul chk13: ; Bug-Check .DW chk13 ; ; Copyright-Info .DB "C(2)00 2ybD 4GAF C" ; ; Programm-Code Ende ; ; ****************************************** ; * EEPROM-Inhalt mit Default beginnt hier * ; ****************************************** ; .ESEG .ORG 0x0000 ; efrq: ; Die Default-NF-Frequenz .DW cfrq ebpm: ; Die Default-Geschwindigkeit in BpM .DB cbpm etxt: ; Dieser Text wird zu Beginn ausgegeben .DB "hello!" .DB chcr,cnul etxte: ; ; Copyright-Info ; .DB "(C)2002 by DG4FAC" ; ; Ende des EEPROM-Segmentes ;
; ******************************************************** ; * CW.asm gibt Morsezeichen aus, die im EEPROM-Speicher * ; * gespeichert sind und die über die serielle Schnitt- * ; * stelle des STK200-Boards eingegeben werden. Die Aus- * ; * gabe der NF erfolgt an PD5. Baudrate 9k6. Takt 4 MHz.* ; * AVR AT90S8515. Programm (C)2002 by DG4FAC G.Schmidt * ; * Version 0.1-200 vom 12.1.2002 * ; * Bugs und Dankschreiben an [email protected] * ; ******************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Benutzte Register ; ; Register R0 wird für Lesen im Programmspeicher benutzt ; Register R0..R9 werden für Berechnungen benutzt ; .DEF rcml = R10 ; Compare Match-Zahl, LSB .DEF rcmh = R11 ; dto., MSB .DEF rikl = R12 ; Interrupt-Anzahl kurz, LSB .DEF rikh = R13 ; dto., MSB .DEF rill = R14 ; Interrupt-Anzahl lang, LSB .DEF rilh = R15 ; dto., MSB .DEF rmp = R16 ; Multipurpose register, nicht bei Ints .DEF rim = R17 ; Interrupt multi-purpose .DEF rfl = R18 ; Flag Register, bei Ints und Normal .DEF rst = R19 ; Statusregister bei Interrupts .DEF rnsc = R20 ; Anzahl Punkte/Striche beim Senden .DEF rmcd = R21 ; Morsecode beim Senden .DEF x22 = R22 ; unbenutzt .DEF x23 = R23 ; unbenutzt .DEF rctl = R24 ; Zähler für Interrupt Tonerzeugung .DEF rcth = R25 ; (wird als Doppelregister verwendet) ; ; Register XH:XL R27:R26 Zeiger in den Empfangspuffer ; Register YH:YL R29:R28 Zeiger beim Senden ; Register ZH:ZL R31:R30 Zeiger für Lesen im Programmspeicher ; ; Bits im Flagregister ; .EQU besc = 7 ; ESCape-Sequenz, hole Parameter .EQU bmesc = 0x80 ; Maskenbyte für ESC .EQU bstx = 6 ; Starte Sendeprozess .EQU bmstx = 0x40 ; Maskenbyte für bstx .EQU bctx = 5 ; Beende Sendeprozess nach Leerzeichen .EQU bmctx = 0x20 ; Maskenbyte für bctx .EQU btxa = 4 ; Sendeprozess aktiv .EQU bmtxa = 0x10 ; Maskenbyte für btxc .EQU btxe = 3 ; Abschluss des Sendens .EQU bmtxe = 0x08 ; Maskenbyte für btxe .EQU bpl = 2 ; Lange Pause zur Buchstabentrennung .EQU bmpl = 0x04 ; Maskenbyte für bpl .EQU bpk = 1 ; Kurze Pause zur Punkt/Strich-Trennung .EQU bmpk = 0x02 ; Maskenbyte für bpk .EQU bquiet = 0 ; Dauerhaft inaktiviert bei Leerzeichen .EQU bmquiet = 0x01 ; Maskenbyte für bquiet ; ; Default-Werte ; .EQU cfrq=1000 ; NF-Frequenz .EQU cbpm=60 ; Gebegeschwindigkeit ; ; Basisdefinitionen variabel nach Schaltung ; .EQU fqu = 4000000 ; Quarzfrequenz des AVR .EQU fbd = 9600 ; Baudrate des UART .EQU pctrl = PORTB ; Control-Port für RTS/CTS (nicht benutzt) .EQU pdrr = DDRB ; Datenrichtungsregister Controlport .EQU brts = PB2 ; RTS bit Input (beim STK nicht benutzt) .EQU bcts = PB4 ; CTS bit Output (beim STK nicht benutzt) .EQU pnfd = DDRD ; Port für Richtung NF-Ausgabe (8515) .EQU doc1 = PD5 ; NF-Ausgabe über OC1B-Pin (8515) ; ; Umrechnungen in Timer- und Counterwerte ; .EQU nps = 8 ; Prescaler-Einstellung von Timer 1 .EQU ccm = fqu/nps/2; Konstante für Compare Match .EQU cint =2941 ; Konstante für Int-Berechnung, ; experimentell ermittelt ; ; Definitionen fix ; .EQU bddv = (fqu/(16*fbd))-1 ; Baudraten-Teiler .EQU cnul = 0x00 ; Ende für nullterminierte Strings .EQU chbsp = 0x08 ; Backspace character .EQU chcr = 0x0D ; Carriage Return character .EQU chlf = 0x0A ; Line Feed character .EQU chff = 0x0C ; Form Feed character .EQU chesc= 0x1B ; ESCape-Zeichen ; ; Definitionen I/O ; ; Definitionen für Timer-Controlregister TCCR1A .EQU t1aus = 0b10000000 ; Schaltet den NF-Ausgang aus .EQU t1ein = 0b01000000 ; Schaltet den NF-Ausgang ein ; Definition für Timer-Interrupt-Mask-Register TIMSK .EQU t1CompInt=0b01000000 ; Interrupt bei Compare Match ; Definition für Timer-Controlregister TCCR1B .EQU t1TaktInt=0b00001010 ; Timer-Takt CLK / 8, Clear Comp ; Definitionen für UART-Betrieb in UCR .EQU siorxtx = 0b00011000 ; Betrieb RX und TX ohne Int .EQU siorxint= 0b10011000 ; Betrieb RX mit Int, TX ohne Int ; Definition für den SLEEP-Mode in MCUCR .EQU sleepmode=0b00100000 ; Aufwachen bei Interrupt ; ; Positionen im SRAM ; .EQU sfrq = 0x0060 ; Eingestellte NF-Frequenz, Wort .EQU sbpm = 0x0062 ; Eingestellte Geschwindigkeit, Byte .EQU srtx = 0x0063 ; RAM-Puffer für Ein-/Ausgabezeile .EQU srte = 0x00B3 ; Ende des nutzbaren Puffers, benutzt werden ; auch zwei weitere Folgebytes ; ; Programm beginnt hier ; .CSEG .ORG 0x0000 ; ; Reset- und Interrupt-Sprungtabelle 8515 ; rjmp start ; Reset-Vector reti ; Ext Int 0, nicht benutzt reti ; Ext Int 1, nicht benutzt reti ; Timer 1 Capture Event, nicht benutzt rjmp itc1m ; Timer 1 Compare Match A reti ; Timer 1 Compare Match B reti ; Timer 1 Overflow, nicht benutzt reti ; Timer 0 Overflow, nicht benutzt reti ; SPI STC, nicht benutzt rjmp iurxc ; UART Rx Complete reti ; UART Tx data register empty, nicht benutzt reti ; UART Tx All sent, nicht benutzt reti ; ANA_COMP, nicht benutzt ; ; Interrupt-Service-Routinen ; itc1m: ; Timer 1 Compare Match Interrupt in rst,SREG ; Rette Statusregister sbiw rctl,1 ; Zähler eins abzählen brne itc1mz ; Raus, wenn nicht Null mov rcth,rikh ; Setze Zähler auf kurze Dauer mov rctl,rikl ldi rim,t1aus ; Ton auf aus tst rnsc ; Zeichen fertig gesendet? breq itc1m0 ; Verzweige, wenn Zeichen fertig sbrc rfl,bpk ; kurze Pausen-Flag? rjmp itc1ms ; Pause zwischen Punkt/Strich war schon sbr rfl,bmpk ; Setze kurze Pause-Flag rjmp itc1my ; und Ausgabe auf inaktiv, fertig itc1ms: ; Sende nächsten Punkt/Strich cbr rfl,bmpk ; Lösche kurze Pause-Flag ldi rim,t1ein; Ton an = Toggle dec rnsc ; Weiteren Punkt/Strich gesendet rol rmcd ; Punkt oder Strich senden? brcc itc1my ; Punkt senden mov rcth,rilh ; Lange Dauer einstellen mov rctl,rill rjmp itc1my itc1m0: ; Zeichen fertig gesendet sbrc rfl,bctx ; Sendung beenden? rjmp itc1mx sbrc rfl,bpl ; Lange Pause senden? rjmp itc1mn ; Nächsten Buchstaben beginnen sbr rfl,bmpl ; Setze langes Pausen-Flag mov rcth,rilh ; Dauer auf lang stellen mov rctl,rill itc1my: ; Stelle Modus inaktiv/toggle ein sbrc rfl,bquiet ; bei Leerzeichen Ton aus ldi rim,t1aus ; Ton auf aus out TCCR1A,rim itc1mz: out SREG,rst ; Stelle Status wieder her reti itc1mn: cbr rfl,bmpl ; Langes Pausen-Flag aus ld rim,y+ ; Nächsten Buchstaben lesen tst rim ; Null-Terminator breq itc1mn1 cpi rim,chcr ; Ende der Zeile? brne itc1mn2 itc1mn1: sbr rfl,bmctx ; Setze beenden-Flag ldi rim,' ' ; Sende noch ein Leerzeichen itc1mn2: out UDR,rim ; Debug subi rim,0x20 ; ASCII-Control-Zeichen weg brcc itc1mn3 ldi rim,'?'-0x20 ; Sende Fragezeichen itc1mn3: cpi rim,0x40 ; Kleinbuchstabe? brcs itc1mn4 subi rim,0x20 ; in Grossbuchstaben wandeln cpi rim,0x40 brcs itc1mn4 ldi rim,'?'-0x20 itc1mn4: add rim,rim ; Mal 2 für Tabelle push ZH ; Register retten push ZL push R0 ldi ZH,HIGH(2*morse) ; Zeichentabelle laden ldi ZL,LOW(2*morse) add ZL,rim ; Zeichen dazu zählen brcc itc1mn5 ; Kein Übertrag? inc ZH ; Übertrag itc1mn5: lpm ; Lese Zeichencode aus Tabelle mov rmcd,R0 ; in Zeichencode-Register adiw ZL,1 ; Zeiger auf nächstes Byte lpm ; aus Tabelle lesen mov rnsc,R0 ; Anzahl Striche/Punkte pop R0 ; Wieder herstellen der Register pop ZL pop ZH tst rnsc ; Undefiniertes Zeichen? breq itc1mn ; Überspringe Zeichen sbr rfl,bmquiet ; Leerzeichen sbrs rnsc,7 ; Leerzeichen? cbr rfl,bmquiet ; Kein Leerzeichen cbr rnsc,0x80 ; Lösche höchstes Bit mov rim,YL ; CTS einschalten? sub rim,XL brcs itc1mn6 ; Ausschalten cpi rim,3 brcs itc1mn6 cbi pctrl,bcts ; CTS einschalten rjmp itc1ms itc1mn6: sbi pctrl,bcts ; CTS ausschalten rjmp itc1ms ; Sende ein Zeichen itc1mx: ; Sendung einstellen clr rim ; Timer 1 abschalten out TCCR1B,rim ; Timer-Takt aus out TCNT1H,rim ; Timer auf Null stellen out TCNT1L,rim out TIMSK,rim ; Timer Interrupts aus out TCCR1A,rim ; Timer Compare Mode aus cbr rfl,bmctx+bmtxa ; Ende-Flag und aktiv ausschalten sbr rfl,bmtxe ; Beenden-Flag einschalten ldi YH,HIGH(srte) ; Buffer auf Ende ldi YL,LOW(srte) st Y,rim ; Null-terminieren rjmp itc1mz ; ; UART Rx Complete Interrupt ; iurxc: in rst,SREG ; Rette Statusregister in rim,UDR ; Lese Zeichen von SIO cpi rim,chesc ; ESCape-Sequenz? brne iurx1 sbr rfl,bmesc ; Setze ESCape-Bit rjmp iurxz iurx1: cpi rim,chbsp ; Backspace-Char? brne iurx2 cpi XL,LOW(srtx) ; Schon auf Anfang? breq iurxz sbiw XL,1 ; Eine Position zurück ldi rim,chcr ; Zeilenabschluss st x+,rim clr rim ; Null-terminieren st x,rim sbiw XL,1 ; zurück ldi rim,chbsp ; Backspace zurücksenden rjmp iurxe ; Echo character iurx2: cpi XL,low(srte) ; Pufferüberlauf? brcs iurx3 ; Nein, weiter ldi rim,chcr ; Character überschreiben iurx3: cpi rim,chcr ; Zeilenende? brne iurxw ; Nein, in Puffer und fertig sbrs rfl,btxa ; Überspringe Ausgabe wenn aktiv out UDR,rim ; Carriage Return schon mal ausgeben st x+,rim ; CR-Zeichen anhängen clr rim ; Null terminieren st x,rim ldi XH,HIGH(srtx) ; Puffer auf Anfang ldi XL,LOW(srtx) sbr rfl,bmstx ; Über Flag Sender starten ldi rim,chlf ; Sende Zeilenvorschub rjmp iurxe ; Echo Line-Feed iurxw: st x+,rim ; Speichere character ldi rim,chcr ; Abschluss terminieren st x+,rim ; mit Carriage return clr rim ; Null-terminieren st x,rim sbrs rfl,btxa ; Sender aktiv? rjmp iurxs ; Nein, CTS nicht prüfen mov rim,YL ; CTS ein oder aus? sub rim,XL ; Distanz zwischen Ein- und Ausgabe brcs iurxo ; Eingabe > Ausgabe: CTS aus cpi rim,3 ; mindestens zwei Zeichen Vorlauf? brcs iurxo ; Nein, dann CTS ausschalten cbi pctrl,bcts ; CTS einschalten rjmp iurxs ; Zeichen herstellen und raus iurxo: sbi pctrl,bcts ; CTS ausschalten iurxs: sbiw XL,2 ; Char wieder herstellen ld rim,x+ ; durch Lesen im SRAM iurxe: sbrs rfl,btxa ; Keine Ausgabe, wenn aktiv out UDR,rim ; Echo character an SIO zurück iurxz: out SREG,rst ; Stelle Status wieder her reti ; Ende des Interrupts ; ; Diverse eigenständige Unterprogramme ; ; Kopiert den EEPROM-Inhalt in das SRAM ; ecopy: sbic EECR,EEWE ; Warte, bis EEPROM bereit rjmp ecopy ; Noch nicht bereit clr YH ; Startadresse im EEPROM auf Null clr YL ldi ZH,HIGH(sfrq) ; Startadresse im SRAM ldi ZL,LOW(sfrq) ; auf ersten Parameter ecopy1: out EEARH,YH ; Leseadresse im EEPROM out EEARL,YL ; 2313 hat nur 128 Bytes, daher nur unteres Register sbi EECR,EERE ; Lese-Strobe setzen cbi EECR,EERE ; und wieder ausschalten in rmp,EEDR ; Byte aus EEPROM-Datenregister lesen st Z+,rmp ; und in SRAM schreiben adiw YL,1 ; nächste EEPROM-Adresse anwählen tst rmp ; Null-Terminator? brne ecopy1 ; Nein: weiter kopieren ret ; ; Schreibe Parameter in das EEPROM zurück ; ewrite: ldi ZH,HIGH(sfrq) ; Zeiger auf SRAM ldi ZL,LOW(sfrq) clr XH ; Zeiger in das EEPROM clr XL ewrite1: sbic EECR,EEWE ; Frage Write-Bit im EEPROM rjmp ewrite1 ; ab und wiederhole bis EEPROM ready out EEARH,XH ; Schreibadresse im EEPROM einstellen out EEARL,XL ld rmp,Z+ ; Lese Byte aus SRAM in Register out EEDR,rmp ; in das EEPROM-Datenregister cli ; Keine Interrupts mehr beim Schreibvorgang sbi EECR,EEMWE ; Setze EEPROM Master Write Enable sbi EECR,EEWE ; Löse Schreibvorgang im EEPROM aus sei ; Jetzt Interrupts wieder zulassen adiw XL,1 ; Nächste EEPROM-Adresse in X-Zeiger cpi XH,$02 ; Ende EEPROM erreicht? brcc ewrite2 ; Überlänge! Null fehlt! Abbrechen! tst rmp ; Nullterminiert? brne ewrite1 ; Noch weitere Bytes schreiben ewrite2: ret ; ; Lese16 wandelt eine ASCII-Zahl im Puffer in binär ; in R1:R0, bei Fehler Rückkehr mit gesetztem Carry-Bit ; lese16: clr R0 ; Leeren Resultat-Register R1:R0 clr R1 ldi ZH,HIGH(srtx) ; Zeige auf Pufferanfang ldi ZL,LOW(srtx) lese16a: ld rmp,Z+ ; Lese ASCII-Ziffer aus SRAM cpi rmp,chcr ; Ende der Zahl mit Carriage Return? breq lese16ok ; Zahl mit Carriage Return beendet cpi rmp,cnul ; Ende der Zahl mit Nullterminiert? breq lese16ok ; Ende mit Nullterminierung cpi rmp,'9'+1 ; Ziffer > ASCII-9? brcc lese16no ; Ja, Fehler! cpi rmp,'0' ; Ziffer < ASCII-0 brcs lese16no ; Ja, auch Fehler subi rmp,'0' ; Wandle Ziffer in binär ; Ab hier wird das bisherige Resultat mit 10 multi; pliziert mov R2,R0 ; Kopiere Binärzahl in Hilfsregister R3:R2 mov R3,R1 add R0,R0 ; Multipliziere mit 2 durch Addieren 16 Bit adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R2 ; Addiere die Kopie der Zahl adc R1,R3 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! ; Hier ist das Multiplizieren mit 10 beendet. add R0,rmp ; Addiere Ziffer zum Resultat hinzu brcc lese16a ; Kein Überlauf des unteren Bytes inc R1 ; Überlauf, oberes Byte erhöhen brne lese16a ; Kein Überlauf des oberen Bytes lese16no: sec ; Setze Carry-Bit bei Fehler und kehre zurück ret lese16ok: clc ; Clear Carry bei fehlerfrei ret ; ; Wandle 16-Bit-Zahl in R1:R0 in ASCII in R4..R9 (nullterm.) ; unterdrücke führende Nullen, sende Ergebnis ohne führende ; Nullen über die SIO ; b16asc: clr ZH ; Z zeigt auf höchste ASCII-Ziffer in ldi ZL,4 ; Register R4 (Zeiger in Register!) ldi rmp,HIGH(10000) ; Beginne mit Zehntausendern mov R3,rmp ; oberes Byte in R3 ldi rmp,LOW(10000) mov R2,rmp ; unteres Byte in R2 rcall b16sub ; Ermittle ASCII-code der Zehntausender ; Stelle durch fortgesetztes Subtrahieren von 10000 ldi rmp,HIGH(1000) ; Weiter mit Tausendern mov R3,rmp ldi rmp,LOW(1000) mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Tausender clr R3 ; Weiter mit Hunderten ldi rmp,100 mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Hunderter ldi rmp,10 ; Weiter mit Zehnern mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Zehner ldi rmp,'0' ; Rest sind Einer add rmp,R0 mov R8,rmp ; R8 kriegt die ASCII-Einer clr R9 ; Nullterminieren in R9 ldi ZL,4 ; Z auf 10000-er Zeichen b16asc1: cpi ZL,9 ; Ende der Zeichenkette erreicht? breq b16asc2 ; Ja, raus ld rmp,z+ ; Zeichen aus Register kopieren cpi rmp,'0' ; Führende Null? breq b16asc1 ; Ja, weitermachen b16asc2: dec ZL ; auf vorheriges Zeichen setzen ; ; Sende nullterminierten Text aus SRAM an SIO ; Z zeigt nicht ins SRAM, sondern in die Register mit ; dem Ergebnis! ; Das Registerpaar Z zeigt auf das erste Zeichen ; txstxt: ld rmp,z+ ; Lese Zeichen aus SRAM/Register tst rmp ; Nullterminator erreicht? breq txstxt1 ; Ja, raus und fertig rcall txch ; Sende character über SIO rjmp txstxt ; Weitermachen mit nächstem Zeichen txstxt1: ret ; ; Ermittle ASCII-Code einer Ziffer der 16-Bit-Binärzahl in ; R1:R0 durch fortgesetztes Subtrahieren einer 16-Bit; Hilfszahl (10000, 1000, 100, 10) in R3:R2. Tritt ein ; Überlauf ab, dann ist die Ziffer gefunden ; (Unterroutine für b16asc) ; b16sub: ldi rmp,'0' ; Setze ASCII-Null b16sub1: sub R0,R2 ; Subtrahiere die Hilfszahl sbc R1,R3 ; in 16-bit brcs b16sub2 ; Ende subtrahieren erreicht? inc rmp ; Einer geht noch! rjmp b16sub1 ; Weiter mit subtrahieren! b16sub2: add R0,R2 ; Zu weit, addiere wieder zurück adc R1,R3 st Z+,rmp ; ASCII-Ziffer in Register schreiben ret ; und zurück ; ; Sende nullterminierten Text aus dem Programmspeicher ; über die SIO aus ; txtext: lpm ; Lese Zeichen aus Programmspeicher mit Zeiger Z tst R0 ; Ergebnis im Register R0 hat Null erreicht? breq txtend ; Ja, raus aus der Routine mov rmp,R0 ; Kopiere Zeichen in Senderegister rcall txch ; Sende character mit Prüfung adiw zl,1 ; Zeiger auf nächstes Zeichen rjmp txtext ; Weitermachen bis Null txtend: ret ; Kehre zurück ; ; Liste alle Parameter über die SIO-Schnittstelle auf ; txpar: ldi ZH,HIGH(2*txtpar1) ; Sende Parametervorspann ldi ZL,LOW(2*txtpar1) ; für NF-Frequenz rcall txtext lds R0,sfrq ; Zeige eingestellte Frequenz an lds R1,sfrq+1 rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar2) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar2) ; Geschwindigkeit an rcall txtext lds R0,sbpm ; Zeige Geschwindigkeit an clr R1 ; nur 8 Bit! rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar3) ; Zeige Vorspann für Counterldi ZL,LOW(2*txtpar3) ; Compare-Match-Zahl ccm an rcall txtext mov R1,rcmh ; Compare-Match-Zahl in R1:R0 mov R0,rcml rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar4) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar4) ; Anzahl NF-Ints bei Punkt an rcall txtext mov R1,rikh ; Anzahl Ints bei Punkt in R1:R0 mov R0,rikl rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar5) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar5) ; Anzahl NF-Ints bei Strich an rcall txtext mov R1,rilh ; Anzahl Ints bei Strich in R1:R0 mov R0,rill rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar6) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar6) ; Ausgabetext an rcall txtext ldi ZH,HIGH(srtx) ; Zeiger Z auf Ausgabetext im SRAM ldi ZL,LOW(srtx) rjmp txstxt ; Sende Inhalt SRAM nullterminiert ; ; 32-Bit durch 16-Bit-Division ; ; Dividiert 32-Bit-Zahl in R3:R2:R1:R0 durch R5:R4 ; Ergebnis in R7:R6 (Ergebnis darf nicht > 16 Bit sein!) ; div32: clr R7 ; Clear Ergebnis-Register R7:R6 clr R6 inc R6 ; Stopbit setzen für 16 Divisionsschritte div32a: clc ; Null in Carry schieben rol R0 ; Multipliziere Divident mit 2 rol R1 rol R2 rol R3 brcs div32e ; Carry ist herausgerollt? Dann 1! cp R3,R5 ; Vergleiche oberes Byte brcs div32n ; Ergebnis ist kleiner, also eine 0 brne div32e ; Ergebnis ist größer, also eine 1 cp R2,R4 ; Vergleich MSB gleich, vergleiche unteres ; Byte brcs div32n ; Unteres Byte kleiner, also eine 0 div32e: sub R2,R4 ; Ziehe den Divisor von den oberen 16 Bit ab sbc R3,R5 sec ; Setze das Carry-Bit, Ergebnis ist eine 1 rjmp div32s ; Zum Reinschieben in das Ergebnis div32n: clc ; Lösche Carry-Bit, Ergebnis ist eine 0 div32s: rol R6 ; Schiebe Carry-Bit von rechts in Ergebnis rol R7 brcc div32a ; Ende, wenn eine 1 links herausrollt ret ; ; Multipliziert 16-Bit-Zahl in R1:R0 mit 16-Bit-Zahl in R5:R4 ; Ergebnis 32 Bit in R9:R8:R7:R6, für jeden Zahlenbereich ; Mul16: clr R3 ; Leere obere zwei Bytes der 16-Bit-Zahl clr R2 clr R9 ; Leere Ergebnis-Register R9:R8:R7:R6 clr R8 clr R7 clr R6 Mul16a: clc ; Null ins Carry-Bit ror R5 ; Schiebe unterstes Bit Divisor in Carry ror R4 ; und dividiere Multiplikant durch 2 brcc Mul16b ; Bit war eine 0, Addition überspringen add R6,R0 ; addiere Multiplikator 32 Bit zum adc R7,R1 ; bisherigen Ergebnis, jeweils mit adc R8,R2 ; Überlauf adc R9,R3 Mul16b: tst R4 ; Schon alle Bits ausmultipliziert? brne Mul16c ; Nein, LSB nicht Null, weiter tst R5 ; Teste auch oberes Byte brne Mul16c ; Nein, MSB nicht Null, weiter ret ; Fertig Mul16c: clc ; Null in Carry-Bit schieben rol R0 ; Zahl durch Linksschieben mit 2 rol R1 ; multiplizieren rol R2 rol R3 rjmp Mul16a ; und weiter dividieren ; ; Dividiert 32 Bit-Zahl in R3:R2:R1:R0 durch eine ; 8-Bit-Zahl in R4 und durch 256, Ergebnis gerundet ; in R8:R7, Wertebereich beachten! ; Div32_8: clr R8 ; Ergebnisspeicher R8:R7:R6 leeren clr R7 clr R6 inc R6 ; Stopbit nach 24 Schritten setzen Div32_8a: clc ; Carry-Bit leeren rol R0 ; Divident mit 2 multiplizieren rol R1 ; durch Linksschieben rol R2 rol R3 brcs Div32_8e ; 1 herausgerollt, Ergebnis=1 cp R3,R4 ; Vergleiche oberstes Byte mit Divisor brcs Div32_8n ; Kleiner, Ergebnis = 0 Div32_8e: sub R3,R4 ; Ziehe Divisor von oberstem Byte ab sec ; Setze Carry für Ergebnis = 1 rjmp Div32_8b ; Ins Ergebnis schieben Div32_8n: clc ; Clear Carry für Ergebnis = 0 Div32_8b: rol R6 ; Ergebnis-Bit von rechts her hineinrotieren rol R7 rol R8 brcc Div32_8a ; Weiter, wenn eine Null herausrollt rol R6 ; Binäre Kommastelle eine 1, aufrunden? brcc Div32_8z ; Nein, wenn Null inc R7 ; Aufrunden LSB Ergebnis brne Div32_8z ; Kein Überlauf bei LSB-Erhöhung inc R8 ; Aufrunden MSB Div32_8z: ret ; ; Rechne Parameter um in Timer- und Zählerwerte ; Oben definiert: ccm = Taktfrequenz / Prescale 8 / 2 ; cint = ccm / 200 ; Compare-Match-Zahl für Timer 1 = ccm / NF-Frequenz ; Anzahl Ints bei Punkt = cint * NF-Frequenz / Speed /256 ; Anzahl Ints bei Punkt = Anzahl Ints bei Strich * 3 ; calctc: ldi rmp,BYTE4(ccm) ; Taktabhängige Konstante ccm mov R3,rmp ; in R3:R2:R1:R0, bei 4 MHz: 625.000 ldi rmp,BYTE3(ccm) ; oder 0x00098968 mov R2,rmp ldi rmp,BYTE2(ccm) mov R1,rmp ldi rmp,BYTE1(ccm) mov R0,rmp lds R4,sfrq ; durch NF-Frequenz in R5:R4 lds R5,sfrq+1 rcall div32 ; ergibt Compare Match-Zahl 16-Bit ; rcall txreg ; Debug-Code!!! mov rcmh,R7 ; Ergebnis in Speicher rcmh:rcml mov rcml,R6 ldi rmp,HIGH(cint) ; Konstante für Anzahl Ints mov R1,rmp ; bei 4 MHz und Teiler 200: 1250 ldi rmp,LOW(cint) mov R0,rmp lds R4,sfrq ; Mal NF-Frequenz in Hz lds R5,sfrq+1 rcall mul16 ; Multplizieren 16 Bit * 16 Bit ; rcall txreg ; Debug code!!! mov R3,R9 ; Ergebnis in R9..R6 nach R3..R0 kopieren mov R2,R8 mov R1,R7 mov R0,R6 lds R4,sbpm ; durch Gebegeschwindigkeit teilen rcall div32_8 ; teilen 32-Bit durch 8-Bit ; rcall txreg ; Debug code!!! mov rikh,R8 ; in Kurzspeicher kopieren mov rikl,R7 mov rilh,R8 ; und in Langspeicher kopieren mov rill,R7 add rill,rill ; Langspeicher mit 2 malnehmen adc rilh,rilh ; durch Addieren add rill,rikl ; und noch einmal dazu zählen adc rilh,rikh ; macht 3 mal so lang ret ; ; Debug code ; ; Display Byte in rmp in Hex an die SIO ; ;txbhx: ; push rmp ; swap rmp ; rcall txnhx1 ; pop rmp ;txnhx1: ; cbr rmp,0b11110000 ; subi rmp,-'0' ; cpi rmp,'9'+1 ; brcs txnhx2 ; subi rmp,-7 ;txnhx2: ; rcall txch ; ret ; ; Display Register R0..R9 in Hex an SIO ; ;txreg: ; ldi rmp,chcr ; rcall txch ; clr ZH ; clr ZL ;txreg1: ; ld rmp,Z+ ; rcall txbhx ; ldi rmp,' ' ; rcall txch ; cpi ZL,11 ; brcs txreg1 ; ldi rmp,chcr ; rcall txch ; ret ; ; Hauptprogramm-Loop, Restart-Vektor ; start: ldi rmp,HIGH(RAMEND) ; Init stack pointer im SRAM out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,bddv ; Baudrate des UART einstellen out UBRR,rmp ldi rmp,siorxtx ; UART Tx und Rx ein, Ints aus out UCR,rmp cbi pdrr,brts ; Bit Richtung ist RTS-Eingang sbi pdrr,bcts ; Bit Richtung ist CTS-Ausgang sbi pctrl,bcts ; CTS ausschalten (invertiert!) rcall ecopy ; Kopiere EEPROM-Inhalt nach RAM ldi ZH,high(2*txtid) ; Sende ID-Text an SIO ldi ZL,low (2*txtid) rcall txtext clr rfl ; Flag auf Anfangswert leer ; ; Bedingungen für Interrupt-Betrieb herstellen ; start1: rcall calctc ; Rechne Timerwerte und Ints aus rcall txpar ; Gib die eingestellten Werte über SIO ldi ZH,HIGH(2*txtcur) ; Sende Cursor-String ldi ZL,LOW(2*txtcur) rcall txtext ldi rmp,sleepmode ; Mode Idle für Sleep einstellen out MCUCR,rmp ; Aufwachen durch Interrupts ldi rmp,siorxint ; Enable RX mit Interrupt out UCR,rmp cbi pctrl,bcts ; Aktiviere CTS-Leitung sei ; Enable General Interrupt Flag ldi XH,HIGH(srtx) ; Puffer auf Anfang stellen ldi XL,LOW(srtx) rcall starttx ; und Text in CW aussenden ldi YH,HIGH(srte+3) ; Ausgabepointer hinter Ende ldi YL,LOW(srte+3) ; des Puffers stellen ldi XH,high(srtx+1) ; Puffer wieder auf Anfang ldi XL,low(srtx+1) ; und überschreiben des Textes clr rmp ; Null-Terminierung schreiben st -x,rmp ldi rmp,chcr ; Setze leeres Textfeld mit CR st -x,rmp ; Jetzt zeigt X-Zeiger auf Anfang ; ; Interrupt loop, ab jetzt praktisch nur Kreisverkehr ; mit Ausbruch bei gesetzten Flagbits ; loop: sleep ; CPU schlafen schicken nop ; Dummy-Befehl, bleibt bei Schlaf hier stehen nop ; nach dem Aufwachen ausgeführt sbrc rfl,bstx ; Sendeflag von Int-Routine gesetzt? rjmp starttx ; Ja: Starte Sendevorgang sbrc rfl,btxe ; Senden-beenden von Int-Routine? rjmp stoptx ; Beende Sendevorgang sbrc rfl,btxa ; Ist die Sendeausgabe aktiv? rjmp loop ; (Während Aussendung keine Parameter!) sbrc rfl,besc ; Parameter mit Menue holen? rjmp getpar ; Hole Parameter über SIO rjmp loop ; weiter im Kreis herum ; ; Startet Sendeprozess ; starttx: sbrc rfl,btxa ; Nicht neu starten, wenn schon aktiv rjmp loop ; Wieder raus! cbr rfl,bmstx ; Setze Flag bit zurück sbi pctrl,bcts ; Stop CTS-Leitung ldi YH,HIGH(srtx) ; Sende-Pointer auf Pufferanfang ldi YL,LOW(srtx) mov rcth,rikh ; Kurze Verzögerung bis zum Start mov rctl,rikl clr rnsc ; Auszugebendes Zeichen beendet, provoziert ; Laden des nächsten Zeichens bei der Senderoutine sbi pnfd,doc1 ; Ausgabe Pin OC1B=PD5 auf Ausgang (8515) clr rmp ; Setze Timer-Werte out TCCR1A,rmp ; OC1 inaktivieren out TCNT1H,rmp ; Timer-Register auf Null setzen out TCNT1L,rmp out OCR1AH,rcmh ; Compare Match auf Dauer entout OCR1AL,rcml ; sprechend der NF-Frequenz ldi rmp,t1CompInt ; Ermögliche Compare Int out TIMSK,rmp ldi rmp,t1TaktInt ; Clear Timer on Compare ; Match und Prescaler CK/8 out TCCR1B,rmp ; Timer-Takt starten rjmp loop ; und CPU bis zum Timer-Int schlafen legen ; ab jetzt läuft wieder alles automatisch ab ; ; Beendet Sendevorgang ; stoptx: cbr rfl,bmtxe ; Setze Beenden-Flag zurück cbi pctrl,bcts ; CTS wieder einschalten ldi ZH,HIGH(2*txtcur) ; Gib Cursor an SIO aus ldi ZL,LOW(2*txtcur) rcall txtext cpi XL,LOW(srtx) ; Schon Zeichen eingegeben? breq loop ; Nein, schlafen legen bis SIO-RX kommt ldi ZH,HIGH(srtx) ; Gepufferte Zeichen auf Cursorldi ZL,LOW(srtx) ; zeile ausgeben an SIO-TX stoptx1: ld rmp,z+ ; Zeichen aus Puffer lesen rcall txch ; an SIO senden cp ZL,XL ; Schon alle ausgegeben? brcs stoptx1 ; Weiter ausgeben rjmp loop ; Alles ausgegeben, schlafen legen ; ; Getpar holt menuegesteuert Parameter vom User ; getpar: ldi rmp,siorxtx ; Rx-Interrupts abschalten out UCR,rmp ; reiner Polling-Betrieb getpar0: rcall calctc ; Rechne aktuelle Parameter um getpara: rcall txpar ; Gib die Parameter aus getparb: ldi ZH,HIGH(2*txtmenue) ; Gib Menue aus ldi ZL,LOW(2*txtmenue) rcall txtext rcall rxch ; Hole ein Zeichen von SIO cpi rmp,chesc ; Ende-Zeichen ESCAPE? brne getpar1 ; Nein, mach weiter im Menue cbr rfl,bmesc ; Setze Menue-Flag zurück http://www.avr-asm-tutorial.net/cq-dl/teil4/Cw01_200.asm (1 of 2)1/20/2009 7:50:03 PM
rjmp start1 ; Ende, starte fast alles neu getpar1: cpi rmp,'s' ; Speichern gewählt? brne getpar1a rjmp getpars ; geh zum Speichern getpar1a: cpi rmp,'l' ; Lesen gewählt? brne getpar1b rjmp getparl ; geh zum Lesen getpar1b: cpi rmp,'d' ; Default-Werte gewählt? brne getpar1c rjmp getpard ; Setze Default Werte getpar1c: cpi rmp,'x' ; Testtext gewählt? brne getpar1d rjmp getparx getpar1d: ldi ZH,HIGH(2*txtf); Zeiger auf Frequenzzeile ldi ZL,LOW(2*txtf) ; im Menue cpi rmp,'f' ; Frequenzeingabe gewählt? brne getpar1e rjmp getpar2 ; ja, hole Zahl getpar1e: ldi ZH,HIGH(2*txtg); Geschwindigkeitseingabe ldi ZL,LOW(2*txtg) ; Zeiger setzen cpi rmp,'g' ; Geschwindigkeitseingabe gewählt? brne getpar1f rjmp getpar2 ; ja, hole Zahl getpar1f: ldi ZH,HIGH(2*txtt) ; Zeiger auf Texteingabe ldi ZL,LOW(2*txtt) cpi rmp,'t' ; Texteingabe gewählt? brne getpar0 ; Nein, gib dem User noch mal das Menue getpar2: ; Hole eine Zahlen- oder Texteingabe in den Puffer push rmp ; wird noch gebraucht (gewählter Menuepunkt) rcall txch ; Echo char an SIO zurück ldi rmp,chcr ; Mache neue Zeile rcall txch rcall txtext ; Gib den ausgewählten Menuetext aus ldi XH,HIGH(srtx) ; Empfangspuffer auf Anfang ldi XL,LOW(srtx) getpar3: rcall rxch ; Hole char von SIO st x+,rmp ; in Puffer out UDR,rmp ; Echo an SIO cpi rmp,chcr ; Ende der Eingabe? brne getpar3 ; Weiter mit Eingabe clr rmp ; String Nullterminieren st x,rmp pop rmp ; Menuepunkt wieder vom Stapel holen cpi rmp,'t' ; Texteingabe gewählt? breq getpara ; Ja, schon fertig, gib die Parameter aus push rmp ; Wird weiterhin gebraucht (Menuepunkt) rcall lese16 ; ASCII-Zahl im Puffer in binär ; in R1:R0 umwandeln pop rmp ; Menuepunkt wieder herstellen brcs getpare ; Fehler in Zahl, Fehlermeldung ausgeben cpi rmp,'f' ; Frequenz gewählt? brne getparg ; Nein, Geschwindigkeit gewählt! mov rmp,R1 ; Zahl zu groß? cpi rmp,0x10 ; Frequenz > 4095 Hz? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,0x01 ; Frequenz < 256 Hz? brcs getpark ; Fehlermeldung Zahl zu niedrig sts sfrq,R0 ; Zahl ok, übertragen sts sfrq+1,R1 rjmp getpar0 ; Rechne Parameter neu aus und gebe aus getparg: ; Neue Geschwindigkeit eingegeben tst R1 ; Zahl <256? brne getparh ; Nein, Fehlermeldung Zahl zu groß mov rmp,R0 cpi rmp,201 ; Zahl >200? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,10 ; Zahl <10? brcs getpark ; Zahl zu niedrig sts sbpm,R0 ; Zahl ok, übertragen rjmp getpar0 ; Beginne Neu mit Berechnung und Ausgabe getpars: ; Speichern der eingestellten Werte im EEPROM rcall ewrite ; Alles übertragen ldi ZH,HIGH(2*txteepw); Meldung ausgeben ldi ZL,LOW(2*txteepw) rcall txtext rjmp getparb ; Menuepunkte ausgeben und weiter getparl: ; Lesen der Werte aus dem EEPROM rcall ecopy ; Alles ins SRAM übertragen rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpard: ; Default-Werte setzen ldi ZH,HIGH(sfrq) ; Zeiger auf Frequenz ldi ZL,LOW(sfrq) ldi rmp,LOW(cfrq) ; LSB Default-Frequenz setzen st z+,rmp ; in SRRAM-Speicher ldi rmp,HIGH(cfrq) ; MSB Default-Frequenz setzen st z+,rmp ; in SRAM-Speicher ldi rmp,cbpm ; Default-Geschwindigkeit st z+,rmp ; in SRAM-Speicher rcall getdeftext ; Default-text in Speicher rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpare: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtzahl) ; Fehler in Zahl ldi ZL,LOW(2*txtzahl) rcall txtext rjmp getpara getpark: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtklein) ; Zahl zu niedrig ldi ZL,LOW(2*txtklein) rcall txtext rjmp getpara getparh: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txthoch) ; Zahl zu hoch ldi ZL,LOW(2*txthoch) rcall txtext rjmp getpara getparx: ; Test-Text ausgeben (nur in der STK-Version!) ldi XH,HIGH(srtx) ; Zeiger X auf SRAM-Text ldi XL,LOW(srtx) ldi ZH,HIGH(2*tsttext) ; Zeiger Z auf Testtext ldi ZL,LOW(2*tsttext) getparx1: lpm ; Lese Zeichen st x+,R0 ; Zeichen in SRAM-Puffer adiw ZL,1 ; nächstes Zeichen tst R0 ; Zeichen eine Null? brne getparx1 ; weiter mit Zeichen rjmp getpara ; fertig getdeftext: ; Default text in Speicher ab Z ldi rmp,'<' ; Verkehrsanfang, eingefügt 2313=>8515 st z+,rmp ldi rmp,10 ; Testtext in Speicher mov R0,rmp ; in Zähler getdeftext1: ldi rmp,'P' ; Paris st z+,rmp ldi rmp,'A' st z+,rmp ldi rmp,'R' st z+,rmp ldi rmp,'I' st z+,rmp ldi rmp,'S' st z+,rmp ldi rmp,' ' st z+,rmp dec R0 brne getdeftext1 sbiw ZL,1 ; Eins zurück ldi rmp,'>' st z+,rmp ldi rmp,chcr ; Textpuffer mit Carriage Return st z+,rmp clr rmp ; und Nullterminator st z,rmp ; entleeren ret ; eingefügt bis hier 2313=>8515 ; ; Warte auf char von der SIO mit Echo ; rxch: in rmp,USR ; Lese Control register sbrs rmp,RXC ; Character vorhanden? rjmp rxch ; Noch nicht, weiter warten in rmp,UDR ; Lese character aud Datenregister ret ; und zurück ; ; Sende text character in rmp an SIO mit Prüfung ; txch: push rmp ; Rette Zeichen auf Stapel txch1: in rmp,USR ; Senderegister leer? sbrs rmp,UDRE ; Ist das UDRE-Bit gesetzt rjmp txch1 ; Nein, weiter warten pop rmp ; Hole Zeichen vom Stapel out UDR,rmp ; Character senden cpi rmp,chcr ; Nach Carriage Return noch ein brne txch2 ; Linefeed? ldi rmp,chlf ; auch den noch senden! rcall txch ldi rmp,chcr ; und Zeichen wieder herstellen txch2: ret ; ; Morsecode der ASCII-Zeichen 0x20 bis 0x5F ; unteres Byte = Code (0=Punkt, 1=Strich) ; oberes Byte = Anzahl Punkte/Striche ; Bit 7 = 1: Leerzeichen ; morse: ; Zeichen 20 .. 2F .DB 0b11100000,0b10000011 ; Blank .DB 0b01000000,5 ; ! = Warten .DB 0b01001000,6 ; " .DB 0b11011000,5 ; # = ~n .DB 0b01101000,5 ; $ = á, °a .DB 0b01000000,5 ; % = é .DB 0b00000000,0 ; & = nicht benutzt .DB 0b01111000,6 ; ' .DB 0b10110000,5 ; ( .DB 0b10110100,6 ; ) .DB 0b00000000,0 ; * = nicht benutzt .DB 0b00010100,6 ; + = Spruchende .DB 0b11001100,6 ; , .DB 0b10000100,6 ; .DB 0b01010100,6 ; . .DB 0b10010000,5 ; / ;Zeichen 30 .. 3F .DB 0b11111000,5 ; 0 .DB 0b01111000,5 ; 1 .DB 0b00111000,5 ; 2 .DB 0b00011000,5 ; 3 .DB 0b00001000,5 ; 4 .DB 0b00000000,5 ; 5 .DB 0b10000000,5 ; 6 .DB 0b11000000,5 ; 7 .DB 0b11100000,5 ; 8 .DB 0b11110000,5 ; 9 .DB 0b11100000,6 ; : .DB 0b10101000,6 ; ; .DB 0b10101000,5 ; < = Verkehrsanfang .DB 0b10001000,5 ; = .DB 0b01010000,5 ; > = Verkehrsende .DB 0b00110000,6 ; ? ;Zeichen 40 .. 4F .DB 0b11110000,4 ; @ = ch .DB 0b01000000,2 ; A .DB 0b10000000,4 ; B .DB 0b10100000,4 ; C .DB 0b10000000,3 ; D .DB 0b00000000,1 ; E .DB 0b00100000,4 ; F .DB 0b11000000,3 ; G .DB 0b00000000,4 ; H .DB 0b00000000,2 ; I .DB 0b01110000,4 ; J .DB 0b10100000,3 ; K .DB 0b01000000,4 ; L .DB 0b11000000,2 ; M .DB 0b10000000,2 ; N .DB 0b11100000,3 ; O ;Zeichen 50 .. 5F .DB 0b01100000,4 ; P .DB 0b11010000,4 ; Q .DB 0b01000000,3 ; R .DB 0b00000000,3 ; S .DB 0b10000000,1 ; T .DB 0b00100000,3 ; U .DB 0b00010000,4 ; V .DB 0b01100000,3 ; W .DB 0b10010000,4 ; X .DB 0b10110000,4 ; Y .DB 0b11000000,4 ; Z .DB 0b01010000,4 ; [ = Ä .DB 0b11100000,4 ; \ = Ö .DB 0b00110000,4 ; ] = Ü .DB 0b00000000,8 ; ^ = Irrung .DB 0b00110100,6 ; _ morseende: .DW morseende-morse ; Prüfzahl, muss 0x0040 sein ; ; Testtext, nur in der STK-Version implementiert! tsttext: .DB "",chcr,cnul ChkT: .DW ChkT ; ; Texte für die serielle Schnittstelle ; Hinweis: Die chk-Werte sind zum Überprüfen im Listing ; eingefügt. Es gibt einen Bug im AVR-Assembler von ATMEL, ; der zu falschen Adressen führt, wenn bestimmte Kombina; tionen von Byte-Konstanten verwendet werden. Dieser Bug ; ist seit zwei Jahren gemeldet und noch immer nicht besei; tigt! Teilweise sind die abenteuerlichen Konstruktionen ; in dieser Liste zur Umgehung dieses Bugs verwendet. ; ; Eingangsmeldung zu Beginn txtid: .DB chff,chcr .DB "+---------------------------------+",chcr .DB "| Morseprogramm (C)2002 by DG4FAC |",chcr .DB "+---------------------------------+",chcr .DB cnul,cnul chk1: .DW chk1 ; für Assembler Bug ; Text für Parameterliste txtpar1: .DB chcr,'E',"ingestellte Parameter:",chcr,'*' txtf: .DB "NF-Frequenz (256..4095 Hz) = ",cnul chk2: ; Bug-Check .DW chk2 txtpar2: .DB " Hz ",chcr,'*' txtg: .DB "Geschwindigkeit (10..200 BpM) = ",cnul chk3: ; Bug-Check .DW chk3 txtpar3: .DB " BpM",chcr,' ',"ccm = ",cnul,cnul chk4: ; Bug-Check .DW chk4 txtpar4: .DB ", Ints (kurz) = ",cnul,cnul chk5: ; Bug-Check .DW chk5 txtpar5: .DB ", Ints (lang) = ",cnul,cnul chk6: ; Bug-Check .DW chk6 txtpar6: .DB chcr,'*' txtt: .DB "Text = ",cnul chk7: ; Bug-Check .DW chk7 txtcur: .DB chcr,'=','>',cnul chk8: ; Bug-Check .DW chk8 txtmenue: .DB "Einstellungen: f=NF-Frequenz, g=Geschwindigkeit," .DB " t=Text, s=Speichern,",chcr .DB " l=Lesen, d=Default, x=Test, ESC=Ende! ",chcr .DB "(f,g,t,s,l,d,x,ESC) => ",cnul chk9: ; Bug-Check .DW chk9 ; Prüfzahl für Assembler-Bug txtzahl: .DB chcr,'F',"ehler in Ziffer oder Zahl bzw. Zahl zu gross!",cnul chk10: ; Bug-Check .DW chk10 ; Prüfzahl für Assembler-Bug txtklein: .DB chcr,'*',"* Zahl zu niedrig! **",cnul,cnul chk11: ; Bug-Check .DW chk11 ; Prüfzahl für Assembler-Bug txthoch: .DB chcr,'*',"* Zahl zu hoch! **",cnul,cnul chk12: ; Bug-Check .DW chk12 ; Prüfzahl für Assembler-Bug txteepw: .DB chcr,'P',"arameter ins EEPROM geschrieben.",chcr,cnul chk13: ; Bug-Check .DW chk13 ; ; Copyright-Info .DB "C(2)00 2ybD 4GAF C" ; ; Programm-Code Ende ; ; ****************************************** ; * EEPROM-Inhalt mit Default beginnt hier * ; ****************************************** ; .ESEG .ORG 0x0000 ; efrq: ; Die Default-NF-Frequenz .DW cfrq ebpm: ; Die Default-Geschwindigkeit in BpM .DB cbpm etxt: ; Dieser Text wird zu Beginn ausgegeben .DB "hello!" .DB chcr,cnul etxte: ; ; Copyright-Info ; .DB "(C)2002 by DG4FAC" ; ; Ende des EEPROM-Segmentes ;
http://www.avr-asm-tutorial.net/cq-dl/teil4/Cw01_200.asm (2 of 2)1/20/2009 7:50:03 PM
cq-dl-Beiträge zu AVR-Mikrocontrollern
Pfad: Home => cq-dl-Beiträge => Teil 4 => Cw01_500.asm
cq-dl-Beiträge zu ATMEL-AVRMikrocontrollern Assembler-Quellcode des CW-Programmes ; ********************************************************** ; * CW.asm gibt Morsezeichen aus, die im EEPROM-Speicher * ; * gespeichert sind und die über die serielle Schnitt* ; * stelle des STK500-Boards eingegeben werden. Die Aus* ; * gabe der NF erfolgt an PD5. Baudrate 9k6. 3,96 MHz * ; * AVR AT90S8515. Programm (C)2002 by DG4FAC G.Schmidt * ; * Version 0.1-500 vom 12.1.2002 * ; * Bugs und Dankschreiben an info!at!avr-asm-tutorial.net * ; ********************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Benutzte Register ; ; Register R0 wird für Lesen im Programmspeicher benutzt ; Register R0..R9 werden für Berechnungen benutzt ; .DEF rcml = R10 ; Compare Match-Zahl, LSB .DEF rcmh = R11 ; dto., MSB .DEF rikl = R12 ; Interrupt-Anzahl kurz, LSB .DEF rikh = R13 ; dto., MSB .DEF rill = R14 ; Interrupt-Anzahl lang, LSB .DEF rilh = R15 ; dto., MSB .DEF rmp = R16 ; Multipurpose register, nicht bei Ints .DEF rim = R17 ; Interrupt multi-purpose .DEF rfl = R18 ; Flag Register, bei Ints und Normal .DEF rst = R19 ; Statusregister bei Interrupts .DEF rnsc = R20 ; Anzahl Punkte/Striche beim Senden .DEF rmcd = R21 ; Morsecode beim Senden .DEF x22 = R22 ; unbenutzt .DEF x23 = R23 ; unbenutzt .DEF rctl = R24 ; Zähler für Interrupt Tonerzeugung .DEF rcth = R25 ; (wird als Doppelregister verwendet) ; ; Register XH:XL R27:R26 Zeiger in den Empfangspuffer ; Register YH:YL R29:R28 Zeiger beim Senden ; Register ZH:ZL R31:R30 Zeiger für Lesen im Programmspeicher ; ; Bits im Flagregister ; .EQU besc = 7 ; ESCape-Sequenz, hole Parameter .EQU bmesc = 0x80 ; Maskenbyte für ESC .EQU bstx = 6 ; Starte Sendeprozess .EQU bmstx = 0x40 ; Maskenbyte für bstx .EQU bctx = 5 ; Beende Sendeprozess nach Leerzeichen .EQU bmctx = 0x20 ; Maskenbyte für bctx .EQU btxa = 4 ; Sendeprozess aktiv .EQU bmtxa = 0x10 ; Maskenbyte für btxc .EQU btxe = 3 ; Abschluss des Sendens .EQU bmtxe = 0x08 ; Maskenbyte für btxe .EQU bpl = 2 ; Lange Pause zur Buchstabentrennung .EQU bmpl = 0x04 ; Maskenbyte für bpl .EQU bpk = 1 ; Kurze Pause zur Punkt/Strich-Trennung .EQU bmpk = 0x02 ; Maskenbyte für bpk .EQU bquiet = 0 ; Dauerhaft inaktiviert bei Leerzeichen .EQU bmquiet = 0x01 ; Maskenbyte für bquiet ; ; Default-Werte ; .EQU cfrq=1000 ; NF-Frequenz .EQU cbpm=60 ; Gebegeschwindigkeit ; ; Basisdefinitionen variabel nach Schaltung ; .EQU fqu = 3960000 ; Quarzfrequenz des AVR .EQU fbd = 9600 ; Baudrate des UART .EQU pctrl = PORTB ; Control-Port für RTS/CTS (nicht benutzt) .EQU pdrr = DDRB ; Datenrichtungsregister Controlport .EQU brts = PB2 ; RTS bit Input (beim STK nicht benutzt) .EQU bcts = PB4 ; CTS bit Output (beim STK nicht benutzt) .EQU pnfd = DDRD ; Port für Richtung NF-Ausgabe (8515) .EQU doc1 = PD5 ; NF-Ausgabe über OC1B-Pin (8515) ; ; Umrechnungen in Timer- und Counterwerte ; .EQU nps = 8 ; Prescaler-Einstellung von Timer 1 .EQU ccm = fqu/nps/2; Konstante für Compare Match .EQU cint =2941 ; Konstante für Int-Berechnung, ; experimentell ermittelt ; ; Definitionen fix ; .EQU bddv = (fqu/(16*fbd))-1 ; Baudraten-Teiler .EQU cnul = 0x00 ; Ende für nullterminierte Strings .EQU chbsp = 0x08 ; Backspace character .EQU chcr = 0x0D ; Carriage Return character .EQU chlf = 0x0A ; Line Feed character .EQU chff = 0x0C ; Form Feed character .EQU chesc= 0x1B ; ESCape-Zeichen ; ; Definitionen I/O ; ; Definitionen für Timer-Controlregister TCCR1A .EQU t1aus = 0b10000000 ; Schaltet den NF-Ausgang aus .EQU t1ein = 0b01000000 ; Schaltet den NF-Ausgang ein ; Definition für Timer-Interrupt-Mask-Register TIMSK .EQU t1CompInt=0b01000000 ; Interrupt bei Compare Match ; Definition für Timer-Controlregister TCCR1B .EQU t1TaktInt=0b00001010 ; Timer-Takt CLK / 8, Clear Comp ; Definitionen für UART-Betrieb in UCR .EQU siorxtx = 0b00011000 ; Betrieb RX und TX ohne Int .EQU siorxint= 0b10011000 ; Betrieb RX mit Int, TX ohne Int ; Definition für den SLEEP-Mode in MCUCR .EQU sleepmode=0b00100000 ; Aufwachen bei Interrupt ; ; Positionen im SRAM ; .EQU sfrq = 0x0060 ; Eingestellte NF-Frequenz, Wort .EQU sbpm = 0x0062 ; Eingestellte Geschwindigkeit, Byte .EQU srtx = 0x0063 ; RAM-Puffer für Ein-/Ausgabezeile .EQU srte = 0x00B3 ; Ende des nutzbaren Puffers, benutzt werden ; auch zwei weitere Folgebytes ; ; Programm beginnt hier ; .CSEG .ORG 0x0000 ; ; Reset- und Interrupt-Sprungtabelle 8515 ; rjmp start ; Reset-Vector reti ; Ext Int 0, nicht benutzt reti ; Ext Int 1, nicht benutzt reti ; Timer 1 Capture Event, nicht benutzt rjmp itc1m ; Timer 1 Compare Match A reti ; Timer 1 Compare Match B reti ; Timer 1 Overflow, nicht benutzt reti ; Timer 0 Overflow, nicht benutzt reti ; SPI STC, nicht benutzt rjmp iurxc ; UART Rx Complete reti ; UART Tx data register empty, nicht benutzt reti ; UART Tx All sent, nicht benutzt reti ; ANA_COMP, nicht benutzt ; ; Interrupt-Service-Routinen ; itc1m: ; Timer 1 Compare Match Interrupt in rst,SREG ; Rette Statusregister sbiw rctl,1 ; Zähler eins abzählen brne itc1mz ; Raus, wenn nicht Null mov rcth,rikh ; Setze Zähler auf kurze Dauer mov rctl,rikl ldi rim,t1aus ; Ton auf aus tst rnsc ; Zeichen fertig gesendet? breq itc1m0 ; Verzweige, wenn Zeichen fertig sbrc rfl,bpk ; kurze Pausen-Flag? rjmp itc1ms ; Pause zwischen Punkt/Strich war schon sbr rfl,bmpk ; Setze kurze Pause-Flag rjmp itc1my ; und Ausgabe auf inaktiv, fertig itc1ms: ; Sende nächsten Punkt/Strich cbr rfl,bmpk ; Lösche kurze Pause-Flag ldi rim,t1ein; Ton an = Toggle dec rnsc ; Weiteren Punkt/Strich gesendet rol rmcd ; Punkt oder Strich senden? brcc itc1my ; Punkt senden mov rcth,rilh ; Lange Dauer einstellen mov rctl,rill rjmp itc1my itc1m0: ; Zeichen fertig gesendet sbrc rfl,bctx ; Sendung beenden? rjmp itc1mx sbrc rfl,bpl ; Lange Pause senden? rjmp itc1mn ; Nächsten Buchstaben beginnen sbr rfl,bmpl ; Setze langes Pausen-Flag mov rcth,rilh ; Dauer auf lang stellen mov rctl,rill itc1my: ; Stelle Modus inaktiv/toggle ein sbrc rfl,bquiet ; bei Leerzeichen Ton aus ldi rim,t1aus ; Ton auf aus out TCCR1A,rim itc1mz: out SREG,rst ; Stelle Status wieder her reti itc1mn: cbr rfl,bmpl ; Langes Pausen-Flag aus ld rim,y+ ; Nächsten Buchstaben lesen tst rim ; Null-Terminator breq itc1mn1 cpi rim,chcr ; Ende der Zeile? brne itc1mn2 itc1mn1: sbr rfl,bmctx ; Setze beenden-Flag ldi rim,' ' ; Sende noch ein Leerzeichen itc1mn2: out UDR,rim ; Debug subi rim,0x20 ; ASCII-Control-Zeichen weg brcc itc1mn3 ldi rim,'?'-0x20 ; Sende Fragezeichen itc1mn3: cpi rim,0x40 ; Kleinbuchstabe? brcs itc1mn4 subi rim,0x20 ; in Grossbuchstaben wandeln cpi rim,0x40 brcs itc1mn4 ldi rim,'?'-0x20 itc1mn4: add rim,rim ; Mal 2 für Tabelle push ZH ; Register retten push ZL push R0 ldi ZH,HIGH(2*morse) ; Zeichentabelle laden ldi ZL,LOW(2*morse) add ZL,rim ; Zeichen dazu zählen brcc itc1mn5 ; Kein Übertrag? inc ZH ; Übertrag itc1mn5: lpm ; Lese Zeichencode aus Tabelle mov rmcd,R0 ; in Zeichencode-Register adiw ZL,1 ; Zeiger auf nächstes Byte lpm ; aus Tabelle lesen mov rnsc,R0 ; Anzahl Striche/Punkte pop R0 ; Wieder herstellen der Register pop ZL pop ZH tst rnsc ; Undefiniertes Zeichen? breq itc1mn ; Überspringe Zeichen sbr rfl,bmquiet ; Leerzeichen sbrs rnsc,7 ; Leerzeichen? cbr rfl,bmquiet ; Kein Leerzeichen cbr rnsc,0x80 ; Lösche höchstes Bit mov rim,YL ; CTS einschalten? sub rim,XL brcs itc1mn6 ; Ausschalten cpi rim,3 brcs itc1mn6 cbi pctrl,bcts ; CTS einschalten rjmp itc1ms itc1mn6: sbi pctrl,bcts ; CTS ausschalten rjmp itc1ms ; Sende ein Zeichen itc1mx: ; Sendung einstellen clr rim ; Timer 1 abschalten out TCCR1B,rim ; Timer-Takt aus out TCNT1H,rim ; Timer auf Null stellen out TCNT1L,rim out TIMSK,rim ; Timer Interrupts aus out TCCR1A,rim ; Timer Compare Mode aus cbr rfl,bmctx+bmtxa ; Ende-Flag und aktiv ausschalten sbr rfl,bmtxe ; Beenden-Flag einschalten ldi YH,HIGH(srte) ; Buffer auf Ende ldi YL,LOW(srte) st Y,rim ; Null-terminieren rjmp itc1mz ; ; UART Rx Complete Interrupt ; iurxc: in rst,SREG ; Rette Statusregister in rim,UDR ; Lese Zeichen von SIO cpi rim,chesc ; ESCape-Sequenz? brne iurx1 sbr rfl,bmesc ; Setze ESCape-Bit rjmp iurxz iurx1: cpi rim,chbsp ; Backspace-Char? brne iurx2 cpi XL,LOW(srtx) ; Schon auf Anfang? breq iurxz sbiw XL,1 ; Eine Position zurück ldi rim,chcr ; Zeilenabschluss st x+,rim clr rim ; Null-terminieren st x,rim sbiw XL,1 ; zurück ldi rim,chbsp ; Backspace zurücksenden rjmp iurxe ; Echo character iurx2: cpi XL,low(srte) ; Pufferüberlauf? brcs iurx3 ; Nein, weiter ldi rim,chcr ; Character überschreiben iurx3: cpi rim,chcr ; Zeilenende? brne iurxw ; Nein, in Puffer und fertig sbrs rfl,btxa ; Überspringe Ausgabe wenn aktiv out UDR,rim ; Carriage Return schon mal ausgeben st x+,rim ; CR-Zeichen anhängen clr rim ; Null terminieren st x,rim ldi XH,HIGH(srtx) ; Puffer auf Anfang ldi XL,LOW(srtx) sbr rfl,bmstx ; Über Flag Sender starten ldi rim,chlf ; Sende Zeilenvorschub rjmp iurxe ; Echo Line-Feed iurxw: st x+,rim ; Speichere character ldi rim,chcr ; Abschluss terminieren st x+,rim ; mit Carriage return clr rim ; Null-terminieren st x,rim sbrs rfl,btxa ; Sender aktiv? rjmp iurxs ; Nein, CTS nicht prüfen mov rim,YL ; CTS ein oder aus? sub rim,XL ; Distanz zwischen Ein- und Ausgabe brcs iurxo ; Eingabe > Ausgabe: CTS aus cpi rim,3 ; mindestens zwei Zeichen Vorlauf? brcs iurxo ; Nein, dann CTS ausschalten cbi pctrl,bcts ; CTS einschalten rjmp iurxs ; Zeichen herstellen und raus iurxo: sbi pctrl,bcts ; CTS ausschalten iurxs: sbiw XL,2 ; Char wieder herstellen ld rim,x+ ; durch Lesen im SRAM iurxe: sbrs rfl,btxa ; Keine Ausgabe, wenn aktiv out UDR,rim ; Echo character an SIO zurück iurxz: out SREG,rst ; Stelle Status wieder her reti ; Ende des Interrupts ; ; Diverse eigenständige Unterprogramme ; ; Kopiert den EEPROM-Inhalt in das SRAM ; ecopy: sbic EECR,EEWE ; Warte, bis EEPROM bereit rjmp ecopy ; Noch nicht bereit clr YH ; Startadresse im EEPROM auf Null clr YL ldi ZH,HIGH(sfrq) ; Startadresse im SRAM ldi ZL,LOW(sfrq) ; auf ersten Parameter ecopy1: out EEARH,YH ; Leseadresse im EEPROM out EEARL,YL ; 2313 hat nur 128 Bytes, daher nur unteres Register sbi EECR,EERE ; Lese-Strobe setzen cbi EECR,EERE ; und wieder ausschalten in rmp,EEDR ; Byte aus EEPROM-Datenregister lesen st Z+,rmp ; und in SRAM schreiben adiw YL,1 ; nächste EEPROM-Adresse anwählen tst rmp ; Null-Terminator? brne ecopy1 ; Nein: weiter kopieren ret ; ; Schreibe Parameter in das EEPROM zurück ; ewrite: ldi ZH,HIGH(sfrq) ; Zeiger auf SRAM ldi ZL,LOW(sfrq) clr XH ; Zeiger in das EEPROM clr XL ewrite1: sbic EECR,EEWE ; Frage Write-Bit im EEPROM rjmp ewrite1 ; ab und wiederhole bis EEPROM ready out EEARH,XH ; Schreibadresse im EEPROM einstellen out EEARL,XL ld rmp,Z+ ; Lese Byte aus SRAM in Register out EEDR,rmp ; in das EEPROM-Datenregister cli ; Keine Interrupts mehr beim Schreibvorgang sbi EECR,EEMWE ; Setze EEPROM Master Write Enable sbi EECR,EEWE ; Löse Schreibvorgang im EEPROM aus sei ; Jetzt Interrupts wieder zulassen adiw XL,1 ; Nächste EEPROM-Adresse in X-Zeiger cpi XH,$02 ; Ende EEPROM erreicht? brcc ewrite2 ; Überlänge! Null fehlt! Abbrechen! tst rmp ; Nullterminiert? brne ewrite1 ; Noch weitere Bytes schreiben ewrite2: ret ; ; Lese16 wandelt eine ASCII-Zahl im Puffer in binär ; in R1:R0, bei Fehler Rückkehr mit gesetztem Carry-Bit ; lese16: clr R0 ; Leeren Resultat-Register R1:R0 clr R1 ldi ZH,HIGH(srtx) ; Zeige auf Pufferanfang ldi ZL,LOW(srtx) lese16a: ld rmp,Z+ ; Lese ASCII-Ziffer aus SRAM cpi rmp,chcr ; Ende der Zahl mit Carriage Return? breq lese16ok ; Zahl mit Carriage Return beendet cpi rmp,cnul ; Ende der Zahl mit Nullterminiert? breq lese16ok ; Ende mit Nullterminierung cpi rmp,'9'+1 ; Ziffer > ASCII-9? brcc lese16no ; Ja, Fehler! cpi rmp,'0' ; Ziffer < ASCII-0 brcs lese16no ; Ja, auch Fehler subi rmp,'0' ; Wandle Ziffer in binär ; Ab hier wird das bisherige Resultat mit 10 multi; pliziert mov R2,R0 ; Kopiere Binärzahl in Hilfsregister R3:R2 mov R3,R1 add R0,R0 ; Multipliziere mit 2 durch Addieren 16 Bit adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R2 ; Addiere die Kopie der Zahl adc R1,R3 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! ; Hier ist das Multiplizieren mit 10 beendet. add R0,rmp ; Addiere Ziffer zum Resultat hinzu brcc lese16a ; Kein Überlauf des unteren Bytes inc R1 ; Überlauf, oberes Byte erhöhen brne lese16a ; Kein Überlauf des oberen Bytes lese16no: sec ; Setze Carry-Bit bei Fehler und kehre zurück ret lese16ok: clc ; Clear Carry bei fehlerfrei ret ; ; Wandle 16-Bit-Zahl in R1:R0 in ASCII in R4..R9 (nullterm.) ; unterdrücke führende Nullen, sende Ergebnis ohne führende ; Nullen über die SIO ; b16asc: clr ZH ; Z zeigt auf höchste ASCII-Ziffer in ldi ZL,4 ; Register R4 (Zeiger in Register!) ldi rmp,HIGH(10000) ; Beginne mit Zehntausendern mov R3,rmp ; oberes Byte in R3 ldi rmp,LOW(10000) mov R2,rmp ; unteres Byte in R2 rcall b16sub ; Ermittle ASCII-code der Zehntausender ; Stelle durch fortgesetztes Subtrahieren von 10000 ldi rmp,HIGH(1000) ; Weiter mit Tausendern mov R3,rmp ldi rmp,LOW(1000) mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Tausender clr R3 ; Weiter mit Hunderten ldi rmp,100 mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Hunderter ldi rmp,10 ; Weiter mit Zehnern mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Zehner ldi rmp,'0' ; Rest sind Einer add rmp,R0 mov R8,rmp ; R8 kriegt die ASCII-Einer clr R9 ; Nullterminieren in R9 ldi ZL,4 ; Z auf 10000-er Zeichen b16asc1: cpi ZL,9 ; Ende der Zeichenkette erreicht? breq b16asc2 ; Ja, raus ld rmp,z+ ; Zeichen aus Register kopieren cpi rmp,'0' ; Führende Null? breq b16asc1 ; Ja, weitermachen b16asc2: dec ZL ; auf vorheriges Zeichen setzen ; ; Sende nullterminierten Text aus SRAM an SIO ; Z zeigt nicht ins SRAM, sondern in die Register mit ; dem Ergebnis! ; Das Registerpaar Z zeigt auf das erste Zeichen ; txstxt: ld rmp,z+ ; Lese Zeichen aus SRAM/Register tst rmp ; Nullterminator erreicht? breq txstxt1 ; Ja, raus und fertig rcall txch ; Sende character über SIO rjmp txstxt ; Weitermachen mit nächstem Zeichen txstxt1: ret ; ; Ermittle ASCII-Code einer Ziffer der 16-Bit-Binärzahl in ; R1:R0 durch fortgesetztes Subtrahieren einer 16-Bit; Hilfszahl (10000, 1000, 100, 10) in R3:R2. Tritt ein ; Überlauf ab, dann ist die Ziffer gefunden ; (Unterroutine für b16asc) ; b16sub: ldi rmp,'0' ; Setze ASCII-Null b16sub1: sub R0,R2 ; Subtrahiere die Hilfszahl sbc R1,R3 ; in 16-bit brcs b16sub2 ; Ende subtrahieren erreicht? inc rmp ; Einer geht noch! rjmp b16sub1 ; Weiter mit subtrahieren! b16sub2: add R0,R2 ; Zu weit, addiere wieder zurück adc R1,R3 st Z+,rmp ; ASCII-Ziffer in Register schreiben ret ; und zurück ; ; Sende nullterminierten Text aus dem Programmspeicher ; über die SIO aus ; txtext: lpm ; Lese Zeichen aus Programmspeicher mit Zeiger Z tst R0 ; Ergebnis im Register R0 hat Null erreicht? breq txtend ; Ja, raus aus der Routine mov rmp,R0 ; Kopiere Zeichen in Senderegister rcall txch ; Sende character mit Prüfung adiw zl,1 ; Zeiger auf nächstes Zeichen rjmp txtext ; Weitermachen bis Null txtend: ret ; Kehre zurück ; ; Liste alle Parameter über die SIO-Schnittstelle auf ; txpar: ldi ZH,HIGH(2*txtpar1) ; Sende Parametervorspann ldi ZL,LOW(2*txtpar1) ; für NF-Frequenz rcall txtext lds R0,sfrq ; Zeige eingestellte Frequenz an lds R1,sfrq+1 rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar2) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar2) ; Geschwindigkeit an rcall txtext lds R0,sbpm ; Zeige Geschwindigkeit an clr R1 ; nur 8 Bit! rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar3) ; Zeige Vorspann für Counterldi ZL,LOW(2*txtpar3) ; Compare-Match-Zahl ccm an rcall txtext mov R1,rcmh ; Compare-Match-Zahl in R1:R0 mov R0,rcml rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar4) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar4) ; Anzahl NF-Ints bei Punkt an rcall txtext mov R1,rikh ; Anzahl Ints bei Punkt in R1:R0 mov R0,rikl rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar5) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar5) ; Anzahl NF-Ints bei Strich an rcall txtext mov R1,rilh ; Anzahl Ints bei Strich in R1:R0 mov R0,rill rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar6) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar6) ; Ausgabetext an rcall txtext ldi ZH,HIGH(srtx) ; Zeiger Z auf Ausgabetext im SRAM ldi ZL,LOW(srtx) rjmp txstxt ; Sende Inhalt SRAM nullterminiert ; ; 32-Bit durch 16-Bit-Division ; ; Dividiert 32-Bit-Zahl in R3:R2:R1:R0 durch R5:R4 ; Ergebnis in R7:R6 (Ergebnis darf nicht > 16 Bit sein!) ; div32: clr R7 ; Clear Ergebnis-Register R7:R6 clr R6 inc R6 ; Stopbit setzen für 16 Divisionsschritte div32a: clc ; Null in Carry schieben rol R0 ; Multipliziere Divident mit 2 rol R1 rol R2 rol R3 brcs div32e ; Carry ist herausgerollt? Dann 1! cp R3,R5 ; Vergleiche oberes Byte brcs div32n ; Ergebnis ist kleiner, also eine 0 brne div32e ; Ergebnis ist größer, also eine 1 cp R2,R4 ; Vergleich MSB gleich, vergleiche unteres ; Byte brcs div32n ; Unteres Byte kleiner, also eine 0 div32e: sub R2,R4 ; Ziehe den Divisor von den oberen 16 Bit ab sbc R3,R5 sec ; Setze das Carry-Bit, Ergebnis ist eine 1 rjmp div32s ; Zum Reinschieben in das Ergebnis div32n: clc ; Lösche Carry-Bit, Ergebnis ist eine 0 div32s: rol R6 ; Schiebe Carry-Bit von rechts in Ergebnis rol R7 brcc div32a ; Ende, wenn eine 1 links herausrollt ret ; ; Multipliziert 16-Bit-Zahl in R1:R0 mit 16-Bit-Zahl in R5:R4 ; Ergebnis 32 Bit in R9:R8:R7:R6, für jeden Zahlenbereich ; Mul16: clr R3 ; Leere obere zwei Bytes der 16-Bit-Zahl clr R2 clr R9 ; Leere Ergebnis-Register R9:R8:R7:R6 clr R8 clr R7 clr R6 Mul16a: clc ; Null ins Carry-Bit ror R5 ; Schiebe unterstes Bit Divisor in Carry ror R4 ; und dividiere Multiplikant durch 2 brcc Mul16b ; Bit war eine 0, Addition überspringen add R6,R0 ; addiere Multiplikator 32 Bit zum adc R7,R1 ; bisherigen Ergebnis, jeweils mit adc R8,R2 ; Überlauf adc R9,R3 Mul16b: tst R4 ; Schon alle Bits ausmultipliziert? brne Mul16c ; Nein, LSB nicht Null, weiter tst R5 ; Teste auch oberes Byte brne Mul16c ; Nein, MSB nicht Null, weiter ret ; Fertig Mul16c: clc ; Null in Carry-Bit schieben rol R0 ; Zahl durch Linksschieben mit 2 rol R1 ; multiplizieren rol R2 rol R3 rjmp Mul16a ; und weiter dividieren ; ; Dividiert 32 Bit-Zahl in R3:R2:R1:R0 durch eine ; 8-Bit-Zahl in R4 und durch 256, Ergebnis gerundet ; in R8:R7, Wertebereich beachten! ; Div32_8: clr R8 ; Ergebnisspeicher R8:R7:R6 leeren clr R7 clr R6 inc R6 ; Stopbit nach 24 Schritten setzen Div32_8a: clc ; Carry-Bit leeren rol R0 ; Divident mit 2 multiplizieren rol R1 ; durch Linksschieben rol R2 rol R3 brcs Div32_8e ; 1 herausgerollt, Ergebnis=1 cp R3,R4 ; Vergleiche oberstes Byte mit Divisor brcs Div32_8n ; Kleiner, Ergebnis = 0 Div32_8e: sub R3,R4 ; Ziehe Divisor von oberstem Byte ab sec ; Setze Carry für Ergebnis = 1 rjmp Div32_8b ; Ins Ergebnis schieben Div32_8n: clc ; Clear Carry für Ergebnis = 0 Div32_8b: rol R6 ; Ergebnis-Bit von rechts her hineinrotieren rol R7 rol R8 brcc Div32_8a ; Weiter, wenn eine Null herausrollt rol R6 ; Binäre Kommastelle eine 1, aufrunden? brcc Div32_8z ; Nein, wenn Null inc R7 ; Aufrunden LSB Ergebnis brne Div32_8z ; Kein Überlauf bei LSB-Erhöhung inc R8 ; Aufrunden MSB Div32_8z: ret ; ; Rechne Parameter um in Timer- und Zählerwerte ; Oben definiert: ccm = Taktfrequenz / Prescale 8 / 2 ; cint = Konstante ; Compare-Match-Zahl für Timer 1 = ccm / NF-Frequenz ; Anzahl Ints bei Punkt = cint * NF-Frequenz / Speed /256 ; Anzahl Ints bei Punkt = Anzahl Ints bei Strich * 3 ; calctc: ldi rmp,BYTE4(ccm) ; Taktabhängige Konstante ccm mov R3,rmp ; in R3:R2:R1:R0, bei 4 MHz: 625.000 ldi rmp,BYTE3(ccm) ; oder 0x00098968 mov R2,rmp ldi rmp,BYTE2(ccm) mov R1,rmp ldi rmp,BYTE1(ccm) mov R0,rmp lds R4,sfrq ; durch NF-Frequenz in R5:R4 lds R5,sfrq+1 rcall div32 ; ergibt Compare Match-Zahl 16-Bit ; rcall txreg ; Debug-Code!!! mov rcmh,R7 ; Ergebnis in Speicher rcmh:rcml mov rcml,R6 ldi rmp,HIGH(cint) ; Konstante für Anzahl Ints mov R1,rmp ; bei 4 MHz: 1250 ldi rmp,LOW(cint) mov R0,rmp lds R4,sfrq ; Mal NF-Frequenz in Hz lds R5,sfrq+1 rcall mul16 ; Multplizieren 16 Bit * 16 Bit ; rcall txreg ; Debug code!!! mov R3,R9 ; Ergebnis in R9..R6 nach R3..R0 kopieren mov R2,R8 mov R1,R7 mov R0,R6 lds R4,sbpm ; durch Gebegeschwindigkeit teilen rcall div32_8 ; teilen 32-Bit durch 8-Bit ; rcall txreg ; Debug code!!! mov rikh,R8 ; in Kurzspeicher kopieren mov rikl,R7 mov rilh,R8 ; und in Langspeicher kopieren mov rill,R7 add rill,rill ; Langspeicher mit 2 malnehmen adc rilh,rilh ; durch Addieren add rill,rikl ; und noch einmal dazu zählen adc rilh,rikh ; macht 3 mal so lang ret ; ; Debug code ; ; Display Byte in rmp in Hex an die SIO ; ;txbhx: ; push rmp ; swap rmp ; rcall txnhx1 ; pop rmp ;txnhx1: ; cbr rmp,0b11110000 ; subi rmp,-'0' ; cpi rmp,'9'+1 ; brcs txnhx2 ; subi rmp,-7 ;txnhx2: ; rcall txch ; ret ; ; Display Register R0..R9 in Hex an SIO ; ;txreg: ; ldi rmp,chcr ; rcall txch ; clr ZH ; clr ZL ;txreg1: ; ld rmp,Z+ ; rcall txbhx ; ldi rmp,' ' ; rcall txch ; cpi ZL,11 ; brcs txreg1 ; ldi rmp,chcr ; rcall txch ; ret ; ; Hauptprogramm-Loop, Restart-Vektor ; start: ldi rmp,HIGH(RAMEND) ; Init stack pointer im SRAM out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,bddv ; Baudrate des UART einstellen out UBRR,rmp ldi rmp,siorxtx ; UART Tx und Rx ein, Ints aus out UCR,rmp cbi pdrr,brts ; Bit Richtung ist RTS-Eingang sbi pdrr,bcts ; Bit Richtung ist CTS-Ausgang sbi pctrl,bcts ; CTS ausschalten (invertiert!) rcall ecopy ; Kopiere EEPROM-Inhalt nach RAM ldi ZH,high(2*txtid) ; Sende ID-Text an SIO ldi ZL,low (2*txtid) rcall txtext clr rfl ; Flag auf Anfangswert leer ; ; Bedingungen für Interrupt-Betrieb herstellen ; start1: rcall calctc ; Rechne Timerwerte und Ints aus rcall txpar ; Gib die eingestellten Werte über SIO ldi ZH,HIGH(2*txtcur) ; Sende Cursor-String ldi ZL,LOW(2*txtcur) rcall txtext ldi rmp,sleepmode ; Mode Idle für Sleep einstellen out MCUCR,rmp ; Aufwachen durch Interrupts ldi rmp,siorxint ; Enable RX mit Interrupt out UCR,rmp cbi pctrl,bcts ; Aktiviere CTS-Leitung sei ; Enable General Interrupt Flag ldi XH,HIGH(srtx) ; Puffer auf Anfang stellen ldi XL,LOW(srtx) rcall starttx ; und Text in CW aussenden ldi YH,HIGH(srte+3) ; Ausgabepointer hinter Ende ldi YL,LOW(srte+3) ; des Puffers stellen ldi XH,high(srtx+1) ; Puffer wieder auf Anfang ldi XL,low(srtx+1) ; und überschreiben des Textes clr rmp ; Null-Terminierung schreiben st -x,rmp ldi rmp,chcr ; Setze leeres Textfeld mit CR st -x,rmp ; Jetzt zeigt X-Zeiger auf Anfang ; ; Interrupt loop, ab jetzt praktisch nur Kreisverkehr ; mit Ausbruch bei gesetzten Flagbits ; loop: sleep ; CPU schlafen schicken nop ; Dummy-Befehl, bleibt bei Schlaf hier stehen nop ; nach dem Aufwachen ausgeführt sbrc rfl,bstx ; Sendeflag von Int-Routine gesetzt? rjmp starttx ; Ja: Starte Sendevorgang sbrc rfl,btxe ; Senden-beenden von Int-Routine? rjmp stoptx ; Beende Sendevorgang sbrc rfl,btxa ; Ist die Sendeausgabe aktiv? rjmp loop ; (Während Aussendung keine Parameter!) sbrc rfl,besc ; Parameter mit Menue holen? rjmp getpar ; Hole Parameter über SIO rjmp loop ; weiter im Kreis herum ; ; Startet Sendeprozess ; starttx: sbrc rfl,btxa ; Nicht neu starten, wenn schon aktiv rjmp loop ; Wieder raus! cbr rfl,bmstx ; Setze Flag bit zurück sbi pctrl,bcts ; Stop CTS-Leitung ldi YH,HIGH(srtx) ; Sende-Pointer auf Pufferanfang ldi YL,LOW(srtx) mov rcth,rikh ; Kurze Verzögerung bis zum Start mov rctl,rikl clr rnsc ; Auszugebendes Zeichen beendet, provoziert ; Laden des nächsten Zeichens bei der Senderoutine sbi pnfd,doc1 ; Ausgabe Pin OC1B=PD5 auf Ausgang (8515) clr rmp ; Setze Timer-Werte out TCCR1A,rmp ; OC1 inaktivieren out TCNT1H,rmp ; Timer-Register auf Null setzen out TCNT1L,rmp out OCR1AH,rcmh ; Compare Match auf Dauer entout OCR1AL,rcml ; sprechend der NF-Frequenz ldi rmp,t1CompInt ; Ermögliche Compare Int out TIMSK,rmp ldi rmp,t1TaktInt ; Clear Timer on Compare ; Match und Prescaler CK/8 out TCCR1B,rmp ; Timer-Takt starten rjmp loop ; und CPU bis zum Timer-Int schlafen legen ; ab jetzt läuft wieder alles automatisch ab ; ; Beendet Sendevorgang ; stoptx: cbr rfl,bmtxe ; Setze Beenden-Flag zurück cbi pctrl,bcts ; CTS wieder einschalten ldi ZH,HIGH(2*txtcur) ; Gib Cursor an SIO aus ldi ZL,LOW(2*txtcur) rcall txtext cpi XL,LOW(srtx) ; Schon Zeichen eingegeben? breq loop ; Nein, schlafen legen bis SIO-RX kommt ldi ZH,HIGH(srtx) ; Gepufferte Zeichen auf Cursorldi ZL,LOW(srtx) ; zeile ausgeben an SIO-TX stoptx1: ld rmp,z+ ; Zeichen aus Puffer lesen rcall txch ; an SIO senden cp ZL,XL ; Schon alle ausgegeben? brcs stoptx1 ; Weiter ausgeben rjmp loop ; Alles ausgegeben, schlafen legen ; ; Getpar holt menuegesteuert Parameter vom User ; getpar: ldi rmp,siorxtx ; Rx-Interrupts abschalten out UCR,rmp ; reiner Polling-Betrieb getpar0: rcall calctc ; Rechne aktuelle Parameter um http://www.avr-asm-tutorial.net/cq-dl/teil4/Cw01_500.html (1 of 2)1/20/2009 7:50:11 PM
cq-dl-Beiträge zu AVR-Mikrocontrollern
getpara: rcall txpar ; Gib die Parameter aus getparb: ldi ZH,HIGH(2*txtmenue) ; Gib Menue aus ldi ZL,LOW(2*txtmenue) rcall txtext rcall rxch ; Hole ein Zeichen von SIO cpi rmp,chesc ; Ende-Zeichen ESCAPE? brne getpar1 ; Nein, mach weiter im Menue cbr rfl,bmesc ; Setze Menue-Flag zurück rjmp start1 ; Ende, starte fast alles neu getpar1: cpi rmp,'s' ; Speichern gewählt? brne getpar1a rjmp getpars ; geh zum Speichern getpar1a: cpi rmp,'l' ; Lesen gewählt? brne getpar1b rjmp getparl ; geh zum Lesen getpar1b: cpi rmp,'d' ; Default-Werte gewählt? brne getpar1c rjmp getpard ; Setze Default Werte getpar1c: cpi rmp,'x' ; Testtext gewählt? brne getpar1d rjmp getparx getpar1d: ldi ZH,HIGH(2*txtf); Zeiger auf Frequenzzeile ldi ZL,LOW(2*txtf) ; im Menue cpi rmp,'f' ; Frequenzeingabe gewählt? brne getpar1e rjmp getpar2 ; ja, hole Zahl getpar1e: ldi ZH,HIGH(2*txtg); Geschwindigkeitseingabe ldi ZL,LOW(2*txtg) ; Zeiger setzen cpi rmp,'g' ; Geschwindigkeitseingabe gewählt? brne getpar1f rjmp getpar2 ; ja, hole Zahl getpar1f: ldi ZH,HIGH(2*txtt) ; Zeiger auf Texteingabe ldi ZL,LOW(2*txtt) cpi rmp,'t' ; Texteingabe gewählt? brne getpar0 ; Nein, gib dem User noch mal das Menue getpar2: ; Hole eine Zahlen- oder Texteingabe in den Puffer push rmp ; wird noch gebraucht (gewählter Menuepunkt) rcall txch ; Echo char an SIO zurück ldi rmp,chcr ; Mache neue Zeile rcall txch rcall txtext ; Gib den ausgewählten Menuetext aus ldi XH,HIGH(srtx) ; Empfangspuffer auf Anfang ldi XL,LOW(srtx) getpar3: rcall rxch ; Hole char von SIO st x+,rmp ; in Puffer out UDR,rmp ; Echo an SIO cpi rmp,chcr ; Ende der Eingabe? brne getpar3 ; Weiter mit Eingabe clr rmp ; String Nullterminieren st x,rmp pop rmp ; Menuepunkt wieder vom Stapel holen cpi rmp,'t' ; Texteingabe gewählt? breq getpara ; Ja, schon fertig, gib die Parameter aus push rmp ; Wird weiterhin gebraucht (Menuepunkt) rcall lese16 ; ASCII-Zahl im Puffer in binär ; in R1:R0 umwandeln pop rmp ; Menuepunkt wieder herstellen brcs getpare ; Fehler in Zahl, Fehlermeldung ausgeben cpi rmp,'f' ; Frequenz gewählt? brne getparg ; Nein, Geschwindigkeit gewählt! mov rmp,R1 ; Zahl zu groß? cpi rmp,0x10 ; Frequenz > 4095 Hz? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,0x01 ; Frequenz < 256 Hz? brcs getpark ; Fehlermeldung Zahl zu niedrig sts sfrq,R0 ; Zahl ok, übertragen sts sfrq+1,R1 rjmp getpar0 ; Rechne Parameter neu aus und gebe aus getparg: ; Neue Geschwindigkeit eingegeben tst R1 ; Zahl <256? brne getparh ; Nein, Fehlermeldung Zahl zu groß mov rmp,R0 cpi rmp,201 ; Zahl >200? brcc getparh ; Fehlermeldung Zahl zu groß cpi rmp,10 ; Zahl <10? brcs getpark ; Zahl zu niedrig sts sbpm,R0 ; Zahl ok, übertragen rjmp getpar0 ; Beginne Neu mit Berechnung und Ausgabe getpars: ; Speichern der eingestellten Werte im EEPROM rcall ewrite ; Alles übertragen ldi ZH,HIGH(2*txteepw); Meldung ausgeben ldi ZL,LOW(2*txteepw) rcall txtext rjmp getparb ; Menuepunkte ausgeben und weiter getparl: ; Lesen der Werte aus dem EEPROM rcall ecopy ; Alles ins SRAM übertragen rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpard: ; Default-Werte setzen ldi ZH,HIGH(sfrq) ; Zeiger auf Frequenz ldi ZL,LOW(sfrq) ldi rmp,LOW(cfrq) ; LSB Default-Frequenz setzen st z+,rmp ; in SRRAM-Speicher ldi rmp,HIGH(cfrq) ; MSB Default-Frequenz setzen st z+,rmp ; in SRAM-Speicher ldi rmp,cbpm ; Default-Geschwindigkeit st z+,rmp ; in SRAM-Speicher rcall getdeftext ; Default-text in Speicher rjmp getpar0 ; Alle Parameter neu berechnen und weiter getpare: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtzahl) ; Fehler in Zahl ldi ZL,LOW(2*txtzahl) rcall txtext rjmp getpara getpark: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txtklein) ; Zahl zu niedrig ldi ZL,LOW(2*txtklein) rcall txtext rjmp getpara getparh: ; Fehlermeldung ausgeben ldi ZH,HIGH(2*txthoch) ; Zahl zu hoch ldi ZL,LOW(2*txthoch) rcall txtext rjmp getpara getparx: ; Test-Text ausgeben (nur in der STK-Version!) ldi XH,HIGH(srtx) ; Zeiger X auf SRAM-Text ldi XL,LOW(srtx) ldi ZH,HIGH(2*tsttext) ; Zeiger Z auf Testtext ldi ZL,LOW(2*tsttext) getparx1: lpm ; Lese Zeichen st x+,R0 ; Zeichen in SRAM-Puffer adiw ZL,1 ; nächstes Zeichen tst R0 ; Zeichen eine Null? brne getparx1 ; weiter mit Zeichen rjmp getpara ; fertig getdeftext: ; Default text in Speicher ab Z ldi rmp,'<' ; Verkehrsanfang, eingefügt 2313=>8515 st z+,rmp ldi rmp,10 ; Testtext in Speicher mov R0,rmp ; in Zähler getdeftext1: ldi rmp,'P' ; Paris st z+,rmp ldi rmp,'A' st z+,rmp ldi rmp,'R' st z+,rmp ldi rmp,'I' st z+,rmp ldi rmp,'S' st z+,rmp ldi rmp,' ' st z+,rmp dec R0 brne getdeftext1 sbiw ZL,1 ; Eins zurück ldi rmp,'>' st z+,rmp ldi rmp,chcr ; Textpuffer mit Carriage Return st z+,rmp clr rmp ; und Nullterminator st z,rmp ; entleeren ret ; eingefügt bis hier 2313=>8515 ; ; Warte auf char von der SIO mit Echo ; rxch: in rmp,USR ; Lese Control register sbrs rmp,RXC ; Character vorhanden? rjmp rxch ; Noch nicht, weiter warten in rmp,UDR ; Lese character aud Datenregister ret ; und zurück ; ; Sende text character in rmp an SIO mit Prüfung ; txch: push rmp ; Rette Zeichen auf Stapel txch1: in rmp,USR ; Senderegister leer? sbrs rmp,UDRE ; Ist das UDRE-Bit gesetzt rjmp txch1 ; Nein, weiter warten pop rmp ; Hole Zeichen vom Stapel out UDR,rmp ; Character senden cpi rmp,chcr ; Nach Carriage Return noch ein brne txch2 ; Linefeed? ldi rmp,chlf ; auch den noch senden! rcall txch ldi rmp,chcr ; und Zeichen wieder herstellen txch2: ret ; ; Morsecode der ASCII-Zeichen 0x20 bis 0x5F ; unteres Byte = Code (0=Punkt, 1=Strich) ; oberes Byte = Anzahl Punkte/Striche ; Bit 7 = 1: Leerzeichen ; morse: ; Zeichen 20 .. 2F .DB 0b11100000,0b10000011 ; Blank .DB 0b01000000,5 ; ! = Warten .DB 0b01001000,6 ; " .DB 0b11011000,5 ; # = ñ .DB 0b01101000,5 ; $ = á, å .DB 0b01000000,5 ; % = é .DB 0b00000000,0 ; & = nicht benutzt .DB 0b01111000,6 ; ' .DB 0b10110000,5 ; ( .DB 0b10110100,6 ; ) .DB 0b00000000,0 ; * = nicht benutzt .DB 0b00010100,6 ; + = Spruchende .DB 0b11001100,6 ; , .DB 0b10000100,6 ; .DB 0b01010100,6 ; . .DB 0b10010000,5 ; / ;Zeichen 30 .. 3F .DB 0b11111000,5 ; 0 .DB 0b01111000,5 ; 1 .DB 0b00111000,5 ; 2 .DB 0b00011000,5 ; 3 .DB 0b00001000,5 ; 4 .DB 0b00000000,5 ; 5 .DB 0b10000000,5 ; 6 .DB 0b11000000,5 ; 7 .DB 0b11100000,5 ; 8 .DB 0b11110000,5 ; 9 .DB 0b11100000,6 ; : .DB 0b10101000,6 ; ; .DB 0b10101000,5 ; < = Verkehrsanfang .DB 0b10001000,5 ; = .DB 0b01010000,5 ; > = Verkehrsende .DB 0b00110000,6 ; ? ;Zeichen 40 .. 4F .DB 0b11110000,4 ; @ = ch .DB 0b01000000,2 ; A .DB 0b10000000,4 ; B .DB 0b10100000,4 ; C .DB 0b10000000,3 ; D .DB 0b00000000,1 ; E .DB 0b00100000,4 ; F .DB 0b11000000,3 ; G .DB 0b00000000,4 ; H .DB 0b00000000,2 ; I .DB 0b01110000,4 ; J .DB 0b10100000,3 ; K .DB 0b01000000,4 ; L .DB 0b11000000,2 ; M .DB 0b10000000,2 ; N .DB 0b11100000,3 ; O ;Zeichen 50 .. 5F .DB 0b01100000,4 ; P .DB 0b11010000,4 ; Q .DB 0b01000000,3 ; R .DB 0b00000000,3 ; S .DB 0b10000000,1 ; T .DB 0b00100000,3 ; U .DB 0b00010000,4 ; V .DB 0b01100000,3 ; W .DB 0b10010000,4 ; X .DB 0b10110000,4 ; Y .DB 0b11000000,4 ; Z .DB 0b01010000,4 ; [ = Ä .DB 0b11100000,4 ; \ = Ö .DB 0b00110000,4 ; ] = Ü .DB 0b00000000,8 ; ^ = Irrung .DB 0b00110100,6 ; _ morseende: .DW morseende-morse ; Prüfzahl, muss 0x0040 sein ; ; Testtext, nur in der STK-Version implementiert! tsttext: .DB "",chcr, cnul ChkT: .DW ChkT ; ; Texte für die serielle Schnittstelle ; Hinweis: Die chk-Werte sind zum Überprüfen im Listing ; eingefügt. Es gibt einen Bug im AVR-Assembler von ATMEL, ; der zu falschen Adressen führt, wenn bestimmte Kombina; tionen von Byte-Konstanten verwendet werden. Dieser Bug ; ist seit zwei Jahren gemeldet und noch immer nicht besei; tigt! Teilweise sind die abenteuerlichen Konstruktionen ; in dieser Liste zur Umgehung dieses Bugs verwendet. ; ; Eingangsmeldung zu Beginn txtid: .DB chff,chcr .DB "+---------------------------------+",chcr .DB "| Morseprogramm (C)2002 by DG4FAC |",chcr .DB "+---------------------------------+",chcr .DB cnul,cnul chk1: .DW chk1 ; für Assembler Bug ; Text für Parameterliste txtpar1: .DB chcr,'E',"ingestellte Parameter:",chcr,'*' txtf: .DB "NF-Frequenz (256..4095 Hz) = ",cnul chk2: ; Bug-Check .DW chk2 txtpar2: .DB " Hz ",chcr,'*' txtg: .DB "Geschwindigkeit (10..200 BpM) = ",cnul chk3: ; Bug-Check .DW chk3 txtpar3: .DB " BpM",chcr,' ',"ccm = ",cnul,cnul chk4: ; Bug-Check .DW chk4 txtpar4: .DB ", Ints (kurz) = ",cnul,cnul chk5: ; Bug-Check .DW chk5 txtpar5: .DB ", Ints (lang) = ",cnul,cnul chk6: ; Bug-Check .DW chk6 txtpar6: .DB chcr,'*' txtt: .DB "Text = ",cnul chk7: ; Bug-Check .DW chk7 txtcur: .DB chcr,'=','>',cnul chk8: ; Bug-Check .DW chk8 txtmenue: .DB "Einstellungen: f=NF-Frequenz, g=Geschwindigkeit," .DB " t=Text, s=Speichern,",chcr .DB " l=Lesen, d=Default, x=Test, ESC=Ende! ",chcr .DB "(f,g,t,s,l,d,x,ESC) => ",cnul chk9: ; Bug-Check .DW chk9 ; Prüfzahl für Assembler-Bug txtzahl: .DB chcr,'F',"ehler in Ziffer oder Zahl bzw. Zahl zu gross!",cnul chk10: ; Bug-Check .DW chk10 ; Prüfzahl für Assembler-Bug txtklein: .DB chcr,'*',"* Zahl zu niedrig! **",cnul,cnul chk11: ; Bug-Check .DW chk11 ; Prüfzahl für Assembler-Bug txthoch: .DB chcr,'*',"* Zahl zu hoch! **",cnul,cnul chk12: ; Bug-Check .DW chk12 ; Prüfzahl für Assembler-Bug txteepw: .DB chcr,'P',"arameter ins EEPROM geschrieben.",chcr,cnul chk13: ; Bug-Check .DW chk13 ; ; Copyright-Info .DB "C(2)00 2ybD 4GAF C" ; ; Programm-Code Ende ; ; ****************************************** ; * EEPROM-Inhalt mit Default beginnt hier * ; ****************************************** ; .ESEG .ORG 0x0000 ; efrq: ; Die Default-NF-Frequenz .DW cfrq ebpm: ; Die Default-Geschwindigkeit in BpM .DB cbpm etxt: ; Dieser Text wird zu Beginn ausgegeben .DB "hello!" .DB chcr,cnul etxte: ; ; Copyright-Info ; .DB "(C)2002 by DG4FAC" ; ; Ende des EEPROM-Segmentes ;
; ******************************************************** ; * CW.asm gibt Morsezeichen aus, die im EEPROM-Speicher * ; * gespeichert sind und die über die serielle Schnitt- * ; * stelle des STK500-Boards eingegeben werden. Die Aus- * ; * gabe der NF erfolgt an PD5. Baudrate 9k6. 3,96 MHz * ; * AVR AT90S8515. Programm (C)2002 by DG4FAC G.Schmidt * ; * Version 0.1-200 vom 12.1.2002 * ; * Bugs und Dankschreiben an [email protected] * ; ******************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Benutzte Register ; ; Register R0 wird für Lesen im Programmspeicher benutzt ; Register R0..R9 werden für Berechnungen benutzt ; .DEF rcml = R10 ; Compare Match-Zahl, LSB .DEF rcmh = R11 ; dto., MSB .DEF rikl = R12 ; Interrupt-Anzahl kurz, LSB .DEF rikh = R13 ; dto., MSB .DEF rill = R14 ; Interrupt-Anzahl lang, LSB .DEF rilh = R15 ; dto., MSB .DEF rmp = R16 ; Multipurpose register, nicht bei Ints .DEF rim = R17 ; Interrupt multi-purpose .DEF rfl = R18 ; Flag Register, bei Ints und Normal .DEF rst = R19 ; Statusregister bei Interrupts .DEF rnsc = R20 ; Anzahl Punkte/Striche beim Senden .DEF rmcd = R21 ; Morsecode beim Senden .DEF x22 = R22 ; unbenutzt .DEF x23 = R23 ; unbenutzt .DEF rctl = R24 ; Zähler für Interrupt Tonerzeugung .DEF rcth = R25 ; (wird als Doppelregister verwendet) ; ; Register XH:XL R27:R26 Zeiger in den Empfangspuffer ; Register YH:YL R29:R28 Zeiger beim Senden ; Register ZH:ZL R31:R30 Zeiger für Lesen im Programmspeicher ; ; Bits im Flagregister ; .EQU besc = 7 ; ESCape-Sequenz, hole Parameter .EQU bmesc = 0x80 ; Maskenbyte für ESC .EQU bstx = 6 ; Starte Sendeprozess .EQU bmstx = 0x40 ; Maskenbyte für bstx .EQU bctx = 5 ; Beende Sendeprozess nach Leerzeichen .EQU bmctx = 0x20 ; Maskenbyte für bctx .EQU btxa = 4 ; Sendeprozess aktiv .EQU bmtxa = 0x10 ; Maskenbyte für btxc .EQU btxe = 3 ; Abschluss des Sendens .EQU bmtxe = 0x08 ; Maskenbyte für btxe .EQU bpl = 2 ; Lange Pause zur Buchstabentrennung .EQU bmpl = 0x04 ; Maskenbyte für bpl .EQU bpk = 1 ; Kurze Pause zur Punkt/Strich-Trennung .EQU bmpk = 0x02 ; Maskenbyte für bpk .EQU bquiet = 0 ; Dauerhaft inaktiviert bei Leerzeichen .EQU bmquiet = 0x01 ; Maskenbyte für bquiet ; ; Default-Werte ; .EQU cfrq=1000 ; NF-Frequenz .EQU cbpm=60 ; Gebegeschwindigkeit ; ; Basisdefinitionen variabel nach Schaltung ; .EQU fqu = 3960000 ; Quarzfrequenz des AVR .EQU fbd = 9600 ; Baudrate des UART .EQU pctrl = PORTB ; Control-Port für RTS/CTS (nicht benutzt) .EQU pdrr = DDRB ; Datenrichtungsregister Controlport .EQU brts = PB2 ; RTS bit Input (beim STK nicht benutzt) .EQU bcts = PB4 ; CTS bit Output (beim STK nicht benutzt) .EQU pnfd = DDRD ; Port für Richtung NF-Ausgabe (8515) .EQU doc1 = PD5 ; NF-Ausgabe über OC1B-Pin (8515) ; ; Umrechnungen in Timer- und Counterwerte ; .EQU nps = 8 ; Prescaler-Einstellung von Timer 1 .EQU ccm = fqu/nps/2; Konstante für Compare Match .EQU cint =2941 ; Konstante für Int-Berechnung, ; experimentell ermittelt ; ; Definitionen fix ; .EQU bddv = (fqu/(16*fbd))-1 ; Baudraten-Teiler .EQU cnul = 0x00 ; Ende für nullterminierte Strings .EQU chbsp = 0x08 ; Backspace character .EQU chcr = 0x0D ; Carriage Return character .EQU chlf = 0x0A ; Line Feed character .EQU chff = 0x0C ; Form Feed character .EQU chesc= 0x1B ; ESCape-Zeichen ; ; Definitionen I/O ; ; Definitionen für Timer-Controlregister TCCR1A .EQU t1aus = 0b10000000 ; Schaltet den NF-Ausgang aus .EQU t1ein = 0b01000000 ; Schaltet den NF-Ausgang ein ; Definition für Timer-Interrupt-Mask-Register TIMSK .EQU t1CompInt=0b01000000 ; Interrupt bei Compare Match ; Definition für Timer-Controlregister TCCR1B .EQU t1TaktInt=0b00001010 ; Timer-Takt CLK / 8, Clear Comp ; Definitionen für UART-Betrieb in UCR .EQU siorxtx = 0b00011000 ; Betrieb RX und TX ohne Int .EQU siorxint= 0b10011000 ; Betrieb RX mit Int, TX ohne Int ; Definition für den SLEEP-Mode in MCUCR .EQU sleepmode=0b00100000 ; Aufwachen bei Interrupt ; ; Positionen im SRAM ; .EQU sfrq = 0x0060 ; Eingestellte NF-Frequenz, Wort .EQU sbpm = 0x0062 ; Eingestellte Geschwindigkeit, Byte .EQU srtx = 0x0063 ; RAM-Puffer für Ein-/Ausgabezeile .EQU srte = 0x00B3 ; Ende des nutzbaren Puffers, benutzt werden ; auch zwei weitere Folgebytes ; ; Programm beginnt hier ; .CSEG .ORG 0x0000 ; ; Reset- und Interrupt-Sprungtabelle 8515 ; rjmp start ; Reset-Vector reti ; Ext Int 0, nicht benutzt reti ; Ext Int 1, nicht benutzt reti ; Timer 1 Capture Event, nicht benutzt rjmp itc1m ; Timer 1 Compare Match A reti ; Timer 1 Compare Match B reti ; Timer 1 Overflow, nicht benutzt reti ; Timer 0 Overflow, nicht benutzt reti ; SPI STC, nicht benutzt rjmp iurxc ; UART Rx Complete reti ; UART Tx data register empty, nicht benutzt reti ; UART Tx All sent, nicht benutzt reti ; ANA_COMP, nicht benutzt ; ; Interrupt-Service-Routinen ; itc1m: ; Timer 1 Compare Match Interrupt in rst,SREG ; Rette Statusregister sbiw rctl,1 ; Zähler eins abzählen brne itc1mz ; Raus, wenn nicht Null mov rcth,rikh ; Setze Zähler auf kurze Dauer mov rctl,rikl ldi rim,t1aus ; Ton auf aus tst rnsc ; Zeichen fertig gesendet? breq itc1m0 ; Verzweige, wenn Zeichen fertig sbrc rfl,bpk ; kurze Pausen-Flag? rjmp itc1ms ; Pause zwischen Punkt/Strich war schon sbr rfl,bmpk ; Setze kurze Pause-Flag rjmp itc1my ; und Ausgabe auf inaktiv, fertig itc1ms: ; Sende nächsten Punkt/Strich cbr rfl,bmpk ; Lösche kurze Pause-Flag ldi rim,t1ein; Ton an = Toggle dec rnsc ; Weiteren Punkt/Strich gesendet rol rmcd ; Punkt oder Strich senden? brcc itc1my ; Punkt senden mov rcth,rilh ; Lange Dauer einstellen mov rctl,rill rjmp itc1my itc1m0: ; Zeichen fertig gesendet sbrc rfl,bctx ; Sendung beenden? rjmp itc1mx sbrc rfl,bpl ; Lange Pause senden? rjmp itc1mn ; Nächsten Buchstaben beginnen sbr rfl,bmpl ; Setze langes Pausen-Flag mov rcth,rilh ; Dauer auf lang stellen mov rctl,rill itc1my: ; Stelle Modus inaktiv/toggle ein sbrc rfl,bquiet ; bei Leerzeichen Ton aus ldi rim,t1aus ; Ton auf aus out TCCR1A,rim itc1mz: out SREG,rst ; Stelle Status wieder her reti itc1mn: cbr rfl,bmpl ; Langes Pausen-Flag aus ld rim,y+ ; Nächsten Buchstaben lesen tst rim ; Null-Terminator breq itc1mn1 cpi rim,chcr ; Ende der Zeile? brne itc1mn2 itc1mn1: sbr rfl,bmctx ; Setze beenden-Flag ldi rim,' ' ; Sende noch ein Leerzeichen itc1mn2: out UDR,rim ; Debug subi rim,0x20 ; ASCII-Control-Zeichen weg brcc itc1mn3 ldi rim,'?'-0x20 ; Sende Fragezeichen itc1mn3: cpi rim,0x40 ; Kleinbuchstabe? brcs itc1mn4 subi rim,0x20 ; in Grossbuchstaben wandeln cpi rim,0x40 brcs itc1mn4 ldi rim,'?'-0x20 itc1mn4: add rim,rim ; Mal 2 für Tabelle push ZH ; Register retten push ZL push R0 ldi ZH,HIGH(2*morse) ; Zeichentabelle laden ldi ZL,LOW(2*morse) add ZL,rim ; Zeichen dazu zählen brcc itc1mn5 ; Kein Übertrag? inc ZH ; Übertrag itc1mn5: lpm ; Lese Zeichencode aus Tabelle mov rmcd,R0 ; in Zeichencode-Register adiw ZL,1 ; Zeiger auf nächstes Byte lpm ; aus Tabelle lesen mov rnsc,R0 ; Anzahl Striche/Punkte pop R0 ; Wieder herstellen der Register pop ZL pop ZH tst rnsc ; Undefiniertes Zeichen? breq itc1mn ; Überspringe Zeichen sbr rfl,bmquiet ; Leerzeichen sbrs rnsc,7 ; Leerzeichen? cbr rfl,bmquiet ; Kein Leerzeichen cbr rnsc,0x80 ; Lösche höchstes Bit mov rim,YL ; CTS einschalten? sub rim,XL brcs itc1mn6 ; Ausschalten cpi rim,3 brcs itc1mn6 cbi pctrl,bcts ; CTS einschalten rjmp itc1ms itc1mn6: sbi pctrl,bcts ; CTS ausschalten rjmp itc1ms ; Sende ein Zeichen itc1mx: ; Sendung einstellen clr rim ; Timer 1 abschalten out TCCR1B,rim ; Timer-Takt aus out TCNT1H,rim ; Timer auf Null stellen out TCNT1L,rim out TIMSK,rim ; Timer Interrupts aus out TCCR1A,rim ; Timer Compare Mode aus cbr rfl,bmctx+bmtxa ; Ende-Flag und aktiv ausschalten sbr rfl,bmtxe ; Beenden-Flag einschalten ldi YH,HIGH(srte) ; Buffer auf Ende ldi YL,LOW(srte) st Y,rim ; Null-terminieren rjmp itc1mz ; ; UART Rx Complete Interrupt ; iurxc: in rst,SREG ; Rette Statusregister in rim,UDR ; Lese Zeichen von SIO cpi rim,chesc ; ESCape-Sequenz? brne iurx1 sbr rfl,bmesc ; Setze ESCape-Bit rjmp iurxz iurx1: cpi rim,chbsp ; Backspace-Char? brne iurx2 cpi XL,LOW(srtx) ; Schon auf Anfang? breq iurxz sbiw XL,1 ; Eine Position zurück ldi rim,chcr ; Zeilenabschluss st x+,rim clr rim ; Null-terminieren st x,rim sbiw XL,1 ; zurück ldi rim,chbsp ; Backspace zurücksenden rjmp iurxe ; Echo character iurx2: cpi XL,low(srte) ; Pufferüberlauf? brcs iurx3 ; Nein, weiter ldi rim,chcr ; Character überschreiben iurx3: cpi rim,chcr ; Zeilenende? brne iurxw ; Nein, in Puffer und fertig sbrs rfl,btxa ; Überspringe Ausgabe wenn aktiv out UDR,rim ; Carriage Return schon mal ausgeben st x+,rim ; CR-Zeichen anhängen clr rim ; Null terminieren st x,rim ldi XH,HIGH(srtx) ; Puffer auf Anfang ldi XL,LOW(srtx) sbr rfl,bmstx ; Über Flag Sender starten ldi rim,chlf ; Sende Zeilenvorschub rjmp iurxe ; Echo Line-Feed iurxw: st x+,rim ; Speichere character ldi rim,chcr ; Abschluss terminieren st x+,rim ; mit Carriage return clr rim ; Null-terminieren st x,rim sbrs rfl,btxa ; Sender aktiv? rjmp iurxs ; Nein, CTS nicht prüfen mov rim,YL ; CTS ein oder aus? sub rim,XL ; Distanz zwischen Ein- und Ausgabe brcs iurxo ; Eingabe > Ausgabe: CTS aus cpi rim,3 ; mindestens zwei Zeichen Vorlauf? brcs iurxo ; Nein, dann CTS ausschalten cbi pctrl,bcts ; CTS einschalten rjmp iurxs ; Zeichen herstellen und raus iurxo: sbi pctrl,bcts ; CTS ausschalten iurxs: sbiw XL,2 ; Char wieder herstellen ld rim,x+ ; durch Lesen im SRAM iurxe: sbrs rfl,btxa ; Keine Ausgabe, wenn aktiv out UDR,rim ; Echo character an SIO zurück iurxz: out SREG,rst ; Stelle Status wieder her reti ; Ende des Interrupts ; ; Diverse eigenständige Unterprogramme ; ; Kopiert den EEPROM-Inhalt in das SRAM ; ecopy: sbic EECR,EEWE ; Warte, bis EEPROM bereit rjmp ecopy ; Noch nicht bereit clr YH ; Startadresse im EEPROM auf Null clr YL ldi ZH,HIGH(sfrq) ; Startadresse im SRAM ldi ZL,LOW(sfrq) ; auf ersten Parameter ecopy1: out EEARH,YH ; Leseadresse im EEPROM out EEARL,YL ; 2313 hat nur 128 Bytes, daher nur unteres Register sbi EECR,EERE ; Lese-Strobe setzen cbi EECR,EERE ; und wieder ausschalten in rmp,EEDR ; Byte aus EEPROM-Datenregister lesen st Z+,rmp ; und in SRAM schreiben adiw YL,1 ; nächste EEPROM-Adresse anwählen tst rmp ; Null-Terminator? brne ecopy1 ; Nein: weiter kopieren ret ; ; Schreibe Parameter in das EEPROM zurück ; ewrite: ldi ZH,HIGH(sfrq) ; Zeiger auf SRAM ldi ZL,LOW(sfrq) clr XH ; Zeiger in das EEPROM clr XL ewrite1: sbic EECR,EEWE ; Frage Write-Bit im EEPROM rjmp ewrite1 ; ab und wiederhole bis EEPROM ready out EEARH,XH ; Schreibadresse im EEPROM einstellen out EEARL,XL ld rmp,Z+ ; Lese Byte aus SRAM in Register out EEDR,rmp ; in das EEPROM-Datenregister cli ; Keine Interrupts mehr beim Schreibvorgang sbi EECR,EEMWE ; Setze EEPROM Master Write Enable sbi EECR,EEWE ; Löse Schreibvorgang im EEPROM aus sei ; Jetzt Interrupts wieder zulassen adiw XL,1 ; Nächste EEPROM-Adresse in X-Zeiger cpi XH,$02 ; Ende EEPROM erreicht? brcc ewrite2 ; Überlänge! Null fehlt! Abbrechen! tst rmp ; Nullterminiert? brne ewrite1 ; Noch weitere Bytes schreiben ewrite2: ret ; ; Lese16 wandelt eine ASCII-Zahl im Puffer in binär ; in R1:R0, bei Fehler Rückkehr mit gesetztem Carry-Bit ; lese16: clr R0 ; Leeren Resultat-Register R1:R0 clr R1 ldi ZH,HIGH(srtx) ; Zeige auf Pufferanfang ldi ZL,LOW(srtx) lese16a: ld rmp,Z+ ; Lese ASCII-Ziffer aus SRAM cpi rmp,chcr ; Ende der Zahl mit Carriage Return? breq lese16ok ; Zahl mit Carriage Return beendet cpi rmp,cnul ; Ende der Zahl mit Nullterminiert? breq lese16ok ; Ende mit Nullterminierung cpi rmp,'9'+1 ; Ziffer > ASCII-9? brcc lese16no ; Ja, Fehler! cpi rmp,'0' ; Ziffer < ASCII-0 brcs lese16no ; Ja, auch Fehler subi rmp,'0' ; Wandle Ziffer in binär ; Ab hier wird das bisherige Resultat mit 10 multi; pliziert mov R2,R0 ; Kopiere Binärzahl in Hilfsregister R3:R2 mov R3,R1 add R0,R0 ; Multipliziere mit 2 durch Addieren 16 Bit adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R2 ; Addiere die Kopie der Zahl adc R1,R3 brcs lese16no ; Überlauf beim Addieren, Fehler! add R0,R0 ; Multipliziere mit 2 durch Addieren adc R1,R1 brcs lese16no ; Überlauf beim Addieren, Fehler! ; Hier ist das Multiplizieren mit 10 beendet. add R0,rmp ; Addiere Ziffer zum Resultat hinzu brcc lese16a ; Kein Überlauf des unteren Bytes inc R1 ; Überlauf, oberes Byte erhöhen brne lese16a ; Kein Überlauf des oberen Bytes lese16no: sec ; Setze Carry-Bit bei Fehler und kehre zurück ret lese16ok: clc ; Clear Carry bei fehlerfrei ret ; ; Wandle 16-Bit-Zahl in R1:R0 in ASCII in R4..R9 (nullterm.) ; unterdrücke führende Nullen, sende Ergebnis ohne führende ; Nullen über die SIO ; b16asc: clr ZH ; Z zeigt auf höchste ASCII-Ziffer in ldi ZL,4 ; Register R4 (Zeiger in Register!) ldi rmp,HIGH(10000) ; Beginne mit Zehntausendern mov R3,rmp ; oberes Byte in R3 ldi rmp,LOW(10000) mov R2,rmp ; unteres Byte in R2 rcall b16sub ; Ermittle ASCII-code der Zehntausender ; Stelle durch fortgesetztes Subtrahieren von 10000 ldi rmp,HIGH(1000) ; Weiter mit Tausendern mov R3,rmp ldi rmp,LOW(1000) mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Tausender clr R3 ; Weiter mit Hunderten ldi rmp,100 mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Hunderter ldi rmp,10 ; Weiter mit Zehnern mov R2,rmp rcall b16sub ; Ermittle ASCII-Code der Zehner ldi rmp,'0' ; Rest sind Einer add rmp,R0 mov R8,rmp ; R8 kriegt die ASCII-Einer clr R9 ; Nullterminieren in R9 ldi ZL,4 ; Z auf 10000-er Zeichen b16asc1: cpi ZL,9 ; Ende der Zeichenkette erreicht? breq b16asc2 ; Ja, raus ld rmp,z+ ; Zeichen aus Register kopieren cpi rmp,'0' ; Führende Null? breq b16asc1 ; Ja, weitermachen b16asc2: dec ZL ; auf vorheriges Zeichen setzen ; ; Sende nullterminierten Text aus SRAM an SIO ; Z zeigt nicht ins SRAM, sondern in die Register mit ; dem Ergebnis! ; Das Registerpaar Z zeigt auf das erste Zeichen ; txstxt: ld rmp,z+ ; Lese Zeichen aus SRAM/Register tst rmp ; Nullterminator erreicht? breq txstxt1 ; Ja, raus und fertig rcall txch ; Sende character über SIO rjmp txstxt ; Weitermachen mit nächstem Zeichen txstxt1: ret ; ; Ermittle ASCII-Code einer Ziffer der 16-Bit-Binärzahl in ; R1:R0 durch fortgesetztes Subtrahieren einer 16-Bit; Hilfszahl (10000, 1000, 100, 10) in R3:R2. Tritt ein ; Überlauf ab, dann ist die Ziffer gefunden ; (Unterroutine für b16asc) ; b16sub: ldi rmp,'0' ; Setze ASCII-Null b16sub1: sub R0,R2 ; Subtrahiere die Hilfszahl sbc R1,R3 ; in 16-bit brcs b16sub2 ; Ende subtrahieren erreicht? inc rmp ; Einer geht noch! rjmp b16sub1 ; Weiter mit subtrahieren! b16sub2: add R0,R2 ; Zu weit, addiere wieder zurück adc R1,R3 st Z+,rmp ; ASCII-Ziffer in Register schreiben ret ; und zurück ; ; Sende nullterminierten Text aus dem Programmspeicher ; über die SIO aus ; txtext: lpm ; Lese Zeichen aus Programmspeicher mit Zeiger Z tst R0 ; Ergebnis im Register R0 hat Null erreicht? breq txtend ; Ja, raus aus der Routine mov rmp,R0 ; Kopiere Zeichen in Senderegister rcall txch ; Sende character mit Prüfung adiw zl,1 ; Zeiger auf nächstes Zeichen rjmp txtext ; Weitermachen bis Null txtend: ret ; Kehre zurück ; ; Liste alle Parameter über die SIO-Schnittstelle auf ; txpar: ldi ZH,HIGH(2*txtpar1) ; Sende Parametervorspann ldi ZL,LOW(2*txtpar1) ; für NF-Frequenz rcall txtext lds R0,sfrq ; Zeige eingestellte Frequenz an lds R1,sfrq+1 rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar2) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar2) ; Geschwindigkeit an rcall txtext lds R0,sbpm ; Zeige Geschwindigkeit an clr R1 ; nur 8 Bit! rcall b16asc ; Wandle in ASCII und sende Zahl ldi ZH,HIGH(2*txtpar3) ; Zeige Vorspann für Counterldi ZL,LOW(2*txtpar3) ; Compare-Match-Zahl ccm an rcall txtext mov R1,rcmh ; Compare-Match-Zahl in R1:R0 mov R0,rcml rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar4) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar4) ; Anzahl NF-Ints bei Punkt an rcall txtext mov R1,rikh ; Anzahl Ints bei Punkt in R1:R0 mov R0,rikl rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar5) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar5) ; Anzahl NF-Ints bei Strich an rcall txtext mov R1,rilh ; Anzahl Ints bei Strich in R1:R0 mov R0,rill rcall b16asc ; Wandle in ASCII und sende ldi ZH,HIGH(2*txtpar6) ; Zeige Vorspann für ldi ZL,LOW(2*txtpar6) ; Ausgabetext an rcall txtext ldi ZH,HIGH(srtx) ; Zeiger Z auf Ausgabetext im SRAM ldi ZL,LOW(srtx) rjmp txstxt ; Sende Inhalt SRAM nullterminiert ; ; 32-Bit durch 16-Bit-Division ; ; Dividiert 32-Bit-Zahl in R3:R2:R1:R0 durch R5:R4 ; Ergebnis in R7:R6 (Ergebnis darf nicht > 16 Bit sein!) ; div32: clr R7 ; Clear Ergebnis-Register R7:R6 clr R6 inc R6 ; Stopbit setzen für 16 Divisionsschritte div32a: clc ; Null in Carry schieben rol R0 ; Multipliziere Divident mit 2 rol R1 rol R2 rol R3 brcs div32e ; Carry ist herausgerollt? Dann 1! cp R3,R5 ; Vergleiche oberes Byte brcs div32n ; Ergebnis ist kleiner, also eine 0 brne div32e ; Ergebnis ist größer, also eine 1 cp R2,R4 ; Vergleich MSB gleich, vergleiche unteres ; Byte brcs div32n ; Unteres Byte kleiner, also eine 0 div32e: sub R2,R4 ; Ziehe den Divisor von den oberen 16 Bit ab sbc R3,R5 sec ; Setze das Carry-Bit, Ergebnis ist eine 1 rjmp div32s ; Zum Reinschieben in das Ergebnis div32n: clc ; Lösche Carry-Bit, Ergebnis ist eine 0 div32s: rol R6 ; Schiebe Carry-Bit von rechts in Ergebnis rol R7 brcc div32a ; Ende, wenn eine 1 links herausrollt ret ; ; Multipliziert 16-Bit-Zahl in R1:R0 mit 16-Bit-Zahl in R5:R4 ; Ergebnis 32 Bit in R9:R8:R7:R6, für jeden Zahlenbereich ; Mul16: clr R3 ; Leere obere zwei Bytes der 16-Bit-Zahl clr R2 clr R9 ; Leere Ergebnis-Register R9:R8:R7:R6 clr R8 clr R7 clr R6 Mul16a: clc ; Null ins Carry-Bit ror R5 ; Schiebe unterstes Bit Divisor in Carry ror R4 ; und dividiere Multiplikant durch 2 brcc Mul16b ; Bit war eine 0, Addition überspringen add R6,R0 ; addiere Multiplikator 32 Bit zum adc R7,R1 ; bisherigen Ergebnis, jeweils mit adc R8,R2 ; Überlauf adc R9,R3 Mul16b: tst R4 ; Schon alle Bits ausmultipliziert? brne Mul16c ; Nein, LSB nicht Null, weiter tst R5 ; Teste auch oberes Byte brne Mul16c ; Nein, MSB nicht Null, weiter ret ; Fertig Mul16c: clc ; Null in Carry-Bit schieben rol R0 ; Zahl durch Linksschieben mit 2 rol R1 ; multiplizieren rol R2 rol R3 rjmp Mul16a ; und weiter dividieren ; ; Dividiert 32 Bit-Zahl in R3:R2:R1:R0 durch eine ; 8-Bit-Zahl in R4 und durch 256, Ergebnis gerundet ; in R8:R7, Wertebereich beachten! ; Div32_8: clr R8 ; Ergebnisspeicher R8:R7:R6 leeren clr R7 clr R6 inc R6 ; Stopbit nach 24 Schritten setzen Div32_8a: clc ; Carry-Bit leeren rol R0 ; Divident mit 2 multiplizieren rol R1 ; durch Linksschieben rol R2 rol R3 brcs Div32_8e ; 1 herausgerollt, Ergebnis=1 cp R3,R4 ; Vergleiche oberstes Byte mit Divisor brcs Div32_8n ; Kleiner, Ergebnis = 0 Div32_8e: sub R3,R4 ; Ziehe Divisor von oberstem Byte ab sec ; Setze Carry für Ergebnis = 1 rjmp Div32_8b ; Ins Ergebnis schieben Div32_8n: clc ; Clear Carry für Ergebnis = 0 Div32_8b: rol R6 ; Ergebnis-Bit von rechts her hineinrotieren rol R7 rol R8 brcc Div32_8a ; Weiter, wenn eine Null herausrollt rol R6 ; Binäre Kommastelle eine 1, aufrunden? brcc Div32_8z ; Nein, wenn Null inc R7 ; Aufrunden LSB Ergebnis brne Div32_8z ; Kein Überlauf bei LSB-Erhöhung inc R8 ; Aufrunden MSB Div32_8z: ret ; ; Rechne Parameter um in Timer- und Zählerwerte ; Oben definiert: ccm = Taktfrequenz / Prescale 8 / 2 ; cint = ccm / 200 ; Compare-Match-Zahl für Timer 1 = ccm / NF-Frequenz ; Anzahl Ints bei Punkt = cint * NF-Frequenz / Speed /256 ; Anzahl Ints bei Punkt = Anzahl Ints bei Strich * 3 ; calctc: ldi rmp,BYTE4(ccm) ; Taktabhängige Konstante ccm mov R3,rmp ; in R3:R2:R1:R0, bei 4 MHz: 625.000 ldi rmp,BYTE3(ccm) ; oder 0x00098968 mov R2,rmp ldi rmp,BYTE2(ccm) mov R1,rmp ldi rmp,BYTE1(ccm) mov R0,rmp lds R4,sfrq ; durch NF-Frequenz in R5:R4 lds R5,sfrq+1 rcall div32 ; ergibt Compare Match-Zahl 16-Bit ; rcall txreg ; Debug-Code!!! mov rcmh,R7 ; Ergebnis in Speicher rcmh:rcml mov rcml,R6 ldi rmp,HIGH(cint) ; Konstante für Anzahl Ints mov R1,rmp ; bei 4 MHz und Teiler 200: 1250 ldi rmp,LOW(cint) mov R0,rmp lds R4,sfrq ; Mal NF-Frequenz in Hz lds R5,sfrq+1 rcall mul16 ; Multplizieren 16 Bit * 16 Bit ; rcall txreg ; Debug code!!! mov R3,R9 ; Ergebnis in R9..R6 nach R3..R0 kopieren mov R2,R8 mov R1,R7 mov R0,R6 lds R4,sbpm ; durch Gebegeschwindigkeit teilen rcall div32_8 ; teilen 32-Bit durch 8-Bit ; rcall txreg ; Debug code!!! mov rikh,R8 ; in Kurzspeicher kopieren mov rikl,R7 mov rilh,R8 ; und in Langspeicher kopieren mov rill,R7 add rill,rill ; Langspeicher mit 2 malnehmen adc rilh,rilh ; durch Addieren add rill,rikl ; und noch einmal dazu zählen adc rilh,rikh ; macht 3 mal so lang ret ; ; Debug code ; ; Display Byte in rmp in Hex an die SIO ; ;txbhx: ; push rmp ; swap rmp ; rcall txnhx1 ; pop rmp ;txnhx1: ; cbr rmp,0b11110000 ; subi rmp,-'0' ; cpi rmp,'9'+1 ; brcs txnhx2 ; subi rmp,-7 ;txnhx2: ; rcall txch ; ret ; ; Display Register R0..R9 in Hex an SIO ; ;txreg: ; ldi rmp,chcr ; rcall txch ; clr ZH ; clr ZL ;txreg1: ; ld rmp,Z+ ; rcall txbhx ; ldi rmp,' ' ; rcall txch ; cpi ZL,11 ; brcs txreg1 ; ldi rmp,chcr ; rcall txch ; ret ; ; Hauptprogramm-Loop, Restart-Vektor ; start: ldi rmp,HIGH(RAMEND) ; Init stack pointer im SRAM out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,bddv ; Baudrate des UART einstellen out UBRR,rmp ldi rmp,siorxtx ; UART Tx und Rx ein, Ints aus out UCR,rmp cbi pdrr,brts ; Bit Richtung ist RTS-Eingang sbi pdrr,bcts ; Bit Richtung ist CTS-Ausgang sbi pctrl,bcts ; CTS ausschalten (invertiert!) rcall ecopy ; Kopiere EEPROM-Inhalt nach RAM ldi ZH,high(2*txtid) ; Sende ID-Text an SIO ldi ZL,low (2*txtid) rcall txtext clr rfl ; Flag auf Anfangswert leer ; ; Bedingungen für Interrupt-Betrieb herstellen ; start1: rcall calctc ; Rechne Timerwerte und Ints aus rcall txpar ; Gib die eingestellten Werte über SIO ldi ZH,HIGH(2*txtcur) ; Sende Cursor-String ldi ZL,LOW(2*txtcur) rcall txtext ldi rmp,sleepmode ; Mode Idle für Sleep einstellen out MCUCR,rmp ; Aufwachen durch Interrupts ldi rmp,siorxint ; Enable RX mit Interrupt out UCR,rmp cbi pctrl,bcts ; Aktiviere CTS-Leitung sei ; Enable General Interrupt Flag ldi XH,HIGH(srtx) ; Puffer auf Anfang stellen ldi XL,LOW(srtx) rcall starttx ; und Text in CW aussenden ldi YH,HIGH(srte+3) ; Ausgabepointer hinter Ende ldi YL,LOW(srte+3) ; des Puffers stellen ldi XH,high(srtx+1) ; Puffer wieder auf Anfang ldi XL,low(srtx+1) ; und überschreiben des Textes clr rmp ; Null-Terminierung schreiben st -x,rmp ldi rmp,chcr ; Setze leeres Textfeld mit CR st -x,rmp ; Jetzt zeigt X-Zeiger auf Anfang ; ; Interrupt loop, ab jetzt praktisch nur Kreisverkehr ; mit Ausbruch bei gesetzten Flagbits ; loop: sleep ; CPU schlafen schicken nop ; Dummy-Befehl, bleibt bei Schlaf hier stehen nop ; nach dem Aufwachen ausgeführt sbrc rfl,bstx ; Sendeflag von Int-Routine gesetzt? rjmp starttx ; Ja: Starte Sendevorgang sbrc rfl,btxe ; Senden-beenden von Int-Routine? rjmp stoptx ; Beende Sendevorgang sbrc rfl,btxa ; Ist die Sendeausgabe aktiv? rjmp loop ; (Während Aussendung keine Parameter!) sbrc rfl,besc ; Parameter mit Menue holen? rjmp getpar ; Hole Parameter über SIO rjmp loop ; weiter im Kreis herum ; ; Startet Sendeprozess ; starttx: sbrc rfl,btxa ; Nicht neu starten, wenn schon aktiv rjmp loop ; Wieder raus! cbr rfl,bmstx ; Setze Flag bit zurück sbi pctrl,bcts ; Stop CTS-Leitung ldi YH,HIGH(srtx) ; Sende-Pointer auf Pufferanfang ldi YL,LOW(srtx) mov rcth,rikh ; Kurze Verzögerung bis zum Start mov rctl,rikl clr rnsc ; Auszugebendes Zeichen beendet, provoziert ; Laden des nächsten Zeichens bei der Senderoutine sbi pnfd,doc1 ; Ausgabe Pin OC1B=PD5 auf Ausgang (8515) clr rmp ; Setze Timer-Werte out TCCR1A,rmp ; OC1 inaktivieren out TCNT1H,rmp ; Timer-Register auf Null setzen out TCNT1L,rmp out OCR1AH,rcmh ; Compare Match auf Dauer entout OCR1AL,rcml ; sprechend der NF-Frequenz ldi rmp,t1CompInt ; Ermögliche Compare Int out TIMSK,rmp ldi rmp,t1TaktInt ; Clear Timer on Compare ; Match und Prescaler CK/8 out TCCR1B,rmp ; Timer-Takt starten rjmp loop ; und CPU bis zum Timer-Int schlafen legen ; ab jetzt läuft wieder alles automatisch ab ; ; Beendet Sendevorgang ; stoptx: cbr rfl,bmtxe ; Setze Beenden-Flag zurück cbi pctrl,bcts ; CTS wieder einschalten ldi ZH,HIGH(2*txtcur) ; Gib Cursor an SIO aus ldi ZL,LOW(2*txtcur) rcall txtext cpi XL,LOW(srtx) ; Schon Zeichen eingegeben? breq loop ; Nein, schlafen legen bis SIO-RX kommt ldi ZH,HIGH(srtx) ; Gepufferte Zeichen auf Cursorldi ZL,LOW(srtx) ; zeile ausgeben an SIO-TX stoptx1: ld rmp,z+ ; Zeichen aus Puffer lesen rcall txch ; an SIO senden cp ZL,XL ; Schon alle ausgegeben? brcs stoptx1 ; Weiter ausgeben rjmp loop ; Alles ausgegeben, schlafen legen ; ; Getpar holt menuegesteuert Parameter vom User ; getpar: ldi rmp,siorxtx ; Rx-Interrupts abschalten out UCR,rmp ; reiner Polling-Betrieb getpar0: rcall calctc ; Rechne aktuelle Parameter um getpara: rcall txpar ; Gib die Parameter aus getparb: ldi ZH,HIGH(2*txtmenue) ; Gib Menue aus ldi ZL,LOW(2*txtmenue) rcall txtext rcall rxch ; Hole ein Zeichen von SIO cpi rmp,chesc ; Ende-Zeichen ESCAPE? brne getpar1 ; Nein, mach weiter im Menue cbr rfl,bmesc ; Setze Menue-Flag zurück http://www.avr-asm-tutorial.net/cq-dl/teil4/Cw01_500.asm (1 of 2)1/20/2009 7:50:19 PM
gavrasm on DOS -------------If you get the error message "No DPMI" when trying to start gavrasm.exe on a DOS machine, please download the DOS package "Base files (program and units, basego32.zip" (or the full package, if you like), of Free Pascal from the website http://www.freepascal.org Unpack the files and move the file "cwsdpmi.exe" to a directory, which is in the path of your DOS. This should resolve the problem. Unfortunately I cannot verify this, because I don't have a DOS machine here. Remember: Using the DOS-version in a Win-Environment requires that all filenames (e.g. of include files) have to be compatible with the 8.3 format.
; ; Complete instruction set ; test file for gavrasm compiler ; ; If testhigh is 0 the lower range of values is tested, ; if testhigh is 1 the upper range of values is tested ; .EQU testhigh=0 ; .IF testhigh==0 ; Lower range of values .DEF r = R0 .DEF rh = R16 .DEF rd = R24 .DEF rf = R16 .DEF rm = R0 .EQU p = 0 .EQU pl = 0 .EQU b = 0 .EQU k63 = 0 .EQU k127 = 0 .EQU k255 = 0 .EQU k4095 = 0 .EQU k65535 = 0 .EQU k4M = 0 .ELSE ; Upper range of values .DEF r = R31 .DEF rh = R31 .DEF rd = R30 .DEF rf = R23 .DEF rm = R30 .EQU p = 63 .EQU pl = 31 .EQU b = 7 .EQU k63 = 63 .EQU k127 = -63 .EQU k255 = 255 .EQU k4095 = -2048 .EQU k65535 = 65535 .EQU k4M = 4194303 .ENDIF ; instruct: adc r,r add r,r adiw rd,k63 and r,r andi rh,k255 asr r bclr b bld r,b .IF testhigh==0 brbc b,-64 brbs b,-64 brcc -64 brcs -64 .ELSE brbc b,+63 brbs b,+63 brcc +63 brcs +63 .ENDIF break .IF testhigh==0 breq -64 brge -64 brhc -64 brhs -64 brid -64 brie -64 brlo -64 brlt -64 brmi -64 brne -64 brpl -64 brsh -64 brtc -64 brts -64 brvc -64 brvs -64 .ELSE breq +63 brge +63 brhc +63 brhs +63 brid +63 brie +63 brlo +63 brlt +63 brmi +63 brne +63 brpl +63 brsh +63 brtc +63 brts +63 brvc +63 brvs +63 .ENDIF bset b bst r,b call k4M cbi pl,b cbr rh,k255 clc clh cli cln clr r cls clt clv clz com r cp r,r cpc r,r cpi rh,k255 cpse r,r dec r eicall eijmp elpm elpm r,Z elpm r,Z+ eor r,r fmul rf,rf fmuls rf,rf fmulsu rf,rf icall ijmp in r,p inc r jmp k4M ld r,X ld r,X+ ld r,-X ld r,Y ld r,Y+ ld r,-Y ld r,Z ld r,Z+ ld r,-Z ldd r,Y+k63 ldd r,Z+k63 ldi rh,k255 lds r,k65535 lpm lpm r,Z lpm r,Z+ lsl r lsr r mov r,r movw rm,rm mul r,r muls rh,rh neg r nop or r,r ori rh,k255 out p,r pop r push r .IF testhigh==0 rcall -2048 .ELSE rcall +2047 .ENDIF ret reti .IF testhigh==0 rjmp -2048 .ELSE rcall +2047 .ENDIF rol r ror r sbc r,r sbci rh,k255 sbi pl,b sbic pl,b sbis pl,b sbiw rd,k63 sbr rh,k255 sbrc r,b sbrs r,b sec seh sei sen ser rh ses set sev sez sleep spm st X,r st X+,r st -X,r st Y,r st Y+,r st -Y,r st Z,r st Z+,r st -Z,r std Y+k63,r std Z+k63,r sts k65535,r sub r,r subi rh,k255 swap r tst r wdr .EXIT This is the complete instruction set in one file. http://www.avr-asm-tutorial.net/gavrasm/instr_linux.asm1/20/2009 7:50:28 PM
Akkuloader with an ATmega16
Path: Home => Akkuloader
Akkuload - a microprocessor controlled loader for accu cells To the english description Important message: A serious bug has been detected, please follow the instructions on the download page!
Path: Home => Akkuloader Zur deutschen Beschreibung
Akkuload - a microprocessor controlled loader for accu cells Changes Changes on May 2, 2005 In version 1 of March 2005, a serious bug causes a false calculation of the load current. The load current is roughly half the value than is selected and displayed. To avoid this bug, download the new version of akkucalc. asm as of May 5, 2005, re-assemble and re-program. (Thanks for uncovering this bug to Sebastian Mazur).
Description The acculoader loads up to four single accu cells with predefined currents between 5 mA and 220 mA (max. up to 350 mA in a single channel). The characteristics and all necessary parameters are selectable for each of the four channels separatly, so that mixed loading of different cells is possible with no limitations. The load characteristics are preselected, the microprocessor measures and controls currents and capacities. The control over the loader can either take place with a serial RS232 interface and a terminal program like Hyperterminal or by three keys. Output and control uses a 4-line-LCD. The characteristica and the load history of up to 32 accumulators are storable internally, so that the necessary parameters (load capacity, currents, etc.) can be selected very quick. The number of full loads with nominal capacity is stored and automatically updated each time a load ends. Using the RS232 interface, all informations are readable and all functions can be performed. By using the monitoring mode all measured values are traced and can be copied to a textfile. These values can be used to display the load characteristics via PC software.
Functions Akkuload uses an ATMEL AVR ATmega16 at an internal clock of 8 Mcs/s. Attached to the processor are: ● ● ● ● ● ● ● ● ●
four pulsewidth generators for the control over the load current, four outputs for initiating the unload cycle, four AD converter channels for measuring the accu cell voltages, four AD converter channels for measuring the four load currents, three inputs for the keys, in part combined with four LED outputs for signalling the loading state of the channels, two serial ports (RXD/TXD) for communication over the RS232 interface, 10 Port-In- and Outputs for control over the LCD display, combined with four in- and outputs for the In-System-Programming-Interface.
Download Hint: the switch schematic in the 2005 version had a bug in the wiring of channel 3. This is corrected in the below refernced version of 2007. ●
●
schematics ❍ microprocessor part, GIF-format ❍ analog part, GIF-format ❍ analog part, PDF-format commented source code files ❍ All five asm files in one zipfile ❍ Akkuload main routines ❍ Calculation routines ❍ Key input routines ❍ LCD routines ❍ UART communication routines
Path: Home => Akkuloader Zur englischen Beschreibung
Akkuload - ein Mikroprossor-gesteuertes Ladegerät für Akkuzellen Änderungen Neue Version vom 26.6.05 In der Routine akkuuart.asm ist der Text über den Zustand des Monitoring falsch ausgegeben. Betroffen ist nur der Betrieb mit RS232/SIO. Der Fehler ist korrigiert.
Neue Version vom 2.5.05 In der Routine akkucalc.asm vom März führt ein Fehler dazu, dass der angezeigte Strom doppelt so groß erscheint als er tatsächlich ist. Entsprechend falsch sind auch alle abgeleiteten Größen (Ladekapazität etc.). Bitte die Version von akkucalc.asm vom 2.5.05 verwenden! (Danke an den Entdecker, Sebastian Mazur)
Beschreibung Der Akkulader lädt maximal vier kleine Akkus einzeln mit wählbaren Strömen zwischen 5 mA und 220 mA (max. bis ca. 350 mA in einem einzelnen Kanal). Das Laden und alle Einstellungen dazu erfolgt individuell für jeden einzelnen Akku, gemischte Bestückung der vier Kanäle ist daher ohne Einschränkung möglich. Der Ladevorgang jedes einzelnen der maximal vier Akkus wird vorgewählt und von einem Mikroprozessor gemessen und überwacht, der Ströme und Ladekapazitäten misst und korrigiert. Die Bedienung des Geräts erfolgt entweder über die serielle Schnittstelle eines Rechners oder über drei Tasten. Die Bedienung und die Ausgabe der aktuellen Werte erfolgt über eine vierzeilige LCD-Anzeige.
Die Charakteristika und die Ladegeschichte von maximal 32 Akkus sind intern speicherbar, so dass über die Nummer des Akkus rasch die nötigen Einstellungen (Ladekapazität, Ströme, etc.) vorgenommen sind. Die Anzahl Volladungen der einzelnen Akkus mit Nennkapazität werden gespeichert und automatisch nach Abschluss des Ladevorganges aktualisiert. Über die SIO-Schnittstelle können alle Informationen ausgelesen und alle Funktionen bedient werden. Im Monitoring-Modus können die Messwerte mitgeschrieben und später mittels Software auf dem PC ausgewertet werden.
Funktionsweise Das Gerät wird durch einen ATMEL-AVR ATmega16 mit einem internen Takt von 8 MHz gesteuert. An den Prozessor sind angeschlossen: ● ● ● ● ● ● ● ● ●
vier pulsweiten-getaktete Ausgänge für die Steuerung des Ladestroms, vier Ausgänge zur Ansteuerung des Entladevorgangs, vier AD-Wandler-Kanäle zur Überwachung der Spannung der vier Akkus, vier AD-Wandler-Kanäle zur Überwachung des Stroms beim Entladen und Laden der vier Akkus, drei Eingänge für die Bedientasten, teilweise kombiniert mit vier LED-Ausgängen zur Anzeige des Ladezustands, zwei serielle Ports für die Kommunikation über die RS232- Schnittstelle, 10 Port-Ein- und Ausgänge für die Ansteuerung der LCD-Anzeige, kombiniert mit vier Ein-/Ausgängen für die Programmierschnittstelle zur In-System-Programmierung des Prozessors.
Download Hinweis: Im Schaltbild des Analogteils in der Version aus 2005 waren die AD-Wandler-Anschlüsse von Kanal 3 vertauscht. Dieser Fehler ist in der unten referenzierten Version korrigiert. ●
●
●
Alles in einem: Ausführliche Beschreibung, Schaltbilder, ein Anwendungsbeispiel, kommentierte Software in einer Datei zum Download (1,2 MB). Schaltpläne ❍ Mikroprozessor-Teil, GIF-Format ❍ Analogteil, GIF-Format ❍ Analogteil, PDF-Format Kommentierte Assembler-Quelldateien ❍ Alle fünf Quelldateien in einem Zipfile ❍ Akkuload Hauptprogramm ❍ Berechnungsroutinen ❍ Tastatureingaben ❍ LCD-Routinen ❍ UART Kommunikationsroutinen
; Demonstrates the use of the EEPROM ; ; During programming a counter location, defined in the EEPROM, ; is set to zero. At every restart of the processor this counter is incremented ; and its content is displayed in hex format on the LEDs. ; Please refer to the hints given on the end of this code to avoid ; some confusion. ; .NOLIST .INCLUDE "8515def.inc" .LIST ;
; Define constants ; .equ cnt=$0000 ; Adress of the counter location in the EEPROM ;
; Reset-/Interrupt-Vector RJMP main ; Jump to main program ; main: LDI mpr,$FF ; all bits of Port B are output OUT DDRB,mpr
; Program reads a byte in the EEPROM LDI mpr,LOW(cnt) ; Set EEPROM adress OUT EEARL,mpr ; to the EEPROM-adress port LDI mpr,HIGH(cnt) ; Low/High-Byte separate OUT EEARH,mpr ; EEPROM has 512 Byte locations SBI EECR,EERE ; Set Read-Enable-Bit EERE ; in EEPROM-Control-Register EECR IN neu,EEDR ; Read byte from EPROM-location ; Increment counter and write back to EEPROM INC neu wart:
; If EEPROM busy, wait first SBIC EECR,1 ; Busy Bit 1 in EEPROM-Control-Register RJMP wart ; not ready, wait ; The EEPROM-Adress is still correct, so we don't need to adjust it ; We just transfer the new data to the EEPROM-data register OUT EEDR,neu ; New value to EEPROM-Data register ; The following two write commands must not be interrupted, because safe write operations ; are only valid if these come within four commands. Otherwise hardware skips the write operation. ; Interrupts must be disabled therefore (interrupts are not used here). CLI
; Now the two write commands: SBI EECR,EEMWE ; Switches EEPROM Master Write Enable on SBI EECR,EEWE ; Enables write operation in the EEPROM ; Within the following 1,5 milliseconds the byte is written to the EEPROM location. ; This is sensitive only in case you want to use the EEPROM for further operations. ; Not here: We write the inverted content of the counter to the LEDs on Port B ; and end the program in an indefinit loop. COM neu ; invert OUT PORTB,neu ; to Port B loop: RJMP loop ; wait forever.
; Here starts the setting of the counter to zero during programming ; First we have to tell the assembler that the following code goes to the EEPROM segment ; and not to the code segment: .ESEG
; Demonstates the use of the EEPROMs ; ; During the programming a counter location, defined in the EEPROM, ; is set to zero. At every restart of the processor this counter is incremented ; and its content is displayed in hex format on the LEDs. ; Please refer to the hints given on the end of this code to avoid confusion! ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Define constants ; .equ cnt=$0000 ; Adress of the counter location in the EEPROM ; ; Define registers .def mpr=R16 ; Universal register .def neu=R17 ; Counter value interim storage ; ; Reset-/Interrupt-Vector rjmp main ; Jump to main program ; main: ldi mpr,$FF ; all bits of Port B are Output out DDRB,mpr ; Program reads a byte from the EEPROM location ldi mpr,LOW(cnt) ; Set the EEPROM location to read from out EEARL,mpr ; tell this to EEPROM-Port ldi mpr,HIGH(cnt) ; Low/High-Byte will be read separately out EEARH,mpr ; as there are 512 byte locations available sbi EECR,EERE ; Set the Read-Enable-Bit EERE in the ; EEPROM-Control-Register EECR in neu,EEDR ; Read the byte from the EEPROM-location ; Increment the counter and write back to the same EEPROM location inc neu wart: ; If EEPROM is not ready, wait first sbic EECR,1 ; Read bit 1 in the EEPROM-Control-Register rjmp wart ; and repeat until EEPROM reports ready ; The EEPROM-adress location isn't changed, so we don't need to set that first ; by transfer of the EEPROM-write adress to EEARL/EEARH out EEDR,neu ; New counter value to the EEPROM-data register ; The two write commands must not be interrupted, because they must be executed ; within four commands to ensure prevention of any unwanted write commands to ; the EEPROM. So we have to disable any interrupts before entering the write ; sequence. cli ; disables all interrupts ; Now we can start the two write commands: sbi EECR,EEMWE ; Switches on the EEPROM Master Write Enable sbi EECR,EEWE ; Starts the write command to the EEPROM ; During the following ca. 1,5 milliseconds the byte is written to the EEPROM. ; This affects us only if we want to use the EEPROM for further operations. ; Not here: we write the inverted content of the counter to the Port B, the ; LED-port, and end the program with a indefinite loop. com neu ; invert the counter (XOR FFh) out PORTB,neu ; to Port B loop: rjmp loop ; wait undefinitely ; Here we start defining the initial value of the counter location in the EEPROM ; during programming. ; First we tell the assembler, that the following informations go to the EEPROM. .ESEG ; Now we define the EEPROM-content: .DB $00 ; One byte with a zero ; That's about it. ; IMPORTANT HINTS ; During programming the content of the EEPROM-file TESTEEP.EEP ; will be loaded separately and programmed after the code is loaded. ; Don't forget this! ; During the programming sequence of the different locations for code ; and EEPROM content the software for the board releases the Reset pin ; of the processor, e.g. between programming and verification. As this ; short pause already causes the processor to restart and execute the ; code. Verification of the EEPROM content will therefore fail, because ; the counter is already incremented and does not match its original ; programmed value. Every read operation of the EEPROM content ; onboard will have the same effect. ; The execution of the restart command using the ISP software also ; causes multiple startups of the processor and increases the counter ; value, so don't expect to see correct counting values. ; Exact up-counts are only seen when switching the supply voltage ; of the board off and on. ; To avoid unwanted upcounting during program, verification and read ; operation would require setting a startup delay time, but this is a ; little bit too complex for a beginner. http://www.avr-asm-tutorial.net/avr_en/source/TESTEEP.asm1/20/2009 7:56:36 PM
; TestRam tests external SRAM on the STK-200 board ; ************************************************ ; TestRam tests external SRAM on the STK200 board ; Counts SRAM-positions by writing AAh and 55h ; in each position, verifies the content and displays ; the MSB of the highest SRAM-adress on the LEDs. ; ************************************************ ; Explanation on the RAM access ; The MUX-IC 74HC573 and the SRAM 62256-70 have to be ; installed on the board. ; Port A is multiplexed adress bus for the LSB adress and the ; data bus ; Port C is the upper adress bus ; Port D Bit 6 is /RD (Read) on the SRAM, Bit 7 is /WR (Write) ; Line ALE (Adress Latch Enable) is used, Pin 30 of the 8515 ; If the whole external SRAM has been tested ok, all LEDs are ; switched on except LED7. The highest adress then is ; 7FFFh, if a 32 kB SRAM is used.
; ************************************************ ; TestRam tests external SRAM on the STK200 board ; Counts SRAM-positions by writing AAh and 55h ; in each position, verifies the content and displays ; the MSB of the highest SRAM-adress on the LEDs. ; ************************************************ ; Explanation on the RAM access ; The MUX-IC 74HC573 and the SRAM 62256-70 have to be ; installed on the board. ; Port A is multiplexed adress bus for the LSB adress and the ; data bus ; Port C is the upper adress bus ; Port D Bit 6 is /RD (Read) on the SRAM, Bit 7 is /WR (Write) ; Line ALE (Adress Latch Enable) is used, Pin 30 of the 8515 ; If the whole external SRAM has been tested ok, all LEDs are ; switched on except LED7. The highest adress then is ; 7FFFh, if a 32 kB SRAM is used. ; 8515-definitions .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; Registers .def mp = R16 ; Multi-Purpose .def soll = R17 ; AA and 55 ; Reset-/Interrupt-Vector rjmp main ; Main program main: ldi mp,LOW(RAMEND) ;Initiate Stackpointer out SPL,mp ; for the use with subroutines ldi mp,HIGH(RAMEND) out SPH,mp ; Port B is driver for the LEDs ldi mp,0xFF ; All outputs out DDRB,mp ; to data direction register Port B in mp,MCUCR ; Read MCU-Control-Register ori mp,0x80 ; Set Bit 7 out MCUCR,mp ; If the SRAM used is slower than 70 ns and therefore requires an ; additional WAIT-state, then Bit 6 has to set additionally. The ; ORI command then must be ORI mp,0xC0 to set bit 6, too. ldi XL,LOW(RAMEND) ; Register XL is R26, LSB RAM-Adress ldi XH,HIGH(RAMEND) ; Register XH is R27, MSB RAM-Adress ldi soll,0b10101010 ; Bit pattern AAh for test loop: inc XL ; Increment 16-bit adress counter by 1 brne check ; No carry to MSB inc XH ; Increment MSB breq check ; MSB overroll, end check: st X,soll ; write Bit pattern to SRAM ld mp,X ; Read the same SRAM-Adress cp mp,soll ; compare written and read brne Zurueck ; Not equal, skip further tests com soll ; Invert bit pattern (XOR FF) st X,soll ; Test again with 0101.0101=55h ld mp,X ; Read cp mp,soll ; Compare brne Zurueck ; Not equal, skip further testing com soll ; invert test pattern again rjmp loop ; Go on with next location Zurueck: ld mp,-X ; Drecrement Pointer X, Result in mp doesn't matter Ungleich: com XH ; XOR FF of the highest RAM-Adress out PORTB,XH ; to the LEDs ende: rjmp ende ; Loop for ever http://www.avr-asm-tutorial.net/avr_en/source/TESTRAM.asm1/20/2009 7:56:40 PM
AVR-Hardware, LCD-display test
Path: Home => AVR-Übersicht => Hardware => LCD ; ************************************************
; TestLcd clears the LCD-Display and displays a text on the LCD. ; ************************************************
; Basics of the LCD operation on the STK200 boad ; Hardware: The 2-line-LCD-Display must be mounted
; ; on the board and connected correctly. It is recommended ; to connect a 14-pin female connector to the backside ; of the LCD display board that fits the male connector
; ; on the STK200. That's all for the hardware, all other
; ; hardware is already on board of the STK200 (lousy ; documentation of this!). ; Software: The access to the LCD is in this case programmed ; in memory-mapped mode, not via I/O commands. This ; has the advantage that the same commands of the RAM ; access can be used, complicated port bits programming ; is avoided and external 32 kB memory can be used in ; parallel. (Example programs for LCD access found in the ; internet are all I/O-mapped and do not run correct with ; the 32 kB memory on board the STK200 in parallel. ; The adress for LCD access is $8000 for commands and ; $C000 for access to the charcater generator and the ; display lines (both read and write). ; As memory-mapped access is too fast for most LCDs ; the WAIT-Bit in the MCUCR register has to be set to insert ; a wait state. This slows down the RAM access, too, but ; should not be a problem in most cases. To speed-up ; RAM access the WAIT-bit could be set to zero during ; times where only RAM access is made and set to 1 again ; when access to the LCD follows. ; Used Ports: The following ports are used, both by LCD and RAM ; access: ; Port A: Alternate use as LSB adress bus and data bus; not ; used during LCD access, but blocked by memory; mapped access ; Port C: MSB adress bus for SRAM access; used for LCD ; access: Bit 7 (Adress signal) and 6 (RS signal of ; the LCD) ; Port D: Bit 6 is /RD (Read) on the SRAM, not used by LCD ; access, Bit 7 is /WR (Write) on the SRAM and on the ; LCD ; ; Test program sequence: Sequentially the following steps are ; executed: ; 0. Wait until the LCD is not busy any more ; 1. Clear the LCD ; 2. Set the transfer mode to 8-bit, a fixed display window and ; define other display properties ; 3. Output to lines of text on the LCD window ; Before each operation takes place a LED is switched on ; to allow debugging the single steps. If all steps are executed ; correct LED 4 is on and the text on the LCD is visible ; (if not visible: correct contrast setting of the LCD). ; ; Structure of the software: All LCD operations are programmed as ; subroutines and so can be exported and used in other programs ; easily. Used registers are: mp=Allround register for handing ; values to subroutines; R26/27=XL/XH is a 16-bit-adress ; for SRAM/LCD-access commands ST X and LD X ;
; Registers .def mp = R16 ; Multi-Purpose .def test = R17 ; Counts the test phases for debugging
; Reset-/Interrupt-Vector RJMP main
; Subroutines for LCD access LcdWt: ; Wait until the LCD-Busy-Flag is zero LDI XH,0x80 ; Upper byte of RAM adress of LCD LDI XL,0x00 ; Lower Byte of RAM-Adresse of LCD LD mp,X ; Read Busy flag und AC-Adress ROL mp ; Shift Bit 7 to carry flag BRCS LcdWt ; If one, then busy, repeat RET LcdCl: ; Clear the LCD LDI mp,0x01 ; Clear command is 01h LcdBef: ; Command byte in mp to LCD, if it is ready PUSH mp ; Command in mp to stack RCALL LcdWt ; Wait until LCD is not busy POP mp ; Pop command from stack ST X,mp ; Command to LCD RET ; End of subroutine LcdInit: ; Init mode of LCD LDI mp,0b00111000 ; 8-Bit-Transfer, not four bit RCALL LcdBef ; command to LCD LDI mp,0b00000110 ; Increment, Display freeze RCALL LcdBef ; command to LCD LDI mp,0b00010000 ; Cursor move, not shift RCALL LcdBef ; command to LCD RET ; Back LcdBu: ; Write character in mp to LCD PUSH mp ; Save character on stack RCALL LcdWt ; Wait until not busy POP mp ; Restore character LDI XH,0xC0 ; Storage of LCD on adress 0C00h ST X,mp ; Write character to LCD RET ; Back LcdTs: ; Write the word "Test" to the current line PUSH mp ; Save the line adress in mp RCALL LcdWt ; Wait for not busy POP mp ; Line adress to mp ORI mp,0x80 ; Set Bit 7 of line adress RCALL LcdBef ; Command to LCD LDI mp,'T' ; Letter T RCALL LcdBu ; Write to LCD LDI mp,'e' ; Letter e RCALL LcdBu ; Write to LCD LDI mp,'s' ; Letter s RCALL LcdBu ; Write to LCD LDI mp,'t' ; Letter t RCALL LcdBu ; Write to LCD RET ; Ready, Back LcdTst: ; Write the word "Test" to line 1 and 2 LDI mp,0x00 ; Line 1 starts at adress 00h RCALL LcdTs ; Write to line 1 LDI mp,0x40 ; Line 2 starts at adress 40h RCALL LcdTs ; Write to line 2 LDI mp,0b00001111 ; Command Display On, Cursor On und Blink RCALL LcdBef ; Command to LCD RET ; Back
; Main program main: OUT LDI OUT
LDI mp,LOW(RAMEND) ;Initiate Stackpointer SPL,mp ; for subroutine use mp,HIGH(RAMEND) SPH,mp
; Port B outputs to the LEDs LDI mp,0xFF ; All output OUT DDRB,mp ; to Data Direction IN mp,MCUCR ; Read MCU-Control-Register ORI mp,0xC0 ; Set Bit 7 (SRAM) and Bit 6 (WAIT-STATE) OUT MCUCR,mp
; ************************************************ ; TestLcd clears the LCD-Display and outputs ; a test text in line 1 and 2 of the display ; ************************************************ ; Basics of the LCD operation on the STK200 boad ; Hardware: The 2-line-LCD-Display must be mounted ; on the board and connected correctly. It is recommended ; to connect a 14-pin female connector to the backside ; of the LCD display board that fits the male connector ; on the STK200. That's all for the hardware, all other ; hardware is already on board of the STK200 (lousy ; documentation of this!). ; Software: The access to the LCD is in this case programmed ; in memory-mapped mode, not via I/O commands. This ; has the advantage that the same commands of the RAM ; access can be used, complicated port bits programming ; is avoided and external 32 kB memory can be used in ; parallel. (Example programs for LCD access found in the ; internet are all I/O-mapped and do not run correct with ; the 32 kB memory on board the STK200 in parallel. ; The adress for LCD access is $8000 for commands and ; $C000 for access to the charcater generator and the ; display lines (both read and write). ; As memory-mapped access is too fast for most LCDs ; the WAIT-Bit in the MCUCR register has to be set to insert ; a wait state. This slows down the RAM access, too, but ; should not be a problem in most cases. To speed-up ; RAM access the WAIT-bit could be set to zero during ; times where only RAM access is made and set to 1 again ; when access to the LCD follows. ; Used Ports: The following ports are used, both by LCD and RAM ; access: ; Port A: Alternate use as LSB adress bus and data bus; not ; used during LCD access, but blocked by memory; mapped access ; Port C: MSB adress bus for SRAM access; used for LCD ; access: Bit 7 (Adress signal) and 6 (RS signal of ; the LCD) ; Port D: Bit 6 is /RD (Read) on the SRAM, not used by LCD ; access, Bit 7 is /WR (Write) on the SRAM and on the ; LCD ; ; Test program sequence: Sequentially the following steps are ; executed: ; 0. Wait until the LCD is not busy any more ; 1. Clear the LCD ; 2. Set the transfer mode to 8-bit, a fixed display window and ; define other display properties ; 3. Output to lines of text on the LCD window ; Before each operation takes place a LED is switched on ; to allow debugging the single steps. If all steps are executed ; correct LED 4 is on and the text on the LCD is visible ; (if not visible: correct contrast setting of the LCD). ; ; Structure of the software: All LCD operations are programmed as ; subroutines and so can be exported and used in other programs ; easily. Used registers are: mp=Allround register for handing ; values to subroutines; R26/27=XL/XH is a 16-bit-adress ; for SRAM/LCD-access commands ST X and LD X ; ; ; 8515-definitions load .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; Registers .def mp = R16 ; Multi-Purpose .def test = R17 ; Counts the different test phases ; Reset-/Interrupt-Vector rjmp main ; Subroutines for memory-mapped LCD access LcdWt: ; Wait until the LCD-busy-flag is zero ldi XH,0x80 ; Upper Byte of the RAM adress of the LCD ldi XL,0x00 ; Lower Byte of the RAM adress of the LCD ld mp,X ; Read busy flag and AC-Adress rol mp ; Shift bit 7 of the LCD status to the carry flag brcs LcdWt ; If one then LCD is still busy, repeat ret LcdCl: ; Clear the LCD ldi mp,0x01 ; Clear command of the LCD display = 01h LcdBef: ; LCD command in register mp to LCD, if ready push mp ; Command byte in mp is used later, save on stack rcall LcdWt ; Wait until display is not busy any more pop mp ; Recall command byte from stack st X,mp ; Send command to LCD ret ; End of the subroutine LcdInit: ; Init mode of the LCD ldi mp,0b00111000 ; 8-Bit-mode, not 4-Bit-mode rcall LcdBef ; command byte to the LCD ldi mp,0b00000110 ; Increment, display freeze rcall LcdBef ; commad byte to the LCD ldi mp,0b00010000 ; Cursor move, not shift rcall LcdBef ; command byte to the LCD ret ; Return from subroutine LcdBu: ; Write character in register mp on the LCD push mp ; Character used later, save on stack rcall LcdWt ; Wait until LCD is ready to receive pop mp ; Pop character from stack ldi XH,0xC0 ; LCD adress to register pair X st X,mp ; Write character to LCD ret ; Return from subroutine LcdTs: ; Write the word "Test" to the current LCD display line push mp ; Save the line adress in mp rcall LcdWt ; Wait until the LCD is ready pop mp ; Pop line Adress ori mp,0x80 ; Set bit 7 of the line adress rcall LcdBef ; and send to LCD ldi mp,'T' ; Load character T rcall LcdBu ; Write character to the LCD ldi mp,'e' ; Load character e rcall LcdBu ; Write to LCD ldi mp,'s' ; Load charcater s rcall LcdBu ; Write to LCD ldi mp,'t' ; Load character t rcall LcdBu ; Write to LCD ret ; Ready, return back LcdTst: ; Write "Test" to line 1 and 2 of the display ldi mp,0x00 ; Line 1 starts at adress 00h of the display rcall LcdTs ; Write test to line 1 ldi mp,0x40 ; Line 2 starts at adress 40h of the display rcall LcdTs ; Write test to line 2 ldi mp,0b00001111 ; Command for display On, Cursor On and Blink rcall LcdBef ; Command byte to LCD ret ; Return from subroutine ; Main program main: ldi mp,LOW(RAMEND) ;Initiate Stackpointer out SPL,mp ; for the use with subroutines ldi mp,HIGH(RAMEND) out SPH,mp ; Port B is the LED driver ldi mp,0xFF ; All outputs out DDRB,mp ; to data direction register of port B in mp,MCUCR ; Read MCU-Control-Register ori mp,0xC0 ; Set Bit 7 (SRAM) and Bit 6 (WAIT-STATE) out MCUCR,mp ; Here the test of the LCD starts ldi test,0xFE ; Set Bit 0 to 0 = LED 0 on out PORTB,test ; LED 0 on rcall LcdWt ; Wait until LCD isn't busy any more ; if an error happens here, the LED 0 won't go off sec ; Set Carry Flag to 1 rol test ; Shift the zero in test one position left to switch LED 1 on out PORTB,test rcall LcdCl ; Clear the LCD sec ; Set Carry flag to 1 rol test ; and shift test again one position left to switch LED 2 on out PORTB,test rcall LcdInit ; Some intiialisation of the LCD sec ; Set Carry Flag again to 1 rol test ; and shift test left to switch LED 3 on out PORTB,test rcall LcdTst ; Write the testlines sec ; Set Carry flag again to 1 rol test ; and shift test to set LED 4 on out PORTB,test; Test is successfuly completed ende: rjmp ende ; Loop for always http://www.avr-asm-tutorial.net/avr_en/source/TESTLCD.asm1/20/2009 7:56:47 PM
AVR-Hardware-Tutorial: SIO use
Path: Home => AVR-Overview => Hardware => SIO
; Tests the Serial Communication Port ; ; Sends a text over the serial communication port of an AVR with ; 9k6 8N1 and echoes incoming characters on the SIO port of an AVR. ;
; Hardware: Connection between the serial interfaces ; Windows serial communication with HyperTerminal (see text) ; or equivalent terminal program with ANSI ; .NOLIST .INCLUDE "8515def.inc" .LIST ;
;Reset-/Interrupt-Vector RJMP main ; main: OUT LDI OUT ;
LDI mpr,bdteiler ; Baudgenerator UBRR,mpr ; Divider set mpr,0b00011000 ; Enable TX and RX UCR,mpr ; to UART Control Register
; Send all capital letters ; LDI c,'A' ; first letter LDI nc,90-65+1 ; number of letters tloop: SBIS USR,UDRE ; Jump if transmit buffer empty RJMP tloop ; Wait a bit OUT UDR,c ; Letter to transmit buffer INC c ; next letter DEC nc ; Count letters to send BRNE tloop ; next letter ;
; Wait until character comes in, echo back forever ; rloop: SBIS USR,RXC ; Test RXC-bit for waiting char RJMP rloop ; no character available IN c,UDR ; Read char from UART rwait: SBIS USR,UDRE ; Wait until TX ready RJMP rwait ; TX not ready yet OUT UDR,c ; Send char CPI c,0x0D ; Return-char? BRNE rloop ; No Return, go on LDI c,0x0A ; Load Linefeed RJMP rwait ; Send additional Linefeed
; Test of the serial interface ; ; Sends a text on the serial interface using 9k6 8N1 and ; then echoes incoming charcaters ; ; Hardware: Connection between STK200 board an computer ; Windows-communication program HyperTerminal (see text) ; or equivalent terminal program with ANSI ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Constants ; .EQU fq=4000000 ; XTal frequency .EQU baud=9600 ; Baudrate .EQU bdteiler=(fq/(16*baud))-1 ; Baud-Divider ; ; Registers ; .DEF mpr=R16 ; Universal register .DEF nc=R17 ; Counter .DEF c=R18 ; Character ; ;Reset-/Interrupt-Vector rjmp main ; main: ldi mpr,bdteiler ; Baudgenerator out UBRR,mpr ; Set divider ldi mpr,0b00011000 ; Enable TX and RX out UCR,mpr ; to UART Control Register ; ; Send all Upcase characters ; ldi c,'A' ; first character ldi nc,90-65+1 ; Number of characters tloop: sbis USR,UDRE ; Jump if send buffer empty rjmp tloop ; Wait a bit out UDR,c ; Character to send buffer inc c ; next character dec nc ; Counter decrement brne tloop ; next character ; ; Wait until character received and echo forever ; rloop: sbis USR,RXC ; Test RXC-bit for waiting characters rjmp rloop ; None available, wait in c,UDR ; Get character from UART rwait: sbis USR,UDRE ; Wait until transmit buffer empty rjmp rwait ; Transmitter busy out UDR,c ; Send character cpi c,0x0D ; Return-character? brne rloop ; No Return, go on ldi c,0x0A ; Laad Linefeed rjmp rwait ; Send additional Linefeed http://www.avr-asm-tutorial.net/avr_en/source/TESTSIO.asm1/20/2009 7:56:52 PM
Hard- and Software for the communication of the STK-200 via the SIO/UART ========================================================================= 1. Hardware Required is a 9 pin male plug for the STK200-board connection and either a 25-pin female or an additional 9-pin plug for the computer's serial interface. The following three pins have to be connected within a 9-pin or a 25-pin connector to ensure proper signal levels for the board and the computer: 9-pin: 25-pin: Name of the line ---------------------------------------------Pin 4 = Pin 20 = Data Terminal Ready DTR Pin 8 = Pin 5 = Clear To Send CTS Pin 6 = Pin 6 = Data Set Ready DSR The following pins of the connectors have to be connected with a cable: 9-pin: 25-pin: Name of the line ---------------------------------------------Pin 2 = Pin 3 = Read Data RD Pin 3 = Pin 2 = Transmit Data TD Pin 5 = Pin 7 = Signal Ground SG That's it all for the hardware part.
2. Software Basically every terminal program is applicable for communication. Common in the Windows world is HyperTerminal, as it is supplied with Win32. The installation is as follows: a) In the Start-menu select PROGRAMS-HYPERTERMINAL. b) In the open folder select HYPERTRM.EXE. c) Select a name for the connection, e.g. STK200Sio and click Ok. d) In the call-window leave the number empty. Open the selection box CONNECT_VIA and select DIRECTCONNECTION_VIA_COMX. (X is usually COM2, if your mause is connected to COM1.) Select Ok. e) In the PROPERTIES-window select a baudrate of 9600 bps, 8 databits, no parity, 1 stopbit and protocol HARDWARE. f) In the white HyterTerminal-window you can communicate with the STK200 now. After switching the STK200 board on it sends an identification string, then echoes back all all characters you send to the board. For a real hard test send a ASCII text file to the board (TRANSMIT-TEXTFILE) g) By closing the Hyperterminal window answer the dialog with YES. Answering the following question, if the session should be stored, also with YES and you can use the same terminal properties when you connect the next time to your STK200 board. To open the next session you can simply click on the proper icon in the HyperTerminal folder.
3. Experiences with HyperTerminal At baudrates above 19200 no proper connection is possible. Non-US-ASCII characters are not properly handled. That's Windows. At the beginning of a line one char is lost, if you transfer a text file. This is due to the addition of a linefeed char after the STK200 has received a carriage return. There is not enough time to echo the next char then. (C)2002 by http://www.avr-asm-tutorial.net http://www.avr-asm-tutorial.net/avr_en/TESTSIO.txt1/20/2009 7:56:53 PM
SIOHEX echoes hex codes of received characters on a STK200 board
Path: Home => AVR-Overview => Hardware => SIO hex ; ; Test of the Serial IO ; ; Receives charactern from the SIO with 9k6 8N1 and sends back ; their hex values as text ; ; Hardware: Serial connection between the board and a terminal ; Communication: Terminal program, e.g. HyperTerminal ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Constants ; .EQU fq=4000000 ; XTal-frequency .EQU baud=9600 ; Baudrate .EQU bdteiler=(fq/(16*baud))-1 ; Baud-Divider .EQU RamStart = 0x0060 ; ; Register ; .DEF mpr=R16 ; Universal register .DEF cc=R17 ; Char copy .DEF h=R18 ; Various values ; ; XL/XH = R26/R27 are used as Pointer to the SRAM (input buffer position) ; YL/YH = R28/R29 are used as Pointer to the SRAM (output buffer position) ; ; Program code starts here ; .CSEG ; ; Reset-Vector ; RJMP main ; Reset-vector ; main: LDI XH,HIGH(RamStart) LDI XL,LOW(RamStart) LDI YH,HIGH(RamStart) LDI YL,LOW(RamStart) LDI mpr,0x0D ; Start with a new line ST X+,mpr ; put to the SRAM buffer and inc input pointer LDI mpr,0x0A ; additional linefeed ST X+,mpr LDI mpr,bdteiler ; Set baudrate generator OUT UBRR,mpr ; to divider port LDI mpr,0b00011000 ; Enable TX and RX OUT UCR,mpr ; to UART Control Register ; ; Main program loop asks UART for characters and transmits buffered chars in SRAM ; tloop: SBIC USR,RXC ; Jump if receiver is empty RJMP rx ; Receive the next char SBIC USR,UDRE ; Jump if the transmitter is not ready to receive chars RJMP tx ; Send next char RJMP tloop ; All over again ; ; Receive a char and store it in the SRAM buffer ; rx: LDI mpr,' ' ; Transmits a blank as separator ST X+,mpr ; Store it in the SRAM buffer and inc the pointer IN mpr,UDR ; Get a char from the UART receiver port MOV cc,mpr ; Make a copy of that char SWAP mpr ; Swap upper and lower nibble of the char ANDI mpr,0x0F ; Delete the upper nibble part CPI mpr,10 ; Nibble > 9? BRCS rx1 ; No LDI h,7 ; Add 7 to get hex A to F ADD mpr,h rx1: LDI h,'0' ; from 0 to '0' ADD mpr,h ST X+,mpr ; and copy to SRAM ANDI cc,0x0F ; Same procedure with the lower nibble CPI cc,10 BRCS rx2 LDI h,7 ADD cc,h rx2: LDI h,'0' ADD cc,h ST X+,cc LDI cc,'h' ; Send 'h' to signal hex ST X+,cc ; and copy to SRAM buffer RJMP tloop ; and return to the main program loop ; ; Send characters stored in the SRAM-buffer, if there are such waiting ; tx: CP XL,YL ; Compare input and output position BREQ tx1 ; No chars available LD mpr,Y+ ; Get a char from the SRAM and inc the output pointer OUT UDR,mpr ; Transfer this char to the transmitter port RJMP tloop ; and return back to the main program loop tx1: LDI XH,HIGH(RamStart) ; All sent, set the pointer to the beginning of the SRAM LDI XL,LOW(RamStart) LDI YH,HIGH(RamStart) LDI YL,LOW(RamStart) RJMP tloop ; and return to the main program loop ; ; End Of Code ;
; ; Test of the Serial IO ; ; Receives charactern from the SIO with 9k6 8N1 and sends back ; their hex values as text ; ; Hardware: Serial connection between the board and a terminal ; Communication: Terminal program, e.g. HyperTerminal ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Constants ; .EQU fq=4000000 ; XTal-frequency .EQU baud=9600 ; Baudrate .EQU bdteiler=(fq/(16*baud))-1 ; Baud-Divider .EQU RamStart = 0x0060 ; ; Register ; .DEF mpr=R16 ; Universal register .DEF cc=R17 ; Char copy .DEF h=R18 ; Various values ; ; XL/XH = R26/R27 are used as Pointer to the SRAM (input buffer position) ; YL/YH = R28/R29 are used as Pointer to the SRAM (output buffer position) ; ; Program code starts here ; .CSEG ; ; Reset-Vector ; rjmp main ; Reset-vector ; main: ldi XH,HIGH(RamStart) ldi XL,LOW(RamStart) ldi YH,HIGH(RamStart) ldi YL,LOW(RamStart) ldi mpr,0x0D ; Start with a new line st X+,mpr ; put to the SRAM buffer and inc input pointer ldi mpr,0x0A ; additional linefeed st X+,mpr ldi mpr,bdteiler ; Set baudrate generator out UBRR,mpr ; to divider port ldi mpr,0b00011000 ; Enable TX and RX out UCR,mpr ; to UART Control Register ; ; Main program loop asks UART for characters and transmits buffered chars in SRAM ; tloop: sbic USR,RXC ; Jump if receiver is empty rjmp rx ; Receive the next char sbic USR,UDRE ; Jump if the transmitter is not ready to receive chars rjmp tx ; Send next char rjmp tloop ; All over again ; ; Receive a char and store it in the SRAM buffer ; rx: ldi mpr,' ' ; Transmits a blank as separator st X+,mpr ; Store it in the SRAM buffer and inc the pointer in mpr,UDR ; Get a char from the UART receiver port mov cc,mpr ; Make a copy of that char swap mpr ; Swap upper and lower nibble of the char andi mpr,0x0F ; Delete the upper nibble part cpi mpr,10 ; Nibble > 9? brcs rx1 ; No ldi h,7 ; Add 7 to get hex A to F add mpr,h rx1: ldi h,'0' ; from 0 to '0' add mpr,h st X+,mpr ; and copy to SRAM andi cc,0x0F ; Same procedure with the lower nibble cpi cc,10 brcs rx2 ldi h,7 add cc,h rx2: ldi h,'0' add cc,h st X+,cc ldi cc,'h' ; Send 'h' to signal hex st X+,cc ; and copy to SRAM buffer rjmp tloop ; and return to the main program loop ; ; Send characters stored in the SRAM-buffer, if there are such waiting ; tx: cp XL,YL ; Compare input and output position breq tx1 ; No chars available ld mpr,Y+ ; Get a char from the SRAM and inc the output pointer out UDR,mpr ; Transfer this char to the transmitter port rjmp tloop ; and return back to the main program loop tx1: ldi XH,HIGH(RamStart) ; All sent, set the pointer to the beginning of the SRAM ldi XL,LOW(RamStart) ldi YH,HIGH(RamStart) ldi YL,LOW(RamStart) rjmp tloop ; and return to the main program loop ; ; End Of Code ; http://www.avr-asm-tutorial.net/avr_en/source/SIOHEX.asm1/20/2009 7:56:57 PM
AVR-Tutorial, Requirements
Path: Home => AVR-Overview => Tutorial
Tutorial for learning the assembler language of
AVR-Single-Chip-Processor AT90Sxxxx of ATMEL by programming practical examples.
Basic requirements
Targets The following lectures introduce to the assembly language of AVR-Single-chip-Processors of the AT90Sxxxx series by ATMEL. After going through these examples you should be able to write simple programs for these processors. The examples might be compiled with the ATMEL or any other assembler, the resulting binary code should be transferred to the board and started there. Every program step is commented.
Requirements All examples are compatible with the ATMEL-assembler. If you use other assemblers you probably have to change the code to meet their special syntax requirements. The examples run directy on the STK-200-programming board, as supplied by ATMEL. For all hardware-components on this board examples for their programming and use are supplied in additional examples. ATTENTION! All examples require the definition file "8515def.inc" in the same directory with the source code, otherwise numerous error messages occur during assebling. The file could be found in the home directory of the ATMEL software package or on ATMEL's homepage. Change the path in the INCLUDE line from folder X:\avrtools\appnotes\ to your path or copy this file into your source code folder.
AVR-Single-Chip-Processor AT90Sxxxx of ATMEL in practical examples. The following examples are small applications to test and use these processors practically and to demonstrate their usefulness. All applications were tested, but I cannot guarantee their correct function. HTMLFormat Clock
PcmDec
PwgSio
ASMFormat
Short description
Link
Clock
SIO controlled digital clock in a 2313. Could be synchronized with the time normal DCF77, operated on 77.5 kHz in the VLF band in Germany. Wiring see the Links.
GIF PDF
PcmDec
PCM-encoded remote control signals in a length from 0.8 to 2.2 ms are decoded using a AT90S2323 on a small test board and convert it to a analogue voltage of 0 to 5 Volts.
PcmDec
PwgSio
Pulse generator, generates exact signals of a desired duration and frequency, times are controlled by input from an ANSI-compatible terminal program via the SIO of the chip, e.g. on the STK200 board.
PwgSio
Signal generator with frequency and pulse-width Main program, adjustment, normal and inverted digital outputs, LCD routines, Zipped sources frequency/time/rpm/pulse-width display on LCD, applying an ATmega8 with ADC channels, Xtal Freq table source clock, etc.
RectGen
fcount_m8_v3 fcount_V03
Frequency counter with ATmega8, nine modes, 16 MHz xtal
Description Scheme Preamp
eggtimer_asm eggtimer
Eggtimer with an ATtiny2313V as a gift
eggtimer
steppermotor
Stepper motor controller and driver with an ATtiny13
Path: Home => AVR-Overview => Applications => Clock
XTal Clock on a AT90S2313, DCF synchronized ; *************************************************************** ; * DCF-synchronized Digital Clock for RS232 communication on * ; * a 2313-Experimental-Board, Version 0.2 as of 12.01.2001 * ; * Features: XTal driven digital clock for exact date and time * ; * information, adjusted and read over a SIO-I/O 9k6 8N1 * ; * connection, Self-adjusting date- and time-synchronisation * ; * to a connected receiver for the Frankfurt/Germany based * ; * official clock reference DCF77 (optional) * ; * (C)2001 by Gerhard Schmidt * ; * report bugs to info!at!avr-asm-tutorial.net * ; *************************************************************** ; ; Hardware requirements: ; - 2313 board (see extra doc) ; - RS232 compatible terminal, e.g. PC+Win+HyperTerminal to adjust/read ; the date and time informationen ; - (RXD/TXD, RTS/CTS)-crosswired RS232 cable connection between the ; 2313-board and the terminal ; - Optional: DCF77 clock with active low receiver signal (second ticks) ; ; Software features: ; - Interrupt-driven and buffered SIO-I/O with RTS/CTS hardware protocol ; - Interrupt-driven clock signals with exact timing ; - SLEEP mode for reduced power consumption of the MPU ; - Exact date calculation from 2001 up to the year 2099 ; - Transmits ANSI color codes for terminal control ; - DCF synchronisation: self-adjusting to unexact signal lengthes and ; spurious signals by software, loss-of-signal-detection, full parity bit ; checking, convenient hardware debugging opportunities by terminal display ; of the signal length and all detected errors during the sampling process ; .NOLIST .INCLUDE "C:\avrtools\appnotes\2313def.inc" .LIST ; ; Constants ; ; Constants for Sio properties .EQU fq=10000000; Xtal frequency on board in Hz .EQU baud=9600; Baudrate for SIO communication .EQU bddiv=(fq/(16*baud))-1; Baudrate divider .EQU ticks=fq/16000; ticks per second for the timing functions ; Constants for Sio communications .EQU ccr=0x0D; Carriage return character .EQU clf=0x0A; Line feed character .EQU cnul=0x00; NUL character .EQU cesc=0x1B; ESCAPE character .EQU cBs=0x08; Backspace character ; Bit assignment of the RTS and CTS pins on Port B .EQU bIRts = 2 .EQU bOCts = 4 ; Locations in the Internal SRAM .EQU sDTF = 0x60 ; Date/Time information, first location in SRAM .EQU dDDT = 0 ; relative distance, Tens of Days .EQU dDD = 1 ; relative distance, Days .EQU dDMT = 3 ; relative distance, Tens of Monthes .EQU dDM = 4 ; relative distance, Month .EQU dDY = 9 ; relative distance, Years .EQU dTHT = 12 ; relative disance, Tens of Hours .EQU dTH = 13 ; relative distance, Hours .EQU dTMT = 15 ; relative distance, Tens of Minutes .EQU dTM = 16 ; relative distance, Minutes .EQU dTST = 18 ; relative distance, Tens of Seconds .EQU dTS = 19 ; relative distance, Seconds .EQU sDTL = 0x74 ; Date/Time information, last location in SRAM .EQU sDTT = 0x6C ; Position of Time .EQU sSec = 0x73 ; Adress of seconds .EQU sSecT = 0x72 ; Adress of tens of seconds .EQU sMin = 0x70 ; Adress of minutes ; Constants for Sio Rx- and Tx-Buffers .EQU RxBuF = 0x75 ; Sio Rx-Buffer, first location in SRAM .EQU RxBuL = 0x84 ; Sio Rx-Buffer, last location in SRAM (16 Bytes) .EQU TxBuF = 0x85 ; Sio Tx-Buffer, first location in SRAM .EQU TxBuL = 0xA4 ; Sio Tx-Buffer, last location in SRAM (32 Bytes) ; ; Used registers ; ; Register mainly for Program Memory Read Operations .DEF rlpm = R0 ; Used for read operations with LPM ; SIO Tx Buffer In pointer .DEF rsiotxin = R1 ; Registers for the DCF77-Receiver option .DEF rdcfp = R2 ; Parity bit counter for DCF signals .DEF rdcf1 = R3 ; Last Receiver Shift Register .DEF rdcf2 = R4 ; Receiver Shift register .DEF rdcf3 = R5 ; Receiver Shift register .DEF rdcf4 = R6 ; Receiver Shift register .DEF rdcf5 = R7 ; First Receiver Shift Register .DEF rDcfCmp = R8 ; Compare length of DCF pulse (self-adjusted) .DEF rDcfLc = R9 ; Last count length detected .EQU cDcfCmpDflt = 125 ; Default length for DCF signals (selfadjusted) .DEF rDcfCnt = R10 ; DCF length count, interrupt driven .DEF rDcfL0 = R11 ; Distance of last short pulse from medium count length .DEF rDcfL1 = R12 ; Distance of last long pulse from medium count length ; For all purposes .DEF rmpr = R16 ; Multi-purpose register ; Low level ticker for seconds counting by interrupt (1.6 ms) .DEF rtckh = R17 ; MSB .DEF rtckl = R18 ; LSB ; DCF77 Tick register for signal length, driven by timer interrupt (1.6 ms) .DEF rDcfl = R19 ; Timer 0 flag register with the following bits: .DEF rdtf = R20 ; Date/Time Flag register .EQU bEos = 0 ; Bit 0: End of second reached .EQU mEos = 1 .EQU bMin = 1 ; Bit 1: Echo minutes only .EQU mMin = 2 .EQU bDTm = 2 ; Bit 2: DT mode active .EQU mDTm = 4 ; Bits used by DCF77: .EQU bDcfm = 3 ; Bit 3: DCF mode active .EQU mDcfm = 8 .EQU bDcfCtm = 4 ; Bit 4: DCF echo counters .EQU mDcfCtm = 16 .EQU bDcfSync = 5 ; Bit 5: DCF synch tickers at next second .EQU mDcfSync = 32 .EQU bDcfRdy = 6 ; Bit 6: DCF bit ready .EQU mDcfRdy = 64 .EQU bDcfOk = 7 ; Bit 7: DCF Signal is ok .EQU mDcfOk = 128 ; SIO flag register ; Bit 5: Unused ; Bit 6: Unused ; Bit 7: Unused .DEF rsioflg = R21 ; SIO flag register .EQU bEcho = 0 ; Bit 0: Echo all incoming characters .EQU mEcho = 1 .EQU bAddLf = 1 ; Bit 1: Insert LF after CR .EQU mAddLf = 2 .EQU bTxBAct = 2 ; Bit 2: Transmitter buffer active .EQU mTxBAct = 4 .EQU bTxAct = 3 ; Bit 3: Character transmission active .EQU mTxAct = 8 .EQU bRxCmp = 4 ; Bit 4: Rx-Line complete .EQU mRxCmp = 16 ; DCF error flag register .DEF rDcfErr = R22 ; Last DCF-error .EQU bDcfPem = 0 ; Bit 0: Parity error minute .EQU mDcfPem = 1 .EQU bDcfPeh = 1 ; Bit 1: Parity error hour .EQU mDcfPeh = 2 .EQU bDcfPed = 2 ; Bit 2: Parity error date .EQU mDcfPed = 4 .EQU bDcfCts = 3 ; Bit 3: Count too short .EQU mDcfCts = 8 .EQU bDcfCtl = 4 ; Bit 4: Count too long .EQU mDcfCtl = 16 .EQU bDcfSok = 5 ; Bit 5: Synchronisation ( not an error!) .EQU mDcfSOk = 32 .EQU bDcfEtr = 6 ; Bit 6: Error to be reported .EQU mDcfEtr = 64 .EQU bDcfAny = 7 ; Bit 7: Any error .EQU mDcfAny = 128 ; DCF shift register counter .DEF rDcfs = R23 ; Shift Register Bit Counter ; ; Code starts here ; .CSEG .ORG $0000 ; ; Reset- and Interrupt-vectors ; RJMP Start ; Reset-vector RJMP IInt0 ; External Interrupt Request 0 RJMP IInt1 ; External Interrupt Request 1 RJMP TCpt1 ; Timer/Counter1 Capture event RJMP TCmp1 ; Timer/Counter1 Compare match RJMP TOvf1 ; Timer/Counter1 Overflow RJMP TOvf0 ; Timer/Counter0 Overflow RJMP URxAv ; Uart Rx char available RJMP UTxDe ; Uart Tx data register empty RJMP UTxCp ; Uart Tx complete RJMP AnaCp ; Analog comparator ; ; ************** Interrupt service routines ******** ; ; External Interrupt 0: Started by a negative edge on the DCF input ; IInt0: PUSH rmpr IN rmpr,SREG SBRS rdtf,bDcfSync RJMP IInt01 CBR rdtf,mDcfSync ; Synchronize DCF and internal tickers CLR rtckl CLR rtckh IInt01: CLR rDcfl OUT SREG,rmpr POP rmpr RETI ; ; External Interrupt 1 : Started by a positive edge on the DCF input ; IInt1: PUSH rmpr IN rmpr,SREG CPI rDcfl,10 ; Exclude short signals BRCS IInt1r MOV rDcfCnt,rDcfl ; Store count length INC rDcfs SBR rdtf,mDcfRdy ; Flag received bit ready IInt1r: OUT SREG,rmpr POP rmpr RETI ; ; Timer/Counter 1, Capture event interrupt, not used ; TCpt1: RETI ; ; Timer/Counter 1, Compare match interrupt, not used ; TCmp1: RETI ; ; Timer/Counter 1, Overflow interrupt, not used ; TOvf1: RETI ; ; Timer/Counter 0, Overflow interrupt, used to count times ; TOvf0: PUSH rmpr ; Save Register rmpr LDI rmpr,6 ; Set Counter to 6 to int after 250 ticks OUT TCNT0,rmpr IN rmpr,SREG ; Save status register INC rtckl BRNE TOvf0a INC rtckh TOvf0a: CPI rtckl,LOW(ticks) ; End of second reached? BRNE TOvf0b CPI rtckh,HIGH(ticks) BRNE TOvf0b SBR rdtf,mEos ; Set End of second flag CLR rtckl CLR rtckh TOvf0b: INC rDcfl ; DCF77 counter tick BRNE TOvf0c DEC rDcfl TOvf0c: OUT SREG,rmpr ; Restore anything POP rmpr RETI ; ; Uart Rx Complete Interrupt ; URxAv: PUSH rmpr ; Save mpr register IN rmpr,SREG ; Save SREG PUSH rmpr SBIC USR,FE ; Framing error? RJMP URxAv2 IN rmpr,UDR ; Read Char SBRC rsioflg,bEcho RCALL siotxch ; Echo character CPI rmpr,cBs ; Backspace? BRNE URxAv0 CPI XL,RxBuF+1 ; Backspace BRCS URxAv3 DEC XL RJMP URxAv3 URxAv0: ST X+,rmpr ; Store in RX buffer CPI XL,RxBuL+1 ; End of buffer reached? BRCS URxAv1 LDI XL,RxBuF URxAv1: CPI rmpr,cCr ; End of input line? BRNE URxAv3 SBR rSioFlg,mRxCmp ; Set Line complete flag RJMP URxAv3 URxAv2: IN rmpr,UDR ; Clear Framing error bit URxAv3: POP rmpr ; Restore SREG OUT SREG,rmpr POP rmpr ; Restore rmpr RETI ; ; Uart Data register empty interrupt ; UTxDe: PUSH rmpr ; Save register IN rmpr,SREG ; Save status register PUSH rmpr CP YL,rsiotxin ; Compare Buffer In and Out BRNE UTxDeCh UTxDeOff: CBR rSioFlg,mTxBAct ; No more chars to send RJMP UTxDeRet UTxDeCh: SBIC PortB,bIRts ; RTS input ready? RJMP UTxDeOff LD rmpr,Y+ ; Read char OUT UDR,rmpr CPI YL,TxBuL+1 ; Check end of buffer BRCS UTxDeRet LDI YL,TxBuF ; Point to buffer start UTxDeRet: POP rmpr ; Restore status register OUT SREG,rmpr POP rmpr ; Restore register RETI ; ; Uart Tx complete interrupt ; UTxCp: PUSH rmpr IN rmpr,SREG CBR rsioflg,mTxAct ; Clear the flag (not used here) OUT SREG,rmpr POP rmpr RETI ; ; Analog comparator interrupt ; AnaCp: RETI ; ; ******* End of interrupt service routines *********** ; ; Sio service subroutines to be called from various sources ; ; ; Char in rmpr to Tx-Buffer ; siotxch: SBIC PortB,bIRts ; Send only, if RTS is active RET PUSH ZH PUSH ZL CLR ZH ; Point to TX buffer input position MOV ZL,rSioTxIn ST Z+,rmpr CPI ZL,TxBuL+1 ; End of buffer reached? BRCS siotxchx LDI ZL,TxBuF siotxchx: ; Wait here to avoid buffer overrun CP ZL,YL BREQ siotxchx CLI ; Enter critical situation, disable interrupts SBRC rsioflg,bTxBAct RJMP sioTxChy SBR rsioflg,mTxBAct OUT UDR,rmpr RJMP sioTxChn sioTxChy: MOV rsioTxIn,ZL sioTxChn: SEI ; End of critical situation POP ZL POP ZH CPI rmpr,cCr ; Add linefeeds after carriage return? BRNE sioTxChz SBRS rsioflg,bAddLf RJMP sioTxChz LDI rmpr,cLf RCALL sioTxCh LDI rmpr,cCr sioTxChz: RET ; ; Transmits a null-terminated text from memory that Z points to ; TxTxt: PUSH rlpm PUSH rmpr TxTxt1: LPM ; Read a char from the program memory at Z MOV rmpr,rlpm CPI rmpr,cnul ; End of text? BREQ TxTxtz RCALL siotxch ADIW ZL,1 RJMP TxTxt1 TxTxtz: POP rmpr POP rlpm RET ; ; Send date/time to SIO ; DispDT: RCALL DcfErr CLR ZH ; Send time info in SRAM to SIO LDI ZL,sDTF DispDT1: LD rmpr,Z+ ; Read from SRAM RCALL siotxch ; Transmit CPI rmpr,cCr ; Last char? BRNE DispDT1 RET ; ; Send a byte as decimal number to the SIO ; DispByte: RCALL siotxch ; preface LDI rmpr,' ' RCALL siotxch LDI rmpr,'=' RCALL siotxch LDI rmpr,' ' RCALL siotxch LDI ZH,100 ; send 100's SUB ZL,ZH BRCS DispByte1 LDI rmpr,'1' SUB ZL,ZH BRCS DispByte1 SUB ZL,ZH INC rmpr DispByte1: RCALL siotxch ADD ZL,ZH LDI ZH,10 ; send 10's SUB ZL,ZH BRCS DispByte3 LDI rmpr,'0' DispByte2: INC rmpr SUB ZL,ZH BRCC DispByte2 RJMP DispByte4 DispByte3: CPI rmpr,' ' BREQ DispByte4 LDI rmpr,'0' DispByte4: ADD ZL,ZH RCALL siotxch LDI rmpr,'0' ; send 1's ADD rmpr,ZL RCALL siotxch LDI rmpr,' ' RCALL siotxch RJMP siotxch ; ************** End of SIO subrourines ******************* ; ; ***************** Various subroutines ******************* ; ; DT mode active, display date/time ; DTModeX: SBRS rdtf,bMin ; Minutes only? RJMP DTModeX1 LDS rmpr,sSec ; End of minute? CPI rmpr,'0' BRNE DTModeX2 LDS rmpr,sSecT CPI rmpr,'0' BRNE DTModeX2 DTModeX1: RCALL DispDT ; Display date and time DTModeX2: RET ; Return to loop ; ; DCF mode active, display DCF characteristics ; DCFModeX: RCALL DcfErr ; Report any DCF77 errors first SBRC rdtf,bDcfCtm RJMP DCFModeX2 OR rDcfs,rDcfs ; Report DCF signals bitwise LDI rmpr,cCr BREQ DCFModeX1 DEC rDcfLc LDI rmpr,'1' CP rDcfLc,rDcfCmp BRCC DCFModeX1 DEC rmpr DCFModeX1: RJMP siotxch DCFModeX2: LDI rmpr,'b' ; Report signal number MOV ZL,rDcfs RCALL DispByte LDI rmpr,'c' ; Report detected signal length in ticks MOV ZL,rDcfLc RCALL DispByte LDI rmpr,'m' ; Report current discriminating value MOV ZL,rDcfCmp RCALL DispByte LDI rmpr,cCr RJMP siotxch ; ; Reports any DCF errors ; DcfErr: SBRS rDcfErr,bDcfEtr ; Any unreported errors? RET CBR rDcfErr,mDcfEtr LDI ZH,HIGH(2*TxtDcfErr) ; Error text intro LDI ZL,LOW(2*TxtDcfErr) RCALL TxTxt MOV rmpr,rDcfErr ANDI rmpr,0x3F DcfErr1: ADIW ZL,1 CLC ; Identify next error bit ROR rmpr BRCC DcfErr3 RCALL TxTxt DcfErr2: OR rmpr,rmpr ; No more bits set? BRNE DcfErr1 ANDI rDcfErr,0x80 LDI rmpr,cCr RJMP siotxch DcfErr3: ADIW ZL,1 ; Point to next text sequence LPM OR rlpm,rlpm BRNE DcfErr3 RJMP DcfErr2 ; ; DCF synchronisation ; Dcf: CLR rDcfs ; End of minute, clear bit counter SBR rDcfErr,(mDcfSOk | mDcfEtr) ; Set synch to be reported LDI rmpr,'0' CLR ZH LDI ZL,sSec ; Second ST Z,rmpr ST -Z,rmpr DEC ZL DEC ZL MOV rmpr,rDcf1 ; Minute ROR rmpr ROR rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST Z,rmpr MOV rmpr,rDcf2 ; Tens of minutes ROR rmpr MOV rmpr,rDcf1 ROR rmpr ROR rmpr SWAP rmpr ANDI rmpr,0x07 ORI rmpr,0x30 ST -Z,rmpr DEC ZL ; Hour MOV rmpr,rDcf2 ROR rmpr ROR rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST -Z,rmpr MOV rmpr,rDcf2 ; Tens of hours ROL rmpr ROL rmpr ROL rmpr ANDI rmpr,0x03 ORI rmpr,0x30 ST -Z,rmpr LDI ZL,sDTF+dDD ; Day MOV rmpr,rDcf3 ROR rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST Z,rmpr MOV rmpr,rDcf3 ; Tens of Days ROR rmpr SWAP rmpr ANDI rmpr,0x03 ORI rmpr,0x30 ST -Z,rmpr ADIW ZL,4 ; Month MOV rmpr,rDcf4 ROR rmpr ROR rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST Z,rmpr MOV rmpr,rDcf4 ; Tens of monthes SWAP rmpr ROR rmpr ROR rmpr ANDI rmpr,0x01 ORI rmpr,0x30 ST -Z,rmpr ADIW ZL,6 ; Years MOV rmpr,rDcf4 ROL rmpr MOV rmpr,rDcf5 ROL rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST Z,rmpr MOV rmpr,rDcf5 ; Tens of years ROL rmpr SWAP rmpr ANDI rmpr,0x0F ORI rmpr,0x30 ST -Z,rmpr RET ; ; Next second ; ChkDcf: ; Check DCF77 info complete CBR rdtf,mEos ; Clear seconds flag bit SBRC rdtf,bDcfOk ; Last DCF tick was ok? RJMP NewDcfSec SBR rdtf,mDcfSync ; Minute is over MOV rmpr,rDcfs ; Have all 59 DCF ticks been received? CPI rmpr,59 BRCS CntTooShort ; Less than 59 ticks BRNE CountTooLong ; More than 59 ticks SBRS rDcfErr,bDcfAny ; Any errors in parity? RJMP Dcf RJMP CntReset ; No DCF synch, clear all CountTooLong: SBRS rdtf,bDcfm ; DCF echo mode on? RJMP CntReset SBR rDcfErr,(mDcfCtl | mDcfEtr | mDcfAny) ; Set DCF error type RJMP CntReset CntTooShort: SBR rDcfErr,mDcfCts ; Set DCF error type OR rDcfs,rDcfs ; DCF shift register totally empty? BREQ CntReset SBR rDcfErr,(mDcfEtr | mDcfAny) ; Set error to report CntReset: CLR rDcfs ; Clear the DCF shift counter CBR rDcfErr,mDcfAny ; Clear the global DCF error bit NewDcfSec: CBR rdtf,mDcfOk ; Clear the DCF tick ok bit IncSec: CLR ZH ; Point to Date/Time info in SRAM LDI ZL,sDTF+dTS ; Second RCALL IncNmbr ; Next second and handle overflow CPI rmpr,60 ; end of minute? BRCC IncMin IncRet: RET IncMin: LDI rmpr,'0' ; Clear seconds ST Z,rmpr ST -Z,rmpr LDI ZL,sDTF+dTM ; Next minute RCALL IncNmbr CPI rmpr,60 ; End of the hour? BRCS IncRet IncHour: LDI rmpr,'0' ; Clear minutes ST Z,rmpr ST -Z,rmpr LDI ZL,sDTF+dTH ; Next hour RCALL IncNmbr CPI rmpr,24 ; End of the day? BRCS IncRet LDI rmpr,'0' ; Clear hours ST Z,rmpr ST -Z,rmpr IncDay: LDI ZL,sDTF+dDD ; Next day RCALL IncNmbr CPI rmpr,32 ; End of month? BRCC IncMonth CPI rmpr,31 ; End of month for short monthes BRNE ChkFeb LDI ZL,sDTF+dDM ; Get days RCALL GetByte SUBI rmpr,4 ; Before April? BRCS IncRet CPI rmpr,3 ; April or June? BRCS IncDay1 INC rmpr ; Later than June IncDay1: SBRC rmpr,0 ; Even month? RET RJMP IncMonth ; End of a short month ChkFeb: LDI ZL,sDTF+dDM ; Get current month RCALL GetByte CPI rmpr,2 ; February? BRNE IncRet LDI ZL,sDTF+dDY ; Get year RCALL GetByte ANDI rmpr,0x03 ; February with 29 days? BRNE ChkFeb1 LDI ZL,sDTF+dDD ; Get current day RCALL GetByte CPI rmpr,30 ; Long February ends with 29 RJMP ChkFeb2 ChkFeb1: LDI ZL,sDTF+dDD ; Short February, get actual day RCALL GetByte CPI rmpr,29 ; End of month? ChkFeb2: BRCS IncRet IncMonth: LDI ZL,sDTF+dDD ; Next month, clear days LDI rmpr,'1' ST Z,rmpr LDI rmpr,'0' ST -Z,rmpr LDI ZL,sDTF+dDM ; Next month RCALL IncNmbr CPI rmpr,13 ; End of the year? BRCS IncRet IncYear: LDI rmpr,'1' ; next year, clear month ST Z,rmpr LDI rmpr,'0' ST -Z,rmpr LDI ZL,sDTF+dDY ; Inc years by running into the following ; ; Inc Number at Z and Z-1 and return with the number in one byte ; IncNmbr: LD rmpr,Z ; Inc's a number in SRAM and its tens, if necessary INC rmpr CPI rmpr,'9'+1 BRCS IncNmbr1 LD rmpr,-Z INC rmpr ST Z+,rmpr LDI rmpr,'0' IncNmbr1: ST Z,rmpr ; ; Get byte from Z and Z-1 ; GetByte: LD rmpr,-Z ; Two digit number to binary, load first digit SUBI rmpr,'0' MOV rlpm,rmpr ; Multiply by 10 ADD rmpr,rmpr ADD rmpr,rmpr ADD rmpr,rlpm ADD rmpr,rmpr MOV rlpm,rmpr ; Store result in rlpm INC ZL ; Add second digit LD rmpr,Z SUBI rmpr,'0' ADD rmpr,rlpm RET ; **************** End of the subroutine section *************** ; ; ******************** Main program loop *********************** ; ; Main program routine starts here ; Start: CLI ; Disable interrupts LDI rmpr,RAMEND ; Set stack pointer OUT SPL,rmpr RCALL InitDT ; Init Date/Time-Info in SRAM RCALL InitIo ; Init the I/O properties RCALL InitSio ; Init the SIO properties RCALL InitDcf RCALL InitTimer0 ; Init the timer 0 RCALL InitAna ; Init the Analog comparator ; General Interrupt Mask Register ; External Interrupt Request 1 Enable ; External Interrupt Request 0 Enable LDI rmpr,0b11000000 OUT GIMSK,rmpr ; Timer/Counter Interrupt register ; Disable all TC1 Ints ; Enable TC0 Ints LDI rmpr,0b00000010 OUT TIMSK,rmpr ; Enable interrupts (Master Int Enable) SEI ; Enable all interrupts ; Master Control register settings ; Sleep Enable, Sleep Mode = Idle ; Ext Int 1 on rising edges ; Ext Int 0 on falling edges LDI rmpr,0b00101110 OUT MCUCR,rmpr Loop: SLEEP ; Sleep until interrupt occurs NOP ; needed to wakeup SBRS rdtf,bDcfRdy ; Check if DCF signal has ended RJMP ChkEos ; no, inc the seconds CBR rdtf,mDcfRdy ; DCF: clear active signal ended MOV rDcfLc,rDcfCnt CP rDcfCmp,rDcfLc ; Count parity information, is it a 1 or a 0? BRCC DcfPar INC rDcfp ; Count 1's DcfPar: CPI rDcfs,21 ; Start of minute information? BRCS DcfCrct BRNE DcfPar1 CLR rDcfp ; Clear parity counter RJMP DcfCrct DcfPar1: CPI rDcfs,29 ; Minute parity to check? BRNE DcfPar2 SBRC rDcfp,0 ; Parity even? SBR rDcfErr,(mDcfPem | mDcfEtr | mDcfAny) ; Parity error CLR rDcfp ; Clear parity counter RJMP DcfCrct DcfPar2: CPI rDcfs,36 ; Hour parity to check? BRNE DcfPar3 SBRC rDcfp,0 ; Even? SBR rDcfErr,(mDcfPeh | mDcfEtr | mDcfAny) ; Parity error CLR rDcfp ; Clear parity counter RJMP DcfCrct DcfPar3: CPI rDcfs,59 ; Date parity to check? BRNE DcfCrct SBRC rDcfp,0 ; Even? SBR rDcfErr,(mDcfPed | mDcfEtr | mDcfAny) ; Parity error CLR rDcfp ; Clear Parity counter DcfCrct: CP rDcfCmp,rDcfLc ; Compare DCF signal length with medium value ROR rDcf5 ; Shift the result into the DCF 40-bit storage ROR rDcf4 ROR rDcf3 ROR rDcf2 ROR rDcf1 SBR rdtf,mDcfOk ; Set the DCF signal ok bit SUB rDcfCnt,rDcfCmp ; Calc distance of signal length from medium value BRCS DcfCrct0 ; Underflow = short pulse? MOV rDcfL1,rDcfCnt ; Store this difference in the 1-lengthbyte MOV rmpr,rDcfL0 ; Has ever a 0-signal been received? CPI rmpr,0 BRNE DcfCrct2 http://www.avr-asm-tutorial.net/avr_en/CLOCK.html (1 of 2)1/20/2009 7:57:12 PM
XTal Clock on a AT90S2313, DCF synchronized
DcfCrctUp: INC rDcfCmp ; Only 1-signals received so far, adjust higher medium RJMP Loop DcfCrct0: COM rDcfCnt ; Underflow = Signal 0, negative to positive distance MOV rDcfL0,rDcfCnt ; Store the difference in the 0-lengthbyte OR rDcfl1,rDcfL1 ; Has ever been received a 1-signal? BRNE DcfCrCt2 DcfCrctDwn: DEC rDcfCmp ; All 0's, reduce medium value RJMP Loop DcfCrct2: CP rDcfL1,rDcfL0 ; Compare the differences of the last 0 and 1 received BREQ Loop BRCS DcfCrctDwn ; Adjust the medium value according to the difference RJMP DcfCrctUp ChkEos: SBRS rdtf,bEos ; End of a timer second? RJMP Loop2 RCALL ChkDcf ; Check if DCF is to sychronize SBRS rDTf,bDTm ; DT mode active? RJMP Loop1 RCALL DTModeX ; Output the results ModeOff: CPI XL,RxBuF ; In the time- and dcf-echo-mode only blanks are to be BREQ Loop ; reacted upon. Has a char been sent over the SIO? CLR XH ; Reset RX-buffer to the start LDI XL,RxBuF LD rmpr,X ; Read character CPI rmpr,' ' ; Check for BLANK BRNE StartLoop BLD rsioflg,bEcho ; Restore the echo bit CBR rDTf,(mDTm | mDcfm) ; Clear the mode bits RJMP CursorOn ; send cursor on the SIO Loop1: SBRS rdtf,bDcfm ; DCF mode active? RJMP Loop2 RCALL DCFModeX ; DCF mode execution RJMP ModeOff Loop2: SBRS rSioFlg,bRxCmp ; Line of chars from the SIO complete? RJMP Loop CBR rSioFlg,mRxCmp ; Clear line available bit SBI PortB,bOCts ; Set hardware CTS off CPI XL,RxBuF+1 ; Has only a carriage return been sent? BRNE Cmnd LDI ZH,HIGH(2*TxtIntro) ; Empty line, transmit help text LDI ZL,LOW(2*TxtIntro) RCALL TxTxt CursorOn: LDI ZH,HIGH(2*TxtCursor) ; Display cursor line again LDI ZL,LOW(2*TxtCursor) RCALL TxTxt CLR XH ; Set SIO-RX buffer to start LDI XL,RxBuF CBI PortB,bOCts ; Set hardware clear to send active StartLoop: RJMP Loop Cmnd: LDI ZH,HIGH(2*CmdTab) ; Line received, search for command in table LDI ZL,LOW(2*CmdTab) Cmnd1: CLR XH ; Receive buffer to start LDI XL,RxBuF Cmnd2: LPM ; Read a char from the command table ADIW ZL,1 LDI rmpr,cCr ; end of the command? CP rmpr,rlpm BRNE Cmnd4 LPM ; next byte in command table a Carriage return char? CP rmpr,rlpm BRNE Cmnd3 ADIW ZL,1 ; Jump over that Cmnd3: LPM ; Load the command adress from the tabel and push it to the stack ADIW ZL,1 PUSH rlpm LPM ADIW ZL,1 PUSH rlpm LDI ZH,HIGH(2*TxtYellow) ; Set terminal to different color LDI ZL,LOW(2*TxtYellow) RJMP TxTxt ; after transmit the return jumps to the command adress! Cmnd4: LD rmpr,X+ ; compare the next char in the RX buffer CP rmpr,rlpm BREQ Cmnd2 ; equal, compare next Cmnd5: LDI rmpr,cCr ; not equal, search for next command in table CP rmpr,rlpm ; search end of the current command name BREQ Cmnd6 LPM ; read until a carriage return in the command table is found ADIW ZL,1 RJMP Cmnd5 Cmnd6: LPM ; read over additional carriage returns CP rmpr,rlpm BRNE Cmnd7 ADIW ZL,1 RJMP Cmnd6 Cmnd7: ADIW ZL,2 ; ignore the following word (command adress) LPM LDI rmpr,cnul ; check if the end of tabel is reached CP rmpr,rlpm BRNE Cmnd1 LDI ZH,HIGH(2*TxtError) ; end of table, command is unknown LDI ZL,LOW(2*TxtError) RCALL TxTxt RJMP CursorOn ; receive next command line ; ************************* End of program loop ***************** ; ; ********************** Initialisation routines **************** ; ; Init the Date/Time-Info in the SRAM ; InitDT: LDI ZH,HIGH(2*DfltDT) ; Point Z to default value in program memory LDI ZL,LOW(2*DfltDT) CLR XH ; Point X to date/time info in SRAM LDI XL,sDTF CLR rmpr ; Test for NUL = end of default? InitDT1: LPM ; Read from program memory ADIW ZL,1 ; Inc Z pointer CP rmpr,rlpm ; Test for end BRNE InitDT2 RET InitDT2: ST X+,rlpm ; Copy to SRAM location and inc X pointer RJMP InitDT1 ; Next location ; ; Initialize the IO-properties ; InitIo: ; Pins on Port D used: ; Bit 0: Input RxD Sio ; 1: Output TxD Sio ; 2: Input External interrupt 0, DCF signal ; 3: Input External Interrupt 1, DCF signal ; 4: Output TO, not used ; 5: Output T1, not used ; 6: ICP Input Capture Pin, not used LDI rmpr,0b00110010 ; Port D Control OUT DDRD,rmpr ; Data direction register LDI rmpr,0b00001100 ; Pullup resistors on DCF input on to avoid glitches OUT PortD,rmpr ; on open inputs ; Pins on Port B: ; Bit 0: Input AIN2, not used ; 1: Input AIN1, not used ; 2: Input SIO incoming RTS signal ; 3: Output OC1, not used ; 4: Output SIO outgoing CTS signal ; 5: Input MOSI, programming interface ; 6: Input MISO, programming interface ; 7: Input SCK, programming interface LDI rmpr,0b00011000 ; Port B Control OUT DDRB,rmpr ; Data Direction Register LDI rmpr,0b00000000 ; No pullup resistors OUT PortB,rmpr RET ; ; Initialize the SIO properties ; InitSio: LDI rsioflg,0b00000011 ; Echo on, insert LF after CR on CLR XH ; Set Rx Buffer LDI XL,RxBuF LDI rmpr,TxBuF ; Set Tx Buffer In MOV rsiotxin,rmpr CLR YH ; Set Tx Buffer Out LDI YL,TxBuF LDI rmpr,bddiv ; Init baud generator OUT UBRR,rmpr ; set divider in UART baud rate register LDI rmpr,0b00011000 ; Enable TX and RX, disable Ints LDI ZL,0 ; Wait for 65 ms LDI ZH,0 InitSio1: SBIW ZL,1 BRNE InitSio1 LDI rmpr,0b11111000 ; Enable TX und RX 8 Bit and Ints OUT UCR,rmpr ; in UART Control Register CBI PortB,bOCts ; Set Clear to sent active RET ; ; Init the Timer 0 ; InitTimer0: CLR rmpr ; Stop the Timer OUT TCCR0,rmpr LDI rmpr,6 ; Start with 6 to get 250 steps @ 10 MHz and div=64 OUT TCNT0,rmpr ; to Timer register CLR rtckl ; Clear the low-level-ticker CLR rtckh CLR rdtf ; Clear the flags LDI rmpr,3 ; Divide Clock by 64 OUT TCCR0,rmpr ; to Timer 0 Control register RET ; ; Init the Analog comparator ; InitAna: ; Analog comparator is disabled and switched off LDI rmpr,0b00000000 OUT ACSR,rmpr RET ; ; Init DCF ; InitDcf: LDI rmpr,cDcfCmpDflt ; Set medium value to default value MOV rDcfCmp,rmpr CLR rDcfL0 ; Clear the distances CLR rDcfL1 CLR rDcfErr ; Clear the error flags RET ; *************** End of init routines ********************* ; ; ********************* Commands *************************** ; ; Command Table, holds the command text and the command adress ; CmdTab: .DB '?',cCr ; Display list of commands .DW Help .DB 'd','?',cCr,cCr .DW DateOut .DB 'd','s',cCr,cCr ; Mode echo seconds .DW DS .DB 'd','m',cCr,cCr ; Mode echo minutes .DW DM .DB 'd','a','t','e','=',cCr ; Set Date .DW Date .DB 't','i','m','e','=',cCr ; Set Time .DW Time .DB 'd','c','f','b',cCr,cCr ; Enter DCF bit pattern mode .DW DCFMode .DB 'd','c','f','c',cCr,cCr ; Enter DCF counter mode .DW DCFCntMode .DB cnul,cnul,cnul,cnul ; ; Display list of commands Help: LDI ZH,HIGH(2*TxtHelp) LDI ZL,LOW(2*TxtHelp) RCALL TxTxt RJMP CursorOn ; Display date and time DateOut: RCALL DispDT RJMP CursorOn ; Set mode to echo date and time every second DS: CBR rdtf,mMin RCALL DTMode RJMP CursorOn ; Set mode to echo date and time every minute only DM: SBR rdtf,mMin ; Enter Date/Time-Echo-Mode DTMode: SBR rdtf,mDTm ; Set the mode bit LDI ZH,HIGH(2*TxtDtm) ; Display the info text LDI ZL,LOW(2*TxtDtm) SetMode: RCALL TxTxt BST rsioflg,bEcho ; Store the echo bit and switch it off CBR rsioflg,mEcho CBI PortB,bOCts ; Clear to send active to receive characters CLR XH ; Set RX-buffer to start LDI XL,RxBuF RJMP Loop ; ; Enter DCFMode ; DCFMode: CBR rdtf,mDcfCtm ; clear the mode bit DCFMode1: SBR rdtf,mDcfm ; set echo mode LDI ZH,HIGH(2*TxtDcf) ; Display the info text LDI ZL,LOW(2*TxtDcf) RJMP SetMode ; ; Enter DCF counter echo mode ; DCFCntMode: SBR rdtf,mDcfCtm ; set the DCF echo mode bit RJMP DCFMode1 ; ; Set date ; Date: CLR ZH ; Point Z to Date in SRAM LDI ZL,sDTF Date1: LD rmpr,X+ ; Take over char from Rx-buffer CPI rmpr,cCr BREQ Date2 ST Z+,rmpr LD rmpr,Z ; End reached? CPI rmpr,',' BREQ Date2 CPI rmpr,cCr BRNE Date1 Date2: RCALL DispDT ; send date and time to SIO RJMP CursorOn ; Cursor on and back to loop ; ; Set time ; Time: CLR ZH ; Point Z to Time in SRAM LDI ZL,sDTT RJMP Date1 ; ; Texts to be displayed ; TxtIntro: .DB cesc,'[','0',59,'1',59,'3','7',59,'4','0','m'; Set screen colours .DB cesc,'[','H',cesc,'[','J' ; ANSI Clear screen .DB "Hello world!" .DB ccr,'T' .DB "his is the experimental 2313 board (C)DG4FAC at work. ? for help. " .DB cnul,cnul TxtCursor: .DB ccr,cesc,'[','3','2','m','*','>',cesc,'[','3','6','m',cnul TxtYellow: .DB cesc,'[','3','3','m',cnul TxtError: .DB ccr,cesc,'[','3','1','m' .DB "Error! Unknown command! " .DB cCr,cnul TxtHelp: .DB "List of commands" .DB ':',cCr .DB " d?: Display date/time." .DB cCr,' ' .DB " date=dd.yy.yyyy: Set date" .DB '.',cCr .DB " time=hh:mm:ss: Set time." .DB cCr,' ' .DB " ds: Display seconds" .DB '.',cCr .DB " dm: Display minutes." .DB cCr,' ' .DB " dcfb: DCF77 bit pattern echo." .DB cCr,' ' .DB " dcfc: DCF77 counter echo." .DB cCr,' ' .DB " ?: Display this list." .DB cCr,cNul TxtDtm: .DB "Date/Time echo. SPACE to leave" .DB '.',cesc,'[','3','5','m',cCr,cnul TxtDcf: .DB "DCF echo mode. SPACE to leave." .DB cesc,'[','3','4','m',cCr,cnul,cnul TxtDcfErr: .DB ' ',' ','D','C','F',':',' ',cNul .DB "Minutes wrong!" .DB ' ',cNul .DB "Hours wrong!" .DB ' ',cNul .DB "Date wrong" .DB '!',cNul .DB "Count too short!" .DB ' ',cNul .DB "Count too long" .DB '!',cNul .DB "Synchronisation." .DB cNul,cNul ; ; Default date and time ; DfltDT: .DB "11.01.2001, 23:12:58" .DB cCr,cNul ; ; End of code segment ; ; 1015 words, only a few left in the 2313. Enough for a copyright. ; Copyright: .DB " 2C00 1GDF4CA"
Path: Home => AVR-Overview => Applications => PCM-Decoder
AVR-Single-Chip-Processors AT90Sxxxx in practical examples.
Decoder for remote control signals 1. 2. 3. 4. 5.
General overview Details of remote control signal encoding Conversion of remote control signals to an analogue voltage Required hardware Program structure
General overview The decoder converts puls-encoded remote control signals, supplied on the input pin of a AT90S2323 processor (Port-Bit 0, Pin 5), to a pulse generator on the output (Port-Bit 1, Pin 6). By filtering this pulse generator output in a simple RC network a analogue signal results, which linearly rises with the pulse length of the applied PCM signal on the input. The analogue signal can be fed into a comparator and driver for remote controlled equipment like airplane models etc. If false signals are detected or if the input signal is missing a pre-defined default value is set on the analogue output. To detect this condition, a LED can be connected to Port-Bit 2, Pin 7, for testing purposes. The current version of the program is predefined to detect active high PCM signals of 0.8 to 2.2 ms duration and an overall signal length of 25 ms. The default value for the analogue output is set to be 50% (2.50 Volt) after a single failure. All parameters of the program can be re-adjusted for other purposes and timings. Only a few commented parameters in the source code have to be re-adjusted to fit to other timings. HTML-Format Source-code-Format Flow chart in GIF-Format Flow chart in PDF-Format Wiring in PDF-Format
Wiring in GIF-Format
To top of this document
Details on remote control signal encoding A remote control signal has to encode an analogue signal (e.g. a voltage sensed from a variable resistor) to a digital signal, which can be decoded by the receiver back to the analogue signal and drives a motor or other equipment. The analogue signal is first encoded to a pulse signal. The duration of that pulse varies with the input signal. If the voltage is zero, the signal is more or less exactly 0.8 ms long. If the voltage is on the upper end of the scale, the signal is 2.2 ms long. The information of the analog voltage is encoded into the pulse length, it is modulating the pulse length. These signals are called PCM-encoded. More time is between this and the next pulse: two pulses are 25 ms from each other, 40 pulses fit into one second and so a frequency of 40 Hz results. The pulse-encoded signal is then fed into a transmitter. This encodes the pulse a second time and modulates a high-frequency signal, which is transmitted over the antenna. A HF receiver decodes the received signal again and its output is the same PCMencoded signal that we modulated on the HF. To top of this document
Conversion to an analogue signal The pure PCM-encoded signal has to be converted first to be used further on. This stage is what the AT90S2323 is doing: he counts the length of the pulse, between 0.8 and 2.2 ms, and converts it to pulse width signal on the output. There are many other ways to do this, but these are more or less convenient and reliable. We leave the counting to a micro. The small AT90S2323 is clocked by a crystal oscillator, so he knows exactly what time it is. The only things he has to do is counting: the length of the input signal and the length of the output signal. Easy, but looking at the program below, the clock signal has to fit exactly. The AT90S2323 has no pulse width generator, so we do have to do this counting by software. Switching the pulse width generator on and off is smoothed by the external RC network, so a very smooth output signal results. To top of this document
Required hardware The required hardware is very lean (the PDF-file).
The Xtal is responsable for the whole timing of the processor, the two capacitors improve the oscillator reliablility. The RC combination on pin 1 provides the Reset signal for the processor. The PCM-encoded input signal is connected to pin 5 of the processor. The PWG output loads and unloads the capacitor and is smoothed by the resistor on the output pin 6. The error LED is connected to pin 7 and only required for testing purposes. To top of this document
; ******************************************************************* ; * PCM2PWG-Decoder for AT90S2323, Version 0.4 as of 20.12.2000 * ; * Translates pulse coded input to Pulse Width Generator Output * ; * (C)2000 by [email protected] * ; ******************************************************************* ; ; Currently adjusted for 800 to 2200 µs signals and 4 MHz. ; Adjust constants below to fit to other timings! ; ; VCC LED PWG PCM PCM-Inp: 25ms = 40 Hz cycle time ; Out Out Inp active high pulse encoded signal ; +-8----7----6----5--+ ; | | PWG-Out: 1.4 ms = 714 Hz cycle time ; O AT 90 S 2323 | active high for 0..1.4 ms ; | | ; +-1----2----3----4--+ LED-Out: Active low if error occurs ; RST XT1 XT2 GND ; ; ********* Input Signal PCM encoded ************************** ; ; Phase I II III IV V VI VII ; PCM 1---------------------------; Inp \\\\\ / \\\\\\\\\\\\ / ; 0----------------------------------; ; ********* Output Signal Pulse Width Generator *************** ; ; PWG 1 -----------------; Out / \ / ; 0---------------; Time µs |<--0..cCycl->|<-cCycl..0-->| 800 - 2200 µs: ; |<----------cCycl---------->| cCycl = 1400 µs ; ; ************************************************************* ; .NOLIST .INCLUDE "C:\avrtools\appnotes\2323def.inc" .LIST ; ; Used registers ; .DEF rmpr = R16 ; Multi Purpose register .DEF rdel = R17 ; Register for delay loops .DEF rpwc = R18 ; Counter for pulse width generator .DEF rpws = R19 ; Current setting of pulse width generator .DEF rpwn = R20 ; Next setting of the pulse width generator .DEF rtry = R21 ; Number of retries .DEF rctr = R22 ; Counter for active cycles .DEF rerr = R23 ; Number of failures ; ZL = R30 and ZH = R31 used for counting long times ; ; IO-Port-Bits ; .EQU pbmode = 0b00000110 ; Port B Output pins .EQU bIn = 0 ; Input pin is 0, active high .EQU bOut = 1 ; Output pin for PWG, pulse width generator .EQU bFail = 2 ; Output pin for failure LED, active low ; ; Constants (1 cycle = 6 µs = 24 clock cycles @ 4 MHz) ; ; Adjust the xtal frequency for other clocks ; .EQU cXtal = 4 ; Crystal frequency in MHz ; ; Adjust this cycle length for other total lengthes of the input signal ; .EQU cPulseDuration = 25000 ; Total pulse length in µs ; ; Adjust the timing for other active high encoder pulses ; .EQU cMinPulse = 800 ; Minimum pulse length in µs .EQU cMaxPulse = 2200 ; Maximum pulse length in µs ; ; Adjust this fallback value in % of total duration if errors or ; signal-losses occur, 0%: Output is low, 100%: output is high, ; 50%: Output is 50% pulse width ; .EQU cFallBackValue = 50 ; 50% pulse width ; ; The following are automatically calculated from the above, ; don't adjust these without knowing what you do! ; .EQU cUnitLength = 24 ; Clock cycles per unit loop .EQU cCycleLength = cUnitLength/cXtal ; Total cycle length in µs .EQU cCycl = cPulseDuration/cCycleLength ; Cycles in 25 ms .EQU cStep = (cMaxPulse-cMinPulse)/cCycleLength ; Active cycles .EQU cTolerance = cStep/10; Tolerated short/long signal length .EQU cMinSteps = cMinPulse/cCycleLength ; Start counting active pulse .EQU cMaxSteps = cMaxPulse/cCycleLength ; Stop counting active pulse .EQU cMin1 = cMinSteps-cTolerance-1 ; Unit loops that must be active high .EQU cTlp = cTolerance-1 ; Early low units that are tolerated as Zero .EQU cTla = cTolerance ; Late units tolerated as One .EQU cMin0 = (cCycl-cMaxSteps)*90/100 ; Units that must be inactive .EQU cFbck = 1 ; Number of false signals to tolerate before default is set .EQU cDflt = cstep*cFallbackValue/100 ; Default fallback value for PWG ; ; Macros for Error-LED-Out (Active low) ; .MACRO LedErrOn cbi PortB,bFail .ENDM .MACRO LedErrOff sbi PortB,bFail .ENDM ; ; Macros for delays ; .MACRO Delay1 ; Single delay step nop .ENDM ; .MACRO Delay2 ; Double delay step nop nop .ENDM ; .MACRO Delay3 ; Delay macro for multiples of 3 steps ldi rdel,@0 MD3: dec rdel brne MD3 .ENDM ; .MACRO Delay4 ; Delay Macro for multiples of 4 steps ldi rdel,@0 MD4: dec rdel nop brne MD4 .ENDM ; .MACRO MacPwm ; Macro for Pulse Width Generator dec rpwc ; 1 1 1 1 breq MAP2 ; 1 1 2 2 cp rpws,rpwc ; 1 1 brcs MAP1 ; 1 2 nop ; 1 cbi PortB,bOut; 2 rjmp MAP3 ; 2 MAP1: sbi PortB,bOut; 2 rjmp MAP3 ; 2 MAP2: mov rpws,rpwn ; 1 1 ldi rpwc,cstep; 1 1 sbi PortB,bOut; 2 2 nop ; 1 1 nop ; 1 1 MAP3: ;----------;9999 ;=========== .ENDM ; ; Start of main program, Init variables ; Start: ldi rpwc,cDflt ; Counter of the pwm ldi rpws,cDflt ; Current pwm value ldi rpwn,cDflt ; Next pwg value ; ; Init the Port B ; ldi rmpr,pbmode ; Set data direction mode out DDRB,rmpr ; on Port B sbi PortB,bIn ; Switch pull-up resistor on ; ; Start phase I : Wait until input is pulled low ; ; Set error condition until signal is correct ; PH1: macpwm ; 9 ldi rerr,cFbck; 1 ldi rpwn,cDflt; 1 lederron ; 2 delay3(3) ; 9 ; ; Set one cycle length for negative edge detection ; PH1a: ldi ZL,Low(cCycl); 1 ldi ZH,HIGH(cCycl);1 ; --; 24 ; === ; Wait until input is pulled low or timeout occurs ; PH1b: macpwm ; 9 9 9 sbis PinB,bIn ; 2 2 1 rjmp PH2 ; 2 sbiw ZL,1 ; 2 2 breq PH1c ; 1 2 delay4(2) ; 8 rjmp PH1b ; 2 ; --; 24 ; === ; Timed out with input high ; PH1c: ; (15) delay4(1) ; 4 ; ; Error occured with input high, count subsequent errors ; PH1d: ;(19 19) dec rerr ; 1 1 brne PH1a ; 2 1 ; ; Too many subsequent errors with input high, set error condition ; delay1 ; 1 rjmp PH1 ; 2 ; --; 24 ; === ; Start Phase II: Input is low, wait for pulse or timeout ; PH2: ; (12) delay3(2) ; 6 delay2 ; 2 rjmp PH2b ; 2 ; ; Error occured with input low, set error condition ; PH2a: macpwm ; 9 ldi rerr,cFbck; 1 ldi rpwn,cDflt; 1 lederron ; 2 delay3(3) ; 9 ; ; Set counter for time out during input low ; PH2b: ; (22 22) ldi ZL,LOW(cCycl); 1 1 ldi ZH,HIGH(cCycl); 1 1 ; -----; 24 24 ; ====== ; Wait until input goes high or time-out occurs ; PH2c: macpwm ; 9 9 9 9 sbic PinB,bIn ; 2 2 2 1 rjmp PH3 ; 2 sbiw ZL,1 ; 2 2 2 breq PH2d ; 1 2 2 delay4(2) ; 8 rjmp PH2c ; 2 ; --; 24 ; === ; Time out during input low, count subsequent errors ; PH2d: ; (15 15) delay4(1) ; 4 4 PH2e: ; (19 19) dec rtry ; 1 1 brne PH2b ; 1 2 nop ; 1 rjmp PH2a ; 2 ; --; 24 ; === ; Phase III: Input gone high, check for cMin1 phases if stable ; PH3: ; (12) delay3(3) ; 9 delay2 ; 2 ldi rtry,cMin1; 1 ; --; 24 ; === ; Wait until cMin1 phases passed with input high ; PH3a: macpwm ; 9 9 9 sbis PinB,bIn ; 2 2 1 rjmp PH3b ; 2 dec rtry ; 1 1 breq PH4 ; 1 2 delay3(3) ; 9 rjmp PH3a ; 2 ; --; 24 ; === ; Early low detected during cMin1-phase, error with input low ; PH3b: ; (12) delay3(1) ; 3 delay2 ; 2 rjmp PH2e ; 2 ; ; Phase IV: Input is high for cMin1, pre-tolerance time ; PH4: ;(14) delay4(2) ; 8 ldi rtry,cTlp ; 1 ldi rctr,cstep; 1 ; --; 24 ; === ; Wait for pre-tolerance time or input early low ; PH4a: macpwm ; 9 9 9 sbis PinB,bIn ; 2 2 1 rjmp PH7 ; 2 dec rtry ; 1 1 breq PH5 ; 1 2 delay3(3) ; 9 rjmp PH4a ; 2 ; --; 24 ; === ; ; Phase V: End of pre-tolerance has been reached, count down now ; PH5: ; (14) delay4(2) ; 8 delay2 ; 2 ; --; 24 ; === PH5a: macpwm ; 9 9 9 sbis PinB,bIn ; 2 2 1 rjmp PH7 ; 2 dec rctr ; 1 1 breq PH6 ; 1 2 delay3(3) ; 9 rjmp PH5a ; 2 ; --; 24 ; === ; Phase VI: End of count reached, tolerate overtime ; PH6: ; (14) delay3(3) ; 9 ldi rtry,cTla ; 1 ; --; 24 ; === ; Overtime for cTla, break if overtime or input low ; PH6a: macpwm ; 9 9 9 sbis PinB,bIn ; 2 2 1 rjmp PH7 ; 2 dec rtry ; 1 1 breq PH6b ; 1 2 delay3(3) ; 9 rjmp PH6a ; 2 ; --; 24 ; === ; Active signal too long, failure condition with input high ; PH6b: ; (14) delay3(1) ; 3 rjmp PH1d ; 2 ; ; Phase VII: Input inactive, check minimum inactive time ; ; PH7: ; (12) delay4(2) ; 8 delay2 ; 2 ldi ZL,LOW(cMin0); 1 ldi ZH,HIGH(cMin0); 1 ; --; 24 ; === PH7a: macpwm ; 9 9 9 sbic PinB,bIn ; 2 2 1 rjmp PH7b ; 2 sbiw ZL,1 ; 2 2 breq PH7c ; 1 2 delay4(2) ; 8 rjmp PH7a ; 2 ; --; 24 ; === ; Early end of minimum inactive time, failure condition with input high ; PH7b: ; (12) delay3(1) ; 3 delay2 ; 2 rjmp PH1d ; 2 ; ; End of minimum inactive time reached, set result, restart with input low ; PH7c: ; (15) mov rpwn,rctr ; 1 ldi rerr,cFbck; 1 lederroff ; 2 delay1 ; 1 rjmp PH2b ; 2 ; ; End of the program ; http://www.avr-asm-tutorial.net/avr_en/source/PCM2PWG4.asm1/20/2009 7:57:25 PM
Terminal controled signal generator
Path: Home => AVR-Overview => Applications => Signal generator => Source code ; ************************************************************** ; * Pulse Width Generator, programmable via Serial I/O 9k6 8N1 * * ; * Input cycle length in µs first, then active cycle in µs ; * Default cycle length is 25,000 µs, default active cycle is * * ; * 2,000 µs. Output is on Port D, Bit 2 * ; * Written for the STK200 board, AT90S8515 ; * (C)2000 by info!at!avr-asm-tutorial, error reports welcome * ; ************************************************************** ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Used registers ; .DEF rlpm=R0; Used for LPM commands .DEF rchar=R1; Character buffer for SIO communications .DEF rilo=R2; Low byte word input/active loop .DEF rihi=R3; High byte word input/active loop .DEF rjlo=R4; Low byte for multiplication/inactive loop .DEF rjhi=R5; High byte for multiplication/inactive loop ; .DEF rmpr=R16; A multipurpose register, byte/word .DEF rcl=R18; Desired cycle time, word .DEF rch=R19 .DEF ral=R20; Desired active time, word .DEF rah=R21 ; X=R26/27: Counter for Active time ; Y=R28/29: Counter for inactive time ; Z=R30/31: Pointer for program memory read operation ; ; Constants ; .EQU OutPort=PortD; Desired output port .EQU DataDir=DDRD; Data direction register of that port .EQU ActivePin=2; Desired output pin .EQU ModeControl=0b00000100; Control word for port .EQU cDefCyc=25000; Default cycle length in µs .EQU cDefAct=2000; Default active high length in µs .EQU fq=4000000; Xtal frequency on board in Hz .EQU baud=9600; Baudrate for SIO communication .EQU bddiv=(fq/(16*baud))-1; Baudrate divider .EQU ccr=0x0D; Carriage return character .EQU clf=0x0A; Line feed character .EQU cnul=0x00; NUL character .EQU cesc=0x1B; ESCAPE character .EQU cbel=0x07; Bell character ; ; Macro Check input value for default and transfer to desired register ; .MACRO Default MOV @0,rilo; Copy result to desired register pair MOV @1,rihi MOV rmpr,rilo; Test if input is zero OR rmpr,rihi BRNE nodef; Not zero, don't set default value LDI @0,LOW(@2); Set to default value LDI @1,HIGH(@2) nodef: .ENDM ; ; Code segment start ; .CSEG ; ; Reset- and interrupt-vectors, interrupts not used here ; RJMP Start; Reset vector RETI; Ext Int 0 RETI; Ext Int 1 RETI; Timer 1 Capt RETI; Timer 1 CompA RETI; Timer 1 CompB RETI; Timer 1 OVF RETI; Timer 0 OVF RETI; Serial Transfer Complete RETI; UART Rx Complete RETI; UART Data register empty RETI; UART Tx Complete RETI; Analog Comparator ; ; Subroutine for string transmit ; TxStr: SBIS USR,UDRE; Wait until transmit buffer empty RJMP TxStr LPM; Read next character from program memory AND rlpm,rlpm; NUL = end of string BRNE txsend RET txsend: LPM; Read the same char again OUT UDR,rlpm; Tx character read ADIW ZL,1; point to next char im memory RJMP TxStr ; ; Subroutine for receiving a number (word, 0..65535) ; RxWord: CLR rilo; Set buffer to zero CLR rihi rxw1: SBIS USR,RXC; Test if rx buffer empty RJMP rxw1; receive char not available, repeat IN rmpr,UDR; Get the char from the SIO OUT UDR,rmpr; Echo this character CPI rmpr,ccr; Return char = end of input BREQ rxwx SUBI rmpr,'0'; Subtract 48 BRCS rxwerr; not a decimal number, return wit carry set CPI rmpr,10; number >9? BRCS rxwok; legal decimal number rxwerr: LDI ZL,LOW(2*ErrorStr); Echo error string LDI ZH,HIGH(2*ErrorStr) RCALL TxStr SEC; Set carry flag, not a legal number RET rxwok: MOV rjlo,rilo; Copy word for multiplicaton MOV rjhi,rihi LSL rilo; Multiply with 2 = 2* ROL rihi BRCS rxwerr; Overflow, return with carry set LSL rilo; Multiply again by 2, = 4* ROL rihi BRCS rxwerr; Overflow, return with carry set ADD rilo,rjlo; Add copy, = 5* ADC rihi,rjhi BRCS rxwerr; Overflow, return with carry set LSL rilo; Multiply by 2, = 10* ROL rihi BRCS rxwerr; Overflow, return with carry set CLR rjhi; Add the decimal number ADD rilo,rmpr ADC rihi,rjhi BRCS rxwerr; Overflow, return with carry set RJMP rxw1; Get next character rxwx: SBIS USR,UDRE; Wait until transmit buffer empty RJMP rxwx LDI rmpr,clf; Transmit additional line feed char OUT UDR,rmpr CLC; Clear carry, no errors RET ; ; Start of program ; Start: ; ; Setup the stack for the use of subroutines ; LDI rmpr,HIGH(RAMEND); Stack setting to highest RAM adress OUT SPH,rmpr LDI rmpr,LOW(RAMEND) OUT SPL,rmpr ; ; Initiate Port D to output on Bit 2 ; LDI rmpr,ModeControl; Set output pin OUT DataDir,rmpr; to Data Direction register ; ; Initiate SIO communication ; LDI rmpr,bddiv; Set baud rate OUT UBRR,rmpr LDI rmpr,0b00011000; Enable TX and RX OUT UCR,rmpr ; ; Transmit the hello sequence ; hello: LDI ZH,HIGH(2*InitStr); Point Z to string LDI ZL,LOW(2*InitStr) RCALL TxStr ; ; Get value for total cycle length ; getcycle: LDI ZH,HIGH(2*CycleStr); Point Z to string LDI ZL,LOW(2*CycleStr) RCALL TxStr RCALL RxWord BRCS getcycle; Repeat if error Default rcl,rch,cDefCyc ; ; Get value for active cycle length ; getactive: LDI ZH,HIGH(2*ActiveStr); Point Z to string LDI ZL,LOW(2*ActiveStr) RCALL TxStr RCALL RxWord BRCS getactive; Repeat if error Default ral,rah,cDefAct ; ; Calculate counter value for active time ; MOV XL,ral; Calculate active time MOV XH,rah SBIW XL,5; at least 4 cycles BRCS getcycle; illegal active time ADIW XL,1; At least one cycle required MOV rilo,XL MOV rihi,XH ; ; Calculate counter value for inactive time ; MOV YL,rcl; Calculate inactive time MOV YH,rch SUB YL,XL; Subtract active time SBC YH,XH BRCS getcycle; Active time longer than cycle time SBIW YL,5; Subtract loop delays BRCS getcycle; Less than 3 loop cycles are illegal ADIW YL,1; minimum 1 loop MOV rjlo,YL MOV rjhi,YH LDI ZH,HIGH(2*WaitStr); Output operating string LDI ZL,LOW(2*WaitStr) RCALL TxStr ; ; Counting loop starts here, check char at SIO available? ; ctloop: SBI OutPort,ActivePin; Start active phase ActLoop: SBIW XL,1; 0.5 µs BRNE ActLoop; 0.5 µs SBIC USR,RXC; Test if rx buffer empty RJMP getcycle; get new cycle time CBI Outport,ActivePin; Start Inactive phase InactLoop: SBIW YL,1; 0.5 µs BRNE InactLoop; 0.5 µs MOV XL,rilo; Refresh counters MOV XH,rihi MOV YL,rjlo MOV YH,rjhi NOP NOP RJMP ctloop; start a new cycle ; ; Text strings for transmit, ANSI screen encoded! ; ErrorStr: .DB cbel,ccr,clf,cesc,'[','3','3','m' .DB "Error in input! Repeat input! " .DB ccr,clf,cnul,cnul ActiveStr: .DB cesc,'[','3','2','m','*' .DB "Input active time (default = 2,000):" .DB cesc,'[','3','1','m',' ' .DB cnul,cnul CycleStr: .DB cesc,'[','3','2','m','*' .DB "Input cycle time (default = 25,000):" .DB cesc,'[','3','1','m',' ' .DB cnul,cnul WaitStr: .DB cesc,'[','3','5','m','C' .DB "ontinued operation. " .DB ccr,clf,cesc,'[','3','7','m','E' .DB "nter new cycle length to stop and restart generator." .DB ccr,clf,cnul,cnul InitStr: .DB cesc,'[','0',59,'1',59,'3','7',59,'4','0','m'; Set screen colours .DB cesc,'[','H',cesc,'[','J' ; ANSI Clear screen .DB ccr,clf,ccr,clf .DB "Hello world!" .DB ccr,clf .DB "This is the pulse width generator at work." .DB ccr,clf .DB "All times in micro-seconds, accepted range 5..65535." .DB ccr,clf .DB "New value stops operation until all input is completed. " .DB ccr,clf,cnul,cnul ; Check: .DW check ; ; End of code segment ;
Path: Home => AVR-Overview => Applications => Signal generator
AVR-Single-Chip-Processor AT90Sxxxx of ATMEL in practical examples.
ANSI-terminal programmable signal generator This application provides a pulse generator for signals from 5 microseconds to 65,535 microseconds length and selectable high/low pulse length of any combination. The duration of the signal and the active high period duration are programmable providing these numbers (in microseconds) with an ANSI-compatible terminal program to the STK200 via the board's SIO connection. This application requires: ●
●
●
the STK200 board with a AT90S8515 on board; or alternatively: a 2313 with a SIO interface (re-adjustment of some parts of the source code is also required then!), a computer with a RS232 interface and an ANSI-compatible terminal program (like HyperTerminal for Windows), connecting the computer's SIO interface with the board's interface (with at least a two-wire connection).
Usage: 1. Start your terminal program and set the required parameters: COM x, 9600 Baud, 8 bits data, No parity, 1 stop-bit 2. Switch the board on; your terminal should turn to black background and show the welcome message, 3. Input the total length of the pulses in microseconds (5 to 65535) and press return, 4. Input the length of the active high part of the pulse in microseconds (5 to 65534) and press return, 5. Watch Port D, Bit 2 for the pulses! Code in HTML-Format Source code as .asm
Warning: ●
● ● ● ●
●
●
●
Changing the source code in the string area at the end of the code (from Label 'ErrorStr' downwards) can yield strange results due to two different serious compiler bugs! Strings always have to have an even number of chars! The number of byte or char constants on one line has also to be even! Don't mix Strings and byte or char constants on one line! Always place them in extra lines! Watch the list output of the compiler for messages stating "Garbage at end of line!". It is not a fatal error causing a break, but all labels behind this line might be corrupt! Compare the label 'Check' on the end of the compiler listing. If it points exactly to itself anything is ok with the strings. If it doesn't point to itself you sure ran into that bug and your terminal output and all following labels will be corrupted. While the above points result in erroneous label adresses the use of semicolons in strings causes another compiler bug: he ignores the part of the string behind that char on the same line. That bug is not easy to find, so either avoid the use of that char or place it as hex byte on an extra line, together with another char. The golden rule of string programming: ALWAYS place strings behind the end of your executable code. Otherwise you risk jumps to false labels and your AVR might do interesting things with the false code.
; ************************************************************** ; * Pulse Width Generator, programmable via Serial I/O 9k6 8N1 * ; * Input cycle length in µs first, then active cycle in µs * ; * Default cycle length is 25,000 µs, default active cycle is * ; * 2,000 µs. Output is on Port D, Bit 2 * ; * Written for the STK200 board, AT90S8515 * ; * (C)2000 by [email protected], error reports welcome* ; ************************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Used registers ; .DEF rlpm=R0; Used for LPM commands .DEF rchar=R1; Character buffer for SIO communications .DEF rilo=R2; Low byte word input/active loop .DEF rihi=R3; High byte word input/active loop .DEF rjlo=R4; Low byte for multiplication/inactive loop .DEF rjhi=R5; High byte for multiplication/inactive loop ; .DEF rmpr=R16; A multipurpose register, byte/word .DEF rcl=R18; Desired cycle time, word .DEF rch=R19 .DEF ral=R20; Desired active time, word .DEF rah=R21 ; X=R26/27: Counter for Active time ; Y=R28/29: Counter for inactive time ; Z=R30/31: Pointer for program memory read operation ; ; Constants ; .EQU OutPort=PortD; Desired output port .EQU DataDir=DDRD; Data direction register of that port .EQU ActivePin=2; Desired output pin .EQU ModeControl=0b00000100; Control word for port .EQU cDefCyc=25000; Default cycle length in µs .EQU cDefAct=2000; Default active high length in µs .EQU fq=4000000; Xtal frequency on board in Hz .EQU baud=9600; Baudrate for SIO communication .EQU bddiv=(fq/(16*baud))-1; Baudrate divider .EQU ccr=0x0D; Carriage return character .EQU clf=0x0A; Line feed character .EQU cnul=0x00; NUL character .EQU cesc=0x1B; ESCAPE character .EQU cbel=0x07; Bell character ; ; Macro Check input value for default and transfer to desired register ; .MACRO Default mov @0,rilo; Copy result to desired register pair mov @1,rihi mov rmpr,rilo; Test if input is zero or rmpr,rihi brne nodef; Not zero, don't set default value ldi @0,LOW(@2); Set to default value ldi @1,HIGH(@2) nodef: .ENDM ; ; Code segment start ; .CSEG ; ; Reset- and interrupt-vectors, interrupts not used here ; rjmp Start; Reset vector reti; Ext Int 0 reti; Ext Int 1 reti; Timer 1 Capt reti; Timer 1 CompA reti; Timer 1 CompB reti; Timer 1 OVF reti; Timer 0 OVF reti; Serial Transfer Complete reti; UART Rx Complete reti; UART Data register empty reti; UART Tx Complete reti; Analog Comparator ; ; Subroutine for string transmit ; TxStr: sbis USR,UDRE; Wait until transmit buffer empty rjmp TxStr lpm; Read next character from program memory and rlpm,rlpm; NUL = end of string brne txsend ret txsend: lpm; Read the same char again out UDR,rlpm; Tx character read adiw ZL,1; point to next char im memory rjmp TxStr ; ; Subroutine for receiving a number (word, 0..65535) ; RxWord: clr rilo; Set buffer to zero clr rihi rxw1: sbis USR,RXC; Test if rx buffer empty rjmp rxw1; receive char not available, repeat in rmpr,UDR; Get the char from the SIO out UDR,rmpr; Echo this character cpi rmpr,ccr; Return char = end of input breq rxwx subi rmpr,'0'; Subtract 48 brcs rxwerr; not a decimal number, return wit carry set cpi rmpr,10; number >9? brcs rxwok; legal decimal number rxwerr: ldi ZL,LOW(2*ErrorStr); Echo error string ldi ZH,HIGH(2*ErrorStr) rcall TxStr sec; Set carry flag, not a legal number ret rxwok: mov rjlo,rilo; Copy word for multiplicaton mov rjhi,rihi lsl rilo; Multiply with 2 = 2* rol rihi brcs rxwerr; Overflow, return with carry set lsl rilo; Multiply again by 2, = 4* rol rihi brcs rxwerr; Overflow, return with carry set add rilo,rjlo; Add copy, = 5* adc rihi,rjhi brcs rxwerr; Overflow, return with carry set lsl rilo; Multiply by 2, = 10* rol rihi brcs rxwerr; Overflow, return with carry set clr rjhi; Add the decimal number add rilo,rmpr adc rihi,rjhi brcs rxwerr; Overflow, return with carry set rjmp rxw1; Get next character rxwx: sbis USR,UDRE; Wait until transmit buffer empty rjmp rxwx ldi rmpr,clf; Transmit additional line feed char out UDR,rmpr clc; Clear carry, no errors ret ; ; Start of program ; Start: ; ; Setup the stack for the use of subroutines ; ldi rmpr,HIGH(RAMEND); Stack setting to highest RAM adress out SPH,rmpr ldi rmpr,LOW(RAMEND) out SPL,rmpr ; ; Initiate Port D to output on Bit 2 ; ldi rmpr,ModeControl; Set output pin out DataDir,rmpr; to Data Direction register ; ; Initiate SIO communication ; ldi rmpr,bddiv; Set baud rate out UBRR,rmpr ldi rmpr,0b00011000; Enable TX and RX out UCR,rmpr ; ; Transmit the hello sequence ; hello: ldi ZH,HIGH(2*InitStr); Point Z to string ldi ZL,LOW(2*InitStr) rcall TxStr ; ; Get value for total cycle length ; getcycle: ldi ZH,HIGH(2*CycleStr); Point Z to string ldi ZL,LOW(2*CycleStr) rcall TxStr rcall RxWord brcs getcycle; Repeat if error default rcl,rch,cDefCyc ; ; Get value for active cycle length ; getactive: ldi ZH,HIGH(2*ActiveStr); Point Z to string ldi ZL,LOW(2*ActiveStr) rcall TxStr rcall RxWord brcs getactive; Repeat if error default ral,rah,cDefAct ; ; Calculate counter value for active time ; mov XL,ral; Calculate active time mov XH,rah sbiw XL,5; at least 4 cycles brcs getcycle; illegal active time adiw XL,1; At least one cycle required mov rilo,XL mov rihi,XH ; ; Calculate counter value for inactive time ; mov YL,rcl; Calculate inactive time mov YH,rch sub YL,XL; Subtract active time sbc YH,XH brcs getcycle; Active time longer than cycle time sbiw YL,5; Subtract loop delays brcs getcycle; Less than 3 loop cycles are illegal adiw YL,1; minimum 1 loop mov rjlo,YL mov rjhi,YH ldi ZH,HIGH(2*WaitStr); Output operating string ldi ZL,LOW(2*WaitStr) rcall TxStr ; ; Counting loop starts here, check char at SIO available? ; ctloop: sbi OutPort,ActivePin; Start active phase ActLoop: sbiw XL,1; 0.5 µs brne ActLoop; 0.5 µs sbic USR,RXC; Test if rx buffer empty rjmp getcycle; get new cycle time cbi Outport,ActivePin; Start Inactive phase InactLoop: sbiw YL,1; 0.5 µs brne InactLoop; 0.5 µs mov XL,rilo; Refresh counters mov XH,rihi mov YL,rjlo mov YH,rjhi nop nop rjmp ctloop; start a new cycle ; ; Text strings for transmit, ANSI screen encoded! ; ErrorStr: .DB cbel,ccr,clf,cesc,'[','3','3','m' .DB "Error in input! Repeat input! " .DB ccr,clf,cnul,cnul ActiveStr: .DB cesc,'[','3','2','m','*' .DB "Input active time (default = 2,000):" .DB cesc,'[','3','1','m',' ' .DB cnul,cnul CycleStr: .DB cesc,'[','3','2','m','*' .DB "Input cycle time (default = 25,000):" .DB cesc,'[','3','1','m',' ' .DB cnul,cnul WaitStr: .DB cesc,'[','3','5','m','C' .DB "ontinued operation. " .DB ccr,clf,cesc,'[','3','7','m','E' .DB "nter new cycle length to stop and restart generator." .DB ccr,clf,cnul,cnul InitStr: .DB cesc,'[','0',59,'1',59,'3','7',59,'4','0','m'; Set screen colours .DB cesc,'[','H',cesc,'[','J' ; ANSI Clear screen .DB ccr,clf,ccr,clf .DB "Hello world!" .DB ccr,clf .DB "This is the pulse width generator at work." .DB ccr,clf .DB "All times in micro-seconds, accepted range 5..65535." .DB ccr,clf .DB "New value stops operation until all input is completed. " .DB ccr,clf,cnul,cnul ; Check: .DW check ; ; End of code segment ; http://www.avr-asm-tutorial.net/avr_en/source/PWGSIO2.asm1/20/2009 7:57:34 PM
Signal Generator ATmega8
Pfad: Home => AVR-Overview => Applications => Signal Generator => Source Main
Signal generator with ATmega8 - Source code Main program
; ; *************************************************** ; * Adjustable Rectangle Generator with an ATmega8 * ; * Frequency (0.25Hz..8MHz) and Pulse Width (0.00..* ; * 100.00%) variable, LCD display (selectable sing-* ; * le/dual line, 16..40 characters per line), dis- * ; * plays frequency or rounds per minute and pulse * ; * width in % * ; * Requires include files "rectgen_m8_table.inc" * ; * and "Lcd8_02WO_rec.inc" * ; * Version 1 as of April 21, 2006 * ; * (C)2006 by info!at!avr-asm-tutorial.net * ; *************************************************** ; .NOLIST .INCLUDE "m8def.inc" .LIST ; ; Debug infos ; .EQU dbgOn = 0 ; debugging on or off .IF dbgOn .EQU cAdc0 = 1000 ; ADC0 value .EQU cAdc1 = 511 ; ADC1 value .EQU cFlg = 0 ;.EQU cFlg = (1<1 sLcdL2: ; Line 2 of the LCD .BYTE cLcdLw .ENDIF ; ; ************************************************** ; R E S E T - and I N T - V E C T O R S ; ************************************************** ; ; Code segment .CSEG .ORG $0000 ; rjmp main ; Reset vector, jump to main loop reti ; INT0, not used reti ; INT1, not used reti ; TIMER2COMP, not used reti ; TIMER2OVF, not used reti ; TIMER1CAPT, not used reti ; TIMER1COMPA, not used reti ; TIMER1COMPB, not used reti ; TIMER1OVF rjmp TC0OvflwInt ; TIMER0OVF reti ; SPI, STC, not used reti ; USART, RXC, not used reti ; USART, UDRE, not used reti ; USART, TXC, not used rjmp AdcInt ; ADC reti ; EE_RDY, not used reti ; ANA_COMP, not used reti ; TWI, not used reti ; SPM_RDY, not used ; ; ************************************************** ; I N T - V E C T O R - R O U T I N E S ; ************************************************** ; ; TC0 Overflow Interrupt, starts ADC update cycle ; TC0OvflwInt: in rSreg,SREG ; save status dec rAdcC ; downcount start cycle counter brne TC0OvflwInt1 ldi rAdcC,30 ; restart counter ldi rimp,(1<16 ldi rmp,' ' ; add a blank st X+,rmp ldi rmp,'=' ; add = st X+,rmp rcall Dec32 ; write 10 millions .ELSE rcall Dec32 ; write ten millions ld rmp,-X ; read char last position cpi rmp,' ' ; is it a blank? brne L1Lcd00 ldi rmp,'=' ; add = st X,rmp L1Lcd00: adiw XL,1 .ENDIF rcall Dec32 ; write millions ldi rmp,c1000s tst R0 brne L1Lcd0a ldi rmp,' ' L1Lcd0a: st X+,rmp rcall Dec32 ; write 100,000's rcall Dec32 ; write 10,000's rcall Dec32 ; write 1,000's ldi rmp,c1000s tst R0 brne L1Lcd0b ldi rmp,' ' L1Lcd0b: st X+,rmp rcall Dec32 ; write 100's rcall Dec32 ; write 10's inc R0 ; set leading blanks off rcall Dec32 ; write 1's tst R8 ; display decimals? breq L1Lcd1 ldi rmp,cDecs ; set decimal char st X+,rmp rcall Dec32 ; write 0.1's ldi rmp,'0' add rmp,R8 ; write 0.01's st X+,rmp rjmp L1Lcd2 L1Lcd1: ldi rmp,' ' ; set decimals blank st X+,rmp st X+,rmp st X+,rmp L1Lcd2: .IF cLcdLw>16 ldi rmp,' ' ; add another blank st X+,rmp .ENDIF sbrc rFlg,bTime ; display time? rjmp L1Lcd4 ; yes sbrc rFlg,bRpm ; diplay RPM? rjmp L1Lcd3 ; yes ldi rmp,'H' ; display frequency st X+,rmp ldi rmp,'z' st X+,rmp rjmp L1Lcd5 L1Lcd3: ldi rmp,'p' ; display rpm st X+,rmp ldi rmp,'m' st X+,rmp rjmp L1Lcd5 L1Lcd4: ldi rmp,cLcdMicro st X+,rmp ldi rmp,'s' st X+,rmp L1Lcd5: .IF cLcdLw>16 ldi rmp,' ' mov R0,rmp ldi rmp,cLcdLw-19 L1Lcd6: st X+,R0 dec rmp brne L1Lcd6 .ENDIF ret ; ; Calculate next decimal digit of a 32-bit number ; Dec32: lpm R4,Z+ ; read next decimal number lpm R5,Z+ lpm R6,Z+ lpm R7,Z+ clr rmp Dec32a: cp R8,R4 ; compare the number with the decimal digit cpc R9,R5 cpc R10,R6 cpc R11,R7 brcs Dec32b sub R8,R4 sbc R9,R5 sbc R10,R6 sbc R11,R7 inc rmp ; inc result rjmp Dec32a ; repeat Dec32b: add R0,rmp ; add to leading blank flag subi rmp,-'0' ; add ASCII zero tst R0 ; still leading blanks? brne Dec32c ldi rmp,' ' ; set leading blank Dec32c: st X+,rmp ; store char in SAM buffer ret ; ; 32 bit decimal table for conversion ; DecTab32: .DB $00,$CA,$9A,$3B ; 1000 mrd .DB $00,$E1,$F5,$05 ; 100 mio .DB $80,$96,$98,$00 ; 10 mio .DB $40,$42,$0F,$00 ; 1 mio .DB $A0,$86,$01,$00 ; 100,000 DecTab16: .DB $10,$27,$00,$00 ; 10,000 .DB $E8,$03,$00,$00 ; 1,000 .DB $64,$00,$00,$00 ; 100 .DB $0A,$00,$00,$00 ; 10 ; ; Calculate and display time ; CalcTime: rcall GetRate ; read rate to R7:R6:R5:R4 brcc CalcTime1 ; timer is active, calc time rjmp L1Lcd ; timer is inactive, display 0 CalcTime1: mov R2,R4 ; move multiplicator to R7:R:R5:R4:R3:R2 mov R3,R5 mov R4,R6 mov R5,R7 clr R6 clr R7 clr R8 ; clear result in R13:R12:R11:R10:R9:R8 clr R9 clr R10 clr R11 clr R12 clr R13 ldi rmp,HIGH(cTMult) ; load multiplier to R1:R0 mov R1,rmp ldi rmp,LOW(cTMult) mov R0,rmp CalcTime2: lsr R1 ; shift next bit of multiplyer to carry ror R0 brcc CalcTime3 add R8,R2 ; add the multiplicator adc R9,R3 adc R10,R4 adc R11,R5 adc R12,R6 adc R13,R7 CalcTime3: lsl R2 ; multiply multiplicator by 2 rol R3 rol R4 rol R5 rol R6 rol R7 tst R0 ; check LSB for end of multiplication brne CalcTime2 tst R1 ; check MSB for end of multiplication brne CalcTime2 mov rmp,R8 ; keep lowest byte for rounding mov R8,R9 ; shift result right = divide by 256 mov R9,R10 mov R10,R11 mov R11,R12 tst R13 ; check for overflow breq CalcTime5 CalcTime4: ldi rmp,0xFF ; overflow, set to highest number mov R8,rmp mov R9,rmp mov R10,rmp mov R11,rmp rjmp L1Lcd ; and display CalcTime5: cpi rmp,0x80 brcs CalcTime6 ldi rmp,0x01 ; round up add R8,rmp ldi rmp,0x00 adc R9,rmp adc R10,rmp adc R11,rmp brcs CalcTime4 CalcTime6: rjmp L1Lcd ; ; Calculate pulse width, first multiply pulse duration by 10000, ; then divide the result by the total puse duration ; CalcPw: lds R7,sCmp ; Read active pulse duration to R10:R9:R8:R7 lds R8,sCmp+1 clr R9 clr R10 clr R0 ; clear multiplication result in R6:R5:R4:R2:R1:R0 clr R1 clr R2 clr R3 clr R4 clr R5 clr R6 ldi rmp,HIGH(10000) ; set R12:R11 to 10000 mov R12,rmp ldi rmp,LOW(10000) mov R11,rmp CalcPw1: lsr R12 ; shift next bit right to carry ror R11 brcc CalcPw2 ; zero shifted out add R0,R7 ; add the multiplicator adc R1,R8 adc R2,R9 adc R3,R10 CalcPw2: lsl R7 ; multiply by 2 rol R8 rol R9 rol R10 tst R11 ; check end of multiplication brne CalcPw1 ; go on tst R12 brne CalcPw1 ; go on lds R7,sCtc ; read CTC value to R9:R8:R7 lds R8,sCtc+1 clr R9 clr R10 ; clear result of division inc R10 clr R11 clr R12 clr R13 CalcPw3: lsl R0 ; shift left rol R1 rol R2 rol R3 rol R4 rol R5 rol R6 cp R4,R7 ; compare by divider cpc R5,R8 cpc R6,R9 brcs CalcPw4 ; don't sutract sub R4,R7 sbc R5,R8 sbc R6,R9 sec rjmp CalcPw5 ; shift a 1 in CalcPw4: clc ; shift a 0 in CalcPw5: rol R10 ; shift result in rol R11 rol R12 rol R13 brcc CalcPw3 lsl R3 ; round result rol R4 rol R5 rol R6 cp R4,R7 cpc R5,R8 cpc R6,R9 brcs L2Lcd ldi rmp,1 add R10,rmp ldi rmp,0 adc R11,rmp adc R12,rmp adc R13,rmp L2Lcd: mov R8,R10 mov R9,R11 mov R10,R12 mov R11,R13 ldi ZH,HIGH(2*DecTab16) ldi ZL,LOW(2*DecTab16) clr R0 .IF cLcdLn==1 ldi XH,HIGH(sLcdL1) ldi XL,LOW(sLcdL1) .ELSE ldi XH,HIGH(sLcdL2) ldi XL,LOW(sLcdL2) .ENDIF ldi rmp,'P' st X+,rmp ldi rmp,' ' st X+,rmp ldi rmp,'=' st X+,rmp rcall Dec32 ; write 100's rcall Dec32 ; write 10's inc R0 rcall Dec32 ; write 1's ldi rmp,cDecs ; write decimal separator st X+,rmp rcall Dec32 ; write 0.1's ldi rmp,'0' ; write 0.01's add rmp,R8 st X+,rmp ldi rmp,'%' st X+,rmp ldi rmp,' ' mov R0,rmp ldi rmp,cLcdLw-9 L2Lcd1: st X+,R0 dec rmp brne L2Lcd1 ret ; ; ************************************************** ; U P D A T E T I M E R V A L U E S ; ************************************************** ; ; Convert ADC values and set timer ; Convert: ldi ZH,HIGH(2*Datatable) ldi ZL,LOW(2*Datatable) add ZL,rAdc0L ; add ADC0 value adc ZH,rAdc0H add ZL,rAdc0L ; add ADC0 value again adc ZH,rAdc0H lpm R0,Z+ ; read table value lpm R1,Z sts sCtc,R0 ; copy to SRAM sts sCtc+1,R1 clr R2 ; clear for multiplication in R3:R2:R1:R0 clr R3 mov R4,rAdc1L ; copy ADC1 result to R5:R4 mov R5,rAdc1H clr R9 ; clear result in R9:R8:R7:R6 clr R8 clr R7 clr R6 Convert1: lsr R5 ; shift lowest bit to carry ror R4 brcc Convert2 ; bit is zero, don't add add R6,R0 adc R7,R1 adc R8,R2 adc R9,R3 Convert2: lsl R0 ; shift muliplicator one left rol R1 rol R2 rol R3 tst R4 ; test if multiplicator is zero brne Convert1 tst R5 brne Convert1 lsr R9 ; divide result by 2 ror R8 ror R7 lsr R9 ; divide result by 4 ror R8 ror R7 brcc Convert3 ldi rmp,1 ; round up add R7,rmp ldi rmp,0 adc R8,rmp Convert3: sts sCmp,R7 ; store in SRAM sts sCmp+1,R8 mov ZL,rAdc0L ; copy ADC0 to Z mov ZH,rAdc0H ldi XL,LOW(392) ldi XH,HIGH(392) ldi rmp,(1<<WGM13)|(1<<WGM12)|(1<
http://www.avr-asm-tutorial.net/avr_en/signalgen/rectgen_m8_v1_main.html (1 of 2)1/20/2009 7:57:40 PM
http://www.avr-asm-tutorial.net/avr_en/signalgen/rectgen_m8_v1_main.html (2 of 2)1/20/2009 7:57:40 PM
Signal Generator ATmega8
Pfad: Home => AVR-Overview => Applications => Signal Generator
Signal Generator with an ATmega8 This application demonstrates a digital signal generator with an ATMEL ATmega8 with the following properties:
● ● ● ● ● ● ● ● ● ● ●
Adjustabe frequency range: 0,25 c/s to 8 Mcs/s in 1024 steps Wide dynamic range of the frequency without mechanic switching Reprogramming of the frequency characteristics possible Adjustable pulse-width from 0.00 to 100.00% in 1024 steps Independant adjustment of pulse-width and frequency Xtal base frequency 16 Mcs/s for stable frequency Normal and inverted output Polarity change by switch (active low, active high) Selectable display: frequency, time, rounds per minute and pulse-width Suitable for single- and two-line LCDs with 16 to 40 characters per line English or german notation selectable
0. Content ●
●
●
●
1. Hardware ❍ 1.1 Processor section ❍ 1.2 ISP-Interface ❍ 1.3 LCD-Display ❍ 1.4 External connections ■ 1.4.1 Adjusting frequency and pulse-width ■ 1.4.2 Switches ■ 1.4.3 Output connections ❍ 1.5 Power supply ❍ 1.6 Hints on mounting the device 2. Use ❍ 2.1 Switches ❍ 2.2 Output signals 3. How the device works ❍ 3.1 Processor section ❍ 3.2 LCD display ❍ 3.3 ISP interface 4. Software ❍ 4.1 Prior to assembling ❍ 4.2 Frequency table ❍ 4.3 Switches ❍ 4.4 Commented source code
1 Hardware
Th
hardware consists of the AVR processor, the In-System-Programming (ISP) connector, the LCD and diverse external connections.
1.1 Processor part The processor is attached to the following external components. The operating voltage of 5 V is supplied via the pins 7 (+5 V) and 8 (0 V) and blocked by a ceramic capacitor. Pin 1 (= RESET-pin) is tied to the positive operating voltage. On the Xtal pins 9 und 10 (XT1, XT2) an Xtal of 16 MHz is connected. Disconnecting the internal RC clock source and activation of the Xtal clock generator is done by setting the respective fuses. Both Xtal inputs are equipped with ceramic capacitors of 22 pF to improve switching characteristics. The operating voltage for the AD converter is supplied over a LC network of a 22 µH coil and a capacitor of 100 nF on pin 20 (AVCC). As reference voltage AVCC is selected by software, so a capacitor to ground is connected to the AREF (pin 21).
1.2 ISP interface The ISP interface allows programming of the AVR in the completed system without removing the chip. ISP uses the port bits 5 (SCK), 4 (MISO), 3 (MOSI) and the RESET on pin 1. MISO is also used to drive a control signal of the LCD. Pinning of the 10-pole ISP connector is compatible with the ATMEL-/KANDA- standard. The LED connected to the ISP connector signals programming currently active.
1.3 LCD display As display a standard LCD is used. It is connected via a 14 pole parallel cable and connectors that fit to the 14-pole connection of the LCD. The control port of the LCD is connected to the port bits PB0 (LCD Enable) and PB4 (LCD RS input). LCD Enable is by default tied to ground over a resistor of 10 k to avoid spurious signals on that line during phases where the pin PB0 is not (yet) initialised (during startup and during ISP programming). LCD-R/W is tied to ground, because the display is not read. The data port of the LCD is connected to the port D of the AVR. Data transfer to the LCD uses the 8 bits parallel, no multiplexing takes place.
1.4 External connections With the exception of the LCD ad the operating voltage supply, that use separate connections, all other external components are connected via a single 14-pole connection. This has the advantage that the processor board can be easily disconnected and testet separately. The 14-pole cable can be separated to portions reaching the different external components systematically. 1.4.1 Frequency and pulse-width adjust The first four cables are connected to the operating voltage and the first two AD channel inputs of the AVR. The AD channels ADC0 (Frequency adjust) and ADC1 (Pulse-width) are connected to the middle of the variable resistors. The variable resistors are of the 10-turn-type and are connected to the operating voltage. Their nominal value and linearity are uncritical, because the real values are displayed on the LCD. Directly on the variable resistors the signal lines to the ADCs are blocked by capacitors to ground to avoid noise on the ADC inputs. 1.4.2 Switches The next five connections of the 14-pole cable are connected to the port inputs PC2 to PC5. The switch connected to PC5 is only necessary if a single-line LCD is used. Leave this line open, if you use a two-line LCD. All switches connect the port inputs with ground, if closed. The ports have software-activated pullup resistors and are so held on the operating voltage, if not pulled to ground. 1.4.3 Output connections The following three lines of the cable are connected to the outputs of the timer (normal and inverted). These have to be soldered to the CINCH output connectors. The output yield digital signals with the typical characteristics of the AVR output drivers and are directly coupled to the output connectors. The outputs are short-circuit protected, but are not protected against voltages applied externally.
1.5 Power supply The power supply provides a stabilised operating voltage of 5 V at a current of approximately 20 mA. The supply current is depending from the variable resistors. Processor and LCD require below 10 mA, so the whole generator can also be supplied by batteries. To use a small transformer with 6 V secondary voltage, a rectifier bridge with Schottky diodes and a low-drop regulator is used. With a transformer of 7,5 V secondary voltage or more these components can be exchanged to standard components. The charger capacitor has a relatively high capacity, it can be reduced. The two Tantal capacitors suppress instabilities of the regulator.
1.6 Hints on mounting the device
Putting the components together on a piece of experimental board is uncritical. The 14pole connection to the LCD is located to the left, the 14pole connection to the external components (variable resistors, switches, output connectors) to the right. For easy mounting the parallel cable can be separated into portions of four, five, three and two lines. Please don't forget the two capacitors on the middle of the variable resistors. The 10-pole ISP interface is not used very often, so it is not accessible on the outside of the casing. The power supply is located on the upper left of the plastic case.
2 Use After switching power on the LCD displays a message for about 2.5 seconds, showing the machine's function, the software version and the copyright, with the format depending from the available number of characters of the line. The machine the is ready to operate.
2.1 Switches The switch Time changes the display from frequency (switch open) to time (switch closed). Output of the frequency is in Hz (cs/s) with two decimals, output of the time is in microseconds. Both values are rounded and formatted with thousands separators. If frequency output is selected, the switch Rpm changes from frequency to rounds per minute (= 60 * f). If the output of time is selected, this switch is ignored. The switch Inv inverts the output signals by software without changing the output connector. If a single-line LCD is used, the switch Pulse causes the display to show the pulse-width in % instead of the frequency/time normally displayed. In case of a two-line LCD the pulse-width is permanently displayed on line 2.
2.2 Output signals The digital output signal is available in positive and inverted form on the two CINCH connectors. To avoid capacitive effects the lines should be short and not shielded.
3 How is it working? This section describes the functioning of the processor, of the ISP interface and the LCD display.
3.1 Processor portion The processor ATmega8 works with an external Xtal and the respective internal oscillator. Because the processor is shipped with an internal RC oscillator of 1 Mcs/s, the respective fuses must be set first to use the Xtal as clock source. The following fuse combinations must be used: ● ● ●
Reprogramming of these fuses can either be performed externally on a programming board (e.g. with a STK500 and the ATMEL Studio) or in the completed system with the ISP interface (e.g. with PonyProg2000). By programming the fuses with an STK500 the Xtal must be connected to the device, otherwise the Mega8 will not respond any more after the fuses are programmed. The fuses are correctly programmed by selecting one of the two last options of the device.
When using PonyProg please note that the fuses are displayed inverted. To have an orientation: by default CKSEL3..CKSEL0 are 0001 and SUT1.. SUT0 are 10. Read the fuses by pushing the Read-Button first. Of course, the Xtal should be mounted to the AVR before programming the fuses. CLKSEL and SUT1 fuses should be programmed like displayed here, SUT0 can be programmed to either 1 or 0. If you encounter problems during startup, then program this to 1 (SUT1:SUT0 = 11). The open switch inputs are at the start pulled to high by software activation of the internal pull-up resistors. If the swichtes are closed, the input lines are pulled to logic zero. The switch Pulse is only needed, if a single-line LCD is used. Signal generation is performed with the internal 16-bit timer/counter TC1 in the Fast-PWM-mode. The graph shows the function of TC1 in this mode and shows the parameters that affect the operation (blue). The clock
oscillator, controlled by an external Xtal, is divided by the TC1 prescaler either by 1, 8, 64, 256 or 1024 and drives the counter. When the counter reaches the TOP value, which was written by software to the double register ICR1, the counter is reset with the next clock cycle and the compare outputs OC1A (Portbit PB1, Pin 15) und OC1B (Portbit PB2, Pin 16) are activated. The frequency of the generator so is defined by ICR1. Depending from the selected value for the pulse-width of the signal, the compare values in the register pairs COMPA and COMPB are adjusted. If the counter reaches these compare values, the respective compare output is deactivated and stays so until the counter reaches TOP. The two outputs OC1A and OC1B are, by software, of different polarity. They produce inverted signals of the same duration. The switch Inv inverts this polarity by software. Frequency adjustment is done with the variable resistor attached to the ADC0 input. The resistor rings a voltage of between 0.000 and 5.000 V to the ADC0 (port PC0, pin 23). After conversion, a value between 0x00 and 0x1F results. This value is used to pick a respective value for TOP in ICR1 from a table with 1024 values (Lookup-Table, Include file rectgen_m8_table. inc). Depending from the value read from ADC0, the prescaler control byte for TC1 is also prepared. Both values are stored in SRAM until the update of TC1 takes place. Pulse-width adjustment is done with the second variable resistor and the voltage reaches ADC1 (port PC1, pin 24), which is also converted to a value between 0x00 und 0x1F. The TOP value is multiplied by this conversion value and then divided by 1024 to yield the compare value, next to be written to COMPA and COMPB. During the same cycle, the switch Inv is read and the resulting control value for the correct polarity of OC1A and OC1B is determined. With the parameters mentioned, the TC1 counter runs free without additional software overhead. In order to update these parameters the 8-bit timer/counter TC0 is used. The TC0 prescaler divides the system clock by 1024 and overflows after 256 prescaler pulses (@16Mcs/s: every 16,384 ms). TC0 interrupts and decrements a register, from 30. If this reaches zero (after 492 ms), the AD converter is connected to channel 0 and the first conversion is started. The ADC runs with a clock that is derived from the system clock and divided by 128. After the first ADC result is complete, the ADC interrupts the CPU. The interrupt service routine reads the result to a double register, sets a flag bit, muxes channel 1 to the ADC and starts the second conversion. If the result of the second conversion interrupts the CPU, this result is read, the ADC is switched off and a signal flag is set. The conversion of the ADC values and the update of TC1 is done asynchroneous in the main program loop, after the signal flag had been set by the interrupt service routine. The ADC values are converted and TC1 is programmed with the new parameters. After programming the TC1, the LCD is updated and the main program loop ends until the next TC0 overflow interrupt wakes up the CPU.
3.2 LCD display The LCD is connected to the 8-bit data port and the two conrol lines E(nable) and R(egister)S(elect). The R(ead)/W(rite)-line is permanently on Write, the whole timing control is done in correct timing loops. After program start and after a wait time for the internal init of the LCD the LCD is set to the following modes: ● ● ● ● ●
8-Bit interface Single or two line operation (depending from the values in the software) no display shift cursor off blinking off
Then, for 2.5 seconds a message is displayed. After each update of the TC1 timer the LCD is also updated. First the CTC value in ICR1 is multiplicated by the prescaler value (1, 8, 64, 256, 1024). I displaying of the frequency is selected, the clock frequency, multiplied by 100, is divided by this value to yield an integer representing the frequency with a resolution of 0.01 cs/s. If displaying of the time is selected (switch Time closed), the CTC value in ICR1 is multiplied by the prescaler value, and then multiplied by the factor 25.600.000.000 / clock (@16MHz: 1.600) to yield the time in microseconds (*100). If a two-line LCD is used, the value of frequency or time is displayed on line 1. In case of a single-line display, this is displayed only if the switch Pulse is open. The pulse-width is calculated by multiplying the compare value in COMPA or COMPB by 10,000 and dividing by the CTC value in ICR1. The resulting integer is the pulse-width in % with a resolution of 0.01%. This value is written on line 2 (on a two-line LCD) or written on line 1 (of a single-line LCD, if switch Pulse is closed). The display is updated approximately 2 times per second. At higher frequencies the frequency and pulse-width cannot be adjusted to accurate values due to the limited resolution of the 16-bit counter. This is recognised by the last two decimals being displayed. Due to the fact that the displayed numbers are calculated from the real values used in TC1, these numbers are correct.
3.3 ISP interface The ISP interface is for updating the software in circuit. The data and clock signals MOSI, MISO and SCK are accessed on the 1o-pole connector. Via the RESET line (port PC6, pin 1) the ISP interface brings the ATmega8 to the programming mode. After releasing RESET, the AVR restarts. The programming LED indicates an active programming cycle, is only active in case of programming. If not build in, or if a 6-pin connector is used instead of the 10-pin standard, this does not change functioning.
4 Software Te software is written exclusively in assembler language and divided into three different packages. Prior to assembling the source code a number of parameters has to be adjusted to optimise the hardware.
4.1 Prior to assembling 4.1.1 Frequency table The frequency table in the file rectgen_m8_table.inc has 1024 words for converting the selected voltage to the CTC value for ICR1. The values were calculated with a Spreadsheet and exported as an Include-Text-file. If you make changes in that table, please note that this may have consequences for the prescaler values. In that case you will have to readjust the prescaler values in the routine Convert in the file rectgen_m8_v1.asm (current values: 392, 225, 60 and 3). If changing the clock frequency of the Xtal oscillator the constant clock will have to be changed. In that case also the 5-byteconstants cDivFx and cDivUx have to be changed accordingly. These cannot be calculated by the assembler due to overflow problems. Note that changes in the clock frequency are automatically changing the LCD timing loops, no additional adjusts in the driver routine are necessary. The constants cLcdLw and cLcdLn define the connected LCD. Inproper settings might cause strange effects on the display. The constant cLcdMicro defines the micro character of the connected LCD. The default is not a micro but a u, because some displays do not have greek characters and do not use 0xE4 for that character. The constant cEn enables english thousands- and decimal-separators. 4.1.2 Diverse switches The switches Time, Rpm, Inv and Pulse can be placed to different pins, this can be re-defined with the constants pbTime, pbRpm, pbInv and pbPwm. The switches dbgon and dbghx are for debugging only. For correct function of the software these must be set to zero!
4.2 Commented source code The commented source code is available in HTML-format ● ● ●
Source code of the main program LCD drive routines Frequency table
http://www.avr-asm-tutorial.net/avr_en/fcount/fcount_m8_v3.html (4 of 4)1/20/2009 7:58:29 PM
40MHz-Frequency counter with ATmega8
Pfad: Home => AVR-Overview => Applications => Frequency counter
40MHz-Frequency counter with ATmega8 This application of an AVR describes a frequency counter with ATMEL's ATmega8 with the following properties:
● ● ● ●
● ● ●
Digital- and Analog-input, voltage measuring input Input stage with preamp and prescaler by 16 Display with a single- ode double-line LCD Nine measuring- and display modes, adjustable with a potentiometer: ❍ 0: Frequency measurement with prescaler, gate time 0,25 s, result in Hz ❍ 1: Frequency measurement without prescaler, gate time 0,25 s, result in Hz ❍ 2: Frequency measurement as period measurement with conversion, result in 0,01 Hz bzw. 0,001 Hz ❍ 3: Rounds per Minute, without prescaler, via period measurement and conversion, result in rpm ❍ 4: Period duration complete cycle, result in microseconds ❍ 5: Period duration high-period, result in microseconds ❍ 6: Period duration low-period, result in microseconds ❍ 7: Period portion high-period, result in 0,1% ❍ 8: Period portion low-period, result in 0,1% ❍ 9: Voltage measurement, result in 0,001 Volt The selection of mode 9 (potentiometer on the right) swiches frequency/time/period measurements off. SIO-Interface for connecting to a PC Xtal oscillator 16 MHz
0. Content ●
●
●
1. Hardware ❍ 1.1 Input unit ❍ 1.2 Processor unit ❍ 1.3 LCD unit ❍ 1.4 SIO-Interface ❍ 1.5 Hints for mounting 2. Operation ❍ 2.1 Potentiometer for mode selection ❍ 2.2 Voltage measurement 3. Software ❍ 3.1 Adjustments prior to assembling ❍ 3.2 Fuses ❍ 3.3 Commented source code
1 Hardware The frequency counter consists of an input unit (preamp and prescaler) and the processor unit.
1.1 Input unit
The input unit has an analogue and a digital input unit. The signal on the analogue input is amplified by a fast operation amplifier NE592. Its output signal is adjusted to the the input of a NAND 74HCT132. The digital input is directly fed into the NAND. Depending from a processor signal the output of the NAND is either transferred directly to the processor's counting port or is divided by 16 by a 74HCT93.
1.2 Processor unit
The processor unit is designed around the ATmega8. The processor is driven with an xtal of 16 MHz on its pins XTAL1 and XTAL2. The ceramic capacitors of 22 pF help to accelerate the oscillator during start-up. The supply voltage of 5 volts is attached to pin 8 (GND) and 7 (GND) and blocked by a ceramic concensator of 100 nF. Supply of the AD converter is by 5 volt over an inductor of 22 µH to pin 22, also blocked by a 100 nF condensator. The internal reference voltage is filtered by a film capacitor of 100 nF. The AD conversion input pin PC1 is attached to the potentiometer that selects the mode. The resistor of 100 k limits its output voltage to 2,5 V. If a certain mode shall be fixed, a trim potentiometer or a voltage divider with two resistors can be used. On the AD converter channel ADC0 (on PC0) is attached to a voltage divider for the input signal to measure input voltages. In the default case, a maximum of 5.12 V can be measured. By changing the voltage divider resistors, range and resolution can be easily changed. The input of the ADC is blocked against high frequency influence. The signals TXD and RXD make up the serial interface and attached to the driver IC MAX232. The electrolytic condensators on the MAX232 provide the necessary voltages for the serial RS232 interface. The serial signals are attached to a 10-pole connector that delivers a 9-pole standard serial connector. The signals CTS, DSR and CD are activated by resistors of 2k2 to the supply voltage. The I/O pin PC5 drives the prescaler. If high, the prescaler is disabled, if low the signal is divided by 16. The signal input from the preamp/prescaler is attached to the input INT0 (for edge detection) as well as to T0 (for counting signals). The port bits PB0 to PB3 serve the four data bits of the LCD. The Enable-input of the LCD is driven by PB5, its RS input by PB4. The Enable-input is via a 100 k resistor attached to GND to disable the input in case the port bit is inactive. The V0 input of the LCD is attached to a trim potentiometer of 10 k to adjust the contrast of the LCD. The port bits MOSI, SCK and MISO as well as the RESET signal are attached to a 10-pole connector, over which the processor can be programmed. The red LED is on if programming is active. Supply voltage lines are also attached to this connector. The RESET input is attached with a 10 k resistor to the supply voltage.
1.3 LCD display
The LCD is attached over a standard 14-pole connector to the processor unit. You can use LCDs with 8, 16 or more characters per line and single or double lines (adjustable via software).
1.4 SIO interface The counter has a SIO connection, so measuring data can be read and parameters can be written.
1.5 Hints for mounting The complete circuit was build on a breadboard of 10 * 5 cm and wired with enammelled copper.
Two of the srews for the LCD also fix the breadboard.
All components, including the supply and a 9 V battery fit into a small casing.
2. Operation Operation is very easy. A selection between analogue and digital input is not necessary, just attach the source to the required female connector. Just adjust the trim potentiometer to a setting where signals from the analogue input are measured, and where no false signals from the opamp confuse these measurements.
2.1 Potentiometer for mode selection The mode selector (potentiometer) selects the mode. If no input signals are present, and a mode based on signal duration measurement is selected, update of the display is not performed immediately but on the first detected edge. The display of results on a two-line-LCD with 16 characters is as follows: Mode
Measured
Method
Display format
0
Frequency
Counting, Prescaler=16 F=99,999,999 Hz
1
Frequency
Counting, Prescaler=1 f= 9,999,999 Hz
2
Frequency
Period duration
3
Rounds per minute Period duration
u= 9,999,999 rpm
4
Period duration
Period duration
t=99,999,999 us
5
High-period
Period duration
h=99,999,999 us
6
Low-period
Period duration
l=99,999,999 us
7
High-period portion Period duration
P=100.0%
8
Low-period portion Period duration
p=100,0%
9
Voltage
U=9.999V
AD-Conversion
v= 9,999.999 Hz
In a single-line LCD, the voltage is displayed only if mode 9 is selected. At less than 16 character per line, thousand separators and dimension are not displayed. Abbreviations for the measuring code are diplayed only if the displayed value does not require this space.
2.2 Volatge measurements The voltage on the input pin is measured and displayed four times per second.
3. Software The software is completely written in Assembler. Prior to assembling the source code the internal adjustments have to be made (see 3.1). During or after programming the hex code to the chip's flash the fuses have to be changed (see 3.2). Note that after setting the fuses, the chip is only accessible with an external xtal attached.
3.1 Adjustments prior to assembling The following adjustments have to be made on the source code file fcountV03.asm: ● ● ● ● ● ● ● ● ●
The switches debug and debugpulse must be set to 0. If a LCD is attached, cDisplay must be set to 1. If the attached LCD is 8-character-per-line cDisplay8 must be set to 1. For larger LCDs, cDisplay8 must be cleard to 0. If the attached LCD has a single line only, cDisplay2 must be set to 0. For two or more lines set this to 1. If the serial interface is attached and should be used, set cUart to 1. If the prescaler by 16 is connected to a different portbit than PC5, the symbols pPresc and pPrescD as well as bPresc is to be set accordingly. If the processor clock is different from 16 MHz, change cFreq accordingly. If the SIO's baudrate should be different from 9,600 bd, change cBaud. If the voltage divider for measuring voltages is not (1 M and 1 M), adjust cR1 and cR2 accordingly. If the displayed voltages are different from the input voltage, change cRin. Smaller values of cRin yield higher displayed voltages.
3.2 Fuses In its original form ATmega8 runs with its internal RC oscillator. To change this the fuses must be adjusted to the external xtal. The following adjustments shown are with ATMEL's Studio:
In PonyProg 2000 the same looks like this:
Please note: After changing the fuses, the ATmega8 only works with an xtal attached.
; ******************************************************* ; * Frequencycounter, RPM-Meter and Voltmeter * ; * for ATmega8 at 16 MHz crystal clock frequency * ; * with prescaler /1 or /16 * ; * Version 0.3 (C)2009 by info!at!avr-asm-tutorial.net * ; ******************************************************* ; .INCLUDE "m8def.inc" ; .EQU debug = 0 .EQU debugpulse = 0 ; ; Switches for connected hardware ; .EQU cDisplay = 1 ; LCD display connected .EQU cDisplay8 = 0 ; displays 8 characters per line instead of 16 .EQU cDisplay2 = 1 ; two line LCD display connected .EQU cUart = 1 ; Uart active ; attached prescaler on port C .EQU pPresc = PORTC ; prescaler by 16 output attached to port C .EQU pPrescD = DDRC ; data direction of prescaler .EQU bPresc = 5 ; bit 5 enables prescaler by 16 ; ; ================================================ ; Other hardware depending stuff ; ================================================ ; .EQU cFreq = 16000000 ; Clock frequency processor in cycles/s .IF cUart .EQU cBaud = 9600 ; If Uart active, define Baudrate .ENDIF .EQU bLcdE = 5 ; LCD E port bit on Port B .EQU bLcdRs = 4 ; Lcd RS port bit on Port B ; ; ================================================ ; Constants for voltage measurement ; ================================================ ; ; Resistor network as pre-divider for the ADC ; -------------------------------------; R1 R2(k) Meas Accur. MaxVoltage ; kOhm kOhm Volt mV/dig Volt ; -------------------------------------; 1000 1000 5,12 5 10 ; 1000 820 5,68 6 11 ; 1000 680 6,32 6 12 ; 1000 560 7,13 7 14 ; 1000 470 8,01 8 15 ; 1000 330 10,32 10 20 ; 1000 270 12,04 12 23 ; 1000 220 14,20 14 27 ; 1000 180 16,78 16 32 ; 1000 150 19,63 19 38 ; 1000 120 23,98 23 46 ; 1000 100 28,16 28 55 ; .EQU cR1 = 1000 ; Resistor between ADC input and measured voltage .EQU cR2 = 1000 ; Resistor between ADC input and ground .EQU cRin = 8250 ; Input resistance ADC, experimental ; ; Other sSoft switches ; .EQU cNMode = 3 ; number of measurements before mode changes .EQU cDecSep = '.' ; decimal separator for numbers displayed .EQU c1kSep = ',' ; thousands separator .EQU nMeasm = 4 ; number of measurements per second .IF (nMeasm<4)||(nMeasm>7) .ERROR "Number of measurements outside acceptable range" .ENDIF ; ; ================================================ ; Hardware connections ; ================================================ ; ___ ___ ; RESET |1 |_| 28| Prescaler divide by 16 output ; RXD |2 A 27| ; TXD |3 T 26| ; Time inp |4 M 25| ; |5 E 24| Mode select input, 0..2.56 V ; Count in |6 L 23| Voltage input, 0..2.56 V ; VCC |7 22| GND ; GND |8 A 21| AREF (+2.56 V, output) ; XTAL1 |9 T 20| AVCC input ; XTAL2 |10 m 19| SCK/LCD-E ; |11 e 18| MISO/LCD-RS ; |12 g 17| MOSI/LCD-D7 ; |13 a 16| LCD-D6 ; LCD-D4 |14 8 15| LCD-D5 ; |_________| ; ; ; ================================================ ; Derived constants ; ================================================ ; .EQU cR2c = (cR2 * cRin) / (cR2+cRin) .EQU cMultiplier = (641 * (cR1+cR2c))/cR2c ; used for voltage multiplication .EQU cMaxVoltage = 1024*cMultiplier/256 ; in mV .EQU cSafeVoltage = (cMaxVoltage * 5000) / 2560 .EQU cTDiv = 1000/nMeasm ; interval per measurement update ; calculating the CTC and prescaler values for TC1 (frequency measurement) .SET cCmp1F = cFreq/32 ; CTC compare value with counter prescaler = 8 .SET cPre1F = (1<<WGM12)|(1<2097120 .SET cCmp1F = cFreq/256 ; CTC compare value with counter prescaler = 64 .SET cPre1F = (1<<WGM12)|(1<16776960 .SET cCmp1F = cFreq/1024 ; CTC compare value with counter prescaler = 256 .SET cPre1F = (1<<WGM12)|(1<2040000 .SET cCmp2 = cFreq/32000 .SET cPre2 = (1<8160000 .SET cCmp2 = cFreq/64000 .SET cPre2 = (1<16320000 .SET cCmp2 = cFreq/128000 ; counter prescaler = 128 .SET cPre2 = (1< Presc2 => TC2 => CTC => rTDiv => ; ADC0 conversion => ADC1 conversion => bAdc-flag ; ; Main interval timer TC2 ; - uses TC2 as 8-bit-CTC, with compare interrupt ; - starts a ADC conversion ; - on ADC conversion complete: ; * store ADC result ; * convert ADC result ; * if a new counter result: convert this ; * if Uart connected and monitoring f/U: display on Uart ; * if LCD connected and display mode: display f/U result ; ; Operation at 16 MHz clock: ; cFreq => Prescaler/128 => CTC(125) => rTDiv(250) ; 16MHz => 125 kHz => 1 kHz => 4 Hz ; ; Frequeny counting modes (Mode = 0 and 1) ; - uses TC0 as 8-bit-counter to count positive edges ; - uses TC1 as 16-bit-counter to time-out the counter after 250 ms ; ; Timer modes (Mode = 2 to 8) ; - uses edge detection on external INT0 for timeout ; - uses TC1 as 16-bit-counter to time-out from edge to edge ; ; Voltage only (Mode = 9) ; - Timers TC0 and TC1 off ; - Timer TC2 times interval ; ; ============================================== ; Reset and Interrupt Vectors starting here ; ============================================== ; .CSEG .ORG $0000 ; ; Reset/Intvectors ; rjmp Main ; Reset rjmp Int0Int; Int0 reti ; Int1 rjmp TC2CmpInt ; TC2 Comp reti ; TC2 Ovf reti ; TC1 Capt rjmp Tc1CmpAInt ; TC1 Comp A reti ; TC1 Comp B rjmp Tc1OvfInt ; TC1 Ovf rjmp TC0OvfInt ; TC0 Ovf reti ; SPI STC .IF cUart rjmp SioRxcIsr ; USART RX .ELSE reti ; USART RX .ENDIF reti ; USART UDRE reti ; USART TXC rjmp AdcCcInt ; ADC Conv Compl reti ; EERDY reti ; ANA_COMP reti ; TWI reti ; SPM_RDY ; ; ============================================= ; ; Interrupt Service Routines ; ; ============================================= ; ; TC2 Compare Match Interrupt ; counts rTDiv down, if zero: starts an AD conversion ; TC2CmpInt: in rSreg,SREG ; save SREG dec rTDiv ; count down brne TC2CmpInt1 ; not zero, interval not ended ldi rimp,(1<
http://www.avr-asm-tutorial.net/avr_en/fcount/fcountV03.asm
rol XL rol XH sub rmp,rRes1 ; * 1000 sbc R0,rRes2 sbc rDelL,rRes3 sbc rDelH,rRes4 sbc XL,ZH sbc XH,ZH cp XL,rDiv1 ; overflow? cpc XH,rDiv2 cpc ZL,rDiv3 cpc ZH,rDiv4 brcc CalcPwO clr rRes1 ; clear result inc rRes1 clr rRes2 clr rRes3 clr rRes4 CalcPw1: ; dividing loop lsl rmp ; multiply by 2 rol R0 rol rDelL rol rDelH rol XL rol XH rol ZL rol ZH cp XL,rDiv1 ; compare with divisor cpc XH,rDiv2 cpc ZL,rDiv3 cpc ZH,rDiv4 brcs CalcPw2 ; smaller, roll zero in sub XL,rDiv1 ; subtract divisor sbc XH,rDiv2 sbc ZL,rDiv3 sbc ZH,rDiv4 sec ; roll one in rjmp CalcPw3 CalcPw2: clc CalcPw3: ; roll result rol rRes1 rol rRes2 rol rRes3 rol rRes4 brcc CalcPw1 ; roll on lsl rDelL ; round result rol XL rol XH rol ZL rol ZH cp XL,rDiv1 cpc XH,rDiv2 cpc ZL,rDiv3 cpc ZH,rDiv4 brcs CalcPw4 ldi rmp,1 ; round up add rRes1,rmp ldi rmp,0 adc rRes2,rmp adc rRes3,rmp adc rRes4,rmp CalcPw4: tst rRes4 ; check rel="nofollow"> 1000 brne CalcPwE tst rRes3 brne CalcPwE ldi rmp,LOW(1001) cp rRes1,rmp ldi rmp,HIGH(1001) cpc rRes2,rmp brcc CalcPwE clc ; no error ret CalcPwE: ; error sec ret ; ; Display the binary in R2:R1 in the form " 100,0%" ; DisplPw: ldi XH,HIGH(sResult) ldi XL,LOW(sResult) ldi rmp,' ' st X+,rmp st X+,rmp clr R0 ldi ZH,HIGH(1000) ldi ZL,LOW(1000) rcall DisplDecX2 ldi ZH,HIGH(100) ldi ZL,LOW(100) rcall DisplDecX2 ldi ZL,10 inc R0 rcall DisplDecX2 ldi rmp,cDecSep st X+,rmp ldi rmp,'0' add rmp,rRes1 st X+,rmp ldi rmp,'%' st X+,rmp .IF ! cDisplay8 ldi rmp,8 ldi ZL,' ' DisplPw1: st X+,ZL dec rmp brne DisplPw1 .ENDIF ret ; ; If the first characters in the result buffer are empty, ; place the character in ZL here and add equal, if possible ; DisplMode: ldi XH,HIGH(sResult+1) ; point to result buffer ldi XL,LOW(sResult+1) ld rmp,X ; read second char cpi rmp,' ' brne DisplMode1 ldi rmp,'=' st X,rmp DisplMode1: sbiw XL,1 ld rmp,X ; read first char cpi rmp,' ' brne DisplMode2 st X,ZL DisplMode2: ret ; ;================================================= ; Display binary numbers as decimal ;================================================= ; ; Converts a binary in R2:R1 to a digit in X ; binary in Z ; DecConv: clr rmp DecConv1: cp R1,ZL ; smaller than binary digit? cpc R2,ZH brcs DecConv2 ; ended subtraction sub R1,ZL sbc R2,ZH inc rmp rjmp DecConv1 DecConv2: tst rmp brne DecConv3 tst R0 brne DecConv3 ldi rmp,' ' ; suppress leading zero rjmp DecConv4 DecConv3: subi rmp,-'0' DecConv4: st X+,rmp ret ; ; Display fractional number in R3:R2:(Fract)R1 ; DisplFrac: ldi XH,HIGH(sResult) ldi XL,LOW(sResult) .IF ! cDisplay8 ldi rmp,' ' st X+,rmp st X+,rmp .ENDIF clr R0 ldi ZH,HIGH(10000) ldi ZL,LOW(10000) rcall DisplDecY2 ldi ZH,HIGH(1000) ldi ZL,LOW(1000) rcall DisplDecY2 .IF ! cDisplay8 ldi rmp,c1kSep tst R0 brne DisplFrac0 ldi rmp,' ' DisplFrac0: st X+,rmp .ENDIF ldi ZL,100 rcall DisplDecY1 ldi ZL,10 rcall DisplDecY1 ldi rmp,'0' add rmp,R2 st X+,rmp tst R1 ; fraction = 0? brne DisplFrac1 ldi rmp,' ' st X+,rmp ldi rmp,'H' st X+,rmp ldi rmp,'z' st X+,rmp .IF ! cDisplay8 ldi rmp,' ' st X+,rmp st X+,rmp st X+,rmp st X+,rmp .ENDIF ret DisplFrac1: ldi rmp,cDecSep st X+,rmp .IF cDisplay8 ldi ZL,2 .ELSE ldi ZL,3 .ENDIF DisplFrac2: clr rRes3 clr rRes2 mov R0,rRes1 ; * 1 lsl rRes1 ; * 2 adc rRes2,rRes3 lsl rRes1 ; * 4 rol rRes2 add rRes1,R0 ; * 5 adc rRes2,rRes3 lsl rRes1 ; * 10 rol rRes2 ldi rmp,'0' add rmp,rRes2 st X+,rmp dec ZL brne DisplFrac2 .IF ! cDisplay8 ldi rmp,' ' st X+,rmp ldi rmp,'H' st X+,rmp ldi rmp,'z' st X+,rmp ldi rmp,' ' st X+,rmp .ENDIF ret ; ; Convert a decimal in R4:R3:R2, decimal in ZH:ZL ; DisplDecY2: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; overflow byte DisplDecY2a: cp rRes2,ZL cpc rRes3,ZH cpc rRes4,rDiv2 brcs DisplDecY2b ; ended sub rRes2,ZL ; subtract sbc rRes3,ZH sbc rRes4,rDiv2 inc rDiv1 rjmp DisplDecY2a DisplDecY2b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecY2c ldi rmp,' ' DisplDecY2c: st X+,rmp ret ; ; Convert a decimal decimal in R:R2, decimal in ZL ; DisplDecY1: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; overflow byte DisplDecY1a: cp rRes2,ZL cpc rRes3,rDiv2 brcs DisplDecY1b ; ended sub rRes2,ZL ; subtract sbc rRes3,rDiv2 inc rDiv1 rjmp DisplDecY1a DisplDecY1b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecY1c ldi rmp,' ' DisplDecY1c: st X+,rmp ret ; ; Display a 4-byte-binary in decimal format on result line 1 ; 8-bit-display: "12345678" ; 16-bit-display: " 12.345.678 Hz " ; Displ4Dec: ldi rmp,BYTE1(100000000) ; check overflow cp rRes1,rmp ldi rmp,BYTE2(100000000) cpc rRes2,rmp ldi rmp,BYTE3(100000000) cpc rRes3,rmp ldi rmp,BYTE4(100000000) cpc rRes4,rmp brcs Displ4Dec1 rjmp CycleOvf Displ4Dec1: clr R0 ; suppress leading zeroes ldi XH,HIGH(sResult) ; X to result buffer ldi XL,LOW(sResult) .IF ! cDisplay8 ldi rmp,' ' ; clear the first two digits st X+,rmp st X+,rmp .ENDIF ldi ZH,BYTE3(10000000) ; 10 mio ldi ZL,BYTE2(10000000) ldi rmp,BYTE1(10000000) rcall DisplDecX3 ldi ZH,BYTE3(1000000) ; 1 mio ldi ZL,BYTE2(1000000) ldi rmp,BYTE1(1000000) rcall DisplDecX3 .IF ! cDisplay8 ldi rmp,c1kSep ; set separator tst R0 brne Displ4Dec2 ldi rmp,' ' Displ4Dec2: st X+,rmp .ENDIF ldi ZH,BYTE3(100000) ; 100 k ldi ZL,BYTE2(100000) ldi rmp,BYTE1(100000) rcall DisplDecX3 ldi ZH,HIGH(10000) ; 10 k ldi ZL,LOW(10000) rcall DisplDecX2 ldi ZH,HIGH(1000) ; 1 k ldi ZL,LOW(1000) rcall DisplDecX2 .IF ! cDisplay8 ldi rmp,c1kSep ; set separator tst R0 brne Displ4Dec3 ldi rmp,' ' Displ4Dec3: st X+,rmp .ENDIF ldi ZL,100 ; 100 rcall DisplDecX1 ldi ZL,10 rcall DisplDecX1 ldi rmp,'0' ; 1 add rmp,R1 st X+,rmp ret ; ; Convert a decimal in R3:R2:R1, decimal in ZH:ZL:rmp ; DisplDecX3: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; subtractor for byte 4 DisplDecX3a: cp rRes1,rmp ; compare cpc rRes2,ZL cpc rRes3,ZH cpc rRes4,rDiv2 brcs DisplDecX3b ; ended sub rRes1,rmp ; subtract sbc rRes2,ZL sbc rRes3,ZH sbc rRes4,rDiv2 inc rDiv1 rjmp DisplDecX3a DisplDecX3b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecX3c ldi rmp,' ' DisplDecX3c: st X+,rmp ret ; ; Convert a decimal in R3:R2:R1, decimal in ZH:ZL ; DisplDecX2: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; next byte overflow DisplDecX2a: cp rRes1,ZL cpc rRes2,ZH cpc rRes3,rDiv2 brcs DisplDecX2b ; ended sub rRes1,ZL ; subtract sbc rRes2,ZH sbc rRes3,rDiv2 inc rDiv1 rjmp DisplDecX2a DisplDecX2b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecX2c ldi rmp,' ' DisplDecX2c: st X+,rmp ret ; ; Convert a decimal in R2:R1, decimal in ZL ; DisplDecX1: clr rDiv1 ; rDiv1 is counter clr rDiv2 ; next byte overflow DisplDecX1a: cp rRes1,ZL cpc rRes2,rDiv2 brcs DisplDecX1b ; ended sub rRes1,ZL ; subtract sbc rRes2,rDiv2 inc rDiv1 rjmp DisplDecX1a DisplDecX1b: ldi rmp,'0' add rmp,rDiv1 add R0,rDiv1 tst R0 brne DisplDecX1c ldi rmp,' ' DisplDecX1c: st X+,rmp ret ; ;================================================= ; Delay routines ;================================================= ; Delay10ms: ldi rDelH,HIGH(10000) ldi rDelL,LOW(10000) rjmp DelayZ Delay15ms: ldi rDelH,HIGH(15000) ldi rDelL,LOW(15000) rjmp DelayZ Delay4_1ms: ldi rDelH,HIGH(4100) ldi rDelL,LOW(4100) rjmp DelayZ Delay1_64ms: ldi rDelH,HIGH(1640) ldi rDelL,LOW(1640) rjmp DelayZ Delay100us: clr rDelH ldi rDelL,100 rjmp DelayZ Delay40us: clr rDelH ldi rDelL,40 rjmp DelayZ ; ; Delays execution for Z microseconds ; DelayZ: .IF cFreq>18000000 nop nop .ENDIF .IF cFreq>16000000 nop nop .ENDIF .IF cFreq>14000000 nop nop .ENDIF .IF cFreq>12000000 nop nop .ENDIF .IF cFreq>10000000 nop nop .ENDIF .IF cFreq>8000000 nop nop .ENDIF .IF cFreq>6000000 nop nop .ENDIF .IF cFreq>4000000 nop nop .ENDIF sbiw rDelL,1 ; 2 brne DelayZ ; 2 ret ; ; ========================================= ; Main Program Start ; ========================================= ; main: ldi rmp,HIGH(RAMEND) ; set stack pointer out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp clr rFlg ; set flags to default ; .IF debug .EQU number = 100000000 ldi rmp,BYTE4(number) mov rRes4,rmp mov rDiv4,rmp ldi rmp,BYTE3(number) mov rRes3,rmp mov rDiv3,rmp ldi rmp,BYTE2(number) mov rRes2,rmp mov rDiv2,rmp ldi rmp,BYTE1(number) mov rRes1,rmp mov rDiv1,rmp rcall CycleM6 beloop: rjmp beloop .ENDIF .IF debugpulse .EQU nhigh = 100000000 .EQU nlow = 15000 ldi rmp,BYTE4(nhigh) sts sCtr+3,rmp ldi rmp,BYTE3(nhigh) sts sCtr+2,rmp ldi rmp,BYTE2(nhigh) sts sCtr+1,rmp ldi rmp,BYTE1(nhigh) sts sCtr,rmp ldi rmp,BYTE4(nlow) mov rRes4,rmp mov rDiv4,rmp ldi rmp,BYTE3(nlow) mov rRes3,rmp mov rDiv3,rmp ldi rmp,BYTE2(nlow) mov rRes2,rmp mov rDiv2,rmp ldi rmp,BYTE1(nlow) mov rRes1,rmp mov rDiv1,rmp sbr rFlg,1<
http://www.avr-asm-tutorial.net/avr_en/fcount/fcountV03.asm
mov rRes2,rRes3 mov rRes3,rRes4 clr rRes4 rcall Displ4Dec .IF ! cDisplay8 ldi rmp,' ' st X+,rmp ldi rmp,'H' st X+,rmp ldi rmp,'z' st X+,rmp ldi rmp,' ' st X,rmp .ENDIF ldi ZL,'v' rcall DisplMode ret ; ; Measure time, display rounds per minute ; CycleM3: rcall Divide clr R0 ; overflow detection clr rmp lsl rRes1 ; * 2 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp lsl rRes1 ; * 4 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp mov rDiv1,rRes1 ; copy mov rDiv2,rRes2 mov rDiv3,rRes3 mov rDiv4,rRes4 lsl rRes1 ; * 8 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp lsl rRes1 ; * 16 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp lsl rRes1 ; * 32 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp lsl rRes1 ; * 64 rol rRes2 rol rRes3 rol rRes4 adc R0,rmp tst R0 ; overflow? breq CycleM3a rjmp CycleOvf CycleM3a: sub rRes1,rDiv1 sbc rRes2,rDiv2 sbc rRes3,rDiv3 sbc rRes4,rDiv4 mov rRes1,rRes2 mov rRes2,rRes3 mov rRes3,rRes4 clr rRes4 rcall Displ4Dec .IF ! cDisplay8 ldi rmp,' ' st X+,rmp ldi rmp,'r' st X+,rmp ldi rmp,'p' st X+,rmp ldi rmp,'m' st X+,rmp .ENDIF ldi ZL,'u' rcall DisplMode ret ; ; Measure time high+low, display time ; CycleM4: rcall Multiply rcall Displ4Dec rcall DisplSec ldi ZL,'t' rcall DisplMode ret ; ; Measure time high, display time ; CycleM5: sbrs rFlg,bEdge rjmp CycleM5a rcall Multiply rcall Displ4Dec rcall DisplSec ldi ZL,'h' rcall DisplMode CycleM5a: ret ; ; Measure time low, display time ; CycleM6: sbrc rFlg,bEdge rjmp CycleM6a rcall Multiply rcall Displ4Dec rcall DisplSec ldi ZL,'l' rcall DisplMode CycleM6a: ret ; ; Measure time high and low, display pulse width ratio high in % ; if the edge was negative, store the measured time, if positive calculate ; rRes and rDiv hold the active low time, sCtr the last active high time ; to CalcPw: rDelH:rDelL:R0:rmp = active high time ; CycleM7: sbrs rFlg,bEdge rjmp CycleM7a ldi ZH,HIGH(sCtr) ; edge is high, calculate ldi ZL,LOW(sCtr) ld rRes1,Z+ ; copy counter value ld rRes2,Z+ ld rRes3,Z+ ld rRes4,Z+ add rDiv1,rRes1 ; add to total time adc rDiv2,rRes2 adc rDiv3,rRes3 adc rDiv4,rRes4 brcs CycleM7b mov rmp,rRes1 ; copy high value to divisor mov R0,rRes2 mov rDelL,rRes3 mov rDelH,rRes4 rcall CalcPw ; calculate the ratio brcs CycleM7b ; error rcall DisplPw ; display the ratio ldi ZL,'P' rjmp DisplMode CycleM7a: ldi ZH,HIGH(sCtr) ldi ZL,LOW(sCtr) st Z+,rRes1 ; copy counter value st Z+,rRes2 st Z+,rRes3 st Z+,rRes4 ret CycleM7b: ; overflow ldi rmp,'P' rjmp PulseOvFlw ; ; Measure time high and low, display pulse width ratio low in % ; if the edge was negative, store the measured time, if positive calculate ; rRes and rDiv hold the active low time, sCtr the last active high time ; to CalcPw: rDelH:rDelL:R0:rmp = active low time ; CycleM8: sbrs rFlg,bEdge rjmp CycleM8a ldi ZH,HIGH(sCtr) ; edge is high, calculate ldi ZL,LOW(sCtr) ld rmp,Z+ ; read high-time ld R0,Z+ ld rDelL,Z+ ld rDelH,Z add rDiv1,rmp ; add to total time adc rDiv2,R0 adc rDiv3,rDelL adc rDiv4,rDelH mov rmp,rRes1 ; copy the active low time mov R0,rRes2 mov rDelL,rRes3 mov rDelH,rRes4 rcall CalcPw ; calculate the ratio brcs CycleM8b ; error rcall DisplPw ; display the ratio ldi ZL,'p' rjmp DisplMode CycleM8a: ldi ZH,HIGH(sCtr) ldi ZL,LOW(sCtr) st Z+,rRes1 ; copy counter value st Z+,rRes2 st Z+,rRes3 st Z+,rRes4 ret CycleM8b: ; overflow ldi rmp,'p' rjmp PulseOvFlw ; ; Converts an ADC value in R1:R0 to a voltage for display ; cAdc2U input: ADC value, output: Voltage in V for display ; cAdc2U: clr R2 ; clear the registers for left shift in R3:R2 clr R3 ldi rmp,HIGH(cMultiplier) ; Multiplier to R5:R4 mov R5,rmp ldi rmp,LOW(cMultiplier) mov R4,rmp clr XL ; clear result in ZH:ZL:XH:XL clr XH clr ZL clr ZH cAdc2U1: lsr R5 ; shift Multiplier right ror R4 brcc cAdc2U2 ; bit is zero, don't add add XL,R0 ; add to result adc XH,R1 adc ZL,R2 adc ZH,R3 cAdc2U2: mov rmp,R4 ; check zero or rmp,R5 breq cAdc2U3 ; end of multiplication lsl R0 ; multiply by 2 rol R1 rol R2 rol R3 rjmp cAdc2U1 ; go on multipying cAdc2U3: ldi rmp,$80 ; round up add XL,rmp ldi rmp,$00 adc XH,rmp adc ZL,rmp adc ZH,rmp tst ZH ; check overflow mov R1,XH ; copy result to R2:R1 mov R2,ZL ldi XH,HIGH(sResult+16) ; point to result ldi XL,LOW(sResult+16) ldi rmp,'U' st X+,rmp breq cAdc2U5 ldi ZH,HIGH(2*AdcErrTxt) ldi ZL,LOW(2*AdcErrTxt) cAdc2U4: lpm tst R0 breq cAdc2U6 sbiw ZL,1 st X+,R0 rjmp cAdc2U4 cAdc2U5: clr R0 ldi ZH,HIGH(10000) ldi ZL,LOW(10000) rcall DecConv inc R0 ldi ZH,HIGH(1000) ldi ZL,LOW(1000) rcall DecConv ldi rmp,cDecSep st X+,rmp clr ZH ldi ZL,100 rcall DecConv ldi ZL,10 rcall DecConv ldi rmp,'0' add rmp,R1 st X+,rmp ldi rmp,'V' st X,rmp lds rmp,sResult+17 cpi rmp,' ' brne cAdc2U6 ldi rmp,'=' sts sResult+17,rmp cAdc2U6: ret ; AdcErrTxt: .DB "overflw",$00 ; ; =========================================== ; Lcd display routines ; =========================================== ; .IF cDisplay ; if display connected ; ; LcdE pulses the E output for at least 1 us ; LcdE: sbi PORTB,bLcdE .IF cFreq rel="nofollow">14000000 nop nop .ENDIF .IF cFreq>12000000 nop nop .ENDIF .IF cFreq>10000000 nop nop .ENDIF .IF cFreq>8000000 nop nop .ENDIF .IF cFreq>6000000 nop nop .ENDIF .IF cFreq>4000000 nop nop .ENDIF .IF cFreq>2000000 nop nop .ENDIF nop nop cbi PORTB,bLcdE ret ; ; outputs the content of rmp (temporary ; 8-Bit-Interface during startup) ; LcdRs8: out PORTB,rmp rcall LcdE ret ; ; write rmp as 4-bit-command to the LCD ; LcdRs4: mov R0,rmp ; copy rmp swap rmp ; upper nibble to lower nibble andi rmp,0x0F ; clear upper nibble out PORTB,rmp ; write to display interface rcall LcdE ; pulse E mov rmp,R0 ; copy original back andi rmp,0x0F ; clear upper nibble out PORTB,rmp ; write to display interface rcall LcdE mov rmp,R0 ; restore rmp ret ; ; write rmp as data over 4-bit-interface to the LCD ; LcdData4: push rmp ; save rmp mov rmp,R0 ; copy rmp swap rmp ; upper nibble to lower nibble andi rmp,0x0F ; clear upper nibble sbr rmp,1<elp",cCr,cLf txtUartCursor: .DB cCr,cLf,"i> ",cNul http://www.avr-asm-tutorial.net/avr_en/fcount/fcountV03.asm (3 of 4)1/20/2009 7:59:09 PM
****************************************************** * Applying the frequency counter v0.3 as of 9.1.2009 * ****************************************************** Chapters: --------1. Adjustments prior to assembling 2. During programming 3. Measuring modes 3.1 Measuring modes 3.2 Displaying measuring modes 4. Display versions 4.1 Display on 2-line-LCDs 4.2 Display on a single-line-LCD 4.3 Display on 16-/20-character-displays 4.4 Display 8-character-displays 5. Display on the serial interface 5.1 Commands on the serial interface 5.2 Format of voltage- and frequency output
1. Adjustments prior to assembling ---------------------------------The following adjustments have to be made prior to assembling the source code in the file fcountV03.asm: - The switches debug and debugpulse must be set to 0. - If an LCD is attached, cDisplay must be set to 1, otherwise to 0. - If the attached LCD has 8 characters per line, cDisplay8 must be set to 1. If a 16- or 20character-per-line-display is used, cDisplay8 must be set to 0. - If the attached LCD can only display a single line, cDisplay2 must be set to 0. If two or more lines can be displayed, cDisplay2 must be set to 1. - If the serial interface is attached, cUart must be set to 1. If no serial interface is attached or serial interfacing is not wanted, set cUart to 0. - If the prescaler by 16 is attached to another port bit as PC5, specify pPresc and pPrescD as well as bPresc accordingly. - If the processor clock is driven different than at 16 MHz, this frequency has to be written to cFreq. - If the serial interface should work at a baudrate different than with 9600 Bd, cBaud has to be adjusted accordingly. - If the hardware prescaler for measuring voltages is different than with the two resistors of 1M, the two values in cR1 and cR2 have to be changed accordingly. If the displayed voltages are different from the applied voltages, adjust cRin: smaller values for cRin yield a higher voltage on the display, larger values a lower voltage. 2. During programming --------------------- The fuses "Ext.Crystal/Resonator HighFreq.; Start-up time: 16K CK + 64 ms "CKSEL=1111 SUT=11]" and CKOPT must be set, all other internal and external clock fuses must be cleared. The screenshots show the fuses (FusesPony.gif, FusesStudio1.gif and FusesStudio2.gif).
3. Measuring modes -----------------3.1 Measuring modes The measuring mode is selected with the voltage divider or variable resistor. Following modes can be selected: 0: Measuring frequency with prescaler, gate time 0,25 s, result in Hz 1: Measuring frequency without prescaler, gate time 0,25 s, result in Hz 2: Measuring frequency as period measurement with calculation to frequency, result as 0.01 Hz reswp. 0.001 Hz 3: Rounds-per-Minute measurement, without prescaler, via period measurement with conversion to RPM, result in rpm 4: Period length of complete cycle, result in microseconds 5: Period length of high-period, result in microseconds 6: Period length of low-period, result in microseconds 7: Period portion of high-period, result in 0,1% 8: Period portion of low-period, result in 0,1% 9: Voltage measuring only, result in 0,001 Volts Selection of mode Modus 9 (potentiometer to the right) switches frequency/time/period measurement off. 3.2 Displaying measuring modes The measuring mode is displayed on the beginning of the first LCD line with a letter and an equal sign (format "X="). With an 8-character-LCD attached, these two characters are displayed only if the result of the measurement is small enough and allows this. During display, the following abbreviations for the mode are used: - "F=": Frequency in Hz, measured with prescaler, resolution +/- 32 Hz - "f=": Frequency in Hz, measured withput prescaler, resolution +/- 4 Hz - "v=": Frequency in Hz with smaller decimals, measured via time, resolution +/- 0,01 Hz - "u=": Rounds per minute, measured via time, resolution +/- 1 Upm - "t=": Cycle duration in mikrosekunden, resolution +/- 1 microseconds - "h=": Duration of a high-period, resolution +/- 1 microseconds - "l=": Duration of a low-period, resolution +/- 1 microseconds - "P=": Period portion of a high-period in %, resolution +/- 0,1% - "p=": Period portion of a low-period in %, resolution +/- 0,1% - "U=": Voltage on the input, resolution depends from the resistor pair on the input, at (1M + 1M) approx. 4 millivolts.
4. Display versions ------------------The update of the LCD is depending from the mode: in frequency measuring modes the interval is 0.25 seconds, in period measurements it is depending from the duration of measuring period. 4.1 Display on 2-line-LCDs Depending from the selected mode, the first LCD line displays the frequency, the time or the period portion. The second line displays the measured voltage. With a double line LCD with more than 8 characters per line, the measuring mode (0..9) is displayed at the end of the second line. 4.2 Display on a single-line-LCD In modes 0..8 the LCD shows the results of frequency, time or period portion measurements. In mode 9, the voltage is displayed. 4.3 Display on 16-/20-character-displays The display is complete, with mode, thousands and decimal separators as well as the dimension. Mode Format ----------------------0 "F=99,999,999 Hz " 1 "f= 9,999,999 Hz " 2 "v= 9,999.999 Hz " 3 "u= 9,999,999 rpm" 4 "t=99,999,999 us " 5 "h=99,999,999 us " 6 "l=99,999,999 us " 7 "P=100,0% " 8 "p=100,0% " 9 "U=9,999V " 4.4 Display with 8-character-displays Period portions and voltages are displayed complete. Display in other modes are shortened. Suppressed are: - the output of thousands separarors, - the output of the dimension, - if required, the equal-sign and the character for mode recognition. The output displays - frequencies in Hz - rps as rpm - times in microseconds. Mode Format short Format long ---------------------------0 "F=999999" "99999999" 1 "f=999999" "99999999" 2 "v=999999" "99999999" 3 "u=999999" "99999999" 4 "t=999999" "99999999" 5 "h=999999" "99999999" 6 "l=999999" "99999999" 7 "P=100,0%" "P=100,0%" 8 "p=100,0%" "p=100,0%" 9 "U=9,999V" "U=9,999V"
5. Display on the serial interface ---------------------------------The serial interface sends the following text after a reset:
************************************************* * Frequency- and voltmeter (C)2005 by g.schmidt * ************************************************* Commands: elp i> 5.1 Commands on the serial interface The following commands are implementiert: u : Output voltage off U : Output voltage on U=123 : Interval for voltage output 123 cycles f : Output frequency off F : Output frequency on F=123 : Interval for frequency output 123 cycles p : Output the currently selected intervals h, ? : Output help information
5.2 Format of voltage- and frequency output The output of voltages ins in the format "U=4,958V" The output of frequencies is depending from the selected mode, e.g. in mode 0: "F=1.234.567 Hz" http://www.avr-asm-tutorial.net/avr_en/fcount/ReadMe.txt1/20/2009 7:59:14 PM
Pfad: Home => AVR overview => applications => Egg-timer A small homemade project - just for fun - but very practical
The ATtiny2313V-egg-timer 1. Description 2. Hardware 3. Functioning
1. Description Who didn't already run into that: the n-th birthday celebration of friend X.Y. is coming up, and you do not have any idee what your friend's household is missing. Now, you can just go to a shop, that sells any kind of presents, and buy something sense- and useless, wrap it in nice paper, and just hope that no one else will have the same (non-)idea. Otherwise the main question will come up: exchange this piece to another piece, that no one needs? If you are experienced in soldering some electronic parts together and of programming an AVR, you have a clear avantage over all your other friends: producing a very individual gift, that no one can copy by chance, and put it on the birthday gift table. During the next twelve monthes, all our friends, relatives and neighbours receive an up-to-date, individually designed and personally flavoured egg-timer. Of course, equipped with the latest, greatest micro-professor, as available on the electronic device scene today. Not just one of the old-fashioned and traditional flasks, filled with acid-cleaned silica sand. Quietly rippling sand was yesterday, today it is beeping and blinking. The principle is very easy: a row of LEDs, e.g. placed like a bar, is lit green and red (first variation: yellow and red), whenever a minute is over, one more green light changes from green to red. To save battery capacity and use the timer for more than one egg to cook, only one LED is lit at a time in the display cycle. The band is running fast, so that there is some action in the kitchen and you can catch the timer's state in just a short glimpse, looking up from your morning newspaper. If you are too fascinated by all the news in your newspaper, you do NOT even need to keep an eye on your timer: it just beeps the number of elapsed minutes. That makes your egg-timer also compatible with the needs of handicapped people, that arent able to see the disco lights, which makes a major difference to the sand-filled equipment. If ten minutes are over, a yellow light and a continous beep tells you, that the egg yolk has turned to green now, and that the egg can be thrown into the trashcan. I only describe the electronic and software design here, so you must add your own creativity in the package design. For placing the LEDs, consider the following opportunities: 1. A straight line of LEDs signals geometric strength, and is more adapted to straight-thinking people like e.g. mathmaticians or housekeepers, model "straight forward", 2. A circle is possible, and more adapted to people with a similiar belly, model "beercan", 3. For the more playful people, the form of an egg is more appropriate. This model is also good for the short-memoried, because it reminds what the equipment is made for, model "short-term memory", 4. For the more chaotic, untidiness- and complexity-loving neighbor, you can place the LEDs in a random manner on the unregular-shaped casing, model "Hundertwasser" (see an intro to Hundertwasser), 5. The design for the gamer: place the LEDs randomly, and exchange the LEDs every minute randomly by software. He will love to guess where the next LED will get red, model "vote&surprise". From my own experience it has been proven top, to add an individually designed instruction manual to that gift. Humorous formulations, like english words combined with taiwanese grammar, bring good vibes to the birthday celebration. Also, you can write the instruction in english, translate this to japanese language, retranslate it with Babelfish to english, and then exchange single words by their dutch translation. To the top of page
2. Hardware
Th
hardware scheme is pretty easy: the two batteries (2*AA, 2*AAA is suitable, too) supply the hardware with 3 wonderful Volts. More voltage destroys the LEDs very fast, so don't supply with 5 Volts. The row of dualLEDs is cascated with resistors, limiting the currents. Each LED can be driven in both ways by the portpins. When soldering the LEDs, only ensure their uniform direction, their colour can be easily changed by software. All portpins that are not needed to drive a certain single LED, are set to tristate (input) and therefore do not interfere. The ATtiny2313V (without V is also fine) always drives only one LED, the voltage drop, when sourcing from the pin under load, has been calculated in the design of the resistor. The yellow egg-throw LED has its own portpin. A small piezo-speaker is directly pinned to the OC0A output. If you don't have a piezo, you can use a small 32-Ohm-speaker, decoupled with a C of 100 µF. To the top of page
3. Functions 3.1 Processor programming
The processor hardware cannot be programmed in in-systemmode (ISP), because the operating voltage should not exceed 3 V. The processor has to be prgrammed on an extra programming board and then moved to the egg-timer hardware. First, the processor is changed to operate with the internal 4-Mcs/ s-RC-oscillator, to reduce power consumption. The DIV8 fuse stays activated, so that the processor runs with a 500 kcs/s clock. Slow enough for the V-version. Assembler source code for the basic version is for download in asm format here and can be viewed in HTML format here. In the source code, first the portpins are assigned. That means that all LEDs can be mounted in any sequence and assigned here. The drive row must be assigned by the correct output port, portpin and direction port. The extensive program code to drive the correct LED, at the end of the source code, is attributed to that flexibility in placing the LEDs. Time measurement and LED-switching is assigned task of the 16bit-timer 1, that runs in CTC mode. When reaching the value in the compare register A, every 0.2 sec, an interrupt is triggered and the T flag in the status register is set. The interrupt wakes up the processor from idle sleep. The T flag is cleared, and the lowest bit of the LED counter is checked. If this is one, the beep tone is switched off. If it is zero, it is checked whether a beep tone has to be generated. If yes, the speaker output is turned on (Timer 0 is activated) and the number of remaining beep tones is decremented. After that, the last LED that was on, is switched off. The LED counter in incremented by one and is checked, if it is already higher than 10. If yes, the two-second-counter is incremented and checked, if this has reached 30. If yes, a minute is over and the minute register is incremented. If that has exceeded 10, the yellow LED is activated and a continous beep is initiated. At the end, the next LED to be lit is calculated and, according to the minute counter, is switched to red or green. For calculation of the correct routine, the start address of a jump instruction table (red or green) is written to the register pair ZH:ZL and the LED number is added. By executing IJMP a jump to the correct routine is performed. After switching the correct LED, the routines all jump back to the sleep instruction of the main program loop. 3.2 Variations
There is a broad palette of possible variations: 1. LED band speed: ❍ hectic mode: for people that need high speed, we increase the running speed of the LEDs to the double, by reducing the value of 12500 down to 6250 and changing the 30 for the minute recognition to 60. ❍ slow mode: for the one's that need more time to move their eyes, we change the 12500 to 25000 and change the 30 in the minute recognition to 15. 2. Incorrect LED order? No problem: exchange the lines ldi ZH,HIGH(TabRot) ; try with red, and ldi ZL,LOW(TabRot) against the lines ldi ZH,HIGH(TabGruen) ; no, it is green, and ldi ZL,LOW(TabGruen) and the line runs with opposite colors. 3. Higher tone? No problem, in the line ldi rmp,63 ; CTC-value just exchange 63 by a smaller value for timer 0. 4. Egg-timer for a musician? Possible. Just add a table with the CTC values of the gamut to the flash, and, after each minute, change the CTC value with the next value in the table. 5. If your customer prefers hard-boiled eggs of the type "golfball": add four dual-LEDs to the four available portpins and add them to the software. 6. ... The number of possible variations makes it impossible to offer the software for all these variations. The number of variations should be big enough to design an individual gift for each and everyone in your circle of friends, so that no one gets a timer with exactly the same design. But: keep good records on all the distributed variations. Otherwise you might run into new trouble. And now: good luck with your handicrafts.
Steppermotor controller with ATtiny13 This application of an AVR demonstrates controlling a steppermotor with an ATMEL ATtiny13, with the following properties:
● ● ● ● ● ●
Designed for cable drive remote steppermotors with a gear unit. An input voltage between 0..5 V with a resolution of 10 bits (1024 voltage steps, 4.88 mV per voltage step) controls the position of the steppermotor. Number of steps of the motor can be adjusted to up to 65,535 steps, so any gear unit and number of rotations of the motor can be selected by software. Simple driver for driving the motor. Very fast rotation of the motor can be achieved by optimizing the drive frequency. Reduction of current requirements by switching the coils of the motor off after a preselectable activation time following the last step.
1. Hardware The hardware consists of the AVR processor ATtiny13, a six pin standard programming connection for In-System-Programming (ISP), the 7-bit driver ULN2003, the supply for the processor and the filtering of the analogue input signal. The schematic diagram (click on it for a PDF page with higher resolution):
1.1 Microcontroller The processor ATtiny13 provides the following functions. The operating voltage of 5 V is supplied to the pins 8 (+5 V) and 4 (0 V) and blocked with a ceramic capacitor of 100 nF. Pin 1 (= RESET input) is tied with a resistor of 10 kOhm to the operating voltage. Input PB4 (Pin 3) measures the analogue voltage, using the internal AD converter, by comparision with the operating voltage. From its conversion results, software calculates the target value of the steppermotor's steps. The output port pins PB0 to PB3 (pins 5, 6, 7 and 2) control the driver for the coils of the steppermotor.
1.2 ISP-Interface The ISP interface serves as programming interface to program the AVR in the system. The pinout is ATMEL standard. The ISP interface uses the port bits PB2 (SCK, Pin 7), PB1 (MISO, Pin 6), PB0 (MOSI, Pin 5) and the RESET at Pin 1. The operating voltage at VTG, if connected, provides supply current to the programmer. Leave VTG open if the programmer provides its own supply. GND is reference for the negative operating voltage.
1.3 Coil current driver ULN2003A The drive current for the coils of the steppermotor is controlled by the driver IC ULN2003A. The outputs with open-collector-driver transistors allow for voltages of up to 50 V and currents of up to 500 mA. They switch the different coils of the motor on aned off. Induced inductive backdropping voltages, when coils are switched off, are shortcircuited by diodes, that are connected internally from each collector to the pin CD. The motor used here is operated with a 12 V supply voltage and requires a current of approx. 150 mA per active coil (because there are alway two coils active at a time, together 300 mA). The input pins I7..I4 of the driver are controlled by the processor (active high, logic 1 switches coil on).
1.4 Supply The supply voltages are filtered to avoid glitches by the switching coils. Supply of the coils is connected over a diode 1N4007 and smoothed by a capacitor of 100 µF. The processor is supplied by a voltage stabilizer 78L05. Its input is connected to the 12 V supply over a diode 1N4007 and and capacitor of 100 µF. The stabilizer is blocked with tantalum capacitors of 1 µF resp. 2,2 µF to avoid oscillations. The supply and regulation of the controller/driver comes over a four-wire connection cable from a 12 V power supply (click on the picture to download a PDF doc with higher resolution).
The 12 V supply is mounted on a small board.
2. Software The software for the ATtiny13 is written in assembler, the source code is here for download.
2.1 Functioning The software consists of the following basic elements: ● ● ● ● ●
the Reset- and Interrupt vector table, the initialization of the starting values and the hardware components, the AD conversion of the input voltage, the calculation of the target value from the measured voltage, the step control and output to the steppermotor.
Reset- and interrupt vector table The vector table redirects program execution to the main program, if a reset starts the processor. In case of an interrupt, the service routines for the timer/counter and the AD converter are re-directing. Available vectors that are not used here are represented by RETI instructions. Init of start values Initiation of start values is coded starting with the label "Main:". Here ● ● ● ●
the stack is initialized, because interrupts and subroutines are used, the flag register is cleared (see more about its function below), the registers holding the target and actual value of the steppermotor are cleared, the deactivation counter is set to its initial value.
Init of the hardware Initing the hardware consists of: ● ●
●
●
the direction of the four portbits PB0 to PB3 are set to be output and these bits are set to the first step of the motor, the AD conversion counter is set to 64, the sum value of the conversion result is cleared, the digital input driver if channel 2 at PB4 is switched off (the PB4 pin is used exclusively for analog AD conversion), the AD mux is tied to channel ADC2, and the AD converter is inited with the following settings: ❍ reference voltage is the operating voltage of the ATtiny13, ❍ clock divider =128, at 1.2 Mcs/s internal clock and 13 clock cycles per conversion each conversion requires 1.387 ms, summing up of 64 conversion results yields a complete measuring cycle each 88.75 ms or 11.3 measuring cycles per second. ❍ Interrupt after each complete conversion, ❍ no automatic restart on conversion complete (restart is performed in the interrupt service routine). the timer/counter TC0 is set to normal CTC mode (clearing the counter when the compare value is reached), with the following settings: ❍ the duration of a CTC cycle is as long as the output signal for a single step ot the motor should last, this is controlled by the constant cCmpA, that is written to the Compare register A of the counter, ❍ at the end of the CTC cycle an interrupt is triggered, the Compare-Match-Interrupt A is enabled, ❍ the clock prescaler is set to 1024 and the timer is started, the counter clock is therefore 1.2 Mcs/s / 1024 = 1.172 kcs/s, with CTC values between 1 and 255 yield frequencies between 1172 and 4.6 cs/s for the steps of the motor. the processor is set to sleep mode idle, that means: between the interrupts of the counter and the AD converter program execution is stopped.
AD converter measurements of the input voltage The AD converter converts the input voltage on pin 3 (PB4, ADC2) to a value between 0..1023 and triggers a Conversion Complete Interrupt. The Interrupt Service Routine, starting at the label "AdcInt:" reads the result from the ports ADCL und ADCH and sums it to the register pair rAdcH:rAdcL. The counter rAdc is decreased by one. If rAdc reaches zero, the sum value is copied to the register pair rAdcRH:rAdcRL, the sum is cleareed, the counter is again set its initial value 64 and the flag bAdc in the flag register is set to one. Finally, the next conversion is started. Summing up 64 conversion results results in an averaging over these values, removing random results and fluctuations in the input signal, and slows down the measuring process to a convenient cycle duration. The resulting sum value is between zero and 65,535 (0x0000..0xFFFF), an excellent basis for the following calculation of the target value. Conversion of the measuring result to the target value If after waking up the processor and exection of the interrupt service routine the flag rAdc in the flag register is set, the conversion routine starting with the label "AdcRdy:" is called. This routine ● ● ● ● ● ●
clears the flag bit again, copies the sum value to the register pair rAdcCH:rAdcL, multiplies the sum value with the constant cSmSteps (the number of forward steps for the fullscale operation of the motor) to yield a 32-bit result, rounds the lower 16 bits of this number, divides the 32-bit result by 65,536 (by ignoring the lower 16 bits), resulting in the target value as 16 bit number, and writes this result to the target value register pair rSmSH:rSmSL (during which interrupts are disabled to avoid errors in the motor adjustment, should there be a timer interrupt between copying LSB and MSB).
Step control and output to the motor The step control and output to the steppermotor is done in the interrupt service routine of the counter, starting with the label "Tc0IntCA:". First, the actual and the target value registers are compared. If they are equal, a jump to the label "Tc0IntCA0:" is executed. There, the delay counter in the register pair X is decreased by one. If the delay counter reaches zero, the coils are de-energized by writing zeroes to the driving port bits, the delay counter is restarted and the service routine is left. If the actual and the target value are not equal, the actual value is incresed or decreased. This new value is translated like follows to a new value for the stepper driver: ● ● ● ●
the two lowest bits of the (new) actual value are isolated, and are added to the start address of the table "SmTab:" in Z, that points to the respective table in the flash memory, with the instruction LPM, the byte at that address is read to R0, and this is written to the output port, which switches the correct coils of the motor on/off.
The table "SmTab:" with the two words 0x0605 and 0x090A determines the step sequence of the steppermotor as follows: ● ● ● ●
Note: If the coils Q1..Q4 of the stepper are connected in a different manner to the driver outputs, it is necessary and sufficient to change these two words (see below). In the service routine finally the delay counter is restarted, to keep the coils activated for the appropriate preselected time, after changing the output pattern.
2.2 Adjustments prior to assembling In the assembler source code, the following adjustments must/can be made prior to assembling the code: ● ● ●
●
The three debug switches debug_calc, debug_const and debug_out must be set to zero! The constant cSmSteps should be adjusted to the number of steps that the motor should perform over the whole range (maximum: 65,535). The constant cSmFreq must be adjusted to the frequency that the motor is reliably moving (minimum 5 cs/s - for extremely large motors, maximum 1171 cs/s - far too fast for most types of motors). the constant cSmDelay sets the number of cycles, for which the coils stay activated, after having changed the drive pattern of the motor. If cSmDelay is equal to cSmFreq, the delay is exactly one second.
The order of the four coils on connector J2 might be different with different types of motors. If a different order is to be adapted, it is sufficient to change the table SmTab:. The current table is made for a KP4M4-001 type and is generated as follows: Coil color Portbit Step 1 Step 2 Step3 Step 4 Q1
; **************************************************** ; * Steppermotor-Driver with an ATtiny13 - Version 1 * ; * (C)2007 by http://www.avr-asm-tutorial.net * ; **************************************************** ; ; Debugging switches ; .equ debug_calc = 0 .equ debug_const = 0 .equ debug_out = 0 ; .nolist .include "tn13def.inc" .list ; ______ ; / | ; Hardware: | | ; _______ . . +12V black ; ___ / | | | ___ | ; +5V-|___|--|RES VCC|--+5V B3-|I4 O4|-|___|-+Q4 red ; | | | | ___ | ; B3--|PB3 PB2|---------|I5 O5|-|___|-+Q2 brown ; | | | | ___ | ; Analog-In--|PB4 PB1|---------|I6 O6|-|___|-+Q3 green ; | | | | ___ | ; |--|GND PB0|---------|I7 O7|-|___|-+Q1 white ; |________| | | | ; ATtiny13 |-|GND CD|-------+ ; |_______| ; ULN2003 ; ; Funktioning: ; A stepper motor is controled by an analog input voltage ; on pin 3. The input voltage ranges from 0 Volt to ; the operating voltage of the processor. The number ; of steps of the motor over that input range are ; adjusted by the constant cSmSteps (1...65535 steps). ; Stepmotor drive sequence and encoding: ; Portbit PB0 PB2 PB1 PB3 | | ; Color wt bn gn rd | Portbit | Byte ; Step Q4 Q3 Q2 Q1 | 3 2 1 0 | ; ------------------------+---------+-----; 1 1 1 0 0 | 0 1 0 1 | 05 ; 2 0 1 1 0 | 0 1 1 0 | 06 ; 3 0 0 1 1 | 1 0 1 0 | 0A ; 4 1 0 0 1 | 1 0 0 1 | 09 ; results in the word table: .dw 0x0605,0x090A ; ; Timer TC0: ; The timer runs with the processor clock of 1.2 Mcs/s ; with a prescaler value of 1024 in normal CTC mode, ; with the CompareA register as TOP value. Interrupt ; at CompareA match resp. at CTC reset. ; The interrupt service routine compares the actual ; value of motor steps with the target value. If the ; actual value is too low, the actual value is ; increased one step, if it is too high the actual ; step value is decreased. If actual and target value ; are equal, the coils of the motor are switched off ; after a certain pre-selectable delay. ; Timing: step frequency = 1.2 Mcs/s / 1024 / CompA, ; with CompA = 255: step frequency = 4.57 cs/s, ; with CompA = 8: step frequency = 146 cs/s ; ADC: ; Conversion of the analogue voltage on pin 3 / PB4 /ADC2, ; summing up 64 measuring results to average results, ; conversion to the target value for the stepper motor ; Timing: clock divider for conversion = 128, ; 1,2 Mcs/s / 128 = 9,375 kcs/s = 106,7 us clock rate, ; Conversion = 13 cycles = 1,387 ms per conversion ; = 721 conversions per second ; Averaging over 64 conversions = 88,75 ms = 11,3 results ; per second ; ; Constants ; .equ cSmSteps = 1276 ; 2552/2, number of steps for full range .equ cSmFreq = 145 ; frequency stepper motor ; Minimum: 5 cs/s, Maximum: 1171 cs/s .equ cSmDelay = 390 ; number of cycles prior to switching off ; ; Derived constants ; .equ clock = 1200000 ; clock frequency Tiny13 internal .equ Tc0Ctc = 1024 ; prescaler value TC0 .equ cCmpA = clock / 1024 / cSmFreq ; CompareA value ; ; Checking the constants ; .IF cCmpA > 255 .ERROR "Steppermotor frequency too low!" .ENDIF .IF cCmpA < 1 .ERROR "Steppermotor frequency too high!" .ENDIF ; ; SRAM (not used, only for stack operation) ; ; Registers ; ; used: R0 for table reading ; used: R8:R1 for calculation of target value ; free: R10:R8 .def rAdcCL = R11 ; ADC calculation value LSB .def rAdcCH = R12 ; dto., MSB .def rAdcRL = R13 ; ADC transfer value LSB .def rAdcRH = R14 ; dto., MSB .def rSreg = R15 ; status save/restore register .def rmp = R16 ; Multipurpose outside Int .def rimp = R17 ; Multipurpose inside Int .def rFlg = R18 ; Flags .equ bAdc = 0 ; Adc conversion complete flag .def rAdcC = R19 ; ADC counter (64 Adc conversions) .def rAdcL = R20 ; ADC Adder register LSB .def rAdcH = R21 ; dto., MSB .def rSmSL = R22 ; Steppermotor target value LSB .def rSmSH = R23 ; dto., MSB .def rSmIL = R24 ; Steppermotor actual value LSB .def rSmIH = R25 ; dto., MSB ; used: X for activation delay of the coils ; free: Y ; used: Z for table access ; ; ********************************** ; Code Segment Start, Int - Vector ; ********************************** ; .cseg .org $0000 ; rjmp Main ; Reset vector reti ; INT0 vector reti ; PCINT0 vector reti ; TIM0_OVF vector reti ; EE_RDY vector reti ; ANA_COMP vector rjmp Tc0IntCA ; TIM0_COMPA vector reti ; TIM0_COMPB vector reti ; WDT vector rjmp AdcInt ; ADC vector ; ; ********************************** ; Interrupt Service Routines ; ********************************** ; ; Timer-Counter 0 Compare A Interrupt Service Routine ; Tc0IntCA: in rSreg,SREG ; save status cp rSmIL,rSmSL ; compare actual with target value cpc rSmIH,rSmSH breq Tc0IntCA0 ; jump if equal brcs Tc0IntCAF ; actual less than target value sbiw rSmIL,1 ; actual greater than target, one step back rjmp Tc0IntCAS Tc0IntCAF: adiw rSmIL,1 ; one step forward Tc0IntCAS: mov rimp,rSmIL ; copy actual value LSB andi rimp,0x03 ; isolate lowest two bit ldi ZH,HIGH(2*SmTab) ; point Z to table in flash memory ldi ZL,LOW(2*SmTab) add ZL,rimp ; add the two lowest bits ldi rimp,0 ; update upper Byte adc ZH,rimp lpm ; read next value from table to R0 .IF debug_out == 0 out PORTB,R0 ; write value to port .ENDIF ldi XH,HIGH(cSmDelay) ; restart delay counter ldi XL,LOW(cSmDelay) out SREG,rSreg ; restore status reti Tc0IntCA0: sbiw XL,1 ; decrease delay counter brne Tc0IntCAD ; not yet zero ldi rimp,0 ; switch of current on coils out PORTB,rimp ; to output driver ldi XH,HIGH(cSmDelay) ; restart delay counter ldi XL,LOW(cSmDelay) Tc0IntCAD: out SREG,rSreg ; restore status reti ; SmTab: .dw 0x0605,0x090A ; ; Adc Conversion Complete Interrupt Service Routine ; AdcInt: in rSreg,SREG ; save status in rimp,ADCL ; read LSB of ADC result add rAdcL,rimp ; add to the result register in rimp,ADCH ; read MSB of ADC result adc rAdcH,rimp ; add to the result register dec rAdcC ; decrease counter brne AdcInt1 ; if not zero, go on converting mov rAdcRL,rAdcL ; 64 conversions, copy result mov rAdcRH,rAdcH clr rAdcH ; clear sum clr rAdcL ldi rAdcC,64 ; restart conversion counter sbr rFlg,1<
Connecting a LCD display to a STK500 port
Pfad: Home = rel="nofollow"> AVR-Overview => 2-Line-LCD on STK500
Connecting a 2-line-LCD to a STK500 port The develloper board STK500 doesn't come with a LCD interface for connecting a cheap display, like STK200 does. But even the STK200-LCD-port has some disadvantages: It requires talking to the LCD in memory mapped mode, which means, that the LCD interface blocks external SRAM space and requires slowing down the CPU with extra wait states. But it is easy to connect such a display to a port of the processor directly, without using memory mapping. It requires only 6 port bits, and the software is very easy, if you skip reading the display memory. The data sent to the LCD is divided in packages to 4 bits each. Hardware is rather easy, too. One disadvantage is that you have to do the timing without reading the display's enable bit, requiring some adjusted delay loops. But that is not too bad.
Hardware The hardware is shown in the following scheme:
So its only connecting the correct Port-Bit to the appropriate LCD-Pin, ● voltage source pins to the ones on the LCD display, and the correct ● pot pins to the contrast adjustment pins of the LCD. ●
That's all for the hardware.
Software The software for talking to the LCD is completely separated in the file LCD4INCE.html in HTML-format and in the file Lcd4IncE.asm in source file format. The following routines are provided: 1. 2. 3. 4.
Lcd4Init: This routine resets the LCD, Lcd4Chr: This displays the char in rmp on the LCD, Lcd4PBcd: This displays the packed BCD in rmp on the LCD, Lcd4ZTxt: This displays the null-terminated text in the flash memory on the LCD, using the Z pointer, 5. Lcd4RTxt: This displays rmp chars in SRAM where Z points to. The software can be included in an existing program. The header in the file lists the requirements, that the source file must fulfil to work correctly with the LCD rountines.
Pfad: Home => AVR-Overview => 2-Line-LCD on STK500 => LCD base routines
; ******************************************** ; * LCD-base routines for 4-Bit-interface * ; * of a 2-line LCD display to an ATMEL* * ; * Port, Bits 4..7=Data, Bit 1=RS, Bit0=E ; * Version 1, February 2002, (C) 2002 by * * ; * Gerhard Schmidt, bug reports and sug; * gestions to info!at!avr-asm-tutorial.net * ; ******************************************** ; ; Hardware: LCD-Display on the active port ; ; Definitions, that must be dfeined in the calling ; program: ; - Stack operations must be initialised ; - Register rmp (R16..R31) ; - Clock frequency ftakt ; - pLcdPort Active LCD-Port ; - pLcdDdr Data direction registerof the active port ; Subroutines: ; - Lcd4Init: Initialise the LCD ; - Lcd4Chr: Display the character in rmp on the LCD ; - Lcd4PBcd: Display the packed BCD in rmp on the LCD ; - Lcd4ZTxt: Display the null-terminated string on the LCD ; - Lcd4RTxt: Display rmp chars from SRAM, starting at Z ; ; Definition for the LCD-port .EQU cLcdWrite=0b11111111 ; Data direction write the LCD .EQU cLcdDummy=0b00111000 ; Dummy-Function-Word .EQU mLcdRs=0b00000010 ; RS-Bit Mask .EQU bLcdEn=0 ; Enable Bit .EQU c1s=200 ; Wait at start-up time (200 * 5 ms) .EQU c5ms=ftakt/800 ; 5 ms Wait after each control word .EQU c50us=ftakt/80000 ; 50 us Wait after each char ; ; Macro for Enable active time ; ; Version for 10 Mcs clock ;.MACRO enactive nop ; ; nop ; nop ; nop ; nop ;.ENDMACRO ; ; Version für 4 Mcs clock ; .MACRO enactive nop nop .ENDMACRO ; Lcd4Init: rcall LcdDelay1s ; Wait a second for the LCD ldi rmp,cLcdWrite ; Data direction to output out pLcdDdr,rmp ldi rmp,cLcdDummy ; Dummy to catch LCD rcall Lcd4Set ; send three times with 5 ms delay rcall LcdDelay5ms ldi rmp,cLcdDummy rcall Lcd4Set rcall LcdDelay5ms ldi rmp,cLcdDummy rcall Lcd4Set rcall LcdDelay5ms ldi rmp,0b00101000 ; Function Set to 4 Bit rcall Lcd4Ctrl ; output on the Control Port LCD ldi rmp,0b00010100 ; Cursor display shift rcall Lcd4Ctrl ldi rmp,0b00001100 ; LCD on rcall Lcd4Ctrl ldi rmp,0b00000110 ; Entry mode rcall Lcd4Ctrl Lcd4Clear: ldi rmp,0b00000001 ; Set Lcd Clear rcall Lcd4Ctrl Lcd4Home: ldi rmp,0b00000010 ; Set LCD Home Position ; ; Output of rmp on the Control-Port of the LCD ; Lcd4Ctrl: push rmp ; save byte andi rmp,0xF0 ; clear lower nibble rcall Lcd4Set ; output upper nibble pop rmp ; restore byte swap rmp ; swap lower and upper nibble andi rmp,0xF0 ; clear lower nibble rcall Lcd4Set ; output lower nibble rjmp LcdDelay5ms ; done. ; ; Display the packed BCD in rmp on the LCD ; Lcd4PBcd: push rmp ; Save on stack swap rmp ; Higher to lower nibble rcall Lcd4PBcd1 ; Output nibble pop rmp ; Restore from stack Lcd4PBcd1: andi rmp,0x0F ; Mask upper nibble ori rmp,0x30 ; Nibble to ASCII ; ; Display char in rmp on the LCD ; Lcd4Chr: push rmp ; save char on stack andi rmp,0xF0 ; clear lower nibble sbr rmp,mLcdRs ; Set RS-Bit rcall Lcd4Set ; output nibble pop rmp ; get char from stack swap rmp ; swap nibbles andi rmp,0xF0 ; clear lower nibble sbr rmp,mLcdRs ; Set RS-Bit rcall Lcd4Set ; output nibble rjmp LcdDelay50us ; ready ; ; Send nibble in rmp to LCD ; Lcd4Set: out pLcdPort,rmp ; Byte to output port nop sbi pLcdPort,bLcdEn ; Set Enable-Bit enactive ; Delay macro cbi pLcdPort,bLcdEn ; Clear Enable Bit nop ret ; ; Delay by 1 second on start-up ; LcdDelay1s: ldi rmp,c1s ; 200 * 5 ms wait LcdDelay1s1: rcall LcdDelay5ms dec rmp brne LcdDelay1s1 ret ; ; Delay by 5 ms following each Control Word ; LcdDelay5ms: push ZH push ZL ldi ZH,HIGH(c5ms) ldi ZL,LOW(c5ms) LcdDelay5ms1: sbiw ZL,1 brne LcdDelay5ms1 pop ZL pop ZH ret ; ; Delay by 50 Microseconds after each Char ; LcdDelay50us: ldi rmp,c50us LcdDelay50us1: nop dec rmp brne LcdDelay50us1 ret ; ; Display at the position in rmp the string starting at Z (nullterm.) ; Lcd4ZTxt: sbr rmp,0b10000000 ; Set DD-RAM-Adress rcall Lcd4Ctrl Lcd4ZTxt1: lpm ; Get a char tst R0 ; Null-Char? breq Lcd4ZTxtR mov rmp,R0 rcall Lcd4Chr ; display the cahr in rmp adiw ZL,1 ; next char rjmp Lcd4ZTxt1 ; do it again Lcd4ZTxtR: ret ; ; Display rmp chars from SRAM starting at Z on the LCD ; Lcd4RTxt: mov R0,rmp ; R0 is counter Lcd4RTxt1: ld rmp,Z+ ; read char rcall Lcd4Chr dec R0 brne Lcd4RTxt1 ret
; ***************************************** ; * LCD-base routines for 4-Bit-interface * ; * of a 2-line LCD display to an ATMEL- * ; * Port, Bits 4..7=Data, Bit 1=RS, Bit0=E* ; * Version 1, February 2002, (C) 2002 by * ; * Gerhard Schmidt, bug reports and sug- * ; * gestions to [email protected] * ; ***************************************** ; ; Hardware: LCD-Display on the active port ; ; Definitions, that must be defined in the calling ; program: ; - Stack operations must be initialised ; - Register rmp (R16..R31) ; - Clock frequency ftakt ; - pLcdPort Active LCD-Port ; - pLcdDdr Data direction registerof the active port ; Subroutines: ; - Lcd4Init: Initialise the LCD ; - Lcd4Chr: Display the character in rmp on the LCD ; - Lcd4PBcd: Display the packed BCD in rmp on the LCD ; - Lcd4ZTxt: Display the null-terminated string on the LCD ; - Lcd4RTxt: Display rmp chars from SRAM, starting at Z ; ; Definition for the LCD-port .EQU cLcdWrite=0b11111111 ; Data direction write the LCD .EQU cLcdDummy=0b00111000 ; Dummy-Function-Word .EQU mLcdRs=0b00000010 ; RS-Bit Mask .EQU bLcdEn=0 ; Enable Bit .EQU c1s=200 ; Wait at start-up time (200 * 5 ms) .EQU c5ms=ftakt/800 ; 5 ms Wait after each control word .EQU c50us=ftakt/80000 ; 50 us Wait after each char ; ; Macro for Enable active time ; ; Version for 10 Mcs clock ;.MACRO enactive ; nop ; nop ; nop ; nop ; nop ;.ENDMACRO ; ; Version für 4 Mcs clock ; .MACRO enactive nop nop .ENDMACRO ; Lcd4Init: rcall LcdDelay1s ; Wait a second for the LCD ldi rmp,cLcdWrite ; Data direction to output out pLcdDdr,rmp ldi rmp,cLcdDummy ; Dummy to catch LCD rcall Lcd4Set ; send three times with 5 ms delay rcall LcdDelay5ms ldi rmp,cLcdDummy rcall Lcd4Set rcall LcdDelay5ms ldi rmp,cLcdDummy rcall Lcd4Set rcall LcdDelay5ms ldi rmp,0b00101000 ; Function Set to 4 Bit rcall Lcd4Ctrl ; output on the Control Port LCD ldi rmp,0b00010100 ; Cursor display shift rcall Lcd4Ctrl ldi rmp,0b00001100 ; LCD on rcall Lcd4Ctrl ldi rmp,0b00000110 ; Entry mode rcall Lcd4Ctrl Lcd4Clear: ldi rmp,0b00000001 ; Set Lcd Clear rcall Lcd4Ctrl Lcd4Home: ldi rmp,0b00000010 ; Set LCD Home Position ; ; Output of rmp on the Control-Port of the LCD ; Lcd4Ctrl: push rmp ; save byte andi rmp,0xF0 ; clear lower nibble rcall Lcd4Set ; output upper nibble pop rmp ; restore byte swap rmp ; swap lower and upper nibble andi rmp,0xF0 ; clear lower nibble rcall Lcd4Set ; output lower nibble rjmp LcdDelay5ms ; done. ; ; Display the packed BCD in rmp on the LCD ; Lcd4PBcd: push rmp ; Save on stack swap rmp ; Higher to lower nibble rcall Lcd4PBcd1 ; Output nibble pop rmp ; Restore from stack Lcd4PBcd1: andi rmp,0x0F ; Mask upper nibble ori rmp,0x30 ; Nibble to ASCII ; ; Display char in rmp on the LCD ; Lcd4Chr: push rmp ; save char on stack andi rmp,0xF0 ; clear lower nibble sbr rmp,mLcdRs ; Set RS-Bit rcall Lcd4Set ; output nibble pop rmp ; get char from stack swap rmp ; swap nibbles andi rmp,0xF0 ; clear lower nibble sbr rmp,mLcdRs ; Set RS-Bit rcall Lcd4Set ; output nibble rjmp LcdDelay50us ; ready ; ; Send nibble in rmp to LCD ; Lcd4Set: out pLcdPort,rmp ; Byte to output port nop sbi pLcdPort,bLcdEn ; Set Enable-Bit enactive ; Delay macro cbi pLcdPort,bLcdEn ; Clear Enable Bit nop ret ; ; Delay by 1 second on start-up ; LcdDelay1s: ldi rmp,c1s ; 200 * 5 ms wait LcdDelay1s1: rcall LcdDelay5ms dec rmp brne LcdDelay1s1 ret ; ; Delay by 5 ms following each Control Word ; LcdDelay5ms: push ZH push ZL ldi ZH,HIGH(c5ms) ldi ZL,LOW(c5ms) LcdDelay5ms1: sbiw ZL,1 brne LcdDelay5ms1 pop ZL pop ZH ret ; ; Delay by 50 Microseconds after each Char ; LcdDelay50us: ldi rmp,c50us LcdDelay50us1: nop dec rmp brne LcdDelay50us1 ret ; ; Display at the position in rmp the string starting at Z (null-term.) ; Lcd4ZTxt: sbr rmp,0b10000000 ; Set DD-RAM-Adress rcall Lcd4Ctrl Lcd4ZTxt1: lpm ; Get a char tst R0 ; Null-Char? breq Lcd4ZTxtR mov rmp,R0 rcall Lcd4Chr ; display the cahr in rmp adiw ZL,1 ; next char rjmp Lcd4ZTxt1 ; do it again Lcd4ZTxtR: ret ; ; Display rmp chars from SRAM starting at Z on the LCD ; Lcd4RTxt: mov R0,rmp ; R0 is counter Lcd4RTxt1: ld rmp,Z+ ; read char rcall Lcd4Chr dec R0 brne Lcd4RTxt1 ret http://www.avr-asm-tutorial.net/avr_en/source/Lcd4IncE.asm1/20/2009 8:00:33 PM
Pfad: Home => AVR-Overview => 2-Line-LCD on STK500 => demo clock
; *************************************************************** ; * Clock with a 2-line-LCD for STK500 using Timer/Counter1 * ; * Connect the LCD over a 4-bit-data cable to the Port A * ; * Bit0=E, Bit1=RS, Bit4..7:D4..D7 * ; * Includes the LCD basic routines in Lcd4IncE.asm * ; * Adjusted to the board's clock of 3.685 MHz (STK500) * ; * (C)2002 by info!at!avr-asm-tutorial.net * ; * Created: 16.2.2002, Last change: 28.02.2002 * ; *************************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Timing-Scheme to create the 1-second-clock ; ;3.685.000Hz--> 460.625Hz --> 67 Hz --> 1 Hz ; ;+---------+ +----------+ +----------+ +--------+ ;|CPU-Clock| |TC1-Divid.| |TC1-Compa-| |Register| ;|3,685 Mcs|-->|Prescaler |-->|re Match A|-->| rdiv1s |--> 1 s ;|7,37Mcs/2| |Divid. /8 | | /6875 | | /67 | ;+---------+ +----------+ +----------+ +--------+ ; ; Constants ; .EQU ftakt = 3685000 ; Frequency STK500 internal clock .EQU cdivtc1 = 6875 ; Dibvider for TC1 .EQU cdiv1s = 67 ; Divider for 1 s ; ; Active Ports for LCD-Output ; .EQU pLcdPort=PORTA ; LCD connected to PORT A .EQU pLcdDdr=DDRA ; Datenrichtungsregister LCD-Port ; ; Benutzte Register ; .DEF rint= R15 ; Interrupt temp register .DEF rmp = R16 ; Multi-Purpose Register .DEF rdiv1s = R17 ; Teiler durch 67 ; ; Datensegment ; ; SRAM-Speicher für Datum und Uhrzeit ; ; Packed BCD: Z=Bit7..4, E=Bit3..0 ; ; $0060 +1 +2 +3 +4 +5 ; +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ ; | Day | |Month| | Year| | Hour| |Minut| |Secnd| ; | Z E | | Z E | | Z E | | Z E | | Z E | | Z E | ; +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ ; .DSEG .ORG $0060 ; Data segment begins at $0060 ramdt: .BYTE 6 ; dd mm yy hh mm ss, reserve packed BCD ; ; Code starts here ; .CSEG .ORG $0000 ; ; Reset- and Interrupt-Vectors ; rjmp Start ; Reset-vector reti ; External Interrupt Request 0 reti ; External Interrupt Request 1 reti ; Timer/Counter1 Capture event rjmp TCmp1A ; Timer/Counter1 Compare match A reti ; Timer/Counter1 Compare match B reti ; Timer/Counter1 Overflow reti ; Timer/Counter0 Overflow reti ; SPI Serial Transfer complete reti ; Uart Rx char available reti ; Uart Tx data register empty reti ; Uart Tx complete reti ; Analog comparator ; ; ************** Interrupt service routines ******** ; ; ; Timer/Counter 1, Compare match A interrupt ; TCmp1A: in rint,SREG ; save status inc rdiv1s ; Divider by 67, inc by one out SREG,rint ; restore status register reti ; ; ******** End of the Interrupt Service Routines ******* ; ; ******** Some subroutines ********* ; ; Include LCD-4-Bit-Routines ; .NOLIST .INCLUDE "Lcd4Inc.asm" .LIST ; ; Init date-time default ; initdt: ldi ZH,HIGH(2*initdtt) ; Set date/time to default ldi ZL,LOW(2*initdtt) ldi XH,HIGH(ramdt) ldi XL,LOW(ramdt) ldi rmp,6 initdt1: lpm st X+,R0 adiw ZL,1 dec rmp brne initdt1 ret initdtt: ; Default date and time table .DB 0x28,0x02,0x02,0x14,0x05,0x00 ; Packed date/time ; ; Add 1 to BCD number that Z points to ; R0 must be 0x06, R1 is used temporarily, R2 is restart ; value, rmp is maximum value for overflow ; return with carry set if no overflow occurs ; inct: ld R1,Z ; Read Packed BCD to R1 sec ; Set carry adc R1,R0 ; add 06 and carry brhs inct1 ; If half carry: don't sub 06 sub R1,R0 ; no half carry inct1: cp R1,rmp ; max value reached? brcs inct2 ; no overflow mov R1,R2 ; set to restart value inct2: st Z,R1 ; Write to RAM ret ; and return ; ; Display date on LCD ; dispdate: clr rmp ; Set LCD home position ldi ZH,HIGH(2*datet) ; display Date-Text ldi ZL,LOW(2*datet) rcall Lcd4ZTxt ; Display null-terminated string ldi rmp,'.' ; Separator for date mov R0,rmp ldi ZH,HIGH(ramdt) ; Point to date ldi ZL,LOW(ramdt) rcall disp3 ; Display three packed BCDs ; ; Display time on LCD ; disptime: ldi rmp,0x40 ; LCD Cursor to start of line 2 ldi ZH,HIGH(2*timet) ; Display Time-Text ldi ZL,LOW(2*timet) rcall Lcd4ZTxt ; Display null-terminated string ldi rmp,':' ; Separator for time mov R0,rmp ldi ZH,HIGH(ramdt+3) ; Point to time ldi ZL,LOW(ramdt+3) rcall disp3 ; Display three packed BCDs lds rmp,ramdt+5 ; Read seconds com rmp ; Invert out PORTB,rmp ; und display on the LEDs ret ; Fertig ; ; Strings, null-terminated, for date and time on LCD ; datet: .DB "Date: ",0x00,0x00 timet: .DB "Time: ",0x00,0x00 ; ; Display three packed BCDs starting at Z on the LCD ; Separator (: or .) in R0 ; disp3: ld rmp,Z+ ; Read packed BCD 1 rcall Lcd4PBcd ; Display packed BCD mov rmp,R0 ; Display separator rcall Lcd4Chr ld rmp,Z+ ; Read next packed BCD rcall Lcd4PBcd mov rmp,R0 ; Display separator rcall Lcd4Chr ld rmp,Z ; Read third packed BCD rjmp Lcd4PBcd ; ; **************** End of the subroutines **************** ; ; ***************** Main loop starts here **************** ; ; Main program starts here ; Start: ldi rmp,HIGH(RAMEND) ; Initiate stack pointer out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,0b00100000 ; Sleep Mode Idle out MCUCR,rmp ser rmp ; Port B to output, for the LEDs out DDRB,rmp rcall Lcd4Init ; Init LCD output rcall initdt ; Init date and time default rcall dispdate ; Default date/time to LCD clr rmp out TCNT1H,rmp ; High Byte TC1 clear out TCNT1L,rmp ; Low Byte TC1 clear ldi rmp,HIGH(cdivtc1) ; Compare Match, MSB first out OCR1AH,rmp ldi rmp,LOW(cdivtc1) ; Compare Match A, LSB last out OCR1AL,rmp ldi rmp,0b01000000 ; Toggle Output A on Comp Match out TCCR1A,rmp ldi rmp,0b00001010 ; Clear on Comp Match A, Div 8 out TCCR1B,rmp ; Start Timer ldi rmp,0b01000000 ; TC1 CompA Int Enable out TIMSK,rmp sei loop: sleep ; sleep until int at clock nop ; wake-up cpi rdiv1s,cdiv1s ; 67 reached? brcs loop ; not yet clr rdiv1s ; New start, second has ended ldi rmp,0x06 ; Add Packed BCD Seconds mov R0,rmp ; Constant, needed for packed BCD clr R2 ; Start value for overflow to upper digits ldi rmp,0x60 ; overflow at 60 seconds ldi ZH,HIGH(ramdt+5) ; point to SRAM-adress seconds ldi ZL,LOW(ramdt+5) rcall inct ; inc seconds brcs tok ; Time is ok sbiw ZL,1 ; set pointer to minutes rcall inct ; inc minutes brcs tok ; time is ok ldi rmp,0x24 ; maximum value for hours sbiw ZL,1 ; Point to hours rcall inct ; inc hours brcs tok ; time is ok inc R2 ; Set Day/Month default to 1 ldi ZH,HIGH(ramdt+1) ; Point to month ldi ZL,LOW(ramdt+1) ld rmp,Z ; Read month cpi rmp,0x02 ; February? brne nonfeb ; Not February adiw ZL,1 ; Point to year ld rmp,Z ; Read year tst rmp ; Year=00? breq m28 ; February 2000 is 28 days only andi rmp,0x10 ; Tens of Years=odd? ld rmp,Z ; Read year again brne yodd ; Tens of years odd andi rmp,0x03 ; lower nibble year=0,4,8? brne m28 ; February has 28 days m29: ldi rmp,0x30 ; February has 29 days rjmp mok ; month is ok yodd: andi rmp,0x03 ; Tens of years are odd cpi rmp,0x02 ; Lower nibble year=2 or 6? breq m29 ; Yes, February has 29 days m28: ldi rmp,0x29 ; February has 28 days rjmp mok nonfeb: cpi rmp,0x07 ; Month > June? brcs monthok ; No, don't inc dec rmp ; back one month monthok: andi rmp,0x01 ; Odd month? brne m31 ; Month has 31 days ldi rmp,0x31 ; Month has 30 days rjmp mok m31: ldi rmp,0x32 ; Month has 31 days mok: ldi ZH,HIGH(ramdt) ; Point to day ldi ZL,LOW(ramdt) rcall inct ; add 1 day brcs dok ; Date is ok adiw ZL,1 ; Point to month ldi rmp,0x13 ; Max monthes rcall inct ; next month brcs dok ; Date is ok adiw ZL,1 ; Point to year clr R2 ; Default year=00 rcall inct ; Next year ; ; Refresh date on LCD ; dok: rcall dispdate ; Display date ; ; Refresh time on LCD ; tok: rcall disptime ; Display time rjmp loop ; ; End of the program ;
; *************************************************************** ; * Clock with a 2-line-LCD for STK500 using Timer/Counter1 * ; * Connect the LCD over a 4-bit-data cable to the Port A * ; * Bit0=E, Bit1=RS, Bit4..7:D4..D7 * ; * Includes the LCD basic routines in Lcd4IncE.asm * ; * Adjusted to the board's clock of 3.685 MHz (STK500) * ; * (C)2002 by [email protected] * ; * Created: 16.2.2002, Last change: 2802.2002 * ; *************************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Timing-Scheme to create the 1-second-clock ; ;3.685.000Hz--> 460.625Hz --> 67 Hz --> 1 Hz ; ;+---------+ +----------+ +----------+ +--------+ ;|CPU-Clock| |TC1-Divid.| |TC1-Compa-| |Register| ;|3,685 Mcs|-->|Prescaler |-->|re Match A|-->| rdiv1s |--> 1 s ;|7,37Mcs/2| |Divid. /8 | | /6875 | | /67 | ;+---------+ +----------+ +----------+ +--------+ ; ; Constants ; .EQU ftakt = 3685000 ; Frequency STK500 internal clock .EQU cdivtc1 = 6875 ; Dibvider for TC1 .EQU cdiv1s = 67 ; Divider for 1 s ; ; Active Ports for LCD-Output ; .EQU pLcdPort=PORTA ; LCD connected to PORT A .EQU pLcdDdr=DDRA ; Datenrichtungsregister LCD-Port ; ; Benutzte Register ; .DEF rint= R15 ; Interrupt temp register .DEF rmp = R16 ; Multi-Purpose Register .DEF rdiv1s = R17 ; Teiler durch 67 ; ; Datensegment ; ; SRAM-Speicher für Datum und Uhrzeit ; ; Packed BCD: Z=Bit7..4, E=Bit3..0 ; ; $0060 +1 +2 +3 +4 +5 ; +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ ; | Day | |Month| | Year| | Hour| |Minut| |Secnd| ;|ZE||ZE||ZE||ZE||ZE||ZE| ; +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ ; .DSEG .ORG $0060 ; Data segment begins at $0060 ramdt: .BYTE 6 ; dd mm yy hh mm ss, reserve packed BCD ; ; Code starts here ; .CSEG .ORG $0000 ; ; Reset- and Interrupt-Vectors ; rjmp Start ; Reset-vector reti ; External Interrupt Request 0 reti ; External Interrupt Request 1 reti ; Timer/Counter1 Capture event rjmp TCmp1A ; Timer/Counter1 Compare match A reti ; Timer/Counter1 Compare match B reti ; Timer/Counter1 Overflow reti ; Timer/Counter0 Overflow reti ; SPI Serial Transfer complete reti ; Uart Rx char available reti ; Uart Tx data register empty reti ; Uart Tx complete reti ; Analog comparator ; ; ************** Interrupt service routines ******** ; ; ; Timer/Counter 1, Compare match A interrupt ; TCmp1A: in rint,SREG ; save status inc rdiv1s ; Divider by 67, inc by one out SREG,rint ; restore status register reti ; ; ******** End of the Interrupt Service Routines ******* ; ; ******** Some subroutines ********* ; ; Include LCD-4-Bit-Routines ; .NOLIST .INCLUDE "Lcd4IncE.asm" .LIST ; ; Init date-time default ; initdt: ldi ZH,HIGH(2*initdtt) ; Set date/time to default ldi ZL,LOW(2*initdtt) ldi XH,HIGH(ramdt) ldi XL,LOW(ramdt) ldi rmp,6 initdt1: lpm st X+,R0 adiw ZL,1 dec rmp brne initdt1 ret initdtt: ; Default date and time table .DB 0x28,0x02,0x02,0x14,0x05,0x00 ; Packed date/time ; ; Add 1 to BCD number that Z points to ; R0 must be 0x06, R1 is used temporarily, R2 is restart ; value, rmp is maximum value for overflow ; return with carry set if no overflow occurs ; inct: ld R1,Z ; Read Packed BCD to R1 sec ; Set carry adc R1,R0 ; add 06 and carry brhs inct1 ; If half carry: don't sub 06 sub R1,R0 ; no half carry inct1: cp R1,rmp ; max value reached? brcs inct2 ; no overflow mov R1,R2 ; set to restart value inct2: st Z,R1 ; Write to RAM ret ; and return ; ; Display date on LCD ; dispdate: clr rmp ; Set LCD home position ldi ZH,HIGH(2*datet) ; display Date-Text ldi ZL,LOW(2*datet) rcall Lcd4ZTxt ; Display null-terminated string ldi rmp,'.' ; Separator for date mov R0,rmp ldi ZH,HIGH(ramdt) ; Point to date ldi ZL,LOW(ramdt) rcall disp3 ; Display three packed BCDs ; ; Display time on LCD ; disptime: ldi rmp,0x40 ; LCD Cursor to start of line 2 ldi ZH,HIGH(2*timet) ; Display Time-Text ldi ZL,LOW(2*timet) rcall Lcd4ZTxt ; Display null-terminated string ldi rmp,':' ; Separator for time mov R0,rmp ldi ZH,HIGH(ramdt+3) ; Point to time ldi ZL,LOW(ramdt+3) rcall disp3 ; Display three packed BCDs lds rmp,ramdt+5 ; Read seconds com rmp ; Invert out PORTB,rmp ; und display on the LEDs ret ; Fertig ; ; Strings, null-terminated, for date and time on LCD ; datet: .DB "Date: ",0x00,0x00 timet: .DB "Time: ",0x00,0x00 ; ; Display three packed BCDs starting at Z on the LCD ; Separator (: or .) in R0 ; disp3: ld rmp,Z+ ; Read packed BCD 1 rcall Lcd4PBcd ; Display packed BCD mov rmp,R0 ; Display separator rcall Lcd4Chr ld rmp,Z+ ; Read next packed BCD rcall Lcd4PBcd mov rmp,R0 ; Display separator rcall Lcd4Chr ld rmp,Z ; Read third packed BCD rjmp Lcd4PBcd ; ; **************** End of the subroutines **************** ; ; ***************** Main loop starts here **************** ; ; Main program starts here ; Start: ldi rmp,HIGH(RAMEND) ; Initiate stack pointer out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,0b00100000 ; Sleep Mode Idle out MCUCR,rmp ser rmp ; Port B to output, for the LEDs out DDRB,rmp rcall Lcd4Init ; Init LCD output rcall initdt ; Init date and time default rcall dispdate ; Default date/time to LCD clr rmp out TCNT1H,rmp ; High Byte TC1 clear out TCNT1L,rmp ; Low Byte TC1 clear ldi rmp,HIGH(cdivtc1) ; Compare Match, MSB first out OCR1AH,rmp ldi rmp,LOW(cdivtc1) ; Compare Match A, LSB last out OCR1AL,rmp ldi rmp,0b01000000 ; Toggle Output A on Comp Match out TCCR1A,rmp ldi rmp,0b00001010 ; Clear on Comp Match A, Div 8 out TCCR1B,rmp ; Start Timer ldi rmp,0b01000000 ; TC1 CompA Int Enable out TIMSK,rmp sei loop: sleep ; sleep until int at clock nop ; wake-up cpi rdiv1s,cdiv1s ; 67 reached? brcs loop ; not yet clr rdiv1s ; New start, second has ended ldi rmp,0x06 ; Add Packed BCD Seconds mov R0,rmp ; Constant, needed for packed BCD clr R2 ; Start value for overflow to upper digits ldi rmp,0x60 ; overflow at 60 seconds ldi ZH,HIGH(ramdt+5) ; point to SRAM-adress seconds ldi ZL,LOW(ramdt+5) rcall inct ; inc seconds brcs tok ; Time is ok sbiw ZL,1 ; set pointer to minutes rcall inct ; inc minutes brcs tok ; time is ok ldi rmp,0x24 ; maximum value for hours sbiw ZL,1 ; Point to hours rcall inct ; inc hours brcs tok ; time is ok inc R2 ; Set Day/Month default to 1 ldi ZH,HIGH(ramdt+1) ; Point to month ldi ZL,LOW(ramdt+1) ld rmp,Z ; Read month cpi rmp,0x02 ; February? brne nonfeb ; Not February adiw ZL,1 ; Point to year ld rmp,Z ; Read year tst rmp ; Year=00? breq m28 ; February 2000 is 28 days only andi rmp,0x10 ; Tens of Years=odd? ld rmp,Z ; Read year again brne yodd ; Tens of years odd andi rmp,0x03 ; lower nibble year=0,4,8? brne m28 ; February has 28 days m29: ldi rmp,0x30 ; February has 29 days rjmp mok ; month is ok yodd: andi rmp,0x03 ; Tens of years are odd cpi rmp,0x02 ; Lower nibble year=2 or 6? breq m29 ; Yes, February has 29 days m28: ldi rmp,0x29 ; February has 28 days rjmp mok nonfeb: cpi rmp,0x07 ; Month > June? brcs monthok ; No, don't inc dec rmp ; back one month monthok: andi rmp,0x01 ; Odd month? brne m31 ; Month has 31 days ldi rmp,0x31 ; Month has 30 days rjmp mok m31: ldi rmp,0x32 ; Month has 31 days mok: ldi ZH,HIGH(ramdt) ; Point to day ldi ZL,LOW(ramdt) rcall inct ; add 1 day brcs dok ; Date is ok adiw ZL,1 ; Point to month ldi rmp,0x13 ; Max monthes rcall inct ; next month brcs dok ; Date is ok adiw ZL,1 ; Point to year clr R2 ; Default year=00 rcall inct ; Next year ; ; Refresh date on LCD ; dok: rcall dispdate ; Display date ; ; Refresh time on LCD ; tok: rcall disptime ; Display time rjmp loop ; ; End of the program ; http://www.avr-asm-tutorial.net/avr_en/source/Lcd4IncCE.asm1/20/2009 8:00:38 PM
AVR-PWM-ADC-Test for STK500
Pfad: Home => AVR-overview => PWM-ADC Tutorial for learning avr assembler language of
AVR-single-chip-processors AT90Sxxxx of ATMEL using practical examples.
Simple 8-bit-analog-digital-converter with PWM on the STK500 board Task Using the analog-inputs AIN0 and AIN1 and a few hardware components, one can build up a simple AD-converter. The build-in analog comparator is used here to compare an input voltage with a voltage produced with the timer/counter 1 (TC1) in PWM mode. This compare voltage is adjusted, until the result is as accurate as 8 bit resolution requires. TC1 works as pulse-width modulator, the pulse width relation determines the output voltage. The conversion result is displayed on the LEDs of the STK500 board.
Required hardware The picture on the left shows the whole test hardware to connect to the STK500 board. The port pins are viewed from above the board. The voltage to be measured varies between 0.00 to 5.00 volt. It is setup by the 10k potentiometer, using the board's supply voltage, and is filtered by a 100nF. This voltages goes to the analog comparator input AIN0, which is port PB2 on the AT90S8515 of the STK500 board. The compare voltage is generated by the PWM output OC1A, which is Port PD5 of a AT90S8515 on the board. The PWM rectangle produced by TC1 is filtered by three RC combinations of 10k/100nF and fed into the analog comparator input AIN1, which is port bit PB3 of the AT90S8515. That's it all.
Some further hints on that test application. The hardware components can be placed on a small external test board. The connections to the STK500 board can be done by using the STK500 supplied two-wire cables, if proper pins are mounted to the small additional board. Don't forget that the LEDs in this test application must be connected to port C, because we need port B for the analog inputs. Due to this, we can't use this hard- and software for the STK200 board (which has attached the LEDs to port B by hard wiring). After finishing work with this test setting, don't forget to reconnect the LEDs to port B again, using the 10wire parallel cable, other programs might not work correctly with the LEDs connected to port C.
To the top of that page
How it works Generating the compare voltage by pulse-width modulation A pulse-width-modulator generates a rectangle with a certain pulse width. This modulator holds a 1-signal for a certain time period, the rest of the whole time period it is zero. If this rectangle signal is filtered, the pulse width produces a certain analogue voltage. If the relation between pulse and pause of the PWM output is 50%, the voltage output is at 2.50 Volt, if it is at 25% the voltage is at 1.25 Volt, etc.
Loading and unloading of the RC filter is shown in the simulation graphic. The modulation of the PWM output by the rectangle U (PWM) is still visible in the voltage of RC1. This modulation is strongly reduced on RC2. But U(RC2) shows a delay, its lacking behind the mean voltage on U(RC1). This delay of the mean value is even higher on U(RC3). If the mean value has neared its end value very closely, there are still load- and unload-swings around this mean value. The RC filter must be dimensioned in a way that the remaining swings are well below the resolution of the AD converter. With 8 bits resolution and 5.0 Volt operating voltage the remaining swings have to be less than 5/256 = 19.5 mV. Higher resistor and capacitor values or adding further RC combinations reduce the voltage swings. On the other hand, the needed time to reach the mean end value gets longer then. Therefore a PWM based ADC is not very fast (in our case around five values per second). In practice, the compromise between delay time and remaining voltage swing should be optimised. The time value until the voltage reaches its end value and the sensing can take place is selected by the number of necessary PWM cycles. This number of cycles is represented by a constant in the software. In our case this software constant has been set to 128 cycles, which is a conservative value. Without changing the software itself, the constant can be set to up to 65536 PWM cycles. To play around with selecting R and C values, simulating load- and unload cycles, and selecting optimised cycle values, a small Pascal program was devellopped. After compilation it runs on a command line. The source code avr_pwm1.pas can be compiled with a compiler to yield an executable. The frequency that the PWM runs with, can be calculated for 8 bits resolution as f (PWM) = clockfrequency / 510 (9 bit: 1022, 10 bit: 2046) At a clock frequency of 3.685 Mcs/s on the STK500 board we yield 7,225.5 cs/s or 138.4 microseconds per PWM cycle. If we wait for 128 cycles for settlement of the mean voltage before sensing and 8 bit resolution, the conversion time is around 142 milliseconds per measurement or seven measurements per second. Not very fast, but even faster than a man's eye. Reference value of this circuitry is the operating voltage of the processor. This refence voltage can be adjusted with the studio software supplied with the STK500. The accuracy of the PWM signal in the lower range from 0.00 to 0.10 V, but even more in the most upper range, is limited, because the MOS output drivers do not reach exactly the operating voltage bounds. This causes some inaccuracy in the midrange and some non-linearity at the upper and lower bound of the operating voltage. Therefore it doesn't make much sense to design this software for 9 or 10 bits resolution. If you need more accurate values, use a processor with built-in ADC. To the top of that page
The method of sukzessive approximation To measure the input voltage, we could step up with the pulse width of the comparation voltage and stop this increase, if the comparision voltage is higher than the voltage on the measuring input pin. At 8 bit resolution, this would require up to 255 steps, at 10 bit resolution up to 1,023 steps. To speed this up, the conversion method of sukzessive approximation is applied. In the first step the comparator voltage is set to half of the operating voltage, 2.50 Volt. It is determined, if the input voltage is higher or lower than this. If the input voltage is lower, the next comparator voltage is set to 1.25 Volt. If is is higher, the next comparator value is set to 3.75 V. This step is repeated, until we know exact enough, how high the input voltage is. At 8 bits resolution we need to make eight steps, at 10 bits ten steps. This is by a factor of 30 resp. 100 faster than the stepwise increase of the comparator value. This algorithm can be programmed very easily. The fist five steps for an 8 bit conversion are shown in the table. The table lists the comparator voltages at step n and the corresponding value of the 8-bit PWM. If the comparator is higher than the measuring input, the next value is in the upper row of the table. Vice versa, if its smaller. 1 2 3 4 1000.0000 0100.0000 0010.0000 0001.0000
The steps are easy: at every step of the approximation the appropriate bit is first set to one. If the voltage of the PWM is too high, this bit is set to zero in the next step. Easy to program! To the top of that page
Software The software is available in HTML format ADC8.html and as ASM source code file ADC8.asm. The program spents very long times waiting for the PWM to settle for the mean value of the RC filter. This is ideal for using interrupts, because otherwise we would have to program very long and complicated timing loops. Because TC1 has nothing else to do than generating the PWM signal by counting up and down, we can use it for our whole timing too. The processor itself remains sleeping the whole time, and is woken up every 142 microseconds, when TC1 places an interrupt. After woken up and worked through the interrupt routine of TC1, the processor checks the ADC-ready flag. If it is not set, the processor goes back to sleep again. If the flag is set, the result is displayed on the LED output port and the flag is cleared.
Main program The main program runs the following steps: 1. The stack is set up. This is necessary because the program uses timer interrupts. Interrupts use the stack to store their return address on the stack. The stack is located and set up on the top of the SRAM space. 2. The cycle counter is set. The cycle counter determines the number of cycles of the PWM before the sensing of the comparision between the PWM-generated voltage and the analog input takes place. In the example, 128 cycles are selected as constant. Change the constant CycLen0, if you want to set the resampling rate to other values (e.g. to one measurement per second). 3. The bit counter rBlc is set to 0x80 at the beginning of a measuring cycle. This results in a comparator voltage of 2.50 V as comparator voltage. The temporary result rTmp is set to that value accordingly. 4. The port outputs of port C are selected as outputs to drive the LEDs. 5. Port bit PD5 of port D is selected as output, because it generates the PWM output signal on this pin. 6. The sleep mode is set to the idle mode. This allows the processor to react to the SLEEP instruction and to wake up on interrupts by TC1. 7. Timer/Counter 1 is set up as 8-bit-PWM, its clock input is connected to the processor clock, no prescaling takes place to yield the highest possible PWM clock rate. 8. PWM pulse width is set to 50% (0x0080). 9. The Timer Int Mask Register TIMSK is set to allow TC1 overflow interrupts, so that TC1 causes an interrupt each time that the PWM cycle has reached its end. 10. The general interrupt flag of the processor is set to one to allow interrupts. Now the loop starts, which is ran through each time the TC1 overflow interrupt wakes up the processor. After running through the vectored int routine the ready-flag is asked, if the AD conversion has ended and the result value is valid. If this is the case, the flag is cleared, the ADC result is inverted bitwise and the byte is outputted to the LEDs. Then the processor is set to sleep again, until the ready-flag is set after wake-up. To the top of that page
Interrupt processing Central routine of the AD conversion is the pulse width generator of timer/counter 1. Each time TC1 has reached its cycle end the overflow interrupt is generated, the program counter is copied to the stack and program execution jumps to the interrupt vector. This routine does all the necessary control of the PWM-ADC and sets the ready-flag on return, if the AD conversion is ready. After conversion, the next measuring cycle is automatically restarted. By selecting the PWM cycle numbers as constants on the top of the program, the repeating frequency of the measurements and the delay before sensing the different bits can be adjusted. The graphic left shows the algorithm for the whole control of the AD conversion. The int vector routine starts with saving the content of the processor's status register. Prior to returning from the interrupt, the original content of the register is restored. In the next step the cycle counter is decreased by one. If this cycle counter reaches zero, the comparator voltage generated by the PWM and filtered by the RC filter has reached its mean value. If the cycle counter is not zero, further cycles have to be ran through and the interrupt vector routine ends. If the necessary number of PWM cycles has been waited for, the PWM cycle counter is set to the constant value. Then the analog comparator output is asked for the result of the comparision. If the PWM generated comparation value is higher than the input voltage, the actual bit, represented by the value in rBlc, in rTmp is cleared. If not, then this actual bit remains one. Now the bit counter rBlc with the actual bit is shifted right one bit. By this instruction a zero enters rBlc from the left, and the content of the bit 0 is shifted to the carry flag in the status register. If a zero rolls out to carry, the conversion has not reached its end and the sukzessive approximation has to repeat for the next bit. If a one rolls out of the bit counter, the conversion is ready. The result in the temporary result register is copied to the result register, the readyflag in the flag register is set, the PWM cycle counter is set to the constant value that has been defined for the sensing of the highest bit, the temporary result register is cleared and the bit counter is set to $80 (for the next conversion). The currently active bit in rBlc is copied to the temporary result register (set active bit to one), and the current temporary value is copied to the timer/counter's compare register in order to adjust the comparision voltage on the AIN1 input of the comparator. At the end of the interrupt routine, the status register gets back its original content, and the interrupt routine returns.
The whole interrupt routine requires less than 25 clock cycles or 6 microseconds, and is very short. If there would be other tasks for the processor, this wouldn't be a serious delay.
Pfad: Home => AVR-overview => 8-bit-ADC with STK500 => adc8 software
Assembler source code for the conversion of an analogue voltage to an 8-bit-number ; +-----------------------------------------------------+ ; | 8-Bit-Analog-Digital-Converter for ATMEL AT90S8515 | ; | on the STK500 board, Output by LEDs | ; | (C)2003 by http://www.avr-asm-tutorial.net | | ; | Used ports: | ; | Output PWM pulse = OC1A on Port D, Bit 5, via ; | triple RC-filter to | | ; | Inverted analog input AIN1 on Port B Bit 3, | ; | Non-inverted analog input AIN0 on Port B Bit 2 ; | connected to voltage to be measured | | ; | Output Port C connected to LEDs displays result ; +-----------------------------------------------------+ ; ; Written for and tested with AT90S8515 on STK500 ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Constants ; .EQU CycLen = 128 ; Number of PWM-cycles to filter before sensing .EQU CycLen0 = 128 ; Number of PWM-cycles before first sensing ; ; Register ; .DEF rRes = R1 ; final value of the ADC .DEF rTmp = R14 ; Temporary value for ADC .DEF rSrg = R15 ; Temporary register für SREG interim storage in ints .DEF rmp = R16 ; Universal register outside ints .DEF rimp = R17 ; Universal register within ints .DEF rFlg = R18 ; Flag register, Bit 0 = ADC ready .DEF rBlc = R19 ; Bit-level-counter for ADC .DEF rCcL = R24 ; Cycle counter for ADC, LSB .DEF rCcH = R25 ; dto., MSB ; ; Reset- and Interrupt-vectors ; rjmp main ; Reset reti ; INT0 reti ; INT1 reti ; TC1-Capt reti ; TC1-CompA reti ; TC1-CompB rjmp Tc1Ovflw ; TC1-Ovflw reti ; TC0-Ovflw reti ; SPI STC reti ; UART RX reti ; UART UDRE reti ; UART TX reti ; Ana-Comp ; ; TC1-Overflow on each PWM-cycle end ; Tc1Ovflw: in rSrg,SREG ; Save SREG sbiw rCcL,1 ; decrement cycle counter word brne Tc1OvflwR ; if word<>0: int ready ldi rCcH,HIGH(CycLen) ; Set cycle counter for next bit ldi rCcL,LOW(CycLen) sbis ACSR,ACO ; check analogue comparator output eor rTmp,rBlc ; voltage too high, clear last bit lsr rBlc ; shift active bit one position right brcc Tc1OvflwA ; if carry<>0: not ready yet mov rRes,rTmp ; copy the result sbr rFlg,0x01 ; set ready-flag ldi rCcH,HIGH(CycLen0) ; set cycle counter for restart ldi rCcL,LOW(CycLen0) clr rTmp ; clear result ldi rBlc,0x80 ; set bit counter Tc1OvflwA: or rTmp,rBlc ; set next bit to one clr rimp ; set TC1-PWM pulse width out OCR1AH,rimp ; high byte first! out OCR1AL,rTmp ; then low byte! Tc1OvflwR: ; return from Int out SREG,rSrg ; restore SREG to original content reti ; set int flag, return ; ; Main program loop ; main: ldi rmp,HIGH(RAMEND) ; define stack out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rCcH,HIGH(CycLen0) ; set cycle length before first measurement ldi rCcL,LOW(CycLen0) ldi rBlc,0x80 ; set bit counter to highest bit value mov rTmp,rBlc ; clear result ldi rmp,0xFF ; port C all bits output, drives LEDs out DDRC,rmp ; all data direction bits = 1 sbi DDRD,PD5 ; PWM-output bit OC1A as output pin ldi rmp,0b00100000 ; define sleep-mode idle out MCUCR,rmp ldi rmp,0b10000001 ; TC1 as 8-bit-PWM, non-invertet out TCCR1A,rmp ; to TC1 control register A ldi rmp,0b00000001 ; TC1-clock = system clock out TCCR1B,rmp ; to TC1 control register B clr rmp ; puls-width of PWM to $0080 out OCR1AH,rmp ; first the HIGH byte! ldi rmp,0x80 ; then the LOW byte! out OCR1AL,rmp ldi rmp,0b10000000 ; TC1 overflow interrupt enable out TIMSK,rmp ; to Timer Int Mask Register sei ; enable CPU reaction to interrupts ; ; Main loop ; main1: sleep ; CPU sleeps nop ; dummy instruction, loaded before sleep sbrs rFlg,0 ; check ready flag bit 0 if ADC is ready rjmp main1 ; not ready, sleep on cbr rFlg,0x01 ; clear ready bit 0 mov rmp,rRes ; copy result of ADC com rmp ; invert all bits (LED active low) out PortC,rmp ; to LED port rjmp main1 ; done, go to sleep again ; ; End of program ;
; +-----------------------------------------------------+ ; | 8-Bit-Analog-Digital-Converter for ATMEL AT90S8515 | ; | on the STK500 board, Output by LEDs | ; | (C)2003 by http://www.avr-asm-tutorial.net | ; | Used ports: | ; | Output PWM pulse = OC1A on Port D, Bit 5, via | ;| triple RC-filter to | ; | Inverted analog input AIN1 on Port B Bit 3, | ; | Non-inverted analog input AIN0 on Port B Bit 2 | ;| connected to voltage to be measured | ; | Output Port C connected to LEDs displays result | ; +-----------------------------------------------------+ ; ; Written for and tested with AT90S8515 on STK500 ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Constants ; .EQU CycLen = 128 ; Number of PWM-cycles to filter before sensing .EQU CycLen0 = 128 ; Number of PWM-cycles before first sensing ; ; Register ; .DEF rRes = R1 ; final value of the ADC .DEF rTmp = R14 ; Temporary value for ADC .DEF rSrg = R15 ; Temporary register für SREG interim storage in ints .DEF rmp = R16 ; Universal register outside ints .DEF rimp = R17 ; Universal register within ints .DEF rFlg = R18 ; Flag register, Bit 0 = ADC ready .DEF rBlc = R19 ; Bit-level-counter for ADC .DEF rCcL = R24 ; Cycle counter for ADC, LSB .DEF rCcH = R25 ; dto., MSB ; ; Reset- and Interrupt-vectors ; rjmp main ; Reset reti ; INT0 reti ; INT1 reti ; TC1-Capt reti ; TC1-CompA reti ; TC1-CompB rjmp Tc1Ovflw ; TC1-Ovflw reti ; TC0-Ovflw reti ; SPI STC reti ; UART RX reti ; UART UDRE reti ; UART TX reti ; Ana-Comp ; ; TC1-Overflow on each PWM-cycle end ; Tc1Ovflw: in rSrg,SREG ; Save SREG sbiw rCcL,1 ; decrement cycle counter word brne Tc1OvflwR ; if word<>0: int ready ldi rCcH,HIGH(CycLen) ; Set cycle counter for next bit ldi rCcL,LOW(CycLen) sbis ACSR,ACO ; check analogue comparator output eor rTmp,rBlc ; voltage too high, clear last bit lsr rBlc ; shift active bit one position right brcc Tc1OvflwA ; if carry<>0: not ready yet mov rRes,rTmp ; copy the result sbr rFlg,0x01 ; set ready-flag ldi rCcH,HIGH(CycLen0) ; set cycle counter for restart ldi rCcL,LOW(CycLen0) clr rTmp ; clear result ldi rBlc,0x80 ; set bit counter Tc1OvflwA: or rTmp,rBlc ; set next bit to one clr rimp ; set TC1-PWM pulse width out OCR1AH,rimp ; high byte first! out OCR1AL,rTmp ; then low byte! Tc1OvflwR: ; return from Int out SREG,rSrg ; restore SREG to original content reti ; set int flag, return ; ; Main program loop ; main: ldi rmp,HIGH(RAMEND) ; define stack out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rCcH,HIGH(CycLen0) ; set cycle length before first measurement ldi rCcL,LOW(CycLen0) ldi rBlc,0x80 ; set bit counter to highest bit value mov rTmp,rBlc ; clear result ldi rmp,0xFF ; port C all bits output, drives LEDs out DDRC,rmp ; all data direction bits = 1 sbi DDRD,PD5 ; PWM-output bit OC1A as output pin ldi rmp,0b00100000 ; define sleep-mode idle out MCUCR,rmp ldi rmp,0b10000001 ; TC1 as 8-bit-PWM, non-invertet out TCCR1A,rmp ; to TC1 control register A ldi rmp,0b00000001 ; TC1-clock = system clock out TCCR1B,rmp ; to TC1 control register B clr rmp ; puls-width of PWM to $0080 out OCR1AH,rmp ; first the HIGH byte! ldi rmp,0x80 ; then the LOW byte! out OCR1AL,rmp ldi rmp,0b10000000 ; TC1 overflow interrupt enable out TIMSK,rmp ; to Timer Int Mask Register sei ; enable CPU reaction to interrupts ; ; Main loop ; main1: sleep ; CPU sleeps nop ; dummy instruction, loaded before sleep sbrs rFlg,0 ; check ready flag bit 0 if ADC is ready rjmp main1 ; not ready, sleep on cbr rFlg,0x01 ; clear ready bit 0 mov rmp,rRes ; copy result of ADC com rmp ; invert all bits (LED active low) out PortC,rmp ; to LED port rjmp main1 ; done, go to sleep again ; ; End of program ; http://www.avr-asm-tutorial.net/avr_en/source/ADC8.asm1/20/2009 8:00:52 PM
R/2R-network as DAC for an AVR
Path: Home => AVR-overview => R/2R-DAC
Tutorial for learning avr assembler language of
AVR-single-chipprocessors AT90Sxxxx of ATMEL using practical examples.
Simple 8-bit-digitalto-analog-converter using a R/2R-network
Task A conversion of digital values to an analogue voltage can be performed using an integrated circuit. A cheaper and less sophisticated method is a R/2R-network, followed by an opamp. A R/2R-network is made of resistors like shown in the picture. The bits, either at 0 or the operating voltage, enter the network via a resistor of a double value than the rest of the network. Each bit contributes its part to the resulting voltage on the output. Believe it or not, this works fine. The commercial Digital-to-AnalogConverters have those resistor networks within their integrated circuits. The output of an AVR port doesn't drive much current, if its voltages should stay near either ground or the operating voltage. So, the resistors should be above several tens of kilo-ohms. Because these networks do not provide much current, an opamp decouples the R/2R-network from the following rest of the circuit. The resistor values should be as accurate as the whole network is expected to be accurate. Deviations are especially relevant for the higher bits. The following table shows some stepwise increases in the output voltage of a R/2R-network when using a 51/100k combination of resistors. (Calculations were made with a FreePascal program, free source code can be downloaded from here).
Note the extraordinary step in voltage when Bit 7 goes from low to high! It jumps up more than two ordinary steps. This might be unacceptable for an 8-bit-network, but its fine for a four-bit-network.
Required hardware The hardware is easy to built, it is a real resistor grave-yard.
The CA3140 is an opamp with a FET stage on the input, that can be driven down to the negative operating voltage. You might use a 741 opamp instead, but the consequences are discucssed below. The operating voltage of 5 Volt here is taken from the 10-pin-connector. This connector fits to a STK200 or STK500 board. It is also possible to feed the opamp by a higher voltage, which has some advantages (see below). Instead of two parallel resitors, you can use resistors that have approximately half the value of 100k, e.g. 51k. As the network has a resolution of 256, this brings some inaccuracy, see above. To the top of that page
Applying the R/2R-network Generating a sawtooth The following program produces a sawtooth voltage on the output of the R/2R-network. The source code is available for download here. ; ************************************************************** ; * R/2R-Network produces a sawtooth signal on Port D * ; * (C)2005 by info!at!avr-asm-tutorial.net * ; ************************************************************** ; .INCLUDE "8515def.inc" ; ; Register definitions ; .DEF rmp = R16 ; Multipurpose register ; ldi rmp,0xFF; Set all pins of Port D as output out DDRD,rmp sawtooth: out PORTD,rmp inc rmp rjmp sawtooth
The result is somewhat surprising. Doesn't look like a sawtooth but like a saw that has done some efforts in cutting steel. The reason for this is not the R/2R-network but the opamp. He doesn't work fine at the upper bounds of the operating voltage.
So we have to limit the output of the R/2R-network to approximately 2.5 Volts. This is done by software (source code to be downloaded here). ; ************************************************************** ; * R/2R-Network produces a sawtooth signal on Port D * ; * (C)2005 by info!at!avr-asm-tutorial.net * ; ************************************************************** ; .INCLUDE "8515def.inc" ; ; Register definitions ; .DEF rmp = R16 ; Multipurpose register ; ldi rmp,0xFF; Set all pins of Port D as output out DDRD,rmp sawtooth: out PORTD,rmp inc rmp andi rmp,0x7F ; set bit 7 to zero rjmp sawtooth That looks much better now. Note that we would not need Bit 7 of the port now, it can be tied to ground, as it is always low.
Here's the result of exchanging the CA3140 buffer to a cheaper 741 opamp. The 741 is neither operating near the operating voltage nor does he work near ground. So the voltages of our R/2R-network would have to be limited from a minimum of approximately 2 Volts to a maximum of 4 Volts.
To the top of that page
Generating a triangle Generating a triangle is a similiarly easy task: just counting up and down. The source code can be downloaded here. It allows adjusting the frequency by changing the value of the constants "delay" and the upper bound by adjusting the constant "maxAmp". ; ************************************************************** ; * R/2R-Network produces a triangle signal on Port D * ; * (C)2005 by info!at!avr-asm-tutorial.net * ; ************************************************************** ; .INCLUDE "8515def.inc" ; ; Register definitions ; .DEF rmp = R16 ; Multipurpose register .DEF rdl = R17 ; Delay counter ; ; Constants ; .EQU maxAmp = 127 ; Maximum amplitude setting .EQU delay = 1 ; Delay, higher value causes lower frequency ; ; Main program start ; ldi rmp,0xFF; Set all pins of Port D as output out DDRD,rmp triangle: clr rmp loopup: out PORTD,rmp ; to port ldi rdl,delay delayup: dec rdl brne delayup inc rmp ; next higher value cpi rmp,maxAmp brcs loopup loopdwn: out PORTD,rmp ldi rdl,delay delaydwn: dec rdl brne delaydwn dec rmp brne loopdwn rjmp triangle ; and again forever
To the left, the voltage is limited to 2.5 V, to the right it's the full 5.0 V.
To the top of that page
Generating a sinewave At last we need the famous sinewave. If you think that I'll program a sine function generator for that in assembler, you're wrong. I'll let this do the PC in Pascal (by use of the small free-pascal program that can be downloaded here) and include the resulting sinewave table (download here) to my program (download here. ; ************************************************************** ; * Produces a sinewave on a R/2R-network connected to PORTD * ; * (C)2005 by avr-asm-tutorial.net * ; ************************************************************** ; .INCLUDE "8515def.inc" ; ; Register definitions ; .DEF rmp = R16 ; Multipurpose register ; ; Start of program source code ; ldi rmp,0xFF ; Set all pins of port D to be output out DDRD,rmp ldi ZH,HIGH(2*SineTable) ; Point Z to Table in flash ldi ZL,LOW(2*SineTable) clr rmp loop1: nop nop nop loop2: lpm ; Read from table out PORTD,R0 ; Write value to port D adiw ZL,1 ; point to next value dec rmp ; End of Table reached brne loop1 ldi ZH,HIGH(2*SineTable) ; Point Z to Table in flash ldi ZL,LOW(2*SineTable) rjmp loop2 ; ; End of source code ; ; Include sinewave table ; .INCLUDE "sine8_25.txt" ; ; End of program ; ; ; Sinewave table for 8 bit D/A ; VCC=5.000V, uLow=0.000V, uHigh=2.500V ; (generated by sinewave.pas) ; Sinetable: .DB 64,65,67,68,70,72,73,75 .DB 76,78,79,81,82,84,85,87 .DB 88,90,91,92,94,95,97,98 .DB 99,100,102,103,104,105,107,108 .DB 109,110,111,112,113,114,115,116 .DB 117,118,118,119,120,121,121,122 .DB 123,123,124,124,125,125,126,126 .DB 126,127,127,127,127,127,127,127 .DB 128,127,127,127,127,127,127,127 .DB 126,126,126,125,125,124,124,123 .DB 123,122,121,121,120,119,118,118 .DB 117,116,115,114,113,112,111,110 .DB 109,108,107,105,104,103,102,100 .DB 99,98,97,95,94,92,91,90 .DB 88,87,85,84,82,81,79,78 .DB 76,75,73,72,70,68,67,65 .DB 64,62,61,59,58,56,54,53 .DB 51,50,48,47,45,44,42,41 .DB 39,38,36,35,34,32,31,30 .DB 28,27,26,25,23,22,21,20 .DB 19,18,17,15,14,13,13,12 .DB 11,10,9,8,8,7,6,5 .DB 5,4,4,3,3,2,2,2 .DB 1,1,1,0,0,0,0,0 .DB 0,0,0,0,0,0,1,1 .DB 1,2,2,2,3,3,4,4 .DB 5,5,6,7,8,8,9,10 .DB 11,12,13,13,14,15,17,18 .DB 19,20,21,22,23,25,26,27 .DB 28,30,31,32,34,35,36,38 .DB 39,41,42,44,45,47,48,50 .DB 51,53,54,56,58,59,61,62
That's all. It produces a nice sine. You wouldn't expect here to be a digital AVR at work but a nice and clean LC-oscillator. Unfortunately this method cannot be applied to produce sinewaves with more than 1,800 cycles/s, because 4 Mcs/s divided by 256 is only 15,625. And you need at least some clock cycles in between to read from the sinewave table.
To the top of that page
Playing musical notes with the R/2R network The following program uses the R/2R network to play musical notes with the keyboard. It works without changes on a STK200 board with a 4 Mcs/s 8515, with the eight keys connected to port D and the R/2R network connected to port B (download here). ; ****************************************************************** ; * Music on an STK200, using a R/2R network for playing tunes * ; * PortD has eight keys (active low), PortB generates the * ; * output for the R/2R-network, plays notes when key is activated * ; * ATMEL AT90S8515 at 4 Mcs/s * ; * (C)2005 by info!at!avr-asm-tutorial.net * ; ****************************************************************** ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Constants ; .EQU clock = 4000000 ; processor clock .EQU cNSine = 32 ; Table length of sine wave ; .DEF rLen = R1 ; length of delay for the sine wave .DEF rCnt = R2 ; Counter for length delay .DEF rmp = R16 ; multi purpose register .DEF rTab = R17 ; counter for table length ; ldi rmp,0xFF ; all bits of port B = output => R/2R network out DDRB,rmp ; set data direction register wtloop: in rmp,PIND ; read the keys cpi rmp,0xFF ; all keys inactive? breq wtloop ; yes, wait until active ldi ZH,HIGH(2*MusicTable) ; Point Z to music table ldi ZL,LOW(2*MusicTable) tabloop: rol rmp ; rotate next lowest bit into carry brcc tabfound ; found that key pressed adiw ZL,1 ; point Z to next table value rjmp tabloop ; check next bit tabfound: lpm ; read music table value to R0 mov rlen,R0 ; copy to delay, R0 used otherwise ; ; Play a tone until all keys are inactive ; startsine: ldi ZH,HIGH(2*SineTable) ; Set Z to sine wave table ldi ZL,LOW(2*SineTable) ldi rTab,cNSine ; Length of SineWave table ; ; The following code is optimized for reaching equal runtimes ; for all loop runs, clocks cycles needed are calculated ; for all instructions after their execution, ; clock cycles during table read / clock cycles at table end ; loopsine: in rmp,PIND ; 1/- Check if input still active cpi rmp,0xFF ; 2/breq wtloop ; 3/- key not active any more, restart nop ; 4/- delay to synchronize loopnext: lpm ; 7/3 read sinewave value to R0 out PORTB,R0 ; 8/4 copy to R/2R network mov rCnt,rLen ; 9/5 Set delay counter value ; delay loop, requires 3*rLen-1 clock cycles loopdelay: dec rCnt ; (1) next counter value brne loopdelay ; (2/1) not yet zero ; 3*rLen+8/3*rLen+4 at end of delay loop adiw ZL,1 ; 3*rLen+10/3*rLen+6 next value in table dec rTab ; 3*rLen+11/3*rLen+7 table counter brne loopsine ; 3*rLen+13/3*rLen+8 play next value in table ldi ZH,HIGH(2*SineTable) ; -/3*rLen+9 reload first table value ldi ZL,LOW(2*SineTable) ; -/3*rLen+10 ldi rTab,cNSine ; -/3*rLen+11 Length of sine wave table rjmp loopnext ; -/3*rLen+13 restart loop ; ; Table for delays to generate the 8 different frequencies ; ; frequency = clock / TableLength / ( 3 * rLen + 13 ) ; rLen = ( clock /TableLength / frequency - 13 ) / 3 ; MusicTable: ; f=261.6 293.7 329.6 349.2 392.0 440.0 493.9 523.2 (should be) .DB 155, 138, 122, 115, 102, 90, 80, 75 ; f=261.5 292.7 329.8 349.2 391.9 441.7 494.1 525.2 (is really) ; differences caused by rounding and the limited resolution) ; ; Sinewave table for 8 bit D/A ; Table length = 32 values ; VCC=5.000V, uLow=0.000V, uHigh=2.500V ; (generated by sinewave.pas) ; Sinetable: .DB 64,76,88,99,109,117,123,126 .DB 128,126,123,117,109,99,88,76 .DB 64,51,39,28,19,11,5,1 .DB 0,1,5,11,19,28,39,51 ; ; End of program ;
; ************************************************************** ; * R/2R-Network produces a sawtooth signal on Port D * ; * (C)2005 by [email protected] * ; ************************************************************** ; .INCLUDE "8515def.inc" ; ; Register definitions ; .DEF rmp = R16 ; Multipurpose register ; ldi rmp,0xFF; Set all pins of Port D as output out DDRD,rmp sawtooth: out PORTD,rmp inc rmp rjmp sawtooth
; ************************************************************** ; * R/2R-Network produces a sawtooth signal on Port D * ; * (C)2005 by [email protected] * ; ************************************************************** ; .INCLUDE "8515def.inc" ; ; Register definitions ; .DEF rmp = R16 ; Multipurpose register ; ldi rmp,0xFF; Set all pins of Port D as output out DDRD,rmp sawtooth: out PORTD,rmp inc rmp andi rmp,0x7F ; set bit 7 to zero rjmp sawtooth
; ************************************************************** ; * R/2R-Network produces a triangle signal on Port D * ; * (C)2005 by [email protected] * ; ************************************************************** ; .INCLUDE "8515def.inc" ; ; Register definitions ; .DEF rmp = R16 ; Multipurpose register .DEF rdl = R17 ; Delay counter ; ; Constants ; .EQU maxAmp = 127 ; Maximum amplitude setting .EQU delay = 1 ; Delay, higher value causes lower frequency ; ; Main program start ; ldi rmp,0xFF; Set all pins of Port D as output out DDRD,rmp triangle: clr rmp loopup: out PORTD,rmp ; to port ldi rdl,delay delayup: dec rdl brne delayup inc rmp ; next higher value cpi rmp,maxAmp brcs loopup loopdwn: out PORTD,rmp ldi rdl,delay delaydwn: dec rdl brne delaydwn dec rmp brne loopdwn rjmp triangle ; and again forever
; ************************************************************** ; * Produces a sinewave on a R/2R-network connected to PORTD * ; * (C)2005 by avr-asm-tutorial.net * ; ************************************************************** ; .INCLUDE "8515def.inc" ; ; Register definitions ; .DEF rmp = R16 ; Multipurpose register ; ; Start of program source code ; ldi rmp,0xFF ; Set all pins of port D to be output out DDRD,rmp ldi ZH,HIGH(2*SineTable) ; Point Z to Table in flash ldi ZL,LOW(2*SineTable) clr rmp loop1: nop nop nop loop2: lpm ; Read from table out PORTD,R0 ; Write value to port D adiw ZL,1 ; point to next value dec rmp ; End of Table reached brne loop1 ldi ZH,HIGH(2*SineTable) ; Point Z to Table in flash ldi ZL,LOW(2*SineTable) rjmp loop2 ; ; End of source code ; ; Include sinewave table ; .INCLUDE "sine8_25.txt" ; ; End of program ;
; ****************************************************************** ; * Music on an STK200, using a R/2R network for playing tunes * ; * PortD has eight keys (active low), PortB generates the * ; * output for the R/2R-network, plays notes when key is activated * ; * ATMEL AT90S8515 at 4 Mcs/s * ; * (C)2005 by [email protected] * ; ****************************************************************** ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Constants ; .EQU clock = 4000000 ; processor clock .EQU cNSine = 32 ; Table length of sine wave ; .DEF rLen = R1 ; length of delay for the sine wave .DEF rCnt = R2 ; Counter for length delay .DEF rmp = R16 ; multi purpose register .DEF rTab = R17 ; counter for table length ; ldi rmp,0xFF ; all bits of port B = output => R/2R network out DDRB,rmp ; set data direction register wtloop: in rmp,PIND ; read the keys cpi rmp,0xFF ; all keys inactive? breq wtloop ; yes, wait until active ldi ZH,HIGH(2*MusicTable) ; Point Z to music table ldi ZL,LOW(2*MusicTable) tabloop: rol rmp ; rotate next lowest bit into carry brcc tabfound ; found that key pressed adiw ZL,1 ; point Z to next table value rjmp tabloop ; check next bit tabfound: lpm ; read music table value to R0 mov rlen,R0 ; copy to delay, R0 used otherwise ; ; Play a tone until all keys are inactive ; startsine: ldi ZH,HIGH(2*SineTable) ; Set Z to sine wave table ldi ZL,LOW(2*SineTable) ldi rTab,cNSine ; Length of SineWave table ; ; The following code is optimized for reaching equal runtimes ; for all loop runs, clocks cycles needed are calculated ; for all instructions after their execution, ; clock cycles during table read / clock cycles at table end ; loopsine: in rmp,PIND ; 1/- Check if input still active cpi rmp,0xFF ; 2/breq wtloop ; 3/- key not active any more, restart nop ; 4/- delay to synchronize loopnext: lpm ; 7/3 read sinewave value to R0 out PORTB,R0 ; 8/4 copy to R/2R network mov rCnt,rLen ; 9/5 Set delay counter value ; delay loop, requires 3*rLen-1 clock cycles loopdelay: dec rCnt ; (1) next counter value brne loopdelay ; (2/1) not yet zero ; 3*rLen+8/3*rLen+4 at end of delay loop adiw ZL,1 ; 3*rLen+10/3*rLen+6 next value in table dec rTab ; 3*rLen+11/3*rLen+7 table counter brne loopsine ; 3*rLen+13/3*rLen+8 play next value in table ldi ZH,HIGH(2*SineTable) ; -/3*rLen+9 reload first table value ldi ZL,LOW(2*SineTable) ; -/3*rLen+10 ldi rTab,cNSine ; -/3*rLen+11 Length of sine wave table rjmp loopnext ; -/3*rLen+13 restart loop ; ; Table for delays to generate the 8 different frequencies ; ; frequency = clock / TableLength / ( 3 * rLen + 13 ) ; rLen = ( clock /TableLength / frequency - 13 ) / 3 ; MusicTable: ; f=261.6 293.7 329.6 349.2 392.0 440.0 493.9 523.2 (should be) .DB 155, 138, 122, 115, 102, 90, 80, 75 ; f=261.5 292.7 329.8 349.2 391.9 441.7 494.1 525.2 (is really) ; differences caused by rounding and limited resolution) ; ; Sinewave table for 8 bit D/A ; Table length = 32 values ; VCC=5.000V, uLow=0.000V, uHigh=2.500V ; (generated by sinewave.pas) ; Sinetable: .DB 64,76,88,99,109,117,123,126 .DB 128,126,123,117,109,99,88,76 .DB 64,51,39,28,19,11,5,1 .DB 0,1,5,11,19,28,39,51 ; ; End of program ; http://www.avr-asm-tutorial.net/avr_en/source/MUSIC.asm1/20/2009 8:01:07 PM
AVR-Hardware-Testroutines
Path: Home => AVR-Overview => Software
Tutorial for learning the assembly language of AVR-Single-Chip-Processor AT90Sxxxx of ATMEL applying practical examples.
Special software-know-how
(Click the links on the *.asm-files together with the shift key, if you want to download them.) HTML- ASMFormat Format
Comment
LPM
Reads the switches of the STK board, recodes the key into the number of LEDs and displays them (key 0: all eight LEDs are on). A rather useless program but it demonstrates the use of the LPM command and the ROLling and JuMPing in assembly language.
JUMP
JUMP
Jumps over the stack. Subroutine call not via the regular RCALL command but by putting the jump adress on the stack. First the return adress has to be put on the stack, then the adress of the called subroutine. The jump and the return are done via the RET command. Out of the series "tricky assembly programming"!
MAC1
MAC1
Macro examples. Just a useless program to demonstrate the use of macros in assembler.
MAC2
MAC2
Macro examples. Demonstrates the use of jump to labels within and outside macros.
MAC3
MAC3 Macro examples. Demonstrates the use of parameters in macros.
Path: Home => AVR-Übersicht => Software => LPM-Befehl ;
Uses the LPM-command for reading bytes from a table ; located in the code segment ; ; Reads the keys on the STK200 board and translates ; the position of the key to the number of LEDs by use ; of a table in the code segment and displays these ; LEDs (switch 0: 8 LEDs, switch 1: 1 LED, switch 2: ; 2 LEDs, etc.). ; A rather useless program, but it demonstrates the use ; of the LPM command to access the code segment and ; some ROLling and JUMPing. ; .NOLIST .INCLUDE "8515def.inc" .LIST ;
; Registers ; .DEF erg=R0 ; The LPM-command uses R0 .DEF mpr=R16 ; Multi-funktion-register ; ; Registers ZL (R30) and ZH (R31) are also used but are already ; defined in 8515def.inc, so we don't need to do it here. ;
; Reset-/Interrupt-Vector ; RJMP main ; main: OUT DEC OUT OUT
CLR mpr ; Load 0 to register mpr DDRD,mpr ; all D-Ports are Input switches mpr ; Load FF to Register B DDRB,mpr ; All B-Ports are outputs to LEDs PORTD,mpr ; All Pullups Port D on
; Uses the LPM-command for reading bytes from a table ; located in the code segment ; ; Reads the keys on the STK200 board and translates ; the position of the key to the number of LEDs by use ; of a table in the code segment and displays these ; LEDs (switch 0: 8 LEDs, switch 1: 1 LED, switch 2: ; 2 LEDs, etc.). ; A rather useless program, but it demonstrates the use ; of the LPM command to access the code segment and ; some ROLling and JUMPing. ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Registers ; .DEF erg=R0 ; The LPM-command requires R0 .DEF mpr=R16 ; Multi-funktion register ; ; Uses the registers ZL (R30) and ZH (R31)! These are defined ; in 8515def.inc, so we don't need the definition here. ; ; Reset-/Interrupt-Vector ; rjmp main ; main: clr mpr ; Load 0 to Register mpr out DDRD,mpr ; all D-Ports are input for the switches dec mpr ; Load FF to Register B out DDRB,mpr ; All B-Ports are outputs to the LEDs out PORTD,mpr ; All Pullups on D on loop: ldi ZL,LOW(liste2) ; Register pair Z points to the ldi ZH,HIGH(liste2) ; first byte (FF) in the list in mpr,PIND ; read switches cpi mpr,0xFF ; All switches off? All LEDs off! breq lesen ; Jump to lesen if all switches are off incp: inc ZL ; Point Z to next byte in the list brne rolle ; No rollover to next MSB inc ZH ; LSB rollover, increment MSB rolle: ror mpr ; Shift Bit 0 in Carry-Flag brlo incp ; Carry=1, switch not pressed, next in list lesen: lpm ; Read the byte in the list, that register Z points to, to R0=erg out PORTB,erg ; Transfer byte to the LEDs rjmp loop ; Move in circles ; ; The list in the code segment, every byte represent a switch ; The values have to be defined as words! Otherwise a byte, inserted ; with the .DB xx directive, is always paired by the assembler with a ; zero byte and stored as word in the list. When using .DB xx,yy no ; extra zero byte is inserted. The same applies to text strings ; (.DB "abcd..."). Even numbers of bytes or characters are ok, ; odd numbers will be added a zero byte. ; When adding a word like 1234h to the list, the first byte in the list that ; is received from the LPM command is the LSB (34h), not the MSB (12h)! ; liste: .DW 0xFEFF ; 1 LED, 0 LEDs (0 ist second, 1 is first!) .DW 0xF8FC ; 3 LEDs, 2 LEDs .DW 0xE0F0 ; 5 LEDs, 4 LEDs .DW 0x80C0 ; 7 LEDs, 6 LEDs ; .EQU liste2=liste*2 ; This is needed because the adress is ; organized word-wise, while the read; operation is byte-wise. ; http://www.avr-asm-tutorial.net/avr_en/source/TESTLPM.asm1/20/2009 8:01:13 PM
AVR-Hardware-Tutorial, RAM-JUMP
Path: Home => AVR-Overview => Software => JUMP-command
; Calls a subroutine via the stack ; ; This subroutine call is not implemented via the RCALL command ; but by using the processor stack. The sequence is: first the ; return adress is pushed onto the stack, then the adress of ; the subroutine to be called. The jump to the subroutine and ; back to the main program is done by executing the RET; command (popping the adress from the stack and jump to ; this adress). ; This adressing mode is a very unusual opportunity, e.g. to write ; multiple jump adresses to a table and calculate the target adress. ; It is not meant for the beginner. ; .NOLIST .INCLUDE "8515def.inc" .LIST ; .DEF mpr=R16 ; As always, a multi purpose working register ;
; Reset-/Interrupt-Vector table ; RJMP main ; main: OUT LDI OUT
; Tests subroutine calls via stack ; ; This subroutine call is not implemented via the RCALL command ; but by using the processor stack. The sequence is: first the ; return adress is pushed onto the stack, then the adress of ; the subroutine to be called. The jump to the subroutine and ; back to the main program is done by executing the RET; command (popping the adress from the stack and jump to ; this adress). ; This adressing mode is a very unusual opportunity, e.g. to write ; multiple jump adresses to a table and calculate the target adress. ; It is not meant for the beginner. ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; .DEF mpr=R16 ; As always, a multi-purpose working register ; ; Reset-/Interrupt-Vector table ; rjmp main ; main: ldi mpr,HIGH(RAMEND) ; Stack setup out SPH,mpr ldi mpr,LOW(RAMEND) out SPL,mpr ldi mpr,LOW(retret) ; Push return adress onto stack push mpr ldi mpr,HIGH(retret) push mpr ldi mpr,LOW(testup) ; Push subroutine adress onto stack push mpr ldi mpr,HIGH(testup) push mpr ret ; Jump to subroutine ; ; The return routine switches all lamps on to signal correct execution ; retret: ldi mpr,0x00 ; Switch all LEDs on, if correct out PORTB,mpr loop: rjmp loop ; stop execution ; ; ; Test subroutine to be jumped to ; testup: ldi mpr,0xFF ; All lamp drivers tov output out DDRB,mpr ret ; Jump back to call adress on the stack http://www.avr-asm-tutorial.net/avr_en/source/TESTJMP.asm1/20/2009 8:01:16 PM
; ***************************************************** ; * Demonstrates the use of macros with the ATMEL * ; * assembler, just a test program for use with the * ; * ATMEL STK200 board, (C) 2000 Gerhard Schmidt * ; * Report bugs to [email protected] * ; ***************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Used registers ; .DEF mpr=R16 ; a multi purpose register ; ; The following is the macro, a piece of code that ; will be inserted into your program whenever you ; need it there. Whenever you need the same sequences ; all over again and you don't want to call a subroutine ; you should write a macro and insert this sequence by ; calling that macro. ; .MACRO TestMacro inc mpr inc mpr inc mpr .ENDMACRO ; ; ; Start of main program ; ldi mpr,0xFF ; Set PortB (LEDs) to be output out DDRB,mpr ; to Data Direction Register clr mpr ; Clear the register testmacro ; Insert the macro here (three INCs) testmacro ; Insert it once again here (another 3) com mpr ; Invert the result to display it out PORTB,mpr ; and write it to the LEDs loop: rjmp loop ; and end the program in a loop ; ; Assembling this code should result in a 11 word long ; binary, because the two TestMacro-macros blow up to ; six INCs during the assembly process. ; ; After execution the LEDs PB.1 and PB.2 should be on, ; all others should be off to display the result (6 dec ; = 0000.0110 binary)! ; http://www.avr-asm-tutorial.net/avr_en/source/TESTMAC1.asm1/20/2009 8:01:19 PM
; ***************************************************** ; * Jump labels within a macro: the answer is "yes" * ; * Demonstrates the use of macros with the ATMEL * ; * assembler, just a test program for use with the * ; * ATMEL STK200 board, (C) 2000 Gerhard Schmidt * ; * Report bugs to [email protected] * ; ***************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Used registers ; .DEF mpr=R16 ; a multi purpose register ; ; The following is the macro which includes a label ; within that macro (mjmp) and a jump to a label ; outside that macro. Both jumps are translated ; correctly. ; .MACRO TestMacro inc mpr ; INC the register mpr brne mjmp ; If no overflow skip the next rjmp ovf ; Jump if overflow occurs mjmp: .ENDMACRO ; .LISTMAC ; ; Start of main program ; ldi mpr,0xFF ; Set PortB (LEDs) to be output out DDRB,mpr ; to Data Direction Register ; ldi mpr,0xFE ; Set the register to 254 testmacro ; Insert the macro here (one INC) testmacro ; Insert it once again here (another INC) ; As there should have been an overflow during the last INC ; the following code should not be reached. If executed, ; it would result in all LEDs on. ; outp: out PORTB,mpr loop: rjmp loop ; and end the program in a loop ; ; The overflow should occur and this code will be executed ; ovf: ldi mpr,0xFF ; Blank all LEDs out PORTB,mpr ; at Port B rjmp loop ; Jump to indefinite loop ; ; After execution all the LEDs PB.0 to PB.7 should be off. http://www.avr-asm-tutorial.net/avr_en/source/TESTMAC2.asm1/20/2009 8:01:22 PM
; ***************************************************** ; * To pass a parameter to a macro is easy to program * ; * Demonstrates the use of macros with the ATMEL * ; * assembler, just a test program for use with the * ; * ATMEL STK200 board, (C) 2000 Gerhard Schmidt * ; * Report bugs to [email protected] * ; ***************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Used registers ; .DEF mpr=R16 ; a multi purpose register ; ; The following is the macro which includes a label ; within that macro (mjmp) and the value for the ; register is handed over with the first parameter ; referenced by @0. ; .MACRO TestMacro ldi mpr,@0 ; Set mpr-value to first parameter inc mpr ; Add one brne mjmp ; If no overflow skip the next rjmp ovf ; Jump if overflow occurs mjmp: .ENDMACRO ; .LISTMAC ; ; Start of main program ; ldi mpr,0xFF ; Set PortB (LEDs) to be output out DDRB,mpr ; to Data Direction Register ; ldi mpr,0xFE ; Set the register to 254 ; Here comes the macro: 0xFF is passed to it, then INCed ; and, as it is zero, jumps to the label ovf ; testmacro(0xff) ; Insert the macro here ; ; As there should have been an overflow during the last INC ; the following code should not be reached. If executed, ; it would result in all LEDs on. ; outp: out PORTB,mpr loop: rjmp loop ; and end the program in a loop ; ; The overflow should occur and this code will be executed ; ovf: ldi mpr,0xFF ; Blank all LEDs out PORTB,mpr ; at Port B rjmp loop ; Jump to indefinite loop ; ; After execution all the LEDs PB.0 to PB.7 should be off. http://www.avr-asm-tutorial.net/avr_en/source/TESTMAC3.asm1/20/2009 8:01:26 PM
Connecting a keypad to an AVR
Pfad: Home => AVR-Overview => keypad
Connecting a keypad to an AVR This page shows how to connect a common 12-key-keypad to an AVR and read it by assembler software. The chapters are: 1. How a keypad works 2. AVR: I/O-connection of the matrix 3. AVR: Connecting to an ADC with a resistor network
1. How a keypad works Keypads are switches, that are connected to rows and columns. If key "1" is pressed, the column 1 is electrically connected to row 1, if key "2" is pressed column 2 with row 1, and so on ...
To find out, if any one of the 12 keys is pressed, it would only be necessary to tie the three column lines to ground, connect all four row lines and, via a resistor of 10 kΩ, pull them to the operating voltage. If no key is pressed, the row lines are at plus. Any pressed key pulls down the row lines to zero volts.
To detect, which one of the 12 keys is pressed, pull down the three column lines one by one to ground (the other two column lines to plus) and read the resulting four row-lines. If one of the four row-lines is low, stop further reading and identify the key code from the column and row info. Like that: Column
Reading such a keypad using digital logic components, you need at least: ● ● ●
an oscillator, a shift register and a start/stop gate for generating the column signals, detection if one of the four row signals is zero, a recoder for conversion of the seven signals to the keycode.
Or a complete IC doing all that (probably you won't get such an IC at your local electronic parts dealer). Or you better use a micro. To top of page
2. AVR: I/O-connection of the matrix A keypad matrix can be connected directly to an I/O port of an AVR, with no additional components. The example shows a connection to the lower seven I/O pins of Port B. Other ports can be used similiarly. The port pins PB4..PB6 are defined as outputs, they provide the column signals. The port pins PB0..PB3 are used to read in the row results. Pull-up resistors on these inputs are enabled by software, external resistors are not necessary. The following software example first demonstrates initialization of the ports. This software part has to be excecuted only once at the AVR's program start.
Init-routine ; ; Init keypad-I/O ; .DEF rmp = R16 ; define a multipurpose register ; define ports .EQU pKeyOut = PORTB ; Output and Pull-Up-Port .EQU pKeyInp = PINB ; read keypad input .EQU pKeyDdr = DDRB ; data direction register of the port ; Init-routine InitKey: ldi rmp,0b01110000 ; data direction register column lines output out pKeyDdr,rmp ; set direction register ldi rmp,0b00001111 ; Pull-Up-Resistors to lower four port pins out pKeyOut,rmp ; to output port
Check for any key pressed The following routine detects if any one of the 12 keys is pressed. This routine is called in intervals, e. g. in a delay loop or by use of a timer. ; ; Check any key pressed ; AnyKey: ldi rmp,0b00001111 input lines out pKeyOut,rmp in rmp,pKeyInp ori rmp,0b11110000 cpi rmp,0b11111111 breq NoKey
; PB4..PB6=Null, pull-Up-resistors to ; ; ; ; ;
of port pins PB0..PB3 read key results mask all upper bits with a one all bits = One? yes, no key is pressed
Identify the key pressed Now the keypad is read out. One after the other the port bits PB6, PB5 and PB4 are set to low, and PB0..PB3 are checked for zeros. The register pair Z (ZH:ZL) points to a table with the key codes. When leaving the routine, this pair points to the key code of the pressed key. By using the LPM instruction, the key code is read from the table in the flash memory to the register R0. ; ; Identify the key pressed ; ReadKey: ldi ZH,HIGH(2*KeyTable) ; Z is pointer to key code table ldi ZL,LOW(2*KeyTable) ; read column 1 ldi rmp,0b00111111 ; PB6 = 0 out pKeyOut,rmp in rmp,pKeyInp ; read input line ori rmp,0b11110000 ; mask upper bits cpi rmp,0b11111111 ; a key in this column pressed? brne KeyRowFound ; key found adiw ZL,4 ; column not found, point Z one row down ; read column 2 ldi rmp,0b01011111 ; PB5 = 0 out pKeyOut,rmp in rmp,pKeyInp ; read again input line ori rmp,0b11110000 ; mask upper bits cpi rmp,0b11111111 ; a key in this column? brne KeyRowFound ; column found adiw ZL,4 ; column not found, another four keys down ; read column 3 ldi rmp,0b01101111 ; PB4 = 0 out pKeyOut,rmp in rmp,pKeyInp ; read last line ori rmp,0b11110000 ; mask upper bits cpi rmp,0b11111111 ; a key in this column? breq NoKey ; unexpected: no key in this column pressed KeyRowFound: ; column identified, now identify row lsr rmp ; shift a logic 0 in left, bit 0 to carry brcc KeyFound ; a zero rolled out, key is found adiw ZL,1 ; point to next key code of that column rjmp KeyRowFound ; repeat shift KeyFound: ; pressed key is found lpm ; read key code to R0 rjmp KeyProc ; countinue key processing NoKey: rjmp NoKeyPressed ; no key pressed ; ; Table for code conversion ; KeyTable: .DB 0x0A,0x07,0x04,0x01 ; First column, keys *, 7, 4 und 1 .DB 0x00,0x08,0x05,0x02 ; second column, keys 0, 8, 5 und 2 .DB 0x0B,0x09,0x06,0x03 ; third column, keys #, 9, 6 und 3
Debouncing The routines KeyProc and NoKeyPressed have to debounce the pressed key. For example by counting a counter up whenever the same key is identified. Repeat this for e.g. 50 milliseconds. The NoKeyPressed routine clears the counter and the key pressed. Because the timing depends on other necessary timing requirements of the AVR program, it is not shown here.
Hints, Disadvantages The routines shown above do not leave more time between the setting of the column address and the reading of the row information. At high clock frequencies and/or longer connections between keypad and processor it is necessary to leave more time between write and read (e.g. by inserting NOP instructions). The internal pull-ups resistors have values around 50 kΩ. Long lines or a noisy environment might interfere and produce glitches. If you like it less sensitive, add four external pull-ups. A disadvantage of the circuit is that it requires seven port lines exclusively. The modification with an AD converter and a resistor network (see chapter 3) is more economic and saves port lines. To the top of that page
3. Connection to an ADC with a resistor matrix Most of the Tiny and Mega-AVR devices nowadays have an AD converter on board. Without additional external hardware these are capable of measuring analog voltages and resolve these with 10 bit resolution. Those who want to save I/O ports only have to get the keypad to produce an analog voltage. That's the task for a resistor matrix. Resistor matrix
This is such a resistor matrix. The columns are connected to ground, in between the column connections are three stacked resistors. The rows are connected via four such stacked resistors to the operating voltage (e.g. 5 V). The AD converter input is blocked by a condensator of 1 nF because the ADC doesn't like high frequencies, that could be caught by the keys, the resistors and the more or less long lines in between all this. If the key "5" is pressed, a voltage divider gets active: * 1 k + 820 Ω = 1,82k to ground, * 3,3 k + 680 Ω + 180 Ω = 4,16k to plus. At an operating voltage of 5 Volt a divided voltage of 5 * 1,82 / (1,82 + 4,16) = 1,522 Volt is seen on the AD converter input. If we consider 5% tolerance of the resistors, the resulting voltage is somewhere between 1,468 and 1,627 Volts. The 10-bit AD converter converts this (at 5 V reference voltage) to a value between 300 and 333. If we ignore the lower two bits of the result (e.g. divide the AD result by four or left-adjusting the result - if the ADC provides that function) this yields an 8-bit-value between 74 and 78. Each key pressed produces a typical voltage range, to be converted to the key code. Voltages and key recognition
The combinations of resistors yield the voltages assembled in the following table. Given are the voltage ranges of the keys, the 8-bit-AD converter values for these keys and the optimal detection values between the different keys. Key
Voltage
8-bit-AD value
Detection
V(min.) V(typ.) V(max.) min. typ. max. (from AD value)
1
0,225
0,248
0,272
11
13
14
7
2
0,396
0,434
0,474
20
22
25
18
3
0,588
0,641
0,698
29
33
36
28
4
0,930
0,969
1,048
47
49
54
42
5
1,468
1,522
1,627
74
78
84
64
6
1,959
2,020
2,139
99 103
110
91
7
2,563
2,688
2,809 130 137
144
121
8
3,285
3,396
3,500 167 173
180
156
9
3,740
3,832
3,917 190 195
201
185
*
4,170
4,237
4,298 212 216
221
207
0
4,507
4,550
4,588 229 232
235
225
#
4,671
4,700
4,726 238 240
242
237
As can be seen from the table, there is no overlapping of the different detection values for the keys, taking 5% tolerance of the resistors into account. Those who like to play around with other resistor combinations, can download the calculation sheet (as Open-Office-Format, as Excel-XP-Format).
Hints for the AD converter hardware ATtiny devices in the most cases provide only the opportunity to use an internally generated voltage or the supply voltage of the AVR as reference for the AD converter. For the keypad conversion only the supply voltage is suitable as reference. This option has to be selected when initiating the AD converter hardware at program start. Many ATmega types can connect the reference voltage to an external pin, AREF. This pin can either be input or output. It is an output if either the supply voltage or the internal reference are selected as AD converter reference. In this case the AREF pin should have a condensator to ground to further reduce noise on the reference voltage. The AREF pin is an input, if an external reference source is selected as option. In this case an external source provides the reference voltage. If an external source provides the reference voltage, the keypad matrix should also be supplied by this source. Note in that case that the keypad consumes up to 10 mA, to improve noise sensitivity. ATmega devices allow to supply the AD converter from an extra pin (AVCC) to further reduce noise. If only the keypad uses AD conversion the low necessary resolution of 8 bits does not require a separate supply for the AVCC pin, it can be tied to the normal supply voltage. If other measuring tasks have to be performed on other channels, the AVCC pin should be connected to the supply voltage via a coil of 22 µH and should be blocked by a condensator of 100 nF to ground.
Initiation and reading the AD converter result For reading the keypad matrix voltage one AD converter channel is required. The AD converter is initiated once during program start. The two example codes show a start sequence for single conversion, here for an ATmega8, and one for an interrupt driven automatic start of the ADC, here for an ATtiny13. ATmega8: manual start of the ADC The first example shows a routine for an ATmega8, without interrupts, with a manual start and stop of the the AD converter. The keypad signal is connected to AD channel ADC0. .DEF rKey = R15 ; Register for AD value .DEF rmp = R16 ; Multi purpose register ; set MUX to channel 0, left adjust the result, AREF taken from AVCC ldi rmp,(1<
Converting the AD result to the key code The conversion result is, as such, not very useful. The voltages and the conversion result do not follow easy mathematical laws (the resistor values 4.7 - 5.6 - 6.8 - 8.2 must have been designed by a drunken math professor, the formula V = R1 / (R1 + R2) is not very easy to handle), so that we better use a table to resolve our key codes. The table cannot be a primitive look-up table, because we have 256 different possible results of the conversion, and we like more slim tables. Like a monkey, we climb the matrix tree by going step by step through the following table: KeyTable: .DB 7, 255, 18, 1, 28, 2, 42, 3, 64, 4, 91, 5 .DB 121, 6, 156, 7, 185, 8, 207, 9, 225, 10, 237, 0, 255, 11 The first byte is the compare value for our conversion result, the second byte is the key code, if this compare value is greater than our result. If the result is between 0 and <7: no key is pressed (key code is 255), if it is between 7 and <18 the key code is 1, etc. Or, if you prefer ASCII for the key codes: KeyTable: .DB 7, 0 , 18, '1', 28, '2', 42, '3', 64, '4', 91, '5' .DB 121, '6', 156, '7', 185, '8', 207, '9', 225, '*', 237, '0', 255, '#' The code for key translation goes like this: ; ; Converting a AD result to a key code ; GetKeyCode: ; if the AD result can change in between, the result must be copied first mov R1,rKey ; copy AD result to R1 ldi ZH,HIGH(2*KeyTable) ; Z points to conversion table ldi ZL,LOW(2*KeyTable) GetKeyCode1: lpm ; read one table value cp R1,R0 ; compare AD result with table value brcs GetKeyCode2 ; less than table value, key identified inc R0 ; test, if table end is reached breq GetKeyCode2 ; reached end of table adiw ZL,2 ; point to next table entry rjmp GetKeyCode1 ; go on comparing next entry GetKeyCode2: adiw ZL,1 ; point to MSB = key code lpm ; read key code to R0 ; Of course we have to check, if no key is pressed (R0 = 0xFF resp. if ASCII: R0 = 0) and we have to check for glitches (if the same key code comes 20 or more times, I take it for serious ...).
Experiences The hard- and software work very reliable. In the first version the resistor values of the matrix were ten times higher. This version was more vulnerable to HF noise, e.g. when transmitting with a 2 W VHF transmitter nearby.
; ***************************************************** ; * Demonstrates the use of the Include routines * ; * LCD_INC.ASM for use with the LCD on board of the * ; * ATMEL STK200 (C) 1999 Gerhard Schmidt * ; * Report bugs to [email protected] * ; ***************************************************** ; ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST .def mpr=R16 ; My multipurpose register, required ; Reset-/Interrupt-Vectors rjmp main ; Includes the LCD-routines, file must be in the same path .INCLUDE "LCD_INC.ASM" ; Main program main: ldi mpr,LOW(RAMEND) ; Set up stack out SPL,mpr ldi mpr,HIGH(RAMEND) out SPH,mpr ldi mpr,0xC0 ; Switch on external SRAM and WAIT out MCUCR,mpr rcall lcd_cl ; Clears display rcall lcd_st ; Standard display mode ldi mpr,0x05; Cursor position to line 1, col 5 rcall lcd_sc ldi mpr,'H' ; Output Hello World rcall lcd_ch ldi mpr,'e' rcall lcd_ch ldi mpr,'l' rcall lcd_ch rcall lcd_ch ldi mpr,'o' rcall lcd_ch ldi mpr,0x45 ; Cursor position to line 2, col 5 rcall lcd_sc ldi mpr,'W' rcall lcd_ch ldi mpr,'o' rcall lcd_ch ldi mpr,'r' rcall lcd_ch ldi mpr,'d' rcall lcd_ch ldi mpr,'!' rcall lcd_ch rcall lcd_on loop: rjmp loop ; Uff! Next week we learn how to create and read a table http://www.avr-asm-tutorial.net/avr_en/source/LCD_TEST.asm1/20/2009 8:01:41 PM
; Test of a decimal keyboard ; ; Reads the keyboard on port B and displays the activated key on the LEDs ; in hex format.; ; The decimal keyboard is attached to port B: ; Bit 6: *=Bit0 7=Bit1 4=Bit2 1=Bit3 ; Bit 5: 0=Bit0 8=Bit1 5=Bit2 2=Bit3 ; Bit 4: #=Bit0 9=Bit1 6=Bit2 3=Bit3 ; ; Tests with this program showed that parallel operation of the LEDs and ; of the keyboard on the same port (B) is practically impossible due to ; insufficient driving currents of the port pins. No correct values are read. ; ; 8515-Definitions .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Registers used .def mpko=R15 ; Last keyboard status .def mpr=R16 ; Multi-function register .def mpk=R25 ; Multi-function register for keyboard-interrupt ; ; RAM-Adresses .equ taste=$0060 ; First Ram-adress, here the pressed key will be placed ; ; Reset-/Interrupt vector table, most of the inactive rjmp main reti ; Ext Int 0 reti ; Ext Int 1 reti ; TC1 Capture rjmp test ; TC1 Compare A reti ; Compare B reti ; TC1 Overflow reti ; TC0 Overflow reti ; Serial Transfer Complete reti ; Serial Rx Complete reti ; Data Register Empty reti ; Serial Tx Complete reti ; Analog Comparator ; Main program main: ldi mpr,HIGH(RAMEND) ; Stack Pointer Init for Interrupts out SPH,mpr ldi mpr,LOW(RAMEND) out SPL,mpr ; General control register clr mpr ; no SRAM, no Wait, no Sleep-Mode, out MCUCR,mpr ; Ext.Int not used ; Port B is output und keyboard-Input ldi mpr,0x70 ; all as Output out DDRB,mpr ldi mpr,0x00 ; all lamps on out PORTB,mpr sts Taste,mpr ; ; Timer/Counter 0 init ldi mpr,$00 ; Prescaler = 256 out TCCR0,mpr ; Timer 1 init ldi mpr,0b00000000 ; Disable Timer Output and PWM-Mode out TCCR1A,mpr ; in Timer Control Register 1A ldi mpr,0b00001011 ; No input noise canceling, clear counter after ; match, Prescaler = 64 ==> 62500 Hz = 16 µs out TCCR1B,mpr ; in Timer Control Register 1B ldi mpr,HIGH(625) ; Compare-value in Compare-Register A out OCR1AH,mpr ; High Byte first ldi mpr,LOW(625) out OCR1AL,mpr ; Low Byte last ldi mpr,0xFF ; No Interrupt on Compare B out OCR1BH,mpr ; High Byte first out OCR1BL,mpr ; Low Byte last ; Interrupt start clr mpr ; External interrupts disable out GIMSK,mpr ; to General Interrupt mask register ldi mpr,0b01000000 ; Timer 1: Overflow Int Off, Compare A Int on, out TIMSK,mpr ; Compare B Int Off, Input Int Off, Timer 0: Int Off sei ; interrupt enable ; Incefinite Loop, all execution is interrupt-driven loop: rjmp loop ; Interrupt Routine for TC1 Compare Match B tc1ci: in mpk,SREG ; save Status-Register push mpk ldi mpk,0b11110000 ; Upper Nibble to output, lower to input out DDRB,mpk ; on Port B ldi mpk,0x0F ; Upper Nibble to zero, lower nibble sets Pullup resistors out PORTB,mpk in mpk,PINB ; Read result from keyboard cp mpk,mpko ; Compare with older value breq tc1cir ; ne change: return mov mpko,mpk ; Copy new status to old status sts taste,mpk ; New LED-Status tc1cir: ldi mpk,0xFF ; Port B to all output out DDRB,mpr lds mpk,taste ; pressed key to LEDs out PORTB,mpk pop mpk ; Return from Interrupt out SREG,mpk ; restore Status-Register reti tc0ci: ldi mpr,0xFF out PORTB,mpr reti tc2ci: ldi mpr,0xAA out PORTB,mpr reti test: ; LDI mpk,0x0F ; OUT DDRB,mpk ; LDI mpk,0xF0 ; OUT PORTB,mpk reti http://www.avr-asm-tutorial.net/avr_en/source/TESTKBD.asm1/20/2009 8:01:43 PM
Guestbook at avr-asm-tutorial.net
Main ==> Guestbook
Guestbook at avr-asm-tutorial.net I disabled automatic processing of entries in my guestbook due to those silly email-collectors and spam scripts. Does somebody ever buy something from guys that spam email accounts or guestbooks? I personally know nobody, do you? If you like to be listed here, please sent me a mail with a) your name, b) your email address, c) the text, to info at avr-asm-tutorial.net. I enjoy and appreciate this, and will be happy to enter your text here. Date/Time
#
eMail
Name
Text
Hector
Mr. Have a good day. Very good site! I liked the Akkuload project. Maybe a translation of "Akkulader_Beschreibung.pdf"?. Maybe another projects to learn more? Also downloaded the Beginner's course, english, PDF. Happy to found this site. Regards from LIMA - PERU Hector
n3or
Vielen Dank für die geleistete Arbeit. Das ASM-Einsteigertutorial ist echt spitze! Viele Grüße, n3or
Robert
This is one of the best and most helpful AVR-asm on-line tutorials I've ever found. Excellent language and examples. Great respect for the author as he apparently is a great proffesional, tutor and example to follow. Congratulations :)
22.06.2007 lightup um 171 @ 20:02:46 onlinehome.de
Designerboy
Alle Achtung! Das Programmieren an sich ist schon eine Kunst. Anderen Menschen eine solche Fülle Informationen und Material geordnet und übersichtlich zur Verfügung zu stellen ist schon fast edel :-)
18.06.2007 zone_subzero 170 @ um 20:52:23 onet.eu
Very good site! Well done! I am procesorek :-) impatiently waiting for new articles. High regard.
04.06.2007 ikke um 169 @ 14:42:06 web.de
Ikke
17.03.2008 hsegurak 174 @ um 01:34:18 gmail.com
28.12.2007 info um 173 @ 00:05:42 n3or.de
10.09.2007 admin um 172 @ 03:06:43 ematma.pl
29.05.2007 Patrick.Schnedl um 168 @ Patrick 20:27:48 austriamicrosystems.com
12.05.2007 davor66 um 167 @ 12:47:10 yahoo.com
Danke für die Arbeit ! *Thumbs Up* Wirklich eine Großartige seite !!!!!!! Ich finde es echt wunderbar und bemerkenswert von ihnen das sie sich so viel zeit genommen haben diese seite zu schreiben .... in welchem Detail + die ganzen Bilder und und und VIELEN DANK ! *Standing Ovation*
Hello Gerhard I'am very glad to see nice electronic, informatic and ham Davor Kordic site on the internet. Congratulations Davor, 9A4KJ
Lev
Wow, hammer Seite, aber vor allem knie ich mich nieder und sage 'Respekt' mit Bewunderung, da es so eine Monsterarbeit war alles hier Beschriebene erlernt zu haben.. Ich dachte Assembler wäre wirklich nicht sooo schwer, muss aber gestehen dass gute physik und elektronikkenntnisse wirklich nicht schaden würden. In diesem Sinne tschüss! LG lev
Ian Smith
Very impressive site,good to see work shared,and very helpful for a defector from PIC microcontrollers!
LotharK
Hi, eine wirklich einmalige, klasse Seite. DANKE! Ich bin so begeistert, dass ich mir soeben das Starterkit STK500 bestellt habe. Ich habe zwar vor 17 Jahren mal Assembler "gelernt", aber mittlerweile alles wieder vergessen. Irgendwie hatten mich andere Programmiersprachen fasziniert. Obwohl ich nun schon viele Jahre als Programmierer arbeite, kostet es mich einige Mühe, sich in ASM einzudenken.
Ulrich Linndau
Vielen Dank für die tolle Internetseite ! Meine Kenntnisse in ASM. sind damit erheblich besser geworden. Mit freundlichen Grüßen Ulrich Lindau
mike köppl
hallo - diese seiten sind eine primas schule für mich um sich mit assembler zu beschäftigen.werde sie wärmstens empfehlen,hoffe das die homepage so lang wie möglich erhalten bleibt.habe selten so gute seiten im net endecken können.danke und weiter so gruß mike aus altlußheim
27.12.2006 pabloorell um 161 @ 21:41:36 gmail.com
Pablo
Hello I am very interested in programming AVR microcontrollers. Let me know if you or someone have information about building the programmer software with Delphi,C+ + or Visual Basic . Your website is very helpfull Thanks Pablo Chile South America
14.12.2006 rexwilliams um 160 @ 18:08:45 blueyonder.co.uk
I appreciate the help you have given Rex Williams with the AVR tutorial very much, many thanks, Rex.
02.12.2006 eduardo um 159 @ 14:24:36 tecblue.com.br
Eduardo M. Pimentel
From Brasil Congratulations [Meus Parabéns] Eduardo
25.11.2006 blancohombre 158 @ um 12:13:46 hotmail.com
anton
Hy ! Sehr schön weiter so ! MFG Anton PS: Suche einen assembler bzw. dissambler speziell für den AT90SC6464.
12.11.2006 sibon29 um 157 @ 21:36:08 yahoo.com
mehdi
01.03.2007 test um 166 @ 19:34:32 test.de
30.01.2007 zapsmith um 165 @ 22:30:20 blueyonder.co.uk
27.01.2007 l_kriegerow um 164 @ 20:03:21 gmx.de
26.01.2007 UlrichLindau 163 @ um 06:25:33 T-Online.de
07.01.2007 koepplmike um 162 @ 09:16:44 aol.com
Saul Ruiz Mora
Estimated teacher, I was a pupil of the National Autonomous University of Mexico "UNAM", and I have considered very useful did of the knowledge of my companions of your handbook, I linking your site in the following direction. http://www. ingenieria.unam.mx/ biblioteca_digital/ biblioteca_general. php Thank you for your work and time.
Jeshu Chakrabarty
I am Jeshu Chakrabarty. I am from India. I am trying to learn AVR of my one & i found your site exrimly usefull I hav downloded the site Thanks for Providing it!! Jeshu
05.10.2006 order 154 @ um 23:32:07 ledstyle.de
Matthias
Tolle Seite, Super Erklärungen, mal sehen wie lange es dauert bis mein (gerade gekauftes) STK500 brennt. ;-)
29.09.2006 karsten.eppert um 153 @ 22:08:21 web.de
Karsten Eppert
Kein Kommentar. Einfach Klasse für Einsteiger. 73 Karsten DK4AS
Michael
Prima Seite für ASM-Einsteiger. Bin ja BASCOM-AVR Fan, aber etwas ASM kann nicht schaden. Also vielen Dank für die gute Arbeit an Ihrer Homepage.
02.08.2006 nikolaus.bartels um 151 @ 19:08:34 gmx.de
Nikolaus Bartels
Vielen Dank für die viele Mühe,die Sie sich gemacht haben, um anderen die Möglichkeit zur Weiterbildung zu geben. Mit freundlichem Gruß, Nikolaus Bartels
28.07.2006 Nik um 150 @ 10:04:57 mail.ca
Nik
Hello! I'm Nik! Nice site!
27.07.2006 Karter um 149 @ 18:30:38 mail.ca
Karter
Hello! I'm Karter! I wana talk you something, please mail me.
05.07.2006 202054373 um 148 @ 10:36:05 cput.ac.za
owen
i want to connect a 16 Char x 2 Line LCD to a Atmel 8 chip,but i don't know how initilize the LCD,do you have any source code(Assembler) to do that? if you do,could you mail me where i can find it,please!
19.06.2006 ldark um 147 @ 23:07:34 poczta.onet.pl
Ldarko
hi very usefull site :) !! thanks
17.06.2006 boblock um 146 @ 16:35:35 yahoo.com.br
Roberto Locks
Good job, good man. Thank you, my AVR teacher.
14.06.2006 okolyt um 145 @ 09:17:04 bob.de
Jo fetttttt, besten Dank für http:// www.avr-asm-tutorial.net/avr_de/ Okolyten Bob keypad/keyboard.html viele grüße auch von Fritze und Kalle
24.10.2006 sarumo74 um 156 @ 06:09:57 prodigy.net.mx
19.10.2006 mails4_jeshu um 155 @ 12:39:47 yahoo.co.uk
23.08.2006 dl1dmw um 152 @ 10:08:06 darc.de
Kazuhisa Azumi
Thanks for gavrasm. I succesfully compiled it on Mac OS X using free pascal. I am developping equipments for scientific research and looked for atmel-style assembler on OSX (not gcc-style). Your gavrasm will help my research so much. Thank you again !! ( Kazuhisa Azumi (from Japan)
03.06.2006 suresh_16001 um 143 @ 19:07:40 yahoo.com
Suresh
Hi I am Suresh. I am new to AVR controllers. Your website proved instrumental for me in working on it for the first time. You are a true guide for me. Thank you very much for the support in the form of this information you extended. warm regards suresh
01.06.2006 hamidpac3 142 @ um 15:33:01 yahoo.com
hamid reza
hallo ic heise hamid aus dem iran pls help me about ac motor control
4t
Eine wirklich klasse Seite! Der Inhalt ist vom aller feinsten! Danke! Gottes Segen & ein frohes Osterfest! 4t
Jose
Excelent job creating this page, I love it. Thank you for sharing your knowledge.
MACHU Laurent
Thank you for building this site. I have put many answers in front of my questions. It is now a pleasure to program my AVRs Thank you
Roland Tennert
Ich glaube mein Hauptproblem liegt darin dass ich ein konkretes ziel habe welches ich mit dem kopf durch die wand erreichen wollte. Ich hab mir einfach das meiner meinung nach optimalste Paket ausgesucht um schnell an mein ziel zu kommen ohne im geringsten zu ahnen was ich mir da auflade. Also werde ich von vorn anfangen und einfach nur spielen. Alles mal ausprobieren, auch assembler. Die Hardware ist mit Mega 8 mehr als ausreichend für meine zwecke, wen ich da nicht schon wieder falsch liege. Ich werds ja merken. Also dann, vielen dank und sollte ich in ein paar monaten mein erstes projekt fertig haben meld ich mich noch mal. Ich bleib dran. Die elektronik hab ich mir auch selbst beigebracht. Tschüß und nach möglichkeit nicht solch schwierigen fälle wie mich Roland Tennert [email protected]
Dean Baxter
Hi i would just like to thank you for putting together such a good manuael on AVR programming it is really dawnting for me and your guide makes it much better Thanks a million Dean
Korte
Hallo, ich finde Ihre Webseite sehr informativ und interessant. Mit freundlichen Gruessen aus Berlin Korte
Roland
So, Hr. Gerhard Schmidt ! Habe mir just das STK 500 bestellt, um mit der ASM Programmieren so richtig loslegen zu können. Habe mich natürlich vorweg mit mit den Datenblättern der Prozessoren beschäftigt. Keine Ahnung wie lange es dauern wird bis die Programmierung leichter von der Hand gehen wird, aber als erstes Projekt möchte ich mich gerne in das Projekt "Metallsuchgerät" Einarbeiten. Vorerst recht einfach gehalten, jedoch in späterer Folge sollte der Speicher recht Raumgreifend mit Nullen und Einsen gefüllt werden :-) Diese Seite ist bis DATO mein persönliches Aushängeschild in PUNKTO Assembler Programmierung. Also weiter so ! Danke nochmals für diese Sehr Informative Seite. Ist ja auch der GRUND warum ich mit der Programmierung angefangen habe. Freue mich schon auf das Board. mfg Roland
Fajrul Ilhami
hello..sir i am student collage on Indonesia. i am, so interest with avr. i have the problem about how to use software codevision. if you have any e-book about bascom, or C language. thanks for u attention.. i hope u help me ... thanks best regard, fajrul
Stefan
Lieber Webmaster! Ich habe grossen Respekt vor Ihrer Arbeit und möchte Ihnen auf diesem Weg danke sagen für Ihre Mühe! Ich beschäftige mich seit meinem 12 Lebensjahr mit Assembler Programmierung (6502,8080,8085,Z80,80C535, MC68000er) und nun auch mit PIC bzw. AVR. Ihr Kursus ist sehr leicht verständlich und Übersichtlich gestalltet. (Heute bin 33 Jahre Jung :) ) Vielen Dank nocheinmal! Schöne Grüße aus Köln. Einen schönen Tag noch! Stefan
terry rose
Hello, I'm just returning to using assembler after 20 years! (6800 and 8080). I am reading your math routines to try and understand how to use fixed point math with the AVR. Thank you very much for your time to put this excellent web site together. Regards, Terry Rose
Wolfgang Geenen
Glückwunsch und danke für diese Seite. Ich hab hier so ziemlich alles gelernt, um meinen Roboter zu programmieren. Und natürlich diverse andere Projekte. Wolfgang alias Gino
steve
a very useful site for me! all essential information about learning AVR is here well orgarnized. highly recommend this site!
Jürgen Jungnickel
Also ich hab in der Berufsschule angefangen Assembler zu programmieren und ihre Seite hat mir sehr dabei geholfen den Schulstoff besser zu verstehen. Darum wollte ich ihnen danken für die tollen Beispiele und Erklärungen. Danke!!!!!!!
Atommann
Hi! Your page is wonderful! I read the article about the R/2R-network, and have done some experiment. Successful! Danke!
Vignesh
Excellent effort to explain the most sought info for any microcontroller!! Cheers, Vignesh
07.12.2005 a um 126 @ 20:03:35 bb.cc
Georg
Vielen Dank für das PDF des AVRASM-Tutorials. Danke! Danke! Danke! Nach so etwas hab ich gesucht und ich hab's gefunden. Danke!
06.11.2005 gerdstrecker um 125 @ 08:07:29 freenet.de
Will mich mal etwas intensiver mit Gerd Strecker DCC für Spur N (Einbau in BR80) beschäftigen.
04.06.2006 azumi um 144 @ 06:00:29 eng.hokudai.ac.jp
16.04.2006 ih4ven0email um 141 @ 15:23:29 hotmail.com 10.04.2006 jm_corona64 um 140 @ 05:25:55 hotmail.com 25.03.2006 lmachu um 139 @ 07:36:49 waika9.com
19.03.2006 zippel um 138 @ 18:10:59 datel-dessau.de
16.03.2006 deanobaxter um 137 @ 06:26:58 hotmail.com
13.03.2006 korte2004 um 136 @ 12:59:06 yahoo.com
12.03.2006 Roland.Stotz um 135 @ 16:39:29 infineon.com
02.03.2006 fajrul_ilhami 134 @ um 19:58:39 yahoo.com
23.02.2006 stevie72 um 133 @ 13:44:34 gmx.de
07.02.2006 terry um 132 @ 17:03:37 terrywrose.plus.com
02.02.2006 ginotronik um 131 @ 22:10:15 web.de
07.01.2006 u741koolhip um 130 @ 01:46:41 hotmail.com
29.12.2005 Juergen.Jungnickel um 129 @ 22:12:50 o2online.de
19.12.2005 atommann um 128 @ 03:35:30 gmail.com 10.12.2005 vignesh.e um 127 @ 16:28:06 gmail.com
Dieter Aschmann
Hallo Gerhard ! Ein gelungener Auftritt, mit gut gestaltetem Inhalt für alle Freunde der AtmelController und deren Programmierung. Sicher lich immer mit viel Mühe verbunden das Ganze so aktuell zu halten. Weiter so, .... Dieter
Guadalupe
thanks for the information, i'm a newbie on programmation and i hope to learn from your tutorials. greetings from Mexico
jai parkash
u really done a great job by providing such information on AVR mc..its really wonderfull.. thankssss
dg8yho
Tolle Seite, hier habe ich alles gefunden, was ich als Newby brauche. 2 Tage Google vorher war nicht so erfolgreich! Danke nd ´73 de Jan DG8YHO
D. Kuckenburg
Guten Tag, alles schön und gut, aber was ich nirgends finden konnte: für was steht die Abkürzung AVR ? Gibt es eine Erklärung ? Vielen Dank im voraus. Gruß D.Kuckenburg
cooli
hallo! Ich möchte mir einen mc (AT90S2313) zulegen... Aber wo kann ich Sachen anschließen? Zum Beispiel wo schließe ich eine LED an? An welchen pins? MfG, Cooli
Sascha
Für diese Seite haben Sie ein dickes Lob verdient. Sie hat mir den Einstieg in das Thema sehr einfach gemacht. Vielen Dank
22.08.2005 sinbaal um 117 @ 08:07:05 mail.ru
Delphi
Advice pease, how to read data from device connected to PD*, but we need to read not answers from device connected to PD*, and his "hex" code, which was written in external chip by programmer. Read from some address on connected chip. Thanks
18.08.2005 fabianschuiki um 116 @ 15:30:38 bluewin.ch
Schui
Super Seite! Echt Toll! Gruss, Fsch.
15.08.2005 marwa_mekki um 115 @ 11:06:22 hotmail.com
Hello I want to compare a register value with constant 2,if it is equal then branch.When i do the following: marwa mekki asm("CPI R22,2"); asm("BREQ 5"); i get a copilation error: Error: operand out of range: -193
16.10.2005 DAschm0912 124 @ um 10:35:30 aol.com
07.10.2005 nickelkitten910 um 123 @ 17:36:29 yahoo.com.mx 05.10.2005 jaigodara um 122 @ 20:53:47 gmail.com 04.10.2005 dg8yho um 121 @ 19:38:27 test.de
12.09.2005 d.kuckenburg um 120 @ 09:18:53 gmx.de
01.09.2005 killss70 um 119 @ 20:26:36 yahoo.de
28.08.2005 spam um 118 @ 01:35:25 mail.com
02.08.2005 nitramgv um 114 @ 19:48:46 hotmail.com
Pedro Martin
Hello there, I am from Mexico, I am a student and I speak Spanish anda I am leranning English and German and ATmega8535 too :). Now, Im studying master career. I could see your personal website and I liked it so much. Saludos desde México... I would like to do a doctor study there in German.
02.07.2005 saman.kh 113 @ um 20:33:34 atlas.cz
Zdenek Simunek
Sehr schoen gemachte Seiten Ich vuensche Ihnen viel Spas in Ihre Arbeit. Gruss von Simunek
Carlos Almeida
Very good and well documented. Explanation with easy and detail. A must read.... Thank you ffoxtrotjunkatyahoo.com
18.05.2005 sam104 111 @ um 06:10:58 sam104.de
Karsten Scholz
Gott was für eine Hilfe..ich bin echt froh über die Seite hat mir den Anfang echt erleichtert und heute ist alles so einfach.
29.04.2005 matagnex um 110 @ 10:19:07 yahoo.fr
Xavier Matagne
I thank you very much for your good tutorial. I teach the 90s8515 and it helps me. Xavier
24.04.2005 zerpo um 109 @ 09:43:30 poczta.onet.pl
zerpo
Great site, everything's cool!
09.04.2005 DAschm0912 108 @ um 17:45:57 aol.com
Dieter Aschmann
Toll gemacht Ihr AVR- Assembler Tutorial, erspart dem Anfänger wie mir teure Bücher. Eine Bitte machen Sie weiter so.
17.03.2005 sirios um 107 @ 20:37:11 otenet.gr
Dimarakis Thanos
Hello from Greece. Thanks.
06.03.2005 a.hellebaut 106 @ um 16:27:01 skynet.be
Thanks for the opportunity to learn assembly for the ATMEL-chips. I Armand have age 79 but I will try it./ I have HELLEBAUT good young compnanions Best regards. ON4TL;
03.06.2005 ffoxtrotjunk um 112 @ 14:44:50 yahoo.com
25.02.2005 spam um 105 @ 16:18:52 spam.schaf.at
Schaf
sehr brauchbare infos für anfänger, und mal wieder ne gelegenheit die grundlagen aufzufrischen. thx lg Martin
04.02.2005 PE1NPG um 104 @ 14:18:33 amsat.org
Jean-Pierre van der Zanden
Very helpfull site for me, thanks for putting in all the effort!! Jean-Pierre
18.01.2005 meser um 103 @ 09:47:39 ptt.yu
Srdjan
thanks for knowledge Srdjan Srbija
Klaus Mund
hallo, danke für die prima aufbereiteten infos und die freundlicherweise zum lernen zur verfügung gestellten erfahrungen. viele grüsse aus BY
05.01.2005 Hightech20002002 um 101 @ 16:54:30 yahoo.de
Uwe Kunrad
Eine wirklich tolle Seite. Für Anfänger wie mich ein absoltes muß. Sehr gut verständlicher Inhalt. Werde ich auf jedenfall weiter empfehlen. Danke nochmal für diese erstklassige Hilfestellung Gruß Uwe
31.12.2004 michael-guenther 100 @ um 18:03:54 onlinehome.de
Michael Günther
Tolle Seite, da macht der Anfang Spass.
06.12.2004 michael.docholiday um 99 @ 03:40:34 gmail.com
You have a wonderful website! I have been wanting to know how to apply my knowledge of programming to interface with Michael King hardware for a long time. Any other suggestions you might provide to get me started would be great! Thank you.
04.12.2004 John um 98 @ 12:22:39 rogers.com
John
30.11.2004 aesv um 97 @ 08:31:07 yahoo.com
It's an excellent web site, congratulations a bunch of resources alfred salazar to keep on the way with ATMEL. Afredo Salazar UNI - Lima -Peru
16.01.2005 kmund4 102 @ um 12:58:31 aol.com
best in class tutorial! excellent work!
Peter
Hallo Tolle seiten echt super gemacht mach weiter so ich schaue mal wieder vorbei MfG Peter http://www. versandhandel-woike.de
27.11.2004 Voyager um 95 @ 08:24:53 mail.isis.de
Frank
Dein Tutorial ist wirklich verständlich und ausführlich. Bei vielen anderen Anleitungen werden oft feine Details, oder genaue Erklärungen von Schaltungen weg gelassen. Aber bei deinem Einstiegskurs ist alle inklusive. :-) Ich kann mich noch nicht zwischen einem PIC oder Atmel Controller entscheiden...
21.11.2004 Adank um 94 @ 09:29:40 (ohnedas)iba-lg.de
Michael Adank
Tolle Seite, ich bin sehr dankbar über die überaus hilfreiche AVR Infos schöne Grüße aus Lüneburg
14.10.2004 ThommyMaier um 93 @ 12:36:39 web.de
Thommy
Echt klasse Mikrocontroller Einsteiger Seite!! Gut kommentierte Beispiele mit Bildern auch die ADU kommt nicht zu kurz dickes Lob...aus Freiburg!! Thommy
26.09.2004 Virenchocha um 92 @ 21:47:41 msn.com
In french Viren means Virus but don't fear, I am a simple Guy. Your tutorials are just great, I have got my Viren Chocha feet wet, now I'm gonna test newly acquired knowledge(by your grace) on hardware. Thanks a lot, thanks a lot raised to infinity.
29.11.2004 pewok1 um 96 @ 04:24:48 gmx.de
Krishna Kumar
Thanks very very much for the excellent tutorial, it's indespensable ! I got started in a few days flat, I'm already writing some of my own code, albeit very basic code. But thanks a lot, Danke Schon !!
10.09.2004 larmakle 90 @ um 19:52:18 hotmail.se
Magnus
The "Beginners Introduction ...to ATMEL-AVR..." was just what I needed. Perfect! Now I need to find a place where I can find an example on how to "program" the UART so that I can talk RS232 from AVR to PC. Best Regards Magnus
09.09.2004 nichtimnetz 89 @ um 17:24:28 email.com
Benjamin
Klasse Seite - Ich leg jetzt mit atmel los...
dave w.
das tutorial ist echt umfangreich.. danke! so viel mühe :) damit kann man auch etwas komplexere sachen machen, als leds einschalten hehe.. ich hocke grade an ner luxuslüftersteuerung.. dave überlingen, bodensee
Paulo
Hi ! I am in my first steps with microcontrollers design and I was looking for help and tutorial, then, I found your homepage. Thanks for your help. May GOD bless you Paulo/ Brazil
12.08.2004 Ralf 86 @ um 03:08:39 zaeper.de
Ralf Zaeper
Sehr schoene Seiten. Habe schon viel von den AVR Angeboten abgesaugt und gelernt. Toll, dass sich jemand hinsetzt uns sich so viel Arbeit macht. Guesse aus derzeit Houston, Texas, Ralf
Ich bin sehr dankbar für ihre Seite,da sie mir viel weiter geholfen hat,gut eingerichtet ist,einfach Klasse und in deutsch geschrieben ist. Es lebe LINUX. Es lebe die FREIEIT.
Robert
Hallo Toll, Ihre Seiten!!! Muss mir gleich mal den AVR-Kurs ausdrucken und durchlesen :-) Alles Gute noch und viel Gesundheit :-)
Oscar Jacuinde Beltran
THIS PAGE IS AWESOME, THIS IS THE FIRST TIME I LEARN ASM AND I LEARN TO PROGRAM A AVR AND ITS ALL CLEAR LIKE WATER
17.06.2004 dionjolink 81 @ um 15:59:26 planet.nl
Dion Jolink
This message is written in Dutch: Leuke website, goede leerzame tutorials. Ik wacht met smart op de nieuwe:P Groetjes, Dion www.ddsoftware.net
12.06.2004 gertvdmeij um 80 @ 14:46:39 planet.nl
Gert van der Meij
Thanks for the nice tutorial!
21.05.2004 sarumo74 um 79 @ 20:54:08 hotmail.com
Ruiz Mora; Saul.
Dear Senor Schmidt. Your tutorial its best tutorial over AVR. Muchas gracias from México and U.N.A.M.
04.05.2004 ralcocer 78 @ um 01:52:53 ralcocer.cjb.net
Rafael Alcocer
I have put a link on my website http:// ralcocer-avr-projects.com Come join my webring Rafael Alcocer
alexander schmidt
ach noch was : das erfolgreiche projekt ist auch dein verdienst .... http://fireback.bbbk.du.nw.schule.de/ danke nochmal , gruss alex
alexander schmidt
DANKE !!!! sie haben mir buchstäblich den arsch gerettet !!! (bbbk - technikerschule duisburg, nur noch 47 tage bis zur abschlussklausur !!)
16.09.2004 kumar_ramanathan um 91 @ 08:07:34 yahoo.com
02.09.2004 dave_in_the_grave 88 @ um 14:31:56 gmx.de
26.08.2004 paulinhovjr um 87 @ 02:04:42 ig.com.br
27.07.2004 king-big-alex um 84 @ 12:28:13 web.de
22.07.2004 avr um 83 @ 03:32:44 pukshofer.com
12.07.2004 jacuinde 82 @ um 20:49:23 telnor.net
29.04.2004 messerjokke um 77 @ 21:36:21 nexgo.de
29.04.2004 messerjokke um 76 @ 21:32:22 nexgo.de
15.04.2004 andreaswohlrabvarioboard Andreas um 75 @ Wohlrab 09:55:49 t-online.de
Ich bin gerade dabei mich für einen Microkontroler zu entscheiden. Ihre Seite hat sehr dazu beigetragen das ich misch warscheinlich für die AVRSerie von ATMEL entscheiden werde. Ich hätte gern gewußt woher die Platiene der Platine mit dem LCDAVR stammt.
30.03.2004 creek55 74 @ um 18:23:19 yahoo.com
Nick Wolf
Thank You !
29.03.2004 service um 73 @ 17:11:57 data-complex.net
Hallo, bin gerad dabei den Anfängerkurs (quer-) zu lesen. Prima Thomas Horn geschrieben. Gute Arbeit. Beste Grüße Tom
Karlheinz Storck
Alle Achtung, eine sehr gut durchdachte und übersichtliche Website. War 'ne tolle Hilfe. Was mir noch fehlt ist die Möglichkeit den Assembler-Quelltext in mehrere Dateien zu splitten, so wie man das üblicherweise in C macht. Gibt's da Vorlagen? Viele Grüße Karlheinz
B.Boemer
Hallo, sehr schoene seiten zum thema avr. der kurs ist ja das beste was bisjetzt gesehen habe. mach weiter so Gruss Bernd
Jonas
Nice Homepage. I searched a long time after something like that and i´m very happy that i have found this great page. Thx a lot for this page.
widyasmoro
i'm a student in one of Indonesian Univ. i'm sorry i can't use dutch language, so i use english i just wanna ask to you how can we make a fire fighter robot using micro controller, because i'm still confuse to aplicate the microcontroller system in my project that is fire fighter robot, of course in a minimum budget please give me the clear explanation and step by step from the first step until finish, it will be better if you can attach picture in it i'm waiting for your answer , thank you very much
13.02.2004 hans.hafner um 68 @ 10:35:49 hit-htl.at
Hans Hafner
Ich verwende das AVR-ASMTutorial in einer schriftlichen Facharbeit, wobei ich die allgemein üblichen Zitierregeln einhalte und auf den Autor verweise. mfG Hans Hafner
07.02.2004 rdcastellano um 67 @ 16:29:37 earthlink.net
Ronnie
Thank you for the help this site gives me
05.02.2004 info um 66 @ 20:39:42 postmaelectronics.nl
Koen Postma
Great AVR turorial. It's really a good starting point for my students!
17.01.2004 hhstamer 65 @ um 14:50:24 gmx.de
Hans-Hasso Stamer
Lieber Herr Schmidt, sehr gutes Material, sehr praxisorientiert Glückwunsch! Habe vor 25 Jahren (!) Z80 programmiert, bin mit AVR "Wiedereinsteiger". Die Sache macht mir riesigen Spaß: habe hier AVRStudio in einer Mac-Emulation zu laufen, was nach 14 Tagen harter Arbeit auch endlich trotz USBSeriell-Adapter mit "doppelter Emulation" - der Mac emuliert eine RS232-Schnittstelle über USB und außerdem einen PC, der diese SS untergeschoben bekommt und brav darüber ausgibt - funktioniert:-) Grüße - Hans-Hasso Stamer, Musiker, früher mal als Ingenieur im Schulungszentrum von "Robotron", zuständig für MRES 20, ein Mikrorechnerentwicklungssystem.....
14.01.2004 dl8oai um 64 @ 22:42:13 darc.de
Hallo OM Schmidt, Ihr AVRAnfängerkurs ist echt prima. Ich habe jetzt den 68HC11 verlassen und bin zu den AVR's gekommen. Alles was Volker Herrig man so schnell mal für AVRAssembler so wissen muss habe ich von Ihnen. Besten Dank. 73 de Volker, DL8OAI, Z47
23.03.2004 kll1 um 72 @ 10:09:13 online.de
16.03.2004 bomer um 71 @ 12:35:09 uni-muenster.de 13.03.2004 jonas_kneer um 70 @ 21:42:42 lycos.de
22.02.2004 avissena_ken um 69 @ 08:04:14 yahoo.com
Wolfgang Jahn
Hallo Gerhard, eine sehr gute Seite. Sie hat mir als Anfänger sehr geholfen. Allerdings habe ich die 2 (*4*)-Zeilige LCD (204) im 4-bitModus nicht zum Laufen gebracht. Es wurden nur Zeile 1 und 3 angezeigt. In den einschlägigen Foren sind nur unzureichende Infos erhältlich. Den entscheidenden Hinweis bekam ich dann aus der Schweiz: Man muß die 2-ZeilenInitialisierung 2* durchführen! Ich hab es einfach ausprobiert und es funktionierte. Dann hat es mich aber gepackt und ich wollte wissen: Warum? Ganz einfach: Das Display initialisiert im 8 bit-Modus und die Umschaltung erfolgt im bit #3 (unteres Nibble). Aber genau das ist bei mir hardwaremäßig (4 bit Ansteuerung) auf 0 gelegt. Es wird also nie auf 2 Zeilen initialisiert. Das erfolgt erst bei der angesprochenen Befehlswiederholung. Beim ersten Mal wird in 8 bit Ansteuerung auf 4 bit umgeschaltet, beim 2. Mal erfolgt die Umschaltung im 4 bit Modus auf 2 Zeilen. Jetzt ist eigentlich nur noch offen, was mit dem zweiten Nibble des ersten Befehls passiert. Viele Grüße aus dem Frankenwald W
07.01.2004 adsf um 62 @ 22:47:05 asdf.ch
lanny.ch
Hi!!!! Ich bin ein Atmel-Freak, es sind wohl die besten uC's! Gruss aus der Schweiz dani
26.12.2003 supagu um 61 @ 13:53:27 hotmail.com
Fabian Mathews
Exellent website, very helpful when learning AVR :)
09.12.2003 mokdadr um 60 @ 11:44:44 yahoo.de
Mokdad raed
14.01.2004 w.jahn um 63 @ 06:36:52 addcom.de
08.12.2003 NoValid um 59 @ 16:19:35 Address.de
Markus
Ich habe eine Seite zum Assembler lernen gesucht und muss sagen: Das ist sie ! Top gemacht und super erklärt ! Einziger Kritikpunkt: ein paar Rechtschreibfehler Top Seite ! Werde ich weiter empfehlen !
07.12.2003 gorbhag um 58 @ 22:37:59 o2.pl
Artur
Very nice and useful page! The best for beginners in avr-world..
Dan Carroll
I am trying to set up the 2 line LCD display for the STK500,using Lcd4IncCE.asm,but get an error on compiling "can't find Lcd4Inc.asm, I cannot find this file, is it available? Regards Dan
Scott
This site is just about everything I need right now to move into ASM for Micro Processors. Well done!
06.11.2003 finanse um 55 @ 17:41:09 leier.tarnow.pl
Piotr Kaczecki
Hallo, ich habe Deine Seite besichtigt. Eine prima Leistung ! Ich glaube, sie wird für mich für lange Zeit die Nummer 1 werden ! Ich melde mich noch irgendwann. Grüsse Piotr aus Polen (auf Deutsch: Peter)
05.11.2003 elwinulje um 54 @ 12:12:41 hotmail.com
Elwin
Great site! Keep up the good work =)
31.10.2003 C.R.Z 53 @ um 09:51:07 t-online.de
Zellmeier Rupert
Der Assemblerkurs ist Klasse nur tu ich mich halt sehr schwer. Wie heist es so schön! Kunst das kommt von können, käme es von wollen so hiese es Wunst
31.10.2003 coronapune 52 @ um 06:12:23 vsnl.com
I am going to learn AVR for the first time. It seems your pages will reduce Vasant Nehete my faltering steps in the beginning. Thanks for putting correct material in less memory. vasant Nehete
04.12.2003 carrolld 57 @ um 14:31:56 lsel.ns.ca
14.11.2003 scott.pinkerton um 56 @ 00:25:04 adl.lep.com.au
adriana
hello, i would like to thank you for this wonderful page. it's great for people trying to find their way through assembly language. i am very grateful to you.
F. Dandy
I am looking a good and nice web for the AVR rto widen my 8051 knowledge and I found a cave with a lots of GOLD --- a lot for the AVR beginners like me. Thanks a lot.
17.08.2003 kgrun99999 um 49 @ 20:53:52 aol.com
kurt
Das ist ja eine ganz tolle Anleitung... Ich fange gerade erst mit dem ATmega an und dank vieler Tips und Beispiele wirds bestimmt gut klappen.. Vielen Dank für diese Arbeit !!
14.08.2003 c1tan 48 @ um 18:24:48 plymouth.ac.uk
CS Tan
Very good and interesting, keep up the good work. Good luck.
04.08.2003 squall_here um 47 @ 17:51:36 yahoo.com
squall
i want to know about a mocrocontroller atmel AVR, aplication, and how to use it
11.06.2003 Bertram.Energietechnik um 46 @ 21:56:57 t-online.de
Detlev Bertram
vielen Dank für die Einführung
Klaus
Danke für das Tutorial, ich würde wahrscheinlich sonst nur Bahnhof verstehen. Wenn ich mal weiter bin, stell ich gerne meine Erfahrung auch zur Verfügung. Nochmals Danke.
02.06.2003 hogantech 44 @ um 14:42:45 netspace.net.au
Michael Hogan
Hello I just found your site and it has already answered a few of my questions. I will give more feedback as I proceed through the tutorial. Regards Michael
30.05.2003 j-l-h um 43 @ 19:30:37 web.de
Jens Lieberum
sehr gut verständlich saubere Arbeit Sir/Lady
Juppi
Dein avr-asm-tutorial ist das Beste, was ich jemals in Sachen Assembler gelesen habe. Der Aufbau dieser Domain mit allen Querverweisen, die Liebe zum Detail und die Kunst sich in einen Assembler-Anfänger reinzudenken, übertrifft alles, was ich bisher gesehen habe.(Ich habe vor 25 Jahren mit Cobol und Fortran angefangen, dann C etc. etc.) Kompliment - Juppi
Andreas Weber
Ganz tolle Seite, echt spitze. Besonders hilfreich fand ich konvert. asm, von der Routinen habe ich mir echt sehr viel abgeschaut. Weiter so ! Gruß Andy
25.02.2003 Gerry.keely um 40 @ 12:15:39 esbi.ie
Gerry Keely
Very good introduction to AVR assembly language programming Many Thanks Gerry
15.02.2003 Albert-Hildebrand um 39 @ 23:29:36 t-online.de
Albert Hildebrand
Einfach super gemacht; Respekt!
10.02.2003 hans.demharter um 38 @ 19:37:04 t-online.de
hansdemharter
prima weiter so dl5mfo
10.02.2003 hans-peter.Demharter um 37 @ 19:34:00 telekom.de
Hans Demharter
prima so danke gruß dl5mfo
Uwe
Super Seite! Bin schon ganz Karusell in Kopf da Anfänger. Komme also öfters vorbei!
Ed Nowicki
I enjoyed learning AVR assembly by using your tutorial. Thank you for taking the time to create these pages and sharing your knowledge. Best regards, Ed
04.01.2003 customecu um 34 @ 02:00:28 websurf.net
Steve
Hello, Your page is GREAT... just what I needed. I would send you a Frauline if I could. :) Happy 2003, Steve
12.12.2002 vikram_mutneja um 33 @ 19:07:09 yahoo.com
vikram
are SRAM contents reset to 0 on power on reset
30.10.2003 adrianaadl 51 @ um 15:59:47 hotmail.com
19.08.2003 my8051_ph 50 @ um 12:23:52 hotmail.com
04.06.2003 Klaus.lukkes 45 @ um 15:42:05 aht-partner.de
17.05.2003 juppi um 42 @ 12:08:01 gmx.li
18.04.2003 spam um 41 @ 01:44:05 tech-chat.de
09.02.2003 Uwe 36 @ um 12:30:20 hebels.de 12.01.2003 enowicki um 35 @ 22:06:51 letllc.com
http://www.avr-asm-tutorial.net/gb_new.html (1 of 2)1/20/2009 8:01:52 PM
Guestbook at avr-asm-tutorial.net
29.11.2002 Klaus_Falkenberg um 32 @ 09:09:43 web.de
Klaus Falkenberg
Hallo, prima Seite ! Kann schon einiges auf den AVRs, habe hier aber noch reichlich dazugelernt. Viele Grüße Klaus
10.10.2002 birdix um 31 @ 14:36:54 hotmail.com
RoBSki!
http://Gheos.com/avr :) FREE AVR stuff for the masses
25.09.2002 kiran_mr 30 @ um 14:23:39 yahoo.com
Kiran M.R.
Thanks for coming out with a excellent website..i am a new entrant into the AVR world. Regards, -Kiran
07.09.2002 ferlopvi um 29 @ 21:05:40 earthlink.net
Flopez
Very nice and helpfull site!
26.07.2002 Hemmecke um 28 @ 08:51:25 fh-swf.de
Martin Hemmecke
Sehr schöne Seite, gut verständliche Beispiele. Arbeite gerade an einer Fließkommaarithmetik für den AT90. Da ich allerdings erst seit 2 Tagen das STK500 habe, dauert es noch ein bisschen. Sende Euch eine Kopie des Codes dann zu. Gruß Martin
24.07.2002 bruno.marcio um 27 @ 08:25:04 bol.com.br
Bruno Marcio
Im looking for this tutorial so long time, nice job!! regards,
Graeme & John
Thank you for an excellent tutorial. John and I are 3rd year students studying for Bachelor of Information Technology. We are working on a project using the STK500 AVR and had difficulty finding useful information. Your tutorial fills all our needs excellently. Graeme Youngman, John Corkery, c/o Otago Polytechnic Dunedin New Zealand
David Pilote
Great Stuff..I'm heading off into a mega 8 program with tons of info! Not to sure about the pointer *2 and than how you point to the 5th value but are using a 10? Could you explain a little more there (pointerregister)access. Thanks...DP
25.06.2002 mkoala um 24 @ 14:30:40 libero.it
massimo
Hi! Sorry, but I speak only bad english :-)). Your Site Is very good for beginners! Thank You for your help.
17.06.2002 Aqua-Perl um 23 @ 21:57:54 t-oline.de
Werner Bühre Ich bin Anfäger
21.05.2002 ahuman1 22 @ um 19:24:43 netzero.net
Art Human
This is the first of it's type that I've seen. VeryGood!!!
Hugues Belanger
Great page thanks for the info, I want to start programming on AVR's and so far your page has been very helpfull. I do have a question about AVR's and what they can and can't do. I found a MC68HC11 base PSX gamepad emulator on the interenet and this controller is very expensive and hard to source I'd like to know if the AVR family can be use to duplicate the design and how hard would it be to port the code... Here's the url http://www.gamesx.com/ controldata/psxcont/psxcont.htm See the section titled "A Microcontroller to Emulate a PSX Controller" Please be kind and replay ASAP Thanks in advance Hugues
08.04.2002 ethomkin um 20 @ 22:54:59 yahoo.com
ethomkin
Tan: I'm trying to get thru to you. Write me back if this works. Your email address on your card reads: [email protected] and this isn't going thru. I'm at the library and someone is helping me but with little success. Hope you get this. Eunice Thompson
07.04.2002 bigbud2k um 19 @ 14:52:52 htmail.com
fatnbald
Your page is fantastic...thank you so much..best information Ive found yet
06.04.2002 frogfot um 18 @ 10:14:19 hotmail.com
frogfot
You have the best page ever about AVR. Im really thankful for great help :)
31.03.2002 walterjosch um 17 @ 16:33:58 gmx.de
bin schon immer ein neugieriger Leser für Mikrocontroller gewesen, jetzt möchte ich, dank Ihrer Beiträge walterjosch.de in der CQ/DL in den praktischen Teil übergehen, mein call DL3OAZ mfg Walter
02.07.2002 younggl2 um 26 @ 02:18:56 tekotago.ac.nz
25.06.2002 davidp um 25 @ 19:28:00 osc.on.ca
10.04.2002 hbelanger um 21 @ 17:44:55 701.com
Stefan Steinbach
Eine echt super gelungene Seite. Sehr übersichtlich gestaltet und überaus nützlich. Für Anfänger ideal geeignet! Nur weiter so, schade dass es nur wenige solcher informativen Seiten im Internet gibt. Also, weiter so!!! Thx
27.03.2002 hesna.ayhan um 15 @ 15:35:30 chello.at
AYHAN Engin
Ich habe mir das Beispiel mit der "Division einer 16Bit Zahl durch eine 8Bit zahl runter- geladen.In welcher version von AVR Studio3 funktioniert dieses Beispiel.Mit der Version 3.52 geht es nicht,hab es schon ausprobiert. Hilfeeeeeeeeee ! Bitte Danke.
24.03.2002 sergej.bergmann um 14 @ 09:47:49 uni.de
Sergej Bergmann
15.03.2002 brooksc um 13 @ 03:22:56 iprimus.com.au
Clive Brooks
13.03.2002 bam um 12 @ 03:48:32 xtra.co.nz
This is the sort of tuition that I've been looking for on AVR's As a beginner. It fills in a hole between Rob Mullions getting the hardware and being able to actually do something with it without going nuts. Many Thanks.
27.03.2002 webmaster um 16 @ 23:02:00 elektroniktreff.de
Excellent work
Rodrigues
Tolle Website. Für Menschen wie ich, die autodidaktisch AVRs programmieren lernen wollen, eine unglaubliche Hilfe. Sehr übersichtlich und anwenderfreundlich. Vielen Dank.
03.03.2002 espenhoel 10 @ um 13:42:30 hotmail.com
Espen Hoel
Thanks man, for a very resourceful and well written set of webpages! this is absolutely the best instructions for "beginners" that I've come across on the net.
26.02.2002 um 9 03:59:51
royojt @ yahoo.com
This is an excellent site!! I will be a Roy Delatore frequent visitor to this site. MABUHAY!!!
23.02.2002 8 um 13:31:51
utewend @ compuserve.de
07.03.2002 sbo um 11 @ 06:09:19 aol.com
04.02.2002 um 7 21:56:16
letsittry @ gmx.de
Wolfgang Sedlmeier
Ich bin Ausbilder im Elektronikbereich, dies ist eine sehr gute Bereicherung. Danke für Ihre Veröffentlichung
Rainer
Ich finde es immer wieder bewundernswert, wie selbstlos manche Menschen ihr Wissen an andere weitergeben und dabei noch zusätzliche Arbeit im Erstellen und Pflegen einer eigenen Homepage investieren. Suuuuuper Seite! Vielen Dank!!! Hallo, ich habe den Tip für diese Seite von einem Kollegen bekommen. Jetzt werde ich mir erstmal die gepackte Tutorial-Seite mal zu Gemüte führen. Bis bald !
03.02.2002 6 um 16:09:54
info @ edv-sailer.de
Hartmut Sailer
31.01.2002 um 5 21:41:01
schoenborn-boehm @ t-online.de
danke für diese super seite, hoffe, a.schoenborndass sie mir beim einstieg helfen boehm wird.
22.01.2002 um 4 20:52:24
f.toft @ mail.dk
17.01.2002 um 3 12:28:43
Leslie.Belter @ Lift24.de
04.01.2002 um 2 19:29:15
03.01.2002 um 1 04:55:49
edmond.cambier @ skynet.be
prooney797 @ aol.com
Mr. Flemming Toft
Thank a lot for the WERY fine step by step You have made. I have tryed to locate some very interesting places at the web, and your have be attched to favorites. The starter kit STK500 is very hard to get i Denmark but i still think that this kit is the best choice for a beginner (Atmel and Assembler). If you have any comments on that please feel free to reply :-)) ...or if you have more NEED to know for a beginner. Best regards Flemming
Leslie
Habe vor 1ner Woche angefangen mit Assembler und muß sagen, das die Seite hier echt spitze ist weiter so ....:-)
Edmond
Hello, Thank you very much for your assembler-course. It helps me enormous. I will be the first buyer if you ever make a printed book of this course. Herzlichen dank und fiele gruesse von Edmond.
Paul
Thanks for the excellent web site! I have wanted to try using the Atmel chips for awhile now, but did not know where to start. Your tutorial is just what I needed to give me a kickstart. Thank you for the time and energy you have put into the site to share with others. Sincerely, Paul Rooney Rochester, NY USA
Top 25 of 206 search engines , sorted by number of files N
search engines
times
%
1 http://www.google.de
5,812
42.71
2 http://www.google.com
2,767
20.34
3 http://www.google.at
391
2.87
4 http://www.google.co.in
384
2.82
5 http://www.google.co.uk
276
2.03
6 http://www.google.ch
230
1.69
7 http://www.google.nl
219
1.61
8 http://www.google.pl
206
1.51
9 http://www.google.ca
188
1.38
10 http://www.google.co.id
113
0.83
11 http://www.google.hu
111
0.82
110
0.81
13 http://www.google.fi
108
0.79
14 http://www.google.se
107
0.79
15 http://www.google.cz
107
0.79
16 http://www.google.gr
103
0.76
17 http://www.google.it
101
0.74
18 http://www.google.ro
94
0.69
19 http://search.live.com
87
0.64
20 http://search.yahoo.com
79
0.58
21 http://www.google.be
79
0.58
73
0.54
73
0.54
12
22
http://www.google.com. au
http://de.search.yahoo. com
23 http://www.google.fr 24
http://www.google.com. vn
66
0.49
25
http://www.google.com. br
64
0.47
Sum
13,607 100.00
To top of page
Top 25 of 7827 search strings , sorted by number of files N
search strings
times
%
1 avr tutorial
378
2.78
2 avr assembler
222
1.63
3 avr
200
1.47
4 avr programming
143
1.05
5 attiny13
134
0.98
6 assembler
129
0.95
7 avr assembly
104
0.76
8 assembler tutorial
84
0.62
9 avr asm
78
0.57
10 ATtiny13
73
0.54
11 atmega8
66
0.49
12 avr assembler tutorial
62
0.46
13 AVR
51
0.37
14 avr asm tutorial
51
0.37
15 assembler commands
48
0.35
16 stk500
42
0.31
17 avr programing
40
0.29
18 AVR tutorial
40
0.29
19 avr assembly tutorial
38
0.28
20 avr keypad
35
0.26
21 assembler avr
33
0.24
22 avr tutorials
33
0.24
23 STK500
31
0.23
30
0.22
29
0.21
24
schrittmotor steuerung
25 atmega16 Sum
13,607 100.00
To top of page
Top 10 of 10 return codes and messages , sorted by number of files N return codes and messages 1 200 OK 2 304 Not changed 3 206 Partial information 4 404 File not found
times
%
347,368
89.21
37,791
9.71
2,336
0.60
827
0.21
http://www.avr-asm-tutorial.net/weblox_en/2008m12.html (1 of 2)1/20/2009 8:06:42 PM
Weblog statistic for http://www.avr-asm-tutorial.net
5 301 Permanently moved
809
0.21
6 403 Forbidden
81
0.02
7 405 Method not allowed
76
0.02
8 300 Multiple choices
63
0.02
9 400 Error in request
5
0.00
10 xxx Unknown error
5
0.00
Sum
389,361 100.00
To top of page
Top 25 of 244 files not found , sorted by number of files N
Top 25 of 210 search engines , sorted by number of files N
search engines
times
%
1 http://www.google.de
6,141
42.62
2 http://www.google.com
2,721
18.88
3 http://www.google.at
450
3.12
4 http://www.google.co.in
391
2.71
5 http://www.google.co.uk
298
2.07
6 http://www.google.ch
270
1.87
7 http://www.google.pl
249
1.73
8 http://www.google.nl
223
1.55
9 http://www.google.ca
188
1.30
10 http://www.google.com.au
172
1.19
11 http://www.google.se
135
0.94
12 http://www.google.co.id
124
0.86
13 http://www.google.ro
122
0.85
14 http://www.google.fi
105
0.73
15 http://www.google.com.br
101
0.70
16 http://www.google.it
99
0.69
17 http://search.live.com
96
0.67
18 http://www.google.hu
92
0.64
19 http://de.search.yahoo.com
85
0.59
20 http://www.google.com.mx
84
0.58
21 http://www.google.be
82
0.57
22 http://www.google.cz
81
0.56
23 http://www.google.gr
81
0.56
79
0.55
78
0.54
24
http://www.stumbleupon. com
25 http://www.google.fr Sum
14,409 100.00
To top of page
Top 25 of 8392 search strings , sorted by number of files N
search strings
times
%
1 avr tutorial
377
2.62
2 avr assembler
255
1.77
3 avr
192
1.33
4 avr programming
144
1.00
5 assembler
139
0.96
6 attiny13
136
0.94
7 avr assembly
90
0.62
8 assembler tutorial
90
0.62
9 avr asm
90
0.62
77
0.53
11 atmega8
60
0.42
12 avr assembler tutorial
58
0.40
13 assembler commands
51
0.35
14 stk500
45
0.31
15 ATtiny13
42
0.29
16 AVR
41
0.28
17 gavrasm
40
0.28
18 AVR tutorial
40
0.28
19 avr asm tutorial
40
0.28
20 avr assembly tutorial
36
0.25
21 atmel tutorial
36
0.25
22 r2r network
35
0.24
23 AVR Assembler
35
0.24
24 Assembler
35
0.24
25 MCUCR
34
0.24
10
http://www.avr-asm-tutorial.net/avr_en/beginner/index. html
Sum
14,409 100.00
To top of page
Top 12 of 12 return codes and messages , sorted by number of files N return codes and messages 1 200 OK 2 304 Not changed 3 206 Partial information 4 404 File not found
times
%
355,278
89.27
38,631
9.71
2,025
0.51
951
0.24
http://www.avr-asm-tutorial.net/weblox_en/2008m11.html (1 of 2)1/20/2009 8:07:01 PM
Weblog statistic for http://www.avr-asm-tutorial.net
5 301 Permanently moved
869
0.22
6 403 Forbidden
145
0.04
7 300 Multiple choices
59
0.01
8 405 Method not allowed
10
0.00
9 xxx Unknown error
5
0.00
10 401 Not authorized
1
0.00
11 xxx Unknown error
1
0.00
12 400 Error in request
1
0.00
Sum
397,976 100.00
To top of page
Top 25 of 202 files not found , sorted by number of files N