Skip to content

Instantly share code, notes, and snippets.

@SamJakob
Last active December 2, 2022 22:14
Show Gist options
  • Save SamJakob/13aa57307d43e524fe5cf2ca2e3a2f78 to your computer and use it in GitHub Desktop.
Save SamJakob/13aa57307d43e524fe5cf2ca2e3a2f78 to your computer and use it in GitHub Desktop.
gpio.s (v1.1.1)
/**
* gpio.s - Library for working with GPIO pins in ARM32 assembly.
* Version 1.1.1 (Dec/2/22)
*
* Changelog:
* ==========
* v1.1.1 (Dec/2/22):
* - Make gpioMode pull up for input and pull down for output.
* v1.1 (Dec/1/22):
* - Add gpioRead and gpioPull functions.
* v1.0 (Oct/10/22):
* - Initial Release
*
* License:
* ========
*
* Copyright 2022 Sam Jakob M.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/*
* An alternative approach to controlling the GPIO pins that uses memory-mapped IO.
*/
.text
/**** PRE-REQUISITES (DEPENDENCIES) ****/
// GPIO CONSTANTS
// ==============
// You can copy these constants to your program, or link them to both files
// with the .include directive.
/* Used to set whether a pin is intended for output or input. */
.set gpio_input, 0b000
.set gpio_output, 0b001
/*
* Used to set what peripheral a pin should be used for (e.g., mini UART)
*
* Refer to the tables on pp. 102 - 104; 'Alternative Function Assigments'
* for the specifics of how each GPIO function refers to each pin.
*/
.set gpio_function_0, 0b100
.set gpio_function_1, 0b101
.set gpio_function_2, 0b110
.set gpio_function_3, 0b111
.set gpio_function_4, 0b011
.set gpio_function_5, 0b010
// Function Call Macros
// ====================
// Defines macros for PROLOGUE and EPILOGUE based on the ARM calling
// convention. You can copy this into your program or include the file with the
// .include directive.
/*
Macros to execute function prologue and epilogue based on the
ARM calling convention.
https://developer.arm.com/documentation/dui0040/d/using-the-procedure-call-standards/using-the-arm-procedure-call-standard/apcs-register-names-and-usage?lang=en
*/
.macro PROLOGUE
PUSH {R4-R11, LR} // Push the register state from before the function call.
MOV R4, R0 // Copy passed arguments (R0-R3) into scratch registers.
MOV R5, R1
MOV R6, R2
MOV R7, R3
.endm
.macro EPILOGUE
POP {R4-R11, LR} // Restore the original registers.
BX LR // Return to caller.
.endm
// ARM32 Division
// ==============
// Naive implementation at general division on ARM with remainder using the
// subtraction method.
/**
* Performs division given a numerator (R0) and denominator (R1).
* The quotient is returned in R0 and the remainder in R1.
*/
divide:
// R4 = N (numerator)
// R5 = D (denominator)
PROLOGUE
MOV R6, #0 // Q = 0
div_start:
CMP R4, R5
BLT div_complete
ADD R6, R6, #1 // Q + Q + 1
SUB R4, R4, R5 // N = N - D
B div_start
div_complete:
MOV R0, R6 // R0 contains Q (quotient) from R6
MOV R1, R4 // R1 contains R (remainder) from R4
EPILOGUE
// An alias to call the divide function.
.macro divide numerator:req, denominator:req
MOV R0, \numerator
MOV R1, \denominator
BL divide
.endm
// An alias for divide, however it moves the remainder into
// R0 for semantic 'correctness'.
.macro modulo numerator:req, denominator:req
divide \numerator, \denominator
MOV R0, R1
.endm
/**** BEGIN GPIOLIB ****/
.extern open
.extern close
.extern mmap
.extern munmap
/**
* The length, in bytes, to map of the GPIO memory.
*
* Per the Broadcom BCM2835 ARM peripherals technical specifications, there are
* 41 GPIO registers, each with 32-bit accesses.
*
* Hence, 41 * 4 bytes => 164
*/
.set map_length, 164
/**
* Initializes the GPIO library. Maps the necessary memory.
*/
.global initializeGPIO
initializeGPIO:
PROLOGUE
// Open the GPIO memory file.
LDR R0, =gpioMemDevFile
LDR R1, =0x181002 // Flags: O_RDWR | O_SYNC | O_CLOEXEC
MOV R2, $0f // Mode: 0
BL open
MOV R4, R0 // Move the file descriptor into R4, ready
// for the next system call.
// Then, mmap that file into shared space.
MOV R0, $0 // addr: 0 (let the system pick a suitable address).
MOV R1, $map_length // length: use $map_length.
MOV R2, $3 // prot: PROT_READ | PROT_WRITE
MOV R3, $1 // flags: MAP_SHARED = 1
// R4 is the file descriptor returned by the call to open.
MOV R5, $0 // offset: 0 bytes (gpiomem already starts at the GPIO registers)
PUSH {R4, R5}
BL mmap
POP {R4, R5}
// Save the GPIO base address in R0.
LDR R1, =gpioMMIOBase
STR R0, [R1]
// Close the file descriptor as it's no longer needed now that we've called
// mmap.
MOV R0, R4 // Move the file descriptor back into R0.
BL close
EPILOGUE
/**
* Tears down the GPIO library. Unmaps any used memory.
*/
.global closeGPIO
closeGPIO:
PROLOGUE
// Load the GPIO base address.
LDR R0, =gpioMMIOBase
LDR R0, [R0]
// Set R1 to the map length.
MOV R1, $map_length
// Unmap the memory.
BL munmap
// Overwrite the GPIO MMIO base address with 0 to indicate there is no
// initialized mapping.
MOV R4, $0
LDR R0, =gpioMMIOBase
STR R4, [R0]
// Overwrite registers used.
MOV R0, $0
MOV R1, $0
EPILOGUE
.global gpioMode
gpioMode:
// R0 = pin index
// R1 = mode: input (0) or output (1)
PROLOGUE
// R0 has been moved to R4 (first scratch register).
// Maximum pin index is 53.
CMP R4, $53
BHI 0f // Perform an unsigned compare (BHI - Branch HIgher than)
// If the pin is invalid (i.e., greater than 53), just jump
// to the end of the function call and do nothing else.
// R0 should still be set from the function call.
// TODO: check mode
CMP R1, $0
BNE 1f
// If the pin is in input mode, automatically pull up the pin.
MOV R1, $2 // 0b10 (2) is pull up.
BL gpioPull
B 2f
1:
// If we're here, R1 is for output.
MOV R1, $1 // 0b01 (1) is pull down.
BL gpioPull
2:
// Load the MMIO base address into R6.
LDR R6, =gpioMMIOBase
LDR R6, [R6]
// Divide R0 (pin index) by 10 to determine the register and bit mask.
MOV R1, $10
BL divide
// R0 -> quotient (register offset)
// R1 -> remainder (pin offset for bitmask)
// Load the value of the relevant register into R7.
// See STR call at end of function for notes on LSL #2.
LDR R7, [R6, R0, LSL #2]
// Calculate the offset (each pin has a 3-bit field).
MOV R2, $3 // Number of bits per field.
MUL R8, R2, R1 // Each pin has a 3-bit field, so multiply the
// offset by 3 and store the offset in R8.
LSL R5, R5, R8 // Shift the mode by the offset.
// Clear that pin's value in the register.
MOV R3, $0b111 // Define the 3-bit mask, 0b111.
LSL R3, R3, R8 // Shift mask left by R8 times.
BIC R7, R7, R3 // Now bit-clear R7 with our mask. This has the
// same effect as NOTing the value and ANDing it to
// R7.
// Next, apply the actual mode for the pin and store the updated register
// value (at word-offset R0, shifting R0 left by 2 multiplies by 4, thereby
// making it a word offset instead of a byte offset).
ORR R7, R7, R5
STR R7, [R6, R0, LSL #2]
0:
EPILOGUE
.global gpioSet
gpioSet:
// R0 = pin index
// R1 = set (1) or clear (0)
PROLOGUE
// R0 has been moved to R4 (first scratch register).
// Maximum pin index is 53.
CMP R4, $53
BHI 0f // Perform an unsigned compare (BHI - Branch HIgher than)
// If the pin is invalid (i.e., greater than 53), just jump
// to the end of the function call and do nothing else.
// R0 should still be set from the function call.
MOV R1, $32 // Divide R0 by $32 so we can determine which 'set' of pins
// we're working with.
BL divide
// R0 -> quotient (register offset)
// R1 -> remainder (pin offset)
// R0 is our register offset (0 or 1).
// R1 will become the bit mask which we obtain by shifting
// 1 left by our pin offset.
MOV R7, $1
LSL R1, R7, R1 // R1 = 1 << R1 (shift 1 by the pin offset for this
// register).
// Load the MMIO base address into R6.
LDR R6, =gpioMMIOBase
LDR R6, [R6]
// Add the register offset to R6.
ADD R6, R6, R0
// Set R5 to the MMIO base plus set/clear offset
// (based on whether we're setting or clearing the pin, as
// specified by the user and copied into R5 by the prologue).
CMP R5, $0 // If R5 = 0 we're clearing, otherwise we're setting.
BEQ 1f
ADD R6, R6, $0x1C // IS SET: GPIO base + GPIO pin output set offset.
B 2f
1: ADD R6, R6, $0x28 // IS CLEAR: GPIO base + GPIO pin output clear offset.
2: // End Branch: Set/Clear offset decided.
LDR R5, =gpioMMIOBase
LDR R5, [R5]
// Now store our pin offset in the MMIO register.
STR R1, [R6]
0:
EPILOGUE
.global gpioRead
gpioRead:
// R0 = pin index
PROLOGUE
// R0 has been moved to R4 (first scratch register).
// Maximum pin index is 53.
CMP R4, $53
BHI 0f // Perform an unsigned compare (BHI - Branch HIgher than)
// If the pin is invalid (i.e., greater than 53), just jump
// to the end of the function call and do nothing else.
// R0 should still be set from the function call.
MOV R1, $32 // Divide R0 by $32 so we can determine which 'set' of pins
// we're working with.
BL divide
// R0 -> quotient (register offset)
// R1 -> remainder (pin offset)
// R0 is our register offset (0 or 1).
// R1 will become the bit mask which we obtain by shifting
// 1 left by our pin offset.
MOV R7, $1
LSL R1, R7, R1 // R1 = 1 << R1 (shift 1 by the pin offset for this
// register).
// Load the MMIO base address into R6.
LDR R6, =gpioMMIOBase
LDR R6, [R6]
// Add the offset for GPLEV0 (the first register for GPIO Pin Level).
ADD R6, R6, $0x34
// Add the register offset to R6.
ADD R6, R6, R0
// Read the value at R6 and store it in R5.
LDR R5, [R6]
// AND R5 by R1 to check only the bit for the pin we're interested in.
// Then, store the result in R0.
AND R0, R5, R1
// Compare the value of R0 with 0, if it's 0 we simply return from the
// function.
// Otherwise we abstract the raw value by simply returning 1 if that bit
// was on.
// (e.g., for pin 4 this value would be 1 << 4, so we abstract that by
// just returning 1 instead.)
CMP R0, $0
BEQ 0f
MOV R0, $1
0:
EPILOGUE
.global gpioPull
gpioPull:
// R0 = pin index
// R1 = PUD (0 = disable, 1 = pull down, 2 = pull up)
PROLOGUE
// R0 has been moved to R4 (first scratch register).
// Maximum pin index is 53.
CMP R4, $53
BHI 0f // Perform an unsigned compare (BHI - Branch HIgher than)
// If the pin is invalid (i.e., greater than 53), just jump
// to the end of the function call and do nothing else.
// R0 should still be set from the function call.
// Mask R1 to 0b11 (which is the maximum possible value).
AND R1, R1, $0b11
// 1. Write to GPPUD to set the required control signal.
LDR R6, =gpioMMIOBase
LDR R6, [R6]
// Add the offset for GPPUD the pull-up-down control enable register.
ADD R6, R6, $0x94
LDR R5, [R6]
AND R5, R5, $0xFFFFFFFC // Mask off all but the last two bits.
ORR R5, R5, R1 // OR by R1 to mask on the bits we want.
STR R5, [R6] // Store the new value back.
// 2. Wait 150 cycles (set up time).
MOV R0, $150
BL cycleSleep
// 3. Write to GPPUDCLK to clock the control signal into the desired
// GPIO pads.
MOV R0, R4
MOV R1, $32 // Divide R0 by $32 so we can determine which 'set' of pins
// we're working with.
BL divide
// R0 -> quotient (register offset)
// R1 -> remainder (pin offset)
// R0 is our register offset (0 or 1).
// R1 will become the bit mask which we obtain by shifting
// 1 left by our pin offset.
MOV R7, $1
LSL R1, R7, R1 // R1 = 1 << R1 (shift 1 by the pin offset for this
// register).
// Load the MMIO base address into R6.
LDR R6, =gpioMMIOBase
LDR R6, [R6]
ADD R6, R6, $0x98
// Add the register offset to R6.
ADD R6, R6, R0
MOV R9, R6 // Back this up into R9, it'll be used later.
// Store our new mask back in the GPPUDCLK register.
STR R1, [R6]
// 4. Wait 150 cycles (hold time).
MOV R0, $150
BL cycleSleep
// 5. Write to GPPUD to remove the control signal.
LDR R6, =gpioMMIOBase
LDR R6, [R6]
// Add the offset for GPPUD the pull-up-down control enable register.
ADD R6, R6, $0x94
LDR R5, [R6]
AND R5, R5, $0xFFFFFFFC // Mask off all but the last two bits.
STR R5, [R6] // Store the new value back.
// 6. Write to GPPUDCLK to remove the clock.
MOV R6, R9 // Restore our backup for the GPPUDCLK regsiter.
MOV R1, $0
STR R1, [R6] // Store 0 in R6 to clear the clock register.
// Wait some time for the change to propagate to the pin.
MOV R0, $3000
BL cycleSleep
0:
EPILOGUE
/**
* Sleeps for **at least** N CPU cycles, where N = R0.
*/
cycleSleep:
PROLOGUE
// Check that the input is greater than 0. If it isn't, exit immediately.
CMP R0, $0
BLE 1f
0:
// Subtract 1 from R0, and if not yet zero, continue this loop.
SUB R0, R0, $1
CMP R0, $0
BNE 0b
1:
EPILOGUE
.data
.align 4
gpioMemDevFile: .asciz "/dev/gpiomem"
.bss
.align 4
/**
* Holds the GPIO base address for MMIO operations.
* You can check if this has been initialized (i.e., whether initializeGPIO
* has been called) by comparing this to zero.
*/
gpioMMIOBase: .word
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment