Tiny-Fish           

von  Thomas Baum                      
Elektronik-Labor  Projekte   AVR   T13-Contest

 


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




Youtube-Video: http://www.youtube.com/watch?v=oVeatVkZ8ys&feature=youtu.be

Download:  fish.zip


;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
 



Elektronik-Labor  Projekte   AVR   T13-Contest