Skip to main content

esp_hal/i2c/
lp_i2c.rs

1//! Low-power I2C driver
2
3use crate::{
4    gpio::lp_io::LowPowerOutputOpenDrain,
5    peripherals::{LP_AON, LP_I2C0, LP_IO, LP_PERI, LPWR},
6    time::Rate,
7};
8
9const LP_I2C_FILTER_CYC_NUM_DEF: u8 = 7;
10
11/// I2C-specific transmission errors
12#[derive(Debug, Clone, Copy, PartialEq)]
13#[cfg_attr(feature = "defmt", derive(defmt::Format))]
14pub enum Error {
15    /// The transmission exceeded the FIFO size.
16    ExceedingFifo,
17    /// The acknowledgment check failed.
18    AckCheckFailed,
19    /// A timeout occurred during transmission.
20    TimeOut,
21    /// The arbitration for the bus was lost.
22    ArbitrationLost,
23    /// The execution of the I2C command was incomplete.
24    ExecIncomplete,
25    /// The number of commands issued exceeded the limit.
26    CommandNrExceeded,
27    /// The response received from the I2C device was invalid.
28    InvalidResponse,
29}
30
31#[allow(unused)]
32enum OperationType {
33    Write = 0,
34    Read  = 1,
35}
36
37#[allow(unused)]
38#[derive(Eq, PartialEq, Copy, Clone)]
39enum Ack {
40    Ack,
41    Nack,
42}
43
44#[derive(PartialEq)]
45#[allow(unused)]
46enum Command {
47    Start,
48    Stop,
49    End,
50    Write {
51        /// This bit is to set an expected ACK value for the transmitter.
52        ack_exp: Ack,
53        /// Enables checking the ACK value received against the ack_exp
54        /// value.
55        ack_check_en: bool,
56        /// Length of data (in bytes) to be written. The maximum length is
57        /// 255, while the minimum is 1.
58        length: u8,
59    },
60    Read {
61        /// Indicates whether the receiver will send an ACK after this byte
62        /// has been received.
63        ack_value: Ack,
64        /// Length of data (in bytes) to be read. The maximum length is 255,
65        /// while the minimum is 1.
66        length: u8,
67    },
68}
69
70// https://github.com/espressif/esp-idf/blob/master/components/ulp/lp_core/lp_core_i2c.c#L122
71// TX/RX RAM size is 16*8 bit
72// TX RX FIFO has 16 bit depth
73// The clock source of APB_CLK in LP_I2C is CLK_AON_FAST.
74// Configure LP_I2C_SCLK_SEL to select the clock source for I2C_SCLK.
75// When LP_I2C_SCLK_SEL is 0, select CLK_ROOT_FAST as clock source,
76// and when LP_I2C_SCLK_SEL is 1, select CLK _XTALD2 as the clock source.
77// Configure LP_EXT_I2C_CK_EN high to enable the clock source of I2C_SCLK.
78// Adjust the timing registers accordingly when the clock frequency changes.
79
80/// Represents a Low-Power I2C peripheral.
81pub struct LpI2c {
82    i2c: LP_I2C0<'static>,
83}
84
85impl LpI2c {
86    fn lp_i2c_configure_io(ionum: usize, pullup_en: bool) {
87        let lp_io = LP_IO::regs();
88        let lp_aon = LP_AON::regs();
89        unsafe {
90            // Set the IO pin to high to avoid them from toggling from Low to
91            // High state during initialization. This can register a spurious
92            // I2C start condition.
93            lp_io
94                .out_data_w1ts()
95                .write(|w| w.out_data_w1ts().bits(1 << ionum));
96
97            lp_aon
98                .gpio_mux()
99                .modify(|r, w| w.sel().bits(r.sel().bits() | (1 << ionum)));
100
101            // Set output mode to Open Drain
102            lp_io.pin(ionum).modify(|_, w| w.pad_driver().set_bit());
103
104            // Enable output (writing to write-1-to-set register, then internally the
105            // `GPIO_OUT_REG` will be set)
106            lp_io
107                .out_enable_w1ts()
108                .write(|w| w.enable_w1ts().bits(1 << ionum));
109
110            lp_io.gpio(ionum).modify(|_, w| {
111                // Enable input
112                w.fun_ie().set_bit();
113                // Disable the internal weak pull-down
114                w.fun_wpd().clear_bit();
115                // Configure the internal weak pull-up
116                w.fun_wpu().bit(pullup_en)
117            });
118        }
119    }
120
121    /// Creates a new instance of the `LpI2c` peripheral.
122    pub fn new(
123        i2c: LP_I2C0<'static>,
124        _sda: LowPowerOutputOpenDrain<'_, 6>,
125        _scl: LowPowerOutputOpenDrain<'_, 7>,
126        frequency: Rate,
127    ) -> Self {
128        let me = Self { i2c };
129
130        // Configure LP I2C GPIOs
131
132        // Initialize IO Pins
133        // NOTE: We always initialize the SCL pin (GPIO7) first, then the
134        // SDA (GPIO6) pin. This order of initialization is important to
135        // avoid any spurious I2C start conditions on the bus.
136        Self::lp_i2c_configure_io(7, true);
137        Self::lp_i2c_configure_io(6, true);
138        unsafe {
139            let lp_io = LP_IO::regs();
140            // Select LP I2C function for the SDA and SCL pins
141            lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1));
142            lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1));
143        }
144
145        // Initialize LP I2C HAL */
146        me.i2c
147            .register_block()
148            .clk_conf()
149            .modify(|_, w| w.sclk_active().set_bit());
150
151        let lp_peri = LP_PERI::regs();
152        // Enable LP I2C controller clock
153        lp_peri
154            .clk_en()
155            .modify(|_, w| w.lp_ext_i2c_ck_en().set_bit());
156
157        lp_peri
158            .reset_en()
159            .modify(|_, w| w.lp_ext_i2c_reset_en().set_bit());
160        lp_peri
161            .reset_en()
162            .modify(|_, w| w.lp_ext_i2c_reset_en().clear_bit());
163
164        // Set LP I2C source clock
165        LPWR::regs()
166            .lpperi()
167            .modify(|_, w| w.lp_i2c_clk_sel().clear_bit());
168
169        // Initialize LP I2C Master mode
170        me.i2c.register_block().ctr().modify(|_, w| unsafe {
171            // Clear register
172            w.bits(0);
173            // Use open drain output for SDA and SCL
174            w.sda_force_out().set_bit();
175            w.scl_force_out().set_bit();
176            // Ensure that clock is enabled
177            w.clk_en().set_bit()
178        });
179
180        // First, reset the fifo buffers
181        me.i2c
182            .register_block()
183            .fifo_conf()
184            .modify(|_, w| w.nonfifo_en().clear_bit());
185
186        me.i2c.register_block().ctr().modify(|_, w| {
187            w.tx_lsb_first().clear_bit();
188            w.rx_lsb_first().clear_bit()
189        });
190
191        me.reset_fifo();
192
193        // Set LP I2C source clock
194        LPWR::regs()
195            .lpperi()
196            .modify(|_, w| w.lp_i2c_clk_sel().clear_bit());
197
198        // Configure LP I2C timing paramters. source_clk is ignored for LP_I2C in this
199        // call
200
201        let source_clk = 16_000_000;
202        let bus_freq = frequency.as_hz();
203
204        let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1;
205        let sclk_freq: u32 = source_clk / clkm_div;
206        let half_cycle: u32 = sclk_freq / bus_freq / 2;
207
208        // SCL
209        let scl_low = half_cycle;
210        // default, scl_wait_high < scl_high
211        // Make 80KHz as a boundary here, because when working at lower frequency, too
212        // much scl_wait_high will faster the frequency according to some
213        // hardware behaviors.
214        let scl_wait_high = if bus_freq >= 80 * 1000 {
215            half_cycle / 2 - 2
216        } else {
217            half_cycle / 4
218        };
219        let scl_high = half_cycle - scl_wait_high;
220        let sda_hold = half_cycle / 4;
221        let sda_sample = half_cycle / 2; // TODO + scl_wait_high;
222        let setup = half_cycle;
223        let hold = half_cycle;
224        // default we set the timeout value to about 10 bus cycles
225        // log(20*half_cycle)/log(2) = log(half_cycle)/log(2) +  log(20)/log(2)
226        let tout = (4 * 8 - (5 * half_cycle).leading_zeros()) + 2;
227
228        // According to the Technical Reference Manual, the following timings must be
229        // subtracted by 1. However, according to the practical measurement and
230        // some hardware behaviour, if wait_high_period and scl_high minus one.
231        // The SCL frequency would be a little higher than expected. Therefore, the
232        // solution here is not to minus scl_high as well as scl_wait high, and
233        // the frequency will be absolutely accurate to all frequency
234        // to some extent.
235        let scl_low_period = scl_low - 1;
236        let scl_high_period = scl_high;
237        let scl_wait_high_period = scl_wait_high;
238        // sda sample
239        let sda_hold_time = sda_hold - 1;
240        let sda_sample_time = sda_sample - 1;
241        // setup
242        let scl_rstart_setup_time = setup - 1;
243        let scl_stop_setup_time = setup - 1;
244        // hold
245        let scl_start_hold_time = hold - 1;
246        let scl_stop_hold_time = hold - 1;
247        let time_out_value = tout;
248        let time_out_en = true;
249
250        // Write data to registers
251        unsafe {
252            me.i2c.register_block().clk_conf().modify(|_, w| {
253                w.sclk_sel().clear_bit();
254                w.sclk_div_num().bits((clkm_div - 1) as u8)
255            });
256
257            // scl period
258            me.i2c
259                .register_block()
260                .scl_low_period()
261                .write(|w| w.scl_low_period().bits(scl_low_period as u16));
262
263            me.i2c.register_block().scl_high_period().write(|w| {
264                w.scl_high_period().bits(scl_high_period as u16);
265                w.scl_wait_high_period().bits(scl_wait_high_period as u8)
266            });
267            // sda sample
268            me.i2c
269                .register_block()
270                .sda_hold()
271                .write(|w| w.time().bits(sda_hold_time as u16));
272            me.i2c
273                .register_block()
274                .sda_sample()
275                .write(|w| w.time().bits(sda_sample_time as u16));
276
277            // setup
278            me.i2c
279                .register_block()
280                .scl_rstart_setup()
281                .write(|w| w.time().bits(scl_rstart_setup_time as u16));
282            me.i2c
283                .register_block()
284                .scl_stop_setup()
285                .write(|w| w.time().bits(scl_stop_setup_time as u16));
286
287            // hold
288            me.i2c
289                .register_block()
290                .scl_start_hold()
291                .write(|w| w.time().bits(scl_start_hold_time as u16));
292            me.i2c
293                .register_block()
294                .scl_stop_hold()
295                .write(|w| w.time().bits(scl_stop_hold_time as u16));
296
297            me.i2c.register_block().to().write(|w| {
298                w.time_out_en().bit(time_out_en);
299                w.time_out_value().bits(time_out_value.try_into().unwrap())
300            });
301        }
302
303        // Enable SDA and SCL filtering. This configuration matches the HP I2C filter
304        // config
305
306        me.i2c
307            .register_block()
308            .filter_cfg()
309            .modify(|_, w| unsafe { w.sda_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) });
310        me.i2c
311            .register_block()
312            .filter_cfg()
313            .modify(|_, w| unsafe { w.scl_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) });
314
315        me.i2c
316            .register_block()
317            .filter_cfg()
318            .modify(|_, w| w.sda_filter_en().set_bit());
319        me.i2c
320            .register_block()
321            .filter_cfg()
322            .modify(|_, w| w.scl_filter_en().set_bit());
323
324        // Configure the I2C master to send a NACK when the Rx FIFO count is full
325        me.i2c
326            .register_block()
327            .ctr()
328            .modify(|_, w| w.rx_full_ack_level().set_bit());
329
330        // Synchronize the config register values to the LP I2C peripheral clock
331        me.lp_i2c_update();
332
333        me
334    }
335
336    /// Update I2C configuration
337    fn lp_i2c_update(&self) {
338        self.i2c
339            .register_block()
340            .ctr()
341            .modify(|_, w| w.conf_upgate().set_bit());
342    }
343
344    /// Resets the transmit and receive FIFO buffers.
345    fn reset_fifo(&self) {
346        self.i2c
347            .register_block()
348            .fifo_conf()
349            .modify(|_, w| w.tx_fifo_rst().set_bit());
350
351        self.i2c
352            .register_block()
353            .fifo_conf()
354            .modify(|_, w| w.tx_fifo_rst().clear_bit());
355
356        self.i2c
357            .register_block()
358            .fifo_conf()
359            .modify(|_, w| w.rx_fifo_rst().set_bit());
360
361        self.i2c
362            .register_block()
363            .fifo_conf()
364            .modify(|_, w| w.rx_fifo_rst().clear_bit());
365    }
366}