This repository has been archived on 2026-08-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
donglora/vendor/esp-hal-procmacros/src/lib.rs
T
swaitsandClaude Opus 4.6 3102fc747a 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>
2026-03-29 20:42:54 -06:00

395 lines
12 KiB
Rust

//! ## 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()
}