├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── examples ├── at24c04-test1.rs ├── lm75-test1.rs ├── mcp3008-test1.rs ├── nrf24-test1.rs ├── nrf24-test2.rs ├── nrf24-test3.rs └── ws2812-test1.rs ├── pics ├── ft232h-i2c-example1.png └── ft232h-spi-example1.png └── src ├── error.rs ├── ftdimpsse.rs ├── gpio.rs ├── i2c.rs ├── lib.rs ├── spi.rs └── x232h.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | target: x86_64-unknown-linux-gnu 18 | override: true 19 | - name: Install libftdi 20 | run: sudo apt-get -y install libftdi1-2 libftdi1-dev 21 | - name: Build ftdi-embedded-hal library 22 | run: cargo build --lib 23 | - name: Build ftdi-embedded-hal examples 24 | run: cargo build --examples 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | target 6 | **/*.rs.bk 7 | .gdb_history 8 | Cargo.lock 9 | rusty-tags.vi 10 | .idea 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ftdi-embedded-hal" 3 | version = "0.1.0" 4 | authors = ["Sergey Matyukevich "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | nb = "1.0.0" 9 | rand = "0.8" 10 | itertools = "0.10" 11 | embedded-hal = "0.2" 12 | ftdi = "0.1" 13 | 14 | [dev-dependencies] 15 | embedded-nrf24l01 = "0.2.0" 16 | eeprom24x = "0.3.0" 17 | lm75 = "0.1.2" 18 | 19 | [dev-dependencies.adc-mcp3008] 20 | git = "https://github.com/geomatsi/adc-mcp3008.git" 21 | branch = "misc-updates" 22 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTE 2 | 3 | Check new [ftdi-embedded-hal](https://github.com/ftdi-rs/ftdi-embedded-hal) project in the [ftdi-rs](https://github.com/ftdi-rs) organization for the up-to-date implementation of FTDI MPSSE embedded HAL. 4 | 5 | --- 6 | 7 | # `ftdi-embedded-hal` 8 | Implementation of the [embedded-hal](https://crates.io/crates/embedded-hal) traits for [FTDI chips](https://www.ftdichip.com) supporting [Multi Protocol Synchronous Serial Engine (MPSSE)](https://www.ftdichip.com/Support/SoftwareExamples/MPSSE/LibMPSSE-SPI.htm). 9 | 10 | This implementation of `embedded-hal` allows you to work with various SPI/I2C devices over USB. If your Linux board has accessible I2C or SPI pins, then it is possible to connect I2C/SPI devices directly. For this purpose [`linux-embedded-hal`](https://github.com/rust-embedded/linux-embedded-hal) implementation of `embedded-hal` traits can be used. However normal Linux workstations usually do not have direct access to SPI/I2C, but have enough USB ports. In this case `ftdi-embedded-hal` can be used. For instance, this may come particularly handy for playing with various I2C/SPI devices, e.g. trying them out or working on driver development. 11 | 12 | ## Devices 13 | Currently support for GPIO/SPI/I2C on FT232H and FT2232H chips has been implemented. There are many development boards and modules for FTDI chips. 14 | The following modules have been used for development and testing: 15 | - FT232H development module [UM232H](https://www.ftdichip.com/Products/Modules/DevelopmentModules.htm#UM232H) 16 | - FT232H development module [CJMCU-232H](https://www.elecrow.com/cjmcu-ft232h-high-speed-multifunctional-usb-to-jtag-uart-fifo-spi-i2c-module-for-arduino.html) 17 | - [FT2232H development module](https://aliexpress.com/item/EYEWINK-FT2232HL-development-board-learning-board-FT2232H-MINI-FT4232H-UM232H-development-board/32806818411.html) 18 | 19 | ## Features, limitations, TODO 20 | Brief list of features supported out of the box: 21 | - supported devices: FT232H, FT2232H 22 | - MPSSE mode is supported, no pure bit-banging support 23 | - SPI/I2C/GPIO support 24 | - SPI 25 | - MODE0, MODE2 26 | - 500kHz, 1MHz, 3MHz, 5MHz 27 | - I2C bus 28 | - 100kHz, 400kHz 29 | - GPIO 30 | - output mode only 31 | 32 | Limitations: 33 | - FTDI device is selected by USB PID/VID, so only the first connected device will be handled 34 | - no gpio input support 35 | - i2c/spi are mutually exclusive due to h/w circuitry (not sure if anything can be done from s/w side to support both simultaneously) 36 | - GPIO functionality is disabled for ADBUS[0..3] and BDBUS[0..4] pins (SK/DI/DO/CS) 37 | - no support for SPI MODE1 and MODE3 (not sure if anything can be done from s/w side as this is a limitation for FTDI chips) 38 | - not enough flexibility in clock selection 39 | - fixed list of suppported SPI/I2C frequencies 40 | - clock is selected once for the first bus instance and should be the same for all the other bus instances, runtime changes are not supported 41 | - tests can be run only when FTDI devices are connected 42 | 43 | TODO: 44 | - [ ] more cleanup and refactoring in terms of idiomatic Rust 45 | - [ ] suppport several connected FTDI devices with the same USB PID/VID 46 | - [ ] enable ADBUS[0..3] and BDBUS[0..3] pins as GPIO pins as well, just keep track of selected functionality, it is already done anyway to disable i2c and spi at the same time 47 | - [ ] add gpio input support 48 | - [ ] add support for FT4232H 49 | - [ ] more flexibility in clock configuration (?) 50 | - [ ] add more I2C/SPI/GPIO examples/tests, e.g. 74HC595, [MCP3008](https://crates.io/crates/mcp3008), [PCF8574](https://crates.io/crates/pcf857x), and more 51 | 52 | ## Circuit examples 53 | ### SPI nRF24L01+ breakout board 54 | ![alt text](pics/ft232h-spi-example1.png) 55 | 56 | ### I2C EEPROM at24c04 57 | ![alt text](pics/ft232h-i2c-example1.png) 58 | 59 | ## Useful documents and links 60 | Projects 61 | - [`ftdi` - Rust wrapper over libftdi1 library for FTDI devices](https://crates.io/crates/ftdi) 62 | - [`embedded-hal` - Hardware Abstraction Layer (HAL) for embedded systems](https://crates.io/crates/embedded-hal) 63 | - [`linux-embedded-hal` - implementation of the `embedded-hal` traits for Linux devices](https://github.com/rust-embedded/linux-embedded-hal) 64 | - [device driver crates that use `embedded-hal` interface](https://github.com/rust-embedded/awesome-embedded-rust#driver-crates) 65 | 66 | Data sheets for FTDI chips and development modules: 67 | - [FT232H datasheet](https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232H.pdf) 68 | - [FT2232H datasheet](https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT2232H.pdf) 69 | - [UM232H module datasheet](https://www.ftdichip.com/Support/Documents/DataSheets/Modules/DS_UM232H.pdf) 70 | 71 | Application notes provided by FTDI: 72 | - [AN 135: FTDI MPSSE Basics](https://www.ftdichip.com/Support/Documents/AppNotes/AN_135_MPSSE_Basics.pdf) 73 | - [AN 108: Command Processor for MPSSE and MCU Host Bus Emulation Modes](https://www.ftdichip.com/Support/Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf) 74 | - [AN 113: Interfacing FT2232H Hi-Speed Devices to I2C Bus](https://www.ftdichip.com/Support/Documents/AppNotes/AN_113_FTDI_Hi_Speed_USB_To_I2C_Example.pdf) 75 | - [AN 114: Interfacing FT2232H Hi-Speed Devices to SPI Bus](https://www.ftdichip.com/Support/Documents/AppNotes/AN_114_FTDI_Hi_Speed_USB_To_SPI_Example.pdf) 76 | - [Interfacing FTDI USB Hi-Speed Devices to a JTAG TAP](https://www.ftdichip.com/Support/Documents/AppNotes/AN_129_FTDI_Hi_Speed_USB_To_JTAG_Example.pdf) 77 | -------------------------------------------------------------------------------- /examples/at24c04-test1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Run example tests: cargo test --example=at24c04-test1 -- --test-threads=1"); 3 | } 4 | 5 | #[cfg(test)] 6 | mod test { 7 | use eeprom24x::Eeprom24x; 8 | use eeprom24x::SlaveAddr; 9 | use ftdi_embedded_hal as hal; 10 | use ftdi_embedded_hal::x232h::FTx232H; 11 | use std::thread::sleep; 12 | use std::time::Duration; 13 | 14 | #[test] 15 | fn at24x_test_t1() { 16 | let dev = FTx232H::init(0x0403, 0x6014).unwrap(); 17 | let i2c = dev.i2c(hal::i2c::I2cSpeed::CLK_400kHz).unwrap(); 18 | let mut eeprom = Eeprom24x::new_24x04(i2c, SlaveAddr::default()); 19 | 20 | let delay = Duration::from_millis(5); 21 | let byte_w = 0xe5; 22 | let addr = 0x0; 23 | 24 | eeprom.write_byte(addr, byte_w).unwrap(); 25 | sleep(delay); 26 | 27 | let byte_r = eeprom.read_byte(addr).unwrap(); 28 | 29 | assert_eq!(byte_w, byte_r); 30 | } 31 | 32 | #[test] 33 | fn at24x_test_t2() { 34 | let dev = FTx232H::init(0x0403, 0x6014).unwrap(); 35 | let i2c = dev.i2c(hal::i2c::I2cSpeed::CLK_400kHz).unwrap(); 36 | let mut eeprom = Eeprom24x::new_24x04(i2c, SlaveAddr::default()); 37 | 38 | let delay = Duration::from_millis(5); 39 | let data_w: [u8; 4] = [0xaa, 0xbb, 0xcc, 0xdd]; 40 | let mut data_r: [u8; 4] = [0; 4]; 41 | 42 | for i in 0..data_w.len() { 43 | eeprom.write_byte(i as u32, data_w[i]).unwrap(); 44 | sleep(delay); 45 | } 46 | 47 | for i in 0..data_r.len() { 48 | data_r[i] = eeprom.read_byte(i as u32).unwrap(); 49 | } 50 | 51 | assert_eq!(data_w, data_r); 52 | } 53 | 54 | #[test] 55 | fn at24x_test_t3() { 56 | let dev = FTx232H::init(0x0403, 0x6014).unwrap(); 57 | let i2c = dev.i2c(hal::i2c::I2cSpeed::CLK_400kHz).unwrap(); 58 | let mut eeprom = Eeprom24x::new_24x04(i2c, SlaveAddr::default()); 59 | 60 | let delay = Duration::from_millis(5); 61 | let data_w: [u8; 4] = [0xaa, 0xbb, 0xcc, 0xdd]; 62 | let mut data_r: [u8; 4] = [0; 4]; 63 | 64 | for i in 0..data_w.len() { 65 | eeprom.write_byte(i as u32, data_w[i]).unwrap(); 66 | sleep(delay); 67 | } 68 | 69 | eeprom.read_data(0x0, &mut data_r).unwrap(); 70 | 71 | assert_eq!(data_w, data_r); 72 | } 73 | 74 | #[test] 75 | fn at24x_test_t4() { 76 | let dev = FTx232H::init(0x0403, 0x6014).unwrap(); 77 | let i2c = dev.i2c(hal::i2c::I2cSpeed::CLK_400kHz).unwrap(); 78 | let mut eeprom = Eeprom24x::new_24x04(i2c, SlaveAddr::default()); 79 | 80 | let delay = Duration::from_millis(50); 81 | let addrs: [u32; 4] = [0x00, 0x10, 0x20, 0x30]; 82 | let mut data_r = [0x00; 16]; 83 | let data_w = [0xAB; 16]; 84 | 85 | for addr in addrs.iter() { 86 | eeprom.write_page(*addr, &data_w).unwrap(); 87 | sleep(delay); 88 | eeprom.read_data(*addr, &mut data_r).unwrap(); 89 | assert_eq!(data_w, data_r); 90 | } 91 | } 92 | 93 | #[test] 94 | fn at24x_test_t5() { 95 | let dev = FTx232H::init(0x0403, 0x6014).unwrap(); 96 | let i2c = dev.i2c(hal::i2c::I2cSpeed::CLK_400kHz).unwrap(); 97 | let mut eeprom = Eeprom24x::new_24x04(i2c, SlaveAddr::default()); 98 | let delay = Duration::from_millis(5); 99 | 100 | // check high memory addresses: 1 bit passed as a part of i2c addr 101 | let addrs1: [u32; 4] = [0x100, 0x10F, 0x1F0, 0x1EE]; 102 | let byte_w1 = 0xe5; 103 | let addrs2: [u32; 4] = [0x00, 0x0F, 0xF0, 0xEE]; 104 | let byte_w2 = 0xaa; 105 | 106 | // write bytes 107 | 108 | for addr in addrs1.iter() { 109 | eeprom.write_byte(*addr, byte_w1).unwrap(); 110 | sleep(delay); 111 | } 112 | 113 | for addr in addrs2.iter() { 114 | eeprom.write_byte(*addr, byte_w2).unwrap(); 115 | sleep(delay); 116 | } 117 | 118 | // read bytes and check 119 | 120 | for addr in addrs1.iter() { 121 | let byte_r = eeprom.read_byte(*addr).unwrap(); 122 | assert_eq!(byte_w1, byte_r); 123 | sleep(delay); 124 | } 125 | 126 | for addr in addrs2.iter() { 127 | let byte_r = eeprom.read_byte(*addr).unwrap(); 128 | assert_eq!(byte_w2, byte_r); 129 | sleep(delay); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /examples/lm75-test1.rs: -------------------------------------------------------------------------------- 1 | use ftdi_embedded_hal as hal; 2 | use hal::x232h::FTx232H; 3 | use lm75::{Lm75, SlaveAddr}; 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | 7 | fn main() { 8 | let dev = FTx232H::init(0x0403, 0x6010).unwrap(); 9 | let i2c = dev.i2c(hal::i2c::I2cSpeed::CLK_400kHz).unwrap(); 10 | let mut sensor = Lm75::new(i2c, SlaveAddr::default()); 11 | let delay = Duration::from_secs(1); 12 | 13 | for _ in 0..5 { 14 | let temperature = sensor.read_temperature().unwrap(); 15 | println!("Temperature: {}", temperature); 16 | sleep(delay); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/mcp3008-test1.rs: -------------------------------------------------------------------------------- 1 | use adc_mcp3008::*; 2 | use ftdi_embedded_hal as hal; 3 | use std::thread::sleep; 4 | use std::time::Duration; 5 | 6 | fn main() { 7 | let dev = hal::x232h::FTx232H::init(0x0403, 0x6014).unwrap(); 8 | let spi = dev.spi(hal::spi::SpiSpeed::CLK_1MHz).unwrap(); 9 | let ncs = dev.pl2().unwrap(); 10 | 11 | let mut adc = Mcp3008::new(spi, ncs).unwrap(); 12 | 13 | loop { 14 | println!("CH0: {:?}", adc.read_channel(Channels8::CH0)); 15 | println!("CH1: {:?}", adc.read_channel(Channels8::CH1)); 16 | println!("CH2: {:?}", adc.read_channel(Channels8::CH2)); 17 | println!("CH3: {:?}", adc.read_channel(Channels8::CH3)); 18 | println!("CH4: {:?}", adc.read_channel(Channels8::CH4)); 19 | println!("CH5: {:?}", adc.read_channel(Channels8::CH5)); 20 | println!("CH6: {:?}", adc.read_channel(Channels8::CH6)); 21 | println!("CH7: {:?}", adc.read_channel(Channels8::CH7)); 22 | sleep(Duration::from_millis(1000)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/nrf24-test1.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::{blocking::spi::Transfer, digital::v2::OutputPin}; 2 | use ftdi_embedded_hal as hal; 3 | use hal::x232h::FTx232H; 4 | 5 | fn main() { 6 | let regs: Vec = vec![0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9]; 7 | 8 | let dev = FTx232H::init(0x0403, 0x6014).unwrap(); 9 | let mut spidev = dev.spi(hal::spi::SpiSpeed::CLK_1MHz).unwrap(); 10 | let mut pl2 = dev.pl2().unwrap(); 11 | 12 | // This example refers to specific schematics: 13 | // nRF24 CSN pin is connected to PinL2 rather than TMS/CS pin 14 | for r in regs { 15 | pl2.set_low().unwrap(); 16 | 17 | // send command: read register r 18 | let mut cmd = [0x1F & r; 1]; 19 | spidev.transfer(&mut cmd).unwrap(); 20 | 21 | // send dummy value: read previous cmd result 22 | let mut dummy = [0xff]; 23 | let regval = spidev.transfer(&mut dummy).unwrap(); 24 | 25 | pl2.set_high().unwrap(); 26 | 27 | println!("REG[0x{:x}] = [{:08b}]", r, regval[0]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/nrf24-test2.rs: -------------------------------------------------------------------------------- 1 | use embedded_nrf24l01::Configuration; 2 | use embedded_nrf24l01::CrcMode; 3 | use embedded_nrf24l01::DataRate; 4 | use embedded_nrf24l01::NRF24L01; 5 | use ftdi_embedded_hal as hal; 6 | use hal::x232h::FTx232H; 7 | use std::thread::sleep; 8 | use std::time::Duration; 9 | 10 | // Simple Tx test for embedded-nrf24l01 crate 11 | 12 | fn main() { 13 | let dev = FTx232H::init(0x0403, 0x6014).unwrap(); 14 | let spidev = dev.spi(hal::spi::SpiSpeed::CLK_1MHz).unwrap(); 15 | let ce = dev.pl1().unwrap(); 16 | let cs = dev.pl2().unwrap(); 17 | 18 | // nRF24L01 setup 19 | let mut nrf = NRF24L01::new(ce, cs, spidev).unwrap(); 20 | nrf.set_frequency(120).unwrap(); 21 | nrf.set_rf(&DataRate::R250Kbps, 3 /* 0 dBm */).unwrap(); 22 | nrf.set_crc(CrcMode::OneByte).unwrap(); 23 | nrf.set_auto_retransmit(0b0100, 0b1111).unwrap(); 24 | 25 | let addr: [u8; 5] = [0xe5, 0xe4, 0xe3, 0xe2, 0xe1]; 26 | nrf.set_rx_addr(0, &addr).unwrap(); 27 | nrf.set_tx_addr(&addr).unwrap(); 28 | nrf.set_pipes_rx_lengths(&[None; 6]).unwrap(); 29 | nrf.flush_tx().unwrap(); 30 | nrf.flush_rx().unwrap(); 31 | 32 | let delay = Duration::from_millis(1000); 33 | let mut tx = nrf.tx().unwrap(); 34 | let msg = b"hello"; 35 | 36 | sleep(delay); 37 | 38 | loop { 39 | println!("Tx: {:?}", msg); 40 | tx.send(msg).unwrap(); 41 | tx.wait_empty().unwrap(); 42 | 43 | sleep(delay); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/nrf24-test3.rs: -------------------------------------------------------------------------------- 1 | use embedded_nrf24l01::Configuration; 2 | use embedded_nrf24l01::CrcMode; 3 | use embedded_nrf24l01::DataRate; 4 | use embedded_nrf24l01::NRF24L01; 5 | use ftdi_embedded_hal as hal; 6 | use hal::x232h::FTx232H; 7 | use std::thread::sleep; 8 | use std::time::Duration; 9 | 10 | // Simple Rx test for embedded-nrf24l01 crate 11 | 12 | fn main() { 13 | let dev = FTx232H::init(0x0403, 0x6014).unwrap(); 14 | let spidev = dev.spi(hal::spi::SpiSpeed::CLK_1MHz).unwrap(); 15 | let ce = dev.pl1().unwrap(); 16 | let cs = dev.pl2().unwrap(); 17 | 18 | let mut nrf = NRF24L01::new(ce, cs, spidev).unwrap(); 19 | nrf.set_frequency(120).unwrap(); 20 | nrf.set_rf(&DataRate::R250Kbps, 3 /* 0 dBm */).unwrap(); 21 | nrf.set_crc(CrcMode::OneByte).unwrap(); 22 | nrf.set_auto_retransmit(0b0100, 0b1111).unwrap(); 23 | nrf.set_auto_ack(&[true; 6]).unwrap(); 24 | 25 | let addr: [u8; 5] = [0xe5, 0xe4, 0xe3, 0xe2, 0xe1]; 26 | nrf.set_rx_addr(0, &addr).unwrap(); 27 | nrf.set_pipes_rx_lengths(&[None; 6]).unwrap(); 28 | nrf.set_pipes_rx_enable(&[true, false, false, false, false, false]) 29 | .unwrap(); 30 | nrf.flush_tx().unwrap(); 31 | nrf.flush_rx().unwrap(); 32 | 33 | let delay = Duration::from_millis(500); 34 | let mut rx = nrf.rx().unwrap(); 35 | 36 | loop { 37 | let pipe = rx.can_read().unwrap(); 38 | 39 | if pipe.is_some() { 40 | let data = rx.read().unwrap(); 41 | println!("Rx: {:?}", data.as_ref()); 42 | } 43 | 44 | sleep(delay); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/ws2812-test1.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::blocking::spi::Write; 2 | use ftdi_embedded_hal as hal; 3 | use hal::x232h::FTx232H; 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | 7 | fn main() { 8 | let dev = FTx232H::init(0x0403, 0x6014).unwrap(); 9 | let mut spi = dev.spi(hal::spi::SpiSpeed::CLK_3MHz).unwrap(); 10 | 11 | // spi sequence for ws2812 color value 0x10 12 | let b1 = [0x92, 0x69, 0x24]; 13 | 14 | // spi sequence for ws2812 color value 0x00 15 | let b0 = [0x92, 0x49, 0x24]; 16 | 17 | // spi sequences for single led of specific color 18 | let g = [b1, b0, b0]; 19 | let r = [b0, b1, b0]; 20 | let b = [b0, b0, b1]; 21 | let x = [b0, b0, b0]; 22 | 23 | // initial pattern 24 | let mut pattern = vec![r, r, g, g, x, x, b, b]; 25 | 26 | println!("ready to go..."); 27 | 28 | loop { 29 | println!("next pattern..."); 30 | let stream = pattern 31 | .clone() 32 | .into_iter() 33 | .flatten() 34 | .into_iter() 35 | .flatten() 36 | .collect::>(); 37 | 38 | spi.write(stream.as_slice()).unwrap(); 39 | sleep(Duration::from_millis(400)); 40 | // rotate pattern 41 | pattern.rotate_right(1); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pics/ft232h-i2c-example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geomatsi/ftdi-embedded-hal-archive/fbd9cadecae90bed0828fc9d3370ae2dc8a1e309/pics/ft232h-i2c-example1.png -------------------------------------------------------------------------------- /pics/ft232h-spi-example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geomatsi/ftdi-embedded-hal-archive/fbd9cadecae90bed0828fc9d3370ae2dc8a1e309/pics/ft232h-spi-example1.png -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use ftdi; 2 | use std::fmt; 3 | use std::io; 4 | 5 | pub type Result = std::result::Result; 6 | 7 | #[derive(Debug)] 8 | pub enum X232Error { 9 | Io(io::Error), 10 | FTDI(ftdi::Error), 11 | HAL(ErrorKind), 12 | } 13 | 14 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 15 | pub enum ErrorKind { 16 | InvalidParams, 17 | InvalidClock, 18 | BusBusy, 19 | I2cNoAck, 20 | GpioPinBusy, 21 | GpioInvalidPin, 22 | SpiModeNotSupported, 23 | } 24 | 25 | impl ErrorKind { 26 | fn as_str(&self) -> &str { 27 | match *self { 28 | ErrorKind::InvalidParams => "Invalid input params", 29 | ErrorKind::BusBusy => "Bus is busy", 30 | ErrorKind::InvalidClock => "Clock is not valid", 31 | ErrorKind::I2cNoAck => "No ACK from slave", 32 | ErrorKind::GpioPinBusy => "GPIO pin is already in use", 33 | ErrorKind::GpioInvalidPin => "No such GPIO pin", 34 | ErrorKind::SpiModeNotSupported => "Mode not supported", 35 | } 36 | } 37 | } 38 | 39 | impl fmt::Display for X232Error { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | match *self { 42 | X232Error::Io(ref err) => err.fmt(f), 43 | X232Error::FTDI(ref err) => err.fmt(f), 44 | X232Error::HAL(ref err) => write!(f, "A regular error occurred {:?}", err.as_str()), 45 | } 46 | } 47 | } 48 | 49 | impl From for X232Error { 50 | fn from(e: io::Error) -> Self { 51 | X232Error::Io(e) 52 | } 53 | } 54 | impl From for X232Error { 55 | fn from(e: ftdi::Error) -> Self { 56 | X232Error::FTDI(e) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ftdimpsse.rs: -------------------------------------------------------------------------------- 1 | //#![deny(missing_docs)] 2 | #![deny(unsafe_code)] 3 | 4 | use std::convert::From; 5 | use std::time::Duration; 6 | 7 | /// MPSSE opcodes. 8 | /// 9 | /// Exported for use by [`mpsse`] macro. May also be used for manual command array construction. 10 | /// 11 | /// Data clocking MPSSE commands are broken out into separate enums for API ergonomics: 12 | /// * [`ClockDataOut`] 13 | /// * [`ClockBitsOut`] 14 | /// * [`ClockDataIn`] 15 | /// * [`ClockBitsIn`] 16 | /// * [`ClockData`] 17 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 18 | #[repr(u8)] 19 | #[non_exhaustive] 20 | pub enum MpsseCmd { 21 | /// Used by [`set_gpio_lower`][`MpsseCmdBuilder::set_gpio_lower`]. 22 | SetDataBitsLowbyte = 0x80, 23 | /// Used by [`gpio_lower`][`MpsseCmdBuilder::gpio_lower`]. 24 | GetDataBitsLowbyte = 0x81, 25 | /// Used by [`set_gpio_upper`][`MpsseCmdBuilder::set_gpio_upper`]. 26 | SetDataBitsHighbyte = 0x82, 27 | /// Used by [`gpio_upper`][`MpsseCmdBuilder::gpio_upper`]. 28 | GetDataBitsHighbyte = 0x83, 29 | /// Used by [`enable_loopback`][`MpsseCmdBuilder::enable_loopback`]. 30 | EnableLoopback = 0x84, 31 | /// Used by [`disable_loopback`][`MpsseCmdBuilder::disable_loopback`]. 32 | DisableLoopback = 0x85, 33 | /// Used by [`set_clock`][`MpsseCmdBuilder::set_clock`]. 34 | SetClockFrequency = 0x86, 35 | /// Used by [`send_immediate`][`MpsseCmdBuilder::send_immediate`]. 36 | SendImmediate = 0x87, 37 | /// Used by [`wait_on_io_high`][`MpsseCmdBuilder::wait_on_io_high`]. 38 | WaitOnIOHigh = 0x88, 39 | /// Used by [`wait_on_io_low`][`MpsseCmdBuilder::wait_on_io_low`]. 40 | WaitOnIOLow = 0x89, 41 | /// Used by [`set_clock`][`MpsseCmdBuilder::set_clock`]. 42 | DisableClockDivide = 0x8A, 43 | /// Used by [`set_clock`][`MpsseCmdBuilder::set_clock`]. 44 | EnableClockDivide = 0x8B, 45 | /// Used by [`enable_3phase_data_clocking`][`MpsseCmdBuilder::enable_3phase_data_clocking`]. 46 | Enable3PhaseClocking = 0x8C, 47 | /// Used by [`disable_3phase_data_clocking`][`MpsseCmdBuilder::disable_3phase_data_clocking`]. 48 | Disable3PhaseClocking = 0x8D, 49 | /// Used by [`disable_adaptive_data_clocking`][`MpsseCmdBuilder::disable_adaptive_data_clocking`]. 50 | EnableAdaptiveClocking = 0x96, 51 | /// Used by [`enable_adaptive_data_clocking`][`MpsseCmdBuilder::enable_adaptive_data_clocking`]. 52 | DisableAdaptiveClocking = 0x97, 53 | // EnableDriveOnlyZero = 0x9E, 54 | } 55 | 56 | /// Modes for clocking data out of the FTDI device. 57 | /// 58 | /// This is an argument to the [`clock_data_out`] method. 59 | /// 60 | /// [`clock_data_out`]: MpsseCmdBuilder::clock_data_out 61 | #[repr(u8)] 62 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 63 | pub enum ClockDataOut { 64 | /// Positive clock edge MSB first. 65 | /// 66 | /// The data is sent MSB first. 67 | /// 68 | /// The data will change to the next bit on the rising edge of the CLK pin. 69 | MsbPos = 0x10, 70 | /// Negative clock edge MSB first. 71 | /// 72 | /// The data is sent MSB first. 73 | /// 74 | /// The data will change to the next bit on the falling edge of the CLK pin. 75 | MsbNeg = 0x11, 76 | /// Positive clock edge LSB first. 77 | /// 78 | /// The first bit in will be the LSB of the first byte and so on. 79 | /// 80 | /// The data will change to the next bit on the rising edge of the CLK pin. 81 | LsbPos = 0x18, 82 | /// Negative clock edge LSB first. 83 | /// 84 | /// The first bit in will be the LSB of the first byte and so on. 85 | /// 86 | /// The data will change to the next bit on the falling edge of the CLK pin. 87 | LsbNeg = 0x19, 88 | } 89 | 90 | impl From for u8 { 91 | fn from(value: ClockDataOut) -> u8 { 92 | value as u8 93 | } 94 | } 95 | 96 | /// Modes for clocking bits out of the FTDI device. 97 | /// 98 | /// This is an argument to the [`clock_bits_out`] method. 99 | /// 100 | /// [`clock_bits_out`]: MpsseCmdBuilder::clock_bits_out 101 | #[repr(u8)] 102 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 103 | pub enum ClockBitsOut { 104 | /// Positive clock edge MSB first. 105 | /// 106 | /// The data is sent MSB first (bit 7 first). 107 | /// 108 | /// The data will change to the next bit on the rising edge of the CLK pin. 109 | MsbPos = 0x12, 110 | /// Negative clock edge MSB first. 111 | /// 112 | /// The data is sent MSB first (bit 7 first). 113 | /// 114 | /// The data will change to the next bit on the falling edge of the CLK pin. 115 | MsbNeg = 0x13, 116 | /// Positive clock edge LSB first (bit 0 first). 117 | /// 118 | /// The first bit in will be the LSB of the first byte and so on. 119 | /// 120 | /// The data will change to the next bit on the rising edge of the CLK pin. 121 | LsbPos = 0x1A, 122 | /// Negative clock edge LSB first (bit 0 first). 123 | /// 124 | /// The first bit in will be the LSB of the first byte and so on. 125 | /// 126 | /// The data will change to the next bit on the falling edge of the CLK pin. 127 | LsbNeg = 0x1B, 128 | } 129 | 130 | impl From for u8 { 131 | fn from(value: ClockBitsOut) -> u8 { 132 | value as u8 133 | } 134 | } 135 | 136 | /// Modes for clocking data into the FTDI device. 137 | /// 138 | /// This is an argument to the [`clock_data_in`] method. 139 | /// 140 | /// [`clock_data_in`]: MpsseCmdBuilder::clock_data_in 141 | #[repr(u8)] 142 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 143 | pub enum ClockDataIn { 144 | /// Positive clock edge MSB first. 145 | /// 146 | /// The first bit in will be the MSB of the first byte and so on. 147 | /// 148 | /// The data will be sampled on the rising edge of the CLK pin. 149 | MsbPos = 0x20, 150 | /// Negative clock edge MSB first. 151 | /// 152 | /// The first bit in will be the MSB of the first byte and so on. 153 | /// 154 | /// The data will be sampled on the falling edge of the CLK pin. 155 | MsbNeg = 0x24, 156 | /// Positive clock edge LSB first. 157 | /// 158 | /// The first bit in will be the LSB of the first byte and so on. 159 | /// 160 | /// The data will be sampled on the rising edge of the CLK pin. 161 | LsbPos = 0x28, 162 | /// Negative clock edge LSB first. 163 | /// 164 | /// The first bit in will be the LSB of the first byte and so on. 165 | /// 166 | /// The data will be sampled on the falling edge of the CLK pin. 167 | LsbNeg = 0x2C, 168 | } 169 | 170 | impl From for u8 { 171 | fn from(value: ClockDataIn) -> u8 { 172 | value as u8 173 | } 174 | } 175 | 176 | /// Modes for clocking data bits into the FTDI device. 177 | /// 178 | /// This is an argument to the [`clock_bits_in`] method. 179 | /// 180 | /// [`clock_bits_in`]: MpsseCmdBuilder::clock_bits_in 181 | #[repr(u8)] 182 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 183 | pub enum ClockBitsIn { 184 | /// Positive clock edge MSB first. 185 | /// 186 | /// The data will be shifted up so that the first bit in may not be in bit 7 187 | /// but from 6 downwards depending on the number of bits to shift 188 | /// (i.e. a length of 1 bit will have the data bit sampled in bit 0 of the 189 | /// byte sent back to the PC). 190 | /// 191 | /// The data will be sampled on the rising edge of the CLK pin. 192 | MsbPos = 0x22, 193 | /// Negative clock edge MSB first. 194 | /// 195 | /// The data will be shifted up so that the first bit in may not be in bit 7 196 | /// but from 6 downwards depending on the number of bits to shift 197 | /// (i.e. a length of 1 bit will have the data bit sampled in bit 0 of the 198 | /// byte sent back to the PC). 199 | /// 200 | /// The data will be sampled on the falling edge of the CLK pin. 201 | MsbNeg = 0x26, 202 | /// Positive clock edge LSB first. 203 | /// 204 | /// The data will be shifted down so that the first bit in may not be in bit 205 | /// 0 but from 1 upwards depending on the number of bits to shift 206 | /// (i.e. a length of 1 bit will have the data bit sampled in bit 7 of the 207 | /// byte sent back to the PC). 208 | /// 209 | /// The data will be sampled on the rising edge of the CLK pin. 210 | LsbPos = 0x2A, 211 | /// Negative clock edge LSB first. 212 | /// 213 | /// The data will be shifted down so that thefirst bit in may not be in bit 214 | /// 0 but from 1 upwards depending on the number of bits to shift 215 | /// (i.e. a length of 1 bit will have the data bit sampled in bit 7 of the 216 | /// byte sent back to the PC). 217 | /// 218 | /// The data will be sampled on the falling edge of the CLK pin. 219 | LsbNeg = 0x2E, 220 | } 221 | 222 | impl From for u8 { 223 | fn from(value: ClockBitsIn) -> u8 { 224 | value as u8 225 | } 226 | } 227 | 228 | /// Modes for clocking data in and out of the FTDI device. 229 | /// 230 | /// This is an argument to the [`clock_data`] method. 231 | /// 232 | /// [`clock_data`]: MpsseCmdBuilder::clock_data 233 | #[repr(u8)] 234 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 235 | pub enum ClockData { 236 | /// MSB first, data in on positive edge, data out on negative edge. 237 | MsbPosIn = 0x31, 238 | /// MSB first, data in on negative edge, data out on positive edge. 239 | MsbNegIn = 0x34, 240 | /// LSB first, data in on positive edge, data out on negative edge. 241 | LsbPosIn = 0x39, 242 | /// LSB first, data in on negative edge, data out on positive edge. 243 | LsbNegIn = 0x3C, 244 | } 245 | 246 | impl From for u8 { 247 | fn from(value: ClockData) -> u8 { 248 | value as u8 249 | } 250 | } 251 | 252 | /// Modes for clocking data bits in and out of the FTDI device. 253 | /// 254 | /// This is an argument to the [`clock_bits`] method. 255 | /// 256 | /// [`clock_bits`]: MpsseCmdBuilder::clock_bits 257 | #[repr(u8)] 258 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 259 | pub enum ClockBits { 260 | /// MSB first, data in on positive edge, data out on negative edge. 261 | MsbPosIn = 0x33, 262 | /// MSB first, data in on negative edge, data out on positive edge. 263 | MsbNegIn = 0x36, 264 | /// LSB first, data in on positive edge, data out on negative edge. 265 | LsbPosIn = 0x3B, 266 | /// LSB first, data in on negative edge, data out on positive edge. 267 | LsbNegIn = 0x3E, 268 | } 269 | 270 | impl From for u8 { 271 | fn from(value: ClockBits) -> u8 { 272 | value as u8 273 | } 274 | } 275 | 276 | impl From for u8 { 277 | fn from(value: MpsseCmd) -> Self { 278 | value as u8 279 | } 280 | } 281 | 282 | /// Initialization settings for the MPSSE. 283 | /// 284 | /// Used by [`initialize_mpsse`]. 285 | /// 286 | /// [`initialize_mpsse`]: FtdiMpsse::initialize_mpsse 287 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 288 | pub struct MpsseSettings { 289 | /// Reset the MPSSE on initialization. 290 | /// 291 | /// This calls [`reset`] if `true`. 292 | /// 293 | /// [`reset`]: FtdiCommon::reset 294 | pub reset: bool, 295 | /// USB in transfer size in bytes. 296 | /// 297 | /// This gets passed to [`set_usb_parameters`]. 298 | /// 299 | /// [`set_usb_parameters`]: FtdiCommon::set_usb_parameters 300 | pub in_transfer_size: u32, 301 | /// Read timeout. 302 | /// 303 | /// This gets passed along with [`write_timeout`] to [`set_timeouts`]. 304 | /// 305 | /// [`set_timeouts`]: FtdiCommon::set_timeouts 306 | /// [`write_timeout`]: MpsseSettings::write_timeout 307 | pub read_timeout: Duration, 308 | /// Write timeout. 309 | /// 310 | /// This gets passed along with [`read_timeout`] to [`set_timeouts`]. 311 | /// 312 | /// [`set_timeouts`]: FtdiCommon::set_timeouts 313 | /// [`read_timeout`]: MpsseSettings::read_timeout 314 | pub write_timeout: Duration, 315 | /// Latency timer. 316 | /// 317 | /// This gets passed to [`set_latency_timer`]. 318 | /// 319 | /// [`set_latency_timer`]: FtdiCommon::set_latency_timer 320 | pub latency_timer: Duration, 321 | /// Bitmode mask. 322 | /// 323 | /// * A bit value of `0` sets the corresponding pin to an input. 324 | /// * A bit value of `1` sets the corresponding pin to an output. 325 | /// 326 | /// This gets passed to [`set_bit_mode`]. 327 | /// 328 | /// [`set_bit_mode`]: FtdiCommon::set_bit_mode 329 | pub mask: u8, 330 | /// Clock frequency. 331 | /// 332 | /// If not `None` this will call [`set_clock`] to set the clock frequency. 333 | /// 334 | /// [`set_clock`]: crate::FtdiMpsse::set_clock 335 | pub clock_frequency: Option, 336 | } 337 | 338 | impl std::default::Default for MpsseSettings { 339 | fn default() -> Self { 340 | MpsseSettings { 341 | reset: true, 342 | in_transfer_size: 4096, 343 | read_timeout: Duration::from_secs(1), 344 | write_timeout: Duration::from_secs(1), 345 | latency_timer: Duration::from_millis(16), 346 | mask: 0x00, 347 | clock_frequency: None, 348 | } 349 | } 350 | } 351 | 352 | /// FTDI Multi-Protocol Synchronous Serial Engine (MPSSE) command builder. 353 | /// 354 | /// For details about the MPSSE read the [FTDI MPSSE Basics]. 355 | /// 356 | /// This structure is a `Vec` that the methods push bytewise commands onto. 357 | /// These commands can then be written to the device with the [`write_all`] 358 | /// method. 359 | /// 360 | /// This is useful for creating commands that need to do multiple operations 361 | /// quickly, since individual [`write_all`] calls can be expensive. 362 | /// For example, this can be used to set a GPIO low and clock data out for 363 | /// SPI operations. 364 | /// 365 | /// If dynamic command layout is not required, the [`mpsse`] macro can build 366 | /// command `[u8; N]` arrays at compile-time. 367 | /// 368 | /// [FTDI MPSSE Basics]: https://www.ftdichip.com/Support/Documents/AppNotes/AN_135_MPSSE_Basics.pdf 369 | /// [`write_all`]: FtdiCommon::write_all 370 | pub struct MpsseCmdBuilder(pub Vec); 371 | 372 | impl MpsseCmdBuilder { 373 | /// Create a new command builder. 374 | /// 375 | /// # Example 376 | /// 377 | /// ``` 378 | /// use libftd2xx::MpsseCmdBuilder; 379 | /// 380 | /// MpsseCmdBuilder::new(); 381 | /// ``` 382 | pub const fn new() -> MpsseCmdBuilder { 383 | MpsseCmdBuilder(Vec::new()) 384 | } 385 | 386 | /// Create a new command builder from a vector. 387 | /// 388 | /// # Example 389 | /// 390 | /// ``` 391 | /// use libftd2xx::MpsseCmdBuilder; 392 | /// 393 | /// MpsseCmdBuilder::with_vec(Vec::new()); 394 | /// ``` 395 | pub const fn with_vec(vec: Vec) -> MpsseCmdBuilder { 396 | MpsseCmdBuilder(vec) 397 | } 398 | 399 | /// Get the MPSSE command as a slice. 400 | /// 401 | /// # Example 402 | /// 403 | /// ```no_run 404 | /// use libftd2xx::{DeviceType, Ft232h, FtdiCommon, MpsseCmdBuilder}; 405 | /// 406 | /// let cmd = MpsseCmdBuilder::new().set_clock(100_000, DeviceType::FT232H); 407 | /// 408 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 409 | /// ft.write_all(cmd.as_slice())?; 410 | /// # Ok::<(), std::boxed::Box>(()) 411 | /// ``` 412 | pub fn as_slice(&self) -> &[u8] { 413 | self.0.as_slice() 414 | } 415 | 416 | /// Enable the MPSSE loopback state. 417 | /// 418 | /// # Example 419 | /// 420 | /// ```no_run 421 | /// use libftd2xx::{Ft232h, FtdiCommon, FtdiMpsse, MpsseCmdBuilder}; 422 | /// 423 | /// let cmd = MpsseCmdBuilder::new().enable_loopback(); 424 | /// 425 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 426 | /// ft.initialize_mpsse_default()?; 427 | /// ft.write_all(cmd.as_slice())?; 428 | /// # Ok::<(), std::boxed::Box>(()) 429 | /// ``` 430 | pub fn enable_loopback(mut self) -> Self { 431 | self.0.push(MpsseCmd::EnableLoopback.into()); 432 | self 433 | } 434 | 435 | /// Disable the MPSSE loopback state. 436 | /// 437 | /// # Example 438 | /// 439 | /// ```no_run 440 | /// use libftd2xx::{Ft232h, FtdiCommon, FtdiMpsse, MpsseCmdBuilder}; 441 | /// 442 | /// let cmd = MpsseCmdBuilder::new().disable_loopback(); 443 | /// 444 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 445 | /// ft.initialize_mpsse_default()?; 446 | /// ft.write_all(cmd.as_slice())?; 447 | /// # Ok::<(), std::boxed::Box>(()) 448 | /// ``` 449 | pub fn disable_loopback(mut self) -> Self { 450 | self.0.push(MpsseCmd::DisableLoopback.into()); 451 | self 452 | } 453 | 454 | /// Disable 3 phase data clocking. 455 | /// 456 | /// This is only avaliable on FTx232H devices. 457 | /// 458 | /// This will give a 2 stage data shift which is the default state. 459 | /// 460 | /// It will appears as: 461 | /// 462 | /// 1. Data setup for 1/2 clock period 463 | /// 2. Pulse clock for 1/2 clock period 464 | /// 465 | /// # Example 466 | /// 467 | /// ```no_run 468 | /// use libftd2xx::{Ft232h, FtdiCommon, FtdiMpsse, MpsseCmdBuilder}; 469 | /// 470 | /// let cmd = MpsseCmdBuilder::new().disable_3phase_data_clocking(); 471 | /// 472 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 473 | /// ft.initialize_mpsse_default()?; 474 | /// ft.write_all(cmd.as_slice())?; 475 | /// # Ok::<(), std::boxed::Box>(()) 476 | /// ``` 477 | pub fn disable_3phase_data_clocking(mut self) -> Self { 478 | self.0.push(MpsseCmd::Disable3PhaseClocking.into()); 479 | self 480 | } 481 | 482 | /// Enable 3 phase data clocking. 483 | /// 484 | /// This is only avaliable on FTx232H devices. 485 | /// 486 | /// This will give a 3 stage data shift for the purposes of supporting 487 | /// interfaces such as I2C which need the data to be valid on both edges of 488 | /// the clock. 489 | /// 490 | /// It will appears as: 491 | /// 492 | /// 1. Data setup for 1/2 clock period 493 | /// 2. Pulse clock for 1/2 clock period 494 | /// 3. Data hold for 1/2 clock period 495 | /// 496 | /// # Example 497 | /// 498 | /// ```no_run 499 | /// use libftd2xx::{Ft232h, FtdiCommon, FtdiMpsse, MpsseCmdBuilder}; 500 | /// 501 | /// let cmd = MpsseCmdBuilder::new().enable_3phase_data_clocking(); 502 | /// 503 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 504 | /// ft.initialize_mpsse_default()?; 505 | /// ft.write_all(cmd.as_slice())?; 506 | /// # Ok::<(), std::boxed::Box>(()) 507 | /// ``` 508 | pub fn enable_3phase_data_clocking(mut self) -> Self { 509 | self.0.push(MpsseCmd::Enable3PhaseClocking.into()); 510 | self 511 | } 512 | 513 | /// Enable adaptive data clocking. 514 | /// 515 | /// This is only available on FTx232H devices. 516 | pub fn enable_adaptive_data_clocking(mut self) -> Self { 517 | self.0.push(MpsseCmd::EnableAdaptiveClocking.into()); 518 | self 519 | } 520 | 521 | /// Enable adaptive data clocking. 522 | /// 523 | /// This is only available on FTx232H devices. 524 | pub fn disable_adaptive_data_clocking(mut self) -> Self { 525 | self.0.push(MpsseCmd::DisableAdaptiveClocking.into()); 526 | self 527 | } 528 | 529 | /// Set the pin direction and state of the lower byte (0-7) GPIO pins on the 530 | /// MPSSE interface. 531 | /// 532 | /// The pins that this controls depends on the device. 533 | /// 534 | /// * On the FT232H this will control the AD0-AD7 pins. 535 | /// 536 | /// # Arguments 537 | /// 538 | /// * `state` - GPIO state mask, `0` is low (or input pin), `1` is high. 539 | /// * `direction` - GPIO direction mask, `0` is input, `1` is output. 540 | /// 541 | /// # Example 542 | /// 543 | /// ```no_run 544 | /// use libftd2xx::{Ft232h, FtdiCommon, FtdiMpsse, MpsseCmdBuilder}; 545 | /// 546 | /// let cmd = MpsseCmdBuilder::new() 547 | /// .set_gpio_lower(0xFF, 0xFF) 548 | /// .set_gpio_lower(0x00, 0xFF); 549 | /// 550 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 551 | /// ft.initialize_mpsse_default()?; 552 | /// ft.write_all(cmd.as_slice())?; 553 | /// # Ok::<(), std::boxed::Box>(()) 554 | /// ``` 555 | pub fn set_gpio_lower(mut self, state: u8, direction: u8) -> Self { 556 | self.0 557 | .extend_from_slice(&[MpsseCmd::SetDataBitsLowbyte.into(), state, direction]); 558 | self 559 | } 560 | 561 | /// Set the pin direction and state of the upper byte (8-15) GPIO pins on 562 | /// the MPSSE interface. 563 | /// 564 | /// The pins that this controls depends on the device. 565 | /// This method may do nothing for some devices, such as the FT4232H that 566 | /// only have 8 pins per port. 567 | /// 568 | /// # Arguments 569 | /// 570 | /// * `state` - GPIO state mask, `0` is low (or input pin), `1` is high. 571 | /// * `direction` - GPIO direction mask, `0` is input, `1` is output. 572 | /// 573 | /// # FT232H Corner Case 574 | /// 575 | /// On the FT232H only CBUS5, CBUS6, CBUS8, and CBUS9 can be controlled. 576 | /// These pins confusingly map to the first four bits in the direction and 577 | /// state masks. 578 | /// 579 | /// # Example 580 | /// 581 | /// ```no_run 582 | /// use libftd2xx::{Ft232h, FtdiCommon, FtdiMpsse, MpsseCmdBuilder}; 583 | /// 584 | /// let cmd = MpsseCmdBuilder::new() 585 | /// .set_gpio_upper(0xFF, 0xFF) 586 | /// .set_gpio_upper(0x00, 0xFF); 587 | /// 588 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 589 | /// ft.initialize_mpsse_default()?; 590 | /// ft.write_all(cmd.as_slice())?; 591 | /// # Ok::<(), std::boxed::Box>(()) 592 | /// ``` 593 | pub fn set_gpio_upper(mut self, state: u8, direction: u8) -> Self { 594 | self.0 595 | .extend_from_slice(&[MpsseCmd::SetDataBitsHighbyte.into(), state, direction]); 596 | self 597 | } 598 | 599 | /// Get the pin state state of the lower byte (0-7) GPIO pins on the MPSSE 600 | /// interface. 601 | /// 602 | /// # Example 603 | /// 604 | /// ```no_run 605 | /// use libftd2xx::{Ft232h, FtdiCommon, FtdiMpsse, MpsseCmdBuilder}; 606 | /// 607 | /// let cmd = MpsseCmdBuilder::new().gpio_lower().send_immediate(); 608 | /// 609 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 610 | /// ft.initialize_mpsse_default()?; 611 | /// ft.write_all(cmd.as_slice())?; 612 | /// let mut buf: [u8; 1] = [0; 1]; 613 | /// ft.read_all(&mut buf)?; 614 | /// println!("GPIO lower state: 0x{:02X}", buf[0]); 615 | /// # Ok::<(), std::boxed::Box>(()) 616 | /// ``` 617 | pub fn gpio_lower(mut self) -> Self { 618 | self.0.push(MpsseCmd::GetDataBitsLowbyte.into()); 619 | self 620 | } 621 | 622 | /// Get the pin state state of the upper byte (8-15) GPIO pins on the MPSSE 623 | /// interface. 624 | /// 625 | /// See [`set_gpio_upper`] for additional information about physical pin 626 | /// mappings. 627 | /// 628 | /// # Example 629 | /// 630 | /// ```no_run 631 | /// use libftd2xx::{Ft232h, FtdiCommon, FtdiMpsse, MpsseCmdBuilder}; 632 | /// 633 | /// let cmd = MpsseCmdBuilder::new().gpio_upper().send_immediate(); 634 | /// 635 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 636 | /// ft.initialize_mpsse_default()?; 637 | /// ft.write_all(cmd.as_slice())?; 638 | /// let mut buf: [u8; 1] = [0; 1]; 639 | /// ft.read_all(&mut buf)?; 640 | /// println!("GPIO upper state: 0x{:02X}", buf[0]); 641 | /// # Ok::<(), std::boxed::Box>(()) 642 | /// ``` 643 | /// 644 | /// [`set_gpio_upper`]: FtdiMpsse::set_gpio_upper 645 | pub fn gpio_upper(mut self) -> Self { 646 | self.0.push(MpsseCmd::GetDataBitsHighbyte.into()); 647 | self 648 | } 649 | 650 | /// Send the preceding commands immediately. 651 | /// 652 | /// # Example 653 | /// 654 | /// ``` 655 | /// use libftd2xx::MpsseCmdBuilder; 656 | /// 657 | /// let cmd = MpsseCmdBuilder::new() 658 | /// .set_gpio_upper(0xFF, 0xFF) 659 | /// .set_gpio_upper(0x00, 0xFF) 660 | /// .send_immediate(); 661 | /// ``` 662 | pub fn send_immediate(mut self) -> Self { 663 | self.0.push(MpsseCmd::SendImmediate.into()); 664 | self 665 | } 666 | 667 | /// Make controller wait until GPIOL1 or I/O1 is high before running further commands. 668 | /// 669 | /// # Example 670 | /// 671 | /// ``` 672 | /// use libftd2xx::{ClockData, MpsseCmdBuilder}; 673 | /// 674 | /// // Assume a "chip ready" signal is connected to GPIOL1. This signal is pulled high 675 | /// // shortly after AD3 (chip select) is pulled low. Data will not be clocked out until 676 | /// // the chip is ready. 677 | /// let cmd = MpsseCmdBuilder::new() 678 | /// .set_gpio_lower(0x0, 0xb) 679 | /// .wait_on_io_high() 680 | /// .clock_data(ClockData::MsbPosIn, &[0x12, 0x34, 0x56]) 681 | /// .set_gpio_lower(0x8, 0xb) 682 | /// .send_immediate(); 683 | /// ``` 684 | pub fn wait_on_io_high(mut self) -> Self { 685 | self.0.push(MpsseCmd::WaitOnIOHigh.into()); 686 | self 687 | } 688 | 689 | /// Make controller wait until GPIOL1 or I/O1 is low before running further commands. 690 | /// 691 | /// # Example 692 | /// 693 | /// ``` 694 | /// use libftd2xx::{ClockData, MpsseCmdBuilder}; 695 | /// 696 | /// // Assume a "chip ready" signal is connected to GPIOL1. This signal is pulled low 697 | /// // shortly after AD3 (chip select) is pulled low. Data will not be clocked out until 698 | /// // the chip is ready. 699 | /// let cmd = MpsseCmdBuilder::new() 700 | /// .set_gpio_lower(0x0, 0xb) 701 | /// .wait_on_io_low() 702 | /// .clock_data(ClockData::MsbPosIn, &[0x12, 0x34, 0x56]) 703 | /// .set_gpio_lower(0x8, 0xb) 704 | /// .send_immediate(); 705 | /// ``` 706 | pub fn wait_on_io_low(mut self) -> Self { 707 | self.0.push(MpsseCmd::WaitOnIOLow.into()); 708 | self 709 | } 710 | 711 | /// Clock data out. 712 | /// 713 | /// This will clock out bytes on TDI/DO. 714 | /// No data is clocked into the device on TDO/DI. 715 | /// 716 | /// This will panic for data lengths greater than `u16::MAX + 1`. 717 | pub fn clock_data_out(mut self, mode: ClockDataOut, data: &[u8]) -> Self { 718 | let mut len = data.len(); 719 | assert!(len <= 65536, "data length cannot exceed u16::MAX + 1"); 720 | if len == 0 { 721 | return self; 722 | } 723 | len -= 1; 724 | self.0 725 | .extend_from_slice(&[mode.into(), (len & 0xFF) as u8, ((len >> 8) & 0xFF) as u8]); 726 | self.0.extend_from_slice(&data); 727 | self 728 | } 729 | 730 | /// Clock data in. 731 | /// 732 | /// This will clock in bytes on TDO/DI. 733 | /// No data is clocked out of the device on TDI/DO. 734 | /// 735 | /// # Arguments 736 | /// 737 | /// * `mode` - Data clocking mode. 738 | /// * `len` - Number of bytes to clock in. 739 | /// This will panic for values greater than `u16::MAX + 1`. 740 | pub fn clock_data_in(mut self, mode: ClockDataIn, mut len: usize) -> Self { 741 | assert!(len <= 65536, "data length cannot exceed u16::MAX + 1"); 742 | if len == 0 { 743 | return self; 744 | } 745 | len -= 1; 746 | self.0 747 | .extend_from_slice(&[mode.into(), (len & 0xFF) as u8, ((len >> 8) & 0xFF) as u8]); 748 | self 749 | } 750 | 751 | /// Clock data in and out simultaneously. 752 | /// 753 | /// This will panic for data lengths greater than `u16::MAX + 1`. 754 | pub fn clock_data(mut self, mode: ClockData, data: &[u8]) -> Self { 755 | let mut len = data.len(); 756 | assert!(len <= 65536, "data length cannot exceed u16::MAX + 1"); 757 | if len == 0 { 758 | return self; 759 | } 760 | len -= 1; 761 | self.0 762 | .extend_from_slice(&[mode.into(), (len & 0xFF) as u8, ((len >> 8) & 0xFF) as u8]); 763 | self.0.extend_from_slice(&data); 764 | self 765 | } 766 | 767 | /// Clock data bits out. 768 | /// 769 | /// # Arguments 770 | /// 771 | /// * `mode` - Bit clocking mode. 772 | /// * `data` - Data bits. 773 | /// * `len` - Number of bits to clock out. 774 | /// This will panic for values greater than 8. 775 | pub fn clock_bits_out(mut self, mode: ClockBitsOut, data: u8, mut len: u8) -> Self { 776 | assert!(len <= 8, "data length cannot exceed 8"); 777 | if len == 0 { 778 | return self; 779 | } 780 | len -= 1; 781 | self.0.extend_from_slice(&[mode.into(), len, data]); 782 | self 783 | } 784 | 785 | /// Clock data bits in. 786 | /// 787 | /// # Arguments 788 | /// 789 | /// * `mode` - Bit clocking mode. 790 | /// * `len` - Number of bits to clock in. 791 | /// This will panic for values greater than 8. 792 | pub fn clock_bits_in(mut self, mode: ClockBitsIn, mut len: u8) -> Self { 793 | assert!(len <= 8, "data length cannot exceed 8"); 794 | if len == 0 { 795 | return self; 796 | } 797 | len -= 1; 798 | self.0.extend_from_slice(&[mode.into(), len]); 799 | self 800 | } 801 | 802 | /// Clock data bits in and out simultaneously. 803 | /// 804 | /// # Arguments 805 | /// 806 | /// * `mode` - Bit clocking mode. 807 | /// * `len` - Number of bits to clock in. 808 | /// This will panic for values greater than 8. 809 | pub fn clock_bits(mut self, mode: ClockBits, data: u8, mut len: u8) -> Self { 810 | assert!(len <= 8, "data length cannot exceed 8"); 811 | if len == 0 { 812 | return self; 813 | } 814 | len -= 1; 815 | self.0.extend_from_slice(&[mode.into(), len, data]); 816 | self 817 | } 818 | } 819 | 820 | /// Construct an MPSSE command array at compile-time. 821 | /// 822 | /// Alternative to [`MpsseCmdBuilder`]. Parses a specialized grammar that gathers MPSSE commands 823 | /// into pseudo-statements contained within zero or more assigned blocks. The pseudo-assignment 824 | /// syntax of each block creates a fixed-length `[u8; N]` array that is bound with `let` or 825 | /// `const`[^const_note]. 826 | /// 827 | /// [^const_note]: In `const` bindings, all values used as command parameters and data must be const. 828 | /// 829 | /// # Syntax 830 | /// 831 | /// ```compile_fail 832 | /// mpsse! { let command_data = { command1(); command2(); /* ... */ commandN(); }; } 833 | /// ``` 834 | /// or 835 | /// ```compile_fail 836 | /// mpsse! { let (command_data, READ_LEN) = { command1(); command2(); /* ... */ commandN(); }; } 837 | /// ``` 838 | /// The second form provides the caller with a constant size value of the expected data length to 839 | /// read after writing the commands to the device. 840 | /// 841 | /// # Commands 842 | /// 843 | /// * [`enable_loopback()`][`MpsseCmdBuilder::enable_loopback`] 844 | /// * [`disable_loopback()`][`MpsseCmdBuilder::disable_loopback`] 845 | /// * [`enable_3phase_data_clocking()`][`MpsseCmdBuilder::enable_3phase_data_clocking`] 846 | /// * [`disable_3phase_data_clocking()`][`MpsseCmdBuilder::disable_3phase_data_clocking`] 847 | /// * [`set_gpio_lower(state: u8, direction: u8)`][`MpsseCmdBuilder::set_gpio_lower`] 848 | /// * [`set_gpio_upper(state: u8, direction: u8)`][`MpsseCmdBuilder::set_gpio_upper`] 849 | /// * [`gpio_lower() -> usize`][`MpsseCmdBuilder::gpio_lower`] 850 | /// * [`gpio_upper() -> usize`][`MpsseCmdBuilder::gpio_upper`] 851 | /// * [`send_immediate()`][`MpsseCmdBuilder::send_immediate`] 852 | /// * [`wait_on_io_high()`][`MpsseCmdBuilder::wait_on_io_high`] 853 | /// * [`wait_on_io_low()`][`MpsseCmdBuilder::wait_on_io_low`] 854 | /// * [`clock_data_out(mode: ClockDataOut, data: [u8])`][`MpsseCmdBuilder::clock_data_out`] 855 | /// * [`clock_data_in(mode: ClockDataIn, len: u16) -> std::ops::Range`][`MpsseCmdBuilder::clock_data_in`] 856 | /// * [`clock_data(mode: ClockData, data: [u8]) -> std::ops::Range`][`MpsseCmdBuilder::clock_data`] 857 | /// * [`clock_bits_out(mode: ClockBitsOut, data: u8, len: u8)`][`MpsseCmdBuilder::clock_bits_out`] 858 | /// * [`clock_bits_in(mode: ClockBitsIn, len: u8) -> usize`][`MpsseCmdBuilder::clock_bits_in`] 859 | /// * [`clock_bits(mode: ClockBits, data: u8, len: u8) -> usize`][`MpsseCmdBuilder::clock_bits`] 860 | /// 861 | /// Command pseudo-statements that read data from the device may optionally have the form: 862 | /// ``` 863 | /// # use libftd2xx::{mpsse, ClockDataIn}; 864 | /// mpsse! { 865 | /// // command_data and DATA_IN_RANGE are both declared in the scope of the macro expansion. 866 | /// let command_data = { 867 | /// const DATA_IN_RANGE = clock_data_in(ClockDataIn::MsbNeg, 3); 868 | /// }; 869 | /// } 870 | /// ``` 871 | /// This provides a constant [`Range`][`std::ops::Range`] or [`usize`] index value that may be used 872 | /// to subscript the data read from the device. 873 | /// 874 | /// `clock_data` and `clock_data_out` require that the second argument is a fixed-length, square 875 | /// bracketed list of `u8` values. Compile-time limitations make arbitrary array concatenation or 876 | /// coersion infeasible. 877 | /// 878 | /// # Asserts 879 | /// 880 | /// For `let` bindings, the standard [`assert`] macro is used for validating parameter size inputs. 881 | /// For `const` bindings, [`const_assert`][`static_assertions::const_assert`] is used instead. 882 | /// 883 | /// `const_assert` lacks the ability to provide meaningful compile errors, so it may be useful 884 | /// to temporarily use a `let` binding within function scope to diagnose failing macro expansions. 885 | /// 886 | /// # User Abstractions 887 | /// 888 | /// With macro shadowing, it is possible to extend the macro with additional rules for abstract, 889 | /// device-specific commands. 890 | /// 891 | /// Comments within the implementation of this macro contain hints on how to implement these rules. 892 | /// 893 | /// For example, a SPI device typically delineates transfers with the CS line. Fundamental 894 | /// commands like `cs_high` and `cs_low` can be implmented this way, along with other 895 | /// device-specific abstractions. 896 | /// 897 | /// ``` 898 | /// # use libftd2xx::mpsse; 899 | /// macro_rules! mpsse { 900 | /// // Practical abstraction of CS line for SPI devices. 901 | /// ($passthru:tt {cs_low(); $($tail:tt)*} -> [$($out:tt)*]) => { 902 | /// mpsse!($passthru { 903 | /// set_gpio_lower(0x0, 0xb); 904 | /// $($tail)* 905 | /// } -> [$($out)*]); 906 | /// }; 907 | /// ($passthru:tt {cs_high(); $($tail:tt)*} -> [$($out:tt)*]) => { 908 | /// mpsse!($passthru { 909 | /// set_gpio_lower(0x8, 0xb); 910 | /// $($tail)* 911 | /// } -> [$($out)*]); 912 | /// }; 913 | /// 914 | /// // Hypothetical device-specific command. Leverages both user and libftd2xx commands. 915 | /// ($passthru:tt 916 | /// {const $idx_id:ident = command_42([$($data:expr),* $(,)*]); $($tail:tt)*} -> 917 | /// [$($out:tt)*]) => { 918 | /// mpsse!($passthru { 919 | /// cs_low(); 920 | /// const $idx_id = clock_data(::libftd2xx::ClockData::MsbPosIn, [0x42, $($data,)*]); 921 | /// cs_high(); 922 | /// $($tail)* 923 | /// } -> [$($out)*]); 924 | /// }; 925 | /// 926 | /// // Everything else handled by libftd2xx crate implementation. 927 | /// ($($tokens:tt)*) => { 928 | /// ::libftd2xx::mpsse!($($tokens)*); 929 | /// }; 930 | /// } 931 | /// 932 | /// mpsse! { 933 | /// const (COMMAND_DATA, READ_LEN) = { 934 | /// wait_on_io_high(); 935 | /// const COMMAND_42_RESULT_RANGE = command_42([11, 22, 33]); 936 | /// send_immediate(); 937 | /// }; 938 | /// } 939 | /// ``` 940 | /// 941 | /// # Example 942 | /// 943 | /// ```no_run 944 | /// use libftd2xx::{mpsse, ClockDataIn, ClockDataOut, Ft232h, FtdiCommon, FtdiMpsse}; 945 | /// 946 | /// mpsse! { 947 | /// const (COMMAND_DATA, READ_LEN) = { 948 | /// set_gpio_lower(0xFA, 0xFB); 949 | /// set_gpio_lower(0xF2, 0xFB); 950 | /// clock_data_out(ClockDataOut::MsbNeg, [0x12, 0x34, 0x56]); 951 | /// const DATA_IN_RANGE = clock_data_in(ClockDataIn::MsbNeg, 3); 952 | /// set_gpio_lower(0xFA, 0xFB); 953 | /// send_immediate(); 954 | /// }; 955 | /// } 956 | /// 957 | /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; 958 | /// ft.initialize_mpsse_default()?; 959 | /// ft.write_all(&COMMAND_DATA)?; 960 | /// let mut buf: [u8; READ_LEN] = [0; READ_LEN]; 961 | /// ft.read_all(&mut buf)?; 962 | /// println!("Data slice in: {:?}", &buf[DATA_IN_RANGE]); 963 | /// # Ok::<(), std::boxed::Box>(()) 964 | /// ``` 965 | #[macro_export] 966 | macro_rules! mpsse { 967 | // Replacement method for counting comma-separated expressions. 968 | // https://danielkeep.github.io/tlborm/book/blk-counting.html#repetition-with-replacement 969 | (@replace_expr $_t:tt $sub:expr) => {$sub}; 970 | (@count_elements $($tts:expr),* $(,)*) => {(0usize $(+ mpsse!(@replace_expr $tts 1usize))*)}; 971 | 972 | // Assert that is selectively compile-time depending on let vs. const expansion. 973 | // 974 | // Unfortunately, the compile-time error is not very helpful due to the lack of message and 975 | // macro depth, but still ensures safe command construction. 976 | // 977 | // Temporarily running a let expansion can be helpful to diagnose errors. 978 | (@assert ((let, $_user_passthru:tt), $_read_len:expr), $e:expr, $msg:expr) => { 979 | ::std::assert!($e, $msg); 980 | }; 981 | (@assert ((const, $_user_passthru:tt), $_read_len:expr), $e:expr, $_msg:expr) => { 982 | ::static_assertions::const_assert!($e); 983 | }; 984 | 985 | // Unit rule 986 | () => {}; 987 | 988 | // let command_data = { command1(); command2(); ... commandN(); }; 989 | (let $id:ident = {$($commands:tt)*}; $($tail:tt)*) => { 990 | mpsse!(((let, ($id, _)), 0) {$($commands)*} -> []); 991 | mpsse!($($tail)*); 992 | }; 993 | 994 | // const COMMAND_DATA = { command1(); command2(); ... commandN(); }; 995 | (const $id:ident = {$($commands:tt)*}; $($tail:tt)*) => { 996 | mpsse!(((const, ($id, _)), 0) {$($commands)*} -> []); 997 | mpsse!($($tail)*); 998 | }; 999 | 1000 | // let (command_data, READ_LEN) = { command1(); command2(); ... commandN(); }; 1001 | (let ($id:ident, $read_len_id:ident) = {$($commands:tt)*}; $($tail:tt)*) => { 1002 | mpsse!(((let, ($id, $read_len_id)), 0) {$($commands)*} -> []); 1003 | mpsse!($($tail)*); 1004 | }; 1005 | 1006 | // const (COMMAND_DATA, READ_LEN) = { command1(); command2(); ... commandN(); }; 1007 | (const ($id:ident, $read_len_id:ident) = {$($commands:tt)*}; $($tail:tt)*) => { 1008 | mpsse!(((const, ($id, $read_len_id)), 0) {$($commands)*} -> []); 1009 | mpsse!($($tail)*); 1010 | }; 1011 | 1012 | // Rules generally follow a structure based on three root token trees: 1013 | // () {} -> [] 1014 | // 1015 | // "Statements" are recursively shifted off the front of the input and the resulting u8 tokens 1016 | // are appended to the output. Recursion ends when the input token tree is empty. 1017 | // 1018 | // Rules have the following form: 1019 | // ($passthru:tt {(); $($tail:tt)*} -> [$($out:tt)*]) 1020 | // 1021 | // For functions that perform data reads, cumulative read_len can be accessed with this form: 1022 | // (($passthru:tt, $read_len:tt) {(); $($tail:tt)*} -> [$($out:tt)*]) 1023 | // 1024 | // Additionally, the following form is used to provide the invoker with a usize index or 1025 | // range to later access a specific data read `const READ_INDEX = ();`: 1026 | // (($passthru:tt, $read_len:tt) {const $idx_id:ident = (); $($tail:tt)*} -> [$($out:tt)*]) 1027 | 1028 | ($passthru:tt {enable_loopback(); $($tail:tt)*} -> [$($out:tt)*]) => { 1029 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::EnableLoopback as u8,]); 1030 | }; 1031 | ($passthru:tt {disable_loopback(); $($tail:tt)*} -> [$($out:tt)*]) => { 1032 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::DisableLoopback as u8,]); 1033 | }; 1034 | ($passthru:tt {enable_3phase_data_clocking(); $($tail:tt)*} -> [$($out:tt)*]) => { 1035 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::Enable3PhaseClocking as u8,]); 1036 | }; 1037 | ($passthru:tt {disable_3phase_data_clocking(); $($tail:tt)*} -> [$($out:tt)*]) => { 1038 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::Disable3PhaseClocking as u8,]); 1039 | }; 1040 | ($passthru:tt {enable_adaptive_data_clocking(); $($tail:tt)*} -> [$($out:tt)*]) => { 1041 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::EnableDataClocking as u8,]); 1042 | }; 1043 | ($passthru:tt {disable_adaptive_data_clocking(); $($tail:tt)*} -> [$($out:tt)*]) => { 1044 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::DisableDataClocking as u8,]); 1045 | }; 1046 | ($passthru:tt {set_gpio_lower($state:expr, $direction:expr); $($tail:tt)*} -> [$($out:tt)*]) => { 1047 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::SetDataBitsLowbyte as u8, $state as u8, $direction as u8,]); 1048 | }; 1049 | ($passthru:tt {set_gpio_upper($state:expr, $direction:expr); $($tail:tt)*} -> [$($out:tt)*]) => { 1050 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::SetDataBitsHighbyte as u8, $state as u8, $direction as u8,]); 1051 | }; 1052 | (($passthru:tt, $read_len:tt) {gpio_lower(); $($tail:tt)*} -> [$($out:tt)*]) => { 1053 | mpsse!(($passthru, ($read_len + 1)) {$($tail)*} -> [$($out)* $crate::MpsseCmd::GetDataBitsLowbyte as u8,]); 1054 | }; 1055 | (($passthru:tt, $read_len:tt) {const $idx_id:ident = gpio_lower(); $($tail:tt)*} -> [$($out:tt)*]) => { 1056 | const $idx_id: usize = $read_len; 1057 | mpsse!(($passthru, $read_len) {gpio_lower(); $($tail)*} -> [$($out)*]); 1058 | }; 1059 | (($passthru:tt, $read_len:tt) {gpio_upper(); $($tail:tt)*} -> [$($out:tt)*]) => { 1060 | mpsse!(($passthru, ($read_len + 1)) {$($tail)*} -> [$($out)* $crate::MpsseCmd::GetDataBitsHighbyte as u8,]); 1061 | }; 1062 | (($passthru:tt, $read_len:tt) {const $idx_id:ident = gpio_upper(); $($tail:tt)*} -> [$($out:tt)*]) => { 1063 | const $idx_id: usize = $read_len; 1064 | mpsse!(($passthru, $read_len) {gpio_upper(); $($tail)*} -> [$($out)*]); 1065 | }; 1066 | ($passthru:tt {send_immediate(); $($tail:tt)*} -> [$($out:tt)*]) => { 1067 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::SendImmediate as u8,]); 1068 | }; 1069 | ($passthru:tt {wait_on_io_high(); $($tail:tt)*} -> [$($out:tt)*]) => { 1070 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::WaitOnIOHigh as u8,]); 1071 | }; 1072 | ($passthru:tt {wait_on_io_low(); $($tail:tt)*} -> [$($out:tt)*]) => { 1073 | mpsse!($passthru {$($tail)*} -> [$($out)* $crate::MpsseCmd::WaitOnIOLow as u8,]); 1074 | }; 1075 | ($passthru:tt {clock_data_out($mode:expr, [$($data:expr),* $(,)*]); $($tail:tt)*} -> [$($out:tt)*]) => { 1076 | mpsse!(@assert $passthru, (mpsse!(@count_elements $($data,)*) as usize > 0_usize && mpsse!(@count_elements $($data,)*) as usize <= 65536_usize), "data length must be in 1..=(u16::MAX + 1)"); 1077 | mpsse!($passthru {$($tail)*} -> [$($out)* $mode as $crate::ClockDataOut as u8, 1078 | ((mpsse!(@count_elements $($data,)*) - 1) & 0xFF_usize) as u8, 1079 | (((mpsse!(@count_elements $($data,)*) - 1) >> 8) & 0xFF_usize) as u8, 1080 | $($data as u8,)*]); 1081 | }; 1082 | (($passthru:tt, $read_len:tt) {clock_data_in($mode:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { 1083 | mpsse!(@assert ($passthru, $read_len), (($len) as usize > 0_usize && ($len) as usize <= 65536_usize), "data length must be in 1..=(u16::MAX + 1)"); 1084 | mpsse!(($passthru, ($read_len + ($len))) {$($tail)*} -> [$($out)* $mode as $crate::ClockDataIn as u8, 1085 | ((($len) - 1) & 0xFF_usize) as u8, 1086 | (((($len) - 1) >> 8) & 0xFF_usize) as u8,]); 1087 | }; 1088 | (($passthru:tt, $read_len:tt) {const $range_id:ident = clock_data_in($mode:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { 1089 | const $range_id: ::std::ops::Range = $read_len..$read_len + ($len); 1090 | mpsse!(($passthru, $read_len) {clock_data_in($mode, $len); $($tail)*} -> [$($out)*]); 1091 | }; 1092 | (($passthru:tt, $read_len:tt) {clock_data($mode:expr, [$($data:expr),* $(,)*]); $($tail:tt)*} -> [$($out:tt)*]) => { 1093 | mpsse!(@assert ($passthru, $read_len), (mpsse!(@count_elements $($data,)*) as usize > 0_usize && mpsse!(@count_elements $($data,)*) as usize <= 65536_usize), "data length must be in 1..=(u16::MAX + 1)"); 1094 | mpsse!(($passthru, ($read_len + mpsse!(@count_elements $($data,)*))) {$($tail)*} -> [$($out)* $mode as $crate::ClockData as u8, 1095 | ((mpsse!(@count_elements $($data,)*) - 1) & 0xFF_usize) as u8, 1096 | (((mpsse!(@count_elements $($data,)*) - 1) >> 8) & 0xFF_usize) as u8, 1097 | $($data as u8,)*]); 1098 | }; 1099 | (($passthru:tt, $read_len:tt) {const $range_id:ident = clock_data($mode:expr, [$($data:expr),* $(,)*]); $($tail:tt)*} -> [$($out:tt)*]) => { 1100 | const $range_id: ::std::ops::Range = $read_len..$read_len + mpsse!(@count_elements $($data,)*); 1101 | mpsse!(($passthru, $read_len) {clock_data($mode, [$($data,)*]); $($tail)*} -> [$($out)*]); 1102 | }; 1103 | ($passthru:tt {clock_bits_out($mode:expr, $data:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { 1104 | mpsse!(@assert $passthru, ($len as u8 > 0_u8 && $len as u8 <= 8_u8), "data length must be in 1..=8"); 1105 | mpsse!($passthru {$($tail)*} -> [$($out)* $mode as $crate::ClockBitsOut as u8, (($len) - 1) as u8, $data as u8,]); 1106 | }; 1107 | (($passthru:tt, $read_len:tt) {clock_bits_in($mode:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { 1108 | mpsse!(@assert ($passthru, $read_len), ($len as u8 > 0_u8 && $len as u8 <= 8_u8), "data length must be in 1..=8"); 1109 | mpsse!(($passthru, ($read_len + 1)) {$($tail)*} -> [$($out)* $mode as $crate::ClockBitsIn as u8, (($len) - 1) as u8,]); 1110 | }; 1111 | (($passthru:tt, $read_len:tt) {const $idx_id:ident = clock_bits_in($mode:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { 1112 | const $idx_id: usize = $read_len; 1113 | mpsse!(($passthru, $read_len) {clock_bits_in($mode, $len); $($tail)*} -> [$($out)*]); 1114 | }; 1115 | (($passthru:tt, $read_len:tt) {clock_bits($mode:expr, $data:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { 1116 | mpsse!(@assert ($passthru, $read_len), ($len as u8 > 0_u8 && $len as u8 <= 8_u8), "data length must be in 1..=8"); 1117 | mpsse!(($passthru, ($read_len + 1)) {$($tail)*} -> [$($out)* $mode as $crate::ClockBits as u8, (($len) - 1) as u8, $data as u8,]); 1118 | }; 1119 | (($passthru:tt, $read_len:tt) {const $idx_id:ident = clock_bits($mode:expr, $data:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { 1120 | const $idx_id: usize = $read_len; 1121 | mpsse!(($passthru, $read_len) {clock_bits($mode, $data, $len); $($tail)*} -> [$($out)*]); 1122 | }; 1123 | 1124 | // Emit command_data 1125 | ((($const_let:tt, ($id:tt, _)), $read_len:expr) {} -> [$($out:tt)*]) => { 1126 | $const_let $id: [u8; mpsse!(@count_elements $($out)*)] = [$($out)*]; 1127 | }; 1128 | 1129 | // Emit command_data, READ_LEN 1130 | ((($const_let:tt, ($id:tt, $read_len_id:tt)), $read_len:expr) {} -> [$($out:tt)*]) => { 1131 | $const_let $id: [u8; mpsse!(@count_elements $($out)*)] = [$($out)*]; 1132 | const $read_len_id: usize = $read_len; 1133 | }; 1134 | } 1135 | -------------------------------------------------------------------------------- /src/gpio.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Result, X232Error}; 2 | use crate::ftdimpsse::MpsseCmdBuilder; 3 | 4 | use embedded_hal::digital::v2::OutputPin; 5 | use std::cell::RefCell; 6 | use std::fmt; 7 | use std::io::{Read, Write}; 8 | use std::sync::Mutex; 9 | 10 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 11 | pub enum PinBank { 12 | Low, 13 | High, 14 | } 15 | 16 | impl fmt::Display for PinBank { 17 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 18 | match self { 19 | PinBank::Low => write!(f, "L"), 20 | PinBank::High => write!(f, "H"), 21 | } 22 | } 23 | } 24 | 25 | #[macro_export] 26 | macro_rules! declare_gpio_pin { 27 | ($pin: ident, $bit: expr, $bank: expr) => { 28 | pub fn $pin(&self) -> Result { 29 | if !*self.$pin.borrow() { 30 | return Err(X232Error::HAL(ErrorKind::GpioPinBusy)); 31 | } 32 | 33 | if $bit > 7 { 34 | return Err(X232Error::HAL(ErrorKind::GpioInvalidPin)); 35 | } 36 | 37 | self.$pin.replace(false); 38 | Ok(GpioPin::new(&self.mtx, $bit, $bank)) 39 | } 40 | }; 41 | } 42 | 43 | pub struct GpioPin<'a> { 44 | ctx: &'a Mutex>, 45 | bank: PinBank, 46 | bit: u8, 47 | } 48 | 49 | impl<'a> fmt::Display for GpioPin<'a> { 50 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 51 | match self.bank { 52 | PinBank::Low => write!(f, "P{}{}", self.bank, self.bit - 4), 53 | PinBank::High => write!(f, "P{}{}", self.bank, self.bit), 54 | } 55 | } 56 | } 57 | 58 | impl<'a> GpioPin<'a> { 59 | pub fn new(ctx: &'a Mutex>, bit: u8, bank: PinBank) -> GpioPin { 60 | GpioPin { ctx, bank, bit } 61 | } 62 | 63 | pub fn get_bit(&self) -> u8 { 64 | self.bit 65 | } 66 | 67 | pub fn get_bank(&self) -> PinBank { 68 | self.bank 69 | } 70 | 71 | fn set_pin(&mut self, val: bool) -> Result<()> { 72 | let mut value: [u8; 1] = [0]; 73 | 74 | let read = match self.bank { 75 | PinBank::Low => MpsseCmdBuilder::new().gpio_lower().send_immediate(), 76 | PinBank::High => MpsseCmdBuilder::new().gpio_upper().send_immediate(), 77 | }; 78 | 79 | let lock = self.ctx.lock().unwrap(); 80 | let mut ftdi = lock.borrow_mut(); 81 | 82 | ftdi.usb_purge_buffers()?; 83 | ftdi.write_all(read.as_slice())?; 84 | ftdi.read_exact(&mut value)?; 85 | 86 | let v = if val { 87 | value[0] | (1 << self.bit) 88 | } else { 89 | value[0] & (!(1 << self.bit)) 90 | }; 91 | 92 | let write = match self.bank { 93 | PinBank::Low => MpsseCmdBuilder::new() 94 | .set_gpio_lower(v, 0b1111_1011) 95 | .send_immediate(), 96 | PinBank::High => MpsseCmdBuilder::new() 97 | .set_gpio_upper(v, 0b1111_1111) 98 | .send_immediate(), 99 | }; 100 | 101 | ftdi.usb_purge_buffers()?; 102 | ftdi.write_all(write.as_slice())?; 103 | 104 | Ok(()) 105 | } 106 | } 107 | 108 | impl<'a> OutputPin for GpioPin<'a> { 109 | type Error = X232Error; 110 | 111 | fn set_low(&mut self) -> Result<()> { 112 | self.set_pin(false)?; 113 | Ok(()) 114 | } 115 | 116 | fn set_high(&mut self) -> Result<()> { 117 | self.set_pin(true)?; 118 | Ok(()) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/i2c.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::identity_op)] 2 | 3 | use crate::error::{ErrorKind, Result, X232Error}; 4 | use crate::ftdimpsse::{ClockBitsIn, ClockDataIn, ClockDataOut, MpsseCmdBuilder}; 5 | 6 | use std::cell::RefCell; 7 | use std::io::{Read, Write}; 8 | use std::sync::Mutex; 9 | 10 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 11 | #[allow(non_camel_case_types)] 12 | pub enum I2cSpeed { 13 | CLK_AUTO, 14 | CLK_100kHz, 15 | CLK_400kHz, 16 | } 17 | 18 | pub struct I2cBus<'a> { 19 | ctx: &'a Mutex>, 20 | } 21 | 22 | impl<'a> I2cBus<'a> { 23 | pub fn new(ctx: &'a Mutex>) -> I2cBus { 24 | I2cBus { ctx } 25 | } 26 | } 27 | 28 | impl<'a> I2cBus<'a> { 29 | fn i2c_write_to(addr: u8) -> u8 { 30 | (addr << 1) | 0x0 31 | } 32 | 33 | fn i2c_read_from(addr: u8) -> u8 { 34 | (addr << 1) | 0x1 35 | } 36 | } 37 | 38 | impl<'a> I2cBus<'a> { 39 | fn i2c_start(&self, mut cmd: MpsseCmdBuilder, pins: u8) -> MpsseCmdBuilder { 40 | for _ in 0..4 { 41 | cmd = cmd.set_gpio_lower((pins & 0b1111_1000) | 0b11, 0b1111_1011); 42 | } 43 | 44 | for _ in 0..4 { 45 | cmd = cmd.set_gpio_lower((pins & 0b1111_1000) | 0b01, 0b1111_1011); 46 | } 47 | 48 | for _ in 0..4 { 49 | cmd = cmd.set_gpio_lower((pins & 0b1111_1000) | 0b00, 0b1111_1011); 50 | } 51 | 52 | cmd 53 | } 54 | 55 | fn i2c_stop(&self, mut cmd: MpsseCmdBuilder, pins: u8) -> MpsseCmdBuilder { 56 | for _ in 0..4 { 57 | cmd = cmd.set_gpio_lower((pins & 0b1111_1000) | 0b01, 0b1111_1011); 58 | } 59 | 60 | for _ in 0..4 { 61 | cmd = cmd.set_gpio_lower((pins & 0b1111_1000) | 0b11, 0b1111_1011); 62 | } 63 | 64 | for _ in 0..4 { 65 | cmd = cmd.set_gpio_lower((pins & 0b1111_1100) | 0b00, 0b1111_1000); 66 | } 67 | 68 | cmd 69 | } 70 | 71 | fn i2c_write_byte_ack(&self, cmd: MpsseCmdBuilder, byte: u8, pins: u8) -> MpsseCmdBuilder { 72 | cmd 73 | // make sure no occasional SP: SDA output(1) SCL output(0) 74 | .set_gpio_lower((pins & 0b1111_1000) | 0b10, 0b1111_1011) 75 | // send single byte using MPSSE 76 | .clock_data_out(ClockDataOut::MsbNeg, &[byte]) 77 | // get pins ready for SAK: DO input, DI input, SK output(0) 78 | .set_gpio_lower((pins & 0b1111_1000) | 0b00, 0b1111_1001) 79 | // SAK: recv using MPSSE 80 | .clock_bits_in(ClockBitsIn::MsbPos, 1) 81 | // request immediate response from FTDI to host 82 | .send_immediate() 83 | } 84 | 85 | fn i2c_read_byte(&self, cmd: MpsseCmdBuilder, nack: bool, pins: u8) -> MpsseCmdBuilder { 86 | let state = if nack { 87 | (pins & 0b1111_1000) | 0b10 88 | } else { 89 | (pins & 0b1111_1000) | 0b00 90 | }; 91 | 92 | cmd 93 | // make sure no occasional SP: SDA output(1), SCL output(0) 94 | .set_gpio_lower((pins & 0b1111_1000) | 0b10, 0b1111_1011) 95 | // prepare to read: SDA input, SCL output(0) 96 | .set_gpio_lower((pins & 0b1111_1000) | 0b00, 0b1111_1001) 97 | // read byte using MPSSE 98 | .clock_data_in(ClockDataIn::MsbNeg, 1) 99 | // prepare SDA for NACK/ACK 100 | .set_gpio_lower(state, 0b1111_1011) 101 | // NACK/ACK to slave: we pretend we read it 102 | .clock_bits_in(ClockBitsIn::MsbPos, 1) 103 | // request immediate response from FTDI to PC 104 | .send_immediate() 105 | } 106 | } 107 | 108 | impl<'a> embedded_hal::blocking::i2c::Read for I2cBus<'a> { 109 | type Error = X232Error; 110 | 111 | fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<()> { 112 | if buffer.is_empty() { 113 | return Ok(()); 114 | } 115 | 116 | let lock = self.ctx.lock().unwrap(); 117 | let mut ftdi = lock.borrow_mut(); 118 | 119 | let cmd_read_low_pins = MpsseCmdBuilder::new().gpio_lower().send_immediate(); 120 | let mut cmd = MpsseCmdBuilder::new(); 121 | let mut pins: Vec = vec![0]; 122 | let mut ack: Vec = vec![0]; 123 | 124 | // get current state of low pins 125 | ftdi.usb_purge_buffers()?; 126 | ftdi.write_all(cmd_read_low_pins.as_slice())?; 127 | ftdi.read_exact(&mut pins)?; 128 | 129 | // ST: send using bit-banging 130 | cmd = self.i2c_start(cmd, pins[0]); 131 | 132 | // SAD + R: send using MPSSE 133 | cmd = self.i2c_write_byte_ack(cmd, I2cBus::i2c_read_from(address), pins[0]); 134 | 135 | // send command and read back one bit 136 | ftdi.usb_purge_buffers()?; 137 | ftdi.write_all(cmd.as_slice())?; 138 | ftdi.read_exact(&mut ack)?; 139 | 140 | // check ACK bit from slave 141 | if ack[0] & 0x1 == 0x1 { 142 | return Err(X232Error::HAL(ErrorKind::I2cNoAck)); 143 | } 144 | 145 | // READ bytes from slave 146 | for i in 0..buffer.len() { 147 | let mut cmd = MpsseCmdBuilder::new(); 148 | let mut data: Vec = vec![0, 0]; 149 | let nack: bool = i == (buffer.len() - 1); 150 | 151 | cmd = self.i2c_read_byte(cmd, nack, pins[0]); 152 | 153 | ftdi.usb_purge_buffers()?; 154 | ftdi.write_all(cmd.as_slice())?; 155 | ftdi.read_exact(&mut data)?; 156 | 157 | buffer[i] = data[0]; 158 | } 159 | 160 | let mut cmd = MpsseCmdBuilder::new(); 161 | 162 | // SP: send using bit-banging 163 | cmd = self.i2c_stop(cmd, pins[0]); 164 | 165 | ftdi.usb_purge_buffers()?; 166 | ftdi.write_all(cmd.as_slice())?; 167 | 168 | Ok(()) 169 | } 170 | } 171 | 172 | impl<'a> embedded_hal::blocking::i2c::Write for I2cBus<'a> { 173 | type Error = X232Error; 174 | 175 | fn write(&mut self, address: u8, bytes: &[u8]) -> Result<()> { 176 | if bytes.is_empty() { 177 | return Ok(()); 178 | } 179 | 180 | let lock = self.ctx.lock().unwrap(); 181 | let mut ftdi = lock.borrow_mut(); 182 | 183 | let cmd_read_low_pins = MpsseCmdBuilder::new().gpio_lower().send_immediate(); 184 | let mut cmd = MpsseCmdBuilder::new(); 185 | let mut pins: Vec = vec![0]; 186 | let mut ack: Vec = vec![0]; 187 | 188 | // get current state of low pins 189 | ftdi.usb_purge_buffers()?; 190 | ftdi.write_all(cmd_read_low_pins.as_slice())?; 191 | ftdi.read_exact(&mut pins)?; 192 | 193 | // ST: send using bit-banging 194 | cmd = self.i2c_start(cmd, pins[0]); 195 | 196 | // SAD + W: send using MPSSE 197 | cmd = self.i2c_write_byte_ack(cmd, I2cBus::i2c_write_to(address), pins[0]); 198 | 199 | // send command and read back one bit 200 | ftdi.usb_purge_buffers()?; 201 | ftdi.write_all(cmd.as_slice())?; 202 | ftdi.read_exact(&mut ack)?; 203 | 204 | // check ACK bit from slave 205 | if ack[0] & 0x1 == 0x1 { 206 | return Err(X232Error::HAL(ErrorKind::I2cNoAck)); 207 | } 208 | 209 | // WRITE bytes to slave 210 | for byte in bytes { 211 | let mut cmd = MpsseCmdBuilder::new(); 212 | 213 | cmd = self.i2c_write_byte_ack(cmd, *byte, pins[0]); 214 | 215 | // send command and read back one bit 216 | ftdi.usb_purge_buffers()?; 217 | ftdi.write_all(cmd.as_slice())?; 218 | ftdi.read_exact(&mut ack)?; 219 | 220 | // check ACK bit from slave 221 | if ack[0] & 0x1 == 0x1 { 222 | return Err(X232Error::HAL(ErrorKind::I2cNoAck)); 223 | } 224 | } 225 | 226 | let mut cmd = MpsseCmdBuilder::new(); 227 | 228 | // SP: send using bit-banging 229 | cmd = self.i2c_stop(cmd, pins[0]); 230 | 231 | ftdi.usb_purge_buffers()?; 232 | ftdi.write_all(cmd.as_slice())?; 233 | 234 | Ok(()) 235 | } 236 | } 237 | 238 | impl<'a> embedded_hal::blocking::i2c::WriteRead for I2cBus<'a> { 239 | type Error = X232Error; 240 | 241 | fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<()> { 242 | // FIXME: simplified: do not fallback to Read or Write, just throw error 243 | if bytes.is_empty() || buffer.is_empty() { 244 | return Err(X232Error::HAL(ErrorKind::InvalidParams)); 245 | } 246 | 247 | let lock = self.ctx.lock().unwrap(); 248 | let mut ftdi = lock.borrow_mut(); 249 | 250 | let cmd_read_low_pins = MpsseCmdBuilder::new().gpio_lower().send_immediate(); 251 | let mut cmd = MpsseCmdBuilder::new(); 252 | let mut pins: Vec = vec![0]; 253 | let mut ack: Vec = vec![0]; 254 | 255 | // get current state of low pins 256 | ftdi.usb_purge_buffers()?; 257 | ftdi.write_all(cmd_read_low_pins.as_slice())?; 258 | ftdi.read_exact(&mut pins)?; 259 | 260 | // ST: send using bit-banging 261 | cmd = self.i2c_start(cmd, pins[0]); 262 | 263 | // SAD + W: send using MPSSE 264 | cmd = self.i2c_write_byte_ack(cmd, I2cBus::i2c_write_to(address), pins[0]); 265 | 266 | // send command and read back one bit 267 | ftdi.usb_purge_buffers()?; 268 | ftdi.write_all(cmd.as_slice())?; 269 | ftdi.read_exact(&mut ack)?; 270 | 271 | // check ACK bit from slave 272 | if ack[0] & 0x1 == 0x1 { 273 | return Err(X232Error::HAL(ErrorKind::I2cNoAck)); 274 | } 275 | 276 | // WRITE bytes to slave 277 | for byte in bytes { 278 | let mut cmd = MpsseCmdBuilder::new(); 279 | 280 | cmd = self.i2c_write_byte_ack(cmd, *byte, pins[0]); 281 | 282 | // send command and read back one bit 283 | ftdi.usb_purge_buffers()?; 284 | ftdi.write_all(cmd.as_slice())?; 285 | ftdi.read_exact(&mut ack)?; 286 | 287 | // check ACK bit from slave 288 | if ack[0] & 0x1 == 0x1 { 289 | return Err(X232Error::HAL(ErrorKind::I2cNoAck)); 290 | } 291 | } 292 | 293 | let mut cmd = MpsseCmdBuilder::new(); 294 | let mut ack: Vec = vec![0]; 295 | 296 | // SR: send using bit-banging 297 | cmd = self.i2c_start(cmd, pins[0]); 298 | 299 | // SAD + R: send using MPSSE 300 | cmd = self.i2c_write_byte_ack(cmd, I2cBus::i2c_read_from(address), pins[0]); 301 | 302 | // send command and read back one bit 303 | ftdi.usb_purge_buffers()?; 304 | ftdi.write_all(cmd.as_slice())?; 305 | ftdi.read_exact(&mut ack)?; 306 | 307 | // check ACK bit from slave 308 | if ack[0] & 0x1 == 0x1 { 309 | return Err(X232Error::HAL(ErrorKind::I2cNoAck)); 310 | } 311 | 312 | // READ bytes from slave 313 | for i in 0..buffer.len() { 314 | let mut cmd = MpsseCmdBuilder::new(); 315 | let mut data: Vec = vec![0, 0]; 316 | let nack: bool = i == (buffer.len() - 1); 317 | 318 | cmd = self.i2c_read_byte(cmd, nack, pins[0]); 319 | 320 | ftdi.usb_purge_buffers()?; 321 | ftdi.write_all(cmd.as_slice())?; 322 | ftdi.read_exact(&mut data)?; 323 | 324 | buffer[i] = data[0]; 325 | } 326 | 327 | let mut cmd = MpsseCmdBuilder::new(); 328 | 329 | // SP: send using bit-banging 330 | cmd = self.i2c_stop(cmd, pins[0]); 331 | 332 | ftdi.usb_purge_buffers()?; 333 | ftdi.write_all(cmd.as_slice())?; 334 | 335 | Ok(()) 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod ftdimpsse; 3 | pub mod gpio; 4 | pub mod i2c; 5 | pub mod spi; 6 | pub mod x232h; 7 | 8 | #[cfg(test)] 9 | mod tests { 10 | macro_rules! ftdi_test_suite { 11 | ($vendor: expr, $product: expr, $channel: expr) => { 12 | use crate::gpio::PinBank; 13 | use crate::i2c::I2cSpeed; 14 | use crate::spi::SpiSpeed; 15 | use crate::x232h::FTx232H; 16 | use crate::x232h::Interface; 17 | use embedded_hal::blocking::spi::Transfer; 18 | use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3}; 19 | use itertools::iproduct; 20 | use rand::Rng; 21 | 22 | #[test] 23 | fn test_init_t1() { 24 | let mut dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 25 | assert_eq!(dev.is_loopback(), false); 26 | 27 | dev.loopback(true).unwrap(); 28 | assert_eq!(dev.is_loopback(), true); 29 | } 30 | 31 | #[test] 32 | fn test_init_t2() { 33 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 34 | 35 | let pl0 = dev.pl0().unwrap(); 36 | assert_eq!(pl0.get_bit(), 4); 37 | assert_eq!(pl0.get_bank(), PinBank::Low); 38 | assert_eq!(format!("{}", pl0), "PL0"); 39 | 40 | let pl1 = dev.pl1().unwrap(); 41 | assert_eq!(pl1.get_bit(), 5); 42 | assert_eq!(pl1.get_bank(), PinBank::Low); 43 | assert_eq!(format!("{}", pl1), "PL1"); 44 | 45 | let pl2 = dev.pl2().unwrap(); 46 | assert_eq!(pl2.get_bit(), 6); 47 | assert_eq!(pl2.get_bank(), PinBank::Low); 48 | assert_eq!(format!("{}", pl2), "PL2"); 49 | 50 | let pl3 = dev.pl3().unwrap(); 51 | assert_eq!(pl3.get_bit(), 7); 52 | assert_eq!(pl3.get_bank(), PinBank::Low); 53 | assert_eq!(format!("{}", pl3), "PL3"); 54 | } 55 | 56 | #[test] 57 | fn test_init_t3() { 58 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 59 | 60 | let ph0 = dev.ph0().unwrap(); 61 | assert_eq!(ph0.get_bit(), 0); 62 | assert_eq!(ph0.get_bank(), PinBank::High); 63 | assert_eq!(format!("{}", ph0), "PH0"); 64 | 65 | let ph1 = dev.ph1().unwrap(); 66 | assert_eq!(ph1.get_bit(), 1); 67 | assert_eq!(ph1.get_bank(), PinBank::High); 68 | assert_eq!(format!("{}", ph1), "PH1"); 69 | 70 | let ph2 = dev.ph2().unwrap(); 71 | assert_eq!(ph2.get_bit(), 2); 72 | assert_eq!(ph2.get_bank(), PinBank::High); 73 | assert_eq!(format!("{}", ph2), "PH2"); 74 | 75 | let ph3 = dev.ph3().unwrap(); 76 | assert_eq!(ph3.get_bit(), 3); 77 | assert_eq!(ph3.get_bank(), PinBank::High); 78 | assert_eq!(format!("{}", ph3), "PH3"); 79 | 80 | let ph4 = dev.ph4().unwrap(); 81 | assert_eq!(ph4.get_bit(), 4); 82 | assert_eq!(ph4.get_bank(), PinBank::High); 83 | assert_eq!(format!("{}", ph4), "PH4"); 84 | 85 | let ph5 = dev.ph5().unwrap(); 86 | assert_eq!(ph5.get_bit(), 5); 87 | assert_eq!(ph5.get_bank(), PinBank::High); 88 | assert_eq!(format!("{}", ph5), "PH5"); 89 | 90 | let ph6 = dev.ph6().unwrap(); 91 | assert_eq!(ph6.get_bit(), 6); 92 | assert_eq!(ph6.get_bank(), PinBank::High); 93 | assert_eq!(format!("{}", ph6), "PH6"); 94 | 95 | let ph7 = dev.ph7().unwrap(); 96 | assert_eq!(ph7.get_bit(), 7); 97 | assert_eq!(ph7.get_bank(), PinBank::High); 98 | assert_eq!(format!("{}", ph7), "PH7"); 99 | } 100 | 101 | #[test] 102 | fn test_init_t4() { 103 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 104 | assert_eq!(dev.is_loopback(), false); 105 | 106 | let ph0_0 = dev.ph0(); 107 | let ph0_1 = dev.ph0(); 108 | let ph0_2 = dev.ph0(); 109 | 110 | assert!(ph0_0.is_ok(), "First pin instance should be OK"); 111 | assert!(ph0_1.is_err(), "There should be no second pin instance"); 112 | assert!(ph0_2.is_err(), "There should be no third pin instance"); 113 | } 114 | 115 | #[test] 116 | fn test_init_t5() { 117 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 118 | assert_eq!(dev.is_loopback(), false); 119 | 120 | let mut spidev = dev.spi(SpiSpeed::CLK_AUTO).unwrap(); 121 | 122 | let res = spidev.set_mode(MODE_0); 123 | assert!(res.is_ok(), "Can't set SPI MODE0"); 124 | 125 | let res = spidev.set_mode(MODE_1); 126 | assert!(res.is_err(), "SPI MODE1 should not be supported"); 127 | 128 | let res = spidev.set_mode(MODE_2); 129 | assert!(res.is_ok(), "Can't set SPI MODE2"); 130 | 131 | let res = spidev.set_mode(MODE_3); 132 | assert!(res.is_err(), "SPI MODE3 should not be supported"); 133 | } 134 | 135 | #[test] 136 | fn test_init_t6() { 137 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 138 | assert_eq!(dev.is_loopback(), false); 139 | 140 | let spi1 = dev.spi(SpiSpeed::CLK_AUTO); 141 | assert!(spi1.is_ok(), "1st spi instance should be ok"); 142 | 143 | let i2c = dev.i2c(I2cSpeed::CLK_AUTO); 144 | assert!(i2c.is_err(), "i2c instance after spi should not be ok"); 145 | 146 | let spi2 = dev.spi(SpiSpeed::CLK_AUTO); 147 | assert!(spi2.is_ok(), "2nd spi instance should be ok"); 148 | } 149 | 150 | #[test] 151 | fn test_init_t7() { 152 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 153 | assert_eq!(dev.is_loopback(), false); 154 | 155 | let i2c1 = dev.i2c(I2cSpeed::CLK_AUTO); 156 | assert!(i2c1.is_ok(), "1st i2c instance should be ok"); 157 | 158 | let spi = dev.spi(SpiSpeed::CLK_AUTO); 159 | assert!(spi.is_err(), "spi instance after i2c should not be ok"); 160 | 161 | let i2c2 = dev.i2c(I2cSpeed::CLK_AUTO); 162 | assert!(i2c2.is_ok(), "2nd i2c instance should be ok"); 163 | } 164 | 165 | #[test] 166 | fn test_init_t8() { 167 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 168 | assert_eq!(dev.is_loopback(), false); 169 | 170 | let spi1 = dev.spi(SpiSpeed::CLK_1MHz); 171 | assert!(spi1.is_ok(), "1st spi instance should be ok"); 172 | 173 | let spi2 = dev.spi(SpiSpeed::CLK_1MHz); 174 | assert!( 175 | spi2.is_ok(), 176 | "2nd spi instance with the same clock should be ok" 177 | ); 178 | 179 | let spi3 = dev.spi(SpiSpeed::CLK_3MHz); 180 | assert!( 181 | spi3.is_err(), 182 | "3rd spi failure: clock should be the same or auto" 183 | ); 184 | 185 | let spi4 = dev.spi(SpiSpeed::CLK_AUTO); 186 | assert!(spi4.is_ok(), "4th spi with AUTO clock should be ok"); 187 | } 188 | 189 | #[test] 190 | fn test_init_t9() { 191 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 192 | assert_eq!(dev.is_loopback(), false); 193 | 194 | let spi1 = dev.spi(SpiSpeed::CLK_AUTO); 195 | assert!(spi1.is_ok(), "1st spi instance should be ok"); 196 | 197 | let spi2 = dev.spi(SpiSpeed::CLK_1MHz); 198 | assert!( 199 | spi2.is_err(), 200 | "2nd spi instance with non-AUTO clock should fail" 201 | ); 202 | 203 | let spi3 = dev.spi(SpiSpeed::CLK_AUTO); 204 | assert!(spi3.is_ok(), "3rd spi with AUTO clock should be ok"); 205 | } 206 | 207 | #[test] 208 | fn test_init_t10() { 209 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 210 | assert_eq!(dev.is_loopback(), false); 211 | 212 | let i2c1 = dev.i2c(I2cSpeed::CLK_100kHz); 213 | assert!(i2c1.is_ok(), "1st i2c instance should be ok"); 214 | 215 | let i2c2 = dev.i2c(I2cSpeed::CLK_100kHz); 216 | assert!( 217 | i2c2.is_ok(), 218 | "2nd i2c instance with the same clock should be ok" 219 | ); 220 | 221 | let i2c3 = dev.i2c(I2cSpeed::CLK_400kHz); 222 | assert!( 223 | i2c3.is_err(), 224 | "3rd i2c failure: clk should be the same or auto" 225 | ); 226 | 227 | let i2c4 = dev.i2c(I2cSpeed::CLK_AUTO); 228 | assert!(i2c4.is_ok(), "4th i2c with AUTO clock should be ok"); 229 | } 230 | 231 | #[test] 232 | fn test_init_t11() { 233 | let dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 234 | assert_eq!(dev.is_loopback(), false); 235 | 236 | let i2c1 = dev.i2c(I2cSpeed::CLK_AUTO); 237 | assert!(i2c1.is_ok(), "1st i2c instance should be ok"); 238 | 239 | let i2c2 = dev.i2c(I2cSpeed::CLK_400kHz); 240 | assert!( 241 | i2c2.is_err(), 242 | "2nd i2c instance with non-AUTO clock should fail" 243 | ); 244 | 245 | let i2c3 = dev.i2c(I2cSpeed::CLK_AUTO); 246 | assert!(i2c3.is_ok(), "3rd i2c with AUTO clock should be ok"); 247 | } 248 | 249 | #[test] 250 | fn test_loopback_t1() { 251 | let mut dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 252 | dev.loopback(true).unwrap(); 253 | assert_eq!(dev.is_loopback(), true); 254 | 255 | let mut spidev = dev.spi(SpiSpeed::CLK_AUTO).unwrap(); 256 | 257 | // loopback: 1-byte messages 258 | for v in 0x0..0xff { 259 | let mut tx = [v; 1]; 260 | let cx = tx; 261 | let rx = spidev.transfer(&mut tx).unwrap(); 262 | 263 | assert_eq!(cx, rx); 264 | } 265 | } 266 | 267 | #[test] 268 | fn test_loopback_t2() { 269 | let mut dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 270 | dev.loopback(true).unwrap(); 271 | assert_eq!(dev.is_loopback(), true); 272 | 273 | let mut spidev = dev.spi(SpiSpeed::CLK_AUTO).unwrap(); 274 | 275 | // loopback: 3-byte messages 276 | for (x, y, z) in iproduct!(1..5, 11..15, 21..25) { 277 | let mut tx = [x, y, z]; 278 | let cx = tx; 279 | let rx = spidev.transfer(&mut tx).unwrap(); 280 | assert_eq!(cx, rx); 281 | } 282 | } 283 | 284 | #[test] 285 | fn test_loopback_t3() { 286 | let mut dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 287 | dev.loopback(true).unwrap(); 288 | assert_eq!(dev.is_loopback(), true); 289 | 290 | let mut spidev = dev.spi(SpiSpeed::CLK_AUTO).unwrap(); 291 | 292 | // loopback: 5-byte random messages 293 | for _ in 1..10 { 294 | let mut rng = rand::thread_rng(); 295 | let mut tx: Vec = (0..5) 296 | .map(|_| { 297 | // 0 (inclusive) to 254 (inclusive) 298 | rng.gen_range(0..255) 299 | }) 300 | .collect(); 301 | let cx = tx.clone(); 302 | let rx = spidev.transfer(&mut tx).unwrap(); 303 | assert_eq!(cx, rx); 304 | } 305 | } 306 | 307 | #[test] 308 | fn test_loopback_multi_bus_t1() { 309 | let mut dev = FTx232H::init_chan($vendor, $product, $channel).unwrap(); 310 | dev.loopback(true).unwrap(); 311 | assert_eq!(dev.is_loopback(), true); 312 | 313 | let mut spidev1 = dev.spi(SpiSpeed::CLK_AUTO).unwrap(); 314 | let mut spidev2 = dev.spi(SpiSpeed::CLK_AUTO).unwrap(); 315 | 316 | // loopback: 1-byte messages on both protocol buses 317 | for v in 0x0..0xff { 318 | let mut tx1 = [v; 1]; 319 | let cx1 = tx1; 320 | 321 | let mut tx2 = [v; 1]; 322 | let cx2 = tx2; 323 | 324 | let rx1 = spidev1.transfer(&mut tx1).unwrap(); 325 | let rx2 = spidev2.transfer(&mut tx2).unwrap(); 326 | 327 | assert_eq!(cx1, rx1); 328 | assert_eq!(cx2, rx2); 329 | } 330 | } 331 | }; 332 | } 333 | 334 | mod ft232h { 335 | ftdi_test_suite!(0x0403, 0x6014, Interface::A); 336 | } 337 | 338 | mod ft2232h_a { 339 | ftdi_test_suite!(0x0403, 0x6010, Interface::A); 340 | } 341 | 342 | mod ft2232h_b { 343 | ftdi_test_suite!(0x0403, 0x6010, Interface::B); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/spi.rs: -------------------------------------------------------------------------------- 1 | pub use embedded_hal::spi::{Mode, Phase, Polarity}; 2 | pub use embedded_hal::spi::{MODE_0, MODE_1, MODE_2, MODE_3}; 3 | 4 | use crate::error::{ErrorKind, Result, X232Error}; 5 | use crate::ftdimpsse::{ClockData, ClockDataIn, ClockDataOut, MpsseCmdBuilder}; 6 | 7 | use nb; 8 | 9 | use std::cell::RefCell; 10 | use std::io::{Read, Write}; 11 | use std::sync::Mutex; 12 | 13 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 14 | #[allow(non_camel_case_types)] 15 | pub enum SpiSpeed { 16 | CLK_AUTO, 17 | CLK_500kHz, 18 | CLK_1MHz, 19 | CLK_2MHz, 20 | CLK_2_5MHz, 21 | CLK_3MHz, 22 | CLK_5MHz, 23 | CLK_10MHz, 24 | CLK_20MHz, 25 | } 26 | 27 | pub struct SpiBus<'a> { 28 | ctx: &'a Mutex>, 29 | mode: Mode, 30 | cmd_r: ClockDataIn, 31 | cmd_w: ClockDataOut, 32 | cmd_rw: ClockData, 33 | } 34 | 35 | impl<'a> SpiBus<'a> { 36 | pub fn new(ctx: &'a Mutex>) -> SpiBus { 37 | SpiBus { 38 | ctx, 39 | mode: MODE_0, 40 | cmd_r: ClockDataIn::MsbPos, 41 | cmd_w: ClockDataOut::MsbNeg, 42 | // cmd_rw = cmd_r | cmd_w 43 | cmd_rw: ClockData::MsbPosIn, 44 | } 45 | } 46 | 47 | pub fn set_mode(&mut self, mode: Mode) -> Result<()> { 48 | if mode == MODE_0 { 49 | self.cmd_r = ClockDataIn::MsbPos; 50 | self.cmd_w = ClockDataOut::MsbNeg; 51 | // cmd_rw = cmd_r | cmd_w 52 | self.cmd_rw = ClockData::MsbPosIn; 53 | self.mode = mode; 54 | return Ok(()); 55 | } 56 | 57 | if mode == MODE_2 { 58 | self.cmd_r = ClockDataIn::MsbNeg; 59 | self.cmd_w = ClockDataOut::MsbPos; 60 | // cmd_rw = cmd_r | cmd_w 61 | self.cmd_rw = ClockData::MsbNegIn; 62 | self.mode = mode; 63 | return Ok(()); 64 | } 65 | 66 | Err(X232Error::HAL(ErrorKind::SpiModeNotSupported)) 67 | } 68 | 69 | pub fn get_mode(&mut self) -> Mode { 70 | self.mode 71 | } 72 | } 73 | 74 | impl<'a> embedded_hal::blocking::spi::Transfer for SpiBus<'a> { 75 | type Error = X232Error; 76 | 77 | fn transfer<'b>(&mut self, buffer: &'b mut [u8]) -> Result<&'b [u8]> { 78 | if buffer.is_empty() { 79 | return Ok(buffer); 80 | } 81 | 82 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 83 | .clock_data(self.cmd_rw, buffer) 84 | .send_immediate(); 85 | 86 | let lock = self.ctx.lock().unwrap(); 87 | let mut ftdi = lock.borrow_mut(); 88 | 89 | ftdi.usb_purge_buffers()?; 90 | ftdi.write_all(cmd.as_slice())?; 91 | ftdi.read_exact(buffer)?; 92 | 93 | Ok(buffer) 94 | } 95 | } 96 | 97 | impl<'a> embedded_hal::blocking::spi::Write for SpiBus<'a> { 98 | type Error = X232Error; 99 | 100 | fn write(&mut self, buffer: &[u8]) -> Result<()> { 101 | if buffer.is_empty() { 102 | return Ok(()); 103 | } 104 | 105 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 106 | .clock_data_out(self.cmd_w, buffer) 107 | .send_immediate(); 108 | 109 | let lock = self.ctx.lock().unwrap(); 110 | let mut ftdi = lock.borrow_mut(); 111 | 112 | ftdi.usb_purge_buffers()?; 113 | ftdi.write_all(cmd.as_slice())?; 114 | 115 | Ok(()) 116 | } 117 | } 118 | 119 | impl<'a> embedded_hal::spi::FullDuplex for SpiBus<'a> { 120 | type Error = X232Error; 121 | 122 | fn read(&mut self) -> nb::Result { 123 | let mut buffer: [u8; 1] = [0]; 124 | 125 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 126 | .clock_data(self.cmd_rw, &buffer) 127 | .send_immediate(); 128 | 129 | let lock = self.ctx.lock().unwrap(); 130 | let mut ftdi = lock.borrow_mut(); 131 | 132 | ftdi.usb_purge_buffers() 133 | .map_err(|e| nb::Error::Other(X232Error::FTDI(e)))?; 134 | ftdi.write_all(cmd.as_slice()) 135 | .map_err(|e| nb::Error::Other(X232Error::Io(e)))?; 136 | ftdi.read_exact(&mut buffer) 137 | .map_err(|e| nb::Error::Other(X232Error::Io(e)))?; 138 | 139 | Ok(buffer[0]) 140 | } 141 | 142 | fn send(&mut self, byte: u8) -> nb::Result<(), X232Error> { 143 | let cmd: MpsseCmdBuilder = MpsseCmdBuilder::new() 144 | .clock_data_out(self.cmd_w, &[byte]) 145 | .send_immediate(); 146 | 147 | let lock = self.ctx.lock().unwrap(); 148 | let mut ftdi = lock.borrow_mut(); 149 | 150 | ftdi.usb_purge_buffers() 151 | .map_err(|e| nb::Error::Other(X232Error::FTDI(e)))?; 152 | ftdi.write_all(cmd.as_slice()) 153 | .map_err(|e| nb::Error::Other(X232Error::Io(e)))?; 154 | 155 | Ok(()) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/x232h.rs: -------------------------------------------------------------------------------- 1 | use ftdi::BitMode; 2 | pub use ftdi::Interface; 3 | 4 | use crate::error::{ErrorKind, Result, X232Error}; 5 | 6 | use crate::ftdimpsse::MpsseCmd; 7 | use crate::ftdimpsse::MpsseCmdBuilder; 8 | use crate::gpio::GpioPin; 9 | use crate::gpio::PinBank; 10 | use crate::i2c::I2cBus; 11 | use crate::i2c::I2cSpeed; 12 | use crate::spi::SpiBus; 13 | use crate::spi::SpiSpeed; 14 | 15 | use std::cell::RefCell; 16 | use std::io::Write; 17 | use std::sync::Mutex; 18 | 19 | pub struct FTx232H { 20 | mtx: Mutex>, 21 | loopback: bool, 22 | 23 | i2c: RefCell>, 24 | spi: RefCell>, 25 | 26 | pl0: RefCell, 27 | pl1: RefCell, 28 | pl2: RefCell, 29 | pl3: RefCell, 30 | 31 | ph0: RefCell, 32 | ph1: RefCell, 33 | ph2: RefCell, 34 | ph3: RefCell, 35 | ph4: RefCell, 36 | ph5: RefCell, 37 | ph6: RefCell, 38 | ph7: RefCell, 39 | } 40 | 41 | impl FTx232H { 42 | pub fn init(vendor: u16, product: u16) -> Result { 43 | FTx232H::init_ctx(vendor, product, ftdi::Interface::A) 44 | } 45 | 46 | pub fn init_chan(vendor: u16, product: u16, intf: ftdi::Interface) -> Result { 47 | FTx232H::init_ctx(vendor, product, intf) 48 | } 49 | 50 | fn init_ctx(vendor: u16, product: u16, intf: ftdi::Interface) -> Result { 51 | let mut device = ftdi::find_by_vid_pid(vendor, product) 52 | .interface(intf) 53 | .open()?; 54 | 55 | device.set_write_chunksize(1024); 56 | device.set_read_chunksize(1024); 57 | device.usb_reset()?; 58 | device.set_latency_timer(5)?; 59 | device.set_bitmode(0, BitMode::Mpsse)?; 60 | device.usb_purge_buffers()?; 61 | 62 | // Device settings: 63 | // - disable DIV_5 => 60MHz 64 | // - disable adaptive clocking 65 | // - disable 3-phase clocking 66 | // - disable loopback 67 | // - low bits: all outputs(0) 68 | // FIXME: current approach is limited: fixed in/out pin configuration: 69 | let cmd_init = MpsseCmdBuilder::with_vec(vec![MpsseCmd::DisableClockDivide.into()]) 70 | .disable_adaptive_data_clocking() 71 | .disable_3phase_data_clocking() 72 | .disable_loopback() 73 | .set_gpio_lower(0x0, 0b1111_1111) 74 | .set_gpio_upper(0x0, 0b1111_1111); 75 | 76 | device.write_all(cmd_init.as_slice())?; 77 | 78 | let d = FTx232H { 79 | mtx: Mutex::new(RefCell::new(device)), 80 | loopback: false, 81 | 82 | i2c: RefCell::new(None), 83 | spi: RefCell::new(None), 84 | 85 | pl0: RefCell::new(true), 86 | pl1: RefCell::new(true), 87 | pl2: RefCell::new(true), 88 | pl3: RefCell::new(true), 89 | 90 | ph0: RefCell::new(true), 91 | ph1: RefCell::new(true), 92 | ph2: RefCell::new(true), 93 | ph3: RefCell::new(true), 94 | ph4: RefCell::new(true), 95 | ph5: RefCell::new(true), 96 | ph6: RefCell::new(true), 97 | ph7: RefCell::new(true), 98 | }; 99 | 100 | Ok(d) 101 | } 102 | 103 | pub fn loopback(&mut self, lp: bool) -> Result<()> { 104 | self.loopback = lp; 105 | 106 | let cmd = if lp { 107 | MpsseCmdBuilder::new().enable_loopback() 108 | } else { 109 | MpsseCmdBuilder::new().disable_loopback() 110 | }; 111 | 112 | let lock = self.mtx.lock().unwrap(); 113 | let mut ftdi = lock.borrow_mut(); 114 | 115 | ftdi.write_all(cmd.as_slice())?; 116 | 117 | Ok(()) 118 | } 119 | 120 | pub fn is_loopback(&self) -> bool { 121 | self.loopback 122 | } 123 | 124 | // spi/i2c buses 125 | 126 | pub fn spi(&self, speed: SpiSpeed) -> Result { 127 | if (*self.i2c.borrow()).is_some() { 128 | return Err(X232Error::HAL(ErrorKind::BusBusy)); 129 | } 130 | 131 | if (*self.spi.borrow()).is_none() { 132 | let lock = self.mtx.lock().unwrap(); 133 | let mut ftdi = lock.borrow_mut(); 134 | 135 | self.spi.replace(Some(speed)); 136 | 137 | // SPI: DI - input, DO - output(0), SK - output(0) 138 | ftdi.write_all( 139 | MpsseCmdBuilder::new() 140 | .set_gpio_lower(0x0, 0b1111_1011) 141 | .as_slice(), 142 | )?; 143 | 144 | // FIXME: set fixed speed 1MHz for all devices assuming 60MHz clock 145 | // SCK_freq = 60MHz / ((1 + (div1 | (div2 << 8))) * 2) 146 | let (div1, div2) = match speed { 147 | SpiSpeed::CLK_500kHz => (0x3b, 0x0), 148 | SpiSpeed::CLK_1MHz | SpiSpeed::CLK_AUTO => (0x1d, 0x0), 149 | SpiSpeed::CLK_2MHz => (0xe, 0x0), 150 | SpiSpeed::CLK_2_5MHz => (0xb, 0x0), 151 | SpiSpeed::CLK_3MHz => (0x9, 0x0), 152 | SpiSpeed::CLK_5MHz => (0x5, 0x0), 153 | SpiSpeed::CLK_10MHz => (0x2, 0x0), 154 | SpiSpeed::CLK_20MHz => (0x1, 0x0), 155 | }; 156 | 157 | ftdi.write_all( 158 | MpsseCmdBuilder::with_vec(vec![MpsseCmd::SetClockFrequency.into(), div1, div2]) 159 | .as_slice(), 160 | )?; 161 | } else if speed != SpiSpeed::CLK_AUTO { 162 | // clock sanity check 163 | if Some(speed) != *self.spi.borrow() { 164 | return Err(X232Error::HAL(ErrorKind::InvalidClock)); 165 | } 166 | } 167 | 168 | Ok(SpiBus::new(&self.mtx)) 169 | } 170 | 171 | pub fn i2c(&self, speed: I2cSpeed) -> Result { 172 | if (*self.spi.borrow()).is_some() { 173 | return Err(X232Error::HAL(ErrorKind::BusBusy)); 174 | } 175 | 176 | if (*self.i2c.borrow()).is_none() { 177 | let lock = self.mtx.lock().unwrap(); 178 | let mut ftdi = lock.borrow_mut(); 179 | 180 | self.i2c.replace(Some(speed)); 181 | 182 | // I2C: DI - input, DO - output(0), SK - output(0) 183 | ftdi.write_all( 184 | MpsseCmdBuilder::new() 185 | .set_gpio_lower(0x0, 0b1111_1011) 186 | .as_slice(), 187 | )?; 188 | 189 | // FIXME: set fixed speed 1MHz for all devices assuming 60MHz clock 190 | // SCK_freq = 60MHz / ((1 + (div1 | (div2 << 8))) * 2) 191 | let (div1, div2) = match speed { 192 | I2cSpeed::CLK_100kHz | I2cSpeed::CLK_AUTO => (0x2b, 0x1), 193 | I2cSpeed::CLK_400kHz => (0x4a, 0x0), 194 | }; 195 | 196 | ftdi.write_all( 197 | MpsseCmdBuilder::with_vec(vec![MpsseCmd::SetClockFrequency.into(), div1, div2]) 198 | .as_slice(), 199 | )?; 200 | } else if speed != I2cSpeed::CLK_AUTO { 201 | // clock sanity check 202 | if Some(speed) != *self.i2c.borrow() { 203 | return Err(X232Error::HAL(ErrorKind::InvalidClock)); 204 | } 205 | } 206 | 207 | Ok(I2cBus::new(&self.mtx)) 208 | } 209 | 210 | // gpio pins: low bank 211 | crate::declare_gpio_pin!(pl0, 4, PinBank::Low); 212 | crate::declare_gpio_pin!(pl1, 5, PinBank::Low); 213 | crate::declare_gpio_pin!(pl2, 6, PinBank::Low); 214 | crate::declare_gpio_pin!(pl3, 7, PinBank::Low); 215 | 216 | // gpio pins: high bank 217 | crate::declare_gpio_pin!(ph0, 0, PinBank::High); 218 | crate::declare_gpio_pin!(ph1, 1, PinBank::High); 219 | crate::declare_gpio_pin!(ph2, 2, PinBank::High); 220 | crate::declare_gpio_pin!(ph3, 3, PinBank::High); 221 | crate::declare_gpio_pin!(ph4, 4, PinBank::High); 222 | crate::declare_gpio_pin!(ph5, 5, PinBank::High); 223 | crate::declare_gpio_pin!(ph6, 6, PinBank::High); 224 | crate::declare_gpio_pin!(ph7, 7, PinBank::High); 225 | } 226 | 227 | impl Drop for FTx232H { 228 | fn drop(&mut self) { 229 | let lock = match self.mtx.lock() { 230 | Ok(guard) => guard, 231 | Err(poisoned) => poisoned.into_inner(), 232 | }; 233 | let mut ftdi = lock.borrow_mut(); 234 | 235 | ftdi.usb_purge_buffers().unwrap(); 236 | } 237 | } 238 | --------------------------------------------------------------------------------