Skip to main content Skip to local navigation

Exploring RISC-V Options: the RP2350 (Part 1)

In engineering education there is an ever-present problem with technology-anchored pedagogy: when do you switch from current technology to the next one? Right now, the cool kid on the block in the RISC-V architecture. Will it be the next ARM or will it be the next PowerPC or MIPS? It's hard to tell. However, a collegial choice has been made in our department to adopt it in our undergraduate curriculum, so RISC-V it is.

We're now using RISC-V as the target of choice in our Computer Organization course, EECS 2021. Unfortunately, low stock issues have hit our ESP32-C3 development board and so alternatives need to be found. I'm going to explore the RISC-V RP2350 from the folks at Raspberry Pi as chip supply, in December 2025, appears to be good and multiple manufacturers have inexpensive development boards. (e.g. the OG Nano 2, and also Adafruit, Seeed, Waveshare, DFRobot, Sparkfun, etc.), from international distributors like DIgikey and Mouser to Canadian ones like PiShop and RobotShop)

An RP2350 board from Seeed Studio.
An RP2350 board from Seeed Studio.
RISC-V selection in the Arduino IDE.
RISC-V selection in the Arduino IDE.

RP2350 boards are supported, through extensions, in the Arduino 2.0 IDE. This provides a boot loader-based toolchain with C++ as the main development language while also supporting Assembler, both in-line and as add-on files.

The RP2350 is a unique device, with both ARM Cortex and RISC-V processors on board. The Arduino IDE supports switching between them from a drop-down menu in the IDE.

  • Seeed Studio Xiao-RP2350: works in Arduino 2.0 IDE

It's possible to do in-line assembler and to have the processor send serial data back to the Arduino's Serial Monitor.

It's also possible to write a C++ program in the Arduino IDE and call an assembler file. Here,

// riscv_with_asm.ino
extern "C" void test(void); // permits calling the assembler function, test in test.S.

void setup() {
  // set up LED and make it blink...
  test();  // call the assembler function (blinking LED)
}

void loop() {
 /* this never runs...*/
  delay(1000);
}

and here I've blatantly copied the main.s file from the bare-metal-rp2350 GitHub project by Igor Michalak and renamed the file and function to test.S, as follows:

# test.S
####################
### Simple Blink ###
####################
# by Igor Michalak @ https://github.com/igormichalak/bare-metal-rp2350/blob/master/main.s
.section .text

.equ ATOMIC_XOR,	0x1000
.equ ATOMIC_SET,	0x2000
.equ ATOMIC_CLEAR,	0x3000

.equ RESETS_BASE,			0x40020000
.equ RESETS_RESET,			RESETS_BASE+0x0
.equ RESETS_RESET_DONE,		RESETS_BASE+0x8

.equ IO_BANK0_BASE,			0x40028000
.equ IO_BANK0_GPIO25_CTRL,	IO_BANK0_BASE+0x0cc

.equ PADS_BANK0_BASE,		0x40038000
.equ PADS_BANK0_GPIO25,		PADS_BANK0_BASE+0x68

.equ SIO_BASE,				0xd0000000
.equ SIO_GPIO_OE_SET,		SIO_BASE+0x038
.equ SIO_GPIO_OUT_SET,		SIO_BASE+0x018
.equ SIO_GPIO_OUT_CLR,		SIO_BASE+0x020

.equ TIMER0_BASE,		0x400b0000
.equ TIMER0_TIMEHR,		TIMER0_BASE+0x08
.equ TIMER0_TIMELR,		TIMER0_BASE+0x0c
.equ TIMER0_TIMERAWH,	TIMER0_BASE+0x24
.equ TIMER0_TIMERAWL,	TIMER0_BASE+0x28

.global test
test:
	li		t0, (1<<6)|(1<<9)
	li		t1, RESETS_RESET+ATOMIC_CLEAR
	sw		t0, (t1)
	li		t1, RESETS_RESET_DONE
1:
	lw		t2, (t1)
	and		t2, t2, t0
	bne		t2, t0, 1b

	li		t0, 0x1f
	li		t1, IO_BANK0_GPIO25_CTRL+ATOMIC_CLEAR
	sw		t0, (t1)

	li		t0, 0x05
	li		t1, IO_BANK0_GPIO25_CTRL+ATOMIC_SET
	sw		t0, (t1)

	li		t0, (1<<25)
	li		t1, SIO_GPIO_OE_SET
	sw		t0, (t1)

	li		t0, (1<<7)|(1<<8)
	li		t1, PADS_BANK0_GPIO25+ATOMIC_CLEAR
	sw		t0, (t1)

	li		t0, (1<<25)
	li		t1, SIO_GPIO_OUT_SET
	li		t2, SIO_GPIO_OUT_CLR
loop:
	sw		t0, (t1)
	li		a0, 100
	call	wait_ms
	sw		t0, (t2)
	li		a0, 100
	call	wait_ms
	j		loop

# Don't use with durations longer than 1 hour.
wait_ms:
	mv		a2, a0
	li		a0, TIMER0_TIMELR
	li		a1, TIMER0_TIMEHR
	lw		a0, (a0)
	lw		a1, (a1)
	li		a3, 1000
	mul		a2, a2, a3
	add		a0, a0, a2
	sltu	a3, a0, a2
	add		a1, a1, a3
	li		a2, TIMER0_TIMERAWL
	li		a3, TIMER0_TIMERAWH
1:
	lw		a5, (a3)
	blt		a5, a1, 1b
2:
	lw		a5, (a3)
	bne		a5, a1, 3f
	lw		a4, (a2)
	blt		a4, a0, 2b
3:
	ret

This is just a proof of concept. I'd like to go further and incorporate this as inline Assembler in the .ino file, for instance. More later...

The LED blinking on the RP2350 board after calling the test.S file from the Arduino C++ file.

a pen

James Andrew Smith is a Professional Engineer and Associate Professor in the Electrical Engineering and Computer Science Department of York University’s Lassonde School, with degrees in Electrical and Mechanical Engineering from the University of Alberta and McGill University.  Previously a program director in biomedical engineering, his research background spans robotics, locomotion, human birth, music and engineering education. While on sabbatical in 2018-19 with his wife and kids he lived in Strasbourg, France and he taught at the INSA Strasbourg and Hochschule Karlsruhe and wrote about his personal and professional perspectives.  James is a proponent of using social media to advocate for justice, equity, diversity and inclusion as well as evidence-based applications of research in the public sphere. You can find him on Twitter.  You can find him on BlueSky. Originally from Québec City, he now lives in Toronto, Canada.