mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-10-02 14:44:42 +00:00
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:
parent
bc2f1a02cc
commit
9378639e4c
@ -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)
|
- Implementation OutputPin and InputPin for AnyPin (#1067)
|
||||||
- Implement `estimate_xtal_frequency` for ESP32-C6 / ESP32-H2 (#1174)
|
- Implement `estimate_xtal_frequency` for ESP32-C6 / ESP32-H2 (#1174)
|
||||||
- A way to push into I2S DMA buffer via a closure (#1189)
|
- A way to push into I2S DMA buffer via a closure (#1189)
|
||||||
|
- Added basic `LP-I2C` driver for C6 (#1185)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -618,6 +618,7 @@ pub fn load_lp_code(input: TokenStream) -> TokenStream {
|
|||||||
use #hal_crate::gpio::lp_gpio::LowPowerPin;
|
use #hal_crate::gpio::lp_gpio::LowPowerPin;
|
||||||
use #hal_crate::gpio::*;
|
use #hal_crate::gpio::*;
|
||||||
use #hal_crate::uart::lp_uart::LpUart;
|
use #hal_crate::uart::lp_uart::LpUart;
|
||||||
|
use #hal_crate::i2c::lp_i2c::LpI2c;
|
||||||
};
|
};
|
||||||
#[cfg(any(feature = "esp32s2", feature = "esp32s3"))]
|
#[cfg(any(feature = "esp32s2", feature = "esp32s3"))]
|
||||||
let imports = quote! {
|
let imports = quote! {
|
||||||
@ -727,7 +728,12 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
"LpUart" => {
|
"LpUart" => {
|
||||||
create_peripheral.push(quote!(
|
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() };
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -61,14 +61,14 @@ ufmt-write = { version = "0.1.0", optional = true }
|
|||||||
# IMPORTANT:
|
# IMPORTANT:
|
||||||
# Each supported device MUST have its PAC included below along with a
|
# Each supported device MUST have its PAC included below along with a
|
||||||
# corresponding feature.
|
# corresponding feature.
|
||||||
esp32 = { 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 = "8231fea", 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 = "8231fea", 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 = "8231fea", 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 = "8231fea", 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 = "8231fea", 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 = "8231fea", 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 = "8231fea", features = ["critical-section"], optional = true }
|
esp32s3 = { git = "https://github.com/esp-rs/esp-pacs", rev = "ef39b1c", features = ["critical-section"], optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
basic-toml = "0.1.8"
|
basic-toml = "0.1.8"
|
||||||
|
@ -2616,6 +2616,8 @@ pub mod rtc_io {
|
|||||||
|
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
#[cfg(esp32c6)]
|
||||||
|
use super::OpenDrain;
|
||||||
use super::{Floating, Input, Output, PullDown, PullUp, PushPull, Unknown};
|
use super::{Floating, Input, Output, PullDown, PullUp, PushPull, Unknown};
|
||||||
|
|
||||||
/// A GPIO pin configured for low power operation
|
/// A GPIO pin configured for low power operation
|
||||||
@ -2735,6 +2737,23 @@ pub mod rtc_io {
|
|||||||
private: PhantomData,
|
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)]
|
#[cfg(esp32s3)]
|
||||||
@ -2787,6 +2806,8 @@ pub mod lp_gpio {
|
|||||||
|
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
#[cfg(esp32c6)]
|
||||||
|
use super::OpenDrain;
|
||||||
use super::{Floating, Input, Output, PullDown, PullUp, PushPull, Unknown};
|
use super::{Floating, Input, Output, PullDown, PullUp, PushPull, Unknown};
|
||||||
|
|
||||||
/// A GPIO pin configured for low power operation
|
/// A GPIO pin configured for low power operation
|
||||||
@ -2880,6 +2901,21 @@ pub mod lp_gpio {
|
|||||||
private: PhantomData,
|
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) {
|
pub(crate) fn init_low_power_pin(pin: u8) {
|
||||||
|
@ -1649,3 +1649,470 @@ impl Instance for crate::peripherals::I2C1 {
|
|||||||
1
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 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 the `ulp-riscv-hal` package (#840)
|
||||||
- Add LP_UART basic driver (#1113)
|
- Add LP_UART basic driver (#1113)
|
||||||
|
- Added basic `LP-I2C` driver for C6 (#1185)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ categories = [
|
|||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
embedded-hal-02 = { version = "0.2.7", package = "embedded-hal", optional = true, features = ["unproven"] }
|
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 }
|
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 }
|
esp32s2-ulp = { version = "0.1.0", features = ["critical-section"], optional = true }
|
||||||
esp32s3-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 }
|
nb = { version = "1.1.0", optional = true }
|
||||||
@ -59,6 +59,10 @@ required-features = ["embedded-hal-02"]
|
|||||||
name = "uart"
|
name = "uart"
|
||||||
required-features = ["embedded-hal-02", "esp32c6"]
|
required-features = ["embedded-hal-02", "esp32c6"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "i2c"
|
||||||
|
required-features = ["embedded-hal-02", "esp32c6"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
esp32s2-ulp = { git = "https://github.com/esp-rs/esp-pacs", rev = "bcab40a" }
|
esp32s2-ulp = { git = "https://github.com/esp-rs/esp-pacs", rev = "bcab40a" }
|
||||||
esp32s3-ulp = { git = "https://github.com/esp-rs/esp-pacs", rev = "bcab40a" }
|
esp32s3-ulp = { git = "https://github.com/esp-rs/esp-pacs", rev = "bcab40a" }
|
||||||
|
25
esp-lp-hal/examples/i2c.rs
Normal file
25
esp-lp-hal/examples/i2c.rs
Normal 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
587
esp-lp-hal/src/i2c.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,8 @@ use core::arch::global_asm;
|
|||||||
pub mod delay;
|
pub mod delay;
|
||||||
pub mod gpio;
|
pub mod gpio;
|
||||||
#[cfg(esp32c6)]
|
#[cfg(esp32c6)]
|
||||||
|
pub mod i2c;
|
||||||
|
#[cfg(esp32c6)]
|
||||||
pub mod uart;
|
pub mod uart;
|
||||||
|
|
||||||
pub mod pac {
|
pub mod pac {
|
||||||
|
@ -5,10 +5,10 @@ use crate::pac::LP_UART;
|
|||||||
const UART_FIFO_SIZE: u16 = 128;
|
const UART_FIFO_SIZE: u16 = 128;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub unsafe fn conjure() -> Option<LpUart> {
|
pub unsafe fn conjure() -> LpUart {
|
||||||
Some(LpUart {
|
LpUart {
|
||||||
uart: LP_UART::steal(),
|
uart: LP_UART::steal(),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
46
esp32c6-hal/examples/lp_core_i2c.rs
Normal file
46
esp32c6-hal/examples/lp_core_i2c.rs
Normal 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 {}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
//! This shows a very basic example of running code on the LP core.
|
//! 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
|
//! 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
|
//! 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);
|
let mut uart1 = Uart::new_with_config(peripherals.UART1, config, Some(pins), &clocks);
|
||||||
|
|
||||||
// Set up (LP) UART:
|
// Set up (LP) UART:
|
||||||
|
|
||||||
let lp_tx = io.pins.gpio5.into_low_power().into_push_pull_output();
|
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();
|
let lp_rx = io.pins.gpio4.into_low_power().into_floating_input();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user