Last active
December 2, 2022 22:14
-
-
Save SamJakob/13aa57307d43e524fe5cf2ca2e3a2f78 to your computer and use it in GitHub Desktop.
gpio.s (v1.1.1)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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