feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+394
@@ -0,0 +1,394 @@
|
||||
//! ## Overview
|
||||
//!
|
||||
//! Procedural macros for use with the `esp-hal` family of HAL packages. In
|
||||
//! general, you should not need to depend on this package directly, as the
|
||||
//! relevant procmacros are re-exported by the various HAL packages.
|
||||
//!
|
||||
//! Provides macros for:
|
||||
//!
|
||||
//! - Placing statics and functions into RAM
|
||||
//! - Marking interrupt handlers
|
||||
//! - Automatically creating an `embassy` executor instance and spawning the
|
||||
//! defined entry point
|
||||
//!
|
||||
//! These macros offer developers a convenient way to control memory placement
|
||||
//! and define interrupt handlers in their embedded applications, allowing for
|
||||
//! optimized memory usage and precise handling of hardware interrupts.
|
||||
//!
|
||||
//! Key Components:
|
||||
//! - [`interrupt`](attr.interrupt.html) - Attribute macro for marking
|
||||
//! interrupt handlers. Interrupt handlers are used to handle specific
|
||||
//! hardware interrupts generated by peripherals.
|
||||
//!
|
||||
//! The macro allows users to specify the interrupt name explicitly or use
|
||||
//! the function name to match the interrupt.
|
||||
//! - [`main`](attr.main.html) - Creates a new `executor` instance and declares
|
||||
//! an application entry point spawning the corresponding function body as an
|
||||
//! async task.
|
||||
//! - [`ram`](attr.ram.html) - Attribute macro for placing statics and
|
||||
//! functions into specific memory sections, such as SRAM or RTC RAM (slow or
|
||||
//! fast) with different initialization options. See its documentation for
|
||||
//! details.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! #### `main` macro
|
||||
//!
|
||||
//! Requires the `embassy` feature to be enabled.
|
||||
//!
|
||||
//! ```rust, no_run
|
||||
//! #[main]
|
||||
//! async fn main(spawner: Spawner) {
|
||||
//! // Your application's entry point
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Feature Flags
|
||||
#![doc = document_features::document_features!()]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
|
||||
|
||||
#[allow(unused)]
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
#[cfg(feature = "embassy")]
|
||||
mod embassy;
|
||||
#[cfg(feature = "interrupt")]
|
||||
mod interrupt;
|
||||
#[cfg(any(
|
||||
feature = "is-lp-core",
|
||||
feature = "is-ulp-core",
|
||||
feature = "has-lp-core",
|
||||
feature = "has-ulp-core"
|
||||
))]
|
||||
mod lp_core;
|
||||
|
||||
#[cfg(feature = "ram")]
|
||||
#[derive(Debug, Default, darling::FromMeta)]
|
||||
#[darling(default)]
|
||||
struct RamArgs {
|
||||
rtc_fast: bool,
|
||||
rtc_slow: bool,
|
||||
persistent: bool,
|
||||
zeroed: bool,
|
||||
}
|
||||
|
||||
/// Sets which segment of RAM to use for a function or static and how it should
|
||||
/// be initialized.
|
||||
///
|
||||
/// Requires the `ram` feature.
|
||||
///
|
||||
/// # Options
|
||||
///
|
||||
/// - `rtc_fast`: Use RTC fast RAM.
|
||||
/// - `rtc_slow`: Use RTC slow RAM. **Note**: not available on all targets
|
||||
/// - `persistent`: Persist the contents of the `static` across resets. See [the
|
||||
/// section below](#persistent) for details.
|
||||
/// - `zeroed`: Initialize the memory of the `static` to zero. The initializer
|
||||
/// expression will be discarded. Types used must implement
|
||||
/// [`bytemuck::Zeroable`].
|
||||
///
|
||||
/// Using both `rtc_fast` and `rtc_slow` or `persistent` and `zeroed` together
|
||||
/// is an error.
|
||||
///
|
||||
/// ## `persistent`
|
||||
///
|
||||
/// Initialize the memory to zero after the initial boot. Thereafter,
|
||||
/// initialization is skipped to allow communication across `software_reset()`,
|
||||
/// deep sleep, watchdog timeouts, etc.
|
||||
///
|
||||
/// Types used must implement [`bytemuck::AnyBitPattern`].
|
||||
///
|
||||
/// ### Warnings
|
||||
///
|
||||
/// - A system-level or lesser reset occurring before the ram has been zeroed
|
||||
/// *could* skip initialization and start the application with the static
|
||||
/// filled with random bytes.
|
||||
/// - There is no way to keep some kinds of resets from happening while updating
|
||||
/// a persistent static—not even a critical section.
|
||||
///
|
||||
/// If these are issues for your application, consider adding a checksum
|
||||
/// alongside the data.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// #[ram(rtc_fast)]
|
||||
/// static mut SOME_INITED_DATA: [u8; 2] = [0xaa, 0xbb];
|
||||
///
|
||||
/// #[ram(rtc_fast, persistent)]
|
||||
/// static mut SOME_PERSISTENT_DATA: [u8; 2] = [0; 2];
|
||||
///
|
||||
/// #[ram(rtc_fast, zeroed)]
|
||||
/// static mut SOME_ZEROED_DATA: [u8; 8] = [0; 8];
|
||||
/// ```
|
||||
///
|
||||
/// See the `ram` example in the esp-hal repository for a full usage example.
|
||||
///
|
||||
/// [`bytemuck::AnyBitPattern`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.AnyBitPattern.html
|
||||
/// [`bytemuck::Zeroable`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.Zeroable.html
|
||||
#[cfg(feature = "ram")]
|
||||
#[proc_macro_attribute]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
use darling::{ast::NestedMeta, Error, FromMeta};
|
||||
use proc_macro::Span;
|
||||
use proc_macro_error2::abort;
|
||||
use syn::{parse, Item};
|
||||
|
||||
let attr_args = match NestedMeta::parse_meta_list(args.into()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return TokenStream::from(Error::from(e).write_errors());
|
||||
}
|
||||
};
|
||||
|
||||
let RamArgs {
|
||||
rtc_fast,
|
||||
rtc_slow,
|
||||
persistent,
|
||||
zeroed,
|
||||
} = match FromMeta::from_list(&attr_args) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return e.write_errors().into();
|
||||
}
|
||||
};
|
||||
|
||||
let item: Item = parse(input).expect("failed to parse input");
|
||||
|
||||
#[cfg(not(feature = "rtc_slow"))]
|
||||
if rtc_slow {
|
||||
abort!(
|
||||
Span::call_site(),
|
||||
"rtc_slow is not available for this target"
|
||||
);
|
||||
}
|
||||
|
||||
let is_fn = matches!(item, Item::Fn(_));
|
||||
let section_name = match (is_fn, rtc_fast, rtc_slow, persistent, zeroed) {
|
||||
(true, false, false, false, false) => Ok(".rwtext"),
|
||||
(true, true, false, false, false) => Ok(".rtc_fast.text"),
|
||||
(true, false, true, false, false) => Ok(".rtc_slow.text"),
|
||||
|
||||
(false, false, false, false, false) => Ok(".data"),
|
||||
|
||||
(false, true, false, false, false) => Ok(".rtc_fast.data"),
|
||||
(false, true, false, true, false) => Ok(".rtc_fast.persistent"),
|
||||
(false, true, false, false, true) => Ok(".rtc_fast.bss"),
|
||||
|
||||
(false, false, true, false, false) => Ok(".rtc_slow.data"),
|
||||
(false, false, true, true, false) => Ok(".rtc_slow.persistent"),
|
||||
(false, false, true, false, true) => Ok(".rtc_slow.bss"),
|
||||
|
||||
_ => Err(()),
|
||||
};
|
||||
|
||||
let section = match (is_fn, section_name) {
|
||||
(true, Ok(section_name)) => quote::quote! {
|
||||
#[link_section = #section_name]
|
||||
#[inline(never)] // make certain function is not inlined
|
||||
},
|
||||
(false, Ok(section_name)) => quote::quote! {
|
||||
#[link_section = #section_name]
|
||||
},
|
||||
(_, Err(_)) => {
|
||||
abort!(Span::call_site(), "Invalid combination of ram arguments");
|
||||
}
|
||||
};
|
||||
|
||||
let trait_check = if zeroed {
|
||||
Some("zeroable")
|
||||
} else if persistent {
|
||||
Some("persistable")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let trait_check = trait_check.map(|name| {
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
|
||||
let hal = proc_macro2::Ident::new(
|
||||
if let Ok(FoundCrate::Name(ref name)) = crate_name("esp-hal") {
|
||||
name
|
||||
} else {
|
||||
"crate"
|
||||
},
|
||||
Span::call_site().into(),
|
||||
);
|
||||
|
||||
let assertion = quote::format_ident!("assert_is_{name}");
|
||||
let Item::Static(ref item) = item else {
|
||||
abort!(item, "Expected a `static`");
|
||||
};
|
||||
let ty = &item.ty;
|
||||
quote::quote! {
|
||||
const _: () = #hal::__macro_implementation::#assertion::<#ty>();
|
||||
}
|
||||
});
|
||||
|
||||
let output = quote::quote! {
|
||||
#section
|
||||
#item
|
||||
#trait_check
|
||||
};
|
||||
|
||||
output.into()
|
||||
}
|
||||
|
||||
/// Mark a function as an interrupt handler.
|
||||
///
|
||||
/// Optionally a priority can be specified, e.g. `#[handler(priority =
|
||||
/// esp_hal::interrupt::Priority::Priority2)]`.
|
||||
///
|
||||
/// If no priority is given, `Priority::min()` is assumed
|
||||
#[cfg(feature = "interrupt")]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
#[proc_macro_attribute]
|
||||
pub fn handler(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
use darling::{ast::NestedMeta, FromMeta};
|
||||
use proc_macro::Span;
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use syn::{parse::Error as ParseError, spanned::Spanned, ItemFn, ReturnType, Type};
|
||||
|
||||
use self::interrupt::{check_attr_whitelist, WhiteListCaller};
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct MacroArgs {
|
||||
priority: Option<syn::Expr>,
|
||||
}
|
||||
|
||||
let mut f: ItemFn = syn::parse(input).expect("`#[handler]` must be applied to a function");
|
||||
let original_span = f.span();
|
||||
|
||||
let attr_args = match NestedMeta::parse_meta_list(args.into()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return TokenStream::from(darling::Error::from(e).write_errors());
|
||||
}
|
||||
};
|
||||
|
||||
let args = match MacroArgs::from_list(&attr_args) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return TokenStream::from(e.write_errors());
|
||||
}
|
||||
};
|
||||
|
||||
let root = Ident::new(
|
||||
if let Ok(FoundCrate::Name(ref name)) = crate_name("esp-hal") {
|
||||
name
|
||||
} else {
|
||||
"crate"
|
||||
},
|
||||
Span::call_site().into(),
|
||||
);
|
||||
|
||||
let priority = if let Some(priority) = args.priority {
|
||||
quote::quote!( #priority )
|
||||
} else {
|
||||
quote::quote! { #root::interrupt::Priority::min() }
|
||||
};
|
||||
|
||||
// XXX should we blacklist other attributes?
|
||||
|
||||
if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
|
||||
return error;
|
||||
}
|
||||
|
||||
let valid_signature = f.sig.constness.is_none()
|
||||
&& f.sig.abi.is_none()
|
||||
&& f.sig.generics.params.is_empty()
|
||||
&& f.sig.generics.where_clause.is_none()
|
||||
&& f.sig.variadic.is_none()
|
||||
&& match f.sig.output {
|
||||
ReturnType::Default => true,
|
||||
ReturnType::Type(_, ref ty) => match **ty {
|
||||
Type::Tuple(ref tuple) => tuple.elems.is_empty(),
|
||||
Type::Never(..) => true,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
&& f.sig.inputs.len() <= 1;
|
||||
|
||||
if !valid_signature {
|
||||
return ParseError::new(
|
||||
f.span(),
|
||||
"`#[handler]` handlers must have signature `[unsafe] fn([&mut Context]) [-> !]`",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
f.sig.abi = syn::parse_quote_spanned!(original_span => extern "C");
|
||||
let orig = f.sig.ident;
|
||||
let vis = f.vis.clone();
|
||||
f.sig.ident = Ident::new(
|
||||
&format!("__esp_hal_internal_{}", orig),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
let new = f.sig.ident.clone();
|
||||
|
||||
quote::quote_spanned!(original_span =>
|
||||
#f
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#vis static #orig: #root::interrupt::InterruptHandler = #root::interrupt::InterruptHandler::new(#new, #priority);
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Load code to be run on the LP/ULP core.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// let lp_core_code = load_lp_code!("path.elf");
|
||||
/// lp_core_code.run(&mut lp_core, lp_core::LpCoreWakeupSource::HpCpu, lp_pin);
|
||||
/// ````
|
||||
#[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))]
|
||||
#[proc_macro]
|
||||
pub fn load_lp_code(input: TokenStream) -> TokenStream {
|
||||
lp_core::load_lp_code(input)
|
||||
}
|
||||
|
||||
/// Marks the entry function of a LP core / ULP program.
|
||||
#[cfg(any(feature = "is-lp-core", feature = "is-ulp-core"))]
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
#[proc_macro_attribute]
|
||||
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
lp_core::entry(args, input)
|
||||
}
|
||||
|
||||
/// Creates a new `executor` instance and declares an application entry point
|
||||
/// spawning the corresponding function body as an async task.
|
||||
///
|
||||
/// The following restrictions apply:
|
||||
///
|
||||
/// * The function must accept exactly 1 parameter, an
|
||||
/// `embassy_executor::Spawner` handle that it can use to spawn additional
|
||||
/// tasks.
|
||||
/// * The function must be declared `async`.
|
||||
/// * The function must not use generics.
|
||||
/// * Only a single `main` task may be declared.
|
||||
///
|
||||
/// ## Examples
|
||||
/// Spawning a task:
|
||||
///
|
||||
/// ``` rust
|
||||
/// #[main]
|
||||
/// async fn main(_s: embassy_executor::Spawner) {
|
||||
/// // Function body
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "embassy")]
|
||||
#[proc_macro_attribute]
|
||||
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
use self::embassy::{
|
||||
main::{main, run},
|
||||
Args,
|
||||
};
|
||||
|
||||
let args = syn::parse_macro_input!(args as Args);
|
||||
let f = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
|
||||
run(&args.meta, f, main()).unwrap_or_else(|x| x).into()
|
||||
}
|
||||
Reference in new issue
Block a user