From ba541eb1a3dbbba2b006944cb04632e986cb8132 Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Mon, 14 Nov 2022 22:33:13 +0000 Subject: [PATCH 1/3] Prototype of an I2C constructor which doesn't own pins --- rp2040-hal/src/i2c.rs | 22 ++++++++ rp2040-hal/src/i2c/controller.rs | 91 ++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/rp2040-hal/src/i2c.rs b/rp2040-hal/src/i2c.rs index 54ce88721..02b55ab80 100644 --- a/rp2040-hal/src/i2c.rs +++ b/rp2040-hal/src/i2c.rs @@ -302,6 +302,28 @@ macro_rules! hal { Self::new_controller(i2c, sda_pin, scl_pin, freq.into(), resets, system_clock.into()) } } + + impl I2C<$I2CX, ()> { + $crate::paste::paste! { + /// Configures the I2C peripheral to work in master mode + /// + /// This constructor variant doesn't take the sda/scl pins as + /// parameters. It's the callers responsibility to configure + /// some GPIOs to the right function mode to actually connect the + /// peripheral to some physical pins. + pub fn [<$i2cX _without_pins>]( + i2c: $I2CX, + freq: F, + resets: &mut RESETS, + system_clock: SystemF) -> Self + where + F: Into, + SystemF: Into, + { + Self::new_controller_without_pins(i2c, freq.into(), resets, system_clock.into()) + } + } + } )+ } } diff --git a/rp2040-hal/src/i2c/controller.rs b/rp2040-hal/src/i2c/controller.rs index fdcfa3e3c..cf343b867 100644 --- a/rp2040-hal/src/i2c/controller.rs +++ b/rp2040-hal/src/i2c/controller.rs @@ -14,6 +14,97 @@ use eh1_0_alpha::i2c as eh1; use super::{i2c_reserved_addr, Controller, Error, SclPin, SdaPin, I2C}; +impl> + I2C +{ + /// Configures the I2C peripheral to work in controller mode + pub fn new_controller_without_pins( + i2c: T, + freq: HertzU32, + resets: &mut RESETS, + system_clock: HertzU32, + ) -> Self + { + let freq = freq.to_Hz(); + assert!(freq <= 1_000_000); + assert!(freq > 0); + + i2c.reset_bring_down(resets); + i2c.reset_bring_up(resets); + + i2c.ic_enable.write(|w| w.enable().disabled()); + + // select controller mode & speed + i2c.ic_con.modify(|_, w| { + w.speed().fast(); + w.master_mode().enabled(); + w.ic_slave_disable().slave_disabled(); + w.ic_restart_en().enabled(); + w.tx_empty_ctrl().enabled() + }); + + // Clear FIFO threshold + i2c.ic_tx_tl.write(|w| unsafe { w.tx_tl().bits(0) }); + i2c.ic_rx_tl.write(|w| unsafe { w.rx_tl().bits(0) }); + + let freq_in = system_clock.to_Hz(); + + // There are some subtleties to I2C timing which we are completely ignoring here + // See: https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 + let period = (freq_in + freq / 2) / freq; + let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low + let hcnt = period - lcnt; // and 2/5 (40%) of the period high + + // Check for out-of-range divisors: + assert!(hcnt <= 0xffff); + assert!(lcnt <= 0xffff); + assert!(hcnt >= 8); + assert!(lcnt >= 8); + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA signal to + // bridge the undefined region of the falling edge of SCL. A smaller hold + // time of 120ns is used for fast mode plus. + let sda_tx_hold_count = if freq < 1000000 { + // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) + // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 10000000) + 1 + } else { + // fast mode plus requires a clk_in > 32MHz + assert!(freq_in >= 32_000_000); + + // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) + // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 25000000) + 1 + }; + assert!(sda_tx_hold_count <= lcnt - 2); + + unsafe { + i2c.ic_fs_scl_hcnt + .write(|w| w.ic_fs_scl_hcnt().bits(hcnt as u16)); + i2c.ic_fs_scl_lcnt + .write(|w| w.ic_fs_scl_lcnt().bits(lcnt as u16)); + i2c.ic_fs_spklen.write(|w| { + w.ic_fs_spklen() + .bits(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 }) + }); + i2c.ic_sda_hold + .modify(|_r, w| w.ic_sda_tx_hold().bits(sda_tx_hold_count as u16)); + } + + // Enable I2C block + i2c.ic_enable.write(|w| w.enable().enabled()); + + Self { + i2c, + pins: (), + mode: PhantomData, + } + } +} + impl, Sda: PinId + BankPinId, Scl: PinId + BankPinId> I2C, Pin), Controller> { From cc364756711e5ec4bfd3922b18aca3057f6fbe42 Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Mon, 14 Nov 2022 22:40:09 +0000 Subject: [PATCH 2/3] Fix formatting --- rp2040-hal/src/i2c/controller.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/rp2040-hal/src/i2c/controller.rs b/rp2040-hal/src/i2c/controller.rs index cf343b867..4dcb806af 100644 --- a/rp2040-hal/src/i2c/controller.rs +++ b/rp2040-hal/src/i2c/controller.rs @@ -14,17 +14,14 @@ use eh1_0_alpha::i2c as eh1; use super::{i2c_reserved_addr, Controller, Error, SclPin, SdaPin, I2C}; -impl> - I2C -{ +impl> I2C { /// Configures the I2C peripheral to work in controller mode pub fn new_controller_without_pins( i2c: T, freq: HertzU32, resets: &mut RESETS, system_clock: HertzU32, - ) -> Self - { + ) -> Self { let freq = freq.to_Hz(); assert!(freq <= 1_000_000); assert!(freq > 0); From feea69743b6a808278e3d26af5efa016709ea4bc Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Thu, 17 Nov 2022 21:19:28 +0000 Subject: [PATCH 3/3] Add example --- rp2040-hal/examples/i2c_without_pins.rs | 99 +++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 rp2040-hal/examples/i2c_without_pins.rs diff --git a/rp2040-hal/examples/i2c_without_pins.rs b/rp2040-hal/examples/i2c_without_pins.rs new file mode 100644 index 000000000..4263dba93 --- /dev/null +++ b/rp2040-hal/examples/i2c_without_pins.rs @@ -0,0 +1,99 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an RP2040. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Some traits we need +use embedded_hal::blocking::i2c::Write; +use fugit::RateExtU32; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then performs a single I²C +/// write to a fixed address. +#[rp2040_hal::entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + // We still need to configure the pins into an appropriate function mode + let _sda_pin = pins.gpio18.into_mode::(); + let _scl_pin = pins.gpio19.into_mode::(); + // let not_an_scl_pin = pins.gpio20.into_mode::(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = + hal::I2C::i2c1_without_pins(pac.I2C1, 400.kHz(), &mut pac.RESETS, &clocks.system_clock); + + // Write three bytes to the I²C device with 7-bit address 0x2C + i2c.write(0x2c, &[1, 2, 3]).unwrap(); + + // Demo finish - just loop until reset + + loop { + cortex_m::asm::wfi(); + } +} + +// End of file