From 6b0c588a46953e472f05ab5adc8260f2b25eb3bf Mon Sep 17 00:00:00 2001 From: Kyle Schwarz Date: Thu, 20 Apr 2023 20:42:36 +0000 Subject: [PATCH] Driver: Add FTD3XX --- CMakeLists.txt | 14 +++ README.md | 6 ++ api/icsneocpp/event.cpp | 105 ++++++++++++++++++- cmake/FindFTD3XX.cmake | 24 +++++ device/devicefinder.cpp | 8 ++ include/icsneo/api/event.h | 37 ++++++- include/icsneo/platform/ftd3xx.h | 33 ++++++ include/icsneo/platform/ftdi3.h | 19 ---- platform/ftd3xx.cpp | 174 +++++++++++++++++++++++++++++++ 9 files changed, 399 insertions(+), 21 deletions(-) create mode 100644 cmake/FindFTD3XX.cmake create mode 100644 include/icsneo/platform/ftd3xx.h delete mode 100644 include/icsneo/platform/ftdi3.h create mode 100644 platform/ftd3xx.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f46726..1d4b458 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.2) project(libicsneo VERSION 0.3.0) +cmake_policy(SET CMP0074 OLD) + option(LIBICSNEO_BUILD_TESTS "Build all tests." OFF) option(LIBICSNEO_BUILD_DOCS "Build documentation. Don't use in Visual Studio." OFF) option(LIBICSNEO_BUILD_EXAMPLES "Build examples." ON) @@ -158,6 +160,14 @@ else() # Darwin or Linux endif() endif() +find_package(FTD3XX) +if(FTD3XX_FOUND) + set(LIBICSNEO_ENABLE_FTD3XX 1) + list(APPEND PLATFORM_SRC + platform/ftd3xx.cpp + ) +endif() + if(LIBICSNEO_ENABLE_TCP) list(APPEND PLATFORM_SRC platform/tcp.cpp @@ -291,6 +301,10 @@ endif() if(LIBICSNEO_ENABLE_FTDI) target_compile_definitions(icsneocpp PRIVATE ICSNEO_ENABLE_FTDI) endif() +if(LIBICSNEO_ENABLE_FTD3XX) + target_compile_definitions(icsneocpp PRIVATE ICSNEO_ENABLE_FTD3XX) + target_link_libraries(icsneocpp PRIVATE FTD3XX::FTD3XX) +endif() if(LIBICSNEO_ENABLE_TCP) target_compile_definitions(icsneocpp PRIVATE ICSNEO_ENABLE_TCP) if(WIN32) diff --git a/README.md b/README.md index 1984683..4c9975b 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,12 @@ icsneo_closeDevice(myDevice); ``` ## Building from Source +### FTD3XX +Some devices require FTD3XX for USB communication: +1. Download the archive for the target platform from [FTDI's website](https://ftdichip.com/drivers/d3xx-drivers/) + - Windows users should download the "Application Library (DLL)" package +2. Extract the archive +3. Configure libicsneo with the CMake option `FTD3XX_ROOT` set to the path containing `f3d3xx.h` (`-DFTD3XX_ROOT=`) ### Windows Building will require MSVC 2017 version 15.7 or newer and CMake to be installed. ### macOS diff --git a/api/icsneocpp/event.cpp b/api/icsneocpp/event.cpp index 82a4abb..d817013 100644 --- a/api/icsneocpp/event.cpp +++ b/api/icsneocpp/event.cpp @@ -133,6 +133,41 @@ static constexpr const char* ERROR_SETTING_SOCKET_OPTION = "A call to setsockopt static constexpr const char* GETIFADDRS_ERROR = "A call to getifaddrs() failed."; static constexpr const char* SEND_TO_ERROR = "A call to sendto() failed."; +// FTD3XX +static constexpr const char* FT_OK = "FTD3XX success."; +static constexpr const char* FT_INVALID_HANDLE = "Invalid FTD3XX handle."; +static constexpr const char* FT_DEVICE_NOT_FOUND = "FTD3XX device not found."; +static constexpr const char* FT_DEVICE_NOT_OPENED = "FTD3XX device not opened."; +static constexpr const char* FT_IO_ERROR = "FTD3XX IO error."; +static constexpr const char* FT_INSUFFICIENT_RESOURCES = "Insufficient resources for FTD3XX."; +static constexpr const char* FT_INVALID_PARAMETER = "Invalid FTD3XX parameter."; +static constexpr const char* FT_INVALID_BAUD_RATE = "Invalid FTD3XX baud rate."; +static constexpr const char* FT_DEVICE_NOT_OPENED_FOR_ERASE = "FTD3XX device not opened for erase."; +static constexpr const char* FT_DEVICE_NOT_OPENED_FOR_WRITE = "FTD3XX not opened for write."; +static constexpr const char* FT_FAILED_TO_WRITE_DEVICE = "FTD3XX failed to write device."; +static constexpr const char* FT_EEPROM_READ_FAILED = "FTD3XX EEPROM read failed."; +static constexpr const char* FT_EEPROM_WRITE_FAILED = "FTD3XX EEPROM write failed."; +static constexpr const char* FT_EEPROM_ERASE_FAILED = "FTD3XX EEPROM erase failed."; +static constexpr const char* FT_EEPROM_NOT_PRESENT = "FTD3XX EEPROM not present."; +static constexpr const char* FT_EEPROM_NOT_PROGRAMMED = "FTD3XX EEPROM not programmed."; +static constexpr const char* FT_INVALID_ARGS = "Invalid FTD3XX arguments."; +static constexpr const char* FT_NOT_SUPPORTED = "FTD3XX not supported."; +static constexpr const char* FT_NO_MORE_ITEMS = "No more FTD3XX items."; +static constexpr const char* FT_TIMEOUT = "FTD3XX timeout."; +static constexpr const char* FT_OPERATION_ABORTED = "FTD3XX operation aborted."; +static constexpr const char* FT_RESERVED_PIPE = "Reserved FTD3XX pipe."; +static constexpr const char* FT_INVALID_CONTROL_REQUEST_DIRECTION = "Invalid FTD3XX control request direction."; +static constexpr const char* FT_INVALID_CONTROL_REQUEST_TYPE = "Invalid FTD3XX control request type."; +static constexpr const char* FT_IO_PENDING = "FTD3XX IO pending."; +static constexpr const char* FT_IO_INCOMPLETE = "FTD3XX IO incomplete."; +static constexpr const char* FT_HANDLE_EOF = "Handle FTD3XX EOF."; +static constexpr const char* FT_BUSY = "FTD3XX busy."; +static constexpr const char* FT_NO_SYSTEM_RESOURCES = "No FTD3XX system resources."; +static constexpr const char* FT_DEVICE_LIST_NOT_READY = "FTD3XX device list not ready."; +static constexpr const char* FT_DEVICE_NOT_CONNECTED = "FTD3XX device not connected."; +static constexpr const char* FT_INCORRECT_DEVICE_PATH = "Incorrect FTD3XX device path."; +static constexpr const char* FT_OTHER_ERROR = "Other FTD3XX error."; + static constexpr const char* TOO_MANY_EVENTS = "Too many events have occurred. The list has been truncated."; static constexpr const char* UNKNOWN = "An unknown internal error occurred."; static constexpr const char* INVALID = "An invalid internal error occurred."; @@ -286,7 +321,75 @@ const char* APIEvent::DescriptionForType(Type type) { return GETIFADDRS_ERROR; case Type::SendToError: return SEND_TO_ERROR; - + + // FTD3XX + case Type::FTOK: + return FT_OK; + case Type::FTInvalidHandle: + return FT_INVALID_HANDLE; + case Type::FTDeviceNotFound: + return FT_DEVICE_NOT_FOUND; + case Type::FTDeviceNotOpened: + return FT_DEVICE_NOT_OPENED; + case Type::FTIOError: + return FT_IO_ERROR; + case Type::FTInsufficientResources: + return FT_INSUFFICIENT_RESOURCES; + case Type::FTInvalidParameter: + return FT_INVALID_PARAMETER; + case Type::FTInvalidBaudRate: + return FT_INVALID_BAUD_RATE; + case Type::FTDeviceNotOpenedForErase: + return FT_DEVICE_NOT_OPENED_FOR_ERASE; + case Type::FTDeviceNotOpenedForWrite: + return FT_DEVICE_NOT_OPENED_FOR_WRITE; + case Type::FTFailedToWriteDevice: + return FT_FAILED_TO_WRITE_DEVICE; + case Type::FTEEPROMReadFailed: + return FT_EEPROM_READ_FAILED; + case Type::FTEEPROMWriteFailed: + return FT_EEPROM_WRITE_FAILED; + case Type::FTEEPROMEraseFailed: + return FT_EEPROM_ERASE_FAILED; + case Type::FTEEPROMNotPresent: + return FT_EEPROM_NOT_PRESENT; + case Type::FTEEPROMNotProgrammed: + return FT_EEPROM_NOT_PROGRAMMED; + case Type::FTInvalidArgs: + return FT_INVALID_ARGS; + case Type::FTNotSupported: + return FT_NOT_SUPPORTED; + case Type::FTNoMoreItems: + return FT_NO_MORE_ITEMS; + case Type::FTTimeout: + return FT_TIMEOUT; + case Type::FTOperationAborted: + return FT_OPERATION_ABORTED; + case Type::FTReservedPipe: + return FT_RESERVED_PIPE; + case Type::FTInvalidControlRequestDirection: + return FT_INVALID_CONTROL_REQUEST_DIRECTION; + case Type::FTInvalidControlRequestType: + return FT_INVALID_CONTROL_REQUEST_TYPE; + case Type::FTIOPending: + return FT_IO_PENDING; + case Type::FTIOIncomplete: + return FT_IO_INCOMPLETE; + case Type::FTHandleEOF: + return FT_HANDLE_EOF; + case Type::FTBusy: + return FT_BUSY; + case Type::FTNoSystemResources: + return FT_NO_SYSTEM_RESOURCES; + case Type::FTDeviceListNotReady: + return FT_DEVICE_LIST_NOT_READY; + case Type::FTDeviceNotConnected: + return FT_DEVICE_NOT_CONNECTED; + case Type::FTIncorrectDevicePath: + return FT_INCORRECT_DEVICE_PATH; + case Type::FTOtherError: + return FT_OTHER_ERROR; + // Other Errors case Type::TooManyEvents: return TOO_MANY_EVENTS; diff --git a/cmake/FindFTD3XX.cmake b/cmake/FindFTD3XX.cmake new file mode 100644 index 0000000..f0b72aa --- /dev/null +++ b/cmake/FindFTD3XX.cmake @@ -0,0 +1,24 @@ +find_path(FTD3XX_INCLUDE_DIR + NAMES ftd3xx.h FTD3XX.h + HINTS "${FTD3XX_ROOT}" +) + +find_library(FTD3XX_LIBRARY + NAMES libftd3xx-static.a FTD3XX.lib + PATH_SUFFIXES x64/Static + HINTS "${FTD3XX_ROOT}" +) + +mark_as_advanced(FTD3XX_FOUND FTD3XX_INCLUDE_DIR FTD3XX_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FTD3XX + REQUIRED_VARS FTD3XX_INCLUDE_DIR FTD3XX_LIBRARY +) +if(FTD3XX_FOUND AND NOT TARGET D3XX::D3XX) + add_library(FTD3XX::FTD3XX INTERFACE IMPORTED) + set_target_properties(FTD3XX::FTD3XX PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${FTD3XX_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${FTD3XX_LIBRARY}" + ) +endif() diff --git a/device/devicefinder.cpp b/device/devicefinder.cpp index 6031c54..b0fff8f 100644 --- a/device/devicefinder.cpp +++ b/device/devicefinder.cpp @@ -19,6 +19,10 @@ #include "icsneo/platform/ftdi.h" #endif +#ifdef ICSNEO_ENABLE_FTD3XX +#include "icsneo/platform/ftd3xx.h" +#endif + #ifdef ICSNEO_ENABLE_TCP #include "icsneo/platform/tcp.h" #endif @@ -67,6 +71,10 @@ std::vector> DeviceFinder::FindAll() { FTDI::Find(newDriverFoundDevices); #endif + #ifdef ICSNEO_ENABLE_FTD3XX + FTD3XX::Find(newDriverFoundDevices); + #endif + // Weak because we don't want to keep devices open if they go out of scope elsewhere static std::vector> foundDevices; diff --git a/include/icsneo/api/event.h b/include/icsneo/api/event.h index f8918ca..968dfd3 100644 --- a/include/icsneo/api/event.h +++ b/include/icsneo/api/event.h @@ -110,7 +110,42 @@ public: ErrorSettingSocketOption = 0x3107, GetIfAddrsError = 0x3108, SendToError = 0x3109, - + + // FTD3XX + FTOK = 0x4000, // placeholder + FTInvalidHandle = FTOK + 1, + FTDeviceNotFound = FTOK + 2, + FTDeviceNotOpened = FTOK + 3, + FTIOError = FTOK + 4, + FTInsufficientResources = FTOK + 5, + FTInvalidParameter = FTOK + 6, + FTInvalidBaudRate = FTOK + 7, + FTDeviceNotOpenedForErase = FTOK + 8, + FTDeviceNotOpenedForWrite = FTOK + 9, + FTFailedToWriteDevice = FTOK + 10, + FTEEPROMReadFailed = FTOK + 11, + FTEEPROMWriteFailed = FTOK + 12, + FTEEPROMEraseFailed = FTOK + 13, + FTEEPROMNotPresent = FTOK + 14, + FTEEPROMNotProgrammed = FTOK + 15, + FTInvalidArgs = FTOK + 16, + FTNotSupported = FTOK + 17, + FTNoMoreItems = FTOK + 18, + FTTimeout = FTOK + 19, + FTOperationAborted = FTOK + 20, + FTReservedPipe = FTOK + 21, + FTInvalidControlRequestDirection = FTOK + 22, + FTInvalidControlRequestType = FTOK + 23, + FTIOPending = FTOK + 24, + FTIOIncomplete = FTOK + 25, + FTHandleEOF = FTOK + 26, + FTBusy = FTOK + 27, + FTNoSystemResources = FTOK + 28, + FTDeviceListNotReady = FTOK + 29, + FTDeviceNotConnected = FTOK + 30, + FTIncorrectDevicePath = FTOK + 31, + FTOtherError = FTOK + 32, + NoErrorFound = 0xFFFFFFFD, TooManyEvents = 0xFFFFFFFE, Unknown = 0xFFFFFFFF diff --git a/include/icsneo/platform/ftd3xx.h b/include/icsneo/platform/ftd3xx.h new file mode 100644 index 0000000..24fe29d --- /dev/null +++ b/include/icsneo/platform/ftd3xx.h @@ -0,0 +1,33 @@ +#ifndef __FTD3XX_H_ +#define __FTD3XX_H_ + +#ifdef __cplusplus + +#include + +#include "icsneo/communication/driver.h" +#include "icsneo/device/founddevice.h" + +namespace icsneo { + +class FTD3XX : public Driver { +public: + static void Find(std::vector& foundDevices); + FTD3XX(const device_eventhandler_t& err, neodevice_t& forDevice); + ~FTD3XX() override { if(isOpen()) close(); } + bool open() override; + bool isOpen() override; + bool close() override; + bool isEthernet() const override { return false; } +private: + neodevice_t& device; + std::optional handle; + void readTask() override; + void writeTask() override; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/platform/ftdi3.h b/include/icsneo/platform/ftdi3.h deleted file mode 100644 index 0e87c24..0000000 --- a/include/icsneo/platform/ftdi3.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef __FTDI3_H_ -#define __FTDI3_H_ - -#define INTREPID_USB_VENDOR_ID (0x093c) - -// This is currently a stub for the FTDI3 driver, -// it uses the FTDI driver to find devices but will -// not allow them to connect! -#define FTDI3 FTDI - -#if defined _WIN32 -#include "icsneo/platform/windows/ftdi.h" -#elif defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) -#include "icsneo/platform/posix/ftdi.h" -#else -#warning "This platform is not supported by the FTDI driver" -#endif - -#endif \ No newline at end of file diff --git a/platform/ftd3xx.cpp b/platform/ftd3xx.cpp new file mode 100644 index 0000000..e51f149 --- /dev/null +++ b/platform/ftd3xx.cpp @@ -0,0 +1,174 @@ +#include +#include "icsneo/api/eventmanager.h" + +#define FTD3XX_STATIC +#include + +#include "icsneo/platform/ftd3xx.h" + +static constexpr auto READ_PIPE_ID = 0x82; +static constexpr auto WRITE_PIPE_ID = 0x02; + +using namespace icsneo; + +static void addEvent(FT_STATUS status, APIEvent::Severity severity) { + const auto internalEvent = static_cast(APIEvent::Type::FTOK) + status; + EventManager::GetInstance().add(APIEvent((APIEvent::Type)internalEvent, severity)); +} + +void FTD3XX::Find(std::vector& found) { + DWORD count; + if(const auto ret = FT_CreateDeviceInfoList(&count); ret != FT_OK) { + addEvent(ret, APIEvent::Severity::EventWarning); + return; + } + if(count == 0) { + return; + } + std::vector devices(count); + if(const auto ret = FT_GetDeviceInfoList(devices.data(), &count); ret != FT_OK) { + addEvent(ret, APIEvent::Severity::EventWarning); + return; + } + for(const auto& dev : devices) { + FoundDevice foundDevice = {}; + std::copy(dev.SerialNumber, dev.SerialNumber + sizeof(foundDevice.serial), foundDevice.serial); + foundDevice.makeDriver = [](const device_eventhandler_t& eh, neodevice_t& forDevice) { + return std::unique_ptr(new FTD3XX(eh, forDevice)); + }; + found.push_back(std::move(foundDevice)); + } +} + + +FTD3XX::FTD3XX(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err), device(forDevice) { +} + +bool FTD3XX::open() { + if(isOpen()) { + report(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::Error); + return false; + } + + void* tmpHandle; + if(const auto ret = FT_Create(device.serial, FT_OPEN_BY_SERIAL_NUMBER, &tmpHandle); ret != FT_OK) { + addEvent(ret, APIEvent::Severity::Error); + return false; + } + handle.emplace(tmpHandle); + + closing = false; + readThread = std::thread(&FTD3XX::readTask, this); + writeThread = std::thread(&FTD3XX::writeTask, this); + + return true; +} + +bool FTD3XX::isOpen() { + return handle.has_value(); +} + +bool FTD3XX::close() { + if(!isOpen() && !isDisconnected()) { + report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); + return false; + } + + closing = true; + disconnected = false; + + if(readThread.joinable()) + readThread.join(); + if(writeThread.joinable()) + writeThread.join(); + + uint8_t flush; + WriteOperation flushop; + while(readQueue.try_dequeue(flush)) {} + while(writeQueue.try_dequeue(flushop)) {} + + if(const auto ret = FT_Close(*handle); ret != FT_OK) { + addEvent(ret, APIEvent::Severity::EventWarning); + } + + closing = false; + + return true; +} + +void FTD3XX::readTask() { + EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + + static constexpr auto bufferSize = 2048; + uint8_t buffer[bufferSize] = {}; + + FT_SetStreamPipe(*handle, false, false, READ_PIPE_ID, bufferSize); + FT_SetPipeTimeout(*handle, READ_PIPE_ID, 1); + while(!closing && !isDisconnected()) { + ULONG received = 0; + OVERLAPPED overlap = {}; + FT_InitializeOverlapped(*handle, &overlap); + #ifdef _WIN32 + FT_ReadPipe(*handle, READ_PIPE_ID, buffer, bufferSize, &received, &overlap); + #else + FT_ReadPipeAsync(*handle, 0, buffer, bufferSize, &received, &overlap); + #endif + while(!closing) { + const auto ret = FT_GetOverlappedResult(*handle, &overlap, &received, true); + if(ret == FT_IO_PENDING) + continue; + if(ret != FT_OK) { + if(ret == FT_IO_ERROR) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } else { + addEvent(ret, APIEvent::Severity::Error); + } + FT_AbortPipe(*handle, READ_PIPE_ID); + } + break; + } + FT_ReleaseOverlapped(*handle, &overlap); + if(received > 0) { + readQueue.enqueue_bulk(buffer, received); + } + } +} + +void FTD3XX::writeTask() { + EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + + FT_SetPipeTimeout(*handle, WRITE_PIPE_ID, 100); + WriteOperation writeOp; + while(!closing && !isDisconnected()) { + if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100))) + continue; + + const auto size = static_cast(writeOp.bytes.size()); + ULONG sent = 0; + OVERLAPPED overlap = {}; + FT_InitializeOverlapped(*handle, &overlap); + FT_SetStreamPipe(*handle, false, false, WRITE_PIPE_ID, size); + #ifdef _WIN32 + FT_WritePipe(*handle, WRITE_PIPE_ID, writeOp.bytes.data(), size, &sent, &overlap); + #else + FT_WritePipeAsync(*handle, 0, writeOp.bytes.data(), size, &sent, &overlap); + #endif + while(!closing) { + const auto ret = FT_GetOverlappedResult(*handle, &overlap, &sent, true); + if(ret == FT_IO_PENDING) + continue; + if(ret != FT_OK) { + if(ret == FT_IO_ERROR) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } else { + addEvent(ret, APIEvent::Severity::Error); + } + FT_AbortPipe(*handle, WRITE_PIPE_ID); + } + break; + } + FT_ReleaseOverlapped(*handle, &overlap); + } +}