Chapter 27: GPIO Configuration

September 17, 2026 · View on GitHub

Introduction

GPIO configuration is the most complex function in our blink driver. GPIO_Config must modify three separate hardware blocks — pad control, IO function select, and SIO output enable — to transform a default-state pin into a driven output. This chapter walks through every instruction of GPIO_Config, showing how it configures the electrical properties, selects the SIO function, and enables the output driver.

GPIO_Config — Full Source

.global GPIO_Config
.type GPIO_Config, @function
GPIO_Config:
  addi  sp, sp, -4                               # allocate stack frame
  sw    ra, 0(sp)                                # save return address
.GPIO_Config_Modify_Pad:
  li    t0, PADS_BANK0_BASE                      # load PADS_BANK0_BASE address
  add   t0, t0, a0                               # PADS_BANK0_BASE + PAD_OFFSET
  lw    t1, 0(t0)                                # read pad register value
  li    t2, ~(1<<7)                              # mask to clear OD bit
  and   t1, t1, t2                               # clear OD bit
  ori   t1, t1, (1<<6)                           # set IE bit
  li    t2, ~(1<<8)                              # mask to clear ISO bit
  and   t1, t1, t2                               # clear ISO bit
  sw    t1, 0(t0)                                # store pad config
.GPIO_Config_Modify_CTRL:
  li    t0, IO_BANK0_BASE                        # load IO_BANK0 base
  add   t0, t0, a1                               # IO_BANK0_BASE + CTRL_OFFSET
  lw    t1, 0(t0)                                # read CTRL register
  andi  t1, t1, ~0x1f                            # clear FUNCSEL field
  ori   t1, t1, 0x05                             # set FUNCSEL to SIO (5)
  sw    t1, 0(t0)                                # store CTRL config
.GPIO_Config_Enable_OE:
  li    t0, SIO_GPIO_OE_SET                      # load SIO GPIO_OE_SET address
  li    t1, 1                                    # bit value
  sll   t1, t1, a2                               # shift to GPIO position
  sw    t1, 0(t0)                                # enable output for GPIO
  lw    ra, 0(sp)                                # restore return address
  addi  sp, sp, 4                                # deallocate stack frame
  ret                                            # return

Parameters

GPIO_Config takes three parameters:

RegisterParameterValue for GPIO16
a0PAD_OFFSET0x44 (PADS_BANK0_GPIO16_OFFSET)
a1CTRL_OFFSET0x84 (IO_BANK0_GPIO16_CTRL_OFFSET)
a2GPIO number16

Using offsets and a GPIO number makes the function reusable for any pin.

Stack Frame

Prologue

  addi  sp, sp, -4                               # allocate stack frame
  sw    ra, 0(sp)                                # save return address

GPIO_Config is called from main via call GPIO_Config. The call instruction writes the return address to ra. But GPIO_Config is itself called from main (which was reached via j from Reset_Handler), so we must preserve ra on the stack.

Although GPIO_Config does not itself call any functions in the current code, the stack frame follows the conservative pattern for non-leaf functions. The 4-byte frame holds exactly one word — the saved ra.

Before:  sp → [...]
After:   sp → [saved ra]  ← sp decreased by 4

Epilogue

  lw    ra, 0(sp)                                # restore return address
  addi  sp, sp, 4                                # deallocate stack frame
  ret                                            # return

The epilogue reverses the prologue: load ra from the stack, restore sp, and return.

Phase 1: Pad Configuration

.GPIO_Config_Modify_Pad:
  li    t0, PADS_BANK0_BASE                      # load PADS_BANK0_BASE address
  add   t0, t0, a0                               # PADS_BANK0_BASE + PAD_OFFSET
  lw    t1, 0(t0)                                # read pad register value

PADS_BANK0_BASE + a0 computes the GPIO-specific pad register address. For GPIO16: 0x40038000 + 0x44 = 0x40038044.

Clear Output Disable (OD)

  li    t2, ~(1<<7)                              # mask to clear OD bit
  and   t1, t1, t2                               # clear OD bit

Bit 7 is the Output Disable flag. When set, the pad's output driver is turned off regardless of other settings. We clear it to allow output.

~(1<<7) = 0xFFFFFF7F — all bits set except bit 7. The li pseudo-instruction loads this 32-bit constant.

Set Input Enable (IE)

  ori   t1, t1, (1<<6)                           # set IE bit

Bit 6 is the Input Enable flag. Even though we are configuring an output, enabling the input allows GPIO state readback — useful for debugging. (1<<6) = 64, which fits in the 12-bit signed immediate range for ori.

Clear Isolation (ISO)

  li    t2, ~(1<<8)                              # mask to clear ISO bit
  and   t1, t1, t2                               # clear ISO bit

Bit 8 is the Isolation flag. When set, the pad is electrically isolated from the internal logic. This is a power-saving feature that must be cleared for normal operation.

Write Back

  sw    t1, 0(t0)                                # store pad config

The modified value is written back to the pad register.

Pad Register Bit Map

BitNameDefaultOur SettingPurpose
7OD00 (clear)Output Disable — must be 0 for output
6IE11 (set)Input Enable — allows readback
8ISO10 (clear)Isolation — must be 0 for operation

Phase 2: Function Select

.GPIO_Config_Modify_CTRL:
  li    t0, IO_BANK0_BASE                        # load IO_BANK0 base
  add   t0, t0, a1                               # IO_BANK0_BASE + CTRL_OFFSET
  lw    t1, 0(t0)                                # read CTRL register
  andi  t1, t1, ~0x1f                            # clear FUNCSEL field
  ori   t1, t1, 0x05                             # set FUNCSEL to SIO (5)
  sw    t1, 0(t0)                                # store CTRL config

Address Computation

IO_BANK0_BASE + a1 = 0x40028000 + 0x84 = 0x40028084 for GPIO16.

Clear FUNCSEL

  andi  t1, t1, ~0x1f                            # clear FUNCSEL field

Bits [4:0] of the CTRL register select which peripheral function drives the pin. ~0x1f = 0xFFFFFFE0 clears the bottom 5 bits.

Set SIO Function

  ori   t1, t1, 0x05                             # set FUNCSEL to SIO (5)

FUNCSEL value 5 selects SIO (Single-cycle IO). This connects the pin to the SIO block, which provides fast GPIO output through dedicated set/clear registers.

FUNCSEL Values

ValueFunctionUse Case
0JTAGDebug
1SPISerial peripheral
2UARTSerial communication
3I2CBus communication
4PWMPulse width modulation
5SIOGeneral-purpose GPIO
7PIO0Programmable IO
31NULLDisabled

Phase 3: Enable Output

.GPIO_Config_Enable_OE:
  li    t0, SIO_GPIO_OE_SET                      # load SIO GPIO_OE_SET address
  li    t1, 1                                    # bit value
  sll   t1, t1, a2                               # shift to GPIO position
  sw    t1, 0(t0)                                # enable output for GPIO

Dynamic Bit Position

  li    t1, 1                                    # bit value
  sll   t1, t1, a2                               # shift to GPIO position

sll (Shift Left Logical) shifts the value 1 left by a2 positions. For GPIO16: 1 << 16 = 0x00010000. This creates a bitmask with only the target GPIO bit set.

SIO Atomic Write

  sw    t1, 0(t0)                                # enable output for GPIO

SIO_GPIO_OE_SET (0xd0000038) is an atomic set register. Writing 0x00010000 sets bit 16 (enabling GPIO16's output driver) without affecting any other GPIO outputs. No read-modify-write is needed — the SIO hardware handles atomicity.

SIO vs APB

FeatureAPB (Pads, IO_BANK0)SIO
AccessRead-modify-writeAtomic set/clear
SpeedMulti-cycle (APB bus)Single-cycle
Address0x4xxxxxxx0xd0000000
Race safetyManual (must read first)Hardware atomic

Three Configuration Blocks

GPIO_Config

  ├── Phase 1: PADS_BANK0 (electrical)
  │     └── Clear OD, set IE, clear ISO

  ├── Phase 2: IO_BANK0 (function)
  │     └── Set FUNCSEL = 5 (SIO)

  └── Phase 3: SIO (output enable)
        └── Set OE bit for target GPIO

Contrast with ARM GPIO_Config

AspectARMRISC-V
Pad configSame registers, same bitsSame registers, same bits
FUNCSELSame register, same value (5)Same register, same value (5)
Output enablemcrr p0 coprocessor instructionsw to SIO_GPIO_OE_SET memory address
Stack framepush {lr} / pop {pc}addi sp/sw ra / lw ra/addi sp

The only architectural difference is the output enable step. On ARM, SIO is accessed via coprocessor instructions. On RISC-V, the same SIO hardware is memory-mapped at 0xd0000000.

Summary

  • GPIO_Config configures a GPIO pin in three phases: pad control, function select, output enable.
  • The stack frame saves ra because GPIO_Config is called from main.
  • Pad configuration clears OD (bit 7) and ISO (bit 8), sets IE (bit 6).
  • Function select sets FUNCSEL bits [4:0] to 5 (SIO).
  • Output enable uses sll to create a dynamic bitmask and writes it to SIO_GPIO_OE_SET.
  • SIO atomic registers eliminate the need for read-modify-write on the output enable step.
  • The function uses offsets as parameters, making it reusable for any GPIO pin.