C6: LP_I2C basic driver (#1185)

* After more analysis and coding

* More work is done, writing/reading WIP

* `write` prototype done, small fixes. Read next

* pre-rebase

* Rebased and updated

* Pre-final state of driver

* More work (near-final state) done

* WIP

* WIP

* working

* cleanup

* changelog

* address review comments

* remove Option from conjure and improve lp-i2c example description

---------

Co-authored-by: Kirill Mikhailov <konnor1980@yandex.ru>
This commit is contained in:
Juraj Sadel 2024-02-21 15:18:54 +01:00 committed by GitHub
parent bc2f1a02cc
commit 9378639e4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1189 additions and 15 deletions

View File

@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Implementation OutputPin and InputPin for AnyPin (#1067)
- Implement `estimate_xtal_frequency` for ESP32-C6 / ESP32-H2 (#1174)
- A way to push into I2S DMA buffer via a closure (#1189)
- Added basic `LP-I2C` driver for C6 (#1185)
### Fixed

View File

@ -618,6 +618,7 @@ pub fn load_lp_code(input: TokenStream) -> TokenStream {
use #hal_crate::gpio::lp_gpio::LowPowerPin;
use #hal_crate::gpio::*;
use #hal_crate::uart::lp_uart::LpUart;
use #hal_crate::i2c::lp_i2c::LpI2c;
};
#[cfg(any(feature = "esp32s2", feature = "esp32s3"))]
let imports = quote! {
@ -727,7 +728,12 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
}
"LpUart" => {
create_peripheral.push(quote!(
let mut #param_name = unsafe { the_hal::uart::conjure().unwrap() };
let mut #param_name = unsafe { the_hal::uart::conjure() };
));
}
"LpI2c" => {
create_peripheral.push(quote!(
let mut #param_name = unsafe { the_hal::i2c::conjure() };
));
}
_ => {

View File

@ -61,14 +61,14 @@ ufmt-write = { version = "0.1.0", optional = true }
# IMPORTANT:
# Each supported device MUST have its PAC included below along with a
# corresponding feature.
esp32 = { git = "https://github.com/esp-rs/esp-pacs", rev = "8231fea", features = ["critical-section"], optional = true }
esp32c2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "8231fea", features = ["critical-section"], optional = true }
esp32c3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "8231fea", features = ["critical-section"], optional = true }
esp32c6 = { git = "https://github.com/esp-rs/esp-pacs", rev = "8231fea", features = ["critical-section"], optional = true }
esp32h2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "8231fea", features = ["critical-section"], optional = true }
esp32p4 = { git = "https://github.com/esp-rs/esp-pacs", rev = "8231fea", features = ["critical-section"], optional = true }
esp32s2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "8231fea", features = ["critical-section"], optional = true }
esp32s3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "8231fea", features = ["critical-section"], optional = true }
esp32 = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
esp32c2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
esp32c3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
esp32c6 = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
esp32h2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
esp32p4 = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
esp32s2 = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
esp32s3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
[build-dependencies]
basic-toml = "0.1.8"

View File

@ -2616,6 +2616,8 @@ pub mod rtc_io {
use core::marker::PhantomData;
#[cfg(esp32c6)]
use super::OpenDrain;
use super::{Floating, Input, Output, PullDown, PullUp, PushPull, Unknown};
/// A GPIO pin configured for low power operation
@ -2735,6 +2737,23 @@ pub mod rtc_io {
private: PhantomData,
}
}
#[cfg(esp32c6)]
/// Configures the pin as an pullup input and a push pull output pin.
pub fn into_open_drain_output(self) -> LowPowerPin<OpenDrain, PIN> {
self.into_pull_up_input();
self.into_push_pull_output();
use crate::peripherals::GPIO;
let gpio = unsafe { &*GPIO::PTR };
gpio.pin(PIN).modify(|_, w| w.pad_driver().bit(true));
self.pulldown_enable(false);
LowPowerPin {
private: PhantomData,
}
}
}
#[cfg(esp32s3)]
@ -2787,6 +2806,8 @@ pub mod lp_gpio {
use core::marker::PhantomData;
#[cfg(esp32c6)]
use super::OpenDrain;
use super::{Floating, Input, Output, PullDown, PullUp, PushPull, Unknown};
/// A GPIO pin configured for low power operation
@ -2880,6 +2901,21 @@ pub mod lp_gpio {
private: PhantomData,
}
}
pub fn into_open_drain_output(self) -> LowPowerPin<OpenDrain, PIN> {
use crate::peripherals::GPIO;
let gpio = unsafe { &*GPIO::PTR };
gpio.pin(PIN as usize)
.modify(|_, w| w.pad_driver().bit(true));
self.pulldown_enable(false);
self.into_pull_up_input().into_push_pull_output();
LowPowerPin {
private: PhantomData,
}
}
}
pub(crate) fn init_low_power_pin(pin: u8) {

View File

@ -1649,3 +1649,470 @@ impl Instance for crate::peripherals::I2C1 {
1
}
}
#[cfg(lp_i2c0)]
pub mod lp_i2c {
//! Low-power I2C driver
use fugit::HertzU32;
use crate::{
gpio::{lp_gpio::LowPowerPin, OpenDrain},
peripherals::{LP_CLKRST, LP_I2C0},
};
const LP_I2C_FILTER_CYC_NUM_DEF: u8 = 7;
/// I2C-specific transmission errors
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
ExceedingFifo,
AckCheckFailed,
TimeOut,
ArbitrationLost,
ExecIncomplete,
CommandNrExceeded,
InvalidResponse,
}
#[allow(unused)]
enum OperationType {
Write = 0,
Read = 1,
}
#[allow(unused)]
#[derive(Eq, PartialEq, Copy, Clone)]
enum Ack {
Ack,
Nack,
}
#[allow(unused)]
enum Opcode {
RStart = 6,
Write = 1,
Read = 3,
Stop = 2,
End = 4,
}
#[derive(PartialEq)]
#[allow(unused)]
enum Command {
Start,
Stop,
End,
Write {
/// This bit is to set an expected ACK value for the transmitter.
ack_exp: Ack,
/// Enables checking the ACK value received against the ack_exp
/// value.
ack_check_en: bool,
/// Length of data (in bytes) to be written. The maximum length is
/// 255, while the minimum is 1.
length: u8,
},
Read {
/// Indicates whether the receiver will send an ACK after this byte
/// has been received.
ack_value: Ack,
/// Length of data (in bytes) to be read. The maximum length is 255,
/// while the minimum is 1.
length: u8,
},
}
impl From<Command> for u16 {
fn from(c: Command) -> u16 {
let opcode = match c {
Command::Start => Opcode::RStart,
Command::Stop => Opcode::Stop,
Command::End => Opcode::End,
Command::Write { .. } => Opcode::Write,
Command::Read { .. } => Opcode::Read,
};
let length = match c {
Command::Start | Command::Stop | Command::End => 0,
Command::Write { length: l, .. } | Command::Read { length: l, .. } => l,
};
let ack_exp = match c {
Command::Start | Command::Stop | Command::End | Command::Read { .. } => Ack::Nack,
Command::Write { ack_exp: exp, .. } => exp,
};
let ack_check_en = match c {
Command::Start | Command::Stop | Command::End | Command::Read { .. } => false,
Command::Write {
ack_check_en: en, ..
} => en,
};
let ack_value = match c {
Command::Start | Command::Stop | Command::End | Command::Write { .. } => Ack::Nack,
Command::Read { ack_value: ack, .. } => ack,
};
let mut cmd: u16 = length.into();
if ack_check_en {
cmd |= 1 << 8;
} else {
cmd &= !(1 << 8);
}
if ack_exp == Ack::Nack {
cmd |= 1 << 9;
} else {
cmd &= !(1 << 9);
}
if ack_value == Ack::Nack {
cmd |= 1 << 10;
} else {
cmd &= !(1 << 10);
}
cmd |= (opcode as u16) << 11;
cmd
}
}
impl From<Command> for u32 {
fn from(c: Command) -> u32 {
let opcode = match c {
Command::Start => Opcode::RStart,
Command::Stop => Opcode::Stop,
Command::End => Opcode::End,
Command::Write { .. } => Opcode::Write,
Command::Read { .. } => Opcode::Read,
};
let length = match c {
Command::Start | Command::Stop | Command::End => 0,
Command::Write { length: l, .. } | Command::Read { length: l, .. } => l,
};
let ack_exp = match c {
Command::Start | Command::Stop | Command::End | Command::Read { .. } => Ack::Nack,
Command::Write { ack_exp: exp, .. } => exp,
};
let ack_check_en = match c {
Command::Start | Command::Stop | Command::End | Command::Read { .. } => false,
Command::Write {
ack_check_en: en, ..
} => en,
};
let ack_value = match c {
Command::Start | Command::Stop | Command::End | Command::Write { .. } => Ack::Nack,
Command::Read { ack_value: ack, .. } => ack,
};
let mut cmd: u32 = length.into();
if ack_check_en {
cmd |= 1 << 8;
} else {
cmd &= !(1 << 8);
}
if ack_exp == Ack::Nack {
cmd |= 1 << 9;
} else {
cmd &= !(1 << 9);
}
if ack_value == Ack::Nack {
cmd |= 1 << 10;
} else {
cmd &= !(1 << 10);
}
cmd |= (opcode as u32) << 11;
cmd
}
}
// https://github.com/espressif/esp-idf/blob/master/components/ulp/lp_core/lp_core_i2c.c#L122
// TX/RX RAM size is 16*8 bit
// TX RX FIFO has 16 bit depth
// The clock source of APB_CLK in LP_I2C is CLK_AON_FAST.
// Configure LP_I2C_SCLK_SEL to select the clock source for I2C_SCLK.
// When LP_I2C_SCLK_SEL is 0, select CLK_ROOT_FAST as clock source,
// and when LP_I2C_SCLK_SEL is 1, select CLK _XTALD2 as the clock source.
// Configure LP_EXT_I2C_CK_EN high to enable the clock source of I2C_SCLK.
// Adjust the timing registers accordingly when the clock frequency changes.
pub struct LpI2c {
i2c: LP_I2C0,
}
impl LpI2c {
pub fn new(
i2c: LP_I2C0,
_sda: LowPowerPin<OpenDrain, 6>,
_scl: LowPowerPin<OpenDrain, 7>,
frequency: HertzU32,
) -> Self {
let me = Self { i2c };
// Configure LP I2C GPIOs
// Initialize IO Pins
let lp_io = unsafe { &*crate::peripherals::LP_IO::PTR };
let lp_aon = unsafe { &*crate::peripherals::LP_AON::PTR };
let lp_peri = unsafe { &*crate::peripherals::LP_PERI::PTR };
unsafe {
lp_aon
.gpio_mux()
.modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 6)));
lp_aon
.gpio_mux()
.modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 7)));
lp_io.gpio6().modify(|_, w| w.mcu_sel().variant(1)); // TODO
lp_io.gpio7().modify(|_, w| w.mcu_sel().variant(1));
// Set output mode to Normal
lp_io.pin6().modify(|_, w| w.pad_driver().set_bit());
// Enable output (writing to write-1-to-set register, then internally the
// `GPIO_OUT_REG` will be set)
lp_io
.out_enable_w1ts()
.write(|w| w.enable_w1ts().bits(1 << 6));
// Enable input
lp_io.gpio6().modify(|_, w| w.fun_ie().set_bit());
// Disable pulldown (enable internal weak pull-down)
lp_io.gpio6().modify(|_, w| w.fun_wpd().clear_bit());
// Enable pullup
lp_io.gpio6().modify(|_, w| w.fun_wpu().set_bit());
// Same process for SCL pin
lp_io.pin7().modify(|_, w| w.pad_driver().set_bit());
// Enable output (writing to write-1-to-set register, then internally the
// `GPIO_OUT_REG` will be set)
lp_io
.out_enable_w1ts()
.write(|w| w.enable_w1ts().bits(1 << 7));
// Enable input
lp_io.gpio7().modify(|_, w| w.fun_ie().set_bit());
// Disable pulldown (enable internal weak pull-down)
lp_io.gpio7().modify(|_, w| w.fun_wpd().clear_bit());
// Enable pullup
lp_io.gpio7().modify(|_, w| w.fun_wpu().set_bit());
// Select LP I2C function for the SDA and SCL pins
lp_io.gpio6().modify(|_, w| w.mcu_sel().bits(1));
lp_io.gpio7().modify(|_, w| w.mcu_sel().bits(1));
}
// Initialize LP I2C HAL */
me.i2c.clk_conf().modify(|_, w| w.sclk_active().set_bit());
// Enable LP I2C controller clock
lp_peri
.clk_en()
.modify(|_, w| w.lp_ext_i2c_ck_en().set_bit());
lp_peri
.reset_en()
.modify(|_, w| w.lp_ext_i2c_reset_en().set_bit());
lp_peri
.reset_en()
.modify(|_, w| w.lp_ext_i2c_reset_en().clear_bit());
// Set LP I2C source clock
unsafe { &*LP_CLKRST::PTR }
.lpperi()
.modify(|_, w| w.lp_i2c_clk_sel().clear_bit());
// Initialize LP I2C Master mode
me.i2c.ctr().modify(|_, w| unsafe {
// Clear register
w.bits(0)
// Use open drain output for SDA and SCL
.sda_force_out()
.set_bit()
.scl_force_out()
.set_bit()
// Ensure that clock is enabled
.clk_en()
.set_bit()
});
// First, reset the fifo buffers
me.i2c.fifo_conf().modify(|_, w| w.nonfifo_en().clear_bit());
me.i2c
.ctr()
.modify(|_, w| w.tx_lsb_first().clear_bit().rx_lsb_first().clear_bit());
me.reset_fifo();
// Set LP I2C source clock
unsafe { &*LP_CLKRST::PTR }
.lpperi()
.modify(|_, w| w.lp_i2c_clk_sel().clear_bit());
// Configure LP I2C timing paramters. source_clk is ignored for LP_I2C in this
// call
let source_clk = 16_000_000;
let bus_freq = frequency.raw();
let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1;
let sclk_freq: u32 = source_clk / clkm_div;
let half_cycle: u32 = sclk_freq / bus_freq / 2;
// SCL
let scl_low = half_cycle;
// default, scl_wait_high < scl_high
// Make 80KHz as a boundary here, because when working at lower frequency, too
// much scl_wait_high will faster the frequency according to some
// hardware behaviors.
let scl_wait_high = if bus_freq >= 80 * 1000 {
half_cycle / 2 - 2
} else {
half_cycle / 4
};
let scl_high = half_cycle - scl_wait_high;
let sda_hold = half_cycle / 4;
let sda_sample = half_cycle / 2; // TODO + scl_wait_high;
let setup = half_cycle;
let hold = half_cycle;
// default we set the timeout value to about 10 bus cycles
// log(20*half_cycle)/log(2) = log(half_cycle)/log(2) + log(20)/log(2)
let tout = (4 * 8 - (5 * half_cycle).leading_zeros()) + 2;
// According to the Technical Reference Manual, the following timings must be
// subtracted by 1. However, according to the practical measurement and
// some hardware behaviour, if wait_high_period and scl_high minus one.
// The SCL frequency would be a little higher than expected. Therefore, the
// solution here is not to minus scl_high as well as scl_wait high, and
// the frequency will be absolutely accurate to all frequency
// to some extent.
let scl_low_period = scl_low - 1;
let scl_high_period = scl_high;
let scl_wait_high_period = scl_wait_high;
// sda sample
let sda_hold_time = sda_hold - 1;
let sda_sample_time = sda_sample - 1;
// setup
let scl_rstart_setup_time = setup - 1;
let scl_stop_setup_time = setup - 1;
// hold
let scl_start_hold_time = hold - 1;
let scl_stop_hold_time = hold - 1;
let time_out_value = tout;
let time_out_en = true;
// Write data to registers
unsafe {
me.i2c.clk_conf().modify(|_, w| {
w.sclk_sel()
.clear_bit()
.sclk_div_num()
.bits((clkm_div - 1) as u8)
});
// scl period
me.i2c
.scl_low_period()
.write(|w| w.scl_low_period().bits(scl_low_period as u16));
me.i2c.scl_high_period().write(|w| {
w.scl_high_period()
.bits(scl_high_period as u16)
.scl_wait_high_period()
.bits(scl_wait_high_period as u8)
});
// sda sample
me.i2c
.sda_hold()
.write(|w| w.time().bits(sda_hold_time as u16));
me.i2c
.sda_sample()
.write(|w| w.time().bits(sda_sample_time as u16));
// setup
me.i2c
.scl_rstart_setup()
.write(|w| w.time().bits(scl_rstart_setup_time as u16));
me.i2c
.scl_stop_setup()
.write(|w| w.time().bits(scl_stop_setup_time as u16));
// hold
me.i2c
.scl_start_hold()
.write(|w| w.time().bits(scl_start_hold_time as u16));
me.i2c
.scl_stop_hold()
.write(|w| w.time().bits(scl_stop_hold_time as u16));
me.i2c.to().write(|w| {
w.time_out_en()
.bit(time_out_en)
.time_out_value()
.variant(time_out_value.try_into().unwrap())
});
}
// Enable SDA and SCL filtering. This configuration matches the HP I2C filter
// config
me.i2c
.filter_cfg()
.modify(|_, w| unsafe { w.sda_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) });
me.i2c
.filter_cfg()
.modify(|_, w| unsafe { w.scl_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) });
me.i2c
.filter_cfg()
.modify(|_, w| w.sda_filter_en().set_bit());
me.i2c
.filter_cfg()
.modify(|_, w| w.scl_filter_en().set_bit());
// Configure the I2C master to send a NACK when the Rx FIFO count is full
me.i2c.ctr().modify(|_, w| w.rx_full_ack_level().set_bit());
// Synchronize the config register values to the LP I2C peripheral clock
me.lp_i2c_update();
me
}
/// Update I2C configuration
fn lp_i2c_update(&self) {
self.i2c.ctr().modify(|_, w| w.conf_upgate().set_bit());
}
fn reset_fifo(&self) {
self.i2c
.fifo_conf()
.modify(|_, w| w.tx_fifo_rst().set_bit());
self.i2c
.fifo_conf()
.modify(|_, w| w.tx_fifo_rst().clear_bit());
self.i2c
.fifo_conf()
.modify(|_, w| w.rx_fifo_rst().set_bit());
self.i2c
.fifo_conf()
.modify(|_, w| w.rx_fifo_rst().clear_bit());
}
}
}

View File

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add GPIO input support and implement additional `embedded-hal` output traits for the C6's LP core [#720]
- Add the `ulp-riscv-hal` package (#840)
- Add LP_UART basic driver (#1113)
- Added basic `LP-I2C` driver for C6 (#1185)
### Changed

View File

@ -24,7 +24,7 @@ categories = [
cfg-if = "1.0.0"
embedded-hal-02 = { version = "0.2.7", package = "embedded-hal", optional = true, features = ["unproven"] }
embedded-hal-1 = { version = "1.0.0", package = "embedded-hal", optional = true }
esp32c6-lp = { version = "0.1.0", features = ["critical-section"], optional = true }
esp32c6-lp = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
esp32s2-ulp = { version = "0.1.0", features = ["critical-section"], optional = true }
esp32s3-ulp = { version = "0.1.0", features = ["critical-section"], optional = true }
nb = { version = "1.1.0", optional = true }
@ -59,6 +59,10 @@ required-features = ["embedded-hal-02"]
name = "uart"
required-features = ["embedded-hal-02", "esp32c6"]
[[example]]
name = "i2c"
required-features = ["embedded-hal-02", "esp32c6"]
[patch.crates-io]
esp32s2-ulp = { git = "https://github.com/esp-rs/esp-pacs", rev = "bcab40a" }
esp32s3-ulp = { git = "https://github.com/esp-rs/esp-pacs", rev = "bcab40a" }

View File

@ -0,0 +1,25 @@
//! Uses `LP_I2C` and reads calibration data from BMP180 sensor.
//!
//! This example dumps the calibration data from a BMP180 sensor, to view them,
//! logic analyzer or oscilloscope is required.
//!
//! The following wiring is assumed:
//! - SDA => GPIO6
//! - SCL => GPIO7
#![no_std]
#![no_main]
use embedded_hal_02::blocking::i2c::WriteRead;
use esp_lp_hal::{i2c::LpI2c, prelude::*};
use panic_halt as _;
#[entry]
fn main(mut i2c: LpI2c) -> ! {
let _peripherals = esp32c6_lp::Peripherals::take().unwrap();
loop {
let mut data = [0u8; 22];
i2c.write_read(0x77, &[0xaa], &mut data).ok();
}
}

587
esp-lp-hal/src/i2c.rs Normal file
View File

@ -0,0 +1,587 @@
//! Low-power I2C driver
use esp32c6_lp::LP_I2C0;
const LP_I2C_TRANS_COMPLETE_INT_ST_S: u32 = 7;
const LP_I2C_END_DETECT_INT_ST_S: u32 = 3;
const LP_I2C_NACK_INT_ST_S: u32 = 10;
const I2C_LL_INTR_MASK: u32 = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S)
| (1 << LP_I2C_END_DETECT_INT_ST_S)
| (1 << LP_I2C_NACK_INT_ST_S);
const LP_I2C_FIFO_LEN: u32 = 16;
#[doc(hidden)]
pub unsafe fn conjure() -> LpI2c {
LpI2c {
i2c: LP_I2C0::steal(),
}
}
/// I2C-specific transmission errors
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
ExceedingFifo,
AckCheckFailed,
TimeOut,
ArbitrationLost,
ExecIncomplete,
CommandNrExceeded,
InvalidResponse,
}
enum OperationType {
Write = 0,
Read = 1,
}
#[derive(Eq, PartialEq, Copy, Clone)]
enum Ack {
Ack,
Nack,
}
enum Opcode {
RStart = 6,
Write = 1,
Read = 3,
Stop = 2,
End = 4,
}
#[derive(PartialEq)]
enum Command {
Start,
Stop,
End,
Write {
/// This bit is to set an expected ACK value for the transmitter.
ack_exp: Ack,
/// Enables checking the ACK value received against the ack_exp value.
ack_check_en: bool,
/// Length of data (in bytes) to be written. The maximum length is 255,
/// while the minimum is 1.
length: u8,
},
Read {
/// Indicates whether the receiver will send an ACK after this byte has
/// been received.
ack_value: Ack,
/// Length of data (in bytes) to be read. The maximum length is 255,
/// while the minimum is 1.
length: u8,
},
}
impl From<Command> for u16 {
fn from(c: Command) -> u16 {
let opcode = match c {
Command::Start => Opcode::RStart,
Command::Stop => Opcode::Stop,
Command::End => Opcode::End,
Command::Write { .. } => Opcode::Write,
Command::Read { .. } => Opcode::Read,
};
let length = match c {
Command::Start | Command::Stop | Command::End => 0,
Command::Write { length: l, .. } | Command::Read { length: l, .. } => l,
};
let ack_exp = match c {
Command::Start | Command::Stop | Command::End | Command::Read { .. } => Ack::Nack,
Command::Write { ack_exp: exp, .. } => exp,
};
let ack_check_en = match c {
Command::Start | Command::Stop | Command::End | Command::Read { .. } => false,
Command::Write {
ack_check_en: en, ..
} => en,
};
let ack_value = match c {
Command::Start | Command::Stop | Command::End | Command::Write { .. } => Ack::Nack,
Command::Read { ack_value: ack, .. } => ack,
};
let mut cmd: u16 = length.into();
if ack_check_en {
cmd |= 1 << 8;
} else {
cmd &= !(1 << 8);
}
if ack_exp == Ack::Nack {
cmd |= 1 << 9;
} else {
cmd &= !(1 << 9);
}
if ack_value == Ack::Nack {
cmd |= 1 << 10;
} else {
cmd &= !(1 << 10);
}
cmd |= (opcode as u16) << 11;
cmd
}
}
impl From<Command> for u32 {
fn from(c: Command) -> u32 {
let opcode = match c {
Command::Start => Opcode::RStart,
Command::Stop => Opcode::Stop,
Command::End => Opcode::End,
Command::Write { .. } => Opcode::Write,
Command::Read { .. } => Opcode::Read,
};
let length = match c {
Command::Start | Command::Stop | Command::End => 0,
Command::Write { length: l, .. } | Command::Read { length: l, .. } => l,
};
let ack_exp = match c {
Command::Start | Command::Stop | Command::End | Command::Read { .. } => Ack::Nack,
Command::Write { ack_exp: exp, .. } => exp,
};
let ack_check_en = match c {
Command::Start | Command::Stop | Command::End | Command::Read { .. } => false,
Command::Write {
ack_check_en: en, ..
} => en,
};
let ack_value = match c {
Command::Start | Command::Stop | Command::End | Command::Write { .. } => Ack::Nack,
Command::Read { ack_value: ack, .. } => ack,
};
let mut cmd: u32 = length.into();
if ack_check_en {
cmd |= 1 << 8;
} else {
cmd &= !(1 << 8);
}
if ack_exp == Ack::Nack {
cmd |= 1 << 9;
} else {
cmd &= !(1 << 9);
}
if ack_value == Ack::Nack {
cmd |= 1 << 10;
} else {
cmd &= !(1 << 10);
}
cmd |= (opcode as u32) << 11;
cmd
}
}
enum CommandRegister {
COMD0,
COMD1,
COMD2,
COMD3,
COMD4,
COMD5,
COMD6,
COMD7,
}
impl CommandRegister {
fn advance(&mut self) {
*self = match *self {
CommandRegister::COMD0 => CommandRegister::COMD1,
CommandRegister::COMD1 => CommandRegister::COMD2,
CommandRegister::COMD2 => CommandRegister::COMD3,
CommandRegister::COMD3 => CommandRegister::COMD4,
CommandRegister::COMD4 => CommandRegister::COMD5,
CommandRegister::COMD5 => CommandRegister::COMD6,
CommandRegister::COMD6 => CommandRegister::COMD7,
CommandRegister::COMD7 => panic!("Cannot advance beyond COMD7"),
}
}
}
// https://github.com/espressif/esp-idf/blob/master/components/ulp/lp_core/lp_core_i2c.c#L122
// TX/RX RAM size is 16*8 bit
// TX RX FIFO has 16 bit depth
// The clock source of APB_CLK in LP_I2C is CLK_AON_FAST.
// Configure LP_I2C_SCLK_SEL to select the clock source for I2C_SCLK.
// When LP_I2C_SCLK_SEL is 0, select CLK_ROOT_FAST as clock source,
// and when LP_I2C_SCLK_SEL is 1, select CLK _XTALD2 as the clock source.
// Configure LP_EXT_I2C_CK_EN high to enable the clock source of I2C_SCLK.
// Adjust the timing registers accordingly when the clock frequency changes.
pub struct LpI2c {
i2c: LP_I2C0,
}
impl LpI2c {
/// Send data bytes from the `bytes` array to a target slave with the
/// address `addr`
fn master_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> {
let mut cmd_iterator = CommandRegister::COMD0;
// If SCL is busy, reset the Master FSM
if self.i2c.sr().read().bus_busy().bit_is_set() {
self.i2c.ctr().modify(|_, w| w.fsm_rst().set_bit());
}
if bytes.len() > 255 {
return Err(Error::ExceedingFifo);
}
// Reset FIFO and command list
self.reset_fifo();
self.add_cmd_lp(&mut cmd_iterator, Command::Start)?;
// Load device address and R/W bit into FIFO
self.write_fifo(addr << 1 | OperationType::Write as u8);
self.add_cmd_lp(
&mut cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: 1_u8,
},
)?;
self.enable_interrupts(I2C_LL_INTR_MASK);
let mut data_idx = 0;
let mut remaining_bytes = bytes.len() as u32;
let mut fifo_available = LP_I2C_FIFO_LEN - 1;
while remaining_bytes > 0 {
let fifo_size = if remaining_bytes < fifo_available {
remaining_bytes
} else {
fifo_available
};
remaining_bytes -= fifo_size;
// Write data to the FIFO
for &byte in &bytes[data_idx as usize..(data_idx as usize) + fifo_size as usize] {
self.write_fifo(byte);
}
// Add a Write command with the specified length
self.add_cmd_lp(
&mut cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: fifo_size as u8,
},
)?;
// Check if this is the last chunk
let cmd = if remaining_bytes == 0 {
Command::Stop
} else {
Command::End
};
// Add the Stop/End command
self.add_cmd_lp(&mut cmd_iterator, cmd)?;
// Start the I2C transaction
self.lp_i2c_update();
self.i2c.ctr().modify(|_, w| w.trans_start().set_bit());
// Wait for the transaction to complete
self.wait_for_completion()?;
// Update the index for the next data chunk
data_idx += fifo_size;
fifo_available = LP_I2C_FIFO_LEN;
}
Ok(())
}
fn master_read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> {
// Check size constraints
if buffer.len() > 254 {
return Err(Error::ExceedingFifo);
}
let mut cmd_iterator = CommandRegister::COMD0;
self.add_cmd_lp(&mut cmd_iterator, Command::Start)?;
// Load device address
self.write_fifo(addr << 1 | OperationType::Read as u8);
self.add_cmd_lp(
&mut cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: 1_u8,
},
)?;
self.enable_interrupts(
1 << LP_I2C_TRANS_COMPLETE_INT_ST_S | 1 << LP_I2C_END_DETECT_INT_ST_S,
);
let mut remaining_bytes = buffer.len();
while remaining_bytes > 0 {
let fifo_size = if remaining_bytes < LP_I2C_FIFO_LEN as usize {
remaining_bytes
} else {
LP_I2C_FIFO_LEN as usize
};
remaining_bytes -= fifo_size;
if fifo_size == 1 {
// Read one byte and send NACK
self.add_cmd_lp(
&mut cmd_iterator,
Command::Read {
ack_value: Ack::Nack,
length: 1, // which is `fifo_size`
},
)?;
// Send STOP command after reading
self.add_cmd_lp(&mut cmd_iterator, Command::Stop)?;
} else if fifo_size > 1 && remaining_bytes == 0 {
// This means it is the last transaction
// Read all but the last byte and send ACKs
self.add_cmd_lp(
&mut cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: (fifo_size - 1) as u8,
},
)?;
// Read the last byte and send NACK
self.add_cmd_lp(
&mut cmd_iterator,
Command::Read {
ack_value: Ack::Nack,
length: 1,
},
)?;
// Send STOP command after reading
self.add_cmd_lp(&mut cmd_iterator, Command::Stop)?;
} else {
// This means we have to read data more than we can fit into the Rx FIFO
// Read fifo_size bytes and send ACKs
self.add_cmd_lp(
&mut cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: fifo_size as u8,
},
)?;
// Send END command signaling more data to come
self.add_cmd_lp(&mut cmd_iterator, Command::End)?;
}
self.lp_i2c_update();
// Initiate I2C transfer
self.i2c.ctr().modify(|_, w| w.trans_start().set_bit());
// Await for completion (This function or mechanism should handle waiting for
// the I2C transfer to complete)
self.wait_for_completion()?;
// Read from FIFO into the current chunk
for byte in buffer.iter_mut() {
*byte = self.read_fifo();
}
}
Ok(())
}
fn master_write_read(
&mut self,
addr: u8,
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), Error> {
// it would be possible to combine the write and read
// in one transaction but filling the tx fifo with
// the current code is somewhat slow even in release mode
// which can cause issues
self.master_write(addr, bytes)?;
self.master_read(addr, buffer)?;
Ok(())
}
/// Update I2C configuration
fn lp_i2c_update(&self) {
self.i2c.ctr().modify(|_, w| w.conf_upgate().set_bit());
}
fn reset_fifo(&self) {
self.i2c
.fifo_conf()
.modify(|_, w| w.tx_fifo_rst().set_bit());
self.i2c
.fifo_conf()
.modify(|_, w| w.tx_fifo_rst().clear_bit());
self.i2c
.fifo_conf()
.modify(|_, w| w.rx_fifo_rst().set_bit());
self.i2c
.fifo_conf()
.modify(|_, w| w.rx_fifo_rst().clear_bit());
}
fn wait_for_completion(&self) -> Result<(), Error> {
loop {
let interrupts = self.i2c.int_status().read();
// Handle completion cases
// A full transmission was completed
if interrupts.nack_int_st().bit_is_set() {
self.i2c
.int_clr()
.write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) });
return Err(Error::InvalidResponse);
} else if interrupts.trans_complete_int_st().bit_is_set() {
self.disable_interrupts();
self.i2c
.int_clr()
.write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) });
break;
} else if interrupts.end_detect_int_st().bit_is_set() {
self.i2c
.int_clr()
.write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) });
break;
}
}
Ok(())
}
fn enable_interrupts(&self, mask: u32) {
self.i2c.int_ena().write(|w| unsafe { w.bits(mask) });
}
fn disable_interrupts(&self) {
self.i2c.int_ena().write(|w| unsafe { w.bits(0) });
}
fn write_fifo(&self, data: u8) {
self.i2c
.data()
.write(|w| unsafe { w.fifo_rdata().bits(data) });
}
fn read_fifo(&self) -> u8 {
self.i2c.data().read().fifo_rdata().bits()
}
fn add_cmd_lp(
&self,
command_register: &mut CommandRegister,
command: Command,
) -> Result<(), Error> {
match *command_register {
CommandRegister::COMD0 => {
self.i2c
.comd0()
.write(|w| unsafe { w.command0().bits(command.into()) });
}
CommandRegister::COMD1 => {
self.i2c
.comd1()
.write(|w| unsafe { w.command1().bits(command.into()) });
}
CommandRegister::COMD2 => {
self.i2c
.comd2()
.write(|w| unsafe { w.command2().bits(command.into()) });
}
CommandRegister::COMD3 => {
self.i2c
.comd3()
.write(|w| unsafe { w.command3().bits(command.into()) });
}
CommandRegister::COMD4 => {
self.i2c
.comd4()
.write(|w| unsafe { w.command4().bits(command.into()) });
}
CommandRegister::COMD5 => {
self.i2c
.comd5()
.write(|w| unsafe { w.command5().bits(command.into()) });
}
CommandRegister::COMD6 => {
self.i2c
.comd6()
.write(|w| unsafe { w.command6().bits(command.into()) });
}
CommandRegister::COMD7 => {
self.i2c
.comd7()
.write(|w| unsafe { w.command7().bits(command.into()) });
}
}
command_register.advance();
Ok(())
}
}
#[cfg(feature = "embedded-hal-02")]
impl embedded_hal_02::blocking::i2c::Read for LpI2c {
type Error = Error;
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
self.master_read(address, buffer)
}
}
#[cfg(feature = "embedded-hal-02")]
impl embedded_hal_02::blocking::i2c::Write for LpI2c {
type Error = Error;
fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> {
self.master_write(addr, bytes)
}
}
#[cfg(feature = "embedded-hal-02")]
impl embedded_hal_02::blocking::i2c::WriteRead for LpI2c {
type Error = Error;
fn write_read(
&mut self,
address: u8,
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), Self::Error> {
self.master_write_read(address, bytes, buffer)
}
}

View File

@ -6,6 +6,8 @@ use core::arch::global_asm;
pub mod delay;
pub mod gpio;
#[cfg(esp32c6)]
pub mod i2c;
#[cfg(esp32c6)]
pub mod uart;
pub mod pac {

View File

@ -5,10 +5,10 @@ use crate::pac::LP_UART;
const UART_FIFO_SIZE: u16 = 128;
#[doc(hidden)]
pub unsafe fn conjure() -> Option<LpUart> {
Some(LpUart {
pub unsafe fn conjure() -> LpUart {
LpUart {
uart: LP_UART::steal(),
})
}
}
#[derive(Debug)]

View File

@ -0,0 +1,46 @@
//! This shows a very basic example of running code on the LP core.
//!
//! Code on LP core uses LP_I2C initialized on HP core. For more information
//! check `lp_core_i2c` example in the `esp-lp-hal`.
//!
//! Make sure to first compile the `esp-lp-hal/examples/i2c.rs` example
#![no_std]
#![no_main]
use esp32c6_hal::{
gpio::lp_gpio::IntoLowPowerPin,
i2c::lp_i2c::LpI2c,
lp_core,
peripherals::Peripherals,
prelude::*,
IO,
};
use esp_backtrace as _;
use esp_println::println;
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let lp_sda = io.pins.gpio6.into_low_power().into_open_drain_output();
let lp_scl = io.pins.gpio7.into_low_power().into_open_drain_output();
let lp_i2c = LpI2c::new(peripherals.LP_I2C0, lp_sda, lp_scl, 100u32.kHz());
let mut lp_core = esp32c6_hal::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");
// start LP core
lp_core_code.run(&mut lp_core, lp_core::LpCoreWakeupSource::HpCpu, lp_i2c);
println!("lpcore run");
loop {}
}

View File

@ -1,7 +1,7 @@
//! This shows a very basic example of running code on the LP core.
//!
//! Code on LP core uses LP_UART initialized on HP core. For more information
//! check `lp_core_uart` example in the `esp-lp-hal.
//! check `lp_core_uart` example in the `esp-lp-hal`.
//!
//! Make sure to first compile the `esp-lp-hal/examples/uart.rs` example
@ -50,7 +50,6 @@ fn main() -> ! {
let mut uart1 = Uart::new_with_config(peripherals.UART1, config, Some(pins), &clocks);
// Set up (LP) UART:
let lp_tx = io.pins.gpio5.into_low_power().into_push_pull_output();
let lp_rx = io.pins.gpio4.into_low_power().into_floating_input();