First RP2040 board and third MCU family (after ESP32-S3 and nRF52840). Hardware: RP2040 + SX1262 radio, 850-930 MHz HF, PE4259 antenna switch, LED on GPIO25, no OLED, USB-C via FPC ribbon cable to adapter board. New files: - src/hal/rp2040.rs: MCU primitives (SPI bus, USB driver, flash unique ID MAC) - src/board/waveshare_rp2040_lora.rs: board implementation - ld/rp2040-memory.x: linker memory layout Also fixes cortex-m-rt/set-vtor (moved from global dep to per-board feature, since Cortex-M0+ has no VTOR register) and makes build.rs memory.x selection target-aware.
128 lines
4.9 KiB
Rust
128 lines
4.9 KiB
Rust
use minijinja::{context, Environment};
|
|
use owo_colors::OwoColorize;
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
|
|
fn main() {
|
|
// Protocol tests run on the host without a board feature.
|
|
if std::env::var_os("DONGLORA_HOST_TEST").is_some() {
|
|
return;
|
|
}
|
|
|
|
let board_dir = "src/board";
|
|
|
|
// ── Discover boards vs helpers by content ───────────────────────
|
|
//
|
|
// A .rs file in src/board/ that implements LoRaBoard is a board.
|
|
// Everything else (traits.rs, esp32s3.rs, etc.) is a helper module.
|
|
// No exclusion lists — detection is based on what the file contains.
|
|
let mut boards: Vec<String> = Vec::new();
|
|
|
|
for entry in fs::read_dir(board_dir).expect("failed to read src/board directory") {
|
|
let p = match entry {
|
|
Ok(e) => e.path(),
|
|
Err(_) => continue,
|
|
};
|
|
if !p.is_file() || p.extension().and_then(|e| e.to_str()) != Some("rs") {
|
|
continue;
|
|
}
|
|
let stem = match p.file_stem().and_then(|s| s.to_str()) {
|
|
Some("mod") => continue, // Skip generated file
|
|
Some(s) => s.to_string(),
|
|
None => continue,
|
|
};
|
|
let content = match fs::read_to_string(&p) {
|
|
Ok(c) => c,
|
|
Err(_) => continue,
|
|
};
|
|
if content.contains("LoRaBoard for Board") {
|
|
boards.push(stem);
|
|
}
|
|
}
|
|
|
|
boards.sort(); // Deterministic ordering
|
|
|
|
// ── Validate exactly one board feature is enabled ───────────────
|
|
let enabled: Vec<_> = std::env::vars()
|
|
.filter(|(k, _)| k.starts_with("CARGO_FEATURE_"))
|
|
.map(|(k, _)| k.replace("CARGO_FEATURE_", "").to_lowercase())
|
|
.filter(|f| boards.contains(f))
|
|
.collect();
|
|
|
|
match enabled.len() {
|
|
0 => panic!(
|
|
"\n\n{}\n",
|
|
format!("No board selected! Found: {:?}", boards)
|
|
.red()
|
|
.bold()
|
|
),
|
|
1 => println!("cargo:rustc-cfg=board_selected"),
|
|
_ => panic!(
|
|
"\n\n{}\n",
|
|
format!("Conflict: Multiple boards enabled {:?}", enabled)
|
|
.yellow()
|
|
.bold()
|
|
),
|
|
}
|
|
|
|
// ── Auto-discover helper modules used by board files ────────────
|
|
//
|
|
// Scan each board file for `use super::<name>` to find which helper
|
|
// modules they depend on. Generate cfg gates so helpers compile only
|
|
// when a dependent board is selected.
|
|
let mut helper_boards: HashMap<String, Vec<String>> = HashMap::new();
|
|
|
|
for board in &boards {
|
|
let path = format!("{}/{}.rs", board_dir, board);
|
|
if let Ok(content) = fs::read_to_string(&path) {
|
|
for line in content.lines() {
|
|
let trimmed = line.trim();
|
|
if let Some(rest) = trimmed.strip_prefix("use super::") {
|
|
let helper = rest
|
|
.split(|c: char| !c.is_alphanumeric() && c != '_')
|
|
.next()
|
|
.unwrap_or("");
|
|
if !helper.is_empty() && helper != "traits" {
|
|
helper_boards
|
|
.entry(helper.to_string())
|
|
.or_default()
|
|
.push(board.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Render board module template ────────────────────────────────
|
|
let mut env = Environment::new();
|
|
let template = fs::read_to_string("src/board/mod.rs.j2").expect("missing src/board/mod.rs.j2");
|
|
env.add_template("mod", &template)
|
|
.expect("failed to parse board template");
|
|
let rendered = env
|
|
.get_template("mod")
|
|
.expect("template not found")
|
|
.render(context!(boards => boards, helpers => helper_boards))
|
|
.expect("failed to render board module");
|
|
fs::write("src/board/mod.rs", rendered).expect("failed to write src/board/mod.rs");
|
|
|
|
println!("cargo:rerun-if-changed=src/board/mod.rs.j2");
|
|
println!("cargo:rerun-if-changed={}", board_dir);
|
|
|
|
// cortex-m-rt's link.x does `INCLUDE memory.x`. Copy the correct layout
|
|
// into OUT_DIR based on the target MCU family.
|
|
let target = std::env::var("TARGET").unwrap_or_default();
|
|
let memory_x = match target.as_str() {
|
|
"thumbv7em-none-eabihf" => Some("ld/nrf52840-memory.x"),
|
|
"thumbv6m-none-eabi" => Some("ld/rp2040-memory.x"),
|
|
_ => None,
|
|
};
|
|
if let Some(src) = memory_x {
|
|
let out_dir = std::env::var("OUT_DIR").unwrap();
|
|
fs::copy(src, format!("{out_dir}/memory.x"))
|
|
.expect("failed to copy memory.x");
|
|
println!("cargo:rustc-link-search={out_dir}");
|
|
}
|
|
println!("cargo:rerun-if-changed=ld/nrf52840-memory.x");
|
|
println!("cargo:rerun-if-changed=ld/rp2040-memory.x");
|
|
}
|