├── .cargo └── config ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── i2c-eeprom24x.rs ├── serial.rs └── spi.rs ├── memory.x └── src ├── i2c.rs ├── lib.rs ├── serial.rs └── spi.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | # vim:ft=toml: 2 | [target.thumbv7m-none-eabi] 3 | runner = 'arm-none-eabi-gdb' 4 | rustflags = [ 5 | "-C", "link-arg=-Tlink.x", 6 | ] 7 | 8 | [build] 9 | target = "thumbv7m-none-eabi" 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | format: 7 | name: Rustfmt 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - run: rustup component add rustfmt 17 | - uses: actions-rs/cargo@v1 18 | with: 19 | command: fmt 20 | args: --all -- --check 21 | 22 | build: 23 | runs-on: ubuntu-latest 24 | 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | profile: minimal 30 | toolchain: stable 31 | target: thumbv7m-none-eabi 32 | override: true 33 | - uses: actions-rs/cargo@v1 34 | with: 35 | command: build 36 | 37 | examples: 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: stable 46 | target: thumbv7m-none-eabi 47 | override: true 48 | - uses: actions-rs/cargo@v1 49 | with: 50 | command: build 51 | args: --examples 52 | 53 | doctest: 54 | runs-on: ubuntu-latest 55 | 56 | steps: 57 | - uses: actions/checkout@v2 58 | - uses: actions-rs/toolchain@v1 59 | with: 60 | profile: minimal 61 | toolchain: stable 62 | target: thumbv7m-none-eabi 63 | override: true 64 | - uses: actions-rs/cargo@v1 65 | with: 66 | command: test 67 | args: --doc 68 | 69 | docs: 70 | runs-on: ubuntu-latest 71 | 72 | steps: 73 | - uses: actions/checkout@v2 74 | - uses: actions-rs/toolchain@v1 75 | with: 76 | profile: minimal 77 | toolchain: stable 78 | target: thumbv7m-none-eabi 79 | override: true 80 | - uses: actions-rs/cargo@v1 81 | with: 82 | command: doc 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | .idea 6 | .vscode 7 | rusty-tags.vi 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitbang-hal" 3 | version = "0.3.3" 4 | authors = ["Paul Sajna "] 5 | edition = "2021" 6 | description = "Implements embedded-hal traits by bitbanging" 7 | license = "MIT" 8 | repository = "https://github.com/sajattack/bitbang-hal" 9 | readme = "README.md" 10 | keywords = ["no_std", "embedded", "bitbang", "embedded-hal", "hal"] 11 | categories = ["embedded", "no-std"] 12 | 13 | [dependencies] 14 | nb = "1" 15 | 16 | [dependencies.embedded-hal] 17 | version = "0.2.7" 18 | features = ["unproven"] 19 | 20 | [dev-dependencies.stm32f1xx-hal] 21 | version = "0.9" 22 | features = ["stm32f103", "rt", "medium"] 23 | 24 | [dev-dependencies] 25 | cortex-m = "0.7" 26 | cortex-m-rt = "0.7" 27 | panic-halt = "0.2.0" 28 | eeprom24x = "0.5.0" 29 | lm75 = "0.2" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Paul Sajna 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `embedded-hal` traits implementation by bit banging 2 | 3 | [![crates.io](https://img.shields.io/crates/v/bitbang-hal.svg)](https://crates.io/crates/bitbang-hal) 4 | [![Docs](https://docs.rs/bitbang-hal/badge.svg)](https://docs.rs/bitbang-hal) 5 | 6 | This is a [bit banging] implementation of the [`embedded-hal`] traits. 7 | 8 | [bit banging]: https://en.wikipedia.org/wiki/Bit_banging 9 | [`embedded-hal`]: https://github.com/rust-embedded/embedded-hal 10 | 11 | ## Usage 12 | 13 | See example programs in the `examples` folder. 14 | 15 | ## Support 16 | 17 | For questions, issues, feature requests, and other changes, please file an 18 | issue in the github project. 19 | 20 | ## License 21 | 22 | Licensed under MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 23 | 24 | ### Contributing 25 | 26 | Unless you explicitly state otherwise, any contribution intentionally submitted 27 | for inclusion in the work by you shall be licensed as above, without any 28 | additional terms or conditions. 29 | 30 | -------------------------------------------------------------------------------- /examples/i2c-eeprom24x.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use nb::block; 5 | use panic_halt as _; 6 | 7 | use eeprom24x::Eeprom24x; 8 | use eeprom24x::SlaveAddr; 9 | 10 | use cortex_m_rt::entry; 11 | use stm32f1xx_hal::{prelude::*, stm32}; 12 | 13 | #[entry] 14 | fn main() -> ! { 15 | let pdev = stm32::Peripherals::take().unwrap(); 16 | 17 | let mut flash = pdev.FLASH.constrain(); 18 | let rcc = pdev.RCC.constrain(); 19 | let mut gpioa = pdev.GPIOA.split(); 20 | 21 | let clocks = rcc 22 | .cfgr 23 | .use_hse(8.MHz()) 24 | .sysclk(32.MHz()) 25 | .pclk1(16.MHz()) 26 | .freeze(&mut flash.acr); 27 | 28 | let mut delay = pdev.TIM2.counter_hz(&clocks); 29 | delay.start(10.Hz()).unwrap(); 30 | let mut tmr = pdev.TIM3.counter_hz(&clocks); 31 | tmr.start(200.kHz()).unwrap(); 32 | 33 | let scl = gpioa.pa1.into_open_drain_output(&mut gpioa.crl); 34 | let sda = gpioa.pa2.into_open_drain_output(&mut gpioa.crl); 35 | 36 | let i2c = bitbang_hal::i2c::I2cBB::new(scl, sda, tmr); 37 | let mut eeprom = Eeprom24x::new_24x04(i2c, SlaveAddr::default()); 38 | 39 | // check high memory addresses: 1 bit passed as a part of i2c addr 40 | let addrs: [u32; 4] = [0x100, 0x10F, 0x1F0, 0x1EE]; 41 | let byte = 0xe5; 42 | 43 | for addr in addrs.iter() { 44 | eeprom.write_byte(*addr, byte).unwrap(); 45 | // need to wait before next write 46 | block!(delay.wait()).ok(); 47 | } 48 | 49 | loop { 50 | for addr in addrs.iter() { 51 | let _ = eeprom.read_byte(*addr).unwrap(); 52 | block!(delay.wait()).ok(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/serial.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use nb::block; 5 | use panic_halt as _; 6 | 7 | use cortex_m_rt::entry; 8 | use stm32f1xx_hal::{prelude::*, stm32}; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | let pdev = stm32::Peripherals::take().unwrap(); 13 | 14 | let mut flash = pdev.FLASH.constrain(); 15 | let rcc = pdev.RCC.constrain(); 16 | let mut gpiob = pdev.GPIOB.split(); 17 | 18 | let clocks = rcc 19 | .cfgr 20 | .use_hse(8.MHz()) 21 | .sysclk(32.MHz()) 22 | .pclk1(16.MHz()) 23 | .freeze(&mut flash.acr); 24 | 25 | let mut delay = pdev.TIM2.delay_us(&clocks); 26 | let mut tmr = pdev.TIM3.counter_hz(&clocks); 27 | tmr.start(115_200.Hz()).unwrap(); 28 | 29 | // use 5V tolerant pins to test with UART-to-USB connector 30 | let tx = gpiob.pb10.into_push_pull_output(&mut gpiob.crh); 31 | let rx = gpiob.pb11.into_floating_input(&mut gpiob.crh); 32 | 33 | let mut serial = bitbang_hal::serial::Serial::new(tx, rx, tmr); 34 | 35 | loop { 36 | for byte in b"Hello, World!\r\n" { 37 | block!(serial.write(*byte)).unwrap(); 38 | } 39 | 40 | delay.delay_ms(1000u16); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/spi.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use nb::block; 5 | use panic_halt as _; 6 | 7 | use cortex_m_rt::entry; 8 | use stm32f1xx_hal::{prelude::*, stm32}; 9 | 10 | use bitbang_hal::spi::MODE_0; 11 | use bitbang_hal::spi::SPI; 12 | 13 | #[entry] 14 | fn main() -> ! { 15 | let pdev = stm32::Peripherals::take().unwrap(); 16 | 17 | let mut flash = pdev.FLASH.constrain(); 18 | let rcc = pdev.RCC.constrain(); 19 | let mut gpioa = pdev.GPIOA.split(); 20 | 21 | let clocks = rcc 22 | .cfgr 23 | .use_hse(8.MHz()) 24 | .sysclk(32.MHz()) 25 | .pclk1(16.MHz()) 26 | .freeze(&mut flash.acr); 27 | 28 | let mut delay = pdev.TIM2.delay_us(&clocks); 29 | let mut tmr = pdev.TIM3.counter_hz(&clocks); 30 | tmr.start(6.MHz()).unwrap(); 31 | 32 | let miso = gpioa.pa0.into_floating_input(&mut gpioa.crl); 33 | let mosi = gpioa.pa1.into_push_pull_output(&mut gpioa.crl); 34 | let sck = gpioa.pa2.into_push_pull_output(&mut gpioa.crl); 35 | 36 | let mut spi = SPI::new(MODE_0, miso, mosi, sck, tmr); 37 | 38 | loop { 39 | for byte in b"Hello, World!" { 40 | block!(spi.send(*byte)).unwrap(); 41 | } 42 | 43 | delay.delay_ms(1000u16); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH : ORIGIN = 0x08000000, LENGTH = 64K 4 | RAM : ORIGIN = 0x20000000, LENGTH = 20K 5 | } 6 | -------------------------------------------------------------------------------- /src/i2c.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # Synchronous implementation of embedded-hal I2C traits based on GPIO bitbang 3 | 4 | This implementation consumes the following hardware resources: 5 | - A periodic timer to mark clock cycles 6 | - Two GPIO pins for SDA and SCL lines. 7 | 8 | Note that the current implementation does not support I2C clock stretching. 9 | 10 | ## Hardware requirements 11 | 12 | 1. Configure GPIO pins as Open-Drain outputs. 13 | 2. Configure timer frequency to be twice the desired I2C clock frequency. 14 | 15 | ## Blue Pill example 16 | 17 | Here is a sample code for LM75A I2C temperature sensor 18 | on Blue Pill or any other stm32f1xx board: 19 | 20 | ```no_run 21 | use stm32f1xx_hal as hal; 22 | use hal::{prelude::*, timer::Timer, stm32}; 23 | use lm75::{Lm75, SlaveAddr}; 24 | use bitbang_hal; 25 | 26 | // ... 27 | 28 | let pdev = stm32::Peripherals::take().unwrap(); 29 | 30 | let mut flash = pdev.FLASH.constrain(); 31 | let mut rcc = pdev.RCC.constrain(); 32 | let mut gpioa = pdev.GPIOA.split(&mut rcc.apb2); 33 | 34 | let clocks = rcc 35 | .cfgr 36 | .use_hse(8.mhz()) 37 | .sysclk(32.mhz()) 38 | .pclk1(16.mhz()) 39 | .freeze(&mut flash.acr); 40 | 41 | let tmr = Timer::tim3(pdev.TIM3, &clocks, &mut rcc.apb1).start_count_down(200.khz()); 42 | let scl = gpioa.pa1.into_open_drain_output(&mut gpioa.crl); 43 | let sda = gpioa.pa2.into_open_drain_output(&mut gpioa.crl); 44 | 45 | let i2c = bitbang_hal::i2c::I2cBB::new(scl, sda, tmr); 46 | let mut sensor = Lm75::new(i2c, SlaveAddr::default()); 47 | let temp = sensor.read_temperature().unwrap(); 48 | 49 | //... 50 | ``` 51 | */ 52 | 53 | use embedded_hal::blocking::i2c::{Read, Write, WriteRead}; 54 | use embedded_hal::digital::v2::{InputPin, OutputPin}; 55 | use embedded_hal::timer::{CountDown, Periodic}; 56 | use nb::block; 57 | 58 | /// I2C error 59 | #[derive(Debug, Eq, PartialEq)] 60 | pub enum Error { 61 | /// GPIO error 62 | Bus(E), 63 | /// No ack received 64 | NoAck, 65 | /// Invalid input 66 | InvalidData, 67 | } 68 | 69 | /// Bit banging I2C device 70 | pub struct I2cBB 71 | where 72 | SCL: OutputPin, 73 | SDA: OutputPin + InputPin, 74 | CLK: CountDown + Periodic, 75 | { 76 | scl: SCL, 77 | sda: SDA, 78 | clk: CLK, 79 | } 80 | 81 | impl I2cBB 82 | where 83 | SCL: OutputPin, 84 | SDA: OutputPin + InputPin, 85 | CLK: CountDown + Periodic, 86 | { 87 | /// Create instance 88 | pub fn new(scl: SCL, sda: SDA, clk: CLK) -> Self { 89 | I2cBB { scl, sda, clk } 90 | } 91 | 92 | /// Send a raw I2C start. 93 | /// 94 | /// **This is a low-level control function.** For normal I2C devices, 95 | /// please use the embedded-hal traits [Read], [Write], or 96 | /// [WriteRead]. 97 | pub fn raw_i2c_start(&mut self) -> Result<(), crate::i2c::Error> { 98 | self.set_scl_high()?; 99 | self.set_sda_high()?; 100 | self.wait_for_clk(); 101 | 102 | self.set_sda_low()?; 103 | self.wait_for_clk(); 104 | 105 | self.set_scl_low()?; 106 | self.wait_for_clk(); 107 | 108 | Ok(()) 109 | } 110 | 111 | /// Send a raw I2C stop. 112 | /// 113 | /// **This is a low-level control function.** For normal I2C devices, 114 | /// please use the embedded-hal traits [Read], [Write], or 115 | /// [WriteRead]. 116 | pub fn raw_i2c_stop(&mut self) -> Result<(), crate::i2c::Error> { 117 | self.set_scl_high()?; 118 | self.wait_for_clk(); 119 | 120 | self.set_sda_high()?; 121 | self.wait_for_clk(); 122 | 123 | Ok(()) 124 | } 125 | 126 | fn i2c_is_ack(&mut self) -> Result> { 127 | self.set_sda_high()?; 128 | self.set_scl_high()?; 129 | self.wait_for_clk(); 130 | 131 | let ack = self.sda.is_low().map_err(Error::Bus)?; 132 | 133 | self.set_scl_low()?; 134 | self.set_sda_low()?; 135 | self.wait_for_clk(); 136 | 137 | Ok(ack) 138 | } 139 | 140 | fn i2c_read_byte(&mut self, should_send_ack: bool) -> Result> { 141 | let mut byte: u8 = 0; 142 | 143 | self.set_sda_high()?; 144 | 145 | for bit_offset in 0..8 { 146 | self.set_scl_high()?; 147 | self.wait_for_clk(); 148 | 149 | if self.sda.is_high().map_err(Error::Bus)? { 150 | byte |= 1 << (7 - bit_offset); 151 | } 152 | 153 | self.set_scl_low()?; 154 | self.wait_for_clk(); 155 | } 156 | 157 | if should_send_ack { 158 | self.set_sda_low()?; 159 | } else { 160 | self.set_sda_high()?; 161 | } 162 | 163 | self.set_scl_high()?; 164 | self.wait_for_clk(); 165 | 166 | self.set_scl_low()?; 167 | self.set_sda_low()?; 168 | self.wait_for_clk(); 169 | 170 | Ok(byte) 171 | } 172 | 173 | fn i2c_write_byte(&mut self, byte: u8) -> Result<(), crate::i2c::Error> { 174 | for bit_offset in 0..8 { 175 | let out_bit = (byte >> (7 - bit_offset)) & 0b1; 176 | 177 | if out_bit == 1 { 178 | self.set_sda_high()?; 179 | } else { 180 | self.set_sda_low()?; 181 | } 182 | 183 | self.set_scl_high()?; 184 | self.wait_for_clk(); 185 | 186 | self.set_scl_low()?; 187 | self.set_sda_low()?; 188 | self.wait_for_clk(); 189 | } 190 | 191 | Ok(()) 192 | } 193 | 194 | /// Read raw bytes from the slave. 195 | /// 196 | /// **This is a low-level control function.** For normal I2C devices, 197 | /// please use the embedded-hal traits [Read], [Write], or 198 | /// [WriteRead]. 199 | #[inline] 200 | pub fn raw_read_from_slave(&mut self, input: &mut [u8]) -> Result<(), crate::i2c::Error> { 201 | for i in 0..input.len() { 202 | let should_send_ack = i != (input.len() - 1); 203 | input[i] = self.i2c_read_byte(should_send_ack)?; 204 | } 205 | Ok(()) 206 | } 207 | 208 | /// Send raw bytes to the slave. 209 | /// 210 | /// **This is a low-level control function.** For normal I2C devices, 211 | /// please use the embedded-hal traits [Read], [Write], or 212 | /// [WriteRead]. 213 | #[inline] 214 | pub fn raw_write_to_slave(&mut self, output: &[u8]) -> Result<(), crate::i2c::Error> { 215 | for byte in output { 216 | self.i2c_write_byte(*byte)?; 217 | self.check_ack()?; 218 | } 219 | Ok(()) 220 | } 221 | 222 | #[inline] 223 | fn set_scl_high(&mut self) -> Result<(), crate::i2c::Error> { 224 | self.scl.set_high().map_err(Error::Bus) 225 | } 226 | 227 | #[inline] 228 | fn set_scl_low(&mut self) -> Result<(), crate::i2c::Error> { 229 | self.scl.set_low().map_err(Error::Bus) 230 | } 231 | 232 | #[inline] 233 | fn set_sda_high(&mut self) -> Result<(), crate::i2c::Error> { 234 | self.sda.set_high().map_err(Error::Bus) 235 | } 236 | 237 | #[inline] 238 | fn set_sda_low(&mut self) -> Result<(), crate::i2c::Error> { 239 | self.sda.set_low().map_err(Error::Bus) 240 | } 241 | 242 | #[inline] 243 | fn wait_for_clk(&mut self) { 244 | block!(self.clk.wait()).ok(); 245 | } 246 | 247 | #[inline] 248 | fn check_ack(&mut self) -> Result<(), crate::i2c::Error> { 249 | if !self.i2c_is_ack()? { 250 | Err(Error::NoAck) 251 | } else { 252 | Ok(()) 253 | } 254 | } 255 | } 256 | 257 | impl Write for I2cBB 258 | where 259 | SCL: OutputPin, 260 | SDA: OutputPin + InputPin, 261 | CLK: CountDown + Periodic, 262 | { 263 | type Error = crate::i2c::Error; 264 | 265 | fn write(&mut self, addr: u8, output: &[u8]) -> Result<(), Self::Error> { 266 | // ST 267 | self.raw_i2c_start()?; 268 | 269 | // SAD + W 270 | self.i2c_write_byte((addr << 1) | 0x0)?; 271 | self.check_ack()?; 272 | 273 | self.raw_write_to_slave(output)?; 274 | 275 | // SP 276 | self.raw_i2c_stop() 277 | } 278 | } 279 | 280 | impl Read for I2cBB 281 | where 282 | SCL: OutputPin, 283 | SDA: OutputPin + InputPin, 284 | CLK: CountDown + Periodic, 285 | { 286 | type Error = crate::i2c::Error; 287 | 288 | fn read(&mut self, addr: u8, input: &mut [u8]) -> Result<(), Self::Error> { 289 | if input.is_empty() { 290 | return Ok(()); 291 | } 292 | 293 | // ST 294 | self.raw_i2c_start()?; 295 | 296 | // SAD + R 297 | self.i2c_write_byte((addr << 1) | 0x1)?; 298 | self.check_ack()?; 299 | 300 | self.raw_read_from_slave(input)?; 301 | 302 | // SP 303 | self.raw_i2c_stop() 304 | } 305 | } 306 | 307 | impl WriteRead for I2cBB 308 | where 309 | SCL: OutputPin, 310 | SDA: OutputPin + InputPin, 311 | CLK: CountDown + Periodic, 312 | { 313 | type Error = crate::i2c::Error; 314 | 315 | fn write_read(&mut self, addr: u8, output: &[u8], input: &mut [u8]) -> Result<(), Self::Error> { 316 | if output.is_empty() || input.is_empty() { 317 | return Err(Error::InvalidData); 318 | } 319 | 320 | // ST 321 | self.raw_i2c_start()?; 322 | 323 | // SAD + W 324 | self.i2c_write_byte((addr << 1) | 0x0)?; 325 | self.check_ack()?; 326 | 327 | self.raw_write_to_slave(output)?; 328 | 329 | // SR 330 | self.raw_i2c_start()?; 331 | 332 | // SAD + R 333 | self.i2c_write_byte((addr << 1) | 0x1)?; 334 | self.check_ack()?; 335 | 336 | self.raw_read_from_slave(input)?; 337 | 338 | // SP 339 | self.raw_i2c_stop() 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a [bit banging] implementation of the [`embedded-hal`] traits. 2 | //! 3 | //! [bit banging]: https://en.wikipedia.org/wiki/Bit_banging 4 | //! [`embedded-hal`]: https://github.com/rust-embedded/embedded-hal 5 | //! 6 | //! ## Usage examples 7 | //! 8 | //! See usage examples in the examples folder in the crate sources 9 | 10 | #![no_std] 11 | #![deny(missing_docs)] 12 | 13 | pub mod i2c; 14 | pub mod serial; 15 | pub mod spi; 16 | -------------------------------------------------------------------------------- /src/serial.rs: -------------------------------------------------------------------------------- 1 | //! Serial communication (USART) 2 | //! 3 | //! This implementation consumes the following hardware resources: 4 | //! - Periodic timer to mark clock cycles 5 | //! - Output GPIO pin for transmission (TX) 6 | //! - Input GPIO pin for reception (RX) 7 | //! 8 | //! The timer must be configured to twice the desired communication frequency. 9 | //! 10 | 11 | use embedded_hal::digital::v2::{InputPin, OutputPin}; 12 | use embedded_hal::serial; 13 | use embedded_hal::timer::{CountDown, Periodic}; 14 | use nb::block; 15 | 16 | /// Serial communication error type 17 | #[derive(Debug)] 18 | pub enum Error { 19 | /// Bus error 20 | Bus(E), 21 | } 22 | 23 | /// Bit banging serial communication (USART) device 24 | pub struct Serial 25 | where 26 | TX: OutputPin, 27 | RX: InputPin, 28 | Timer: CountDown + Periodic, 29 | { 30 | tx: TX, 31 | rx: RX, 32 | timer: Timer, 33 | } 34 | 35 | impl Serial 36 | where 37 | TX: OutputPin, 38 | RX: InputPin, 39 | Timer: CountDown + Periodic, 40 | { 41 | /// Create instance 42 | pub fn new(tx: TX, rx: RX, timer: Timer) -> Self { 43 | Serial { tx, rx, timer } 44 | } 45 | 46 | #[inline] 47 | fn wait_for_timer(&mut self) { 48 | block!(self.timer.wait()).ok(); 49 | } 50 | } 51 | 52 | impl serial::Write for Serial 53 | where 54 | TX: OutputPin, 55 | RX: InputPin, 56 | Timer: CountDown + Periodic, 57 | { 58 | type Error = crate::serial::Error; 59 | 60 | fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error> { 61 | let mut data_out = byte; 62 | self.tx.set_low().map_err(Error::Bus)?; // start bit 63 | self.wait_for_timer(); 64 | for _bit in 0..8 { 65 | if data_out & 1 == 1 { 66 | self.tx.set_high().map_err(Error::Bus)?; 67 | } else { 68 | self.tx.set_low().map_err(Error::Bus)?; 69 | } 70 | data_out >>= 1; 71 | self.wait_for_timer(); 72 | } 73 | self.tx.set_high().map_err(Error::Bus)?; // stop bit 74 | self.wait_for_timer(); 75 | Ok(()) 76 | } 77 | 78 | fn flush(&mut self) -> nb::Result<(), Self::Error> { 79 | Ok(()) 80 | } 81 | } 82 | 83 | impl serial::Read for Serial 84 | where 85 | TX: OutputPin, 86 | RX: InputPin, 87 | Timer: CountDown + Periodic, 88 | { 89 | type Error = crate::serial::Error; 90 | 91 | fn read(&mut self) -> nb::Result { 92 | let mut data_in = 0; 93 | // wait for start bit 94 | while self.rx.is_high().map_err(Error::Bus)? {} 95 | self.wait_for_timer(); 96 | for _bit in 0..8 { 97 | data_in <<= 1; 98 | if self.rx.is_high().map_err(Error::Bus)? { 99 | data_in |= 1 100 | } 101 | self.wait_for_timer(); 102 | } 103 | // wait for stop bit 104 | self.wait_for_timer(); 105 | Ok(data_in) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/spi.rs: -------------------------------------------------------------------------------- 1 | //! Serial Peripheral Interface 2 | //! 3 | //! This implementation consumes the following hardware resources: 4 | //! - Periodic timer to mark clock cycles 5 | //! - Output GPIO pin for clock signal (SCLK) 6 | //! - Output GPIO pin for data transmission (Master Output Slave Input - MOSI) 7 | //! - Input GPIO pin for data reception (Master Input Slave Output - MISO) 8 | //! 9 | //! The timer must be configured to twice the desired communication frequency. 10 | //! 11 | //! SS/CS (slave select) must be handled independently. 12 | //! 13 | //! MSB-first and LSB-first bit orders are supported. 14 | //! 15 | 16 | pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3}; 17 | 18 | use embedded_hal::digital::v2::{InputPin, OutputPin}; 19 | use embedded_hal::spi::{FullDuplex, Mode, Polarity}; 20 | use embedded_hal::timer::{CountDown, Periodic}; 21 | use nb::block; 22 | 23 | /// Error type 24 | #[derive(Debug)] 25 | pub enum Error { 26 | /// Communication error 27 | Bus(E), 28 | /// Attempted read without input data 29 | NoData, 30 | } 31 | 32 | /// Transmission bit order 33 | #[derive(Debug)] 34 | pub enum BitOrder { 35 | /// Most significant bit first 36 | MSBFirst, 37 | /// Least significant bit first 38 | LSBFirst, 39 | } 40 | 41 | impl Default for BitOrder { 42 | /// Default bit order: MSB first 43 | fn default() -> Self { 44 | BitOrder::MSBFirst 45 | } 46 | } 47 | 48 | /// A Full-Duplex SPI implementation, takes 3 pins, and a timer running at 2x 49 | /// the desired SPI frequency. 50 | pub struct SPI 51 | where 52 | Miso: InputPin, 53 | Mosi: OutputPin, 54 | Sck: OutputPin, 55 | Timer: CountDown + Periodic, 56 | { 57 | mode: Mode, 58 | miso: Miso, 59 | mosi: Mosi, 60 | sck: Sck, 61 | timer: Timer, 62 | read_val: Option, 63 | bit_order: BitOrder, 64 | } 65 | 66 | impl SPI 67 | where 68 | Miso: InputPin, 69 | Mosi: OutputPin, 70 | Sck: OutputPin, 71 | Timer: CountDown + Periodic, 72 | { 73 | /// Create instance 74 | pub fn new(mode: Mode, miso: Miso, mosi: Mosi, sck: Sck, timer: Timer) -> Self { 75 | let mut spi = SPI { 76 | mode, 77 | miso, 78 | mosi, 79 | sck, 80 | timer, 81 | read_val: None, 82 | bit_order: BitOrder::default(), 83 | }; 84 | 85 | match mode.polarity { 86 | Polarity::IdleLow => spi.sck.set_low(), 87 | Polarity::IdleHigh => spi.sck.set_high(), 88 | } 89 | .unwrap_or(()); 90 | 91 | spi 92 | } 93 | 94 | /// Set transmission bit order 95 | pub fn set_bit_order(&mut self, order: BitOrder) { 96 | self.bit_order = order; 97 | } 98 | 99 | /// Allows for an access to the timer type. 100 | /// This can be used to change the speed. 101 | /// 102 | /// In closure you get ownership of the timer 103 | /// so you can destruct it and build it up again if necessary. 104 | /// 105 | /// # Example 106 | /// 107 | /// ```Rust 108 | ///spi.access_timer(|mut timer| { 109 | /// timer.set_freq(4.mhz()); 110 | /// timer 111 | ///}); 112 | ///``` 113 | /// 114 | pub fn access_timer(&mut self, f: F) 115 | where 116 | F: FnOnce(Timer) -> Timer, 117 | { 118 | // Create a zeroed timer. 119 | // This is unsafe, but its safety is guaranteed, though, because the zeroed timer is never used. 120 | let timer = unsafe { core::mem::zeroed() }; 121 | // Get the timer in the struct. 122 | let timer = core::mem::replace(&mut self.timer, timer); 123 | // Give the timer to the closure and put the result back into the struct. 124 | self.timer = f(timer); 125 | } 126 | 127 | fn read_bit(&mut self) -> nb::Result<(), crate::spi::Error> { 128 | let is_miso_high = self.miso.is_high().map_err(Error::Bus)?; 129 | let shifted_value = self.read_val.unwrap_or(0) << 1; 130 | if is_miso_high { 131 | self.read_val = Some(shifted_value | 1); 132 | } else { 133 | self.read_val = Some(shifted_value); 134 | } 135 | Ok(()) 136 | } 137 | 138 | #[inline] 139 | fn set_clk_high(&mut self) -> Result<(), crate::spi::Error> { 140 | self.sck.set_high().map_err(Error::Bus) 141 | } 142 | 143 | #[inline] 144 | fn set_clk_low(&mut self) -> Result<(), crate::spi::Error> { 145 | self.sck.set_low().map_err(Error::Bus) 146 | } 147 | 148 | #[inline] 149 | fn wait_for_timer(&mut self) { 150 | block!(self.timer.wait()).ok(); 151 | } 152 | } 153 | 154 | impl FullDuplex for SPI 155 | where 156 | Miso: InputPin, 157 | Mosi: OutputPin, 158 | Sck: OutputPin, 159 | Timer: CountDown + Periodic, 160 | { 161 | type Error = crate::spi::Error; 162 | 163 | #[inline] 164 | fn read(&mut self) -> nb::Result { 165 | match self.read_val { 166 | Some(val) => Ok(val), 167 | None => Err(nb::Error::Other(crate::spi::Error::NoData)), 168 | } 169 | } 170 | 171 | fn send(&mut self, byte: u8) -> nb::Result<(), Self::Error> { 172 | for bit_offset in 0..8 { 173 | let out_bit = match self.bit_order { 174 | BitOrder::MSBFirst => (byte >> (7 - bit_offset)) & 0b1, 175 | BitOrder::LSBFirst => (byte >> bit_offset) & 0b1, 176 | }; 177 | 178 | if out_bit == 1 { 179 | self.mosi.set_high().map_err(Error::Bus)?; 180 | } else { 181 | self.mosi.set_low().map_err(Error::Bus)?; 182 | } 183 | 184 | match self.mode { 185 | MODE_0 => { 186 | self.wait_for_timer(); 187 | self.set_clk_high()?; 188 | self.read_bit()?; 189 | self.wait_for_timer(); 190 | self.set_clk_low()?; 191 | } 192 | MODE_1 => { 193 | self.set_clk_high()?; 194 | self.wait_for_timer(); 195 | self.read_bit()?; 196 | self.set_clk_low()?; 197 | self.wait_for_timer(); 198 | } 199 | MODE_2 => { 200 | self.wait_for_timer(); 201 | self.set_clk_low()?; 202 | self.read_bit()?; 203 | self.wait_for_timer(); 204 | self.set_clk_high()?; 205 | } 206 | MODE_3 => { 207 | self.set_clk_low()?; 208 | self.wait_for_timer(); 209 | self.read_bit()?; 210 | self.set_clk_high()?; 211 | self.wait_for_timer(); 212 | } 213 | } 214 | } 215 | 216 | Ok(()) 217 | } 218 | } 219 | 220 | impl embedded_hal::blocking::spi::transfer::Default 221 | for SPI 222 | where 223 | Miso: InputPin, 224 | Mosi: OutputPin, 225 | Sck: OutputPin, 226 | Timer: CountDown + Periodic, 227 | { 228 | } 229 | 230 | impl embedded_hal::blocking::spi::write::Default 231 | for SPI 232 | where 233 | Miso: InputPin, 234 | Mosi: OutputPin, 235 | Sck: OutputPin, 236 | Timer: CountDown + Periodic, 237 | { 238 | } 239 | --------------------------------------------------------------------------------