feat: add 0x0b_spi_cbm bare-metal SPI loopback driver

This commit is contained in:
Kevin Thomas
2026-04-05 18:49:36 -04:00
parent 9a760585e6
commit e82bd3d326
29 changed files with 2320 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
{
"configurations": [
{
"name": "ARM GCC",
"includePath": [
"${workspaceFolder}/Inc/**"
],
"defines": [
"__GNUC__",
"__ARM_ARCH_8M_MAIN__",
"__ARMCC_VERSION"
],
"compilerPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc",
"compileCommands": "${workspaceFolder}/compile_commands.json",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "gcc-arm",
"compilerArgs": [
"-mcpu=cortex-m33",
"-mthumb"
]
},
{
"name": "ARM GCC (Windows)",
"includePath": [
"${workspaceFolder}/Inc/**"
],
"defines": [
"__GNUC__",
"__ARM_ARCH_8M_MAIN__",
"__ARMCC_VERSION"
],
"compilerPath": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gcc.exe",
"compileCommands": "${workspaceFolder}/compile_commands.json",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "gcc-arm",
"compilerArgs": [
"-mcpu=cortex-m33",
"-mthumb"
]
}
],
"version": 4
}

View File

@@ -0,0 +1,9 @@
{
"recommendations": [
"marus25.cortex-debug",
"ms-vscode.cpptools",
"ms-vscode.cpptools-extension-pack",
"ms-vscode.vscode-serial-monitor",
"raspberry-pi.raspberry-pi-pico"
]
}

View File

@@ -0,0 +1,47 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug RP2350 (OpenOCD)",
"cwd": "${workspaceFolder}",
"executable": "${workspaceFolder}/build/spi.elf",
"request": "launch",
"type": "cortex-debug",
"servertype": "openocd",
"serverpath": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd",
"searchDspi": [
"${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts"
],
"gdbPath": "${userHome}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gdb",
"device": "RP2350",
"configFiles": [
"interface/cmsis-dap.cfg",
"target/rp2350.cfg"
],
"svdFile": "${userHome}/.pico-sdk/sdk/2.2.0/src/rp2350/hardware_regs/RP2350.svd",
"overrideLaunchCommands": [
"set arch armv8-m.main",
"set output-radix 16",
"monitor reset init",
"load",
"monitor reset halt"
],
"openOCDPreConfigLaunchCommands": [
"set USE_CORE { cm0 cm1 }"
],
"openOCDLaunchCommands": [
"adapter speed 5000"
],
"preLaunchTask": "Compile Project",
"showDevDebugOutput": "raw",
"windows": {
"serverpath": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe",
"searchDspi": [
"${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/scripts"
],
"gdbPath": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin/arm-none-eabi-gdb.exe",
"svdFile": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0/src/rp2350/hardware_regs/RP2350.svd"
}
}
]
}

View File

@@ -0,0 +1,40 @@
{
"cmake.showSystemKits": false,
"cmake.options.statusBarVisibility": "hidden",
"cmake.options.advanced": {
"build": {
"statusBarVisibility": "hidden"
},
"launch": {
"statusBarVisibility": "hidden"
},
"debug": {
"statusBarVisibility": "hidden"
}
},
"cmake.configureOnEdit": false,
"cmake.automaticReconfigure": false,
"cmake.configureOnOpen": false,
"cmake.generator": "Ninja",
"cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake",
"C_Cpp.debugShortcut": false,
"terminal.integrated.env.windows": {
"PICO_SDK_PATH": "${env:USERPROFILE}/.pico-sdk/sdk/2.2.0",
"PICO_TOOLCHAIN_PATH": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1",
"Path": "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}"
},
"terminal.integrated.env.osx": {
"PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0",
"PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1",
"PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}"
},
"terminal.integrated.env.linux": {
"PICO_SDK_PATH": "${env:HOME}/.pico-sdk/sdk/2.2.0",
"PICO_TOOLCHAIN_PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1",
"PATH": "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.2.0-a4/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}"
},
"raspberry-pi-pico.cmakeAutoConfigure": true,
"raspberry-pi-pico.useCmakeTools": false,
"raspberry-pi-pico.cmakePath": "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake",
"raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja"
}

128
drivers/0x0b_spi_cbm/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,128 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Compile Project",
"type": "shell",
"command": "make",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": "$gcc",
"windows": {
"command": ".\\build.bat"
}
},
{
"label": "Clean Project",
"type": "shell",
"command": "make clean",
"group": "build",
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": [],
"windows": {
"command": ".\\clean.bat"
}
},
{
"label": "Run Project",
"type": "shell",
"command": "${userHome}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool",
"args": [
"load",
"build/spi.uf2",
"-fx"
],
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": [],
"dependsOn": [
"Compile Project"
],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/picotool/2.2.0-a4/picotool/picotool.exe"
}
},
{
"label": "Flash",
"type": "shell",
"command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd",
"args": [
"-s",
"${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts",
"-f",
"interface/cmsis-dap.cfg",
"-f",
"target/rp2350.cfg",
"-c",
"adapter speed 5000; program build/spi.elf verify reset exit"
],
"problemMatcher": [],
"dependsOn": [
"Compile Project"
],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe"
}
},
{
"label": "Rescue Reset",
"type": "process",
"command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd",
"args": [
"-s",
"${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts",
"-f",
"interface/cmsis-dap.cfg",
"-f",
"target/${command:raspberry-pi-pico.getChip}-rescue.cfg",
"-c",
"adapter speed 5000; reset halt; exit"
],
"problemMatcher": [],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe"
}
},
{
"label": "RISC-V Reset (RP2350)",
"type": "process",
"command": "${userHome}/.pico-sdk/openocd/0.12.0+dev/openocd",
"args": [
"-s",
"${userHome}/.pico-sdk/openocd/0.12.0+dev/scripts",
"-c",
"set USE_CORE { rv0 rv1 cm0 cm1 }",
"-f",
"interface/cmsis-dap.cfg",
"-f",
"target/rp2350.cfg",
"-c",
"adapter speed 5000; init;",
"-c",
"write_memory 0x40120158 8 { 0x3 }; echo [format \"Info : ARCHSEL 0x%02x\" [read_memory 0x40120158 8 1]];",
"-c",
"reset halt; targets rp2350.rv0; echo [format \"Info : ARCHSEL_STATUS 0x%02x\" [read_memory 0x4012015C 8 1]]; exit"
],
"problemMatcher": [],
"windows": {
"command": "${env:USERPROFILE}/.pico-sdk/openocd/0.12.0+dev/openocd.exe"
}
}
]
}
],
"problemMatcher": []
}
]
}

View File

@@ -0,0 +1,254 @@
/**
******************************************************************************
* @file rp2350.h
* @author Kevin Thomas
* @brief RP2350 Device Peripheral Access Layer Header File.
*
* Memory-mapped register structures and peripheral base addresses
* for the RP2350 microcontroller (Cortex-M33 dual-core). All
* register offsets verified against the RP2350 datasheet
* (RP-008373-DS-2).
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#ifndef __RP2350_H
#define __RP2350_H
#include <stdint.h>
#include <stdbool.h>
/*!< Defines 'read / write' permissions */
#define __IO volatile
/*!< Stack addresses */
#define STACK_TOP 0x20082000UL
#define STACK_LIMIT 0x2007A000UL
/*!< Memory map */
#define XIP_BASE 0x10000000UL
#define SRAM_BASE 0x20000000UL
#define SIO_BASE 0xD0000000UL
#define PPB_BASE 0xE0000000UL
/*!< APB peripherals */
#define CLOCKS_BASE 0x40010000UL
#define RESETS_BASE 0x40020000UL
#define IO_BANK0_BASE 0x40028000UL
#define PADS_BANK0_BASE 0x40038000UL
#define XOSC_BASE 0x40048000UL
#define UART0_BASE 0x40070000UL
#define SPI0_BASE 0x40080000UL
/**
* @brief XOSC (External Crystal Oscillator)
*/
typedef struct
{
__IO uint32_t CTRL; // Control register Address offset: 0x00
__IO uint32_t STATUS; // Status register Address offset: 0x04
__IO uint32_t DORMANT; // Dormant mode Address offset: 0x08
__IO uint32_t STARTUP; // Startup delay Address offset: 0x0C
__IO uint32_t COUNT; // Frequency count Address offset: 0x10
} XOSC_TypeDef;
/**
* @brief CLOCKS
*/
typedef struct
{
__IO uint32_t RESERVED0[12]; // GPOUT0..GPOUT3 registers Address offset: 0x00-0x2C
__IO uint32_t CLK_REF_CTRL; // Reference clock control Address offset: 0x30
__IO uint32_t RESERVED1[5]; // CLK_REF_DIV..CLK_SYS_SELECTED Address offset: 0x34-0x44
__IO uint32_t CLK_PERI_CTRL; // Peripheral clock control Address offset: 0x48
} CLOCKS_TypeDef;
/**
* @brief RESETS
*/
typedef struct
{
__IO uint32_t RESET; // Reset control Address offset: 0x00
__IO uint32_t WDSEL; // Watchdog select Address offset: 0x04
__IO uint32_t RESET_DONE; // Reset done status Address offset: 0x08
} RESETS_TypeDef;
/**
* @brief IO_BANK0 GPIO Control (one per GPIO)
*/
typedef struct
{
__IO uint32_t STATUS; // GPIO status Address offset: 0x00
__IO uint32_t CTRL; // GPIO control Address offset: 0x04
} IO_BANK0_GPIO_TypeDef;
/**
* @brief IO_BANK0
*/
typedef struct
{
IO_BANK0_GPIO_TypeDef GPIO[30]; // GPIO 0-29 status/ctrl pairs Address offset: 0x000-0x0E8
} IO_BANK0_TypeDef;
/**
* @brief PADS_BANK0
*/
typedef struct
{
__IO uint32_t VOLTAGE_SELECT; // Voltage select Address offset: 0x00
__IO uint32_t GPIO[30]; // GPIO 0-29 pad control Address offset: 0x04-0x78
} PADS_BANK0_TypeDef;
/**
* @brief SPI (PrimeCell SSP)
*/
typedef struct
{
__IO uint32_t SSPCR0; // Control register 0 Address offset: 0x000
__IO uint32_t SSPCR1; // Control register 1 Address offset: 0x004
__IO uint32_t SSPDR; // Data register Address offset: 0x008
__IO uint32_t SSPSR; // Status register Address offset: 0x00C
__IO uint32_t SSPCPSR; // Clock prescale register Address offset: 0x010
} SPI_TypeDef;
/**
* @brief Peripheral Definitions
*/
#define XOSC ((XOSC_TypeDef *) XOSC_BASE)
#define CLOCKS ((CLOCKS_TypeDef *) CLOCKS_BASE)
#define RESETS ((RESETS_TypeDef *) RESETS_BASE)
#define IO_BANK0 ((IO_BANK0_TypeDef *) IO_BANK0_BASE)
#define PADS_BANK0 ((PADS_BANK0_TypeDef *) PADS_BANK0_BASE)
#define SPI0 ((SPI_TypeDef *) SPI0_BASE)
#define SIO ((volatile uint32_t *) SIO_BASE)
#define CPACR ((volatile uint32_t *) (PPB_BASE + 0x0ED88UL))
/**
* @brief XOSC bit definitions
*/
#define XOSC_STATUS_STABLE_SHIFT 31U
/**
* @brief CPACR bit definitions
*/
#define CPACR_CP0_SHIFT 0U
#define CPACR_CP1_SHIFT 1U
/**
* @brief CLK_REF bit definitions
*/
#define CLK_REF_CTRL_SRC_XOSC 2U
/**
* @brief CLOCKS bit definitions
*/
#define CLK_PERI_CTRL_ENABLE_SHIFT 11U
#define CLK_PERI_CTRL_AUXSRC_SHIFT 5U
#define CLK_PERI_CTRL_AUXSRC_MASK (0x07U << CLK_PERI_CTRL_AUXSRC_SHIFT)
#define CLK_PERI_CTRL_AUXSRC_XOSC 4U
/**
* @brief RESETS bit definitions
*/
#define RESETS_RESET_IO_BANK0_SHIFT 6U
#define RESETS_RESET_PADS_BANK0_SHIFT 9U
#define RESETS_RESET_SPI0_SHIFT 18U
#define RESETS_RESET_UART0_SHIFT 26U
/**
* @brief IO_BANK0 bit definitions
*/
#define IO_BANK0_CTRL_FUNCSEL_MASK 0x1FU
#define IO_BANK0_CTRL_FUNCSEL_SPI 0x01U
#define IO_BANK0_CTRL_FUNCSEL_UART 0x02U
#define IO_BANK0_CTRL_FUNCSEL_SIO 0x05U
#define IO_BANK0_CTRL_FUNCSEL_NULL 0x1FU
/**
* @brief PADS_BANK0 bit definitions
*/
#define PADS_BANK0_OD_SHIFT 7U
#define PADS_BANK0_IE_SHIFT 6U
#define PADS_BANK0_ISO_SHIFT 8U
#define PADS_BANK0_PUE_SHIFT 3U
#define PADS_BANK0_PDE_SHIFT 2U
/**
* @brief SIO GPIO register offsets (word indices from SIO_BASE)
*/
#define SIO_GPIO_OUT_SET_OFFSET (0x018U / 4U)
#define SIO_GPIO_OUT_CLR_OFFSET (0x020U / 4U)
#define SIO_GPIO_OE_SET_OFFSET (0x038U / 4U)
/**
* @brief UART register offsets (word indices from UART0_BASE)
*/
#define UART_DR_OFFSET (0x000U / 4U)
#define UART_FR_OFFSET (0x018U / 4U)
#define UART_IBRD_OFFSET (0x024U / 4U)
#define UART_FBRD_OFFSET (0x028U / 4U)
#define UART_LCR_H_OFFSET (0x02CU / 4U)
#define UART_CR_OFFSET (0x030U / 4U)
/**
* @brief UART flag register bit definitions
*/
#define UART_FR_TXFF_MASK 32U
#define UART_FR_RXFE_MASK 16U
/**
* @brief UART line control and enable values
*/
#define UART_LCR_H_8N1_FIFO 0x70U
#define UART_CR_ENABLE ((3U << 8) | 1U)
/**
* @brief SPI0 SSPCR0 bit definitions
*/
#define SPI_SSPCR0_DSS_SHIFT 0U
#define SPI_SSPCR0_DSS_8BIT 0x07U
#define SPI_SSPCR0_FRF_SHIFT 4U
#define SPI_SSPCR0_SCR_SHIFT 8U
/**
* @brief SPI0 SSPCR1 bit definitions
*/
#define SPI_SSPCR1_LBM_SHIFT 0U
#define SPI_SSPCR1_SSE_SHIFT 1U
/**
* @brief SPI0 SSPSR bit definitions
*/
#define SPI_SSPSR_TFE_MASK (1U << 0)
#define SPI_SSPSR_TNF_MASK (1U << 1)
#define SPI_SSPSR_RNE_MASK (1U << 2)
#define SPI_SSPSR_BSY_MASK (1U << 4)
/**
* @brief SPI0 pin definitions (SPI0 alternate function on GPIO16-19)
*/
#define SPI_MISO_PIN 16U
#define SPI_CS_PIN 17U
#define SPI_SCK_PIN 18U
#define SPI_MOSI_PIN 19U
/**
* @brief SPI0 clock configuration for 1 MHz baud at 12 MHz clk_peri.
*
* Baud = clk_peri / (CPSDVSR * (1 + SCR))
* = 12 MHz / (12 * (1 + 0)) = 1 MHz
*/
#define SPI_CPSDVSR_1MHZ 12U
#define SPI_SCR_1MHZ 0U
#endif /* __RP2350_H */

View File

@@ -0,0 +1,33 @@
/**
******************************************************************************
* @file rp2350_coprocessor.h
* @author Kevin Thomas
* @brief Coprocessor access control driver header for RP2350.
*
* Enables coprocessor access via the CPACR register.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#ifndef __RP2350_COPROCESSOR_H
#define __RP2350_COPROCESSOR_H
#include "rp2350.h"
/**
* @brief Enable coprocessor access via CPACR with DSB/ISB barriers.
* @retval None
*/
void coprocessor_enable(void);
#endif /* __RP2350_COPROCESSOR_H */

View File

@@ -0,0 +1,42 @@
/**
******************************************************************************
* @file rp2350_delay.h
* @author Kevin Thomas
* @brief Delay driver header for RP2350.
*
* Millisecond and microsecond busy-wait delays calibrated for
* a 12 MHz clock.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#ifndef __RP2350_DELAY_H
#define __RP2350_DELAY_H
#include "rp2350.h"
/**
* @brief Delay for the specified number of milliseconds.
* @param ms number of milliseconds to delay
* @retval None
*/
void delay_ms(uint32_t ms);
/**
* @brief Delay for the specified number of microseconds.
* @param us number of microseconds to delay
* @retval None
*/
void delay_us(uint32_t us);
#endif /* __RP2350_DELAY_H */

View File

@@ -0,0 +1,33 @@
/**
******************************************************************************
* @file rp2350_reset.h
* @author Kevin Thomas
* @brief Reset controller driver header for RP2350.
*
* Provides subsystem reset release for IO_BANK0.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#ifndef __RP2350_RESET_H
#define __RP2350_RESET_H
#include "rp2350.h"
/**
* @brief Release IO_BANK0 from reset and wait until ready.
* @retval None
*/
void reset_init_subsystem(void);
#endif /* __RP2350_RESET_H */

View File

@@ -0,0 +1,33 @@
/**
******************************************************************************
* @file rp2350_reset_handler.h
* @author Kevin Thomas
* @brief Reset handler header for RP2350.
*
* Entry point after reset. Performs stack initialization, XOSC
* setup, subsystem reset release, UART initialization,
* coprocessor enable, and branches to main().
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#ifndef __RP2350_RESET_HANDLER_H
#define __RP2350_RESET_HANDLER_H
/**
* @brief Reset handler entry point (naked, noreturn).
* @retval None
*/
void Reset_Handler(void) __attribute__((noreturn));
#endif /* __RP2350_RESET_HANDLER_H */

View File

@@ -0,0 +1,71 @@
/**
******************************************************************************
* @file rp2350_spi.h
* @author Kevin Thomas
* @brief SPI0 master driver header for RP2350.
*
* Provides full-duplex SPI0 master mode on GPIO16-19 with
* software-controlled chip select. Configures Motorola SPI
* frame format, 8-bit data, CPOL=0, CPHA=0, 1 MHz clock.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#ifndef __RP2350_SPI_H
#define __RP2350_SPI_H
#include "rp2350.h"
/**
* @brief Release SPI0 from reset in the RESETS controller.
* @retval None
*/
void spi_release_reset(void);
/**
* @brief Initialise SPI0 in master mode at 1 MHz, 8-bit, CPOL=0/CPHA=0.
*
* Configures SSPCR0, SSPCPSR, and SSPCR1 then enables the port.
* Also configures GPIO16-19 pads and IO funcsel for SPI.
*
* @retval None
*/
void spi_init(void);
/**
* @brief Assert the chip-select line (drive CS low).
* @retval None
*/
void spi_cs_select(void);
/**
* @brief Deassert the chip-select line (drive CS high).
* @retval None
*/
void spi_cs_deselect(void);
/**
* @brief Perform a full-duplex SPI transfer.
*
* Sends @p len bytes from @p tx while simultaneously receiving
* @p len bytes into @p rx. The caller is responsible for
* asserting and deasserting CS around this call.
*
* @param tx pointer to transmit buffer (must be @p len bytes)
* @param rx pointer to receive buffer (must be @p len bytes)
* @param len number of bytes to transfer
* @retval None
*/
void spi_transfer(const uint8_t *tx, uint8_t *rx, uint32_t len);
#endif /* __RP2350_SPI_H */

View File

@@ -0,0 +1,34 @@
/**
******************************************************************************
* @file rp2350_stack.h
* @author Kevin Thomas
* @brief Stack pointer initialization header for RP2350.
*
* Sets MSP, PSP, MSPLIM, and PSPLIM from the STACK_TOP and
* STACK_LIMIT values defined in rp2350.h.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#ifndef __RP2350_STACK_H
#define __RP2350_STACK_H
#include "rp2350.h"
/**
* @brief Initialize MSP, PSP, MSPLIM, and PSPLIM stack pointers.
* @retval None
*/
void stack_init(void);
#endif /* __RP2350_STACK_H */

View File

@@ -0,0 +1,73 @@
/**
******************************************************************************
* @file rp2350_uart.h
* @author Kevin Thomas
* @brief UART0 driver header for RP2350.
*
* Bare-metal UART0 driver supporting TX/RX on GPIO 0/1 at
* 115200 baud (12 MHz XOSC clock).
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#ifndef __RP2350_UART_H
#define __RP2350_UART_H
#include "rp2350.h"
/**
* @brief Release UART0 from reset and wait until ready.
* @retval None
*/
void uart_release_reset(void);
/**
* @brief Initialize UART0 pins, baud rate, line control, and enable.
* @retval None
*/
void uart_init(void);
/**
* @brief Check whether a received byte is waiting in the UART FIFO.
* @retval bool true if at least one byte is available
*/
bool uart_is_readable(void);
/**
* @brief Read one character from UART0 (blocking).
* @retval char the received character
*/
char uart_getchar(void);
/**
* @brief Transmit one character over UART0 (blocking).
* @param c character to transmit
* @retval None
*/
void uart_putchar(char c);
/**
* @brief Transmit a null-terminated string over UART0.
* @param str pointer to the string to send
* @retval None
*/
void uart_puts(const char *str);
/**
* @brief Convert a lowercase ASCII character to uppercase.
* @param c input character
* @retval char uppercase equivalent or original character
*/
char uart_to_upper(char c);
#endif /* __RP2350_UART_H */

View File

@@ -0,0 +1,46 @@
/**
******************************************************************************
* @file rp2350_xosc.h
* @author Kevin Thomas
* @brief XOSC driver header for RP2350.
*
* External crystal oscillator initialization and peripheral
* clock enable using the XOSC registers.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#ifndef __RP2350_XOSC_H
#define __RP2350_XOSC_H
#include "rp2350.h"
/**
* @brief Initialize the external crystal oscillator and wait until stable.
* @retval None
*/
void xosc_init(void);
/**
* @brief Enable the XOSC peripheral clock via CLK_PERI_CTRL.
* @retval None
*/
void xosc_enable_peri_clk(void);
/**
* @brief Switch CLK_REF source to XOSC for a stable 12 MHz clk_sys.
* @retval None
*/
void xosc_set_clk_ref(void);
#endif /* __RP2350_XOSC_H */

View File

@@ -0,0 +1,79 @@
# ------------------------------------------------------------------------------
# @file Makefile
# @author Kevin Thomas
# @brief Build script for RP2350 bare-metal C SPI loopback driver.
#
# Compiles, links, and generates UF2 firmware for the RP2350.
# ------------------------------------------------------------------------------
# Toolchain
CC = arm-none-eabi-gcc
OBJCOPY = arm-none-eabi-objcopy
SIZE = arm-none-eabi-size
# Target
TARGET = spi
# Directories
SRC_DIR = Src
INC_DIR = Inc
BUILD_DIR = build
# CPU flags
CPU_FLAGS = -mcpu=cortex-m33 -mthumb
# Compiler flags
CFLAGS = $(CPU_FLAGS) -Og -g3 -Wall -Wextra \
-ffunction-sections -fdata-sections \
-I$(INC_DIR)
# Linker flags
LDFLAGS = $(CPU_FLAGS) -T linker.ld -nostdlib -Wl,--gc-sections
# Source files
SRCS = $(SRC_DIR)/vector_table.c \
$(SRC_DIR)/rp2350_reset_handler.c \
$(SRC_DIR)/rp2350_stack.c \
$(SRC_DIR)/rp2350_xosc.c \
$(SRC_DIR)/rp2350_reset.c \
$(SRC_DIR)/rp2350_coprocessor.c \
$(SRC_DIR)/rp2350_uart.c \
$(SRC_DIR)/rp2350_spi.c \
$(SRC_DIR)/rp2350_delay.c \
$(SRC_DIR)/main.c \
$(SRC_DIR)/image_def.c
# Object files
OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS))
# Rules
.PHONY: all clean flash
all: $(BUILD_DIR)/$(TARGET).uf2
@echo "==================================="
@echo "SUCCESS! Created $(TARGET).bin and $(TARGET).uf2"
@echo "==================================="
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJS)
$(CC) $(LDFLAGS) $(OBJS) -o $@
$(SIZE) $@
$(BUILD_DIR)/$(TARGET).bin: $(BUILD_DIR)/$(TARGET).elf
$(OBJCOPY) -O binary $< $@
$(BUILD_DIR)/$(TARGET).uf2: $(BUILD_DIR)/$(TARGET).bin
python3 uf2conv.py -b 0x10000000 -f 0xe48bff59 -o $@ $<
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
clean:
rm -rf $(BUILD_DIR)
flash: $(BUILD_DIR)/$(TARGET).elf
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \
-c "adapter speed 5000" \
-c "program $< verify reset exit"

View File

@@ -0,0 +1,35 @@
/**
******************************************************************************
* @file image_def.c
* @author Kevin Thomas
* @brief RP2350 IMAGE_DEF block for boot ROM image recognition.
*
* Must appear within the first 4 KB of flash for the boot ROM
* to accept the image.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include <stdint.h>
/**
* @brief IMAGE_DEF block structure placed in flash
*/
__attribute__((section(".embedded_block"), used))
const uint8_t picobin_block[] = {
0xD3, 0xDE, 0xFF, 0xFF,
0x42, 0x01, 0x21, 0x10,
0xFF, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x79, 0x35, 0x12, 0xAB
};

View File

@@ -0,0 +1,122 @@
/**
******************************************************************************
* @file main.c
* @author Kevin Thomas
* @brief SPI loopback demonstration.
*
* Performs a full-duplex SPI0 transfer in master mode with
* MOSI wired to MISO for loopback verification. Prints TX
* and RX data over UART every second.
*
* Wiring (loopback test):
* GPIO0 -> UART TX (USB-to-UART adapter RX)
* GPIO1 -> UART RX (USB-to-UART adapter TX)
* GPIO19 (MOSI) -> GPIO16 (MISO)
* GPIO18 (SCK) -> logic analyzer (optional)
* GPIO17 (CS) -> active-low slave (optional)
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "rp2350_spi.h"
#include "rp2350_uart.h"
#include "rp2350_delay.h"
#include "rp2350_xosc.h"
/**
* @brief Hex digit lookup table for byte-to-hex conversion.
*/
static const char _hex_lut[16] = "0123456789ABCDEF";
/**
* @brief Print a byte as a two-digit hex string over UART.
* @param value byte to print
* @retval None
*/
static void _print_hex(uint8_t value)
{
char buf[3];
buf[0] = _hex_lut[value >> 4];
buf[1] = _hex_lut[value & 0x0FU];
buf[2] = '\0';
uart_puts(buf);
}
/**
* @brief Print a buffer as hex bytes separated by spaces over UART.
* @param label text label to print before the data
* @param buf byte buffer to print
* @param len number of bytes in buffer
* @retval None
*/
static void _print_buffer(const char *label, const uint8_t *buf, uint32_t len)
{
uart_puts(label);
for (uint32_t i = 0; i < len; i++) {
_print_hex(buf[i]);
if (i + 1 < len)
uart_putchar(' ');
}
uart_puts("\r\n");
}
/**
* @brief Perform one SPI loopback transfer and print TX/RX over UART.
* @param tx_buf transmit buffer
* @param rx_buf receive buffer (cleared after printing)
* @param len number of bytes to transfer
* @retval None
*/
static void _loopback_transfer(const uint8_t *tx_buf, uint8_t *rx_buf, uint32_t len)
{
spi_cs_select();
spi_transfer(tx_buf, rx_buf, len);
spi_cs_deselect();
_print_buffer("TX: ", tx_buf, len);
_print_buffer("RX: ", rx_buf, len);
uart_puts("\r\n");
}
/**
* @brief Clear a byte buffer to zero.
* @param buf pointer to buffer
* @param len number of bytes to clear
* @retval None
*/
static void _clear_buffer(uint8_t *buf, uint32_t len)
{
for (uint32_t i = 0; i < len; i++)
buf[i] = 0;
}
/**
* @brief Application entry point for the SPI loopback demo.
* @retval int does not return
*/
int main(void)
{
/** @brief Transmit test pattern for SPI loopback. */
static const uint8_t tx[] = {0xDE, 0xAD, 0xBE, 0xEF};
/** @brief Buffer length for SPI loopback transfer. */
static const uint32_t len = sizeof(tx);
uint8_t rx[sizeof(tx)] = {0};
xosc_set_clk_ref();
spi_release_reset();
spi_init();
uart_puts("SPI loopback initialized (MOSI->MISO on GPIO19->GPIO16)\r\n");
while (1) {
_loopback_transfer(tx, rx, len);
_clear_buffer(rx, len);
delay_ms(1000);
}
}

View File

@@ -0,0 +1,34 @@
/**
******************************************************************************
* @file rp2350_coprocessor.c
* @author Kevin Thomas
* @brief Coprocessor access control driver implementation for RP2350.
*
* Grants access to coprocessors 0 and 1 by setting the
* corresponding bits in CPACR with DSB/ISB barriers.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "rp2350_coprocessor.h"
void coprocessor_enable(void)
{
uint32_t value;
value = *CPACR;
value |= (1U << CPACR_CP1_SHIFT);
value |= (1U << CPACR_CP0_SHIFT);
*CPACR = value;
__asm__ volatile ("dsb");
__asm__ volatile ("isb");
}

View File

@@ -0,0 +1,55 @@
/**
******************************************************************************
* @file rp2350_delay.c
* @author Kevin Thomas
* @brief Delay driver implementation for RP2350.
*
* Busy-wait millisecond and microsecond delays calibrated for
* a 12 MHz clock.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "rp2350_delay.h"
void delay_ms(uint32_t ms)
{
if (ms == 0)
return;
__asm__ volatile (
"mov r4, #3600\n\t"
"mul r5, %0, r4\n\t"
"1:\n\t"
"subs r5, #1\n\t"
"bne 1b\n\t"
:
: "r" (ms)
: "r4", "r5", "cc"
);
}
void delay_us(uint32_t us)
{
if (us == 0)
return;
__asm__ volatile (
"mov r4, #4\n\t"
"mul r5, %0, r4\n\t"
"1:\n\t"
"subs r5, #1\n\t"
"bne 1b\n\t"
:
: "r" (us)
: "r4", "r5", "cc"
);
}

View File

@@ -0,0 +1,33 @@
/**
******************************************************************************
* @file rp2350_reset.c
* @author Kevin Thomas
* @brief Reset controller driver implementation for RP2350.
*
* Releases IO_BANK0 from reset and waits until the subsystem
* is ready.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "rp2350_reset.h"
void reset_init_subsystem(void)
{
uint32_t value;
value = RESETS->RESET;
value &= ~(1U << RESETS_RESET_IO_BANK0_SHIFT);
RESETS->RESET = value;
while ((RESETS->RESET_DONE & (1U << RESETS_RESET_IO_BANK0_SHIFT)) == 0) {
}
}

View File

@@ -0,0 +1,47 @@
/**
******************************************************************************
* @file rp2350_reset_handler.c
* @author Kevin Thomas
* @brief Reset handler implementation for RP2350.
*
* Entry point after power-on or system reset. Initializes the
* stack, XOSC, subsystem resets, UART, coprocessor, then
* branches to main().
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "rp2350_reset_handler.h"
#include "rp2350_stack.h"
#include "rp2350_xosc.h"
#include "rp2350_reset.h"
#include "rp2350_spi.h"
#include "rp2350_uart.h"
#include "rp2350_coprocessor.h"
extern int main(void);
void __attribute__((naked, noreturn)) Reset_Handler(void)
{
__asm__ volatile (
"bl stack_init\n\t"
"bl xosc_init\n\t"
"bl xosc_enable_peri_clk\n\t"
"bl reset_init_subsystem\n\t"
"bl spi_release_reset\n\t"
"bl uart_release_reset\n\t"
"bl uart_init\n\t"
"bl coprocessor_enable\n\t"
"b main\n\t"
);
}

View File

@@ -0,0 +1,205 @@
/**
******************************************************************************
* @file rp2350_spi.c
* @author Kevin Thomas
* @brief SPI0 master driver implementation for RP2350.
*
* Full-duplex SPI0 master on GPIO16 (MISO), GPIO17 (CS),
* GPIO18 (SCK), GPIO19 (MOSI). Motorola SPI frame format,
* 8-bit data, CPOL=0, CPHA=0, 1 MHz clock from 12 MHz
* clk_peri.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "rp2350_spi.h"
/**
* @brief Bit mask for the chip-select pin.
*/
#define CS_PIN_MASK (1U << SPI_CS_PIN)
/**
* @brief Clear the SPI0 reset bit in the reset controller.
* @retval None
*/
static void _spi_clear_reset(void)
{
uint32_t value;
value = RESETS->RESET;
value &= ~(1U << RESETS_RESET_SPI0_SHIFT);
RESETS->RESET = value;
}
/**
* @brief Wait until SPI0 is out of reset.
* @retval None
*/
static void _spi_wait_reset_done(void)
{
while ((RESETS->RESET_DONE & (1U << RESETS_RESET_SPI0_SHIFT)) == 0) {
}
}
/**
* @brief Configure a GPIO pad for SPI: input enabled, no pull, no isolation.
* @param pin GPIO pin number to configure
* @retval None
*/
static void _configure_spi_pad(uint8_t pin)
{
uint32_t value;
value = PADS_BANK0->GPIO[pin];
value &= ~(1U << PADS_BANK0_OD_SHIFT);
value |= (1U << PADS_BANK0_IE_SHIFT);
value &= ~(1U << PADS_BANK0_ISO_SHIFT);
PADS_BANK0->GPIO[pin] = value;
}
/**
* @brief Configure a GPIO pad for CS: output enabled, pull-up, no isolation.
* @param pin GPIO pin number to configure
* @retval None
*/
static void _configure_cs_pad(uint8_t pin)
{
uint32_t value;
value = PADS_BANK0->GPIO[pin];
value &= ~(1U << PADS_BANK0_OD_SHIFT);
value |= (1U << PADS_BANK0_IE_SHIFT);
value |= (1U << PADS_BANK0_PUE_SHIFT);
value &= ~(1U << PADS_BANK0_ISO_SHIFT);
PADS_BANK0->GPIO[pin] = value;
}
/**
* @brief Set a GPIO funcsel to SPI alternate function.
* @param pin GPIO pin number
* @retval None
*/
static void _set_funcsel_spi(uint8_t pin)
{
IO_BANK0->GPIO[pin].CTRL = IO_BANK0_CTRL_FUNCSEL_SPI;
}
/**
* @brief Set a GPIO funcsel to SIO for software control (CS pin).
* @param pin GPIO pin number
* @retval None
*/
static void _set_funcsel_sio(uint8_t pin)
{
IO_BANK0->GPIO[pin].CTRL = IO_BANK0_CTRL_FUNCSEL_SIO;
}
/**
* @brief Configure CS pin as output, initially deasserted (high).
* @retval None
*/
static void _cs_init(void)
{
SIO[SIO_GPIO_OUT_SET_OFFSET] = CS_PIN_MASK;
SIO[SIO_GPIO_OE_SET_OFFSET] = CS_PIN_MASK;
}
/**
* @brief Configure SSPCR0 for 8-bit Motorola SPI, SCR=0.
* @retval None
*/
static void _configure_cr0(void)
{
uint32_t value = 0;
value |= (SPI_SSPCR0_DSS_8BIT << SPI_SSPCR0_DSS_SHIFT);
value |= (SPI_SCR_1MHZ << SPI_SSPCR0_SCR_SHIFT);
SPI0->SSPCR0 = value;
}
/**
* @brief Configure SSPCPSR clock prescaler for 1 MHz.
* @retval None
*/
static void _configure_prescaler(void)
{
SPI0->SSPCPSR = SPI_CPSDVSR_1MHZ;
}
/**
* @brief Enable the SPI0 peripheral (SSE=1 in SSPCR1).
* @retval None
*/
static void _enable_spi(void)
{
SPI0->SSPCR1 = (1U << SPI_SSPCR1_SSE_SHIFT);
}
/**
* @brief Wait until the SPI transmit FIFO has space.
* @retval None
*/
static void _wait_tx_not_full(void)
{
while ((SPI0->SSPSR & SPI_SSPSR_TNF_MASK) == 0) {
}
}
/**
* @brief Wait until the SPI receive FIFO has data.
* @retval None
*/
static void _wait_rx_not_empty(void)
{
while ((SPI0->SSPSR & SPI_SSPSR_RNE_MASK) == 0) {
}
}
void spi_release_reset(void)
{
_spi_clear_reset();
_spi_wait_reset_done();
}
void spi_init(void)
{
_configure_spi_pad(SPI_MOSI_PIN);
_configure_spi_pad(SPI_MISO_PIN);
_configure_spi_pad(SPI_SCK_PIN);
_configure_cs_pad(SPI_CS_PIN);
_set_funcsel_spi(SPI_MOSI_PIN);
_set_funcsel_spi(SPI_MISO_PIN);
_set_funcsel_spi(SPI_SCK_PIN);
_set_funcsel_sio(SPI_CS_PIN);
_cs_init();
_configure_cr0();
_configure_prescaler();
_enable_spi();
}
void spi_cs_select(void)
{
SIO[SIO_GPIO_OUT_CLR_OFFSET] = CS_PIN_MASK;
}
void spi_cs_deselect(void)
{
SIO[SIO_GPIO_OUT_SET_OFFSET] = CS_PIN_MASK;
}
void spi_transfer(const uint8_t *tx, uint8_t *rx, uint32_t len)
{
for (uint32_t i = 0; i < len; i++) {
_wait_tx_not_full();
SPI0->SSPDR = tx[i];
_wait_rx_not_empty();
rx[i] = (uint8_t)SPI0->SSPDR;
}
}

View File

@@ -0,0 +1,38 @@
/**
******************************************************************************
* @file rp2350_stack.c
* @author Kevin Thomas
* @brief Stack pointer initialization for RP2350.
*
* Sets MSP, PSP, MSPLIM, and PSPLIM using inline assembly.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "rp2350_stack.h"
void stack_init(void)
{
__asm__ volatile (
"ldr r0, =%0\n\t"
"msr PSP, r0\n\t"
"ldr r0, =%1\n\t"
"msr MSPLIM, r0\n\t"
"msr PSPLIM, r0\n\t"
"ldr r0, =%0\n\t"
"msr MSP, r0\n\t"
:
: "i" (STACK_TOP), "i" (STACK_LIMIT)
: "r0"
);
}

View File

@@ -0,0 +1,126 @@
/**
******************************************************************************
* @file rp2350_uart.c
* @author Kevin Thomas
* @brief UART0 driver implementation for RP2350.
*
* Configures UART0 on GPIO 0 (TX) and GPIO 1 (RX) at 115200
* baud using the 12 MHz XOSC clock.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "rp2350_uart.h"
#define UART_BASE ((volatile uint32_t *) UART0_BASE)
/**
* @brief Clear the UART0 reset bit in the reset controller.
* @retval None
*/
static void _uart_clear_reset_bit(void)
{
uint32_t value;
value = RESETS->RESET;
value &= ~(1U << RESETS_RESET_UART0_SHIFT);
RESETS->RESET = value;
}
/**
* @brief Wait until the UART0 block is out of reset.
* @retval None
*/
static void _uart_wait_reset_done(void)
{
while ((RESETS->RESET_DONE & (1U << RESETS_RESET_UART0_SHIFT)) == 0) {
}
}
/**
* @brief Configure GPIO pins 0 (TX) and 1 (RX) for UART function.
* @retval None
*/
static void _uart_configure_pins(void)
{
IO_BANK0->GPIO[0].CTRL = IO_BANK0_CTRL_FUNCSEL_UART;
IO_BANK0->GPIO[1].CTRL = IO_BANK0_CTRL_FUNCSEL_UART;
PADS_BANK0->GPIO[0] = 0x04;
PADS_BANK0->GPIO[1] = 0x40;
}
/**
* @brief Set UART0 baud rate divisors for 115200 at 12 MHz.
* @retval None
*/
static void _uart_set_baud(void)
{
UART_BASE[UART_CR_OFFSET] = 0;
UART_BASE[UART_IBRD_OFFSET] = 6;
UART_BASE[UART_FBRD_OFFSET] = 33;
}
/**
* @brief Configure line control and enable UART0.
* @retval None
*/
static void _uart_enable(void)
{
UART_BASE[UART_LCR_H_OFFSET] = UART_LCR_H_8N1_FIFO;
UART_BASE[UART_CR_OFFSET] = UART_CR_ENABLE;
}
void uart_release_reset(void)
{
_uart_clear_reset_bit();
_uart_wait_reset_done();
}
void uart_init(void)
{
_uart_configure_pins();
_uart_set_baud();
_uart_enable();
}
bool uart_is_readable(void)
{
return (UART_BASE[UART_FR_OFFSET] & UART_FR_RXFE_MASK) == 0;
}
char uart_getchar(void)
{
while (UART_BASE[UART_FR_OFFSET] & UART_FR_RXFE_MASK) {
}
return (char)(UART_BASE[UART_DR_OFFSET] & 0xFF);
}
void uart_putchar(char c)
{
while (UART_BASE[UART_FR_OFFSET] & UART_FR_TXFF_MASK) {
}
UART_BASE[UART_DR_OFFSET] = (uint32_t)c;
}
void uart_puts(const char *str)
{
while (*str) {
uart_putchar(*str++);
}
}
char uart_to_upper(char c)
{
if (c >= 'a' && c <= 'z')
return (char)(c - 32);
return c;
}

View File

@@ -0,0 +1,46 @@
/**
******************************************************************************
* @file rp2350_xosc.c
* @author Kevin Thomas
* @brief XOSC driver implementation for RP2350.
*
* Configures the external crystal oscillator and enables the
* peripheral clock sourced from XOSC.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include "rp2350_xosc.h"
void xosc_init(void)
{
XOSC->STARTUP = 0x00C4U;
XOSC->CTRL = 0x00FABAA0U;
while ((XOSC->STATUS & (1U << XOSC_STATUS_STABLE_SHIFT)) == 0) {
}
}
void xosc_enable_peri_clk(void)
{
uint32_t value;
value = CLOCKS->CLK_PERI_CTRL;
value &= ~CLK_PERI_CTRL_AUXSRC_MASK;
value |= (1U << CLK_PERI_CTRL_ENABLE_SHIFT);
value |= (CLK_PERI_CTRL_AUXSRC_XOSC << CLK_PERI_CTRL_AUXSRC_SHIFT);
CLOCKS->CLK_PERI_CTRL = value;
}
void xosc_set_clk_ref(void)
{
CLOCKS->CLK_REF_CTRL = CLK_REF_CTRL_SRC_XOSC;
}

View File

@@ -0,0 +1,38 @@
/**
******************************************************************************
* @file vector_table.c
* @author Kevin Thomas
* @brief Vector table with initial stack pointer and reset handler.
*
* Placed in the .vectors section at the start of flash.
* The Thumb bit (bit 0 = 1) is automatically set by the
* linker for function pointers in Thumb mode.
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
#include <stdint.h>
extern uint32_t _stack_top;
extern void Reset_Handler(void);
typedef void (*vector_func_t)(void);
/**
* @brief Vector table placed in .vectors section
*/
__attribute__((section(".vectors"), used))
const void *_vectors[2] = {
&_stack_top,
Reset_Handler
};

View File

@@ -0,0 +1,114 @@
/**
******************************************************************************
* @file linker.ld
* @author Kevin Thomas
* @brief Minimal linker script for bare-metal RP2350 development.
*
* Defines FLASH (XIP 32 MB) and RAM (520 kB SRAM) regions.
* The vector table is placed at the start of flash (0x10000000).
*
******************************************************************************
* @attention
*
* Copyright (c) 2026 Kevin Thomas.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/**
* Entry point.
*/
ENTRY(Reset_Handler)
/**
* Define memory regions.
*/
__XIP_BASE = 0x10000000;
__XIP_SIZE = 32M;
__SRAM_BASE = 0x20000000;
__SRAM_SIZE = 520K;
__STACK_SIZE = 32K;
/**
* Memory layout.
*/
MEMORY
{
RAM (rwx) : ORIGIN = __SRAM_BASE, LENGTH = __SRAM_SIZE
FLASH (rx) : ORIGIN = __XIP_BASE, LENGTH = __XIP_SIZE
}
/**
* Program headers.
*/
PHDRS
{
text PT_LOAD FLAGS(5);
}
/**
* Section placement.
*/
SECTIONS
{
. = ORIGIN(FLASH);
/**
* Vector table MUST be first at 0x10000000.
*/
.vectors :
{
KEEP(*(.vectors))
} > FLASH :text
/**
* Verify vector table placement.
*/
ASSERT((ADDR(.vectors) == ORIGIN(FLASH)),
"Vector table must be at start of flash (0x10000000)")
/**
* Text and read-only data.
*/
.text :
{
. = ALIGN(4);
*(.text*)
*(.rodata*)
KEEP(*(.init))
KEEP(*(.fini))
KEEP(*(.ARM.attributes))
} > FLASH :text
/**
* IMAGE_DEF block at end of code.
*/
.embedded_block :
{
KEEP(*(.embedded_block))
} > FLASH :text
/**
* Non-secure stack symbols.
*/
__StackTop = ORIGIN(RAM) + LENGTH(RAM);
__StackLimit = __StackTop - __STACK_SIZE;
__stack = __StackTop;
_stack_top = __StackTop;
/**
* Stack section (no load).
*/
.stack (NOLOAD) : { . = ALIGN(8); } > RAM
/**
* Provide vector table symbol to startup code.
*/
PROVIDE(__Vectors = ADDR(.vectors));
}

View File

@@ -0,0 +1,438 @@
#!/usr/bin/env python3
import sys
import struct
import subprocess
import re
import os
import os.path
import argparse
import json
from time import sleep
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
UF2_MAGIC_END = 0x0AB16F30 # Ditto
INFO_FILE = "/INFO_UF2.TXT"
appstartaddr = 0x2000
familyid = 0x0
def is_uf2(buf):
w = struct.unpack("<II", buf[0:8])
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
def is_hex(buf):
try:
w = buf[0:30].decode("utf-8")
except UnicodeDecodeError:
return False
if w[0] == ":" and re.match(rb"^[:0-9a-fA-F\r\n]+$", buf):
return True
return False
def convert_from_uf2(buf):
global appstartaddr
global familyid
numblocks = len(buf) // 512
curraddr = None
currfamilyid = None
families_found = {}
prev_flag = None
all_flags_same = True
outp = []
for blockno in range(numblocks):
ptr = blockno * 512
block = buf[ptr : ptr + 512]
hd = struct.unpack(b"<IIIIIIII", block[0:32])
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
print("Skipping block at " + ptr + "; bad magic")
continue
if hd[2] & 1:
# NO-flash flag set; skip block
continue
datalen = hd[4]
if datalen > 476:
assert False, "Invalid UF2 data size at " + ptr
newaddr = hd[3]
if (hd[2] & 0x2000) and (currfamilyid == None):
currfamilyid = hd[7]
if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid):
currfamilyid = hd[7]
curraddr = newaddr
if familyid == 0x0 or familyid == hd[7]:
appstartaddr = newaddr
padding = newaddr - curraddr
if padding < 0:
assert False, "Block out of order at " + ptr
if padding > 10 * 1024 * 1024:
assert False, "More than 10M of padding needed at " + ptr
if padding % 4 != 0:
assert False, "Non-word padding size at " + ptr
while padding > 0:
padding -= 4
outp.append(b"\x00\x00\x00\x00")
if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]):
outp.append(block[32 : 32 + datalen])
curraddr = newaddr + datalen
if hd[2] & 0x2000:
if hd[7] in families_found.keys():
if families_found[hd[7]] > newaddr:
families_found[hd[7]] = newaddr
else:
families_found[hd[7]] = newaddr
if prev_flag == None:
prev_flag = hd[2]
if prev_flag != hd[2]:
all_flags_same = False
if blockno == (numblocks - 1):
print("--- UF2 File Header Info ---")
families = load_families()
for family_hex in families_found.keys():
family_short_name = ""
for name, value in families.items():
if value == family_hex:
family_short_name = name
print(
"Family ID is {:s}, hex value is 0x{:08x}".format(
family_short_name, family_hex
)
)
print("Target Address is 0x{:08x}".format(families_found[family_hex]))
if all_flags_same:
print("All block flag values consistent, 0x{:04x}".format(hd[2]))
else:
print("Flags were not all the same")
print("----------------------------")
if len(families_found) > 1 and familyid == 0x0:
outp = []
appstartaddr = 0x0
return b"".join(outp)
def convert_to_carray(file_content):
outp = "const unsigned long bindata_len = %d;\n" % len(file_content)
outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {"
for i in range(len(file_content)):
if i % 16 == 0:
outp += "\n"
outp += "0x%02x, " % file_content[i]
outp += "\n};\n"
return bytes(outp, "utf-8")
def convert_to_uf2(file_content):
global familyid
datapadding = b""
while len(datapadding) < 512 - 256 - 32 - 4:
datapadding += b"\x00\x00\x00\x00"
numblocks = (len(file_content) + 255) // 256
outp = []
for blockno in range(numblocks):
ptr = 256 * blockno
chunk = file_content[ptr : ptr + 256]
flags = 0x0
if familyid:
flags |= 0x2000
hd = struct.pack(
b"<IIIIIIII",
UF2_MAGIC_START0,
UF2_MAGIC_START1,
flags,
ptr + appstartaddr,
256,
blockno,
numblocks,
familyid,
)
while len(chunk) < 256:
chunk += b"\x00"
block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
assert len(block) == 512
outp.append(block)
return b"".join(outp)
class Block:
def __init__(self, addr, default_data=0xFF):
self.addr = addr
self.bytes = bytearray([default_data] * 256)
def encode(self, blockno, numblocks):
global familyid
flags = 0x0
if familyid:
flags |= 0x2000
hd = struct.pack(
"<IIIIIIII",
UF2_MAGIC_START0,
UF2_MAGIC_START1,
flags,
self.addr,
256,
blockno,
numblocks,
familyid,
)
hd += self.bytes[0:256]
while len(hd) < 512 - 4:
hd += b"\x00"
hd += struct.pack("<I", UF2_MAGIC_END)
return hd
def convert_from_hex_to_uf2(buf):
global appstartaddr
appstartaddr = None
upper = 0
currblock = None
blocks = []
for line in buf.split("\n"):
if line[0] != ":":
continue
i = 1
rec = []
while i < len(line) - 1:
rec.append(int(line[i : i + 2], 16))
i += 2
tp = rec[3]
if tp == 4:
upper = ((rec[4] << 8) | rec[5]) << 16
elif tp == 2:
upper = ((rec[4] << 8) | rec[5]) << 4
elif tp == 1:
break
elif tp == 0:
addr = upper + ((rec[1] << 8) | rec[2])
if appstartaddr == None:
appstartaddr = addr
i = 4
while i < len(rec) - 1:
if not currblock or currblock.addr & ~0xFF != addr & ~0xFF:
currblock = Block(addr & ~0xFF)
blocks.append(currblock)
currblock.bytes[addr & 0xFF] = rec[i]
addr += 1
i += 1
numblocks = len(blocks)
resfile = b""
for i in range(0, numblocks):
resfile += blocks[i].encode(i, numblocks)
return resfile
def to_str(b):
return b.decode("utf-8")
def get_drives():
drives = []
if sys.platform == "win32":
r = subprocess.check_output(
[
"powershell",
"-Command",
"(Get-WmiObject Win32_LogicalDisk -Filter \"VolumeName='RPI-RP2'\").DeviceID",
]
)
drive = to_str(r).strip()
if drive:
drives.append(drive)
else:
searchpaths = ["/mnt", "/media"]
if sys.platform == "darwin":
searchpaths = ["/Volumes"]
elif sys.platform == "linux":
searchpaths += [
"/media/" + os.environ["USER"],
"/run/media/" + os.environ["USER"],
]
if "SUDO_USER" in os.environ.keys():
searchpaths += ["/media/" + os.environ["SUDO_USER"]]
searchpaths += ["/run/media/" + os.environ["SUDO_USER"]]
for rootpath in searchpaths:
if os.path.isdir(rootpath):
for d in os.listdir(rootpath):
if os.path.isdir(os.path.join(rootpath, d)):
drives.append(os.path.join(rootpath, d))
def has_info(d):
try:
return os.path.isfile(d + INFO_FILE)
except:
return False
return list(filter(has_info, drives))
def board_id(path):
with open(path + INFO_FILE, mode="r") as file:
file_content = file.read()
return re.search(r"Board-ID: ([^\r\n]*)", file_content).group(1)
def list_drives():
for d in get_drives():
print(d, board_id(d))
def write_file(name, buf):
with open(name, "wb") as f:
f.write(buf)
print("Wrote %d bytes to %s" % (len(buf), name))
def load_families():
# The expectation is that the `uf2families.json` file is in the same
# directory as this script. Make a path that works using `__file__`
# which contains the full path to this script.
filename = "uf2families.json"
pathname = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename)
with open(pathname) as f:
raw_families = json.load(f)
families = {}
for family in raw_families:
families[family["short_name"]] = int(family["id"], 0)
return families
def main():
global appstartaddr, familyid
def error(msg):
print(msg, file=sys.stderr)
sys.exit(1)
parser = argparse.ArgumentParser(description="Convert to UF2 or flash directly.")
parser.add_argument(
"input",
metavar="INPUT",
type=str,
nargs="?",
help="input file (HEX, BIN or UF2)",
)
parser.add_argument(
"-b",
"--base",
dest="base",
type=str,
default="0x2000",
help="set base address of application for BIN format (default: 0x2000)",
)
parser.add_argument(
"-f",
"--family",
dest="family",
type=str,
default="0x0",
help="specify familyID - number or name (default: 0x0)",
)
parser.add_argument(
"-o",
"--output",
metavar="FILE",
dest="output",
type=str,
help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible',
)
parser.add_argument(
"-d", "--device", dest="device_path", help="select a device path to flash"
)
parser.add_argument(
"-l", "--list", action="store_true", help="list connected devices"
)
parser.add_argument(
"-c", "--convert", action="store_true", help="do not flash, just convert"
)
parser.add_argument(
"-D", "--deploy", action="store_true", help="just flash, do not convert"
)
parser.add_argument(
"-w", "--wait", action="store_true", help="wait for device to flash"
)
parser.add_argument(
"-C",
"--carray",
action="store_true",
help="convert binary file to a C array, not UF2",
)
parser.add_argument(
"-i",
"--info",
action="store_true",
help="display header information from UF2, do not convert",
)
args = parser.parse_args()
appstartaddr = int(args.base, 0)
families = load_families()
if args.family.upper() in families:
familyid = families[args.family.upper()]
else:
try:
familyid = int(args.family, 0)
except ValueError:
error(
"Family ID needs to be a number or one of: "
+ ", ".join(families.keys())
)
if args.list:
list_drives()
else:
if not args.input:
error("Need input file")
with open(args.input, mode="rb") as f:
inpbuf = f.read()
from_uf2 = is_uf2(inpbuf)
ext = "uf2"
if args.deploy:
outbuf = inpbuf
elif from_uf2 and not args.info:
outbuf = convert_from_uf2(inpbuf)
ext = "bin"
elif from_uf2 and args.info:
outbuf = ""
convert_from_uf2(inpbuf)
elif is_hex(inpbuf):
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
elif args.carray:
outbuf = convert_to_carray(inpbuf)
ext = "h"
else:
outbuf = convert_to_uf2(inpbuf)
if not args.deploy and not args.info:
print(
"Converted to %s, output size: %d, start address: 0x%x"
% (ext, len(outbuf), appstartaddr)
)
if args.convert or ext != "uf2":
if args.output == None:
args.output = "flash." + ext
if args.output:
write_file(args.output, outbuf)
if ext == "uf2" and not args.convert and not args.info:
drives = get_drives()
if len(drives) == 0:
if args.wait:
print("Waiting for drive to deploy...")
while len(drives) == 0:
sleep(0.1)
drives = get_drives()
elif not args.output:
error("No drive to deploy.")
for d in drives:
print("Flashing %s (%s)" % (d, board_id(d)))
write_file(d + "/NEW.UF2", outbuf)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,22 @@
[
{
"short_name": "RP2040",
"id": "0xe48bff56",
"description": "Raspberry Pi RP2040"
},
{
"short_name": "RP2350-ARM-S",
"id": "0xe48bff59",
"description": "Raspberry Pi RP2350, ARM, Secure"
},
{
"short_name": "RP2350-ARM-NS",
"id": "0xe48bff5a",
"description": "Raspberry Pi RP2350, ARM, Non-Secure"
},
{
"short_name": "RP2350-RISCV",
"id": "0xe48bff5b",
"description": "Raspberry Pi RP2350, RISC-V"
}
]