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