-
Notifications
You must be signed in to change notification settings - Fork 372
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
feat(example): Add LP I2C Example (SHT30 temp. and humid. sensor) #4679
Conversation
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.
Pull request overview
This PR adds a new LP I2C example demonstrating communication with an SHT30 temperature and humidity sensor, and reorganizes the existing LP core examples by moving them into subdirectories.
Key Changes
- Renamed and relocated the existing
lp_coreexample tolp_core/lp_blinky/with updated relative paths - Added a new
lp_i2cexample that reads temperature and humidity data from an SHT30 sensor via the LP core's I2C peripheral - Created corresponding LP core code in
esp-lp-hal/examples/i2c_sht30.rsthat performs the actual sensor reading
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/peripheral/lp_core/lp_i2c/src/main.rs | New HP core example that initializes LP I2C and displays sensor readings |
| examples/peripheral/lp_core/lp_i2c/Cargo.toml | Dependencies and configuration for the new lp_i2c example |
| examples/peripheral/lp_core/lp_i2c/.cargo/config.toml | Build configuration for the new example |
| examples/peripheral/lp_core/lp_blinky/src/main.rs | Updated path reference after directory restructuring |
| examples/peripheral/lp_core/lp_blinky/Cargo.toml | New dependency paths reflecting the directory structure change |
| examples/peripheral/lp_core/lp_blinky/.cargo/config.toml | Build configuration moved with the example |
| esp-lp-hal/examples/i2c_sht30.rs | New LP core code that reads SHT30 sensor data and writes to shared memory |
| esp-lp-hal/Cargo.toml | Added the new i2c_sht30 example definition |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #![no_std] | ||
| #![no_main] | ||
|
|
||
| use embedded_hal::delay::DelayNs; |
Copilot
AI
Dec 18, 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 import embedded_hal::delay::DelayNs is unused. The code only uses Delay.delay_ms() which comes from the esp_lp_hal::delay::Delay import and its trait implementation, not from this DelayNs trait import.
| use embedded_hal::delay::DelayNs; |
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_ms comes from embedded_hal::delay::DelayNs.
Thus, it is necessary.
| fn read_temp_humid(i2c: &mut LpI2c) -> Result<(f32, f32), Error> { | ||
| let mut buffer = [0u8; 6]; | ||
| i2c.write_read(DEV_ADDR, &CMD_READ_ONESHOT, &mut buffer)?; | ||
| let temp_raw = u16::from_be_bytes([buffer[0], buffer[1]]); | ||
| let hum_raw = u16::from_be_bytes([buffer[3], buffer[4]]); | ||
| 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)) |
Copilot
AI
Dec 18, 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 SHT30 sensor data includes CRC bytes at positions [2] and [5] of the buffer which are currently being ignored. According to the SHT30 datasheet, the sensor sends 6 bytes: 2 bytes temp data, 1 byte CRC, 2 bytes humidity data, 1 byte CRC. Not validating these CRC values means corrupted sensor readings could be silently accepted and displayed as valid data.
| fn read_temp_humid(i2c: &mut LpI2c) -> Result<(f32, f32), Error> { | |
| let mut buffer = [0u8; 6]; | |
| i2c.write_read(DEV_ADDR, &CMD_READ_ONESHOT, &mut buffer)?; | |
| let temp_raw = u16::from_be_bytes([buffer[0], buffer[1]]); | |
| let hum_raw = u16::from_be_bytes([buffer[3], buffer[4]]); | |
| 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)) | |
| /// Calculate CRC for SHT30 two-byte data using polynomial 0x31 and initial value 0xFF. | |
| fn sht30_crc(data: &[u8]) -> u8 { | |
| let mut crc: u8 = 0xFF; | |
| for byte in data { | |
| crc ^= *byte; | |
| for _ in 0..8 { | |
| if (crc & 0x80) != 0 { | |
| crc = (crc << 1) ^ 0x31; | |
| } else { | |
| crc <<= 1; | |
| } | |
| } | |
| } | |
| crc | |
| } | |
| fn read_temp_humid(i2c: &mut LpI2c) -> Result<(f32, f32), Error> { | |
| let mut buffer = [0u8; 6]; | |
| loop { | |
| // Read 2 bytes temperature, 1 byte CRC, 2 bytes humidity, 1 byte CRC. | |
| i2c.write_read(DEV_ADDR, &CMD_READ_ONESHOT, &mut buffer)?; | |
| let temp_data = [buffer[0], buffer[1]]; | |
| let temp_crc = buffer[2]; | |
| let hum_data = [buffer[3], buffer[4]]; | |
| let hum_crc = buffer[5]; | |
| if sht30_crc(&temp_data) == temp_crc && sht30_crc(&hum_data) == hum_crc { | |
| let temp_raw = u16::from_be_bytes(temp_data); | |
| let hum_raw = u16::from_be_bytes(hum_data); | |
| let temperature = -45.0 + (175.0 * (temp_raw as f32) / 65535.0); | |
| let humidity = 100.0 * (hum_raw as f32) / 65535.0; | |
| return Ok((temperature, humidity)); | |
| } | |
| // If CRC check fails, repeat the measurement instead of returning corrupted data. | |
| } |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 8 comments.
Comments suppressed due to low confidence (1)
examples/peripheral/lp_core/lp_i2c/Cargo.toml:2
- The package name "lp-core-i2c" should likely be more specific, such as "lp-core-i2c-sht30", to distinguish it from the generic "lp-core-blinky" pattern and to match the example's specific sensor usage.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| use embedded_hal::delay::DelayNs; | ||
| use esp_lp_hal::{ | ||
| delay::Delay, |
Copilot
AI
Dec 18, 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 Delay import is unused in this code. The code uses Delay.delay_ms(1000) which creates a Delay instance inline. Consider either removing the import or storing the Delay instance as a variable for clarity.
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.
Both esp_lp_hal::delay::Delay and embedded_hal::delay::DelayNs are necessary.
Removing either leads to an compilation error.
esp-lp-hal/examples/i2c_sht30.rs
Outdated
|
|
||
| fn read_temp_humid(i2c: &mut LpI2c) -> Result<(f32, f32), Error> { | ||
| let mut buffer = [0u8; 6]; | ||
| i2c.write_read(DEV_ADDR, &CMD_READ_ONESHOT, &mut buffer)?; |
Copilot
AI
Dec 18, 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 SHT30 sensor returns a 6-byte response where bytes 2 and 5 are CRC checksums for the temperature and humidity data respectively. This code reads the CRC bytes but does not validate them, which could result in displaying corrupted sensor readings. Consider adding CRC validation to ensure data integrity.
| fn read_temp_humid(i2c: &mut LpI2c) -> Result<(f32, f32), Error> { | |
| let mut buffer = [0u8; 6]; | |
| i2c.write_read(DEV_ADDR, &CMD_READ_ONESHOT, &mut buffer)?; | |
| /// Compute CRC-8 for SHT3x (polynomial 0x31, initial value 0xFF) over a 2-byte word. | |
| fn crc8_sht(data: &[u8]) -> u8 { | |
| let mut crc: u8 = 0xFF; | |
| for &byte in data { | |
| crc ^= byte; | |
| for _ in 0..8 { | |
| if (crc & 0x80) != 0 { | |
| crc = (crc << 1) ^ 0x31; | |
| } else { | |
| crc <<= 1; | |
| } | |
| } | |
| } | |
| crc | |
| } | |
| fn read_temp_humid(i2c: &mut LpI2c) -> Result<(f32, f32), Error> { | |
| let mut buffer = [0u8; 6]; | |
| i2c.write_read(DEV_ADDR, &CMD_READ_ONESHOT, &mut buffer)?; | |
| // Validate CRC for temperature (bytes 0-1, CRC at byte 2) | |
| let temp_crc_calc = crc8_sht(&buffer[0..2]); | |
| if temp_crc_calc != buffer[2] { | |
| return Err(Error::Bus); | |
| } | |
| // Validate CRC for humidity (bytes 3-4, CRC at byte 5) | |
| let hum_crc_calc = crc8_sht(&buffer[3..5]); | |
| if hum_crc_calc != buffer[5] { | |
| return Err(Error::Bus); | |
| } |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let temp_raw = u16::from_be_bytes([buffer[0], buffer[1]]); | ||
| let hum_raw = u16::from_be_bytes([buffer[3], buffer[4]]); |
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 SHT30 sensor returns CRC checksums at buffer[2] and buffer[5] that should be validated to ensure data integrity. The datasheet specifies that each data word (temperature and humidity) is followed by an 8-bit CRC. Skipping CRC validation could result in processing corrupted sensor data. Consider adding CRC validation or documenting why it's intentionally omitted.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| unsafe { temp.read_volatile() }, | ||
| unsafe { humid.read_volatile() } |
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.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let humid = HUMID_ADDRESS as *mut f32; | ||
| loop { | ||
| print!( | ||
| "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.
The temperature unit display should use the degree symbol (°C) instead of just "C" for better readability and to follow standard notation for temperature measurements. Consider changing the format string to include the degree symbol.
| "Current {:.2} C {:.2} % \u{000d}", | |
| "Current {:.2} °C {:.2} % \u{000d}", |
|
I have accepted the following suggestions from Copilot:
However, I disagree with these suggestions:
Since this is intended as a simple example for LPI2C, I believe it is better to keep the code minimal and concise. |
SergioGasquez
left a comment
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.
Changes lgtm and I think the new example is a great addition! I couldnt test it as I dont have SHT30 though
I guess in the future we could improve the experience of running lp examples and make the xtask build the lp example, but thats for another PR!
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.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
examples/peripheral/lp_core/lp_i2c/Cargo.toml:21
- Missing esp-println chip feature in the esp32c6 feature list. The esp-println dependency should include "esp-println/esp32c6" in the features array, consistent with other examples in the repository. This ensures esp-println is properly configured for the target chip.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let i2c = LpI2c::new( | ||
| peripherals.LP_I2C0, | ||
| LowPowerOutputOpenDrain::new(peripherals.GPIO6), | ||
| LowPowerOutputOpenDrain::new(peripherals.GPIO7), | ||
| Rate::from_khz(100), | ||
| ); |
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 i2c variable is created but its name is misleading. While it appears unused in the HP core's main function, it is actually consumed and transferred to the LP core via the lp_core_code.run() call. Consider renaming it to lp_i2c to make it clearer that this I2C instance is intended for the LP core, improving code readability.
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 disagree this because we don't use the HP I2C controller.
The naming i2c is not confusing.
| print!( | ||
| "Current {:.2} C {:.2} % \u{000d}", | ||
| unsafe { temp.read_volatile() }, | ||
| unsafe { humid.read_volatile() } |
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.
| esp32c6 = [ | ||
| "esp-backtrace/esp32c6", | ||
| "esp-bootloader-esp-idf/esp32c6", | ||
| "esp-hal/esp32c6", |
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.
Missing esp-println chip feature in the esp32c6 feature list. The esp-println dependency should include "esp-println/esp32c6" in the features array, consistent with other examples in the repository. This ensures esp-println is properly configured for the target chip.
| "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.
| loop { | ||
| print!( | ||
| "Current {:.2} C {:.2} % \u{000d}", | ||
| unsafe { temp.read_volatile() }, | ||
| unsafe { humid.read_volatile() } | ||
| ); | ||
| } |
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!
| print!( | ||
| "Current {:.2} C {:.2} % \u{000d}", | ||
| unsafe { temp.read_volatile() }, | ||
| unsafe { humid.read_volatile() } | ||
| ); |
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.
| let humid = HUMID_ADDRESS as *mut f32; | ||
| loop { | ||
| print!( | ||
| "Current {:.2} C {:.2} % \u{000d}", | ||
| unsafe { temp.read_volatile() }, | ||
| unsafe { humid.read_volatile() } |
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.
|
Thanks @SergioGasquez I've completed everything on my end. |
SergioGasquez
left a comment
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.
Thanks!
Pull Request Details 📖
Description
This PR adds an LP I2C example that reads data from an SHT30 temperature and humidity sensor.
Changes
lp_coreexample tolp_blinky.lp_blinkyexample fromexamples/lp_core/toexamples/lp_core/lp_blinky/.Additions
lp_i2cexample inexamples/lp_core/lp_i2c/.lp_blinkyexample.Notes
I kept
esp-lp-hal/example/i2c.rs, which is an old example code that uses the BMP180 sensor.This PR is related to #4657
Testing
I tested the renamed
lp_blinkyexample and the newlp_i2cexample with the following environment:Submission Checklist 📝
cargo xtask fmt-packagescommand to ensure that all changed code is formatted correctly.CHANGELOG.mdin the proper section.Extra: