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
+170
@@ -0,0 +1,170 @@
|
||||
use darling::ast::NestedMeta;
|
||||
use syn::{
|
||||
parse::{Parse, ParseBuffer},
|
||||
punctuated::Punctuated,
|
||||
Token,
|
||||
};
|
||||
|
||||
pub(crate) struct Args {
|
||||
pub(crate) meta: Vec<NestedMeta>,
|
||||
}
|
||||
|
||||
impl Parse for Args {
|
||||
fn parse(input: &ParseBuffer) -> syn::Result<Self> {
|
||||
let meta = Punctuated::<NestedMeta, Token![,]>::parse_terminated(input)?;
|
||||
Ok(Args {
|
||||
meta: meta.into_iter().collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod main {
|
||||
use std::{cell::RefCell, fmt::Display, thread};
|
||||
|
||||
use darling::{export::NestedMeta, FromMeta};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{ReturnType, Type};
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct Args {}
|
||||
|
||||
pub fn run(
|
||||
args: &[NestedMeta],
|
||||
f: syn::ItemFn,
|
||||
main: TokenStream,
|
||||
) -> Result<TokenStream, TokenStream> {
|
||||
let _args = Args::from_list(args).map_err(|e| e.write_errors())?;
|
||||
|
||||
let fargs = f.sig.inputs.clone();
|
||||
|
||||
let ctxt = Ctxt::new();
|
||||
|
||||
if f.sig.asyncness.is_none() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must be async");
|
||||
}
|
||||
if !f.sig.generics.params.is_empty() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must not be generic");
|
||||
}
|
||||
if f.sig.generics.where_clause.is_some() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses");
|
||||
}
|
||||
if f.sig.abi.is_some() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier");
|
||||
}
|
||||
if f.sig.variadic.is_some() {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must not be variadic");
|
||||
}
|
||||
match &f.sig.output {
|
||||
ReturnType::Default => {}
|
||||
ReturnType::Type(_, ty) => match &**ty {
|
||||
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
|
||||
Type::Never(_) => {}
|
||||
_ => ctxt.error_spanned_by(
|
||||
&f.sig,
|
||||
"main function must either not return a value, return `()` or return `!`",
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
if fargs.len() != 1 {
|
||||
ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner.");
|
||||
}
|
||||
|
||||
ctxt.check()?;
|
||||
|
||||
let f_body = f.block;
|
||||
let out = &f.sig.output;
|
||||
|
||||
let result = quote! {
|
||||
#[doc(hidden)]
|
||||
#[::embassy_executor::task()]
|
||||
async fn __embassy_main(#fargs) #out {
|
||||
#f_body
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
|
||||
::core::mem::transmute(t)
|
||||
}
|
||||
|
||||
#main
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// A type to collect errors together and format them.
|
||||
///
|
||||
/// Dropping this object will cause a panic. It must be consumed using
|
||||
/// `check`.
|
||||
///
|
||||
/// References can be shared since this type uses run-time exclusive mut
|
||||
/// checking.
|
||||
#[derive(Default)]
|
||||
pub struct Ctxt {
|
||||
// The contents will be set to `None` during checking. This is so that checking can be
|
||||
// enforced.
|
||||
errors: RefCell<Option<Vec<syn::Error>>>,
|
||||
}
|
||||
|
||||
impl Ctxt {
|
||||
/// Create a new context object.
|
||||
///
|
||||
/// This object contains no errors, but will still trigger a panic if it
|
||||
/// is not `check`ed.
|
||||
pub fn new() -> Self {
|
||||
Ctxt {
|
||||
errors: RefCell::new(Some(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an error to the context object with a tokenenizable object.
|
||||
///
|
||||
/// The object is used for spanning in error messages.
|
||||
pub fn error_spanned_by<A: ToTokens, T: Display>(&self, obj: A, msg: T) {
|
||||
self.errors
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
// Curb monomorphization from generating too many identical methods.
|
||||
.push(syn::Error::new_spanned(obj.into_token_stream(), msg));
|
||||
}
|
||||
|
||||
/// Consume this object, producing a formatted error string if there are
|
||||
/// errors.
|
||||
pub fn check(self) -> Result<(), TokenStream> {
|
||||
let errors = self.errors.borrow_mut().take().unwrap();
|
||||
match errors.len() {
|
||||
0 => Ok(()),
|
||||
_ => Err(to_compile_errors(errors)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
|
||||
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
|
||||
quote!(#(#compile_errors)*)
|
||||
}
|
||||
|
||||
impl Drop for Ctxt {
|
||||
fn drop(&mut self) {
|
||||
if !thread::panicking() && self.errors.borrow().is_some() {
|
||||
panic!("forgot to check for errors");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> TokenStream {
|
||||
quote! {
|
||||
#[esp_hal::entry]
|
||||
fn main() -> ! {
|
||||
let mut executor = ::esp_hal_embassy::Executor::new();
|
||||
let executor = unsafe { __make_static(&mut executor) };
|
||||
executor.run(|spawner| {
|
||||
spawner.must_spawn(__embassy_main(spawner));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{parse::Error, spanned::Spanned, AttrStyle, Attribute};
|
||||
|
||||
pub(crate) enum WhiteListCaller {
|
||||
Interrupt,
|
||||
}
|
||||
|
||||
pub(crate) fn check_attr_whitelist(
|
||||
attrs: &[Attribute],
|
||||
caller: WhiteListCaller,
|
||||
) -> Result<(), TokenStream> {
|
||||
let whitelist = &[
|
||||
"doc",
|
||||
"link_section",
|
||||
"cfg",
|
||||
"allow",
|
||||
"warn",
|
||||
"deny",
|
||||
"forbid",
|
||||
"cold",
|
||||
"ram",
|
||||
"inline",
|
||||
];
|
||||
|
||||
'o: for attr in attrs {
|
||||
for val in whitelist {
|
||||
if eq(attr, val) {
|
||||
continue 'o;
|
||||
}
|
||||
}
|
||||
|
||||
let err_str = match caller {
|
||||
WhiteListCaller::Interrupt => {
|
||||
"this attribute is not allowed on an interrupt handler controlled by esp-hal"
|
||||
}
|
||||
};
|
||||
|
||||
return Err(Error::new(attr.span(), err_str).to_compile_error().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `true` if `attr.path` matches `name`
|
||||
fn eq(attr: &Attribute, name: &str) -> bool {
|
||||
attr.style == AttrStyle::Outer && attr.path().is_ident(name)
|
||||
}
|
||||
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()
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
#[allow(unused)]
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
#[cfg(any(feature = "is-lp-core", feature = "is-ulp-core"))]
|
||||
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
use proc_macro2::{Ident, Span};
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use quote::format_ident;
|
||||
use syn::{
|
||||
parse::Error,
|
||||
parse_macro_input,
|
||||
spanned::Spanned,
|
||||
FnArg,
|
||||
GenericArgument,
|
||||
ItemFn,
|
||||
PatType,
|
||||
PathArguments,
|
||||
Type,
|
||||
};
|
||||
|
||||
pub(crate) fn make_magic_symbol_name(args: &Vec<&PatType>) -> String {
|
||||
let mut res = String::from("__ULP_MAGIC_");
|
||||
for &a in args {
|
||||
let t = &a.ty;
|
||||
let quoted = to_string(t);
|
||||
res.push_str("ed);
|
||||
res.push('$');
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub(crate) fn simplename(t: &Type) -> String {
|
||||
match t {
|
||||
Type::Path(p) => p.path.segments.last().unwrap().ident.to_string(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extract_pin(ty: &Type) -> u8 {
|
||||
let mut res = 255u8;
|
||||
if let Type::Path(p) = ty {
|
||||
let segment = p.path.segments.last().unwrap();
|
||||
if let PathArguments::AngleBracketed(g) = &segment.arguments {
|
||||
for arg in &g.args {
|
||||
match arg {
|
||||
GenericArgument::Type(t) => {
|
||||
res = extract_pin(t);
|
||||
}
|
||||
GenericArgument::Const(c) => {
|
||||
res = quote! { #c }.to_string().parse().unwrap();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
// This is a specialized implementation - won't fit other use-cases
|
||||
fn to_string(ty: &Type) -> String {
|
||||
let mut res = String::new();
|
||||
if let Type::Path(p) = ty {
|
||||
let segment = p.path.segments.last().unwrap();
|
||||
res.push_str(&segment.ident.to_string());
|
||||
|
||||
if let PathArguments::AngleBracketed(g) = &segment.arguments {
|
||||
res.push('<');
|
||||
let mut pushed = false;
|
||||
for arg in &g.args {
|
||||
if pushed {
|
||||
res.push(',');
|
||||
}
|
||||
|
||||
match arg {
|
||||
GenericArgument::Type(t) => {
|
||||
pushed = true;
|
||||
res.push_str(&to_string(t));
|
||||
}
|
||||
GenericArgument::Const(c) => {
|
||||
pushed = true;
|
||||
res.push_str("e! { #c }.to_string());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
res.push('>');
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
let found_crate = crate_name("esp-lp-hal").expect("esp-lp-hal is present in `Cargo.toml`");
|
||||
let hal_crate = match found_crate {
|
||||
FoundCrate::Itself => quote!(esp_lp_hal),
|
||||
FoundCrate::Name(name) => {
|
||||
let ident = Ident::new(&name, Span::call_site());
|
||||
quote!( #ident )
|
||||
}
|
||||
};
|
||||
|
||||
if !args.is_empty() {
|
||||
return Error::new(Span::call_site(), "This attribute accepts no arguments")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let f = parse_macro_input!(input as ItemFn);
|
||||
|
||||
let mut argument_types = Vec::new();
|
||||
let mut create_peripheral = Vec::new();
|
||||
|
||||
let mut used_pins: Vec<u8> = Vec::new();
|
||||
|
||||
for (num, arg) in f.sig.inputs.iter().enumerate() {
|
||||
let param_name = format_ident!("param{}", num);
|
||||
match arg {
|
||||
FnArg::Receiver(_) => {
|
||||
return Error::new(arg.span(), "invalid argument")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
FnArg::Typed(t) => {
|
||||
match simplename(&t.ty).as_str() {
|
||||
"Output" => {
|
||||
let pin = extract_pin(&t.ty);
|
||||
if used_pins.contains(&pin) {
|
||||
return Error::new(arg.span(), "duplicate pin")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
used_pins.push(pin);
|
||||
create_peripheral.push(quote!(
|
||||
let mut #param_name = unsafe { the_hal::gpio::conjure_output().unwrap() };
|
||||
));
|
||||
}
|
||||
"Input" => {
|
||||
let pin = extract_pin(&t.ty);
|
||||
if used_pins.contains(&pin) {
|
||||
return Error::new(arg.span(), "duplicate pin")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
used_pins.push(pin);
|
||||
create_peripheral.push(quote!(
|
||||
let mut #param_name = unsafe { the_hal::gpio::conjure_input().unwrap() };
|
||||
));
|
||||
}
|
||||
"LpUart" => {
|
||||
create_peripheral.push(quote!(
|
||||
let mut #param_name = unsafe { the_hal::uart::conjure() };
|
||||
));
|
||||
}
|
||||
"LpI2c" => {
|
||||
create_peripheral.push(quote!(
|
||||
let mut #param_name = unsafe { the_hal::i2c::conjure() };
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Error::new(arg.span(), "invalid argument to main")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
argument_types.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let magic_symbol_name = make_magic_symbol_name(&argument_types);
|
||||
|
||||
let param_names: Vec<Ident> = argument_types
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(num, _)| format_ident!("param{}", num))
|
||||
.collect();
|
||||
|
||||
quote!(
|
||||
#[allow(non_snake_case)]
|
||||
#[export_name = "main"]
|
||||
pub fn __risc_v_rt__main() -> ! {
|
||||
#[export_name = #magic_symbol_name]
|
||||
static ULP_MAGIC: [u32; 0] = [0u32; 0];
|
||||
|
||||
unsafe { ULP_MAGIC.as_ptr().read_volatile(); }
|
||||
|
||||
use #hal_crate as the_hal;
|
||||
#(
|
||||
#create_peripheral;
|
||||
)*
|
||||
|
||||
main(#(#param_names),*);
|
||||
}
|
||||
|
||||
#f
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))]
|
||||
pub fn load_lp_code(input: TokenStream) -> TokenStream {
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use litrs::StringLit;
|
||||
use object::{File, Object, ObjectSection, ObjectSymbol, Section, SectionKind};
|
||||
use parse::Error;
|
||||
use proc_macro::Span;
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use syn::{parse, Ident};
|
||||
|
||||
let hal_crate = if cfg!(any(feature = "is-lp-core", feature = "is-ulp-core")) {
|
||||
crate_name("esp-lp-hal")
|
||||
} else {
|
||||
crate_name("esp-hal")
|
||||
};
|
||||
|
||||
let hal_crate = if let Ok(FoundCrate::Name(ref name)) = hal_crate {
|
||||
let ident = Ident::new(name, Span::call_site().into());
|
||||
quote!( #ident )
|
||||
} else {
|
||||
quote!(crate)
|
||||
};
|
||||
|
||||
let first_token = match input.into_iter().next() {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Error::new(
|
||||
Span::call_site().into(),
|
||||
"You need to give the path to an ELF file",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
};
|
||||
let arg = match StringLit::try_from(&first_token) {
|
||||
Ok(arg) => arg,
|
||||
Err(_) => {
|
||||
return Error::new(
|
||||
Span::call_site().into(),
|
||||
"You need to give the path to an ELF file",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
};
|
||||
let elf_file = arg.value();
|
||||
|
||||
if !Path::new(elf_file).exists() {
|
||||
return Error::new(Span::call_site().into(), "File not found")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let bin_data = fs::read(elf_file).unwrap();
|
||||
let obj_file = File::parse(&*bin_data).unwrap();
|
||||
let sections = obj_file.sections();
|
||||
|
||||
let mut sections: Vec<Section> = sections
|
||||
.into_iter()
|
||||
.filter(|section| {
|
||||
matches!(
|
||||
section.kind(),
|
||||
SectionKind::Text
|
||||
| SectionKind::ReadOnlyData
|
||||
| SectionKind::Data
|
||||
| SectionKind::UninitializedData
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
sections.sort_by(|a, b| a.address().partial_cmp(&b.address()).unwrap());
|
||||
|
||||
let mut binary: Vec<u8> = Vec::new();
|
||||
let mut last_address = if cfg!(feature = "has-lp-core") {
|
||||
0x5000_0000
|
||||
} else {
|
||||
0x0
|
||||
};
|
||||
|
||||
for section in sections {
|
||||
if section.address() > last_address {
|
||||
let fill = section.address() - last_address;
|
||||
binary.extend(std::iter::repeat(0).take(fill as usize));
|
||||
}
|
||||
|
||||
binary.extend_from_slice(section.data().unwrap());
|
||||
last_address = section.address() + section.size();
|
||||
}
|
||||
|
||||
let magic_symbol = obj_file
|
||||
.symbols()
|
||||
.find(|s| s.name().unwrap().starts_with("__ULP_MAGIC_"));
|
||||
|
||||
let magic_symbol = if let Some(magic_symbol) = magic_symbol {
|
||||
magic_symbol.name().unwrap()
|
||||
} else {
|
||||
return Error::new(
|
||||
Span::call_site().into(),
|
||||
"Given file doesn't seem to be an LP/ULP core application.",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
};
|
||||
|
||||
let magic_symbol = magic_symbol.trim_start_matches("__ULP_MAGIC_");
|
||||
let args: Vec<proc_macro2::TokenStream> = magic_symbol
|
||||
.split("$")
|
||||
.map(|t| {
|
||||
let t = if t.contains("OutputOpenDrain") {
|
||||
t.replace("OutputOpenDrain", "LowPowerOutputOpenDrain")
|
||||
} else {
|
||||
t.replace("Output", "LowPowerOutput")
|
||||
};
|
||||
let t = t.replace("Input", "LowPowerInput");
|
||||
t.parse().unwrap()
|
||||
})
|
||||
.filter(|v: &proc_macro2::TokenStream| !v.is_empty())
|
||||
.collect();
|
||||
|
||||
#[cfg(feature = "has-lp-core")]
|
||||
let imports = quote! {
|
||||
use #hal_crate::lp_core::LpCore;
|
||||
use #hal_crate::lp_core::LpCoreWakeupSource;
|
||||
use #hal_crate::gpio::lp_io::LowPowerOutput;
|
||||
use #hal_crate::gpio::*;
|
||||
use #hal_crate::uart::lp_uart::LpUart;
|
||||
use #hal_crate::i2c::lp_i2c::LpI2c;
|
||||
};
|
||||
#[cfg(feature = "has-ulp-core")]
|
||||
let imports = quote! {
|
||||
use #hal_crate::ulp_core::UlpCore as LpCore;
|
||||
use #hal_crate::ulp_core::UlpCoreWakeupSource as LpCoreWakeupSource;
|
||||
use #hal_crate::gpio::*;
|
||||
};
|
||||
|
||||
#[cfg(feature = "has-lp-core")]
|
||||
let rtc_code_start = quote! { _rtc_fast_data_start };
|
||||
#[cfg(feature = "has-ulp-core")]
|
||||
let rtc_code_start = quote! { _rtc_slow_data_start };
|
||||
|
||||
quote! {
|
||||
{
|
||||
#imports
|
||||
|
||||
struct LpCoreCode {}
|
||||
|
||||
static LP_CODE: &[u8] = &[#(#binary),*];
|
||||
|
||||
extern "C" {
|
||||
static #rtc_code_start: u32;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(LP_CODE as *const _ as *const u8, &#rtc_code_start as *const u32 as *mut u8, LP_CODE.len());
|
||||
}
|
||||
|
||||
impl LpCoreCode {
|
||||
pub fn run(
|
||||
&self,
|
||||
lp_core: &mut LpCore,
|
||||
wakeup_source: LpCoreWakeupSource,
|
||||
#(_: #args),*
|
||||
) {
|
||||
lp_core.run(wakeup_source);
|
||||
}
|
||||
}
|
||||
|
||||
LpCoreCode {}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
Reference in new issue
Block a user