
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
