embassy-rp

This commit is contained in:
Adam Macdonald 2025-02-28 19:28:57 +00:00
parent eb63191b08
commit dbdaee9c6d
7 changed files with 1219 additions and 389 deletions

1110
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,20 +8,27 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
rp235x-hal = { version = "0.2.0", features = [ # Embassy
"critical-section-impl", embassy-rp = { version = "0.3.1", features = [
"rp235xa",
"binary-info", "binary-info",
"critical-section-impl",
"time-driver",
] } ] }
embassy-executor = { version = "0.7.0", features = [
"arch-cortex-m",
"executor-thread",
"executor-interrupt",
"task-arena-size-65536",
] }
embassy-time = "0.4.0"
cortex-m-rt = "0.7.5"
critical-section = "1.2.0" critical-section = "1.2.0"
embedded-hal = "1.0.0"
fugit = "0.3.7"
scd4x = { git = "https://github.com/twokilohertz/scd4x-rs.git", branch = "conversion-fixes", features = [
"scd41",
] }
rtt-target = "0.6.1" rtt-target = "0.6.1"
ssd1351 = "0.5.0"
embedded-hal-bus = "0.3.0" [profile.dev]
embedded-graphics = "0.8.1" opt-level = "s"
# Super-optimised release build, maximum performance, minimal debuggability # Super-optimised release build, maximum performance, minimal debuggability
# Build with cargo build --profile dist # Build with cargo build --profile dist

View File

@ -3,16 +3,22 @@ use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
fn main() { fn main() {
// Put the linker script somewhere the linker can find it
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
println!("cargo:rustc-link-search={}", out.display()); println!("cargo:rustc-link-search={}", out.display());
// The file `memory.x` is loaded by cortex-m-rt's `link.x` script, which // ARM build
// is what we specify in `.cargo/config.toml` for Arm builds
let memory_x = include_bytes!("memory.x"); let memory_x = include_bytes!("memory.x");
let mut f = File::create(out.join("memory.x")).unwrap(); let mut f = File::create(out.join("memory.x")).unwrap();
f.write_all(memory_x).unwrap(); f.write_all(memory_x).unwrap();
println!("cargo:rerun-if-changed=memory.x"); println!("cargo:rerun-if-changed=memory.x");
// RISC-V build
let rp235x_riscv_x = include_bytes!("rp235x_riscv.x");
let mut f = File::create(out.join("rp235x_riscv.x")).unwrap();
f.write_all(rp235x_riscv_x).unwrap();
println!("cargo:rerun-if-changed=rp235x_riscv.x");
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
} }

View File

@ -73,5 +73,3 @@ SECTIONS {
PROVIDE(start_to_end = __end_block_addr - __start_block_addr); PROVIDE(start_to_end = __end_block_addr - __start_block_addr);
PROVIDE(end_to_start = __start_block_addr - __end_block_addr); PROVIDE(end_to_start = __start_block_addr - __end_block_addr);

252
rp235x_riscv.x Normal file
View File

@ -0,0 +1,252 @@
MEMORY {
/*
* The RP2350 has either external or internal flash.
*
* 2 MiB is a safe default here, although a Pico 2 has 4 MiB.
*/
FLASH : ORIGIN = 0x10000000, LENGTH = 2048K
/*
* RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping.
* This is usually good for performance, as it distributes load on
* those banks evenly.
*/
RAM : ORIGIN = 0x20000000, LENGTH = 512K
/*
* RAM banks 8 and 9 use a direct mapping. They can be used to have
* memory areas dedicated for some specific job, improving predictability
* of access times.
* Example: Separate stacks for core0 and core1.
*/
SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K
SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K
}
/* # Developer notes
- Symbols that start with a double underscore (__) are considered "private"
- Symbols that start with a single underscore (_) are considered "semi-public"; they can be
overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" {
static mut _heap_size }`).
- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a
symbol is not dropped if it appears in or near the front of the linker arguments and "it's not
needed" by any of the preceding objects (linker arguments)
- `PROVIDE` is used to provide default values that can be overridden by a user linker script
- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and*
the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM
initialization routine. There's also a second benefit: `32`-byte aligned boundaries
means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`.
*/
PROVIDE(_stext = ORIGIN(FLASH));
PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM));
PROVIDE(_max_hart_id = 0);
PROVIDE(_hart_stack_size = 2K);
PROVIDE(_heap_size = 0);
PROVIDE(InstructionMisaligned = ExceptionHandler);
PROVIDE(InstructionFault = ExceptionHandler);
PROVIDE(IllegalInstruction = ExceptionHandler);
PROVIDE(Breakpoint = ExceptionHandler);
PROVIDE(LoadMisaligned = ExceptionHandler);
PROVIDE(LoadFault = ExceptionHandler);
PROVIDE(StoreMisaligned = ExceptionHandler);
PROVIDE(StoreFault = ExceptionHandler);
PROVIDE(UserEnvCall = ExceptionHandler);
PROVIDE(SupervisorEnvCall = ExceptionHandler);
PROVIDE(MachineEnvCall = ExceptionHandler);
PROVIDE(InstructionPageFault = ExceptionHandler);
PROVIDE(LoadPageFault = ExceptionHandler);
PROVIDE(StorePageFault = ExceptionHandler);
PROVIDE(SupervisorSoft = DefaultHandler);
PROVIDE(MachineSoft = DefaultHandler);
PROVIDE(SupervisorTimer = DefaultHandler);
PROVIDE(MachineTimer = DefaultHandler);
PROVIDE(SupervisorExternal = DefaultHandler);
PROVIDE(MachineExternal = DefaultHandler);
PROVIDE(DefaultHandler = DefaultInterruptHandler);
PROVIDE(ExceptionHandler = DefaultExceptionHandler);
/* # Pre-initialization function */
/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function,
then the function this points to will be called before the RAM is initialized. */
PROVIDE(__pre_init = default_pre_init);
/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */
PROVIDE(_setup_interrupts = default_setup_interrupts);
/* # Multi-processing hook function
fn _mp_hook() -> bool;
This function is called from all the harts and must return true only for one hart,
which will perform memory initialization. For other harts it must return false
and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt).
*/
PROVIDE(_mp_hook = default_mp_hook);
/* # Start trap function override
By default uses the riscv crates default trap handler
but by providing the `_start_trap` symbol external crates can override.
*/
PROVIDE(_start_trap = default_start_trap);
SECTIONS
{
.text.dummy (NOLOAD) :
{
/* This section is intended to make _stext address work */
. = ABSOLUTE(_stext);
} > FLASH
.text _stext :
{
/* Put reset handler first in .text section so it ends up as the entry */
/* point of the program. */
KEEP(*(.init));
KEEP(*(.init.rust));
. = ALIGN(4);
__start_block_addr = .;
KEEP(*(.start_block));
KEEP(*(.boot_info));
. = ALIGN(4);
*(.trap);
*(.trap.rust);
*(.text.abort);
*(.text .text.*);
. = ALIGN(4);
} > FLASH
/* ### Picotool 'Binary Info' Entries
*
* Picotool looks through this block (as we have pointers to it in our
* header) to find interesting information.
*/
.bi_entries : ALIGN(4)
{
/* We put this in the header */
__bi_entries_start = .;
/* Here are the entries */
KEEP(*(.bi_entries));
/* Keep this block a nice round size */
. = ALIGN(4);
/* We put this in the header */
__bi_entries_end = .;
} > FLASH
.rodata : ALIGN(4)
{
*(.srodata .srodata.*);
*(.rodata .rodata.*);
/* 4-byte align the end (VMA) of this section.
This is required by LLD to ensure the LMA of the following .data
section will have the correct alignment. */
. = ALIGN(4);
} > FLASH
.data : ALIGN(32)
{
_sidata = LOADADDR(.data);
__sidata = LOADADDR(.data);
_sdata = .;
__sdata = .;
/* Must be called __global_pointer$ for linker relaxations to work. */
PROVIDE(__global_pointer$ = . + 0x800);
*(.sdata .sdata.* .sdata2 .sdata2.*);
*(.data .data.*);
. = ALIGN(32);
_edata = .;
__edata = .;
} > RAM AT > FLASH
.bss (NOLOAD) : ALIGN(32)
{
_sbss = .;
*(.sbss .sbss.* .bss .bss.*);
. = ALIGN(32);
_ebss = .;
} > RAM
.end_block : ALIGN(4)
{
__end_block_addr = .;
KEEP(*(.end_block));
} > FLASH
/* fictitious region that represents the memory available for the heap */
.heap (NOLOAD) :
{
_sheap = .;
. += _heap_size;
. = ALIGN(4);
_eheap = .;
} > RAM
/* fictitious region that represents the memory available for the stack */
.stack (NOLOAD) :
{
_estack = .;
. = ABSOLUTE(_stack_start);
_sstack = .;
} > RAM
/* fake output .got section */
/* Dynamic relocations are unsupported. This section is only used to detect
relocatable code in the input files and raise an error if relocatable code
is found */
.got (INFO) :
{
KEEP(*(.got .got.*));
}
.eh_frame (INFO) : { KEEP(*(.eh_frame)) }
.eh_frame_hdr (INFO) : { *(.eh_frame_hdr) }
}
PROVIDE(start_to_end = __end_block_addr - __start_block_addr);
PROVIDE(end_to_start = __start_block_addr - __end_block_addr);
/* Do not exceed this mark in the error messages above | */
ASSERT(ORIGIN(FLASH) % 4 == 0, "
ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned");
ASSERT(ORIGIN(RAM) % 32 == 0, "
ERROR(riscv-rt): the start of the RAM must be 32-byte aligned");
ASSERT(_stext % 4 == 0, "
ERROR(riscv-rt): `_stext` must be 4-byte aligned");
ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, "
BUG(riscv-rt): .data is not 32-byte aligned");
ASSERT(_sidata % 32 == 0, "
BUG(riscv-rt): the LMA of .data is not 32-byte aligned");
ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, "
BUG(riscv-rt): .bss is not 32-byte aligned");
ASSERT(_sheap % 4 == 0, "
BUG(riscv-rt): start of .heap is not 4-byte aligned");
ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), "
ERROR(riscv-rt): The .text section must be placed inside the FLASH region.
Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'");
ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, "
ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts.
Consider changing `_max_hart_id` or `_hart_stack_size`.");
ASSERT(SIZEOF(.got) == 0, "
.got section detected in the input files. Dynamic relocations are not
supported. If you are linking to C code compiled using the `gcc` crate
then modify your build script to compile the C code _without_ the
-fPIC flag. See the documentation of the `gcc::Config.fpic` method for
details.");
/* Do not exceed this mark in the error messages above | */

View File

@ -1,2 +0,0 @@
/// Pico 2 W on-board crystal oscillator frequency (AEL 12.0)
pub const XTAL_FREQ_HZ: u32 = 12_000_000u32;

View File

@ -1,179 +1,50 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
// RP235x HAL use core::panic::PanicInfo;
use rp235x_hal as hal;
use hal::gpio::FunctionSpi;
use hal::spi::Spi;
// Display
use ssd1351::{
mode::GraphicsMode,
prelude::SPIInterface,
properties::{DisplayRotation, DisplaySize},
};
// Sensor
// use scd4x::Scd4x;
use embedded_graphics::{
pixelcolor::Rgb565,
prelude::{Point, Primitive, RgbColor, Size},
primitives::{PrimitiveStyleBuilder, Rectangle},
Drawable,
};
use embedded_hal::spi::MODE_0;
use fugit::RateExtU32;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
mod constants; use embassy_executor::Spawner;
use embassy_rp::block::ImageDef;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
rtt_init_print!();
rprintln!("RTT logging initialised");
let _peripherals = embassy_rp::init(Default::default());
loop {
unsafe {
core::arch::asm!("wfi");
}
}
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
rprintln!("Panicked! {}", info);
loop {
unsafe {
core::arch::asm!("wfi");
}
}
}
#[link_section = ".start_block"] #[link_section = ".start_block"]
#[used] #[used]
pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); static IMAGE_DEF: ImageDef = ImageDef::secure_exe();
#[hal::entry] // Program metadata for picotool
fn main() -> ! {
rtt_init_print!();
rprintln!("Logging over RTT initialised");
let mut peripherals = hal::pac::Peripherals::take().unwrap();
let mut watchdog = hal::Watchdog::new(peripherals.WATCHDOG);
let clocks = hal::clocks::init_clocks_and_plls(
constants::XTAL_FREQ_HZ,
peripherals.XOSC,
peripherals.CLOCKS,
peripherals.PLL_SYS,
peripherals.PLL_USB,
&mut peripherals.RESETS,
&mut watchdog,
)
.unwrap();
let mut timer = hal::Timer::new_timer0(peripherals.TIMER0, &mut peripherals.RESETS, &clocks);
let sio = hal::Sio::new(peripherals.SIO);
let pins = hal::gpio::Pins::new(
peripherals.IO_BANK0,
peripherals.PADS_BANK0,
sio.gpio_bank0,
&mut peripherals.RESETS,
);
rprintln!("Core RP2350 hardware initialisation successful");
// Display
let mosi_pin = pins.gpio19.into_function::<FunctionSpi>();
let sclk_pin = pins.gpio18.into_function::<FunctionSpi>();
let cs_pin = pins.gpio17.into_push_pull_output();
let dc_pin = pins.gpio20.into_push_pull_output();
let mut rst_pin = pins.gpio21.into_push_pull_output();
// SPI initialisation
let spi_pins = (mosi_pin, sclk_pin);
let spi = Spi::<_, _, _, 8>::new(peripherals.SPI0, spi_pins).init(
&mut peripherals.RESETS,
&clocks.peripheral_clock,
16_u32.MHz(),
MODE_0,
);
let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs_pin).unwrap();
let spi_interface = SPIInterface::new(spi_device, dc_pin);
let mut display: GraphicsMode<_> = ssd1351::builder::Builder::new()
.with_size(DisplaySize::Display128x128)
.with_rotation(DisplayRotation::Rotate0)
.connect_interface(spi_interface)
.into();
display.reset(&mut rst_pin, &mut timer).unwrap();
display.init().unwrap();
let rect = Rectangle::new(Point::new(0, 40), Size::new(40, 20)).into_styled(
PrimitiveStyleBuilder::new()
.fill_color(Rgb565::CYAN)
.build(),
);
rect.draw(&mut display).unwrap();
// // Initialise SCD41 sensor
// let i2c0 = hal::I2C::i2c0(
// peripherals.I2C0,
// pins.gpio4.reconfigure(), // Pin 6 on Pico 2 (SDA)
// pins.gpio5.reconfigure(), // Pin 7 on Pico 2 (SCL)
// 400.kHz(),
// &mut peripherals.RESETS,
// &clocks.peripheral_clock,
// );
// timer.delay_ms(30); // Power-up delay
// let mut scd41 = Scd4x::new(i2c0, timer);
// scd41.wake_up();
// match scd41.reinit() {
// Ok(_) => rprintln!("Initialised SCD41"),
// Err(error) => rprintln!("Failed to initialise SCD41: {:?}", error),
// }
// timer.delay_ms(30); // Soft reset delay
// match scd41.serial_number() {
// Ok(serial) => rprintln!("SCD41 serial number: {}", serial),
// Err(error) => rprintln!("SCD41 did not respond to get_serial_number: {:?}", error),
// }
// match scd41.self_test_is_ok() {
// Ok(ok) => {
// if ok {
// rprintln!("SCD41 reported successful self-test")
// } else {
// rprintln!("SCD41 reported unsuccessful self-test!")
// }
// }
// Err(_) => rprintln!("SCD41 failed to perform self-test"),
// }
// match scd41.start_periodic_measurement() {
// Ok(_) => rprintln!("Configured sensor to measure every 5 seconds"),
// Err(error) => rprintln!("SCD41 start_periodic_measurement() failed: {:?}", error),
// }
// loop {
// timer.delay_ms(5010);
// match scd41.measurement() {
// Ok(data) => rprintln!(
// "CO2: {}, temperature: {}, humidity: {}",
// data.co2,
// data.temperature,
// data.humidity
// ),
// Err(error) => rprintln!("SCD41 get_measurement() failed: {:?}", error),
// }
// }
loop {
hal::arch::wfi();
}
}
#[inline(never)]
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
rprintln!("Panicked! {}", info);
loop {
hal::arch::nop()
}
}
/// Program metadata for `picotool info`
#[link_section = ".bi_entries"] #[link_section = ".bi_entries"]
#[used] #[used]
pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 3] = [ pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [
hal::binary_info::rp_program_name!(c"Pico Environment Sensor"), embassy_rp::binary_info::rp_program_name!(c"Pico Environment Sensor"),
hal::binary_info::rp_cargo_version!(), embassy_rp::binary_info::rp_program_description!(
hal::binary_info::rp_program_build_attribute!(), c"A CO2, temperature & humidity sensing application for the RPi Pico 2 W"
),
embassy_rp::binary_info::rp_cargo_version!(),
embassy_rp::binary_info::rp_program_build_attribute!(),
]; ];