You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/tock_workshop/index.md
+187-1Lines changed: 187 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -205,7 +205,7 @@ We also need to add our print command, on command number `1`. For serial debug p
205
205
206
206
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.
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
+
usekernel::hil::time;
324
+
325
+
pubstructMockCapsule<'a, A:time::Alarm<'a>> {
326
+
alarm:&'aA,
327
+
}
328
+
329
+
impl<'a, A:time::Alarm<'a>> MockCapsule<'a, A> {
330
+
pubfnnew(alarm:&'aA) ->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
+
usekernel::hil::time::ConvertTicks;
350
+
351
+
// ...
352
+
353
+
impl<'a, A:time::Alarm<'a>> MockCapsule<'a, A> {
354
+
pubfnnew(alarm:&'aA) ->Self { /* ... */}
355
+
356
+
// We must also add an `init` function to start this cycle.
357
+
pubfninit(&'aself) {
358
+
letdt=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>> AlarmClientforMockCapsule<'a, A> {
364
+
fnalarm(&self) {
365
+
kernel::debug!("Periodic \"hi\" message");
366
+
letdt=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.
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`.
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`.
`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.
0 commit comments