mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 12:50:53 +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)
|
||||
- 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
|
||||
|
||||
|
@ -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() };
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
|
@ -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"
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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" }
|
||||
|
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 gpio;
|
||||
#[cfg(esp32c6)]
|
||||
pub mod i2c;
|
||||
#[cfg(esp32c6)]
|
||||
pub mod uart;
|
||||
|
||||
pub mod pac {
|
||||
|
@ -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)]
|
||||
|
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.
|
||||
//!
|
||||
//! 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();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user