Skip to content

Commit f4ae764

Browse files
committed
added periodic print task
1 parent 9828bf6 commit f4ae764

1 file changed

Lines changed: 187 additions & 1 deletion

File tree

docs/tock_workshop/index.md

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ We also need to add our print command, on command number `1`. For serial debug p
205205

206206
With our capsule implementation done, we need to add it to the board's configuration. We need to add a field for the capsule in the board's `Cy8cproto0624343w` structure.
207207

208-
```rust title="boards/cy8cproto_62_4343_w/main.rs"
208+
```rust title="boards/cy8cproto_62_4343_w/src/main.rs"
209209
/// Supported drivers by the platform
210210
pub struct Cy8cproto0624343w {
211211
// ... Previous lines removed for simplicity
@@ -312,3 +312,189 @@ int main(void) {
312312
}
313313
}
314314
```
315+
316+
To test the application, run the `make` command in the example's root directory, change the `APP` Makefile variable in the board's directory and run `make program`.
317+
318+
### Periodic Print
319+
320+
We want to modify the capsule now, so that it prints a message every second, without the application's intervention. First we must modify the `Mock` capsule structure, to include a reference to an *"Alarm source"*. To do so, we should make the structure generic over any implementor of the `hil`
321+
322+
```rust title="capsules/extra/src/mock.rs"
323+
use kernel::hil::time;
324+
325+
pub struct MockCapsule<'a, A: time::Alarm<'a>> {
326+
alarm: &'a A,
327+
}
328+
329+
impl<'a, A: time::Alarm<'a>> MockCapsule<'a, A> {
330+
pub fn new(alarm: &'a A) -> Self {
331+
Self { alarm }
332+
}
333+
}
334+
335+
// ...
336+
```
337+
338+
:::note `impl` block
339+
Do not forget to change the `SyscallDriver` implementation block to use the newly added generic and lifetime.
340+
:::
341+
342+
#### Tock `Alarm` design
343+
344+
Tock is asynchronous by design, and any time consuming operation, such as adding delays in code, must not block the execution of the kernel. Non-blocking delays using a callback based mechanism, where the piece of code that must await a period of time arms an alarm using the interface exposed by the `time::Alarm` trait, and the underlying alarm will call a previously defined function at the expiration moment. The function to be invoked is specified by implementing the `time::AlarmClient`, and is the sole method `alarm`.
345+
346+
In our case, the implementation is straight forward. The capsule will print a message, and then re-arm the alarm:
347+
348+
```rust title="capsules/extra/src/mock.rs"
349+
use kernel::hil::time::ConvertTicks;
350+
351+
// ...
352+
353+
impl<'a, A: time::Alarm<'a>> MockCapsule<'a, A> {
354+
pub fn new(alarm: &'a A) -> Self { /* ... */}
355+
356+
// We must also add an `init` function to start this cycle.
357+
pub fn init(&'a self) {
358+
let dt = self.alarm.ticks_from_seconds(1);
359+
self.alarm.set_alarm(self.alarm.now(), dt);
360+
}
361+
}
362+
363+
impl<'a, A: time::Alarm<'a>> AlarmClient for MockCapsule<'a, A> {
364+
fn alarm(&self) {
365+
kernel::debug!("Periodic \"hi\" message");
366+
let dt = self.alarm.ticks_from_seconds(1);
367+
self.alarm.set_alarm(self.alarm.now(), dt);
368+
}
369+
}
370+
```
371+
372+
#### `Component` system
373+
374+
In Tock, initializing a board mainly consists of three steps:
375+
376+
1. Setting any MCU-specific configurations necessary for the MCU to operate correctly.
377+
2. Statically declaring memory for various kernel resources (i.e. capsules) and configuring the capsules correctly.
378+
3. Loading processes, configuring the core kernel, and starting the kernel.
379+
380+
Components are designed to simplify the second step (configuring capsules) while also reducing the chance for misconfiguration or other setup errors. A component encapsulates peripheral-specific and capsule-specific initialization for the Tock kernel in a factory method, which reduces repeated code and simplifies the boot sequence.
381+
382+
The `Component` trait encapsulates all of the initialization and configuration of a kernel extension inside the `Component::finalize()` function call. The `Component::Output` type defines what type this component generates. Note that instantiating a component does not instantiate the underlying `Component::Output` type; instead, the memory is statically allocated and provided as an argument to the `Component::finalize()` method, which correctly initializes the memory to instantiate the `Component::Output` object. If instantiating and initializing the `Component::Output` type requires parameters, these should be passed in the component's `new()` function.
383+
384+
Using a component is as follows:
385+
386+
```rust
387+
let obj = CapsuleComponent::new(configuration, required_hw)
388+
.finalize(capsule_component_static!());
389+
```
390+
391+
All required resources and configuration is passed via the constructor, and all required static memory is defined by the `[name]_component_static!()` macro and passed to the `finalize()` method.
392+
393+
As mentioned before, the capsule will need an alarm source. Most microcontrollers do not have an abundance of hardware sources to fulfill the needs of every scenario, so there is a need to multiplex one or more time-sources to be able to configure multiple alarms. Tock has support for this through `VirtualMuxAlarms` which act as alarm sources but all multiplex a single `MuxAlarm`, backed by a board's peripheral.
394+
395+
The component must receive a **static** reference to a generic `MuxAlarm`, and should create and setup a new virtual alarm instance, whose client must be the mock capsule it will generate. As a result, the component will need a static memory regions (`StaticInput` type) for both the `MockCapsule` and the `VirtualMuxAlarm`.
396+
397+
```rust title="boards/components/src/mock.rs"
398+
pub struct MockCapsuleComponent<A: 'static + Alarm<'static>> {
399+
alarm_mux: &'static MuxAlarm<'static, A>,
400+
}
401+
402+
impl<A: 'static + Alarm<'static>> MockCapsuleComponent<A> {
403+
pub fn new(alarm_mux: &'static MuxAlarm<'static, A>) -> Self {
404+
Self { alarm_mux }
405+
}
406+
}
407+
408+
impl<A: 'static + Alarm<'static>> Component for MockCapsuleComponent<A> {
409+
type StaticInput = (
410+
&'static mut MaybeUninit<VirtualMuxAlarm<'static, A>>,
411+
&'static mut MaybeUninit<MockCapsule<'static, VirtualMuxAlarm<'static, A>>>,
412+
);
413+
414+
type Output = &'static MockCapsule<'static, VirtualMuxAlarm<'static, A>>;
415+
416+
fn finalize(self, static_memory: Self::StaticInput) -> Self::Output {
417+
let virtual_alarm = static_memory.0.write(VirtualMuxAlarm::new(self.alarm_mux));
418+
virtual_alarm.setup();
419+
420+
let mock = static_memory.1.write(MockCapsule::new(virtual_alarm));
421+
virtual_alarm.set_alarm_client(mock);
422+
423+
mock
424+
}
425+
}
426+
```
427+
428+
The allocation of the memory segments is usually done through a marco. It is out of this workshop's scope to dive into writing macros, but the macro bellow takes a `type` that must implement the `hil::time::Alarm` trait and returns a tuple of static mutable references to `MaybeUninit` wrappers of the `VirtualMuxAlarm` nad the `MockCapsule`.
429+
430+
```rust title="boards/components/src/mock.rs"
431+
// ...
432+
433+
#[macro_export]
434+
macro_rules! mock_component_static {
435+
($A:ty $(,)?) => {{
436+
let virtual_alarm = kernel::static_buf!(
437+
capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>
438+
);
439+
440+
let mock = kernel::static_buf!(
441+
capsules_extra::mock::MockCapsule<'static, VirtualMuxAlarm<'static, $A>>
442+
);
443+
444+
(virtual_alarm, mock)
445+
};};
446+
}
447+
```
448+
449+
:::note `MaybeUninit`
450+
`MaybeUninit<T>` in Rust is a special wrapper type that allows you to safely work with memory that has not been initialized yet. Normally, Rust enforces that all variables are fully initialized before use to maintain memory safety. However, some situations require allocating memory first and filling it later.
451+
:::
452+
453+
#### Refactor board's configuration
454+
455+
Because we made fundamental changes to the capsule, we need to make a few modifications in order to run the kernel.
456+
457+
```rust title="boards/cy8cproto_62_4343_w/src/main.rs"
458+
/// Supported drivers by the platform
459+
pub struct Cy8cproto0624343w {
460+
// ...
461+
systick: cortexm0p::systick::SysTick,
462+
// highlight-start
463+
mock_capsule: &'static capsules_extra::mock::MockCapsule<
464+
'static,
465+
VirtualMuxAlarm<'static, psoc62xa::tcpwm::Tcpwm0<'static>>,
466+
>,
467+
// highlight-end
468+
}
469+
470+
// ...
471+
472+
pub unsafe fn main() {
473+
// ...
474+
475+
// highlight-start
476+
let mock_capsule = components::mock::MockCapsuleComponent::new(mux_alarm)
477+
.finalize(components::mock_component_static!(psoc62xa::tcpwm::Tcpwm0));
478+
mock_capsule.init();
479+
// highlight-end
480+
481+
let cy8cproto0624343w = Cy8cproto0624343w {
482+
// ...
483+
gpio,
484+
// Currently, the CPU runs at 8MHz, that being the frequency of the IMO.
485+
systick: cortexm0p::systick::SysTick::new_with_calibration(8_000_000),
486+
// highlight-next-line
487+
mock_capsule
488+
};
489+
490+
// ...
491+
492+
}
493+
```
494+
495+
To test our modifications, as now we no longer need an application, run:
496+
497+
```shell
498+
probe-rs erase --chip CY8C624ABZI-S2D44
499+
make flash
500+
```

0 commit comments

Comments
 (0)