Skip to content
Snippets Groups Projects
Commit fd9aee93 authored by Alexandru RADOVICI's avatar Alexandru RADOVICI
Browse files

Add Lab 01 Exercises

parent b986681f
No related branches found
No related tags found
No related merge requests found
......@@ -15,7 +15,7 @@ import TabItem from '@theme/TabItem';
- How to debug a microcontroller's firmware
- How the binary of the firmware looks like and how to inspect it
- How to cross-compile Rust software
- How to use *probe-rs* for RP2 processors
- How to use `probe-rs` for RP2 processors
- How to print messages from a microcontroller
- How to use the Visual Studio Code probe-rs extension
......@@ -472,6 +472,11 @@ fn main() {
}
```
This example instructs the linker to use:
- the project's linker script - `memory.x`
- the `cortex-m-rt` linker script - `Tlink.x`
- the `defmt`'s linker script - `Tdefmt.x`
### Inspect binaries
When working in Rust for the Raspberry Pi Pico 2, `rust-objdump` can be used to
......@@ -651,18 +656,22 @@ Importing this crate is enough to provide a valid interrupt vector.
use rp235x_hal as _;
```
### The `.start_block` and `.end_blocks`
### The `.start_block` and `.end_block`s
The `rp235x_hal` crate provides the valid `.start_block` and `.end_block`.
```rust
use rp235x_hal::block::ImageDef;
#[link_section = ".start_block"]
#[unsafe(link_section = ".start_block")]
#[used]
pub static IMAGE_DEF: mageDef = ImageDef::secure_exe();
pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe();
```
:::warning
Older Rust versions do not require `unsafe` around the `link_section` attribute.
:::
This code creates a static variable `IMAGE_DEF` of type `ImageDef` and places it
in the `.start_block` section, a dedicated section for bootloader
or secure boot metadata. The `#[used]` attribute ensures the variable is
......@@ -826,18 +835,12 @@ use cortex_m_semihosting::hprintln;
#[entry]
fn main() -> ! {
hprintln!("Device has started").unwrap();
hprintln!("Device has started");
// write the rest of the code here
loop {}
}
```
:::note
The `unwrap` method is called on `hprintln!` to handle the result of the logging operation.
In real applications, you might want to handle potential
errors more gracefully. In this case, if `hprintln!` fails, the device just stop working.
:::
:::tip
The main advantage of semihosting is that it works with all the embedded frameworks.
:::
......@@ -865,7 +868,7 @@ use cortex_m_semihosting::hprintln;
fn main() -> ! {
// Trigger a panic by dividing by zero (for demonstration purposes)
let result = divide_by_zero();
hprintln!("Result: {}", result).unwrap();
hprintln!("Result: {}", result);
loop {}
}
......@@ -881,7 +884,7 @@ fn divide_by_zero() -> u32 {
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
// Print a panic message
hprintln!("Panic occurred: {:?}", info).unwrap();
hprintln!("Panic occurred: {:?}", info);
// Enter an infinite loop to halt the system after a panic
loop {}
......@@ -1014,7 +1017,11 @@ of this variable represents the lowest priority type of message the will be prin
| `warn!` | 3 |
| `error!` | 4 |
To avoid setting this every time or in the command line, a `.cargo/config.toml` entry can be used.
:::warning
The `DEFMT_LOG` variable has to be set during the build.
:::
To avoid setting this variable at every build, a `.cargo/config.toml` entry can be used.
```toml
[env]
......@@ -1034,3 +1041,358 @@ This table show the advantages and disadvantages of both methods.
| **Debugger Required?** | Yes | No (can use other transports) |
| **Memory Usage** | High (standard Rust formatting) | Low (compact binary format) |
| **Use Case** | Debugging with full text output | Real-time logging on embedded devices |
## Exercises
1. Create a new project using `cargo init`. Configure the project using a configuration file
to build for the lab board's target. Test it by running `cargo build`. Take a look at [Empty firmware](#empty-firmware)(**1p**).
:::tip
*Make sure you are building a binary that does not depend on `std`, has no `main` function
and provides a panic handler.*
To prevent `rust-analyzer` from showing an error on the first line and to automatically
format your source when saving, create a `.vscode/settings.json` file with:
```json
{
"rust-analyzer.cargo.allTargets": false,
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file",
},
}
```
:::
2. Inspect the binary and figure out what sections are missing. Explain why each of the sections
is missing. take a look at [Inspect binaries](#inspect-binaries). (**1p**)
3. Add the `cortex-m-rt` crate to your project and define a
function as an *entry point* for your firmware. Inspect the binary and see if the required sections
are present. (**1p**)
:::tip
The `cargo add` command adds crates to a project.
:::
4. Write a linker script to add the correct sections to your binary.
Take a look at [Linker script](#linker-script). Inspect the binary and see if the
required sections are present. (**1p**)
5. Write the build script to instruct the linker to use the linker script. Take a look at [Build script](#build-script).
Inspect the binary and see if the required sections are present. Is one of the sections missing? (**1p**)
:::tip
Make sure that the build script only uses linker scripts that exist in the project.
Some crates, like `cortex-m-rt` and `defmt` provide linker scripts.
:::
6. Print a message from the main function using semihosting. Try running
the firmware using `probe-rs run target/...$app_name`. Why doesn't it work? Take a look at [Semihosting](#semihosting) (**1p**).
7. Add the `rp235x_hal` crate to the project and configure the `.start_block` and the `.end_block`. Take
a look at [The `.start_block` and `.end_block`](#the-start_block-and-end_blocks). (**1p**)
8. Use semihosting to print the panic message in the *panic handler*. Use the `panic!` macro to
generate a panic. Take a look at [Printing the panic](#printing-the-panic) (**1p**).
9. Use the `defmt` and `defmt_rtt` crates to print `info!` and `error!` messages. Generate a panic to verify the error message.
Take a look at [Using `defmt`](#using-defmt) (**1p**)
:::tip
- Make sure you add all the necessary linker scripts to the `build.rs` build script.
- Make sure you enable the `defmt` and `critical-section-impl` features of the `rp235x_hal` crate.
- Make sure you set the `DEFMT_LOG` variable to `info` when building to see the `info` messages or
use the `.cargo/config.toml` file (take a look at [Filtering Messages](#filtering-messages)).
- Messages are printed together with the location where the message was issued. To supress this
use the ` --no-location` parameter for `probe-rs run`.
:::
10. Use the `panic-probe` crate to display better panics. (**1p**)
:::tip
- This crate provides a panic handler.
- Enable the `print-defmt` features of `panic-probe` to use `defmt` to print panics.
:::
11. Make the necessary configuration so that you can use `cargo run` to flash the firmware. Take a look
at [Using `cargo run`](#using-cargo-run) (**1p**)
:::tip
You can add the `--no-location` parameter to the runner.
:::
## VS Code probe-rs Extension
The [`probe-rs` extension for *Visual Studio Code*](https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger) is a powerful tool designed to streamline
the development and debugging of embedded Rust applications. It provides a seamless experience
for flashing firmware, setting breakpoints, and inspecting memory on embedded devices,
including the Raspberry Pi Pico 1 and Pico 2. By integrating with `probe-rs`, this extension
removes the complexity of configuring traditional debugging setups that rely on *GDB* and *OpenOCD*.
Using `probe-rs` within *VS Code* greatly simplifies the debugging workflow. Instead of manually
invoking command-line tools to flash firmware and debug, users can configure the extension via a
simple `launch.json` file. This configuration specifies the target chip, the firmware binary
to flash, and debugging options. Once set up, developers can start debugging with a
single click or by pressing `F5`, making the process efficient and user-friendly.
It integrates natively with `defmt`, allowing developers to view structured log messages
directly within VS Code’s terminal. This is especially useful for debugging
applications running on resource-constrained devices like the Raspberry Pi Pico 1 and Pico 2,
where traditional logging methods may be impractical.
### Set up `launch.json`
VS Code's `probe-rs` extension uses a configuration file called `launch.json` located in the
`.vscode` folder of the project.
<Tabs>
<TabItem value="rp2350" label="Raspberry Pi Pico 2" default>
```json
{
"version": "0.2.0",
"configurations": [
{
"preLaunchTask": "rust: cargo build",
"type": "probe-rs-debug",
"request": "launch",
"name": "Raspberry Pi Pico 2",
"cwd": "${workspaceFolder}",
"connectUnderReset": false,
"chip": "RP235x",
"flashingConfig": {
"flashingEnabled": true,
"haltAfterReset": false,
},
"coreConfigs": [
{
"coreIndex": 0,
"programBinary": "./target/thumbv8m.main-none-eabihf/debug/${workspaceFolderBasename}",
"svdFile": "./rp2350.svd",
"rttEnabled": true,
}
]
}
]
}
```
</TabItem>
<TabItem value="rp2040" label="Raspberry Pi Pico" default>
```json
{
"version": "0.2.0",
"configurations": [
{
"preLaunchTask": "rust: cargo build",
"type": "probe-rs-debug",
"request": "launch",
"name": "Raspberry Pi Pico",
"cwd": "${workspaceFolder}",
"connectUnderReset": false,
"chip": "RP2040",
"flashingConfig": {
"flashingEnabled": true,
"haltAfterReset": false,
},
"coreConfigs": [
{
"coreIndex": 0,
"programBinary": "./target/thumbv6m-none-eabi/debug/${workspaceFolderBasename}",
"svdFile": "./rp2040.svd",
"rttEnabled": true,
}
]
}
]
}
```
</TabItem>
</Tabs>
This `launch.json` file configures VS Code's `probe-rs` extension for debugging
firmware on the Raspberry Pi Pico. It defines how the debugger interacts
with the `RP235x` or the `RP2040` chip, including *flashing*, *RTT support*, and the
*firmware's location*.
The `"version": "0.2.0"` field specifies the configuration format. The
`"configurations"` array holds debug settings, with a single configuration
for the Pico.
Before debugging, `"preLaunchTask": "rust: cargo build"` ensures the firmware
is compiled. The `"type": "probe-rs-debug"` field tells *VS Code* to use
`probe-rs` as the debug backend. The `"request": "launch"` mode starts a
new debugging session. The `"name"` field labels this configuration.
The `"cwd": "${workspaceFolder}"` sets the working directory to the project
root. The `"connectUnderReset": false` prevents connecting while the device
is held in reset. The `"chip": "RP235x"` or `"chip": "RP2040"` tells probe-rs which microcontroller
is in use.
The `"flashingConfig"` section controls how firmware is written to flash.
With `"flashingEnabled": true`, the firmware is flashed before debugging.
The `"haltAfterReset": false` allows execution to continue after reset.
The `coreConfigs` array defines how individual CPU cores are handled. Since
RP235x has multiple cores, this example configures **core 0**. The firmware
binary is loaded from `./target/thumbv8m.main-none-eabihf/debug/${workspaceFolderBasename}`.
The `"svdFile": "./rp2350.svd"` provides **register descriptions** for
debugging.
The `"rttEnabled": true"` setting enables **Real-Time Transfer (RTT)**,
allowing debug output to be streamed efficiently without interfering with
program execution. This is used for reading the messages sent by `defmt`.
### Running the firmware
This is how the `probe-rs` extension for *VS Code* UI looks like when
debugging firmware.
![probe-rs VSCode extension](probe_rs_vscode.webp)
To run firmware on the Raspberry Pi Pico 1 or 2 using the `probe-rs` extension
in *Visual Studio Code*, open your project and ensure your `launch.json`
file is [correctly configured](#set-up-launchjson). Before starting, connect
your *debugger** to the Pico and power the board.
:::tip
If you are using the lab board, please make sure you are using the USB-C connector.
:::
#### Starting the Debugging Session
1. Open the **Run and Debug** panel in VS Code by clicking the **Run** icon
in the left sidebar or pressing `Ctrl+Shift+D`.
2. Select *"Raspberry Pi Pico 2"* (or the name of your configuration) from
the drop-down menu at the top of the panel.
3. Click the **Start Debugging** button (<span style={{color: "green"}}>▶</span>), or press `F5`.
The firmware will be flashed automatically if `"flashingEnabled": true` is
set in `launch.json`. Once the process completes, execution starts, and the
debugger attaches to the target.
#### Checking for Errors and Debug Output
If something goes wrong, look for errors in the **PROBLEMS** tab at the
bottom of *VS Code*. If flashing fails, check the **TERMINAL** or **DEBUG
CONSOLE** for `probe-rs` messages. Common issues include:
- The *debug probe is not detected*. Try reconnecting the hardware.
- The *Pico is not powered on*. Ensure proper connections.
- *Flashing errors* due to a missing or corrupted firmware binary.
If using `defmt` for logging, open the **TERMINAL** to see formatted
debug messages. If *RTT* is enabled (`"rttEnabled": true`), `defmt` output will
stream here in real-time.
#### Stopping the Running Firmware
To stop execution, press the **Stop Debugging** button (<span style={{color: "orange"}}>■</span>) in the debug
toolbar or use the shortcut `Shift+F5`. This halts the processor and
disconnects the debugger. If the device is still running after stopping,
you may need to **power-cycle** the Pico to fully reset it.
### Using Breakpoints
When debugging firmware on the Raspberry Pi Pico 1 and 2 using the `probe-rs`
extension in *Visual Studio Code*, breakpoints allow you to pause execution
at specific lines of code, while stepping lets you *move through code
line-by-line* to inspect its behavior.
#### Placing a Breakpoint
1. Open the Rust source file you want to debug.
2. Click in the **gutter** (left margin) next to a line of code where you
want execution to pause. A **red dot** will appear, indicating an
**active breakpoint**.
3. Alternatively, place the cursor on a line and press `F9` to toggle a
breakpoint.
Breakpoints are **only effective in code that is actually executed**. If a
breakpoint is placed on an unused function, it will be ignored.
#### Starting and Hitting a Breakpoint
1. Start debugging with `F5` (or the **Start Debugging** button in the
**Run and Debug** panel).
2. When execution reaches a breakpoint, the program **pauses** at that
line, allowing you to inspect variables and memory.
3. Hover over variables to see their values or use the **Variables**
panel in the Debug sidebar.
#### Stepping Through Code
When a breakpoint is hit, you can control execution using the **Debug Toolbar**:
- **Step Over (`F10`)** → Executes the current line and moves to the next.
If the line contains a function call, the function runs but isn’t entered.
- **Step Into (`F11`)** → Enters a function call to debug inside it.
- **Step Out (`Shift+F11`)** → Runs until the current function returns.
- **Continue (`F5`)** → Resumes execution until the next breakpoint or
program end.
#### Removing or Disabling Breakpoints
To remove a breakpoint, click the **red dot** again or press `F9` on the
selected line. To temporarily disable a breakpoint, right-click it and
select **Disable Breakpoint**.
Using breakpoints and stepping makes it easier to understand how firmware
executes and diagnose issues efficiently in `probe-rs` with VS Code.
![probe-rs VS Code extension debug](probe_rs_debug.webp)
### Watches
The `probe-rs` extension in *VS Code* provides powerful debugging tools
to inspect variables, function execution, and hardware registers while
developing firmware for the Raspberry Pi Pico 2.
The **WATCH** panel in VS Code allows developers to track specific
variables and expressions while debugging. This is useful for monitoring
register values, checking memory locations, and debugging complex logic.
1. Start debugging (`F5`) and hit a **breakpoint** where you want to inspect
variables.
2. Open the **WATCH** panel in the Debug sidebar.
3. Click **"Add Expression"** and enter a variable name or memory address.
- Example: `some_variable` (for normal variables).
- Example: `*(0xD0000014 as *const u32)` (to read a register).
4. The expression updates each time the debugger stops.
### Call Stack: Tracing Function Execution
The **CALL STACK** panel shows the sequence of function calls that led to
the current execution point. This is essential for understanding how the
program reached a specific line of code.
#### How to Use the Call Stack
1. When execution stops at a breakpoint, check the **CALL STACK** panel.
2. The top function is the **currently executing function**.
3. Clicking on any function in the stack navigates to its source.
#### Why Use the Call Stack?
- Understand **nested function calls** (e.g., deep function recursion).
- Debug **unexpected behavior** by seeing which path the code took.
- Find where a **panic** originated in case of a crash.
### Peripherals: Inspecting Hardware Registers
The **Peripherals** panel lets you inspect hardware registers based on
the **SVD (System View Description) file** for the **RP235x** or **RP2040** chip. This
allows direct observation of GPIO states, timers, UART, and more.
:::warning
Please make sure that the project folder contains the correct SVD file.
:::
#### SVD Files for The Raspberry Pi Pico
| Processor | Crate that provides the SVD File |
|-|-|
| *RP2350* | [rp235x_pac](https://github.com/rp-rs/rp235x-pac/tree/main/svd) |
| *RP2040* | [rp2040_pac](https://github.com/rp-rs/rp2040-pac/tree/main/svd) |
:::tip
Make sure you download the `.pacthed` versions of the files.
:::
#### How to Enable Peripheral Debugging
1. Ensure `"svdFile": "./rp2350.svd"` or `"svdFile": "./rp2040.svd"` is set in `launch.json`.
2. Start debugging and hit a **breakpoint**.
3. Open the **Peripherals** panel in the Debug sidebar.
4. Expand peripheral groups (e.g., `GPIO`, `TIMER`, `UART`) to see
live register values.
#### **Why Use the Peripherals Panel?**
- Observe **hardware state changes** while stepping through code.
- Debug **GPIO settings, clock configurations, and interrupts**.
- Avoid manual memory reads by using **structured register views**.
## Extra exercises
1. Setup the `probe-rs` VS Code Extension to run the firmare directly from *Visual Studio Code*.
2. Define a few variables throughout the firmware and perform some math. Use the `probe-rs` VS Code extension
to go step by step throughout the running firmware and look at the variables using the **Watches**.
3. Define a few fuctions throughout the firmware and perform some math. Use the `probe-rs` VS Code extension
to go step by step through the running firmware and look at the called functions using the **Call Stack**.
\ No newline at end of file
website/lab/01/probe_rs_debug.webp

162 KiB

website/lab/01/probe_rs_vscode.webp

187 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment