-
Notifications
You must be signed in to change notification settings - Fork 377
feat(example): Add LP I2C Example (SHT30 temp. and humid. sensor) #4679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
993a990
19b42f7
dc6f427
2e0cf06
91bb151
924f041
a9dd7c7
473ec0c
0434643
b618d92
c9159f0
4fd3b7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| //! Uses `LP_I2C` and reads temperature and humidity from SHT30 sensor. | ||
| //! | ||
| //! This code runs on the LP core and writes the sensor data to LP memory, | ||
| //! which can then be accessed by the HP core. | ||
| //! | ||
| //! The following wiring is assumed: | ||
| //! - SDA => GPIO6 | ||
| //! - SCL => GPIO7 | ||
|
|
||
| //% CHIPS: esp32c6 | ||
|
|
||
| #![no_std] | ||
| #![no_main] | ||
|
|
||
| use embedded_hal::delay::DelayNs; | ||
| use esp_lp_hal::{ | ||
| delay::Delay, | ||
|
||
| i2c::{Error, LpI2c}, | ||
| prelude::*, | ||
| }; | ||
| use panic_halt as _; | ||
|
|
||
puyogo-suzuki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // LP SRAM addresses used as a simple shared-memory interface between the LP and HP cores. | ||
| // These addresses must match the locations that the HP core expects to read from. | ||
| // - 0x5000_2000: f32 temperature value | ||
| // - 0x5000_2004: f32 humidity value (4 bytes after temperature to hold a second f32) | ||
| // Adjust with care: changing these requires updating the corresponding HP-core code and | ||
| // ensuring the chosen addresses stay within a valid, reserved LP memory region. | ||
| const TEMP_ADDRESS: u32 = 0x5000_2000; | ||
| const HUMID_ADDRESS: u32 = 0x5000_2004; | ||
|
|
||
| // I2C address of the SHT30 temperature and humidity sensor. | ||
| const DEV_ADDR: u8 = 0x44; | ||
| // SHT30 command for single-shot measurement, clock stretching disabled, high repeatability. | ||
| const CMD_READ_ONESHOT: [u8; 2] = [0x2C, 0x06]; | ||
|
|
||
| fn read_temp_humid(i2c: &mut LpI2c) -> Result<(f32, f32), Error> { | ||
| let mut buffer = [0u8; 6]; | ||
| // Send single-shot measurement command. | ||
| i2c.write(DEV_ADDR, &CMD_READ_ONESHOT)?; | ||
puyogo-suzuki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Wait for the measurement to complete (up to 15 ms per datasheet). | ||
| Delay.delay_ms(15); | ||
| // Read measurement results. | ||
| i2c.read(DEV_ADDR, &mut buffer)?; | ||
| let temp_raw = u16::from_be_bytes([buffer[0], buffer[1]]); | ||
| let hum_raw = u16::from_be_bytes([buffer[3], buffer[4]]); | ||
|
Comment on lines
+45
to
+46
|
||
| let temperature = -45.0 + (175.0 * (temp_raw as f32) / 65535.0); | ||
| let humidity = 100.0 * (hum_raw as f32) / 65535.0; | ||
| Ok((temperature, humidity)) | ||
| } | ||
|
|
||
| #[entry] | ||
| fn main(mut i2c: LpI2c) -> ! { | ||
| let temp_ptr = TEMP_ADDRESS as *mut f32; | ||
| let hum_ptr = HUMID_ADDRESS as *mut f32; | ||
| loop { | ||
| let (temp, humid) = read_temp_humid(&mut i2c).unwrap_or((f32::NAN, f32::NAN)); | ||
| unsafe { | ||
| temp_ptr.write_volatile(temp); | ||
| hum_ptr.write_volatile(humid); | ||
| } | ||
| Delay.delay_ms(1000); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,27 @@ | ||||||||
| [package] | ||||||||
| name = "lp-core-blinky" | ||||||||
| version = "0.0.0" | ||||||||
| edition = "2024" | ||||||||
| publish = false | ||||||||
|
|
||||||||
| [dependencies] | ||||||||
| esp-backtrace = { path = "../../../../esp-backtrace", features = [ | ||||||||
| "panic-handler", | ||||||||
| "println", | ||||||||
| ] } | ||||||||
| esp-bootloader-esp-idf = { path = "../../../../esp-bootloader-esp-idf" } | ||||||||
| esp-hal = { path = "../../../../esp-hal", features = ["log-04", "unstable"] } | ||||||||
| esp-println = { path = "../../../../esp-println", features = ["log-04"] } | ||||||||
|
|
||||||||
| [features] | ||||||||
| esp32c6 = [ | ||||||||
| "esp-backtrace/esp32c6", | ||||||||
| "esp-bootloader-esp-idf/esp32c6", | ||||||||
| "esp-hal/esp32c6", | ||||||||
|
||||||||
| "esp-hal/esp32c6", | |
| "esp-hal/esp32c6", | |
| "esp-println/esp32c6", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that only examples/async/embassy_hello_world_defmt enables it.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| [target.'cfg(target_arch = "riscv32")'] | ||
| runner = "espflash flash --monitor" | ||
| rustflags = [ | ||
| "-C", "link-arg=-Tlinkall.x", | ||
| "-C", "force-frame-pointers", | ||
| ] | ||
|
|
||
| [target.'cfg(target_arch = "xtensa")'] | ||
| runner = "espflash flash --monitor" | ||
| rustflags = [ | ||
| # GNU LD | ||
| "-C", "link-arg=-Wl,-Tlinkall.x", | ||
| "-C", "link-arg=-nostartfiles", | ||
|
|
||
| # LLD | ||
| # "-C", "link-arg=-Tlinkall.x", | ||
| # "-C", "linker=rust-lld", | ||
| ] | ||
|
|
||
| [env] | ||
| ESP_LOG = "info" | ||
|
|
||
| [unstable] | ||
| build-std = ["core", "alloc"] | ||
puyogo-suzuki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,75 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! SHT30 humidity and temperature sensor example running on the LP core via LP I2C | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! Code on LP core reads humidity and temperature from an SHT30 sensor via I2C. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! The current value is printed by the HP core. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! ⚠️ Make sure to first compile the `esp-lp-hal/examples/i2c_sht30.rs` example ⚠️ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
puyogo-suzuki marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! The following wiring is assumed: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! - SDA => GPIO6 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! - SCL => GPIO7 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #![no_std] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #![no_main] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use esp_backtrace as _; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use esp_hal::{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gpio::lp_io::LowPowerOutputOpenDrain, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| i2c::lp_i2c::LpI2c, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| load_lp_code, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lp_core::{LpCore, LpCoreWakeupSource}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| main, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| time::Rate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use esp_println::{print, println}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esp_bootloader_esp_idf::esp_app_desc!(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
puyogo-suzuki marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Shared-memory locations used for communication with the LP core firmware. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The `esp-lp-hal` example `i2c_sht30.rs` running on the LP core writes the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // latest temperature and humidity readings as `f32` values to these addresses: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // - TEMP_ADDRESS: 4-byte `f32` at 0x5000_2000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // - HUMID_ADDRESS: 4-byte `f32` at 0x5000_2004 (immediately after temperature) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // These addresses must match the memory layout used by the LP core code; if you | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // change them here, you must also update the corresponding addresses in the LP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // core example so both cores agree on the shared memory region. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const TEMP_ADDRESS: u32 = 0x5000_2000; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const HUMID_ADDRESS: u32 = 0x5000_2004; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[main] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn main() -> ! { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| esp_println::logger::init_logger_from_env(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let peripherals = esp_hal::init(esp_hal::Config::default()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // configure LP I2C | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let i2c = LpI2c::new( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| peripherals.LP_I2C0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LowPowerOutputOpenDrain::new(peripherals.GPIO6), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LowPowerOutputOpenDrain::new(peripherals.GPIO7), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Rate::from_khz(100), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+51
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut lp_core = LpCore::new(peripherals.LP_CORE); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lp_core.stop(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| println!("lp core stopped"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // load code to LP core | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let lp_core_code = load_lp_code!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "../../../../esp-lp-hal/target/riscv32imac-unknown-none-elf/release/examples/i2c_sht30" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // start LP core | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lp_core_code.run(&mut lp_core, LpCoreWakeupSource::HpCpu, i2c); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| println!("lp core run"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let temp = TEMP_ADDRESS as *mut f32; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let humid = HUMID_ADDRESS as *mut f32; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loop { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Current {:.2} C {:.2} % \u{000d}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Current {:.2} C {:.2} % \u{000d}", | |
| "Current {:.2} °C {:.2} % \u{000d}", |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading temperature and humidity values separately with two volatile reads could result in reading inconsistent data if the LP core updates the values between the two reads. The HP core might read a temperature from one sensor reading cycle and humidity from another. Consider adding a comment documenting this potential race condition, or reading both values atomically if data consistency is critical.
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The HP core continuously reads shared memory values written by the LP core without any synchronization mechanism. This creates a potential race condition where the HP core could read a partially updated value if it reads in the middle of the LP core's write operation. While f32 writes might be atomic on some architectures, this is not guaranteed. Consider using atomic operations or adding a synchronization mechanism (like a flag or sequence number) to ensure the HP core reads consistent data.
| print!( | |
| "Current {:.2} C {:.2} % \u{000d}", | |
| unsafe { temp.read_volatile() }, | |
| unsafe { humid.read_volatile() } | |
| // Take a consistent snapshot of temperature and humidity. | |
| // We read temperature, then humidity, then temperature again. | |
| // If both temperature reads match bit-for-bit, we assume the LP core | |
| // was not in the middle of updating the values and print the pair. | |
| let (t, h) = loop { | |
| let t1 = unsafe { temp.read_volatile() }; | |
| let h = unsafe { humid.read_volatile() }; | |
| let t2 = unsafe { temp.read_volatile() }; | |
| if t1.to_bits() == t2.to_bits() { | |
| break (t1, h); | |
| } | |
| // Otherwise, try again until we get a stable snapshot. | |
| }; | |
| print!( | |
| "Current {:.2} C {:.2} % \u{000d}", | |
| t, | |
| h |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a simple example.
We don't need to think about a potential race condition.
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable naming is inconsistent between the HP and LP cores. The HP core uses humid while the LP core uses hum_ptr and hum_raw. Consider using consistent naming across both cores (either humid or humidity throughout) to improve code maintainability and make the relationship between the cores clearer.
| let humid = HUMID_ADDRESS as *mut f32; | |
| loop { | |
| print!( | |
| "Current {:.2} C {:.2} % \u{000d}", | |
| unsafe { temp.read_volatile() }, | |
| unsafe { humid.read_volatile() } | |
| let hum_ptr = HUMID_ADDRESS as *mut f32; | |
| loop { | |
| print!( | |
| "Current {:.2} C {:.2} % \u{000d}", | |
| unsafe { temp.read_volatile() }, | |
| unsafe { hum_ptr.read_volatile() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think that this naming is confusing.
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format string uses \u{000d} (carriage return) to overwrite the line. Consider adding a comment explaining this behavior, as it creates an updating display rather than scrolling output. This would help developers understand why the carriage return is used instead of a newline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This follows the lp_blinky example.
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The infinite loop continuously reads and prints sensor values without any delay, which will consume 100% CPU on the HP core. Consider adding a small delay between iterations to reduce CPU usage and power consumption, especially since the LP core only updates the values every 1000ms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a simple example!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The import
embedded_hal::delay::DelayNsis unused. The code only usesDelay.delay_ms()which comes from theesp_lp_hal::delay::Delayimport and its trait implementation, not from this DelayNs trait import.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delay.delay_mscomes fromembedded_hal::delay::DelayNs.Thus, it is necessary.