-
Drawing a map
12/03/2016 at 18:55 • 0 commentsThe map currently is 8x8 bits with a floor and wall tile. 8 bytes are used to represent a map. Each byte codes for one line horizontally. The map shown in the main project image is the following:
wall: .db 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff floor: .db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 main_map: .db 0xff, 0x81, 0x81, 0x1, 0x81, 0x81, 0x81, 0xfb
To copy this map to the display buffer the following code is used:
draw_map: ldi xl, low(buffer) ldi xh, high(buffer) ldi yl, low(buffer) ldi yh, high(buffer) ldi zl, low(2*main_map) ldi zh, high(2*main_map) movw r25:r24,z ldi r21, 128 ldi r22,0 ldi r18, 8 tile_no: movw y,x movw z,r25:r24 ; load map lpm r16, z ; bit map/line value adiw r25:r24, 1 ldi r17, 8 ; 8 bit/tiles per line draw_line: lsl r16 brcs draw_wall draw_floor: ldi zl, low(2*floor) ldi zh, high(2*floor) lpm r19, z rcall draw_tile rjmp end_tile draw_wall: ldi zl, low(2*wall) ldi zh, high(2*wall) lpm r19, z rcall draw_tile end_tile: dec r17 brne draw_line rcall next_line dec r18 brne tile_no ret
-
Drawing to the display buffer
12/03/2016 at 18:43 • 0 commentsThe display buffer is organized such that each byte corresponds to an 8 bit line of pixels going down. The display is 128x64 pixels. that means there are 128x8 bytes resulting in 1024 byte buffer. 8 bytes code for a 8x8 bit tile.
The following image describes how a tile is coded (Don't judge I'm not a pixel artist):
This translates to the following in assembly:
player_sprite: .db 0x0e,0x1f,0xff,0xfb, 0xfb, 0xfb, 0x37, 0xfa player_pos: .db 4, 4
The following is the assembly code to copy this tile to the display buffer:
draw_player: ldi yl, low(buffer) ldi yh, high(buffer) movw x, y ldi zl, low(2*player_pos) ldi zh, high(2*player_pos) lpm r16, z+ lpm r17, z ldi zl, low(2*player_sprite) ldi zh, high(2*player_sprite) lpm r19, z+ ldi r18, 8 ldi r21, 0 buf_pos_y: dec r17 breq y_set rcall next_line rjmp buf_pos_y y_set: mul r16,r18 add xl, r0 adc xh, r1 movw y,x rcall draw_tile ret ;;;;;;;;;;;;;;;;;;;;;;;;;; draw_tile: ldi r20, 8 fill_tile: st y+, r19 lpm r19, z+ dec r20 brne fill_tile end_draw: ret ;;;;;;;;;;;;;;;;;;;;;;;;; next_line: adiw x, 60 ; increase buffer address by 128, new line adiw x, 60 adiw x, 8 ret
To increase the line curser I have to add 128. I couldn't find a command to add 128 so I used the adiw (which allows to add a max value of 63 to a 16 bit register).
To send the display buffer, the adafruit display() command was converted to assembly:
display: mcommand 0x21 mcommand 0x00 mcommand 127 mcommand 0x22 mcommand 0x0 mcommand 0x7 sbi PORTB, 2 ;cs high sbi PORTB, 1 ;dc low cbi PORTB, 2 ;cs high ldi r23,4 ldi r22,0 ldi r21,0 ldi YL, low(buffer) ldi YH, high(buffer) send_buffer: ld r20, Y+ rcall spi_send inc r21 brne send_buffer inc r22 cpse r22,r23 brne send_buffer sbi PORTB, 2 ;cs ret
My way of counting to 1024 which is hex 0x400 is to use two registers r21 and r22. When (r22 ==4) I know I sent 1024 bytes.
-
OLED initialization
12/03/2016 at 17:20 • 0 commentsHaving the peripherals set-up I looked at the adafruit library. More specificially the display() and begin() functions. The begin is the initialization routine in the adafruit 1306 library. Converting begin() function to assembly looks like the following:
OLED_init: sbi PORTB, rst ; rst high ldi r18, 21 ; delay 1ms ldi r19, 199 L1: dec r19 brne L1 dec r18 brne L1 cbi PORTB, rst ;rst low ldi r18, 208 ; delay 10ms ldi r19, 202 L2: dec r19 brne L2 dec r18 brne L2 sbi PORTB, rst ; rst high mcommand 0xae mcommand 0xd5 mcommand 0x80 mcommand 0xa8 mcommand 63 mcommand 0xD3 mcommand 0x0 mcommand 0x40 mcommand 0x8d mcommand 0x14 mcommand 0x20 mcommand 0x00 mcommand (0xa8|0x1); mcommand 0xc8 mcommand 0xDA mcommand 0x12 mcommand 0x81 mcommand 0xcf mcommand 0xd9 mcommand 0xf1 mcommand 0xdb mcommand 0x40 mcommand 0xa4 mcommand 0xa6 mcommand 0x2e mcommand 0xaf ret
I used http://www.bretmulvey.com/avrdelay.html for the delay assembly code.
the mcommand is a macro:
.macro mcommand ldi r20, @0 rcall command .endmacro
and the command subroutine looks like the following (recall to send spi data I put the data byte in r20):
command: sbi PORTB, cs ;cs high cbi PORTB, dc ;dc low cbi PORTB, cs ;cs low rcall spi_send sbi PORTB, cs; cs high ret
-
Getting the OLED working
12/03/2016 at 17:00 • 0 commentsI bought one of these OLED from ebay for ~$5. I used this page as a guide for the wiring and code:
https://github.com/jandelgado/arduino/wiki/SSD1306-based-OLED-connected-to-Arduino
The Arduino communicates using the SPI
I have the wiring as follows
OLED - Arduino pin - AVR pin (PORTB) - Function
D0 - Pin 13 - B5 - MISO
D1 - Pin 11 - B3 - CLOCK
RST - Pin 8 - B0 - RESET
DC - Pin 9 - B1 - Data/Command
CS - Pin 10 - B2 - Chip select
First I set-up the AVR peripherals (using http://brittonkerin.com/cduino/lessons.html as a guide for uart). UART is used for debugging:
setup_uart: ldi r16, low(bittimer) sts UBRR0L, r16 ldi r16, high(bittimer) sts UBRR0H, r16 ldi r16, (3<<UCSZ00) sts UCSR0C, r16 ldi r16, (1<<RXEN0)|(1<<TXEN0) sts UCSR0B, r16 ret
To send a byte over uart I store it in r16 and invoke the tx_uart subroutine
tx_uart: lds r17, UCSR0A sbrs r17, UDRE0 rjmp tx_uart sts UDR0, r16 ret
Setting up the SPI:setup_spi: ldi r16, (1<<5)|(1<<3)|(1<<rst)|(1<<cs)|(1<<dc) out DDRB, r16 ldi r16, (1<<SPE)|(1<<MSTR) out SPCR,r16 ldi r16, (1<<SPI2X) out SPSR, r16 ret
To send a byte over spi I store it in r20 and invoke spi_send subroutinespi_send: out spdr, r20 wait_sprs: in r16, SPSR sbrs r16, SPIF rjmp wait_sprs ret