AVR-Assembler-Tutorial

AVRAssemblerTutorial

To my private Homepage

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 Sitemap with all pages at this site Send feedback/questions to the author Leave a new note in my guestbook Visit my forum, enter a new message Visit the Download page for a zipped copy of this webpage or the beginner's course in one PDF doc. Have a look at this webpage's visitor statistic and see how popular this page is! Zur deutschen Seite Sitemap mit allen Seiten auf dieser Webseite Feedback/Fragen an den Autor senden Neuer Eintrag in mein Gästebuch In das Forum gehen, einen neuen Eintrag schreiben Besuchen Sie die Download-Seite um eine Kopie dieser Webseite down zu loaden oder den kompletten Anfängerkurs in einem einzigen PDFDokument. 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 >> ]

Visitors on this page since 01.12.2001:

©2001-2008 by Gerhard Schmidt, Huegelstrasse1, D-64319 Pfungstadt/Germany You may send me emails to info !at! avr-asm-tutorial.net (remove ! and blanks). You may use the information on these pages and distribute it, but keep the copyright information with it.
http://www.avr-asm-tutorial.net/1/20/2009 7:28:38 PM

AVR-Tutorial

Path: Home => AVR-Overview

Tutorial for learning assembly language for the

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 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 Programming and testing of the hardware of the STK200-Board: EEPROM, external RAM, LCDdisplay, SIO-interface Converting an analog voltage to digital using the STK500 board, the on-board analog comparator and timer/counter 1 as pulse width generator 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. 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 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. avr-source AVR-Webring

A command line assembler with extended error checking and commenting, free for download

Binary multiplication, division, conversion of number formats and fixed decimals in detail, hardware multiplication 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 Connectinga 4*3 keypad to an AVR and sensing using Port connections or with a resistor matrix and an AD converter

Software-Know-How, special assembler commands: LPM, stack jumps, macros

Accu loader applying an ATmega16

Top of that page

Index

Error list

New on this webpage
since: Description and link Source code fcountV03 stepper.asm eggtimer.asm -

10.01.2009 Frequency counter with ATmega8, nine modes, LCD, 16 MHz xtal 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.09.2008 Added 16-by-24-bit hardware multiplication 20.01.2008 Hardware multiplication with ATmega 28.06.2007 Steppermotor controller/driver with an ATtiny13 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 29.09.2006 13.08.2006 New version 2.0 of the command line assembler gavrasm (free Assembler for Linux, Dos and Win) 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 04.05.2006 New version 1.7 of gavrasm (free AVR assembler for download). Corrects a bug in the ELIF directive.

Zipped sources akkuload.asm zipped -

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 27.09.2005 28.03.2005 28.03.2005 New version 1.6 of gavrasm (free AVR assembler for download). Adds support for new CAN-, Tiny- and one new Mega-Type. New version 1.5 of gavrasm (free AVR assembler for download). Corrected two minor bugs. Accu cell loader hardware and assembler software applying an ATmega16 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. sawtooth1 sawtooth2 triangle sinewave and wavetable music -

03.01.2005

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 31.05.2003 17.05.2003 gavrasm (Free AVR assembler) in improved version 0.3 for download. gavrasm (Free AVR assembler) in improved version 0.2 for download Analog-to-Digital-Conversion using the analog comparator and timer/counter 1 of the AT90S8515 on board a STK500 -

-

ADC8.asm 8-Bit ADC 10-Bit ADC Lcd4IncE.asm Lcd4IncCE.asm

09.05.2003 Fixed decimal point numbers 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 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 01.12.2001 Binary math (multiplication and division) Moved these pages from http://www.dg4fac.de to this new location at http://www.avr-asm-tutorial.net due to elevated traffic. MULT8E.asm DIV8E.asm 8515STD.asm CLOCK.asm SIOHEX.asm PCM2PWG4.asm PWGSIO2.asm TESTMAC1.asm TESTMAC2.asm TESTMAC3. asm AVR-Webring

10.07.2001 Structure of asm source code 24.09.2001 Intro to the studio version 3.52 12.08.2001 Beginner's introduction to AVR assembler language DCF synchronised clock with serial interface in a 2313 14.01.2001 Echoes serial characters back as hex, for the STK200 board Small application page 23.12.2000 PCM to Analogue Decoder for remote control systems Terminal controlled pulse generator

09.12.2000 Examples for the use of macros in assembler language!

Top of that page

Index

Download

avr-source

Known and corrected bugs
Date File(s) Error description Error in source code for keypad Akkuload-Analog schematic: Error in wiring of channel 3 Status Thanks

10.01.2009 keyboard.html 26.08.2007 switch_schem.gif

corrected Carl Rheinlaender 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. 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.

06.01.2005

gavrasm and Pony-Prog

Pending

(Self)

01.10.2004 CALC.html 05.12.2003 05.07.2003 Lcd4IncE Lcd4IncCE

False description of the BLD and Corrected Mark BST instruction Bug in the LCD-Clock asm file prevented compilation Corrected Dan Carroll Corrected Thilo Mölls Andreas Wander Jan de Jong

FP_CONV10, HTML A missing code line caused .ASM calculation errors Corrected the pullup resistor in the experimental circuit, due to occasional reset problems reported, to a lower value Corrected an error in the source code for the 4-Bit-LCD software Elapsed processor time with wrong dimension Two int vectors missing! 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.

24.12.2002 exp2313.gif

Corrected

24.12.2002 (several) 15.07.2002 DIVISION.html 16.02.2002 8515STD.html 8515std.asm

Corrected

Corrected Armin Kniesel Corrected -

testsint.asm sioint.asm 31.12.2001 sioint.inc bcdmath.inc bcdmath.asm

Removed (myself)

23.12.2001

clock.gif clock.pdf

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. 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. Errors using .DEF and .EQU When working with a DCF77 signal the seconds are incorrect (59.th second is already 0). Error in the calculation of tens of monthes from the DCF77 signals Some minor additions and changes in the text

bcdmath.inc 25.11.2001 sioint.inc testsint.asm

Corrected

Axel Rühl

24.09.2001 (Several)

Corrected

Stefan Beyer Thomas Baumann Frank Dalchow Brian Tangney

CLOCK.html CLOCK.asm 03.06.2001 (Several) TEST1.html TEST1.asm TEST1.html 02.12.2000 TEST1.asm

Open!

Corrected corrected

Some translation errors in the text corrected

It was stated that the frequency of Timo the LEDs is 800 kHz. In fact it is corrected Engelmann only 667 kHz!

Top of that page

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 >> ]

Visitors on this page since 16.12.2001:

©2002-2009 by http://www.avr-asm-tutorial.net You may use, copy and distribute these pages as long as you keep the copyright information with it.
http://www.avr-asm-tutorial.net/avr_en/index.html1/20/2009 7:28:59 PM

AVR-Tutorial

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) 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 Alles über Zeitschleifen, mit dem beliebten Sekundenblinker 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 Ansteuerung von Peripherie am Beispiel des STK200: Programmierung und Testen der Hardware auf dem STK-Board: EEPROM, externes RAM, LCDDisplay, SIO-Schnittstelle 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 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. Abdruck einer Artikelserie in der Amateurfunkzeitschrift cq-dl des Deutschen Amateur-Radio Club DARC:
q

Präsentation der AVRMikroprozessoren im PDFFormat mit praktischen Beispielen für den ATtiny13 und mit Assembler-Quelltext 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 Interrupts und Interrupt-Programmierung Ausführliche Erklärungen und Routinen zu den Themen Multiplikation, Division, Zahlenumwandlung und Festkommazahlen in Assembler mit Beispielen (binär, dezimal, hex, ASCII), HardwareMultiplikation Anschluss einer 2-Zeilen-LCD an das STK500 mit Basisroutinen und Beispielprogramm für eine Uhr

Anschluss einer 12-er-Tastatur an einen AVR und Auslesen mittels Portansteuerung oder mit Widerstandsmatrix und einem AD-Wandler

Software-Know-How, Spezialitäten einiger Assemblerbefehle (LPM, StackSprung, Makros)

Akkulader mit einem ATmega16

q

q

q

Teil I: Hardware von AVRProzessoren Teil II: Software von AVRProzessoren Teil III: Programmieren der Prozessoren Teil IV: Morse-Ausgabe mit AVR-Prozessor

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!

Seitenanfang

Inhalte

Fehlerhinweise

asm-Sourcen

AVR-Webring

Neu auf dieser Seite
seit: Beschreibung und Link Sourcecode fcountV03

Verbesserte Version 3 des Frequenzzählers mit Frequenz-, 09.01.2009 Perioden-, Periodenanteil-, Umdrehungs- und Spannungsmessung mit ATmega8 Aktualisierte und verbesserte Version des Anfängerkurses in 07.01.2009 einem PDF-Dokument Update des gezippten Abbildes der Webseite 23.12.2008 28.09.2008 gavrasm Assembler in Version 2.2 (deutsch, englisch und französisch, für 115 AVR-Typen) zum kostenlosen Download Erweiterung der Hardware-Multiplikation um 16-mit-24-BitMultiplikation

-

schrittmotor_v1.asm eieruhr.asm -

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 02.12.2006 gavrasm Assembler in Version 2.1 zum kostenlosen Download 29.10.2006 ATtiny2313-Eieruhr 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 15.4.2006 23.2.2006 28.12.2005 27.9.2005 28.3.2005 gavrasm Assembler in Version 1.7 zum kostenlosen Download. Korrigiert einen Fehler bei der Behandlung der ELIF-Direktive. Präsentation der AVR-Mikroprozessoren an Beispielen mit dem ATtiny13 Anschluss einer 12-er-Tastatur an einen AVR und Auslesen mittels I/O-Leitungen oder einen AD-Wandler gavrasm Assembler in Version 1.6 zum kostenlosen Download. Unterstützt neue CAN, Tiny- und einen neuen Mega-Typ. gavrasm Assembler in Version 1.5 zum kostenlosen Download. Beseitigt zwei kleine Fehler. Hardware und Assembler-Software für ein Akkuladegerät für bis zu vier einzelnen Zellen

(diverse) 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. 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 -

6.1.2005

4.1.2005

R/2R-Widerstandsnetzwerk als Digital-zu-Analog-Wandler, mit einigen einfachen Anwendungen

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. 28.03.2004 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 30.11.2003 Anfängerkurs als komplette Datei im PDF-Format (Download, (78 Seiten, 850 kB)).

-

-

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 16.08.2003 Ein Window Caller zum komfortablen Umgang mit dem Kommandozeilen-Assembler zum kostenlosen Download 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. adc8.asm 8-Bit-ADC 10-Bit-ADC

17.05.2003 8-Bit-AD-Wandler am STK500 Board 09.05.2003 Rechnen mit Festkommazahlen 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 Lcd4Inc.asm Lcd4IncC.asm konvert.asm

11.08.2002 Erstellen von Tabellen im Programm-Flash-Speicher 11.04.2002 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

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 1.12.2001 Grundrechenarten in Assembler (Multiplikation und Division) Umzug dieser Seiten von http://www.dg4fac.de nach hier: http:// www.avr-asm-tutorial.net, bedingt durch sehr viel Webverkehr. mult8.asm div8d.asm 8515std.asm dcf77uhr.asm siohex.asm pcm2pwg4.asm pwgsio2.asm testmac1.asm testmac2.asm testmac3.asm AVR-Webring

7.10.2001 Struktur eines Assemblerprogrammes mit Vorlage für den 8515 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 Kleine Anwendungsseite 23.12.2000 PCM zu Analog Decoder für Fernsteuerungen SIO-gesteuerter Rechteck-Signalgenerator 09.12.2000 Beispiele für die Anwendung von Makros in Assembler!

Seitenanfang

Inhalte

Bekannte und korrigierte Fehler:
Datum Datei(en) Fehlerbeschreibung Fehler beim Auslesen der Zehnertastatur über einen Portanschluss Fehler bei der AutorangeImplementierung im Frequenzzähler mit ATmega8 Status Dank

09.01.2009 HTML-Seite Beschreibung 09.01.2009 Quellcode html-listing 26.08.2007 switch_schem.gif

korrigiert Carl Rheinländer

in v3 korrigiert

Akkulader-Analogteil: Fehler im Schaltbild: vertauschen AD-Wandler- korrigiert Jonny Bijlsma Anschlüsse bei Kanal 3 Akkuload: Fehler in der Umrechnung gemessener Spannungen in Ströme führt zu halbem Ladestrom und falscher Anzeige 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!

02.02.2005 akkucalc.asm

korrigiert Sebastian Mazur

06.01.2005

gavrasm mit Pony-Prog

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 24.12.2002 exp2313.gif Pullup-Widerstand in der Schaltung verkleinert, weil gelegentlich Probleme beim Reset auftreten

Korrigiert

Thilo Mölls Andreas Wander

Korrigiert

24.12.2002 (diverse)

Einige Link-Fehler im BeginnerTutorial (Portbeschreibungen) sowie Jan de Korrigiert einen Fehler im Uhrenquellcode der 4Jong Bit-LCD-Steuerung beseitigt Fehler bei der Angabe der Prozessorzeit Fehler bei der Beschreibung der Datenrichtungsregister Zwei Interrupt-Vektoren fehlten! Zahlreiche HTML-Syntaxfehler Behoben Armin Kniesel Behoben Ralf Schumnig Korrigiert Korrigiert -

15.07.2002 division.html 29.04.2002 test2.html 16.02.2002 8515std.html 8515std.asm

13.01.2002 (alle)

testsint.asm sioint.asm 31.12.2001 sioint.inc bcdmath.inc bcdmath.asm

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.

(Selber gemerkt)

23.12.2001

clock.gif clock.pdf

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! Die beiden inc-Dateien lassen sich nicht von der Webseite laden. Die Dateien wurden in .asm umbenannt und die aufrufende Quelldatei korrigiert. Falsche Verwendung von .DEF und . EQU Instruktionen 1. Falsche Verwendung des LDIBefehls für R1 2. Falsche Angabe der Verzögerung bei Delay10

bcdmath.inc 25.11.2001 sioint.inc testsint.asm

Korrigiert

Axel Rühl

24.9.2001 (Diverse)

Korrigiert

Stefan Beyer

sprung.html

Korrigiert caswi

03.06.2001

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 Kleinere Fehler und Ergänzungen im Text Es wurde behauptet, die Blinkfrequenz der LEDs betrüge 800 kHz. Tatsächlich sind es nur 667 kHz. Inhalte asm-Sourcen Korrigiert Frank Dalchow Timo Engelmann

(Diverse)

02.12.2000

test1.html test1.asm

Korrigiert

Seitenanfang

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 ]

Besucher auf dieser Seite seit 16.12.2001:

©2002-2009 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/index.html1/20/2009 7:29:12 PM

Sitemap der Webseite http://www.avr-asm-tutorial.net

Sitemap der Webseite http://www.avr-asmtutorial.net in deutsch
Hauptseite Deutsche Leitseite
q

q

q

q

q

q

q

q

q

q q

q

q

q

q

q

q

q

Anfänger's Einführung in AVR-Assembler r Gesamter Kurs als PDF beginner_de.pdf r Hardware ISP-Programmier-Interface r Warum in Assembler programmieren? r Werkzeuge für die AVR-Assembler-Programmierung s Erste Schritte mit dem Studio 4 s Erste Schritte mit dem Studio 3.52 s Direktiven in Assembler s Struktur von AVR-Assembler-Programmen s Standard 8515 Programm-Datei Struktur s Programmplanung r Verwendung von Registern in Assembler r Verwendung von Ports in Assembler s Port Details im AT90S8515 r Verwendung von SRAM in Assembler r Relative und absolute Sprünge, Reset- und Interrupt-Vektoren r Rechnen in Assembler r Tabellen: Instruktionen, Abkürzungen, Ports, Vektoren, Direktiven, Ausdrücke Binäres Rechnen in AVR Assembler r Binäres Multiplizieren zweier 8-Bit-Zahlen in AVR Assembler r Assembler Quelltext der Multiplikation r Division einer 16-Bit-Zahl durch eine 8-Bit-Zahl r Assembler Quellcode der Division r Zahlenumwandlung in AVR-Assembler r Quelltext der Zahlenumwandlungsroutinen r Umgang mit Festkommazahlen in AVR Assembler r Assembler Quelltext der Umwandlung einer 8-Bit-Zahl in eine dreistellige Festkommazahl r Assembler Quelltext der Umwandlung einer 10-Bit-Zahl in eine vierstellige Festkommazahl r Hardware-Multiplikation mit ATmega Vier einfache Programmierbeispiele als erste Schritte zum Lernen r Test 1: Ausgabe an die Leuchtdioden. r Test 2: Eingabe von einem Port r Test 3: Timer im Polling mode r Test 4: Timer im Interupt mode Zeitschleifen r 8-Bit-Zeitschleifen s Hardware für die Tonausgabe über Lautsprecher r 16-Bit-Zeitschleifen s Hardware für den LED-Blinker Einführung in die Interrupt-Programmierung r Interrupt-Vektoren r Interruptquellen r Programmablauf bei Interrupts s Grafik Ablauf von Interrupt-Programmen s Grafik Ablauf bei mehreren Interrupts r Interrupt und Ressourcen AVR-Hardware-Testroutinen für einen AT90S8515 auf dem STK200 r Vorrausetzungen für die Beispiele r Demonstriert den Gebrauch des EEPROMs r TestRam testet exteres RAM auf dem STK-200 board r Tested eine angeschlossene LCD-Anzeige s Include-Routinen zur LCD-Ansteuerung s Uhrenanwendung zum LCD-Test r Testet die Serielle Schnittstelle auf dem Board s Hardware der Verbindung des Boards mit dem PC s SIO-Software s Hex-Echo über SIO r Anschluss eines Keyboards an Port B Kleine AVR-Anwendungen r DCF77 Uhr mit dem AVR-Controller AT90S2313 s Schaltbild clock.pdf r Decoder für Fernsteuersignale mit AT90S2313 s Software für PCM-Decoder s aBLAUFDIAGRAMM pcm2flow.pdf s Schaltbild pcm2pwg4.pdf r ANSI-Terminal programmierbarer Signalgenerator s Quellcode für den Pulsweiten-Generator r Einstellbarer Rechteckgenerator mit ATmega8 und LCD-Anzeige s Quellcode Hauptprogramm, html s Quellcode LCD-Routinen, html s Quellcode Frequenztabelle, html s Quellcode alles, asm s Schaltbild r Frequenzzähler mit ATmega8 und LCD-Anzeige s Quellcode Programm, html s Quellcode Programm, asm s Schaltbild Prozessorteil s Schaltbild Vorverstärker, Vorteiler s ATmega8 Fuses, Studio, Teil 1 s ATmega8 Fuses, Studio, Teil 2 s ATmega8 Fuses, PonyProg r ATtiny2313-Eieruhr zum Verschenken s Quellcode, html s Quellcode, asm s Beispiel-Anleitung, Open-Office-Format s Beispiel-Anleitung, Rich-Text-Format s Schaltbild, GIF, groß s Schaltbild, GIF, verkleinert r Steppermotorsteuerung mit ATtiny13 s Quellcode im .asm-Format s Quellcode im .html-Format s Schaltbild Steuerung im .gif-Format s Schaltbild Steuerung im .pdf-Format s Bild der Steuerung s Schaltbild Netzteil im .gif-Format s Schaltbild Netzteil im .pdf-Format s Bild Netzteil s Bild Schrittmotor s Bild Steuerung und Schrittmotor, klein Anschluss einer 2-zeiligen LCD-Anzeige an das STK500 r Include-Routinen für LCD-Anzeige am STK500 r Uhrenanwendung für LCD-Anzeige an das STK500 AVR-PWM-ADC für STK500 r Software für PWM-ADC R/2R-Netzwerk als DAC für einen AVR Spezielles Programmier-Know-How r Anwendung der LPM-Instruktion r JUMP über den Stack r Makro-Befehl-Beispiel r Sprungziele in Makros r Parameter-Übergabe an Makro Anschluss einer 12-er-Tastatur an einen AVR r Open-Office Spreadsheet r Excel Spreadsheet Präsentation der AVR-Mikrokontroller mit Anwendungsbeispielen r Teil_1_Prozessoren.pdf r Teil_2_BitsAndBytes.pdf r Teil_3_Befehle.pdf r Teil_4_AufbauTiny13.pdf r Anwendungsbeispiele.pdf r Teil_5b_Beispiel01_02.pdf r Teil_6_Beispiel03_07.pdf r Teil_7_Beispiel08_10.pdf r Teil_8_Beispiel_11.pdf r Teil_5a_UebersichtBeispiele.pdf r Quellcode der Beispiele: s bsp01_loop.asm s bsp02_led.asm s bsp03_blink.asm s bsp04_blink_langsam.asm s bsp04_blink_langsam_takte.asm s bsp04_blink_langsam_tn12.asm s bsp05_blink_Timer.asm s bsp05_blink_Timer_kurz.asm s bsp06_lsp.asm s bsp07_keyint.asm s bsp08_morsekey.asm s bsp09_adcmorsekey.asm s bsp10_morsebake.asm s bsp11_1750_SinePwm.asm AVR-Einführung für Funkamateure r Teil 1: Eigenschaften von AVR-Mikrokontrollern s Schaltbild mit 2313 s Anschlüsse eines 2323 r Teil 2: Software der Prozessoren s Quellcode: beispiel.asm r Teil 3: Programmieren der Prozessoren s ISP-Interface zum Programmieren r Teil 4: Beispielanwendung CW-Geber s Schaltbild des CW-Gebers s Quellcode für 2313: Cw01.asm s Listing für 2313: Cw01.html s Quellcode für STK500: Cw01_500.asm s Listing der Software für STK500 s Quellcode für STK200: Cw01_200.asm s Listing für STK200: Cw01_200.html Gerd's AVR Assembler r Deutsche Download-Seite r LiesMich.Txt r gavrasm_lin_de_22.zip r gavrasm_win_de_22.zip r gavrasm_dos_de_22.zip r DosRead.Txt r gavrasm_sources_doswin_22.zip r gavrasm_sources_lin_22.zip r Alle Instruktionen: instr.asm r Alle Instruktionen (DOS/WIN-Format): instr_doswin.asm r All Instruktionen (Linux Format): instr_linux.asm r Einführung in Gerd's AVR Assembler Gerd's AVR Assembler Caller r Software (Win): gavrasmW.zip r LiesMich.txt Akkuload - ein Mikroprozessor-gesteuertes Ladegerät r Akkulader_Beschreibung.pdf r Deutsche Download-Seite s Quellcode gesamt: akkuload.zip s Quellcode Hauptprogramm: akkuload.asm s Quellcode akkuuart.asm s Quellcode akkucalc.asm s Quellcode akkukey.asm s Quellcode akkulcd.asm r Schaltbild Prozessorteil, GIF-Format r Schaltbild Analogteil, GIF-Format r Schaltbild Prozessorteil, PDF-Format Links zu allen Quelldateien dieses Tutorials r 8515std.asm r adc8.asm r avr_pwm1.pas r dcf77uhr.asm r div8d.asm r eieruhr.asm r fp_conv10_de.asm r fp_conv8_de.asm r konvert.asm r lcd_inc.asm r lcd_test.asm r Lcd4Inc.asm r Lcd4IncC.asm r mult8.asm r musik.asm r pcm2pwg4.asm r pwgsio2.asm r r2r.pas r sawtooth1.asm r sawtooth2.asm r sine8_25.txt r sinewave.asm r sinewave.pas r siohex.asm r test1.asm r test2.asm r test3.asm r test4.asm r testeep.asm r testjmp.asm r testkbd.asm r testlcd.asm r testlpm.asm r testmac1.asm r testmac2.asm r testmac3.asm r testram.asm r testsio.asm r 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

©2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/sitemap_de.html (2 of 2)1/20/2009 7:29:18 PM

Anfänger's Einführung in AVR-Assembler

Pfad: Home => AVR-Überblick => Programmiertechniken => Menue

Hauptseite Hardware
Interface Progr Exp2313 Boards

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.

Warum Asm? Werkzeuge
Edit Asm ISP Studio3 Studio4 Struktur

Themenüberblick der Programmiertechniken
Thema Hardware Warum Werkzeuge Erläuterung ISP-Interface, ParallelportProgrammierer, 2313Experimentierboard, fertige Boards Befehle

Register
Was? Welche? Zeiger Tabellen Wahl

Warum eigentlich Assembler lernen? Editor, Assembler, ISP, Studio3, Studio4, Struktur Was ist ein Register? .DEF, LDI, MOV

Ports
Was? Welche? Status

SRAM
Was? Wozu? Wie? Stack

Welche unterschiedlichen Register es CLR, ANDI, CBR, CPI, gibt SBCI, SBR, SER, SUBI Register Register als Zeiger Was sollte man in welches Register packen? Was ist ein Port? Ports Welche Ports gibt es? Statusregister als wichtigster Port Was ist das SRAM? Wozu braucht man SRAM? SRAM Wie verwendet man das SRAM? Stack im SRAM Was passiert beim Reset? Lineare Programmabläufe und Verzweigung Ablauf Timing von Befehlen Makros Unterprogramme Interrupts Zahlen und Zeichen LD, ST, LPM, ADIW, SBIW, .DB, .DW .INCLUDE, OUT, IN, CBI, SBI, SLEEP CLx, SEx, BCLR, BSET STS, LDS, LD, ST, STD, LDD PUSH, POP, RCALL, RET .CSEG, .ORG, .ESEG, INC, BRNE, BREQ, BRxx NOP, DEC .MACRO, .ENDMACRO RET, RCALL, RJMP, SBRC, SBRS, SBIC, SBIS RETI ORI, OR, ANDI, AND, CBR, SBR, EOR, COM, NEG, BLD, CLT, SET, BST LSL, LSR, ASR, ROL, ROR, SWAP ADD, ADC, SUB, SBC, CP, CPC, CPI, TST .DB, .DW alle . -

Ablauf
Reset Verzweig Timing Makros Unterprog Interrupts

Rechnen
Zahlen Bits Schieben Add, Sub Umwandlung

Tabellen
Befehle funktional alphabetisch Ports funktional alphabetisch Vektoren Abkürzungen Direktiven Fortgeschr Ausdrücke Linksschieben AVR-Seite Home

Bitmanipulationen Rechnen Schieben und Rotieren Addieren, Subtrahieren, Vergleichen Zahlenumwandlung Befehle nach Funktion Befehle alphabetisch Ports Tabellen Vektoren Abkürzungen Direktiven Ausdrücke

Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/index.html1/20/2009 7:29:24 PM

Programmiertechniken AVR Assembler

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.

Themenüberblick der Programmiertechniken
Thema Hardware Warum Erläuterung ISP-Interface, Parallelport-Programmierer, 2313Experimentierboard, fertige Boards Warum eigentlich Assembler lernen? Befehle

Werkzeuge Editor, Assembler, ISP, Studio3, Studio4, Struktur Was ist ein Register? Welche unterschiedlichen Register es gibt Register Register als Zeiger Was sollte man in welches Register packen? Was ist ein Port? Ports Welche Ports gibt es? Statusregister als wichtigster Port Was ist das SRAM? SRAM Wozu braucht man SRAM? Wie verwendet man das SRAM? Stack im SRAM Was passiert beim Reset? Lineare Programmabläufe und Verzweigung Ablauf Timing von Befehlen Makros Unterprogramme Interrupts Zahlen und Zeichen Bitmanipulationen Rechnen .DEF, LDI, MOV CLR, ANDI, CBR, CPI, SBCI, SBR, SER, SUBI LD, ST, LPM, ADIW, SBIW, . DB, .DW .INCLUDE, OUT, IN, CBI, SBI, SLEEP CLx, SEx, BCLR, BSET STS, LDS, LD, ST, STD, LDD PUSH, POP, RCALL, RET .CSEG, .ORG, .ESEG, INC, BRNE, BREQ, BRxx NOP, DEC .MACRO, .ENDMACRO RET, RCALL, RJMP, SBRC, SBRS, SBIC, SBIS RETI ORI, OR, ANDI, AND, CBR, SBR, EOR, COM, NEG, BLD, CLT, SET, BST LSL, LSR, ASR, ROL, ROR, SWAP ADD, ADC, SUB, SBC, CP, CPC, CPI, TST .DB, .DW alle . -

Schieben und Rotieren Addieren, Subtrahieren, Vergleichen Zahlenumwandlung Befehle nach Funktion Befehle alphabetisch Ports

Tabellen

Vektoren Abkürzungen Direktiven Ausdrücke

Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/rechts.html1/20/2009 7:29:29 PM

Befehle in AVR-Assemblersprache

Pfad: Home => AVR-Überblick => Programmiertechniken => Tabellen

Programmiertechniken in AVR Assemblersprache
Tabellen
q q q q

Befehle nach Funktion geordnet Befehle alphabetisch Ports Abkürzungen

Befehle nach Funktion geordnet
Zur Erklärung über Abkürzungen bei Parametern siehe die Liste der Abkürzungen. Funktion 0 Register setzen 255 Konstante Register => Register SRAM => Register, direkt SRAM => Register SRAM => Register mit INC DEC, SRAM => Register SRAM, indiziert => Register Port => Register Kopieren Stack => Register Programmspeicher Z => R0 Register => SRAM, direkt Register => SRAM Register => SRAM mit INC DEC, Register => SRAM Register => SRAM, indiziert Register => Port Register => Stack 8 Bit, +1 Addition 8 Bit 8 Bit+Carry 16 Bit, Konstante 8 Bit, -1 8 Bit Subtraktion 8 Bit, Konstante 8 Bit - Carry 8 Bit - Carry, Konstante 16 Bit Logisch, links Logisch, rechts Schieben Rotieren, links über Carry Rotieren, rechts über Carry Arithmetisch, rechts Nibbletausch Und Und, Konstante Oder Binär Oder, Konstante Exklusiv-Oder Einer-Komplement Zweier-Komplement Register, Setzen Register, Rücksetzen Bits ändern Register, Kopieren nach T-Flag Register, Kopie von T-Flag Port, Setzen Port, Rücksetzen Zero-Flag Carry Flag Negativ Flag Statusbit setzen Zweierkompliment Überlauf Flag Halbübertrag Flag Signed Flag Transfer Flag Interrupt Enable Flag Zero-Flag Carry Flag Negativ Flag Statusbit rücksetzen Zweierkompliment Überlauf Flag Halbübertrag Flag Signed Flag Transfer Flag Interrupt Enable Flag Register, Register Vergleiche Register, Register + Carry Register, Konstante Register, ≤0 Relativ Indirekt, Adresse in Z Unbedingte Unterprogramm, relativ Verzweigung Unterprogramm, Adresse in Z Return vom Unterprogramm Return vom Interrupt Statusbit gesetzt Statusbit rückgesetzt Springe bei gleich Springe bei ungleich Springe bei Überlauf Springe bei Carry=0 Springe bei gleich oder größer Springe bei kleiner Springe bei negativ Unterfunktion Befehl CLR r1 SER rh LDI rh,k255 MOV r1,r2 LDS r1,k65535 LD r1,rp LD r1,rp+ LD r1,-rp LDD r1,ry+k63 IN r1,p1 POP r1 LPM STS k65535,r1 ST rp,r1 ST rp+,r1 ST -rp,r1 STD ry+k63,r1 OUT p1,r1 PUSH r1 INC r1 ADD r1,r2 ADC r1,r2 ADIW rd,k63 DEC r1 SUB r1,r2 SUBI rh,k255 SBC r1,r2 SBCI rh,k255 SBIW rd,k63 LSL r1 LSR r1 ROL r1 ROR r1 ASR r1 SWAP r1 AND r1,r2 ZNV ZNV Flags ZNV Clk 1 1 1 1 2 2 2 2 2 1 2 3 2 2 2 2 2 1 2 1

ZCNVH 1 ZCNVH 1 ZCNVS 2 ZNV 1

ZCNVH 1 ZCNVH 1 ZCNVH 1 ZCNVH 1 ZCNVS 2 ZCNV ZCNV ZCNV ZCNV ZCNV 1 1 1 1 1 1 1 1 1 1 1 1

ANDI rh,k255 Z N V OR r1,r2 ORI rh,k255 EOR r1,r2 COM r1 NEG r1 SBR rh,k255 CBR rh,255 BST r1,b7 BLD r1,b7 SBI pl,b7 CBI pl,b7 SEZ SEC SEN SEV SEH SES SET SEI CLZ CLC CLN CLV CLH CLS CLT CLI CP r1,r2 CPC r1,r2 CPI rh,k255 TST r1 RJMP k4096 IJMP RCALL k4096 ICALL RET RETI BRBS b7,k127 BRBC b7,k127 BREQ k127 BRNE k127 BRCS k127 BRCC k127 BRSH k127 BRLO k127 BRMI k127 I Z C N V H S T I Z C N V H S T I ZNV ZNV ZNV ZCNV

ZCNVH 1 ZNV ZNV T 1 1 1 1 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

ZCNVH 1 ZCNVH 1 ZCNVH 1 ZNV 1 2 2 3 3 4 4 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2 1/2/3 1/2/3 1/2/3 1/2/3 1/2/3 1 1 1

BRPL k127 Springe bei positiv Bedingte Verzweigung Springe bei größer oder gleich (Vorzeichen) BRGE k127 Springe bei kleiner Null (Vorzeichen) Springe bei Halbübertrag Springe bei HalfCarry=0 Springe bei gesetztem T-Bit Springe bei gelöschtem T-Bit Springe bei Zweierkomplementüberlauf Springe bei Zweierkomplement-Flag=0 Springe bei Interrupts eingeschaltet Springe bei Interrupts ausgeschaltet Registerbit=0 Registerbit=1 Bedingte Sprünge Portbit=0 Portbit=1 Vergleiche, Sprung bei gleich No Operation Andere Sleep Watchdog Reset BRLT k127 BRHS k127 BRHC k127 BRTS k127 BRTC k127 BRVS k127 BRVC k127 BRIE k127 BRID k127 SBRC r1,b7 SBRS r1,b7 SBIC pl,b7 SBIS pl,b7 CPSE r1,r2 NOP SLEEP WDR

Zum Seitenanfang

Befehlsliste alphabetisch
Assemblerdirektiven
.CSEG .DB .DEF .DW .ENDMACRO .ESEG .EQU .INCLUDE .MACRO .ORG

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. r1 r2 rh Register rd rp ry k63 k127 Konstante k255 Bedeutung Allgemeines Quell- und Zielregister Allgemeines Quellregister Oberes Register Doppelregister Pointerregister Pointerregister mit Ablage Pointer-Konstante Bedingte Sprungdistanz 8-Bit-Konstante R0..R31 R16..R31 R24(R25), R26(R27), R28(R29), R30 (R31) X=R26(R27), Y=R28(R29), Z=R30(R31) Y=R28(R29), Z=R30(R31) 0..63 -64..+63 0..255 -2048..+2047 0..65535 0..7 0..63 0..31 Wertebereich

k4096 Relative Sprungdistanz k65535 16-Bit-Adresse Bit Port b7 p1 pl Bitposition Beliebiger Port Unterer Port

Zum Seitenanfang

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/commands.html1/20/2009 7:29:34 PM

Register im AVR

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
q

q q

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:
q q

q q

q

q q

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 X X+ -X Vorgang Lese/Schreibe von der Adresse X und lasse den Zeiger unverändert Lese/Schreibe von der Adresse X und erhöhe den Zeiger anschließend um Eins Vermindere den Zeiger um Eins und lese/schreibe dann erst von der neuen Adresse Beispiele LD R1,X ST X,R1 LD R1,X+ ST X+,R1 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.

Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/register.html1/20/2009 7:29:38 PM

Direktiven und Ausdrücke in AVR-Assemblersprache

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 .DEVICE .DEF Kopf .EQU .SET .INCLUDE Beschreibung 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) Legt ein Synonym für ein Register fest (z.B. .DEF MeinReg = R16) 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) Fixiert den Wert eines Symboles (keine spätere Neudefinition möglich) 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") Beginn des Codesegmentes (alles was danach folgt, wird als Code übersetzt und in den Programmraum gespeichert) 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.) Fügt ein binäres Wort in den Programmraum ein (produziert z.B. eine Tabelle im Code!) Makros werden in die Listdatei aufgenommen (ohne diese Angabe wird Code aus Makros nicht im .LST-file ausgegeben) Beginn eines Makros (Code wird nicht erzeugt, erst bei Aufruf des Makros mit seinem Name (Syntax: .MACRO makroname parameter, Aufruf mit: makroname parameter) Beginn des EEPROM-Speichers (die erzeugten Inhalte landen beim Programmieren im EEPROM-Segment) 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".) Fügt ein binäres Wort in den EEPROM-Raum ein (Im EEPROMSegment wird erst das niedrigere, dann das höhere Byte eingefügt) Beginn des Datensegments (hier dürfen dann nur BYTE-Direktiven und Labels stehen, bei der Übersetzung werden nur die Labels entsprechend ausgewertet) Reserviert ein oder mehrere Bytes im Datensegment (fügt den Bytewert im Unterschied zu .DB nicht wirklich ein!) Legt die Anfangsadresse im jeweiligen Segment fest (z.B. .ORG 0x0000) Schaltet die Listing-Ausgabe ein (der produzierte Code wird menschenlesbar in einer .LST-Datei ausgegeben) Schaltet die Ausgabe in die .LST-Datei aus 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") Ende des Assembler-Codes (stellt weitere Übersetzung ein)

.CSEG

.DB

Code .DW .LISTMAC

.MACRO

.ENDMACRO Ende des Makros .ESEG

EEPROM .DB

.DW

.DSEG SRAM .BYTE .ORG .LIST Überall .NOLIST .INCLUDE .EXIT

Fortgeschrittene Assemblerdirektiven
Fortgeschrittene Assemblerdirektiven erlauben
q

q

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:
q

q

q

q

q

q

q

.IF <Bedingung>, dann Codezeilen, dann .ENDIF. Bedingung ist ein Ausdruck, der entweder wahr oder falsch sein kann, also z.B. ein Vergleich wie a<b, a>b oder a==b. .IF <Bedingung>, 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 <Bedingung 1>, dann Codezeilen, dann .ELIF <Bedingung 2>, 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 "<text>". 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 "<text>". 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 Zeichen + Rechnen * / & | Binär ^ ~ << >> < > == <= Logisch >= != && || ! Addition Subtraktion oder negative Zahl Multiplikation Ganzzahlen-Division Bitweise UND-Verknüpfung Bitweise ODER-Verknüpfung Bitweise Exklusive-ODER-Verknüpfung Bitweise NICHT-Verknüpfung Linksschieben Rechtsschieben Kleiner als Größer als Gleich Kleiner gleich Größer gleich Ungleich UND-Verknüpfung ODER-Verknüpfung NICHT Beschreibung

Links schieben
Anmerkung zum Linksschieben (<<): sehr häufig sind z.B. folgende Zeilen zu sehen: ldi r16,1<<ADSC out ADCSRA,r16 Dabei ist ADSC ein in der Headerdatei definiertes einzelnes Bit in einem Port (hier: das Bit, das eine AD-Umwandlung startet, bei den meisten Mega-Typen eine 6), ADCSRA ist ein ebenfalls im Header definierter Port. Der Ausdruck << bewirkt dabei, dass eine Eins (binär 0000.0001) genau sechs Mal links geschoben wird und zu 0100.0000 wird. Diese Zahl wird an den Port ADCSRA ausgegeben. Die Verwendung dieser Konstruktion hat den Vorteil, dass die genaue Lage des ADSC-Bits im Port ADCSRA gar nicht bekannt sein muss und sich von Headerdatei zu Headerdatei ändern kann, ohne dass der Quellcode geändert werden müsste. Gegenüber der Konstruktion ldi r16,0b01000000 out 0x06,r16 erhöht sich auch noch immens die Transparenz, weil doch ziemlich kryptisch bleibt, was denn nun hier genau gemacht werden soll. Außerdem erspart man sich die Mühe, die Nullen exakt abzuzählen. Was nun, wenn man gleichzeitig auch noch andere Bits in diesem Port setzen möchte, wie z.B. das Interrupt-Enable-Bit ADIE (Bit 3)? Dann muss bitweise ODER-verknüpft werden: ldi r16,(1<<ADSC) | (1<<ADIE) out ADCSRA,r16 Die Klammern setzen die richtigen Rechenprioritäten. Zum Seitenanfang

©2002-2005 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/diraus.html1/20/2009 7:29:41 PM

Gerd's AVR Assembler

Pfad: Home => AVR-Assembler

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
q q q

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
q

q

q

Deutsch: r Linux (i386) r Dos r Win Englisch: r Linux (i386) r Dos r Win Französisch: r Linux (i386) r Dos r 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
q q

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:
q

q

q

q q q q q q q q q q q q q q q q q q

Version 2.1 (Dezember 2006) r Linux (i386) r Dos r Win Version 2.0 (September 2006) r Linux (i386) r Dos r Win Version 1.9 (August 2006) r Linux (i386) r Dos r 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)

©2003-2008 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/gavrasm/index_de.html1/20/2009 7:29:44 PM

Gerd's AVR Assembler

Pfad: Home => AVR-Assembler

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
q q q

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
q q

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:
q

q

q

q q q q q q q q q q q q q q q q q q

Version 2.1 (December 2006) r Linux (i386) r Dos r Win Version 2.0 (September 2006) r Linux (i386) r Dos r Win Version 1.9 (August 2006) r Linux (i386) r Dos r 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)

©2003-2008 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/gavrasm/index_en.html1/20/2009 7:29:45 PM

http://www.avr-asm-tutorial.net/gavrasm/v22/ReadMe.Txt

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 gavrasm@avr-asm-tutorial.net (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:
q

q

q

q

q

q

q

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 R: Register, r L: Label, r C: EQU-constant, r V: SET-variable, r M: Local macro-symbol (label in a macro), r 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:
q q q q

q

q

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

Separators in binary- and hex-constants
gavrasm allows the use of the dot as separator in constants, e.g. .EQU abc = 0b0001.0010 .EQU def = 0x0FFF.FFFF Don't ever try to assemble this code with another assembler! To the top of that page
©2003 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/gavrasm/GAVRASMI.html1/20/2009 7:29:50 PM

http://www.avr-asm-tutorial.net/gavrasm/gavrasmw/ReadMe.txt

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 info@avr-asm-tutorial.net 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

Back to the index page. Zurück zur Index-Seite.

http://www.avr-asm-tutorial.net/gavrasm/mailform.html1/20/2009 7:29:55 PM

http://www.avr-asm-tutorial.net/gavrasm/v22/LiesMich.Txt

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 gavrasm@avr-asm-tutorial.net.
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:
q

q

q

q

q

q

q

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 R: Register, r L: Label oder Marke, r C: EQU-Konstante, r V: SET-Variable, r M: Lokales Makro-Symbol (Label/Marke in einem Makro), r 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:
q

q q

q

q

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

Trennzeichen in Binär- und Hex-Konstanten
Der Assembler erlaubt die Verwendung des Punktes als Trennzeichen in Konstanten, wie z.B. .EQU abc = 0b0001.0010 .EQU def = 0x0FFF.FFFF Versuchen Sie diesen Quellcode niemals mit einem anderen Assembler zu assemblieren! An den Seitenanfang
©2003 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/gavrasm/gavrasmi.html1/20/2009 7:30:35 PM

http://www.avr-asm-tutorial.net/gavrasm/gavrasmw/LiesMich.txt

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 info@avr-asm-tutorial.net 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
q q q

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.

Wenn diese Bedingungen nicht vorliegen, dann ist die Verwendung des Stapels ziemlich nutzlos und verschwendet bloss Zeit. Stapel zum Ablegen von Rücksprungadressen Noch wertvoller ist der Stapel bei Sprüngen in Unterprogramme, nach deren Abarbeitung wieder exakt an die aufrufende Stelle im Programm zurück gesprungen werden soll. Dann wird beim Aufruf des Unterprogrammes die Rücksprungadresse auf den Stapel abgelegt, nach Beendigung wieder vom Stapel geholt und in den Programmzähler bugsiert. Dazu dient die Konstruktion mit dem Befehl RCALL irgendwas ; Springe in das UP irgendwas [...] hier geht es normal weiter im Programm Hier landet der Sprung zum Label irgendwas irgendwo im Programm, irgendwas: ; das hier ist das Sprungziel [...] Hier wird zwischendurch irgendwas getan [...] und jetzt kommt der Rücksprung an den Aufrufort im Programm: RET Beim RCALL wird der Programmzähler, eine 16-Bit-Adresse, auf dem Stapel abgelegt. Das sind zwei PUSHs, dann sind die 16 Bits auf dem Stapel. Beim Erreichen des Befehls RET wird der Programmzähler mit zwei POPs wieder hergestellt und die Ausführung des Programmes geht an der Stelle weiter, die auf den RCALL folgt. Damit braucht man sich weiter um die Adresse keine Sorgen zu machen, an der der Programmzähler abgelegt wurde, weil der Stapel automatisch manipuliert wird. Selbst das vielfache Verschachteln solcher Aufrufe ist möglich, weil jedes Unterprogramm, das von einem Unterprogramm aufgerufen wurde, zuoberst auf dem Stapel die richtige Rücksprungadresse findet. Unverzichtbar ist der Stapel bei der Verwendung von Interrupts. Das sind Unterbrechungen des Programmes aufgrund von äußeren Ereignissen, z.B. Signale von der Hardware. Damit nach Bearbeitung dieser äußeren "Störung" der Programmablauf wieder an der Stelle vor der Unterbrechung fortgesetzt werden kann, muss die Rücksprungadresse bei der Unterbrechung auf den Stapel. Interrupts ohne Stapel sind also schlicht nicht möglich. Fehlermöglichkeiten beim (Hoch-)Stapeln Für den Anfang gibt es reichlich Möglichkeiten, mit dem Stapeln üble Bugs zu produzieren. Sehr beliebt ist die Verwendung des Stapels ohne vorheriges Setzen des Stapelzeigers. Da der Zeiger zu Beginn bei Null steht, klappt aber auch rein gar nix, wenn man den ersten Schritt vergisst. Beliebt ist auch, irgendwelche Werte auf dem Stapel liegen zu lassen, weil die Anzahl der POPs nicht exakt der Anzahl der PUSHs entspricht. Das ist aber schon seltener. Es kommt vorzugsweise dann vor, wenn zwischendurch ein bedingter Sprung nach woanders vollführt wurde und dort beim Programmieren vergessen wird, dass der Stapel noch was in Petto hat. Noch seltener ist ein Überlaufen des Stapels, wenn zuviele Werte abgelegt werden und der Stapelzeiger sich bedrohlich auf andere, am Anfang des SRAM abgelegten Werte zubewegt oder noch niedriger wird und in den Bereich der Ports und der Register gerät. Das hat ein lustiges Verhalten des Chips, auch äußerlich, zur Folge. Kommt aber meistens fast nie vor, nur bei vollstopftem SRAM. Zum Seitenanfang
©2002-2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/beginner/sram.html1/20/2009 7:31:18 PM

Ports im AVR

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 Akkumulator Stack Ext.SRAM/ Ext.Interrupt Ext.Int. Link SREG Status Register Register Link SREG SPL/SPH MCUCR GIMSK GIFR TIMSK TIFR TCCR0 TCNT0 TCCR1A TCCR1B TCNT1 OCR1A OCR1B ICR1L/H WDTCR EEAR EEDR EECR SPCR SPSR SPDR UDR USR UCR UBRR

SPL/SPH Stackpointer MCUCR INT MCU General Control Register Interrupt Mask Register Flag Register Timer Int. Timer Int Mask Register Timer Interrupt Flag Register Timer 0 Timer/Counter 0 Control Register Timer/Counter 0 Timer/Counter Control Register 1 A Timer/Counter Control Register 1 B

Timer Interrupts

Timer 0

Timer 1

Timer 1

Timer/Counter 1 Output Compare Register 1 A Output Compare Register 1 B Input Capture Register

Watchdog Timer

WDT

Watchdog Timer Control Register EEPROM Adress Register

EEPROM

EEPROM EEPROM Data Register EEPROM Control Register Serial Peripheral Control Register

SPI

SPI

Serial Peripheral Status Register Serial Peripheral Data Register UART Data Register

UART

UART

UART Status Register UART Control Register UART Baud Rate Register

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 Z C N V S T I Rechnen ADD, ADC, ADIW, DEC, INC, SUB, SUBI, SBC, SBCI, SBIW ADD, ADC, ADIW, SUB, SUBI, SBC, SBCI, SBIW ADD, ADC, ADIW, DEC, INC, SUB, SUBI, SBC, SBCI, SBIW ADD, ADC, ADIW, DEC, INC, SUB, SUBI, SBC, SBCI, SBIW SBIW Logik AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR COM, NEG AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR AND, ANDI, OR, ORI, EOR, COM, NEG, SBR, CBR NEG Vergleich Bits Schieben Sonstige

CP, CPC, CPI BCLR Z, BSET Z, CLZ, SEZ, TST ASR, LSL, LSR, ROL, ROR CLR CP, CPC, CPI BCLR C, BSET C, CLC, SEC ASR, LSL, LSR, ROL, ROR -

CP, CPC, CPI BCLR N, BSET N, CLN, SEN, TST ASR, LSL, LSR, ROL, ROR CLR CP, CPC, CPI BCLR V, BSET V, CLV, SEV, TST ASR, LSL, LSR, ROL, ROR CLR BCLR S, BSET S, CLS, SES RETI

H ADD, ADC, SUB, SUBI, SBC, SBCI

CP, CPC, CPI BCLR H, BSET H, CLH, SEH BCLR I, BSET I, CLI, SEI

BCLR T, BSET T, BST, CLT, SET -

Zum Seitenanfang
©2002-2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/beginner/ports.html1/20/2009 7:31:29 PM

Details wichtiger Ports im AVR

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 0x5F 3 V 2 N Möglichkeiten 0: Interrupts ausgeschaltet 1: Interrupts erlaubt/eingeschaltet 0: Gespeichertes Bit ist 0 1: Gespeichertes Bit ist 1 0: Kein Halbübertrag aufgetreten 1: Halbübertrag aufgetreten 0: Vorzeichen positiv 1: Vorzeichen negativ 0: Kein Übertrag aufgetreten 1: Übertrag aufgetreten 1: Ergebnis war negativ/kleiner 1: Ergebnis war Null/gleich 0: Kein Übertrag aufgetreten 1: Übertrag aufgetreten 1 Z 0 C Befehl CLI SEI CLT SET CLH SEH CLS SES CLV SEV SEN SEZ CLC SEC

SREG Status Register Akkumulator 0x3F 7 I Bit Name 7 6 5 4 3 2 1 0 I T H S V N Z C 6 T 5 H Bedeutung Globales Interrupt Flag Bitspeicher Halbübertrags-Flag Vorzeichen-Flag Zweierkomplement-Übertrags-Flag Negativ-Flag Null-Flag Übertrags-Flag 4 S

0: Ergebnis war nicht negativ/kleiner CLN 0: Ergebnis war nicht Null/ungleich CLZ

Zum Seitenanfang

Stackpointer
Port Funktion Port-Adresse RAM-Adresse 0x5D/0x5E Verfügbarkeit Ab AT90S8515 aufwärts, nur bei Devices mit >256 Byte internem SRAM

SPL/SPH Stackpointer 003D/0x3E Name SPL SPH Bedeutung

Low-Byte des Stackpointers Ab AT90S2313 aufwärts, nicht bei 1200 High-Byte des Stackpointers

Zum Seitenanfang

SRAM und External Interrupt Steuerung
Port Funktion Port-Adresse RAM-Adresse 0x55 3 ISC11 2 ISC10 Möglichkeiten 0=Kein externes SRAM angeschlossen 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 1 ISC01 0 ISC00

MCUCR MCU General Control Register 0x35 7 SRE Bit Name 7 6 5 4 3 2 1 0 SRE 6 SRW 5 SE Bedeutung Ext.SRAM Enable 4 SM

SRW Ext.SRAM Wait States SE SM ISC11 ISC10 ISC01 ISC00 Sleep Enable Sleep Mode

Zum Seitenanfang

Externe Interrupt-Steuerung
Port Funktion Port-Adresse RAM-Adresse 0x5B 3 2 Möglichkeiten 1 0 -

GIMSK General Interrupt Maskenregister 0x3B 7 INT1 Bit Name 7 6 0...5 INT1 INT0 6 INT0 5 Bedeutung 4 -

Interrupt durch externen Pin INT1 0: Externer INT1 ausgeschaltet (Verknüpft mit Modus in MCUCR) 1: Externer INT1 eingeschaltet Interrupt durch externen Pin INT0 0: Externer INT0 ausgeschaltet (Verknüpft mit Modus in MCUCR) 1: Externer INT0 eingeschaltet (Nicht benutzt)

Port

Funktion

Port-Adresse RAM-Adresse 0x5A 3 2 1 Möglichkeiten Automatisch Rücksetzen durch Bearbeitung der Int-Routine oder Rücksetzen per Befehl 0 -

GIFR General Interrupt Flag Register 0x3A 7 INTF1 Bit Name 7 6 0...5 INTF1 INTF0 6 INTF0 5 Bedeutung Interrupt durch externen Pin INT1 aufgetreten Interrupt durch externen Pin INT0 aufgetreten 4 -

(Nicht benutzt)

Zum Seitenanfang

Timer Interrupt-Steuerung
Port Funktion Port-Adresse RAM-Adresse 0x59 3 TICIE1 2 1 TOIE0 Möglichkeiten 0: Kein Int bei Überlauf 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 0: Kein Int bei Capture 1: Int bei Capture 0: Kein Int bei Überlauf 1: Int bei Überlauf 0 -

TIMSK Timer Interrupt Maskenregister 0x39 7 TOIE1 Bit Name 7 6 5 4 3 2 1 0 TOIE0 TOIE1 6 OCIE1A 5 OCIE1B 4 -

Bedeutung Timer/Counter 1 Überlauf-Interrupt

OCIE1A Timer/Counter 1 Vergleichszahl A Interrupt OCIE1B Timer/Counter 1 Vergleichszahl B Interrupt (Nicht benutzt) TICIE1 Timer/Counter 1 Capture-Ereignis Interrupt (Nicht benutzt) Timer/Counter 0 Überlauf-Interrupt (Nicht benutzt)

Port

Funktion

Port-Adresse RAM-Adresse 0x58 3 ICF1 2 1 TOV0 Möglichkeiten Interrupt-Modus: Automatisches Rücksetzen bei Bearbeitung der zugehörigen Int-Routine 0 -

TIFR Timer Interrupt Flag Register 0x38 7 TOV1 Bit Name 7 6 5 4 3 2 1 0 TOV0 ICF1 TOV1 6 OCF1A 5 OCF1B 4 -

Bedeutung Timer/Counter 1 Überlauf erreicht

OCF1A Timer/Counter 1 Vergleichszahl A erreicht OCF1B Timer/Counter 1 Vergleichszahl B erreicht (Nicht benutzt) (Nicht benutzt) Timer/Counter 0 Überlauf aufgetreten (Nicht benutzt)

Timer/Counter 1 Capture-Ereignis eingetreten ODER Polling-Modus: Rücksetzen per Befehl

Zum Seitenanfang

Timer/Counter 0
Port Funktion Port-Adresse RAM-Adresse 0x53 3 2 CS02 1 CS01 0 CS00

TCCR0 Timer/Counter 0 Control Register 0x33 7 Bit Name 6 5 Bedeutung 4 -

Möglichkeiten 000: Timer anhalten 001: Clock = Taktfrequenz 010: Clock = Taktfrequenz / 8 011: Clock = Taktfrequenz / 64 100: Clock = Taktfrequenz / 256 101: Clock = Taktfrequenz / 1024 110: Clock = abfallende Flanke externer Pin T0 111: Clock = ansteigende Flanke externer Pin T0

2..0 CS02..CS00 Timer Takt

3..7

(nicht benutzt)

Port

Funktion

Port-Adresse RAM-Adresse 0x52

TCNT0 Timer/Counter 0 Zählregister 0x32

Zum Seitenanfang

Timer/Counter 1
Port Funktion Port-Adresse RAM-Adresse 0x4F 2 1 PWM11 0 PWM10

TCCR1A Timer/Counter 1 Control Register A 0x2F 7 6 5 4 3 Möglichkeiten

COM1A1 COM1A0 COM1B1 COM1B0 Bit 7 6 5 4 3 2 Name COM1A1 COM1A0 Bedeutung

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 (nicht benutzt) 00: PWM aus 01: 8-Bit PWM Pulsweitengenerator 10: 9-Bit PWM 11: 10-Bit PWM

1..0

PWM11 PWM10

Port

Funktion

Port-Adresse RAM-Adresse 0x4E 2 CS12 1 CS11 0 CS10

TCCR1B Timer/Counter 1 Control Register B 0x2E 7 ICNC1 Bit 7 6 6 ICES1 5 Bedeutung Noise Canceler am ICP-Pin Flankenauswahl bei Capture 4 3 CTC1

Name ICNC1 ICES1

Möglichkeiten 0: ausgeschaltet, erste Flanke löst Sampling aus 1: eingeschaltet, Mindestdauer vier Taktzyklen 0: fallende Flanke löst Capture aus 1: steigende Flanke löst Capture aus

5..4 (nicht benutzt) 3 CTC1 Rücksetzen bei 1: Zähler wird bei Gleichheit auf Null gesetzt Compare Match A 000: Zähler anhalten 001: Clock 010: Clock / 8 011: Clock / 64 100: Clock / 256 101: Clock / 1024 110: fallende Flanke externer Pin T1 111: steigende Flanke externer Pin T1

2..0 CS12..CS10 Taktauswahl

Port

Funktion

Port-Adresse RAM-Adresse 0x4C/0x4D

TCNT1L/H Timer/Counter 1 Zählregister 0x2C/0x2D

Port

Funktion

Port-Adresse RAM-Adresse 0x4A/0x4B hex

OCR1AL/H Timer/Counter 1 Output Vergleichsregister A 0x2A/0x2B

Port

Funktion

Port-Adresse RAM-Adresse 0x48/0x49

OCR1BL/H Timer/Counter 1 Output Vergleichsregister B 0x28/0x29

Port

Funktion

Port-Adresse RAM-Adresse 0x44/0x45

ICR1L/H Timer/Counter 1 Input Capture Register 0x24/0x25

Zum Seitenanfang

Watchdog-Timer
Port Funktion Port-Adresse RAM-Adresse 0x41 3 WDE 2 WDP2 1 WDP1 0 WDP0

WDTCR Watchdog Timer Control Register 0x21 7 Bit 7..5 4 3 WDTOE WDE Name 6 5 4 WDTOE

Bedeutung (Nicht benutzt) Watchdog Turnoff Enable Watchdog Enable

WDT-Zyklus bei 5,0Volt Vorausgehendes Setzen zum Abschalten von WDE erforderlich 1: Watchdog aktiv

000: 15 ms 001: 30 ms 010: 60 ms 011: 120 ms 2..0 WDP2..WDP0 Watchdog Timer Prescaler 100: 240 ms 101: 490 ms 110: 970 ms 111: 1,9 s

Zum Seitenanfang

EEPROM
Port Funktion Port-Adresse RAM-Adresse 0x3E/0x3F

EEARL/H EEPROM Adress Register 0x1E/0x1F

EEARH nur bei Typen mit mehr als 256 Bytes EEPROM (ab AT90S8515 aufwärts) Port Funktion Port-Adresse RAM-Adresse 0x3D

EEDR EEPROM Data Register 0x1D

Port

Funktion

Port-Adresse RAM-Adresse 0x3C 3 2 EEMWE 1 EEWE 0 EERE

EECR EEPROM Control Register 0x1C 7 Bit 7..3 2 1 0 EEWE EERE EEPROM Write Enable EEPROM Read Enable Name 6 5 Bedeutung 4 -

Funktion (Nicht benutzt)

EEMWE EEPROM Master Write Enable Vorausgehendes Setzen ermöglicht Schreiben Setzen löst Schreiben aus Setzen löst Auslesen aus

Zum Seitenanfang

Serial Peripheral Interface SPI
Port Funktion Port-Adresse RAM-Adresse 0x2D 4 MSTR 3 CPOL Funktion 0: Interrupts disabled 1: Interrupts enabled 0: SPI abgeschaltet 1: SPI eingeschaltet 0: MSB zuerst 1: LSB zuerst 0: Slave 1: Master 0: Positive Clock Phase 1: Negative Clock Phase 0: Sampling zu Beginn der Clock Phase 1: Sampling am Ende der Clock Phase 00: Clock / 4 SCK Taktfrequenz 0 SPR0 01: Clock / 16 10: Clock / 64 11: Clock / 128 2 CPHA 1 SPR1 0 SPR0

SPCR SPI Control Register 0x0D 7 SPIE Bit Name 7 6 5 4 3 2 1 SPIE SPE 6 SPE 5 DORD

Bedeutung SPI Interrupt Enable SPI Enable

DORD Data Order MSTR Master/Slave Select CPOL Clock Polarity CPHA Clock Phase SPR1

Port

Funktion

Port-Adresse RAM-Adresse 0x2E 4 3 Funktion Interruptanforderung 2 1 0 -

SPSR SPI Status Register 0x0E 7 SPIF Bit Name 7 6 5..0 SPIF 6 WCOL 5 -

Bedeutung SPI Interrupt Flag

WCOL Write Collision Flag Schreibkollision aufgetreten (Nicht benutzt)

Port

Funktion

Port-Adresse RAM-Adresse 0x2F

SPDR SPI Data Register 0x0F

Zum Seitenanfang

UART
Port Funktion Port-Adresse RAM-Adresse 0x2C

UDR UART I/O Data Register 0x0C

Port

Funktion

Port-Adresse RAM-Adresse 0x2B 4 FE 3 OR Funktion 1: Zeichen empfangen 1: Schieberegister leer 1: Ungültiges Stop-Bit 1: Zeichenverlust (Nicht benutzt) 2 1 0 -

USR UART Status Register 0x0B 7 RXC Bit Name 7 6 5 4 3 2..0 RXC TXC FE OR 6 TXC 5 UDRE Bedeutung UART Receive Complete UART Transmit Complete Framing Error Overrun

UDRE UART Data Register Empty 1: Senderegister frei

Port

Funktion

Port-Adresse RAM-Adresse 0x2A 4 RXEN 3 TXEN 2 CHR9 1 RXB8 Funktion 1: Interrupt bei empfangenem Zeichen 1: Interrupt bei leerem Senderschieberegister 1: Empfänger eingeschaltet 1: Sender eingeschaltet 1: Zeichen mit 9 Bit Länge 9.Datenbit beim Empfang 9.Datenbit beim Senden 0 TXB8

UCR UART Control Register 0x0A 7 RXCIE Bit Name 7 6 5 4 3 2 1 0 6 TXCIE 5 UDRIE

Bedeutung

RXCIE RX Complete Interrupt Enable TXCIE TX Complete Interrupt Enable RXEN Receiver Enabled TXEN Transmitter Enable CHR9 9-bit Characters RXB8 Receive Data Bit 8 TXB8 Transmit Data Bit 8

UDRIE Data Register Empty Interrupt Enable 1: Interrupt bei leerem Senderegister

Port

Funktion

Port-Adresse RAM-Adresse 0x29

UBRR UART Baud Rate Register 0x09

Zum Seitenanfang

Analog Comparator
Port Funktion Port-Adresse RAM-Adresse 0x28 2 ACIC 1 ACIS1 0 ACIS0

ACSR Analog Comparator Control and Status Register 0x08 7 ACD Bit Name 7 6 5 4 3 2 1 0 ACO ACI Interrupt Flag ACD 6 5 ACO 4 ACI 3 ACIE Funktion Abschaltung des Comparators (Nicht benutzt) Comparator Output Lesen: Ausgang des Comparators 1: Interruptanforderung 1: Interrupts enabled 00: Interrupt bei Pegelwechsel Input Capture Enable ACIS0 01: Nicht benutzt) 10: Interrupt bei fallender Flanke 11: Interrupt bei steigender Flanke

Bedeutung Disable

ACIE Interrupt Enable

ACIC Input Capture Enable 1: Verbindung zu Timer 1 Capture ACIS1

Zum Seitenanfang

I/O Ports
Port Register A DDRA PINA B DDRB PINB C DDRC PINC D DDRD PIND Funktion Port-Adresse RAM-Adresse 0x1B 0x19 0x18 0x16 0x15 0x13 0x12 0x10 0x3B 0x3A 0x39 0x38 0x37 0x36 0x35 0x34 0x33 0x32 0x31 0x30

PORTA Data Register Input Pins Address

Data Direction Register 0x1A

PORTB Data Register Input Pins Address

Data Direction Register 0x17

PORTC Data Register Input Pins Address

Data Direction Register 0x14

PORTD Data Register Input Pins Address

Data Direction Register 0x11

Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/pdetail.html1/20/2009 7:31:32 PM

Programmablaufsteuerung in AVR Assembler

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 RESET INT0 INT1 TIMER1 CAPT Int Vector Adresse 2313 2323 8515 0000 0000 0000 0001 0001 0001 0002 0003 0002 0003 0004 0005 0006 0008 0009 ausgelöst durch ... Hardware Reset, Power-On Reset, Watchdog Reset Pegelwechsel am externen INT0-Pin Pegelwechsel am externen INT1-Pin Fangschaltung am Timer 1 Timer1 = Compare A Timer1 = Compare B Timer1 = Compare 1 Timer1 Überlauf Timer0 Überlauf Serielle Übertragung komplett UART Zeichen im Empfangspuffer

TIMER1 COMPA TIMER1 COMPB TIMER1 OVF TIMER0 OVF SPI STC UART RX UART UDRE UART TX ANA_COMP

TIMER1 COMP1 0004 0005 -

0006 0002 0007 0007 0008 0009 -

000A UART Senderegister leer 000B UART Alles gesendet 000C Analog Comparator

Man erkennt, dass die Fähigkeit, Interrupts auszulösen, sehr unterschiedlich ausgeprägt ist. Sie entspricht der Ausstattung der Chips mit Hardware. Die Adressen folgen aufeinander, sind aber für den Typ spezifisch durchnumeriert. Die Reihenfolge hat einen weiteren Sinn: Je höher in der Liste, desto wichtiger. Wenn also zwei Geräte gleichzeitig die Unterbrechung auslösen, gewinnt die jeweils obere in der Liste. Die niedrigerwertige kommt erst dran, wenn die höherwertige fertig bearbeitet ist. Damit das funktioniert, schaltet der erfolgreiche Interrupt das Interrupt-Status-Bit aus. Dann kann der niederwertige erst mal verhungern. Erst wenn das Interrupt-Status-Bit wieder angeschaltet wird, kann die nächste anstehende Unterbrechung ihren Lauf nehmen. Für das Setzen des Statusbits kann die Unterbrechungsroutine, ein Unterprogramm, zwei Wege einschlagen. Es kann am Ende mit der Instruktion RETI abschließen. Diese Instruktion stellt den ursprünglichen Befehlszähler wieder her, der vor der Unterbrechung gerade bearbeitet werden sollte und setzt das Interrupt-Status-Bit auf Eins. Der zweite Weg ist das Setzen des Status-Bits per Instruktion SEI ; Setze Interrupt Enabled RET ; Kehre zurück und das Abschließen der Unterbrechungsroutine mit einem normalen RET. Aber Obacht, das ist nicht identisch im Verhalten! Im zweiten Fall tritt die noch immer anstehende Unterbrechung schon ein, bevor die anschließende Return-Instruktion bearbeitet wird, weil das Status-Bit schon um eine Instruktion früher wieder Ints zulässt. Das führt zu einem Zustand, der das überragende Stapelkonzept so richtig hervorhebt: ein verschachtelter Interrupt. Die Unterbrechung während der Unterbrechung packt wieder die Rücksprung-Adresse auf den Stapel (jetzt liegen dort zwei Adressen herum), bearbeitet die Unterbrechungsroutine für den zweiten Interrupt und kehrt an die oberste Adresse auf dem Stapel zurück. Die zeigt auf die noch verbliebenen RET-Instruktion, die jetzt erst bearbeitet wird. Diese nimmt nun auch die noch verbliebene Rücksprung-Adresse vom Stapel und kehrt zur Originaladresse zurück, die zur Zeit der Unterbrechung zur Bearbeitung gerade anstand. Durch die LIFO-Stapelei ist das Verschachteln solcher Aufrufe über mehrere Ebenen kein Problem, solange der Stapelzeiger noch richtiges SRAM zum Ablegen vorfindet. Folgen allerdings zu viele Interrupts, bevor der vorherige richtig beendet wurde, dann kann der Stapel auf dem SRAM überlaufen. Aber das muss man schon mit Absicht programmieren, weil es doch sehr selten vorkommt. Klar ist auch, dass an der Adresse des Int-Vektors nur für eine Ein-Wort-Instruktion Platz ist. Das ist in der Regel eine RJMP-Instruktion an die Adresse des Interrupt Unterprogrammes. Es kann auch einfach eine RETI-Instruktion sein, wenn dieser Vektor gar nicht benötigt wird. Wegen der Notwendigkeit, die Interrupts möglichst rasch wieder freizugeben, damit der nächste anstehende Int nicht völlig aushungert, sollten Interrupt-Service-Routinen nicht allzu lang sein. Lange Prozeduren sollten unbedingt die zweite Methode zur Wiederherstellung des I-Bits wählen, also sofort nach Abarbeitung der zeitkritischen Schritte die Interrupts mit SEI wieder vorzeitig zulassen, bevor die Routine ganz abgearbeitet ist. Eine ganz, ganz wichtige Regel sollte in jedem Fall eingehalten werden: Die erste Instruktion einer Interrupt-Service-Routine ist die Rettung des Statusregisters auf dem Stapel oder in einem Register. Die letzte Instruktion der Routine ist die Wiederherstellung desselben in den Originalzustand vor der Unterbrechung. Jede Instruktion in der Unterbrechungsroutine, die irgendeines der Flags (außer dem IBit) verändert, würde unter Umständen verheerende Nebenfolgen auslösen, weil im unterbrochenen Programmablauf plötzlich irgendeins der verwendeten Flags anders wird als an dieser Stelle im Programmablauf vorgesehen, wenn die Interrupt-Service Routine zwischendurch zugeschlagen hat. Das ist auch der Grund, warum alle verwendeten Register in Unterbrechungsroutinen entweder gesichert und am Ende der Routine wiederhergestellt werden müssen oder aber speziell nur für die Int-Routinen reserviert sein müssen. Sonst wäre nach einer irgendwann auftretenden Unterbrechung für nichts mehr zu garantieren. Es ist nichts mehr so wie es war vor der Unterbrechung. Weil das alles so wichtig ist, hier eine ausführliche beispielhafte Unterbrechungsroutine. .CSEG ; Hier beginnt das Code-Segment .ORG 0000 ; Die Adresse auf Null RJMP Start ; Der Reset-Vektor an die Adresse 0000 RJMP IService ; 0001: erster Interrupt-Vektor, INT0 service routine [...] hier eventuell weitere Int-Vektoren Start: ; Hier beginnt das Hauptprogramm [...] hier kann alles mögliche stehen IService: ; Hier beginnt die Interrupt-Service-Routine PUSH R16 ; Benutztes Register auf den Stapel IN R16,SREG ; Statusregister Zustand einlesen PUSH R16 ; ebenfalls auf den Stapel ablegen [...] Hier macht die Int-Service-Routine irgendwas, benutzt Register R16 POP R16 ; alten Inhalt von SREG vom Stapel holen OUT SREG,R16 ; und Statusregister wiederherstellen POP R16 ; alten Inhalt von R16 wiederherstellen RETI ; und zurückkehren Das klingt alles ziemlich umständlich, ist aber wirklich lebenswichtig für korrekt arbeitende Programme. Es vereinfacht sich entsprechend nur dann, wenn man Register satt zur Verfügung hat und wegen exklusiver Nutzung durch die Service- Routine auf das Sichern verzichten kann. Das war für den Anfang alles wirklich wichtige über Interrupts. Es gäbe noch eine Reihe von Tips, aber das würde für den Anfang etwas zu verwirrend. Zum Seitenanfang
©2002-2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/beginner/sprung.html1/20/2009 7:31:41 PM

Rechnen in AVR Assembler

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:
q q q q q

Positive Ganzzahlen, Vorzeichenbehaftete ganze Zahlen, Binary Coded Digit, BCD, Gepackte BCD, ASCII-Format.

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 R16, Ziffer 1 R17, Ziffer 2 R18, Ziffer 3 7 0 0 0 6 0 0 0 5 0 0 0 4 3 2 1 0 Wertigkeit 128 64 32 16 8 4 2 1 0 0 0 1 0 0 0 1 0 1 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 1 4 und 3 02 2 und 1 50 0 0 0 0 0 0 1 0 0 1 0 1 0 0 0 0 Instruktionen zum Setzen: LDI R17,0x02 ; Oberes Byte LDI R16,0x50 ; Unteres Byte

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 0 1 0 1 0 0 1 1 Korrektur 6 wieder abziehen keine keine (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.

Zum Seitenanfang
©2002-2009 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/rechnen.html1/20/2009 7:31:48 PM

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:
q q q

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, q den ATtiny13, dessen Reset-Eingang an Pin 1 mit einem Widerstand von 10 kΩ an die Betriebsspannung führt, q 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.
q

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
q q q q

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.:
q q q q q q q q

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.

Andere Boards
Es gibt eine Vielzahl an Boards. Eine Einführung in alle verfügbare Boards kann hier nicht gegeben werden. Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/beginner/hardware.html1/20/2009 7:32:00 PM

Warum Assembler lernen?

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

Ausprobieren
Nur Mut bei den ersten Schritten. Wenn Sie schon eine Programmiersprache können, vergessen Sie sie erst mal gründlich, weil sonst die allerersten Schritte schwerfallen. Hinter jeder Assemblersprache steckt auch ein Prozessorkonzept, und große Teile der erlernten Hochsprachenkonzepte machen in Assembler sowieso keinen Sinn. Die ersten fünf Befehle gehen schwer, dann geht es exponentiell leichter. Nach den ersten 10 Zeilen nehmen Sie den ausgedruckten Instruction Set Summary mal für eine Stunde mit in die Badewanne und wundern sich ein wenig, was es so alles zu Programmieren und zum Merken gibt. Versuchen Sie zu Anfang keine Mega-Maschine zu programmieren, das geht in jeder Sprache gründlich schief. Heben Sie erfolgreich programmierte Codezeilen gut dokumentiert auf, Sie brauchen sie sowieso bald wieder. Viel Lernerfolg. zum Seitenanfang
©2002-2008 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/Warum.html1/20/2009 7:32:01 PM

Werkzeuge der Assembler-Programmierung

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.

Die dargestellten Fenster sind alle © ATMEL. Es wird darauf hingewiesen, dass die Bilder mit Software erstellt wurden, die heute nicht mehr verfügbar und sinnvoll anwendbar ist. Sie wird hier nur verwendet, um die Teilschritte klarzumachen.

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:

q q q q q

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.

Aber dazu dann später im Abschnitt über Ports. Soweit dieser kleine Ausflug in die Welt des Simulators. Er kann noch viel mehr, deshalb sollte dieses Werkzeug oft und insbesondere bei allen hartnäckigen Fällen von Fehlern verwendet werden. Klicken Sie sich mal durch seine Menues, es gibt viel zu entdecken. Zum Seitenanfang
©2002-2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/beginner/werkzeuge.html1/20/2009 7:32:45 PM

Werkzeuge der Assembler-Programmierung: Studio

Pfad: Home => AVR-deutsch => Programmiertechniken => Studio 4

Erste Schritte mit dem Studio 4
In diesem Abschnitt werden die ersten Schritte vorgestellt, die zum Assembler-Programmieren und dem Simulieren mit dem ATMEL-Studio 4 nötig sind. Das Studio gibt es als freie Software auf der Webseite von ATMEL zum Download. Die dargestellten Fenster sind alle © ATMEL. Es wird darauf hingewiesen, dass es unterschiedliche Versionen der Studios mit unterschiedlichem Aussehen und unterschiedlicher Bedienung gibt. Diese Darstellung ist kein Handbuch, sie soll lediglich mit einigen wenigen Möglichkeiten vertraut machen, die Anfängern helfen können, die ersten Schritte zu machen. Die Installation wird hier nicht beschrieben. Zum Seitenanfang

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.

Verfügt man nicht über ein STK500 oder ein anderes, vom Studio unterstütztes Programmiergerät, dann kann man die vom Studio erzeugte .hex- und .eep-Dateien natürlich auch mittels eines externen Programmes in den AVR übertragen. Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/studio4.html1/20/2009 7:32:47 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_1.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_1.gif1/20/2009 7:32:52 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_2.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_2.gif1/20/2009 7:32:55 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_3.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_3.gif1/20/2009 7:32:59 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_4.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_4.gif1/20/2009 7:33:06 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_5.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_5.gif1/20/2009 7:33:12 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_6.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_6.gif1/20/2009 7:33:15 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_7.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_7.gif1/20/2009 7:33:20 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_8.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_8.gif1/20/2009 7:33:27 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_9.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_9.gif1/20/2009 7:33:28 PM

http://www.avr-asm-tutorial.net/avr_gra/st4_10.gif

http://www.avr-asm-tutorial.net/avr_gra/st4_10.gif1/20/2009 7:33:30 PM

Werkzeuge der Assembler-Programmierung

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

Strukturierung von Programmen
Der beschriebene Standardaufbau ist in der Vorlage enthalten, die auch als .asm-Version im Quellverzeichnis vorliegt. Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/struktur.html1/20/2009 7:33:32 PM

Standard sheet for 8515 assembler

Pfad: Home => AVR-main => beginners => Standard 8515 file

Standard 8515 programming file structure
; *************************************************************** ; * * ; * * ; * * ; * * ; * * ; * (C)2002 by Letzte Änderung: * ; *************************************************************** ; ; Hardware Anforderungen: ; ; Software Funktionen: ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Konstanten ; .EQU xyz = 12345 ; ; Benutzte Register ; .DEF mpr = R16 ; ; Code beginnt hier ; .CSEG .ORG $0000 ; ; Reset- und Interrupt-Vektoren ; rjmp Start ; Reset-vector rjmp IInt0 ; External Interrupt Request 0 rjmp IInt1 ; External Interrupt Request 1 rjmp TCpt1 ; Timer/Counter1 Capture event rjmp TCmpA ; Timer/Counter1 Compare match A rjmp TCmpB ; Timer/Counter1 Compare Match B rjmp TOvf1 ; Timer/Counter1 Overflow rjmp TOvf0 ; Timer/Counter0 Overflow rjmp SIStc ; SPI Serial Transfer complete 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 ; IInt0: reti ; ; External Interrupt 1 ; IInt1: reti ; ; Timer/Counter 1, Capture event ; TCpt1: reti ; ; Timer/Counter 1, Compare match interrupt A ; TCmpA: reti ; ; Timer/Counter 1, Compare Match interrupt B ; TCmpB: reti ; ; Timer/Counter 1, Overflow interrupt ; TOvf1: reti ; ; Timer/Counter 0, Overflow interrupt ; TOvf0: reti ; ; SPI Serial Transfer Complete ; SIStc: reti ; ; Uart Rx Complete Interrupt ; URxAv: reti ; ; Uart Data register empty interrupt ; UTxDe: reti ; ; Uart Tx complete interrupt ; UTxCp: reti ; ; Analog comparator interrupt ; AnaCp: reti ; ; **************** Ende der Interrupt Service Routinen ********* ; ; Verschiedene Unterprogramme ; ; **************** Ende der Unterprogramme ********************* ; ; ******************** Hauptprogram **************************** ; ; Hauptprogramm beginnt hier ; Start: rjmp start

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/8515std.html1/20/2009 7:33:33 PM

Werkzeuge der Assembler-Programmierung: Studio

Pfad: Home => AVR-deutsch => Programmiertechniken => Werkzeuge

Werkzeuge für die AVR-AssemblerProgrammierung
In diesem Abschnitt werden die ersten Schritte vorgestellt, die zum Assembler-Programmieren mit dem Board STK500 mittels ATMEL-Studio 3 (Version 3.52) nötig sind. Beschrieben wird 1. das Editieren, 2. das Assemblieren, und 3. der Simulator. Die nötigen Software-Werkzeuge gibt es bei auf der Webseite von ATMEL, die auch das Copyright für diese freie Software besitzen. Die dargestellten Fenster sind alle © ATMEL. Es wird darauf hingewiesen, dass es unterschiedliche Versionen der Software mit unterschiedlichem Aussehen und unterschiedlicher Bedienung gibt. Diese Darstellung ist kein Handbuch, sie soll lediglich mit einigen wenigen Möglichkeiten vertraut machen, die Anfängern helfen können, die ersten Schritte zu machen. Die Installation wird hier nicht beschrieben. Nach dem Starten von Studio 3 sollte sich etwa folgendes Bild zeigen.

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.

Für die Simulation gibt es eine Reihe weiterer Ansichten und Möglichkeiten, die hier nicht im Detail beschrieben werden sollen. Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/studio352.html1/20/2009 7:33:35 PM

http://www.avr-asm-tutorial.net/avr_gra/stu352.gif

http://www.avr-asm-tutorial.net/avr_gra/stu352.gif1/20/2009 7:33:40 PM

http://www.avr-asm-tutorial.net/avr_gra/newproj.gif

http://www.avr-asm-tutorial.net/avr_gra/newproj.gif1/20/2009 7:33:41 PM

http://www.avr-asm-tutorial.net/avr_gra/projnew.gif

http://www.avr-asm-tutorial.net/avr_gra/projnew.gif1/20/2009 7:33:43 PM

http://www.avr-asm-tutorial.net/avr_gra/newfile.gif

http://www.avr-asm-tutorial.net/avr_gra/newfile.gif1/20/2009 7:33:44 PM

http://www.avr-asm-tutorial.net/avr_gra/edit.gif

http://www.avr-asm-tutorial.net/avr_gra/edit.gif1/20/2009 7:33:45 PM

http://www.avr-asm-tutorial.net/avr_gra/asm.gif

http://www.avr-asm-tutorial.net/avr_gra/asm.gif1/20/2009 7:33:50 PM

http://www.avr-asm-tutorial.net/avr_gra/sim1.gif

http://www.avr-asm-tutorial.net/avr_gra/sim1.gif1/20/2009 7:33:53 PM

http://www.avr-asm-tutorial.net/avr_gra/regs1.gif

http://www.avr-asm-tutorial.net/avr_gra/regs1.gif1/20/2009 7:33:55 PM

http://www.avr-asm-tutorial.net/avr_gra/io2.gif

http://www.avr-asm-tutorial.net/avr_gra/io2.gif1/20/2009 7:33:58 PM

http://www.avr-asm-tutorial.net/avr_gra/io3.gif

http://www.avr-asm-tutorial.net/avr_gra/io3.gif1/20/2009 7:34:00 PM

Programmplanung in Assembler

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:
q q q q

q

q

q

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:
q

q

q

q

q

Interrupts ermöglichen: r 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! r 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. r 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: r 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). r Die ISR-Addressen sind prozessortyp-spezifisch angeordnet! Beim Portieren zu einem anderen Prozessortyp sind diese Addressen entsprechend anzupassen. r 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! r 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. r 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: r 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. r 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. r 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. r Da auch Interrupts mit höherer Priorität blockiert werden, sollte bei zeitkritischen Operationen niedriger prioritäre Ints besonders kurz sein. r 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: r 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. r 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: r 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<<bint0 ; signalisiere INT0 nach außerhalb out SREG,rsreg ; stelle Status wieder her reti ; Sprung zurück und Int wieder zulassen isr_tc0_Overflow: ; TC0 Overflow 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<<btc0 ; setze TC0-Weiterbehandlungs-Flagge out SREG,rsreg ; stelle Status wieder her reti ; Sprung zurück und Int wieder zulassen ; ; Hauptprogramm-Start ; main: ldi rmp,HIGH(RAMEND) ; setze Stapelregister out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ; ... weiteres ; INT Enable bei TC0 Overflow ldi rmp,1<<TOIE0 ; Overflow Interrupt Enable Timer 0 out TIMSK,rmp ; Interrupt-Maske der Timer setzen ldi rmp,(1<<CS00)|(1<<CS02) ; Teiler durch 1024 out TCCR0,rmp ; Timer starten ; INT Enable beim INT0-Eingang ldi rmp,(1<<SE)|(1<<ISC00) ; SLEEP-Enable und INT0 bei allen Flanken out MCUCR,rmp ; an das Kontrollregister ldi rmp,1<<INT0 ; INT0 ermöglichen out GICR,rmp ; im Interrupt-Kontrollregister ; Interrupt Status Flag setzen sei ; setze Interrupts an ; ; Hauptprogramm-Loop ; loop: sleep ; schlafen legen nop ; Dummy nach dem Aufwachen sbrc rflg,bint0 ; keine INT0-Anforderung rcall mache_int0 ; behandle INT0-Ergebnis sbrc rflg,btc0 ; keine TC0-Overflow-Behandlung rcall mache_tc0 ; behandle TC0-Overflow rjmp loop ; lege dich wieder schlafen ; ; Behandlung der Anforderungen ; mache_int0: ; Behandle INT0-Ergebnis cbr rflg,1<<bint0 ; Setze INT0-Flagge zurück ; ... mache weiteres ret ; fertig, zurück zum Loop mache_tc0: ; Behandle TC0-Overflow cbr rflg,1<<btc0 ; Setze TC0-Flage wieder zurück ; ... mache weiteres ret ; fertig, zurück zum Loop

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
q q

q q

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

©2006 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/beginner/planung.html1/20/2009 7:34:03 PM

Binäres Rechnen in AVR Assembler

Pfad: Home => AVR-Übersicht => Binäres Rechnen

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 r Dezimales Multiplizieren als Vorlage r Binäres Multiplizieren r Assemblerprogramm Multiplikation r Rotieren in Assembler r Multiplikation im Studio 2. Division einer 16-Bit-Zahl durch eine 8-Bit-Zahl r Dezimale Division als Vorlage r Binäre Division r Binäre Division im Simulator 3. Umwandlungen von und zu Binärzahlen r Allgemeines zur Formatumwandlung r Zahlenformate und allgemeine Programmierregeln r ASCII nach Binär r BCD nach Binär r Binärzahl mit 10 multiplizieren r Binär nach ASCII r Binär nach BCD r Binär nach Hex-ASCII r Hex-ASCII nach Binär 4. Rechnen mit Festkommazahlen r Sinn und Unsinn von Fließkommazahlen r Lineare Umrechnungen r Beispiel 1: 8-Bit-AD-Wandler zu 0,00 bis 5,00 Volt r Beispiel 2: 10-Bit-AD-Wandler zu 0,000 bis 5,000 Volt 5. Hardware Multiplikation mit ATmega r 8- mal 8-Bit-Multiplikation r 16- mal 8-Bit-Multiplikation r 16- mal 16-Bit-Multiplikation r 16- mal 24-Bit-Multiplikation

Zum Seitenanfang
©2002-2008 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/index.html1/20/2009 7:34:05 PM

Binäre Multiplikation in AVR Assembler

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!): . * Z2 = reh = R4 = 0x00 rmh = R1 = 0x00 . rm1 = R0 = 0xAA rm2 = R2 = 0x55 0 1 0 1 0 1 0 1 rel = R3 = 0x00

Z1 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0

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:

Multiplikation im Studio
Die folgenden Bilder zeigen die einzelnen Schritte im Studio: 1. MULT8_1.gif: Der Object-Code ist gestartet, der Cursor steht auf dem ersten Befehl. Mit F11 machen wir Einzelschritte. 2. MULT8_2.gif: In die Register R0 und R2 werden die beiden Werte AA und 55 geschrieben, die wir multiplizieren wollen. 3. MULT8_3.gif: R2 wurde nach rechts geschoben, um das niedrigste Bit in das Carry-Bit zu schieben. Aus 55 (0101.0101) ist 2A geworden (0010.1010), die rechteste 1 liegt im Carry-Bit des Status-Registers herum. 4. MULT8_4.gif: Weil im Carry eine Eins war, wird 00AA in den Registern R1:R0 zu dem (leeren) Registerpaar R4:R3 hinzu addiert. 00AA steht nun auch dort herum. 5. MULT8_5.gif: Jetzt muss das Registerpaar R1:R0 mit sich selbst addiert oder in unserem Fall einmal nach links geschoben werden. Aus 00AA (0000.0000.1010.1010) wird jetzt 0154 (0000.0001.0101.0100), weil wir von rechts eine Null hinein geschoben haben. 6. Die gesamte Multiplikation geht solange weiter, bis alle Einsen in R2 durch das Rechtsschieben herausgeflogen sind. Die Zahl ist dann fertig multipliziert, wenn nur noch Nullen in R2 stehen. Die weiteren Schritte sind nicht mehr abgebildet. 7. MULT8_6.gif: Mit F5 haben wir im Simulator den Rest durchlaufen lassen und sind am Ausgang der Multiplikationsroutine angelangt, wo wir einen Breakpoint gesetzt hatten. Das Ergebnisregister R4:R3 enthält nun den Wert 3872, das Ergebnis der Multiplikation von AA mit 55. Das war sicherlich nicht schwer, wenn man sich die Rechengänge von Dezimal in Binär übersetzt. Binär ist viel einfacher als Dezimal! Zum Seitenanfang
©2002-2009 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/multiplikation.html1/20/2009 7:34:08 PM

Multiplikation zweier 8-Bit-Zahlen

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

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/mult8.html1/20/2009 7:34:10 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/mult8.asm

; 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

http://www.avr-asm-tutorial.net/avr_de/quellen/mult8.asm1/20/2009 7:34:12 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_1.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_1.gif1/20/2009 7:34:16 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_2.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_2.gif1/20/2009 7:34:20 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_3.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_3.gif1/20/2009 7:34:23 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_4.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_4.gif1/20/2009 7:34:25 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_5.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_5.gif1/20/2009 7:34:28 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_6.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/MULT8_6.gif1/20/2009 7:34:32 PM

Binäres Rechnen in AVR Assembler

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.

Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/division.html1/20/2009 7:34:35 PM

Division einer 16-Bit-Zahl durch eine 8-Bit-Zahl

Pfad: Home => AVR-Übersicht => Binäres Rechnen => Division

Assembler Quellcode der Division
; Div8 dividiert eine 16-Bit-Zahl durch eine 8-Bit-Zahl ; Test: 16-Bit-Zahl: 0xAAAA, 8-Bit-Zahl: 0x55 ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Registers ; .DEF rd1l = R0 ; LSB Divident .DEF rd1h = R1 ; MSB Divident .DEF rd1u = R2 ; Hifsregister .DEF rd2 = R3 ; Divisor .DEF rel = R4 ; LSB Ergebnis .DEF reh = R5 ; MSB Ergebnis .DEF rmp = R16; Hilfsregister zum Laden ; .CSEG .ORG 0 ; rjmp start ; start: ; ; Vorbelegen mit den Testzahlen ; ldi rmp,0xAA ; 0xAAAA in Divident mov rd1h,rmp mov rd1l,rmp ldi rmp,0x55 ; 0x55 in Divisor mov rd2,rmp ; ; Divieren von rd1h:rd1l durch rd2 ; div8: clr rd1u ; Leere Hilfsregister clr reh ; Leere Ergebnisregister clr rel ; (Ergebnisregister dient auch als inc rel ; Zähler bis 16! Bit 1 auf 1 setzen) ; ; Hier beginnt die Divisionsschleife ; div8a: clc ; Carry-Bit leeren rol rd1l ; nächsthöheres Bit des Dividenten rol rd1h ; in das Hilfsregister rotieren rol rd1u ; (entspricht Multipliklation mit 2) brcs div8b ; Eine 1 ist herausgerollt, ziehe ab cp rd1u,rd2 ; Divisionsergebnis 1 oder 0? brcs div8c ; Überspringe Subtraktion, wenn kleiner div8b: sub rd1u,rd2; Subtrahiere Divisor sec ; Setze carry-bit, Ergebnis ist eine 1 rjmp div8d ; zum Schieben des Ergebnisses div8c: clc ; Lösche carry-bit, Ergebnis ist eine 0 div8d: rol rel ; Rotiere carry-bit in das Ergebnis rol reh brcc div8a ; solange Nullen aus dem Ergebnis ; rotieren: weitermachen ; Ende der Division erreicht stop: rjmp stop ; Endlosschleife

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/div8d.html1/20/2009 7:34:37 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/div8d.asm

; Div8 dividiert eine 16-Bit-Zahl durch eine 8-Bit-Zahl ; Test: 16-Bit-Zahl: 0xAAAA, 8-Bit-Zahl: 0x55 ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Registers ; .DEF rd1l = R0 ; LSB Divident .DEF rd1h = R1 ; MSB Divident .DEF rd1u = R2 ; Hifsregister .DEF rd2 = R3 ; Divisor .DEF rel = R4 ; LSB Ergebnis .DEF reh = R5 ; MSB Ergebnis .DEF rmp = R16; Hilfsregister zum Laden ; .CSEG .ORG 0 ; rjmp start ; start: ; ; Vorbelegen mit den Testzahlen ; ldi rmp,0xAA ; 0xAAAA in Divident mov rd1h,rmp mov rd1l,rmp ldi rmp,0x55 ; 0x55 in Divisor mov rd2,rmp ; ; Divieren von rd1h:rd1l durch rd2 ; div8: clr rd1u ; Leere Hilfsregister clr reh ; Leere Ergebnisregister clr rel ; (Ergebnisregister dient auch als inc rel ; Zähler bis 16! Bit 1 auf 1 setzen) ; ; Hier beginnt die Divisionsschleife ; div8a: clc ; Carry-Bit leeren rol rd1l ; nächsthöheres Bit des Dividenten rol rd1h ; in das Hilfsregister rotieren rol rd1u ; (entspricht Multipliklation mit 2) brcs div8b ; Eine 1 ist herausgerollt, ziehe ab cp rd1u,rd2 ; Divisionsergebnis 1 oder 0? brcs div8c ; Überspringe Subtraktion, wenn kleiner div8b: sub rd1u,rd2; Subtrahiere Divisor sec ; Setze carry-bit, Ergebnis ist eine 1 rjmp div8d ; zum Schieben des Ergebnisses div8c: clc ; Lösche carry-bit, Ergebnis ist eine 0 div8d: rol rel ; Rotiere carry-bit in das Ergebnis rol reh brcc div8a ; solange Nullen aus dem Ergebnis ; rotieren: weitermachen ; Ende der Division erreicht stop: rjmp stop ; Endlosschleife
http://www.avr-asm-tutorial.net/avr_de/quellen/div8d.asm1/20/2009 7:34:38 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D1.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D1.gif1/20/2009 7:34:42 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D2.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D2.gif1/20/2009 7:34:45 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D3.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D3.gif1/20/2009 7:34:48 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D4.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D4.gif1/20/2009 7:34:51 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D5.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D5.gif1/20/2009 7:34:54 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D6.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D6.gif1/20/2009 7:34:57 PM

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D7.gif

http://www.avr-asm-tutorial.net/avr_de/rechnen/DIV8D7.gif1/20/2009 7:35:19 PM

Zahlenumwandlung in AVR Assembler

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:
q

q

q

q

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:
q

q

q

q

q

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.

Von Hex nach Binär
Mit Hex4ToBin2 geht es den umgekehrten Weg. Da hier das Problem der falschen Ziffern auftreten kann, ist wieder jede Menge Fehlerbehandlungscode zusätzlich nötig. Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/konversion.html1/20/2009 7:35:23 PM

Zahlenumwandlung in AVR Assembler

Pfad: Home => AVR-Übersicht => Binäres Rechnen => Zahlenumwandlung => Quelltext

Quelltext der Zahlenumwandlungsroutinen
asm-Version dieses Quelltextes: konvert.asm

Allgemeine Bedingungen
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ******************************************************** * 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 zur Zahlenumwandlung
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;

************* Ü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

Zum Seitenanfang
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/konvert.html1/20/2009 7:35:30 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/konvert.asm

; ******************************************************** ; * Routinen zur Zahlumwandlung, Version 0.1 Januar 2002 * ; * (C)2002 by info@avr-asm-tutorial.net * ; ******************************************************** ; ; 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 R1 R4:R3:R2 R7:R6:R5 Eingangswert 255 Multiplikator 502 Ergebnis 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

Beispiel 2: 10-Bit-AD-Wandler mit Festkommaausgabe
8-Bit-AD-Wandler sind selten, 10-Bit ist da schon gebräuchlicher. Weil 10 Bits noch etwas genauer sind, dürfen es diesmal vier dezimale Stellen sein. Wobei die letzte dieser Stellen naturgemäß klappert. Hier geht es zur HTML-Form und hier zum Assembler-Quelltext des Programmes. Aufgrund des größeren Darstellungsbereichs braucht das Programm etwas mehr untere Register zum Rumrechnen. Die Ausführungszeit ist etwas länger als mit 8 Bits und 3 Stellen, aber auch nicht übermäßig. Zum Seitenanfang
©2003 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/fpconv.html1/20/2009 7:35:34 PM

8-Bit-Binär zu 3-Digit-Dezimal-Festkommazahl

Pfad: Home => AVR-Übersicht => Binäres Rechnen => Festkommazahl => 8-Bit-Wandlung

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 ;

©2003 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/fp_conv8_de.html1/20/2009 7:35:36 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/fp_conv8_de.asm

; 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

10-Bit-Binär zu 4-Digit-Dezimal-Festkommazahl

Pfad: Home => AVR-Übersicht => Binäres Rechnen => Festkommazahl => 10-Bit-Wandlung

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 ;

©2003 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechnen/fp_conv10_de.html1/20/2009 7:35:39 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/fp_conv10_de.asm

; 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. 8-mit-8-Bit-Zahlen 16-mit-8-Bit-Zahlen 16-mit-16-Bit-Zahlen 16-mit-24-Bit-Zahlen

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:

; ; Test Hardware Multiplikation 16 mal 16 ; ; Definiere Register ; .def Res1 = R2 .def Res2 = R3 .def Res3 = R4 .def Res4 = R5 .def m1L = R16 .def m1M = R17 .def m2L = R18 .def m2M = R19 .def tmp = R20 ; ; Lade Startwerte ; .equ m1 = 10000 .equ m2 = 25000 ; ldi m1M,HIGH(m1) ldi m1L,LOW(m1) ldi m2M,HIGH(m2) ldi m2L,LOW(m2) ; ; Multipliziere ; clr R20 ; leer, fuer Uebertraege mul m1M,m2M ; Multipliziere MSBs mov Res3,R0 ; Kopie in obere Ergebnisregister mov Res4,R1 mul m1L,m2L ; Multipliziere LSBs mov Res1,R0 ; Kopie in untere Ergebnisregister mov Res2,R1 mul m1M,m2L ; Multipliziere 1M mit 2L add Res2,R0 ; Addiere zum Ergebnis adc Res3,R1 adc Res4,tmp ; Addiere Uebertrag mul m1L,m2M ; Multipliziere 1L mit 2M add Res2,R0 ; Addiere zum Ergebnis adc Res3,R1 adc Res4,tmp ; ; Multiplikation fertig ;

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:

; Hardware Multiplikation 16 mit 24 Bit .include "m8def.inc" ; ; Register Definitionen .def a1 = R2 ; definiere 16-bit Register .def a2 = R3 .def b1 = R4 ; definiere 24-bit Register .def b2 = R5 .def b3 = R6 .def e1 = R7 ; definiere 40-bit Ergebnisregister .def e2 = R8 .def e3 = R9 .def e4 = R10 .def e5 = R11 .def c0 = R12 ; Hilfsregister fuer Additionen .def rl = R16 ; Laderegister ; ; Load constants .equ a = 10000 ; Multiplikator a, hex 2710 .equ b = 1000000 ; Multiplikator b, hex 0F4240 ldi rl,BYTE1(a) ; lade a mov a1,rl ldi rl,BYTE2(a) mov a2,rl ldi rl,BYTE1(b) ; lade b mov b1,rl ldi rl,BYTE2(b) mov b2,rl ldi rl,BYTE3(b) mov b3,rl ; ; Loesche Registers clr e1 ; Loesche Ergebnisregister clr e2 clr e3 clr e4 clr e5 clr c0 ; loesche Hilfsregister ; ; Multipliziere mul a2,b3 ; Term 1 add e4,R0 ; addiere zum Ergebnis adc e5,R1 mul a2,b2 ; Term 2 add e3,R0 adc e4,R1 adc e5,c0 ; (addiere moeglichen Ueberlauf) mul a2,b1 ; Term 3 add e2,R0 adc e3,R1 adc e4,c0 adc e5,c0 mul a1,b3 ; Term 4 add e3,R0 adc e4,R1 adc e5,c0 mul a1,b2 ; Term 5 add e2,R0 adc e3,R1 adc e4,c0 adc e5,c0 mul a1,b1 ; Term 6 add e1,R0 adc e2,R1 adc e3,c0 adc e4,c0 adc e5,c0 ; ; fertig. nop ; Ergebnis sollte sein hex 02540BE400

Die vollständige Abarbeitung braucht
q q q

10 Taktzyklen für das Laden der Konstanten, 6 Taktzyklen für das Löschen der Register, und 33 Taktzyklen für die Multiplikation.

Zum Seitenanfang
©2008 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/rechnen/hardmult.html1/20/2009 7:35:58 PM

AVR-Tutorial

Pfad: Home => AVR-Übersicht => Tutorium

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 Test1 Erläuterung zum Inhalt 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. 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. 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. 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.

Test2

Test2

Test3

Test3

Test4

Test4

©2002 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/avr_tut.html1/20/2009 7:36:01 PM

AVR-Tutorium, Teil 1

Pfad: Home => AVR-Übersicht => Tutorium => Teil 1

; 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

; 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 ...
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/test1.html1/20/2009 7:36:02 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/test1.asm

; 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 LDI mp,LOW(RAMEND) ;Initiate Stackpointer SPL,mp mp,HIGH(RAMEND) 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 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

; Unterprogramm Lampe 1
; schaltet Lampe 1 ein Lampe1: IN mp,PINB ; Lese Zustand von Port B CBR mp,0b00000010 ; Lösche Bit 1 mit CBR-Befehl OUT PORTB,mp ; Schreibe Ergebnis zurück RET ; Adresse vom Stapel und zurück
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/test2.html1/20/2009 7:36:08 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/test2.asm

; 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

; 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: OUT LDI OUT LDI mp,LOW(RAMEND) ;Initiate Stackpointer (Unterprogramme!) SPL,mp 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 mp in das Register z1

; Vorteiler des Zählers = 1024, 4 MHz/1024 = 3906,25 Hz
; entspricht einem Zählimpuls alle 256 µs. 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. Das mit einer Frequenz von 15,25878906 Hz der Fall, entsprechend ; alle 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
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/test3.html1/20/2009 7:36:14 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/test3.asm

; 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

; 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: 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

; 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
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/test4.html1/20/2009 7:36:18 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/test4.asm

; 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.

Überblick
1. 8-Bit-Register-Zeitschleife 2. 16-Bit-Doppelregister-Zeitschleife

To the top of that page
©2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/zeitschleifen/index.html1/20/2009 7:36:21 PM

Zeitschleifen in AVR Assembler

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:
q q

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:
q

q

q

q

q

AVRs ohne interne Oszillatoren: diese brauchen entweder r einen externen Quarz, r einen externen Keramikresonator, oder r 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 r zwischen internen und externen Taktquellen auswählt, r einen internen Vorteiler zwischen Taktquelle und Prozessortakt schalten kann (Clock Prescaler, siehe unten), r 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:

.inc "tn13def.inc" ; für einen ATtiny13 .equ c1 = 0 ; Bestimmt die Tonhöhe sbi DDRB,0 ; Portbit ist Ausgang Loop: sbi PORTB,0 ; Portbit auf high ldi R16,c1 Loop1: nop nop nop nop nop dec R16 brne Loop1 cbi PORTB,0 ; Portbit auf low ldi R16,c1 Loop2: nop nop nop nop nop dec R16 brne Loop2 rjmp Loop Gut brumm! To the top of that page
©2009 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/zeitschleifen/delay8.html1/20/2009 7:36:24 PM

Zeitschleifen in AVR Assembler

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).

; ; Verzoegerung 16-Bit mit variabler Dauer ; .include "tn13def.inc" ; ; Hardware-abhaengige Konstante ; .equ fc = 1200000 ; Prozessortakt (default) ; ; Meine Konstante ; .equ fck = fc / 1000 ; Taktfrequenz in kHz ; ; Verzoegerungsroutine ; Delay1ms: ; 1 ms Routine .equ c1ms = (1000*fck)/4000 - 1 ; Konstante fuer 1 ms ldi R25,HIGH(c1ms) ; lade Zaehler ldi R24,LOW(c1ms) rjmp delay ; Delay100ms: ; 100 ms Routine .equ c100ms = (100*fck)/4 - 1 ; Konstante fuer 100 ms ldi R25,HIGH(c100ms) ; lade Zaehler ldi R24,LOW(c100ms) rjmp delay ; ; Verzoederungsschleife, erwartet Konstante in R25:R24 ; Delay: sbiw R24,1 ; herunter zaehlen brne Delay ; zaehle bis Null nop ; zusaetzliche Verzoegerung

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:

.equ c1 = 0 ; 0 Takte, macht der Assembler alleine ldi R25,HIGH(c1) ; 1 Takt ldi R24,LOW(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 nop ; tue nichts, 1 Takt sbiw R24 ; 2 Takte brne Loop ; 2 Takte wenn nicht Null, 1 Takt bei Null Jeder Schleifendurchlauf (bis auf den letzten) braucht jetzt zehn Takte und es gelten folgende Formeln: nc = 2 + 10*(c1 - 1) + 9 oder nc = 10 * c1 + 1 Maximal sind jetzt 655361 Takte Verzögerung möglich

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:

.inc "tn13def.inc" ; für einen ATtiny13 .equ c1 = 60000 ; Bestimmt die Blinkfrequenz sbi DDRB,0 ; Portbit ist Ausgang Loop: sbi PORTB,0 ; Portbit auf high ldi R25,HIGH(c1) ldi R24,LOW(c1) Loop1: nop nop nop nop nop sbiw R24,1 brne Loop1 cbi PORTB,0 ; Portbit auf low ldi R25,HIGH(c1) ldi R24,LOW(c1) Loop2: nop nop nop nop nop sbiw R24,1 brne Loop2 rjmp Loop Gut blink! To the top of that page
©2009 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/zeitschleifen/delay16.html1/20/2009 7:36:27 PM

http://www.avr-asm-tutorial.net/avr_de/zeitschleifen/speaker.gif

http://www.avr-asm-tutorial.net/avr_de/zeitschleifen/speaker.gif1/20/2009 7:36:28 PM

http://www.avr-asm-tutorial.net/avr_de/zeitschleifen/led.gif

http://www.avr-asm-tutorial.net/avr_de/zeitschleifen/led.gif1/20/2009 7:36:30 PM

Interruptprogrammierung in AVR Assembler

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.

To the top of that page
©2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/interrupts/index.html1/20/2009 7:36:35 PM

Interruptprogrammierung in AVR Assembler

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 Merke:
q

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

q

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.

q q

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:
q q q

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.

Aufbau bei großen AVR
Große AVR haben einen Adressraum, der mit relativen Sprungbefehlen nicht mehr ganz zugänglich ist. Bei diesen hat die Sprungliste Einträge mit jeweils zwei Worten Länge. Etwa so: jmp Main ; Reset, Sprung zur Initiierung jmp Int0Sr ; Externer Interrupt an INT0, Sprung zur ServiceRoutine reti ; Irgendein anderer Interrupt, nicht benutzt nop jmp IntTc0OvflwSr ; Überlauf Timer 0, Behandlungsroutine reti ; Irgendwelche anderen Interrupts, nicht benutzt nop reti ; Und noch mehr Interrupts, auch nicht benutzt nop Bei Ein-Wort-Einträgen (z.B. RETI) sind die NOP-Instruktionen eingefügt, um ein weiteres Wort zu ergänzen, damit die Adressierung der nachfolgenden Sprungziel-Einträge wieder stimmt. To the top of that page
©2009 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/interrupts/int_vektor.html1/20/2009 7:36:37 PM

Interruptprogrammierung in AVR Assembler

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:
q q q q

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) 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 Wählbar mit Maske, welche Bits bei Pegelwechsel einen Interrupt auslösen 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 Timern mit nur einem Vergleichswert: Übereinstimmung mit COMP-Wert erreicht bei Timern mit mehreren Vergleichswerten: Übereinstimmung mit COMP-Wert erreicht bei Timer im Zählmodus: Übereinstimmung der gezählten Impulse mit CAPT-Wert erreicht ein vollständiges Zeichen wurde empfangen und liegt im Eingangspuffer Zeichen in Sendepuffer übernommen, frei für nächstes Zeichen letztes Zeichen fertig ausgesendet, Senden beendet Schreibvorgang im EEPROM ist beendet, der nächste Schreibvorgang kann begonnen werden eine Umwandlung der Eingangsspannung ist erfolgt, der Ergebniswert kann vom Datenregister abgeholt werden je nach Einstellung der ACIS-Bits erfolgt ein Interrupt bei jedem Polaritätätswechsel oder nur bei positiven bzw. negativen Flanken (weitere Interrupt-Arten sind den Handbüchern zu entnehmen) bei vielen tiny/mega PCIn

Pegelwechseln an einem bestimmten Portanschluss Externe Interrupts (INTn) Port Interrupts Pegelwechsel an einem Port (PCIn)

Überlauf

bei allen Timern TCnO

Timer Interrupts

Vergleichswert Vergleichswerte A, B, C Fangwert Zeichen empfangen

viele A,B: viele C wenige viele viele viele viele fast alle alle mit ADC alle einige

TCnC TCnA... TCnC TCnP UnRX UnUD UnTX EERY ADC ANA ANAn

UART

Senderegister frei Senden beendet

EEPROM ADC Analog Komparator Weitere

EEPROM ready Wandlung komplett Wechsel bei Vergleich (diverse)

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 AT90S1200 AT90S2313 AT90S2323 AT90S2343 AT90S4433 AT90S4434 AT90S8515 AT90S8535 ATmega8 0000 0001 0002 RES INT0 INT1 RES INT0 TC0O RES INT0 TC0O RES INT0 INT1 RES INT0 INT1 RES INT0 INT1 RES INT0 INT1 RES INT0 INT1 TC1P TC1C TC1O TC0O SPIS U0RX U0UD U0TX ADC EERY ANA U0RX U0UD U0TX ADC EERY ANA U0RX U0UD U0TX ADC EERY ANA U0RX U0UD U0TX ADC EERY ANA TWI U0RX U0UD U0TX ADC EERY ANA TWI SPMR INT2 TC0C SPMR U0RX U0UD U0TX ANA TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS TC1P TC1A TC1B TC1O TC0O SPIS TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS TC1P TC1A TC1B TC1O TC0O SPIS TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS 0003 0004 0005 0006 0007 0008 0009 000A 000B 000C 000D 000E 000F 0010 0011 0012 0013 0014 RES INT0 TC0O ANA TC1P TC1C TC1O TC0O U0RX U0UD U0TX ANA

ATmega8515 RES INT0 INT1 ATmega8535 RES INT0 INT1 ATtiny11 ATtiny12 ATtiny13 ATtiny15L ATtiny22 ATtiny24 ATtiny25 ATtiny26 ATtiny28 ATtiny44 ATtiny45 ATtiny84 ATtiny85 ATtiny261 ATtiny461 ATtiny861 ATtiny2313 RES INT0 PCI0 RES INT0 PCI0 RES INT0 PCI0 RES INT0 PCI0 RES INT0 TC0O RES INT0 PCI0 RES INT0 PCI0 RES INT0 PCI0 RES INT0 INT1 RES INT0 PCI0 RES INT0 PCI0 RES INT0 PCI0 RES INT0 PCI0 RES INT0 PCI0 RES INT0 PCI0 RES INT0 PCI0 RES INT0 INT1

U0RX U0UD U0TX ANA INT2 TC0C EERY SPMR

TC0O EERY ANA TC0A TC0B WDT ADC TC1A TC1O TC0O EERY ANA ADC PCI1 WDT TC1P TC1A TC1B TC1O TC0A TC0B TC0O ANA ADC EERY USIS USIO TC1A TC1O TC0O EERY ANA ADC TC1B TC0A TC0B WDT USIS USIO TC1A TC1B TC1O TC0O USIS USIO EERY ANA ADC PCI0 TC0O ANA PCI1 WDT TC1P TC1A TC1B TC1O TC0A TC0B TC0O ANA ADC EERY USIS USIO TC1A TC1O TC0O EERY ANA ADC TC1B TC0A TC0B WDT USIS USIO PCI1 WDT TC1P TC1A TC1B TC1O TC0A TC0B TC0O ANA ADC EERY USIS USIO TC1A TC1O TC0O EERY ANA ADC TC1B TC0A TC0B WDT USIS USIO TC1A TC1B TC1O TC0O USIS USIO EERY ANA ADC WDT INT1 TC0A TC0B TC0P TC1D FAUL TC1A TC1B TC1O TC0O USIS USIO EERY ANA ADC WDT INT1 TC0A TC0B TC0P TC1D FAUL TC1A TC1B TC1O TC0O USIS USIO EERY ANA ADC WDT INT1 TC0A TC0B TC0P TC1D FAUL TC1P TC1A TC1O TC0O U0RX U0UD U0TX ANA PCI0 TC1B TC0A TC0B USIS USIO EERY WDTO

Man beachte, dass
q q

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 AT90CAN32 AT90CAN64 ATmega16 ATmega32 ATmega48 ATmega64 ATmega88 ATmega103 ATmega128 ATmega161 ATmega162 ATmega163 ATmega164 ATmega165 ATmega168 ATmega169 ATmega323 ATmega324 ATmega325 ATmega329 ATmega406 ATmega640 ATmega644 ATmega645 ATmega649 ATmega1280 ATmega1281 ATmega2560 ATmega2561 ATmega3250 ATmega3290 ATmega6450 ATmega6490 0012 0014 0016 0018 001A 001C 001E RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 TC2C TC2O TC1P TC1A TC1B TC1C TC1O RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 TC2C TC2O TC1P TC1A TC1B TC1C TC1O RES INT0 INT1 TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS U0RX U0UD U0TX ADC EERY RES INT0 INT1 INT2 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS U0RX U0UD U0TX RES INT0 INT1 PCI0 PCI1 PCI2 WDT TC2A TC2B TC2O TC1P TC1A TC1B TC1O TC0A TC0B RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 TC2C TC2O TC1P TC1A TC1B TC1O TC0C RES INT0 INT1 PCI0 PCI1 PCI2 WDT TC2A TC2B TC2O TC1P TC1A TC1B TC1O TC0A TC0B RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 TC2C TC2O TC1P TC1A TC1B TC1O TC0C RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 TC2C TC2O TC1P TC1A TC1B TC1O TC0C RES INT0 INT1 INT2 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS U0RX U1RX U0UD RES INT0 INT1 INT2 PCI0 PCI1 TC3P TC3A TC3B TC3O TC2C TC2O TC1P TC1A TC1B TC1O RES INT0 INT1 TC2C TC2O TC1P TC1A TC1B TC1O TC0O SPIS U0RX U0UD U0TX ADC EERY RES INT0 INT1 INT2 PCI0 PCI1 PCI2 PCI3 WDT TC2A TC2B TC2O TC1P TC1A TC1B TC1O RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS U0RX U0UD U0TX

AT90CAN128 RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 TC2C TC2O TC1P TC1A TC1B TC1C TC1O

RES INT0 INT1 PCI0 PCI1 PCI2 WDT TC2A TC2B TC2O TC1P TC1A TC1B TC1O TC0A TC0B RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS 0012 0014 0016 RES INT0 INT1 INT2 TC2C TC20 TC1P TC1A TC1B TC1O TC0C TC0O SPIS U0RX U0UD U0TX 001E U0RX U0UD U0TX

AVR-Typ/Adr 0000 0002 0004 0006 0008 000A 000C 000E 0010

0018 001A 001C

RES INT0 INT1 INT2 PCI0 PCI1 PCI2 PCI3 WDT TC2A TC2B TC2O TC1P TC1A TC1B TC1O RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS U0RX U0UD U0TX U0RX U0UD U0TX

RES BATI INT0 INT1 INT2 INT3 PCI0 PCI1 WDT WAKE TC1C TC1O TC0A TC0B TC0O TWBU RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 PCI0 PCI1 PCI2 WDT TC2A TC2B TC2O

RES INT0 INT1 INT2 PCI0 PCI1 PCI2 PCI3 WDT TC2A TC2B TC2O TC1P TC1A TC1B TC1O RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 PCI0 RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 PCI0 RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 PCI0 RES INT0 INT1 INT2 INT3 INT4 INT5 INT6 INT7 PCI0 U0RX U0UD U0TX U0RX U0UD U0TX

PCI1 PCI2 WDT TC2A TC2B TC2O PCI1 PCI2 WDT TC2A TC2B TC2O PCI1 PCI2 WDT TC2A TC2B TC2O PCI1 PCI2 WDT TC2A TC2B TC2O U0RX U0UD U0TX U0RX U0UD U0TX U0RX U0UD U0TX U0RX U0UD U0TX

RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS RES INT0 PCI0 PCI1 TC2C TC2O TC1P TC1A TC1B TC1O TC0C TC0O SPIS

To the top of that page Interrupts mit den Vektoren von 0020 bis 003E: AVR-Typ/Adr 0020 AT90CAN32 AT90CAN64 ATmega16 ATmega32 ATmega48 ATmega64 ATmega88 ATmega103 ATmega128 ATmega161 ATmega162 ATmega163 ATmega164 ATmega165 ATmega168 ATmega169 0022 0024 0026 0028 002A 002C 002E 0030 0032 0034 0036 0038 003A 003C 003E TC0C TC0O CANI OVRI SPIS TC0C TC0O CANI OVRI SPIS ANA TWI TC0O SPIS TC0O SPIS TC0O SPIS TC0O SPIS TC0O SPIS INT2 TC0C SPMR TWI SPMR SPMR SPMR U0RX U0UD U0TX ANAC ADC EERY TC3P TC3A TC3B TC3C TC3O U0RX U0UD U0TX ANAC ADC EERY TC3P TC3A TC3B TC3C TC3O U0RX U0UD U0TX ANAC ADC EERY TC3P TC3A TC3B TC3C TC3O

AT90CAN128 TC0C TC0O CANI OVRI SPIS ADC EERY ANA

U0RX U0UD U0TX ADC EERY ANA TWI U0RX U0UD U0TX ADC EERY ANA TWI U0RX U0UD U0TX ADC EERY ANA

U0RX U0UD U0TX ADC EERY ANA TC1C TC3P TC3A TC3B TC3C TC3O U1RX U1UD

U0TX U0UD U0TX ADC EERY ANA TC1C TC3P TC3A TC3B TC3C TC3O U1RX U1UD U0RX U1RX U0UD U1UD U0TX U1TX EERY ANA SPMR U0RX U0UD U0TX ANA ADC EERY SPMR SPMR EERY TWI

U1UD U0TX U1TX EERY ANA TC0C TC0O SPIS ANA TWI TC0A TC0B TC0O SPIS USIS USIO ANA TC0O SPIS ADC

U0RX U0UD U0TX ADC EERY ANA TWI ADC 0026 TWSI U0RX U0UD U0TX ANA ADC EERY SPMR EERY SPMR LCD EERY SPMR LCD 0028 002A 002C 002E 0030

USIS USIO ANA 0022 0024

AVR-Typ/Adr 0020 ATmega323 ATmega324 ATmega325 ATmega329 ATmega406 ATmega640 ATmega644 ATmega645 ATmega649 ATmega1280 ATmega1281 ATmega2560 ATmega2561 ATmega3250 ATmega3290 ATmega6450 ATmega6490

0032

0034

0036

0038 003A 003C 003E

ADC EERY ANA

TC0A TC0B TC0O SPIS USIS USIO ANA USIS USIO ANA TWI ADC ADC

EERY TWI

VADC CADC CADR EERY SPMR U0RX U0UD U0TX ANA ADC EERY TC3P EERY TWI

TC1P TC1A TC1B TC1C TC1O TC0A TC0B TC0O SPIS TC0A TC0B TC0O SPIS USIS USIO ANA USIS USIO ANA ADC ADC U0RX U0UD U0TX ANA ADC EERY SPMR EERY SPMR LCD

TC1P TC1A TC1B TC1C TC1O TC0A TC0B TC0O SPIS TC1P TC1A TC1B TC1C TC1O TC0A TC0B TC0O SPIS TC1P TC1A TC1B TC1C TC1O TC0A TC0B TC0O SPIS TC1P TC1A TC1B TC1C TC1O TC0A TC0B TC0O SPIS USIS USIO ANA USIS USIO ANA USIS USIO ANA USIS USIO ANA ADC ADC ADC ADC EERY SPMR EERY SPMR LCD EERY SPMR EERY SPMR LCD PCI2 PCI3 PCI2 PCI3 PCI2 PCI3 PCI2 PCI2

U0RX U0UD U0TX ANA ADC EERY TC3P U0TX U0UD U0TX ANA ADC EERY TC3P U0RX U0UD U0TX ANA ADC EERY TC3P U0TX U0UD U0TX ANA ADC EERY TC3P

To the top of that page Vektoren mit Adressen 0040 bis 005F: AVR-Typ/Adr 0040 AT90CAN32 AT90CAN64 ATmega64 ATmega128 ATmega1280 ATmega1281 ATmega2560 ATmega2561 0042 0044 0046 0048 004A 004C 004E 0050 0052 0054 0056 0058 005A 005C 005E SPMR SPMR SPMR U1RX U1UD U1TX TWI U1RX U1UD U1TX TWI U1TX TWI U1TX TWI SPMR SPMR

AT90CAN128 U1RX U1UD U1TX TWI

TC3A TC3B TC3C TC3O U1RX U1UD U1TX TWI SPMR TC4P TC4A TC4B TC4C TC4O TC5P TC5A TC3A TC3B TC3C TC3O U1RX U1UD U1TX TWI SPMR TC4P TC4A TC4B TC4C TC4O TC5P TC5A TC3A TC3B TC3C TC3O U1RX U1UD U1TX TWI SPMR TC4P TC4A TC4B TC4C TC4O TC5P TC5A TC3A TC3B TC3C TC3O U1RX U1UD U1TX TWI SPMR TC4P TC4A TC4B TC4C TC4O TC5P TC5A

Vektoren mit Adressen 0060 bis 0070: AVR-Typ/Adr 0060 0062 0064 0066 ATmega1280 ATmega1281 ATmega2560 ATmega2561 TC5B TC5C TC5O TC5B TC5C TC5O U2RX U2UD U2TX U3RX U3UD U3TX TC5B TC5C TC5O 0068 006A 006C 006E 0070 TC5B TC5C TC5O U2RX U2UD U2TX U3RX U3UD U3TX

To the top of that page
©2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/interrupts/int_quellen.html1/20/2009 7:36:42 PM

Interruptprogrammierung in AVR Assembler

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:
q q q q

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
q q q q q q q

q

q q

q

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.).

Ablauf bei mehreren interruptgesteuerten Abläufen
Sind mehrere Interrupts aktiv, beginnt der Programmablauf genauso und auch alle weiteren Abläufe sind ganz ähnlich. Durch die Wahl der Reihenfolge der Nachbearbeitung lässt sich steuern, welche zuerst fertig ist. Das Schema lässt sich leicht um weitere Interrupts und deren Nachbearbeitung erweitern. Das Ganze funktioniert nur, wenn die Interrupt-Service-Routinen kurz sind und sich nicht gegenseitig blockieren und behindern. Bei zunehmendem Zeitbedarf und vielen rasch aufeinander folgenden Interrupts sind sorgfältige Überlegungen zum Timing wichtig. Folgen zwei Setzvorgänge im Flaggenregister zu rasch aufeinander und dauert die Nachbearbeitung zu lange, würde ein Ereignis verpasst. Man muss dann andere Wege versuchen, die Lasten bei der Bearbeitung zu verteilen. Man erkennt hier nebenbei die Zentrierung des gesamten Programmablaufes auf die Interrupts. Sie sind die zentrale Steuerung. To the top of that page
©2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/interrupts/int_ablauf.html1/20/2009 7:36:52 PM

Interruptprogrammierung in AVR Assembler

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 Code Zeitbedarf Takte Vorteile Nachteile Registerverbrauch

1

Register

in R15,SREG 2 [...] out SREG,R15 push R0 in R0,SREG [...] 6 out SREG,R0 pop R0 sts $0060,R0 in R0,SREG [...] 6 out SREG,R0 lds R0,$0060

Schnell

2

Stapel

Kein RegisterLahm verbrauch

3

SRAM

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<<bitA ; setze Bearbeitungsflagge A [...] Isr2: [...] sbr rFlag,1<<bitB ; setze Bearbeitungsflagge B [...] Wenn jetzt in der Hauptprogrammschleife die beiden Bits nacheinander abgefragt und Behandlungsroutinen aufgerufen werden, kann entweder bitA oder bitB oder sowohl bitA als auch bitB gesetzt sein. Auf jeden Fall müssen die gesetzten Flaggen so schnell wie möglich wieder auf Null gesetzt werden, denn der nächste Interrupt kann schon bald wieder zuschlagen. Auf keinen Fall dürfen wir beide Flaggen erst nach einer aufwändigen Bearbeitung löschen, weil wir dann vielleicht die nächste Bearbeitungsanforderung verpassen würden. Es lohnt sich dann, mit der in schnellerer Folge auftretenden Anforderung zu beginnen und dann die etwas gemütlicher werkelnde zweite Anforderung zu bearbeiten. Wenn A häufiger ist als B, z.B. so: Loop: sleep ; schlafen legen nop ; Dummy nach Aufwachen sbrc rFlag,bitA ; frage erst bitA ab rcall BearbA ; bearbeite Anforderung A sbrc rFlag,bitB ; frage dann bitB ab rcall BearbB ; bearbeite dann Anforderung B sbrc rFlag,bitA ; frage zur Sicherheit noch mal bitA ab rcall BearbA ; bearbeite weitere Anforderung A, falls nötig rjmp Loop ; gehe wieder schlafen ; BearbA: ; Bearbeite Anforderung A cbr rFlag,1<<bitA ; lösche vor der Bearbeitung die gesetzte Flagge [...] ret ; BearbB: ; Bearbeite Anforderung B cbr rFlag,1<<bitB ; lösche vor der Bearbeitung die gesetzte Flagge [...] ret Man beachte, dass in beiden Fällen immer nur ein Bit im Register rFlag zurückgesetzt werden darf, also auf keinen Fall CLR rFlag, weil es könnten ja in seltenen Fällen auch beide gleichzeitig gesetzt worden sein. Ist eine der Behandlungsroutinen ein längliches Monster, z.B. der Update einer LCD-Anzeige, 16 Schreibvorgänge im EEPROM oder das Leeren eines Pufferspeichers mit 80 Zeichen im SRAM, dann kann das zum Verhungern der jeweils anderen Bearbeitungsroutine führen. In diesen Fällen ist Obacht geboten. Dann muss man eben das EEPROM auch interrupt-gesteuert befüllen (mit dem EE_RDY-Interrupt), auch wenn das einigen Programmieraufwand mehr erfordert. Oder sich eine andere Lösung ausdenken. Betrachten Sie die genannten Konflikte der Bearbeitung von Signalen als anspruchsvolle Denksportaufgabe, dann haben Sie den richtigen Ansatz. Die oben beschriebene Zugriffssteuerung über eine InterruptBlockage mit CLI/SEI ist auf jeden Fall dann zwingend, wenn ein Doppelregister außerhalb von Interrupt-Service-Routinen auf einen neuen Wert gesetzt werden muss und innerhalb von Service-Routinen verwendet wird. Zwischen dem Setzen des einen Bytes des Doppelregisters und des zweiten könnte ein Interrupt zuschlagen und einen völlig verqueren Wert vorfinden. Solche Fehler sind teuflisch, weil es in der Regel korrekt funktioniert und nur alle paar Stunden ein Mal dieser Fall eintritt. So einen eingebauten Bug findet man garantiert nicht, weil er sich so selten in freier Wildbahn in der Schaltung zeigt. Daher sollten folgende Regeln gelten:
q

q

q

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.

To the top of that page
©2009 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/interrupts/int_ressourcen.html1/20/2009 7:36:56 PM

http://www.avr-asm-tutorial.net/avr_de/interrupts/int_ablauf_sm.gif

http://www.avr-asm-tutorial.net/avr_de/interrupts/int_ablauf_sm.gif1/20/2009 7:36:58 PM

http://www.avr-asm-tutorial.net/avr_de/interrupts/int_ablauf2_sm.gif

http://www.avr-asm-tutorial.net/avr_de/interrupts/int_ablauf2_sm.gif1/20/2009 7:37:01 PM

AVR-Hardware-Testroutinen

Pfad: Home => AVR-Übersicht => Hardware

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 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 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. 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! Ähnlich: Sendet die Hexadezimalverschlüsselung der empfangenen Zeichen zurück. Die gleichen Hinweise wie oben treffen zu (siehe Text!)

LCD

LCD

SIO

SIO

SIO

SIO

©2002 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/avr_hard.html1/20/2009 7:37:06 PM

AVR-Hardware-Tutorium, EEPROM-Test

Pfad: Home => AVR-Übersicht => Hardware => EEPROM

; 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 ;

; 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.
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/testeep.html1/20/2009 7:37:08 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/testeep.asm

; 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.

; 8515-Bibliothek laden
.NOLIST .INCLUDE "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: OUT LDI OUT LDI mp,LOW(RAMEND) ;Initiate Stackpointer SPL,mp ; wegen verwendeten Unterprogrammen mp,HIGH(RAMEND) 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
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/testram.html1/20/2009 7:37:11 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/testram.asm

; ************************************************ ; 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 ; ;

; 8515-Bibliothek laden
.NOLIST .INCLUDE "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 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

; Hauptprogramm
main: OUT LDI OUT LDI mp,LOW(RAMEND) ;Initiate Stackpointer SPL,mp ; wegen verwendeten Unterprogrammen mp,HIGH(RAMEND) 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
©2002-2006 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/testlcd.html1/20/2009 7:37:18 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/testlcd.asm

; ************************************************ ; 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

http://www.avr-asm-tutorial.net/avr_de/quellen/lcd_inc.asm

; ******************************************** ; * LCD-Interface routines for multi-purpose * ; * use with the ATMEL STK200 board, Version * ; * 0.1 Beta, (C) 1999 Gerhard Schmidt * ; * Report bugs to info@avr-asm-tutorial.net * ; ******************************************** ; ; 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 ;

; Konstanten
; .EQU fq=4000000 ; Quarzfrequenz .EQU baud=9600 ; Baudrate .EQU bdteiler=(fq/(16*baud))-1 ; Baud-Teiler ;

; Register
; .DEF mpr=R16 ; Universalregister .DEF nc=R17 ; Zähler .DEF c=R18 ; Zeichen ;

;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

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/testsio.html1/20/2009 7:37:23 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/testsio.asm

; 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 "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Konstanten ; .EQU fq=4000000 ; Quarzfrequenz .EQU baud=9600 ; Baudrate .EQU bdteiler=(fq/(16*baud))-1 ; Baud-Teiler ; ; Register ; .DEF mpr=R16 ; Universalregister .DEF nc=R17 ; Zähler .DEF c=R18 ; Zeichen ; ;Reset-/Interrupt-Vektoren rjmp main ; main: ldi mpr,bdteiler ; Baudgenerator out UBRR,mpr ; Teiler setzen ldi mpr,0b00011000 ; Enable TX und RX out 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

http://www.avr-asm-tutorial.net/avr_de/quellen/testsio.asm1/20/2009 7:37:24 PM

http://www.avr-asm-tutorial.net/avr_de/TestSio.Txt

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 ;

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/siohex.html1/20/2009 7:37:28 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/siohex.asm

; ; 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.

Vorgehen
Bei blutigen Anfängern: Die Beispiele nacheinander in den Editor laden und die kommentierten Befehle lesen, die einzelnen Programmschritte verstehen, den Quelltext assemblieren und auf dem Board laufen lassen. Fortgeschrittene: Den entsprechenden Quelltext für die Komponente heraussuchen und die komponentenspezifischen Befehle verstehen, eventuell für eigene Zwecke anpassen.
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/avr_allg.html1/20/2009 7:37:33 PM

Quellcode LCD-Include-Routine

Pfad: Home => AVR-Übersicht

Assembler-Quellcode der LCD-IncludeRoutinen für das STK200
; *********************************************** ; * LCD-Interface routines for multi-purpose * ; * use with the ATMEL STK200 board, Version * ; * 0.1 Beta, (C) 1999 Gerhard Schmidt * ; * Report bugs to info!at!avr-asm-tutorial.net * ; *********************************************** ; ; 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
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/lcd_inc.html1/20/2009 7:37:34 PM

Include routine for LCD displays on the ATMEL STK200 board

Pfad: Home => AVR-Übersicht

Assembler-Quellcode der LCD-IncludeDemonstration für das STK200
; ***************************************************** ; * 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 info!at!avr-asm-tutorial.net * ; ***************************************************** ; .NOLIST .INCLUDE "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
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/lcd_test.html1/20/2009 7:37:36 PM

AVR-Tutorial

Pfad: Home => AVR-Übersicht => SIO-Spezial

Tutorial für das Erlernen der Assemblersprache von AVR-Einchip-Prozessoren AT90Sxxxx von ATMEL anhand geeigneter praktischer Beispiele.

STK board über die SIO an PC anschliessen

Anmerkung: Die Dateien testsint.asm, sioint.asm und bcdmath.asm enthielten einen ernsten Fehler (dauerhafte Interrupts der SIO). Da die Routine sioint.asm externes SRAM erforderlich macht und daher am STK500 ohnehin nicht funktioniert, wurde dieser Quellcode vorläufig aus dem Quellenverzeichnis genommen. Sorry, manche Fehler merkt man erst nach vielen Monaten.
©2002 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/avr_sio.html1/20/2009 7:37:39 PM

<A HREF="beginner/werkzeuge.html#assembler">Assembler</A> Quellcode des AVR Keyboard-Tests

Pfad: Home => AVR-Übersicht => Hardware => Keyboard-Benutzung

Test eines Keyboards an Port B
; Test des Keyboards ; ; Liest das Keyboard am Port B und zeigt die gedrückte Taste auf den LEDs ; in hexadezimaler Form aus. ; ; Keyboard ist an Port B angeschlossen: ; 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 ; ; 8515-Definitionen .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Register .def mpko=R15 ; Alter Tastenstand .def mpr=R16 ; Multifunktionsregister .def mpk=R25 ; Multifunktionsregister für Keyboard-Interrupt ; ; RAM-Adressen .equ taste=$0060 ; Erste Ramadresse, Hier kommt die Taste hin ; ; Reset-/Interruptvektoren 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 ; Hauptprogramm main: LDI mpr,HIGH(RAMEND) ; Stack Pointer Init wegen Interrupts OUT SPH,mpr LDI mpr,LOW(RAMEND) OUT SPL,mpr ; General control register CLR mpr ; kein SRAM, kein Wait, kein Sleep-Mode, OUT MCUCR,mpr ; Ext.Int egal ; Port B ist Output und Tastatur-Input LDI mpr,0x70 ; alles auf Output OUT DDRB,mpr LDI mpr,0x00 ; alle Lampen an OUT PORTB,mpr STS Taste,mpr ; ; Timer/Counter 0 initiieren LDI mpr,$00 ; Prescaler = 256 OUT TCCR0,mpr ; Timer 1 initiieren LDI mpr,0b00000000 ; Disable Timer Output und 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-Wert in Compare-Register A OUT OCR1AH,mpr ; High Byte zuerst LDI mpr,LOW(625) OUT OCR1AL,mpr ; Low Byte zuletzt LDI mpr,0xFF ; No Interrupt on Compare B OUT OCR1BH,mpr ; High Byte zuerst OUT OCR1BL,mpr ; Low Byte zuletzt ; Interrupts starten CLR mpr ; External interrupts disable OUT GIMSK,mpr ; an 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 ; Interrupts zulassen ; Unendlicher Loop, alles Interrupt-gesteuert loop: RJMP loop ; Interrupt Rountine bei TC1 Compare Match B tc1ci: IN mpk,SREG ; Status-Register retten PUSH mpk LDI mpk,0b11110000 ; Oberes Nibble Ausgabe, Unteres Eingabe OUT DDRB,mpk ; an Port B LDI mpk,0x0F ; Oberes Nibble gibt Null aus, unteres setzt Pullups OUT PORTB,mpk IN mpk,PINB ; Lese Ergebnis von Tastatur CP mpk,mpko ; Vergleiche mit altem Stand BREQ tc1cir ; Keine Änderung, Rückkehr MOV mpko,mpk ; Kopiere neuen Stand in alten Stand STS taste,mpk ; Neuer LED-Stand tc1cir: LDI mpk,0xFF ; Port B wieder auf Ausgabe OUT DDRB,mpr LDS mpk,taste ; gedrückte Taste auf LEDs ausgeben OUT PORTB,mpk POP mpk ; Rückkehr vom Interrupt OUT SREG,mpk ; Status-Register wieder herstellen 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
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/testkbd.html1/20/2009 7:37:41 PM

AVR-Anwendungen

Pfad: Home => AVR-Übersicht => Anwendungen

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 ASMFormat Kurzbeschreibung Link

DCF77Uhr

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.) 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.

GIF PDF

PcmDec

PcmDec

PwgSio

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. Frequenzzähler, misst Frequenzen bis zu 40 MHz und zeigt Frequenz, Umdrehungszahl, Periodendauer, Periodenanteile und eine Spannung an, mit SIO-Interface

Hauptprogramm, Gezippte LCD-Routinen, Quellen Frequenztabelle

RectGen

fcount_m8_v3

fcountV2

fcount

eieruhr

eieruhr

ATtiny2313-Eieruhr zum Angeben, Vielzweck-Geschenk Eieruhr in Dutzenden Varianten zum individuellen Beschenken des gesamten Bekanntenkreises Stepper

schrittmotor

ATtiny13-Schrittmotorsteuerung, Einstellung eines schrittmotor Schrittmotors mit einer Analogspannung von 0..5V, einstellbar bis 65535 Einzelschritte Vollausschlag

©2002-2009 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/apps.html1/20/2009 7:37:44 PM

DCF77 Uhr mit dem AVR-Controller AT90S2313

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

DCF77 Uhr mit dem AVR-Controller AT90S2313

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 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"
©2002 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/dcf77uhr.html (2 of 2)1/20/2009 7:37:53 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/dcf77uhr.asm

; *************************************************************** ; * 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@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 (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

http://www.avr-asm-tutorial.net/avr_de/quellen/dcf77uhr.asm

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

http://www.avr-asm-tutorial.net/avr_gra/clock.gif

http://www.avr-asm-tutorial.net/avr_gra/clock.gif1/20/2009 7:38:04 PM

Experimental 2313 board (C)2001 DG4FAC

18pk 18pk 22µ 2k2 DSR 6 RTS 7 CTS 8 9 2k2 1 2 CD RD 2k2 100 nk 16 13 I1 14 O2 8 I3 7 O4 1 + 22µ 3 4 + 22µ + 15 6 + 2 O1 12 11 I2 O3 9 I4 10 5 100 nk 20 RXD 2 PD0 Loop J1Normal 3 PD1 TXD Data Loop J2Normal 16 PB4 CTS Control RTS 14 PB2 AT90S2313 10 5 4

22µ

XTal 10MHz 1

3 TD DTR 4

MAX232

PD2 6 PD3 7

INT0 INT1

2 3 4

DCF77Clock

5 DS9 (Male)

10 8 6 4 2

9 MISO SCK 7 RESET 5 LED LED 3 MOSI 1

18 PB6 19 PB7 17 PB5 1k RESET 1 1N4148 1k + 1µT 270k

4 x 1N4001 ~ 1 9 ... 15 V ~ 2 330pK + 470µ 35V 7805 330pK + 1µT

To PC 6 RTS 7 CTS 8 9 1 2 3 4 RD TD

Nullmodem-cable DSR CD

To Board 1 RD 2 TD 3

6 7 8 9 RTS CTS

DTR GND

4 5

DB9 5 (Female)

DB9 (Female)

Decoder für Fernsteuersignale, Quellcode

Pfad: Home => AVR-Übersicht => Anwendungen => PCM-Decoder => Quellcode

Assembler Quellcode für den PCM-Dekoder
; ******************************************************************* ; * PCM2PWG-Dekoder fuer AT90S2323, Version 0.4 vom 20.12.2000 * ; * Dekodiert PCM Pulse in pulsweitenmoduliertes Rechtecksignal * ; * (C)2000 by info!at!avr-asm-tutorial.net * ; ******************************************************************* ; ; Momentan eingestellt auf PCM-Signale von 800 bis 2200 µs und 4 MHz ; Taktfrequenz des Prozessors. ; Durch Anpassung der Konstanten unten kann der Code an andere Signale ; angepasst werden. ; ; VCC LED PWG PCM PCM-Ein: 25ms = 40 Hz Frequenzeingang ; Aus Aus Ein positiv gepulste PCM-Signale ; +-8----7----6----5--+ ; | | PWG-Aus: 1.4 ms = 714 Hz Ausgangsfrequenz ; O AT 90 S 2323 | positiv gepulst fuer 0..1,4 ms ; | | ; +-1----2----3----4--+ LED-Aus: Ausgang auf Null, wenn Fehler ; RST XT1 XT2 GND ; ; ********* Eingangssignal PCM-kodiert ************************ ; ; Phase I II III IV V VI VII ; PCM 1---------------------------; Ein \\\\\ / \\\\\\\\\\\\ / ; 0----------------------------------; ; ********* Ausgangssignal Pulsweite-Oszillator *************** ; ; PWG 1 -----------------; Aus / \ / ; 0---------------; Zeit µs |<--0..cCycl->|<-cCycl..0-->| 800 - 2200 µs: ; |<----------cCycl---------->| cCycl = 1400 µs ; ; ************************************************************* ; .NOLIST .INCLUDE "2323def.inc" .LIST ; ; Verwendete Register ; .DEF rmpr = R16 ; Vielzweckregister .DEF rdel = R17 ; Register fuer Verzoergerungsschleifen .DEF rpwc = R18 ; Zaehler fuer den Pulsweiten-Generator .DEF rpws = R19 ; Aktueller Ausgangswert fuer den PulsweitenGenerator .DEF rpwn = R20 ; Naechster Wert fuer Pulsweiten-Generator .DEF rtry = R21 ; Anzahl der zulaessigen Fehlversuche .DEF rctr = R22 ; Zaehler fuer aktive Schleifen .DEF rerr = R23 ; Anzahl Fehlversuche ; ZL = R30 and ZH = R31 werden fuer lange Zeitschleifen benutzt ; ; IO-Port-Bits ; .EQU pbmode = 0b00000110 ; Port B Port-Modus .EQU bIn = 0 ; Eingangspin 0, positive Polaritaet .EQU bOut = 1 ; Ausgabe fuer Pulsweitengenerator .EQU bFail = 2 ; Ausgang fuer die Fehler-LED, aktiv Null ; ; Konstanten (1 Zyklus = 6 µs = 24 Taktzyklen bei 4 MHz) ; ; Anpassung der Quarzfrequenz an andere Taktfrequenzen des Prozessors ; .EQU cXtal = 4 ; Taktfrequenz in MHz ; ; Anpassung der Gesamtlaenge an andere Eingangssignale ; .EQU cPulseDuration = 25000 ; Gesamtdauer des Eingangsimpulses in µs ; ; Anpassung der Zeiten an andere PCM-Pulsweiten ; .EQU cMinPulse = 800 ; Minimum Pulsdauer in µs .EQU cMaxPulse = 2200 ; Maximum Pulsdauer in µs ; ; Anpassung des Defaultwertes fuer den Pulsweiten-Generator bei ; Fehlsignalen oder Signalverlust, 0%: Ausgang wird Null, 100%: ; Ausgang wird dauerhaft Eins, 50%: Ausgang geht auf 50% Pulsweite ; .EQU cFallBackValue = 50 ; 50% Pulsweite ; ; Die folgenden Werte werden automatisch aus den obigen Angaben ; ausgerechnet. Bitte nur aendern, wenn Du weisst was Du tust! ; .EQU cUnitLength = 24 ; Taktzyklen pro Schleifendurchgang .EQU cCycleLength = cUnitLength/cXtal ; Gesamtschleifendauer in µs .EQU cCycl = cPulseDuration/cCycleLength ; Schleifen in 25 ms .EQU cStep = (cMaxPulse-cMinPulse)/cCycleLength ; Aktive Schleifen .EQU cTolerance = cStep/10; Tolerierte Frueh- und Spaetschleifendauer .EQU cMinSteps = cMinPulse/cCycleLength ; Schleifen bis zum Zaehlbeginn .EQU cMaxSteps = cMaxPulse/cCycleLength ; Schleifen nach Zaehlende .EQU cMin1 = cMinSteps-cTolerance-1 ; Mindestschleifen mit Eingang hoch .EQU cTlp = cTolerance-1 ; Fruehzeitige inaktive Schleifen, toleriert .EQU cTla = cTolerance ; Schleifen nach Ende der aktiven Zaehlzeit .EQU cMin0 = (cCycl-cMaxSteps)*90/100 ; Schleifen mit inaktivem Signal .EQU cFbck = 1 ; Number of false signals to tolerate before default is set .EQU cDflt = cstep*cFallbackValue/100 ; Default-Wert fuer den PWG ; ; Makros fuer den Error-LED-Ausgang (auf Aktiv low eingestellt) ; .MACRO LedErrOn CBI PortB,bFail .ENDM .MACRO LedErrOff SBI PortB,bFail .ENDM ; ; Makros fuer Verzoegerungen ; .MACRO Delay1 ; Einzelschritt NOP .ENDM ; .MACRO Delay2 ; Doppelschritt NOP NOP .ENDM ; .MACRO Delay3 ; Verzoegerung um Vielfache von je 3 Schritten LDI rdel,@0 MD3: DEC rdel BRNE MD3 .ENDM ; .MACRO Delay4 ; Verzoegerung um Vielfache von je 4 Schritten LDI rdel,@0 MD4: DEC rdel NOP BRNE MD4 .ENDM ; .MACRO MacPwm ; Makro fuer den Pulsweiten-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: ;----------; 9 9 9 9 ;=========== .ENDM ; ; Beginn des Hauptprogrammes, initiere Variablen ; Start: LDI rpwc,cDflt ; Zaehler fuer den PWG LDI rpws,cDflt ; Aktueller PWG-Wert LDI rpwn,cDflt ; Naechster PWG-Wert ; Initiiere den Port ; LDI rmpr,pbmode OUT DDRB,rmpr SBI PortB,bIn ; B ; Setze Datenrichtungsregister ; von Port B Schalte Pull-Up-Widerstand am Eingang an

; Starte Phase I : Warte bis der Eingang Null wird ; ; Schalte Fehler ein, bis das Signal korrekt ist ; PH1: MacPwm ; 9 LDI rerr,cFbck; 1 LDI rpwn,cDflt; 1 LedErrOn ; 2 Delay3(3) ; 9 ; ; Setze maximale Zeitdauer fuer die Erkennung eines Null-Signals ; PH1a: LDI ZL,Low(cCycl); 1 LDI ZH,HIGH(cCycl);1 ; --; 24 ; === ; Warte bis der Eingang Null ist oder Zeitueberlauf eintritt ; 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 ; === ; Zeitueberlauf mit Eingang auf Eins ; PH1c: ; (15) Delay4(1) ; 4 ; ; Fehler bei aktivem Eingang, zaehle die Fehler ; PH1d: ;(19 19) DEC rerr ; 1 1 BRNE PH1a ; 2 1 ; ; Zu viele Fehler in Folge, setze Fehlerbedingung ; Delay1 ; 1 RJMP PH1 ; 2 ; --; 24 ; === ; Starte Phase II: Eingang ist Null, warte auf aktives Signal ; PH2: ; (12) Delay3(2) ; 6 Delay2 ; 2 RJMP PH2b ; 2 ; ; Fehler aufgetreten, setze Fehlerbedingung ; PH2a: MacPwm ; 9 LDI rerr,cFbck; 1 LDI rpwn,cDflt; 1 LedErrOn ; 2 Delay3(3) ; 9 ; ; Setze Zaehler fuer Zeitueberlauf bei inaktivem Eingang ; PH2b: ; (22 22) LDI ZL,LOW(cCycl); 1 1 LDI ZH,HIGH(cCycl); 1 1 ; -----; 24 24 ; ====== ; Warte bis der Eingang eins ist oder Zeitueberlauf passiert ; 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 ; === ; Zeitueberlauf bei inaktivem Eingang, zaehle Fehler ; 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: Eingang ist aktiv geworden, warte cMin1 Schleifen, ob stabil ; PH3: ; (12) Delay3(3) ; 9 Delay2 ; 2 LDI rtry,cMin1; 1 ; --; 24 ; === ; Warte bis cMin1 Schleifen mit aktivem Eingang abgelaufen ; 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 ; === ; Fruehes Inaktivierung des Einganges, Fehler bei Eingang Null ; PH3b: ; (12) Delay3(1) ; 3 Delay2 ; 2 RJMP PH2e ; 2 ; ; Phase IV: Eingang ist Eins fuer cMin1 Schleifen, starte Toleranzzeit ; PH4: ;(14) Delay4(2) ; 8 LDI rtry,cTlp ; 1 LDI rctr,cstep; 1 ; --; 24 ; === ; Warte fuer Toleranzzeit oder auf inaktiven Eingang ; 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: Ende der Toleranzzeit, beginne Abwaertszaehlung ; 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: Ende des Abwaertszaehlens, toleriere Nachlaufzeit ; PH6: ; (14) Delay3(3) ; 9 LDI rtry,cTla ; 1 ; --; 24 ; === ; Zeitueberlauf nach cTla Schleifen, beende bei Ueberlauf oder inaktiv ; 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 ; === ; Aktives Signal zu lang, Fehlerbedingung mit aktivem Eingang ; PH6b: ; (14) Delay3(1) ; 3 RJMP PH1d ; 2 ; ; Phase VII: Eingang inaktiv, pruefe auf ausreichende inaktive Zeit ; ; 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 ; === ; Fruehzeitiges Ende der Signalpause, Fehlerbedingung bei inaktivem Eingang ; PH7b: ; (12) Delay3(1) ; 3 Delay2 ; 2 RJMP PH1d ; 2 ; ; Ende der Mindestdauer des inaktiven Signals, setze neuen Messwert ; PH7c: ; (15) MOV rpwn,rctr ; 1 LDI rerr,cFbck; 1 LedErrOff ; 2 Delay1 ; 1 RJMP PH2b ; 2 ; ; Ende des Programmes ;

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/pcm2pwg4.html1/20/2009 7:38:11 PM

Decoder für Fernsteuersignale

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.

Zum Anfang des Dokumentes
©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/remote.html1/20/2009 7:38:21 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/pcm2pwg4.asm

; ******************************************************************* ; * PCM2PWG-Dekoder fuer AT90S2323, Version 0.4 vom 20.12.2000 * ; * Dekodiert PCM Pulse in pulsweitenmoduliertes Rechtecksignal * ; * (C)2000 by info@avr-asm-tutorial.net * ; ******************************************************************* ; ; Momentan eingestellt auf PCM-Signale von 800 bis 2200 µs und 4 MHz ; Taktfrequenz des Prozessors. ; Durch Anpassung der Konstanten unten kann der Code an andere Signale ; angepasst werden. ; ; VCC LED PWG PCM PCM-Ein: 25ms = 40 Hz Frequenzeingang ; Aus Aus Ein positiv gepulste PCM-Signale ; +-8----7----6----5--+ ; | | PWG-Aus: 1.4 ms = 714 Hz Ausgangsfrequenz ; O AT 90 S 2323 | positiv gepulst fuer 0..1,4 ms ; | | ; +-1----2----3----4--+ LED-Aus: Ausgang auf Null, wenn Fehler ; RST XT1 XT2 GND ; ; ********* Eingangssignal PCM-kodiert ************************ ; ; Phase I II III IV V VI VII ; PCM 1---------------------------; Ein \\\\\ / \\\\\\\\\\\\ / ; 0----------------------------------; ; ********* Ausgangssignal Pulsweite-Oszillator *************** ; ; PWG 1 -----------------; Aus / \ / ; 0---------------; Zeit µs |<--0..cCycl->|<-cCycl..0-->| 800 - 2200 µs: ; |<----------cCycl---------->| cCycl = 1400 µs ; ; ************************************************************* ; .NOLIST .INCLUDE "C:\avrtools\appnotes\2323def.inc" .LIST ; ; Verwendete Register ; .DEF rmpr = R16 ; Vielzweckregister .DEF rdel = R17 ; Register fuer Verzoergerungsschleifen .DEF rpwc = R18 ; Zaehler fuer den Pulsweiten-Generator .DEF rpws = R19 ; Aktueller Ausgangswert fuer den Pulsweiten-Generator .DEF rpwn = R20 ; Naechster Wert fuer Pulsweiten-Generator .DEF rtry = R21 ; Anzahl der zulaessigen Fehlversuche .DEF rctr = R22 ; Zaehler fuer aktive Schleifen .DEF rerr = R23 ; Anzahl Fehlversuche ; ZL = R30 and ZH = R31 werden fuer lange Zeitschleifen benutzt ; ; IO-Port-Bits ; .EQU pbmode = 0b00000110 ; Port B Port-Modus .EQU bIn = 0 ; Eingangspin 0, positive Polaritaet .EQU bOut = 1 ; Ausgabe fuer Pulsweitengenerator .EQU bFail = 2 ; Ausgang fuer die Fehler-LED, aktiv Null ; ; Konstanten (1 Zyklus = 6 µs = 24 Taktzyklen bei 4 MHz) ; ; Anpassung der Quarzfrequenz an andere Taktfrequenzen des Prozessors ; .EQU cXtal = 4 ; Taktfrequenz in MHz ; ; Anpassung der Gesamtlaenge an andere Eingangssignale ; .EQU cPulseDuration = 25000 ; Gesamtdauer des Eingangsimpulses in µs ; ; Anpassung der Zeiten an andere PCM-Pulsweiten ; .EQU cMinPulse = 800 ; Minimum Pulsdauer in µs .EQU cMaxPulse = 2200 ; Maximum Pulsdauer in µs ; ; Anpassung des Defaultwertes fuer den Pulsweiten-Generator bei ; Fehlsignalen oder Signalverlust, 0%: Ausgang wird Null, 100%: ; Ausgang wird dauerhaft Eins, 50%: Ausgang geht auf 50% Pulsweite ; .EQU cFallBackValue = 50 ; 50% Pulsweite ; ; Die folgenden Werte werden automatisch aus den obigen Angaben ; ausgerechnet. Bitte nur aendern, wenn Du weisst was Du tust! ; .EQU cUnitLength = 24 ; Taktzyklen pro Schleifendurchgang .EQU cCycleLength = cUnitLength/cXtal ; Gesamtschleifendauer in µs .EQU cCycl = cPulseDuration/cCycleLength ; Schleifen in 25 ms .EQU cStep = (cMaxPulse-cMinPulse)/cCycleLength ; Aktive Schleifen .EQU cTolerance = cStep/10; Tolerierte Frueh- und Spaetschleifendauer .EQU cMinSteps = cMinPulse/cCycleLength ; Schleifen bis zum Zaehlbeginn .EQU cMaxSteps = cMaxPulse/cCycleLength ; Schleifen nach Zaehlende .EQU cMin1 = cMinSteps-cTolerance-1 ; Mindestschleifen mit Eingang hoch .EQU cTlp = cTolerance-1 ; Fruehzeitige inaktive Schleifen, toleriert .EQU cTla = cTolerance ; Schleifen nach Ende der aktiven Zaehlzeit .EQU cMin0 = (cCycl-cMaxSteps)*90/100 ; Schleifen mit inaktivem Signal .EQU cFbck = 1 ; Number of false signals to tolerate before default is set .EQU cDflt = cstep*cFallbackValue/100 ; Default-Wert fuer den PWG ; ; Makros fuer den Error-LED-Ausgang (auf Aktiv low eingestellt) ; .MACRO LedErrOn cbi PortB,bFail .ENDM .MACRO LedErrOff sbi PortB,bFail .ENDM ; ; Makros fuer Verzoegerungen ; .MACRO Delay1 ; Einzelschritt nop .ENDM ; .MACRO Delay2 ; Doppelschritt nop nop .ENDM ; .MACRO Delay3 ; Verzoegerung um Vielfache von je 3 Schritten ldi rdel,@0 MD3: dec rdel brne MD3 .ENDM ; .MACRO Delay4 ; Verzoegerung um Vielfache von je 4 Schritten ldi rdel,@0 MD4: dec rdel nop brne MD4 .ENDM ; .MACRO MacPwm ; Makro fuer den Pulsweiten-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 ; ; Beginn des Hauptprogrammes, initiere Variablen ; Start: ldi rpwc,cDflt ; Zaehler fuer den PWG ldi rpws,cDflt ; Aktueller PWG-Wert ldi rpwn,cDflt ; Naechster PWG-Wert ; Initiiere den Port B ; ldi rmpr,pbmode ; Setze Datenrichtungsregister out DDRB,rmpr ; von Port B sbi PortB,bIn ; Schalte Pull-Up-Widerstand am Eingang an ; Starte Phase I : Warte bis der Eingang Null wird ; ; Schalte Fehler ein, bis das Signal korrekt ist ; PH1: macpwm ; 9 ldi rerr,cFbck; 1 ldi rpwn,cDflt; 1 lederron ; 2 delay3(3) ; 9 ; ; Setze maximale Zeitdauer fuer die Erkennung eines Null-Signals ; PH1a: ldi ZL,Low(cCycl); 1 ldi ZH,HIGH(cCycl);1 ; --; 24 ; === ; Warte bis der Eingang Null ist oder Zeitueberlauf eintritt ; 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 ; === ; Zeitueberlauf mit Eingang auf Eins ; PH1c: ; (15) delay4(1) ; 4 ; ; Fehler bei aktivem Eingang, zaehle die Fehler ; PH1d: ;(19 19) dec rerr ; 1 1 brne PH1a ; 2 1 ; ; Zu viele Fehler in Folge, setze Fehlerbedingung ; delay1 ; 1 rjmp PH1 ; 2 ; --; 24 ; === ; Starte Phase II: Eingang ist Null, warte auf aktives Signal ; PH2: ; (12) delay3(2) ; 6 delay2 ; 2 rjmp PH2b ; 2 ; ; Fehler aufgetreten, setze Fehlerbedingung ; PH2a: macpwm ; 9 ldi rerr,cFbck; 1 ldi rpwn,cDflt; 1 lederron ; 2 delay3(3) ; 9 ; ; Setze Zaehler fuer Zeitueberlauf bei inaktivem Eingang ; PH2b: ; (22 22) ldi ZL,LOW(cCycl); 1 1 ldi ZH,HIGH(cCycl); 1 1 ; -----; 24 24 ; ====== ; Warte bis der Eingang eins ist oder Zeitueberlauf passiert ; 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 ; === ; Zeitueberlauf bei inaktivem Eingang, zaehle Fehler ; 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: Eingang ist aktiv geworden, warte cMin1 Schleifen, ob stabil ; PH3: ; (12) delay3(3) ; 9 delay2 ; 2 ldi rtry,cMin1; 1 ; --; 24 ; === ; Warte bis cMin1 Schleifen mit aktivem Eingang abgelaufen ; 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 ; === ; Fruehes Inaktivierung des Einganges, Fehler bei Eingang Null ; PH3b: ; (12) delay3(1) ; 3 delay2 ; 2 rjmp PH2e ; 2 ; ; Phase IV: Eingang ist Eins fuer cMin1 Schleifen, starte Toleranzzeit ; PH4: ;(14) delay4(2) ; 8 ldi rtry,cTlp ; 1 ldi rctr,cstep; 1 ; --; 24 ; === ; Warte fuer Toleranzzeit oder auf inaktiven Eingang ; 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: Ende der Toleranzzeit, beginne Abwaertszaehlung ; 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: Ende des Abwaertszaehlens, toleriere Nachlaufzeit ; PH6: ; (14) delay3(3) ; 9 ldi rtry,cTla ; 1 ; --; 24 ; === ; Zeitueberlauf nach cTla Schleifen, beende bei Ueberlauf oder inaktiv ; 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 ; === ; Aktives Signal zu lang, Fehlerbedingung mit aktivem Eingang ; PH6b: ; (14) delay3(1) ; 3 rjmp PH1d ; 2 ; ; Phase VII: Eingang inaktiv, pruefe auf ausreichende inaktive Zeit ; ; 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 ; === ; Fruehzeitiges Ende der Signalpause, Fehlerbedingung bei inaktivem Eingang ; PH7b: ; (12) delay3(1) ; 3 delay2 ; 2 rjmp PH1d ; 2 ; ; Ende der Mindestdauer des inaktiven Signals, setze neuen Messwert ; PH7c: ; (15) mov rpwn,rctr ; 1 ldi rerr,cFbck; 1 lederroff ; 2 delay1 ; 1 rjmp PH2b ; 2 ; ; Ende des Programmes ;
http://www.avr-asm-tutorial.net/avr_de/quellen/pcm2pwg4.asm1/20/2009 7:38:24 PM

http://www.avr-asm-tutorial.net/avr_gra/pcm2flow.gif

http://www.avr-asm-tutorial.net/avr_gra/pcm2flow.gif1/20/2009 7:38:27 PM

Program Flowchart PCM-to-Pulse-Width-Generator (C)2000 DG4FAC

Start

Init Pulse Width

Set rErr, Default

Set rErr, Default

Generator values

Set ErrorLedOn

Set ErrorLedOn

Init Port B cMin1 Set rCtr to cStep Set rRtry to cTla

Timeout-Counter

Timeout-Counter

Set rRtry to

Set rRtry to cTlp

Longtime-Counter Z = cMin0

I/O properties

Z = cCycl

Z = cCycl

PWG

PWG

PWG

PWG

PWG

PWG

PWG

=0 bIn =0 =1 =1 =1 bIn bIn bIn

=1

=0

=0

=0 bIn =1

=0

=1 bIn =0

bIn

=1

>0 Z =0 =0 =0 rRtry rRtry rCtr =0

DEC

>0

DEC

>0

DEC

>0

DEC

>0

DEC

>0

DEC rRtry =0

>0

DEC Z =0

Z

=0

>0 rErr =0

DEC

>0

DEC

rErr

Set rpwn to rCtr Set cErr to cFbck Set ErrorLedOff

=0

I

II

III

IV

V

VI

VII

bIn

1

0

Pulse Coded Modulation to Pulse Width Generator
+
8 VCC B2 5 Input PCM-coded signal B0 7 390 LED

+

+
47k

AT90S2323
B1
6

Analog signal output

RES X1 1 2 XTAL

X2 GND 3 4

100k

1µT + 1µT + 22 pK 4 MHz 22 pK
(C)2000 by DG4FAC

http://www.avr-asm-tutorial.net/avr_gra/pcm2pwg4.gif

http://www.avr-asm-tutorial.net/avr_gra/pcm2pwg4.gif1/20/2009 7:38:31 PM

Terminal kontrollierter Rechteckgenerator

Pfad: Home => AVR-Überblick => Anwendungen => Signal generator => Quellcode

Quellcode für den Pulsweiten-Generator
; ***************************************************************** ; * Pulsweiten-Generator, programmierbar ueber die SIO 9k6 8N1 * ; * Eingabe der Pulslaenge und der aktiven Dauer in µs per * * ; * Terminal. Ausgabe der Pulse an Port D, Bit 2 des STK 200 * ; * Geschrieben fuer das STK200 board und AT90S8515, anpassbar ; * an AT90S2313 oder aehnliche Chips mit SIO * ; * (C)2000 by info!at!avr-asm-tutorial.net, Bugs sind willkommen * ; ***************************************************************** ; .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Used registers ; .DEF rlpm=R0; Benutzt fuer LPM Befehle .DEF rchar=R1; Zeichenpuffer SIO Kommunikation .DEF rilo=R2; Low byte bei Zahleneingabe, Zaehler fuer aktiv high .DEF rihi=R3; High byte bei Zahleneingabe, Zaehler fuer aktiv high .DEF rjlo=R4; Low byte bei Multiplikation/inaktive phase .DEF rjhi=R5; High byte bei Multiplikation/inactive phase ; .DEF rmpr=R16; Vielzweckregister, byte/word .DEF rcl=R18; Zykluszeit, word .DEF rch=R19 .DEF ral=R20; Zeitraum fuer aktiv high, word .DEF rah=R21 ; X=R26/27: Zaehler fuer for Aktiv high ; Y=R28/29: Zaehler fuer inaktiv low ; Z=R30/31: Pointer fuer Lesen aus dem Programmspeicher ; ; Konstanten ; .EQU OutPort=PortD; Ausgabeport fuer die Signale .EQU DataDir=DDRD; Datenrichtungsregister des aktiven Ports .EQU ActivePin=2; Gewuenschter Ausgabeanschluss .EQU ModeControl=0b00000100; Mode Kontrollwort fuer Port .EQU cDefCyc=25000; Default Wert fuer Zyklusdauer in us .EQU cDefAct=2000; Default Wert fuer Aktiv High in us .EQU fq=4000000; Quarzfrequenz auf dem Board in Hz .EQU baud=9600; Baudrate fuer SIO Kommunikation .EQU bddiv=(fq/(16*baud))-1; Baudratenteiler .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 ; ; Makro Pruefe Eingabewert auf Default und kopiere in Register ; .MACRO Default MOV @0,rilo; Kopiere den Eingabewert in das Registerpaar MOV @1,rihi MOV rmpr,rilo; Teste ob input Null ist OR rmpr,rihi BRNE nodef; Nich Null, setze Default nicht LDI @0,LOW(@2); Setze default Wert LDI @1,HIGH(@2) nodef: .ENDM ; ; Code segment startet hier ; .CSEG ; ; Reset- und Interrupt-Vektoren, werden hier nicht benutzt ; 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 fuer String aussenden ; TxStr: SBIS USR,UDRE; Warte bis Sendepuffer leer ist RJMP TxStr LPM; Lese naechsten Buchstaben aus Programmspeicher AND rlpm,rlpm; NUL = Ende des Strings BRNE txsend RET txsend: LPM; Lese den Buchstaben noch einmal OUT UDR,rlpm; Sende Buchstaben ADIW ZL,1; Zeige auf naechstes Byte im Speicher RJMP TxStr ; ; Subroutine fuer den Zahlenempfang (word, 0..65535) ; RxWord: CLR rilo; Leere Puffer CLR rihi rxw1: SBIS USR,RXC; Teste ob Buchstabe empfangen RJMP rxw1; Kein Buchstabe vorhanden, wiederhole IN rmpr,UDR; Hole das Zeichen von der SIO OUT UDR,rmpr; Echo zurueck an das Terminal CPI rmpr,ccr; Return char = Ende der Eingabe BREQ rxwx SUBI rmpr,'0'; Subtrahiere 48 BRCS rxwerr; Keine Dezimalzahl, zurueck mit Carry gesetzt CPI rmpr,10; Ziffer >9? BRCS rxwok; keine Dezimalzahl rxwerr: LDI ZL,LOW(2*ErrorStr); Sende Error String LDI ZH,HIGH(2*ErrorStr) RCALL TxStr SEC; Setze Carry, keine zulaessige Zahl RET rxwok: MOV rjlo,rilo; Kopie des word fuer Multiplikaton MOV rjhi,rihi LSL rilo; Multipliziere mit 2 = 2* ROL rihi BRCS rxwerr; Ueberlauf, zurueck mit Carry LSL rilo; Multipliziere noch mal mit 2, = 4* ROL rihi BRCS rxwerr; Ueberlauf ADD rilo,rjlo; Addiere Kopie, = 5* ADC rihi,rjhi BRCS rxwerr; Ueberlauf LSL rilo; Multipliziere mit 2, = 10* ROL rihi BRCS rxwerr; Ueberlauf CLR rjhi; Addiere die Dezimalzahl ADD rilo,rmpr ADC rihi,rjhi BRCS rxwerr; Ueberlauf RJMP rxw1; Warte auf naechstes Zeichen rxwx: SBIS USR,UDRE; Warte bis Sendepuffer leer RJMP rxwx LDI rmpr,clf; Sende zusaetzlichen Zeilenvorschub OUT UDR,rmpr CLC; Clear carry, keine Fehler RET ; ; Start des Programmes ; Start: ; ; Benutze den Stack fuer Unterprogramme ; LDI rmpr,HIGH(RAMEND); Stack auf hoechste RAM Adresse OUT SPH,rmpr LDI rmpr,LOW(RAMEND) OUT SPL,rmpr ; ; Initiieren Port output ; LDI rmpr,ModeControl; Setze output Modus OUT DataDir,rmpr; an Datenrichtungsregister ; ; Initiiere SIO Kommunikation ; LDI rmpr,bddiv; Setze Baudrate OUT UBRR,rmpr LDI rmpr,0b00011000; Enable von TX und RX OUT UCR,rmpr ; ; Sende Hello Sequenz ; hello: LDI ZH,HIGH(2*InitStr); Point Z auf String LDI ZL,LOW(2*InitStr) RCALL TxStr ; ; Hole Wert fuer die Gesamtdauer ; getcycle: LDI ZH,HIGH(2*CycleStr); Point Z auf String LDI ZL,LOW(2*CycleStr) RCALL TxStr RCALL RxWord BRCS getcycle; Wiederhole, wenn Fehler Default rcl,rch,cDefCyc ; ; Hole Wert fuer die aktive Dauer ; getactive: LDI ZH,HIGH(2*ActiveStr); Point Z auf String LDI ZL,LOW(2*ActiveStr) RCALL TxStr RCALL RxWord BRCS getactive; Wiederhole, wenn Fehler Default ral,rah,cDefAct ; ; Berechne Zaehlerwert fuer die aktive Dauer ; MOV XL,ral; Berechne aktive Zeit MOV XH,rah SBIW XL,5; mindestens 4 Zyklen BRCS getcycle; ungueltige aktive Dauer ADIW XL,1; Mindestens ein Zyklus erforderlich MOV rilo,XL MOV rihi,XH ; ; Berechne Dauer der inaktiven Zeit ; MOV YL,rcl; Berechne inaktive Zeit MOV YH,rch SUB YL,XL; Subtrahiere Aktive Zeit SBC YH,XH BRCS getcycle; Aktive Zeit kuerzer als Gesamtzeit SBIW YL,5; Subtrahiere Schleifenverzoegerung BRCS getcycle; Weniger als drei Schleifendurchgaenge geht nicht ADIW YL,1; minimum 1 loop MOV rjlo,YL MOV rjhi,YH LDI ZH,HIGH(2*WaitStr); Gib ok-String aus LDI ZL,LOW(2*WaitStr) RCALL TxStr ; ; Zaehlen beginnt hier, pruefe ob Zeichen auf SIO ; ctloop: SBI OutPort,ActivePin; Starte aktive Phase ActLoop: SBIW XL,1; 0.5 µs BRNE ActLoop; 0.5 µs SBIC USR,RXC; Teste ob SIO RX leer RJMP getcycle; Hole Eingabe von SIO CBI Outport,ActivePin; Starte inaktive phase InactLoop: SBIW YL,1; 0.5 µs BRNE InactLoop; 0.5 µs MOV XL,rilo; Setze Zaehlerstand neu MOV XH,rihi MOV YL,rjlo MOV YH,rjhi NOP NOP RJMP ctloop; starte von vorne ; ; Text Strings fuer zum Senden, ANSI kodiert! ; ErrorStr: .DB cbel,ccr,clf,cesc,'[','3','3','m' .DB "Fehler bei der Eingabe! " .DB ccr,clf,cnul,cnul ActiveStr: .DB cesc,'[','3','2','m','*' .DB "Gib inaktive Zeit ein (default = 2,000):" .DB cesc,'[','3','1','m',' ' .DB cnul,cnul CycleStr: .DB cesc,'[','3','2','m','*' .DB "Gib Zykluszeit ein (default = 25,000):" .DB cesc,'[','3','1','m',' ' .DB cnul,cnul WaitStr: .DB cesc,'[','3','5','m','B' .DB "etrieb auf dem Port." .DB ccr,clf,cesc,'[','3','7','m','E' .DB "ingabe einer neuen Zyklusdauer stoppt den Generator." .DB ccr,clf,cnul,cnul InitStr: .DB cesc,'[','0',59,'1',59,'3','7',59,'4','0','m'; Setze Screenfarben .DB cesc,'[','H',cesc,'[','J' ; ANSI Clear screen .DB ccr,clf,ccr,clf .DB "Hallo Welt! " .DB ccr,clf .DB "Hier ist der Pulsweitengenerator bei der Arbeit!" .DB ccr,clf .DB "Alle Zeiten in Mikrosekunden, im Bereich 5..65535." .DB ccr,clf .DB "Neuer Wert unterbricht den Betrieb bis komplett eingegeben" .DB ccr,clf,cnul,cnul ; Check: .DW check ; ; Ende des code segments ;

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/pwgsio2.html1/20/2009 7:38:37 PM

Terminal kontrollierter Rechteckgenerator

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:
q

q

q

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:
q

q q q

q

q

q

q

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.

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/pwg.html1/20/2009 7:38:42 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/pwgsio2.asm

; ************************************************************** ; * Pulsweiten-Generator, programmierbar ueber die SIO 9k6 8N1 * ; * Eingabe der Pulslaenge und der aktiven Dauer in µs per * ; * Terminal. Ausgabe der Pulse an Port D, Bit 2 des STK 200 * ; * Geschrieben fuer das STK200 board und AT90S8515, anpassbar * ; * an AT90S2313 oder aehnliche Chips mit SIO * ; * (C)2000 by info@avr-asm-tutorial.net, Bugs sind willkommen * ; ************************************************************** ; .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; ; Used registers ; .DEF rlpm=R0; Benutzt fuer LPM Befehle .DEF rchar=R1; Zeichenpuffer SIO Kommunikation .DEF rilo=R2; Low byte bei Zahleneingabe, Zaehler fuer aktiv high .DEF rihi=R3; High byte bei Zahleneingabe, Zaehler fuer aktiv high .DEF rjlo=R4; Low byte bei Multiplikation/inaktive phase .DEF rjhi=R5; High byte bei Multiplikation/inactive phase ; .DEF rmpr=R16; Vielzweckregister, byte/word .DEF rcl=R18; Zykluszeit, word .DEF rch=R19 .DEF ral=R20; Zeitraum fuer aktiv high, word .DEF rah=R21 ; X=R26/27: Zaehler fuer for Aktiv high ; Y=R28/29: Zaehler fuer inaktiv low ; Z=R30/31: Pointer fuer Lesen aus dem Programmspeicher ; ; Konstanten ; .EQU OutPort=PortD; Ausgabeport fuer die Signale .EQU DataDir=DDRD; Datenrichtungsregister des aktiven Ports .EQU ActivePin=2; Gewuenschter Ausgabeanschluss .EQU ModeControl=0b00000100; Mode Kontrollwort fuer Port .EQU cDefCyc=25000; Default Wert fuer Zyklusdauer in us .EQU cDefAct=2000; Default Wert fuer Aktiv High in us .EQU fq=4000000; Quarzfrequenz auf dem Board in Hz .EQU baud=9600; Baudrate fuer SIO Kommunikation .EQU bddiv=(fq/(16*baud))-1; Baudratenteiler .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 ; ; Makro Pruefe Eingabewert auf Default und kopiere in Register ; .MACRO Default mov @0,rilo; Kopiere den Eingabewert in das Registerpaar mov @1,rihi mov rmpr,rilo; Teste ob input Null ist or rmpr,rihi brne nodef; Nich Null, setze Default nicht ldi @0,LOW(@2); Setze default Wert ldi @1,HIGH(@2) nodef: .ENDM ; ; Code segment startet hier ; .CSEG ; ; Reset- und Interrupt-Vektoren, werden hier nicht benutzt ; 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 fuer String aussenden ; TxStr: sbis USR,UDRE; Warte bis Sendepuffer leer ist rjmp TxStr lpm; Lese naechsten Buchstaben aus Programmspeicher and rlpm,rlpm; NUL = Ende des Strings brne txsend ret txsend: lpm; Lese den Buchstaben noch einmal out UDR,rlpm; Sende Buchstaben adiw ZL,1; Zeige auf naechstes Byte im Speicher rjmp TxStr ; ; Subroutine fuer den Zahlenempfang (word, 0..65535) ; RxWord: clr rilo; Leere Puffer clr rihi rxw1: sbis USR,RXC; Teste ob Buchstabe empfangen rjmp rxw1; Kein Buchstabe vorhanden, wiederhole in rmpr,UDR; Hole das Zeichen von der SIO out UDR,rmpr; Echo zurueck an das Terminal cpi rmpr,ccr; Return char = Ende der Eingabe breq rxwx subi rmpr,'0'; Subtrahiere 48 brcs rxwerr; Keine Dezimalzahl, zurueck mit Carry gesetzt cpi rmpr,10; Ziffer >9? brcs rxwok; keine Dezimalzahl rxwerr: ldi ZL,LOW(2*ErrorStr); Sende Error String ldi ZH,HIGH(2*ErrorStr) rcall TxStr sec; Setze Carry, keine zulaessige Zahl ret rxwok: mov rjlo,rilo; Kopie des word fuer Multiplikaton mov rjhi,rihi lsl rilo; Multipliziere mit 2 = 2* rol rihi brcs rxwerr; Ueberlauf, zurueck mit Carry lsl rilo; Multipliziere noch mal mit 2, = 4* rol rihi brcs rxwerr; Ueberlauf add rilo,rjlo; Addiere Kopie, = 5* adc rihi,rjhi brcs rxwerr; Ueberlauf lsl rilo; Multipliziere mit 2, = 10* rol rihi brcs rxwerr; Ueberlauf clr rjhi; Addiere die Dezimalzahl add rilo,rmpr adc rihi,rjhi brcs rxwerr; Ueberlauf rjmp rxw1; Warte auf naechstes Zeichen rxwx: sbis USR,UDRE; Warte bis Sendepuffer leer rjmp rxwx ldi rmpr,clf; Sende zusaetzlichen Zeilenvorschub out UDR,rmpr clc; Clear carry, keine Fehler ret ; ; Start des Programmes ; Start: ; ; Benutze den Stack fuer Unterprogramme ; ldi rmpr,HIGH(RAMEND); Stack auf hoechste RAM Adresse out SPH,rmpr ldi rmpr,LOW(RAMEND) out SPL,rmpr ; ; Initiieren Port output ; ldi rmpr,ModeControl; Setze output Modus out DataDir,rmpr; an Datenrichtungsregister ; ; Initiiere SIO Kommunikation ; ldi rmpr,bddiv; Setze Baudrate out UBRR,rmpr ldi rmpr,0b00011000; Enable von TX und RX out UCR,rmpr ; ; Sende Hello Sequenz ; hello: ldi ZH,HIGH(2*InitStr); Point Z auf String ldi ZL,LOW(2*InitStr) rcall TxStr ; ; Hole Wert fuer die Gesamtdauer ; getcycle: ldi ZH,HIGH(2*CycleStr); Point Z auf String ldi ZL,LOW(2*CycleStr) rcall TxStr rcall RxWord brcs getcycle; Wiederhole, wenn Fehler default rcl,rch,cDefCyc ; ; Hole Wert fuer die aktive Dauer ; getactive: ldi ZH,HIGH(2*ActiveStr); Point Z auf String ldi ZL,LOW(2*ActiveStr) rcall TxStr rcall RxWord brcs getactive; Wiederhole, wenn Fehler default ral,rah,cDefAct ; ; Berechne Zaehlerwert fuer die aktive Dauer ; mov XL,ral; Berechne aktive Zeit mov XH,rah sbiw XL,5; mindestens 4 Zyklen brcs getcycle; ungueltige aktive Dauer adiw XL,1; Mindestens ein Zyklus erforderlich mov rilo,XL mov rihi,XH ; ; Berechne Dauer der inaktiven Zeit ; mov YL,rcl; Berechne inaktive Zeit mov YH,rch sub YL,XL; Subtrahiere Aktive Zeit sbc YH,XH brcs getcycle; Aktive Zeit kuerzer als Gesamtzeit sbiw YL,5; Subtrahiere Schleifenverzoegerung brcs getcycle; Weniger als drei Schleifendurchgaenge geht nicht adiw YL,1; minimum 1 loop mov rjlo,YL mov rjhi,YH ldi ZH,HIGH(2*WaitStr); Gib ok-String aus ldi ZL,LOW(2*WaitStr) rcall TxStr ; ; Zaehlen beginnt hier, pruefe ob Zeichen auf SIO ; ctloop: sbi OutPort,ActivePin; Starte aktive Phase ActLoop: sbiw XL,1; 0.5 µs brne ActLoop; 0.5 µs sbic USR,RXC; Teste ob SIO RX leer rjmp getcycle; Hole Eingabe von SIO cbi Outport,ActivePin; Starte inaktive phase InactLoop: sbiw YL,1; 0.5 µs brne InactLoop; 0.5 µs mov XL,rilo; Setze Zaehlerstand neu mov XH,rihi mov YL,rjlo mov YH,rjhi nop nop rjmp ctloop; starte von vorne ; ; Text Strings fuer zum Senden, ANSI kodiert! ; ErrorStr: .DB cbel,ccr,clf,cesc,'[','3','3','m' .DB "Fehler bei der Eingabe! " .DB ccr,clf,cnul,cnul ActiveStr: .DB cesc,'[','3','2','m','*' .DB "Gib inaktive Zeit ein (default = 2,000):" .DB cesc,'[','3','1','m',' ' .DB cnul,cnul CycleStr: .DB cesc,'[','3','2','m','*' .DB "Gib Zykluszeit ein (default = 25,000):" .DB cesc,'[','3','1','m',' ' .DB cnul,cnul WaitStr: .DB cesc,'[','3','5','m','B' .DB "etrieb auf dem Port." .DB ccr,clf,cesc,'[','3','7','m','E' .DB "ingabe einer neuen Zyklusdauer stoppt den Generator." .DB ccr,clf,cnul,cnul InitStr: .DB cesc,'[','0',59,'1',59,'3','7',59,'4','0','m'; Setze Screenfarben .DB cesc,'[','H',cesc,'[','J' ; ANSI Clear screen .DB ccr,clf,ccr,clf .DB "Hallo Welt! " .DB ccr,clf .DB "Hier ist der Pulsweitengenerator bei der Arbeit!" .DB ccr,clf .DB "Alle Zeiten in Mikrosekunden, im Bereich 5..65535." .DB ccr,clf .DB "Neuer Wert unterbricht den Betrieb bis komplett eingegeben" .DB ccr,clf,cnul,cnul ; Check: .DW check ; ; Ende des code segments ;
http://www.avr-asm-tutorial.net/avr_de/quellen/pwgsio2.asm1/20/2009 7:38:45 PM

Rechteckgenerator ATmega8 - Quellcode Hauptprogramm

Pfad: Home => AVR-Übersicht => Anwendungen => Rechteckgenerator = > Quellcode Hauptprogramm

Rechteckgenerator mit ATmega8 - Quellcode Hauptprogramm

; ; *************************************************** ; * Einstellbarer Rechteckgenerator mit ATmega8 * ; * Frequenz (0.25Hz..8MHz) und PulsWeite (0.00.. * ; * 100.00%) variabel, LCD-Anzeige (waehlbar: ein- * ; * zeilige/mehrzeilige Anzeige, 16..40 Zeichen pro * ; * Zeile), zeigt Frequenz oder Umdrehungsgeschwin- * ; * digkeit sowie Pulsweite in % an * ; * Benoetigt die Dateien "rectgen_m8_table.inc" * ; * und "Lcd8_02WO_rec.inc" * ; * Version 1 vom 21. April 2006 * ; * (C)2006 by info!at!avr-asm-tutorial.net * ; *************************************************** ; .NOLIST .INCLUDE "m8def.inc" .LIST ; ; Debug Informationen ; .EQU dbgOn = 0 ; Debug ein oder aus .IF dbgOn .EQU cAdc0 = 1000 ; ADC0-Wert .EQU cAdc1 = 511 ; ADC1-Wert .EQU cFlg = 0 ;.EQU cFlg = (1<<bTime) ;.EQU cFlg = (1<<bRpm) ;.EQU cFlg = (1<<bPw) .ENDIF .EQU dbghx = 0 ; Debug Hex-Ausgabe auf LCD ; ; Hardware ; ___________ ; __ 10k/ | _ ;+5V O-|__|--|RESET PC5|--O--O O--| Pulseweite anzeigen ; | | _ (nur einzeilige LCD!) ; LCD D0 O--|PD0 PC4|--O--O O--| Signale invertieren ; | | _ ; LCD D1 O--|PD1 PC3|--O--O O--| UPM anzeigen ; | | _ ; LCD D2 O--|PD2 PC2|--O--O O--| Zeit anzeigen ; | | ; LCD D3 O--|PD3 ADC1|--O 0..5V Pulsweiteneinstellung ; | A T | ; LCD D4 O--|PD4 ADC0|--O 0..5V Frequenzeinstellung ; | mega | ; +5V O--|VCC GND|--O GND ; | 8 | 10nF ; GND O--|GND AREF|--O--||--| AREF ; | | ___ 22H/10nF ; XTAL1 O--|XTAL1 AVCC|--O--|___|--O +5V ; | | ; XTAL2 O--|XTAL2 PB5SCK|--O LCD R/W, SCK ; | | ; LCD D5 O--|PD5 PB4MISO|--O LCD RS, MISO ; | | ; LCD D6 O--|PD6 PB3MOSI|--O MOSI ; | | ; LCD D7 O--|PD7 PB2OC1B|--O Ausgang B ; | | ; LCD E O--|PB0 PB1OC1A|--O Ausgang A ; |____________| ; ; ; Timing ; -----; ; TC0: Vorteiler = 1024, Ueberlauf Interrupt, @16MHz: ; 16.384 ms, abwaertszaehler von 30 auf 0, bei 0: ; (491.52 ms) startet ADC-Wandlerzyklus ; ; ADC-Kanaele 1 und 2: Wandlung gestartet mit TC0, ; ADC-Teiler = 128, @16MHz, erster ADC-Complete ; Interrupt nach 200 us, zweiter Interrupt nach ; 104 us, ADC wird abgeschaltet und Komplett-Flagge ; gesetzt nach zwei vollstaendigen Wandlungen ; ; ADC-Komplett-Flagge: liest Wert fuer die Einstellung ; des TC1-CTC (ICR1-CTC) aus Tabelleneintrag von ; ADC0, berechnet COMPA/COMPB-Wert aus dem CTC-Wert ; und ADC1, setzt TC1-ICR1 und COMPA/COMPB-Werte, ; TC1-Vorteiler und TC1-Ausgangspoaritaet ; ; TC1: Schneller PWM mode/WGM=14, Vorteiler = 1..1024 (ab; haengig von gewaehlter Frequenz), ICR1 bestimmt den ; TOP-Wert, COMPA/B bestimmt phasenmodulierte Umkehr ; des Ausgangssignals ; ; ************************************************** ; D E F I N I T I O N E N ; ************************************************** ; ; Konstanten ; .EQU clock = 16000000 ; Taktfrequenz .EQU cDivF5 = $00 ; = clock * 100 (5 Bytes) .EQU cDivF4 = $5F .EQU cDivF3 = $5E .EQU cDivF2 = $10 .EQU cDivF1 = $00 .EQU cDivU5 = $16 ; = clock * 100 * 60 (5 Bytes) .EQU cDivU4 = $5A .EQU cDivU3 = $0B .EQU cDivU2 = $C0 .EQU cDivU1 = $00 .EQU cLcdLw = 24 ; Anzahl Zeichen pro Zeile der LCD .EQU cLcdLn = 2 ; Anzahl Zeilen der LCD ;.EQU cLcdMicro = $E4 ; Mikro-Zeichen der LCD, nicht ok bei L2432 .EQU cLcdMicro = 'u' ; anstelle des Mikro-Zeichens .EQU cEn = 0 ; Englische oder deutsche Version ; ; Abhaengige Konstanten ; .EQU cTMult = (256000000/clock)*100 .IF cEn .EQU c1000s = $2C ; Komma .EQU cDecs = $2E ; Punkt .ELSE .EQU c1000s = $2E ; Punkt .EQU cDecs = $2C ; Komma .ENDIF ; ; Register ; ; benutzt: R0..R13 fuer Berechnungen ; frei: R14 .DEF rSreg = R15 ; Status-Sicherungs-Register .DEF rmp = R16 ; Multipurpose Register ausserhalb Ints .DEF rimp = R17 ; Multipurpose Register innerhalb Ints .DEF rFlg = R18 ; Flaggenregister .EQU bTime = 0 ; Zeit anstelle Frequenz anzeigen .EQU bRpm = 1 ; UPM anstelle Frequenz anzeigen .EQU bInv = 2 ; Invertieren des Ausgangssignals .EQU bAdc0 = 3 ; ADC-Kanal 0 Komplett-Flagge .EQU bAdc1 = 4 ; ADC-Kanal 1 Komplett-Flagge .IF cLcdLn==1 .EQU bPw = 7 ; Anzeige Pulsweite auf Zeile 1 (einzeilige LCD) .ENDIF .DEF rAdc0L = R19 ; LSB letztes ADC0-Ergebnis .DEF rAdc0H = R20 ; dto., MSB .DEF rAdc1L = R21 ; LSB letztes ADC1-Ergebnis .DEF rAdc1H = R22 ; dto., MSB .DEF rAdcC = R23 ; Zaehler fuer verzoegerten ADC Startzyklus ; frei: R24..R25 ; benutzt: X (R27:R26) fuer Berechnungen ; frei: Y (R29:R28) ; benutzt: Z (R31:R30) fuer Berechnungen ; ; Ports ; ; LCD-Port-Anschluesse .EQU pLcdData = PORTD .EQU pLcdCtrl = PORTB .EQU pbLcdE = 0 .EQU pbLcdRs = 4 .EQU pbLcdRw = 5 ; Schalter Port-Anschluesse .EQU pSwtchOut = PORTC .EQU pSwtchIn = PINC .EQU pbTime = 2 .EQU pbRpm = 3 .EQU pbInv = 4 .EQU pbPwm = 5 ; Signalausgaenge .EQU pSignOut = PORTB .EQU pSignDdr = DDRB .EQU pbSignA = 1 .EQU pbSignB = 2 ; ; SRAM Positionen ; .DSEG .ORG $0060 sCtc: ; CTC-Teiler fuer TC1 .BYTE 2 sCmp: ; COMPA/B Pulsweite TC1 .BYTE 2 sPre: ; Vorteiler TC1 .BYTE 1 sMode: ; Modus TC1 .BYTE 1 sLcdL1: ; Zeile 1 der LCD .BYTE cLcdLw .IF cLcdLn>1 sLcdL2: ; Zeile 2 der LCD .BYTE cLcdLw .ENDIF ; ; ************************************************** ; R E S E T - und I N T - V E C T O R E N ; ************************************************** ; ; Code-Segment .CSEG .ORG $0000 ; rjmp main ; Reset Vektor, Sprung zum Beginn reti ; INT0, nicht verwendet reti ; INT1, nicht verwendet reti ; TIMER2COMP, nicht verwendet reti ; TIMER2OVF, nicht verwendet reti ; TIMER1CAPT, nicht verwendet reti ; TIMER1COMPA, nicht verwendet reti ; TIMER1COMPB, nicht verwendet reti ; TIMER1OVF, nicht verwendet rjmp TC0OvflwInt ; TIMER0OVF reti ; SPI, STC, nicht verwendet reti ; USART, RXC, nicht verwendet reti ; USART, UDRE, nicht verwendet reti ; USART, TXC, nicht verwendet rjmp AdcInt ; ADC reti ; EE_RDY, nicht verwendet reti ; ANA_COMP, nicht verwendet reti ; TWI, nicht verwendet reti ; SPM_RDY, nicht verwendet ; ; ************************************************** ; I N T - V E K T O R - R O U T I N E N ; ************************************************** ; ; TC0 Ueberlauf Interrupt, startet ADC-Messzyklus ; TC0OvflwInt: in rSreg,SREG ; sichere Status dec rAdcC ; zaehle Verzoegerungszaehler abwaerts brne TC0OvflwInt1 ldi rAdcC,30 ; Neustart Zaehler ldi rimp,(1<<REFS0) ; ADMUX auf Kanal 0 out ADMUX,rimp ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)| (1<<ADPS1)|(1<<ADPS0) out ADCSRA,rimp ; starte Umwandlung Kanal 0 cbr rFlg,1<<bAdc0 ; setze Kanal-0-Komplett-Flagge TC0OvflwInt1: out SREG,rSreg ; Status wieder herstellen reti ; ; ADC-Fertig-Interrupt, lese ADC-Wert ; AdcInt: in rSreg,SREG ; sichere Status sbrc rFlg,bAdc0 ; Kanal 0 fertig? rjmp AdcInt1 ; ja, starte naechsten Kanal in rAdc0L,ADCL ; lese Kanal 0 in rAdc0H,ADCH ldi rimp,(1<<REFS0)|(1<<MUX0) ; setze Kanal 1 out ADMUX,rimp ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)| (1<<ADPS1)|(1<<ADPS0) out ADCSRA,rimp ; starte Umwandlung Kanal 1 sbr rFlg,1<<bAdc0 ; setze Kanal-0-Komplettflagge out SREG,rSreg ; stelle Status wieder her reti AdcInt1: in rAdc1L,ADCL ; lese Kanal 1 in rAdc1H,ADCH ldi rimp,(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) ; ADC ausschalten out ADCSRA,rimp sbr rFlg,1<<bAdc1 ; setze Kanal-1-Komplettflagge out SREG,rSreg ; stelle Status wieder her reti ; ; ************************************************** ; S U B R O U T I N E N ; ************************************************** ; ; Lese aktuellen CTC- und Vorteiler-Wert und berechnet ; die Rate in R7:R6:R5:R4 ; GetRate: lds rmp,sPre ; lese Vorteiler nach rmp andi rmp,(1<<CS12)|(1<<CS11)|(1<<CS10) breq GetRate2 ; Vorteiler ist Null, setze Carry lds R4,sCtc ; lese Teiler nach R7:R6:R5:R4 lds R5,sCtc+1 clr R6 clr R7 dec rmp breq GetRate1 ; Vorteiler = 1 lsl R4 ; Teiler * 2 rol R5 rol R6 rol R7 lsl R4 ; Teiler * 4 rol R5 rol R6 rol R7 lsl R4 ; Teiler * 8 rol R5 rol R6 rol R7 dec rmp breq GetRate1 ; Teiler = 8 lsl R4 ; Teiler * 16 rol R5 rol R6 rol R7 lsl R4 ; Teiler * 32 rol R5 rol R6 rol R7 lsl R4 ; Teiler * 64 rol R5 rol R6 rol R7 dec rmp breq GetRate1 ; Vorteiler = 64 lsl R4 ; Teiler * 128 rol R5 rol R6 rol R7 lsl R4 ; Teiler * 256 rol R5 rol R6 rol R7 dec rmp breq GetRate1 ; Vorteiler = 256 lsl R4 ; Teiler * 512 rol R5 rol R6 rol R7 lsl R4 ; Teiler * 1024 rol R5 rol R6 rol R7 GetRate1: ; Timer ist aktiv, loesche carry clc ret GetRate2: ; Timer ist inaktiv, setze carry sec ret ; ; Berechne Anzeigentext aus aktuellen Timer-Parametern ; Calc: sbrc rFlg,bTime ; Zeit anzeigen? rjmp CalcTime ; ja, berechne Zeit clr R8 ; loesche Divisionsergebnis in R11:R10:R9:R8 inc R8 ; setze letztes Bit als Zaehler clr R9 clr R10 clr R11 rcall GetRate brcs L1Lcd ; Timer ist aus, Frequenz = 0 clr R3 ; setze Teiler in R3:R2:R1:R0:ZH:ZL:XH:XL clr R2 clr R1 sbrc rFlg,bRpm ; UPM anzeigen? rjmp Calc1 ; ja ldi rmp,cDivF5 ; Frequenz anzeigen mov R0,rmp ldi ZH,cDivF4 ldi ZL,cDivF3 ldi XH,cDivF2 ldi XL,cDivF1 rjmp Calc2 Calc1: ldi rmp,cDivU5 ; zeige UPM an mov R0,rmp ldi ZH,cDivU4 ldi ZL,cDivU3 ldi XH,cDivU2 ldi XL,cDivU1 Calc2: lsl XL ; * 2 rol XH rol ZL rol ZH rol R0 rol R1 rol R2 rol R3 cp R0,R4 ; vergleichen cpc R1,R5 cpc R2,R6 cpc R3,R7 brcs Calc3 sub R0,R4 ; subtrahieren sbc R1,R5 sbc R2,R6 sbc R3,R7 sec rjmp Calc4 Calc3: clc Calc4: rol R8 ; rolle Ergebnisbit ein rol R9 rol R10 rol R11 brcc Calc2 ; wiederhole 32 mal lsl ZH ; aufrunden? rol R0 rol R1 rol R2 rol R3 cp R0,R4 cpc R1,R5 cpc R2,R6 cpc R3,R7 brcs Calc5 ldi rmp,1 add R8,rmp ldi rmp,0 adc R9,rmp adc R10,rmp adc R11,rmp Calc5: ; ; Ergebnis in R11:R10:R9:R8 auf Zeile 1 ; L1Lcd: clr R0 ; R0 ist Flagge fuer fuehrende Nullen ldi ZH,HIGH(2*DecTab32) ; Zeiger auf Dezimaltabelle ldi ZL,LOW(2*DecTab32) ldi XH,HIGH(sLcdL1) ; X zeigt auf LCD-Zeile ldi XL,LOW(sLcdL1) ldi rmp,'F' ; zeige Frequenz an sbrc rFlg,bRpm ldi rmp,'R' ; zeige UPM an sbrc rFlg,bTime ldi rmp,'T' ; zeige Zeit an st X+,rmp ; setze erstes Zeichen .IF cLcdLw>16 ldi rmp,' ' ; fuege ein Leerzeichen zu st X+,rmp ldi rmp,'=' ; fuege = zu st X+,rmp rcall Dec32 ; schreibe 100 Millionen-Stelle .ELSE rcall Dec32 ; schreibe 10 Millionen-Stelle ld rmp,-X ; lese letzte Zeichenposition cpi rmp,' ' ; Leerzeichen? brne L1Lcd00 ldi rmp,'=' ; fuege = an st X,rmp L1Lcd00: adiw XL,1 .ENDIF rcall Dec32 ; schreibe Millionen ldi rmp,c1000s tst R0 brne L1Lcd0a ldi rmp,' ' L1Lcd0a: st X+,rmp rcall Dec32 ; schreibe 100,000'er rcall Dec32 ; schreibe 10,000'er rcall Dec32 ; schreibe 1,000'er ldi rmp,c1000s tst R0 brne L1Lcd0b ldi rmp,' ' L1Lcd0b: st X+,rmp rcall Dec32 ; schreibe 100'er rcall Dec32 ; schreibe 10'er inc R0 ; schreibe fuehrende Nullen rcall Dec32 ; schreibe 1'er tst R8 ; zeige Nachkommastellen an? breq L1Lcd1 ldi rmp,cDecs ; setze Dezimalzeichen st X+,rmp rcall Dec32 ; schreibe 0.1'er ldi rmp,'0' add rmp,R8 ; schreibe 0.01'er st X+,rmp rjmp L1Lcd2 L1Lcd1: ldi rmp,' ' ; leere Dezimalstellen st X+,rmp st X+,rmp st X+,rmp L1Lcd2: .IF cLcdLw>16 ldi rmp,' ' ; fuege Leerzeichen hinzu st X+,rmp .ENDIF sbrc rFlg,bTime ; Zeit anzeigen? rjmp L1Lcd4 ; ja sbrc rFlg,bRpm ; UPM anzeigen? rjmp L1Lcd3 ; ja ldi rmp,'H' ; Frequenz anzeigen st X+,rmp ldi rmp,'z' st X+,rmp rjmp L1Lcd5 L1Lcd3: ldi rmp,'p' ; zeige Upm an 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 ; ; Berechne naechste Dezimalstelle einer 32-Bit-Zahl ; Dec32: lpm R4,Z+ ; lese naechste Dezimalzahl lpm R5,Z+ lpm R6,Z+ lpm R7,Z+ clr rmp Dec32a: cp R8,R4 ; vergleiche die Zahl mit der Dezimalzahl 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 ; erhoehe Ergebnis rjmp Dec32a ; wiederhole Dec32b: add R0,rmp ; addiere zur fuehrenden Nullen-Flagge subi rmp,-'0' ; addiere ASCII Null tst R0 ; fuehrende Null? brne Dec32c ldi rmp,' ' ; setze fuehrende Null Dec32c: st X+,rmp ; speichere Zeichen im SRAM ret ; ; 32 Bit Dezimaltabelle fuer Zahlumwandlung ; DecTab32: .DB $00,$CA,$9A,$3B ; 1000 mio .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 ; ; Berechne und zeige Zeit an ; CalcTime: rcall GetRate ; Lese Rate nach R7:R6:R5:R4 brcc CalcTime1 ; Timer ist aktiv, berechne Zeit rjmp L1Lcd ; Timer ist inaktiv, zeige 0 an CalcTime1: mov R2,R4 ; kopiere Multiplikator nach R7:R:R5:R4:R3:R2 mov R3,R5 mov R4,R6 mov R5,R7 clr R6 clr R7 clr R8 ; loesche Ergebnis in R13:R12:R11:R10:R9:R8 clr R9 clr R10 clr R11 clr R12 clr R13 ldi rmp,HIGH(cTMult) ; lade Multiplikator in R1:R0 mov R1,rmp ldi rmp,LOW(cTMult) mov R0,rmp CalcTime2: lsr R1 ; schiebe naechstes Bit des Multiplikators in Carry ror R0 brcc CalcTime3 add R8,R2 ; addiere den Multiplikator adc R9,R3 adc R10,R4 adc R11,R5 adc R12,R6 adc R13,R7 CalcTime3: lsl R2 ; Multipliziere Multiplikator mit 2 rol R3 rol R4 rol R5 rol R6 rol R7 tst R0 ; pruefe LSB auf Ende der Multiplikation brne CalcTime2 tst R1 ; pruefe MSB auf Ende der Multiplikation brne CalcTime2 mov rmp,R8 ; niedrigstes Byte fuer Runden mov R8,R9 ; schiebe Ergebnis rechts = dividiere mit 256 mov R9,R10 mov R10,R11 mov R11,R12 tst R13 ; pruefe auf Ueberlauf breq CalcTime5 CalcTime4: ldi rmp,0xFF ; Ueberlauf, setze auf groesste Zahl mov R8,rmp mov R9,rmp mov R10,rmp mov R11,rmp rjmp L1Lcd ; und zeige an CalcTime5: cpi rmp,0x80 brcs CalcTime6 ldi rmp,0x01 ; runde auf add R8,rmp ldi rmp,0x00 adc R9,rmp adc R10,rmp adc R11,rmp brcs CalcTime4 CalcTime6: rjmp L1Lcd ; ; Berechne Pulsweite, zuerst Pulsdauer mit 10000 multiplizieren, ; dann das Ergebnis durch die gesamte Pulsdauer dividieren ; CalcPw: lds R7,sCmp ; Lese aktive Pulsdauer nach R10:R9:R8:R7 lds R8,sCmp+1 clr R9 clr R10 clr R0 ; loesche Multiplikationsergebnis in R6:R5:R4:R2:R1: R0 clr R1 clr R2 clr R3 clr R4 clr R5 clr R6 ldi rmp,HIGH(10000) ; setze R12:R11 auf 10000 mov R12,rmp ldi rmp,LOW(10000) mov R11,rmp CalcPw1: lsr R12 ; schiebe naechstes Bit rechts in Carry ror R11 brcc CalcPw2 ; Null herausgeschoben add R0,R7 ; addiere den Multiplikator adc R1,R8 adc R2,R9 adc R3,R10 CalcPw2: lsl R7 ; multipliziere mit 2 rol R8 rol R9 rol R10 tst R11 ; pruefe Ende der Multiplikation brne CalcPw1 ; weitermachen tst R12 brne CalcPw1 ; weitermachen lds R7,sCtc ; lese CTC-Wert nach R9:R8:R7 lds R8,sCtc+1 clr R9 clr R10 ; loesche Divisionsergebnis inc R10 clr R11 clr R12 clr R13 CalcPw3: lsl R0 ; schiebe links rol R1 rol R2 rol R3 rol R4 rol R5 rol R6 cp R4,R7 ; vergleiche mit Teiler cpc R5,R8 cpc R6,R9 brcs CalcPw4 ; nicht sutrahieren sub R4,R7 sbc R5,R8 sbc R6,R9 sec rjmp CalcPw5 ; schiebe eine 1 hinein CalcPw4: clc ; schiebe eine 0 hinein CalcPw5: rol R10 ; schiebe in Ergebnis hinein rol R11 rol R12 rol R13 brcc CalcPw3 lsl R3 ; runde Ergebnis 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 ; schreibe 100'er rcall Dec32 ; schreibe 10'er inc R0 rcall Dec32 ; schreibe 1'er ldi rmp,cDecs ; schreibe Dezimaltrenner st X+,rmp rcall Dec32 ; schreibe 0.1'er ldi rmp,'0' ; schreibe 0.01'er 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 ; ; ************************************************** ; E R N E U E R E T I M E R W E R T E ; ************************************************** ; ; Wandle ADC-Werte um und setze Timer ; Convert: ldi ZH,HIGH(2*Datatable) ldi ZL,LOW(2*Datatable) add ZL,rAdc0L ; addiere ADC0-Wert adc ZH,rAdc0H add ZL,rAdc0L ; addiere ADC0-Wert erneut adc ZH,rAdc0H lpm R0,Z+ ; lese Tabellenwert lpm R1,Z sts sCtc,R0 ; kopiere ins SRAM sts sCtc+1,R1 clr R2 ; loesche Ergebnis fuer Multiplikation in R3:R2:R1:R0 clr R3 mov R4,rAdc1L ; kopiere ADC1-Wert nach R5:R4 mov R5,rAdc1H clr R9 ; loesche Ergebnis in R9:R8:R7:R6 clr R8 clr R7 clr R6 Convert1: lsr R5 ; schiebe niedrigstes Bit in Carry ror R4 brcc Convert2 ; Bit ist Null, addiere nicht add R6,R0 adc R7,R1 adc R8,R2 adc R9,R3 Convert2: lsl R0 ; schiebe Muliplikator eins links rol R1 rol R2 rol R3 tst R4 ; teste ob Multiplikator Nul ist brne Convert1 tst R5 brne Convert1 lsr R9 ; dividiere Ergebnis durch 2 ror R8 ror R7 lsr R9 ; dividiere Ergebnis durch 4 ror R8 ror R7 brcc Convert3 ldi rmp,1 ; runde auf add R7,rmp ldi rmp,0 adc R8,rmp Convert3: sts sCmp,R7 ; speichere im SRAM sts sCmp+1,R8 mov ZL,rAdc0L ; kopiere ADC0 nach Z mov ZH,rAdc0H ldi XL,LOW(392) ldi XH,HIGH(392) ldi rmp,(1<<WGM13)|(1<<WGM12)|(1<<CS10) cp ZL,XL cpc ZH,XH brcc Convert4 ldi rmp,(1<<WGM13)|(1<<WGM12)|(1<<CS11) ldi XL,LOW(225) ldi XH,HIGH(225) cp ZL,XL cpc ZH,XH brcc Convert4 ldi rmp,(1<<WGM13)|(1<<WGM12)|(1<<CS10)|(1<<CS11) ldi XL,LOW(60) ldi XH,HIGH(60) cp ZL,XL cpc ZH,XH brcc Convert4 ldi rmp,(1<<WGM13)|(1<<WGM12)|(1<<CS12) cpi ZL,3 brcc Convert4 ldi rmp,(1<<WGM13)|(1<<WGM12)|(1<<CS12)|(1<<CS10) Convert4: sts sPre,rmp ; sichere Vorteiler-Kontrollbyte im SRAM ldi rmp,(1<<COM1A1)|(1<<COM1B1)|(1<<WGM11) sbis pSwtchIn,pbInv ; invertiert? ldi rmp,(1<<COM1A1)|(1<<COM1A0)|(1<<COM1B1)|(1<<COM1B0)|

http://www.avr-asm-tutorial.net/avr_de/rechteckgen/rectgen_m8_v1_main.html (1 of 2)1/20/2009 7:38:51 PM

Rechteckgenerator ATmega8 - Quellcode Hauptprogramm

(1<<WGM11) sts sMode,rmp ret ; ; Schreibe die neuen Werte in TC1 ; UpdateTc1: lds rmp,sCmp+1 ; setze Compare-Match-Wert A out OCR1AH,rmp lds rmp,sCmp out OCR1AL,rmp lds rmp,sCmp+1 ; setze Compare-Match-Wert B out OCR1BH,rmp lds rmp,sCmp out OCR1BL,rmp lds rmp,sCtc+1 ; setze CTC-Wert out ICR1H,rmp lds rmp,sCtc out ICR1L,rmp lds rmp,sMode ; setze Modus TC1 out TCCR1A,rmp lds rmp,sPre out TCCR1B,rmp ret ; ; ************************************************** ; L C D - R O U T I N E N ; ************************************************** ; ; Initiieren der LCD ; InitLcd: ldi ZH,HIGH(2*LcdInitText) ldi ZL,LOW(2*LcdInitText) rjmp LcdInit ; ; Zeige Zeile 1 der LCD an ; LcdL1: ldi ZH,HIGH(sLcdL1) ; Z auf Zeile 1 im SRAM ldi ZL,LOW(sLcdL1) rjmp LcdLine1 ; ; Zeige Zeile 2 der LCD an ; .IF cLcdLn>1 LcdL2: ldi ZH,HIGH(sLcdL2) ; Z auf Zeile 2 im SRAM ldi ZL,LOW(sLcdL2) rjmp LcdLine2 .ENDIF ; ; Zeige TC1 Parameter in Hex auf LCD-Zeile 1 ; .IF dbghx LcdHexPar: rcall LcdHome lds ZH,sCtc+1 lds ZL,sCtc rcall Displ4Hex ldi rmp,' ' mov R0,rmp rcall LcdChar lds ZH,sCmp+1 lds ZL,sCmp rcall Displ4Hex ldi rmp,' ' mov R0,rmp rcall LcdChar lds rmp,sMode rcall Displ2Hex ldi rmp,' ' mov R0,rmp rcall LcdChar lds rmp,sPre rcall Displ2Hex ldi rmp,' ' mov R0,rmp rjmp LcdChar Displ4Hex: mov rmp,ZH rcall Displ2Hex mov rmp,ZL Displ2Hex: push rmp swap rmp rcall Displ1Hex pop rmp Displ1Hex: andi rmp,0x0F subi rmp,-'0' cpi rmp,'9'+1 brcs Displ1Hex1 subi rmp,-7 Displ1Hex1: mov R0,rmp rjmp LcdChar .ENDIF ; ; ************************************************** ; H A U P T P R O G R A M M I N I T ; ************************************************** ; Main: ldi rmp,HIGH(RAMEND) ; Stapel init out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ; Schalter-Inputs init ldi rmp,(1<<pbTime)|(1<<pbRpm)|(1<<pbInv)|(1<<pbPwm) out pSwtchOut,rmp ; Init der Signalausgabe-Pins sbi pSignDdr,pbSignA sbi pSignDdr,pbSignB cbi pSignOut,pbSignA sbi pSignOut,pbSignB ; Init Flaggen clr rFlg .IF dbgOn ; debug Berechnungen ldi rmp,HIGH(cAdc0) mov rAdc0H,rmp ldi rmp,LOW(cAdc0) mov rAdc0L,rmp ldi rmp,HIGH(cAdc1) mov rAdc1H,rmp ldi rmp,LOW(cAdc1) mov rAdc1L,rmp ldi rFlg,cFlg rcall Convert rcall UpdateTc1 .IF cLcdLn>1 rcall Calc rcall CalcPw .ELSE sbrc rFlg,bPw rjmp dbgPw rcall Calc rjmp dbgloop dbgPw: rcall CalcPw .ENDIF dbgloop: rjmp dbgloop .ENDIF ; Init der LCD rcall InitLcd ; Init des ADC ldi rmp,(1<<REFS0) ; setze ADMUX auf Kanal 0 out ADMUX,rmp ldi rmp,(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) out ADCSRA,rmp ; starte Umwandlung Kanal 0 ldi rAdcC,1 ; setze Startwert fuer ADC-Verzoegerung ldi rmp,(1<<CS02)|(1<<CS00) ; Vorteiler 1024 out TCCR0,rmp ldi rmp,1<<TOIE0 ; ermoegliche TC0 Ueberlauf Int out TIMSK,rmp sei ; ; ************************************************** ; H A U P T P R O G R A M M S C H L E I F E ; ************************************************** ; Loop: sleep nop sbrs rFlg,bAdc1 ; ADC fertig Flagge gesetzt? rjmp Loop ; nein, schlaf weiter cbr rFlg,1<<bAdc1 ; loesche ADC fertig Flagge .IF cLcdLn == 1 cbr rFlg,(1<<bTime)|(1<<bRpm)|(1<<bPw)|(1<<bInv) sbis pSwtchIn,pbPwm sbr rFlg,1<<bPw .ELSE cbr rFlg,(1<<bTime)|(1<<bRpm)|(1<<bInv) .ENDIF sbis pSwtchIn,pbInv ; Invertiere Signal? sbr rFlg,1<<bInv sbis pSwtchIn,pbTime sbr rFlg,1<<bTime sbis pSwtchIn,pbRpm sbr rFlg,1<<bRpm rcall Convert ; wandle ADC nach TC1-Werte rcall UpdateTc1 .IF cLcdLn>1 rcall Calc ; berechne Frequenz rcall CalcPw ; berechne Pulsweite rcall LcdL2 ; zeige in Zeile 2 an .ELSE sbrc rFlg,bPw ; pruefe of Pulsweite angezeigt rjmp CPw ; ja, berechne Pulsweite rcall Calc ; berechne Frequenz rjmp Loop1 ; und zeige Frequenz an CPw: rcall CalcPw ; berechne Pulsweite .ENDIF Loop1: rcall LcdL1 ; zeige Zeile 1 an .IF dbghx rcall LcdHexPar .ENDIF rjmp Loop ; weiterschlafen ; ; ************************************************** ; L C D - R O U T I N E N E I N L E S E N ; ************************************************** ; .INCLUDE "Lcd8_02WO_rect.inc" ; ; Init-Text der LCD ; LcdInitText: .IF cLcdLn == 1 .IF cLcdLw == 40 .DB "Rechteck- Generator DG4FAC ATmega8 V1.0 " .ELSE .IF cLcdLw == 24 .DB "RechtGenerator M8 V1.0 " .ELSE .IF cLcdLw == 20 .DB "RechtGenerator V1.0 " .ELSE .IF cLcdLw == 16 .DB "RechtGen M8 V1.0" .ELSE .DB "RG M8 V1" .ENDIF .ENDIF .ENDIF .ENDIF .ELSE .IF cLcdLw == 40 .DB "Rechteck- Generator DG4FAC ATmega8 V1.0",0x0D .DB "(C)2006 by info!at!avr-asm-tutorial.net " .ELSE .IF cLcdLw == 24 .DB "RechtGenerator M8 V1.0 ",0x0D .DB "www.avr-asm-tutorial.net" .ELSE .IF cLcdLw == 20 .DB "RechtGenerator V1.0",0x0D .DB "avr-asm-tutorial.net" .ELSE .IF cLcdLw == 16 .DB "RechtGen M8 V1.0",0x0D,"avr-asm-tutorial",0x00 .ELSE .DB "RG M8 V1",0x0D,"avrasm" .ENDIF .ENDIF .ENDIF .ENDIF .ENDIF .DB 0,0 ; ; ************************************************** ; K O N V E R S I O N S T A B E L L E ; ************************************************** ; .INCLUDE "rectgen_m8_table.inc" ;
©2006 by http://www.avr-asm-tutorial.net

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:

q q q q q q q q q q q

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

0. Inhalt
q

q

q

q

1. Hardware r 1.1 Prozessorteil r 1.2 ISP-Interface r 1.3 LCD-Anzeige r 1.4 Externe Anschlässe s 1.4.1 Einstellpotis s 1.4.2 Schalter s 1.4.3 Ausgangsbuchsen r 1.5 Netzteil r 1.6 Aufbauhinweise 2. Bedienung r 2.1 Schalter r 2.2 Ausgangssignale 3. Funktionsweise r 3.1 Prozessorteil r 3.2 LCD-Anzeige r 3.3 ISP-Interface 4. Software r 4.1 Einstellungen vor dem Assemblieren r 4.2 Frequenztabelle r 4.3 Diverse Schalter r 4.4 Kommentierter Quellcode

1 Hardware

D

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:
q q q

CKOPT = 0 CLKSEL3..CKSEL0 = 1111 SUT1..SUT0 = 11 oder 10

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:
q q q q q

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
q q q

Hauptprogramm LCD-Routinen Frequenztabelle

und als Assembler-Quellcode gezippt zur Verfügung: alles in einem Paket
©2006 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/rechteckgen/rectgen_m8.html1/20/2009 7:39:10 PM

Rechteckgenerator ATmega8 - Quellcode LCD-Routinen

Pfad: Home => AVR-Übersicht => Anwendungen => Rechteckgenerator = > Quellcode LCDRoutinen

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 ;

©2006 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/rechteckgen/rectgen_m8_v1_lcd.html1/20/2009 7:39:17 PM

Rechteckgenerator ATmega8 - Quellcode Frequenztabelle

Pfad: Home => AVR-Übersicht => Anwendungen => Rechteckgenerator = > Quellcode Frequenztabelle

Rechteckgenerator mit ATmega8 - Quellcode Frequenztabelle

; Tabelle fuer die Umwandlung von ADC-Werten zu TC1-Compare-Werten ; TC1-Vorteiler: n<3: 1024, n<60: 256, n<225: 64, n<392: 8, darueber: 1 DataTable: ; 16MHz-Zaehler TC1 .DW 0 ; nAdc= 0, f= aus .DW 62500 ; nAdc= 1, f= 0,25, upm= 15 .DW 31250 ; nAdc= 2, f= 0,5, upm= 30 .DW 62500 ; nAdc= 3, f= 1, upm= 60 .DW 59524 ; nAdc= 4, f= 1,05, upm= 63 .DW 56818 ; nAdc= 5, f= 1,1, upm= 66 .DW 54348 ; nAdc= 6, f= 1,15, upm= 69 .DW 52083 ; nAdc= 7, f= 1,2, upm= 72 .DW 50000 ; nAdc= 8, f= 1,25, upm= 75 .DW 48077 ; nAdc= 9, f= 1,3, upm= 78 .DW 46296 ; nAdc= 10, f= 1,35, upm= 81 .DW 44643 ; nAdc= 11, f= 1,4, upm= 84 .DW 43103 ; nAdc= 12, f= 1,45, upm= 87 .DW 41667 ; nAdc= 13, f= 1,5, upm= 90 .DW 40323 ; nAdc= 14, f= 1,55, upm= 93 .DW 39063 ; nAdc= 15, f= 1,6, upm= 96 .DW 37879 ; nAdc= 16, f= 1,65, upm= 99 .DW 36765 ; nAdc= 17, f= 1,7, upm= 102 .DW 35714 ; nAdc= 18, f= 1,75, upm= 105 .DW 34722 ; nAdc= 19, f= 1,8, upm= 108 .DW 33784 ; nAdc= 20, f= 1,85, upm= 111 .DW 32895 ; nAdc= 21, f= 1,9, upm= 114 .DW 32051 ; nAdc= 22, f= 1,95, upm= 117 .DW 31250 ; nAdc= 23, f= 2, upm= 120 .DW 30488 ; nAdc= 24, f= 2,05, upm= 123 .DW 29762 ; nAdc= 25, f= 2,1, upm= 126 .DW 29070 ; nAdc= 26, f= 2,15, upm= 129 .DW 28409 ; nAdc= 27, f= 2,2, upm= 132 .DW 27778 ; nAdc= 28, f= 2,25, upm= 135 .DW 27174 ; nAdc= 29, f= 2,3, upm= 138 .DW 26596 ; nAdc= 30, f= 2,35, upm= 141 .DW 26042 ; nAdc= 31, f= 2,4, upm= 144 .DW 25510 ; nAdc= 32, f= 2,45, upm= 147 .DW 25000 ; nAdc= 33, f= 2,5, upm= 150 .DW 24510 ; nAdc= 34, f= 2,55, upm= 153 .DW 24038 ; nAdc= 35, f= 2,6, upm= 156 .DW 23585 ; nAdc= 36, f= 2,65, upm= 159 .DW 23148 ; nAdc= 37, f= 2,7, upm= 162 .DW 22727 ; nAdc= 38, f= 2,75, upm= 165 .DW 22321 ; nAdc= 39, f= 2,8, upm= 168 .DW 21930 ; nAdc= 40, f= 2,85, upm= 171 .DW 21552 ; nAdc= 41, f= 2,9, upm= 174 .DW 21186 ; nAdc= 42, f= 2,95, upm= 177 .DW 20833 ; nAdc= 43, f= 3, upm= 180 .DW 20492 ; nAdc= 44, f= 3,05, upm= 183 .DW 20161 ; nAdc= 45, f= 3,1, upm= 186 .DW 19841 ; nAdc= 46, f= 3,15, upm= 189 .DW 19531 ; nAdc= 47, f= 3,2, upm= 192 .DW 19231 ; nAdc= 48, f= 3,25, upm= 195 .DW 18939 ; nAdc= 49, f= 3,3, upm= 198 .DW 18657 ; nAdc= 50, f= 3,35, upm= 201 .DW 18382 ; nAdc= 51, f= 3,4, upm= 204 .DW 18116 ; nAdc= 52, f= 3,45, upm= 207 .DW 17857 ; nAdc= 53, f= 3,5, upm= 210 .DW 17606 ; nAdc= 54, f= 3,55, upm= 213 .DW 17361 ; nAdc= 55, f= 3,6, upm= 216 .DW 17123 ; nAdc= 56, f= 3,65, upm= 219 .DW 16892 ; nAdc= 57, f= 3,7, upm= 222 .DW 16667 ; nAdc= 58, f= 3,75, upm= 225 .DW 16447 ; nAdc= 59, f= 3,8, upm= 228 .DW 64935 ; nAdc= 60, f= 3,85, upm= 231 .DW 64103 ; nAdc= 61, f= 3,9, upm= 234 .DW 63291 ; nAdc= 62, f= 3,95, upm= 237 .DW 62500 ; nAdc= 63, f= 4, upm= 240 .DW 61728 ; nAdc= 64, f= 4,05, upm= 243 .DW 60976 ; nAdc= 65, f= 4,1, upm= 246 .DW 60241 ; nAdc= 66, f= 4,15, upm= 249 .DW 59524 ; nAdc= 67, f= 4,2, upm= 252 .DW 58824 ; nAdc= 68, f= 4,25, upm= 255 .DW 58140 ; nAdc= 69, f= 4,3, upm= 258 .DW 57471 ; nAdc= 70, f= 4,35, upm= 261 .DW 56818 ; nAdc= 71, f= 4,4, upm= 264 .DW 56180 ; nAdc= 72, f= 4,45, upm= 267 .DW 55556 ; nAdc= 73, f= 4,5, upm= 270 .DW 54945 ; nAdc= 74, f= 4,55, upm= 273 .DW 54348 ; nAdc= 75, f= 4,6, upm= 276 .DW 53763 ; nAdc= 76, f= 4,65, upm= 279 .DW 53191 ; nAdc= 77, f= 4,7, upm= 282 .DW 52632 ; nAdc= 78, f= 4,75, upm= 285 .DW 52083 ; nAdc= 79, f= 4,8, upm= 288 .DW 51546 ; nAdc= 80, f= 4,85, upm= 291 .DW 51020 ; nAdc= 81, f= 4,9, upm= 294 .DW 50505 ; nAdc= 82, f= 4,95, upm= 297 .DW 50000 ; nAdc= 83, f= 5, upm= 300 .DW 49505 ; nAdc= 84, f= 5,05, upm= 303 .DW 49020 ; nAdc= 85, f= 5,1, upm= 306 .DW 48544 ; nAdc= 86, f= 5,15, upm= 309 .DW 48077 ; nAdc= 87, f= 5,2, upm= 312 .DW 47619 ; nAdc= 88, f= 5,25, upm= 315 .DW 47170 ; nAdc= 89, f= 5,3, upm= 318 .DW 46729 ; nAdc= 90, f= 5,35, upm= 321 .DW 46296 ; nAdc= 91, f= 5,4, upm= 324 .DW 45872 ; nAdc= 92, f= 5,45, upm= 327 .DW 45455 ; nAdc= 93, f= 5,5, upm= 330 .DW 45045 ; nAdc= 94, f= 5,55, upm= 333 .DW 44643 ; nAdc= 95, f= 5,6, upm= 336 .DW 44248 ; nAdc= 96, f= 5,65, upm= 339 .DW 43860 ; nAdc= 97, f= 5,7, upm= 342 .DW 43478 ; nAdc= 98, f= 5,75, upm= 345 .DW 43103 ; nAdc= 99, f= 5,8, upm= 348 .DW 42735 ; nAdc= 100, f= 5,85, upm= 351 .DW 42373 ; nAdc= 101, f= 5,9, upm= 354 .DW 42017 ; nAdc= 102, f= 5,95, upm= 357 .DW 41667 ; nAdc= 103, f= 6, upm= 360 .DW 41322 ; nAdc= 104, f= 6,05, upm= 363 .DW 40984 ; nAdc= 105, f= 6,1, upm= 366 .DW 40650 ; nAdc= 106, f= 6,15, upm= 369 .DW 40323 ; nAdc= 107, f= 6,2, upm= 372 .DW 40000 ; nAdc= 108, f= 6,25, upm= 375 .DW 39683 ; nAdc= 109, f= 6,3, upm= 378 .DW 39370 ; nAdc= 110, f= 6,35, upm= 381 .DW 39063 ; nAdc= 111, f= 6,4, upm= 384 .DW 38760 ; nAdc= 112, f= 6,45, upm= 387 .DW 38462 ; nAdc= 113, f= 6,5, upm= 390 .DW 38168 ; nAdc= 114, f= 6,55, upm= 393 .DW 37879 ; nAdc= 115, f= 6,6, upm= 396 .DW 37594 ; nAdc= 116, f= 6,65, upm= 399 .DW 37313 ; nAdc= 117, f= 6,7, upm= 402 .DW 37037 ; nAdc= 118, f= 6,75, upm= 405 .DW 36765 ; nAdc= 119, f= 6,8, upm= 408 .DW 36496 ; nAdc= 120, f= 6,85, upm= 411 .DW 36232 ; nAdc= 121, f= 6,9, upm= 414 .DW 35971 ; nAdc= 122, f= 6,95, upm= 417 .DW 35714 ; nAdc= 123, f= 7, upm= 420 .DW 35461 ; nAdc= 124, f= 7,05, upm= 423 .DW 35211 ; nAdc= 125, f= 7,1, upm= 426 .DW 34965 ; nAdc= 126, f= 7,15, upm= 429 .DW 34722 ; nAdc= 127, f= 7,2, upm= 432 .DW 34483 ; nAdc= 128, f= 7,25, upm= 435 .DW 34247 ; nAdc= 129, f= 7,3, upm= 438 .DW 34014 ; nAdc= 130, f= 7,35, upm= 441 .DW 33784 ; nAdc= 131, f= 7,4, upm= 444 .DW 33557 ; nAdc= 132, f= 7,45, upm= 447 .DW 33333 ; nAdc= 133, f= 7,5, upm= 450 .DW 33113 ; nAdc= 134, f= 7,55, upm= 453 .DW 32895 ; nAdc= 135, f= 7,6, upm= 456 .DW 32680 ; nAdc= 136, f= 7,65, upm= 459 .DW 32468 ; nAdc= 137, f= 7,7, upm= 462 .DW 32258 ; nAdc= 138, f= 7,75, upm= 465 .DW 32051 ; nAdc= 139, f= 7,8, upm= 468 .DW 31847 ; nAdc= 140, f= 7,85, upm= 471 .DW 31646 ; nAdc= 141, f= 7,9, upm= 474 .DW 31447 ; nAdc= 142, f= 7,95, upm= 477 .DW 31250 ; nAdc= 143, f= 8, upm= 480 .DW 31056 ; nAdc= 144, f= 8,05, upm= 483 .DW 30864 ; nAdc= 145, f= 8,1, upm= 486 .DW 30675 ; nAdc= 146, f= 8,15, upm= 489 .DW 30488 ; nAdc= 147, f= 8,2, upm= 492 .DW 30303 ; nAdc= 148, f= 8,25, upm= 495 .DW 30120 ; nAdc= 149, f= 8,3, upm= 498 .DW 29940 ; nAdc= 150, f= 8,35, upm= 501 .DW 29762 ; nAdc= 151, f= 8,4, upm= 504 .DW 29586 ; nAdc= 152, f= 8,45, upm= 507 .DW 29412 ; nAdc= 153, f= 8,5, upm= 510 .DW 29240 ; nAdc= 154, f= 8,55, upm= 513 .DW 29070 ; nAdc= 155, f= 8,6, upm= 516 .DW 28902 ; nAdc= 156, f= 8,65, upm= 519 .DW 28736 ; nAdc= 157, f= 8,7, upm= 522 .DW 28571 ; nAdc= 158, f= 8,75, upm= 525 .DW 28409 ; nAdc= 159, f= 8,8, upm= 528 .DW 28249 ; nAdc= 160, f= 8,85, upm= 531 .DW 28090 ; nAdc= 161, f= 8,9, upm= 534 .DW 27933 ; nAdc= 162, f= 8,95, upm= 537 .DW 27778 ; nAdc= 163, f= 9, upm= 540 .DW 27624 ; nAdc= 164, f= 9,05, upm= 543 .DW 27473 ; nAdc= 165, f= 9,1, upm= 546 .DW 27322 ; nAdc= 166, f= 9,15, upm= 549 .DW 27174 ; nAdc= 167, f= 9,2, upm= 552 .DW 27027 ; nAdc= 168, f= 9,25, upm= 555 .DW 26882 ; nAdc= 169, f= 9,3, upm= 558 .DW 26738 ; nAdc= 170, f= 9,35, upm= 561 .DW 26596 ; nAdc= 171, f= 9,4, upm= 564 .DW 26455 ; nAdc= 172, f= 9,45, upm= 567 .DW 26316 ; nAdc= 173, f= 9,5, upm= 570 .DW 26178 ; nAdc= 174, f= 9,55, upm= 573 .DW 26042 ; nAdc= 175, f= 9,6, upm= 576 .DW 25907 ; nAdc= 176, f= 9,65, upm= 579 .DW 25773 ; nAdc= 177, f= 9,7, upm= 582 .DW 25641 ; nAdc= 178, f= 9,75, upm= 585 .DW 25510 ; nAdc= 179, f= 9,8, upm= 588 .DW 25381 ; nAdc= 180, f= 9,85, upm= 591 .DW 25253 ; nAdc= 181, f= 9,9, upm= 594 .DW 25126 ; nAdc= 182, f= 9,95, upm= 597 .DW 25000 ; nAdc= 183, f= 10, upm= 600 .DW 23810 ; nAdc= 184, f= 10,5, upm= 630 .DW 22727 ; nAdc= 185, f= 11, upm= 660 .DW 21739 ; nAdc= 186, f= 11,5, upm= 690 .DW 20833 ; nAdc= 187, f= 12, upm= 720 .DW 20000 ; nAdc= 188, f= 12,5, upm= 750 .DW 19231 ; nAdc= 189, f= 13, upm= 780 .DW 18519 ; nAdc= 190, f= 13,5, upm= 810 .DW 17857 ; nAdc= 191, f= 14, upm= 840 .DW 17241 ; nAdc= 192, f= 14,5, upm= 870 .DW 16667 ; nAdc= 193, f= 15, upm= 900 .DW 16129 ; nAdc= 194, f= 15,5, upm= 930 .DW 15625 ; nAdc= 195, f= 16, upm= 960 .DW 15152 ; nAdc= 196, f= 16,5, upm= 990 .DW 14706 ; nAdc= 197, f= 17, upm= 1020 .DW 14286 ; nAdc= 198, f= 17,5, upm= 1050 .DW 13889 ; nAdc= 199, f= 18, upm= 1080 .DW 13514 ; nAdc= 200, f= 18,5, upm= 1110 .DW 13158 ; nAdc= 201, f= 19, upm= 1140 .DW 12821 ; nAdc= 202, f= 19,5, upm= 1170 .DW 12500 ; nAdc= 203, f= 20, upm= 1200 .DW 12195 ; nAdc= 204, f= 20,5, upm= 1230 .DW 11905 ; nAdc= 205, f= 21, upm= 1260 .DW 11628 ; nAdc= 206, f= 21,5, upm= 1290 .DW 11364 ; nAdc= 207, f= 22, upm= 1320 .DW 11111 ; nAdc= 208, f= 22,5, upm= 1350 .DW 10870 ; nAdc= 209, f= 23, upm= 1380 .DW 10638 ; nAdc= 210, f= 23,5, upm= 1410 .DW 10417 ; nAdc= 211, f= 24, upm= 1440 .DW 10204 ; nAdc= 212, f= 24,5, upm= 1470 .DW 10000 ; nAdc= 213, f= 25, upm= 1500 .DW 9804 ; nAdc= 214, f= 25,5, upm= 1530 .DW 9615 ; nAdc= 215, f= 26, upm= 1560 .DW 9434 ; nAdc= 216, f= 26,5, upm= 1590 .DW 9259 ; nAdc= 217, f= 27, upm= 1620 .DW 9091 ; nAdc= 218, f= 27,5, upm= 1650 .DW 8929 ; nAdc= 219, f= 28, upm= 1680 .DW 8772 ; nAdc= 220, f= 28,5, upm= 1710 .DW 8621 ; nAdc= 221, f= 29, upm= 1740 .DW 8475 ; nAdc= 222, f= 29,5, upm= 1770 .DW 8333 ; nAdc= 223, f= 30, upm= 1800 .DW 8197 ; nAdc= 224, f= 30,5, upm= 1830 .DW 64516 ; nAdc= 225, f= 31, upm= 1860 .DW 63492 ; nAdc= 226, f= 31,5, upm= 1890 .DW 62500 ; nAdc= 227, f= 32, upm= 1920 .DW 61538 ; nAdc= 228, f= 32,5, upm= 1950 .DW 60606 ; nAdc= 229, f= 33, upm= 1980 .DW 59701 ; nAdc= 230, f= 33,5, upm= 2010 .DW 58824 ; nAdc= 231, f= 34, upm= 2040 .DW 57971 ; nAdc= 232, f= 34,5, upm= 2070 .DW 57143 ; nAdc= 233, f= 35, upm= 2100 .DW 56338 ; nAdc= 234, f= 35,5, upm= 2130 .DW 55556 ; nAdc= 235, f= 36, upm= 2160 .DW 54795 ; nAdc= 236, f= 36,5, upm= 2190 .DW 54054 ; nAdc= 237, f= 37, upm= 2220 .DW 53333 ; nAdc= 238, f= 37,5, upm= 2250 .DW 52632 ; nAdc= 239, f= 38, upm= 2280 .DW 51948 ; nAdc= 240, f= 38,5, upm= 2310 .DW 51282 ; nAdc= 241, f= 39, upm= 2340 .DW 50633 ; nAdc= 242, f= 39,5, upm= 2370 .DW 50000 ; nAdc= 243, f= 40, upm= 2400 .DW 49383 ; nAdc= 244, f= 40,5, upm= 2430 .DW 48780 ; nAdc= 245, f= 41, upm= 2460 .DW 48193 ; nAdc= 246, f= 41,5, upm= 2490 .DW 47619 ; nAdc= 247, f= 42, upm= 2520 .DW 47059 ; nAdc= 248, f= 42,5, upm= 2550 .DW 46512 ; nAdc= 249, f= 43, upm= 2580 .DW 45977 ; nAdc= 250, f= 43,5, upm= 2610 .DW 45455 ; nAdc= 251, f= 44, upm= 2640 .DW 44944 ; nAdc= 252, f= 44,5, upm= 2670 .DW 44444 ; nAdc= 253, f= 45, upm= 2700 .DW 43956 ; nAdc= 254, f= 45,5, upm= 2730 .DW 43478 ; nAdc= 255, f= 46, upm= 2760 .DW 43011 ; nAdc= 256, f= 46,5, upm= 2790 .DW 42553 ; nAdc= 257, f= 47, upm= 2820 .DW 42105 ; nAdc= 258, f= 47,5, upm= 2850 .DW 41667 ; nAdc= 259, f= 48, upm= 2880 .DW 41237 ; nAdc= 260, f= 48,5, upm= 2910 .DW 40816 ; nAdc= 261, f= 49, upm= 2940 .DW 40404 ; nAdc= 262, f= 49,5, upm= 2970 .DW 40000 ; nAdc= 263, f= 50, upm= 3000 .DW 39604 ; nAdc= 264, f= 50,5, upm= 3030 .DW 39216 ; nAdc= 265, f= 51, upm= 3060 .DW 38835 ; nAdc= 266, f= 51,5, upm= 3090 .DW 38462 ; nAdc= 267, f= 52, upm= 3120 .DW 38095 ; nAdc= 268, f= 52,5, upm= 3150 .DW 37736 ; nAdc= 269, f= 53, upm= 3180 .DW 37383 ; nAdc= 270, f= 53,5, upm= 3210 .DW 37037 ; nAdc= 271, f= 54, upm= 3240 .DW 36697 ; nAdc= 272, f= 54,5, upm= 3270 .DW 36364 ; nAdc= 273, f= 55, upm= 3300 .DW 36036 ; nAdc= 274, f= 55,5, upm= 3330 .DW 35714 ; nAdc= 275, f= 56, upm= 3360 .DW 35398 ; nAdc= 276, f= 56,5, upm= 3390 .DW 35088 ; nAdc= 277, f= 57, upm= 3420 .DW 34783 ; nAdc= 278, f= 57,5, upm= 3450 .DW 34483 ; nAdc= 279, f= 58, upm= 3480 .DW 34188 ; nAdc= 280, f= 58,5, upm= 3510 .DW 33898 ; nAdc= 281, f= 59, upm= 3540 .DW 33613 ; nAdc= 282, f= 59,5, upm= 3570 .DW 33333 ; nAdc= 283, f= 60, upm= 3600 .DW 33058 ; nAdc= 284, f= 60,5, upm= 3630 .DW 32787 ; nAdc= 285, f= 61, upm= 3660 .DW 32520 ; nAdc= 286, f= 61,5, upm= 3690 .DW 32258 ; nAdc= 287, f= 62, upm= 3720 .DW 32000 ; nAdc= 288, f= 62,5, upm= 3750 .DW 31746 ; nAdc= 289, f= 63, upm= 3780 .DW 31496 ; nAdc= 290, f= 63,5, upm= 3810 .DW 31250 ; nAdc= 291, f= 64, upm= 3840 .DW 31008 ; nAdc= 292, f= 64,5, upm= 3870 .DW 30769 ; nAdc= 293, f= 65, upm= 3900 .DW 30534 ; nAdc= 294, f= 65,5, upm= 3930 .DW 30303 ; nAdc= 295, f= 66, upm= 3960 .DW 30075 ; nAdc= 296, f= 66,5, upm= 3990 .DW 29851 ; nAdc= 297, f= 67, upm= 4020 .DW 29630 ; nAdc= 298, f= 67,5, upm= 4050 .DW 29412 ; nAdc= 299, f= 68, upm= 4080 .DW 29197 ; nAdc= 300, f= 68,5, upm= 4110 .DW 28986 ; nAdc= 301, f= 69, upm= 4140 .DW 28777 ; nAdc= 302, f= 69,5, upm= 4170 .DW 28571 ; nAdc= 303, f= 70, upm= 4200 .DW 28369 ; nAdc= 304, f= 70,5, upm= 4230 .DW 28169 ; nAdc= 305, f= 71, upm= 4260 .DW 27972 ; nAdc= 306, f= 71,5, upm= 4290 .DW 27778 ; nAdc= 307, f= 72, upm= 4320 .DW 27586 ; nAdc= 308, f= 72,5, upm= 4350 .DW 27397 ; nAdc= 309, f= 73, upm= 4380 .DW 27211 ; nAdc= 310, f= 73,5, upm= 4410 .DW 27027 ; nAdc= 311, f= 74, upm= 4440 .DW 26846 ; nAdc= 312, f= 74,5, upm= 4470 .DW 26667 ; nAdc= 313, f= 75, upm= 4500 .DW 26490 ; nAdc= 314, f= 75,5, upm= 4530 .DW 26316 ; nAdc= 315, f= 76, upm= 4560 .DW 26144 ; nAdc= 316, f= 76,5, upm= 4590 .DW 25974 ; nAdc= 317, f= 77, upm= 4620 .DW 25806 ; nAdc= 318, f= 77,5, upm= 4650 .DW 25641 ; nAdc= 319, f= 78, upm= 4680 .DW 25478 ; nAdc= 320, f= 78,5, upm= 4710 .DW 25316 ; nAdc= 321, f= 79, upm= 4740 .DW 25157 ; nAdc= 322, f= 79,5, upm= 4770 .DW 25000 ; nAdc= 323, f= 80, upm= 4800 .DW 24845 ; nAdc= 324, f= 80,5, upm= 4830 .DW 24691 ; nAdc= 325, f= 81, upm= 4860 .DW 24540 ; nAdc= 326, f= 81,5, upm= 4890 .DW 24390 ; nAdc= 327, f= 82, upm= 4920 .DW 24242 ; nAdc= 328, f= 82,5, upm= 4950 .DW 24096 ; nAdc= 329, f= 83, upm= 4980 .DW 23952 ; nAdc= 330, f= 83,5, upm= 5010 .DW 23810 ; nAdc= 331, f= 84, upm= 5040 .DW 23669 ; nAdc= 332, f= 84,5, upm= 5070 .DW 23529 ; nAdc= 333, f= 85, upm= 5100 .DW 23392 ; nAdc= 334, f= 85,5, upm= 5130 .DW 23256 ; nAdc= 335, f= 86, upm= 5160 .DW 23121 ; nAdc= 336, f= 86,5, upm= 5190 .DW 22989 ; nAdc= 337, f= 87, upm= 5220 .DW 22857 ; nAdc= 338, f= 87,5, upm= 5250 .DW 22727 ; nAdc= 339, f= 88, upm= 5280 .DW 22599 ; nAdc= 340, f= 88,5, upm= 5310 .DW 22472 ; nAdc= 341, f= 89, upm= 5340 .DW 22346 ; nAdc= 342, f= 89,5, upm= 5370 .DW 22222 ; nAdc= 343, f= 90, upm= 5400 .DW 22099 ; nAdc= 344, f= 90,5, upm= 5430 .DW 21978 ; nAdc= 345, f= 91, upm= 5460 .DW 21858 ; nAdc= 346, f= 91,5, upm= 5490 .DW 21739 ; nAdc= 347, f= 92, upm= 5520 .DW 21622 ; nAdc= 348, f= 92,5, upm= 5550 .DW 21505 ; nAdc= 349, f= 93, upm= 5580 .DW 21390 ; nAdc= 350, f= 93,5, upm= 5610 .DW 21277 ; nAdc= 351, f= 94, upm= 5640 .DW 21164 ; nAdc= 352, f= 94,5, upm= 5670 .DW 21053 ; nAdc= 353, f= 95, upm= 5700 .DW 20942 ; nAdc= 354, f= 95,5, upm= 5730 .DW 20833 ; nAdc= 355, f= 96, upm= 5760 .DW 20725 ; nAdc= 356, f= 96,5, upm= 5790 .DW 20619 ; nAdc= 357, f= 97, upm= 5820 .DW 20513 ; nAdc= 358, f= 97,5, upm= 5850 .DW 20408 ; nAdc= 359, f= 98, upm= 5880 .DW 20305 ; nAdc= 360, f= 98,5, upm= 5910 .DW 20202 ; nAdc= 361, f= 99, upm= 5940 .DW 20101 ; nAdc= 362, f= 99,5, upm= 5970 .DW 20000 ; nAdc= 363, f= 100, upm= 6000 .DW 19048 ; nAdc= 364, f= 105, upm= 6300 .DW 18182 ; nAdc= 365, f= 110, upm= 6600 .DW 17391 ; nAdc= 366, f= 115, upm= 6900 .DW 16667 ; nAdc= 367, f= 120, upm= 7200 .DW 16000 ; nAdc= 368, f= 125, upm= 7500 .DW 15385 ; nAdc= 369, f= 130, upm= 7800 .DW 14815 ; nAdc= 370, f= 135, upm= 8100 .DW 14286 ; nAdc= 371, f= 140, upm= 8400 .DW 13793 ; nAdc= 372, f= 145, upm= 8700 .DW 13333 ; nAdc= 373, f= 150, upm= 9000 .DW 12903 ; nAdc= 374, f= 155, upm= 9300 .DW 12500 ; nAdc= 375, f= 160, upm= 9600 .DW 12121 ; nAdc= 376, f= 165, upm= 9900 .DW 11765 ; nAdc= 377, f= 170, upm= 10200 .DW 11429 ; nAdc= 378, f= 174,99, upm= 10500 .DW 11111 ; nAdc= 379, f= 180, upm= 10800 .DW 10811 ; nAdc= 380, f= 185, upm= 11100 .DW 10526 ; nAdc= 381, f= 190,01, upm= 11400 .DW 10256 ; nAdc= 382, f= 195,01, upm= 11700 .DW 10000 ; nAdc= 383, f= 200, upm= 12000 .DW 9756 ; nAdc= 384, f= 205, upm= 12300 .DW 9524 ; nAdc= 385, f= 210, upm= 12600 .DW 9302 ; nAdc= 386, f= 215,01, upm= 12900 .DW 9091 ; nAdc= 387, f= 220, upm= 13200 .DW 8889 ; nAdc= 388, f= 225, upm= 13500 .DW 8696 ; nAdc= 389, f= 229,99, upm= 13799 .DW 8511 ; nAdc= 390, f= 234,99, upm= 14099 .DW 8333 ; nAdc= 391, f= 240,01, upm= 14401 .DW 65306 ; nAdc= 392, f= 245, upm= 14700 .DW 64000 ; nAdc= 393, f= 250, upm= 15000 .DW 62745 ; nAdc= 394, f= 255, upm= 15300 .DW 61538 ; nAdc= 395, f= 260, upm= 15600 .DW 60377 ; nAdc= 396, f= 265, upm= 15900 .DW 59259 ; nAdc= 397, f= 270, upm= 16200 .DW 58182 ; nAdc= 398, f= 275, upm= 16500 .DW 57143 ; nAdc= 399, f= 280, upm= 16800 .DW 56140 ; nAdc= 400, f= 285, upm= 17100 .DW 55172 ; nAdc= 401, f= 290, upm= 17400 .DW 54237 ; nAdc= 402, f= 295, upm= 17700 .DW 53333 ; nAdc= 403, f= 300, upm= 18000 .DW 52459 ; nAdc= 404, f= 305, upm= 18300 .DW 51613 ; nAdc= 405, f= 310, upm= 18600 .DW 50794 ; nAdc= 406, f= 315, upm= 18900 .DW 50000 ; nAdc= 407, f= 320, upm= 19200 .DW 49231 ; nAdc= 408, f= 325, upm= 19500 .DW 48485 ; nAdc= 409, f= 330, upm= 19800 .DW 47761 ; nAdc= 410, f= 335, upm= 20100 .DW 47059 ; nAdc= 411, f= 340, upm= 20400 .DW 46377 ; nAdc= 412, f= 345, upm= 20700 .DW 45714 ; nAdc= 413, f= 350, upm= 21000 .DW 45070 ; nAdc= 414, f= 355, upm= 21300 .DW 44444 ; nAdc= 415, f= 360, upm= 21600 .DW 43836 ; nAdc= 416, f= 365, upm= 21900 .DW 43243 ; nAdc= 417, f= 370, upm= 22200 .DW 42667 ; nAdc= 418, f= 375, upm= 22500 .DW 42105 ; nAdc= 419, f= 380, upm= 22800 .DW 41558 ; nAdc= 420, f= 385, upm= 23100 .DW 41026 ; nAdc= 421, f= 390, upm= 23400 .DW 40506 ; nAdc= 422, f= 395, upm= 23700 .DW 40000 ; nAdc= 423, f= 400, upm= 24000 .DW 39506 ; nAdc= 424, f= 405, upm= 24300 .DW 39024 ; nAdc= 425, f= 410, upm= 24600 .DW 38554 ; nAdc= 426, f= 415, upm= 24900 .DW 38095 ; nAdc= 427, f= 420, upm= 25200 .DW 37647 ; nAdc= 428, f= 425, upm= 25500 .DW 37209 ; nAdc= 429, f= 430, upm= 25800 .DW 36782 ; nAdc= 430, f= 435, upm= 26100 .DW 36364 ; nAdc= 431, f= 440, upm= 26400 .DW 35955 ; nAdc= 432, f= 445, upm= 26700 .DW 35556 ; nAdc= 433, f= 449,99, upm= 27000 .DW 35165 ; nAdc= 434, f= 455, upm= 27300 .DW 34783 ; nAdc= 435, f= 459,99, upm= 27600 .DW 34409 ; nAdc= 436, f= 464,99, upm= 27900 .DW 34043 ; nAdc= 437, f= 469,99, upm= 28200 .DW 33684 ; nAdc= 438, f= 475, upm= 28500 .DW 33333 ; nAdc= 439, f= 480, upm= 28800 .DW 32990 ; nAdc= 440, f= 485, upm= 29100 .DW 32653 ; nAdc= 441, f= 490, upm= 29400 .DW 32323 ; nAdc= 442, f= 495, upm= 29700 .DW 32000 ; nAdc= 443, f= 500, upm= 30000 .DW 31683 ; nAdc= 444, f= 505, upm= 30300 .DW 31373 ; nAdc= 445, f= 509,99, upm= 30600 .DW 31068 ; nAdc= 446, f= 515, upm= 30900 .DW 30769 ; nAdc= 447, f= 520, upm= 31200 .DW 30476 ; nAdc= 448, f= 525, upm= 31500 .DW 30189 ; nAdc= 449, f= 529,99, upm= 31800 .DW 29907 ; nAdc= 450, f= 534,99, upm= 32100 .DW 29630 ; nAdc= 451, f= 539,99, upm= 32400 .DW 29358 ; nAdc= 452, f= 545, upm= 32700 .DW 29091 ; nAdc= 453, f= 550, upm= 33000 .DW 28829 ; nAdc= 454, f= 555, upm= 33300 .DW 28571 ; nAdc= 455, f= 560,01, upm= 33601 .DW 28319 ; nAdc= 456, f= 564,99, upm= 33900 .DW 28070 ; nAdc= 457, f= 570, upm= 34200 .DW 27826 ; nAdc= 458, f= 575, upm= 34500 .DW 27586 ; nAdc= 459, f= 580, upm= 34800 .DW 27350 ; nAdc= 460, f= 585,01, upm= 35101 .DW 27119 ; nAdc= 461, f= 589,99, upm= 35400 .DW 26891 ; nAdc= 462, f= 594,99, upm= 35700 .DW 26667 ; nAdc= 463, f= 599,99, upm= 36000 .DW 26446 ; nAdc= 464, f= 605,01, upm= 36300 .DW 26230 ; nAdc= 465, f= 609,99, upm= 36599 .DW 26016 ; nAdc= 466, f= 615,01, upm= 36900 .DW 25806 ; nAdc= 467, f= 620,01, upm= 37201 .DW 25600 ; nAdc= 468, f= 625, upm= 37500 .DW 25397 ; nAdc= 469, f= 630, upm= 37800 .DW 25197 ; nAdc= 470, f= 635, upm= 38100 .DW 25000 ; nAdc= 471, f= 640, upm= 38400 .DW 24806 ; nAdc= 472, f= 645,01, upm= 38700 .DW 24615 ; nAdc= 473, f= 650,01, upm= 39001 .DW 24427 ; nAdc= 474, f= 655,01, upm= 39301 .DW 24242 ; nAdc= 475, f= 660,01, upm= 39601 .DW 24060 ; nAdc= 476, f= 665, upm= 39900 .DW 23881 ; nAdc= 477, f= 669,99, upm= 40199 .DW 23704 ; nAdc= 478, f= 674,99, upm= 40499 .DW 23529 ; nAdc= 479, f= 680,01, upm= 40801 .DW 23358 ; nAdc= 480, f= 684,99, upm= 41099 .DW 23188 ; nAdc= 481, f= 690,01, upm= 41401 .DW 23022 ; nAdc= 482, f= 694,99, upm= 41699 .DW 22857 ; nAdc= 483, f= 700, upm= 42000 .DW 22695 ; nAdc= 484, f= 705, upm= 42300 .DW 22535 ; nAdc= 485, f= 710,01, upm= 42600 .DW 22378 ; nAdc= 486, f= 714,99, upm= 42899 .DW 22222 ; nAdc= 487, f= 720,01, upm= 43200 .DW 22069 ; nAdc= 488, f= 725, upm= 43500 .DW 21918 ; nAdc= 489, f= 729,99, upm= 43800 .DW 21769 ; nAdc= 490, f= 734,99, upm= 44099 .DW 21622 ; nAdc= 491, f= 739,99, upm= 44399 .DW 21477 ; nAdc= 492, f= 744,98, upm= 44699 .DW 21333 ; nAdc= 493, f= 750,01, upm= 45001 .DW 21192 ; nAdc= 494, f= 755, upm= 45300 .DW 21053 ; nAdc= 495, f= 759,99, upm= 45599 .DW 20915 ; nAdc= 496, f= 765, upm= 45900 .DW 20779 ; nAdc= 497, f= 770,01, upm= 46200 .DW 20645 ; nAdc= 498, f= 775,01, upm= 46500 .DW 20513 ; nAdc= 499, f= 779,99, upm= 46800 .DW 20382 ; nAdc= 500, f= 785,01, upm= 47100 .DW 20253 ; nAdc= 501, f= 790,01, upm= 47400 .DW 20126 ; nAdc= 502, f= 794,99, upm= 47699 .DW 20000 ; nAdc= 503, f= 800, upm= 48000 .DW 19876 ; nAdc= 504, f= 804,99, upm= 48299 .DW 19753 ; nAdc= 505, f= 810, upm= 48600 .DW 19632 ; nAdc= 506, f= 815, upm= 48900 .DW 19512 ; nAdc= 507, f= 820,01, upm= 49200 .DW 19394 ; nAdc= 508, f= 825, upm= 49500 .DW 19277 ; nAdc= 509, f= 830, upm= 49800 .DW 19162 ; nAdc= 510, f= 834,99, upm= 50099 .DW 19048 ; nAdc= 511, f= 839,98, upm= 50399 .DW 18935 ; nAdc= 512, f= 845, upm= 50700 .DW 18824 ; nAdc= 513, f= 849,98, upm= 50999 .DW 18713 ; nAdc= 514, f= 855,02, upm= 51301 .DW 18605 ; nAdc= 515, f= 859,98, upm= 51599 .DW 18497 ; nAdc= 516, f= 865,01, upm= 51900 .DW 18391 ; nAdc= 517, f= 869,99, upm= 52199 .DW 18286 ; nAdc= 518, f= 874,99, upm= 52499 .DW 18182 ; nAdc= 519, f= 879,99, upm= 52799 .DW 18079 ; nAdc= 520, f= 885, upm= 53100 .DW 17978 ; nAdc= 521, f= 889,98, upm= 53399 .DW 17877 ; nAdc= 522, f= 895, upm= 53700 .DW 17778 ; nAdc= 523, f= 899,99, upm= 53999 .DW 17680 ; nAdc= 524, f= 904,98, upm= 54299 .DW 17582 ; nAdc= 525, f= 910,02, upm= 54601 .DW 17486 ; nAdc= 526, f= 915,02, upm= 54901 .DW 17391 ; nAdc= 527, f= 920,02, upm= 55201 .DW 17297 ; nAdc= 528, f= 925,02, upm= 55501 .DW 17204 ; nAdc= 529, f= 930,02, upm= 55801 .DW 17112 ; nAdc= 530, f= 935,02, upm= 56101 .DW 17021 ; nAdc= 531, f= 940,02, upm= 56401 .DW 16931 ; nAdc= 532, f= 945,01, upm= 56701 .DW 16842 ; nAdc= 533, f= 950,01, upm= 57000 .DW 16754 ; nAdc= 534, f= 955, upm= 57300 .DW 16667 ; nAdc= 535, f= 959,98, upm= 57599 .DW 16580 ; nAdc= 536, f= 965,02, upm= 57901 .DW 16495 ; nAdc= 537, f= 969,99, upm= 58199 .DW 16410 ; nAdc= 538, f= 975,02, upm= 58501 .DW 16327 ; nAdc= 539, f= 979,97, upm= 58798 .DW 16244 ; nAdc= 540, f= 984,98, upm= 59099 .DW 16162 ; nAdc= 541, f= 989,98, upm= 59399 .DW 16080 ; nAdc= 542, f= 995,02, upm= 59701 .DW 16000 ; nAdc= 543, f= 1000, upm= 60000 .DW 15238 ; nAdc= 544, f= 1050,01 .DW 14545 ; nAdc= 545, f= 1100,03 .DW 13913 ; nAdc= 546, f= 1150 .DW 13333 ; nAdc= 547, f= 1200,03 .DW 12800 ; nAdc= 548, f= 1250 .DW 12308 ; nAdc= 549, f= 1299,97 .DW 11852 ; nAdc= 550, f= 1349,98 .DW 11429 ; nAdc= 551, f= 1399,95 .DW 11034 ; nAdc= 552, f= 1450,06 .DW 10667 ; nAdc= 553, f= 1499,95 .DW 10323 ; nAdc= 554, f= 1549,94 .DW 10000 ; nAdc= 555, f= 1600 .DW 9697 ; nAdc= 556, f= 1649,99 .DW 9412 ; nAdc= 557, f= 1699,96 .DW 9143 ; nAdc= 558, f= 1749,97 .DW 8889 ; nAdc= 559, f= 1799,98 .DW 8649 ; nAdc= 560, f= 1849,92 .DW 8421 ; nAdc= 561, f= 1900,01 .DW 8205 ; nAdc= 562, f= 1950,03 .DW 8000 ; nAdc= 563, f= 2000 .DW 7805 ; nAdc= 564, f= 2049,97 .DW 7619 ; nAdc= 565, f= 2100,01 .DW 7442 ; nAdc= 566, f= 2149,96 .DW 7273 ; nAdc= 567, f= 2199,92 .DW 7111 ; nAdc= 568, f= 2250,04 .DW 6957 ; nAdc= 569, f= 2299,84 .DW 6809 ; nAdc= 570, f= 2349,83 .DW 6667 ; nAdc= 571, f= 2399,88 .DW 6531 ; nAdc= 572, f= 2449,85 .DW 6400 ; nAdc= 573, f= 2500 .DW 6275 ; nAdc= 574, f= 2549,8 .DW 6154 ; nAdc= 575, f= 2599,94 .DW 6038 ; nAdc= 576, f= 2649,88 .DW 5926 ; nAdc= 577, f= 2699,97 .DW 5818 ; nAdc= 578, f= 2750,09 .DW 5714 ; nAdc= 579, f= 2800,14 .DW 5614 ; nAdc= 580, f= 2850,02 .DW 5517 ; nAdc= 581, f= 2900,13 .DW 5424 ; nAdc= 582, f= 2949,85 .DW 5333 ; nAdc= 583, f= 3000,19 .DW 5246 ; nAdc= 584, f= 3049,94 .DW 5161 ; nAdc= 585, f= 3100,17 .DW 5079 ; nAdc= 586, f= 3150,23 .DW 5000 ; nAdc= 587, f= 3200 .DW 4923 ; nAdc= 588, f= 3250,05 .DW 4848 ; nAdc= 589, f= 3300,33 .DW 4776 ; nAdc= 590, f= 3350,08 .DW 4706 ; nAdc= 591, f= 3399,92 .DW 4638 ; nAdc= 592, f= 3449,76 .DW 4571 ; nAdc= 593, f= 3500,33 .DW 4507 ; nAdc= 594, f= 3550,03 .DW 4444 ; nAdc= 595, f= 3600,36 .DW 4384 ; nAdc= 596, f= 3649,64 .DW 4324 ; nAdc= 597, f= 3700,28 .DW 4267 ; nAdc= 598, f= 3749,71 .DW 4211 ; nAdc= 599, f= 3799,57 .DW 4156 ; nAdc= 600, f= 3849,86 .DW 4103 ; nAdc= 601, f= 3899,59 .DW 4051 ; nAdc= 602, f= 3949,64 .DW 4000 ; nAdc= 603, f= 4000 .DW 3951 ; nAdc= 604, f= 4049,61 .DW 3902 ; nAdc= 605, f= 4100,46 .DW 3855 ; nAdc= 606, f= 4150,45 .DW 3810 ; nAdc= 607, f= 4199,48 .DW 3765 ; nAdc= 608, f= 4249,67 .DW 3721 ; nAdc= 609, f= 4299,92 .DW 3678 ; nAdc= 610, f= 4350,19 .DW 3636 ; nAdc= 611, f= 4400,44 .DW 3596 ; nAdc= 612, f= 4449,39 .DW 3556 ; nAdc= 613, f= 4499,44 .DW 3516 ; nAdc= 614, f= 4550,63 .DW 3478 ; nAdc= 615, f= 4600,35 .DW 3441 ; nAdc= 616, f= 4649,81 .DW 3404 ; nAdc= 617, f= 4700,35 .DW 3368 ; nAdc= 618, f= 4750,59 .DW 3333 ; nAdc= 619, f= 4800,48 .DW 3299 ; nAdc= 620, f= 4849,95 .DW 3265 ; nAdc= 621, f= 4900,46 .DW 3232 ; nAdc= 622, f= 4950,5 .DW 3200 ; nAdc= 623, f= 5000 .DW 3168 ; nAdc= 624, f= 5050,51 .DW 3137 ; nAdc= 625, f= 5100,41 .DW 3107 ; nAdc= 626, f= 5149,66 .DW 3077 ; nAdc= 627, f= 5199,87 .DW 3048 ; nAdc= 628, f= 5249,34 .DW 3019 ; nAdc= 629, f= 5299,77 .DW 2991 ; nAdc= 630, f= 5349,38 .DW 2963 ; nAdc= 631, f= 5399,93 .DW 2936 ; nAdc= 632, f= 5449,59 .DW 2909 ; nAdc= 633, f= 5500,17 .DW 2883 ; nAdc= 634, f= 5549,77 .DW 2857 ; nAdc= 635, f= 5600,28 .DW 2832 ; nAdc= 636, f= 5649,72 .DW 2807 ; nAdc= 637, f= 5700,04 .DW 2783 ; nAdc= 638, f= 5749,19 .DW 2759 ; nAdc= 639, f= 5799,2 .DW 2735 ; nAdc= 640, f= 5850,09 .DW 2712 ; nAdc= 641, f= 5899,71 .DW 2689 ; nAdc= 642, f= 5950,17 .DW 2667 ; nAdc= 643, f= 5999,25 .DW 2645 ; nAdc= 644, f= 6049,15 .DW 2623 ; nAdc= 645, f= 6099,89 .DW 2602 ; nAdc= 646, f= 6149,12 .DW 2581 ; nAdc= 647, f= 6199,15 .DW 2560 ; nAdc= 648, f= 6250 .DW 2540 ; nAdc= 649, f= 6299,21 .DW 2520 ; nAdc= 650, f= 6349,21 .DW 2500 ; nAdc= 651, f= 6400 .DW 2481 ; nAdc= 652, f= 6449,01 .DW 2462 ; nAdc= 653, f= 6498,78 .DW 2443 ; nAdc= 654, f= 6549,32 .DW 2424 ; nAdc= 655, f= 6600,66 .DW 2406 ; nAdc= 656, f= 6650,04 .DW 2388 ; nAdc= 657, f= 6700,17 .DW 2370 ; nAdc= 658, f= 6751,05 .DW 2353 ; nAdc= 659, f= 6799,83 .DW 2336 ; nAdc= 660, f= 6849,32 .DW 2319 ; nAdc= 661, f= 6899,53 .DW 2302 ; nAdc= 662, f= 6950,48 .DW 2286 ; nAdc= 663, f= 6999,13 .DW 2270 ; nAdc= 664, f= 7048,46 .DW 2254 ; nAdc= 665, f= 7098,49 .DW 2238 ; nAdc= 666, f= 7149,24 .DW 2222 ; nAdc= 667, f= 7200,72 .DW 2207 ; nAdc= 668, f= 7249,66 .DW 2192 ; nAdc= 669, f= 7299,27 .DW 2177 ; nAdc= 670, f= 7349,56 .DW 2162 ; nAdc= 671, f= 7400,56 .DW 2148 ; nAdc= 672, f= 7448,79 .DW 2133 ; nAdc= 673, f= 7501,17 .DW 2119 ; nAdc= 674, f= 7550,73 .DW 2105 ; nAdc= 675, f= 7600,95 .DW 2092 ; nAdc= 676, f= 7648,18 .DW 2078 ; nAdc= 677, f= 7699,71 .DW 2065 ; nAdc= 678, f= 7748,18 .DW 2051 ; nAdc= 679, f= 7801,07 .DW 2038 ; nAdc= 680, f= 7850,83 .DW 2025 ; nAdc= 681, f= 7901,23 .DW 2013 ; nAdc= 682, f= 7948,34 .DW 2000 ; nAdc= 683, f= 8000 .DW 1988 ; nAdc= 684, f= 8048,29 .DW 1975 ; nAdc= 685, f= 8101,27 .DW 1963 ; nAdc= 686, f= 8150,79 .DW 1951 ; nAdc= 687, f= 8200,92 .DW 1939 ; nAdc= 688, f= 8251,68 .DW 1928 ; nAdc= 689, f= 8298,76 .DW 1916 ; nAdc= 690, f= 8350,73 .DW 1905 ; nAdc= 691, f= 8398,95 .DW 1893 ; nAdc= 692, f= 8452,19 .DW 1882 ; nAdc= 693, f= 8501,59 .DW 1871 ; nAdc= 694, f= 8551,58 .DW 1860 ; nAdc= 695, f= 8602,15 .DW 1850 ; nAdc= 696, f= 8648,65 .DW 1839 ; nAdc= 697, f= 8700,38 .DW 1829 ; nAdc= 698, f= 8747,95 .DW 1818 ; nAdc= 699, f= 8800,88 .DW 1808 ; nAdc= 700, f= 8849,56 .DW 1798 ; nAdc= 701, f= 8898,78 .DW 1788 ; nAdc= 702, f= 8948,55 .DW 1778 ; nAdc= 703, f= 8998,88 .DW 1768 ; nAdc= 704, f= 9049,77 .DW 1758 ; nAdc= 705, f= 9101,25 .DW 1749 ; nAdc= 706, f= 9148,08 .DW 1739 ; nAdc= 707, f= 9200,69 .DW 1730 ; nAdc= 708, f= 9248,55 .DW 1720 ; nAdc= 709, f= 9302,33 .DW 1711 ; nAdc= 710, f= 9351,26 .DW 1702 ; nAdc= 711, f= 9400,71 .DW 1693 ; nAdc= 712, f= 9450,68 .DW 1684 ; nAdc= 713, f= 9501,19 .DW 1675 ; nAdc= 714, f= 9552,24 .DW 1667 ; nAdc= 715, f= 9598,08 .DW 1658 ; nAdc= 716, f= 9650,18 .DW 1649 ; nAdc= 717, f= 9702,85 .DW 1641 ; nAdc= 718, f= 9750,15 .DW 1633 ; nAdc= 719, f= 9797,92 .DW 1624 ; nAdc= 720, f= 9852,22 .DW 1616 ; nAdc= 721, f= 9900,99 .DW 1608 ; nAdc= 722, f= 9950,25 .DW 1600 ; nAdc= 723, f= 10000 .DW 1524 ; nAdc= 724, f= 10498,69 .DW 1455 ; nAdc= 725, f= 10996,56 .DW 1391 ; nAdc= 726, f= 11502,52 .DW 1333 ; nAdc= 727, f= 12003 .DW 1280 ; nAdc= 728, f= 12500 .DW 1231 ; nAdc= 729, f= 12997,56 .DW 1185 ; nAdc= 730, f= 13502,11 .DW 1143 ; nAdc= 731, f= 13998,25 .DW 1103 ; nAdc= 732, f= 14505,89 .DW 1067 ; nAdc= 733, f= 14995,31 .DW 1032 ; nAdc= 734, f= 15503,88 .DW 1000 ; nAdc= 735, f= 16000 .DW 970 ; nAdc= 736, f= 16494,85 .DW 941 ; nAdc= 737, f= 17003,19 .DW 914 ; nAdc= 738, f= 17505,47 .DW 889 ; nAdc= 739, f= 17997,75 .DW 865 ; nAdc= 740, f= 18497,11 .DW 842 ; nAdc= 741, f= 19002,38 .DW 821 ; nAdc= 742, f= 19488,43 .DW 800 ; nAdc= 743, f= 20000 .DW 780 ; nAdc= 744, f= 20512,82 .DW 762 ; nAdc= 745, f= 20997,38 .DW 744 ; nAdc= 746, f= 21505,38 .DW 727 ; nAdc= 747, f= 22008,25 .DW 711 ; nAdc= 748, f= 22503,52 .DW 696 ; nAdc= 749, f= 22988,51 .DW 681 ; nAdc= 750, f= 23494,86 .DW 667 ; nAdc= 751, f= 23988,01 .DW 653 ; nAdc= 752, f= 24502,3 .DW 640 ; nAdc= 753, f= 25000 .DW 627 ; nAdc= 754, f= 25518,34 .DW 615 ; nAdc= 755, f= 26016,26 .DW 604 ; nAdc= 756, f= 26490,07 .DW 593 ; nAdc= 757, f= 26981,45 .DW 582 ; nAdc= 758, f= 27491,41 .DW 571 ; nAdc= 759, f= 28021,02 .DW 561 ; nAdc= 760, f= 28520,5 .DW 552 ; nAdc= 761, f= 28985,51 .DW 542 ; nAdc= 762, f= 29520,3 .DW 533 ; nAdc= 763, f= 30018,76 .DW 525 ; nAdc= 764, f= 30476,19 .DW 516 ; nAdc= 765, f= 31007,75 .DW 508 ; nAdc= 766, f= 31496,06 .DW 500 ; nAdc= 767, f= 32000 .DW 492 ; nAdc= 768, f= 32520,33 .DW 485 ; nAdc= 769, f= 32989,69 .DW 478 ; nAdc= 770, f= 33472,8 .DW 471 ; nAdc= 771, f= 33970,28 .DW 464 ; nAdc= 772, f= 34482,76 .DW 457 ; nAdc= 773, f= 35010,94 .DW 451 ; nAdc= 774, f= 35476,72 .DW 444 ; nAdc= 775, f= 36036,04 .DW 438 ; nAdc= 776, f= 36529,68 .DW 432 ; nAdc= 777, f= 37037,04 .DW 427 ; nAdc= 778, f= 37470,73 .DW 421 ; nAdc= 779, f= 38004,75 .DW 416 ; nAdc= 780, f= 38461,54 .DW 410 ; nAdc= 781, f= 39024,39 .DW 405 ; nAdc= 782, f= 39506,17 .DW 400 ; nAdc= 783, f= 40000 .DW 395 ; nAdc= 784, f= 40506,33 .DW 390 ; nAdc= 785, f= 41025,64 .DW 386 ; nAdc= 786, f= 41450,78 .DW 381 ; nAdc= 787, f= 41994,75 .DW 376 ; nAdc= 788, f= 42553,19 .DW 372 ; nAdc= 789, f= 43010,75 .DW 368 ; nAdc= 790, f= 43478,26 .DW 364 ; nAdc= 791, f= 43956,04 .DW 360 ; nAdc= 792, f= 44444,44 .DW 356 ; nAdc= 793, f= 44943,82 .DW 352 ; nAdc= 794, f= 45454,55 .DW 348 ; nAdc= 795, f= 45977,01 .DW 344 ; nAdc= 796, f= 46511,63 .DW 340 ; nAdc= 797, f= 47058,82 .DW 337 ; nAdc= 798, f= 47477,74 .DW 333 ; nAdc= 799, f= 48048,05 .DW 330 ; nAdc= 800, f= 48484,85 .DW 327 ; nAdc= 801, f= 48929,66 .DW 323 ; nAdc= 802, f= 49535,6 .DW 320 ; nAdc= 803, f= 50000 .DW 317 ; nAdc= 804, f= 50473,19 .DW 314 ; nAdc= 805, f= 50955,41 .DW 311 ; nAdc= 806, f= 51446,95 .DW 308 ; nAdc= 807, f= 51948,05 .DW 305 ; nAdc= 808, f= 52459,02 .DW 302 ; nAdc= 809, f= 52980,13 .DW 299 ; nAdc= 810, f= 53511,71 .DW 296 ; nAdc= 811, f= 54054,05 .DW 294 ; nAdc= 812, f= 54421,77 .DW 291 ; nAdc= 813, f= 54982,82 .DW 288 ; nAdc= 814, f= 55555,56 .DW 286 ; nAdc= 815, f= 55944,06 .DW 283 ; nAdc= 816, f= 56537,1 .DW 281 ; nAdc= 817, f= 56939,5 .DW 278 ; nAdc= 818, f= 57553,96 .DW 276 ; nAdc= 819, f= 57971,01 .DW 274 ; nAdc= 820, f= 58394,16 .DW 271 ; nAdc= 821, f= 59040,59 .DW 269 ; nAdc= 822, f= 59479,55 .DW 267 ; nAdc= 823, f= 59925,09 .DW 264 ; nAdc= 824, f= 60606,06 .DW 262 ; nAdc= 825, f= 61068,7 .DW 260 ; nAdc= 826, f= 61538,46 .DW 258 ; nAdc= 827, f= 62015,5 .DW 256 ; nAdc= 828, f= 62500 .DW 254 ; nAdc= 829, f= 62992,13 .DW 252 ; nAdc= 830, f= 63492,06 .DW 250 ; nAdc= 831, f= 64000 .DW 248 ; nAdc= 832, f= 64516,13

http://www.avr-asm-tutorial.net/avr_de/rechteckgen/rectgen_m8_v1_freqtab.html (1 of 2)1/20/2009 7:39:25 PM

Rechteckgenerator ATmega8 - Quellcode Frequenztabelle

.DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW .DW

246 ; nAdc= 833, f= 65040,65 244 ; nAdc= 834, f= 65573,77 242 ; nAdc= 835, f= 66115,7 241 ; nAdc= 836, f= 66390,04 239 ; nAdc= 837, f= 66945,61 237 ; nAdc= 838, f= 67510,55 235 ; nAdc= 839, f= 68085,11 234 ; nAdc= 840, f= 68376,07 232 ; nAdc= 841, f= 68965,52 230 ; nAdc= 842, f= 69565,22 229 ; nAdc= 843, f= 69869 227 ; nAdc= 844, f= 70484,58 225 ; nAdc= 845, f= 71111,11 224 ; nAdc= 846, f= 71428,57 222 ; nAdc= 847, f= 72072,07 221 ; nAdc= 848, f= 72398,19 219 ; nAdc= 849, f= 73059,36 218 ; nAdc= 850, f= 73394,5 216 ; nAdc= 851, f= 74074,07 215 ; nAdc= 852, f= 74418,6 213 ; nAdc= 853, f= 75117,37 212 ; nAdc= 854, f= 75471,7 211 ; nAdc= 855, f= 75829,38 209 ; nAdc= 856, f= 76555,02 208 ; nAdc= 857, f= 76923,08 206 ; nAdc= 858, f= 77669,9 205 ; nAdc= 859, f= 78048,78 204 ; nAdc= 860, f= 78431,37 203 ; nAdc= 861, f= 78817,73 201 ; nAdc= 862, f= 79601,99 200 ; nAdc= 863, f= 80000 199 ; nAdc= 864, f= 80402,01 198 ; nAdc= 865, f= 80808,08 196 ; nAdc= 866, f= 81632,65 195 ; nAdc= 867, f= 82051,28 194 ; nAdc= 868, f= 82474,23 193 ; nAdc= 869, f= 82901,55 192 ; nAdc= 870, f= 83333,33 190 ; nAdc= 871, f= 84210,53 189 ; nAdc= 872, f= 84656,08 188 ; nAdc= 873, f= 85106,38 187 ; nAdc= 874, f= 85561,5 186 ; nAdc= 875, f= 86021,51 185 ; nAdc= 876, f= 86486,49 184 ; nAdc= 877, f= 86956,52 183 ; nAdc= 878, f= 87431,69 182 ; nAdc= 879, f= 87912,09 181 ; nAdc= 880, f= 88397,79 180 ; nAdc= 881, f= 88888,89 179 ; nAdc= 882, f= 89385,47 178 ; nAdc= 883, f= 89887,64 177 ; nAdc= 884, f= 90395,48 176 ; nAdc= 885, f= 90909,09 175 ; nAdc= 886, f= 91428,57 174 ; nAdc= 887, f= 91954,02 173 ; nAdc= 888, f= 92485,55 172 ; nAdc= 889, f= 93023,26 171 ; nAdc= 890, f= 93567,25 170 ; nAdc= 891, f= 94117,65 169 ; nAdc= 892, f= 94674,56 168 ; nAdc= 893, f= 95238,1 168 ; nAdc= 894, f= 95238,1 167 ; nAdc= 895, f= 95808,38 166 ; nAdc= 896, f= 96385,54 165 ; nAdc= 897, f= 96969,7 164 ; nAdc= 898, f= 97560,98 163 ; nAdc= 899, f= 98159,51 162 ; nAdc= 900, f= 98765,43 162 ; nAdc= 901, f= 98765,43 161 ; nAdc= 902, f= 99378,88 160 ; nAdc= 903, f= 100000 152 ; nAdc= 904, f= 105263,16 145 ; nAdc= 905, f= 110344,83 139 ; nAdc= 906, f= 115107,91 133 ; nAdc= 907, f= 120300,75 128 ; nAdc= 908, f= 125000 123 ; nAdc= 909, f= 130081,3 119 ; nAdc= 910, f= 134453,78 116 ; nAdc= 911, f= 137931,03 113 ; nAdc= 912, f= 141592,92 112 ; nAdc= 913, f= 142857,14 111 ; nAdc= 914, f= 144144,14 110 ; nAdc= 915, f= 145454,55 109 ; nAdc= 916, f= 146788,99 108 ; nAdc= 917, f= 148148,15 107 ; nAdc= 918, f= 149532,71 106 ; nAdc= 919, f= 150943,4 105 ; nAdc= 920, f= 152380,95 104 ; nAdc= 921, f= 153846,15 103 ; nAdc= 922, f= 155339,81 102 ; nAdc= 923, f= 156862,75 101 ; nAdc= 924, f= 158415,84 100 ; nAdc= 925, f= 160000 99 ; nAdc= 926, f= 161616,16 98 ; nAdc= 927, f= 163265,31 97 ; nAdc= 928, f= 164948,45 96 ; nAdc= 929, f= 166666,67 95 ; nAdc= 930, f= 168421,05 94 ; nAdc= 931, f= 170212,77 93 ; nAdc= 932, f= 172043,01 92 ; nAdc= 933, f= 173913,04 91 ; nAdc= 934, f= 175824,18 90 ; nAdc= 935, f= 177777,78 89 ; nAdc= 936, f= 179775,28 88 ; nAdc= 937, f= 181818,18 87 ; nAdc= 938, f= 183908,05 86 ; nAdc= 939, f= 186046,51 85 ; nAdc= 940, f= 188235,29 84 ; nAdc= 941, f= 190476,19 83 ; nAdc= 942, f= 192771,08 82 ; nAdc= 943, f= 195121,95 81 ; nAdc= 944, f= 197530,86 80 ; nAdc= 945, f= 200000 79 ; nAdc= 946, f= 202531,65 78 ; nAdc= 947, f= 205128,21 77 ; nAdc= 948, f= 207792,21 76 ; nAdc= 949, f= 210526,32 75 ; nAdc= 950, f= 213333,33 74 ; nAdc= 951, f= 216216,22 73 ; nAdc= 952, f= 219178,08 72 ; nAdc= 953, f= 222222,22 71 ; nAdc= 954, f= 225352,11 70 ; nAdc= 955, f= 228571,43 69 ; nAdc= 956, f= 231884,06 68 ; nAdc= 957, f= 235294,12 67 ; nAdc= 958, f= 238805,97 66 ; nAdc= 959, f= 242424,24 65 ; nAdc= 960, f= 246153,85 64 ; nAdc= 961, f= 250000 63 ; nAdc= 962, f= 253968,25 62 ; nAdc= 963, f= 258064,52 61 ; nAdc= 964, f= 262295,08 60 ; nAdc= 965, f= 266666,67 59 ; nAdc= 966, f= 271186,44 58 ; nAdc= 967, f= 275862,07 57 ; nAdc= 968, f= 280701,75 56 ; nAdc= 969, f= 285714,29 55 ; nAdc= 970, f= 290909,09 54 ; nAdc= 971, f= 296296,3 53 ; nAdc= 972, f= 301886,79 52 ; nAdc= 973, f= 307692,31 51 ; nAdc= 974, f= 313725,49 50 ; nAdc= 975, f= 320000 49 ; nAdc= 976, f= 326530,61 48 ; nAdc= 977, f= 333333,33 47 ; nAdc= 978, f= 340425,53 46 ; nAdc= 979, f= 347826,09 45 ; nAdc= 980, f= 355555,56 44 ; nAdc= 981, f= 363636,36 43 ; nAdc= 982, f= 372093,02 42 ; nAdc= 983, f= 380952,38 41 ; nAdc= 984, f= 390243,9 40 ; nAdc= 985, f= 400000 39 ; nAdc= 986, f= 410256,41 38 ; nAdc= 987, f= 421052,63 37 ; nAdc= 988, f= 432432,43 36 ; nAdc= 989, f= 444444,44 35 ; nAdc= 990, f= 457142,86 34 ; nAdc= 991, f= 470588,24 33 ; nAdc= 992, f= 484848,48 32 ; nAdc= 993, f= 500000 31 ; nAdc= 994, f= 516129,03 30 ; nAdc= 995, f= 533333,33 29 ; nAdc= 996, f= 551724,14 28 ; nAdc= 997, f= 571428,57 27 ; nAdc= 998, f= 592592,59 26 ; nAdc= 999, f= 615384,62 25 ; nAdc= 1000, f= 640000 24 ; nAdc= 1001, f= 666666,67 23 ; nAdc= 1002, f= 695652,17 22 ; nAdc= 1003, f= 727272,73 21 ; nAdc= 1004, f= 761904,76 20 ; nAdc= 1005, f= 800000 19 ; nAdc= 1006, f= 842105,26 18 ; nAdc= 1007, f= 888888,89 17 ; nAdc= 1008, f= 941176,47 16 ; nAdc= 1009, f= 1000000 15 ; nAdc= 1010, f= 1066666,67 14 ; nAdc= 1011, f= 1142857,14 13 ; nAdc= 1012, f= 1230769,23 12 ; nAdc= 1013, f= 1333333,33 11 ; nAdc= 1014, f= 1454545,45 10 ; nAdc= 1015, f= 1600000 9 ; nAdc= 1016, f= 1777777,78 8 ; nAdc= 1017, f= 2000000 7 ; nAdc= 1018, f= 2285714,29 6 ; nAdc= 1019, f= 2666666,67 5 ; nAdc= 1020, f= 3200000 4 ; nAdc= 1021, f= 4000000 3 ; nAdc= 1022, f= 5333333,33 2 ; nAdc= 1023, f= 8000000

©2006 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/rechteckgen/rectgen_m8_v1_freqtab.html (2 of 2)1/20/2009 7:39:25 PM

40 MHz-Frequenzzähler mit ATmega8, Quellcode

Pfad: Home => AVR-Übersicht => Anwendungen => Frequenzzähler => Software

Software für den Frequenzzähler

; **************************************************** ; * Frequenzzaehler, Drehzahlmesser und Voltmeter * ; * fuer ATmega8 bei 16 MHz Taktfrequenz (Quarzosz.) * ; * ohne Autorange, mit Vorteiler /1 oder /16 * ; * Version 0.3 (C)2009 by info@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 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<<CS11) ; CTC and counter prescaler = 8 .IF cFreq>2097120 .SET cCmp1F = cFreq/256 ; CTC compare value with counter prescaler = 64 .SET cPre1F = (1<<WGM12)|(1<<CS11)|(1<<CS10) ; counter prescaler = 64 .ENDIF .IF cFreq>16776960 .SET cCmp1F = cFreq/1024 ; CTC compare value with counter prescaler = 256 .SET cPre1F = (1<<WGM12)|(1<<CS12) ; counter prescaler = 256 .ENDIF ; calculating the CTC and counter prescaler values for TC2 (LCD/ UART update) .SET cCmp2 = cFreq/8000 .SET cPre2 = (1<<CS21) ; counter prescaler = 8 .IF cFreq>2040000 .SET cCmp2 = cFreq/32000 .SET cPre2 = (1<<CS21)|(1<<CS20) ; counter prescaler = 32 .ENDIF .IF cFreq>8160000 .SET cCmp2 = cFreq/64000 .SET cPre2 = (1<<CS22) ; counter prescaler = 64 .ENDIF .IF cFreq>16320000 .SET cCmp2 = cFreq/128000 ; counter prescaler = 128 .SET cPre2 = (1<<CS22)|(1<<CS20) .ENDIF ; ; Uart constants ; .IF cUart .EQU cNul = $00 .EQU cClrScr = $0C .EQU cCr = $0D .EQU cLf = $0A .ENDIF ; ; Debug definitions for testing ; ; (none) ; ; ================================================ ; Register definitons ; ================================================ ; ; R0 used for LPM and for calculation purposes .DEF rRes1 = R1 ; Result byte 1 .DEF rRes2 = R2 ; Result byte 2 .DEF rRes3 = R3 ; Result byte 3 .DEF rRes4 = R4 ; Result byte 4 .DEF rDiv1 = R5 ; Divisor byte 1 .DEF rDiv2 = R6 ; Divisor byte 2 .DEF rDiv3 = R7 ; Divisor byte 3 .DEF rDiv4 = R8 ; Divisor byte 4 .DEF rCpy1 = R9 ; Copy byte 1 .DEF rCpy2 = R10 ; Copy byte 2 .DEF rCpy3 = R11 ; Copy byte 3 .DEF rCpy4 = R12 ; Copy byte 4 .DEF rCtr1 = R13 ; Counter/Timer byte 1 .DEF rCtr2 = R14 ; Counter/Timer byte 2 .DEF rCtr3 = R15 ; Counter/Timer byte 3 .DEF rmp = R16 ; Multipurpose register outside interrupts .DEF rimp = R17 ; Multipurpose register inside interrupts .DEF rSreg = R18 ; Save status register inside interrupts .DEF rTDiv = R19 ; Internal divider for TC2 count down .DEF rMode = R20 ; Current mode of operation .DEF rNMode = R21 ; Number of inadequate measurements .DEF rir = R22 ; interrim calculation register .DEF rFlg = R23 ; Flag register .EQU bCyc = 2 ; measure cycle ended .EQU bMode = 3 ; measuring mode, 1 = frequency, 0 = time .EQU bEdge = 4 ; measured edge, 1 = rising, 0 = falling .EQU bOvf = 5 ; overflow bit .EQU bAdc = 6 ; ADC conversion complete flag bit .EQU bUartRxLine = 7 ; Uart line complete flag bit .DEF rDelL = R24 ; delay counter for LCD, LSB .DEF rDelH = R25 ; dto., MSB ; X = R26..R27 used for calculation purposes ; Y = R28..R29: free ; Z = R30..R31 used for LPM and calculation purposes ; ; ================================================ ; SRAM definitions ; ================================================ ; .DSEG .ORG Sram_Start ; ; Result display space in SRAM ; sResult: .BYTE 32 ; ; Uart receive buffer space in SRAM ; sUartRxBs is buffer start ; sUartRxBe is buffer end ; sUartRxBp is buffer input position ; .IF cUart .EQU UartRxbLen = 38 ; Buffer length in bytes ; sUartFlag: ; flag register for Uart .BYTE 1 .EQU bUMonU = 0 ; displays voltage over Uart .EQU bUMonF = 1 ; displays frequency over Uart ; free: bits 2..7 sUartMonUCnt: ; counter for Monitoring voltage .BYTE 1 sUartMonURpt: ; counter preset for monitoring voltage .BYTE 1 sUartMonFCnt: ; counter for Monitoring frequency .BYTE 1 sUartMonFRpt: ; counter preset for monitoring voltage .BYTE 1 sUartRxBp: ; buffer pointer .BYTE 1 sUartRxBs: ; buffer .BYTE UartRxbLen sUartRxBe: ; buffer end .ENDIF ; ; Main interval timer characteristics ; sTMeas: ; ms per measuring interval (default: 250) .BYTE 1 ; ; ADC result ; sAdc0L: ; ADC result, channel 0, LSB .BYTE 1 sAdc0H: ; ADC result, channel 0, MSB .BYTE 1 sAdc1L: ; ADC result, channel 1, LSB .BYTE 1 sAdc1H: ; ADC result, channel 1, MSB .BYTE 1 ; ; Interim storage for counter value during time measurement ; sCtr: .BYTE 4 ; ; ================================================ ; Selected mode flags ; ================================================ ; ; Mode Measuring Prescale Display ; --------------------------------------------; 0 Frequency 16 Frequency ; 1 Frequency 1 Frequency ; 2 Time HL 1 Frequency ; 3 Time HL 1 Rounds per Minute ; 4 Time HL 1 Time ; 5 Time H 1 Time ; 6 Time L 1 Time ; 7 PW ratio H 1 Pulse width ratio H % ; 8 PW ratio L 1 Pulse width ratio L % ; 9 none Voltage only ; (for a single line LCD) ; .EQU cModeFrequency16 = 0 .EQU cModeFrequency = 1 .EQU cModeTimeFreq = 2 .EQU cModeTimeRpm = 3 .EQU cModeTimeTimeHL = 4 .EQU cModeTimeTimeH = 5 .EQU cModeTimeTimeL = 6 .EQU cModeTimePwrH = 7 .EQU cModeTimePwrL = 8 .EQU cModeVoltage = 9 ; sModeSlct: ; Selected mode .BYTE 1 sModeNext: ; next selected mode .BYTE 1 ; ; ================================================== ; Info on timer and counter interrupt operation ; ================================================== ; ; Clock => 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<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)| (1<<ADPS1)|(1<<ADPS0) out ADCSRA,rimp ; start ADC conversion lds rTDiv,sTMeas ; restart interval timer TC2CmpInt1: out SREG,rSreg ; restore SREG reti ; ; External Interrupt INT0 Service Routine ; active in modes 2 to 6 (measuring the signal duration), ; detects positive going edges of the input ; INT1, TC1 is in free running mode, ; reads the current counter state of TC1, ; copies it to the result registers, ; clears the counter and restarts it ; Int0Int: in rSreg,SREG ; 1, save SREG sbrc rFlg,bCyc ; 2/3, check if cycle flag signals ok for copy rjmp Int0Int1 ; 4, no, last result hasn't been read in rCpy1,TCNT1L ; 4, read timer 1 LSB in rCpy2,TCNT1H ; 5, dto., MSB mov rCpy3,rCtr2 ; 6, copy the counter bytes mov rCpy4,rCtr3 ; 7 sbr rFlg,1<<bCyc ; 8, set cycle end flag bit cbr rFlg,1<<bEdge ; 9, set falling edge sbic PIND,2 ; 10/11, check if input = 0 sbr rFlg,1<<bEdge ; 11, no, set edge flag to rising Int0Int1: ; 4/11 ldi rimp,0 ; 5/12, reset the timer out TCNT1H,rimp ; 6/13, set TC1 zero to restart out TCNT1L,rimp ; 7/14 mov rCtr1,rimp ; 8/15, clear the upper bytes mov rCtr2,rimp ; 9/16 mov rCtr3,rimp ; 10/17 out SREG,rSreg ; 11/18, restore SREG reti ; 15/22 ; ; TC1 Compare Match A Interrupt Service Routine ; active in modes 0 and 1 (measuring the number of ; sigals on the T1 input), timeout every 0.25s, ; reads the counter TC0, copies the count to ; the result registers and clears TC0 ; Tc1CmpAInt: in rSreg,SREG ; 1, save SREG sbrc rFlg,bCyc ; 2/3, check if cycle flag signals ok for copy rjmp TC1CmpAInt1 ; 4, no, last result hasn't been read in rCpy1,TCNT0 ; 4, read counter TC0 mov rCpy2,rCtr1 ; 5, copy counter bytes to result mov rCpy3,rCtr2 ; 6 mov rCpy4,rCtr3 ; 7 sbr rFlg,1<<bCyc ; 8, set cycle end flag bit Tc1CmpAInt1: ; 4/8 ldi rimp,0 ; 5/9, clear counter out TCNT0,rimp ; 6/10 mov rCtr1,rimp ; 7/11, clear counter bytes mov rCtr2,rimp ; 8/12 mov rCtr3,rimp ; 9/13 out SREG,rSreg ; 10/14, restore SREG reti ; 14/18 ; ; TC1 Overflow Interrupt Service Routine ; active in modes 2 to 6 counting clock cycles to measure time ; increases the upper bytes and detects overflows ; Tc1OvfInt: in rSreg,SREG ; 1, save SREG inc rCtr2 ; 2, increase byte 3 of the counter brne Tc1OvfInt1 ; 3/4, no overflow inc rCtr3 ; 4, increase byte 4 of the counter brne Tc1OvfInt1 ; 5/6, no overflow sbr rFlg,(1<<bOvf)|(1<<bCyc) ; 6, set overflow and end of cycle bit Tc1OvfInt1: ; 4/6 out SREG,rSreg ; 5/7, restore SREG reti ; 9/11 ; ; TC0 Overflow Interrupt Service Routine ; active in modes 0 and 1 counting positive edges on T1 ; increases the upper bytes and detects overflows ; Tc0OvfInt: in rSreg,SREG ; 1, save SREG inc rCtr1 ; 2, increase byte 2 of the counter brne Tc0OvfInt1 ; 3/4, no overflow inc rCtr2 ; 4, increase byte 3 of the counter brne Tc0OvfInt1 ; 5/6, no overflow inc rCtr3 ; 6, increase byte 4 of the counter brne Tc0OvfInt1 ; 7/8, no overflow sbr rFlg,(1<<bOvf)|(1<<bCyc) ; 8, set overflow bit Tc0OvfInt1: ; 4/6/8 out SREG,rSreg ; 5/7/9, restore SREG reti ; 9/11/13 ; ; Uart RxC Interrupt Service Routine ; receives a character, signals errors, echoes it back, ; puts it into the SRAM line buffer, checks for carriage ; return characters, if yes echoes additional linefeed ; and sets line-complete flag ; .IF cUart SioRxCIsr: in rSreg,SREG ; 1, Save SReg in rimp,UCSRA ; 2, Read error flags andi rimp,(1<<FE)|(1<<DOR)|(1<<PE) ; 3, isolate error bits in rimp,UDR ; 4, read character from UART breq SioRxCIsr1 ; 5/6, no errors ldi rimp,'*' ; 6, signal an error out UDR,rimp ; 7 rjmp SioRxCIsr4 ; 9, return from int SioRxCIsr1: ; 6 out UDR,rimp ; 7, echo the character push ZH ; 9, Save Z register push ZL ; 11 ldi ZH,HIGH(sUartRxBs) ; 12, Load Position for next RX char lds ZL,sUartRxBp ; 14 st Z+,rimp ; 16, save char in buffer cpi ZL,LOW(sUartRxBe+1) ; 17, End of buffer? brcc SioRxCIsr2 ; 18/19, Buffer overflow sts sUartRxBp,ZL ; 20, Save next pointer position SioRxCIsr2: ; 19/20 cpi rimp,cCr ; 20/21, Carriage Return? brne SioRxCIsr3 ; 21/22/23, No, go on ldi rimp,cLf ; 22/23, Echo linefeed out UDR,rimp ; 23/24 sbr rFlg,(1<<bUartRxLine) ; 24/25, Set line complete flag rjmp SioRxCIsr3a SioRxCIsr3: ; 22/23/24/25 cpi rimp,cLf brne SioRxCIsr3a sbr rFlg,(1<<bUartRxLine) SioRxCIsr3a: pop ZL ; 24/25/26/27, restore Z-register pop ZH ; 26/27/28/29 SioRxCIsr4: ; 9/26/27/28/29 out SREG,rSreg ; 10/27/28/29/30, restore SREG reti ; 14/31/32/33/34, return from Int .ENDIF ; ; ADC has completed a conversion ; used in all modes, ADC has completed a conversion ; if ADMUX channel is 0 then set ADMUX=1 and start ; another coversion ; if ADMUX channel is 1 he set ADMUX=0, disable the ADC ; and set teh ADC cycle end flag ; .EQU cStopAdc = (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) .EQU cStartAdc = (1<<ADEN)|(1<<ADSC)|(1<<ADIE)|cStopAdc AdcCcInt: in rSreg,SREG ; 1, save SREG in rimp,ADMUX ; 2, read the current channel info sbrc rimp,0 ; 3/4, jump if channel = 0 rjmp AdcCcInt1 ; 5 sbr rimp,1 ; 5, set channel MUX to one out ADMUX,rimp ; 6 in rimp,ADCL ; 7, read result LSB sts sAdc0L,rimp ; 9, store result LSB in rimp,ADCH ; 10, read result MSB sts sAdc0H,rimp ; 12, store result MSB ldi rimp,cStartAdc out ADCSRA,rimp ; 14, start next conversion out SREG,rSreg ; 15, restore SREG reti ; 19 AdcCcInt1: ; 5 cbr rimp,1 ; 6, set MUX to channel 0 out ADMUX,rimp ; 7 in rimp,ADCL ; 8, read result LSB sts sAdc1L,rimp ; 10, store result LSB in rimp,ADCH ; 11, read result MSB sts sAdc1H,rimp ; 13, store result MSB sbr rFlg,1<<bAdc ; 14, set flag bit ldi rimp,cStopAdc ; 15, ADC off out ADCSRA,rimp ; 16, switch ADC off out SREG,rSreg ; 17, restore SREG reti ; 21 ; ; ================================================ ; Common subroutines ; ================================================ ; ; Setting timer/counter modes for measuring ; SetModeNext: rcall ClrTc ; clear the timers TC0 and TC1, disable INT0 lds rmp,sModeNext ; read next mode mov rMode,rmp ; copy to current mode ldi ZH,HIGH(SetModeTab) ldi ZL,LOW(SetModeTab) add ZL,rmp ldi rmp,0 adc ZH,rmp ijmp ; Table mode setting SetModeTab: rjmp SetMode0 ; f div 16, f rjmp SetMode1 ; f, f rjmp SetModeT ; t, f rjmp SetModeT ; t, u rjmp SetModeT ; t, t rjmp SetModeE ; th, t rjmp SetModeE ; tl, t rjmp SetModeE ; th, p rjmp SetModeE ; tl, p ret ; U, U ; ; Set counters/timers to mode 0 ; TC0 counts input signals (positive edges) ; TC1 times the gate at 250 ms ; INT0 disabled ; SetMode0: cbi pPresc,bPresc ; enable prescaler rjmp SetModeF ; frequency measurement ; ; Set counters/timers to mode 1 ; SetMode1: sbi pPresc,bPresc ; disable prescaler ; Set timer/counter mode to frequency measurement SetModeF: ldi rmp,HIGH(cCmp1F) ; set the compare match high value out OCR1AH,rmp ldi rmp,LOW(cCmp1F) ; set the compare match low value out OCR1AL,rmp ldi rmp,0xFF ; disable the compare match B out OCR1BH,rmp out OCR1BL,rmp ldi rmp,0 ; CTC mode out TCCR1A,rmp ldi rmp,cPre1F ; set the prescaler value for TC1 out TCCR1B,rmp ldi rmp,(1<<CS02)|(1<<CS01)|(1<<CS00) ; count rising edges on T0 out TCCR0,rmp ldi rmp,(1<<OCIE2)|(1<<OCIE1A)|(1<<TOIE0) ; enable TC2Cmp, TC1CmpAInt and TC0OverflowInt out TIMSK,rmp ret ; ; Set timer/counter mode to time measurement ; SetModeT: sbi pPresc,bPresc ; disable prescaler ldi rmp,0 ; timing mode out TCCR1A,rmp ldi rmp,1<<CS10 ; count with prescaler = 1 out TCCR1B,rmp ldi rmp,(1<<SE)|(1<<ISC01)|(1<<ISC00) ; sleep enable, positive edges on INT0 interrupt out MCUCR,rmp ldi rmp,1<<INT0 ; enable INT0 interrupt out GICR,rmp ldi rmp,(1<<OCIE2)|(1<<TOIE1) ; enable TC2Cmp, TC1Ovflw out TIMSK,rmp ret ; ; Set timer/counter mode to time measurement, all edges ; SetModeE: sbi pPresc,bPresc ; disable prescaler ldi rmp,0 ; timing mode out TCCR1A,rmp ldi rmp,1<<CS10 ; count with prescaler = 1 out TCCR1B,rmp ldi rmp,(1<<SE)|(1<<ISC00) ; sleep enable, any logical change on INT0 interrupts out MCUCR,rmp ldi rmp,1<<INT0 ; enable INT0 interrupt out GICR,rmp ldi rmp,(1<<OCIE2)|(1<<TOIE1) ; enable TC2Cmp, TC1Ovflw out TIMSK,rmp ret ; ; ; clears the timers and resets the upper bytes ; ClrTc: clr rmp ; disable INT0 out GICR,rmp clr rmp ; stop the counters/timers out TCCR0,rmp ; stop TC0 counting/timing out TCCR1B,rmp ; stop TC1 counting/timing out TCNT0,rmp ; clear TC0 out TCNT1L,rmp ; clear TC1 out TCNT1H,rmp clr rCtr1 ; clear upper bytes clr rCtr2 clr rCtr3 ldi rmp,1<<OCIE2 ; enable only output compare of TC2 ints out TIMSK,rmp ; timer int disable ret ; ; ======================================================= ; Math routines ; ======================================================= ; ; Divides cFreq/256 by the timer value in rDiv4:rDiv3:rDiv2:rDiv1 ; yields frequency in R4:R3:R2:(Fract):R1 ; Divide: clr rmp ; rmp:R0:ZH:ZL:XH:XL is divisor clr R0 clr ZH ldi ZL,BYTE3(cFreq/256) ; set divisor ldi XH,BYTE2(cFreq/256) ldi XL,BYTE1(cFreq/256) clr rRes1 ; set result inc rRes1 clr rRes2 clr rRes3 clr rRes4 Divide1: lsl XL ; multiply divisor by 2 rol XH rol ZL rol ZH rol R0 rol rmp cp ZL,rDiv1 ; compare with divident cpc ZH,rDiv2 cpc R0,rDiv3 cpc rmp,rDiv4 brcs Divide2 sub ZL,rDiv1 sbc ZH,rDiv2 sbc R0,rDiv3 sbc rmp,rDiv4 sec rjmp Divide3 Divide2: clc Divide3: rol rRes1 rol rRes2 rol rRes3 rol rRes4 brcc Divide1 ret ; ; Multiply measured time in rRes4:rRes3:rRes2:rRes1 by 65536 / fq (MHz) ; rmp:R0 are the upper bytes of the input ; ZH:ZL:rDiv4:rDiv3:rDiv2:rDiv1 is the interim result ; XH:XL is the multiplicator ; result is in rRes4:rRes3:rRes2:rRes1 ; .EQU cMulti = 65536000 / (cFreq/1000) ; Multiply: ldi XH,HIGH(cMulti) ; set multiplicator ldi XL,LOW(cMulti) clr ZH clr ZL clr rDiv4 clr rDiv3 clr rDiv2 clr rDiv1 clr R0 clr rmp Multiply1: cpi XL,0 brne Multiply2 cpi XH,0 breq Multiply4 Multiply2: lsr XH ror XL brcc Multiply3 add rDiv1,rRes1 adc rDiv2,rRes2 adc rDiv3,rRes3 adc rDiv4,rRes4 adc ZL,R0 adc ZH,rmp Multiply3: lsl rRes1 rol rRes2 rol rRes3 rol rRes4 rol R0 rol rmp rjmp Multiply1 Multiply4: ldi rmp,128 ; round result clr R0 add rDiv2,rmp adc rDiv3,R0 adc rDiv4,R0 adc ZL,R0 adc ZH,R0 mov rRes1,rDiv3 ; move result mov rRes2,rDiv4 mov rRes3,ZL mov rRes4,ZH ret ; ; Display seconds at buffer end ; DisplSec: .IF ! cDisplay8 ldi rmp,' ' st X+,rmp ldi rmp,'u' st X+,rmp ldi rmp,'s' st X+,rmp ldi rmp,' ' st X,rmp .ENDIF ret ; ; An overflow has occurred during pulse width calculation ; PulseOvflw: ldi XH,HIGH(sResult) ldi XL,LOW(sResult) st X+,rmp .IF cDisplay8 ldi ZH,HIGH(2*TxtPOvflw8) ldi ZL,LOW(2*TxtPOvflw8) ldi rmp,7 .ELSE ldi ZH,HIGH(2*TxtPOvflw16) ldi ZL,LOW(2*TxtPOvflw16) ldi rmp,15 .ENDIF PulseOvflw1: lpm adiw ZL,1 st X+,R0 dec rmp brne PulseOvflw1 ret .IF cDisplay8 TxtPOvflw8: .DB ":error! " .ELSE TxtPOvflw16: .DB ":error calcul.! " .ENDIF ; ; ====================================================== ; Pulse width calculations ; ====================================================== ; ; Calculate the pulse width ratio ; active cycle time is in rDelH:rDelL:R0:rmp ; total cycle time is in rDiv ; result will be in rRes ; overflow: carry flag is set ; CalcPwO: ; overflow sec ret CalcPw: mov rRes1,rmp ; copy active cycle time to rRes mov rRes2,R0 mov rRes3,rDelL mov rRes4,rDelH lsl rRes1 ; * 2 rol rRes2 rol rRes3 rol rRes4 brcs CalcPwO ; overflow lsl rRes1 ; * 4 rol rRes2 rol rRes3
http://www.avr-asm-tutorial.net/avr_de/fcount/fcount_m8_v3.html (1 of 4)1/20/2009 7:39:38 PM

40 MHz-Frequenzzähler mit ATmega8, Quellcode

rol rRes4 brcs CalcPwO ; overflow lsl rRes1 ; * 8 rol rRes2 rol rRes3 rol rRes4 brcs CalcPwO ; overflow mov XL,rRes1 ; copy to Z:X mov XH,rRes2 mov ZL,rRes3 mov ZH,rRes4 lsl rRes1 ; * 16 rol rRes2 rol rRes3 rol rRes4 brcs CalcPwO add rRes1,XL ; * 24 adc rRes2,XH adc rRes3,ZL adc rRes4,ZH clr ZH ; clear the four MSBs of divisor clr ZL clr XH mov XL,rDelH ; * 256 mov rDelH,rDelL mov rDelL,R0 mov R0,rmp clr rmp lsl R0 ; * 512 rol rDelL rol rDelH rol XL rol XH lsl R0 ; * 1024 rol rDelL rol rDelH 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 > 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<<bEdge rcall CycleM7 bploop: rjmp bploop .ENDIF ; ; Clear the output storage ; ldi ZH,HIGH(sResult) ldi ZL,LOW(sResult) ldi rmp,' ' mov R0,rmp ldi rmp,32 main1: st Z+,R0 dec rmp brne main1 ; ; Init the Uart ; .IF cUart rcall UartInit ldi rmp,1<<bUMonU ; monitor U over Uart sts sUartFlag,rmp ldi rmp,20 ; 5 seconds sts sUartMonURpt,rmp ; set repeat default value ldi rmp,1 sts sUartMonUCnt,rmp ldi rmp,4 ; 1 seconds sts sUartMonFCnt,rmp .ENDIF ; ; Init the LCD ; .IF cDisplay rcall LcdInit .ENDIF ; ; Disable the Analog comparator ; ldi rmp,1<<ACD out ACSR,rmp ; ; Disable the external prescaler by 16 ; sbi pPrescD,bPresc ; set prescaler port bit to output sbi pPresc,bPresc ; disable the prescaler ; ; Init the ADC ; ldi rmp,(1<<REFS1)|(1<<REFS0) ; int.ref, channel 0 out ADMUX,rmp ldi rmp,(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) ; prescaler = 128 out ADCSRA,rmp ; ; Start main interval timer ; ldi rmp,cCmp2 ; set Compare Match out OCR2,rmp ldi rmp,cPre2|(1<<WGM21) ; CTC mode and prescaler out TCCR2,rmp ; ; Start timer/counter TC2 interrupts ; ldi rmp,(1<<OCIE2) ; Interrupt mask out TIMSK,rmp ; ; Set initial mode to mode 1 ; ldi rmp,1 ; initial mode = 1 sts sModeNext,rmp rcall SetModeNext ; sei ; enable interrupts ; loop: sleep ; send CPU to sleep nop sbrc rFlg,bCyc ; check cycle end rcall Cycle sbrc rFlg,bAdc rcall Interval .IF cUart sbrc rFlg,bUartRxLine ; check line complete rcall UartRxLine ; call line complete .ENDIF rjmp loop ; go to sleep ; ; Timer interval for calculation and display ; Interval: cbr rFlg,1<<bAdc ; clear flag lds ZL,sAdc1L ; read ADC channel 1 value lds ZH,sAdc1H mov XL,ZL ; copy to X mov XH,ZH lsl ZL ; multiply by 2 rol ZH lsl ZL ; multiply by 4 rol ZH add ZL,XL ; multiply by 5 adc ZH,XH lsr ZH ; multiply by 2.5 ror ZL cpi ZH,10 ; larger than 9? brcs Interval1 ldi ZH,9 ; set to 9 Interval1: sts sModeNext,ZH ; store next mode cp rMode,ZH ; new mode? breq Interval2 ; continue current mode rcall SetModeNext ; start new mode Interval2: .IF cUart || cDisplay lds R0,sAdc0L ; read ADC value lds R1,sAdc0H rcall cAdc2U ; convert to text .IF cDisplay rcall LcdDisplayFT rcall LcdDisplayU .ENDIF .IF cUart rcall UartMonU .ENDIF .ENDIF ret ; ; Frequency/Time measuring cycle ended, calculate results ; Cycle: sbrc rFlg,bOvf ; check overflow rjmp CycleOvf ; jump to overflow mov rRes1,rCpy1 ; copy counter mov rRes2,rCpy2 mov rRes3,rCpy3 mov rRes4,rCpy4 cbr rFlg,(1<<bCyc)|(1<<bOvf) ; clear cycle flag and overflow mov rDiv1,rRes1 ; copy again mov rDiv2,rRes2 mov rDiv3,rRes3 mov rDiv4,rRes4 .IF cUart ldi ZH,HIGH(UartMonF) ; put monitoring frequency on stack ldi ZL,LOW(UartMonF) push ZL push ZH .ENDIF ldi ZH,HIGH(CycleTab) ; point to mode table ldi ZL,LOW(CycleTab) add ZL,rMode ; displace table by mode brcc Cycle1 inc ZH Cycle1: ijmp ; call the calculation routine ; overflow occurred CycleOvf: cbr rFlg,(1<<bCyc)|(1<<bOvf) ; clear cycle flag and overflow ldi XH,HIGH(sResult) ; point to result buffer ldi XL,LOW(sResult) .IF cDisplay8 ldi ZH,HIGH(2*TxtOvf8) ; point to short message ldi ZL,LOW(2*TxtOvf8) ldi rmp,8 .ELSE ldi ZH,HIGH(2*TxtOvf16) ; point to long message ldi ZL,LOW(2*TxtOvf16) ldi rmp,16 .ENDIF CycleOvf1: lpm adiw ZL,1 st X+,R0 dec rmp brne CycleOvf1 ret ; .IF cDisplay8 TxtOvf8: .DB " ovflow" .ELSE TxtOvf16: .DB " overflow " .ENDIF ; Table with routines for the 8 modes CycleTab: rjmp CycleM0 rjmp CycleM1 rjmp CycleM2 rjmp CycleM3 rjmp CycleM4 rjmp CycleM5 rjmp CycleM6 rjmp CycleM7 rjmp CycleM8 ret ; voltage only ; ; Mode 0: Measured prescaled frequency, display frequency ; CycleM0: clr R5 ; for detecting an overflow in R5 lsl R1 ; * 2 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 4 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 8 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 16 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 32 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 64 rol R2 rol R3 rol R4 rol R5 tst R5 ; check overflow breq CycleM0a ; no error rjmp CycleOvf CycleM0a: 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,'F' rjmp DisplMode ; ; Mode 1: Frequency measured, prescale = 1, display frequency ; CycleM1: clr rDiv1 ; detect overflow in rDiv1 lsl rRes1 ; * 2 rol rRes2 rol rRes3 rol rRes4
http://www.avr-asm-tutorial.net/avr_de/fcount/fcount_m8_v3.html (2 of 4)1/20/2009 7:39:38 PM

40 MHz-Frequenzzähler mit ATmega8, Quellcode

rol rDiv1 lsl rRes1 ; * 4 rol rRes2 rol rRes3 rol rRes4 rol rDiv1 tst rDiv1 ; check overflow breq CycleM1a ; no error rjmp CycleOvf CycleM1a: 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,'f' rjmp DisplMode ; ; Mode 2: Time measured, prescale = 1, display frequency ; CycleM2: rcall Divide tst rRes4 brne CycleM2a rcall DisplFrac ldi ZL,'v' rcall DisplMode ret CycleM2a: mov rRes1,rRes2 ; number too big, skip fraction 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>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<<bLcdRs ; set Rs to one out PORTB,rmp ; write to display interface rcall LcdE ; pulse E mov rmp,R0 ; copy original again andi rmp,0x0F ; clear upper nibble sbr rmp,1<<bLcdRs ; set Rs to one out PORTB,rmp ; write to display interface rcall LcdE rcall Delay40us pop rmp ; restore rmp ret ; ; writes the text in flash to the LCD, number of ; characters in rmp ; LcdText: lpm ; read character from flash adiw ZL,1 rcall LcdData4 ; write to rcall delay40us dec rmp brne LcdText ret ; ; Inits the LCD with a 4-bit-interface ; LcdInit: ldi rmp,0x0F | (1<<bLcdE) | (1<<bLcdRs) out DDRB,rmp clr rmp out PORTB,rmp rcall delay15ms ; wait for complete self-init ldi rmp,0x03 ; Function set 8-bit interface rcall LcdRs8 rcall delay4_1ms ; wait for 4.1 ms ldi rmp,0x03 ; Function set 8-bit interface rcall LcdRs8 rcall delay100us ; wait for 100 us ldi rmp,0x03 ; Function set 8-bit interface rcall LcdRs8 rcall delay40us ; delay 40 us ldi rmp,0x02 ; Function set 4-bit-interface rcall LcdRs8 rcall delay40us .IF cDisplay2 ldi rmp,0x28 ; 4-bit-interface, two line display .ELSE ldi rmp,0x20 ; 4-bit-interface, single line display .ENDIF rcall LcdRs4 rcall delay40us ; delay 40 us ldi rmp,0x08 ; display off rcall LcdRs4 rcall delay40us ; delay 40 us ldi rmp,0x01 ; display clear rcall LcdRs4 rcall delay1_64ms ; delay 1.64 ms ldi rmp,0x06 ; increment, don't shift rcall LcdRs4 rcall delay40us ; delay 40 us ldi rmp,0x0C ; display on rcall LcdRs4 rcall delay40us ldi rmp,0x80 ; position on line 1 rcall LcdRs4 rcall delay40us ; delay 40 us .IF cDisplay8 ldi rmp,8 ldi ZH,HIGH(2*LcdInitTxt8) ldi ZL,LOW(2*LcdInitTxt8) .ELSE ldi rmp,16 ldi ZH,HIGH(2*LcdInitTxt16) ldi ZL,LOW(2*LcdInitTxt16) .ENDIF rcall LcdText .IF cDisplay2 ldi rmp,0xC0 ; line 2 rcall LcdRs4 rcall delay40us ; delay 40 us .IF cDisplay8 ldi rmp,8 .ELSE ldi XH,HIGH(sResult+25) ldi XL,LOW(sResult+25) ldi ZH,HIGH(2*LcdInitTxtMode) ldi ZL,LOW(2*LcdInitTxtMode) ldi rmp,6 LcdInitMode: lpm adiw ZL,1 st X+,R0 dec rmp brne LcdInitMode ldi rmp,16 .ENDIF rcall LcdText .ENDIF ret .IF cDisplay8 LcdInitTxt8: .DB "F-CNT V1" .IF cDisplay2 .DB "-DG4FAC-" .ENDIF .ELSE LcdInitTxt16: .DB "Freq-counter V01" .IF cDisplay2 .DB " (C)2005 DG4FAC " LcdInitTxtMode: .DB " Mode=" .ENDIF .ENDIF ; ; Display frequency/time on Lcd ; LcdDisplayFT: .IF ! cDisplay2 ; single line display cpi rMode,cModeVoltage ; voltage display selected? breq LcdDisplayFT2 .ENDIF ldi rmp,$80 ; set display position to line 1 rcall LcdRs4 rcall Delay40us ldi ZH,HIGH(sResult) ; point Z to line buffer ldi ZL,LOW(sResult) .IF cDisplay8 ldi rmp,8 .ELSE ldi rmp,16 .ENDIF LcdDisplayFT1: ld R0,Z+ ; read a char rcall LcdData4 ; display on LCD dec rmp brne LcdDisplayFT1 LcdDisplayFT2: ret ; ; Display voltage on the display ; LcdDisplayU: .IF cDisplay2 ; two-line LCD connected .IF !cDisplay8 lds rmp,sModeNext subi rmp,-'0' sts sResult+31,rmp .ENDIF ldi rmp,$C0 ; output to line 2 .ELSE cpi rMode,cModeVoltage ; check switch brne LcdDisplayU2 ldi rmp,$80 ; output to line 1 .ENDIF rcall LcdRs4 ; set output position rcall Delay40us ldi ZH,HIGH(sResult+16) ; point to result ldi ZL,LOW(sResult+16) .IF cDisplay8 ldi rmp,8 .ELSE ldi rmp,16 .ENDIF LcdDisplayU1: ld R0,Z+ ; read character rcall LcdData4 dec rmp ; next char brne LcdDisplayU1 ; continue with chars LcdDisplayU2: ret ; .ENDIF ; end LCD routines to be included ; ; =========================================== ; Uart routines ; =========================================== ; .IF cUart UartInit: ; Init the Uart on startup .EQU cUbrr = (cFreq/cBaud/16)-1 ; calculating UBRR single speed ldi rmp,LOW(sUartRxBs) ; set buffer pointer to start sts sUartRxBp,rmp ldi rmp,HIGH(cUbrr) ; set URSEL to zero, set baudrate msb out UBRRH,rmp ldi rmp,LOW(cUbrr) ; set baudrate lsb out UBRRL,rmp ldi rmp,(1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0) ; set 8 bit characters out UCSRC,rmp ldi rmp,(1<<RXCIE)|(1<<RXEN)|(1<<TXEN) ; enable RX/TX and RX-Ints out UCSRB,rmp rcall delay10ms ; delay for 10 ms duration ldi ZH,HIGH(2*txtUartInit) ldi ZL,LOW(2*txtUartInit) rjmp UartSendTxt ; ; Uart receive buffer space in SRAM ; sUartRxBs is buffer start ; sUartRxBe is buffer end ; sUartRxBp is buffer input position ; .EQU UartRxbLen = 38 ; Buffer length in bytes ; sUartFlag: ; flag register for Uart ; .BYTE 1 ; .EQU bUMonU = 0 ; displays voltage over Uart ; .EQU bUMonF = 1 ; displays frequency over Uart ; ; free: bits 2..7 ; sUartMonUCnt: ; counter for Monitoring voltage ; .BYTE 1 ; sUartMonURpt: ; counter preset for monitoring voltage ; .BYTE 1 ; sUartRxBp: ; buffer pointer ; .BYTE 1 ; sUartRxBs: ; buffer ; .BYTE UartRxbLen ; sUartRxBe: ; buffer end ; .EQU cNul = $00 ; .EQU cClrScr = $0C ; .EQU cCr = $0D ; .EQU cLf = $0A ; UartRxLine: cbr rFlg,1<<bUartRxLine ; clear line complete flag ldi rmp,LOW(sUartRxBs) ; set buffer pointer to start sts sUartRxBp,rmp ldi ZH,HIGH(UartReturn) ; push return adress to stack ldi ZL,LOW(UartReturn) push ZL push ZH ldi ZH,HIGH(sUartRxBs) ; set Z to Buffer-Start ldi ZL,LOW(sUartRxBs) ld rmp,Z+ ; read first character cpi rmp,'h' ; help? brne UartRxLine1 rjmp UartHelp UartRxLine1: cpi rmp,'?' ; help? brne UartRxLine2 rjmp UartHelp UartRxLine2: cpi rmp,'U' ; monitor U on brne UartRxLine3 rcall UartGetPar sec rjmp UartMonUSetC UartRxLine3: cpi rmp,'u' ; monitor U off brne UartRxLine4 clc rjmp UartMonUSetC UartRxLine4: cpi rmp,'F' ; monitor F on brne UartRxLine5 rcall UartGetPar sec rjmp UartMonFSetC UartRxLine5: cpi rmp,'f' ; monitor f off brne UartRxLine6 clc rjmp UartMonFSetC UartRxLine6: cpi rmp,'p' ; parameter? brne UartRxLine7 rjmp UartMonPar UartRxLine7: ldi ZH,HIGH(2*txtUartUnknown) ; send unknown command ldi ZL,LOW(2*txtUartUnknown) ret UartHelp: ldi ZH,HIGH(2*txtUartHelp) ; send help text ldi ZL,LOW(2*txtUartHelp) ret UartMonUSetC: lds rmp,sUartFlag brcs UartMonUSetC1 cbr rmp,1<<bUMonU ; clear flag sts sUartFlag,rmp ldi ZH,HIGH(2*txtUartUOff) ldi ZL,LOW(2*txtUartUOff) ret UartMonUSetC1: brne UartMonUSetC2 sts sUartMonURpt,R0 sts sUartMonUCnt,R0 UartMonUSetC2: sbr rmp,1<<bUMonU ; set flag sts sUartFlag,rmp ldi ZH,HIGH(2*txtUartUOn) ldi ZL,LOW(2*txtUartUOn) ret UartMonFSetC: lds rmp,sUartFlag brcs UartMonFSetC1 cbr rmp,1<<bUMonF ; clear flag sts sUartFlag,rmp ldi ZH,HIGH(2*txtUartFOff) ldi ZL,LOW(2*txtUartFOff) ret UartMonFSetC1: brne UartMonFSetC2 sts sUartMonFRpt,R0 sts sUartMonFCnt,R0 UartMonFSetC2: sbr rmp,1<<bUMonF ; set flag sts sUartFlag,rmp ldi ZH,HIGH(2*txtUartFOn) ldi ZL,LOW(2*txtUartFOn) ret UartMonPar: ldi ZH,HIGH(2*txtUartNul) ldi ZL,LOW(2*txtUartNul); ldi rmp,'U' rcall UartSendChar ldi rmp,'=' rcall UartSendChar ldi rmp,'$' rcall UartSendChar lds rmp,sUartMonURpt rcall UartHexR ldi rmp,',' rcall UartSendChar ldi rmp,' ' rcall UartSendChar ldi rmp,'F' rcall UartSendChar ldi rmp,'=' rcall UartSendChar ldi rmp,'$' rcall UartSendChar lds rmp,sUartMonFRpt rjmp UartHexR ; ; Get Parameter from line ; UartGetPar: clr R0 ; result register ld rmp,Z+ ; read char cpi rmp,cCr ; carriage return breq UartGetParNoPar cpi rmp,cLf ; line feed breq UartGetParNoPar cpi rmp,'=' ; brne UartGetParErr UartGetPar1: ld rmp,Z+ ; read next char cpi rmp,cCr ; carriage return? breq UartGetPar2 cpi rmp,cLf ; line feed? breq UartGetPar2 subi rmp,'0' ; subtract 0 brcs UartGetParErr cpi rmp,10 ; larger than 9? brcc UartGetParErr mov rir,R0 ; copy to rir lsl R0 ; * 2 brcs UartGetParErr lsl R0 ; * 4 brcs UartGetParErr add R0,rir ; * 5 brcs UartGetParErr lsl R0 ; * 10 brcs UartGetParErr add R0,rmp ; add new decimal brcs UartGetParErr rjmp UartGetPar1 UartGetPar2: sez ret UartGetParErr: ldi ZH,HIGH(2*txtUartErr) ldi ZL,LOW(2*txtUartErr) rcall UartSendTxt UartGetParNoPar: clz ; No parameter set ret ; ; Hex output over Uart, for debugging ; UartHexR: push rmp swap rmp rcall UartHexN pop rmp UartHexN: andi rmp,0x0F subi rmp,-'0' cpi rmp,'9'+1 brcs UartHexN1 subi rmp,-7 UartHexN1: rjmp UartSendChar ret ; ; Return from Uart-Routines, displays text in Z ; UartReturn: rcall UartSendTxt ; send text in Z ldi ZH,HIGH(2*txtUartCursor) ldi ZL,LOW(2*txtUartCursor) rjmp UartSendTxt ; ; Send character in rmp over Uart ; UartSendChar: sbis UCSRA,UDRE ; wait for empty buffer rjmp UartSendChar out UDR,rmp ret ; ; Monitoring the voltage over the Uart ; UartMonU: lds rmp,sUartFlag ; flag register for Uart sbrs rmp,bUMonU ; displays voltage over Uart ret lds rmp,sUartMonUCnt ; read counter dec rmp sts sUartMonUCnt,rmp brne UartMonU2 lds rmp,sUartMonURpt sts sUartMonUCnt,rmp ldi ZH,HIGH(sResult+16) ldi ZL,LOW(sResult+16) ldi rmp,8 UartMonU1: sbis UCSRA,UDRE ; wait for empty buffer rjmp UartMonU1 ld R0,Z+ out UDR,R0 dec rmp brne UartMonU1 ldi rmp,cCr rcall UartSendChar ldi rmp,cLf rjmp UartSendChar UartMonU2: ret ; ; Monitor frequency over UART ; UartMonF: lds rmp,sUartFlag ; flag register for Uart sbrs rmp,bUMonF ; displays frequency over Uart ret lds rmp,sUartMonFCnt ; read counter dec rmp sts sUartMonFCnt,rmp brne UartMonF2 lds rmp,sUartMonFRpt sts sUartMonFCnt,rmp ldi ZH,HIGH(sResult)
http://www.avr-asm-tutorial.net/avr_de/fcount/fcount_m8_v3.html (3 of 4)1/20/2009 7:39:38 PM

40 MHz-Frequenzzähler mit ATmega8, Quellcode

ldi ZL,LOW(sResult) ldi rmp,16 UartMonF1: sbis UCSRA,UDRE ; wait for empty buffer rjmp UartMonF1 ld R0,Z+ out UDR,R0 dec rmp brne UartMonF1 ldi rmp,cCr rcall UartSendChar ldi rmp,cLf rjmp UartSendChar UartMonF2: ret ; ; Send text from flash to UART, null byte ends transmit ; UartSendTxt: lpm ; read character from flash adiw ZL,1 tst R0 ; check end of text breq UartSendTxtRet UartSendTxtWait: sbis UCSRA,UDRE ; wait for empty char rjmp UartSendTxtWait out UDR,R0 ; send char rjmp UartSendTxt UartSendTxtRet: ret ; ; Uart text constants ; txtUartInit: .DB " ",cClrScr .DB "************************************************* ",cCr,cLf .DB "* Frequency- and voltmeter (C)2005 by g.schmidt * ",cCr,cLf .DB "************************************************* ",cCr,cLf txtUartMenue: .DB cCr,cLf,"Commands: <h>elp",cCr,cLf txtUartCursor: .DB cCr,cLf,"i> ",cNul txtUartUnknown: .DB cCr,cLf,"Unknown command!",cNul,cNul txtUartUOff: .DB "Voltage monitoring is off.",cNul,cNul txtUartUOn: .DB "Voltage monitoring is on. ",cNul,cNul txtUartFOff: .DB "Frequency monitoring is off.",cNul,cNul txtUartFOn: .DB "Frequency monitoring is on. ",cNul,cNul txtUartErr: .DB "Error in parameter! ",cNul,cNul txtUartHelp: .DB cCr,cLf,"Help: ",cCr,cLf .DB "U[=N](on) or u(Off): monitor voltage output, N=1..255,",cCr,cLf .DB "F[=N](On) or f(Off): monitor frequency output N=1..255, ",cCr, cLf .DB "p: display monitoring parameters, ",cCr,cLf .DB "h or ?: this text." txtUartNul: .DB cNul,cNul .ENDIF ; ; End of source code ;

©2006-2009 by http://www.avr-asm-tutorial.net

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:

q q q q

q q q

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: r 0: Frequenzmessung mit Vorteiler, Gatezeit 0,25 s, Ergebnis in Hz r 1: Frequenzmessung ohne Vorteiler, Gatezeit 0,25 s, Ergebnis in Hz r 2: Frequenzmessung als Periodenmessung mit Frequenz- umrechnung, Ergebnis in 0,01 Hz bzw. 0,001 Hz r 3: Umdrehungsmessung, ohne Vorteiler, ueber Periodenmessung mit Umrechnung, Ergebnis in Upm r 4: Periodendauer gesamte Schwingung, Ergebnis in Mikrosekunden r 5: Periodendauer High-Periode, Ergebnis in Mikrosekunden r 6: Periodendauer Low-Periode, Ergebnis in Mikrosekunden r 7: Periodenanteil High-Periode, Ergebnis in 0,1% r 8: Periodenanteil Low-Periode, Ergebnis in 0,1% r 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
q q q

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).

0. Inhalt
q

q

q

1. Hardware r 1.1 Eingangsteil r 1.2 Prozessor r 1.3 LCD-Anzeige r 1.4 SIO-Anschluss r 1.5 Aufbauhinweise 2. Bedienung r 2.1 Potentiometer für Mode-Auswahl r 2.2 Spannungsmessung 3. Software r 3.1 Einstellungen vor dem Assemblieren r 3.2 Fuses r 3.3 Kommentierter Quellcode

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 0 1 2 3 4 5 6 7 8 9 Messgröße Frequenz Frequenz Frequenz Umdrehungszahl Periode High-Periode Low-Periode Messmethode Anzeigenformat

Zählung, Vorteiler=16 F=99.999.999 Hz Zählung, Vorteiler=1 f= 9.999.999 Hz Periodenmessung Periodenmessung Periodenmessung Periodenmessung Periodenmessung v= 9.999,999 Hz u= 9.999.999 rpm t=99.999.999 us h=99.999.999 us l=99.999.999.us P=100,0% p=100,0% U=9,999V

High-Periodenanteil Periodenmessung Low-Periodenanteil Periodenmessung Spannung 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:
q q q q q q q q q

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.

3.3 Kommentierter Quellcode
Der Quellcode liegt in HTML-Form (hier) und als Assembler-Quelltext (hier) vor. In der Textdatei LiesMich3.txt sind weitere Hinweise zur Programmierung und Bedienung zusammengestellt.
©2006-2009 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/fcount/fcount_m8.html1/20/2009 7:40:23 PM

http://www.avr-asm-tutorial.net/avr_de/fcount/fcountV03.asm

; **************************************************** ; * Frequenzzaehler, Drehzahlmesser und Voltmeter * ; * fuer ATmega8 bei 16 MHz Taktfrequenz (Quarzosz.) * ; * ohne Autorange, mit Vorteiler /1 oder /16 * ; * Version 0.3 (C)2009 by info@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 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<<CS11) ; CTC and counter prescaler = 8 .IF cFreq>2097120 .SET cCmp1F = cFreq/256 ; CTC compare value with counter prescaler = 64 .SET cPre1F = (1<<WGM12)|(1<<CS11)|(1<<CS10) ; counter prescaler = 64 .ENDIF .IF cFreq>16776960 .SET cCmp1F = cFreq/1024 ; CTC compare value with counter prescaler = 256 .SET cPre1F = (1<<WGM12)|(1<<CS12) ; counter prescaler = 256 .ENDIF ; calculating the CTC and counter prescaler values for TC2 (LCD/UART update) .SET cCmp2 = cFreq/8000 .SET cPre2 = (1<<CS21) ; counter prescaler = 8 .IF cFreq>2040000 .SET cCmp2 = cFreq/32000 .SET cPre2 = (1<<CS21)|(1<<CS20) ; counter prescaler = 32 .ENDIF .IF cFreq>8160000 .SET cCmp2 = cFreq/64000 .SET cPre2 = (1<<CS22) ; counter prescaler = 64 .ENDIF .IF cFreq>16320000 .SET cCmp2 = cFreq/128000 ; counter prescaler = 128 .SET cPre2 = (1<<CS22)|(1<<CS20) .ENDIF ; ; Uart constants ; .IF cUart .EQU cNul = $00 .EQU cClrScr = $0C .EQU cCr = $0D .EQU cLf = $0A .ENDIF ; ; Debug definitions for testing ; ; (none) ; ; ================================================ ; Register definitons ; ================================================ ; ; R0 used for LPM and for calculation purposes .DEF rRes1 = R1 ; Result byte 1 .DEF rRes2 = R2 ; Result byte 2 .DEF rRes3 = R3 ; Result byte 3 .DEF rRes4 = R4 ; Result byte 4 .DEF rDiv1 = R5 ; Divisor byte 1 .DEF rDiv2 = R6 ; Divisor byte 2 .DEF rDiv3 = R7 ; Divisor byte 3 .DEF rDiv4 = R8 ; Divisor byte 4 .DEF rCpy1 = R9 ; Copy byte 1 .DEF rCpy2 = R10 ; Copy byte 2 .DEF rCpy3 = R11 ; Copy byte 3 .DEF rCpy4 = R12 ; Copy byte 4 .DEF rCtr1 = R13 ; Counter/Timer byte 1 .DEF rCtr2 = R14 ; Counter/Timer byte 2 .DEF rCtr3 = R15 ; Counter/Timer byte 3 .DEF rmp = R16 ; Multipurpose register outside interrupts .DEF rimp = R17 ; Multipurpose register inside interrupts .DEF rSreg = R18 ; Save status register inside interrupts .DEF rTDiv = R19 ; Internal divider for TC2 count down .DEF rMode = R20 ; Current mode of operation .DEF rNMode = R21 ; Number of inadequate measurements .DEF rir = R22 ; interrim calculation register .DEF rFlg = R23 ; Flag register .EQU bCyc = 2 ; measure cycle ended .EQU bMode = 3 ; measuring mode, 1 = frequency, 0 = time .EQU bEdge = 4 ; measured edge, 1 = rising, 0 = falling .EQU bOvf = 5 ; overflow bit .EQU bAdc = 6 ; ADC conversion complete flag bit .EQU bUartRxLine = 7 ; Uart line complete flag bit .DEF rDelL = R24 ; delay counter for LCD, LSB .DEF rDelH = R25 ; dto., MSB ; X = R26..R27 used for calculation purposes ; Y = R28..R29: free ; Z = R30..R31 used for LPM and calculation purposes ; ; ================================================ ; SRAM definitions ; ================================================ ; .DSEG .ORG Sram_Start ; ; Result display space in SRAM ; sResult: .BYTE 32 ; ; Uart receive buffer space in SRAM ; sUartRxBs is buffer start ; sUartRxBe is buffer end ; sUartRxBp is buffer input position ; .IF cUart .EQU UartRxbLen = 38 ; Buffer length in bytes ; sUartFlag: ; flag register for Uart .BYTE 1 .EQU bUMonU = 0 ; displays voltage over Uart .EQU bUMonF = 1 ; displays frequency over Uart ; free: bits 2..7 sUartMonUCnt: ; counter for Monitoring voltage .BYTE 1 sUartMonURpt: ; counter preset for monitoring voltage .BYTE 1 sUartMonFCnt: ; counter for Monitoring frequency .BYTE 1 sUartMonFRpt: ; counter preset for monitoring voltage .BYTE 1 sUartRxBp: ; buffer pointer .BYTE 1 sUartRxBs: ; buffer .BYTE UartRxbLen sUartRxBe: ; buffer end .ENDIF ; ; Main interval timer characteristics ; sTMeas: ; ms per measuring interval (default: 250) .BYTE 1 ; ; ADC result ; sAdc0L: ; ADC result, channel 0, LSB .BYTE 1 sAdc0H: ; ADC result, channel 0, MSB .BYTE 1 sAdc1L: ; ADC result, channel 1, LSB .BYTE 1 sAdc1H: ; ADC result, channel 1, MSB .BYTE 1 ; ; Interim storage for counter value during time measurement ; sCtr: .BYTE 4 ; ; ================================================ ; Selected mode flags ; ================================================ ; ; Mode Measuring Prescale Display ; --------------------------------------------; 0 Frequency 16 Frequency ; 1 Frequency 1 Frequency ; 2 Time HL 1 Frequency ; 3 Time HL 1 Rounds per Minute ; 4 Time HL 1 Time ; 5 Time H 1 Time ; 6 Time L 1 Time ; 7 PW ratio H 1 Pulse width ratio H % ; 8 PW ratio L 1 Pulse width ratio L % ; 9 none Voltage only ; (for a single line LCD) ; .EQU cModeFrequency16 = 0 .EQU cModeFrequency = 1 .EQU cModeTimeFreq = 2 .EQU cModeTimeRpm = 3 .EQU cModeTimeTimeHL = 4 .EQU cModeTimeTimeH = 5 .EQU cModeTimeTimeL = 6 .EQU cModeTimePwrH = 7 .EQU cModeTimePwrL = 8 .EQU cModeVoltage = 9 ; sModeSlct: ; Selected mode .BYTE 1 sModeNext: ; next selected mode .BYTE 1 ; ; ================================================== ; Info on timer and counter interrupt operation ; ================================================== ; ; Clock => 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<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) out ADCSRA,rimp ; start ADC conversion lds rTDiv,sTMeas ; restart interval timer TC2CmpInt1: out SREG,rSreg ; restore SREG reti ; ; External Interrupt INT0 Service Routine ; active in modes 2 to 6 (measuring the signal duration), ; detects positive going edges of the input ; INT1, TC1 is in free running mode, ; reads the current counter state of TC1, ; copies it to the result registers, ; clears the counter and restarts it ; Int0Int: in rSreg,SREG ; 1, save SREG sbrc rFlg,bCyc ; 2/3, check if cycle flag signals ok for copy rjmp Int0Int1 ; 4, no, last result hasn't been read in rCpy1,TCNT1L ; 4, read timer 1 LSB in rCpy2,TCNT1H ; 5, dto., MSB mov rCpy3,rCtr2 ; 6, copy the counter bytes mov rCpy4,rCtr3 ; 7 sbr rFlg,1<<bCyc ; 8, set cycle end flag bit cbr rFlg,1<<bEdge ; 9, set falling edge sbic PIND,2 ; 10/11, check if input = 0 sbr rFlg,1<<bEdge ; 11, no, set edge flag to rising Int0Int1: ; 4/11 ldi rimp,0 ; 5/12, reset the timer out TCNT1H,rimp ; 6/13, set TC1 zero to restart out TCNT1L,rimp ; 7/14 mov rCtr1,rimp ; 8/15, clear the upper bytes mov rCtr2,rimp ; 9/16 mov rCtr3,rimp ; 10/17 out SREG,rSreg ; 11/18, restore SREG reti ; 15/22 ; ; TC1 Compare Match A Interrupt Service Routine ; active in modes 0 and 1 (measuring the number of ; sigals on the T1 input), timeout every 0.25s, ; reads the counter TC0, copies the count to ; the result registers and clears TC0 ; Tc1CmpAInt: in rSreg,SREG ; 1, save SREG sbrc rFlg,bCyc ; 2/3, check if cycle flag signals ok for copy rjmp TC1CmpAInt1 ; 4, no, last result hasn't been read in rCpy1,TCNT0 ; 4, read counter TC0 mov rCpy2,rCtr1 ; 5, copy counter bytes to result mov rCpy3,rCtr2 ; 6 mov rCpy4,rCtr3 ; 7 sbr rFlg,1<<bCyc ; 8, set cycle end flag bit Tc1CmpAInt1: ; 4/8 ldi rimp,0 ; 5/9, clear counter out TCNT0,rimp ; 6/10 mov rCtr1,rimp ; 7/11, clear counter bytes mov rCtr2,rimp ; 8/12 mov rCtr3,rimp ; 9/13 out SREG,rSreg ; 10/14, restore SREG reti ; 14/18 ; ; TC1 Overflow Interrupt Service Routine ; active in modes 2 to 6 counting clock cycles to measure time ; increases the upper bytes and detects overflows ; Tc1OvfInt: in rSreg,SREG ; 1, save SREG inc rCtr2 ; 2, increase byte 3 of the counter brne Tc1OvfInt1 ; 3/4, no overflow inc rCtr3 ; 4, increase byte 4 of the counter brne Tc1OvfInt1 ; 5/6, no overflow sbr rFlg,(1<<bOvf)|(1<<bCyc) ; 6, set overflow and end of cycle bit Tc1OvfInt1: ; 4/6 out SREG,rSreg ; 5/7, restore SREG reti ; 9/11 ; ; TC0 Overflow Interrupt Service Routine ; active in modes 0 and 1 counting positive edges on T1 ; increases the upper bytes and detects overflows ; Tc0OvfInt: in rSreg,SREG ; 1, save SREG inc rCtr1 ; 2, increase byte 2 of the counter brne Tc0OvfInt1 ; 3/4, no overflow inc rCtr2 ; 4, increase byte 3 of the counter brne Tc0OvfInt1 ; 5/6, no overflow inc rCtr3 ; 6, increase byte 4 of the counter brne Tc0OvfInt1 ; 7/8, no overflow sbr rFlg,(1<<bOvf)|(1<<bCyc) ; 8, set overflow bit Tc0OvfInt1: ; 4/6/8 out SREG,rSreg ; 5/7/9, restore SREG reti ; 9/11/13 ; ; Uart RxC Interrupt Service Routine ; receives a character, signals errors, echoes it back, ; puts it into the SRAM line buffer, checks for carriage ; return characters, if yes echoes additional linefeed ; and sets line-complete flag ; .IF cUart SioRxCIsr: in rSreg,SREG ; 1, Save SReg in rimp,UCSRA ; 2, Read error flags andi rimp,(1<<FE)|(1<<DOR)|(1<<PE) ; 3, isolate error bits in rimp,UDR ; 4, read character from UART breq SioRxCIsr1 ; 5/6, no errors ldi rimp,'*' ; 6, signal an error out UDR,rimp ; 7 rjmp SioRxCIsr4 ; 9, return from int SioRxCIsr1: ; 6 out UDR,rimp ; 7, echo the character push ZH ; 9, Save Z register push ZL ; 11 ldi ZH,HIGH(sUartRxBs) ; 12, Load Position for next RX char lds ZL,sUartRxBp ; 14 st Z+,rimp ; 16, save char in buffer cpi ZL,LOW(sUartRxBe+1) ; 17, End of buffer? brcc SioRxCIsr2 ; 18/19, Buffer overflow sts sUartRxBp,ZL ; 20, Save next pointer position SioRxCIsr2: ; 19/20 cpi rimp,cCr ; 20/21, Carriage Return? brne SioRxCIsr3 ; 21/22/23, No, go on ldi rimp,cLf ; 22/23, Echo linefeed out UDR,rimp ; 23/24 sbr rFlg,(1<<bUartRxLine) ; 24/25, Set line complete flag rjmp SioRxCIsr3a SioRxCIsr3: ; 22/23/24/25 cpi rimp,cLf brne SioRxCIsr3a sbr rFlg,(1<<bUartRxLine) SioRxCIsr3a: pop ZL ; 24/25/26/27, restore Z-register pop ZH ; 26/27/28/29 SioRxCIsr4: ; 9/26/27/28/29 out SREG,rSreg ; 10/27/28/29/30, restore SREG reti ; 14/31/32/33/34, return from Int .ENDIF ; ; ADC has completed a conversion ; used in all modes, ADC has completed a conversion ; if ADMUX channel is 0 then set ADMUX=1 and start ; another coversion ; if ADMUX channel is 1 he set ADMUX=0, disable the ADC ; and set teh ADC cycle end flag ; .EQU cStopAdc = (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) .EQU cStartAdc = (1<<ADEN)|(1<<ADSC)|(1<<ADIE)|cStopAdc AdcCcInt: in rSreg,SREG ; 1, save SREG in rimp,ADMUX ; 2, read the current channel info sbrc rimp,0 ; 3/4, jump if channel = 0 rjmp AdcCcInt1 ; 5 sbr rimp,1 ; 5, set channel MUX to one out ADMUX,rimp ; 6 in rimp,ADCL ; 7, read result LSB sts sAdc0L,rimp ; 9, store result LSB in rimp,ADCH ; 10, read result MSB sts sAdc0H,rimp ; 12, store result MSB ldi rimp,cStartAdc out ADCSRA,rimp ; 14, start next conversion out SREG,rSreg ; 15, restore SREG reti ; 19 AdcCcInt1: ; 5 cbr rimp,1 ; 6, set MUX to channel 0 out ADMUX,rimp ; 7 in rimp,ADCL ; 8, read result LSB sts sAdc1L,rimp ; 10, store result LSB in rimp,ADCH ; 11, read result MSB sts sAdc1H,rimp ; 13, store result MSB sbr rFlg,1<<bAdc ; 14, set flag bit ldi rimp,cStopAdc ; 15, ADC off out ADCSRA,rimp ; 16, switch ADC off out SREG,rSreg ; 17, restore SREG reti ; 21 ; ; ================================================ ; Common subroutines ; ================================================ ; ; Setting timer/counter modes for measuring ; SetModeNext: rcall ClrTc ; clear the timers TC0 and TC1, disable INT0 lds rmp,sModeNext ; read next mode mov rMode,rmp ; copy to current mode ldi ZH,HIGH(SetModeTab) ldi ZL,LOW(SetModeTab) add ZL,rmp ldi rmp,0 adc ZH,rmp ijmp ; Table mode setting SetModeTab: rjmp SetMode0 ; f div 16, f rjmp SetMode1 ; f, f rjmp SetModeT ; t, f rjmp SetModeT ; t, u rjmp SetModeT ; t, t rjmp SetModeE ; th, t rjmp SetModeE ; tl, t rjmp SetModeE ; th, p rjmp SetModeE ; tl, p ret ; U, U ; ; Set counters/timers to mode 0 ; TC0 counts input signals (positive edges) ; TC1 times the gate at 250 ms ; INT0 disabled ; SetMode0: cbi pPresc,bPresc ; enable prescaler rjmp SetModeF ; frequency measurement ; ; Set counters/timers to mode 1 ; SetMode1: sbi pPresc,bPresc ; disable prescaler ; Set timer/counter mode to frequency measurement SetModeF: ldi rmp,HIGH(cCmp1F) ; set the compare match high value out OCR1AH,rmp ldi rmp,LOW(cCmp1F) ; set the compare match low value out OCR1AL,rmp ldi rmp,0xFF ; disable the compare match B out OCR1BH,rmp out OCR1BL,rmp ldi rmp,0 ; CTC mode out TCCR1A,rmp ldi rmp,cPre1F ; set the prescaler value for TC1 out TCCR1B,rmp ldi rmp,(1<<CS02)|(1<<CS01)|(1<<CS00) ; count rising edges on T0 out TCCR0,rmp ldi rmp,(1<<OCIE2)|(1<<OCIE1A)|(1<<TOIE0) ; enable TC2Cmp, TC1CmpAInt and TC0OverflowInt out TIMSK,rmp ret ; ; Set timer/counter mode to time measurement ; SetModeT: sbi pPresc,bPresc ; disable prescaler ldi rmp,0 ; timing mode out TCCR1A,rmp ldi rmp,1<<CS10 ; count with prescaler = 1 out TCCR1B,rmp ldi rmp,(1<<SE)|(1<<ISC01)|(1<<ISC00) ; sleep enable, positive edges on INT0 interrupt out MCUCR,rmp ldi rmp,1<<INT0 ; enable INT0 interrupt out GICR,rmp ldi rmp,(1<<OCIE2)|(1<<TOIE1) ; enable TC2Cmp, TC1Ovflw out TIMSK,rmp ret ; ; Set timer/counter mode to time measurement, all edges ; SetModeE: sbi pPresc,bPresc ; disable prescaler ldi rmp,0 ; timing mode out TCCR1A,rmp ldi rmp,1<<CS10 ; count with prescaler = 1 out TCCR1B,rmp ldi rmp,(1<<SE)|(1<<ISC00) ; sleep enable, any logical change on INT0 interrupts out MCUCR,rmp ldi rmp,1<<INT0 ; enable INT0 interrupt out GICR,rmp ldi rmp,(1<<OCIE2)|(1<<TOIE1) ; enable TC2Cmp, TC1Ovflw out TIMSK,rmp ret ; ; ; clears the timers and resets the upper bytes ; ClrTc: clr rmp ; disable INT0 out GICR,rmp clr rmp ; stop the counters/timers out TCCR0,rmp ; stop TC0 counting/timing out TCCR1B,rmp ; stop TC1 counting/timing out TCNT0,rmp ; clear TC0 out TCNT1L,rmp ; clear TC1 out TCNT1H,rmp clr rCtr1 ; clear upper bytes clr rCtr2 clr rCtr3 ldi rmp,1<<OCIE2 ; enable only output compare of TC2 ints out TIMSK,rmp ; timer int disable ret ; ; ======================================================= ; Math routines ; ======================================================= ; ; Divides cFreq/256 by the timer value in rDiv4:rDiv3:rDiv2:rDiv1 ; yields frequency in R4:R3:R2:(Fract):R1 ; Divide: clr rmp ; rmp:R0:ZH:ZL:XH:XL is divisor clr R0 clr ZH ldi ZL,BYTE3(cFreq/256) ; set divisor ldi XH,BYTE2(cFreq/256) ldi XL,BYTE1(cFreq/256) clr rRes1 ; set result inc rRes1 clr rRes2 clr rRes3 clr rRes4 Divide1: lsl XL ; multiply divisor by 2 rol XH rol ZL rol ZH rol R0 rol rmp cp ZL,rDiv1 ; compare with divident cpc ZH,rDiv2 cpc R0,rDiv3 cpc rmp,rDiv4 brcs Divide2 sub ZL,rDiv1 sbc ZH,rDiv2 sbc R0,rDiv3 sbc rmp,rDiv4 sec rjmp Divide3 Divide2: clc Divide3: rol rRes1 rol rRes2 rol rRes3 rol rRes4 brcc Divide1 ret ; ; Multiply measured time in rRes4:rRes3:rRes2:rRes1 by 65536 / fq(MHz) ; rmp:R0 are the upper bytes of the input ; ZH:ZL:rDiv4:rDiv3:rDiv2:rDiv1 is the interim result ; XH:XL is the multiplicator ; result is in rRes4:rRes3:rRes2:rRes1 ; .EQU cMulti = 65536000 / (cFreq/1000) ; Multiply: ldi XH,HIGH(cMulti) ; set multiplicator ldi XL,LOW(cMulti) clr ZH clr ZL clr rDiv4 clr rDiv3 clr rDiv2 clr rDiv1 clr R0 clr rmp Multiply1: cpi XL,0 brne Multiply2 cpi XH,0 breq Multiply4 Multiply2: lsr XH ror XL brcc Multiply3 add rDiv1,rRes1 adc rDiv2,rRes2 adc rDiv3,rRes3 adc rDiv4,rRes4 adc ZL,R0 adc ZH,rmp Multiply3: lsl rRes1 rol rRes2 rol rRes3 rol rRes4 rol R0 rol rmp rjmp Multiply1 Multiply4: ldi rmp,128 ; round result clr R0 add rDiv2,rmp adc rDiv3,R0 adc rDiv4,R0 adc ZL,R0 adc ZH,R0 mov rRes1,rDiv3 ; move result mov rRes2,rDiv4 mov rRes3,ZL mov rRes4,ZH ret ; ; Display seconds at buffer end ; DisplSec: .IF ! cDisplay8 ldi rmp,' ' st X+,rmp ldi rmp,'u' st X+,rmp ldi rmp,'s' st X+,rmp ldi rmp,' ' st X,rmp .ENDIF ret ; ; An overflow has occurred during pulse width calculation ; PulseOvflw: ldi XH,HIGH(sResult) ldi XL,LOW(sResult) st X+,rmp .IF cDisplay8 ldi ZH,HIGH(2*TxtPOvflw8) ldi ZL,LOW(2*TxtPOvflw8) ldi rmp,7 .ELSE ldi ZH,HIGH(2*TxtPOvflw16) ldi ZL,LOW(2*TxtPOvflw16) ldi rmp,15 .ENDIF PulseOvflw1: lpm adiw ZL,1 st X+,R0 dec rmp brne PulseOvflw1 ret .IF cDisplay8 TxtPOvflw8: .DB ":error! " .ELSE TxtPOvflw16: .DB ":error calcul.! " .ENDIF ; ; ====================================================== ; Pulse width calculations ; ====================================================== ; ; Calculate the pulse width ratio ; active cycle time is in rDelH:rDelL:R0:rmp ; total cycle time is in rDiv ; result will be in rRes ; overflow: carry flag is set ; CalcPwO: ; overflow sec ret CalcPw: mov rRes1,rmp ; copy active cycle time to rRes mov rRes2,R0 mov rRes3,rDelL mov rRes4,rDelH lsl rRes1 ; * 2 rol rRes2 rol rRes3 rol rRes4 brcs CalcPwO ; overflow lsl rRes1 ; * 4 rol rRes2 rol rRes3 rol rRes4 brcs CalcPwO ; overflow lsl rRes1 ; * 8 rol rRes2 rol rRes3 rol rRes4 brcs CalcPwO ; overflow mov XL,rRes1 ; copy to Z:X mov XH,rRes2 mov ZL,rRes3 mov ZH,rRes4 lsl rRes1 ; * 16 rol rRes2 rol rRes3 rol rRes4 brcs CalcPwO add rRes1,XL ; * 24 adc rRes2,XH adc rRes3,ZL adc rRes4,ZH clr ZH ; clear the four MSBs of divisor clr ZL clr XH mov XL,rDelH ; * 256 mov rDelH,rDelL mov rDelL,R0 mov R0,rmp clr rmp lsl R0 ; * 512 rol rDelL rol rDelH rol XL rol XH lsl R0 ; * 1024 rol rDelL rol rDelH
http://www.avr-asm-tutorial.net/avr_de/fcount/fcountV03.asm (1 of 4)1/20/2009 7:40:33 PM

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 > 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<<bEdge rcall CycleM7 bploop: rjmp bploop .ENDIF ; ; Clear the output storage ; ldi ZH,HIGH(sResult) ldi ZL,LOW(sResult) ldi rmp,' ' mov R0,rmp ldi rmp,32 main1: st Z+,R0 dec rmp brne main1 ; ; Init the Uart ; .IF cUart rcall UartInit ldi rmp,1<<bUMonU ; monitor U over Uart sts sUartFlag,rmp ldi rmp,20 ; 5 seconds sts sUartMonURpt,rmp ; set repeat default value ldi rmp,1 sts sUartMonUCnt,rmp ldi rmp,4 ; 1 seconds sts sUartMonFCnt,rmp .ENDIF ; ; Init the LCD ; .IF cDisplay rcall LcdInit .ENDIF ; ; Disable the Analog comparator ; ldi rmp,1<<ACD out ACSR,rmp ; ; Disable the external prescaler by 16 ; sbi pPrescD,bPresc ; set prescaler port bit to output sbi pPresc,bPresc ; disable the prescaler ; ; Init the ADC ; ldi rmp,(1<<REFS1)|(1<<REFS0) ; int.ref, channel 0 out ADMUX,rmp ldi rmp,(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) ; prescaler = 128 out ADCSRA,rmp ; ; Start main interval timer ; ldi rmp,cCmp2 ; set Compare Match out OCR2,rmp ldi rmp,cPre2|(1<<WGM21) ; CTC mode and prescaler out TCCR2,rmp ; ; Start timer/counter TC2 interrupts ; ldi rmp,(1<<OCIE2) ; Interrupt mask out TIMSK,rmp ; ; Set initial mode to mode 1 ; ldi rmp,1 ; initial mode = 1 sts sModeNext,rmp rcall SetModeNext ; sei ; enable interrupts ; loop: sleep ; send CPU to sleep nop sbrc rFlg,bCyc ; check cycle end rcall Cycle sbrc rFlg,bAdc rcall Interval .IF cUart sbrc rFlg,bUartRxLine ; check line complete rcall UartRxLine ; call line complete .ENDIF rjmp loop ; go to sleep ; ; Timer interval for calculation and display ; Interval: cbr rFlg,1<<bAdc ; clear flag lds ZL,sAdc1L ; read ADC channel 1 value lds ZH,sAdc1H mov XL,ZL ; copy to X mov XH,ZH lsl ZL ; multiply by 2 rol ZH lsl ZL ; multiply by 4 rol ZH add ZL,XL ; multiply by 5 adc ZH,XH lsr ZH ; multiply by 2.5 ror ZL cpi ZH,10 ; larger than 9? brcs Interval1 ldi ZH,9 ; set to 9 Interval1: sts sModeNext,ZH ; store next mode cp rMode,ZH ; new mode? breq Interval2 ; continue current mode rcall SetModeNext ; start new mode Interval2: .IF cUart || cDisplay lds R0,sAdc0L ; read ADC value lds R1,sAdc0H rcall cAdc2U ; convert to text .IF cDisplay rcall LcdDisplayFT rcall LcdDisplayU .ENDIF .IF cUart rcall UartMonU .ENDIF .ENDIF ret ; ; Frequency/Time measuring cycle ended, calculate results ; Cycle: sbrc rFlg,bOvf ; check overflow rjmp CycleOvf ; jump to overflow mov rRes1,rCpy1 ; copy counter mov rRes2,rCpy2 mov rRes3,rCpy3 mov rRes4,rCpy4 cbr rFlg,(1<<bCyc)|(1<<bOvf) ; clear cycle flag and overflow mov rDiv1,rRes1 ; copy again mov rDiv2,rRes2 mov rDiv3,rRes3 mov rDiv4,rRes4 .IF cUart ldi ZH,HIGH(UartMonF) ; put monitoring frequency on stack ldi ZL,LOW(UartMonF) push ZL push ZH .ENDIF ldi ZH,HIGH(CycleTab) ; point to mode table ldi ZL,LOW(CycleTab) add ZL,rMode ; displace table by mode brcc Cycle1 inc ZH Cycle1: ijmp ; call the calculation routine ; overflow occurred CycleOvf: cbr rFlg,(1<<bCyc)|(1<<bOvf) ; clear cycle flag and overflow ldi XH,HIGH(sResult) ; point to result buffer ldi XL,LOW(sResult) .IF cDisplay8 ldi ZH,HIGH(2*TxtOvf8) ; point to short message ldi ZL,LOW(2*TxtOvf8) ldi rmp,8 .ELSE ldi ZH,HIGH(2*TxtOvf16) ; point to long message ldi ZL,LOW(2*TxtOvf16) ldi rmp,16 .ENDIF CycleOvf1: lpm adiw ZL,1 st X+,R0 dec rmp brne CycleOvf1 ret ; .IF cDisplay8 TxtOvf8: .DB " ovflow" .ELSE TxtOvf16: .DB " overflow " .ENDIF ; Table with routines for the 8 modes CycleTab: rjmp CycleM0 rjmp CycleM1 rjmp CycleM2 rjmp CycleM3 rjmp CycleM4 rjmp CycleM5 rjmp CycleM6 rjmp CycleM7 rjmp CycleM8 ret ; voltage only ; ; Mode 0: Measured prescaled frequency, display frequency ; CycleM0: clr R5 ; for detecting an overflow in R5 lsl R1 ; * 2 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 4 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 8 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 16 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 32 rol R2 rol R3 rol R4 rol R5 lsl R1 ; * 64 rol R2 rol R3 rol R4 rol R5 tst R5 ; check overflow breq CycleM0a ; no error rjmp CycleOvf CycleM0a: 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,'F' rjmp DisplMode ; ; Mode 1: Frequency measured, prescale = 1, display frequency ; CycleM1: clr rDiv1 ; detect overflow in rDiv1 lsl rRes1 ; * 2 rol rRes2 rol rRes3 rol rRes4 rol rDiv1 lsl rRes1 ; * 4 rol rRes2 rol rRes3 rol rRes4 rol rDiv1 tst rDiv1 ; check overflow breq CycleM1a ; no error rjmp CycleOvf CycleM1a: 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,'f' rjmp DisplMode ; ; Mode 2: Time measured, prescale = 1, display frequency ; CycleM2: rcall Divide tst rRes4 brne CycleM2a rcall DisplFrac ldi ZL,'v' rcall DisplMode ret CycleM2a: mov rRes1,rRes2 ; number too big, skip fraction
http://www.avr-asm-tutorial.net/avr_de/fcount/fcountV03.asm (2 of 4)1/20/2009 7:40:33 PM

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>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<<bLcdRs ; set Rs to one out PORTB,rmp ; write to display interface rcall LcdE ; pulse E mov rmp,R0 ; copy original again andi rmp,0x0F ; clear upper nibble sbr rmp,1<<bLcdRs ; set Rs to one out PORTB,rmp ; write to display interface rcall LcdE rcall Delay40us pop rmp ; restore rmp ret ; ; writes the text in flash to the LCD, number of ; characters in rmp ; LcdText: lpm ; read character from flash adiw ZL,1 rcall LcdData4 ; write to rcall delay40us dec rmp brne LcdText ret ; ; Inits the LCD with a 4-bit-interface ; LcdInit: ldi rmp,0x0F | (1<<bLcdE) | (1<<bLcdRs) out DDRB,rmp clr rmp out PORTB,rmp rcall delay15ms ; wait for complete self-init ldi rmp,0x03 ; Function set 8-bit interface rcall LcdRs8 rcall delay4_1ms ; wait for 4.1 ms ldi rmp,0x03 ; Function set 8-bit interface rcall LcdRs8 rcall delay100us ; wait for 100 us ldi rmp,0x03 ; Function set 8-bit interface rcall LcdRs8 rcall delay40us ; delay 40 us ldi rmp,0x02 ; Function set 4-bit-interface rcall LcdRs8 rcall delay40us .IF cDisplay2 ldi rmp,0x28 ; 4-bit-interface, two line display .ELSE ldi rmp,0x20 ; 4-bit-interface, single line display .ENDIF rcall LcdRs4 rcall delay40us ; delay 40 us ldi rmp,0x08 ; display off rcall LcdRs4 rcall delay40us ; delay 40 us ldi rmp,0x01 ; display clear rcall LcdRs4 rcall delay1_64ms ; delay 1.64 ms ldi rmp,0x06 ; increment, don't shift rcall LcdRs4 rcall delay40us ; delay 40 us ldi rmp,0x0C ; display on rcall LcdRs4 rcall delay40us ldi rmp,0x80 ; position on line 1 rcall LcdRs4 rcall delay40us ; delay 40 us .IF cDisplay8 ldi rmp,8 ldi ZH,HIGH(2*LcdInitTxt8) ldi ZL,LOW(2*LcdInitTxt8) .ELSE ldi rmp,16 ldi ZH,HIGH(2*LcdInitTxt16) ldi ZL,LOW(2*LcdInitTxt16) .ENDIF rcall LcdText .IF cDisplay2 ldi rmp,0xC0 ; line 2 rcall LcdRs4 rcall delay40us ; delay 40 us .IF cDisplay8 ldi rmp,8 .ELSE ldi XH,HIGH(sResult+25) ldi XL,LOW(sResult+25) ldi ZH,HIGH(2*LcdInitTxtMode) ldi ZL,LOW(2*LcdInitTxtMode) ldi rmp,6 LcdInitMode: lpm adiw ZL,1 st X+,R0 dec rmp brne LcdInitMode ldi rmp,16 .ENDIF rcall LcdText .ENDIF ret .IF cDisplay8 LcdInitTxt8: .DB "F-CNT V1" .IF cDisplay2 .DB "-DG4FAC-" .ENDIF .ELSE LcdInitTxt16: .DB "Freq-counter V01" .IF cDisplay2 .DB " (C)2005 DG4FAC " LcdInitTxtMode: .DB " Mode=" .ENDIF .ENDIF ; ; Display frequency/time on Lcd ; LcdDisplayFT: .IF ! cDisplay2 ; single line display cpi rMode,cModeVoltage ; voltage display selected? breq LcdDisplayFT2 .ENDIF ldi rmp,$80 ; set display position to line 1 rcall LcdRs4 rcall Delay40us ldi ZH,HIGH(sResult) ; point Z to line buffer ldi ZL,LOW(sResult) .IF cDisplay8 ldi rmp,8 .ELSE ldi rmp,16 .ENDIF LcdDisplayFT1: ld R0,Z+ ; read a char rcall LcdData4 ; display on LCD dec rmp brne LcdDisplayFT1 LcdDisplayFT2: ret ; ; Display voltage on the display ; LcdDisplayU: .IF cDisplay2 ; two-line LCD connected .IF !cDisplay8 lds rmp,sModeNext subi rmp,-'0' sts sResult+31,rmp .ENDIF ldi rmp,$C0 ; output to line 2 .ELSE cpi rMode,cModeVoltage ; check switch brne LcdDisplayU2 ldi rmp,$80 ; output to line 1 .ENDIF rcall LcdRs4 ; set output position rcall Delay40us ldi ZH,HIGH(sResult+16) ; point to result ldi ZL,LOW(sResult+16) .IF cDisplay8 ldi rmp,8 .ELSE ldi rmp,16 .ENDIF LcdDisplayU1: ld R0,Z+ ; read character rcall LcdData4 dec rmp ; next char brne LcdDisplayU1 ; continue with chars LcdDisplayU2: ret ; .ENDIF ; end LCD routines to be included ; ; =========================================== ; Uart routines ; =========================================== ; .IF cUart UartInit: ; Init the Uart on startup .EQU cUbrr = (cFreq/cBaud/16)-1 ; calculating UBRR single speed ldi rmp,LOW(sUartRxBs) ; set buffer pointer to start sts sUartRxBp,rmp ldi rmp,HIGH(cUbrr) ; set URSEL to zero, set baudrate msb out UBRRH,rmp ldi rmp,LOW(cUbrr) ; set baudrate lsb out UBRRL,rmp ldi rmp,(1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0) ; set 8 bit characters out UCSRC,rmp ldi rmp,(1<<RXCIE)|(1<<RXEN)|(1<<TXEN) ; enable RX/TX and RX-Ints out UCSRB,rmp rcall delay10ms ; delay for 10 ms duration ldi ZH,HIGH(2*txtUartInit) ldi ZL,LOW(2*txtUartInit) rjmp UartSendTxt ; ; Uart receive buffer space in SRAM ; sUartRxBs is buffer start ; sUartRxBe is buffer end ; sUartRxBp is buffer input position ; .EQU UartRxbLen = 38 ; Buffer length in bytes ; sUartFlag: ; flag register for Uart ; .BYTE 1 ; .EQU bUMonU = 0 ; displays voltage over Uart ; .EQU bUMonF = 1 ; displays frequency over Uart ; ; free: bits 2..7 ; sUartMonUCnt: ; counter for Monitoring voltage ; .BYTE 1 ; sUartMonURpt: ; counter preset for monitoring voltage ; .BYTE 1 ; sUartRxBp: ; buffer pointer ; .BYTE 1 ; sUartRxBs: ; buffer ; .BYTE UartRxbLen ; sUartRxBe: ; buffer end ; .EQU cNul = $00 ; .EQU cClrScr = $0C ; .EQU cCr = $0D ; .EQU cLf = $0A ; UartRxLine: cbr rFlg,1<<bUartRxLine ; clear line complete flag ldi rmp,LOW(sUartRxBs) ; set buffer pointer to start sts sUartRxBp,rmp ldi ZH,HIGH(UartReturn) ; push return adress to stack ldi ZL,LOW(UartReturn) push ZL push ZH ldi ZH,HIGH(sUartRxBs) ; set Z to Buffer-Start ldi ZL,LOW(sUartRxBs) ld rmp,Z+ ; read first character cpi rmp,'h' ; help? brne UartRxLine1 rjmp UartHelp UartRxLine1: cpi rmp,'?' ; help? brne UartRxLine2 rjmp UartHelp UartRxLine2: cpi rmp,'U' ; monitor U on brne UartRxLine3 rcall UartGetPar sec rjmp UartMonUSetC UartRxLine3: cpi rmp,'u' ; monitor U off brne UartRxLine4 clc rjmp UartMonUSetC UartRxLine4: cpi rmp,'F' ; monitor F on brne UartRxLine5 rcall UartGetPar sec rjmp UartMonFSetC UartRxLine5: cpi rmp,'f' ; monitor f off brne UartRxLine6 clc rjmp UartMonFSetC UartRxLine6: cpi rmp,'p' ; parameter? brne UartRxLine7 rjmp UartMonPar UartRxLine7: ldi ZH,HIGH(2*txtUartUnknown) ; send unknown command ldi ZL,LOW(2*txtUartUnknown) ret UartHelp: ldi ZH,HIGH(2*txtUartHelp) ; send help text ldi ZL,LOW(2*txtUartHelp) ret UartMonUSetC: lds rmp,sUartFlag brcs UartMonUSetC1 cbr rmp,1<<bUMonU ; clear flag sts sUartFlag,rmp ldi ZH,HIGH(2*txtUartUOff) ldi ZL,LOW(2*txtUartUOff) ret UartMonUSetC1: brne UartMonUSetC2 sts sUartMonURpt,R0 sts sUartMonUCnt,R0 UartMonUSetC2: sbr rmp,1<<bUMonU ; set flag sts sUartFlag,rmp ldi ZH,HIGH(2*txtUartUOn) ldi ZL,LOW(2*txtUartUOn) ret UartMonFSetC: lds rmp,sUartFlag brcs UartMonFSetC1 cbr rmp,1<<bUMonF ; clear flag sts sUartFlag,rmp ldi ZH,HIGH(2*txtUartFOff) ldi ZL,LOW(2*txtUartFOff) ret UartMonFSetC1: brne UartMonFSetC2 sts sUartMonFRpt,R0 sts sUartMonFCnt,R0 UartMonFSetC2: sbr rmp,1<<bUMonF ; set flag sts sUartFlag,rmp ldi ZH,HIGH(2*txtUartFOn) ldi ZL,LOW(2*txtUartFOn) ret UartMonPar: ldi ZH,HIGH(2*txtUartNul) ldi ZL,LOW(2*txtUartNul); ldi rmp,'U' rcall UartSendChar ldi rmp,'=' rcall UartSendChar ldi rmp,'$' rcall UartSendChar lds rmp,sUartMonURpt rcall UartHexR ldi rmp,',' rcall UartSendChar ldi rmp,' ' rcall UartSendChar ldi rmp,'F' rcall UartSendChar ldi rmp,'=' rcall UartSendChar ldi rmp,'$' rcall UartSendChar lds rmp,sUartMonFRpt rjmp UartHexR ; ; Get Parameter from line ; UartGetPar: clr R0 ; result register ld rmp,Z+ ; read char cpi rmp,cCr ; carriage return breq UartGetParNoPar cpi rmp,cLf ; line feed breq UartGetParNoPar cpi rmp,'=' ; brne UartGetParErr UartGetPar1: ld rmp,Z+ ; read next char cpi rmp,cCr ; carriage return? breq UartGetPar2 cpi rmp,cLf ; line feed? breq UartGetPar2 subi rmp,'0' ; subtract 0 brcs UartGetParErr cpi rmp,10 ; larger than 9? brcc UartGetParErr mov rir,R0 ; copy to rir lsl R0 ; * 2 brcs UartGetParErr lsl R0 ; * 4 brcs UartGetParErr add R0,rir ; * 5 brcs UartGetParErr lsl R0 ; * 10 brcs UartGetParErr add R0,rmp ; add new decimal brcs UartGetParErr rjmp UartGetPar1 UartGetPar2: sez ret UartGetParErr: ldi ZH,HIGH(2*txtUartErr) ldi ZL,LOW(2*txtUartErr) rcall UartSendTxt UartGetParNoPar: clz ; No parameter set ret ; ; Hex output over Uart, for debugging ; UartHexR: push rmp swap rmp rcall UartHexN pop rmp UartHexN: andi rmp,0x0F subi rmp,-'0' cpi rmp,'9'+1 brcs UartHexN1 subi rmp,-7 UartHexN1: rjmp UartSendChar ret ; ; Return from Uart-Routines, displays text in Z ; UartReturn: rcall UartSendTxt ; send text in Z ldi ZH,HIGH(2*txtUartCursor) ldi ZL,LOW(2*txtUartCursor) rjmp UartSendTxt ; ; Send character in rmp over Uart ; UartSendChar: sbis UCSRA,UDRE ; wait for empty buffer rjmp UartSendChar out UDR,rmp ret ; ; Monitoring the voltage over the Uart ; UartMonU: lds rmp,sUartFlag ; flag register for Uart sbrs rmp,bUMonU ; displays voltage over Uart ret lds rmp,sUartMonUCnt ; read counter dec rmp sts sUartMonUCnt,rmp brne UartMonU2 lds rmp,sUartMonURpt sts sUartMonUCnt,rmp ldi ZH,HIGH(sResult+16) ldi ZL,LOW(sResult+16) ldi rmp,8 UartMonU1: sbis UCSRA,UDRE ; wait for empty buffer rjmp UartMonU1 ld R0,Z+ out UDR,R0 dec rmp brne UartMonU1 ldi rmp,cCr rcall UartSendChar ldi rmp,cLf rjmp UartSendChar UartMonU2: ret ; ; Monitor frequency over UART ; UartMonF: lds rmp,sUartFlag ; flag register for Uart sbrs rmp,bUMonF ; displays frequency over Uart ret lds rmp,sUartMonFCnt ; read counter dec rmp sts sUartMonFCnt,rmp brne UartMonF2 lds rmp,sUartMonFRpt sts sUartMonFCnt,rmp ldi ZH,HIGH(sResult) ldi ZL,LOW(sResult) ldi rmp,16 UartMonF1: sbis UCSRA,UDRE ; wait for empty buffer rjmp UartMonF1 ld R0,Z+ out UDR,R0 dec rmp brne UartMonF1 ldi rmp,cCr rcall UartSendChar ldi rmp,cLf rjmp UartSendChar UartMonF2: ret ; ; Send text from flash to UART, null byte ends transmit ; UartSendTxt: lpm ; read character from flash adiw ZL,1 tst R0 ; check end of text breq UartSendTxtRet UartSendTxtWait: sbis UCSRA,UDRE ; wait for empty char rjmp UartSendTxtWait out UDR,R0 ; send char rjmp UartSendTxt UartSendTxtRet: ret ; ; Uart text constants ; txtUartInit: .DB " ",cClrScr .DB "************************************************* ",cCr,cLf .DB "* Frequency- and voltmeter (C)2005 by g.schmidt * ",cCr,cLf .DB "************************************************* ",cCr,cLf txtUartMenue: .DB cCr,cLf,"Commands: <h>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

http://www.avr-asm-tutorial.net/avr_de/fcount/fcountV03.asm

txtUartUnknown: .DB cCr,cLf,"Unknown command!",cNul,cNul txtUartUOff: .DB "Voltage monitoring is off.",cNul,cNul txtUartUOn: .DB "Voltage monitoring is on. ",cNul,cNul txtUartFOff: .DB "Frequency monitoring is off.",cNul,cNul txtUartFOn: .DB "Frequency monitoring is on. ",cNul,cNul txtUartErr: .DB "Error in parameter! ",cNul,cNul txtUartHelp: .DB cCr,cLf,"Help: ",cCr,cLf .DB "U[=N](on) or u(Off): monitor voltage output, N=1..255,",cCr,cLf .DB "F[=N](On) or f(Off): monitor frequency output N=1..255, ",cCr,cLf .DB "p: display monitoring parameters, ",cCr,cLf .DB "h or ?: this text." txtUartNul: .DB cNul,cNul .ENDIF ; ; End of source code ;

http://www.avr-asm-tutorial.net/avr_de/fcount/fcountV03.asm (4 of 4)1/20/2009 7:40:33 PM

http://www.avr-asm-tutorial.net/avr_de/fcount/LiesMich3.txt

**************************************************** * 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: <h>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<CR><LF>" Die Ausgabe von Frequenzen ist vom eingestellten Modus abhaengig, also z. B. in Modus 0 "F=1.234.567 Hz<CR><LF>"
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

Die ATtiny2313V-Eieruhr - Assembler-Quellcode
; ***************************************************** ; * ATtiny2313-Eieruhr, Version 1, 20.10.2006 * ; * (C)2006 by G.Schmidt info!at!avr-asm-tutorial.net * ; ***************************************************** ; .NOLIST .INCLUDE "tn2313def.inc" .LIST ; ; *************************************** ; Fuses-Voreinstellungen im ATtiny2313V ; *************************************** ; ; - Internen RC-Oszillator auf 4 MHz, CLKSEL3:0 = 0010, SUT1:0 = 10 ; - Interner Clockteiler auf 8, CKDIV8 = Enabled ; ; *************************************** ; Hardware - Schaltung ; *************************************** ; ; __________ ; / |_| | ; +3V O--|Res VCC|--O +3V ; | | ; L1K O--|PD0 PB7|--O L10A ; | | ;L1A/L2K O--|PD1 PB6|--O L9A/L10K ; | AT | ; O--|PA1 PB5|--O L8A/L9K ; | tiny | ; O--|PA0 PB4|--O L7A/L8K ; | 2313 | ;L2A/L3K O--|PD2 PB3|--O ; | V | ;L4K/L3A O--|PD3 PB2|--||--O Lsp ; | (OC0A)| ;L5K/L4A O--|PD4 PB1|--O ; | | ;L6K/L5A O--|PD5 PB0|--O L0K ; | | ; GND O--|GND PD6|--O L6A/L7K ; |___________| ; ; ; *************************************** ; Definiere Ports und Portbits ; *************************************** ; .EQU pOcO = PORTB .EQU dOcO = DDRB .EQU bOcO = 2 ; .EQU pL0K = PORTB .EQU dL0K = DDRB .EQU bL0K = 0 ; .EQU pL1K = PORTD .EQU dL1K = DDRD .EQU bL1K = 0 ; .EQU pL1A = PORTD .EQU dL1A = DDRD .EQU bL1A = 1 ; .EQU pL2K = PORTD .EQU dL2K = DDRD .EQU bL2K = 1 ; .EQU pL2A = PORTD .EQU dL2A = DDRD .EQU bL2A = 2 ; .EQU pL3K = PORTD .EQU dL3K = DDRD .EQU bL3K = 2 ; .EQU pL3A = PORTD .EQU dL3A = DDRD .EQU bL3A = 3 ; .EQU pL4K = PORTD .EQU dL4K = DDRD .EQU bL4K = 3 ; .EQU pL4A = PORTD .EQU dL4A = DDRD .EQU bL4A = 4 ; .EQU pL5K = PORTD .EQU dL5K = DDRD .EQU bL5K = 4 ; .EQU pL5A = PORTD .EQU dL5A = DDRD .EQU bL5A = 5 ; .EQU pL6K = PORTD .EQU dL6K = DDRD .EQU bL6K = 5 ; .EQU pL6A = PORTD .EQU dL6A = DDRD .EQU bL6A = 6 ; .EQU pL7K = PORTD .EQU dL7K = DDRD .EQU bL7K = 6 ; .EQU pL7A = PORTB .EQU dL7A = DDRB .EQU bL7A = 4 ; .EQU pL8K = PORTB .EQU dL8K = DDRB .EQU bL8K = 4 ; .EQU pL8A = PORTB .EQU dL8A = DDRB .EQU bL8A = 5 ; .EQU pL9K = PORTB .EQU dL9K = DDRB .EQU bL9K = 5 ; .EQU pL9A = PORTB .EQU dL9A = DDRB .EQU bL9A = 6 ; .EQU pL10K = PORTB .EQU dL10K = DDRB .EQU bL10K = 6 ; .EQU pL10A = PORTB .EQU dL10A = DDRB .EQU bL10A = 7 ; ; *************************************** ; Funktionsweise Timing ; *************************************** ; ; Timer1 = Zeitmessung ; _______ ______ ______ ______ _____ ______ ; |RC-Osc.| | CDiv |500|Presc.|62,5|TC1CNT|5Hz| LED |0,5|MinCnt| ; | 4 MHz |===<| / 8 |==>| / 8 |===>|/12500|==>|Count|==>| / 30 | ==>1 Min. ; |_______| |______|kHz|______|kHz |______| |_____|Hz |______| ; | ; Timer0 = NF-Erzeugung| ; | ______ ______ ; | |Presc.| |TC0CNT|992Hz ; |=>| / 8 |==>| / 63 |===> OC0A = BeepLautsprecher ; |______| |______| ; ; ; *************************************** ; Register ; *************************************** ; .DEF rmp = R16 .DEF rFlg = R17 .DEF rLed = R18 .DEF rSec = R19 .DEF rMin = R20 .DEF rNb = R21 ; ; *************************************** ; Reset und Interrupt-Vektoren ; *************************************** ; .CSEG .ORG $0000 rjmp Main ; Reset reti ; INT0 Interrupt reti ; INT1 Interrupt reti ; TC1Capt Interrupt rjmp TC1CmpA ; Timer 1 Compare A match reti ; TC1 Overflow Int reti ; TC0 Overflow Int reti ; UART0 RX Int reti ; UART0 TX Int reti ; Ana Comp Int reti ; PCINT reti ; TC1 CompB Int reti ; TC0 Compare Match A Int reti ; TC0 Compare Match B Int reti ; USISTART Int reti ; USIOverflow Int reti ; EERDY Int reti ; WDT Overflow Int ; ; *************************************** ; TC1 Compare Match A Interrupt Routine ; *************************************** ; TC1CmpA: set reti ; ; *************************************** ; Hauptprogramm ; *************************************** ; Main: ; Stack init ldi rmp,LOW(RAMEND) ; Setze Stackpointer out SPL,rmp ; Init Register ldi rFlg,0 ; Init Flagge ldi rLed,0 ; Init LEDs ldi rSec,0 ; Init Sekundenzaehler ldi rMin,1 ; Init Minutenzaehler ldi rNb,1 ; Init Beep-Zaehler ; Init OCR0A-Output fuer Beep sbi dOcO,bOcO ; Init Timer 1 fuer Zeit- und Lampensteuerung ldi rmp,HIGH(12500) ; CTC-Wert out OCR1AH,rmp ldi rmp,LOW(12500) out OCR1AL,rmp clr rmp ; Kontrollwort A out TCCR1A,rmp ldi rmp,(1<<WGM12)|(1<<CS11) ; auf CTC out TCCR1B,rmp ldi rmp,1<<OCIE1A ; Timer1-Interrupt out TIMSK,rmp ; Init Timer 0 fuer Beep-Ausgang ldi rmp,(1<<COM0A0)|(1<<WGM01) ; CTC, Toggle Compare A out TCCR0A,rmp ldi rmp,1<<CS01 ; beep an out TCCR0B,rmp ldi rmp,63 ; CTC-Wert out OCR0A,rmp ldi rmp,1<<SE ; sleep idle out MCUCR,rmp sei ; Enable Ints ; ; *************************************** ; Hauptprogramm - Loop ; *************************************** ; ; in Schlafmodus gehen An: sleep ; Schlafen nop ; Aufwachen brtc An ; T-Flag von Timer nicht gesetzt clt ; T-Flag zuruecksetzen sbrc rLed,0 ; Bit 0 ist Beep-Steuerung rjmp BeepAus tst rNb ; teste beeps = 0 breq BeepAus ; schalte Beep aus ldi rmp,1<<CS01 ; beep an out TCCR0B,rmp dec rNb ; verringere Beeps rjmp LedAus BeepAus: ldi rmp,0 ; Beep ausschalten, Timer stoppen out TCCR0B,rmp LedAus: ldi ZH,HIGH(TabAus) ; LED ausschalten ldi ZL,LOW(TabAus) add ZL,rLed ; aktuelle LED addieren brcc MachAus inc ZH MachAus: ijmp ; aktuelle LED deaktivieren Aus: ; LED ist ausgeschaltet inc rLed ; erhoehe Zaehler cpi rLed,10 ; schon bei Zehn? brne Weiter ; nein clr rLed ; ja, von vorne beginnen inc rSec ; 0,5-Sekundenzaehler erhoehen cpi rSec,30 ; schon bei 30? brcs Weiter ; nein, noch nicht clr rSec ; ja, loesche 0,5-Sekundenzaehler mov rNb,rMin ; Minuten in Beep-Zaehler kopieren inc rMin ; Minutenzaehler erhoehen cpi rMin,11 ; 10 Minuten abgelaufen? brcs Weiter ; nein clr rMin ; Mintenzaehler von vorne ldi rNb,255 ; Beep fuer ganze Minute an sbi dL0K,bL0K ; Ausgang gelbe Lampe an cbi pL0K,bL0K ; gelbe Lampe an Weiter: ; aktuelle LED rot oder gruen? ldi ZH,HIGH(TabRot) ; versuche es mit rot ldi ZL,LOW(TabRot) cp rLed,rMin ; aktuelle LED kleiner als Minutenzaehler brcs Weiter1 ; nein ldi ZH,HIGH(TabGruen) ; nein, es ist gruen ldi ZL,LOW(TabGruen) Weiter1: ; Lampe anschalten add ZL,rLed ; mache die richtige LED an brcc MachAn inc ZH MachAn: ; schalte Lampe an ijmp ; ; *************************************** ; Sprungtabellen ; *************************************** ; ; Sprungtabelle der Lampen-Routinen fuer Rot TabRot: rjmp L1R rjmp L2R rjmp L3R rjmp L4R rjmp L5R rjmp L6R rjmp L7R rjmp L8R rjmp L9R rjmp L10R ; Sprungtabelle der Lampen-Routinen fuer Gruen TabGruen: rjmp L1G rjmp L2G rjmp L3G rjmp L4G rjmp L5G rjmp L6G rjmp L7G rjmp L8G rjmp L9G rjmp L10G ; Sprungtabelle der Lampen-Routinen fuer Ausschalten TabAus: rjmp L1A rjmp L2A rjmp L3A rjmp L4A rjmp L5A rjmp L6A rjmp L7A rjmp L8A rjmp L9A rjmp L10A ; ; *************************************** ; Lampen - Schaltroutinen ; *************************************** ; ; Lampen ausschalten L1A: cbi pL1A,bL1A cbi pL1K,bL1K cbi dL1A,bL1A cbi dL1K,bL1K rjmp Aus L2A: cbi pL2A,bL2A cbi pL2K,bL2K cbi dL2A,bL2A cbi dL2K,bL2K rjmp Aus L3A: cbi pL3A,bL3A cbi pL3K,bL3K cbi dL3A,bL3A cbi dL3K,bL3K rjmp Aus L4A: cbi pL4A,bL4A cbi pL4K,bL4K cbi dL4A,bL4A cbi dL4K,bL4K rjmp Aus L5A: cbi pL5A,bL5A cbi pL5K,bL5K cbi dL5A,bL5A cbi dL5K,bL5K rjmp Aus L6A: cbi pL6A,bL6A cbi pL6K,bL6K cbi dL6A,bL6A cbi dL6K,bL6K rjmp Aus L7A: cbi pL7A,bL7A cbi pL7K,bL7K cbi dL7A,bL7A cbi dL7K,bL7K rjmp Aus L8A: cbi pL8A,bL8A cbi pL8K,bL8K cbi dL8A,bL8A cbi dL8K,bL8K rjmp Aus L9A: cbi pL9A,bL9A cbi pL9K,bL9K cbi dL9A,bL9A cbi dL9K,bL9K rjmp Aus L10A: cbi pL10A,bL10A cbi pL10K,bL10K cbi dL10A,bL10A cbi dL10K,bL10K rjmp Aus ; Lampe auf Gruen L1G: sbi pL1A,bL1A cbi pL1K,bL1K sbi dL1A,bL1A sbi dL1K,bL1K rjmp An L2G: sbi pL2A,bL2A cbi pL2K,bL2K sbi dL2A,bL2A sbi dL2K,bL2K rjmp An L3G: sbi pL3A,bL3A cbi pL3K,bL3K sbi dL3A,bL3A sbi dL3K,bL3K rjmp An L4G: sbi pL4A,bL4A cbi pL4K,bL4K sbi dL4A,bL4A sbi dL4K,bL4K rjmp An L5G: sbi pL5A,bL5A cbi pL5K,bL5K sbi dL5A,bL5A sbi dL5K,bL5K rjmp An L6G: sbi pL6A,bL6A cbi pL6K,bL6K sbi dL6A,bL6A sbi dL6K,bL6K rjmp An L7G: sbi pL7A,bL7A cbi pL7K,bL7K sbi dL7A,bL7A sbi dL7K,bL7K rjmp An L8G: sbi pL8A,bL8A cbi pL8K,bL8K sbi dL8A,bL8A sbi dL8K,bL8K rjmp An L9G: sbi pL9A,bL9A cbi pL9K,bL9K sbi dL9A,bL9A sbi dL9K,bL9K rjmp An L10G: sbi pL10A,bL10A cbi pL10K,bL10K sbi dL10A,bL10A sbi dL10K,bL10K rjmp An ; Lampe auf Rot L1R: cbi pL1A,bL1A sbi pL1K,bL1K sbi dL1A,bL1A sbi dL1K,bL1K rjmp An L2R: cbi pL2A,bL2A sbi pL2K,bL2K sbi dL2A,bL2A sbi dL2K,bL2K rjmp An L3R: cbi pL3A,bL3A sbi pL3K,bL3K sbi dL3A,bL3A sbi dL3K,bL3K rjmp An L4R: cbi pL4A,bL4A sbi pL4K,bL4K sbi dL4A,bL4A sbi dL4K,bL4K rjmp An L5R: cbi pL5A,bL5A sbi pL5K,bL5K sbi dL5A,bL5A sbi dL5K,bL5K rjmp An L6R: cbi pL6A,bL6A sbi pL6K,bL6K sbi dL6A,bL6A sbi dL6K,bL6K rjmp An L7R: cbi pL7A,bL7A sbi pL7K,bL7K sbi dL7A,bL7A sbi dL7K,bL7K rjmp An L8R: cbi pL8A,bL8A sbi pL8K,bL8K sbi dL8A,bL8A sbi dL8K,bL8K rjmp An L9R: cbi pL9A,bL9A sbi pL9K,bL9K sbi dL9A,bL9A sbi dL9K,bL9K rjmp An L10R: cbi pL10A,bL10A sbi pL10K,bL10K sbi dL10A,bL10A sbi dL10K,bL10K rjmp An ; ; Ende des Quellcodes ;

An den Seitenanfang
©2006 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/eieruhr/eieruhr_asm.html1/20/2009 7:40:49 PM

Geschenkprojekt AVR-Eieruhr

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: r 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. r 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.

An den Seitenanfang
©2006 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/eieruhr/eieruhr.html1/20/2009 7:40:57 PM

http://www.avr-asm-tutorial.net/avr_de/eieruhr/eieruhr.gif

http://www.avr-asm-tutorial.net/avr_de/eieruhr/eieruhr.gif1/20/2009 7:41:08 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/eieruhr.asm

; ************************************************** ; * ATtiny2313-Eieruhr, Version 1, 20.10.2006 * ; * (C)2006 by G.Schmidt info@avr-asm-tutorial.net * ; ************************************************** ; .NOLIST .INCLUDE "tn2313def.inc" .LIST ; ; *************************************** ; Fuses-Voreinstellungen im ATtiny2313V ; *************************************** ; ; - Internen RC-Oszillator auf 4 MHz, CLKSEL3:0 = 0010, SUT1:0 = 10 ; - Interner Clockteiler auf 8, CKDIV8 = Enabled ; ; *************************************** ; Hardware - Schaltung ; *************************************** ; ; __________ ; / |_| | ; +3V O--|Res VCC|--O +3V ; | | ; L1K O--|PD0 PB7|--O L10A ; | | ;L1A/L2K O--|PD1 PB6|--O L9A/L10K ; | AT | ; O--|PA1 PB5|--O L8A/L9K ; | tiny | ; O--|PA0 PB4|--O L7A/L8K ; | 2313 | ;L2A/L3K O--|PD2 PB3|--O ; | V | ;L4K/L3A O--|PD3 PB2|--||--O Lsp ; | (OC0A)| ;L5K/L4A O--|PD4 PB1|--O ; | | ;L6K/L5A O--|PD5 PB0|--O L0K ; | | ; GND O--|GND PD6|--O L6A/L7K ; |___________| ; ; ; *************************************** ; Definiere Ports und Portbits ; *************************************** ; .EQU pOcO = PORTB .EQU dOcO = DDRB .EQU bOcO = 2 ; .EQU pL0K = PORTB .EQU dL0K = DDRB .EQU bL0K = 0 ; .EQU pL1K = PORTD .EQU dL1K = DDRD .EQU bL1K = 0 ; .EQU pL1A = PORTD .EQU dL1A = DDRD .EQU bL1A = 1 ; .EQU pL2K = PORTD .EQU dL2K = DDRD .EQU bL2K = 1 ; .EQU pL2A = PORTD .EQU dL2A = DDRD .EQU bL2A = 2 ; .EQU pL3K = PORTD .EQU dL3K = DDRD .EQU bL3K = 2 ; .EQU pL3A = PORTD .EQU dL3A = DDRD .EQU bL3A = 3 ; .EQU pL4K = PORTD .EQU dL4K = DDRD .EQU bL4K = 3 ; .EQU pL4A = PORTD .EQU dL4A = DDRD .EQU bL4A = 4 ; .EQU pL5K = PORTD .EQU dL5K = DDRD .EQU bL5K = 4 ; .EQU pL5A = PORTD .EQU dL5A = DDRD .EQU bL5A = 5 ; .EQU pL6K = PORTD .EQU dL6K = DDRD .EQU bL6K = 5 ; .EQU pL6A = PORTD .EQU dL6A = DDRD .EQU bL6A = 6 ; .EQU pL7K = PORTD .EQU dL7K = DDRD .EQU bL7K = 6 ; .EQU pL7A = PORTB .EQU dL7A = DDRB .EQU bL7A = 4 ; .EQU pL8K = PORTB .EQU dL8K = DDRB .EQU bL8K = 4 ; .EQU pL8A = PORTB .EQU dL8A = DDRB .EQU bL8A = 5 ; .EQU pL9K = PORTB .EQU dL9K = DDRB .EQU bL9K = 5 ; .EQU pL9A = PORTB .EQU dL9A = DDRB .EQU bL9A = 6 ; .EQU pL10K = PORTB .EQU dL10K = DDRB .EQU bL10K = 6 ; .EQU pL10A = PORTB .EQU dL10A = DDRB .EQU bL10A = 7 ; ; *************************************** ; Funktionsweise Timing ; *************************************** ; ; Timer1 = Zeitmessung ; _______ ______ ______ ______ _____ ______ ; |RC-Osc.| | CDiv |500|Presc.|62,5|TC1CNT|5Hz| LED |0,5|MinCnt| ; | 4 MHz |===>| / 8 |==>| / 8 |===>|/12500|==>|Count|==>| / 30 |==>1 Min. ; |_______| |______|kHz|______|kHz |______| |_____|Hz |______| ; | ; Timer0 = NF-Erzeugung| ; | ______ ______ ; | |Presc.| |TC0CNT|992Hz ; |=>| / 8 |==>| / 63 |===> OC0A = Beep-Lautsprecher ; |______| |______| ; ; ; *************************************** ; Register ; *************************************** ; .DEF rmp = R16 .DEF rFlg = R17 .DEF rLed = R18 .DEF rSec = R19 .DEF rMin = R20 .DEF rNb = R21 ; ; *************************************** ; Reset und Interrupt-Vektoren ; *************************************** ; .CSEG .ORG $0000 rjmp Main ; Reset reti ; INT0 Interrupt reti ; INT1 Interrupt reti ; TC1Capt Interrupt rjmp TC1CmpA ; Timer 1 Compare A match reti ; TC1 Overflow Int reti ; TC0 Overflow Int reti ; UART0 RX Int reti ; UART0 TX Int reti ; Ana Comp Int reti ; PCINT reti ; TC1 CompB Int reti ; TC0 Compare Match A Int reti ; TC0 Compare Match B Int reti ; USISTART Int reti ; USIOverflow Int reti ; EERDY Int reti ; WDT Overflow Int ; ; *************************************** ; TC1 Compare Match A Interrupt Routine ; *************************************** ; TC1CmpA: set reti ; ; *************************************** ; Hauptprogramm ; *************************************** ; Main: ; Stack init ldi rmp,LOW(RAMEND) ; Setze Stackpointer out SPL,rmp ; Init Register ldi rFlg,0 ; Init Flagge ldi rLed,0 ; Init LEDs ldi rSec,0 ; Init Sekundenzaehler ldi rMin,1 ; Init Minutenzaehler ldi rNb,1 ; Init Beep-Zaehler ; Init OCR0A-Output fuer Beep sbi dOcO,bOcO ; Init Timer 1 fuer Zeit- und Lampensteuerung ldi rmp,HIGH(12500) ; CTC-Wert out OCR1AH,rmp ldi rmp,LOW(12500) out OCR1AL,rmp clr rmp ; Kontrollwort A out TCCR1A,rmp ldi rmp,(1<<WGM12)|(1<<CS11) ; auf CTC out TCCR1B,rmp ldi rmp,1<<OCIE1A ; Timer1-Interrupt out TIMSK,rmp ; Init Timer 0 fuer Beep-Ausgang ldi rmp,(1<<COM0A0)|(1<<WGM01) ; CTC, Toggle Compare A out TCCR0A,rmp ldi rmp,1<<CS01 ; beep an out TCCR0B,rmp ldi rmp,63 ; CTC-Wert out OCR0A,rmp ldi rmp,1<<SE ; sleep idle out MCUCR,rmp sei ; Enable Ints ; ; *************************************** ; Hauptprogramm - Loop ; *************************************** ; ; in Schlafmodus gehen An: sleep ; Schlafen nop ; Aufwachen brtc An ; T-Flag von Timer nicht gesetzt clt ; T-Flag zuruecksetzen sbrc rLed,0 ; Bit 0 ist Beep-Steuerung rjmp BeepAus tst rNb ; teste beeps = 0 breq BeepAus ; schalte Beep aus ldi rmp,1<<CS01 ; beep an out TCCR0B,rmp dec rNb ; verringere Beeps rjmp LedAus BeepAus: ldi rmp,0 ; Beep ausschalten, Timer stoppen out TCCR0B,rmp LedAus: ldi ZH,HIGH(TabAus) ; LED ausschalten ldi ZL,LOW(TabAus) add ZL,rLed ; aktuelle LED addieren brcc MachAus inc ZH MachAus: ijmp ; aktuelle LED deaktivieren Aus: ; LED ist ausgeschaltet inc rLed ; erhoehe Zaehler cpi rLed,10 ; schon bei Zehn? brne Weiter ; nein clr rLed ; ja, von vorne beginnen inc rSec ; 0,5-Sekundenzaehler erhoehen cpi rSec,30 ; schon bei 30? brcs Weiter ; nein, noch nicht clr rSec ; ja, loesche 0,5-Sekundenzaehler mov rNb,rMin ; Minuten in Beep-Zaehler kopieren inc rMin ; Minutenzaehler erhoehen cpi rMin,11 ; 10 Minuten abgelaufen? brcs Weiter ; nein clr rMin ; Mintenzaehler von vorne ldi rNb,255 ; Beep fuer ganze Minute an sbi dL0K,bL0K ; Ausgang gelbe Lampe an cbi pL0K,bL0K ; gelbe Lampe an Weiter: ; aktuelle LED rot oder gruen? ldi ZH,HIGH(TabRot) ; versuche es mit rot ldi ZL,LOW(TabRot) cp rLed,rMin ; aktuelle LED kleiner als Minutenzaehler brcs Weiter1 ; nein ldi ZH,HIGH(TabGruen) ; nein, es ist gruen ldi ZL,LOW(TabGruen) Weiter1: ; Lampe anschalten add ZL,rLed ; mache die richtige LED an brcc MachAn inc ZH MachAn: ; schalte Lampe an ijmp ; ; *************************************** ; Sprungtabellen ; *************************************** ; ; Sprungtabelle der Lampen-Routinen fuer Rot TabRot: rjmp L1R rjmp L2R rjmp L3R rjmp L4R rjmp L5R rjmp L6R rjmp L7R rjmp L8R rjmp L9R rjmp L10R ; Sprungtabelle der Lampen-Routinen fuer Gruen TabGruen: rjmp L1G rjmp L2G rjmp L3G rjmp L4G rjmp L5G rjmp L6G rjmp L7G rjmp L8G rjmp L9G rjmp L10G ; Sprungtabelle der Lampen-Routinen fuer Ausschalten TabAus: rjmp L1A rjmp L2A rjmp L3A rjmp L4A rjmp L5A rjmp L6A rjmp L7A rjmp L8A rjmp L9A rjmp L10A ; ; *************************************** ; Lampen - Schaltroutinen ; *************************************** ; ; Lampen ausschalten L1A: cbi pL1A,bL1A cbi pL1K,bL1K cbi dL1A,bL1A cbi dL1K,bL1K rjmp Aus L2A: cbi pL2A,bL2A cbi pL2K,bL2K cbi dL2A,bL2A cbi dL2K,bL2K rjmp Aus L3A: cbi pL3A,bL3A cbi pL3K,bL3K cbi dL3A,bL3A cbi dL3K,bL3K rjmp Aus L4A: cbi pL4A,bL4A cbi pL4K,bL4K cbi dL4A,bL4A cbi dL4K,bL4K rjmp Aus L5A: cbi pL5A,bL5A cbi pL5K,bL5K cbi dL5A,bL5A cbi dL5K,bL5K rjmp Aus L6A: cbi pL6A,bL6A cbi pL6K,bL6K cbi dL6A,bL6A cbi dL6K,bL6K rjmp Aus L7A: cbi pL7A,bL7A cbi pL7K,bL7K cbi dL7A,bL7A cbi dL7K,bL7K rjmp Aus L8A: cbi pL8A,bL8A cbi pL8K,bL8K cbi dL8A,bL8A cbi dL8K,bL8K rjmp Aus L9A: cbi pL9A,bL9A cbi pL9K,bL9K cbi dL9A,bL9A cbi dL9K,bL9K rjmp Aus L10A: cbi pL10A,bL10A cbi pL10K,bL10K cbi dL10A,bL10A cbi dL10K,bL10K rjmp Aus ; Lampe auf Gruen L1G: sbi pL1A,bL1A cbi pL1K,bL1K sbi dL1A,bL1A sbi dL1K,bL1K rjmp An L2G: sbi pL2A,bL2A cbi pL2K,bL2K sbi dL2A,bL2A sbi dL2K,bL2K rjmp An L3G: sbi pL3A,bL3A cbi pL3K,bL3K sbi dL3A,bL3A sbi dL3K,bL3K rjmp An L4G: sbi pL4A,bL4A cbi pL4K,bL4K sbi dL4A,bL4A sbi dL4K,bL4K rjmp An L5G: sbi pL5A,bL5A cbi pL5K,bL5K sbi dL5A,bL5A sbi dL5K,bL5K rjmp An L6G: sbi pL6A,bL6A cbi pL6K,bL6K sbi dL6A,bL6A sbi dL6K,bL6K rjmp An L7G: sbi pL7A,bL7A cbi pL7K,bL7K sbi dL7A,bL7A sbi dL7K,bL7K rjmp An L8G: sbi pL8A,bL8A cbi pL8K,bL8K sbi dL8A,bL8A sbi dL8K,bL8K rjmp An L9G: sbi pL9A,bL9A cbi pL9K,bL9K sbi dL9A,bL9A sbi dL9K,bL9K rjmp An L10G: sbi pL10A,bL10A cbi pL10K,bL10K sbi dL10A,bL10A sbi dL10K,bL10K rjmp An ; Lampe auf Rot L1R: cbi pL1A,bL1A sbi pL1K,bL1K sbi dL1A,bL1A sbi dL1K,bL1K rjmp An L2R: cbi pL2A,bL2A sbi pL2K,bL2K sbi dL2A,bL2A sbi dL2K,bL2K rjmp An L3R: cbi pL3A,bL3A sbi pL3K,bL3K sbi dL3A,bL3A sbi dL3K,bL3K rjmp An L4R: cbi pL4A,bL4A sbi pL4K,bL4K sbi dL4A,bL4A sbi dL4K,bL4K rjmp An L5R: cbi pL5A,bL5A sbi pL5K,bL5K sbi dL5A,bL5A sbi dL5K,bL5K rjmp An L6R: cbi pL6A,bL6A sbi pL6K,bL6K sbi dL6A,bL6A sbi dL6K,bL6K rjmp An L7R: cbi pL7A,bL7A sbi pL7K,bL7K sbi dL7A,bL7A sbi dL7K,bL7K rjmp An L8R: cbi pL8A,bL8A sbi pL8K,bL8K sbi dL8A,bL8A sbi dL8K,bL8K rjmp An L9R: cbi pL9A,bL9A sbi pL9K,bL9K sbi dL9A,bL9A sbi dL9K,bL9K rjmp An L10R: cbi pL10A,bL10A sbi pL10K,bL10K sbi dL10A,bL10A sbi dL10K,bL10K rjmp An ; ; Ende des Quellcodes ;
http://www.avr-asm-tutorial.net/avr_de/quellen/eieruhr.asm1/20/2009 7:41:10 PM

Quellcode Schrittmotor-Steuerung mit ATtiny13

Pfad: Home => AVR-Übersicht => Anwendungen => Steppermotor-Steuerung => Quellcode

Quellcode der SchrittmotorSteuerung mit ATtiny13

; *************************************************** ; * Schrittmotor-Steuerung mit 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 schwarz ; ___ / | | | ___ | ; +5V-|___|--|RES VCC|--+5V B3-|I4 O4|-|___|-+Q4 rot ; | | | | ___ | ; B3--|PB3 PB2|---------|I5 O5|-|___|-+Q2 braun ; | | | | ___ | ; Analog-In--|PB4 PB1|---------|I6 O6|-|___|-+Q3 gruen ; | | | | ___ | ; |--|GND PB0|---------|I7 O7|-|___|-+Q1 weiss ; |________| | | | ; ATtiny13 |-|GND CD|-------+ ; |_______| ; ULN2003 ; ; Funktionsweise: ; Ein Schrittmotor wird mit einer analogen Eingangs; spannung an Pin 3 gesteuert. Die Eingangsspannung ; liegt zwischen Null und der Betriebsspannung. Die ; Anzahl Einzelschritte des Motor fuer Vollausschlag ; sind mit der Konstanten cSmSteps einstellbar (1... ; 65535 Schritte). ; Ansteuerung Schrittmotor: ; Portbit PB0 PB2 PB1 PB3 | | ; Farbe ws bn gn rt | Port | Byte ; Schritt 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 ; entspricht: .dw 0x0605,0x090A ; ; Timer TC0: ; Timer läuft bei einem Prozessortakt von 1,2 MHz mit ; einem Vorteiler von 1024 im CTC-Modus mit CompareA ; als TOP-Wert. Interrupt bei CompareA bzw. beim ; CTC-Reset. ; Bei CompareA wird der Soll- und Ist-Wert des ; Schrittmotors verglichen. Ist der Ist-Wert zu ; hoch, wird ein Schritt zurueck gefahren, ist ; der Ist-Wert zu niedrig, wird ein Schritt vor; waerts gefahren. Stimmen Soll- und Ist-Wert ; ueberein, werden nach einer einstellbaren Verzoe; gerung die Magnete des Schrittmotor abgeschaltet ; Timing: 1,2 MHz / 1024 / CompA, ; bei CompA = 255: f(Schrittmotor) = 4,57 Hz ; bei CompA = 8 : f(Schrittmotor) = 146 Hz ; ADC: ; Umwandlung der an Pin 3 / PB4 /ADC2 anliegenden ; Analogspannung, Aufsummieren von 64 Messungen ; zur Mittelung, Umrechnung in die Sollstellung ; des Schrittmotors ; Timing: 1,2 MHz / 128 = 9,375 kHz = 106,7 us ; Conversion = 13 cycles = 1,387 ms / Konversion ; = 721 Konversionen pro Sekunde ; Mittelung ueber 64 Konversionen = 88,75 ms = 11,3 / s ; ; Konstanten ; .equ cSmSteps = 1276 ; 2552/2, Anzahl Schritte pro Umdrehung .equ cSmFreq = 145 ; Frequenz Schrittmotorgeschwindigkeit ; ; Minimum: 5 Hz, Maximum: 1171 Hz .equ cSmDelay = 390 ; Anzahl Takte vor Abschalten der Magnete ; ; Abgeleitete Konstanten ; .equ clock = 1200000 ; Taktfrequenz Tiny13 .equ Tc0Ctc = 1024 ; Vorteiler TC0 .equ cCmpA = clock / 1024 / cSmFreq ; ; Ueberpruefung der Konstanten ; .IF cCmpA > 255 .ERROR "Schrittmotor zu langsam!" .ENDIF .IF cCmpA < 1 .ERROR "Schrittmotor zu schnell!" .ENDIF ; ; SRAM ; ; Register ; ; benutzt: R0 fuer Tabellenlesen ; benutzt: R8:R1 fuer Rechnen ; frei: R10:R8 .def rAdcCL = R11 ; ADC Rechenwert LSB .def rAdcCH = R12 ; dto., MSB .def rAdcRL = R13 ; ADC Uebergabe LSB .def rAdcRH = R14 ; dto., MSB .def rSreg = R15 ; Status Sicherungs-Register .def rmp = R16 ; Multipurpose outside Int .def rimp = R17 ; Multipurpose inside Int .def rFlg = R18 ; Flaggen .equ bAdc = 0 ; Adc conversion complete flag .def rAdcC = R19 ; ADC Zaehler (64 Adc-Werte) .def rAdcL = R20 ; ADC Addier-Register LSB .def rAdcH = R21 ; dto., MSB .def rSmSL = R22 ; Stepmotor-Sollwert LSB .def rSmSH = R23 ; dto., MSB .def rSmIL = R24 ; Stepmotor-Istwert LSB .def rSmIH = R25 ; dto., MSB ; benutzt: X fuer Nachlauf der Magnete ; frei: Y ; benutzt: Z fuer Tabellenwert-Zugriff ; ; ********************************** ; Code Segment Start, Int - Vektor ; ********************************** ; .cseg .org $0000 ; rjmp Main ; Reset Vektor reti ; INT0 Vektor reti ; PCINT0 Vektor reti ; TIM0_OVF Vektor reti ; EE_RDY Vektor reti ; ANA_COMP Vektor rjmp Tc0IntCA ; TIM0_COMPA Vektor reti ; TIM0_COMPB Vektor reti ; WDT Vektor rjmp AdcInt ; ADC Vektor ; ; ********************************** ; Interrupt Service Routinen ; ********************************** ; ; Timer-Counter 0 Compare A Interrupt Service Routine ; Tc0IntCA: in rSreg,SREG ; rette Status cp rSmIL,rSmSL ; vergleiche Ist mit Soll cpc rSmIH,rSmSH breq Tc0IntCA0 brcs Tc0IntCAF ; Ist < Soll sbiw rSmIL,1 ; Ist > Soll, ein Schritt rueckwaerts rjmp Tc0IntCAS Tc0IntCAF: adiw rSmIL,1 ; ein Schritt vorwaerts Tc0IntCAS: mov rimp,rSmIL ; kopiere Zaehlerstand andi rimp,0x03 ; isoliere unterste zwei Bits ldi ZH,HIGH(2*SmTab) ; Zeige auf Tabelle ldi ZL,LOW(2*SmTab) add ZL,rimp ldi rimp,0 adc ZH,rimp lpm ; lese Wert aus Tabelle .IF debug_out == 0 out PORTB,R0 ; schreibe auf Port .ENDIF ldi XH,HIGH(cSmDelay) ; Delay-Zaehler-Restart ldi XL,LOW(cSmDelay) out SREG,rSreg ; Stelle Status wieder her reti Tc0IntCA0: sbiw XL,1 ; Delay-Zaehler verringern brne Tc0IntCAD ; noch nicht Null ldi rimp,0 ; Magnete ausschalten out PORTB,rimp ; an Ausgangstreiber ldi XH,HIGH(cSmDelay) ; Delay-Zaehler-Restart ldi XL,LOW(cSmDelay) Tc0IntCAD: out SREG,rSreg ; stelle Status wieder her reti ; SmTab: .dw 0x0605,0x090A ; ; Adc Conversion Complete Interrupt Service Routine ; AdcInt: in rSreg,SREG ; rette Status in rimp,ADCL ; lese LSB Ergebnis add rAdcL,rimp ; addiere zum Ergebnis in rimp,ADCH ; lese MSB Ergebnis adc rAdcH,rimp ; addiere zum Ergebnis dec rAdcC ; Erniedrige Zaehler brne AdcInt1 mov rAdcRL,rAdcL ; Kopiere Ergebnis mov rAdcRH,rAdcH clr rAdcH ; Loesche Summe clr rAdcL ldi rAdcC,64 ; Setze Zaehler neu sbr rFlg,1<<bAdc ; Setze Flagge AdcInt1: ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)| (1<<ADPS0) out ADCSRA,rmp out SREG,rSreg ; stelle Status wieder her reti ; ; ********************************** ; Hauptprogramm Init und Loop ; ********************************** ; Main: ; Stack Init ldi rmp, LOW(RAMEND) out SPL,rmp ; Init Register clr rFlg ; Flag-Register auf Null clr rSmIL ; Ist-Zaehler Stepmotor auf Null clr rSmIH clr rSmSL ; Soll-Wert Stepmotor auf Null clr rSmSH ldi XH,HIGH(cSmDelay) ; Delay-Zaehler-Restart ldi XL,LOW(cSmDelay) ; Init Output-Port ldi rmp,0x0F ; Ausgabe auf Port 0 is 3 out DDRB,rmp ldi rmp,0x05 ; Schrittmotor auf Schritt 1 setzen out PORTB,rmp ; Debugging session .IF debug_calc .equ adc_result = 128 ldi rmp,HIGH(adc_result) mov rAdcRH,rmp ldi rmp,LOW(adc_result) mov rAdcRL,rmp rjmp dcalc .ENDIF .IF debug_const .equ const = cMSteps ldi rmp,HIGH(const) mov rSmSH,rmp ldi rmp,LOW(const) mov rSmSL,rmp .ENDIF ; Init ADC ldi rAdcC,64 ; Setze Zaehler neu clr rAdcL ; Setze Ergebnis auf Null clr rAdcH ldi rmp,1<<ADC2D ; Digital Input Disable out DIDR0,rmp ldi rmp,1<<MUX1 ; ADC auf Kanal 2 out ADMUX,rmp ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)| (1<<ADPS0) out ADCSRA,rmp ; TC0 initiieren ldi rmp,cCmpA ; CTC-Wert setzen out OCR0A,rmp ldi rmp,1<<WGM01 ; CTC-Mode out TCCR0A,rmp ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler = 1024 out TCCR0B,rmp ldi rmp,1<<OCIE0A ; Interrupt Compare Match A enable out TIMSK0,rmp ; Sleep Mode und Int Enable ldi rmp,1<<SE ; Schlafen Enable out MCUCR,rmp sei ; Int Enable Loop: sleep ; schlafen nop ; Dummy fuer Aufwachen sbrc rFlg,bAdc ; ADC-Flagge? rcall AdcRdy ; wandle ADC-Ergebnis um rjmp Loop ; ; ********************************** ; Rechenroutinen ; ********************************** ; ; ADC Umwandlungs-Egebnis fertig ; AdcRdy: cbr rFlg,1<<bAdc ; setze Flagge zurueck .IF debug_const ret .ENDIF dcalc: mov rAdcCH,rAdcRH ; kopiere Messwert mov rAdcCL,rAdcRL ldi rmp,LOW(cSmSteps) ; Anzahl Schritte in R4:R3:R2:R1 mov R1,rmp ldi rmp,HIGH(cSmSteps) mov R2,rmp clr R3 clr R4 clr R5 ; Ergebnis in R8:R7:R6:R5 loeschen clr R6 clr R7 clr R8 AdcRdy1: lsr rAdcCH ; ein Bit herausschieben ror rAdcCL brcc AdcRdy2 ; nicht addieren add R5,R1 ; addieren adc R6,R2 adc R7,R3 adc R8,R4 AdcRdy2: lsl R1 ; Multiplikator mal zwei rol R2 rol R3 rol R4 mov rmp,rAdcCL ; = Null? or rmp,rAdcCH brne AdcRdy1 ; weiter multiplizieren ldi rmp,0x80 ; aufrunden add R5,rmp adc R6,rmp ldi rmp,0 adc R7,rmp adc R8,rmp cli ; Interrupts aus mov rSmSL,R7 ; Stepmotor-Sollwert LSB mov rSmSH,R8 ; dto., setze MSB .IF debug_out out PORTB,rSmSL .ENDIF sei ; Interrupts wieder an ret ; ; Ende Source Code ;

©2007 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/stepper/schrittmotor_v1.html1/20/2009 7:41:15 PM

Schrittmotor-Steuerung mit ATtiny13

Pfad: Home => 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:

q q

q q q q

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.

0. Inhalt
q

q

1. Hardware r 1.1 Prozessorteil r 1.2 ISP-Interface r 1.3 Spulentreiber ULN2003A r 1.4 Stromversorgung 2. Software r 2.1 Funktionsweise r 2.2 Einstellungen vor dem Assemblieren r 2.3 Kommentierter Quellcode

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:
q q q q q

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
q q q q

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:
q q

q

q

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: r Referenzspannung ist die Betriebsspannung des ATtiny13, r 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. r Interrupt nach jeder abgeschlossenen Messung, r 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: r 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, r nach jedem CTC-Zyklus wird ein Interrupt ausgelöst, der Compare-Match-Interrupt A wird ermöglicht, r 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
q q q

q q q

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:
q q q q

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
q q q q

Schritt 1: 0x05, binär 0 1 0 1, Schritt 2: 0x06, binär 0 1 1 0, Schritt 3: 0x0A, binär 1 0 1 0, Schritt 4: 0x09, binär 1 0 0 1.

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:
q q

q

q

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 Q2 Q3 Q4 rot grün braun weiß PB3 PB1 PB2 PB0 0 0 1 1 0 1 1 0 1 1 0 0 1 0 0 1

Daraus ergeben sich folgende Kodierungen: Magnet 1 0 0 1 1 1 0 0 0 1 1 0 0 0 1 1 0 0 1 1 Portbit 1 1 0 0 0 1 1 0 1 0 0 1

Step 1 2 3 4

Q4 Q3 Q2 Q1 PB3 PB2 PB1 PB0

Byte Word 0x05 0x06 0x0A 0x09 0x0605 0x090A

2.3 Kommentierter Quellcode
Der Quellcode in .asm-Textformat ist hier erhältlich, in .html-Format hier.
©2007 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/stepper/stepper.html1/20/2009 7:41:41 PM

J1 AGND +12V Ain DGND 100 nK 1N4007 100µ16V +

+100µ40V 1N4007 78L05 +5V In Out +1µT Gnd +2µ2T 36V 16V

10k

100 nK

I1

O1

I2

O2

1N 4148

10k

I3

O3

StepperMotor J2 bl KP4M4-001 rd bn gn wt Q4 Q2 Q3 Q1

RST

RESET

VCC

I4

100nK
PB3

ULN 2003

O4

100 nF MKT

PB4

AT tiny 13

PB2

SCK

I5

O5

PB1

1N 4148

MISO

I6

O6

GND

PB0

MOSI

I7

O7

GND VTG 2 4 MOSI 6 GND

CD

J3 ISP6

1 MISO

3 SCK

5 RST

Steppermotor ATtiny13
Version 1, June 2007 http://www.avr-asm-tutorial.net

J1 Si 100mA J1 230 V 230V 7.5VA 1N4007 12V 0.3A 12V 0.3A 1N4007 +1µT 36V
In

7812
Gnd

+1µT 16V 1k4

AGND +12V Ain DGND

Out

+2200µF 24V

1k 10-turn

Steppermotor Supply
Version 1, June 2007 http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/quellen/schrittmotor_v1.asm

; *************************************************** ; * Schrittmotor-Steuerung mit 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 schwarz ; ___ / | | | ___ | ; +5V-|___|--|RES VCC|--+5V B3-|I4 O4|-|___|-+Q4 rot ; | | | | ___ | ; B3--|PB3 PB2|---------|I5 O5|-|___|-+Q2 braun ; | | | | ___ | ; Analog-In--|PB4 PB1|---------|I6 O6|-|___|-+Q3 gruen ; | | | | ___ | ; |--|GND PB0|---------|I7 O7|-|___|-+Q1 weiss ; |________| | | | ; ATtiny13 |-|GND CD|-------+ ; |_______| ; ULN2003 ; ; Funktionsweise: ; Ein Schrittmotor wird mit einer analogen Eingangs; spannung an Pin 3 gesteuert. Die Eingangsspannung ; liegt zwischen Null und der Betriebsspannung. Die ; Anzahl Einzelschritte des Motor fuer Vollausschlag ; sind mit der Konstanten cSmSteps einstellbar (1... ; 65535 Schritte). ; Ansteuerung Schrittmotor: ; Portbit PB0 PB2 PB1 PB3 | | ; Farbe ws bn gn rt | Port | Byte ; Schritt 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 ; entspricht: .dw 0x0605,0x090A ; ; Timer TC0: ; Timer laeuft bei einem Prozessortakt von 1,2 MHz mit ; einem Vorteiler von 1024 im CTC-Modus mit CompareA ; als TOP-Wert. Interrupt bei CompareA bzw. beim ; CTC-Reset. ; Bei CompareA wird der Soll- und Ist-Wert des ; Schrittmotors verglichen. Ist der Ist-Wert zu ; hoch, wird ein Schritt zurueck gefahren, ist ; der Ist-Wert zu niedrig, wird ein Schritt vor; waerts gefahren. Stimmen Soll- und Ist-Wert ; ueberein, werden nach einer einstellbaren Verzoe; gerung die Magnete des Schrittmotor abgeschaltet ; Timing: 1,2 MHz / 1024 / CompA, ; bei CompA = 255: f(Schrittmotor) = 4,57 Hz ; bei CompA = 8 : f(Schrittmotor) = 146 Hz ; ADC: ; Umwandlung der an Pin 3 / PB4 /ADC2 anliegenden ; Analogspannung, Aufsummieren von 64 Messungen ; zur Mittelung, Umrechnung in die Sollstellung ; des Schrittmotors ; Timing: 1,2 MHz / 128 = 9,375 kHz = 106,7 us ; Conversion = 13 cycles = 1,387 ms / Konversion ; = 721 Konversionen pro Sekunde ; Mittelung ueber 64 Konversionen = 88,75 ms = 11,3 / s ; ; Konstanten ; .equ cSmSteps = 1276 ; 2552/2, Anzahl Schritte pro Umdrehung .equ cSmFreq = 145 ; Frequenz Schrittmotorgeschwindigkeit ; ; Minimum: 5 Hz, Maximum: 1171 Hz .equ cSmDelay = 390 ; Anzahl Takte vor Abschalten der Magnete ; ; Abgeleitete Konstanten ; .equ clock = 1200000 ; Taktfrequenz Tiny13 .equ Tc0Ctc = 1024 ; Vorteiler TC0 .equ cCmpA = clock / 1024 / cSmFreq ; ; Ueberpruefung der Konstanten ; .IF cCmpA > 255 .ERROR "Schrittmotor zu langsam!" .ENDIF .IF cCmpA < 1 .ERROR "Schrittmotor zu schnell!" .ENDIF ; ; SRAM ; ; Register ; ; benutzt: R0 fuer Tabellenlesen ; benutzt: R8:R1 fuer Rechnen ; frei: R10:R8 .def rAdcCL = R11 ; ADC Rechenwert LSB .def rAdcCH = R12 ; dto., MSB .def rAdcRL = R13 ; ADC Uebergabe LSB .def rAdcRH = R14 ; dto., MSB .def rSreg = R15 ; Status Sicherungs-Register .def rmp = R16 ; Multipurpose outside Int .def rimp = R17 ; Multipurpose inside Int .def rFlg = R18 ; Flaggen .equ bAdc = 0 ; Adc conversion complete flag .def rAdcC = R19 ; ADC Zaehler (64 Adc-Werte) .def rAdcL = R20 ; ADC Addier-Register LSB .def rAdcH = R21 ; dto., MSB .def rSmSL = R22 ; Stepmotor-Sollwert LSB .def rSmSH = R23 ; dto., MSB .def rSmIL = R24 ; Stepmotor-Istwert LSB .def rSmIH = R25 ; dto., MSB ; benutzt: X fuer Nachlauf der Magnete ; frei: Y ; benutzt: Z fuer Tabellenwert-Zugriff ; ; ********************************** ; Code Segment Start, Int - Vektor ; ********************************** ; .cseg .org $0000 ; rjmp Main ; Reset Vektor reti ; INT0 Vektor reti ; PCINT0 Vektor reti ; TIM0_OVF Vektor reti ; EE_RDY Vektor reti ; ANA_COMP Vektor rjmp Tc0IntCA ; TIM0_COMPA Vektor reti ; TIM0_COMPB Vektor reti ; WDT Vektor rjmp AdcInt ; ADC Vektor ; ; ********************************** ; Interrupt Service Routinen ; ********************************** ; ; Timer-Counter 0 Compare A Interrupt Service Routine ; Tc0IntCA: in rSreg,SREG ; rette Status cp rSmIL,rSmSL ; vergleiche Ist mit Soll cpc rSmIH,rSmSH breq Tc0IntCA0 brcs Tc0IntCAF ; Ist < Soll sbiw rSmIL,1 ; Ist > Soll, ein Schritt rueckwaerts rjmp Tc0IntCAS Tc0IntCAF: adiw rSmIL,1 ; ein Schritt vorwaerts Tc0IntCAS: mov rimp,rSmIL ; kopiere Zaehlerstand andi rimp,0x03 ; isoliere unterste zwei Bits ldi ZH,HIGH(2*SmTab) ; Zeige auf Tabelle ldi ZL,LOW(2*SmTab) add ZL,rimp ldi rimp,0 adc ZH,rimp lpm ; lese Wert aus Tabelle .IF debug_out == 0 out PORTB,R0 ; schreibe auf Port .ENDIF ldi XH,HIGH(cSmDelay) ; Delay-Zaehler-Restart ldi XL,LOW(cSmDelay) out SREG,rSreg ; Stelle Status wieder her reti Tc0IntCA0: sbiw XL,1 ; Delay-Zaehler verringern brne Tc0IntCAD ; noch nicht Null ldi rimp,0 ; Magnete ausschalten out PORTB,rimp ; an Ausgangstreiber ldi XH,HIGH(cSmDelay) ; Delay-Zaehler-Restart ldi XL,LOW(cSmDelay) Tc0IntCAD: out SREG,rSreg ; stelle Status wieder her reti ; SmTab: .dw 0x0605,0x090A ; ; Adc Conversion Complete Interrupt Service Routine ; AdcInt: in rSreg,SREG ; rette Status in rimp,ADCL ; lese LSB Ergebnis add rAdcL,rimp ; addiere zum Ergebnis in rimp,ADCH ; lese MSB Ergebnis adc rAdcH,rimp ; addiere zum Ergebnis dec rAdcC ; Erniedrige Zaehler brne AdcInt1 mov rAdcRL,rAdcL ; Kopiere Ergebnis mov rAdcRH,rAdcH clr rAdcH ; Loesche Summe clr rAdcL ldi rAdcC,64 ; Setze Zaehler neu sbr rFlg,1<<bAdc ; Setze Flagge AdcInt1: ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) out ADCSRA,rmp out SREG,rSreg ; stelle Status wieder her reti ; ; ********************************** ; Hauptprogramm Init und Loop ; ********************************** ; Main: ; Stack Init ldi rmp, LOW(RAMEND) out SPL,rmp ; Init Register clr rFlg ; Flag-Register auf Null clr rSmIL ; Ist-Zaehler Stepmotor auf Null clr rSmIH clr rSmSL ; Soll-Wert Stepmotor auf Null clr rSmSH ldi XH,HIGH(cSmDelay) ; Delay-Zaehler-Restart ldi XL,LOW(cSmDelay) ; Init Output-Port ldi rmp,0x0F ; Ausgabe auf Port 0 is 3 out DDRB,rmp ldi rmp,0x05 ; Schrittmotor auf Schritt 1 setzen out PORTB,rmp ; Debugging session .IF debug_calc .equ adc_result = 128 ldi rmp,HIGH(adc_result) mov rAdcRH,rmp ldi rmp,LOW(adc_result) mov rAdcRL,rmp rjmp dcalc .ENDIF .IF debug_const .equ const = cMSteps ldi rmp,HIGH(const) mov rSmSH,rmp ldi rmp,LOW(const) mov rSmSL,rmp .ENDIF ; Init ADC ldi rAdcC,64 ; Setze Zaehler neu clr rAdcL ; Setze Ergebnis auf Null clr rAdcH ldi rmp,1<<ADC2D ; Digital Input Disable out DIDR0,rmp ldi rmp,1<<MUX1 ; ADC auf Kanal 2 out ADMUX,rmp ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0) out ADCSRA,rmp ; TC0 initiieren ldi rmp,cCmpA ; CTC-Wert setzen out OCR0A,rmp ldi rmp,1<<WGM01 ; CTC-Mode out TCCR0A,rmp ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler = 1024 out TCCR0B,rmp ldi rmp,1<<OCIE0A ; Interrupt Compare Match A enable out TIMSK0,rmp ; Sleep Mode und Int Enable ldi rmp,1<<SE ; Schlafen Enable out MCUCR,rmp sei ; Int Enable Loop: sleep ; schlafen nop ; Dummy fuer Aufwachen sbrc rFlg,bAdc ; ADC-Flagge? rcall AdcRdy ; wandle ADC-Ergebnis um rjmp Loop ; ; ********************************** ; Rechenroutinen ; ********************************** ; ; ADC Umwandlungs-Egebnis fertig ; AdcRdy: cbr rFlg,1<<bAdc ; setze Flagge zurueck .IF debug_const ret .ENDIF dcalc: mov rAdcCH,rAdcRH ; kopiere Messwert mov rAdcCL,rAdcRL ldi rmp,LOW(cSmSteps) ; Anzahl Schritte in R4:R3:R2:R1 mov R1,rmp ldi rmp,HIGH(cSmSteps) mov R2,rmp clr R3 clr R4 clr R5 ; Ergebnis in R8:R7:R6:R5 loeschen clr R6 clr R7 clr R8 AdcRdy1: lsr rAdcCH ; ein Bit herausschieben ror rAdcCL brcc AdcRdy2 ; nicht addieren add R5,R1 ; addieren adc R6,R2 adc R7,R3 adc R8,R4 AdcRdy2: lsl R1 ; Multiplikator mal zwei rol R2 rol R3 rol R4 mov rmp,rAdcCL ; = Null? or rmp,rAdcCH brne AdcRdy1 ; weiter multiplizieren ldi rmp,0x80 ; aufrunden add R5,rmp adc R6,rmp ldi rmp,0 adc R7,rmp adc R8,rmp cli ; Interrupts aus mov rSmSL,R7 ; Stepmotor-Sollwert LSB mov rSmSH,R8 ; dto., setze MSB .IF debug_out out PORTB,rSmSL .ENDIF sei ; Interrupts wieder an ret ; ; Ende Source Code ;
http://www.avr-asm-tutorial.net/avr_de/quellen/schrittmotor_v1.asm1/20/2009 7:41:45 PM

http://www.avr-asm-tutorial.net/avr_de/rechteckgen/bilder/rechteckgen_scheme_v1_m8.gif

http://www.avr-asm-tutorial.net/avr_de/rechteckgen/bilder/rechteckgen_scheme_v1_m8.gif1/20/2009 7:41:47 PM

http://www.avr-asm-tutorial.net/avr_de/fcount/scheme.gif

http://www.avr-asm-tutorial.net/avr_de/fcount/scheme.gif1/20/2009 7:41:52 PM

http://www.avr-asm-tutorial.net/avr_de/fcount/analog3.gif

http://www.avr-asm-tutorial.net/avr_de/fcount/analog3.gif1/20/2009 7:41:53 PM

http://www.avr-asm-tutorial.net/avr_de/fcount/FusesStudio1.gif

http://www.avr-asm-tutorial.net/avr_de/fcount/FusesStudio1.gif1/20/2009 7:41:55 PM

http://www.avr-asm-tutorial.net/avr_de/fcount/FusesPony.gif

http://www.avr-asm-tutorial.net/avr_de/fcount/FusesPony.gif1/20/2009 7:42:19 PM

http://www.avr-asm-tutorial.net/avr_de/eieruhr/eieruhr_600_725.gif

http://www.avr-asm-tutorial.net/avr_de/eieruhr/eieruhr_600_725.gif1/20/2009 7:42:22 PM

http://www.avr-asm-tutorial.net/avr_de/stepper/stepper_v1.gif

http://www.avr-asm-tutorial.net/avr_de/stepper/stepper_v1.gif1/20/2009 7:42:25 PM

http://www.avr-asm-tutorial.net/avr_de/stepper/driver.jpg

http://www.avr-asm-tutorial.net/avr_de/stepper/driver.jpg1/20/2009 7:42:27 PM

http://www.avr-asm-tutorial.net/avr_de/stepper/supply.gif

http://www.avr-asm-tutorial.net/avr_de/stepper/supply.gif1/20/2009 7:42:29 PM

http://www.avr-asm-tutorial.net/avr_de/stepper/supply.jpg

http://www.avr-asm-tutorial.net/avr_de/stepper/supply.jpg1/20/2009 7:42:32 PM

http://www.avr-asm-tutorial.net/avr_de/stepper/kp4m4-001.jpg

http://www.avr-asm-tutorial.net/avr_de/stepper/kp4m4-001.jpg1/20/2009 7:42:34 PM

http://www.avr-asm-tutorial.net/avr_de/stepper/stepper_sm.jpg

http://www.avr-asm-tutorial.net/avr_de/stepper/stepper_sm.jpg1/20/2009 7:42:36 PM

Anschluss einer LCD-Anzeige an das STK500

Pfad: Home => 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, q Stromversorgungspin mit dem an der LCD, und das richtige q Poti mit dem Helligkeitsregler-Eingang der LCD (nein, es geht nicht ohne das Poti!)
q

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.

Testprogramm
Als kleines Testprogramm ist eine Uhr programmiert, die die LCD-Anzeige zur Ausgabe der Uhrzeit und des Datums verwendet. Diese Software ist in der Datei LcdIncC.html im HTML-Format und in der Datei Lcd4IncC.asm im Quelltext-Format enthalten. Leider kann man die Uhr nicht verstellen. Wer möchte kann sich Taster zum Verstellen und ein SIO-Interface für den PC dazuschreiben.
©2002-2006 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/lcd4_500.html1/20/2009 7:42:41 PM

Anschluss einer LCD-Anzeige an das STK500

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

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/Lcd4Inc.html1/20/2009 7:42:43 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/Lcd4Inc.asm

; ***************************************** ; * 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@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

http://www.avr-asm-tutorial.net/avr_de/quellen/Lcd4Inc.asm1/20/2009 7:42:45 PM

Anschluss einer LCD-Anzeige an das STK500

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 ;

©2002 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/Lcd4IncC.html1/20/2009 7:42:48 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/Lcd4IncC.asm

; *************************************************************** ; * 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@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| ;|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 5 0000.1000

U=2,5V 1000.0000

U=0,15625V U=0,3125V 0000.1000 0001.0000 U=0,46875V 0001.1000 U=0,625V 0010.0000 U=0,78125V U=0,9375V 0010.1000 0011.0000 U=1,09375V 0011.1000 U=1,25V 0100.0000 U=1,40625V U=1,5625V 0100.1000 0101.0000 U=1,71875V 0101.1000 U=1,875V 0110.0000 U=2,03125V U=2,1875V 0110.1000 0111.0000 U=2,34375V 0111.1000 U=2,65625V U=2,8125V 1000.1000 1001.0000 U=2,96875V 1001.1000 U=3,125V 1010.0000 U=3,28125V U=3,4375V 1010.1000 1011.0000 U=3,59375V 1011.1000 U=3,75V 1100.0000 U=3,90625V U=4,0625V 1100.1000 1101.0000 U=4,21875V 1101.1000 U=4,375V 1110.0000 U=4,53125V U=4,6875V 1110.1000 1111.0000 U=4,84375V 1111.1000

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.

änderungen und Ergänzungen
Für Experimente mit 9- oder 10-Bit-Auflösung müssen die Register rRes, rTmp und rBlc als Zwei-Byte-Worte ausgeführt werden. Bei der Ausgabe über die LEDs muss entschieden werden, welche Bits dargestellt oder nicht dargestellt werden sollen. Soll das Ergebnis auf einer LCD-Anzeige angezeigt oder über die serielle Schnittstelle des Prozessors an einen Computer in dezimaler Form ausgegeben werden, können die Festkomma-Umrechnungsroutinen in der Rechenabteilung des Anfängerkurses hinzugebaut werden. An den Anfang dieser Seite
©2003 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/avr_adc500.html1/20/2009 7:43:02 PM

8-<A HREF="beginner/rechnen.html#Bits">Bit</A>-Analog-zu-Digital-Converter

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 ;

©2003 by http://www.avr-asm-tutorial.net
http://www.avr-asm-tutorial.net/avr_de/adc8.html1/20/2009 7:43:08 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/adc8.asm

; +-----------------------------------------------------+ ; | 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.

R2R-Netzwerk Berechnungsprogramm, (C)2004 info !at! avr-asm-tutorial.net -----------------------------------------------------------------------Bits Aufloesung: nr=8[bits], Bits=00000000 Spannungen: ub=5.000[V], ul=0.000[V], uh=5.000[V] Widerstaende: R1= 51k0, R2=100k0 Eingabekombinationen und Ausgabespannungen 00000000: 0.000[V] 00000001: 0.019[V] (Delta= 18.69[mV]) 00000010: 0.038[V] (Delta= 19.06[mV]) 00000011: 0.056[V] (Delta= 18.69[mV]) 00000100: 0.076[V] (Delta= 19.62[mV]) 00000101: 0.095[V] (Delta= 18.69[mV]) 00000110: 0.114[V] (Delta= 19.06[mV]) 00000111: 0.132[V] (Delta= 18.69[mV]) 00001000: 0.153[V] (Delta= 20.67[mV]) 00001001: 0.172[V] (Delta= 18.69[mV]) 00001010: 0.191[V] (Delta= 19.06[mV]) 00001011: 0.210[V] (Delta= 18.69[mV]) 00001100: 0.229[V] (Delta= 19.62[mV]) 00001101: 0.248[V] (Delta= 18.69[mV]) 00001110: 0.267[V] (Delta= 19.06[mV]) 00001111: 0.286[V] (Delta= 18.69[mV]) 00010000: 0.308[V] (Delta= 22.72[mV]) 00010001: 0.327[V] (Delta= 18.69[mV]) 00010010: 0.346[V] (Delta= 19.06[mV]) 00010011: 0.365[V] (Delta= 18.69[mV]) 00010100: 0.384[V] (Delta= 19.62[mV]) 00010101: 0.403[V] (Delta= 18.69[mV]) 00010110: 0.422[V] (Delta= 19.06[mV]) 00010111: 0.441[V] (Delta= 18.69[mV]) 00011000: 0.462[V] (Delta= 20.67[mV]) 00011001: 0.480[V] (Delta= 18.69[mV]) 00011010: 0.499[V] (Delta= 19.06[mV]) 00011011: 0.518[V] (Delta= 18.69[mV]) 00011100: 0.538[V] (Delta= 19.62[mV]) 00011101: 0.556[V] (Delta= 18.69[mV]) 00011110: 0.575[V] (Delta= 19.06[mV]) 00011111: 0.594[V] (Delta= 18.69[mV]) 00100000: 0.621[V] (Delta= 26.83[mV]) 00100001: 0.640[V] (Delta= 18.69[mV]) 00100010: 0.659[V] (Delta= 19.06[mV]) 00100011: 0.677[V] (Delta= 18.69[mV]) 00100100: 0.697[V] (Delta= 19.62[mV]) 00100101: 0.716[V] (Delta= 18.69[mV]) 00100110: 0.735[V] (Delta= 19.06[mV]) 00100111: 0.753[V] (Delta= 18.69[mV]) 00101000: 0.774[V] (Delta= 20.67[mV]) 00101001: 0.793[V] (Delta= 18.69[mV]) 00101010: 0.812[V] (Delta= 19.06[mV]) 00101011: 0.830[V] (Delta= 18.69[mV]) 00101100: 0.850[V] (Delta= 19.62[mV]) 00101101: 0.869[V] (Delta= 18.69[mV]) 00101110: 0.888[V] (Delta= 19.06[mV]) 00101111: 0.906[V] (Delta= 18.69[mV]) 00110000: 0.929[V] (Delta= 22.72[mV]) ... 01111110: 2.446[V] (Delta= 19.06[mV]) 01111111: 2.465[V] (Delta= 18.69[mV]) 10000000: 2.517[V] (Delta= 51.72[mV]) 10000001: 2.535[V] (Delta= 18.69[mV]) 10000010: 2.554[V] (Delta= 19.06[mV]) 10000011: 2.573[V] (Delta= 18.69[mV]) ...

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 ;

*

Natürlich muss man einen kleinen Lautsprecher oder Ohrhöhrer an den Bufferausgang anschließen. Ohne das bleiben die schönen Sinuswellen stumm. Zum Anfang dieser Seite
©2005 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/avr_dac.html1/20/2009 7:43:23 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/sawtooth1.asm

; ************************************************************* ; * R/2R-Netzwerk erzeugt eine Saegezahnspannung ueber Port D * ; * (C)2005 by info@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&ouml;hen rjmp sawtooth ; und weiter fuer immer

http://www.avr-asm-tutorial.net/avr_de/quellen/sawtooth1.asm1/20/2009 7:43:27 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/sawtooth2.asm

; ***************************************************** ; * R/2R-Netzwerk als Saegezahn ueber Port D * ; * (C)2005 by info@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

http://www.avr-asm-tutorial.net/avr_de/quellen/sawtooth2.asm1/20/2009 7:43:28 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/triangle.asm

; *********************************************************** ; * R/2R-Netzwerk produziert eine Dreieckspannung an Port D * ; * (C)2005 by info@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

http://www.avr-asm-tutorial.net/avr_de/quellen/triangle.asm1/20/2009 7:43:29 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/sine8_25.txt

; ; 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

http://www.avr-asm-tutorial.net/avr_de/quellen/sine8_25.txt1/20/2009 7:43:32 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/sinewave.asm

; ********************************************************** ; * 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 ;

http://www.avr-asm-tutorial.net/avr_de/quellen/sinewave.asm1/20/2009 7:43:34 PM

http://www.avr-asm-tutorial.net/avr_de/quellen/musik.asm

; ****************************************************************** ; * 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@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 ;
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 LPM LPM Erläuterung zum Inhalt 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. 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! Makros verwenden. Sinnloses Beispiel zum Erlernen, wie ein Makro in den Assembler-Quelltext gelangt und was beim Assemblieren passiert. Makros verwenden. Sprungziele in Makros nach innerhalb und ausserhalb des Makros anwenden. Sinnloses Testprogramm. Makros verwenden. Makro mit Übergabe eines Parameters an das Makro. Sinnloses Testprogramm zum Lernen.

JUMP

JUMP

MAC1 MAC2 MAC3

MAC1 MAC2 MAC3

©2002 by http://www.avr-asm-tutorial.net

http://www.avr-asm-tutorial.net/avr_de/avr_soft.html1/20/2009 7:43:36 PM

AVR-Software-Knowhow: <A HREF="beginner/register.html#LPM">LPM</A>-Befehl

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

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