Skip to content

Commit f0db3a9

Browse files
Willem Vanhullewvhulle
authored andcommitted
Add subsections and embedded writings
1 parent f552a36 commit f0db3a9

14 files changed

Lines changed: 362 additions & 17 deletions

content/blog/2025-06-05_pico-debug.md renamed to content/blog/bare-metal/2025-06-03_pico-debug.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
+++
2-
title = "Pico 2 as debugger"
2+
title = "Pico 2 as debugprobe"
33
description = "How to use the Raspberry Pi Pico 2 as a hardware debugger."
44
draft = false
55
weight = 7
66
[taxonomies]
7-
tags = [ "debug", "Raspberry Pi", "embedded", "Pico", "SWD", "declarative", "Rust", "probe-rs" ]
7+
tags = [ "Raspberry Pi", "Pico", "SWD", "declarative", "Rust", "probe-rs", "cargo-embed" ]
88
+++
99

1010
On most popular microcontrollers used for educational purposes, there is already some hardware debugging support (also called a **hardware debug probe**) on the board itself, such as on the [Micro:bit](https://microbit.org/) or the [ESP32](https://www.espressif.com/en/products/socs/esp32).
@@ -21,17 +21,17 @@ The Pico family of microcontrollers does not have this feature built-in. You hav
2121

2222
In this workshop, we will pursue the first option. If you get stuck, feel free to ask for a pre-made hardware debugger.
2323

24-
### Turning a Normal Pico into a Debugger Pico
24+
### Turning a Pico 2 into a debugger Pico
2525

2626
The Raspberry Pi Foundation provides images for Picos that can be flashed to turn a Pico into a hardware debugger.
2727

2828
1. Download the latest `debugprobe_on_pico.uf2` flash image from the official [`debugprobe`](https://github.com/raspberrypi/debugprobe/releases)releases.
29-
2. Attach the Pico to your laptop while holding the white BOOTSEL button. A mass storage device will appear in your file manager. It will be called something like `RPI-RP2`.
29+
2. Attach the Pico to your laptop while holding the white BOOTSEL button. A mass storage device will appear in your file manager. It will be called something like `RP2350`.
3030
3. Drop the downloaded `.uf2` file onto the mass storage drive emulated by the Pico. Wait for a fraction of a second while the Pico unmounts and reboots as a fresh `debugprobe`.
3131

3232
Now you have successfully made a cheap hardware debugging probe.
3333

34-
### Wire Target to Debugger
34+
### Wire target to debugger
3535

3636
Let's make some aliases:
3737

@@ -65,7 +65,7 @@ Right now, there is no cabling between the debug probe and the target Pico. The
6565

6666
*Remark: You can also connect **T** to **D** for UART communication. However, I have not needed it so far.*
6767

68-
### Configure Flashing from Laptop
68+
### Configure flashing from laptop
6969

7070
There is still one step remaining: we have to configure our laptop's development environment to enable flashing (this applies to any microcontroller with an onboard or external debugger).
7171

@@ -92,7 +92,7 @@ There is still one step remaining: we have to configure our laptop's development
9292
Now you can flash changes in the source code directly to the target Pico (without re-plugging it or holding the BOOTSEL button). The debug probe Pico will function as an intermediary between your laptop and the target Pico.
9393
9494
```bash
95-
cargo run --example external-blink
95+
cargo run
9696
```
9797
9898
You should see two progress bars running to completion in your terminal. As soon as the flash process is finished:
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
+++
2+
title = "Introduction to Embassy"
3+
description = "Overview of the Embassy framework."
4+
draft = false
5+
weight = 8
6+
[taxonomies]
7+
tags = [ "Rust", "Embassy" ]
8+
+++
9+
10+
## Minimal Embassy example
11+
12+
It can be useful to start with a minimal Embassy program. The following does nothing but can serve as a template for future programs.
13+
14+
```rust
15+
#![no_std]
16+
#![no_main]
17+
18+
use defmt_rtt as _;
19+
use embassy_executor::{Spawner, main};
20+
use embassy_rp::config::Config;
21+
use panic_probe as _;
22+
use embassy_rp::bind_interrupts;
23+
24+
bind_interrupts!(struct Irqs {
25+
PIO0_IRQ_0 => InterruptHandler<PIO0>;
26+
});
27+
28+
#[main]
29+
async fn main(_spawner: Spawner) -> ! {
30+
let p = embassy_rp::init(Config::default());
31+
loop {
32+
embassy_futures::yield_now().await;
33+
}
34+
}
35+
```
36+
37+
As you can see, there are two notable attributes at the top of the file.
38+
39+
* `#![no_std]` means that the program does not use the standard library. Embedded systems are too small for the standard library. Instead of using `std::String`, you would a statically allocated `heapless::String`. Most `std` heap allocated container types have an analogue in `heapless`.
40+
* `#![no_main]` means that the program does not have a typical `main` function (with arguments or an exit code) as on a typical operating system. Instead, calling and creating the `main` function is completely handled by the Embassy framework.
41+
42+
Then there are two `use x as _;` lines. These crates don't expose functions or public modules to be used, but they contain setup code that should be included at least once in your embedded program.
43+
44+
* The `panic_probe` crate provides a panic handler that is compatible with Embassy. Panics are **fatal errors**. Every embedded program needs a panic handler because traditional panics would unwind or abort and yield control back to the operating system. This operating system is absent, so we have to tell the compiler how to handle panics. Usually, this means going into an infinite loop.
45+
* The `defmt_rtt` is not useful for the moment, but once you have configured a hardware debugger, it will allow you to log messages to the debugger console. This is useful for debugging your program.
46+
47+
There is a macro call `embassy_rp::bind_interrupts!` that binds hardware interrupts with the Embassy framework. This is necessary to be able to use hardware interrupts in your program. Hardware interrupts can stop the current ongoing computation and jump execution to some handler code elsewhere. Examples of hardware interrupt bindings available on the Pico 2 are:
48+
49+
* `PIO0_IRQ_0` is an interrupt coming from the PIO peripheral.
50+
* `USBCTRL_IRQ` for USB interrupts (relevant in USB serial communication).
51+
* `ADC_IRQ_FIFO` for ADC interrupts (relevant for reading data from the analog-to-digital converter in the moisture sensor).
52+
53+
The `spawner` argument allows users to spawn asynchronous tasks. Keep in mind, however, that each task should be non-generic and completely specified at compile time. This is because the Embassy framework does not support dynamic task creation at runtime.
54+
55+
The last line `loop { yield_now().await }` may seem unnecessary. The reason I have to write it is because the return type of `main` is "never" (written as `!`). The `never` return type is the type for a function that never returns.
56+
57+
Because of the signature of `main`, we cannot simply escape the `main` function. Running this program is the only thing that happens on the microcontroller. So we have to keep looping, even if we have already finished our work.
58+
59+
## Levels of Abstraction in Embedded Rust
60+
61+
This section provides an overview of the different levels of abstraction that can be used when programming microcontrollers in Rust.
62+
63+
### Low Level
64+
65+
The lowest level of software abstraction provides direct access to the microcontroller's hardware registers.
66+
67+
* **Core Support Crate**: Enables access to the core processor's features, like interrupts and system timers. See [Cortex-M](https://crates.io/crates/cortex-m).
68+
* **Peripheral Access Crate (PAC)**: Built on top of the core support crate, the PAC contains auto-generated code for accessing hardware peripherals (like GPIO, ADC, etc.) based on SVD files from the chip manufacturer. See [RP235X-PAC](https://crates.io/crates/rp235x-pac).
69+
70+
The Embassy framework builds on top of the PAC and HAL to provide a more intuitive and convenient API for accessing the hardware.
71+
72+
### Medium Level
73+
74+
If the Embassy framework doesn't suit your needs, you can fall back to a more conventional level of abstraction without `async/await`.
75+
76+
The **Hardware Abstraction Layer (HAL)** is a more convenient way to access the hardware. It provides a higher level of abstraction than the PAC but still allows direct hardware access.
77+
78+
The Pico 2 has [rp235x-hal](https://crates.io/crates/rp235x-hal) as its HAL crate. You can view the [examples](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples), which were used as a reference for this workshop.
79+
80+
*Remark: If you need to preempt tasks (i.e., interrupt a lower-priority task to run a higher-priority one), you should consider using [RTIC](https://github.com/rtic-rs/rtic). RTIC provides a different concurrency model based on preemption and priorities, which may be required for real-time applications.*
81+
82+
### High Level
83+
84+
For commonly used microcontrollers, there is often at least one good **Board Support Package (BSP)**. These are crates that provide a convenient, board-specific API, though they are sometimes less customizable than a HAL. For example, in the case of the Micro:bit controller, the BSP is called [microbit](https://crates.io/crates/microbit) and it allows you to draw shapes on the on-board LED array.
85+
86+
For the Raspberry Pi Pico 2 W, `embassy` (and its `embassy-rp` plugin) come the closest to a full-featured BSP.
87+
88+
## More Reading Material
89+
90+
Interesting books about embedded Rust:
91+
92+
* There is a book for beginners in embedded Rust: [The Discovery Book](https://docs.rust-embedded.org/discovery-mb2/). It assumes you have a Micro:bit v2 (\~€20).
93+
* There is also a book about embedded Rust using an STM32 chip: [The Embedded Rust Book](https://docs.rust-embedded.org/book/).
94+
* Another book about Rust and the Raspberry Pi Pico 2 is [Pico, In-Depth](https://pico.implrust.com/).
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
+++
2+
title = "Logging with defmt over RTT"
3+
description = "How to use the defmt crate to log messages from a Raspberry Pi Pico 2 W over RTT."
4+
draft = false
5+
weight = 9
6+
[taxonomies]
7+
tags = [ "defmt", "RTT", "SWD", "Rust", "GDB" ]
8+
+++
9+
10+
11+
## Simple Logging
12+
13+
RTT (Real-Time Transfer) is a logging protocol that can be used on top of an SWD connection. It does not require specifying the baud rate, etc.
14+
15+
The `defmt` crate is the most popular crate for logging from embedded Rust programs. It exports macros like `info!` and `debug!`, similar to the macros in the standard `log` or `tracing` crates in Rust.
16+
17+
For the debug probe to actually show the log output from the target, you need to enable a "transport". In the case of `defmt`, it is usually the `RTT` transport using the `defmt-rtt` crate. The `defmt-rtt` crate could be compared to `tracing-subscriber` or other mainstream log consumers.
18+
19+
1. Add `defmt` and `defmt-rtt` as a dependency to your `Cargo.toml` file. Also, enable the `defmt` features for all existing dependencies that have it.
20+
21+
2. Import the `defmt-rtt` module in your binary or library:
22+
23+
```rust
24+
use defmt_rtt as _;
25+
```
26+
27+
This may seem useless, but it enables the setup of data necessary to link the binary against the `defmt-rtt` crate.
28+
29+
3. Add a compiler flag under the current target in the `.cargo/config.toml` file: `-C link-arg=-Tdefmt.x`.
30+
31+
```toml
32+
[target.thumbv8m.main-none-eabihf]
33+
rustflags = [
34+
"-C",
35+
"link-arg=--nmagic",
36+
"-C",
37+
"link-arg=-Tlink.x",
38+
"-C",
39+
"link-arg=-Tdefmt.x",
40+
"-C",
41+
"target-cpu=cortex-m33",
42+
]
43+
```
44+
45+
4. Specify the log level for `defmt` in the `.cargo/config.toml` file:
46+
47+
```toml
48+
[env]
49+
DEFMT_LOG = "debug"
50+
```
51+
52+
5. Enable `rtt` in the `Embed.toml` file:
53+
54+
```toml
55+
[default.rtt]
56+
enabled = true
57+
```
58+
59+
6. Add invocations of the `defmt` macros throughout your library or binary code (as necessary). For example, you could write:
60+
61+
```rust
62+
use defmt::info;
63+
64+
async fn main(_spawner: Spawner) -> ! {
65+
loop {
66+
info!("A new iteration of the loop has started.");
67+
}
68+
}
69+
```
70+
71+
There is nothing stopping you from adding such statements to library code.
72+
73+
7. Compile, flash, and run your binary on the target Pico 2 W:
74+
75+
```bash
76+
cargo ru
77+
```
78+
79+
This should open an RTT console that shows the log messages emitted by the `defmt` statements in your code.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
+++
2+
title = "Usage of GDB debugger"
3+
description = "How to use the GDB debugger with a Raspberry Pi Pico 2 W."
4+
draft = false
5+
weight = 10
6+
[taxonomies]
7+
tags = [ "SWD", "Pico", "Rust", "cargo-embed" ]
8+
+++
9+
10+
11+
12+
13+
## Debugging with GDB
14+
15+
Once you start creating slightly more complicated embedded programs, you might want to:
16+
17+
* introspect the values of local variables
18+
* follow the execution flow
19+
20+
For this, you need a piece of software called a debugger. The most commonly used debugger for Rust and C is [GDB](https://en.wikipedia.org/wiki/GNU_Debugger).
21+
22+
*Remark: In VS Code, you can install the `probe-rs-debug` extension to use the `probe-rs` toolkit for debugging. It uses a different protocol than `gdb`. See [instructions](https://probe.rs/docs/tools/debugger/).*
23+
24+
### Setup of `cargo-embed`
25+
26+
Adjust the `Embed.toml` file in the root of this repository if necessary. This file configures the behavior of the `cargo embed` command when run on your laptop.
27+
28+
For example, if the configuration contains the following, a GDB debug server session will be started, and the loaded program will be reset to the first instruction.
29+
30+
```toml
31+
[default.gdb]
32+
enabled = true
33+
34+
[default.reset]
35+
halt_afterwards = true
36+
```
37+
38+
Prevent lines from being merged or reordered during the build step of your program. These kinds of changes can make it harder for the debugger to stop at the right breakpoints. Add the following to `Cargo.toml`:
39+
40+
```toml
41+
[profile.dev]
42+
debug = 2
43+
opt-level = 0
44+
```
45+
46+
To be sure the new configuration is used, you can clear the `target` build cache with `cargo clean` and then build again:
47+
48+
```bash
49+
cargo clean
50+
cargo build --example [BINARY_EXAMPLE_NAME]
51+
```
52+
53+
### Starting a GDB Client
54+
55+
While searching for an appropriate GDB package, look for one that supports the architecture of your target chip. In the case of a Pico 2, `gdb` needs `ARM` support built in.
56+
57+
Install the multi-architecture version of `gdb`:
58+
59+
```bash
60+
sudo apt-get install gdb-multiarch
61+
```
62+
63+
Then run the following command to create and connect a `gdb` debugging client:
64+
65+
```bash
66+
gdb-multiarch target/thumbv8m.main-none-eabi/debug/[BINARY_EXAMPLE_NAME]
67+
```
68+
69+
*Note: The command may also be `gdb`.*
70+
71+
Within the `gdb` client on your laptop, you have to connect to the running `GDB` server on the debug Pico probe:
72+
73+
```txt
74+
target remote :1337
75+
monitor reset halt # optionally resets to the first instruction
76+
tui enable
77+
```
78+
79+
Alternatively, you can tell `gdb` to execute these commands automatically by writing them in a `.gdbinit` file in the root of this repository.
80+
81+
### Common GDB Commands
82+
83+
Breakpoints can be set in the `gdb` client by using the `break` command followed by a line number or function name:
84+
85+
```txt
86+
break [FUNCTION_NAME] # Set a breakpoint at a specific function
87+
break [LINE_NUMBER] # Set a breakpoint at a specific line number
88+
break [FILE_NAME]:[LINE_NUMBER] # Set a breakpoint at a specific line in a file
89+
```
90+
91+
You can also write hardware breakpoints directly in your code with `cortex_m::asm::bkpt()`.
92+
93+
To progress through the execution of your debugged program, you can use:
94+
95+
```txt
96+
continue # Continue execution until the next breakpoint is hit
97+
next # Step to the next line of code
98+
```
99+
100+
For introspection of variables:
101+
102+
```txt
103+
print [VAR_NAME]
104+
```

content/blog/bare-metal/_index.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
+++
2+
title = "Embedded"
3+
description = "Low-level programming with microcontrollers"
4+
insert_anchor_links = "heading"
5+
sort_by ="weight"
6+
template = "section.html"
7+
+++
8+
9+
Some notes from a [workshop](https://github.com/sysghent/plant-pot) on bare metal programming with Rust and the Raspberry Pi Pico 2 W.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

content/blog/streams/_index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
+++
2+
title = "Streams"
3+
description = "Notes about how to utilise streams in Rust to build efficient and composable data processing pipelines."
4+
insert_anchor_links = "heading"
5+
sort_by ="weight"
6+
+++
7+

0 commit comments

Comments
 (0)