1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
//! API for the I2C peripherals //! //! Please be aware that this is a very basic implementation, with lots of //! important things missing. Please be careful when using this API. //! //! The I2C peripherals are described in the user manual, chapter 15. //! //! # Examples //! //! Write data to an I2C slave: //! //! ``` no_run //! # let address = 0x0; //! # let data = [0; 8]; //! # //! use lpc82x_hal::prelude::*; //! use lpc82x_hal::Peripherals; //! //! let mut p = Peripherals::take().unwrap(); //! //! let mut swm = p.SWM.split(); //! let mut syscon = p.SYSCON.split(); //! //! let (i2c0_sda, _) = swm.fixed_functions.i2c0_sda.assign( //! swm.pins.pio0_11.into_swm_pin(), //! &mut swm.handle, //! ); //! let (i2c0_scl, _) = swm.fixed_functions.i2c0_scl.assign( //! swm.pins.pio0_10.into_swm_pin(), //! &mut swm.handle, //! ); //! //! let mut i2c = p.I2C0.enable( //! &mut syscon.handle, //! i2c0_sda, //! i2c0_scl, //! ); //! //! i2c.write(address, &data) //! .expect("Failed to write data"); //! ``` //! //! Please refer to the [examples in the repository] for more example code. //! //! [examples in the repository]: https://github.com/braun-robotics/rust-lpc82x-hal/tree/master/examples use embedded_hal::blocking::i2c; use void::Void; use crate::{ init_state, raw, swm::{ self, I2C0_SCL, I2C0_SDA, PIO0_10, PIO0_11, }, syscon, }; /// Interface to an I2C peripheral /// /// Please refer to the [module documentation] for more information. /// /// # Limitations /// /// This API has the following limitations: /// - Only I2C0 is supported. /// - Only master mode is supported. /// - Errors are not handled. /// /// Additional limitations are documented on the specific methods that they /// apply to. /// /// [module documentation]: index.html pub struct I2C<State = init_state::Enabled> { i2c : raw::I2C0, _state: State, } impl I2C<init_state::Disabled> { pub(crate) fn new(i2c: raw::I2C0) -> Self { I2C { i2c : i2c, _state: init_state::Disabled, } } /// Enable the I2C peripheral /// /// This method is only available, if `I2C` is in the [`Disabled`] state. /// Code that attempts to call this method when the peripheral is already /// enabled will not compile. /// /// Consumes this instance of `I2C` and returns another instance that has /// its `State` type parameter set to [`Enabled`]. /// /// # Limitations /// /// This method expects the I2C mode for PIO0_10 and PIO0_11 to be set to /// standard/fast mode. This is the default value. /// /// The I2C clock frequency is hardcoded to a specific value. For unknown /// reasons, this seems to be 79.6 kHz. /// /// [`Disabled`]: ../init_state/struct.Disabled.html /// [`Enabled`]: ../init_state/struct.Enabled.html pub fn enable(mut self, syscon: &mut syscon::Handle, _ : swm::Function<I2C0_SDA, swm::state::Assigned<PIO0_11>>, _ : swm::Function<I2C0_SCL, swm::state::Assigned<PIO0_10>>, ) -> I2C<init_state::Enabled> { syscon.enable_clock(&mut self.i2c); // We need the I2C mode for the pins set to standard/fast mode, // according to the user manual, section 15.3.1. This is already the // default value (see user manual, sections 8.5.8 and 8.5.9). // Set I2C clock frequency // Here's my thinking: The main clock runs at 12 Mhz by default. The // minimum low and high times of SCL are set to 2 clock cyles each (see // below), meaning a full SCL cycle should take 4 clock ticks. Therefore // dividing the main clock by 8 (which is achieved by writing 7 below), // should result in an I2C frequency near 400 kHz (375 kHz to be // precise). // None of that is correct, of course. When actually running, I'm // measuring an SCL frequency of 79.6 kHz. I wish I knew why. self.i2c.clkdiv.write(|w| unsafe { w.divval().bits(7) }); // SCL low and high times are left at their default values (two clock // cycles each). See user manual, section 15.6.9. // Enable master mode // Set all other configuration values to default. self.i2c.cfg.write(|w| w.msten().enabled()); I2C { i2c : self.i2c, _state: init_state::Enabled(()), } } } impl i2c::Write for I2C<init_state::Enabled> { type Error = Void; /// Write to the I2C bus /// /// Please refer to the [embedded-hal documentation] for details. /// /// # Limitations /// /// Writing multiple bytes should work, but has not been tested. /// /// [embedded-hal documentation]: https://docs.rs/embedded-hal/0.2.1/embedded_hal/blocking/i2c/trait.Write.html#tymethod.write fn write(&mut self, address: u8, data: &[u8]) -> Result<(), Self::Error> { // Wait until peripheral is idle while !self.i2c.stat.read().mststate().is_idle() {} // Write slave address with rw bit set to 0 self.i2c.mstdat.write(|w| unsafe { w.data().bits(address & 0xfe) }); // Start transmission self.i2c.mstctl.write(|w| w.mststart().start()); for &b in data { // Wait until peripheral is ready to transmit while !self.i2c.stat.read().mststate().is_transmit_ready() {} // Write byte self.i2c.mstdat.write(|w| unsafe { w.data().bits(b) }); // Continue transmission self.i2c.mstctl.write(|w| w.mstcontinue().continue_()); } // Wait until peripheral is ready to transmit while !self.i2c.stat.read().mststate().is_transmit_ready() {} // Stop transmission self.i2c.mstctl.modify(|_, w| w.mststop().stop()); Ok(()) } } impl i2c::Read for I2C<init_state::Enabled> { type Error = Void; /// Read from the I2C bus /// /// Please refer to the [embedded-hal documentation] for details. /// /// # Limitations /// /// Reading multiple bytes should work, but has not been tested. /// /// [embedded-hal documentation]: https://docs.rs/embedded-hal/0.2.1/embedded_hal/blocking/i2c/trait.Read.html#tymethod.read fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { // Wait until peripheral is idle while !self.i2c.stat.read().mststate().is_idle() {} // Write slave address with rw bit set to 1 self.i2c.mstdat.write(|w| unsafe { w.data().bits(address | 0x01) }); // Start transmission self.i2c.mstctl.write(|w| w.mststart().start()); for b in buffer { // Wait until peripheral is ready to receive while !self.i2c.stat.read().mststate().is_receive_ready() {} // Read received byte *b = self.i2c.mstdat.read().data().bits(); } Ok(()) } } impl<State> I2C<State> { /// Return the raw peripheral /// /// This method serves as an escape hatch from the HAL API. It returns the /// raw peripheral, allowing you to do whatever you want with it, without /// limitations imposed by the API. /// /// If you are using this method because a feature you need is missing from /// the HAL API, please [open an issue] or, if an issue for your feature /// request already exists, comment on the existing issue, so we can /// prioritize it accordingly. /// /// [open an issue]: https://github.com/braun-robotics/rust-lpc82x-hal/issues pub fn free(self) -> raw::I2C0 { self.i2c } }