esp_hal/i2c/
lp_i2c.rs

1//! Low-power I2C driver
2
3use crate::{
4    gpio::lp_io::LowPowerOutputOpenDrain,
5    peripherals::{LPWR, LP_AON, LP_I2C0, LP_IO, LP_PERI},
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,
83}
84
85impl LpI2c {
86    /// Creates a new instance of the `LpI2c` peripheral.
87    pub fn new(
88        i2c: LP_I2C0,
89        _sda: LowPowerOutputOpenDrain<'_, 6>,
90        _scl: LowPowerOutputOpenDrain<'_, 7>,
91        frequency: Rate,
92    ) -> Self {
93        let me = Self { i2c };
94
95        // Configure LP I2C GPIOs
96        // Initialize IO Pins
97        let lp_io = LP_IO::regs();
98        let lp_aon = LP_AON::regs();
99        let lp_peri = LP_PERI::regs();
100
101        unsafe {
102            // FIXME: use GPIO APIs to configure pins
103            lp_aon
104                .gpio_mux()
105                .modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 6)));
106            lp_aon
107                .gpio_mux()
108                .modify(|r, w| w.sel().bits(r.sel().bits() | (1 << 7)));
109            lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1)); // TODO
110
111            lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1));
112
113            // Set output mode to Normal
114            lp_io.pin(6).modify(|_, w| w.pad_driver().set_bit());
115            // Enable output (writing to write-1-to-set register, then internally the
116            // `GPIO_OUT_REG` will be set)
117            lp_io
118                .out_enable_w1ts()
119                .write(|w| w.enable_w1ts().bits(1 << 6));
120            // Enable input
121            lp_io.gpio(6).modify(|_, w| w.fun_ie().set_bit());
122
123            // Disable pulldown (enable internal weak pull-down)
124            lp_io.gpio(6).modify(|_, w| w.fun_wpd().clear_bit());
125            // Enable pullup
126            lp_io.gpio(6).modify(|_, w| w.fun_wpu().set_bit());
127
128            // Same process for SCL pin
129            lp_io.pin(7).modify(|_, w| w.pad_driver().set_bit());
130            // Enable output (writing to write-1-to-set register, then internally the
131            // `GPIO_OUT_REG` will be set)
132            lp_io
133                .out_enable_w1ts()
134                .write(|w| w.enable_w1ts().bits(1 << 7));
135            // Enable input
136            lp_io.gpio(7).modify(|_, w| w.fun_ie().set_bit());
137            // Disable pulldown (enable internal weak pull-down)
138            lp_io.gpio(7).modify(|_, w| w.fun_wpd().clear_bit());
139            // Enable pullup
140            lp_io.gpio(7).modify(|_, w| w.fun_wpu().set_bit());
141
142            // Select LP I2C function for the SDA and SCL pins
143            lp_io.gpio(6).modify(|_, w| w.mcu_sel().bits(1));
144            lp_io.gpio(7).modify(|_, w| w.mcu_sel().bits(1));
145        }
146
147        // Initialize LP I2C HAL */
148        me.i2c
149            .register_block()
150            .clk_conf()
151            .modify(|_, w| w.sclk_active().set_bit());
152
153        // Enable LP I2C controller clock
154        lp_peri
155            .clk_en()
156            .modify(|_, w| w.lp_ext_i2c_ck_en().set_bit());
157
158        lp_peri
159            .reset_en()
160            .modify(|_, w| w.lp_ext_i2c_reset_en().set_bit());
161        lp_peri
162            .reset_en()
163            .modify(|_, w| w.lp_ext_i2c_reset_en().clear_bit());
164
165        // Set LP I2C source clock
166        LPWR::regs()
167            .lpperi()
168            .modify(|_, w| w.lp_i2c_clk_sel().clear_bit());
169
170        // Initialize LP I2C Master mode
171        me.i2c.register_block().ctr().modify(|_, w| unsafe {
172            // Clear register
173            w.bits(0);
174            // Use open drain output for SDA and SCL
175            w.sda_force_out().set_bit();
176            w.scl_force_out().set_bit();
177            // Ensure that clock is enabled
178            w.clk_en().set_bit()
179        });
180
181        // First, reset the fifo buffers
182        me.i2c
183            .register_block()
184            .fifo_conf()
185            .modify(|_, w| w.nonfifo_en().clear_bit());
186
187        me.i2c.register_block().ctr().modify(|_, w| {
188            w.tx_lsb_first().clear_bit();
189            w.rx_lsb_first().clear_bit()
190        });
191
192        me.reset_fifo();
193
194        // Set LP I2C source clock
195        LPWR::regs()
196            .lpperi()
197            .modify(|_, w| w.lp_i2c_clk_sel().clear_bit());
198
199        // Configure LP I2C timing paramters. source_clk is ignored for LP_I2C in this
200        // call
201
202        let source_clk = 16_000_000;
203        let bus_freq = frequency.as_hz();
204
205        let clkm_div: u32 = source_clk / (bus_freq * 1024) + 1;
206        let sclk_freq: u32 = source_clk / clkm_div;
207        let half_cycle: u32 = sclk_freq / bus_freq / 2;
208
209        // SCL
210        let scl_low = half_cycle;
211        // default, scl_wait_high < scl_high
212        // Make 80KHz as a boundary here, because when working at lower frequency, too
213        // much scl_wait_high will faster the frequency according to some
214        // hardware behaviors.
215        let scl_wait_high = if bus_freq >= 80 * 1000 {
216            half_cycle / 2 - 2
217        } else {
218            half_cycle / 4
219        };
220        let scl_high = half_cycle - scl_wait_high;
221        let sda_hold = half_cycle / 4;
222        let sda_sample = half_cycle / 2; // TODO + scl_wait_high;
223        let setup = half_cycle;
224        let hold = half_cycle;
225        // default we set the timeout value to about 10 bus cycles
226        // log(20*half_cycle)/log(2) = log(half_cycle)/log(2) +  log(20)/log(2)
227        let tout = (4 * 8 - (5 * half_cycle).leading_zeros()) + 2;
228
229        // According to the Technical Reference Manual, the following timings must be
230        // subtracted by 1. However, according to the practical measurement and
231        // some hardware behaviour, if wait_high_period and scl_high minus one.
232        // The SCL frequency would be a little higher than expected. Therefore, the
233        // solution here is not to minus scl_high as well as scl_wait high, and
234        // the frequency will be absolutely accurate to all frequency
235        // to some extent.
236        let scl_low_period = scl_low - 1;
237        let scl_high_period = scl_high;
238        let scl_wait_high_period = scl_wait_high;
239        // sda sample
240        let sda_hold_time = sda_hold - 1;
241        let sda_sample_time = sda_sample - 1;
242        // setup
243        let scl_rstart_setup_time = setup - 1;
244        let scl_stop_setup_time = setup - 1;
245        // hold
246        let scl_start_hold_time = hold - 1;
247        let scl_stop_hold_time = hold - 1;
248        let time_out_value = tout;
249        let time_out_en = true;
250
251        // Write data to registers
252        unsafe {
253            me.i2c.register_block().clk_conf().modify(|_, w| {
254                w.sclk_sel().clear_bit();
255                w.sclk_div_num().bits((clkm_div - 1) as u8)
256            });
257
258            // scl period
259            me.i2c
260                .register_block()
261                .scl_low_period()
262                .write(|w| w.scl_low_period().bits(scl_low_period as u16));
263
264            me.i2c.register_block().scl_high_period().write(|w| {
265                w.scl_high_period().bits(scl_high_period as u16);
266                w.scl_wait_high_period().bits(scl_wait_high_period as u8)
267            });
268            // sda sample
269            me.i2c
270                .register_block()
271                .sda_hold()
272                .write(|w| w.time().bits(sda_hold_time as u16));
273            me.i2c
274                .register_block()
275                .sda_sample()
276                .write(|w| w.time().bits(sda_sample_time as u16));
277
278            // setup
279            me.i2c
280                .register_block()
281                .scl_rstart_setup()
282                .write(|w| w.time().bits(scl_rstart_setup_time as u16));
283            me.i2c
284                .register_block()
285                .scl_stop_setup()
286                .write(|w| w.time().bits(scl_stop_setup_time as u16));
287
288            // hold
289            me.i2c
290                .register_block()
291                .scl_start_hold()
292                .write(|w| w.time().bits(scl_start_hold_time as u16));
293            me.i2c
294                .register_block()
295                .scl_stop_hold()
296                .write(|w| w.time().bits(scl_stop_hold_time as u16));
297
298            me.i2c.register_block().to().write(|w| {
299                w.time_out_en().bit(time_out_en);
300                w.time_out_value().bits(time_out_value.try_into().unwrap())
301            });
302        }
303
304        // Enable SDA and SCL filtering. This configuration matches the HP I2C filter
305        // config
306
307        me.i2c
308            .register_block()
309            .filter_cfg()
310            .modify(|_, w| unsafe { w.sda_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) });
311        me.i2c
312            .register_block()
313            .filter_cfg()
314            .modify(|_, w| unsafe { w.scl_filter_thres().bits(LP_I2C_FILTER_CYC_NUM_DEF) });
315
316        me.i2c
317            .register_block()
318            .filter_cfg()
319            .modify(|_, w| w.sda_filter_en().set_bit());
320        me.i2c
321            .register_block()
322            .filter_cfg()
323            .modify(|_, w| w.scl_filter_en().set_bit());
324
325        // Configure the I2C master to send a NACK when the Rx FIFO count is full
326        me.i2c
327            .register_block()
328            .ctr()
329            .modify(|_, w| w.rx_full_ack_level().set_bit());
330
331        // Synchronize the config register values to the LP I2C peripheral clock
332        me.lp_i2c_update();
333
334        me
335    }
336
337    /// Update I2C configuration
338    fn lp_i2c_update(&self) {
339        self.i2c
340            .register_block()
341            .ctr()
342            .modify(|_, w| w.conf_upgate().set_bit());
343    }
344
345    /// Resets the transmit and receive FIFO buffers.
346    fn reset_fifo(&self) {
347        self.i2c
348            .register_block()
349            .fifo_conf()
350            .modify(|_, w| w.tx_fifo_rst().set_bit());
351
352        self.i2c
353            .register_block()
354            .fifo_conf()
355            .modify(|_, w| w.tx_fifo_rst().clear_bit());
356
357        self.i2c
358            .register_block()
359            .fifo_conf()
360            .modify(|_, w| w.rx_fifo_rst().set_bit());
361
362        self.i2c
363            .register_block()
364            .fifo_conf()
365            .modify(|_, w| w.rx_fifo_rst().clear_bit());
366    }
367}