Auf meiner Schreibtischunterlage entstehen immer mal wieder kleine Zeichnungen
die sich im Laufe der Zeit so ansammeln. So auch ein kleines Auqarium. Das
einzige was so einem echten Desktop-Hintergrund fehlt ist ein Bildschirmschoner
der etwas Bewegung in die Sache bringt.
Das folgende Projekt beschäftigt sich mit einer minimalen Aquariumsimulation
auf Basis des Attiny13a, einem Nokia 5510 Display (Reflektionsfolie entfernt)
und einem 8 mal 8 Pixelfisch namens Fred. Im Vordergrund stehen die durch den geringen Speicher beschränkte grafische
Ausgabe mit dem kleinen AVR sowie die Erstellung von Pseudozufallszahlen ohne
externe Hardware oder Interaktion mit dem Nutzer.
Verwendete Hardware:
* Attiny13a
* Nokia 5510 Display
Verwendete Softwaretechniken:
* SoftwareSPI zur Kommunikation zum
Display
* Pseudozufallszahlengenerator
mittels Timerdrift (Watchdog)
* Timerprogrammierung
* Bitmapmanipulation
* minimalistischer Videorpuffer über
Register
Beschaltung Attiny13a:
____
-| |- VCC
(3Volt)
LCD_SDIN-| |- LCD_SCE
LCD_SCLK-| |- LCD_DC
GND -|__|- LCD_RESET
;AVR-NokiaDisplay-Swimming-Fish Thomas Baum
(th.baum@online.de)
.device ATtiny13A ;Fuse
6AFF
.equ LCD_RESET = 0 ;Pinbelegung
.equ LCD_DC = 1
.equ LCD_SCE = 2
.equ LCD_SDIN = 3
.equ LCD_SCLK = 4
.org 0x0000
rjmp init
.org OVF0addr
rjmp
sleep_timer ;Händler für
Delay
.org WDTaddr
rjmp
random_timer ;Händler für Zufallszahlen
init:
;Initialisierung
sbi DDRB,
LCD_RESET ;Definition als
Output
sbi DDRB,
LCD_DC
sbi DDRB,
LCD_SCE
sbi DDRB,
LCD_SDIN
sbi DDRB,
LCD_SCLK
sbi PORTB, LCD_RESET
ldi r18,
0x1F ;Startposition x
ldi r27,
0x10 ;Startposition y
rcall generate_random_speed ;Zufallszahlenerzeugung
rcall generate_random_direction
ldi r16,
0x21 ;Displayinitialisierung
rcall lcd_write_cmd
ldi r16,
0xD0
rcall lcd_write_cmd
ldi r16,
0x04
rcall lcd_write_cmd
ldi r16,
0x13
rcall lcd_write_cmd
ldi r16,
0x20
rcall lcd_write_cmd
ldi r16,
0x0C
rcall lcd_write_cmd
ldi r16, 0x21 ;Kontrasteinstellung
rcall lcd_write_cmd
ldi r16, 0x80 | 0x3C
rcall lcd_write_cmd
ldi r16,
0x20
rcall lcd_write_cmd
ldi r16, LOW(RAMEND) ;Timer0 Initialisierung
out SPL, r16
ldi r16, (0<<CS02 | 1<<CS01 |
1<<CS01) ;Vorteiler 64
out TCCR0B, r16
ldi r16, (1<<TOIE0)
out TIMSK0, r16
sei
cli ;WatchdogTimer
Initialisierung
wdr
ldi r16, (1<<WDCE) |
(1<<WDE)
out WDTCR, r16
ldi r16, 0x03; parameter
ori r16, (0<<WDCE) |
(0<<WDE) | (1<<WDTIE)
out WDTCR, r16
sei
main:
;Endlosschleife
inc r25 ;Zufallszahlenregister ++
cp r21,
r22 ;Timer abgelaufen?
breq go ;Ja
brne main ;Nein
go: rcall move ;Bewegung
ausführen
rjmp main ;Nochmals
warten
generate_random_speed: ;Geschwindigkeit
-> r22
mov r22,
r23 ;Arbeitskopie der
Zufallszahl
andi r22,
0x3F ;Geschwindigkeitsbegrenzung
ldi r16,
0x30 ;Geschwindigkeitsoffset
add r22, r16 ;Offset+variabler
Teil
ret
generate_random_direction: ;Richtung
-> r24
mov r24,
r23 ;Arbeitskopie der
Zufallszahl
cpi r24,
0x54 ;Grenze nach unten prüfen
(84)
brlo lower
cpi r24,
0xB5 ;Grenze nach oben prüfen
(181)
brsh greater
ret
lower: ldi r24, 0x78 ;Wertberichtigung
ret
greater:
ldi r24,
0x8F ;Wertberichtigung
ret
sleep_timer:
;Timer0
Händler
in r31, SREG ;Statusregister sichern
inc r21 ;Wartezyklen zählen
out SREG,
r31 ;Statusregister
zurückschreiben
reti
random_timer: ;Watchdogtimer
Händler
mov r23,
r25 ;Pseudozufallszahl durch
Timerdrift (r23)
reti
generate_y: ;Zufällige
Schwimmrichtung Vertikal
mov r16,
r23 ;Neue Zufallszahl
nehmen
cpi r16,
0x80 ;Kleiner oder gößer
128?
brlo calc_up ;Hoch
brsh calc_down ;Runter
calc_down: ;y Wert berechnen
cpi r27,
0x01 ;Grenze überprüfen nach
unten
brsh move_down
brlo generate_y_finish
move_down: ;y Wert
ändern
dec r27 ;y--
rjmp generate_y_finish
calc_up: ;y Wert berechnen
cpi r27,
0x28 ;Grenze überprüfen nach
oben
brlo move_up
brsh generate_y_finish
move_up: ;y Wert
ändern
inc r27 ;y++
generate_y_finish:
ret
move:
;Bewegungsablauf
für den Fisch
rcall generate_random_speed ;Neue
Geschwindigkeit erzeugen
ldi r21,
0x00 ;Wartezykluszähler
zurücksetzen
cpi r24,
0x80 ;Horizontale Richtung
ermitteln
breq turn ;Drehung ausführen
brsh left ;Schwimme nach links
brlo right ;Schwimme nach rechts
turn:
;Drehen
rcall generate_random_direction ;Zufallszahlenerzeugung
rcall generate_y ;Neue
vertikale Richtung ermitteln
rcall draw_front ;Frontansicht
zeichnen
ret
left:
rcall draw_left ;Seitenansicht
links zeichnen
cpi r18,
0x00 ;Linke Grenze prüfen
breq left_back ;Schwimm zurück
dec r18 ;x Position --
dec r24 ;Horizontale Richtung --
ret
left_back:
ldi r24,
0x7F ;Schwimm zurück
ret
right:
rcall draw_right ;Seitenansicht
rechts zeichnen
cpi r18,
0x46 ;Rechte Grenze prüfen
breq right_back ;Schwimm zurück
inc r18 ;x Position ++
inc r24 ;Horizontale Richtung ++
ret
right_back:
ldi r24,
0x81 ;Schwimm zurück
ret
draw_right: ;Zeichne Rechtsansicht
rcall load_right ;Videodaten in
Videospeicher laden
rcall set_x_position ;x-Position
ermitteln
rcall set_y_position_1 ;Erste
y-Poition ermitteln
rcall draw_init ;Initialisierung
rcall draw_video_ram_1 ;Zeichne
Videospeicher 1
rcall draw_finish ;Abschluss
rcall set_x_position ;x-Position ermitteln
rcall set_y_position_2 ;Zweite
y-Poition ermitteln
rcall draw_init ;Initialisierung
rcall draw_video_ram_2 ;Zeichne
Videospeicher 2
rcall draw_finish ;Abschluss
ret
draw_left: ;Zeichne Linksansicht
rcall load_left ;Videodaten
in Videospeicher laden
rcall set_x_position ;x-Position
ermitteln
rcall set_y_position_1 ;Erste
y-Poition ermitteln
rcall draw_init ;Initialisierung
rcall draw_video_ram_1 ;Zeichne
Videospeicher 1
rcall draw_finish ;Abschluss
rcall set_x_position ;x-Position
ermitteln
rcall set_y_position_2 ;Zweite
y-Poition ermitteln
rcall draw_init ;Initialisierung
rcall draw_video_ram_2 ;Zeichne
Videospeicher 2
rcall draw_finish ;Abschluss
ret
draw_front: ;Zeichne Frontansicht
rcall load_front ;Videodaten
in Videospeicher laden
rcall set_x_position ;x-Position
ermitteln
rcall set_y_position_1 ;Erste
y-Poition ermitteln
rcall draw_init ;Initialisierung
rcall draw_video_ram_1 ;Zeichne
Videospeicher 1
rcall draw_finish ;Abschluss
rcall set_x_position ;x-Position
ermitteln
rcall set_y_position_2 ;Zweite
y-Poition ermitteln
rcall draw_init ;Initialisierung
rcall draw_video_ram_2 ;Zeichne
Videospeicher 2
rcall draw_finish ;Abschluss
ret
draw_init: ;Datenübertragung vorbereiten
sbi PORTB,
LCD_DC ;LCD Kommandomodus
setzen
cbi PORTB,
LCD_SCE ;LCD aktivieren
ldi r16,
0x00 ;Linker Rest des Bitmap
überschreiben
rcall spi_out ;Daten
schreiben
ret
set_x_position: ;LCD
x-Position setzen
mov r16,
r18 ;Lade x-Position
ori r16,
0x80 ;Wertnormalisierung
rcall lcd_write_cmd ;Daten
schreiben
ret
set_y_position_1: ;LCD
y-Position setzen
mov r17,
r27 ;Lade y-Position
ldi r28, 0x00 ;Zeilennummer zurücksetzen
calc_y:
;Zeilennummer
ermitteln
cpi r17,
0x08 ;Rest gößer 8?
brlo calc_pixel ;Restpixes ermitteln
subi r17,
0x08 ;Volle Zeilen abziehen
inc r28 ;Zeilennummer erhöhen
rjmp calc_y ;Weiter machen
calc_pixel: ;Restpixel
für Ramrotatioen ermitteln
cpi r17,
0x01 ;Größer oder gleich 1?
brlo calc_finish ;Beenden
rcall shift_down ;Nach unten
shiften
dec r17 ;Restpixel--
rjmp calc_pixel ;Weiter machen
calc_finish:
;Positionierung
abschließen
mov r16,
r28 ;Ermittelte Zeilennummer
laden
ori r16,
0x40 ;Wertnormalisierung
rcall lcd_write_cmd ;Daten
schreiben
ret
set_y_position_2: ;LCD
y-Position 2. Zeile
mov r16,
r28 ;Ermittelte Zeilennummer
laden
inc r16 ;Zeilennummer++
ori r16,
0x40 ;Wertnormalisierung
rcall lcd_write_cmd ;Daten
schreiben
ret
shift_down: ;Videodaten
rotieren (16 bit)
ldi r16,
0x00 ;Dummy für Carrybit Addition
lsl r00 ;Linksshift
rol r08 ;Linksotation
adc r00, r16 ;16-Bit Rotation abschließen
lsl r01 ;Für alle Register ...
rol r09
adc r01,
r16
lsl r02
rol r10
adc r02,
r16
lsl r03
rol r11
adc r03,
r16
lsl r04
rol r12
adc r04,
r16
lsl r05
rol r13
adc r05,
r16
lsl r06
rol r14
adc r06,
r16
lsl r07
rol r15
adc r07,
r16
ret
draw_finish:
;LCD Schreibvorgang
abschließen
ldi r16,
0x00 ;Rechter Rest des Bitmap
überschreiben
rcall spi_out ;Daten
schreiben
sbi PORTB,
LCD_SCE ;LCD deaktivieren
ret
draw_video_ram_1: ;Videospeicher
1 zeichnen
mov r16, r00
rcall spi_out
mov r16,
r01
rcall spi_out
mov r16,
r02
rcall spi_out
mov r16,
r03
rcall spi_out
mov r16,
r04
rcall spi_out
mov r16,
r05
rcall spi_out
mov r16,
r06
rcall spi_out
mov r16,
r07
rcall spi_out
ret
draw_video_ram_2: ;Videospeicher
2 zeichnen
mov r16, r08
rcall spi_out
mov r16,
r09
rcall spi_out
mov r16,
r10
rcall spi_out
mov r16,
r11
rcall spi_out
mov r16,
r12
rcall spi_out
mov r16,
r13
rcall spi_out
mov r16,
r14
rcall spi_out
mov r16,
r15
rcall spi_out
ret
load_left: ;Linksansicht
in Videospeicher laden
ldi r16,
0x30
mov r00, r16
ldi r16,
0x3C
mov r01,
r16
ldi r16,
0x77
mov r02,
r16
ldi r16,
0xFE
mov r03,
r16
ldi r16,
0x3C
mov r04,
r16
ldi r16,
0x18
mov r05,
r16
ldi r16,
0x0C
mov r06, r16
ldi r16,
0x1E
mov r07, r16
clr r08
clr r09
clr r10
clr r11
clr r12
clr r13
clr r14
clr r15
ret
load_right: ;Rechtsansicht
in Videospeicher laden
ldi r16,
0x1E
mov r00,
r16
ldi r16, 0x0C
mov r01,
r16
ldi r16,
0x18
mov r02,
r16
ldi r16,
0x3C
mov r03,
r16
ldi r16,
0xFE
mov r04,
r16
ldi r16,
0x77
mov r05,
r16
ldi r16,
0x3C
mov r06,
r16
ldi r16,
0x30
mov r07,
r16
clr r08
clr r09
clr r10
clr r11
clr r12
clr r13
clr r14
clr r15
ret
load_front: ;Vorderansicht in
Videospeicher laden
ldi r16, 0x10
mov r00,
r16
ldi r16,
0x3C
mov r01,
r16
ldi r16,
0x76
mov r02,
r16
ldi r16,
0xDF
mov r03,
r16
ldi r16,
0x76
mov r04,
r16
ldi r16,
0x3C
mov r05,
r16
ldi r16,
0x10
mov r06,
r16
ldi r16,
0x00
mov r07,
r16
clr r08
clr r09
clr r10
clr r11
clr r12
clr r13
clr r14
clr r15
ret
spi_out: ;Software SPI
cbi PORTB,
LCD_SCLK ;Clock auf 0 setzen
ldi r17,
0x08 ;Schleifenzähler für 8
Bit
spi_out_byte: ;Byte
ausgeben
sbrc r16,
0x07 ;Prüfe ob Bit 7 nicht
gesetzt
sbi PORTB,
LCD_SDIN ;Datenleitung auf 1
setzen
sbrs r16,
0x07 ;Prüfe ob Bit 7 gesetzt
cbi PORTB,
LCD_SDIN ;Datenleitung auf 0
setzen
sbi PORTB,
LCD_SCLK ;Clock auf 1 setzen
cbi PORTB,
LCD_SCLK ;Clock auf 0 setzen
lsl r16 ;Shift für nächstes Bit
dec r17 ;Schleifenzähler--
brne spi_out_byte ;Für alle übrigen Bits
ret
lcd_write_cmd: ;LCD
Kommando schreiben
cbi PORTB,
LCD_DC ;LCD Kommandomodus
setzen
rcall lcd_out ;Daten
schreiben
ret
lcd_write_data: ;LCD
Daten schreiben
sbi PORTB,
LCD_DC ;LCD Datenmodus
setzen
rcall lcd_out ;Daten
schreiben
ret
lcd_out:
cbi PORTB,
LCD_SCE ;LCD aktivieren
rcall spi_out ;Daten ausgeben
sbi PORTB,
LCD_SCE ;LCD deaktivieren
ret