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(®).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}