esp_hal/i2c/lp_i2c.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 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());
}
}