Move I2C driver to i2c::master (#2476)
* Move I2C driver to i2c::master * Update esp-hal/CHANGELOG.md Co-authored-by: Dominic Fischer <14130965+Dominaezzz@users.noreply.github.com> --------- Co-authored-by: Dominic Fischer <14130965+Dominaezzz@users.noreply.github.com>
This commit is contained in:
parent
31d714a156
commit
6d6f6a7baa
@ -58,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Peripheral interconnections via GPIO pins now use the GPIO matrix. (#2419)
|
- Peripheral interconnections via GPIO pins now use the GPIO matrix. (#2419)
|
||||||
- The I2S driver has been moved to `i2s::master` (#2472)
|
- The I2S driver has been moved to `i2s::master` (#2472)
|
||||||
- `slave::Spi` constructors no longer take pins (#2485)
|
- `slave::Spi` constructors no longer take pins (#2485)
|
||||||
|
- The `I2c` master driver has been moved from `esp_hal::i2c` to `esp_hal::i2c::master`. (#2476)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,9 @@ the peripheral instance has been moved to the last generic parameter position.
|
|||||||
let spi: Spi<'static, FullDuplexMode, SPI2> = Spi::new_typed(peripherals.SPI2, 1.MHz(), SpiMode::Mode0);
|
let spi: Spi<'static, FullDuplexMode, SPI2> = Spi::new_typed(peripherals.SPI2, 1.MHz(), SpiMode::Mode0);
|
||||||
```
|
```
|
||||||
|
|
||||||
## I2C constructor changes
|
## I2C changes
|
||||||
|
|
||||||
|
The I2C master driver and related types have been moved to `esp_hal::i2c::master`.
|
||||||
|
|
||||||
The `with_timeout` constructors have been removed in favour of `set_timeout` or `with_timeout`.
|
The `with_timeout` constructors have been removed in favour of `set_timeout` or `with_timeout`.
|
||||||
|
|
||||||
|
|||||||
341
esp-hal/src/i2c/lp_i2c.rs
Normal file
341
esp-hal/src/i2c/lp_i2c.rs
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
//! Low-power I2C driver
|
||||||
|
|
||||||
|
use fugit::HertzU32;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
gpio::lp_io::LowPowerOutputOpenDrain,
|
||||||
|
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 {
|
||||||
|
/// The transmission exceeded the FIFO size.
|
||||||
|
ExceedingFifo,
|
||||||
|
/// The acknowledgment check failed.
|
||||||
|
AckCheckFailed,
|
||||||
|
/// A timeout occurred during transmission.
|
||||||
|
TimeOut,
|
||||||
|
/// The arbitration for the bus was lost.
|
||||||
|
ArbitrationLost,
|
||||||
|
/// The execution of the I2C command was incomplete.
|
||||||
|
ExecIncomplete,
|
||||||
|
/// The number of commands issued exceeded the limit.
|
||||||
|
CommandNrExceeded,
|
||||||
|
/// The response received from the I2C device was invalid.
|
||||||
|
InvalidResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
enum OperationType {
|
||||||
|
Write = 0,
|
||||||
|
Read = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(Eq, PartialEq, Copy, Clone)]
|
||||||
|
enum Ack {
|
||||||
|
Ack,
|
||||||
|
Nack,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/// Represents a Low-Power I2C peripheral.
|
||||||
|
pub struct LpI2c {
|
||||||
|
i2c: LP_I2C0,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LpI2c {
|
||||||
|
/// Creates a new instance of the `LpI2c` peripheral.
|
||||||
|
pub fn new(
|
||||||
|
i2c: LP_I2C0,
|
||||||
|
_sda: LowPowerOutputOpenDrain<'_, 6>,
|
||||||
|
_scl: LowPowerOutputOpenDrain<'_, 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 {
|
||||||
|
// FIXME: use GPIO APIs to configure pins
|
||||||
|
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.gpio(6).modify(|_, w| w.mcu_sel().bits(1)); // TODO
|
||||||
|
|
||||||
|
lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1));
|
||||||
|
|
||||||
|
// Set output mode to Normal
|
||||||
|
lp_io.pin(6).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.gpio(6).modify(|_, w| w.fun_ie().set_bit());
|
||||||
|
|
||||||
|
// Disable pulldown (enable internal weak pull-down)
|
||||||
|
lp_io.gpio(6).modify(|_, w| w.fun_wpd().clear_bit());
|
||||||
|
// Enable pullup
|
||||||
|
lp_io.gpio(6).modify(|_, w| w.fun_wpu().set_bit());
|
||||||
|
|
||||||
|
// Same process for SCL pin
|
||||||
|
lp_io.pin(7).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.gpio(7).modify(|_, w| w.fun_ie().set_bit());
|
||||||
|
// Disable pulldown (enable internal weak pull-down)
|
||||||
|
lp_io.gpio(7).modify(|_, w| w.fun_wpd().clear_bit());
|
||||||
|
// Enable pullup
|
||||||
|
lp_io.gpio(7).modify(|_, w| w.fun_wpu().set_bit());
|
||||||
|
|
||||||
|
// Select LP I2C function for the SDA and SCL pins
|
||||||
|
lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1));
|
||||||
|
lp_io.gpio(7).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
|
||||||
|
w.sda_force_out().set_bit();
|
||||||
|
w.scl_force_out().set_bit();
|
||||||
|
// Ensure that clock is enabled
|
||||||
|
w.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();
|
||||||
|
w.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();
|
||||||
|
w.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);
|
||||||
|
w.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);
|
||||||
|
w.time_out_value().bits(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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets the transmit and receive FIFO buffers.
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,4 @@
|
|||||||
//! # Inter-Integrated Circuit (I2C)
|
//! # Inter-Integrated Circuit (I2C) - Master mode
|
||||||
//!
|
|
||||||
//! I2C is a serial, synchronous, multi-device, half-duplex communication
|
|
||||||
//! protocol that allows co-existence of multiple masters and slaves on the
|
|
||||||
//! same bus. I2C uses two bidirectional open-drain lines: serial data line
|
|
||||||
//! (SDA) and serial clock line (SCL), pulled up by resistors.
|
|
||||||
//!
|
|
||||||
//! Espressif devices sometimes have more than one I2C controller, responsible
|
|
||||||
//! for handling communication on the I2C bus. A single I2C controller can be
|
|
||||||
//! a master or a slave.
|
|
||||||
//!
|
|
||||||
//! Typically, an I2C slave device has a 7-bit address or 10-bit address.
|
|
||||||
//! Devices supports both I2C Standard-mode (Sm) and Fast-mode (Fm) which can
|
|
||||||
//! go up to 100KHz and 400KHz respectively.
|
|
||||||
//!
|
//!
|
||||||
//! ## Configuration
|
//! ## Configuration
|
||||||
//!
|
//!
|
||||||
@ -31,7 +18,7 @@
|
|||||||
//!
|
//!
|
||||||
//! ```rust, no_run
|
//! ```rust, no_run
|
||||||
#![doc = crate::before_snippet!()]
|
#![doc = crate::before_snippet!()]
|
||||||
//! # use esp_hal::i2c::I2c;
|
//! # use esp_hal::i2c::master::I2c;
|
||||||
//! # use esp_hal::gpio::Io;
|
//! # use esp_hal::gpio::Io;
|
||||||
//! let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
|
//! let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
|
||||||
//!
|
//!
|
||||||
@ -2318,348 +2305,3 @@ impl Instance for AnyI2c {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(lp_i2c0)]
|
|
||||||
pub mod lp_i2c {
|
|
||||||
//! Low-power I2C driver
|
|
||||||
|
|
||||||
use fugit::HertzU32;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
gpio::lp_io::LowPowerOutputOpenDrain,
|
|
||||||
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 {
|
|
||||||
/// The transmission exceeded the FIFO size.
|
|
||||||
ExceedingFifo,
|
|
||||||
/// The acknowledgment check failed.
|
|
||||||
AckCheckFailed,
|
|
||||||
/// A timeout occurred during transmission.
|
|
||||||
TimeOut,
|
|
||||||
/// The arbitration for the bus was lost.
|
|
||||||
ArbitrationLost,
|
|
||||||
/// The execution of the I2C command was incomplete.
|
|
||||||
ExecIncomplete,
|
|
||||||
/// The number of commands issued exceeded the limit.
|
|
||||||
CommandNrExceeded,
|
|
||||||
/// The response received from the I2C device was invalid.
|
|
||||||
InvalidResponse,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
enum OperationType {
|
|
||||||
Write = 0,
|
|
||||||
Read = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
#[derive(Eq, PartialEq, Copy, Clone)]
|
|
||||||
enum Ack {
|
|
||||||
Ack,
|
|
||||||
Nack,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
/// Represents a Low-Power I2C peripheral.
|
|
||||||
pub struct LpI2c {
|
|
||||||
i2c: LP_I2C0,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LpI2c {
|
|
||||||
/// Creates a new instance of the `LpI2c` peripheral.
|
|
||||||
pub fn new(
|
|
||||||
i2c: LP_I2C0,
|
|
||||||
_sda: LowPowerOutputOpenDrain<'_, 6>,
|
|
||||||
_scl: LowPowerOutputOpenDrain<'_, 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 {
|
|
||||||
// FIXME: use GPIO APIs to configure pins
|
|
||||||
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.gpio(6).modify(|_, w| w.mcu_sel().bits(1)); // TODO
|
|
||||||
|
|
||||||
lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1));
|
|
||||||
|
|
||||||
// Set output mode to Normal
|
|
||||||
lp_io.pin(6).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.gpio(6).modify(|_, w| w.fun_ie().set_bit());
|
|
||||||
|
|
||||||
// Disable pulldown (enable internal weak pull-down)
|
|
||||||
lp_io.gpio(6).modify(|_, w| w.fun_wpd().clear_bit());
|
|
||||||
// Enable pullup
|
|
||||||
lp_io.gpio(6).modify(|_, w| w.fun_wpu().set_bit());
|
|
||||||
|
|
||||||
// Same process for SCL pin
|
|
||||||
lp_io.pin(7).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.gpio(7).modify(|_, w| w.fun_ie().set_bit());
|
|
||||||
// Disable pulldown (enable internal weak pull-down)
|
|
||||||
lp_io.gpio(7).modify(|_, w| w.fun_wpd().clear_bit());
|
|
||||||
// Enable pullup
|
|
||||||
lp_io.gpio(7).modify(|_, w| w.fun_wpu().set_bit());
|
|
||||||
|
|
||||||
// Select LP I2C function for the SDA and SCL pins
|
|
||||||
lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1));
|
|
||||||
lp_io.gpio(7).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
|
|
||||||
w.sda_force_out().set_bit();
|
|
||||||
w.scl_force_out().set_bit();
|
|
||||||
// Ensure that clock is enabled
|
|
||||||
w.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();
|
|
||||||
w.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();
|
|
||||||
w.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);
|
|
||||||
w.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);
|
|
||||||
w.time_out_value().bits(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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resets the transmit and receive FIFO buffers.
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
esp-hal/src/i2c/mod.rs
Normal file
11
esp-hal/src/i2c/mod.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//! # Inter-Integrated Circuit (I2C)
|
||||||
|
//!
|
||||||
|
//! I2C is a serial, synchronous, multi-device, half-duplex communication
|
||||||
|
//! protocol that allows co-existence of multiple masters and slaves on the
|
||||||
|
//! same bus. I2C uses two bidirectional open-drain lines: serial data line
|
||||||
|
//! (SDA) and serial clock line (SCL), pulled up by resistors.
|
||||||
|
|
||||||
|
pub mod master;
|
||||||
|
|
||||||
|
#[cfg(lp_i2c0)]
|
||||||
|
pub mod lp_i2c;
|
||||||
@ -26,7 +26,7 @@ mod imp {
|
|||||||
Pin as _esp_hal_gpio_Pin,
|
Pin as _esp_hal_gpio_Pin,
|
||||||
};
|
};
|
||||||
#[cfg(any(i2c0, i2c1))]
|
#[cfg(any(i2c0, i2c1))]
|
||||||
pub use crate::i2c::Instance as _esp_hal_i2c_Instance;
|
pub use crate::i2c::master::Instance as _esp_hal_i2c_master_Instance;
|
||||||
#[cfg(ledc)]
|
#[cfg(ledc)]
|
||||||
pub use crate::ledc::{
|
pub use crate::ledc::{
|
||||||
channel::{
|
channel::{
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use esp_backtrace as _;
|
use esp_backtrace as _;
|
||||||
use esp_hal::{gpio::Io, i2c::I2c, prelude::*, timer::timg::TimerGroup};
|
use esp_hal::{gpio::Io, i2c::master::I2c, prelude::*, timer::timg::TimerGroup};
|
||||||
use lis3dh_async::{Lis3dh, Range, SlaveAddr};
|
use lis3dh_async::{Lis3dh, Range, SlaveAddr};
|
||||||
|
|
||||||
#[esp_hal_embassy::main]
|
#[esp_hal_embassy::main]
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use esp_backtrace as _;
|
use esp_backtrace as _;
|
||||||
use esp_hal::{gpio::Io, i2c::I2c, prelude::*, timer::timg::TimerGroup};
|
use esp_hal::{gpio::Io, i2c::master::I2c, prelude::*, timer::timg::TimerGroup};
|
||||||
|
|
||||||
#[esp_hal_embassy::main]
|
#[esp_hal_embassy::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use esp_backtrace as _;
|
use esp_backtrace as _;
|
||||||
use esp_hal::{gpio::Io, i2c::I2c, prelude::*};
|
use esp_hal::{gpio::Io, i2c::master::I2c, prelude::*};
|
||||||
use esp_println::println;
|
use esp_println::println;
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
|
|||||||
@ -22,7 +22,7 @@ use embedded_graphics::{
|
|||||||
text::{Alignment, Text},
|
text::{Alignment, Text},
|
||||||
};
|
};
|
||||||
use esp_backtrace as _;
|
use esp_backtrace as _;
|
||||||
use esp_hal::{delay::Delay, gpio::Io, i2c::I2c, prelude::*};
|
use esp_hal::{delay::Delay, gpio::Io, i2c::master::I2c, prelude::*};
|
||||||
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
|
|||||||
@ -29,8 +29,7 @@ use esp_hal::{
|
|||||||
dma::{Dma, DmaPriority},
|
dma::{Dma, DmaPriority},
|
||||||
dma_rx_stream_buffer,
|
dma_rx_stream_buffer,
|
||||||
gpio::Io,
|
gpio::Io,
|
||||||
i2c,
|
i2c::{self, master::I2c},
|
||||||
i2c::I2c,
|
|
||||||
lcd_cam::{
|
lcd_cam::{
|
||||||
cam::{Camera, RxEightBits},
|
cam::{Camera, RxEightBits},
|
||||||
LcdCam,
|
LcdCam,
|
||||||
@ -170,17 +169,17 @@ pub struct Sccb<'d, T> {
|
|||||||
|
|
||||||
impl<'d, T> Sccb<'d, T>
|
impl<'d, T> Sccb<'d, T>
|
||||||
where
|
where
|
||||||
T: i2c::Instance,
|
T: i2c::master::Instance,
|
||||||
{
|
{
|
||||||
pub fn new(i2c: I2c<'d, Blocking, T>) -> Self {
|
pub fn new(i2c: I2c<'d, Blocking, T>) -> Self {
|
||||||
Self { i2c }
|
Self { i2c }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn probe(&mut self, address: u8) -> Result<(), i2c::Error> {
|
pub fn probe(&mut self, address: u8) -> Result<(), i2c::master::Error> {
|
||||||
self.i2c.write(address, &[])
|
self.i2c.write(address, &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(&mut self, address: u8, reg: u8) -> Result<u8, i2c::Error> {
|
pub fn read(&mut self, address: u8, reg: u8) -> Result<u8, i2c::master::Error> {
|
||||||
self.i2c.write(address, &[reg])?;
|
self.i2c.write(address, &[reg])?;
|
||||||
|
|
||||||
let mut bytes = [0u8; 1];
|
let mut bytes = [0u8; 1];
|
||||||
@ -188,7 +187,7 @@ where
|
|||||||
Ok(bytes[0])
|
Ok(bytes[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&mut self, address: u8, reg: u8, data: u8) -> Result<(), i2c::Error> {
|
pub fn write(&mut self, address: u8, reg: u8, data: u8) -> Result<(), i2c::master::Error> {
|
||||||
self.i2c.write(address, &[reg, data])
|
self.i2c.write(address, &[reg, data])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
use esp_hal::{
|
use esp_hal::{
|
||||||
gpio::Io,
|
gpio::Io,
|
||||||
i2c::{I2c, Operation},
|
i2c::master::{I2c, Operation},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
Async,
|
Async,
|
||||||
Blocking,
|
Blocking,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user