i2c_pio/
lib.rs

1#![no_std]
2//! Implements an I2C Controller using the RP2040 PIO block.
3//!
4//! This implementation is based on the c-sdk's example with the following variation:
5//!
6//! - It uses WAIT on GPIO to handle clock stretching without requiring SCL to be SDA+1.
7//! - It keeps autopush enabled a flushes the RX FIFO during write operations.
8//!
9//! # General command word description
10//!
11//! TX Encoding:
12//! | 15:10 | 9     | 8:1  | 0   |
13//! | Instr | Final | Data | NAK |
14//!
15//! If Instr has a value n > 0, then this FIFO word has no
16//! data payload, and the next n + 1 words will be executed as instructions.
17//! Otherwise, shift out the 8 data bits, followed by the ACK bit.
18//!
19//! The Instr mechanism allows stop/start/repstart sequences to be programmed
20//! by the processor, and then carried out by the state machine at defined points
21//! in the datastream.
22//!
23//! The "Final" field should be set for the final byte in a transfer.
24//! This tells the state machine to ignore a NAK: if this field is not
25//! set, then any NAK will cause the state machine to halt and interrupt.
26//!
27//! Autopull should be enabled, with a threshold of 16.
28//! Autopush should be enabled, with a threshold of 8.
29//! The TX FIFO should be accessed with halfword writes, to ensure
30//! the data is immediately available in the OSR.
31//!
32//! Pin mapping:
33//! - Input pin 0 is SDA
34//! - Jump pin is SDA
35//! - Side-set pin 0 is SCL
36//! - Set pin 0 is SDA
37//! - OUT pin 0 is SDA
38//!
39//! The OE outputs should be inverted in the system IO controls!
40//! (It's possible for the inversion to be done in this program,
41//! but costs 2 instructions: 1 for inversion, and one to cope
42//! with the side effect of the MOV on TX shift counter.)
43use core::iter::once;
44
45use either::Either::{Left, Right};
46use fugit::HertzU32;
47use heapless::Deque;
48use i2c_cmd::{restart, start, CmdWord, Data};
49use pio::Instruction;
50use rp2040_hal::{
51    gpio::{
52        AnyPin, Function, FunctionNull, OutputEnableOverride, Pin, PinId, PullType, PullUp,
53        ValidFunction,
54    },
55    pio::{
56        PIOExt, PinDir, PinState, Rx, ShiftDirection, StateMachine, StateMachineIndex, Tx,
57        UninitStateMachine, PIO,
58    },
59};
60
61use crate::i2c_cmd::stop;
62
63mod eh0_2;
64mod eh1_0;
65mod i2c_cmd;
66
67/// Length of an address.
68#[derive(Debug, PartialEq, Eq)]
69#[cfg_attr(feature = "defmt", derive(defmt::Format))]
70pub enum AddressLength {
71    _7,
72    _10,
73}
74pub trait ValidAddressMode:
75    Copy + Into<u16> + embedded_hal::i2c::AddressMode + embedded_hal_0_2::blocking::i2c::AddressMode
76{
77    fn address_len() -> AddressLength;
78}
79macro_rules! impl_valid_addr {
80    ($t:path => $e:expr) => {
81        impl ValidAddressMode for $t {
82            fn address_len() -> AddressLength {
83                $e
84            }
85        }
86    };
87}
88impl_valid_addr!(u8 => AddressLength::_7);
89impl_valid_addr!(u16 => AddressLength::_10);
90// `embedded_hal`s’ SevenBitAddress and TenBitAddress are aliases to u8 and u16 respectively.
91//impl_valid_addr!(embedded_hal::i2c::SevenBitAddress => AddressLength::_7);
92//impl_valid_addr!(embedded_hal::i2c::TenBitAddress => AddressLength::_10);
93//impl_valid_addr!(embedded_hal_0_2::blocking::i2c::SevenBitAddress => AddressLength::_7);
94//impl_valid_addr!(embedded_hal_0_2::blocking::i2c::TenBitAddress => AddressLength::_10);
95
96fn setup<'b, A: ValidAddressMode>(
97    address: A,
98    read: bool,
99    do_restart: bool,
100) -> impl Iterator<Item = CmdWord<'b>> {
101    let read_flag = if read { 1 } else { 0 };
102    let address: u16 = address.into();
103    let address = match A::address_len() {
104        AddressLength::_7 => {
105            let address_and_flag = ((address as u8) << 1) | read_flag;
106            Left(once(address_and_flag).map(CmdWord::address))
107        }
108        AddressLength::_10 => {
109            let addr_hi = 0xF0 | ((address >> 7) as u8) & 0xFE;
110            let addr_lo = (address & 0xFF) as u8;
111
112            Right(if read {
113                let full_addr = [addr_hi, addr_lo]
114                    .into_iter()
115                    .map(Data::address)
116                    .map(CmdWord::Data);
117                let read_addr =
118                    restart().chain(once(CmdWord::Data(Data::address(addr_hi | read_flag))));
119
120                Left(full_addr.chain(read_addr))
121            } else {
122                Right(
123                    [addr_hi | read_flag, addr_lo]
124                        .into_iter()
125                        .map(Data::address)
126                        .map(CmdWord::Data),
127                )
128            })
129        }
130    };
131
132    if do_restart {
133        Left(restart())
134    } else {
135        Right(start())
136    }
137    .chain(address)
138}
139
140#[derive(Debug, PartialEq, Eq)]
141#[cfg_attr(feature = "defmt", derive(defmt::Format))]
142pub enum Error {
143    NoAcknowledgeAddress,
144    NoAcknowledgeData,
145    BusContention,
146}
147
148/// Instance of I2C Controller.
149pub struct I2C<'pio, P, SMI, SDA, SCL>
150where
151    P: PIOExt,
152    SMI: StateMachineIndex,
153    SDA: AnyPin,
154    SCL: AnyPin,
155{
156    pio: &'pio mut PIO<P>,
157    sm: StateMachine<(P, SMI), rp2040_hal::pio::Running>,
158    tx: Tx<(P, SMI)>,
159    rx: Rx<(P, SMI)>,
160    sda: (Pin<SDA::Id, P::PinFunction, PullUp>, OutputEnableOverride),
161    scl: (Pin<SCL::Id, P::PinFunction, PullUp>, OutputEnableOverride),
162}
163
164impl<'pio, P, SMI, SDA, SCL> I2C<'pio, P, SMI, SDA, SCL>
165where
166    P: PIOExt,
167    SMI: StateMachineIndex,
168    SDA: AnyPin,
169    SCL: AnyPin,
170{
171    /// Creates a new instance of this driver.
172    ///
173    /// Note: the PIO must have been reset before using this driver.
174    pub fn new(
175        pio: &'pio mut PIO<P>,
176        sda: SDA,
177        scl: SCL,
178        sm: UninitStateMachine<(P, SMI)>,
179        bus_freq: HertzU32,
180        clock_freq: HertzU32,
181    ) -> Self
182    where
183        SDA: AnyPin<Function = FunctionNull>,
184        SDA::Id: ValidFunction<P::PinFunction>,
185        SCL: AnyPin<Function = FunctionNull>,
186        SCL::Id: ValidFunction<P::PinFunction>,
187    {
188        let (sda, scl): (SDA::Type, SCL::Type) = (sda.into(), scl.into());
189
190        let mut program = pio_proc::pio_asm!(
191            ".side_set 1 opt pindirs"
192
193            "byte_nack:"
194            "  jmp  y--     byte_end  ; continue if NAK was expected"
195            "  irq  wait    0    rel  ; otherwise stop, ask for help (raises the irq line (0+SMI::id())%4)"
196            "  jmp          byte_end  ; resumed, finalize the current byte"
197
198            "byte_send:"
199            "  out  y       1         ; Unpack FINAL"
200            "  set  x       7         ; loop 8 times"
201
202            "bitloop:"
203            "  out  pindirs 1                [7] ; Serialize write data (all-ones is reading)"
204            "  nop                    side 1 [2] ; SCL rising edge"
205            //      polarity
206            "  wait 1       gpio 0           [4] ; Allow clock to be stretched"
207            "  in   pins 1                   [7] ; Sample read data in middle of SCL pulse"
208            "  jmp  x--     bitloop   side 0 [7] ; SCL falling edge"
209
210            // Handle ACK pulse
211            "  out  pindirs 1                [7] ; On reads, we provide the ACK"
212            "  nop                    side 1 [7] ; SCL rising edge"
213            //      polarity
214            "  wait 1       gpio 0           [7] ; Allow clock to be stretched"
215            "  jmp  pin     byte_nack side 0 [2] ; Test SDA for ACK/NACK, fall through if ACK"
216
217            "byte_end:"
218            "  push block             ; flush the current byte in isr to the FIFO"
219
220            ".wrap_target"
221            "  out  x       6         ; Unpack Instr count"
222            "  jmp  !x      byte_send ; Instr == 0, this is a data record"
223            "  out  null    10        ; Instr > 0, remainder of this OSR is invalid"
224
225            "do_exec:"
226            "  out  exec    16        ; Execute one instruction per FIFO word"
227            "  jmp  x--     do_exec"
228            ".wrap"
229        )
230        .program;
231        // patch the program to allow scl to be any pin
232        program.code[7] |= u16::from(scl.id().num);
233        program.code[12] |= u16::from(scl.id().num);
234
235        // Install the program into PIO instruction memory.
236        let installed = pio.install(&program).unwrap();
237        let wrap_target = installed.wrap_target();
238
239        // Configure the PIO state machine.
240        let bit_freq = 32 * bus_freq;
241        let mut int = clock_freq / bit_freq;
242        let rem = clock_freq - (int * bit_freq);
243        let frac = (rem * 256) / bit_freq;
244
245        assert!(
246            (1..=65536).contains(&int) && (int != 65536 || frac == 0),
247            "The ratio between the bus frequency and the system clock must be within [1.0, 65536.0]."
248        );
249
250        // 65536.0 is represented as 0 in the pio's clock divider
251        if int == 65536 {
252            int = 0;
253        }
254        // Using lossy conversion because range have been checked
255        let int: u16 = int as u16;
256        let frac: u8 = frac as u8;
257
258        // init
259        let (mut sm, rx, tx) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed)
260            // use both RX & TX FIFO
261            .buffers(rp2040_hal::pio::Buffers::RxTx)
262            // Pin configuration
263            .set_pins(sda.id().num, 1)
264            .out_pins(sda.id().num, 1)
265            .in_pin_base(sda.id().num)
266            .side_set_pin_base(scl.id().num)
267            .jmp_pin(sda.id().num)
268            // OSR config
269            .out_shift_direction(ShiftDirection::Left)
270            .autopull(true)
271            .pull_threshold(16)
272            // ISR config
273            .in_shift_direction(ShiftDirection::Left)
274            .push_threshold(8)
275            // clock config
276            .clock_divisor_fixed_point(int, frac)
277            .build(sm);
278
279        // enable pull up on SDA & SCL: idle bus
280        let sda: Pin<_, _, PullUp> = sda.into_pull_type();
281        let scl: Pin<_, _, PullUp> = scl.into_pull_type();
282        let sda_override = sda.get_output_enable_override();
283        let scl_override = scl.get_output_enable_override();
284
285        // This will pull the bus high for a little bit of time
286        sm.set_pins([
287            (scl.id().num, PinState::High),
288            (sda.id().num, PinState::High),
289        ]);
290        sm.set_pindirs([
291            (scl.id().num, PinDir::Output),
292            (sda.id().num, PinDir::Output),
293        ]);
294
295        // attach SDA pin to pio
296        let mut sda: Pin<SDA::Id, P::PinFunction, PullUp> = sda.into_function();
297        // configure SDA pin as inverted
298        sda.set_output_enable_override(OutputEnableOverride::Invert);
299
300        // attach SCL pin to pio
301        let mut scl: Pin<SCL::Id, P::PinFunction, PullUp> = scl.into_function();
302        // configure SCL pin as inverted
303        scl.set_output_enable_override(OutputEnableOverride::Invert);
304
305        // the PIO now keeps the pin as Input, we can set the pin state to Low.
306        sm.set_pins([(sda.id().num, PinState::Low), (scl.id().num, PinState::Low)]);
307
308        // Set the state machine on the entry point.
309        sm.exec_instruction(pio::Instruction {
310            operands: pio::InstructionOperands::JMP {
311                condition: pio::JmpCondition::Always,
312                address: wrap_target,
313            },
314            delay: 0,
315            side_set: None,
316        });
317
318        // enable
319        let sm = sm.start();
320
321        Self {
322            pio,
323            sm,
324            tx,
325            rx,
326            sda: (sda, sda_override),
327            scl: (scl, scl_override),
328        }
329    }
330
331    fn has_irq(&mut self) -> bool {
332        let mask = 1 << SMI::id();
333        self.pio.get_irq_raw() & mask != 0
334    }
335
336    fn err_with(&mut self, err: Error) -> Result<(), Error> {
337        // clear RX FiFo
338        while self.rx.read().is_some() {}
339        // Clear Tx FiFo
340        self.sm.drain_tx_fifo();
341        // wait for the state machine to either stall on pull or block on irq
342        self.tx.clear_stalled_flag();
343        while !(self.tx.has_stalled() || self.has_irq()) {}
344
345        // Clear OSR
346        if self.has_irq() {
347            self.sm.exec_instruction(Instruction {
348                operands: pio::InstructionOperands::OUT {
349                    destination: pio::OutDestination::NULL,
350                    bit_count: 16,
351                },
352                delay: 0,
353                side_set: None,
354            });
355            // resume pio driver
356            self.pio.clear_irq(1 << SMI::id());
357        }
358        // generate stop condition
359        self.generate_stop();
360        Err(err)
361    }
362
363    fn generate_stop(&mut self) {
364        // this driver checks for acknoledge error and/or expects data back, so by the time a stop
365        // is generated, the tx fifo should be empty.
366        assert!(self.tx.is_empty(), "TX FIFO is empty");
367
368        stop().for_each(|encoded| {
369            self.tx.write_u16_replicated(encoded);
370        });
371        self.tx.clear_stalled_flag();
372        while !self.tx.has_stalled() {}
373    }
374
375    fn process_queue<'b>(
376        &mut self,
377        queue: impl IntoIterator<Item = CmdWord<'b>>,
378    ) -> Result<(), Error> {
379        let mut output = queue.into_iter().peekable();
380        // - TX FIFO depth (cmd waiting to be sent)
381        // - OSR
382        // - RX FIFO input waiting to be processed
383        let mut input: Deque<Data<'b>, 9> = Deque::new();
384
385        // while we’re not does sending/receiving
386        while output.peek().is_some() || !input.is_empty() {
387            // if there is room in the tx fifo
388            if !self.tx.is_full() {
389                if let Some(mut word) = output.next() {
390                    let last = matches!(
391                        (&mut word, output.peek()),
392                        (CmdWord::Data(_), None) | (CmdWord::Data(_), Some(CmdWord::Raw(_)))
393                    );
394                    let word_u16 = word.encode(last);
395                    self.tx.write_u16_replicated(word_u16);
396                    if let CmdWord::Data(d) = word {
397                        input.push_back(d).expect("`input` is not full");
398                    }
399                }
400            }
401
402            if let Some(word) = self.rx.read() {
403                let word = (word & 0xFF) as u8;
404                if let Some(d) = input.pop_front() {
405                    match d.byte {
406                        Left(exp) if word != exp => {
407                            return self.err_with(Error::BusContention);
408                        }
409                        Right(inp) => *inp = word,
410                        _ => {}
411                    }
412                }
413            } else if self.has_irq() {
414                // the byte that err’ed isn’t in the rx fifo. Once we’re done clearing them, we
415                // know the head of the queue is the byte that failed.
416                let Some(d) = input.pop_front() else {
417                    unreachable!("There cannot be a failure without a transmition")
418                };
419                return self.err_with(if d.is_address {
420                    Error::NoAcknowledgeAddress
421                } else {
422                    Error::NoAcknowledgeData
423                });
424            }
425        }
426        Ok(())
427    }
428}
429
430impl<P, SMI, SDA, SCL> I2C<'_, P, SMI, SDA, SCL>
431where
432    P: PIOExt,
433    SMI: StateMachineIndex,
434    SDA: AnyPin,
435    SDA::Id: ValidFunction<SDA::Function>,
436    SCL: AnyPin,
437    SCL::Id: ValidFunction<SCL::Function>,
438{
439    fn reset_pin<I, F, T>(
440        (mut pin, override_): (Pin<I, P::PinFunction, PullUp>, OutputEnableOverride),
441    ) -> Pin<I, F, T>
442    where
443        I: PinId,
444        F: Function,
445        T: PullType,
446        I: ValidFunction<F>,
447    {
448        // Prevent glitches during reconfiguration
449        pin.set_output_enable_override(OutputEnableOverride::Disable);
450        // reconfigure the pin
451        let mut pin = pin.reconfigure();
452        // revert to normal operation
453        pin.set_output_enable_override(override_);
454        pin
455    }
456
457    /// Frees the state machine and pins.
458    #[allow(clippy::type_complexity)]
459    pub fn free(self) -> ((SDA::Type, SCL::Type), UninitStateMachine<(P, SMI)>) {
460        let Self {
461            pio,
462            sm,
463            tx,
464            rx,
465            sda,
466            scl,
467            ..
468        } = self;
469        let (uninit, program) = sm.uninit(rx, tx);
470        pio.uninstall(program);
471
472        let scl = Self::reset_pin(scl);
473        let sda = Self::reset_pin(sda);
474
475        ((sda, scl), uninit)
476    }
477}