pub struct Components { /* private fields */ }
Expand description
Manages components.
§Abstract
Components are peripherals attached to the AVR - they allow to easily simulate external devices, such as shift registers or screens, without forcing you to think about those devices’ timings with respect to other attached peripherals.
For instance, let’s say that we’ve got a firmware that provides some UART
functionality, but at the same time it requires for PB1
and PB2
to be
toggled in regular intervals (say, because they are attached to watchdog).
Assuming PB1
has to be toggled each 5 ms and PB2
each 15 ms, we could
write a test such as this:
let mut avr = AvrTester::test();
avr.uart0().write([0x01, 0x02, 0x03]);
for cycle in 0.. {
// Keep the watchdog happy:
if cycle % 5 == 0 {
avr.pins().pb1().toggle();
}
if cycle % 15 == 0 {
avr.pins().pb2().toggle();
}
// Check if the response has arrived:
if let Some(response) = avr.uart0().try_read_byte() {
assert_eq!(0x06, response);
break;
}
avr.run_for_ms(1);
}
… but that approach not only scales poorly (imagine having to handle multiple devices, each with its own clock!), but also obfuscates the test - if we’re mostly interested in the UART part, then there shouldn’t be any reason to intertwine it with the pin-toggling.
Here come components - they are like background tasks that are polled after each AVR’s instruction:
let mut avr = AvrTester::test();
// Start the `PB1` toggler:
avr.components().add(async {
loop {
avr_rt().pins().pb1().toggle();
avr_rt().run_for_ms(5).await;
}
});
// Start the `PB2` toggler:
avr.components().add(async {
loop {
avr_rt().pins().pb2().toggle();
avr_rt().run_for_ms(15).await;
}
});
// Perform the test:
avr.uart0().write([0x01, 0x02, 0x03]);
avr.run_for_ms(100);
assert_eq!(Some(0x06), avr.uart0().try_read_byte());
Components are handy, because AvrTester automatically takes care of
their scheduling - we don’t have to worry about PB1
and PB2
’s timings
anymore: we just say “PB1 must be toggled every 5 ms”, “PB2 must be toggled
every 15 ms” and that’s it.
From AvrTester’s perspective, what happens here is basically:
fn run_for_ms(ms):
/* run() in a loop */
fn run():
simavr.run_one_instruction()
for component in components:
component.poll(simavr)
§Writing components
Writing components doesn’t differ that much from writing regular tests - the most important caveat is that components must be asynchronous, so that AvrTester knows when a component has finished its “clock cycle”.
This means that inside components you can’t access regular AvrTester
-
you have to call avr_rt()
, which returns AvrTesterAsync
with its
own set of functions that operate on pins.
Similarly, instead of calling thread::sleep()
you should invoke
avr_rt().run_for_ms(...)
.
§Examples
§PB2 = !PB1
This component implements a simple PB2 = !PB1
gate:
let mut avr = AvrTester::test();
avr.components().add(async {
loop {
let is_high = avr_rt().pins().pb1().is_high();
avr_rt().pins().pb2().set(!is_high);
avr_rt().run().await;
}
});
Implementations§
Source§impl Components
impl Components
Sourcepub fn add(
&mut self,
component: impl Future<Output = ()> + 'static,
) -> ComponentHandle
pub fn add( &mut self, component: impl Future<Output = ()> + 'static, ) -> ComponentHandle
Creates a new component and attaches it into the AVR.
See Components
for more details.