• Drawing a map

    Radek12/03/2016 at 18:55 0 comments

    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

  • Drawing to the display buffer

    Radek12/03/2016 at 18:43 0 comments

    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.

  • OLED initialization

    Radek12/03/2016 at 17:20 0 comments

    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

  • Getting the OLED working

    Radek12/03/2016 at 17:00 0 comments

    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 subroutine
    spi_send:
    	out	spdr, r20
    wait_sprs:
    	in	r16, SPSR
    	sbrs	r16, SPIF
    	rjmp	wait_sprs
    	ret