1 kB roguelike using an Arduino and 0.96" OLED
To make the experience fit your profile, pick a username and tell us what interests you.
We found and based on your interests.
main.asmMain assembly programplain - 4.87 kB - 11/27/2016 at 19:21 |
|
The 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
The 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.
Having 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
I 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
Create an account to leave a comment. Already have an account? Log In.
Become a member to follow this project and never miss any updates
What assembler did you use?