RP2350 Button Driver RISC-V
March 23, 2026 · View on GitHub
FREE Embedded Hacking Course HERE
VIDEO PROMO HERE
RP2350 Button Driver RISC-V
An RP2350 Button driver written entirely in RISC-V Assembler.
Install RISC-V Toolchain (Windows / RP2350 Hazard3)
Official Raspberry Pi guidance for RP2350 RISC-V points to pico-sdk-tools prebuilt releases.
Official References
Install (PowerShell)
$url = "https://github.com/raspberrypi/pico-sdk-tools/releases/download/v2.0.0-5/riscv-toolchain-14-x64-win.zip"
$zipPath = "$env:TEMP\riscv-toolchain-14-x64-win.zip"
$dest = "$HOME\riscv-toolchain-14"
Invoke-WebRequest -Uri $url -OutFile $zipPath
New-Item -ItemType Directory -Path $dest -Force | Out-Null
Expand-Archive -LiteralPath $zipPath -DestinationPath $dest -Force
Get-ChildItem -Path $dest | Select-Object Name
Add Toolchain To User PATH (PowerShell)
$toolBin = "$HOME\riscv-toolchain-14\bin"
$currentUserPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($currentUserPath -notlike "*$toolBin*") {
[Environment]::SetEnvironmentVariable("Path", "$currentUserPath;$toolBin", "User")
}
Close and reopen your terminal after updating PATH.
Verify Toolchain
riscv32-unknown-elf-as --version
riscv32-unknown-elf-ld --version
riscv32-unknown-elf-objcopy --version
Build This Project
.\build.bat
If your toolchain uses a different prefix, pass it explicitly:
.\build.bat riscv-none-elf
Button + LED Wiring (Pico 2 Target)
- GP15 (Pin 20) → tactile button → GND (internal pull-up enabled)
- GP16 (Pin 21) → 330 ohm resistor → LED anode
- LED cathode → GND (Pin 23)
Hardware
Raspberry Pi Pico 2 w/ Header BUY
USB A-Male to USB Micro-B Cable BUY
Raspberry Pi Pico Debug Probe BUY
Complete Component Kit for Raspberry Pi BUY
10pc 25v 1000uF Capacitor BUY
10% PiShop DISCOUNT CODE - KVPE_HS320548_10PC
Build
.\build.bat
Optional Toolchain Prefix Override
.\build.bat riscv-none-elf
Clean
.\clean.bat
Tutorial
A comprehensive 30-chapter technical book teaching RP2350 RISC-V assembly from absolute scratch. Every line of assembler is explained.
Foundations
Chapter 1: What Is a Computer?
- Introduction
- The Fetch-Decode-Execute Cycle
- The Three Core Components
- Microcontroller vs Desktop Computer
- What Is RP2350?
- What Is RISC-V?
- Why Assembly Language?
- What We Are Building
- Summary
Chapter 2: Number Systems — Binary, Hexadecimal, and Decimal
- Introduction
- Decimal — Base 10
- Binary — Base 2
- Hexadecimal — Base 16
- The 0x Prefix
- Bit Numbering
- Common Bit Patterns in Our Firmware
- Two's Complement — Signed Numbers
- Data Sizes on RISC-V Hazard3
- Summary
Chapter 3: Memory — Addresses, Bytes, Words, and Endianness
- Introduction
- The Address Space
- Bytes, Halfwords, and Words
- Alignment
- Little-Endian Byte Order
- Memory-Mapped Registers
- The Stack
- Flash Memory (XIP)
- SRAM
- CSR Access
- Addresses in Our Firmware
- Summary
Chapter 4: What Is a Register?
- Introduction
- The RISC-V Register File
- Register x0: The Hardwired Zero
- ABI Register Names
- Control and Status Registers
- Visualizing Registers
- Register Usage in Our Button Driver
- Summary
Chapter 5: Load-Store Architecture
- Introduction
- The Load-Store Rule
- Load Word — lw
- Store Word — sw
- The Read-Modify-Write Pattern
- Stack Operations
- Alignment
- Memory-Mapped I/O
- Summary
Chapter 6: The Fetch-Decode-Execute Cycle in Detail
- Introduction
- The Three Stages
- Tracing a Button Read
- Branch Penalty
- Instruction Timing
- How This Affects Our Driver
- Summary
RISC-V Instruction Set
Chapter 7: RISC-V Hazard3 ISA Overview
- Introduction
- ISA String Breakdown
- Instruction Formats
- Complete Instruction Map for Our Driver
- Summary
Chapter 8: Immediate and Upper-Immediate Instructions
- Introduction
- I-Type Immediates
- U-Type Immediates
- Building 32-Bit Constants — The li Pseudo-Instruction
- The la Pseudo-Instruction
- Immediate Usage Summary
- Summary
Chapter 9: Arithmetic and Logic Instructions
- Introduction
- Arithmetic Instructions
- Logic Instructions
- Shift Instructions
- Instruction Summary Table
- Summary
Chapter 10: Memory Access — Load and Store Deep Dive
- Introduction
- Addressing Mode
- Load/Store Instruction Family
- Every Load in Our Driver
- Every Store in Our Driver
- The Read-Modify-Write Pattern Revisited
- Write-Only SIO Registers
- CSR Access
- Alignment Rules
- Summary
Chapter 11: Branch Instructions
- Introduction
- How Branches Work
- Branch Instructions in RISC-V
- Pseudo-Instructions
- Branches in Our Button Driver
- No Flags Register
- Branch Timing
- Summary
Chapter 12: Jumps, Calls, and Returns
- Introduction
- The Jump and Link Instruction — jal
- The Jump and Link Register — jalr
- Pseudo-Instructions
- Call Graph of Our Button Driver
- Leaf vs. Non-Leaf Functions
- Jumps in Our Driver
- Summary
Assembly Programming
Chapter 13: Pseudo-Instructions and Assembler Conveniences
- Introduction
- What Is a Pseudo-Instruction?
- Pseudo-Instructions in Our Driver
- Complete Expansion Table
- Why Pseudo-Instructions Matter
- Summary
Chapter 14: Assembler Directives
- Introduction
- Section Directives
- Symbol Directives
- Constant Directives
- Data Directives
- Alignment Directives
- Type and Size Directives
- Complete Directive Table
- Summary
Chapter 15: Calling Convention and Stack Frames
- Introduction
- Argument and Return Registers
- Caller-Saved vs. Callee-Saved
- Stack Frame Anatomy
- Stack Frame in Button_Init
- Stack Frame in Button_IsPressed
- Leaf Functions Need No Frame
- Reset_Handler — A Special Case
- The Full Call Chain
- Summary
Chapter 16: Bitwise Operations for Hardware Control
- Introduction
- The Bit Manipulation Toolkit
- Setting Bits — ori
- Clearing Bits — andi
- Inverting a Bit — xori
- Dynamic Bit Masks — sll
- Extracting a Bit Field — srli + andi
- Button_Init — Complete Bit Manipulation Sequence
- The FUNCSEL Write
- Summary
Chapter 17: Memory-Mapped I/O and Volatile Access
- Introduction
- The Memory Map
- APB Peripherals
- SIO Registers
- Why Memory-Mapped I/O Works
- Volatile-Like Access in Assembly
- Read-Modify-Write vs. Atomic Set/Clear
- Bus Ordering
- Summary
Hardware Concepts
Chapter 18: RP2350 Hardware Architecture
- Introduction
- The Hazard3 Core
- Memory Architecture
- Clock System
- Reset Controller
- GPIO System
- Button Input Circuit
- LED Output Circuit
- Boot Sequence
- Summary
Build System
Chapter 19: The Linker Script — linker.ld
- Introduction
- Entry Point
- Memory Regions
- Program Headers
- Stack Size
- Sections
- Section Layout in Flash
- Why PHDRS Matters
- Summary
Chapter 20: The Build System — build.bat and clean.bat
- Introduction
- Build Pipeline Overview
- Stage 1: Assembly
- Stage 2: Linking
- Stage 3: Binary Extraction
- Stage 4: UF2 Conversion
- Flashing
- clean.bat
- Error Handling
- Summary
Source Code Walkthroughs
Chapter 21: Boot Metadata — image_def.s
- Introduction
- Source Code
- Section Placement
- Block Start Marker
- Image Type Item
- Entry Point Item
- Last Item and Block End
- Memory Layout
- Why This Matters
- Summary
Chapter 22: Constants and Definitions — constants.s
- Introduction
- Source Code
- Stack Constants
- Vector Table Base
- Crystal Oscillator Registers
- PPB and CSR Constants
- Clock Registers
- Reset Controller Registers
- I/O Bank Registers
- Pad Registers
- SIO Registers
- How .include Works
- Summary
Chapter 23: Stack and Vector Table — stack.s, vector_table.s
- Introduction
- stack.s — Source Code
- stack.s — Line-by-Line
- vector_table.s — Source Code
- vector_table.s — Line-by-Line
- Flash Layout After Linking
- Summary
Chapter 24: Reset Handler — reset_handler.s
- Introduction
- Source Code
- Reset_Handler — Initialization Sequence
- Why This Order Matters
- .size Directive
- Default_Trap_Handler
- Init_Trap_Vector
- No Stack Frame in Reset_Handler
- Summary
Chapter 25: Crystal Oscillator — xosc.s
- Introduction
- Source Code
- Init_XOSC — Setting the Startup Delay
- Init_XOSC — Enabling the Oscillator
- Init_XOSC — Polling for Stability
- Enable_XOSC_Peri_Clock — Switching the Clock Source
- Why Two Functions?
- Register Usage Summary
- Summary
Chapter 26: Reset Controller — reset.s
- Introduction
- Source Code
- Phase 1: Releasing IO_BANK0 from Reset
- Phase 2: Polling for Completion
- Why andi Works Here but not for Clearing
- Hardware Timing
- What IO_BANK0 Enables
- Register Usage
- Summary
Chapter 27: GPIO Configuration — gpio.s
- Introduction
- Source Code — GPIO_Config
- GPIO_Config — Parameters
- GPIO_Config — Stack Frame
- GPIO_Config — Pad Configuration
- GPIO_Config — Function Select
- GPIO_Config — Output Enable
- Source Code — GPIO_Set
- Source Code — GPIO_Clear
- GPIO_Config vs. Button_Init
- Summary
Chapter 28: Button Driver — button.s
- Introduction
- Source Code — Button_Init
- Button_Init — Stack Frame
- Button_Init — Computing the Pad Address
- Button_Init — Pad Configuration (5 Bit Operations)
- Button_Init — Function Select
- Button_Init — Disable Output
- Source Code — Button_Read
- Button_Read — How It Works
- Source Code — Button_IsPressed
- Button_IsPressed — Logic Inversion
- The Three-Function API
- Summary
Chapter 29: Application Entry Point — main.s
- Introduction
- Source Code
- Phase 1: LED Configuration
- Phase 2: Button Initialization
- Phase 3: The Polling Loop
- Control Flow Diagram
- Empty Data Sections
- Why beqz Instead of Button_IsPressed?
- Register Usage
- Summary
Integration
Chapter 30: Full Integration — Build, Flash, Wire, and Test
- Introduction
- The Complete Source File Map
- Step 1: Build the Firmware
- Step 2: Wiring the Hardware
- Step 3: Flash the Firmware
- Step 4: Test the System
- What Happens at Power-On
- Troubleshooting
- Clean Up
- What We Built
- Summary
main.s Code
/**
* FILE: main.s
*
* DESCRIPTION:
* RP2350 Button Driver Main Application (RISC-V).
*
* BRIEF:
* Main application entry point for RP2350 RISC-V button driver. Monitors
* button on GPIO15 and controls LED on GPIO16 based on button state.
*
* AUTHOR: Kevin Thomas
* CREATION DATE: November 2, 2025
* UPDATE DATE: March 21, 2026
*/
.include "constants.s"
/**
* Initialize the .text section.
* The .text section contains executable code.
*/
.section .text # code section
.align 2 # align to 4-byte boundary
/**
* @brief Main application entry point.
*
* @details Implements button monitoring loop. LED on GPIO16 lights up
* when button on GPIO15 is pressed.
*
* @param None
* @retval None
*/
.global main # export main
.type main, @function # mark as function
main:
.GPIO16_Config:
li a0, PADS_BANK0_GPIO16_OFFSET # load PADS_BANK0_GPIO16_OFFSET
li a1, IO_BANK0_GPIO16_CTRL_OFFSET # load IO_BANK0_GPIO16_CTRL_OFFSET
li a2, 16 # load GPIO number
call GPIO_Config # call GPIO_Config
.Button_Init:
call Button_Init # initialize button on GPIO15
.Loop:
call Button_Read # read button state (0=pressed, 1=released)
beqz a0, .Button_Pressed # branch if button pressed (a0==0)
.Button_Released:
li a0, 16 # load GPIO number
call GPIO_Clear # turn off LED
j .Loop_Delay # continue to delay
.Button_Pressed:
li a0, 16 # load GPIO number
call GPIO_Set # turn on LED
.Loop_Delay:
li a0, 10 # 10ms debounce delay
call Delay_MS # call Delay_MS
j .Loop # loop forever
ret # return to caller
/**
* Test data and constants.
* The .rodata section is used for constants and static data.
*/
.section .rodata # read-only data section
/**
* Initialized global data.
* The .data section is used for initialized global or static variables.
*/
.section .data # data section
/**
* Uninitialized global data.
* The .bss section is used for uninitialized global or static variables.
*/
.section .bss # BSS section