From 5a64d9ba8f83a5415a49e942a7535e535df14d52 Mon Sep 17 00:00:00 2001 From: Davo Date: Tue, 7 Jan 2025 10:40:12 -0600 Subject: [PATCH] feat: add support for internal temperature sensor (tsens) for esp32c6 and esp32c3 (#2875) * feat: add basic support for temperature sensor (tsens) for esp32c6 * feat: add basic support for temperature sensor (tsens) for esp32c3 * feat: add configurable clock source for temperature sensor * feat: add Temperature struct to avoid enforcing usage of floats - Also add misc derives to multiple structs - Add power_up / power_down methods to TemperatureSensor - Enable ApbSarAdc via PeripheralGuard * fix: move tsens module to unstable module list --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/lib.rs | 2 + esp-hal/src/soc/esp32c3/peripherals.rs | 1 + esp-hal/src/soc/esp32c6/peripherals.rs | 1 + esp-hal/src/system.rs | 36 +++++ esp-hal/src/tsens.rs | 189 +++++++++++++++++++++++++ esp-metadata/devices/esp32c3.toml | 1 + esp-metadata/devices/esp32c6.toml | 1 + qa-test/src/bin/temperature_sensor.rs | 33 +++++ 9 files changed, 265 insertions(+) create mode 100644 esp-hal/src/tsens.rs create mode 100644 qa-test/src/bin/temperature_sensor.rs diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 9613fcabfc8..3b4ec8961b0 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -54,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `AnyPin::steal(pin_number)` (#2854) - `adc::{AdcCalSource, Attenuation, Resolution}` now implement `Hash` and `defmt::Format` (#2840) - `rtc_cntl::{RtcFastClock, RtcSlowClock, RtcCalSel}` now implement `PartialEq`, `Eq`, `Hash` and `defmt::Format` (#2840) +- Added `tsens::TemperatureSensor` peripheral for ESP32C6 and ESP32C3 (#2875) ### Changed diff --git a/esp-hal/src/lib.rs b/esp-hal/src/lib.rs index 53beb9cac49..1230738192e 100644 --- a/esp-hal/src/lib.rs +++ b/esp-hal/src/lib.rs @@ -267,6 +267,8 @@ unstable_module! { pub mod touch; #[cfg(trace0)] pub mod trace; + #[cfg(tsens)] + pub mod tsens; #[cfg(any(twai0, twai1))] pub mod twai; #[cfg(usb_device)] diff --git a/esp-hal/src/soc/esp32c3/peripherals.rs b/esp-hal/src/soc/esp32c3/peripherals.rs index 00e6548a294..10180d39077 100644 --- a/esp-hal/src/soc/esp32c3/peripherals.rs +++ b/esp-hal/src/soc/esp32c3/peripherals.rs @@ -52,6 +52,7 @@ crate::peripherals! { SW_INTERRUPT <= virtual, TIMG0 <= TIMG0, TIMG1 <= TIMG1, + TSENS <= virtual, TWAI0 <= TWAI0, UART0 <= UART0, UART1 <= UART1, diff --git a/esp-hal/src/soc/esp32c6/peripherals.rs b/esp-hal/src/soc/esp32c6/peripherals.rs index 666e7a252a6..6ae4c7c535d 100644 --- a/esp-hal/src/soc/esp32c6/peripherals.rs +++ b/esp-hal/src/soc/esp32c6/peripherals.rs @@ -80,6 +80,7 @@ crate::peripherals! { TIMG0 <= TIMG0, TIMG1 <= TIMG1, TRACE0 <= TRACE, + TSENS <= virtual, TWAI0 <= TWAI0, TWAI1 <= TWAI1, UART0 <= UART0, diff --git a/esp-hal/src/system.rs b/esp-hal/src/system.rs index b8762146270..38c2459cc9d 100755 --- a/esp-hal/src/system.rs +++ b/esp-hal/src/system.rs @@ -132,6 +132,9 @@ pub enum Peripheral { /// Systimer peripheral. #[cfg(systimer)] Systimer, + /// Temperature sensor peripheral. + #[cfg(tsens)] + Tsens, } impl Peripheral { @@ -398,6 +401,10 @@ impl PeripheralClockControl { Peripheral::Systimer => { perip_clk_en0.modify(|_, w| w.systimer_clk_en().bit(enable)); } + #[cfg(tsens)] + Peripheral::Tsens => { + perip_clk_en1.modify(|_, w| w.tsens_clk_en().bit(enable)); + } } } @@ -610,6 +617,16 @@ impl PeripheralClockControl { perip_rst_en0.modify(|_, w| w.systimer_rst().set_bit()); perip_rst_en0.modify(|_, w| w.systimer_rst().clear_bit()); } + #[cfg(all(tsens, esp32c6))] + Peripheral::Tsens => { + perip_rst_en0.modify(|_, w| w.tsens_rst().set_bit()); + perip_rst_en0.modify(|_, w| w.tsens_rst().clear_bit()); + } + #[cfg(all(tsens, esp32c3))] + Peripheral::Tsens => { + perip_rst_en1.modify(|_, w| w.tsens_rst().set_bit()); + perip_rst_en1.modify(|_, w| w.tsens_rst().clear_bit()); + } }); } } @@ -778,6 +795,16 @@ impl PeripheralClockControl { .systimer_conf() .modify(|_, w| w.systimer_clk_en().bit(enable)); } + #[cfg(tsens)] + Peripheral::Tsens => { + system + .tsens_clk_conf() + .modify(|_, w| w.tsens_clk_en().bit(enable)); + + system + .tsens_clk_conf() + .modify(|_, w| w.tsens_clk_sel().bit(enable)); + } } } @@ -977,6 +1004,15 @@ impl PeripheralClockControl { .systimer_conf() .modify(|_, w| w.systimer_rst_en().clear_bit()); } + #[cfg(tsens)] + Peripheral::Tsens => { + system + .tsens_clk_conf() + .modify(|_, w| w.tsens_rst_en().set_bit()); + system + .tsens_clk_conf() + .modify(|_, w| w.tsens_rst_en().clear_bit()); + } } } } diff --git a/esp-hal/src/tsens.rs b/esp-hal/src/tsens.rs new file mode 100644 index 00000000000..6fe5cfc4623 --- /dev/null +++ b/esp-hal/src/tsens.rs @@ -0,0 +1,189 @@ +//! # Temperature Sensor (tsens) +//! +//! ## Overview +//! +//! The Temperature Sensor peripheral is used to measure the internal +//! temperature inside the chip. The voltage is internally converted via an ADC +//! into a digital value, and has a measuring range of –40 °C to 125 °C. +//! The temperature value depends on factors like microcontroller clock +//! frequency or I/O load. Generally, the chip’s internal temperature is higher +//! than the operating ambient temperature. +//! +//! It is recommended to wait a few hundred microseconds after turning it on +//! before measuring, in order to allow the sensor to stabilize. +//! +//! ## Configuration +//! +//! The temperature sensor can be configured with different clock sources. +//! +//! ## Examples +//! +//! The following example will measure the internal chip temperature every +//! second, and print it +//! +//! ```rust, no_run +#![doc = crate::before_snippet!()] +//! # use esp_hal::tsens::{TemperatureSensor, Config}; +//! # use esp_hal::delay::Delay; +//! +//! let temperature_sensor = TemperatureSensor::new( +//! peripherals.TSENS, +//! Config::default() +//! ).unwrap(); +//! let delay = Delay::new(); +//! delay.delay_micros(200); +//! loop { +//! let temp = temperature_sensor.get_temperature(); +//! println!("Temperature: {:.2}°C", temp.to_celcius()); +//! delay.delay_millis(1_000); +//! } +//! # } +//! ``` +//! +//! ## Implementation State +//! +//! - Temperature calibration range is not supported +//! - Interrupts are not supported + +use crate::{ + peripheral::{Peripheral, PeripheralRef}, + peripherals::TSENS, + system::GenericPeripheralGuard, +}; + +/// Clock source for the temperature sensor +#[derive(Debug, Clone, Default, PartialEq, Eq, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClockSource { + /// Use RC_FAST clock source + RcFast, + /// Use XTAL clock source + #[default] + Xtal, +} + +/// Temperature sensor configuration +#[derive(Debug, Clone, Default, PartialEq, Eq, Copy, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Config { + clock_source: ClockSource, +} + +/// Temperature sensor configuration error +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] +#[non_exhaustive] +pub enum ConfigError {} + +/// Temperature value +/// This struct stores the raw ADC value, and can be used to calculate the +/// temperature in Celsius using the formula: +/// `(raw_value * 0.4386) - (offset * 27.88) - 20.52` +#[derive(Debug)] +pub struct Temperature { + /// Raw ADC value + pub raw_value: u8, + + /// Offset value - depends on the temperature range configured + pub offset: i8, +} + +impl Temperature { + /// Create a new temperature value + #[inline] + pub fn new(raw_value: u8, offset: i8) -> Self { + Self { raw_value, offset } + } + + /// Get the temperature in Celsius + #[inline] + pub fn to_celsius(&self) -> f32 { + (self.raw_value as f32) * 0.4386 - (self.offset as f32) * 27.88 - 20.52 + } + + /// Get the temperature in Fahrenheit + #[inline] + pub fn to_fahrenheit(&self) -> f32 { + let celsius = self.to_celsius(); + (celsius * 1.8) + 32.0 + } + + /// Get the temperature in Kelvin + #[inline] + pub fn to_kelvin(&self) -> f32 { + let celsius = self.to_celsius(); + celsius + 273.15 + } +} + +/// Temperature sensor driver +#[derive(Debug)] +pub struct TemperatureSensor<'d> { + _peripheral: PeripheralRef<'d, TSENS>, + _tsens_guard: GenericPeripheralGuard<{ crate::system::Peripheral::Tsens as u8 }>, + _abp_saradc_guard: GenericPeripheralGuard<{ crate::system::Peripheral::ApbSarAdc as u8 }>, +} + +impl<'d> TemperatureSensor<'d> { + /// Create a new temperature sensor instance with configuration + /// The sensor will be automatically powered up + pub fn new( + peripheral: impl Peripheral

+ 'd, + config: Config, + ) -> Result { + crate::into_ref!(peripheral); + // NOTE: We need enable ApbSarAdc before enabling Tsens + let apb_saradc_guard = GenericPeripheralGuard::new(); + let tsens_guard = GenericPeripheralGuard::new(); + + let mut tsens = Self { + _peripheral: peripheral, + _tsens_guard: tsens_guard, + _abp_saradc_guard: apb_saradc_guard, + }; + tsens.apply_config(&config)?; + + tsens.power_up(); + + Ok(tsens) + } + + /// Power up the temperature sensor + pub fn power_up(&self) { + debug!("Power up"); + let abp_saradc = unsafe { &*crate::peripherals::APB_SARADC::PTR }; + abp_saradc.tsens_ctrl().modify(|_, w| w.pu().set_bit()); + } + + /// Power down the temperature sensor - useful if you want to save power + pub fn power_down(&self) { + let abp_saradc = unsafe { &*crate::peripherals::APB_SARADC::PTR }; + abp_saradc.tsens_ctrl().modify(|_, w| w.pu().clear_bit()); + } + + /// Change the temperature sensor configuration + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + let apb_saradc = unsafe { &*crate::peripherals::APB_SARADC::PTR }; + + // Set clock source + apb_saradc.tsens_ctrl2().write(|w| { + w.clk_sel() + .bit(matches!(config.clock_source, ClockSource::Xtal)) + }); + + Ok(()) + } + + /// Get the raw temperature value + #[inline] + pub fn get_temperature(&self) -> Temperature { + let abp_saradc = unsafe { &*crate::peripherals::APB_SARADC::PTR }; + + let raw_value = abp_saradc.tsens_ctrl().read().out().bits(); + + // TODO Address multiple temperature ranges and offsets + let offset = -1i8; + + Temperature::new(raw_value, offset) + } +} diff --git a/esp-metadata/devices/esp32c3.toml b/esp-metadata/devices/esp32c3.toml index 08a62907cb3..2b43f5cad1d 100644 --- a/esp-metadata/devices/esp32c3.toml +++ b/esp-metadata/devices/esp32c3.toml @@ -52,6 +52,7 @@ symbols = [ "phy", "bt", "wifi", + "tsens", # ROM capabilities "rom_crc_le", diff --git a/esp-metadata/devices/esp32c6.toml b/esp-metadata/devices/esp32c6.toml index 6b59d5e02ea..1b02a16c138 100644 --- a/esp-metadata/devices/esp32c6.toml +++ b/esp-metadata/devices/esp32c6.toml @@ -83,6 +83,7 @@ symbols = [ "wifi6", "ieee802154", "lp_core", + "tsens", # ROM capabilities "rom_crc_le", diff --git a/qa-test/src/bin/temperature_sensor.rs b/qa-test/src/bin/temperature_sensor.rs new file mode 100644 index 00000000000..8158af8d075 --- /dev/null +++ b/qa-test/src/bin/temperature_sensor.rs @@ -0,0 +1,33 @@ +//! This example uses the internal temperature sensor to measure the chip's +//! temperature + +//% CHIPS: esp32c6 esp32c3 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + delay::Delay, + entry, + tsens::{Config, TemperatureSensor}, +}; +use esp_println::println; + +#[entry] +fn main() -> ! { + esp_println::logger::init_logger_from_env(); + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let temperature_sensor = TemperatureSensor::new(peripherals.TSENS, Config::default()).unwrap(); + let delay = Delay::new(); + + // Wait for the sensor to stabilize + delay.delay_micros(200); + + loop { + let temp = temperature_sensor.get_temperature(); + println!("Temperature: {:.2}°C", temp.to_celsius()); + delay.delay_millis(1_000); + } +}