Client

Casual Arcade Player

People Involved

Irene Mayor

Date

15 Feb 2014

Category

CPSC 355

On this page, I summarize the process and source code of the Raspberry Pi Assembly Project, which unfortunately has no visual evidence.

ARM Assembly Shooter Game with Read SNES Controller Input

Single Project Source Code Zip Download

What were the objectives for this project?

This project was created for a course to demonstrate ability to understand basics of working with low-level programming and hardware, such as the RaspberryPi and a simplified SNES-like controller input. The entire project was expected to rely on the lab's particular hardware setup. Input was read directly from pins. Though this project was recommended as group work, I decided to challenge myself by completing it alone.



Assignment Goals:

  • Generate Substantial Amount of Enemies
  • Instill Movement into Enemies
  • Create Obstacles Between Player and Enemies
  • Enable Player to Shoot at Enemies
  • Enable Enemies to Shoot Back at Player
  • Enable Player Movement
  • Menu Management
  • Create loop to Continously Redraw Screen Without Flickering



Deviations from Assignment

Due to the substantial challenge of working with low-level assembly for the first time, I encountered program crashes when trying to meet the minimum number required for enemies. To get around this issue, I devised different stages to get around this obstacle. Each level loaded up different enemies and behaviors, but only did so with very few at a time. It still fit within the requirements and changed the project to be different from my peers.



Final Source Code

Code Snippets

Raspberry Pi Code Snippets

Click to View Player Code Snippet

//////////////////////////////////
// PLAYER CODE HERE //
// //
//////////////////////////////////

.section .text


//Initialize Player's Location + Stats
.globl initPlayer
initPlayer:
ldr r0, =playerLoc
mov r1, #100
mov r2, #300
str r1, [r0]
str r2, [r0, #-4]
bx lr

//Draw Player to Screen
.globl drawPlayer
drawPlayer:

push {lr}

//If the up arrow is pressed
//update the player's position to move up
bl readSNES
ldr r0, =buttons
ldrb r9, [r0, #-4]
cmp r9, #0
bleq buttonRelease
beq moveUp

//If the down arrow is pressed
//update the player's position to move down
bl readSNES
ldr r0, =buttons
ldrb r9, [r0, #-5]
cmp r9, #0
bleq buttonRelease
beq moveDown

//If the left arrow is pressed
//update the player's position to move left
bl readSNES
ldr r0, =buttons
ldrb r9, [r0, #-6]
cmp r9, #0
bleq buttonRelease
beq moveLeft

//If the right arrow is pressed
//update the player's position to move right
bl readSNES
ldr r0, =buttons
ldrb r9, [r0, #-7]
cmp r9, #0
bleq buttonRelease
beq moveRight

//Draw the player's new position to the screen
updatePlayer:
ldr r0, =playerLoc
ldr r1, [r0]
ldr r2, [r0, #-4]
ldr r3, =0xFFFF
mov r4, #100
mov r5, #100
bl drawFillSquare


pop {lr}
bx lr


//Methods below adjust player's position according to direction
moveRight:
bl clearPlayer

ldr r0, =playerLoc
ldr r1, [r0]
cmp r1, #340

addle r1, #20 //New X
ldr r2, [r0, #-4]//Y

push {r1}
add r1, #100

mov r5, #0//If false will equal 0 later
bl checkObstacle//if pixel != black will return 1
add r2, #20
bl checkObstacle
add r2, #20
bl checkObstacle
add r2, #20
bl checkObstacle
add r2, #20
bl checkObstacle
add r2, #20
bl checkObstacle

ldr r0, =playerLoc//Reload Address for player location
pop {r1}

teq r5, #0 //Checks if new location is black
streq r1, [r0]//Stores if it was black

b updatePlayer

//Move player left within limits
moveLeft:

bl clearPlayer

ldr r0, =playerLoc
ldr r1, [r0]
cmp r1, #0

subne r1, #20 //New X
ldr r2, [r0, #-4]//Y

mov r5, #0//If false will equal 0 later
bl checkObstacle//if pixel != black will return 1
add r2, #20 //Check for entire side
bl checkObstacle
add r2, #20
bl checkObstacle
add r2, #20
bl checkObstacle
add r2, #20
bl checkObstacle
add r2, #20
bl checkObstacle

ldr r0, =playerLoc//Reload Address for player location
teq r5, #0 //Checks if it was black
streq r1, [r0]//Stores if it was black

b updatePlayer

//Move player up within limits
moveUp:

bl clearPlayer

ldr r0, =playerLoc
ldr r2, [r0, #-4]
cmp r2, #0

ldr r1, [r0]//X
subne r2, #20//New Y


mov r5, #0//If false will equal 0 later
bl checkObstacle//if pixel != black will return 1
add r1, #20 //Check every 20 interval to cover entire bottom half square
bl checkObstacle
add r1, #20
bl checkObstacle
add r1, #20
bl checkObstacle
add r1, #20
bl checkObstacle
add r1, #20
bl checkObstacle

ldr r0, =playerLoc//Reload Address for player location
teq r5, #0 //Checks if it was black
streq r2, [r0, #-4]//Stores if it was black

b updatePlayer


//Move player down within limits
moveDown:

bl clearPlayer

ldr r0, =playerLoc
ldr r2, [r0, #-4]
cmp r2, #500

ldr r1, [r0]//X
addle r2, #20 //New Y
push {r2}
add r2, #100

mov r5, #0//If false will equal 0 later
bl checkObstacle//if pixel != black will return 1
add r1, #20 //Check every 20 interval to cover entire bottom half square
bl checkObstacle
add r1, #20
bl checkObstacle
add r1, #20
bl checkObstacle
add r1, #20
bl checkObstacle
add r1, #20
bl checkObstacle

ldr r0, =playerLoc//Reload Address for player location
teq r5, #0 //Checks if it was black
pop {r2}
streq r2, [r0, #-4]//Stores if it was black


b updatePlayer
////////////END OF MOVEMENT METHODS//////////////////////////////////

//Clear player's old image off screen
//Placed in every move method to prevent
//flickering when Player stays still
clearPlayer:
push {lr}
ldr r0, =playerLoc
ldr r1, [r0]
ldr r2, [r0, #-4]
ldr r3, =0x0000
mov r4, #100
mov r5, #100
bl drawFillSquare

pop {lr}
bx lr

//Methods associated with the player's score
//Used to add points to player's score
.globl addPoints
addPoints:
push {lr}
//r0 = amount of points need
pointLoop:
cmp r0, #0
beq endPointLoop
bl singlePointAdder
cmp r0, #0
subne r0, #1
b pointLoop
endPointLoop:
pop {lr}
bx lr

//Used to subtract 10 points from player's score
//Called whenver player is hit by enemy bullets
.globl subPoints
subPoints:
push {r0, r1,r2,r3,r4, lr}
ldr r0, =playerPoints
ldr r1, [r0]
cmp r1, #0
beq checkHundreds

subne r1, #1
str r1, [r0]
b endSubPoints

checkHundreds:
ldr r2, [r0, #-4]
cmp r2, #0
beq checkOnes

subne r2, #1
str r2, [r0, #-4]
mov r1, #9
str r1, [r0] b endSubPoints

checkOnes:
ldr r3, =playerPointsOne
mov r4, #0
str r4, [r3]
endSubPoints:
pop {r0,r1,r2,r3,r4, lr}
bx lr


//Used to clear player's points for a new game
.globl clearPoints
clearPoints:
push {r0, r1,r3, lr}
ldr r0, =playerPointsOne
mov r1, #0
str r1, [r0]
ldr r0, =playerPoints
str r1, [r0]
str r1, [r0, #-4]
pop {r0, r1, r3, lr}
bx lr



//Speciall adder to update all three numbers on screen
//Essentaill helps parse the actual score # to something printable
singlePointAdder:
push {r0,r1,r2,r3, r4,lr}
ldr r0, =playerPointsOne
ldr r1, [r0] //Load ones
ldr r2, =playerPoints
ldr r3, [r2] //Load tens
ldr r4, [r2, #-4] //Load hundreds

add r1, #1
cmp r1, #9
ble endAddPoint
mov r1, #0
add r3, #1
cmp r3, #9
ble endAddPoint

mov r3, #0
add r4, #1

endAddPoint:

ldr r0, =playerPointsOne
ldr r2, =playerPoints
str r1, [r0]
str r4, [r2, #-4]
str r3, [r2]

pop {r0, r1,r2,r3, r4,lr}
bx lr


//Used to check if player's points equal zero
.globl checkPlayerPointsZero
checkPlayerPointsZero:
push {lr}
mov r6, #0
ldr r0, =playerPoints
ldr r1, [r0]
cmp r1, #0
bne endPlayerPointCheck

ldr r1, [r0, #-4]
cmp r1, #0
bne endPlayerPointCheck

ldr r0, =playerPointsOne
ldr r1, [r0]
cmp r1, #0
bne endPlayerPointCheck
moveq r6, #1

endPlayerPointCheck:

pop {lr}
bx lr


//Data associated with player's location
.section .data
.align 4
.globl playerLoc
playerLoc:
.int 0,0,0

.globl playerPointsOne
playerPointsOne:
.int 0,0

.globl playerPoints
playerPoints:
.int 0, 0, 0, 0

Click to View SNES Controller Code Snippet

.section .text

.globl InitSNESController
InitSNESController:

.globl initClock
initClock:
///////////////////////////CODE TO INIT CLOCK LINE FOR SNES CONTROLLER////////////////////
//Clock is eleven!
ldr r0, =0x20200004
ldr r1, [r0]
mov r2, #7
lsl r2, #3
bic r1, r2
mov r3, #1
lsl r3, #3
orr r1, r3
str r1, [r0]
mov pc, lr


.globl initLatch
initLatch:
//////////////////////////////////// CODE INIT LATCH LINE FOR SNES CONTROLLER/////////////////////
//latch is 9!
ldr r0, =0x20200000
ldr r1, [r0]
mov r2, #7
lsl r2, #27
bic r1, r2
mov r3, #1
lsl r3, #27
orr r1, r3
str r1, [r0]
mov pc, lr

.globl initData
initData:
////////////////////CODE TO INIT DATA LINE FOR SNES CONTROLLER///////////////////////////////
//Data is 10!
ldr r0, =0x20200004
ldr r1, [r0]
mov r2, #7
bic r1, r2
str r1, [r0]
mov pc, lr


//////////////////////////////////////////////////////////
//NON INIT SNES-RELATED CODE HERE //
// //
//////////////////////////////////////////////////////////


//WRITE CLOCK CODE
.globl writeClock
writeClock:
//r1 contains the value you wish to write
ldr r2, =0x20200000//Clock
mov r3, #1
lsl r3, #11
teq r1, #0
streq r3, [r2, #40]
strne r3, [r2, #28]

bx lr

//WRITE LATCH CODE
.globl writeLatch
writeLatch:
//r1 contains the value you wish to write
ldr r2, =0x20200000//Latch
mov r3, #1
lsl r3, #9
teq r1, #0
streq r3, [r2, #40]
strne r3, [r2, #28]

bx lr

//READ DATA CODE
.globl readData
readData:

ldr r2, =0x20200000
ldr r1, [r2, #52]
mov r3, #1
lsl r3, #10
and r1,r3
teq r1,#0
moveq r4, #0
movne r4, #1

bx lr
////////////////////////////////////////////////////////////////////////////////////////////
///==================================== BIG READER========================================//
////////////////////////////////////////////////////////////////////////////////////////////

.globl readSNES
readSNES:
push {r0,r1,r2,r3,r4,r5,r6,r7,lr}

//Clock is the 11 pin
//writeGPIO(Clock, #1)
mov r1, #1
bl writeClock

//Latch is the 9th pin
//Write GPIO(Latch, #1)
mov r1, #1
bl writeLatch

bl wait12ms


//Write GPIO (Latch, #0)
//mov r0, #9
mov r1, #0
bl writeLatch


ldr r0, =buttons //Load the array of buttons
mov r7, #0 // i =0

pulseLoop:


cmp r7, #16
bge endPulseLoop // if (i >= 16 break out)
bl wait6ms


//Write a 0 GPIO (clock #11)
mov r1, #0////////////////////////////
bl writeClock


bl wait6ms

//Read GPIO DATA
bl readData

ldr r0, =buttons
sub r0, r0,r7//store read b = buttons[i]
strb r4, [r0]

//Write GPIO (clock #11) write 1
mov r1, #1
bl writeClock

add r7, #1//i++

b pulseLoop


endPulseLoop:


pop {r0,r1,r2,r3,r4,r5,r6,r7,lr}
bx lr


//Validate SNES
//Loop through button array to see if a button has been pressed
validSNES:
push {lr}
mov r6, #0 //Counter
mov r0, #0
validLoop:
cmp r6, #-16
beq endLoop
ldr r10, =buttons
ldrb r9, [r10, r6]
cmp r9, #1
movne r0, #1
sub r6, #1
b validLoop

endLoop:
pop {lr}
bx lr

//Only breaks loop when all buttons have been released
.globl buttonRelease
buttonRelease:
push {r0, r9, lr}
mov r0, #1
releaseLoop:
cmp r0, #0
beq releaseEnd
bl readSNES
bl validSNES
b releaseLoop
releaseEnd:
pop {r0,r9,lr}
bx lr


.section data

.globl buttons
buttons:
.byte 17

Click to View Graphics Code Snippet

.section .text

//DRAWING VERTICAL LINES
.globl drawVertLine
drawVertLine:
//r1 = x
//r2 = y
//r4 = length
push {r1,r2,r3,r4,lr}
while1:
cmp r2, r4
bge endWhile1
add r2, #1
bl DrawPixel16bpp
b while1

endWhile1:
pop {r1,r2,r3,r4,lr}
bx lr

//HORIZONTAL LINE
.globl drawHozLine
drawHozLine:
//r1 = x
//r2 = y
//r4 = length
push {r2,r3,r4,lr}
while2:
cmp r1, r4
bge endWhile2
add r1,#1
bl DrawPixel16bpp
b while2

endWhile2:
pop {r2,r3,r4,lr}
bx lr

//DRAW NON-FILLED SQUARE
.globl drawSquare
drawSquare:
///r1 is initial x point
///r2 is intial y point
///R4 has the size
push {r1, r2,r3, r4, r5,r7, lr}



mov r5, r1
mov r6, r2

mov r7, r4
add r4, r4, r2

mov r1, r5
mov r2, r6
bl drawVertLine //Draw vertical Line

mov r4, r7
add r4, r4, r1 // length = r4 + oneside


mov r1, r5
mov r2, r6
bl drawHozLine //Draw horizontal line



add r1, r5, r7
mov r4, r7
add r4, r4, r6 // length = r4 + oneside
mov r2, r6
bl drawVertLine //Draw vertical line


mov r4, r7
add r4, r4, r5 // length = r4 + oneside

mov r1, r5
add r2, r6,r7
bl drawHozLine //Draw Horizontal

pop {r1,r2, r3, r4, r5,r7,lr}
bx lr

//Draw a filled in square
.globl drawFillSquare
drawFillSquare:

//Values pushed in are
// r1 this takes the same role as r1
// r2 this takes the same role as r2
// r4 this takes height
// r5 this takes length
push {r1, r2,r3, r4, r5, lr}
add r4, r4,r2 //Adjust offset (so add r4, r4,r2)
add r5, r5,r1

fillLoop:
cmp r1, r5
bge endFill
bl drawVertLine
add r1, #1
b fillLoop
endFill:

pop {r1,r2, r3, r4, r5, lr}
bx lr

//Clear the screen black
//With given dimensions
.globl clearScreen
clearScreen:
push {r1, r2,r3, r4, r5, lr}
add r4, r4,r2 //Adjust offset (so add r4, r4,r2)
add r5, r5,r1
fillLoop1:
ldr r3, =0x0000
cmp r1, r5
bge endFill1
bl drawVertLine
add r1, #1
b fillLoop1
endFill1:
pop {r1,r2, r3, r4, r5, lr}
bx lr

//Clear the queen
.globl clearQueen
clearQueen:

push {r1, r2,r3, r4, r5, r6, r7, lr}
ldr r3, =0x0000

mov r5, r1
mov r6, r2

mov r7, r4
add r4, r4, r2

mov r1, r5
mov r2, r6
bl drawVertLine //Draw vertical line

ldr r3, =0x0000
mov r4, r7
add r4, r4, r1 // length = r4 + oneside


mov r1, r5
mov r2, r6
bl drawHozLine // Draw horizontal line


ldr r3, =0x0000

add r1, r5, r7
mov r4, r7
add r4, r4, r6 // length = r4 + oneside
mov r2, r6
bl drawVertLine// Draw Vertical Line


ldr r3, =0x0000
mov r4, r7
add r4, r4, r5 // length = r4 + oneside

mov r1, r5
add r2, r6,r7
bl drawHozLine //Draw horizontal line

pop {r1,r2, r3, r4, r5, r6,r7, lr}
bx lr


.section .data

Click to View Stage Setup Code Snippet

.section .text

.globl stage1
stage1:
//Init Bullet stats
bl initBullet

//Init Hit factor of objects
ldr r0, =obstacleHitFactor
mov r1, #2
str r1, [r0] //Pawn 1
str r1, [r0, #-4]//Pawn 2
str r1, [r0, #-8]//Pawn 3
str r1, [r0, #-12]//Pawn 4
str r1, [r0, #-16]//Box
str r1, [r0, #-20]//Placeholder
str r1, [r0, #-24]//Placeholder

//Init Pawn 1
mov r1, #0
mov r2, #500
mov r3, #100
mov r4, #50
mov r5, #600
mov r6, #0
bl initMonsterStats

//Store location of enemy
ldr r0, =obstacleLocStage
str r5, [r0]

//Init Pawn 2
mov r1, #0
mov r2, #500
mov r3, #0
mov r4, #50
mov r5, #700
mov r6, #-4
bl initMonsterStats

//Store location of enemy
ldr r0, =obstacleLocStage
str r5, [r0, #-4]

//Init Pawn 3
mov r1, #100
mov r2, #400
mov r3, #100
mov r4, #50
mov r5, #800
mov r6, #-8
bl initMonsterStats

//Store location of enemy
ldr r0, =obstacleLocStage
str r5, [r0, #-8]

//Init Pawn 4
mov r1, #20
mov r2, #300
mov r3, #200
mov r4, #50
mov r5, #640
mov r6, #-12
bl initMonsterStats

//Store location of enemy
ldr r0, =obstacleLocStage
str r5, [r0, #-12]


//r1 has the obstacle colors
//r2 has enemyOneColor
//r3 has enemyTwoColor
//r4 has enemyThreeColor
//r5 has enemyFourColor
//Save Color Scheme for checkHit in bullet.s
ldr r1, =0x00FF
ldr r2, =0x036a
ldr r3, =0x036a
ldr r4, =0x036a
ldr r5, =0x036a
bl loadColorScheme

//Init obstacleSize
ldr r0, =obstacleSizeStage1
mov r1, #400
mov r2, #50
str r1, [r0]
str r2, [r0, #-4]


//Init locations of everything for bullet
//Init Obstacle Location
ldr r0, =obstacleLocStage
mov r1, #140
str r1, [r0, #-16]


//Location of obstacle
ldr r0, =obstacleLocStage
mov r1, #0
str r1, [r0, #-20]//Place Holder
str r1, [r0, #-24]//Place holder
str r1, [r0, #-28]//Place holder


//Init total enemies on screen
mov r1, #4
bl setEnemyTotal



//Clear the canvas to draw stage1
b clearStage1



stage1Loop:


//Draw player score
//Game title
//And author's name on screen
bl drawScore
bl drawTitle
bl drawName
bl drawStage1




//Intended code for game play
//If there are no longer any enemies here
//Move to next stage
bl getEnemyTotal
cmp r1, #0
beq stage2

//Check if player's points = zero
//If so the player has lost
mov r6, #0
bl checkPlayerPointsZero
cmp r6, #1
beq loseStage


//If start is pressed then go to menu screen
bl readSNES
ldr r0, =buttons
ldrb r7, [r0, #-3]
cmp r7, #0
bleq buttonRelease
beq mainMenuInit

//Draw player on screen
bl drawPlayer

//Clear obstacle
ldr r0, =obstacleLocStage
ldr r1, [r0, #-16]
ldr r2, [r0, #-16]
ldr r0, =obstacleColors
ldr r3, =0x0000
ldr r0, =obstacleSizeStage1
ldr r4, [r0]
ldr r5, [r0, #-4]
bl drawFillSquare


//Check Obstacle Hit Factor to shrink
ldr r0, =obstacleHitFactor
ldr r2, =obstacleSizeStage1
ldr r1, [r0, #-16]
cmp r1, #1
moveq r3, #50
streq r3, [r2]
streq r3, [r2, #-4]

//Check Obstacle Hit Factor to disappear
ldr r0, =obstacleHitFactor
ldr r2, =obstacleColors
ldr r3, =0x0000
ldr r1, [r0, #-16]
cmp r1, #0
streqh r3, [r2]

//Draw Obstacle
ldr r0, =obstacleLocStage
ldr r1, [r0, #-16]
ldr r2, [r0, #-16]
ldr r0, =obstacleColors
ldrh r3, [r0]
ldr r0, =obstacleSizeStage1
ldr r4, [r0]
ldr r5, [r0, #-4]
bl drawFillSquare


//Check Pawn 1 hit factor
//If equal to 0 then it's dead
ldr r0, =obstacleHitFactor
ldr r2, =obstacleColors
ldr r3, =0x0000
ldr r1, [r0]
cmp r1, #0
streqh r3, [r2, #-6]


//Check Pawn 2 hit factor
//If equal to 0 then it's dead
ldr r0, =obstacleHitFactor
ldr r2, =obstacleColors
ldr r3, =0x0000
ldr r1, [r0, #-4]
cmp r1, #0
streqh r3, [r2, #-8]

//Check Pawn 3 Hit factor
//If equal to 0 then it's dead
ldr r0, =obstacleHitFactor
ldr r2, =obstacleColors
ldr r3, =0x0000
ldr r1, [r0, #-8]
cmp r1, #0
streqh r3, [r2, #-10]

//Check Pawn 4 hit factor
//If equal to 0 then it's dead
ldr r0, =obstacleHitFactor
ldr r2, =obstacleColors
ldr r3, =0x0000
ldr r1, [r0, #-12]
cmp r1, #0
streqh r3, [r2, #-12]


//Read 'a' to shoot a bullet.
bl readSNES
ldr r0, =buttons
ldrb r7, [r0, #-8]
cmp r7, #0
bleq buttonRelease
beq shootBullet

//Fire Enemy Bullet
bl moveBullet

//Draw pawn 1 mov r6, #-6
bl findObjectsColor
mov r11, #0
bl movingOpponent

//Draw pawn 2
mov r6, #-8
bl findObjectsColor
mov r11, #-4
bl movingOpponent

//Draw pawn 3
mov r6, #-10
bl findObjectsColor
mov r11, #-8
bl movingOpponent

//Draw pawn 4
mov r6, #-12
bl findObjectsColor
mov r11, #-12
bl movingOpponent




//Loop stage back around to redraw enemies
b stage1Loop

clearStage1:
//Draws a giant black square to clear screen
mov r1, #0
mov r2, #0
ldr r3, =0x0000
mov r4, #700
mov r5, #1000
bl drawFillSquare
//Go ahead and draw the stage now
b stage1Loop


.section .data
.align 4

//HEIGHT, WIDTH of obstacle
obstacleSizeStage1:
.int 0
.int 0
.int 0


Overall this game was extremely simple and draws simple boxes on the screen. This project displays my adaptive capability when I encounter difficulties and deadlines.