Wie im Beispiel Hello World über serielle Schnittstelle senden und im Beispiel LED’s des Pollin Evaluationboards über die serielle Schnittstelle steuern dargestellt, ist es recht einfach möglich Buchstaben oder Bytes zu einem PC zu übertragen und auch anders herum vom PC aus Schaltzustände zu verändern.
Die neue Herausforderung ist es nun Registerinhalte via serieller Schnittstelle zu füllen und Diese wieder auszulesen.
Gedacht ist die Übung als Vorbereitung für eine interne Systemuhr auf dem ATMega8, die von einem PC oder z.B. auch der Raspberry Pi gestellt und abgelesen werden kann.
Wie im Ablaufplan (Bild links) dargestellt, habe ich das Programm so strukturiert, dass
- im Hauptprogramm die Variablen initialisiert werden und der Interrupt sobald ein Byte über RX/UART empfangen wird, aktiviert ist
- Das Unterprogramm IncommingByte entscheidet, ob es sich um ein Aktives, oder Neues Unterprogramm handelt und ruft ein weiteres Unterprogramm FunctionCall auf, was an das aktive oder gewählte Unterprogramm weiterleitet.
- Das Unterprogramm StelleUhr, wird durch das Senden eines Bytes mit dem Inhalt 1 aufgerufen und erwartet das danach 3 weitere Bytes als Inhalt für die Register Stunde, Minute und Sekunde via RX/UART übergeben werden. Diese werden in den Registern gespeichert.
- Das Unterprogramm Sende Uhrzeit, wird mit einem Byte mit Inhalt 2 aufgerufen und sendet 3 Byte mit den Inhalten Stunde, Minute und Sekunde zurück an den PC.
Der hier abgebildete Assemblercode ist mit vielen Kommentaren versehen.
Quellcode des Assemblerprogramms:
; avrdude -p m8 -c stk500v2 -P /dev/ttyACM0 -U flash:w:Registerinhalte_via_UART_steuern_und_ausgeben.hex ; 13.9.2013 Das Programm soll Registerinhalte per serielle Datenübertragung manipulieren können und diese auch wieder ausgeben .NOLIST ;.INCLUDE "/home/henry/Dokumente/Mikrocontroller/AVR-Assembler/AVR_Definitionsfiles/m8def.inc" .INCLUDE "m8def.inc" .LIST ; .def Sekunde = R2 .def Minute = R3 .def Stunde = R4 ;-------------------------------- .def temp = R16 ; Register 16 der Variable temp zuweisen .def ByteNummer = R17 ; Enthält die Nummer des aktuell übertragenen Bytes innerhalb eine Unterprogramms .def aktivesUnterprogramm = R19 ; Enthält die numerische ID des aktuell aktiven Unterprogamms .def EmpfRXByte = R20 ; Enthält das zuletzt via. UART empfangene Byte .equ F_CPU = 16000000 ; Systemtakt in Hz .equ BAUD = 9600 ; Baudrate ; Berechnungen .equ UBRR_VAL = ((F_CPU+BAUD*8)/(BAUD*16)-1) ; clever runden .equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1))) ; Reale Baudrate .equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000) ; Fehler in Promille .if ((BAUD_ERROR>10) || (BAUD_ERROR<-10)) ; max. +/-10 Promille Fehler .error "Systematischer Fehler der Baudrate grösser 1 Prozent und damit zu hoch!" .endif ; Startvektoren .org 0x00 rjmp main .org URXCaddr ; Interruptvektor für UART-Empfang rjmp IncommingByte ; --------------------------------Hauptprogramm-------------------------------------- main: ; Stackpointer initialisieren ldi temp, HIGH(RAMEND) out SPH, temp ldi temp, LOW(RAMEND) out SPL, temp ; Baudrate einstellen ldi temp, HIGH(UBRR_VAL) out UBRRH, temp ldi temp, LOW(UBRR_VAL) out UBRRL, temp ; Frame-Format: 8 Bit ldi temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0) out UCSRC, temp sbi UCSRB, RXCIE ; Interrupt bei Empfang sbi UCSRB, RXEN ; RX (Empfang) aktivieren sei ; Interrupts global aktivieren loop: rjmp loop ; Endlosschleife / idle mode ; ---------------------------ENDE Hauptprogramm-------------------------------------- ; ---------------------------IncommingByte------------------------------------------- ; Interruptroutine: wird ausgeführt sobald ein Byte über das UART empfangen wurde IncommingByte: in EmpfRXByte, UDR ; empfangenes Byte lesen, ; dadurch wird auch der Interrupt gelöscht cpi aktivesUnterprogramm, 1 ; vergleich aktivesUnterprogramm mit 1 brge UPAufruf ; ist aktivesUnterprogramm > 0, springe zu UPAufruf mov aktivesUnterprogramm, EmpfRXByte ; kopiere EmpfRXByte in aktivesUnterprogramm clr ByteNummer ; setze ByteNummer aud 0 UPAufruf: rcall FunctionCall ; rufe Unterprogramm FunctionCall auf EndIncommingByte: reti ; Interrupt beenden ; ----------------------Ende-IncommingByte------------------------------------------- ; ---------------------------FunctionCall-------------------------------------------- ; Abfrage aktivesUnterprogramm und Aufruf der entsprechenden Unterprogramme FunctionCall: cpi aktivesUnterprogramm, 1 breq Byte1 ; Wenn eine 1 übertragen wurde zu Byte1 springen cpi aktivesUnterprogramm, 2 breq Byte2 ; Wenn eine 2 übertragen wurde zu Byte2 springen rjmp EndFunctionCall ; zum Unterprogramm - Ende (Es wurde kein Wert der einem gültigem Unterprogarmm entspricht übertragen.) Byte1: rcall StelleUhr ; StelleUhr aufrufen rjmp EndFunctionCall ; zum Unterprogramm - Ende Byte2: rcall SendeUhrzeit ; SendeUhrzeit aufrufen rjmp EndFunctionCall ; zum Unterprogramm - Ende EndFunctionCall: ret ; Ende - zurück springen ; ----------------------Ende-FunctionCall-------------------------------------------- ; ---------------------------StelleUhr----------------------------------------------- StelleUhr: cpi ByteNummer, 1 ; vergleich ByteNummer mit 1 breq StelleSekunde ; ist ByteNummer = 1, springe zu StelleSekunde cpi ByteNummer, 2 ; vergleich ByteNummer mit 2 breq StelleMinute ; ist ByteNummer = 2, springe zu StelleMinute cpi ByteNummer, 3 ; vergleich ByteNummer mit 3 breq StelleStunde ; ist ByteNummer = 3, springe zu StelleStunde rjmp ErhoeheByteNummer ; springe zu ErhoeheByteNummer StelleSekunde: mov Sekunde, EmpfRXByte ; Kopiere EmpfRXByte in Sekunde rjmp ErhoeheByteNummer ; spinge zu ErhoeheByteNummer StelleMinute: mov Minute, EmpfRXByte ; Kopiere EmpfRXByte in Minute rjmp ErhoeheByteNummer ; spinge zu ErhoeheByteNummer StelleStunde: mov Stunde, EmpfRXByte ; Kopiere EmpfRXByte in Stunde clr aktivesUnterprogramm ; setze aktivesUnterprogramm auf Null, wir sind fertig mit Uhr stellen :-) rjmp EndStelleUhr ; spinge zu EndStelleUhr ErhoeheByteNummer: inc ByteNummer ; erhöhe ByteNummer um 1 EndStelleUhr: ret ; Ende - zurück springen ; ----------------------Ende-StelleUhr----------------------------------------------- ; ---------------------------SendeUhrzeit-------------------------------------------- SendeUhrzeit: cbi UCSRB, RXEN ; RX (Empfang) deaktivieren sbi UCSRB, TXEN ; TX (Senden) aktivieren Sekunde_wait: sbis UCSRA,UDRE ; Warten bis UDR für das nächste Byte bereit ist rjmp Sekunde_wait ; Springe zurück zu Sekunde_wait, solange UDR noch nicht bereit ist out UDR, Sekunde ; sende das Byte Sekunde via UART Minute_wait: sbis UCSRA,UDRE ; Warten bis UDR für das nächste Byte bereit ist rjmp Minute_wait ; Springe zurück zu Minute_wait, solange UDR noch nicht bereit ist out UDR, Minute ; sende das Byte Minute via UART Stunde_wait: sbis UCSRA,UDRE ; Warten bis UDR für das nächste Byte bereit ist rjmp Stunde_wait ; Springe zurück zu Stunde_wait, solange UDR noch nicht bereit ist out UDR, Stunde ; sebnde das Byte Stunde via UART cbi UCSRB, TXEN ; TX (Senden) deaktivieren sbi UCSRB, RXEN ; RX (Empfang) aktivieren clr aktivesUnterprogramm ; setze aktivesUnterprogramm auf Null, wir sind fertig mit Uhrzeit ausgeben :-) EndSendeUhrzeit: ret ; Ende - zurück springen ; ----------------------Ende-SendeUhrzeit--------------------------------------------
Video, zur Demonstration