diff options
| author | Mathias <[email protected]> | 2022-08-19 11:51:42 +0200 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2022-09-27 22:08:49 +0200 |
| commit | 820e6462b6a48a6a59f082bccd5d7a3035937b2f (patch) | |
| tree | bebf1fe09d24fa069aec38acc9aeddb143aa01d8 | |
| parent | 5c882cf4faffe68dd376b90b6ce7504fd5d259c5 (diff) | |
Add preliminary I2C implementation for RP2040
| -rw-r--r-- | embassy-rp/src/clocks.rs | 2 | ||||
| -rw-r--r-- | embassy-rp/src/i2c.rs | 221 | ||||
| -rw-r--r-- | embassy-rp/src/lib.rs | 4 | ||||
| -rw-r--r-- | embassy-rp/src/uart/mod.rs | 5 |
4 files changed, 231 insertions, 1 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 1c446f389..875c129c0 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs | |||
| @@ -114,7 +114,7 @@ pub unsafe fn init() { | |||
| 114 | reset::unreset_wait(peris); | 114 | reset::unreset_wait(peris); |
| 115 | } | 115 | } |
| 116 | 116 | ||
| 117 | pub(crate) fn _clk_sys_freq() -> u32 { | 117 | pub(crate) fn clk_sys_freq() -> u32 { |
| 118 | 125_000_000 | 118 | 125_000_000 |
| 119 | } | 119 | } |
| 120 | 120 | ||
diff --git a/embassy-rp/src/i2c.rs b/embassy-rp/src/i2c.rs new file mode 100644 index 000000000..4a27ee8df --- /dev/null +++ b/embassy-rp/src/i2c.rs | |||
| @@ -0,0 +1,221 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | |||
| 3 | use embassy_hal_common::into_ref; | ||
| 4 | use pac::i2c; | ||
| 5 | |||
| 6 | use crate::{pac, peripherals, Peripheral}; | ||
| 7 | |||
| 8 | /// I2C error | ||
| 9 | #[derive(Debug)] | ||
| 10 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 11 | pub enum Error { | ||
| 12 | /// I2C abort with error | ||
| 13 | Abort(u32), | ||
| 14 | /// User passed in a read buffer that was 0 length | ||
| 15 | InvalidReadBufferLength, | ||
| 16 | /// User passed in a write buffer that was 0 length | ||
| 17 | InvalidWriteBufferLength, | ||
| 18 | /// Target i2c address is out of range | ||
| 19 | AddressOutOfRange(u16), | ||
| 20 | /// Target i2c address is reserved | ||
| 21 | AddressReserved(u16), | ||
| 22 | } | ||
| 23 | |||
| 24 | #[non_exhaustive] | ||
| 25 | #[derive(Copy, Clone)] | ||
| 26 | pub struct Config { | ||
| 27 | pub frequency: u32, | ||
| 28 | pub sda_pullup: bool, | ||
| 29 | pub scl_pullup: bool, | ||
| 30 | } | ||
| 31 | |||
| 32 | impl Default for Config { | ||
| 33 | fn default() -> Self { | ||
| 34 | Self { | ||
| 35 | frequency: 100_000, | ||
| 36 | sda_pullup: false, | ||
| 37 | scl_pullup: false, | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | pub struct I2c<'d, T: Instance, M: Mode> { | ||
| 43 | phantom: PhantomData<(&'d mut T, M)>, | ||
| 44 | } | ||
| 45 | |||
| 46 | impl<'d, T: Instance> I2c<'d, T, Master> { | ||
| 47 | pub fn new_master( | ||
| 48 | _peri: impl Peripheral<P = T> + 'd, | ||
| 49 | scl: impl Peripheral<P = impl SclPin<T>> + 'd, | ||
| 50 | sda: impl Peripheral<P = impl SdaPin<T>> + 'd, | ||
| 51 | config: Config, | ||
| 52 | ) -> Self { | ||
| 53 | into_ref!(_peri, scl, sda); | ||
| 54 | |||
| 55 | assert!(config.frequency <= 1_000_000); | ||
| 56 | assert!(config.frequency > 0); | ||
| 57 | |||
| 58 | let p = T::regs(); | ||
| 59 | |||
| 60 | unsafe { | ||
| 61 | p.ic_enable().write(|w| w.set_enable(false)); | ||
| 62 | |||
| 63 | // select controller mode & speed | ||
| 64 | p.ic_con().write(|w| { | ||
| 65 | // Always use "fast" mode (<= 400 kHz, works fine for standard mode too) | ||
| 66 | w.set_speed(i2c::vals::Speed::FAST); | ||
| 67 | w.set_master_mode(true); | ||
| 68 | w.set_ic_slave_disable(true); | ||
| 69 | w.set_ic_restart_en(true); | ||
| 70 | w.set_tx_empty_ctrl(true); | ||
| 71 | }); | ||
| 72 | |||
| 73 | // Clear FIFO threshold | ||
| 74 | p.ic_tx_tl().write(|w| w.set_tx_tl(0)); | ||
| 75 | p.ic_rx_tl().write(|w| w.set_rx_tl(0)); | ||
| 76 | |||
| 77 | // Configure SCL & SDA pins | ||
| 78 | scl.io().ctrl().write(|w| w.set_funcsel(3)); | ||
| 79 | sda.io().ctrl().write(|w| w.set_funcsel(3)); | ||
| 80 | |||
| 81 | scl.pad_ctrl().write(|w| { | ||
| 82 | w.set_schmitt(true); | ||
| 83 | w.set_pue(config.scl_pullup); | ||
| 84 | }); | ||
| 85 | sda.pad_ctrl().write(|w| { | ||
| 86 | w.set_schmitt(true); | ||
| 87 | w.set_pue(config.sda_pullup); | ||
| 88 | }); | ||
| 89 | |||
| 90 | // Configure baudrate | ||
| 91 | |||
| 92 | // There are some subtleties to I2C timing which we are completely ignoring here | ||
| 93 | // See: https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 | ||
| 94 | let clk_base = crate::clocks::clk_sys_freq(); | ||
| 95 | |||
| 96 | let period = (clk_base + config.frequency / 2) / config.frequency; | ||
| 97 | let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low | ||
| 98 | let hcnt = period - lcnt; // and 2/5 (40%) of the period high | ||
| 99 | |||
| 100 | // Check for out-of-range divisors: | ||
| 101 | assert!(hcnt <= 0xffff); | ||
| 102 | assert!(lcnt <= 0xffff); | ||
| 103 | assert!(hcnt >= 8); | ||
| 104 | assert!(lcnt >= 8); | ||
| 105 | |||
| 106 | // Per I2C-bus specification a device in standard or fast mode must | ||
| 107 | // internally provide a hold time of at least 300ns for the SDA signal to | ||
| 108 | // bridge the undefined region of the falling edge of SCL. A smaller hold | ||
| 109 | // time of 120ns is used for fast mode plus. | ||
| 110 | let sda_tx_hold_count = if config.frequency < 1_000_000 { | ||
| 111 | // sda_tx_hold_count = clk_base [cycles/s] * 300ns * (1s / 1e9ns) | ||
| 112 | // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. | ||
| 113 | // Add 1 to avoid division truncation. | ||
| 114 | ((clk_base * 3) / 10_000_000) + 1 | ||
| 115 | } else { | ||
| 116 | // fast mode plus requires a clk_base > 32MHz | ||
| 117 | assert!(clk_base >= 32_000_000); | ||
| 118 | |||
| 119 | // sda_tx_hold_count = clk_base [cycles/s] * 120ns * (1s / 1e9ns) | ||
| 120 | // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. | ||
| 121 | // Add 1 to avoid division truncation. | ||
| 122 | ((clk_base * 3) / 25_000_000) + 1 | ||
| 123 | }; | ||
| 124 | assert!(sda_tx_hold_count <= lcnt - 2); | ||
| 125 | |||
| 126 | p.ic_fs_scl_hcnt().write(|w| w.set_ic_fs_scl_hcnt(hcnt as u16)); | ||
| 127 | p.ic_fs_scl_lcnt().write(|w| w.set_ic_fs_scl_lcnt(lcnt as u16)); | ||
| 128 | p.ic_fs_spklen() | ||
| 129 | .write(|w| w.set_ic_fs_spklen(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 })); | ||
| 130 | p.ic_sda_hold() | ||
| 131 | .write(|w| w.set_ic_sda_tx_hold(sda_tx_hold_count as u16)); | ||
| 132 | |||
| 133 | // Enable I2C block | ||
| 134 | p.ic_enable().write(|w| w.set_enable(true)); | ||
| 135 | } | ||
| 136 | |||
| 137 | Self { phantom: PhantomData } | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | mod sealed { | ||
| 142 | pub trait Instance {} | ||
| 143 | pub trait Mode {} | ||
| 144 | |||
| 145 | pub trait SdaPin<T: Instance> {} | ||
| 146 | pub trait SclPin<T: Instance> {} | ||
| 147 | } | ||
| 148 | |||
| 149 | pub trait Mode: sealed::Mode {} | ||
| 150 | |||
| 151 | macro_rules! impl_mode { | ||
| 152 | ($name:ident) => { | ||
| 153 | impl sealed::Mode for $name {} | ||
| 154 | impl Mode for $name {} | ||
| 155 | }; | ||
| 156 | } | ||
| 157 | |||
| 158 | pub struct Master; | ||
| 159 | pub struct Slave; | ||
| 160 | |||
| 161 | impl_mode!(Master); | ||
| 162 | impl_mode!(Slave); | ||
| 163 | |||
| 164 | pub trait Instance: sealed::Instance { | ||
| 165 | fn regs() -> pac::i2c::I2c; | ||
| 166 | } | ||
| 167 | |||
| 168 | macro_rules! impl_instance { | ||
| 169 | ($type:ident, $irq:ident) => { | ||
| 170 | impl sealed::Instance for peripherals::$type {} | ||
| 171 | impl Instance for peripherals::$type { | ||
| 172 | fn regs() -> pac::i2c::I2c { | ||
| 173 | pac::$type | ||
| 174 | } | ||
| 175 | } | ||
| 176 | }; | ||
| 177 | } | ||
| 178 | |||
| 179 | impl_instance!(I2C0, I2c0); | ||
| 180 | impl_instance!(I2C1, I2c1); | ||
| 181 | |||
| 182 | pub trait SdaPin<T: Instance>: sealed::SdaPin<T> + crate::gpio::Pin {} | ||
| 183 | pub trait SclPin<T: Instance>: sealed::SclPin<T> + crate::gpio::Pin {} | ||
| 184 | |||
| 185 | macro_rules! impl_pin { | ||
| 186 | ($pin:ident, $instance:ident, $function:ident) => { | ||
| 187 | impl sealed::$function<peripherals::$instance> for peripherals::$pin {} | ||
| 188 | impl $function<peripherals::$instance> for peripherals::$pin {} | ||
| 189 | }; | ||
| 190 | } | ||
| 191 | |||
| 192 | impl_pin!(PIN_0, I2C0, SdaPin); | ||
| 193 | impl_pin!(PIN_1, I2C0, SclPin); | ||
| 194 | impl_pin!(PIN_2, I2C1, SdaPin); | ||
| 195 | impl_pin!(PIN_3, I2C1, SclPin); | ||
| 196 | impl_pin!(PIN_4, I2C0, SdaPin); | ||
| 197 | impl_pin!(PIN_5, I2C0, SclPin); | ||
| 198 | impl_pin!(PIN_6, I2C1, SdaPin); | ||
| 199 | impl_pin!(PIN_7, I2C1, SclPin); | ||
| 200 | impl_pin!(PIN_8, I2C0, SdaPin); | ||
| 201 | impl_pin!(PIN_9, I2C0, SclPin); | ||
| 202 | impl_pin!(PIN_10, I2C1, SdaPin); | ||
| 203 | impl_pin!(PIN_11, I2C1, SclPin); | ||
| 204 | impl_pin!(PIN_12, I2C0, SdaPin); | ||
| 205 | impl_pin!(PIN_13, I2C0, SclPin); | ||
| 206 | impl_pin!(PIN_14, I2C1, SdaPin); | ||
| 207 | impl_pin!(PIN_15, I2C1, SclPin); | ||
| 208 | impl_pin!(PIN_16, I2C0, SdaPin); | ||
| 209 | impl_pin!(PIN_17, I2C0, SclPin); | ||
| 210 | impl_pin!(PIN_18, I2C1, SdaPin); | ||
| 211 | impl_pin!(PIN_19, I2C1, SclPin); | ||
| 212 | impl_pin!(PIN_20, I2C0, SdaPin); | ||
| 213 | impl_pin!(PIN_21, I2C0, SclPin); | ||
| 214 | impl_pin!(PIN_22, I2C1, SdaPin); | ||
| 215 | impl_pin!(PIN_23, I2C1, SclPin); | ||
| 216 | impl_pin!(PIN_24, I2C0, SdaPin); | ||
| 217 | impl_pin!(PIN_25, I2C0, SclPin); | ||
| 218 | impl_pin!(PIN_26, I2C1, SdaPin); | ||
| 219 | impl_pin!(PIN_27, I2C1, SclPin); | ||
| 220 | impl_pin!(PIN_28, I2C0, SdaPin); | ||
| 221 | impl_pin!(PIN_29, I2C0, SclPin); | ||
diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 9ac98d226..e784399d4 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs | |||
| @@ -8,6 +8,7 @@ mod intrinsics; | |||
| 8 | 8 | ||
| 9 | pub mod dma; | 9 | pub mod dma; |
| 10 | pub mod gpio; | 10 | pub mod gpio; |
| 11 | pub mod i2c; | ||
| 11 | pub mod interrupt; | 12 | pub mod interrupt; |
| 12 | pub mod rom_data; | 13 | pub mod rom_data; |
| 13 | pub mod rtc; | 14 | pub mod rtc; |
| @@ -75,6 +76,9 @@ embassy_hal_common::peripherals! { | |||
| 75 | SPI0, | 76 | SPI0, |
| 76 | SPI1, | 77 | SPI1, |
| 77 | 78 | ||
| 79 | I2C0, | ||
| 80 | I2C1, | ||
| 81 | |||
| 78 | DMA_CH0, | 82 | DMA_CH0, |
| 79 | DMA_CH1, | 83 | DMA_CH1, |
| 80 | DMA_CH2, | 84 | DMA_CH2, |
diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index d9285ee51..567c79db3 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs | |||
| @@ -428,9 +428,11 @@ mod eh02 { | |||
| 428 | 428 | ||
| 429 | impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write<u8> for UartTx<'d, T, M> { | 429 | impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write<u8> for UartTx<'d, T, M> { |
| 430 | type Error = Error; | 430 | type Error = Error; |
| 431 | |||
| 431 | fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { | 432 | fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { |
| 432 | self.blocking_write(buffer) | 433 | self.blocking_write(buffer) |
| 433 | } | 434 | } |
| 435 | |||
| 434 | fn bflush(&mut self) -> Result<(), Self::Error> { | 436 | fn bflush(&mut self) -> Result<(), Self::Error> { |
| 435 | self.blocking_flush() | 437 | self.blocking_flush() |
| 436 | } | 438 | } |
| @@ -438,6 +440,7 @@ mod eh02 { | |||
| 438 | 440 | ||
| 439 | impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read<u8> for Uart<'d, T, M> { | 441 | impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read<u8> for Uart<'d, T, M> { |
| 440 | type Error = Error; | 442 | type Error = Error; |
| 443 | |||
| 441 | fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> { | 444 | fn read(&mut self) -> Result<u8, nb::Error<Self::Error>> { |
| 442 | embedded_hal_02::serial::Read::read(&mut self.rx) | 445 | embedded_hal_02::serial::Read::read(&mut self.rx) |
| 443 | } | 446 | } |
| @@ -445,9 +448,11 @@ mod eh02 { | |||
| 445 | 448 | ||
| 446 | impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write<u8> for Uart<'d, T, M> { | 449 | impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write<u8> for Uart<'d, T, M> { |
| 447 | type Error = Error; | 450 | type Error = Error; |
| 451 | |||
| 448 | fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { | 452 | fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { |
| 449 | self.blocking_write(buffer) | 453 | self.blocking_write(buffer) |
| 450 | } | 454 | } |
| 455 | |||
| 451 | fn bflush(&mut self) -> Result<(), Self::Error> { | 456 | fn bflush(&mut self) -> Result<(), Self::Error> { |
| 452 | self.blocking_flush() | 457 | self.blocking_flush() |
| 453 | } | 458 | } |
