Add bare-metal I2C driver for RP2350 (0x07_i2c_cbm)

This commit is contained in:
Kevin Thomas
2026-04-05 17:43:31 -04:00
parent 70a12a85e7
commit 2dc29e4d63
29 changed files with 2239 additions and 0 deletions
+35
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
};
+54
View File
@@ -0,0 +1,54 @@
/**
******************************************************************************
* @file main.c
* @author Kevin Thomas
* @brief I2C demonstration: scan all 7-bit addresses and report devices.
*
* Demonstrates I2C bus scanning using the bare-metal I2C driver.
* I2C1 is configured at 100 kHz on SDA=GPIO2 / SCL=GPIO3. A
* formatted hex table of all responding device addresses is
* printed over UART and repeated every 5 seconds.
*
* Wiring:
* GPIO0 -> UART TX (USB-to-UART adapter RX)
* GPIO1 -> UART RX (USB-to-UART adapter TX)
* GPIO2 -> I2C device SDA (4.7 kohm pull-up to 3.3 V)
* GPIO3 -> I2C device SCL (4.7 kohm pull-up to 3.3 V)
* 3.3V -> I2C device VCC
* GND -> I2C device GND
*
******************************************************************************
* @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_i2c.h"
#include "rp2350_uart.h"
#include "rp2350_xosc.h"
#include "rp2350_delay.h"
#define SCAN_DELAY_MS 5000
/**
* @brief Application entry point for the I2C bus scanner demo.
* @retval int does not return
*/
int main(void)
{
xosc_set_clk_ref();
i2c_release_reset();
i2c_init();
uart_puts("I2C driver initialized: I2C1 @ 100 kHz SDA=GPIO2 SCL=GPIO3\r\n");
while (1) {
i2c_scan();
delay_ms(SCAN_DELAY_MS);
}
}
@@ -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");
}
+39
View File
@@ -0,0 +1,39 @@
/**
******************************************************************************
* @file rp2350_delay.c
* @author Kevin Thomas
* @brief Delay driver implementation for RP2350.
*
* Busy-wait millisecond delay calibrated for a 14.5 MHz clock
* (3600 loop iterations per millisecond).
*
******************************************************************************
* @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"
);
}
+179
View File
@@ -0,0 +1,179 @@
/**
******************************************************************************
* @file rp2350_i2c.c
* @author Kevin Thomas
* @brief RP2350 I2C1 master-mode driver implementation.
*
* Bare-metal driver for the RP2350 Synopsys DW APB I2C controller.
* Configures I2C1 at 100 kHz on SDA=GPIO2 / SCL=GPIO3 with
* internal pull-ups. Provides address probing and bus scanning.
* All register accesses 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.
*
******************************************************************************
*/
#include "rp2350_i2c.h"
#include "rp2350_uart.h"
/**
* @brief Configure GPIO2 and GPIO3 pads for I2C (input enabled, pull-up).
*
* Clears pad isolation, enables input, enables pull-up, and sets
* drive strength to 4 mA on both SDA and SCL pins.
*
* @retval None
*/
static void _i2c_config_pads(void)
{
uint32_t pad_val = (1U << PADS_BANK0_IE_SHIFT) | (1U << PADS_BANK0_PUE_SHIFT) | (1U << 4);
PADS_BANK0->GPIO[I2C_SDA_PIN] = pad_val;
PADS_BANK0->GPIO[I2C_SCL_PIN] = pad_val;
}
/**
* @brief Set GPIO2 and GPIO3 IO mux function to I2C (FUNCSEL=3).
* @retval None
*/
static void _i2c_config_gpio(void)
{
IO_BANK0->GPIO[I2C_SDA_PIN].CTRL = IO_BANK0_CTRL_FUNCSEL_I2C;
IO_BANK0->GPIO[I2C_SCL_PIN].CTRL = IO_BANK0_CTRL_FUNCSEL_I2C;
}
/**
* @brief Configure I2C1 as fast-mode master with 7-bit addressing.
*
* Sets MASTER_MODE, SPEED=FAST, IC_SLAVE_DISABLE, IC_RESTART_EN,
* and TX_EMPTY_CTRL bits in the CON register.
*
* @retval None
*/
static void _i2c_config_con(void)
{
I2C1->CON = (1U << I2C_CON_MASTER_MODE_SHIFT)
| (I2C_CON_SPEED_FAST << I2C_CON_SPEED_SHIFT)
| (1U << I2C_CON_IC_RESTART_EN_SHIFT)
| (1U << I2C_CON_IC_SLAVE_DISABLE_SHIFT)
| (1U << I2C_CON_TX_EMPTY_CTRL_SHIFT);
}
/**
* @brief Program SCL timing for 100 kHz at 12 MHz clk_sys.
*
* Sets fast-mode SCL high/low counts, SDA hold time, and
* spike suppression length.
*
* @retval None
*/
static void _i2c_config_timing(void)
{
I2C1->FS_SCL_HCNT = I2C_FS_SCL_HCNT_VAL;
I2C1->FS_SCL_LCNT = I2C_FS_SCL_LCNT_VAL;
I2C1->FS_SPKLEN = I2C_FS_SPKLEN_VAL;
I2C1->SDA_HOLD = I2C_SDA_TX_HOLD_VAL;
}
/**
* @brief Print a two-digit uppercase hex number over UART.
* @param val byte value to print (0x00-0xFF)
* @retval None
*/
static void _print_hex8(uint8_t val)
{
const char hex[] = "0123456789ABCDEF";
char buf[3] = { hex[val >> 4], hex[val & 0x0F], '\0' };
uart_puts(buf);
}
/**
* @brief Print a single scan table entry for the given address.
*
* Prints the row header at 16-byte boundaries, then the address
* if a device responds, dashes if not, or blanks for reserved
* ranges.
*
* @param addr 7-bit I2C address (0x00-0x7F)
* @retval None
*/
static void _print_scan_entry(uint8_t addr)
{
if (addr % 16 == 0) { _print_hex8(addr); uart_puts(": "); }
if (addr < 0x08 || addr > 0x77) {
uart_puts(" ");
} else if (i2c_probe(addr)) {
_print_hex8(addr);
uart_puts(" ");
} else {
uart_puts("-- ");
}
if (addr % 16 == 15) { uart_puts("\r\n"); }
}
void i2c_release_reset(void)
{
RESETS->RESET |= (1U << RESETS_RESET_I2C1_SHIFT);
RESETS->RESET &= ~(1U << RESETS_RESET_I2C1_SHIFT);
while (!(RESETS->RESET_DONE & (1U << RESETS_RESET_I2C1_SHIFT))) {
}
}
void i2c_init(void)
{
_i2c_config_pads();
_i2c_config_gpio();
I2C1->ENABLE = 0U;
_i2c_config_con();
I2C1->TX_TL = 0U;
I2C1->RX_TL = 0U;
_i2c_config_timing();
I2C1->ENABLE = 1U;
}
bool i2c_probe(uint8_t addr)
{
I2C1->ENABLE = 0U;
I2C1->TAR = addr;
I2C1->ENABLE = 1U;
(void)I2C1->CLR_TX_ABRT;
I2C1->DATA_CMD = (1U << I2C_DATA_CMD_CMD_SHIFT) | (1U << I2C_DATA_CMD_STOP_SHIFT);
uint32_t timeout = I2C_TIMEOUT;
bool aborted = false;
while (timeout > 0U) {
if (I2C1->RAW_INTR_STAT & I2C_RAW_INTR_TX_ABRT) {
aborted = true;
break;
}
if (I2C1->RXFLR) {
break;
}
timeout--;
}
if (aborted) {
(void)I2C1->CLR_TX_ABRT;
}
(void)I2C1->CLR_STOP_DET;
if (!aborted && I2C1->RXFLR) {
(void)I2C1->DATA_CMD;
}
return !aborted && timeout > 0U;
}
void i2c_scan(void)
{
uart_puts("\r\nI2C bus scan:\r\n");
uart_puts(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\r\n");
for (uint8_t addr = 0; addr < 128; addr++) {
_print_scan_entry(addr);
}
}
+33
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) {
}
}
@@ -0,0 +1,45 @@
/**
******************************************************************************
* @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_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 uart_release_reset\n\t"
"bl uart_init\n\t"
"bl coprocessor_enable\n\t"
"b main\n\t"
);
}
+38
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"
);
}
+126
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 14.5 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 14.5 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;
}
+46
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;
}
+38
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
};