395 lines
12 KiB
Rust
395 lines
12 KiB
Rust
use ftdi_vcp_sys::{
|
|
FT_Close, FT_GetBitMode, FT_GetComPortNumber, FT_GetLatencyTimer, FT_OpenEx, FT_Purge, FT_Read,
|
|
FT_ResetDevice, FT_SetBitMode, FT_SetLatencyTimer, FT_Write, DWORD, FT_HANDLE,
|
|
FT_OPEN_BY_DESCRIPTION, FT_STATUS, LONG, LPDWORD, LPVOID, PVOID, UCHAR,
|
|
};
|
|
use std::convert::TryInto;
|
|
use std::ffi::CString;
|
|
use std::io::{Read, Write};
|
|
use std::mem::MaybeUninit;
|
|
|
|
pub mod mpsse;
|
|
|
|
#[derive(Debug)]
|
|
pub enum BitMode {
|
|
Reset,
|
|
AsyncBitbang,
|
|
MPSSE,
|
|
SyncBitbang,
|
|
MCUHost,
|
|
FastSerial,
|
|
CBUSBitbang,
|
|
SyncFIFO,
|
|
Unknown(u8),
|
|
}
|
|
|
|
impl From<UCHAR> for BitMode {
|
|
fn from(src: UCHAR) -> Self {
|
|
use BitMode::*;
|
|
match src {
|
|
0x00 => Reset,
|
|
0x01 => AsyncBitbang,
|
|
0x02 => MPSSE,
|
|
0x04 => SyncBitbang,
|
|
0x08 => MCUHost,
|
|
0x10 => FastSerial,
|
|
0x20 => CBUSBitbang,
|
|
0x40 => SyncFIFO,
|
|
x => Unknown(x),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl BitMode {
|
|
pub fn to_u8(&self) -> UCHAR {
|
|
use BitMode::*;
|
|
match *self {
|
|
Reset => 0x00,
|
|
AsyncBitbang => 0x01,
|
|
MPSSE => 0x02,
|
|
SyncBitbang => 0x04,
|
|
MCUHost => 0x08,
|
|
FastSerial => 0x10,
|
|
CBUSBitbang => 0x20,
|
|
SyncFIFO => 0x40,
|
|
Unknown(x) => x,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Debug)]
|
|
pub enum Error {
|
|
NoError,
|
|
InvalidHandle,
|
|
DeviceNotFound,
|
|
DeviceNotOpened,
|
|
IoError,
|
|
InsufficientResources,
|
|
InvalidParameter,
|
|
InvalidBaudRate,
|
|
DeviceNotOpenedForErase,
|
|
DeviceNotOpenedForWrite,
|
|
FailedToWriteDevice,
|
|
EepromReadFailed,
|
|
EepromWriteFailed,
|
|
EepromEraseFailed,
|
|
EepromNotPresent,
|
|
EepromNotProgrammed,
|
|
InvalidArgs,
|
|
NotSupported,
|
|
OtherError,
|
|
DeviceListNotReady,
|
|
NoComPortAssigned,
|
|
|
|
/// The provided string contained at least one NULL byte
|
|
StringContainsNullByte,
|
|
|
|
// UnknownBitMode(u8),
|
|
UnknownError(FT_STATUS),
|
|
}
|
|
|
|
impl std::fmt::Display for Error {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "VCPError: {:?}", self)
|
|
}
|
|
}
|
|
impl std::error::Error for Error {}
|
|
|
|
impl From<FT_STATUS> for Error {
|
|
fn from(src: FT_STATUS) -> Self {
|
|
match src {
|
|
0 => Error::NoError,
|
|
1 => Error::InvalidHandle,
|
|
2 => Error::DeviceNotFound,
|
|
3 => Error::DeviceNotOpened,
|
|
4 => Error::IoError,
|
|
5 => Error::InsufficientResources,
|
|
6 => Error::InvalidParameter,
|
|
7 => Error::InvalidBaudRate,
|
|
8 => Error::DeviceNotOpenedForErase,
|
|
9 => Error::DeviceNotOpenedForWrite,
|
|
10 => Error::FailedToWriteDevice,
|
|
11 => Error::EepromReadFailed,
|
|
12 => Error::EepromWriteFailed,
|
|
13 => Error::EepromEraseFailed,
|
|
14 => Error::EepromNotPresent,
|
|
15 => Error::EepromNotProgrammed,
|
|
16 => Error::InvalidArgs,
|
|
17 => Error::NotSupported,
|
|
18 => Error::OtherError,
|
|
19 => Error::DeviceListNotReady,
|
|
x => Error::UnknownError(x),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct VCP {
|
|
handle: FT_HANDLE,
|
|
bit_mode: BitMode,
|
|
}
|
|
|
|
impl VCP {
|
|
pub fn new_from_name(name: &str) -> Result<VCP, Error> {
|
|
let c_str = CString::new(name).or(Err(Error::StringContainsNullByte))?;
|
|
let mut handle = MaybeUninit::<FT_HANDLE>::uninit();
|
|
let result = Error::from(unsafe {
|
|
FT_OpenEx(
|
|
c_str.as_ptr() as PVOID,
|
|
FT_OPEN_BY_DESCRIPTION,
|
|
handle.as_mut_ptr(),
|
|
)
|
|
});
|
|
if result != Error::NoError {
|
|
return Err(result);
|
|
}
|
|
let handle = unsafe { handle.assume_init() };
|
|
|
|
let mut bit_mode = MaybeUninit::<UCHAR>::uninit();
|
|
let result = Error::from(unsafe { FT_GetBitMode(handle, bit_mode.as_mut_ptr()) });
|
|
if result != Error::NoError {
|
|
unsafe { FT_Close(handle) };
|
|
return Err(result);
|
|
}
|
|
let bit_mode = BitMode::from(unsafe { bit_mode.assume_init() });
|
|
Ok(VCP { handle, bit_mode })
|
|
}
|
|
|
|
pub fn com_port(&self) -> Result<usize, Error> {
|
|
let mut com_port_number = MaybeUninit::<LONG>::uninit();
|
|
let result =
|
|
Error::from(unsafe { FT_GetComPortNumber(self.handle, com_port_number.as_mut_ptr()) });
|
|
if result != Error::NoError {
|
|
Err(result)
|
|
} else {
|
|
let com_port_number = unsafe { com_port_number.assume_init() };
|
|
if let Ok(e) = com_port_number.try_into() {
|
|
Ok(e)
|
|
} else {
|
|
Err(Error::NoComPortAssigned)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Set the given signals to "OUTPUT". All other signals will be "INPUT".
|
|
pub fn set_bitmode(&mut self, outputs: u8, bitmode: BitMode) -> Result<(), Error> {
|
|
let result = Error::from(unsafe { FT_SetBitMode(self.handle, outputs, bitmode.to_u8()) });
|
|
if result != Error::NoError {
|
|
Err(result)
|
|
} else {
|
|
self.bit_mode = bitmode;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn reset(&mut self) -> Result<(), Error> {
|
|
let result = Error::from(unsafe { FT_ResetDevice(self.handle) });
|
|
if result != Error::NoError {
|
|
Err(result)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn purge(&mut self) -> Result<(), Error> {
|
|
let result = Error::from(unsafe {
|
|
FT_Purge(
|
|
self.handle,
|
|
ftdi_vcp_sys::FT_PURGE_RX | ftdi_vcp_sys::FT_PURGE_TX,
|
|
)
|
|
});
|
|
if result != Error::NoError {
|
|
Err(result)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn latency_timer(&mut self) -> Result<u8, Error> {
|
|
let mut latency = MaybeUninit::<UCHAR>::uninit();
|
|
let result = Error::from(unsafe { FT_GetLatencyTimer(self.handle, latency.as_mut_ptr()) });
|
|
if result != Error::NoError {
|
|
Err(result)
|
|
} else {
|
|
Ok(unsafe { latency.assume_init() })
|
|
}
|
|
}
|
|
|
|
pub fn set_latency_timer(&mut self, latency: u8) -> Result<(), Error> {
|
|
let result = Error::from(unsafe { FT_SetLatencyTimer(self.handle, latency) });
|
|
if result != Error::NoError {
|
|
Err(result)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// pub fn write(&mut self, out_buffer: &[u8]) -> Result<usize, Error> {
|
|
// let mut bytes_written = MaybeUninit::<DWORD>::uninit();
|
|
// let result = Error::from(unsafe {
|
|
// FT_Write(
|
|
// self.handle,
|
|
// out_buffer.as_ptr() as LPVOID,
|
|
// out_buffer
|
|
// .len()
|
|
// .try_into()
|
|
// .expect("couldn't convert buffer length to DWORD"),
|
|
// bytes_written.as_mut_ptr() as LPDWORD,
|
|
// )
|
|
// });
|
|
// if result != Error::NoError {
|
|
// Err(result)
|
|
// } else {
|
|
// Ok(unsafe { bytes_written.assume_init() }
|
|
// .try_into()
|
|
// .expect("invalid number of bytes written"))
|
|
// }
|
|
// }
|
|
|
|
pub fn set_gpio(&mut self, value: u8, direction: u8) -> Result<(), Error> {
|
|
match self.bit_mode {
|
|
BitMode::MPSSE => self
|
|
.write_all(&[mpsse::Command::MC_SETB_LOW.to_u8(), value, direction])
|
|
.or_else(|_| Err(Error::IoError)),
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
|
|
pub fn readb_low(&mut self) -> Result<u8, Error> {
|
|
self.write_all(&[mpsse::Command::MC_READB_LOW.to_u8()])
|
|
.or_else(|_| Err(Error::IoError))?;
|
|
let mut result = [0; 1];
|
|
self.read(&mut result).or_else(|_| Err(Error::IoError))?;
|
|
Ok(result[0])
|
|
}
|
|
|
|
pub fn readb_high(&mut self) -> Result<u8, Error> {
|
|
self.write_all(&[mpsse::Command::MC_READB_HIGH.to_u8()])
|
|
.or_else(|_| Err(Error::IoError))?;
|
|
let mut result = [0; 1];
|
|
self.read(&mut result).or_else(|_| Err(Error::IoError))?;
|
|
Ok(result[0])
|
|
}
|
|
|
|
/// The purpose of this function is unclear. It appears to send some number
|
|
/// of bits out the line.
|
|
pub fn xfer_spi_bits(&mut self, data: u8, bits: usize) -> Result<u8, Error> {
|
|
if bits < 1 {
|
|
return Ok(0);
|
|
}
|
|
|
|
let buffer = &[
|
|
/* Input and output, update data on negative edge read on positive, bits. */
|
|
0x20 /*MC_DATA_IN*/ | 0x10 /*MC_DATA_OUT*/ | 0x01 /*MC_DATA_OCN*/ | 0x02, /*MC_DATA_BITS*/
|
|
bits as u8 - 1,
|
|
data,
|
|
];
|
|
self.write_all(buffer).or_else(|_| Err(Error::IoError))?;
|
|
|
|
let mut return_val = [0; 1];
|
|
self.read_exact(&mut return_val)
|
|
.or_else(|_| Err(Error::IoError))?;
|
|
Ok(return_val[0])
|
|
}
|
|
|
|
pub fn xfer_spi(&mut self, data: &mut [u8]) -> Result<(), Error> {
|
|
if data.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
/* Input and output, update data on negative edge read on positive. */
|
|
let buffer = &[
|
|
0x20 /*MC_DATA_IN*/ | 0x10 /*MC_DATA_OUT*/ | 0x01, /*MC_DATA_OCN*/
|
|
(data.len() - 1) as u8,
|
|
((data.len() - 1) / 256) as u8,
|
|
];
|
|
self.write_all(buffer).or_else(|_| Err(Error::IoError))?;
|
|
self.write_all(data).or_else(|_| Err(Error::IoError))?;
|
|
|
|
self.read_exact(data).or_else(|_| Err(Error::IoError))?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn send_spi(&mut self, data: &[u8]) -> Result<(), Error> {
|
|
if data.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
/* Input and output, update data on negative edge read on positive. */
|
|
let buffer = &[
|
|
0x10 /*MC_DATA_OUT*/ | 0x01, /*MC_DATA_OCN*/
|
|
(data.len() - 1) as u8,
|
|
((data.len() - 1) / 256) as u8,
|
|
];
|
|
self.write_all(buffer).or_else(|_| Err(Error::IoError))?;
|
|
self.write_all(data).or_else(|_| Err(Error::IoError))?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Write for VCP {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
let mut bytes_written = MaybeUninit::<DWORD>::uninit();
|
|
let result = Error::from(unsafe {
|
|
FT_Write(
|
|
self.handle,
|
|
buf.as_ptr() as LPVOID,
|
|
buf.len()
|
|
.try_into()
|
|
.expect("couldn't convert buffer length to DWORD"),
|
|
bytes_written.as_mut_ptr() as LPDWORD,
|
|
)
|
|
});
|
|
if result != Error::NoError {
|
|
Err(std::io::Error::new(std::io::ErrorKind::Other, result))
|
|
} else {
|
|
let bytes_written = unsafe { bytes_written.assume_init() };
|
|
// println!(
|
|
// "Wrote {} bytes (wanted to write {})",
|
|
// bytes_written,
|
|
// buf.len()
|
|
// );
|
|
Ok(bytes_written
|
|
.try_into()
|
|
.expect("invalid number of bytes written"))
|
|
}
|
|
}
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Read for VCP {
|
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
|
|
let mut bytes_read = MaybeUninit::<DWORD>::uninit();
|
|
let result = Error::from(unsafe {
|
|
FT_Read(
|
|
self.handle,
|
|
buf.as_mut_ptr() as LPVOID,
|
|
buf.len()
|
|
.try_into()
|
|
.expect("couldn't convert buffer length to DWORD"),
|
|
bytes_read.as_mut_ptr() as LPDWORD,
|
|
)
|
|
});
|
|
if result != Error::NoError {
|
|
Err(std::io::Error::new(std::io::ErrorKind::Other, result))
|
|
} else {
|
|
let bytes_read = unsafe { bytes_read.assume_init() };
|
|
// println!("Read {} bytes (wanted to read {})", bytes_read, buf.len());
|
|
Ok(bytes_read
|
|
.try_into()
|
|
.expect("invalid number of bytes written"))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for VCP {
|
|
fn drop(&mut self) {
|
|
let result = Error::from(unsafe { FT_Close(self.handle) });
|
|
if result != Error::NoError {
|
|
panic!("unable to close device: {:?}", result);
|
|
}
|
|
}
|
|
}
|