Can I program a RP2040 in rust without any libraries or crates? - Page 1
Forum section: electronics / general

author: cedric!
author_rank: Contributor
timestamp: August 04, 2025, 08:47:31 pm
content: When I program a microcontroller in C, I don't use any libraries. At the lowest level my C program only doing polling and read/write operations in memory. If I want to blink an LED on my RP2040 board, when a certain time has passed, I would do something like this:1) Write specific values to specific memory locations in the uC. This sets up the timer, and sets the uC pin with the LED as output.2) Poll a memory location (the timer) until it is higher than a value in memory3) Write a new value in memory, and flip a bit that drives the outputpin.4) go back to 2)Is it also possible to do this in rust without using any libraries? Is no_std enough?Do I always need to use the unsafe keyword to write to a specific memory address? Do I need the unsafe keyword to read a specific memory address?

author: mark03
author_rank: Frequent Contributor
timestamp: August 04, 2025, 10:46:36 pm
content: It's quite possible that you are "using" the C standard library without realizing it. At least in ARM Cortex-M land, it would be very surprising if you're not linking against libc.That being said, just because you're linking against a library doesn't mean you're using additional code space. It's the linker's job to incorporate only the library functions that are being called in your code. If you don't call them, they won't end up in the generated image. (*)I don't know much about Rust and will leave the question for others to answer.(*) Yes, there are things like _sbrk(), but you can make those stubs if not used.

author: brucehoult
author_rank: Super Contributor
timestamp: August 05, 2025, 12:55:28 am
content: Quote from: cedric! on August 04, 2025, 08:47:31 pmDefinitely possible. I know lunatics who do.https://albertskog.se/ch32v-in-rust/I don't get the point though. Programming microcontrollers is essentially programming the metal "unsafely" in any language. We only use C to avoid having to choose exact registers and instructions ourselves, with typecasts all over the place. Adding another layer of self-flagellation to do that in Rust on a machine with a few hundred bytes or couple of K of RAM seems pointless.Ok, the RP2040 is quite a bit bigger microcontroller than a CH32V003. With up to 16 MB of flash (vs 16 KB on the '003) perhaps you don't even care that a normal Rust "hello world" is 3.5 MB. It's almost like programming a PC.

author: westfw
author_rank: Super Contributor
timestamp: August 05, 2025, 09:00:56 am
content: QuoteMy understanding is that Rust (like many other languages) includes a substantial "run time environment" - are you counting that as "libraries"?(For instance, in BASIC, PASCAL, or FORTRAN, input/output is part of the language, rather than something that is provided by a separate library. Therefore you can do things like "print" or "write" "without using a library" (at the expense of the compiler writer having had to implement those primitives for whatever environment(s) the compiler is targeted toward.IMO, one of the reasons that C has been so successful is that a C compiler author needs to write a lot less code for a new environment than with most languages. It might not DO anything useful, since it needs a bunch of libraries or "painfully written" user code to do any I/O, but it technically implements the C language.)

author: John Coloccia
author_rank: Super Contributor
timestamp: August 05, 2025, 09:36:55 am
content: Quote from: brucehoult on August 05, 2025, 12:55:28 amThat example uses a HAL. I don't think I've found an example yet where someone's gone at it with just Rust...no HAL, no PAC.Anyhow, if I really wanted to use Rust on micros (and it's gaining popularity), I would write my low level stuff in C and just call it from Rust. Doing it all in Rust just feels more painful than it has to be.

author: brucehoult
author_rank: Super Contributor
timestamp: August 05, 2025, 12:02:04 pm
content: Quote from: John Coloccia on August 05, 2025, 09:36:55 amBut that's just Rust, right?https://github.com/ch32-rs/ch32-metapachttps://github.com/ch32-rs/ch32-halQuoteWell, yes, I can't see the point myself.But that IS an example of pure Rust being used on an MCU with 2k RAM and 16k flash, which is pretty small.

author: John Coloccia
author_rank: Super Contributor
timestamp: August 05, 2025, 12:16:55 pm
content: Quote from: brucehoult on August 05, 2025, 12:02:04 pmI think he was looking to do it without "libraries", which I interpret as "no HAL". Like what we used to do in the good ole days...basically just write your own drivers. Which is pretty simple and straightforward in C, and kind of ugly and painful in Rust.

author: brucehoult
author_rank: Super Contributor
timestamp: August 05, 2025, 12:39:10 pm
content: Quote from: John Coloccia on August 05, 2025, 12:16:55 pmYou can always just hand-copy/paste the bits you want, right?Code:pub const GPIOA: gpio::Gpio = unsafe { gpio::Gpio::from_ptr(0x4001_0800usize as _) };pub const GPIOC: gpio::Gpio = unsafe { gpio::Gpio::from_ptr(0x4001_1000usize as _) };pub const GPIOD: gpio::Gpio = unsafe { gpio::Gpio::from_ptr(0x4001_1400usize as _) };pub const ADC1: adc::Adc = unsafe { adc::Adc::from_ptr(0x4001_2400usize as _) };pub const TIM1: timer::Adtm = unsafe { timer::Adtm::from_ptr(0x4001_2c00usize as _) };pub const SPI1: spi::Spi = unsafe { spi::Spi::from_ptr(0x4001_3000usize as _) };pub const USART1: usart::Usart = unsafe { usart::Usart::from_ptr(0x4001_3800usize as _) };And that uses bits from here:https://github.com/ch32-rs/ch32-metapac/blob/main/src/registers/gpio_v0.rsSure, I agree making a struct for GPIO and casting an integer to a pointer to it is less verbose in C. Which is why I use C.BUT Rust IS SAFE! And BLAZINGLY FAST!

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 05, 2025, 01:45:41 pm
content: I don't understand what "no crate" means here. Yes, the example Bruce shown uses a "HAL" crate. But what's the problem? Crates, like modules or whatever, are just made to structure code in units. That's usually a good thing. Do you prefer spaghetti maybe?Now can you write your own low-level code in Rust instead of reusing something someone else wrote? Uh, yeah. Like with any other language. If someone else did, then you can too.Can you access "registers" directly (or any memory area by address) in Rust without wrapping it in an unsafe construct? Nope. That's one of the whole points.Does Rust look like C - but with a worse and convoluted syntax - in unsafe mode? Yes.

author: John Coloccia
author_rank: Super Contributor
timestamp: August 06, 2025, 03:31:19 am
content: I think they're looking for help/encouragement to learn how to do it in Rust, from scratch, or a strategy anyhow. Not a million reasons why they shouldn't.

author: cedric!
author_rank: Contributor
timestamp: August 06, 2025, 07:32:08 pm
content: Dear all,Thank you for all the answers and examples. Most of my embedded C work has been done with a pic 18 microcontroller with the XC8 compiler. In that work I only include <xc8> to get symbolic names for the register locations and bit names. Next i usually include <stdint> to get access to utint8_t, int16_t and so on. i usually write everything else myself on top of that. I'm aware there's a thin C runtime that runs before main() is started. That runtime clears memory, and preloads variables that are declared with an initializer. But after main() is called, I see no extra code being run, I can map each line of C to a group of assembly instructions.I'm now convinced I can write rust in the same way. It looks like only the lowest level read and write functions to the special function registers need to be written in an unsafe block, and the rest can be safe rust. I also think it will yield an executable with comparable size and memory consumption, where I can also map the lines of rust code to groups of assembly instructions.As far as i know there's no way to compile rust to pic18 assembler, so I guess I'm going to do my C vs Rust experiments on a RP2040. (or even on a PC, but that's slightly less fun, as blinking and an LED connected to a memory mapped parallel port isn't on the books anymore.)

author: westfw
author_rank: Super Contributor
timestamp: August 07, 2025, 02:32:12 am
content: Quotelet us know how your experiments turn out.Although even C rp2040 programs come with an unusual amount of overhead, setting up the run-from-serial-flash and such. Their "blink" example is over 20k.

author: cedric!
author_rank: Contributor
timestamp: September 24, 2025, 07:09:44 pm
content: I've found this post that blinks the LED in exactly the same way I intend to do it in rust:https://codalogic.com/blog/2023/01/07/Pico-Assembly-ProgrammingNow I "only" make a rust program that has the same effect.

author: cedric!
author_rank: Contributor
timestamp: October 09, 2025, 11:01:44 am
content: Here somebody makes a rust program very small, on a PC:https://darkcoding.net/software/a-very-small-rust-binary-indeed/That, together with the assembly LED blink program should give me everything I need to make a very small blink program on the RP2040

author: westfw
author_rank: Super Contributor
timestamp: October 09, 2025, 09:34:12 pm
content: QuoteThat wasn't a rust program. That was an assembly language program assembled with the Rust compiler. :-(You can write Fortran code in Ada, too. May result in your rocket blowing up.(And I can write C code in a number of non-C languages, which is also not good.)

author: brucehoult
author_rank: Super Contributor
timestamp: October 09, 2025, 10:26:02 pm
content: Quote from: westfw on October 09, 2025, 09:34:12 pmBut but ... blazingly fast! Memory-safe!

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 09, 2025, 10:54:04 pm
content: Rust is safe and effective.

author: cv007
author_rank: Frequent Contributor
timestamp: October 09, 2025, 10:59:17 pm
content: Your very first problem in Rust for a microcontroller is dealing with mcu registers. Without a volatile keyword in the language you will either depend on a library or create all the ugliness of it in your own code.In contrast, for C/C++ the volatile is a language keyword so you can deal with mcu registers, and other things, with no outside help-https://godbolt.org/z/so9WbcT4xQuoteStick to C which you are already familiar with, and just get better at it. You can be as minimal as you want and can easily move from mcu to mcu. No downsides. If you want to try something else, try C++ where you still have the C language 'contained inside' so can gradually learn it without discarding all your C knowledge.

author: DiTBho
author_rank: Super Contributor
timestamp: October 10, 2025, 06:23:41 am
content: Quote from: cv007 on October 09, 2025, 10:59:17 pmIsn't there an alternative solution? Rust-native, for that?

author: cedric!
author_rank: Contributor
timestamp: October 10, 2025, 07:36:20 am
content: Quote from: cv007 on October 09, 2025, 10:59:17 pmThank you for pointing that out. I am not yet aware howto deal with memory mapped peripherals that alter memory by themselves.It's my understanding that the rust borrow checker can only guarantee memory safety as long as the memory is not changing by itself.Am I correct to say any code dealing with self-changing peripheral memory must use the unsafe keyword?

author: cedric!
author_rank: Contributor
timestamp: October 10, 2025, 07:45:39 am
content: Quote from: cv007 on October 09, 2025, 10:59:17 pmThis is generally good advice. I don't agree fully with the "No downsides". it's true C is minimal, supported by every mcu vendor, and using the features of C++ is optional. But C has downsides. It doesn't have strong guardrails, so if I make a mistake (array access off by one, a pointer that doesn't actually point to what I thought it was pointing to, using undefined behaviors) the compiler will just let it happen, often without warnings.I'm curious if rust actually forces me to write programs more explicitly, so the compiler (and future me) can see what I was trying to do without relying on comments.

author: westfw
author_rank: Super Contributor
timestamp: October 10, 2025, 08:26:33 am
content: QuoteIt's not just that. C's ability to cast constants to (volatile or otherwise) pointers is fundamental to being able to define memory-mapped peripheral registers, and is also fundamentally unsafe...(Hmm. Could also be done at link-time, I guess. Is that any better? Do linkers have the concept of a "signed" library where you could put such definitions and be sure of their (relative) safety and access permissions?)

author: brucehoult
author_rank: Super Contributor
timestamp: October 10, 2025, 09:37:32 am
content: Quote from: westfw on October 10, 2025, 08:26:33 amDriving at 100 km/h on a 2-lane road with opposing traffic, and overtaking using their lane from time to time, is fundamentally unsafe. And yet many millions of people do it every day their entire adult lives, with fewer than 1% ever having a problem. I myself was doing that (including overtaking, in the dark) an hour ago.Kitchen carving knives, and chainsaws, and ladders, and lawnmowers, and many many other things are fundamentally unsafe.You turn on your brain and take a bit of care when you use them.

author: tggzzz
author_rank: Super Contributor
timestamp: October 10, 2025, 10:33:36 am
content: Quote from: cedric! on October 10, 2025, 07:45:39 amThe other nasty is pointer aliasing, particularly in large programs maintained/enhanced over a long period, or multiprocessor/core systems.QuoteThat kind of kick-the-tyres experiment is frequentlyveryvaluable, for the individual and/or for a company. It can cut through an awful lot of hype, forcing people to concentrate on the unpleasant practical details.Company benefit: avoiding wasting time/money/reputation on the latest fad.Individual benefit: working out what to spend their time/energy/career on, and/or gaining career advancement by making good technical choices.

author: tggzzz
author_rank: Super Contributor
timestamp: October 10, 2025, 10:38:08 am
content: Quote from: brucehoult on October 10, 2025, 09:37:32 amTrue. But analogies such as that are silly and detract from the useful discussions.Wherever and whenever possible it is desirable to use dual carriageways with crash barrier dividing the two directions, and to restrict travelling on single carriageway roads to the minimum necessary.

author: brucehoult
author_rank: Super Contributor
timestamp: October 10, 2025, 11:27:26 am
content: Quote from: tggzzz on October 10, 2025, 10:38:08 amWhy so?If anything, programming in C or asm is much safer than those things, because you can contemplate your logic in extreme slow motion, and test it thoroughly before releasing it into production to be used billions of times.QuoteMy nearest such road (other than a few hundred metres near major intersections) is 150km away near a major city (1.8m pop). I go there once or twice a year. Much of my driving is on gravel roads.

author: tggzzz
author_rank: Super Contributor
timestamp: October 10, 2025, 02:41:33 pm
content: Quote from: brucehoult on October 10, 2025, 11:27:26 amEr.. I illustrated that in my next paragraph. Some people would have snipped that in order to (try to) make their point, but you avoided that sin!QuoteSo your environment constrains you to do the "equivalent" of programming in assembler; fair enough.Most people don't live under such constraints, and you should realise the advantages that gives them.

author: brucehoult
author_rank: Super Contributor
timestamp: October 11, 2025, 03:15:54 am
content: Quote from: tggzzz on October 10, 2025, 02:41:33 pmI enjoy programming in assembler. C is often close enough, and is a lot more portable.QuoteI've lived in the San Francisco Bay Area. I've lived in Moscow.Those have advantages, but on the whole I like it here more, especially with Starlink.

author: tggzzz
author_rank: Super Contributor
timestamp: October 11, 2025, 07:52:38 am
content: Quote from: brucehoult on October 11, 2025, 03:15:54 amI've done it since the 70s, and still read assembler when sanity checking C compiler output.I've never had to do that with other languages.

author: brucehoult
author_rank: Super Contributor
timestamp: October 11, 2025, 08:56:05 am
content: Quote from: tggzzz on October 11, 2025, 07:52:38 amThat speaks to limited experience. I was tracking down code generation bugs in the NBS Pascal compiler for PDP-11 in 1981, several years before I first saw a C compiler.There is no inherent reason why code generation for C would be more error-prone than for any other language, and in particular for similarly powerful languages such as Pascal or PL/I or Algol or BCPL (all of which I've used).

author: tggzzz
author_rank: Super Contributor
timestamp: October 11, 2025, 09:12:13 am
content: Quote from: brucehoult on October 11, 2025, 08:56:05 am"Error prone" needs explaining. I'm not thinking solely of compiler bugs.As for those other languages, the Algol-60 compiler worked as expected - unsurprising considering Tony Hoare wrote it! Never needed to bother with PL/1 and BCPL, fortunately.