embedded_registers_derive/
lib.rs

1//! This crate provides a procedural macro for effortless definitions of registers
2//! in embedded device drivers.
3//!
4//! Currently, embedded-registers requires the use of `#![feature(generic_arg_infer)]`.
5//!
6//! # Attribute macro
7//!
8//! Registers are defined by adding `#[register(...)]` to the definition of a bondrewd bitfield.
9//! As a short reminder, bondrewd is another proc macro that allows you to define a bitfield structure,
10//! which is very handy when dealing with registers, where multiple values are often tightly packed bit-on-bit.
11//!
12//! The register attribute macro supports the following arguments:
13//!
14//! <table>
15//!   <tr>
16//!     <td>address</td>
17//!     <td>The virtual address associated to the register.</td>
18//!   </tr>
19//!   <tr>
20//!     <td>read</td>
21//!     <td>Add this if the register should be readable</td>
22//!   </tr>
23//!   <tr>
24//!     <td>write</td>
25//!     <td>Add this if the register should be writeable</td>
26//!   </tr>
27//! </table>
28//!
29//! Adding this attribute to a struct `Foo` will result in two types being defined:
30//! - `Foo` will become the register, essentially a byte array with the correct size that provides
31//!   getter and setter functions for the individual fields.
32//! - `FooBitfield` will become the underlying bondrewd bitfield, which may be used to construct
33//!   a register from scratch, or can be obtained via `Foo::read_all` if you want to unpack all values.
34//!
35//! This has the advantage that reading a register incurs no additional memory and CPU cost to unpack all
36//! values of the bitfield. You only pay for the members you actually access.
37//!
38//! # Simple Example
39//!
40//! This simple example defines the `DeviceId` register of an MCP9808. It has the virtual address
41//! `0b111 (0x7)`, uses big endian byte order with the first member of the struct positioned at the
42//! most significant bit, is 2 bytes in size and is read-only. The register definition
43//! automatically work with both sync and async code.
44//!
45//! ```
46//! #![feature(generic_arg_infer)]
47//! use embedded_registers::{register, i2c::{I2cDeviceAsync, I2cDeviceSync, codecs::OneByteRegAddrCodec}, RegisterInterfaceAsync, RegisterInterfaceSync, ReadableRegister};
48//!
49//! #[register(address = 0b111, mode = "r")]
50//! #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)]
51//! pub struct DeviceId {
52//!     device_id: u8,
53//!     revision: u8,
54//! }
55//!
56//! // sync:
57//! # async fn test<I>(mut i2c: I) -> Result<(), I::Error>
58//! # where
59//! #   I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType
60//! # {
61//! let mut dev = I2cDeviceSync::<_, _, OneByteRegAddrCodec>::new(i2c /* bus */, 0x24 /* i2c addr */);
62//! let reg = dev.read_register::<DeviceId>()?;
63//! # Ok(())
64//! # }
65//!
66//! // async:
67//! # async fn test_async<I>(mut i2c: I) -> Result<(), I::Error>
68//! # where
69//! #   I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType
70//! # {
71//! let mut dev = I2cDeviceAsync::<_, _, OneByteRegAddrCodec>::new(i2c /* bus */, 0x24 /* i2c addr */);
72//! let reg = dev.read_register::<DeviceId>().await?;
73//! # Ok(())
74//! # }
75//! ```
76//!
77//! # Complex Example
78//!
79//! A real-world application may involve describing registers with more complex layouts involving
80//! different data types or even enumerations. Luckily, all of this is fairly simple with bondrewd.
81//!
82//! We also make sure to annotate all fields with `#[register(default = ...)]` to allow
83//! easy reconstruction of the power-up defaults. Have a look at this excerpt
84//! of the Configuration register from the MCP9808:
85//!
86//! ```
87//! #![feature(generic_arg_infer)]
88//! # use defmt::{info, Format};
89//! use embedded_registers::{register, i2c::{I2cDeviceAsync, codecs::OneByteRegAddrCodec}, RegisterInterfaceAsync, ReadableRegister, WritableRegister};
90//! use bondrewd::BitfieldEnum;
91//!
92//! # #[allow(non_camel_case_types)]
93//! #[derive(BitfieldEnum, Clone, PartialEq, Eq, Debug, Format)]
94//! #[bondrewd_enum(u8)]
95//! pub enum Hysteresis {
96//!     Deg_0_0C = 0b00,
97//!     Deg_1_5C = 0b01,
98//!     Deg_3_0C = 0b10,
99//!     Deg_6_0C = 0b11,
100//! }
101//!
102//! #[derive(BitfieldEnum, Clone, PartialEq, Eq, Debug, Format)]
103//! #[bondrewd_enum(u8)]
104//! pub enum ShutdownMode {
105//!     Continuous = 0,
106//!     Shutdown = 1,
107//! }
108//!
109//! #[register(address = 0b001, mode = "rw")]
110//! #[bondrewd(read_from = "msb0", default_endianness = "be", enforce_bytes = 2)]
111//! pub struct Config {
112//!     // padding
113//!     #[bondrewd(bit_length = 5, reserve)]
114//!     #[allow(dead_code)]
115//!     reserved: u8,
116//!
117//!     /// Doc strings will also be shown on the respective read/write functions generated from this definition.
118//!     #[bondrewd(enum_primitive = "u8", bit_length = 2)]
119//!     #[register(default = Hysteresis::Deg_0_0C)]
120//!     pub hysteresis: Hysteresis,
121//!     /// All fields should be documented with information from the datasheet
122//!     #[bondrewd(enum_primitive = "u8", bit_length = 1)]
123//!     #[register(default = ShutdownMode::Continuous)]
124//!     pub shutdown_mode: ShutdownMode,
125//!
126//!     // ... all 16 bits must be filled
127//!     # #[bondrewd(bit_length = 8, reserve)]
128//!     # #[allow(dead_code)]
129//!     # reserved2: u8,
130//! }
131//!
132//! # async fn test<I>(mut i2c: I) -> Result<(), I::Error>
133//! # where
134//! #   I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType
135//! # {
136//! # let mut dev = I2cDeviceAsync::<_, _, OneByteRegAddrCodec>::new(i2c, 0x24);
137//! // This now allows us to read and write the register, while only
138//! // unpacking the fields we require:
139//! let mut reg = dev.read_register::<Config>().await?;
140//! info!("previous shutdown mode: {}", reg.read_shutdown_mode());
141//! reg.write_shutdown_mode(ShutdownMode::Shutdown);
142//! dev.write_register(&reg).await?;
143//!
144//! // If you want to get the full decoded bitfield, you can use either `read_all`
145//! // or `.into()`. If you need to unpack all fields anyway, this might be
146//! // more convenient as it allows you to access the bitfield members more ergonomically.
147//! //let bf: ConfigBitfield = reg.into();
148//! let mut bf = reg.read_all();
149//! info!("previous shutdown mode: {}", bf.shutdown_mode);
150//! bf.shutdown_mode = ShutdownMode::Shutdown;
151//! reg.write_all(bf);
152//! # Ok(())
153//! # }
154//! ```
155
156use darling::ast::NestedMeta;
157use darling::FromMeta;
158use proc_macro as pc;
159use proc_macro2::TokenStream;
160use quote::{format_ident, quote, ToTokens, TokenStreamExt};
161use syn::{spanned::Spanned, Expr, Type};
162
163#[derive(Debug, FromMeta)]
164#[darling(and_then = "Self::validate_mode")]
165struct RegisterArgs {
166    /// The address of the register
167    address: Expr,
168    /// The register mode (one of "r", "w", "rw")
169    #[darling(default)]
170    mode: String,
171    /// The default SPI codec for this register
172    #[darling(default)]
173    spi_codec: Option<Type>,
174    /// The default I2C codec for this register
175    #[darling(default)]
176    i2c_codec: Option<Type>,
177}
178
179impl RegisterArgs {
180    fn validate_mode(self) -> darling::Result<Self> {
181        match self.mode.as_str() {
182            "r" | "w" | "rw" => Ok(self),
183            _ => Err(darling::Error::custom("Unknown mode '{}'").with_span(&self.mode)),
184        }
185    }
186}
187
188#[proc_macro_attribute]
189pub fn register(args: pc::TokenStream, input: pc::TokenStream) -> pc::TokenStream {
190    match register_impl(args.into(), input.into()) {
191        Ok(result) => result.into(),
192        Err(e) => e.into_compile_error().into(),
193    }
194}
195
196fn register_impl(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
197    let args_span = args.span();
198    let args = RegisterArgs::from_list(&NestedMeta::parse_meta_list(args)?)?;
199
200    let input = syn::parse2::<syn::ItemStruct>(input)?;
201    if !input.attrs.iter().any(|x| x.path().is_ident("bondrewd")) {
202        return Err(syn::Error::new(
203            args_span,
204            "A register definition must also include a #[bondrewd()] bitfield spec",
205        ));
206    }
207
208    let ident = input.ident;
209    let debug_format_str = format!("{} ({{:?}}) => {{:?}}", ident);
210    let vis = input.vis;
211    let attrs: TokenStream = input.attrs.iter().map(ToTokens::to_token_stream).collect();
212    let docattrs: TokenStream = input
213        .attrs
214        .iter()
215        .filter(|x| x.path().is_ident("doc"))
216        .map(ToTokens::to_token_stream)
217        .collect();
218    let bitfield_ident = format_ident!("{}Bitfield", ident);
219
220    let mut forward_fns = quote! {};
221    let mut default_arms = Vec::new();
222    let mut filtered_fields = Vec::new();
223
224    for field in input.fields {
225        let mut filtered_field = field.clone();
226        let type_ident = field.ty;
227        let field_name = field
228            .ident
229            .ok_or_else(|| syn::Error::new(args_span, "A field contains a field without an identifier"))?;
230        let field_docattrs: TokenStream = field
231            .attrs
232            .iter()
233            .filter(|x| x.path().is_ident("doc"))
234            .map(ToTokens::to_token_stream)
235            .collect();
236
237        // Generate accessor and mutator methods for fields
238        let read_field_name = format_ident!("read_{field_name}");
239        let read_comment = format!("Retrieves the value of [`{bitfield_ident}::{field_name}`] from this register:");
240        let write_field_name = format_ident!("write_{field_name}");
241        let write_comment = format!("Updates the value of [`{bitfield_ident}::{field_name}`] in this register:");
242        let with_field_name = format_ident!("with_{field_name}");
243        let with_comment =
244            format!("Updates the value of [`{bitfield_ident}::{field_name}`] in this register and allows chaining:");
245
246        forward_fns = quote! {
247            #forward_fns
248
249            #[doc = #read_comment]
250            #[doc = ""]
251            #field_docattrs
252            #[inline]
253            pub fn #read_field_name(&self) -> #type_ident {
254                #bitfield_ident::#read_field_name(&self.data)
255            }
256
257            #[doc = #write_comment]
258            #[doc = ""]
259            #field_docattrs
260            #[inline]
261            pub fn #write_field_name(&mut self, #field_name: #type_ident) {
262                #bitfield_ident::#write_field_name(&mut self.data, #field_name)
263            }
264
265            #[doc = #with_comment]
266            #[doc = ""]
267            #field_docattrs
268            #[inline]
269            pub fn #with_field_name(mut self, #field_name: #type_ident) -> Self {
270                #bitfield_ident::#write_field_name(&mut self.data, #field_name);
271                self
272            }
273        };
274
275        // Look for #[register(default = value)]
276        let default_expr = field.attrs.iter().find_map(|attr| {
277            if attr.path().is_ident("register") {
278                // Parse the attribute arguments
279                attr.parse_args_with(
280                    syn::punctuated::Punctuated::<syn::MetaNameValue, syn::token::Comma>::parse_terminated,
281                )
282                .ok()
283                .and_then(|args| {
284                    args.into_iter().find_map(|meta| {
285                        if meta.path.is_ident("default") {
286                            return Some(meta.value.to_token_stream());
287                        }
288                        None
289                    })
290                })
291            } else {
292                None
293            }
294        });
295
296        // Determine the field's default value
297        let field_default = default_expr.unwrap_or_else(|| {
298            quote! { <#type_ident as ::core::default::Default>::default() }
299        });
300
301        default_arms.push(quote! {
302            #field_name: #field_default,
303        });
304
305        // Filter out #[register(...)] from attributes for re-emission
306        filtered_field.attrs.retain(|attr| !attr.path().is_ident("register"));
307        filtered_fields.push(filtered_field);
308    }
309
310    let read_all_comment = format!("Unpack all fields and return them as a [`{bitfield_ident}`]. If you don't need all fields, this is more expensive than just using the appropriate `read_*` functions directly.");
311    let write_all_comment = format!("Pack all fields in the given [`{bitfield_ident}`] representation. If you only want to write some fields, this is more expensive than just using the appropriate `write_*` functions directly.");
312    let address = args.address;
313
314    let is_read = matches!(args.mode.as_str(), "r" | "rw");
315    let is_write = matches!(args.mode.as_str(), "w" | "rw");
316
317    let spi_codec = args
318        .spi_codec
319        .unwrap_or_else(|| syn::parse_str::<syn::Type>("embedded_registers::spi::codecs::NoCodec").unwrap());
320
321    let i2c_codec = args
322        .i2c_codec
323        .unwrap_or_else(|| syn::parse_str::<syn::Type>("embedded_registers::i2c::codecs::NoCodec").unwrap());
324
325    let mut output = quote! {
326        #[derive(bondrewd::Bitfields, Clone, PartialEq, Eq, core::fmt::Debug, defmt::Format)]
327        #attrs
328        #vis struct #bitfield_ident {
329            #(#filtered_fields),*
330        }
331
332        impl Default for #bitfield_ident {
333            fn default() -> Self {
334                Self {
335                    #(#default_arms)*
336                }
337            }
338        }
339
340        #[derive(Copy, Clone, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
341        #[repr(transparent)]
342        #docattrs
343        pub struct #ident {
344            pub data: [u8; <#bitfield_ident as bondrewd::Bitfields<_>>::BYTE_SIZE],
345        }
346
347        impl #ident {
348            #forward_fns
349
350            #[inline]
351            #[doc = #write_all_comment]
352            pub fn new(value: #bitfield_ident) -> Self {
353                use bondrewd::Bitfields;
354                Self {
355                    data: value.into_bytes(),
356                }
357            }
358
359            #[inline]
360            #[doc = #read_all_comment]
361            pub fn read_all(&self) -> #bitfield_ident {
362                use bondrewd::Bitfields;
363                #bitfield_ident::from_bytes(self.data)
364            }
365
366            #[inline]
367            #[doc = #write_all_comment]
368            pub fn write_all(&mut self, value: #bitfield_ident) {
369                use bondrewd::Bitfields;
370                self.data = value.into_bytes();
371            }
372        }
373
374        impl Default for #ident {
375            fn default() -> Self {
376                use bondrewd::Bitfields;
377                Self {
378                    data: #bitfield_ident::default().into_bytes(),
379                }
380            }
381        }
382
383        impl core::fmt::Debug for #ident {
384            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
385                write!(
386                    f,
387                    #debug_format_str,
388                    self.data,
389                    #bitfield_ident::from(self)
390                )
391            }
392        }
393
394        impl defmt::Format for #ident {
395            fn format(&self, f: defmt::Formatter) {
396                defmt::write!(
397                    f,
398                    #debug_format_str,
399                    self.data,
400                    #bitfield_ident::from(self)
401                )
402            }
403        }
404
405        impl embedded_registers::Register for #ident {
406            type Bitfield = #bitfield_ident;
407            type SpiCodec = #spi_codec;
408            type I2cCodec = #i2c_codec;
409
410            const REGISTER_SIZE: usize = <#bitfield_ident as bondrewd::Bitfields<_>>::BYTE_SIZE;
411            const ADDRESS: u64 = #address;
412
413            #[inline]
414            fn data(&self) -> &[u8] {
415                &self.data
416            }
417
418            #[inline]
419            fn data_mut(&mut self) -> &mut [u8] {
420                &mut self.data
421            }
422        }
423
424        impl AsRef<#bitfield_ident> for #bitfield_ident {
425            #[inline]
426            fn as_ref(&self) -> &#bitfield_ident {
427                self
428            }
429        }
430
431        impl AsRef<#ident> for #ident {
432            #[inline]
433            fn as_ref(&self) -> &#ident {
434                self
435            }
436        }
437
438        impl From<&#ident> for #bitfield_ident {
439            #[inline]
440            fn from(val: &#ident) -> Self {
441                use bondrewd::Bitfields;
442                #bitfield_ident::from_bytes(val.data)
443            }
444        }
445
446        impl From<&#bitfield_ident> for #ident {
447            #[inline]
448            fn from(value: &#bitfield_ident) -> Self {
449                use bondrewd::Bitfields;
450                Self {
451                    data: value.clone().into_bytes(),
452                }
453            }
454        }
455    };
456
457    if is_read {
458        output.append_all(quote! {
459            impl embedded_registers::ReadableRegister for #ident {}
460        });
461    }
462
463    if is_write {
464        output.append_all(quote! {
465            impl embedded_registers::WritableRegister for #ident {}
466        });
467    }
468
469    Ok(output)
470}