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
+395Lines changed: 395 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -498,3 +498,398 @@ To test our modifications, as now we no longer need an application, run:
498
498
probe-rs erase --chip CY8C624ABZI-S2D44
499
499
make flash
500
500
```
501
+
502
+
### Temperature Sensor Capsule
503
+
504
+
For this task, we will have to read the ambient temperature using the onboard thermistor. A thermistor is a temperature-sensitive resistor in which resistance varies with temperature. The thermistor is in a voltage divider configuration which can be seen bellow:
The [Steinhart-Hart](https://en.wikipedia.org/wiki/Steinhart–Hart_equation) equation describes the resistance change of a semiconductor thermistor as related to its temperature. The following equation shows it to be a third-order logarithmic polynomial using three constants.
515
+
516
+
$$
517
+
\frac{1}{T_K} = A + B \cdot \ln(R) + C \cdot \ln(R)^3
518
+
$$
519
+
520
+
Where $A$, $B$ and $C$ are empirical constants, $R$ is the thermistor's resistance in $\Omega$ and $T_k$ is the temperature in kelvin.
521
+
522
+
Consulting the [board's manual](https://www.infineon.com/assets/row/public/documents/30/44/infineon-cy8cproto-062-4343w-psoc-6-wi-fi-bt-prototyping-kit-guide-usermanual-en.pdf), we can see that the voltage divider uses a `10K` resistor, so $R_{ref} = 10000\Omega$.
523
+
524
+
#### Current state
525
+
526
+
The underlying ADC peripheral implementation has two channels configured (`AdcChannel0` and `AdcChannel1`) to sample on demand the two voltage drops on the resistor and thermistor. Also, the four pins mentioned in the schematic are properly setup by the board configuration, in the `TEMPERATURE` section.
527
+
528
+
#### `TemperatureSensor` in Tock
529
+
530
+
Tock already has an implementation for a `SyscallDriver` capsule for reading the temperature. It can be found in the `capsules/extra/src/temperature.rs` module. It utilizes a trait for interacting with various temperature sensors called `TemperatureDriver`, that works in the same notification based paradigm as the `Alarm`, notifying the client when the temperature measurement is complete. Our goal will be to implement this trait for our new `Cy8cprotoThermistor` capsule.
531
+
532
+
As before, the first step will be to define our new module, `cy8cproto_thermistor.rs` and define the structure of our capsule. The capsule will need references to both `AdcChannel`s, and will need to hold a client reference, to notify them.
We need to implement the `TemperatureDriver` trait for our thermistor capsule. The implementation for the `set_client` method is straight-forward, and the `read_temperature` would, in theory, need to start the sampling of both channels.
impl<'a, A:adc::AdcChannel<'a>> adc::ClientforCy8cprotoThermistor<'a, A> {
566
+
fnsample_ready(&self, sample:u16) {
567
+
todo!()
568
+
}
569
+
}
570
+
```
571
+
572
+
This is where we encounter our first issue. Both channels will use the same interface, and therefore the same method implementation when the sampling is done. This means that the capsule has no way of distinguishing between the samples.
573
+
574
+
#### State machine design
575
+
576
+
One viable alternative would be implementing a state machine for sampling both channels and computing the temperature.
B -- Await sample result --> C{"Sample Thermistor"}
587
+
C -- Error --> D
588
+
C -- Await sample result --> F(["Report Temperature"])
589
+
F --> A
590
+
D --> A
591
+
```
592
+
593
+
We need to define the internal state of the capsule, and to do that, we will use an `enum`. We will also need to store the result of the resistor sample in capsule.
The implementation of the `AdcClient` sole method, `sample_ready` will have to take into account the current state. If the method is called after the first reading (`status` is `Status::AwaitingResistorReading`), the capsule must store the result and start the thermistor sampling. If the method is called after both readings are performed, then the capsule must perform the computations mentioned above, to report the measured temperature in hundredths of degrees Celsius (centiCelsius).
643
+
644
+
:::note Error handling
645
+
646
+
Handle all possible errors that the `sample` calls may yield by resetting both the thermistor state to `Status::Idle` and the `resistor_sample`, and reporting the error to the client.
647
+
648
+
```rust title="Reporting the result of a temperature reading"
In our case, the `temp_reading_result` will be the sample error.
653
+
:::
654
+
655
+
#### Computing the temperature
656
+
657
+
Based on the [Infineon documentation](https://www.infineon.com/assets/row/public/documents/30/42/infineon-an2017-psoc-1-temperature-measurement-with-thermistor-applicationnotes-en.pdf?fileId=8ac78c8c7cdc391c017d072814bc4c7b), the empirical constants' values for the thermistor used by our board are:
658
+
659
+
```rust
660
+
/// Reference Resistor value in Ohms
661
+
constR_REF:f64=10_000.0;
662
+
663
+
constA:f64=0.000891358;
664
+
constB:f64=0.000250618;
665
+
constC:f64=0.000000197;
666
+
```
667
+
668
+
To convert from the numerical values to voltages, we need to understand how ADCs work. Every ADC will have a resolution, which is the number of bits which can be used to store the sample result, and a sampling range which is the minimum-to-maximum input voltage it can measure. Our ADC has a resolution of 11 bits (based on the current configuration of the peripheral) and a input range of 0-3.3 Volts, meaning that the result can rage from `0`, which represents the 0V potential to `2047` ($2^{11} - 1$) which will represent 3V3.
669
+
670
+
Based on this, we can define a function that takes the discrete value of the sample and returns the voltage.
671
+
672
+
```rust
673
+
fnconvert(sample:u16) ->f64 {
674
+
((sampleasf64) *3.3) /2047.0
675
+
}
676
+
```
677
+
678
+
Our implementation of the `sample_ready` function should now be complete
As before, we need to ensure the capsule can be easily configured by implementing a new `Component`. You can name the module `cyc8cproto_thermistor.rs`.
716
+
717
+
We should start from the bottom up, considering what should be needed to instantiate this capsule. These are the `AdcChannel`s and an `MuxAdc`, to be able to multiplex an ADC peripheral to sample multiple channels.
adc_mux:&'staticcapsules_core::virtualizers::virtual_adc::MuxAdc<'static, A>,
733
+
thermistor_channel:A::Channel,
734
+
resistor_channel:A::Channel,
735
+
) ->Self {
736
+
Self {
737
+
adc_mux,
738
+
thermistor_channel,
739
+
resistor_channel,
740
+
}
741
+
}
742
+
}
743
+
```
744
+
745
+
The `finalize` implementation of the `Component` trait will need to create the two virtual `AdcDevices` that multiplex the peripheral, and therefore, the static memory needed must accommodate the two devices, and the thermistor capsule.
Fortunately, `libtock-c` already has a modular example that reads multiple sensors. This example can be found in the `examples/sensors` subdirectory. To enable the temperature readings, we must set the `temperature` variable.
875
+
876
+
```c title="examples/sensors/main.c"
877
+
// ...
878
+
#include<libtock/tock.h>
879
+
880
+
staticlibtock_alarm_t alarm;
881
+
staticbool light = false;
882
+
// highlight-next-line
883
+
staticbool temperature = true;
884
+
staticbool humidity = false;
885
+
staticbool ninedof = false;
886
+
staticbool ninedof_accel = false;
887
+
staticbool ninedof_mag = false;
888
+
staticbool ninedof_gyro = false;
889
+
staticbool proximity = false;
890
+
staticbool sound_pressure = false;
891
+
staticbool moisture = false;
892
+
staticbool rainfall = false;
893
+
```
894
+
895
+
Then, compile the application, and flash it along the kernel. Do not forget to update the `APP` variable in the board's `Makefile`.
0 commit comments