Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move I2C driver to i2c::master #2476

Merged
merged 2 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- The I2S driver has been moved to `i2s::master` (#2472)
- `slave::Spi` constructors no longer take pins (#2485)
- The `I2c` master driver has been moved to `esp_hal::i2c::master`. (#2476)
bugadani marked this conversation as resolved.
Show resolved Hide resolved

### Fixed

Expand Down
4 changes: 3 additions & 1 deletion esp-hal/MIGRATING-0.21.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
```

## 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`.

Expand Down
341 changes: 341 additions & 0 deletions esp-hal/src/i2c/lp_i2c.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading
Loading