Skip to main content

esp_hal_procmacros/
lib.rs

1//! ## Overview
2//!
3//! Procedural macros for use with the `esp-hal` family of HAL packages. In
4//! general, you should not need to depend on this package directly, as the
5//! relevant procmacros are re-exported by the various HAL packages.
6//!
7//! Provides macros for:
8//!
9//! - Placing statics and functions into RAM
10//! - Marking interrupt handlers
11//! - Blocking and Async `#[main]` macros
12//!
13//! These macros offer developers a convenient way to control memory placement
14//! and define interrupt handlers in their embedded applications, allowing for
15//! optimized memory usage and precise handling of hardware interrupts.
16//!
17//! Key Components:
18//!  - [`handler`](macro@handler) - Attribute macro for marking interrupt handlers. Interrupt
19//!    handlers are used to handle specific hardware interrupts generated by peripherals.
20//!
21//!  - [`ram`](macro@ram) - Attribute macro for placing statics and functions into specific memory
22//!    sections, such as SRAM or RTC RAM (slow or fast) with different initialization options. See
23//!    its documentation for details.
24//!
25//!  - [`esp_rtos::main`](macro@rtos_main) - Creates a new instance of `esp_rtos::embassy::Executor`
26//!    and declares an application entry point spawning the corresponding function body as an async
27//!    task.
28//!
29//! ## Examples
30//!
31//! #### `main` macro
32//!
33//! Requires the `embassy` feature to be enabled.
34//!
35//! ```rust,ignore
36//! #[main]
37//! async fn main(spawner: Spawner) {
38//!     // Your application's entry point
39//! }
40//! ```
41//!
42//! ## Feature Flags
43#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
44#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
45
46use proc_macro::TokenStream;
47
48mod alert;
49mod blocking;
50mod builder;
51mod doc_replace;
52mod interrupt;
53#[cfg(any(
54    feature = "is-lp-core",
55    feature = "is-ulp-core",
56    feature = "has-lp-core",
57    feature = "has-ulp-core"
58))]
59mod lp_core;
60mod ram;
61mod rtos_main;
62
63/// Sets which segment of RAM to use for a function or static and how it should
64/// be initialized.
65///
66/// # Options
67///
68/// - `rtc_fast`: Use RTC fast RAM.
69/// - `rtc_slow`: Use RTC slow RAM. **Note**: not available on all targets.
70/// - `persistent`: Persist the contents of the `static` across resets. See [the section
71///   below](#persistent) for details.
72/// - `zeroed`: Initialize the memory of the `static` to zero. The initializer expression will be
73///   discarded. Types used must implement [`bytemuck::Zeroable`].
74/// - `reclaimed`: Memory reclaimed from the esp-idf bootloader.
75///
76/// Using both `rtc_fast` and `rtc_slow` or `persistent` and `zeroed` together
77/// is an error.
78///
79/// ## `persistent`
80///
81/// Initialize the memory to zero after the initial boot. Thereafter,
82/// initialization is skipped to allow communication across `software_reset()`,
83/// deep sleep, watchdog timeouts, etc.
84///
85/// Types used must implement [`bytemuck::AnyBitPattern`].
86///
87/// ### Warnings
88///
89/// - A system-level or lesser reset occurring before the ram has been zeroed *could* skip
90///   initialization and start the application with the static filled with random bytes.
91/// - There is no way to keep some kinds of resets from happening while updating a persistent
92///   static—not even a critical section.
93///
94/// If these are issues for your application, consider adding a checksum
95/// alongside the data.
96///
97/// # Examples
98///
99/// ```rust, ignore
100/// #[ram(unstable(rtc_fast))]
101/// static mut SOME_INITED_DATA: [u8; 2] = [0xaa, 0xbb];
102///
103/// #[ram(unstable(rtc_fast, persistent))]
104/// static mut SOME_PERSISTENT_DATA: [u8; 2] = [0; 2];
105///
106/// #[ram(unstable(rtc_fast, zeroed))]
107/// static mut SOME_ZEROED_DATA: [u8; 8] = [0; 8];
108/// ```
109///
110/// See the `ram` example in the qa-test folder of the esp-hal repository for a full usage example.
111///
112/// [`bytemuck::AnyBitPattern`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.AnyBitPattern.html
113/// [`bytemuck::Zeroable`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.Zeroable.html
114#[proc_macro_attribute]
115pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
116    ram::ram(args.into(), input.into()).into()
117}
118
119/// Replaces placeholders in rustdoc doc comments.
120///
121/// The purpose of this macro is to enable us to extract boilerplate, while at
122/// the same time let rustfmt format code blocks. This macro rewrites the whole
123/// documentation of the annotated item.
124///
125/// Replacements can be placed in the documentation as `# {placeholder}`. Each
126/// replacement must be its own line. The `before_snippet` and `after_snippet` placeholders are
127/// expanded to the `esp_hal::before_snippet!()` and `esp_hal::after_snippet!()` macros, and are
128/// expected to be used in example code blocks.
129///
130/// In-line replacements can be placed in the middle of a line as `__placeholder__`. Currently,
131/// only literal strings can be substituted into in-line placeholders, and only one placeholder
132/// can be used per line.
133///
134/// You can also define custom replacements in the attribute. A replacement can be
135/// an unconditional literal (i.e. a string that is always substituted into the doc comment),
136/// or a conditional.
137///
138/// ## Examples
139///
140/// ```rust, ignore
141/// #[doc_replace(
142///   "literal_placeholder" => "literal value",
143///   "conditional_placeholder" => {
144///     cfg(condition1) => "value 1",
145///     cfg(condition2) => "value 2",
146///     _ => "neither value 1 nor value 2",
147///   }
148/// )]
149/// /// Here comes the documentation.
150/// ///
151/// /// The replacements are interpreted outside of code blocks, too:
152/// /// # {literal_placeholder}
153/// ///
154/// /// ```rust, no run
155/// /// // here is some code
156/// /// # {literal_placeholder}
157/// /// // here is some more code
158/// /// # {conditional_placeholder}
159/// ///
160/// /// The macro even supports __conditional_placeholder__ replacements in-line.
161/// /// ```
162/// fn my_function() {}
163/// ```
164#[proc_macro_attribute]
165pub fn doc_replace(args: TokenStream, input: TokenStream) -> TokenStream {
166    doc_replace::replace(args.into(), input.into()).into()
167}
168
169/// Mark a function as an interrupt handler.
170///
171/// Optionally a priority can be specified, e.g. `#[handler(priority =
172/// esp_hal::interrupt::Priority::Priority2)]`.
173///
174/// If no priority is given, `Priority::min()` is assumed
175#[proc_macro_attribute]
176pub fn handler(args: TokenStream, input: TokenStream) -> TokenStream {
177    interrupt::handler(args.into(), input.into()).into()
178}
179
180/// Load code to be run on the LP/ULP core.
181///
182/// ## Example
183/// ```rust, ignore
184/// let lp_core_code = load_lp_code!("path.elf");
185/// lp_core_code.run(&mut lp_core, lp_core::LpCoreWakeupSource::HpCpu, lp_pin);
186/// ````
187#[cfg(any(feature = "has-lp-core", feature = "has-ulp-core"))]
188#[proc_macro]
189pub fn load_lp_code(input: TokenStream) -> TokenStream {
190    lp_core::load_lp_code(input.into(), lp_core::RealFilesystem).into()
191}
192
193/// Marks the entry function of a LP core / ULP program.
194#[cfg(any(feature = "is-lp-core", feature = "is-ulp-core"))]
195#[proc_macro_attribute]
196pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
197    lp_core::entry(args.into(), input.into()).into()
198}
199
200/// Creates a new instance of `esp_rtos::embassy::Executor` and declares an application entry point
201/// spawning the corresponding function body as an async task.
202///
203/// The following restrictions apply:
204///
205/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it
206///   can use to spawn additional tasks.
207/// * The function must be declared `async`.
208/// * The function must not use generics.
209/// * Only a single `main` task may be declared.
210///
211/// ## Examples
212/// Spawning a task:
213///
214/// ```rust,ignore
215/// #[esp_rtos::main]
216/// async fn main(_s: embassy_executor::Spawner) {
217///     // Function body
218/// }
219/// ```
220#[proc_macro_attribute]
221pub fn rtos_main(args: TokenStream, item: TokenStream) -> TokenStream {
222    rtos_main::main(args.into(), item.into()).into()
223}
224
225/// Attribute to declare the entry point of the program
226///
227/// The specified function will be called by the reset handler *after* RAM has
228/// been initialized. If present, the FPU will also be enabled before the
229/// function is called.
230///
231/// The type of the specified function must be `[unsafe] fn() -> !` (never
232/// ending function)
233///
234/// # Properties
235///
236/// The entry point will be called by the reset handler. The program can't
237/// reference to the entry point, much less invoke it.
238///
239/// # Examples
240///
241/// - Simple entry point
242///
243/// ```ignore
244/// #[main]
245/// fn main() -> ! {
246///     loop { /* .. */ }
247/// }
248/// ```
249#[proc_macro_attribute]
250pub fn blocking_main(args: TokenStream, input: TokenStream) -> TokenStream {
251    let f = syn::parse_macro_input!(input as syn::ItemFn);
252    blocking::main(args.into(), f).into()
253}
254
255/// Automatically implement the [Builder Lite] pattern for a struct.
256///
257/// This will create an `impl` which contains methods for each field of a
258/// struct, allowing users to easily set the values. The generated methods will
259/// be the field name prefixed with `with_`, and calls to these methods can be
260/// chained as needed.
261///
262/// ## Example
263///
264/// ```rust, ignore
265/// #[derive(Default)]
266/// enum MyEnum {
267///     #[default]
268///     A,
269///     B,
270/// }
271///
272/// #[derive(Default, BuilderLite)]
273/// #[non_exhaustive]
274/// struct MyStruct {
275///     enum_field: MyEnum,
276///     bool_field: bool,
277///     option_field: Option<i32>,
278/// }
279///
280/// MyStruct::default()
281///     .with_enum_field(MyEnum::B)
282///     .with_bool_field(true)
283///     .with_option_field(-5);
284/// ```
285///
286/// [Builder Lite]: https://matklad.github.io/2022/05/29/builder-lite.html
287#[proc_macro_derive(BuilderLite, attributes(builder_lite))]
288pub fn builder_lite_derive(item: TokenStream) -> TokenStream {
289    builder::builder_lite_derive(item.into()).into()
290}
291
292/// Print a build error and terminate the process.
293///
294/// It should be noted that the error will be printed BEFORE the main function
295/// is called, and as such this should NOT be thought analogous to `println!` or
296/// similar utilities.
297///
298/// ## Example
299///
300/// ```rust, ignore
301/// esp_hal_procmacros::error! {"
302/// ERROR: something really bad has happened!
303/// "}
304/// // Process exits with exit code 1
305/// ```
306#[proc_macro]
307pub fn error(input: TokenStream) -> TokenStream {
308    alert::do_alert(termcolor::Color::Red, input);
309    panic!("Build failed");
310}
311
312/// Print a build warning.
313///
314/// It should be noted that the warning will be printed BEFORE the main function
315/// is called, and as such this should NOT be thought analogous to `println!` or
316/// similar utilities.
317///
318/// ## Example
319///
320/// ```rust,no_run
321/// esp_hal_procmacros::warning! {"
322/// WARNING: something unpleasant has happened!
323/// "};
324/// ```
325#[proc_macro]
326pub fn warning(input: TokenStream) -> TokenStream {
327    alert::do_alert(termcolor::Color::Yellow, input)
328}
329
330macro_rules! unwrap_or_compile_error {
331    ($($x:tt)*) => {
332        match $($x)* {
333            Ok(x) => x,
334            Err(e) => {
335                return e.into_compile_error()
336            }
337        }
338    };
339}
340
341pub(crate) use unwrap_or_compile_error;