Internal Temperature Sensor

An example of this implementation can be found here: 3_temperature.rs.

✅ Preparations: have the board and the timer peripheral initialized in your code.

Before we start to work with an external sensor, where we would have to write a driver, we will access the board's internal temperature sensor first. We'll take a look at the HAL to learn more about how accessing peripherals works in detail and how methods work in Rust.

✅ Open the nrf-hal-common 0.11.1

✅ Open /src/temp.rs, the place where the communication with the boards temperature sensor is implemented.

The integrated temperature is a struct: pub struct Temp(TEMP). It needs to be public, so it can be called from the outside. TEMP is a type defined in the peripheral access crate (pac), it accesses the temperature sensor's register block. In the impl block are all the methods that are defined for Temp.

Methods are different from functions in that they are attached to objects. Let's look at them in detail:

pub fn new() takes TEMP as argument and returns Temp. The method takes ownership of the temperature sensor's register block.

✅ In order to be able to use Temp in your code, you have to bring it into scope first. Add the following lines to your code:


#![allow(unused)]
fn main() {
use nrf52840_hal::{
    self as hal,
    Temp,
    Timer,
};
}

✅ Take ownership of the temperature sensor's register block by calling the new method, using board.TEMP as argument. The variable needs to be mutable.


#![allow(unused)]
fn main() {
let mut temp = Temp::new(board.TEMP);
}

Now that we have an instance of the temperature sensor, we can take a measurement.

✅ Go back to temp.rs in the HAL code.

fn measure() takes a mutable reference to self as an argument. self is the instance of the temperature sensor that was created with fn new(). The method will stop a measurement, if one has already been started, starts a new measurement and block the program until it has completed the measurement and then returns a fixed point number I30F2. The second option is starting a measurement with fn start_measurement() and reading the measurement with fn read() which works in a non-blocking way. A measurement is started or stopped by writing to the register.

We'll stick with the blocking method fn measure() for now.

✅ In your code, add a line that takes a measurement, and one that logs the temperature value.


#![allow(unused)]
fn main() {
let temperature = temp.measure();
defmt::info!("{:?}", temperature);
}

The syntax reflects that methods are attached to objects: The argument &mut self refers to the object in front of the dot, and the parenthesis remain empty.

If you run the code now, you'll run into a compiler error, because the trait defmt::Format is not implemented for I30F2, the return type of fn measure().

✅ Add another method to_num() behind fn measure(). This method casts the fix point number into an f32. In order to be displayable, the type needs to be indicated in the format string.


#![allow(unused)]
fn main() {
let temperature: f32 = temp.measure().to_num();
defmt::info!("{=f32} °C", temperature);
}

✅ Initialize a loop that measures and displays the temperature every 60 seconds.