summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathias <mk@blackbird.online>2022-08-19 11:51:42 +0200
committerDario Nieuwenhuis <dirbaio@dirbaio.net>2022-09-27 22:08:49 +0200
commit820e6462b6a48a6a59f082bccd5d7a3035937b2f (patch)
treebebf1fe09d24fa069aec38acc9aeddb143aa01d8
parent5c882cf4faffe68dd376b90b6ce7504fd5d259c5 (diff)
downloadembassy-820e6462b6a48a6a59f082bccd5d7a3035937b2f.zip
Add preliminary I2C implementation for RP2040
-rw-r--r--embassy-rp/src/clocks.rs2
-rw-r--r--embassy-rp/src/i2c.rs221
-rw-r--r--embassy-rp/src/lib.rs4
-rw-r--r--embassy-rp/src/uart/mod.rs5
4 files changed, 231 insertions, 1 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs
index 1c446f38..875c129c 100644
--- a/embassy-rp/src/clocks.rs
+++ b/embassy-rp/src/clocks.rs
@@ -114,7 +114,7 @@ pub unsafe fn init() {
reset::unreset_wait(peris);
}
-pub(crate) fn _clk_sys_freq() -> u32 {
+pub(crate) fn clk_sys_freq() -> u32 {
125_000_000
}
diff --git a/embassy-rp/src/i2c.rs b/embassy-rp/src/i2c.rs
new file mode 100644
index 00000000..4a27ee8d
--- /dev/null
+++ b/embassy-rp/src/i2c.rs
@@ -0,0 +1,221 @@
+use core::marker::PhantomData;
+
+use embassy_hal_common::into_ref;
+use pac::i2c;
+
+use crate::{pac, peripherals, Peripheral};
+
+/// I2C error
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Error {
+ /// I2C abort with error
+ Abort(u32),
+ /// User passed in a read buffer that was 0 length
+ InvalidReadBufferLength,
+ /// User passed in a write buffer that was 0 length
+ InvalidWriteBufferLength,
+ /// Target i2c address is out of range
+ AddressOutOfRange(u16),
+ /// Target i2c address is reserved
+ AddressReserved(u16),
+}
+
+#[non_exhaustive]
+#[derive(Copy, Clone)]
+pub struct Config {
+ pub frequency: u32,
+ pub sda_pullup: bool,
+ pub scl_pullup: bool,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ frequency: 100_000,
+ sda_pullup: false,
+ scl_pullup: false,
+ }
+ }
+}
+
+pub struct I2c<'d, T: Instance, M: Mode> {
+ phantom: PhantomData<(&'d mut T, M)>,
+}
+
+impl<'d, T: Instance> I2c<'d, T, Master> {
+ pub fn new_master(
+ _peri: impl Peripheral<P = T> + 'd,
+ scl: impl Peripheral<P = impl SclPin<T>> + 'd,
+ sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
+ config: Config,
+ ) -> Self {
+ into_ref!(_peri, scl, sda);
+
+ assert!(config.frequency <= 1_000_000);
+ assert!(config.frequency > 0);
+
+ let p = T::regs();
+
+ unsafe {
+ p.ic_enable().write(|w| w.set_enable(false));
+
+ // select controller mode & speed
+ p.ic_con().write(|w| {
+ // Always use "fast" mode (<= 400 kHz, works fine for standard mode too)
+ w.set_speed(i2c::vals::Speed::FAST);
+ w.set_master_mode(true);
+ w.set_ic_slave_disable(true);
+ w.set_ic_restart_en(true);
+ w.set_tx_empty_ctrl(true);
+ });
+
+ // Clear FIFO threshold
+ p.ic_tx_tl().write(|w| w.set_tx_tl(0));
+ p.ic_rx_tl().write(|w| w.set_rx_tl(0));
+
+ // Configure SCL & SDA pins
+ scl.io().ctrl().write(|w| w.set_funcsel(3));
+ sda.io().ctrl().write(|w| w.set_funcsel(3));
+
+ scl.pad_ctrl().write(|w| {
+ w.set_schmitt(true);
+ w.set_pue(config.scl_pullup);
+ });
+ sda.pad_ctrl().write(|w| {
+ w.set_schmitt(true);
+ w.set_pue(config.sda_pullup);
+ });
+
+ // Configure baudrate
+
+ // 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 clk_base = crate::clocks::clk_sys_freq();
+
+ let period = (clk_base + config.frequency / 2) / config.frequency;
+ 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 config.frequency < 1_000_000 {
+ // sda_tx_hold_count = clk_base [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.
+ ((clk_base * 3) / 10_000_000) + 1
+ } else {
+ // fast mode plus requires a clk_base > 32MHz
+ assert!(clk_base >= 32_000_000);
+
+ // sda_tx_hold_count = clk_base [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.
+ ((clk_base * 3) / 25_000_000) + 1
+ };
+ assert!(sda_tx_hold_count <= lcnt - 2);
+
+ p.ic_fs_scl_hcnt().write(|w| w.set_ic_fs_scl_hcnt(hcnt as u16));
+ p.ic_fs_scl_lcnt().write(|w| w.set_ic_fs_scl_lcnt(lcnt as u16));
+ p.ic_fs_spklen()
+ .write(|w| w.set_ic_fs_spklen(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 }));
+ p.ic_sda_hold()
+ .write(|w| w.set_ic_sda_tx_hold(sda_tx_hold_count as u16));
+
+ // Enable I2C block
+ p.ic_enable().write(|w| w.set_enable(true));
+ }
+
+ Self { phantom: PhantomData }
+ }
+}
+
+mod sealed {
+ pub trait Instance {}
+ pub trait Mode {}
+
+ pub trait SdaPin<T: Instance> {}
+ pub trait SclPin<T: Instance> {}
+}
+
+pub trait Mode: sealed::Mode {}
+
+macro_rules! impl_mode {
+ ($name:ident) => {
+ impl sealed::Mode for $name {}
+ impl Mode for $name {}
+ };
+}
+
+pub struct Master;
+pub struct Slave;
+
+impl_mode!(Master);
+impl_mode!(Slave);
+
+pub trait Instance: sealed::Instance {
+ fn regs() -> pac::i2c::I2c;
+}
+
+macro_rules! impl_instance {
+ ($type:ident, $irq:ident) => {
+ impl sealed::Instance for peripherals::$type {}
+ impl Instance for peripherals::$type {
+ fn regs() -> pac::i2c::I2c {
+ pac::$type
+ }
+ }
+ };
+}
+
+impl_instance!(I2C0, I2c0);
+impl_instance!(I2C1, I2c1);
+
+pub trait SdaPin<T: Instance>: sealed::SdaPin<T> + crate::gpio::Pin {}
+pub trait SclPin<T: Instance>: sealed::SclPin<T> + crate::gpio::Pin {}
+
+macro_rules! impl_pin {
+ ($pin:ident, $instance:ident, $function:ident) => {
+ impl sealed::$function<peripherals::$instance> for peripherals::$pin {}
+ impl $function<peripherals::$instance> for peripherals::$pin {}
+ };
+}
+
+impl_pin!(PIN_0, I2C0, SdaPin);
+impl_pin!(PIN_1, I2C0, SclPin);
+impl_pin!(PIN_2, I2C1, SdaPin);
+impl_pin!(PIN_3, I2C1, SclPin);
+impl_pin!(PIN_4, I2C0, SdaPin);
+impl_pin!(PIN_5, I2C0, SclPin);
+impl_pin!(PIN_6, I2C1, SdaPin);
+impl_pin!(PIN_7, I2C1, SclPin);
+impl_pin!(PIN_8, I2C0, SdaPin);
+impl_pin!(PIN_9, I2C0, SclPin);
+impl_pin!(PIN_10, I2C1, SdaPin);
+impl_pin!(PIN_11, I2C1, SclPin);
+impl_pin!(PIN_12, I2C0, SdaPin);
+impl_pin!(PIN_13, I2C0, SclPin);
+impl_pin!(PIN_14, I2C1, SdaPin);
+impl_pin!(PIN_15, I2C1, SclPin);
+impl_pin!(PIN_16, I2C0, SdaPin);
+impl_pin!(PIN_17, I2C0, SclPin);
+impl_pin!(PIN_18, I2C1, SdaPin);
+impl_pin!(PIN_19, I2C1, SclPin);
+impl_pin!(PIN_20, I2C0, SdaPin);
+impl_pin!(PIN_21, I2C0, SclPin);
+impl_pin!(PIN_22, I2C1, SdaPin);
+impl_pin!(PIN_23, I2C1, SclPin);
+impl_pin!(PIN_24, I2C0, SdaPin);
+impl_pin!(PIN_25, I2C0, SclPin);
+impl_pin!(PIN_26, I2C1, SdaPin);
+impl_pin!(PIN_27, I2C1, SclPin);
+impl_pin!(PIN_28, I2C0, SdaPin);
+impl_pin!(PIN_29, I2C0, SclPin);
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs
index 9ac98d22..e784399d 100644
--- a/embassy-rp/src/lib.rs
+++ b/embassy-rp/src/lib.rs
@@ -8,6 +8,7 @@ mod intrinsics;
pub mod dma;
pub mod gpio;
+pub mod i2c;
pub mod interrupt;
pub mod rom_data;
pub mod rtc;
@@ -75,6 +76,9 @@ embassy_hal_common::peripherals! {
SPI0,
SPI1,
+ I2C0,
+ I2C1,
+
DMA_CH0,
DMA_CH1,
DMA_CH2,
diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs
index d9285ee5..567c79db 100644
--- a/embassy-rp/src/uart/mod.rs
+++ b/embassy-rp/src/uart/mod.rs
@@ -428,9 +428,11 @@ mod eh02 {
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write<u8> for UartTx<'d, T, M> {
type Error = Error;
+
fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(buffer)
}
+
fn bflush(&mut self) -> Result<(), Self::Error> {
self.blocking_flush()
}
@@ -438,6 +440,7 @@ mod eh02 {
impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read<u8> for Uart<'d, T, M> {
type Error = Error;
+
fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> {
embedded_hal_02::serial::Read::read(&mut self.rx)
}
@@ -445,9 +448,11 @@ mod eh02 {
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write<u8> for Uart<'d, T, M> {
type Error = Error;
+
fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(buffer)
}
+
fn bflush(&mut self) -> Result<(), Self::Error> {
self.blocking_flush()
}