-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compile-time MPSSE command array construction macro #41
Conversation
Force pushed to rebase onto the latest |
I just realized you can also leverage macro shadowing in a user crate to do command abstractions. I could include this in the docs and the following test if you think it would be a valuable mention. #[test]
fn user_abstracted_macro() {
macro_rules! mpsse {
// Practical abstraction for SPI devices.
(@intern $passthru:tt cs_low(); $($tail:tt)*) => {
mpsse!(@intern $passthru set_gpio_lower(0x0, 0xb); $($tail)*);
};
(@intern $passthru:tt cs_high(); $($tail:tt)*) => {
mpsse!(@intern $passthru set_gpio_lower(0x8, 0xb); $($tail)*);
};
// Everything else handled by crate implementation.
($($tokens:tt)*) => {
libftd2xx::mpsse!($($tokens)*)
};
}
mpsse! {
const (DATA, DATA_READ_LEN) = {
wait_on_io_high();
cs_low();
wait_on_io_low();
const DATA_RANGE = clock_data(ClockData::MsbPosIn, [11, 22, 33, 44]);
cs_high();
send_immediate();
};
}
assert_eq!(DATA.len(), 16);
assert_eq!(
DATA,
[
MpsseCmd::WaitOnIOHigh as u8,
MpsseCmd::SetDataBitsLowbyte as u8,
0x0,
0xb,
MpsseCmd::WaitOnIOLow as u8,
ClockData::MsbPosIn as u8,
4 as u8,
0 as u8,
11 as u8,
22 as u8,
33 as u8,
44 as u8,
MpsseCmd::SetDataBitsLowbyte as u8,
0x8,
0xb,
MpsseCmd::SendImmediate as u8,
]
);
assert_eq!(DATA_READ_LEN, 4);
assert_eq!(DATA_RANGE, 0..4);
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a while to read and understand all that macro code, picked up a few new things along the way, pretty elegant!
I added comments for a few minor changes, but otherwise looks good to me.
Just saw this after the review; that is really useful and I think it would help some people out! |
I highly recommend looking through the examples in https://danielkeep.github.io/tlborm/book/ there are all kinds of useful patterns you should be aware of when designing a declarative macro. I'll get on implementing those changes in a bit here.
Should I go ahead and also add the following section to the macro doc? /// # User Abstractions
///
/// With macro shadowing, it is possible to extend the macro with additional rules for abstract,
/// device-specific commands.
///
/// Comments within the implementation of this macro contain hints on how to implement these rules.
///
/// For example, a SPI device typically delineates transfers with the CS line. Fundamental
/// commands like `cs_high` and `cs_low` can be implmented this way, along with other
/// device-specific abstractions.
///
/// ```no_run
/// # use libftd2xx::mpsse;
/// macro_rules! mpsse {
/// // Practical abstraction of CS line for SPI devices.
/// (@intern $passthru:tt cs_low(); $($tail:tt)*) => {
/// mpsse!(@intern $passthru set_gpio_lower(0x0, 0xb); $($tail)*);
/// };
/// (@intern $passthru:tt cs_high(); $($tail:tt)*) => {
/// mpsse!(@intern $passthru set_gpio_lower(0x8, 0xb); $($tail)*);
/// };
///
/// // Hypothetical device-specific command. Leverages both user and libftd2xx commands.
/// (@intern $passthru:tt
/// const $idx_id:ident = command_42([$($data:expr),* $(,)*]);
/// $($tail:tt)*) => {
/// mpsse!(@intern $passthru
/// cs_low();
/// const $idx_id = clock_data(::libftd2xx::ClockData::MsbPosIn, [0x42, $($data,)*]);
/// cs_high();
/// $($tail)*);
/// };
///
/// // Everything else handled by libftd2xx crate implementation.
/// ($($tokens:tt)*) => {
/// ::libftd2xx::mpsse!($($tokens)*);
/// };
/// }
///
/// mpsse! {
/// const (DATA, DATA_READ_LEN) = {
/// wait_on_io_high();
/// const COMMAND_42_RESULT_RANGE = command_42([11, 22, 33]);
/// send_immediate();
/// };
/// }
/// ``` |
Yup, looks good, though if it can run I would remove the |
Hmm, that CI failure is curious. I get a passing result when running I wonder if there's a macro shadowing bug in the CI compiler version? EDIT: Yes, when I update to |
Nice find! I get it locally on 1.52.1 as well:
|
It works in |
I have a better macro implementation that's more token hygenic. It does a better job of following the push-down accumulation pattern where the input and output tokens are isolated from each other by the syntax (rather than the shift-append approach). I also got rid of
This guarantees that all input statements are consumed and the macro does not terminate early (without an error at least). It also makes it possible to shadow-override the recursion termination rule. I also put the CirrusNeptune/libftd2xx-rs@mpsse-macro...CirrusNeptune:mpsse-macro-v2 I'm working on a proof of concept for the CC1101 RF module. The I do not think it would be a good idea to provide read/write functionality directly in the libftd2xx macro because there are too many arbitrary details that are best left to the user (expansion scope, error handling, logging/tracing, data types, etc...). Instead maybe a sort of example shadow macro in the docs would be a good idea. #[test]
fn all_commands_read() {
let mut ftdi = MockFtdi::new();
mpsse! {
ftdi <=> {
cs_high();
let sres_status = sres();
let sidle_status = sidle();
};
}
assert_eq!(sres_status.chip_rdy_n(), false);
assert_eq!(sres_status.state(), State::Idle);
assert_eq!(sres_status.fifo_bytes_available(), 0);
assert_eq!(sidle_status.chip_rdy_n(), false);
assert_eq!(sidle_status.state(), State::Idle);
assert_eq!(sidle_status.fifo_bytes_available(), 0);
assert_eq!(ftdi.write_data, [
MpsseCmd::SetDataBitsLowbyte as u8,
0x8 as u8,
0xb as u8,
MpsseCmd::SetDataBitsLowbyte as u8,
0x0 as u8,
0xb as u8,
ClockBits::MsbPosIn as u8,
0x8 as u8,
Command::SRES as u8,
MpsseCmd::SetDataBitsLowbyte as u8,
0x8 as u8,
0xb as u8,
MpsseCmd::SetDataBitsLowbyte as u8,
0x0 as u8,
0xb as u8,
ClockBits::MsbPosIn as u8,
0x8 as u8,
Command::SIDLE as u8,
MpsseCmd::SetDataBitsLowbyte as u8,
0x8 as u8,
0xb as u8,
MpsseCmd::SendImmediate as u8,
]);
assert_eq!(ftdi.read_len, 2);
} Here are the non-generated parts of the macro if you're curious: |
May I go ahead merge the v2 branch into this PR? |
Yup, looks good to me! |
I made a really dumb error and didn't subtract 1 from any of the data clocking implementations. Really should have tested with hardware rather than theoretical mock devices. I'll push a fix in a bit here. |
Good catch, I should have tested on live hardware too 🙂 |
Uses distinct token trees for input, output, and passthru
ad29e8a
to
8d07f97
Compare
Rebased onto main |
Looks like 1.53 is coming in two days so we can get this merged then 👍 Someone finally made a website to track this: https://www.whatrustisit.com/ its everything I ever wanted 😄 |
Rust 1.53 is out! I kicked off CI again, looks like everything is passing. I will test it out with a physical device this weekend and merge it in then. |
Sounds good! For reference it looks like this is the related issue: rust-lang/rust#84195 |
For situations where dynamic MPSSE command data layout is not necessary, a declarative macro may be used to safely construct a valid command array at compile-time.
This macro provides implementations of all commands except for
set_clock
. It parses a rust-like syntax with additional conveniences (i.e.const
bindings have fixed-length array types specified automatically).Additional details are provided in the doc comment for the macro.