├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── README.tpl ├── examples ├── at24c04.rs ├── blink.rs ├── bme280.rs ├── i2cdetect.rs ├── input.rs ├── lm75.rs ├── spi-eh1-loopback.rs ├── spi-flash.rs └── ws2812.rs ├── rustfmt.toml ├── src ├── delay.rs ├── error.rs ├── gpio.rs ├── i2c.rs ├── lib.rs └── spi.rs └── tests ├── issue_18.rs └── version-numbers.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "cargo" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | tags: 6 | - 'v*' 7 | pull_request: 8 | schedule: 9 | - cron: "3 3 * * *" 10 | 11 | name: CI 12 | 13 | jobs: 14 | build: 15 | name: Cargo Build 16 | runs-on: ubuntu-latest 17 | env: {"RUSTFLAGS": "-D warnings"} 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: dtolnay/rust-toolchain@stable 21 | - run: cargo build 22 | 23 | examples: 24 | name: Cargo Examples 25 | runs-on: ubuntu-latest 26 | env: {"RUSTFLAGS": "-D warnings"} 27 | strategy: 28 | matrix: 29 | features: 30 | - libftd2xx 31 | - ftdi 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: dtolnay/rust-toolchain@stable 35 | - run: | 36 | if ${{ matrix.features == 'ftdi' }} 37 | then 38 | sudo apt-get update 39 | sudo apt-get install -y libftdi1 libftdi1-dev pkg-config 40 | fi 41 | cargo build --features ${{ matrix.features }} --examples 42 | 43 | test: 44 | name: Cargo Test 45 | runs-on: ubuntu-latest 46 | env: {"RUSTFLAGS": "-D warnings"} 47 | strategy: 48 | matrix: 49 | features: 50 | - libftd2xx 51 | - ftdi 52 | steps: 53 | - uses: actions/checkout@v4 54 | - uses: dtolnay/rust-toolchain@stable 55 | - run: | 56 | if ${{ matrix.features == 'ftdi' }} 57 | then 58 | sudo apt-get update 59 | sudo apt-get install -y libftdi1 libftdi1-dev pkg-config 60 | fi 61 | cargo test --features ${{ matrix.features }} 62 | 63 | doc: 64 | name: Cargo Doc 65 | runs-on: ubuntu-latest 66 | env: {"RUSTDOCFLAGS": "-D warnings"} 67 | steps: 68 | - uses: actions/checkout@v4 69 | - uses: dtolnay/rust-toolchain@stable 70 | - run: cargo doc --all-features 71 | 72 | clippy: 73 | name: Clippy 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/checkout@v4 77 | - uses: dtolnay/rust-toolchain@stable 78 | with: 79 | components: clippy 80 | - run: cargo clippy --all-features -- --deny warnings 81 | 82 | format: 83 | name: Cargo Fmt 84 | runs-on: ubuntu-latest 85 | steps: 86 | - uses: actions/checkout@v4 87 | - uses: dtolnay/rust-toolchain@nightly 88 | with: 89 | components: rustfmt 90 | - run: cargo +nightly fmt -- --check 91 | 92 | release: 93 | name: crates.io release 94 | if: startsWith(github.ref, 'refs/tags/v') 95 | needs: [build, test, examples, doc, clippy, format] 96 | runs-on: ubuntu-latest 97 | steps: 98 | - uses: actions/checkout@v4 99 | - uses: dtolnay/rust-toolchain@stable 100 | - run: cargo publish --token ${CRATES_IO_TOKEN} 101 | env: 102 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | 4 | # ide files 5 | rusty-tags.vi 6 | .idea 7 | .vscode 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.23.2] - 2025-06-01 8 | ### Fixed 9 | - Fixed SPI SCLK pin states for the `ClockData::MsbPosIn` and `ClockData::LsbPosIn` SPI modes. 10 | 11 | ## [0.23.1] - 2025-03-29 12 | ### Added 13 | - Added a `with_device` method to access device-specific features, such as the EEPROM. 14 | 15 | ## [0.23.0] - 2025-03-09 16 | ### Changed 17 | - Changed the SPI traits to be implemented on the `SpiDevice` struct, instead of a reference to the struct. 18 | - Changed the edition from 2021 to 2024. 19 | 20 | ## [0.22.1] - 2024-12-24 21 | ### Fixed 22 | - Fixed 0-size I2C transfers 23 | 24 | ## [0.22.0] - 2024-05-06 25 | ### Added 26 | - Added support for the upper pins (C0-C7 on the FT232H). 27 | 28 | ## [0.21.1] - 2024-03-26 29 | ### Added 30 | - Added `ftdi-vendored` and `ftdi-libusb1-sys` passthrough features for linking with the `libftdi` backend. 31 | 32 | ## [0.21.0] - 2024-01-09 33 | ### Changed 34 | - updated the v1 release of `embedded-hal` from `1.0.0-rc.3` to `1`. 35 | - updated the v1 release of `embedded-hal-nb` from `1.0.0-rc.3` to `1`. 36 | 37 | ## [0.20.0] - 2023-12-14 38 | ### Changed 39 | - Updated the v1 release of `embedded-hal` from `1.0.0-rc.2` to `1.0.0-rc.3`. 40 | - Updated the v1 release of `embedded-hal-nb` from `1.0.0-rc.2` to `1.0.0-rc.3`. 41 | 42 | ## [0.19.0] - 2023-11-29 43 | ### Changed 44 | - Updated the v1 release of `embedded-hal` from `1.0.0-rc.1` to `1.0.0-rc.2`. 45 | - Updated the v1 release of `embedded-hal-nb` from `1.0.0-rc.1` to `1.0.0-rc.2`. 46 | 47 | ## [0.18.0] - 2023-10-30 48 | ### Added 49 | - Added an I2C implementation for `embedded-hal` version 1. 50 | 51 | ### Changed 52 | - Changed `ErrorKind::I2cNoAck` to have an inner type of `eh1::i2c::NoAcknowledgeSource`. 53 | 54 | ### Fixed 55 | - Fixed asymmetric SPI transfers (read size > write size) with `eh1`. 56 | 57 | ## [0.17.0] - 2023-08-15 58 | ### Changed 59 | - Updated the alpha release of `embedded-hal` from `1.0.0-alpha.11` to `1.0.0-rc.1`. 60 | - Updated the alpha release of `embedded-hal-nb` from `1.0.0-alpha.3` to `1.0.0-rc.1`. 61 | 62 | ## [0.16.0] - 2023-07-05 63 | ### Changed 64 | - Updated the alpha release of `embedded-hal` from `1.0.0-alpha.10` to `1.0.0-alpha.11`. 65 | 66 | ## [0.15.1] - 2023-05-13 67 | ### Fixed 68 | - Changed the `Error` and `ErrorKind` types from private to public. 69 | 70 | ## [0.15.0] - 2023-04-22 71 | ### Changed 72 | - Updated the alpha release of `embedded-hal` from `1.0.0-alpha.9` to `1.0.0-alpha.10`. 73 | 74 | ## [0.14.0] - 2022-11-27 75 | ### Changed 76 | - Removed lifetimes on `OutputPin`, `InputPin`, `I2c`, `Spi`, and `SpiDevice` to improve ease-of-use. 77 | 78 | ## [0.13.0] - 2022-09-28 79 | ### Changed 80 | - Updated the alpha release of `embedded-hal` from `1.0.0-alpha.8` to `1.0.0-alpha.9`. 81 | 82 | ## [0.12.0] - 2022-09-03 83 | ### Added 84 | - Added re-exports for `libftd2xx` and `ftdi` when the respective feature is used. 85 | - Added `embedded-hal` version `1.0.0-alpha.8` trait implementations for: 86 | - GPIOs 87 | - Delay 88 | - SPI 89 | 90 | ### Changed 91 | - Changed the `embedded-hal` version `0.2` re-export name from `embedded-hal` to 92 | `eh0` to differentiate from `embedded-hal` version `1.0.0-alpha.8`. 93 | 94 | ## [0.11.0] - 2022-01-18 95 | ### Added 96 | - Added support for input pins. 97 | 98 | ### Changed 99 | - The `ad0` - `ad7` methods to get an `OutputPin` now return a `Result` to 100 | support input pins, previously these methods were infallible. 101 | 102 | ## [0.10.0] - 2021-11-08 103 | ### Added 104 | - Added support for `libftdi1` as a backend. 105 | 106 | ### Changed 107 | - Renamed to `ftdi-embedded-hal`. 108 | - The `ftd2xx` backend is no longer enabled by default. 109 | - Changed the error type to support multiple backends. 110 | - Updated the edition from 2018 to 2021. 111 | 112 | ## [0.9.1] - 2021-08-10 113 | ### Fixed 114 | - Call `close()` on Drop, allowing recovery from failures in `init()`. 115 | 116 | ## [0.9.0] - 2021-07-01 117 | ### Changed 118 | - Updated the `libftd2xx` dependency from 0.29.0 to 0.31.0. 119 | 120 | ## [0.8.0] - 2021-05-29 121 | ### Changed 122 | - Updated the `libftd2xx` dependency from 0.28.0 to 0.29.0. 123 | 124 | ## [0.7.0] - 2021-04-18 125 | ### Added 126 | - Added checks for missing ACKs from the I2C slave. 127 | Missing ACKs will now return an `NakError` from the I2C traits. 128 | 129 | ### Changed 130 | - Changed the default implementation of I2C traits to wait for a slave ACK 131 | before transmitting more bytes. The previous behavior can be retained by 132 | calling `set_fast(true)`. 133 | 134 | ## [0.6.0] - 2021-04-10 135 | ### Added 136 | - Added support for the FT4232H. 137 | - Added support for the FT2232H. 138 | 139 | ### Changed 140 | - Changed the default linking method on Linux to dynamic. 141 | Static linking can be enabled with the `static` feature flag. 142 | - Changed the I2C pins to input (tri-state) when in idle mode. 143 | 144 | ### Fixed 145 | - Fixed AD0 (SCL) pulling low when when I2C is first initialized. 146 | - Fixed I2C AD0 & AD1 (SCL & SDA out) being pulled low when another OutputPin 147 | changed state. 148 | 149 | ## [0.5.1] - 2021-03-20 150 | ### Fixed 151 | - Fixed the I2C `Read` trait not setting the read address bit. 152 | - Fixed the I2C `Write` trait not driving SDA as an output when clocking data 153 | out. 154 | 155 | ## [0.5.0] - 2021-03-20 156 | ### Added 157 | - Added checks for pin allocation, trying to take output pins 0-2 while using 158 | the SPI interface will now result in panic. 159 | - Added I2C traits. 160 | - Added `Debug` for interface structures. 161 | - Added `with_serial_number` and `with_description` constructors. 162 | 163 | ### Changed 164 | - Changed the FTDI MPSSE initialization to occur once globally for the device 165 | instead of inside the SPI device trait. 166 | - Changed the `Delay` structure to contain dummy data for possible future use. 167 | - Change the `Ft232hHal::with_ft` to `impl From for Ft232hHal`. 168 | 169 | ### Removed 170 | - Removed `Eq` and `PartialEq` traits on the `Delay` structure. 171 | 172 | ## [0.4.0] - 2021-03-05 173 | ### Added 174 | - Added a `Delay` structure that implements the embedded-hal delay traits. 175 | 176 | ### Changed 177 | - Updated `libftd2xx` dependency from 0.24.0 to 0.25.0. 178 | This updates the vendor library from 1.4.8 to 1.4.22 for Linux targets. 179 | This should fix timeout conditions that previously occurred when rapidly 180 | toggling GPIO pins. 181 | 182 | ## [0.3.0] - 2021-02-14 183 | ### Changed 184 | - Improved latency for GPIOs 185 | 186 | ### Fixed 187 | - Fixed the example code for `Ft232hHal::new`. 188 | - Fixed pins 5, 6, 7 not being usable as outputs. 189 | 190 | ## [0.2.0] - 2020-09-13 191 | ### Added 192 | - Added SPI non-blocking traits. 193 | 194 | ### Changed 195 | - Updated to libftd2xx 0.17.0 196 | - Updated to embedded-hal 0.2.4 197 | 198 | ## [0.1.0] - 2020-09-12 199 | - Initial release 200 | 201 | [Unreleased]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.23.2...HEAD 202 | [0.23.2]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.23.1...v0.23.2 203 | [0.23.1]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.23.0...v0.23.1 204 | [0.23.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.22.1...v0.23.0 205 | [0.22.1]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.22.0...v0.22.1 206 | [0.22.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.21.1...v0.22.0 207 | [0.21.1]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.21.0...v0.21.1 208 | [0.21.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.20.0...v0.21.0 209 | [0.20.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.19.0...v0.20.0 210 | [0.19.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.18.0...v0.19.0 211 | [0.18.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.17.0...v0.18.0 212 | [0.17.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.16.0...v0.17.0 213 | [0.16.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.15.1...v0.16.0 214 | [0.15.1]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.15.0...v0.15.1 215 | [0.15.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.14.0...v0.15.0 216 | [0.14.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.13.0...v0.14.0 217 | [0.13.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.12.0...v0.13.0 218 | [0.12.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.11.0...v0.12.0 219 | [0.11.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.10.0...v0.11.0 220 | [0.10.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.9.1...v0.10.0 221 | [0.9.1]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.9.0...v0.9.1 222 | [0.9.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.8.0...v0.9.0 223 | [0.8.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.7.0...v0.8.0 224 | [0.7.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.6.0...v0.7.0 225 | [0.6.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.5.1...v0.6.0 226 | [0.5.1]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.5.0...v0.5.1 227 | [0.5.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.4.0...v0.5.0 228 | [0.4.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.3.0...v0.4.0 229 | [0.3.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.2.0...v0.3.0 230 | [0.2.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/compare/v0.1.0...v0.2.0 231 | [0.1.0]: https://github.com/ftdi-rs/ftdi-embedded-hal/releases/tag/v0.1.0 232 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## README Generation 4 | The README file is generated with [cargo-readme]. 5 | 6 | ```bash 7 | cargo install cargo-readme 8 | cargo readme > README.md 9 | ``` 10 | 11 | [cargo-readme]: https://github.com/livioribeiro/cargo-readme 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ftdi-embedded-hal" 3 | version = "0.23.2" 4 | authors = ["Alex Martens "] 5 | description = "embedded-hal implementation for FTDI USB devices." 6 | keywords = ["ftdi", "usb", "io", "hal"] 7 | categories = ["embedded"] 8 | edition = "2024" 9 | license = "MIT" 10 | repository = "https://github.com/ftdi-rs/ftdi-embedded-hal/" 11 | readme = "README.md" 12 | 13 | [features] 14 | libftd2xx-static = ["libftd2xx/static"] 15 | ftdi-vendored = ["ftdi/vendored"] 16 | ftdi-libusb1-sys = ["ftdi/libusb1-sys"] 17 | default = [] 18 | 19 | [dependencies] 20 | eh0 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"] } 21 | eh1 = { package = "embedded-hal", version = "1" } 22 | ehnb1 = { package = "embedded-hal-nb", version = "1" } 23 | ftdi = { version = "0.1.3", optional = true } 24 | ftdi-mpsse = "0.1" 25 | libftd2xx = { version = "0.33", optional = true } 26 | nb = "1" 27 | 28 | [dev-dependencies] 29 | cfg-if = "1" 30 | eeprom24x = "0.7.0" 31 | lm75 = "1.0.0" 32 | spi-memory = "0.2.0" 33 | version-sync = "0.9.2" 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 - present Alex M. 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 | [![crates.io](https://img.shields.io/crates/v/ftdi-embedded-hal.svg)](https://crates.io/crates/ftdi-embedded-hal) 2 | [![docs.rs](https://docs.rs/ftdi-embedded-hal/badge.svg)](https://docs.rs/ftdi-embedded-hal/) 3 | [![Build Status](https://github.com/ftdi-rs/ftdi-embedded-hal/workflows/CI/badge.svg)](https://github.com/ftdi-rs/ftdi-embedded-hal/actions) 4 | 5 | # ftdi-embedded-hal 6 | 7 | This is an [embedded-hal] implementation for the FTDI chips 8 | that use the [libftd2xx] or [ftdi-rs] drivers. 9 | 10 | This enables development of embedded device drivers without the use of 11 | a microcontroller. The FTDI devices interface with a PC via USB, and 12 | provide a multi-protocol synchronous serial engine to interface 13 | with most GPIO, SPI, and I2C embedded devices. 14 | 15 | **Note:** 16 | This is strictly a development tool. 17 | The crate contains runtime borrow checks and explicit panics to adapt the 18 | FTDI device into the [embedded-hal] traits. 19 | 20 | ## Quickstart 21 | 22 | * Enable the "libftd2xx-static" feature flag to use static linking with libftd2xx driver. 23 | * Linux users only: Add [udev rules]. 24 | 25 | ```toml 26 | [dependencies.ftdi-embedded-hal] 27 | version = "0.23.2" 28 | features = ["libftd2xx", "libftd2xx-static"] 29 | ``` 30 | 31 | ## Limitations 32 | 33 | * Limited trait support: SPI, I2C, Delay, InputPin, and OutputPin traits are implemented. 34 | * Limited device support: FT232H, FT2232H, FT4232H. 35 | * Limited SPI modes support: MODE0, MODE2. 36 | 37 | ## Examples 38 | 39 | ### SPI 40 | 41 | Pin setup: 42 | 43 | * D0 - SCK 44 | * D1 - SDO (MOSI) 45 | * D2 - SDI (MISO) 46 | * D3..D7 - Available for CS 47 | 48 | Communicate with SPI devices using [ftdi-rs] driver: 49 | ```rust 50 | use ftdi_embedded_hal as hal; 51 | 52 | let device = ftdi::find_by_vid_pid(0x0403, 0x6010) 53 | .interface(ftdi::Interface::A) 54 | .open()?; 55 | 56 | let hal = hal::FtHal::init_freq(device, 3_000_000)?; 57 | let spi = hal.spi()?; 58 | ``` 59 | 60 | Communicate with SPI devices using [libftd2xx] driver: 61 | ```rust 62 | use ftdi_embedded_hal as hal; 63 | 64 | let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A")?; 65 | 66 | let hal = hal::FtHal::init_freq(device, 3_000_000)?; 67 | let spi = hal.spi()?; 68 | ``` 69 | 70 | ### I2C 71 | 72 | Communicate with I2C devices using [ftdi-rs] driver: 73 | ```rust 74 | use ftdi_embedded_hal as hal; 75 | 76 | let device = ftdi::find_by_vid_pid(0x0403, 0x6010) 77 | .interface(ftdi::Interface::A) 78 | .open()?; 79 | 80 | let hal = hal::FtHal::init_freq(device, 400_000)?; 81 | let i2c = hal.i2c()?; 82 | ``` 83 | 84 | Communicate with I2C devices using [libftd2xx] driver: 85 | ```rust 86 | use ftdi_embedded_hal as hal; 87 | 88 | let device = libftd2xx::Ft232h::with_description("Single RS232-HS")?; 89 | 90 | let hal = hal::FtHal::init_freq(device, 400_000)?; 91 | let i2c = hal.i2c()?; 92 | ``` 93 | 94 | ### GPIO 95 | 96 | Control GPIO pins using [libftd2xx] driver: 97 | ```rust 98 | use ftdi_embedded_hal as hal; 99 | 100 | let device = libftd2xx::Ft232h::with_description("Single RS232-HS")?; 101 | 102 | let hal = hal::FtHal::init_default(device)?; 103 | let gpio = hal.ad6(); 104 | ``` 105 | 106 | Control GPIO pins using [ftdi-rs] driver: 107 | ```rust 108 | use ftdi_embedded_hal as hal; 109 | 110 | let device = ftdi::find_by_vid_pid(0x0403, 0x6010) 111 | .interface(ftdi::Interface::A) 112 | .open()?; 113 | 114 | let hal = hal::FtHal::init_default(device)?; 115 | let gpio = hal.ad6(); 116 | ``` 117 | 118 | ### More examples 119 | 120 | * [newAM/eeprom25aa02e48-rs]: read data from Microchip 25AA02E48 SPI EEPROM 121 | * [newAM/bme280-rs]: read samples from Bosch BME280 sensor via I2C protocol 122 | 123 | [embedded-hal]: https://github.com/rust-embedded/embedded-hal 124 | [ftdi-rs]: https://github.com/tanriol/ftdi-rs 125 | [libftd2xx crate]: https://github.com/ftdi-rs/libftd2xx-rs/ 126 | [libftd2xx]: https://github.com/ftdi-rs/libftd2xx-rs 127 | [newAM/eeprom25aa02e48-rs]: https://github.com/newAM/eeprom25aa02e48-rs/blob/main/examples/ftdi.rs 128 | [newAM/bme280-rs]: https://github.com/newAM/bme280-rs/blob/main/examples/ftdi-i2c.rs 129 | [udev rules]: https://github.com/ftdi-rs/libftd2xx-rs/#udev-rules 130 | [setup executable]: https://www.ftdichip.com/Drivers/CDM/CDM21228_Setup.zip 131 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/ftdi-embedded-hal.svg)](https://crates.io/crates/ftdi-embedded-hal) 2 | [![docs.rs](https://docs.rs/ftdi-embedded-hal/badge.svg)](https://docs.rs/ftdi-embedded-hal/) 3 | [![Build Status](https://github.com/ftdi-rs/ftdi-embedded-hal/workflows/CI/badge.svg)](https://github.com/ftdi-rs/ftdi-embedded-hal/actions) 4 | 5 | # {{crate}} 6 | 7 | {{readme}} 8 | -------------------------------------------------------------------------------- /examples/at24c04.rs: -------------------------------------------------------------------------------- 1 | use eeprom24x::Eeprom24x; 2 | use eeprom24x::SlaveAddr; 3 | use ftdi_embedded_hal as hal; 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | 7 | fn main() { 8 | cfg_if::cfg_if! { 9 | if #[cfg(feature = "ftdi")] { 10 | let device = ftdi::find_by_vid_pid(0x0403, 0x6014) 11 | .interface(ftdi::Interface::A) 12 | .open() 13 | .unwrap(); 14 | } else if #[cfg(feature = "libftd2xx")] { 15 | let device = libftd2xx::Ft232h::with_description("Single RS232-HS").unwrap(); 16 | } else { 17 | compile_error!("one of features 'ftdi' and 'libftd2xx' shall be enabled"); 18 | } 19 | } 20 | 21 | let hal = hal::FtHal::init_freq(device, 400_000).unwrap(); 22 | let i2c = hal.i2c().unwrap(); 23 | let mut eeprom = Eeprom24x::new_24x04(i2c, SlaveAddr::default()); 24 | let delay = Duration::from_millis(5); 25 | 26 | // check high memory addresses: 1 bit passed as a part of i2c addr 27 | let addrs1: [u32; 4] = [0x100, 0x10F, 0x1F0, 0x1EE]; 28 | let byte_w1 = 0xe5; 29 | let addrs2: [u32; 4] = [0x00, 0x0F, 0xF0, 0xEE]; 30 | let byte_w2 = 0xaa; 31 | 32 | // write bytes 33 | 34 | for addr in addrs1.iter() { 35 | println!("Write byte {:#x} to address {:#x}", byte_w1, *addr); 36 | eeprom.write_byte(*addr, byte_w1).unwrap(); 37 | sleep(delay); 38 | } 39 | 40 | for addr in addrs2.iter() { 41 | println!("Write byte {:#x} to address {:#x}", byte_w2, *addr); 42 | eeprom.write_byte(*addr, byte_w2).unwrap(); 43 | sleep(delay); 44 | } 45 | 46 | // read bytes and check 47 | 48 | for addr in addrs1.iter() { 49 | let byte_r = eeprom.read_byte(*addr).unwrap(); 50 | println!("Read byte from address {:#x}: {:#x}", *addr, byte_r); 51 | assert_eq!(byte_w1, byte_r); 52 | sleep(delay); 53 | } 54 | 55 | for addr in addrs2.iter() { 56 | let byte_r = eeprom.read_byte(*addr).unwrap(); 57 | println!("Read byte from address {:#x}: {:#x}", *addr, byte_r); 58 | assert_eq!(byte_w2, byte_r); 59 | sleep(delay); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/blink.rs: -------------------------------------------------------------------------------- 1 | use eh0::digital::v2::OutputPin; 2 | use ftdi_embedded_hal as hal; 3 | use std::{thread::sleep, time::Duration}; 4 | 5 | const NUM_BLINK: usize = 10; 6 | const SLEEP_DURATION: Duration = Duration::from_millis(500); 7 | 8 | fn main() { 9 | cfg_if::cfg_if! { 10 | if #[cfg(feature = "ftdi")] { 11 | let device = ftdi::find_by_vid_pid(0x0403, 0x6014) 12 | .interface(ftdi::Interface::A) 13 | .open() 14 | .unwrap(); 15 | } else if #[cfg(feature = "libftd2xx")] { 16 | let device: libftd2xx::Ft232h = libftd2xx::Ftdi::new().unwrap().try_into().unwrap(); 17 | } else { 18 | compile_error!("one of features 'ftdi' and 'libftd2xx' shall be enabled"); 19 | } 20 | } 21 | 22 | let hal = hal::FtHal::init_default(device).unwrap(); 23 | let mut output_pin = hal.ad3().unwrap(); 24 | 25 | println!("Starting blinky example"); 26 | for n in 0..NUM_BLINK { 27 | output_pin.set_high().expect("failed to set GPIO"); 28 | sleep(SLEEP_DURATION); 29 | output_pin.set_low().expect("failed to set GPIO"); 30 | sleep(SLEEP_DURATION); 31 | println!("Blinked {}/{} times", n + 1, NUM_BLINK); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/bme280.rs: -------------------------------------------------------------------------------- 1 | //! This example reads the chip ID from a Bosch BME280. 2 | //! 3 | //! The hardware for this example can be purchased from adafruit: 4 | //! 5 | //! * https://www.adafruit.com/product/2264 6 | //! * https://www.adafruit.com/product/2652 7 | //! * https://www.adafruit.com/product/4399 8 | //! * https://www.adafruit.com/product/4472 9 | 10 | use eh0::blocking::i2c::WriteRead; 11 | use eh1::i2c::I2c; 12 | use ftdi_embedded_hal as hal; 13 | 14 | fn main() { 15 | cfg_if::cfg_if! { 16 | if #[cfg(feature = "ftdi")] { 17 | let device = ftdi::find_by_vid_pid(0x0403, 0x6014) 18 | .interface(ftdi::Interface::A) 19 | .open() 20 | .unwrap(); 21 | } else if #[cfg(feature = "libftd2xx")] { 22 | let device: libftd2xx::Ft232h = libftd2xx::Ftdi::new().unwrap().try_into().unwrap(); 23 | } else { 24 | compile_error!("one of features 'ftdi' and 'libftd2xx' shall be enabled"); 25 | } 26 | } 27 | 28 | let hal = hal::FtHal::init_default(device).unwrap(); 29 | let mut i2c = hal.i2c().unwrap(); 30 | 31 | // ID register is constant 32 | const BME280_CHIP_ID: u8 = 0x60; 33 | 34 | let mut buf: [u8; 1] = [0]; 35 | const BME280_ADDR: u8 = 0b1110111; 36 | const BME280_CHIP_ID_ADDR: u8 = 0xD0; 37 | 38 | println!("Reading chip ID from BME280 with embedded-hal v0.2"); 39 | WriteRead::write_read(&mut i2c, BME280_ADDR, &[BME280_CHIP_ID_ADDR], &mut buf) 40 | .expect("Failed to read from BME280"); 41 | assert_eq!(buf[0], BME280_CHIP_ID); 42 | println!("Chip ID ok from embedded-hal v0.2"); 43 | 44 | println!("Reading chip ID from BME280 with embedded-hal v1"); 45 | I2c::write_read(&mut i2c, BME280_ADDR, &[BME280_CHIP_ID_ADDR], &mut buf) 46 | .expect("Failed to read from BME280"); 47 | assert_eq!(buf[0], BME280_CHIP_ID); 48 | println!("Chip ID ok from embedded-hal v1"); 49 | } 50 | -------------------------------------------------------------------------------- /examples/i2cdetect.rs: -------------------------------------------------------------------------------- 1 | use eh1::i2c::I2c; 2 | use ftdi_embedded_hal as hal; 3 | 4 | fn main() { 5 | cfg_if::cfg_if! { 6 | if #[cfg(feature = "ftdi")] { 7 | let device = ftdi::find_by_vid_pid(0x0403, 0x6014) 8 | .interface(ftdi::Interface::A) 9 | .open() 10 | .unwrap(); 11 | } else if #[cfg(feature = "libftd2xx")] { 12 | let device: libftd2xx::Ft232h = libftd2xx::Ftdi::new().unwrap().try_into().unwrap(); 13 | } else { 14 | compile_error!("Enable feature 'ftdi' or 'libftd2xx'"); 15 | } 16 | } 17 | 18 | let hal = hal::FtHal::init_default(device).unwrap(); 19 | let mut i2c = hal.i2c().unwrap(); 20 | 21 | println!(" 0 1 2 3 4 5 6 7 8 9 a b c d e f"); 22 | for row in 0..8 { 23 | print!("{:02x}: ", row << 4); 24 | for col in 0..16 { 25 | let addr = (row << 4) | col; 26 | 27 | // For addresses i2cdetect typically skips: 28 | // 0x00..=0x07 and 0x78..=0x7F 29 | if addr < 0x08 || addr > 0x77 { 30 | print!(" "); 31 | continue; 32 | } 33 | 34 | let mut buf = [0u8; 1]; 35 | if I2c::write_read(&mut i2c, addr, &[], &mut buf).is_ok() { 36 | print!("{:02x} ", addr); 37 | } else { 38 | print!("-- "); 39 | } 40 | } 41 | println!(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/input.rs: -------------------------------------------------------------------------------- 1 | use eh0::digital::v2::InputPin; 2 | use ftdi_embedded_hal as hal; 3 | use std::{thread::sleep, time::Duration}; 4 | 5 | const SLEEP_DURATION: Duration = Duration::from_millis(500); 6 | 7 | fn main() { 8 | cfg_if::cfg_if! { 9 | if #[cfg(feature = "ftdi")] { 10 | let device = ftdi::find_by_vid_pid(0x0403, 0x6014) 11 | .interface(ftdi::Interface::A) 12 | .open() 13 | .unwrap(); 14 | } else if #[cfg(feature = "libftd2xx")] { 15 | let device: libftd2xx::Ft232h = libftd2xx::Ftdi::new().unwrap().try_into().unwrap(); 16 | } else { 17 | compile_error!("one of features 'ftdi' and 'libftd2xx' shall be enabled"); 18 | } 19 | } 20 | 21 | let hal = hal::FtHal::init_default(device).unwrap(); 22 | 23 | let input = hal.adi6().unwrap(); 24 | 25 | println!("Pin readings:"); 26 | loop { 27 | println!("AD6 = {}", input.is_high().expect("gpio read failure")); 28 | sleep(SLEEP_DURATION); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/lm75.rs: -------------------------------------------------------------------------------- 1 | use ftdi_embedded_hal as hal; 2 | use lm75::{Address, Lm75}; 3 | use std::thread::sleep; 4 | use std::time::Duration; 5 | 6 | fn main() { 7 | cfg_if::cfg_if! { 8 | if #[cfg(feature = "ftdi")] { 9 | let device = ftdi::find_by_vid_pid(0x0403, 0x6014) 10 | .interface(ftdi::Interface::A) 11 | .open() 12 | .unwrap(); 13 | } else if #[cfg(feature = "libftd2xx")] { 14 | let device = libftd2xx::Ft232h::with_description("Single RS232-HS").unwrap(); 15 | } else { 16 | compile_error!("one of features 'ftdi' and 'libftd2xx' shall be enabled"); 17 | } 18 | } 19 | 20 | let hal = hal::FtHal::init_freq(device, 400_000).unwrap(); 21 | let i2c = hal.i2c().unwrap(); 22 | let mut sensor = Lm75::new(i2c, Address::default()); 23 | let delay = Duration::from_secs(1); 24 | 25 | loop { 26 | let temperature = sensor.read_temperature().unwrap(); 27 | println!("Temperature: {temperature}"); 28 | sleep(delay); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/spi-eh1-loopback.rs: -------------------------------------------------------------------------------- 1 | //! This is a loopback example for embedded-hal-1 traits: 2 | //! * SpiBus 3 | //! * SpiDevice (TODO) 4 | //! 5 | //! Pin setup: 6 | //! * D1 <-> D2 (connect SDO with SDI) 7 | //! 8 | //! Leave other pins unconnected. 9 | use ftdi_embedded_hal as hal; 10 | use std::thread::sleep; 11 | use std::time::Duration; 12 | 13 | fn main() { 14 | cfg_if::cfg_if! { 15 | if #[cfg(feature = "ftdi")] { 16 | let device = ftdi::find_by_vid_pid(0x0403, 0x6014) 17 | .interface(ftdi::Interface::A) 18 | .open() 19 | .unwrap(); 20 | } else if #[cfg(feature = "libftd2xx")] { 21 | let device = libftd2xx::Ft232h::with_description("Single RS232-HS").unwrap(); 22 | } else { 23 | compile_error!("one of features 'ftdi' and 'libftd2xx' shall be enabled"); 24 | } 25 | } 26 | 27 | let hal = hal::FtHal::init_freq(device, 1_000_000).unwrap(); 28 | let mut spi = hal.spi().unwrap(); 29 | 30 | let delay = Duration::from_millis(250); 31 | 32 | { 33 | use hal::eh1::spi::SpiBus; 34 | 35 | println!("=== SpiBus example with embedded-hal-1 traits ==="); 36 | 37 | // --- Symmetric transfer (Read as much as we write) --- 38 | print!("Starting symmetric transfer..."); 39 | let write = [0xde, 0xad, 0xbe, 0xef]; 40 | let mut read: [u8; 4] = [0x00u8; 4]; 41 | SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Symmetric transfer failed"); 42 | assert_eq!(write, read); 43 | println!(" SUCCESS"); 44 | 45 | // --- Asymmetric transfer (Read more than we write) --- 46 | print!("Starting asymetric transfer (read > write)..."); 47 | let mut read: [u8; 4] = [0x00; 4]; 48 | 49 | SpiBus::transfer(&mut spi, &mut read[0..2], &write[..]) 50 | .expect("Asymmetric transfer failed"); 51 | assert_eq!(write[0], read[0]); 52 | assert_eq!(read[2], 0x00u8); 53 | println!(" SUCCESS"); 54 | sleep(delay); 55 | 56 | // --- Symmetric transfer with huge buffer --- 57 | // Only your RAM is the limit! 58 | print!("Starting huge transfer..."); 59 | let mut write = [0x55u8; 4096]; 60 | for (idx, byte) in write.iter_mut().enumerate() { 61 | *byte = idx as u8; 62 | } 63 | let mut read = [0x00u8; 4096]; 64 | sleep(delay); 65 | 66 | SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Huge transfer failed"); 67 | assert_eq!(write, read); 68 | println!(" SUCCESS"); 69 | sleep(delay); 70 | 71 | // --- Symmetric transfer with huge buffer in-place (No additional allocation 72 | // needed) --- 73 | print!("Starting huge transfer (in-place)..."); 74 | let mut write = [0x55u8; 4096]; 75 | for (idx, byte) in write.iter_mut().enumerate() { 76 | *byte = idx as u8; 77 | } 78 | 79 | SpiBus::transfer_in_place(&mut spi, &mut write[..]).expect("Huge transfer failed"); 80 | for (idx, byte) in write.iter().enumerate() { 81 | assert_eq!(*byte, idx as u8); 82 | } 83 | println!(" SUCCESS"); 84 | sleep(delay); 85 | } 86 | 87 | // TODO: eh1 SpiDevice 88 | } 89 | -------------------------------------------------------------------------------- /examples/spi-flash.rs: -------------------------------------------------------------------------------- 1 | use ftdi_embedded_hal as hal; 2 | use spi_memory::prelude::*; 3 | use spi_memory::series25::Flash; 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | 7 | const LINE: u32 = 0x10; 8 | 9 | fn main() { 10 | cfg_if::cfg_if! { 11 | if #[cfg(feature = "ftdi")] { 12 | let device = ftdi::find_by_vid_pid(0x0403, 0x6014) 13 | .interface(ftdi::Interface::A) 14 | .open() 15 | .unwrap(); 16 | let data: [u8; 8] = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]; 17 | } else if #[cfg(feature = "libftd2xx")] { 18 | let device = libftd2xx::Ft232h::with_description("Single RS232-HS").unwrap(); 19 | let data: [u8; 8] = [0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70]; 20 | } else { 21 | compile_error!("one of features 'ftdi' and 'libftd2xx' shall be enabled"); 22 | } 23 | } 24 | 25 | let hal = hal::FtHal::init_freq(device, 1_000_000).unwrap(); 26 | let spi = hal.spi().unwrap(); 27 | let ncs = hal.ad3().unwrap(); 28 | let delay = Duration::from_millis(10); 29 | 30 | let mut flash = Flash::init(spi, ncs).unwrap(); 31 | let id = flash.read_jedec_id().unwrap(); 32 | println!("JEDEC ID: {id:?}"); 33 | 34 | let addrs: [u32; 5] = [0, LINE, 2 * LINE, 3 * LINE, 4 * LINE]; 35 | let zero: [u8; 8] = [0; 8]; 36 | let mut bytes_w: [u8; 8] = [0; 8]; 37 | let mut bytes_r: [u8; 8] = [0; 8]; 38 | 39 | println!("Write to flash..."); 40 | for addr in addrs.iter() { 41 | bytes_w.copy_from_slice(&data); 42 | println!("Write bytes {:02x?} to address {:02x}", bytes_w, *addr); 43 | flash.write_bytes(*addr, &mut bytes_w).unwrap(); 44 | sleep(delay); 45 | } 46 | 47 | println!("Read from flash and check..."); 48 | for addr in addrs.iter() { 49 | bytes_r.copy_from_slice(&zero); 50 | flash.read(*addr, &mut bytes_r).unwrap(); 51 | println!("Read byte from address {:02x}: {:02x?}", *addr, bytes_r); 52 | assert_eq!(data, bytes_r); 53 | sleep(delay); 54 | } 55 | 56 | let mut buf = [0; LINE as usize]; 57 | let mut addr = 0; 58 | println!("Dump flash..."); 59 | while addr < 0x100 { 60 | flash.read(addr, &mut buf).unwrap(); 61 | println!("{addr:02x}: {buf:02x?}"); 62 | addr += LINE; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/ws2812.rs: -------------------------------------------------------------------------------- 1 | use eh0::blocking::spi::Write; 2 | use ftdi_embedded_hal as hal; 3 | use std::thread::sleep; 4 | use std::time::Duration; 5 | 6 | fn main() { 7 | cfg_if::cfg_if! { 8 | if #[cfg(feature = "ftdi")] { 9 | let device = ftdi::find_by_vid_pid(0x0403, 0x6010) 10 | .interface(ftdi::Interface::A) 11 | .open() 12 | .unwrap(); 13 | } else if #[cfg(feature = "libftd2xx")] { 14 | let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A").unwrap(); 15 | } else { 16 | compile_error!("one of features 'ftdi' and 'libftd2xx' shall be enabled"); 17 | } 18 | } 19 | 20 | let hal = hal::FtHal::init_freq(device, 3_000_000).unwrap(); 21 | let mut spi = hal.spi().unwrap(); 22 | 23 | // spi sequence for ws2812 color value 0x10 24 | let b1 = [0x92, 0x69, 0x24]; 25 | 26 | // spi sequence for ws2812 color value 0x00 27 | let b0 = [0x92, 0x49, 0x24]; 28 | 29 | // spi sequences for single led of specific color 30 | let g = [b1, b0, b0]; 31 | let r = [b0, b1, b0]; 32 | let b = [b0, b0, b1]; 33 | let x = [b0, b0, b0]; 34 | 35 | // initial pattern 36 | let mut pattern = vec![r, r, g, g, x, x, b, b]; 37 | 38 | println!("ready to go..."); 39 | 40 | loop { 41 | println!("next pattern..."); 42 | let stream = pattern 43 | .clone() 44 | .into_iter() 45 | .flatten() 46 | .flatten() 47 | .collect::>(); 48 | 49 | spi.write(stream.as_slice()).unwrap(); 50 | sleep(Duration::from_millis(400)); 51 | // rotate pattern 52 | pattern.rotate_right(1); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | -------------------------------------------------------------------------------- /src/delay.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the [`eh0::blocking::delay`] and [`eh1::delay`] 2 | //! traits. 3 | 4 | /// Delay structure. 5 | /// 6 | /// This is an empty structure that forwards delays to [`std::thread::sleep`]. 7 | /// 8 | /// [`sleep`]: std::thread::sleep 9 | #[derive(Debug, Clone, Copy)] 10 | pub struct Delay { 11 | _0: (), 12 | } 13 | 14 | impl Delay { 15 | /// Create a new delay structure. 16 | /// 17 | /// # Example 18 | /// 19 | /// ``` 20 | /// use ftdi_embedded_hal::Delay; 21 | /// 22 | /// let mut my_delay: Delay = Delay::new(); 23 | /// ``` 24 | pub const fn new() -> Delay { 25 | Delay { _0: () } 26 | } 27 | } 28 | 29 | impl Default for Delay { 30 | fn default() -> Self { 31 | Delay::new() 32 | } 33 | } 34 | 35 | impl eh1::delay::DelayNs for Delay { 36 | fn delay_ns(&mut self, ns: u32) { 37 | std::thread::sleep(std::time::Duration::from_nanos(ns.into())) 38 | } 39 | 40 | fn delay_us(&mut self, us: u32) { 41 | std::thread::sleep(std::time::Duration::from_micros(us.into())) 42 | } 43 | 44 | fn delay_ms(&mut self, ms: u32) { 45 | std::thread::sleep(std::time::Duration::from_millis(ms.into())) 46 | } 47 | } 48 | 49 | macro_rules! impl_eh0_delay_for { 50 | ($UXX:ty) => { 51 | impl eh0::blocking::delay::DelayMs<$UXX> for Delay { 52 | fn delay_ms(&mut self, ms: $UXX) { 53 | std::thread::sleep(std::time::Duration::from_millis(ms.into())) 54 | } 55 | } 56 | 57 | impl eh0::blocking::delay::DelayUs<$UXX> for Delay { 58 | fn delay_us(&mut self, us: $UXX) { 59 | std::thread::sleep(std::time::Duration::from_micros(us.into())) 60 | } 61 | } 62 | }; 63 | } 64 | 65 | impl_eh0_delay_for!(u8); 66 | impl_eh0_delay_for!(u16); 67 | impl_eh0_delay_for!(u32); 68 | impl_eh0_delay_for!(u64); 69 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use eh1::i2c::NoAcknowledgeSource; 2 | use std::{fmt, io}; 3 | 4 | /// Error type. 5 | #[derive(Debug)] 6 | pub enum Error { 7 | /// ftdi-embedded-hal implementation specific error. 8 | Hal(ErrorKind), 9 | /// IO error. 10 | Io(io::Error), 11 | /// Backend specific error. 12 | Backend(E), 13 | } 14 | 15 | /// Internal HAL errors 16 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 17 | #[non_exhaustive] 18 | pub enum ErrorKind { 19 | /// No ACK from the I2C slave 20 | I2cNoAck(NoAcknowledgeSource), 21 | } 22 | 23 | impl ErrorKind { 24 | fn as_str(&self) -> &str { 25 | match *self { 26 | ErrorKind::I2cNoAck(NoAcknowledgeSource::Address) => { 27 | "No ACK from slave during addressing" 28 | } 29 | ErrorKind::I2cNoAck(NoAcknowledgeSource::Data) => { 30 | "No ACK from slave during data transfer" 31 | } 32 | ErrorKind::I2cNoAck(NoAcknowledgeSource::Unknown) => "No ACK from slave", 33 | } 34 | } 35 | } 36 | 37 | impl eh1::i2c::Error for Error { 38 | fn kind(&self) -> eh1::i2c::ErrorKind { 39 | match self { 40 | Self::Hal(ErrorKind::I2cNoAck(src)) => eh1::i2c::ErrorKind::NoAcknowledge(*src), 41 | _ => eh1::i2c::ErrorKind::Other, 42 | } 43 | } 44 | } 45 | 46 | impl eh1::digital::Error for Error { 47 | fn kind(&self) -> eh1::digital::ErrorKind { 48 | eh1::digital::ErrorKind::Other 49 | } 50 | } 51 | 52 | impl fmt::Display for Error { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | match self { 55 | Error::Io(e) => e.fmt(f), 56 | Error::Backend(e) => fmt::Display::fmt(&e, f), 57 | Error::Hal(e) => write!(f, "A regular error occurred {:?}", e.as_str()), 58 | } 59 | } 60 | } 61 | 62 | impl std::error::Error for Error { 63 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 64 | match self { 65 | Error::Io(e) => e.source(), 66 | Error::Backend(e) => e.source(), 67 | Error::Hal(_) => None, 68 | } 69 | } 70 | } 71 | 72 | impl From for Error { 73 | fn from(e: io::Error) -> Self { 74 | Error::Io(e) 75 | } 76 | } 77 | 78 | #[cfg(feature = "ftdi")] 79 | impl From for Error { 80 | fn from(e: ftdi::Error) -> Self { 81 | Error::Backend(e) 82 | } 83 | } 84 | 85 | #[cfg(feature = "libftd2xx")] 86 | impl From for Error { 87 | fn from(e: libftd2xx::TimeoutError) -> Self { 88 | Error::Backend(e) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/gpio.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::{FtInner, PinUse}; 3 | use ftdi_mpsse::{MpsseCmdBuilder, MpsseCmdExecutor}; 4 | use std::sync::{Arc, Mutex}; 5 | 6 | /// Pin number 7 | #[derive(Debug, Copy, Clone)] 8 | pub(crate) enum Pin { 9 | Lower(u8), 10 | Upper(u8), 11 | } 12 | 13 | /// FTDI output pin. 14 | /// 15 | /// This is created by calling [`FtHal::ad0`] - [`FtHal::ad7`]. 16 | /// 17 | /// [`FtHal::ad0`]: crate::FtHal::ad0 18 | /// [`FtHal::ad7`]: crate::FtHal::ad7 19 | #[derive(Debug)] 20 | pub struct OutputPin { 21 | /// Parent FTDI device. 22 | mtx: Arc>>, 23 | /// GPIO pin index. 0-7 for the FT232H. 24 | pin: Pin, 25 | } 26 | 27 | impl OutputPin 28 | where 29 | Device: MpsseCmdExecutor, 30 | E: std::error::Error, 31 | Error: From, 32 | { 33 | pub(crate) fn new( 34 | mtx: Arc>>, 35 | pin: Pin, 36 | ) -> Result, Error> { 37 | { 38 | let mut lock = mtx.lock().expect("Failed to aquire FTDI mutex"); 39 | 40 | lock.allocate_pin_any(pin, PinUse::Output); 41 | 42 | let (byte, idx) = match pin { 43 | Pin::Lower(idx) => (&mut lock.lower, idx), 44 | Pin::Upper(idx) => (&mut lock.upper, idx), 45 | }; 46 | byte.direction |= 1 << idx; 47 | let cmd = MpsseCmdBuilder::new(); 48 | let cmd = match pin { 49 | Pin::Lower(_) => cmd.set_gpio_lower(byte.value, byte.direction), 50 | Pin::Upper(_) => cmd.set_gpio_upper(byte.value, byte.direction), 51 | } 52 | .send_immediate(); 53 | lock.ft.send(cmd.as_slice())?; 54 | } 55 | Ok(OutputPin { mtx, pin }) 56 | } 57 | 58 | pub(crate) fn set(&self, state: bool) -> Result<(), Error> { 59 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 60 | 61 | let byte = match self.pin { 62 | Pin::Lower(_) => &mut lock.lower, 63 | Pin::Upper(_) => &mut lock.upper, 64 | }; 65 | 66 | if state { 67 | byte.value |= self.mask(); 68 | } else { 69 | byte.value &= !self.mask(); 70 | }; 71 | 72 | let cmd = MpsseCmdBuilder::new(); 73 | let cmd = match self.pin { 74 | Pin::Lower(_) => cmd.set_gpio_lower(byte.value, byte.direction), 75 | Pin::Upper(_) => cmd.set_gpio_upper(byte.value, byte.direction), 76 | } 77 | .send_immediate(); 78 | lock.ft.send(cmd.as_slice())?; 79 | 80 | Ok(()) 81 | } 82 | } 83 | 84 | impl OutputPin { 85 | /// Convert the GPIO pin index to a pin mask 86 | pub(crate) fn mask(&self) -> u8 { 87 | let idx = match self.pin { 88 | Pin::Lower(idx) => idx, 89 | Pin::Upper(idx) => idx, 90 | }; 91 | 1 << idx 92 | } 93 | } 94 | 95 | impl eh1::digital::ErrorType for OutputPin 96 | where 97 | Device: MpsseCmdExecutor, 98 | E: std::error::Error, 99 | Error: From, 100 | { 101 | type Error = Error; 102 | } 103 | 104 | impl eh1::digital::OutputPin for OutputPin 105 | where 106 | Device: MpsseCmdExecutor, 107 | E: std::error::Error, 108 | Error: From, 109 | { 110 | fn set_low(&mut self) -> Result<(), Error> { 111 | self.set(false) 112 | } 113 | 114 | fn set_high(&mut self) -> Result<(), Error> { 115 | self.set(true) 116 | } 117 | } 118 | 119 | impl eh0::digital::v2::OutputPin for OutputPin 120 | where 121 | Device: MpsseCmdExecutor, 122 | E: std::error::Error, 123 | Error: From, 124 | { 125 | type Error = Error; 126 | 127 | fn set_low(&mut self) -> Result<(), Error> { 128 | self.set(false) 129 | } 130 | 131 | fn set_high(&mut self) -> Result<(), Error> { 132 | self.set(true) 133 | } 134 | } 135 | 136 | /// FTDI input pin. 137 | /// 138 | /// This is created by calling [`FtHal::adi0`] - [`FtHal::adi7`]. 139 | /// 140 | /// [`FtHal::adi0`]: crate::FtHal::adi0 141 | /// [`FtHal::adi7`]: crate::FtHal::adi7 142 | #[derive(Debug)] 143 | pub struct InputPin { 144 | /// Parent FTDI device. 145 | mtx: Arc>>, 146 | /// GPIO pin index. 0-7 for the FT232H. 147 | pin: Pin, 148 | } 149 | 150 | impl InputPin 151 | where 152 | Device: MpsseCmdExecutor, 153 | E: std::error::Error, 154 | Error: From, 155 | { 156 | pub(crate) fn new( 157 | mtx: Arc>>, 158 | pin: Pin, 159 | ) -> Result, Error> { 160 | { 161 | let mut lock = mtx.lock().expect("Failed to aquire FTDI mutex"); 162 | 163 | lock.allocate_pin_any(pin, PinUse::Input); 164 | 165 | let (byte, idx) = match pin { 166 | Pin::Lower(idx) => (&mut lock.lower, idx), 167 | Pin::Upper(idx) => (&mut lock.upper, idx), 168 | }; 169 | byte.direction &= !(1 << idx); 170 | let cmd = MpsseCmdBuilder::new(); 171 | let cmd = match pin { 172 | Pin::Lower(_) => cmd.set_gpio_lower(byte.value, byte.direction), 173 | Pin::Upper(_) => cmd.set_gpio_upper(byte.value, byte.direction), 174 | } 175 | .send_immediate(); 176 | lock.ft.send(cmd.as_slice())?; 177 | } 178 | Ok(InputPin { mtx, pin }) 179 | } 180 | 181 | pub(crate) fn get(&self) -> Result> { 182 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 183 | 184 | let mut buffer = [0u8; 1]; 185 | let cmd = MpsseCmdBuilder::new(); 186 | let cmd = match self.pin { 187 | Pin::Lower(_) => cmd.gpio_lower(), 188 | Pin::Upper(_) => cmd.gpio_upper(), 189 | } 190 | .send_immediate(); 191 | lock.ft.send(cmd.as_slice())?; 192 | lock.ft.recv(&mut buffer)?; 193 | 194 | Ok((buffer[0] & self.mask()) != 0) 195 | } 196 | } 197 | 198 | impl InputPin { 199 | /// Convert the GPIO pin index to a pin mask 200 | pub(crate) fn mask(&self) -> u8 { 201 | let idx = match self.pin { 202 | Pin::Lower(idx) => idx, 203 | Pin::Upper(idx) => idx, 204 | }; 205 | 1 << idx 206 | } 207 | } 208 | 209 | impl eh1::digital::ErrorType for InputPin 210 | where 211 | Device: MpsseCmdExecutor, 212 | E: std::error::Error, 213 | Error: From, 214 | { 215 | type Error = Error; 216 | } 217 | 218 | impl eh1::digital::InputPin for InputPin 219 | where 220 | Device: MpsseCmdExecutor, 221 | E: std::error::Error, 222 | Error: From, 223 | { 224 | fn is_high(&mut self) -> Result { 225 | self.get() 226 | } 227 | 228 | fn is_low(&mut self) -> Result { 229 | self.get().map(|res| !res) 230 | } 231 | } 232 | 233 | impl eh0::digital::v2::InputPin for InputPin 234 | where 235 | Device: MpsseCmdExecutor, 236 | E: std::error::Error, 237 | Error: From, 238 | { 239 | type Error = Error; 240 | 241 | fn is_high(&self) -> Result { 242 | self.get() 243 | } 244 | 245 | fn is_low(&self) -> Result { 246 | self.get().map(|res| !res) 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/i2c.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::error::ErrorKind::I2cNoAck; 3 | use crate::{FtInner, PinUse}; 4 | use eh1::i2c::{NoAcknowledgeSource, Operation, SevenBitAddress}; 5 | use ftdi_mpsse::{ClockBitsIn, ClockBitsOut, MpsseCmdBuilder, MpsseCmdExecutor}; 6 | use std::sync::{Arc, Mutex}; 7 | 8 | /// SCL bitmask 9 | const SCL: u8 = 1 << 0; 10 | /// SDA bitmask 11 | const SDA: u8 = 1 << 1; 12 | 13 | const BITS_IN: ClockBitsIn = ClockBitsIn::MsbPos; 14 | const BITS_OUT: ClockBitsOut = ClockBitsOut::MsbNeg; 15 | 16 | /// FTDI I2C interface. 17 | /// 18 | /// This is created by calling [`FtHal::i2c`]. 19 | /// 20 | /// [`FtHal::i2c`]: crate::FtHal::i2c 21 | #[derive(Debug)] 22 | pub struct I2c { 23 | /// Parent FTDI device. 24 | mtx: Arc>>, 25 | /// Length of the start, repeated start, and stop conditions. 26 | /// 27 | /// The units for these are dimensionless number of MPSSE commands. 28 | /// More MPSSE commands roughly correlates to more time. 29 | start_stop_cmds: u8, 30 | /// Send I2C commands faster. 31 | fast: bool, 32 | } 33 | 34 | impl I2c 35 | where 36 | Device: MpsseCmdExecutor, 37 | E: std::error::Error, 38 | Error: From, 39 | { 40 | pub(crate) fn new(mtx: Arc>>) -> Result, Error> { 41 | { 42 | let mut lock = mtx.lock().expect("Failed to aquire FTDI mutex"); 43 | 44 | lock.allocate_pin(0, PinUse::I2c); 45 | lock.allocate_pin(1, PinUse::I2c); 46 | lock.allocate_pin(2, PinUse::I2c); 47 | 48 | // clear direction and value of first 3 pins 49 | 50 | lock.direction &= !0x07; 51 | lock.value &= !0x07; 52 | // AD0: SCL 53 | // AD1: SDA (master out) 54 | // AD2: SDA (master in) 55 | // pins are set as input (tri-stated) in idle mode 56 | 57 | // set GPIO pins to new state 58 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 59 | .set_gpio_lower(lock.value, lock.direction) 60 | .enable_3phase_data_clocking() 61 | .send_immediate(); 62 | lock.ft.send(cmd.as_slice())?; 63 | } 64 | 65 | Ok(I2c { 66 | mtx, 67 | start_stop_cmds: 3, 68 | fast: false, 69 | }) 70 | } 71 | 72 | /// Set the length of start and stop conditions. 73 | /// 74 | /// This is an advanced feature that most people will not need to touch. 75 | /// I2C start and stop conditions are generated with a number of MPSSE 76 | /// commands. This sets the number of MPSSE command generated for each 77 | /// stop and start condition. An increase in the number of MPSSE commands 78 | /// roughtly correlates to an increase in the duration. 79 | /// 80 | /// # Example 81 | /// 82 | /// ```no_run 83 | /// use ftdi_embedded_hal as hal; 84 | /// 85 | /// # #[cfg(feature = "libftd2xx")] 86 | /// # { 87 | /// let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A")?; 88 | /// let hal = hal::FtHal::init_freq(device, 3_000_000)?; 89 | /// let mut i2c = hal.i2c()?; 90 | /// i2c.set_stop_start_len(10); 91 | /// # } 92 | /// # Ok::<(), std::boxed::Box>(()) 93 | /// ``` 94 | pub fn set_stop_start_len(&mut self, start_stop_cmds: u8) { 95 | self.start_stop_cmds = start_stop_cmds 96 | } 97 | 98 | /// Enable faster I2C transactions by sending commands in a single write. 99 | /// 100 | /// This is disabled by default, and currently has no effect when using 101 | /// version 1 of the `embedded-hal` traits. 102 | /// 103 | /// Normally the I2C methods will send commands with a delay after each 104 | /// slave ACK to read from the USB device. 105 | /// Enabling this will send I2C commands without a delay, but slave ACKs 106 | /// will only be checked at the end of each call to `read`, `write`, or 107 | /// `write_read`. 108 | /// 109 | /// Additionally this changes the type of errors that can occur: 110 | /// 111 | /// * enabled: NAK errors will be reported as 112 | /// `NoAcknowledgeSource::Unknown`. 113 | /// * disabled: NAK errors will be reported as 114 | /// `NoAcknowledgeSource::Address` or `NoAcknowledgeSource::Data`. 115 | /// 116 | /// # Example 117 | /// 118 | /// ```no_run 119 | /// use ftdi_embedded_hal as hal; 120 | /// 121 | /// # #[cfg(feature = "ftdi")] 122 | /// # { 123 | /// let device = ftdi::find_by_vid_pid(0x0403, 0x6014) 124 | /// .interface(ftdi::Interface::A) 125 | /// .open()?; 126 | /// 127 | /// let hal = hal::FtHal::init_freq(device, 3_000_000)?; 128 | /// let mut i2c = hal.i2c()?; 129 | /// i2c.set_fast(true); 130 | /// # } 131 | /// # Ok::<(), std::boxed::Box>(()) 132 | /// ``` 133 | pub fn set_fast(&mut self, fast: bool) { 134 | self.fast = fast 135 | } 136 | 137 | fn read_fast(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { 138 | assert!(!buffer.is_empty(), "buffer must be a non-empty slice"); 139 | 140 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 141 | 142 | // ST 143 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 144 | for _ in 0..self.start_stop_cmds { 145 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 146 | } 147 | for _ in 0..self.start_stop_cmds { 148 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 149 | } 150 | 151 | mpsse_cmd = mpsse_cmd 152 | // SAD+R 153 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 154 | .clock_bits_out(BITS_OUT, (address << 1) | 1, 8) 155 | // SAK 156 | .set_gpio_lower(lock.value, SCL | lock.direction) 157 | .clock_bits_in(BITS_IN, 1); 158 | 159 | for idx in 0..buffer.len() { 160 | // Bn 161 | mpsse_cmd = mpsse_cmd 162 | .set_gpio_lower(lock.value, SCL | lock.direction) 163 | .clock_bits_in(BITS_IN, 8); 164 | if idx == buffer.len() - 1 { 165 | // NMAK 166 | mpsse_cmd = mpsse_cmd 167 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 168 | .clock_bits_out(BITS_OUT, 0x80, 1) 169 | } else { 170 | // MAK 171 | mpsse_cmd = mpsse_cmd 172 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 173 | .clock_bits_out(BITS_OUT, 0x00, 1) 174 | } 175 | } 176 | 177 | // SP 178 | for _ in 0..self.start_stop_cmds { 179 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 180 | } 181 | for _ in 0..self.start_stop_cmds { 182 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 183 | } 184 | for _ in 0..self.start_stop_cmds { 185 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 186 | } 187 | 188 | mpsse_cmd = mpsse_cmd 189 | // Idle 190 | .set_gpio_lower(lock.value, lock.direction) 191 | .send_immediate(); 192 | 193 | lock.ft.send(mpsse_cmd.as_slice())?; 194 | let mut ack_buf: [u8; 1] = [0; 1]; 195 | lock.ft.recv(&mut ack_buf)?; 196 | lock.ft.recv(buffer)?; 197 | 198 | if (ack_buf[0] & 0b1) != 0x00 { 199 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Unknown))); 200 | } 201 | 202 | Ok(()) 203 | } 204 | 205 | fn read_slow(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { 206 | assert!(!buffer.is_empty(), "buffer must be a non-empty slice"); 207 | 208 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 209 | 210 | // ST 211 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 212 | for _ in 0..self.start_stop_cmds { 213 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 214 | } 215 | for _ in 0..self.start_stop_cmds { 216 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 217 | } 218 | 219 | mpsse_cmd = mpsse_cmd 220 | // SAD+R 221 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 222 | .clock_bits_out(BITS_OUT, (address << 1) | 1, 8) 223 | // SAK 224 | .set_gpio_lower(lock.value, SCL | lock.direction) 225 | .clock_bits_in(BITS_IN, 1) 226 | .send_immediate(); 227 | 228 | lock.ft.send(mpsse_cmd.as_slice())?; 229 | let mut ack_buf: [u8; 1] = [0; 1]; 230 | lock.ft.recv(&mut ack_buf)?; 231 | if (ack_buf[0] & 0b1) != 0x00 { 232 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Address))); 233 | } 234 | 235 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 236 | for idx in 0..buffer.len() { 237 | // Bn 238 | mpsse_cmd = mpsse_cmd 239 | .set_gpio_lower(lock.value, SCL | lock.direction) 240 | .clock_bits_in(BITS_IN, 8); 241 | if idx == buffer.len() - 1 { 242 | // NMAK 243 | mpsse_cmd = mpsse_cmd 244 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 245 | .clock_bits_out(BITS_OUT, 0x80, 1) 246 | } else { 247 | // MAK 248 | mpsse_cmd = mpsse_cmd 249 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 250 | .clock_bits_out(BITS_OUT, 0x00, 1) 251 | } 252 | } 253 | 254 | // SP 255 | for _ in 0..self.start_stop_cmds { 256 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 257 | } 258 | for _ in 0..self.start_stop_cmds { 259 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 260 | } 261 | for _ in 0..self.start_stop_cmds { 262 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 263 | } 264 | 265 | mpsse_cmd = mpsse_cmd 266 | // Idle 267 | .set_gpio_lower(lock.value, lock.direction) 268 | .send_immediate(); 269 | 270 | lock.ft.send(mpsse_cmd.as_slice())?; 271 | lock.ft.recv(buffer)?; 272 | 273 | Ok(()) 274 | } 275 | 276 | fn write_fast(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { 277 | assert!(!bytes.is_empty(), "bytes must be a non-empty slice"); 278 | 279 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 280 | 281 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 282 | 283 | // ST 284 | for _ in 0..self.start_stop_cmds { 285 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 286 | } 287 | for _ in 0..self.start_stop_cmds { 288 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 289 | } 290 | 291 | mpsse_cmd = mpsse_cmd 292 | // SAD+W 293 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 294 | .clock_bits_out(BITS_OUT, addr << 1, 8) 295 | // SAK 296 | .set_gpio_lower(lock.value, SCL | lock.direction) 297 | .clock_bits_in(BITS_IN, 1); 298 | 299 | for byte in bytes.iter() { 300 | mpsse_cmd = mpsse_cmd 301 | // Bi 302 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 303 | .clock_bits_out(BITS_OUT, *byte, 8) 304 | // SAK 305 | .set_gpio_lower(lock.value, SCL | lock.direction) 306 | .clock_bits_in(BITS_IN, 1); 307 | } 308 | 309 | // SP 310 | for _ in 0..self.start_stop_cmds { 311 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 312 | } 313 | for _ in 0..self.start_stop_cmds { 314 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 315 | } 316 | for _ in 0..self.start_stop_cmds { 317 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 318 | } 319 | 320 | mpsse_cmd = mpsse_cmd 321 | // Idle 322 | .set_gpio_lower(lock.value, lock.direction) 323 | .send_immediate(); 324 | 325 | lock.ft.send(mpsse_cmd.as_slice())?; 326 | let mut ack_buf: Vec = vec![0; 1 + bytes.len()]; 327 | lock.ft.recv(ack_buf.as_mut_slice())?; 328 | if ack_buf.iter().any(|&ack| (ack & 0b1) != 0x00) { 329 | Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Unknown))) 330 | } else { 331 | Ok(()) 332 | } 333 | } 334 | 335 | fn write_slow(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { 336 | assert!(!bytes.is_empty(), "bytes must be a non-empty slice"); 337 | 338 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 339 | 340 | // ST 341 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 342 | for _ in 0..self.start_stop_cmds { 343 | mpsse_cmd = mpsse_cmd.set_gpio_lower(SCL | SDA | lock.value, SCL | SDA | lock.direction) 344 | } 345 | for _ in 0..self.start_stop_cmds { 346 | mpsse_cmd = mpsse_cmd.set_gpio_lower(SCL | lock.value, SCL | SDA | lock.direction) 347 | } 348 | 349 | mpsse_cmd = mpsse_cmd 350 | // SAD+W 351 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 352 | .clock_bits_out(BITS_OUT, addr << 1, 8) 353 | // SAK 354 | .set_gpio_lower(lock.value, SCL | lock.direction) 355 | .clock_bits_in(BITS_IN, 1) 356 | .send_immediate(); 357 | 358 | lock.ft.send(mpsse_cmd.as_slice())?; 359 | let mut ack_buf: [u8; 1] = [0; 1]; 360 | lock.ft.recv(&mut ack_buf)?; 361 | if (ack_buf[0] & 0b1) != 0x00 { 362 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Address))); 363 | } 364 | 365 | for (idx, byte) in bytes.iter().enumerate() { 366 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 367 | // Bi 368 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 369 | .clock_bits_out(BITS_OUT, *byte, 8) 370 | // SAK 371 | .set_gpio_lower(lock.value, SCL | lock.direction) 372 | .clock_bits_in(BITS_IN, 1); 373 | 374 | // last byte 375 | if idx == bytes.len() - 1 { 376 | // SP 377 | for _ in 0..self.start_stop_cmds { 378 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 379 | } 380 | for _ in 0..self.start_stop_cmds { 381 | mpsse_cmd = 382 | mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 383 | } 384 | for _ in 0..self.start_stop_cmds { 385 | mpsse_cmd = 386 | mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 387 | } 388 | 389 | // Idle 390 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, lock.direction) 391 | } 392 | 393 | mpsse_cmd = mpsse_cmd.send_immediate(); 394 | 395 | lock.ft.send(mpsse_cmd.as_slice())?; 396 | let mut ack_buf: [u8; 1] = [0; 1]; 397 | lock.ft.recv(&mut ack_buf)?; 398 | if (ack_buf[0] & 0b1) != 0x00 { 399 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Data))); 400 | } 401 | } 402 | 403 | Ok(()) 404 | } 405 | 406 | fn write_read_fast( 407 | &mut self, 408 | address: u8, 409 | bytes: &[u8], 410 | buffer: &mut [u8], 411 | ) -> Result<(), Error> { 412 | assert!(!bytes.is_empty(), "bytes must be a non-empty slice"); 413 | assert!(!buffer.is_empty(), "buffer must be a non-empty slice"); 414 | 415 | // lock at the start to prevent GPIO from being modified while we build 416 | // the MPSSE command 417 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 418 | 419 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 420 | 421 | // ST 422 | for _ in 0..self.start_stop_cmds { 423 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 424 | } 425 | for _ in 0..self.start_stop_cmds { 426 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 427 | } 428 | 429 | mpsse_cmd = mpsse_cmd 430 | // SAD + W 431 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 432 | .clock_bits_out(BITS_OUT, address << 1, 8) 433 | // SAK 434 | .set_gpio_lower(lock.value, SCL | lock.direction) 435 | .clock_bits_in(BITS_IN, 1); 436 | 437 | for byte in bytes { 438 | mpsse_cmd = mpsse_cmd 439 | // Oi 440 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 441 | .clock_bits_out(BITS_OUT, *byte, 8) 442 | // SAK 443 | .set_gpio_lower(lock.value, SCL | lock.direction) 444 | .clock_bits_in(BITS_IN, 1); 445 | } 446 | 447 | // SR 448 | for _ in 0..self.start_stop_cmds { 449 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 450 | } 451 | for _ in 0..self.start_stop_cmds { 452 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 453 | } 454 | for _ in 0..self.start_stop_cmds { 455 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 456 | } 457 | 458 | mpsse_cmd = mpsse_cmd 459 | // SAD + R 460 | .clock_bits_out(BITS_OUT, (address << 1) | 1, 8) 461 | // SAK 462 | .set_gpio_lower(lock.value, SCL | lock.direction) 463 | .clock_bits_in(BITS_IN, 1); 464 | 465 | for idx in 0..buffer.len() { 466 | mpsse_cmd = mpsse_cmd 467 | .set_gpio_lower(lock.value, SCL | lock.direction) 468 | .clock_bits_in(BITS_IN, 8); 469 | if idx == buffer.len() - 1 { 470 | // NMAK 471 | mpsse_cmd = mpsse_cmd 472 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 473 | .clock_bits_out(BITS_OUT, 0x80, 1) 474 | } else { 475 | // MAK 476 | mpsse_cmd = mpsse_cmd 477 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 478 | .clock_bits_out(BITS_OUT, 0x00, 1) 479 | } 480 | } 481 | 482 | // SP 483 | for _ in 0..self.start_stop_cmds { 484 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 485 | } 486 | for _ in 0..self.start_stop_cmds { 487 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 488 | } 489 | for _ in 0..self.start_stop_cmds { 490 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 491 | } 492 | 493 | mpsse_cmd = mpsse_cmd 494 | // Idle 495 | .set_gpio_lower(lock.value, lock.direction) 496 | .send_immediate(); 497 | 498 | lock.ft.send(mpsse_cmd.as_slice())?; 499 | let mut ack_buf: Vec = vec![0; 2 + bytes.len()]; 500 | lock.ft.recv(&mut ack_buf)?; 501 | lock.ft.recv(buffer)?; 502 | 503 | if ack_buf.iter().any(|&ack| (ack & 0b1) != 0x00) { 504 | Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Unknown))) 505 | } else { 506 | Ok(()) 507 | } 508 | } 509 | 510 | fn write_read_slow( 511 | &mut self, 512 | address: u8, 513 | bytes: &[u8], 514 | buffer: &mut [u8], 515 | ) -> Result<(), Error> { 516 | assert!(!bytes.is_empty(), "bytes must be a non-empty slice"); 517 | assert!(!buffer.is_empty(), "buffer must be a non-empty slice"); 518 | 519 | // lock at the start to prevent GPIO from being modified while we build 520 | // the MPSSE command 521 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 522 | 523 | // ST 524 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 525 | for _ in 0..self.start_stop_cmds { 526 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 527 | } 528 | for _ in 0..self.start_stop_cmds { 529 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 530 | } 531 | 532 | mpsse_cmd = mpsse_cmd 533 | // SAD + W 534 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 535 | .clock_bits_out(BITS_OUT, address << 1, 8) 536 | // SAK 537 | .set_gpio_lower(lock.value, SCL | lock.direction) 538 | .clock_bits_in(BITS_IN, 1) 539 | .send_immediate(); 540 | 541 | lock.ft.send(mpsse_cmd.as_slice())?; 542 | let mut ack_buf: [u8; 1] = [0; 1]; 543 | lock.ft.recv(&mut ack_buf)?; 544 | if (ack_buf[0] & 0b1) != 0x00 { 545 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Address))); 546 | } 547 | 548 | for byte in bytes { 549 | let mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 550 | // Oi 551 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 552 | .clock_bits_out(BITS_OUT, *byte, 8) 553 | // SAK 554 | .set_gpio_lower(lock.value, SCL | lock.direction) 555 | .clock_bits_in(BITS_IN, 1) 556 | .send_immediate(); 557 | 558 | lock.ft.send(mpsse_cmd.as_slice())?; 559 | let mut ack_buf: [u8; 1] = [0; 1]; 560 | lock.ft.recv(&mut ack_buf)?; 561 | if (ack_buf[0] & 0b1) != 0x00 { 562 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Data))); 563 | } 564 | } 565 | 566 | // SR 567 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 568 | for _ in 0..self.start_stop_cmds { 569 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 570 | } 571 | for _ in 0..self.start_stop_cmds { 572 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 573 | } 574 | for _ in 0..self.start_stop_cmds { 575 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 576 | } 577 | 578 | mpsse_cmd = mpsse_cmd 579 | // SAD + R 580 | .clock_bits_out(BITS_OUT, (address << 1) | 1, 8) 581 | // SAK 582 | .set_gpio_lower(lock.value, SCL | lock.direction) 583 | .clock_bits_in(BITS_IN, 1) 584 | .send_immediate(); 585 | 586 | lock.ft.send(mpsse_cmd.as_slice())?; 587 | let mut ack_buf: [u8; 1] = [0; 1]; 588 | lock.ft.recv(&mut ack_buf)?; 589 | if (ack_buf[0] & 0b1) != 0x00 { 590 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Data))); 591 | } 592 | 593 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 594 | for idx in 0..buffer.len() { 595 | mpsse_cmd = mpsse_cmd 596 | .set_gpio_lower(lock.value, SCL | lock.direction) 597 | .clock_bits_in(BITS_IN, 8); 598 | if idx == buffer.len() - 1 { 599 | // NMAK 600 | mpsse_cmd = mpsse_cmd 601 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 602 | .clock_bits_out(BITS_OUT, 0x80, 1) 603 | } else { 604 | // MAK 605 | mpsse_cmd = mpsse_cmd 606 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 607 | .clock_bits_out(BITS_OUT, 0x00, 1) 608 | } 609 | } 610 | 611 | // SP 612 | for _ in 0..self.start_stop_cmds { 613 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 614 | } 615 | for _ in 0..self.start_stop_cmds { 616 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 617 | } 618 | for _ in 0..self.start_stop_cmds { 619 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 620 | } 621 | 622 | mpsse_cmd = mpsse_cmd 623 | // Idle 624 | .set_gpio_lower(lock.value, lock.direction) 625 | .send_immediate(); 626 | 627 | lock.ft.send(mpsse_cmd.as_slice())?; 628 | lock.ft.recv(buffer)?; 629 | 630 | Ok(()) 631 | } 632 | 633 | fn transaction( 634 | &mut self, 635 | address: u8, 636 | operations: &mut [Operation<'_>], 637 | ) -> Result<(), Error> { 638 | // lock at the start to prevent GPIO from being modified while we build 639 | // the MPSSE command 640 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 641 | 642 | // ST 643 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 644 | for _ in 0..self.start_stop_cmds { 645 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 646 | } 647 | for _ in 0..self.start_stop_cmds { 648 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 649 | } 650 | lock.ft.send(mpsse_cmd.as_slice())?; 651 | 652 | let mut prev_op_was_a_read: bool = false; 653 | for (idx, operation) in operations.iter_mut().enumerate() { 654 | match operation { 655 | Operation::Read(buffer) => { 656 | if idx == 0 || !prev_op_was_a_read { 657 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 658 | if idx != 0 { 659 | // SR 660 | for _ in 0..self.start_stop_cmds { 661 | mpsse_cmd = mpsse_cmd.set_gpio_lower( 662 | lock.value | SCL | SDA, 663 | SCL | SDA | lock.direction, 664 | ) 665 | } 666 | for _ in 0..self.start_stop_cmds { 667 | mpsse_cmd = mpsse_cmd 668 | .set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 669 | } 670 | for _ in 0..self.start_stop_cmds { 671 | mpsse_cmd = 672 | mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 673 | } 674 | } 675 | 676 | mpsse_cmd = mpsse_cmd 677 | // SAD + R 678 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 679 | .clock_bits_out(BITS_OUT, (address << 1) | 1, 8) 680 | // SAK 681 | .set_gpio_lower(lock.value, SCL | lock.direction) 682 | .clock_bits_in(BITS_IN, 1) 683 | .send_immediate(); 684 | 685 | lock.ft.send(mpsse_cmd.as_slice())?; 686 | let mut ack_buf: [u8; 1] = [0; 1]; 687 | lock.ft.recv(&mut ack_buf)?; 688 | if (ack_buf[0] & 0b1) != 0x00 { 689 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Address))); 690 | } 691 | } 692 | 693 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 694 | for idx in 0..buffer.len() { 695 | mpsse_cmd = mpsse_cmd 696 | .set_gpio_lower(lock.value, SCL | lock.direction) 697 | .clock_bits_in(BITS_IN, 8); 698 | if idx == buffer.len() - 1 { 699 | // NMAK 700 | mpsse_cmd = mpsse_cmd 701 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 702 | .clock_bits_out(BITS_OUT, 0x80, 1) 703 | } else { 704 | // MAK 705 | mpsse_cmd = mpsse_cmd 706 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 707 | .clock_bits_out(BITS_OUT, 0x00, 1) 708 | } 709 | } 710 | lock.ft.send(mpsse_cmd.as_slice())?; 711 | lock.ft.recv(buffer)?; 712 | 713 | prev_op_was_a_read = true; 714 | } 715 | Operation::Write(bytes) => { 716 | if idx == 0 || prev_op_was_a_read { 717 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 718 | if idx != 0 { 719 | // SR 720 | for _ in 0..self.start_stop_cmds { 721 | mpsse_cmd = mpsse_cmd.set_gpio_lower( 722 | lock.value | SCL | SDA, 723 | SCL | SDA | lock.direction, 724 | ) 725 | } 726 | for _ in 0..self.start_stop_cmds { 727 | mpsse_cmd = mpsse_cmd 728 | .set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 729 | } 730 | for _ in 0..self.start_stop_cmds { 731 | mpsse_cmd = 732 | mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 733 | } 734 | } 735 | 736 | mpsse_cmd = mpsse_cmd 737 | // SAD + W 738 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 739 | .clock_bits_out(BITS_OUT, address << 1, 8) 740 | // SAK 741 | .set_gpio_lower(lock.value, SCL | lock.direction) 742 | .clock_bits_in(BITS_IN, 1) 743 | .send_immediate(); 744 | 745 | lock.ft.send(mpsse_cmd.as_slice())?; 746 | let mut ack_buf: [u8; 1] = [0; 1]; 747 | lock.ft.recv(&mut ack_buf)?; 748 | if (ack_buf[0] & 0b1) != 0x00 { 749 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Address))); 750 | } 751 | } 752 | 753 | for byte in *bytes { 754 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 755 | mpsse_cmd = mpsse_cmd 756 | // Oi 757 | .set_gpio_lower(lock.value, SCL | SDA | lock.direction) 758 | .clock_bits_out(BITS_OUT, *byte, 8) 759 | // SAK 760 | .set_gpio_lower(lock.value, SCL | lock.direction) 761 | .clock_bits_in(BITS_IN, 1) 762 | .send_immediate(); 763 | 764 | lock.ft.send(mpsse_cmd.as_slice())?; 765 | let mut ack_buf: [u8; 1] = [0; 1]; 766 | lock.ft.recv(&mut ack_buf)?; 767 | if (ack_buf[0] & 0b1) != 0x00 { 768 | return Err(Error::Hal(I2cNoAck(NoAcknowledgeSource::Data))); 769 | } 770 | } 771 | 772 | prev_op_was_a_read = false; 773 | } 774 | } 775 | } 776 | 777 | let mut mpsse_cmd: MpsseCmdBuilder = MpsseCmdBuilder::new(); 778 | // SP 779 | for _ in 0..self.start_stop_cmds { 780 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value, SCL | SDA | lock.direction) 781 | } 782 | for _ in 0..self.start_stop_cmds { 783 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL, SCL | SDA | lock.direction) 784 | } 785 | for _ in 0..self.start_stop_cmds { 786 | mpsse_cmd = mpsse_cmd.set_gpio_lower(lock.value | SCL | SDA, SCL | SDA | lock.direction) 787 | } 788 | 789 | // Idle 790 | mpsse_cmd = mpsse_cmd 791 | .set_gpio_lower(lock.value, lock.direction) 792 | .send_immediate(); 793 | lock.ft.send(mpsse_cmd.as_slice())?; 794 | 795 | Ok(()) 796 | } 797 | } 798 | 799 | impl eh0::blocking::i2c::Read for I2c 800 | where 801 | Device: MpsseCmdExecutor, 802 | E: std::error::Error, 803 | Error: From, 804 | { 805 | type Error = Error; 806 | 807 | fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { 808 | if self.fast { 809 | self.read_fast(address, buffer) 810 | } else { 811 | self.read_slow(address, buffer) 812 | } 813 | } 814 | } 815 | 816 | impl eh0::blocking::i2c::Write for I2c 817 | where 818 | Device: MpsseCmdExecutor, 819 | E: std::error::Error, 820 | Error: From, 821 | { 822 | type Error = Error; 823 | 824 | fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { 825 | if self.fast { 826 | self.write_fast(addr, bytes) 827 | } else { 828 | self.write_slow(addr, bytes) 829 | } 830 | } 831 | } 832 | 833 | impl eh0::blocking::i2c::WriteRead for I2c 834 | where 835 | Device: MpsseCmdExecutor, 836 | E: std::error::Error, 837 | Error: From, 838 | { 839 | type Error = Error; 840 | 841 | fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { 842 | if self.fast { 843 | self.write_read_fast(address, bytes, buffer) 844 | } else { 845 | self.write_read_slow(address, bytes, buffer) 846 | } 847 | } 848 | } 849 | 850 | impl eh1::i2c::ErrorType for I2c 851 | where 852 | Device: MpsseCmdExecutor, 853 | E: std::error::Error, 854 | Error: From, 855 | { 856 | type Error = Error; 857 | } 858 | 859 | impl eh1::i2c::I2c for I2c 860 | where 861 | Device: MpsseCmdExecutor, 862 | E: std::error::Error, 863 | Error: From, 864 | { 865 | fn transaction( 866 | &mut self, 867 | address: SevenBitAddress, 868 | operations: &mut [Operation<'_>], 869 | ) -> Result<(), Self::Error> { 870 | self.transaction(address, operations) 871 | } 872 | } 873 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is an [embedded-hal] implementation for the FTDI chips 2 | //! that use the [libftd2xx] or [ftdi-rs] drivers. 3 | //! 4 | //! This enables development of embedded device drivers without the use of 5 | //! a microcontroller. The FTDI devices interface with a PC via USB, and 6 | //! provide a multi-protocol synchronous serial engine to interface 7 | //! with most GPIO, SPI, and I2C embedded devices. 8 | //! 9 | //! **Note:** 10 | //! This is strictly a development tool. 11 | //! The crate contains runtime borrow checks and explicit panics to adapt the 12 | //! FTDI device into the [embedded-hal] traits. 13 | //! 14 | //! # Quickstart 15 | //! 16 | //! * Enable the "libftd2xx-static" feature flag to use static linking with libftd2xx driver. 17 | //! * Linux users only: Add [udev rules]. 18 | //! 19 | //! ```toml 20 | //! [dependencies.ftdi-embedded-hal] 21 | //! version = "0.23.2" 22 | //! features = ["libftd2xx", "libftd2xx-static"] 23 | //! ``` 24 | //! 25 | //! # Limitations 26 | //! 27 | //! * Limited trait support: SPI, I2C, Delay, InputPin, and OutputPin traits are implemented. 28 | //! * Limited device support: FT232H, FT2232H, FT4232H. 29 | //! * Limited SPI modes support: MODE0, MODE2. 30 | //! 31 | //! # Examples 32 | //! 33 | //! ## SPI 34 | //! 35 | //! Pin setup: 36 | //! 37 | //! * D0 - SCK 38 | //! * D1 - SDO (MOSI) 39 | //! * D2 - SDI (MISO) 40 | //! * D3..D7 - Available for CS 41 | //! 42 | //! Communicate with SPI devices using [ftdi-rs] driver: 43 | //! ```no_run 44 | //! use ftdi_embedded_hal as hal; 45 | //! 46 | //! # #[cfg(feature = "ftdi")] 47 | //! # { 48 | //! let device = ftdi::find_by_vid_pid(0x0403, 0x6010) 49 | //! .interface(ftdi::Interface::A) 50 | //! .open()?; 51 | //! 52 | //! let hal = hal::FtHal::init_freq(device, 3_000_000)?; 53 | //! let spi = hal.spi()?; 54 | //! # } 55 | //! # Ok::<(), std::boxed::Box>(()) 56 | //! ``` 57 | //! 58 | //! Communicate with SPI devices using [libftd2xx] driver: 59 | //! ```no_run 60 | //! use ftdi_embedded_hal as hal; 61 | //! 62 | //! # #[cfg(feature = "libftd2xx")] 63 | //! # { 64 | //! let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A")?; 65 | //! 66 | //! let hal = hal::FtHal::init_freq(device, 3_000_000)?; 67 | //! let spi = hal.spi()?; 68 | //! # } 69 | //! # Ok::<(), std::boxed::Box>(()) 70 | //! ``` 71 | //! 72 | //! ## I2C 73 | //! 74 | //! Communicate with I2C devices using [ftdi-rs] driver: 75 | //! ```no_run 76 | //! use ftdi_embedded_hal as hal; 77 | //! 78 | //! # #[cfg(feature = "ftdi")] 79 | //! # { 80 | //! let device = ftdi::find_by_vid_pid(0x0403, 0x6010) 81 | //! .interface(ftdi::Interface::A) 82 | //! .open()?; 83 | //! 84 | //! let hal = hal::FtHal::init_freq(device, 400_000)?; 85 | //! let i2c = hal.i2c()?; 86 | //! # } 87 | //! # Ok::<(), std::boxed::Box>(()) 88 | //! ``` 89 | //! 90 | //! Communicate with I2C devices using [libftd2xx] driver: 91 | //! ```no_run 92 | //! use ftdi_embedded_hal as hal; 93 | //! 94 | //! # #[cfg(feature = "libftd2xx")] 95 | //! # { 96 | //! let device = libftd2xx::Ft232h::with_description("Single RS232-HS")?; 97 | //! 98 | //! let hal = hal::FtHal::init_freq(device, 400_000)?; 99 | //! let i2c = hal.i2c()?; 100 | //! # } 101 | //! # Ok::<(), std::boxed::Box>(()) 102 | //! ``` 103 | //! 104 | //! ## GPIO 105 | //! 106 | //! Control GPIO pins using [libftd2xx] driver: 107 | //! ```no_run 108 | //! use ftdi_embedded_hal as hal; 109 | //! 110 | //! # #[cfg(feature = "libftd2xx")] 111 | //! # { 112 | //! let device = libftd2xx::Ft232h::with_description("Single RS232-HS")?; 113 | //! 114 | //! let hal = hal::FtHal::init_default(device)?; 115 | //! let gpio = hal.ad6(); 116 | //! # } 117 | //! # Ok::<(), std::boxed::Box>(()) 118 | //! ``` 119 | //! 120 | //! Control GPIO pins using [ftdi-rs] driver: 121 | //! ```no_run 122 | //! use ftdi_embedded_hal as hal; 123 | //! 124 | //! # #[cfg(feature = "ftdi")] 125 | //! # { 126 | //! let device = ftdi::find_by_vid_pid(0x0403, 0x6010) 127 | //! .interface(ftdi::Interface::A) 128 | //! .open()?; 129 | //! 130 | //! let hal = hal::FtHal::init_default(device)?; 131 | //! let gpio = hal.ad6(); 132 | //! # } 133 | //! # Ok::<(), std::boxed::Box>(()) 134 | //! ``` 135 | //! 136 | //! ## More examples 137 | //! 138 | //! * [newAM/eeprom25aa02e48-rs]: read data from Microchip 25AA02E48 SPI EEPROM 139 | //! * [newAM/bme280-rs]: read samples from Bosch BME280 sensor via I2C protocol 140 | //! 141 | //! [embedded-hal]: https://github.com/rust-embedded/embedded-hal 142 | //! [ftdi-rs]: https://github.com/tanriol/ftdi-rs 143 | //! [libftd2xx crate]: https://github.com/ftdi-rs/libftd2xx-rs/ 144 | //! [libftd2xx]: https://github.com/ftdi-rs/libftd2xx-rs 145 | //! [newAM/eeprom25aa02e48-rs]: https://github.com/newAM/eeprom25aa02e48-rs/blob/main/examples/ftdi.rs 146 | //! [newAM/bme280-rs]: https://github.com/newAM/bme280-rs/blob/main/examples/ftdi-i2c.rs 147 | //! [udev rules]: https://github.com/ftdi-rs/libftd2xx-rs/#udev-rules 148 | //! [setup executable]: https://www.ftdichip.com/Drivers/CDM/CDM21228_Setup.zip 149 | #![forbid(missing_docs)] 150 | #![forbid(unsafe_code)] 151 | 152 | pub use eh0; 153 | pub use eh1; 154 | pub use ftdi_mpsse; 155 | 156 | #[cfg(feature = "ftdi")] 157 | pub use ftdi; 158 | 159 | #[cfg(feature = "libftd2xx")] 160 | pub use libftd2xx; 161 | 162 | mod delay; 163 | mod error; 164 | mod gpio; 165 | mod i2c; 166 | mod spi; 167 | 168 | pub use crate::error::{Error, ErrorKind}; 169 | pub use delay::Delay; 170 | pub use gpio::{InputPin, OutputPin}; 171 | pub use i2c::I2c; 172 | pub use spi::{Spi, SpiDevice}; 173 | 174 | use gpio::Pin; 175 | 176 | use ftdi_mpsse::{MpsseCmdExecutor, MpsseSettings}; 177 | use std::sync::{Arc, Mutex}; 178 | 179 | /// State tracker for each pin on the FTDI chip. 180 | #[derive(Debug, Clone, Copy)] 181 | enum PinUse { 182 | I2c, 183 | Spi, 184 | Output, 185 | Input, 186 | } 187 | 188 | impl std::fmt::Display for PinUse { 189 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 190 | match self { 191 | PinUse::I2c => write!(f, "I2C"), 192 | PinUse::Spi => write!(f, "SPI"), 193 | PinUse::Output => write!(f, "OUTPUT"), 194 | PinUse::Input => write!(f, "INPUT"), 195 | } 196 | } 197 | } 198 | 199 | #[derive(Debug, Default)] 200 | struct GpioByte { 201 | /// GPIO direction. 202 | direction: u8, 203 | /// GPIO value. 204 | value: u8, 205 | /// Pin allocation. 206 | pins: [Option; 8], 207 | } 208 | 209 | #[derive(Debug)] 210 | struct FtInner { 211 | /// FTDI device. 212 | ft: Device, 213 | lower: GpioByte, 214 | upper: GpioByte, 215 | } 216 | 217 | // FtInner deref's into .lower because SPI and I2C code were not adjusted yet to handle the split; 218 | // once those are updated, the Deref implementation can go away again 219 | 220 | impl core::ops::Deref for FtInner { 221 | type Target = GpioByte; 222 | fn deref(&self) -> &GpioByte { 223 | &self.lower 224 | } 225 | } 226 | 227 | impl core::ops::DerefMut for FtInner { 228 | fn deref_mut(&mut self) -> &mut GpioByte { 229 | &mut self.lower 230 | } 231 | } 232 | 233 | impl FtInner { 234 | /// Allocate a pin in the lower byte for a specific use. 235 | pub fn allocate_pin(&mut self, idx: u8, purpose: PinUse) { 236 | assert!(idx < 8, "Pin index {idx} is out of range 0 - 7"); 237 | 238 | if let Some(current) = self.lower.pins[usize::from(idx)] { 239 | panic!( 240 | "Unable to allocate pin {idx} for {purpose}, pin is already allocated for {current}" 241 | ); 242 | } else { 243 | self.lower.pins[usize::from(idx)] = Some(purpose) 244 | } 245 | } 246 | 247 | /// Allocate a pin for a specific use. 248 | pub fn allocate_pin_any(&mut self, pin: Pin, purpose: PinUse) { 249 | let (byte, idx) = match pin { 250 | Pin::Lower(idx) => (&mut self.lower, idx), 251 | Pin::Upper(idx) => (&mut self.upper, idx), 252 | }; 253 | assert!(idx < 8, "Pin index {idx} is out of range 0 - 7"); 254 | 255 | if let Some(current) = byte.pins[usize::from(idx)] { 256 | panic!( 257 | "Unable to allocate pin {idx} for {purpose}, pin is already allocated for {current}" 258 | ); 259 | } else { 260 | byte.pins[usize::from(idx)] = Some(purpose) 261 | } 262 | } 263 | } 264 | 265 | impl From for FtInner { 266 | fn from(ft: Device) -> Self { 267 | FtInner { 268 | ft, 269 | lower: Default::default(), 270 | upper: Default::default(), 271 | } 272 | } 273 | } 274 | 275 | /// FTxxx device. 276 | #[derive(Debug)] 277 | pub struct FtHal { 278 | mtx: Arc>>, 279 | } 280 | 281 | impl FtHal 282 | where 283 | Device: MpsseCmdExecutor, 284 | E: std::error::Error, 285 | Error: From, 286 | { 287 | /// Initialize the FTDI MPSSE with sane defaults. 288 | /// 289 | /// Default values: 290 | /// 291 | /// * Reset the FTDI device. 292 | /// * 4k USB transfer size. 293 | /// * 1s USB read timeout. 294 | /// * 1s USB write timeout. 295 | /// * 16ms latency timer. 296 | /// * 100kHz clock frequency. 297 | /// 298 | /// # Example 299 | /// 300 | /// ```no_run 301 | /// use ftdi_embedded_hal as hal; 302 | /// 303 | /// # #[cfg(feature = "libftd2xx")] 304 | /// # { 305 | /// let device = libftd2xx::Ft232h::with_description("Single RS232-HS")?; 306 | /// let hal = hal::FtHal::init_default(device)?; 307 | /// # } 308 | /// # Ok::<(), std::boxed::Box>(()) 309 | /// ``` 310 | pub fn init_default(device: Device) -> Result, Error> { 311 | let settings: MpsseSettings = MpsseSettings { 312 | clock_frequency: Some(100_000), 313 | ..Default::default() 314 | }; 315 | 316 | Ok(FtHal::init(device, &settings)?) 317 | } 318 | 319 | /// Initialize the FTDI MPSSE with sane defaults and custom frequency 320 | /// 321 | /// # Example 322 | /// 323 | /// ```no_run 324 | /// use ftdi_embedded_hal as hal; 325 | /// 326 | /// # #[cfg(feature = "libftd2xx")] 327 | /// # { 328 | /// let device = libftd2xx::Ft232h::with_description("Single RS232-HS")?; 329 | /// let hal = hal::FtHal::init_freq(device, 3_000_000)?; 330 | /// # } 331 | /// # Ok::<(), std::boxed::Box>(()) 332 | /// ``` 333 | pub fn init_freq(device: Device, freq: u32) -> Result, Error> { 334 | let settings: MpsseSettings = MpsseSettings { 335 | clock_frequency: Some(freq), 336 | ..Default::default() 337 | }; 338 | 339 | Ok(FtHal::init(device, &settings)?) 340 | } 341 | 342 | /// Initialize the FTDI MPSSE with custom values. 343 | /// 344 | /// **Note:** The `mask` field of [`MpsseSettings`] is ignored for this function. 345 | /// 346 | /// **Note:** The clock frequency will be 2/3 of the specified value when in 347 | /// I2C mode. 348 | /// 349 | /// # Panics 350 | /// 351 | /// Panics if the `clock_frequency` field of [`MpsseSettings`] is `None`. 352 | /// 353 | /// # Example 354 | /// 355 | /// ```no_run 356 | /// use ftdi_embedded_hal as hal; 357 | /// use ftdi_mpsse::MpsseSettings; 358 | /// use std::time::Duration; 359 | /// 360 | /// let mpsse = MpsseSettings { 361 | /// reset: false, 362 | /// in_transfer_size: 4096, 363 | /// read_timeout: Duration::from_secs(5), 364 | /// write_timeout: Duration::from_secs(5), 365 | /// latency_timer: Duration::from_millis(32), 366 | /// mask: 0x00, 367 | /// clock_frequency: Some(400_000), 368 | /// }; 369 | /// 370 | /// # #[cfg(feature = "libftd2xx")] 371 | /// # { 372 | /// let device = libftd2xx::Ft232h::with_description("Single RS232-HS")?; 373 | /// let hal = hal::FtHal::init(device, &mpsse)?; 374 | /// # } 375 | /// # Ok::<(), std::boxed::Box>(()) 376 | /// ``` 377 | /// 378 | /// [`MpsseSettings`]: ftdi_mpsse::MpsseSettings 379 | pub fn init(mut device: Device, mpsse_settings: &MpsseSettings) -> Result, E> { 380 | device.init(mpsse_settings)?; 381 | 382 | Ok(FtHal { 383 | mtx: Arc::new(Mutex::new(device.into())), 384 | }) 385 | } 386 | } 387 | 388 | impl FtHal 389 | where 390 | Device: MpsseCmdExecutor, 391 | E: std::error::Error, 392 | Error: From, 393 | { 394 | /// Executes the closure with the device. 395 | /// 396 | /// Useful for accessing EEPROM, or other device-specific functionality. 397 | /// 398 | /// # Example 399 | /// 400 | /// ```no_run 401 | /// use ftdi_embedded_hal as hal; 402 | /// # #[cfg(feature = "libftd2xx")] 403 | /// use hal::libftd2xx::FtdiEeprom; 404 | /// 405 | /// # #[cfg(feature = "libftd2xx")] 406 | /// # { 407 | /// let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A")?; 408 | /// let mut hal = hal::FtHal::init_default(device)?; 409 | /// let serial_number: String = 410 | /// hal.with_device(|d| d.eeprom_read().map(|(_, strings)| strings.serial_number()))?; 411 | /// # } 412 | /// # Ok::<(), std::boxed::Box>(()) 413 | /// ``` 414 | pub fn with_device(&mut self, mut f: F) -> T 415 | where 416 | F: FnMut(&mut Device) -> T, 417 | { 418 | let mut inner = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 419 | let result: T = f(&mut inner.ft); 420 | result 421 | } 422 | 423 | /// Aquire the SPI peripheral for the FT232H. 424 | /// 425 | /// Pin assignments: 426 | /// * AD0 => SCK 427 | /// * AD1 => MOSI 428 | /// * AD2 => MISO 429 | /// 430 | /// # Panics 431 | /// 432 | /// Panics if pin 0, 1, or 2 are already in use. 433 | /// 434 | /// # Example 435 | /// 436 | /// ```no_run 437 | /// use ftdi_embedded_hal as hal; 438 | /// 439 | /// # #[cfg(feature = "libftd2xx")] 440 | /// # { 441 | /// let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A")?; 442 | /// let hal = hal::FtHal::init_freq(device, 3_000_000)?; 443 | /// let spi = hal.spi()?; 444 | /// # } 445 | /// # Ok::<(), std::boxed::Box>(()) 446 | /// ``` 447 | pub fn spi(&self) -> Result, Error> { 448 | Spi::new(self.mtx.clone()) 449 | } 450 | 451 | /// Aquire the SPI peripheral with a chip select pin. 452 | /// 453 | /// This is specific to embedded-hal version 1. 454 | /// 455 | /// Pin assignments: 456 | /// * AD0 => SCK 457 | /// * AD1 => MOSI 458 | /// * AD2 => MISO 459 | /// 460 | /// # Panics 461 | /// 462 | /// Panics if pin 0, 1, 2 or the CS pin are already in use. 463 | /// 464 | /// # Example 465 | /// 466 | /// ```no_run 467 | /// use ftdi_embedded_hal as hal; 468 | /// 469 | /// # #[cfg(feature = "libftd2xx")] 470 | /// # { 471 | /// let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A")?; 472 | /// let hal = hal::FtHal::init_freq(device, 3_000_000)?; 473 | /// let spi = hal.spi_device(3)?; 474 | /// # } 475 | /// # Ok::<(), std::boxed::Box>(()) 476 | /// ``` 477 | pub fn spi_device(&self, cs_idx: u8) -> Result, Error> { 478 | SpiDevice::new(self.mtx.clone(), cs_idx) 479 | } 480 | 481 | /// Aquire the I2C peripheral for the FT232H. 482 | /// 483 | /// Pin assignments: 484 | /// * AD0 => SCL 485 | /// * AD1 => SDA 486 | /// * AD2 => SDA 487 | /// 488 | /// Yes, AD1 and AD2 are both SDA. 489 | /// These pins must be shorted together for I2C operation. 490 | /// 491 | /// # Panics 492 | /// 493 | /// Panics if pin 0, 1, or 2 are already in use. 494 | /// 495 | /// # Example 496 | /// 497 | /// ```no_run 498 | /// use ftdi_embedded_hal as hal; 499 | /// 500 | /// # #[cfg(feature = "libftd2xx")] 501 | /// # { 502 | /// let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A")?; 503 | /// let hal = hal::FtHal::init_freq(device, 3_000_000)?; 504 | /// let i2c = hal.i2c()?; 505 | /// # } 506 | /// # Ok::<(), std::boxed::Box>(()) 507 | /// ``` 508 | pub fn i2c(&self) -> Result, Error> { 509 | I2c::new(self.mtx.clone()) 510 | } 511 | 512 | /// Aquire the digital output pin 0 for the FT232H. 513 | /// 514 | /// # Panics 515 | /// 516 | /// Panics if the pin is already in-use. 517 | pub fn ad0(&self) -> Result, Error> { 518 | OutputPin::new(self.mtx.clone(), Pin::Lower(0)) 519 | } 520 | 521 | /// Aquire the digital input pin 0 for the FT232H. 522 | /// 523 | /// # Panics 524 | /// 525 | /// Panics if the pin is already in-use. 526 | pub fn adi0(&self) -> Result, Error> { 527 | InputPin::new(self.mtx.clone(), Pin::Lower(0)) 528 | } 529 | 530 | /// Aquire the digital output pin 1 for the FT232H. 531 | /// 532 | /// # Panics 533 | /// 534 | /// Panics if the pin is already in-use. 535 | pub fn ad1(&self) -> Result, Error> { 536 | OutputPin::new(self.mtx.clone(), Pin::Lower(1)) 537 | } 538 | 539 | /// Aquire the digital input pin 1 for the FT232H. 540 | /// 541 | /// # Panics 542 | /// 543 | /// Panics if the pin is already in-use. 544 | pub fn adi1(&self) -> Result, Error> { 545 | InputPin::new(self.mtx.clone(), Pin::Lower(1)) 546 | } 547 | 548 | /// Aquire the digital output pin 2 for the FT232H. 549 | /// 550 | /// # Panics 551 | /// 552 | /// Panics if the pin is already in-use. 553 | pub fn ad2(&self) -> Result, Error> { 554 | OutputPin::new(self.mtx.clone(), Pin::Lower(2)) 555 | } 556 | 557 | /// Aquire the digital input pin 2 for the FT232H. 558 | /// 559 | /// # Panics 560 | /// 561 | /// Panics if the pin is already in-use. 562 | pub fn adi2(&self) -> Result, Error> { 563 | InputPin::new(self.mtx.clone(), Pin::Lower(2)) 564 | } 565 | 566 | /// Aquire the digital output pin 3 for the FT232H. 567 | /// 568 | /// # Panics 569 | /// 570 | /// Panics if the pin is already in-use. 571 | pub fn ad3(&self) -> Result, Error> { 572 | OutputPin::new(self.mtx.clone(), Pin::Lower(3)) 573 | } 574 | 575 | /// Aquire the digital input pin 3 for the FT232H. 576 | /// 577 | /// # Panics 578 | /// 579 | /// Panics if the pin is already in-use. 580 | pub fn adi3(&self) -> Result, Error> { 581 | InputPin::new(self.mtx.clone(), Pin::Lower(3)) 582 | } 583 | 584 | /// Aquire the digital output pin 4 for the FT232H. 585 | /// 586 | /// # Panics 587 | /// 588 | /// Panics if the pin is already in-use. 589 | pub fn ad4(&self) -> Result, Error> { 590 | OutputPin::new(self.mtx.clone(), Pin::Lower(4)) 591 | } 592 | 593 | /// Aquire the digital input pin 4 for the FT232H. 594 | /// 595 | /// # Panics 596 | /// 597 | /// Panics if the pin is already in-use. 598 | pub fn adi4(&self) -> Result, Error> { 599 | InputPin::new(self.mtx.clone(), Pin::Lower(4)) 600 | } 601 | 602 | /// Aquire the digital output pin 5 for the FT232H. 603 | /// 604 | /// # Panics 605 | /// 606 | /// Panics if the pin is already in-use. 607 | pub fn ad5(&self) -> Result, Error> { 608 | OutputPin::new(self.mtx.clone(), Pin::Lower(5)) 609 | } 610 | 611 | /// Aquire the digital input pin 5 for the FT232H. 612 | /// 613 | /// # Panics 614 | /// 615 | /// Panics if the pin is already in-use. 616 | pub fn adi5(&self) -> Result, Error> { 617 | InputPin::new(self.mtx.clone(), Pin::Lower(5)) 618 | } 619 | 620 | /// Aquire the digital output pin 6 for the FT232H. 621 | /// 622 | /// # Panics 623 | /// 624 | /// Panics if the pin is already in-use. 625 | pub fn ad6(&self) -> Result, Error> { 626 | OutputPin::new(self.mtx.clone(), Pin::Lower(6)) 627 | } 628 | 629 | /// Aquire the digital input pin 6 for the FT232H. 630 | /// 631 | /// # Panics 632 | /// 633 | /// Panics if the pin is already in-use. 634 | pub fn adi6(&self) -> Result, Error> { 635 | InputPin::new(self.mtx.clone(), Pin::Lower(6)) 636 | } 637 | 638 | /// Aquire the digital output pin 7 for the FT232H. 639 | /// 640 | /// # Panics 641 | /// 642 | /// Panics if the pin is already in-use. 643 | pub fn ad7(&self) -> Result, Error> { 644 | OutputPin::new(self.mtx.clone(), Pin::Lower(7)) 645 | } 646 | 647 | /// Aquire the digital input pin 7 for the FT232H. 648 | /// 649 | /// # Panics 650 | /// 651 | /// Panics if the pin is already in-use. 652 | pub fn adi7(&self) -> Result, Error> { 653 | InputPin::new(self.mtx.clone(), Pin::Lower(7)) 654 | } 655 | 656 | /// Aquire the digital output upper pin 0 for the FT232H. 657 | /// 658 | /// # Panics 659 | /// 660 | /// Panics if the pin is already in-use. 661 | pub fn c0(&self) -> Result, Error> { 662 | OutputPin::new(self.mtx.clone(), Pin::Upper(0)) 663 | } 664 | 665 | /// Aquire the digital input upper pin 0 for the FT232H. 666 | /// 667 | /// # Panics 668 | /// 669 | /// Panics if the pin is already in-use. 670 | pub fn ci0(&self) -> Result, Error> { 671 | InputPin::new(self.mtx.clone(), Pin::Upper(0)) 672 | } 673 | 674 | /// Aquire the digital output upper pin 1 for the FT232H. 675 | /// 676 | /// # Panics 677 | /// 678 | /// Panics if the pin is already in-use. 679 | pub fn c1(&self) -> Result, Error> { 680 | OutputPin::new(self.mtx.clone(), Pin::Upper(1)) 681 | } 682 | 683 | /// Aquire the digital input upper pin 1 for the FT232H. 684 | /// 685 | /// # Panics 686 | /// 687 | /// Panics if the pin is already in-use. 688 | pub fn ci1(&self) -> Result, Error> { 689 | InputPin::new(self.mtx.clone(), Pin::Upper(1)) 690 | } 691 | 692 | /// Aquire the digital output upper pin 2 for the FT232H. 693 | /// 694 | /// # Panics 695 | /// 696 | /// Panics if the pin is already in-use. 697 | pub fn c2(&self) -> Result, Error> { 698 | OutputPin::new(self.mtx.clone(), Pin::Upper(2)) 699 | } 700 | 701 | /// Aquire the digital input upper pin 2 for the FT232H. 702 | /// 703 | /// # Panics 704 | /// 705 | /// Panics if the pin is already in-use. 706 | pub fn ci2(&self) -> Result, Error> { 707 | InputPin::new(self.mtx.clone(), Pin::Upper(2)) 708 | } 709 | 710 | /// Aquire the digital output upper pin 3 for the FT232H. 711 | /// 712 | /// # Panics 713 | /// 714 | /// Panics if the pin is already in-use. 715 | pub fn c3(&self) -> Result, Error> { 716 | OutputPin::new(self.mtx.clone(), Pin::Upper(3)) 717 | } 718 | 719 | /// Aquire the digital input upper pin 3 for the FT232H. 720 | /// 721 | /// # Panics 722 | /// 723 | /// Panics if the pin is already in-use. 724 | pub fn ci3(&self) -> Result, Error> { 725 | InputPin::new(self.mtx.clone(), Pin::Upper(3)) 726 | } 727 | 728 | /// Aquire the digital output upper pin 4 for the FT232H. 729 | /// 730 | /// # Panics 731 | /// 732 | /// Panics if the pin is already in-use. 733 | pub fn c4(&self) -> Result, Error> { 734 | OutputPin::new(self.mtx.clone(), Pin::Upper(4)) 735 | } 736 | 737 | /// Aquire the digital input upper pin 4 for the FT232H. 738 | /// 739 | /// # Panics 740 | /// 741 | /// Panics if the pin is already in-use. 742 | pub fn ci4(&self) -> Result, Error> { 743 | InputPin::new(self.mtx.clone(), Pin::Upper(4)) 744 | } 745 | 746 | /// Aquire the digital output upper pin 5 for the FT232H. 747 | /// 748 | /// # Panics 749 | /// 750 | /// Panics if the pin is already in-use. 751 | pub fn c5(&self) -> Result, Error> { 752 | OutputPin::new(self.mtx.clone(), Pin::Upper(5)) 753 | } 754 | 755 | /// Aquire the digital input upper pin 5 for the FT232H. 756 | /// 757 | /// # Panics 758 | /// 759 | /// Panics if the pin is already in-use. 760 | pub fn ci5(&self) -> Result, Error> { 761 | InputPin::new(self.mtx.clone(), Pin::Upper(5)) 762 | } 763 | 764 | /// Aquire the digital output upper pin 6 for the FT232H. 765 | /// 766 | /// # Panics 767 | /// 768 | /// Panics if the pin is already in-use. 769 | pub fn c6(&self) -> Result, Error> { 770 | OutputPin::new(self.mtx.clone(), Pin::Upper(6)) 771 | } 772 | 773 | /// Aquire the digital input upper pin 6 for the FT232H. 774 | /// 775 | /// # Panics 776 | /// 777 | /// Panics if the pin is already in-use. 778 | pub fn ci6(&self) -> Result, Error> { 779 | InputPin::new(self.mtx.clone(), Pin::Upper(6)) 780 | } 781 | 782 | /// Aquire the digital output upper pin 7 for the FT232H. 783 | /// 784 | /// # Panics 785 | /// 786 | /// Panics if the pin is already in-use. 787 | pub fn c7(&self) -> Result, Error> { 788 | OutputPin::new(self.mtx.clone(), Pin::Upper(7)) 789 | } 790 | 791 | /// Aquire the digital input upper pin 7 for the FT232H. 792 | /// 793 | /// # Panics 794 | /// 795 | /// Panics if the pin is already in-use. 796 | pub fn ci7(&self) -> Result, Error> { 797 | InputPin::new(self.mtx.clone(), Pin::Upper(7)) 798 | } 799 | } 800 | -------------------------------------------------------------------------------- /src/spi.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::{FtInner, PinUse}; 3 | use ftdi_mpsse::{ClockData, ClockDataOut, MpsseCmdBuilder, MpsseCmdExecutor}; 4 | use std::sync::{Arc, Mutex, MutexGuard}; 5 | 6 | /// FTDI SPI polarity. 7 | /// 8 | /// This is a helper type to support multiple embedded-hal versions simultaneously. 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 10 | pub struct Polarity { 11 | /// MPSSE command used to clock data in and out simultaneously. 12 | /// 13 | /// This is set by [`Spi::set_clock_polarity`]. 14 | clk: ClockData, 15 | /// MPSSE command used to clock data out. 16 | /// 17 | /// This is set by [`Spi::set_clock_polarity`]. 18 | clk_out: ClockDataOut, 19 | } 20 | 21 | impl From for Polarity { 22 | fn from(cpol: eh0::spi::Polarity) -> Self { 23 | match cpol { 24 | eh0::spi::Polarity::IdleLow => Polarity { 25 | clk: ClockData::MsbPosIn, 26 | clk_out: ClockDataOut::MsbNeg, 27 | }, 28 | eh0::spi::Polarity::IdleHigh => Polarity { 29 | clk: ClockData::MsbNegIn, 30 | clk_out: ClockDataOut::MsbPos, 31 | }, 32 | } 33 | } 34 | } 35 | 36 | impl From for Polarity { 37 | fn from(cpol: eh1::spi::Polarity) -> Self { 38 | match cpol { 39 | eh1::spi::Polarity::IdleLow => Polarity { 40 | clk: ClockData::MsbPosIn, 41 | clk_out: ClockDataOut::MsbNeg, 42 | }, 43 | eh1::spi::Polarity::IdleHigh => Polarity { 44 | clk: ClockData::MsbNegIn, 45 | clk_out: ClockDataOut::MsbPos, 46 | }, 47 | } 48 | } 49 | } 50 | 51 | impl Default for Polarity { 52 | fn default() -> Self { 53 | Self { 54 | clk: ClockData::MsbPosIn, 55 | clk_out: ClockDataOut::MsbNeg, 56 | } 57 | } 58 | } 59 | 60 | /// FTDI SPI bus. 61 | /// 62 | /// In embedded-hal version 1 this represents an exclusive SPI bus. 63 | /// 64 | /// This is created by calling [`FtHal::spi`]. 65 | /// 66 | /// [`FtHal::spi`]: crate::FtHal::spi 67 | #[derive(Debug)] 68 | pub struct Spi { 69 | /// Parent FTDI device. 70 | mtx: Arc>>, 71 | /// SPI polarity 72 | pol: Polarity, 73 | } 74 | 75 | impl Spi 76 | where 77 | Device: MpsseCmdExecutor, 78 | E: std::error::Error, 79 | Error: From, 80 | { 81 | pub(crate) fn new(mtx: Arc>>) -> Result, Error> { 82 | { 83 | let mut lock = mtx.lock().expect("Failed to aquire FTDI mutex"); 84 | lock.allocate_pin(0, PinUse::Spi); 85 | lock.allocate_pin(1, PinUse::Spi); 86 | lock.allocate_pin(2, PinUse::Spi); 87 | 88 | // clear direction of first 3 pins 89 | lock.direction &= !0x07; 90 | // set SCK (AD0) and MOSI (AD1) as output pins 91 | lock.direction |= 0x03; 92 | 93 | // set GPIO pins to new state 94 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 95 | .set_gpio_lower(lock.value, lock.direction) 96 | .send_immediate(); 97 | lock.ft.send(cmd.as_slice())?; 98 | } 99 | 100 | Ok(Spi { 101 | mtx, 102 | pol: Default::default(), 103 | }) 104 | } 105 | 106 | /// Set the SPI clock polarity. 107 | /// 108 | /// FTD2XX devices only supports [SPI mode] 0 and 2, clock phase is fixed. 109 | /// 110 | /// # Example 111 | /// 112 | /// ```no_run 113 | /// use eh1::spi::Polarity; 114 | /// use ftdi_embedded_hal as hal; 115 | /// 116 | /// # #[cfg(feature = "libftd2xx")] 117 | /// # { 118 | /// let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A")?; 119 | /// let hal = hal::FtHal::init_freq(device, 3_000_000)?; 120 | /// let mut spi = hal.spi()?; 121 | /// spi.set_clock_polarity(Polarity::IdleLow); 122 | /// # } 123 | /// # Ok::<(), std::boxed::Box>(()) 124 | /// ``` 125 | /// 126 | /// [SPI mode]: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface#Mode_numbers 127 | pub fn set_clock_polarity>(&mut self, cpol: P) { 128 | self.pol = cpol.into() 129 | } 130 | } 131 | 132 | impl eh0::blocking::spi::Write for Spi 133 | where 134 | Device: MpsseCmdExecutor, 135 | E: std::error::Error, 136 | Error: From, 137 | { 138 | type Error = Error; 139 | 140 | fn write(&mut self, words: &[u8]) -> Result<(), Error> { 141 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 142 | .clock_data_out(self.pol.clk_out, words) 143 | .send_immediate(); 144 | 145 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 146 | lock.ft.send(cmd.as_slice())?; 147 | 148 | Ok(()) 149 | } 150 | } 151 | 152 | impl eh0::blocking::spi::Transfer for Spi 153 | where 154 | Device: MpsseCmdExecutor, 155 | E: std::error::Error, 156 | Error: From, 157 | { 158 | type Error = Error; 159 | 160 | fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Error> { 161 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 162 | .clock_data(self.pol.clk, words) 163 | .send_immediate(); 164 | 165 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 166 | lock.ft.send(cmd.as_slice())?; 167 | lock.ft.recv(words)?; 168 | 169 | Ok(words) 170 | } 171 | } 172 | 173 | impl eh0::spi::FullDuplex for Spi 174 | where 175 | Device: MpsseCmdExecutor, 176 | E: std::error::Error, 177 | Error: From, 178 | { 179 | type Error = Error; 180 | 181 | fn read(&mut self) -> nb::Result> { 182 | let mut buf: [u8; 1] = [0]; 183 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 184 | .clock_data(self.pol.clk, &buf) 185 | .send_immediate(); 186 | 187 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 188 | match lock.ft.xfer(cmd.as_slice(), &mut buf) { 189 | Ok(()) => Ok(buf[0]), 190 | Err(e) => Err(nb::Error::Other(Error::from(e))), 191 | } 192 | } 193 | 194 | fn send(&mut self, byte: u8) -> nb::Result<(), Error> { 195 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 196 | .clock_data_out(self.pol.clk_out, &[byte]) 197 | .send_immediate(); 198 | 199 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 200 | match lock.ft.send(cmd.as_slice()) { 201 | Ok(()) => Ok(()), 202 | Err(e) => Err(nb::Error::Other(Error::from(e))), 203 | } 204 | } 205 | } 206 | 207 | impl eh1::spi::Error for Error 208 | where 209 | E: std::error::Error, 210 | Error: From, 211 | { 212 | fn kind(&self) -> eh1::spi::ErrorKind { 213 | eh1::spi::ErrorKind::Other 214 | } 215 | } 216 | 217 | impl eh1::spi::ErrorType for Spi 218 | where 219 | Device: MpsseCmdExecutor, 220 | E: std::error::Error, 221 | Error: From, 222 | { 223 | type Error = Error; 224 | } 225 | 226 | impl eh1::spi::SpiBus for Spi 227 | where 228 | Device: MpsseCmdExecutor, 229 | E: std::error::Error, 230 | Error: From, 231 | { 232 | fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { 233 | let data_out: Vec = vec![0; words.len()]; 234 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 235 | .clock_data(self.pol.clk, &data_out) 236 | .send_immediate(); 237 | 238 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 239 | lock.ft.send(cmd.as_slice())?; 240 | lock.ft.recv(words)?; 241 | 242 | Ok(()) 243 | } 244 | 245 | fn write(&mut self, words: &[u8]) -> Result<(), Error> { 246 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 247 | .clock_data_out(self.pol.clk_out, words) 248 | .send_immediate(); 249 | 250 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 251 | lock.ft.send(cmd.as_slice())?; 252 | 253 | Ok(()) 254 | } 255 | 256 | fn flush(&mut self) -> Result<(), Self::Error> { 257 | Ok(()) 258 | } 259 | 260 | fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { 261 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 262 | .clock_data(self.pol.clk, words) 263 | .send_immediate(); 264 | 265 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 266 | 267 | lock.ft.send(cmd.as_slice())?; 268 | lock.ft.recv(words)?; 269 | 270 | Ok(()) 271 | } 272 | 273 | fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { 274 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 275 | .clock_data(self.pol.clk, write) 276 | .send_immediate(); 277 | 278 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 279 | lock.ft.send(cmd.as_slice())?; 280 | lock.ft.recv(read)?; 281 | 282 | let remain: usize = write.len().saturating_sub(read.len()); 283 | if remain != 0 { 284 | let mut remain_buf: Vec = vec![0; remain]; 285 | lock.ft.recv(&mut remain_buf)?; 286 | } 287 | 288 | Ok(()) 289 | } 290 | } 291 | 292 | impl ehnb1::spi::FullDuplex for Spi 293 | where 294 | Device: MpsseCmdExecutor, 295 | E: std::error::Error, 296 | Error: From, 297 | { 298 | fn read(&mut self) -> nb::Result> { 299 | let mut buf: [u8; 1] = [0]; 300 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 301 | .clock_data(self.pol.clk, &buf) 302 | .send_immediate(); 303 | 304 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 305 | match lock.ft.xfer(cmd.as_slice(), &mut buf) { 306 | Ok(()) => Ok(buf[0]), 307 | Err(e) => Err(nb::Error::Other(Error::from(e))), 308 | } 309 | } 310 | 311 | fn write(&mut self, byte: u8) -> nb::Result<(), Error> { 312 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 313 | .clock_data_out(self.pol.clk_out, &[byte]) 314 | .send_immediate(); 315 | 316 | let mut lock = self.mtx.lock().expect("Failed to aquire FTDI mutex"); 317 | match lock.ft.send(cmd.as_slice()) { 318 | Ok(()) => Ok(()), 319 | Err(e) => Err(nb::Error::Other(Error::from(e))), 320 | } 321 | } 322 | } 323 | 324 | pub struct SpiDeviceBus<'a, Device: MpsseCmdExecutor> { 325 | lock: MutexGuard<'a, FtInner>, 326 | pol: Polarity, 327 | } 328 | 329 | impl eh1::spi::ErrorType for SpiDeviceBus<'_, Device> 330 | where 331 | Device: MpsseCmdExecutor, 332 | E: std::error::Error, 333 | Error: From, 334 | { 335 | type Error = Error; 336 | } 337 | 338 | impl eh1::spi::SpiBus for SpiDeviceBus<'_, Device> 339 | where 340 | Device: MpsseCmdExecutor, 341 | E: std::error::Error, 342 | Error: From, 343 | { 344 | fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { 345 | self.lock.ft.xfer( 346 | MpsseCmdBuilder::new() 347 | .clock_data(self.pol.clk, words) 348 | .send_immediate() 349 | .as_slice(), 350 | words, 351 | )?; 352 | Ok(()) 353 | } 354 | 355 | fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { 356 | self.lock.ft.send( 357 | MpsseCmdBuilder::new() 358 | .clock_data_out(self.pol.clk_out, words) 359 | .send_immediate() 360 | .as_slice(), 361 | )?; 362 | Ok(()) 363 | } 364 | 365 | fn flush(&mut self) -> Result<(), Self::Error> { 366 | Ok(()) 367 | } 368 | 369 | fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { 370 | self.lock.ft.xfer( 371 | MpsseCmdBuilder::new() 372 | .clock_data(self.pol.clk, write) 373 | .send_immediate() 374 | .as_slice(), 375 | read, 376 | )?; 377 | Ok(()) 378 | } 379 | 380 | fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { 381 | self.lock.ft.xfer( 382 | MpsseCmdBuilder::new() 383 | .clock_data(self.pol.clk, words) 384 | .send_immediate() 385 | .as_slice(), 386 | words, 387 | )?; 388 | Ok(()) 389 | } 390 | } 391 | 392 | /// FTDI SPI device, a SPI bus with chip select pin. 393 | /// 394 | /// This is created by calling [`FtHal::spi_device`]. 395 | /// 396 | /// This is specific to embedded-hal version 1. 397 | /// 398 | /// [`FtHal::spi_device`]: crate::FtHal::spi_device 399 | #[derive(Debug)] 400 | pub struct SpiDevice { 401 | /// Parent FTDI device. 402 | mtx: Arc>>, 403 | /// SPI polarity 404 | pol: Polarity, 405 | /// Chip select pin index. 0-7 for the FT232H. 406 | cs_idx: u8, 407 | } 408 | 409 | impl SpiDevice 410 | where 411 | Device: MpsseCmdExecutor, 412 | E: std::error::Error, 413 | Error: From, 414 | { 415 | pub(crate) fn new( 416 | mtx: Arc>>, 417 | cs_idx: u8, 418 | ) -> Result, Error> { 419 | { 420 | let mut lock = mtx.lock().expect("Failed to aquire FTDI mutex"); 421 | lock.allocate_pin(0, PinUse::Spi); 422 | lock.allocate_pin(1, PinUse::Spi); 423 | lock.allocate_pin(2, PinUse::Spi); 424 | lock.allocate_pin(cs_idx, PinUse::Output); 425 | 426 | let cs_mask: u8 = 1 << cs_idx; 427 | 428 | // clear direction of first 3 pins and CS 429 | lock.direction &= !(0x07 | cs_mask); 430 | // set SCK (AD0) and MOSI (AD1), and CS as output pins 431 | lock.direction |= 0x03 | cs_mask; 432 | 433 | // set GPIO pins to new state 434 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 435 | .set_gpio_lower(lock.value, lock.direction) 436 | .send_immediate(); 437 | lock.ft.send(cmd.as_slice())?; 438 | } 439 | 440 | Ok(Self { 441 | mtx, 442 | pol: Default::default(), 443 | cs_idx, 444 | }) 445 | } 446 | 447 | pub(crate) fn cs_mask(&self) -> u8 { 448 | 1 << self.cs_idx 449 | } 450 | 451 | /// Set the SPI clock polarity. 452 | /// 453 | /// FTD2XX devices only supports [SPI mode] 0 and 2, clock phase is fixed. 454 | /// 455 | /// # Example 456 | /// 457 | /// ```no_run 458 | /// use eh1::spi::Polarity; 459 | /// use ftdi_embedded_hal as hal; 460 | /// 461 | /// # #[cfg(feature = "libftd2xx")] 462 | /// # { 463 | /// let device = libftd2xx::Ft2232h::with_description("Dual RS232-HS A")?; 464 | /// let hal = hal::FtHal::init_freq(device, 3_000_000)?; 465 | /// let mut spi = hal.spi_device(3)?; 466 | /// spi.set_clock_polarity(Polarity::IdleLow); 467 | /// # } 468 | /// # Ok::<(), std::boxed::Box>(()) 469 | /// ``` 470 | /// 471 | /// [SPI mode]: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface#Mode_numbers 472 | pub fn set_clock_polarity>(&mut self, cpol: P) { 473 | self.pol = cpol.into() 474 | } 475 | } 476 | 477 | impl eh1::spi::ErrorType for SpiDevice 478 | where 479 | Device: MpsseCmdExecutor, 480 | E: std::error::Error, 481 | Error: From, 482 | { 483 | type Error = Error; 484 | } 485 | 486 | impl eh1::spi::SpiDevice for SpiDevice 487 | where 488 | Device: MpsseCmdExecutor, 489 | E: std::error::Error, 490 | Error: From, 491 | { 492 | fn transaction( 493 | &mut self, 494 | operations: &mut [eh1::spi::Operation<'_, u8>], 495 | ) -> Result<(), Self::Error> { 496 | // lock the bus 497 | let mut lock: MutexGuard> = 498 | self.mtx.lock().expect("Failed to aquire FTDI mutex"); 499 | let direction: u8 = lock.direction; 500 | 501 | // assert the chip select pin 502 | let mut value_cs_asserted: u8 = lock.value & !self.cs_mask(); 503 | 504 | // drive pin 0, the clock pin according to the CPOL setting 505 | // Reference: https://github.com/ftdi-rs/ftdi-embedded-hal/pull/69 506 | match self.pol.clk { 507 | ClockData::MsbNegIn | ClockData::LsbNegIn => { 508 | value_cs_asserted |= 1; 509 | } 510 | ClockData::MsbPosIn | ClockData::LsbPosIn => { 511 | value_cs_asserted &= !1; 512 | } 513 | } 514 | 515 | lock.ft.send( 516 | MpsseCmdBuilder::new() 517 | .set_gpio_lower(value_cs_asserted, direction) 518 | .send_immediate() 519 | .as_slice(), 520 | )?; 521 | 522 | let mut bus: SpiDeviceBus = SpiDeviceBus { 523 | lock, 524 | pol: self.pol, 525 | }; 526 | 527 | for op in operations { 528 | match op { 529 | eh1::spi::Operation::Read(buffer) => { 530 | eh1::spi::SpiBus::read(&mut bus, buffer)?; 531 | } 532 | eh1::spi::Operation::Write(buffer) => { 533 | eh1::spi::SpiBus::write(&mut bus, buffer)?; 534 | } 535 | eh1::spi::Operation::Transfer(read, write) => { 536 | eh1::spi::SpiBus::transfer(&mut bus, read, write)?; 537 | } 538 | eh1::spi::Operation::TransferInPlace(buffer) => { 539 | eh1::spi::SpiBus::transfer_in_place(&mut bus, buffer)?; 540 | } 541 | eh1::spi::Operation::DelayNs(micros) => { 542 | std::thread::sleep(std::time::Duration::from_nanos((*micros).into())); 543 | } 544 | } 545 | } 546 | 547 | // flush the bus 548 | { 549 | use eh1::spi::SpiBus; 550 | bus.flush()?; 551 | } 552 | 553 | let mut lock: MutexGuard> = bus.lock; 554 | 555 | // deassert the chip select pin 556 | let mut value_cs_deasserted: u8 = lock.value | self.cs_mask(); 557 | 558 | // drive pin 0, the clock pin according to the CPOL setting 559 | // Reference: https://github.com/ftdi-rs/ftdi-embedded-hal/pull/69 560 | match self.pol.clk { 561 | ClockData::MsbNegIn | ClockData::LsbNegIn => { 562 | value_cs_deasserted |= 1; 563 | } 564 | ClockData::MsbPosIn | ClockData::LsbPosIn => { 565 | value_cs_deasserted &= !1; 566 | } 567 | } 568 | 569 | lock.ft.send( 570 | MpsseCmdBuilder::new() 571 | .set_gpio_lower(value_cs_deasserted, direction) 572 | .send_immediate() 573 | .as_slice(), 574 | )?; 575 | 576 | // unlocking the bus is implicit via Drop 577 | Ok(()) 578 | } 579 | } 580 | -------------------------------------------------------------------------------- /tests/issue_18.rs: -------------------------------------------------------------------------------- 1 | // https://github.com/ftdi-rs/ftdi-embedded-hal/issues/18 2 | #[test] 3 | #[ignore] // compile only 4 | #[allow(dead_code)] 5 | #[cfg_attr(not(feature = "libftd2xx"), allow(unused_imports))] 6 | fn issue_18() { 7 | use ftdi_embedded_hal as hal; 8 | use std::sync::Arc; 9 | 10 | #[cfg(feature = "libftd2xx")] 11 | fn open() -> Result, Box> { 12 | let device = libftd2xx::Ft4232h::with_description("Dual RS232-HS A")?; 13 | let hal = Arc::new(hal::FtHal::init_freq(device, 3_000_000)?); 14 | let spi_dev = hal.spi_device(3)?; 15 | Ok(spi_dev) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/version-numbers.rs: -------------------------------------------------------------------------------- 1 | // See https://crates.io/crates/version-sync 2 | 3 | #[test] 4 | fn test_readme_deps() { 5 | version_sync::assert_markdown_deps_updated!("README.md"); 6 | } 7 | --------------------------------------------------------------------------------