Volver arriba
FTDI chips are great!! They save us a lot of time, they work quite well, and they almost don't need any device driver.
FTDI also provides a very nice and well documented DLL for low level access to FTDI chip internals, but, of course, it is closed source. If you need low level access to FTDI chips, there is an open-source alternative:
https://www.intra2net.com/en/developer/libftdi/
I highly recommend Intra2Net libFTDI, as it is portable to any platform, and works very fast and very well.
Libraries provide a nice low-level access to FTDI chips, but unfortunately, end-users don't like low level UIs. So, you will most probably be using your FT232 chip with some kind of graphical interface. Well, Qt is a very nice piece of software, it has an excellent GUI, it is portable to almost any current platform, and it is very lightweight and fast!!!. It provides a lot of classes and libraries for serial port acces (QSerialPort), Qt Bluetooth, and so.
Unfortunately, it does not provide a class for FTDI chip low level access. Of course you can use standard QSerialPort for basic read and write functions, but if you need some advanced methods (like custom baud rates, reading internal EEPROM, using GPIOs), you will need a custom class.
Here is a basic implementation of the FT232 class, trying to copy some of the QSerialPort behaviour.
ft232.h
#ifndef FT232_H#define FT232_H#include <QIODevice>#include <QThread>#include <QMutex>#include <QWaitCondition>#include <QTimer>#include <QSemaphore>#include <QPointer>#include <QList>#include <QEventLoop>#include <QDebug>#include "ftdi.h"#include "ftdi_i.h"#include "libusb.h"/* USB max packet size = 64 bytes */static constexpr uint16_t FTDI_MTU = 64;/* Timeout before ftdi_read function returns if not enough data */static constexpr uint16_t FTDI_LATENCY = 10;/* FTDI fixed port name */static constexpr const char *FTDI_NAME = "FTDI";/* Default FTDI port parameters */static constexpr int FTDI_VID = 0x0403;static constexpr int FTDI_PID = 0x6001;/* Polling class** Since libftdi does not provide a metod to* get notified when new data arrives, only* way is to have a worker thread to poll FT232 every* FTDI_LATENCY milliseconds.** Constructor parameters:* struct ftdi_context*: already opened ftdi* QMutex*: mutex created on main class, used* to avoid concurrent access to* ftdi_context*** poll(): main worker thread loop* stop(): call this to stop thread* pause(): pauses thread** finished(): emited when main loop ends* dataReady(): emited when new data received* readError(): emited when error trying to read data* modemError(): emited when serial modem errors*/class FT232Poll : public QObject {Q_OBJECTpublic:FT232Poll(struct ftdi_context * p, QMutex * mtx) {ftptr = p;mutexptr = mtx;}void stop() {stopCond = true;}void pause(bool cond) {pauseMutex.lock();pauseCond = cond;pauseMutex.unlock();if (!cond)resumeCond.wakeAll();}public slots:void poll();signals:void finished();void dataReady(QByteArray);void readError();void modemError(unsigned short);private:struct ftdi_context *ftptr;QMutex * mutexptr;QWaitCondition resumeCond;QMutex pauseMutex;bool stopCond, pauseCond;};/* Main FT232 class** Mainly copied from QSerialPort class with some* mods to fit FT232.* Eg.: line property is set on a single fuction call* setPort() and portName() will get/return USB VID/PID* values.* setBaudRate() will accept any arbitrary baudrate* on_FTDIreceive(): slot for receiving Thread data* on_FTDIerror(): slot for receiving Thread errors* on_FTDImodemError(): slot for receiving Thread modem errors** When data is received, it will be stored on an internal* buffer. readyRead() signal will be emitted.** QIODevice::read() function call will read from this buffer,* so call is non-blocking.** If need a blocking call, waitForReadyRead() is implemented* and will return true if new data is available.** Check bytesAvailable() if need to know how many bytes are* stored on buffer.**/class FT232 : public QIODevice{Q_OBJECTpublic:enum PortError {NoError = 0x00, NotOpenError = 0x01, OverrunError = 0x02, ParityError = 0x04,FramingError = 0x10, BreakConditionError = 0x20, FIFOError = 0x40, ReadError = 0x80};Q_FLAG(PortError)Q_DECLARE_FLAGS(PortErrors, PortError)enum LineProperty {SERIAL_8N1, SERIAL_8N2, SERIAL_8N15, SERIAL_8E1, SERIAL_8E2, SERIAL_8E15,SERIAL_8O1, SERIAL_8O2, SERIAL_8O15, SERIAL_8M1, SERIAL_8M2, SERIAL_8M15,SERIAL_8S1, SERIAL_8S2, SERIAL_8S15};Q_ENUM(LineProperty)enum FlowControl {NoFlowControl, HardwareControl, SoftwareControl, DTR_DSR_FlowControl};Q_ENUM(FlowControl)enum PinoutSignal {NoSignal = 0x00, ReceivedDataSignal = 0x02, DataSetReadySignal = 0x10,RingIndicatorSignal = 0x20, ClearToSendSignal = 0x80};Q_FLAG(PinoutSignal)Q_DECLARE_FLAGS(PinoutSignals, PinoutSignal)FT232(QObject * parent = nullptr);virtual ~FT232();bool open(QIODevice::OpenMode mode = QIODevice::ReadWrite);bool isSequential() const {return true;}qint64 bytesAvailable() const;bool waitForReadyRead(int msecs = 30000);void setPort(int VID = FTDI_VID, int PID = FTDI_PID) {usbVID = VID; usbPID = PID;}bool setBaudRate(qint32 baud);qint32 baudRate() {return FTDIbaudRate;}bool setLineProperty(LineProperty line);LineProperty lineProperty() {return FTDIlineProperty;}bool setFlowControl(FlowControl flow);FlowControl flowControl() {return FTDIflowControl;}bool setDataTerminalReady(bool set);bool isDataTerminalReady() {return FTDIdtr;}bool setRequestToSend(bool set);bool isRequestToSend() {return FTDIrts;}PinoutSignals pinoutSignals();PortErrors error() {return errFlag;}void clearError() {errFlag = NoError;}/* FT232 specifics QSerialPortInfo like functions */unsigned int chipID() {return FTDIchipID;}bool hasVendorIdentifier() {return true;}bool hasProductIdentifier() {return true;}int vendorIdentifier() {return usbVID;}int productIdentifier() {return usbPID;}QString portName() {return productName;}QString manufacturer() {return manufacturerName;}QString libVersion() {return libraryVersion;}QByteArray serialNumber() {return serialNmb;}private:bool FTDIdtr, FTDIrts;LineProperty FTDIlineProperty;FlowControl FTDIflowControl;PortErrors errFlag;uint32_t FTDIbaudRate = 57600;int usbVID, usbPID;unsigned int FTDIchipID;QString productName;QByteArray serialNmb;QString manufacturerName;QString libraryVersion;QMutex ftdiMutex;struct ftdi_context *ftdi;QByteArray FTDIreadBuffer;QSemaphore sem;QPointer<FT232Poll> worker;public slots:void on_FTDIreceive(QByteArray data);void on_FTDIerror();void on_FTDImodemError(unsigned short modemStatus);void on_Pause(bool val) {worker->pause(val);}void close();protected:qint64 readData(char * data, qint64 maxSize);qint64 writeData(const char *data, qint64 maxSize);signals:void baudRateChanged(qint32);void linePropertyChanged(FT232::LineProperty);void flowControlChanged(FT232::FlowControl);void dataTerminalReadyChanged(bool);void requestToSendChanged(bool);void errorOccurred();void readyRead();};/* Info class* Similar to QSerialPortInfo class*/class FT232Info {public:static QList<FT232Info> availablePorts(int VID = FTDI_VID, int PID = FTDI_PID);QString portName() {return FTDI_NAME;}QString description() {return descr;}QString manufacturer() {return manuf;}QString serialNumber() {return serialN;}quint16 vendorIdentifier() {return vid;}quint16 productIdentifier() {return pid;}bool hasVendorIdentifier() {return true;}bool hasProductIdentifier() {return true;}private:QString descr;QString manuf;QString serialN;quint16 vid;quint16 pid;};#endif // FT232_H
ft232.c
/* FT232 polling reader** 29/03/2020 First version** Author: Victor Preatoni**/#include "ft232.h"/* Creates ftdi_context struct*/FT232::FT232(QObject *parent): QIODevice (parent){ftdi = ftdi_new();}/* Releases ftdi_context struct*/FT232::~FT232(){ftdi_free(ftdi);}/* Open FT232 and sets basic parameters:* baudRate, latency and chunksize.* It will also try to read some FT232 information,* like chipID, vendor name and product name.** Main polling thread is started here too*/bool FT232::open(QIODevice::OpenMode mode){int ret;struct ftdi_version_info version;ret = ftdi_usb_open(ftdi, usbVID, usbPID);if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));return false;}ret = ftdi_set_baudrate(ftdi, FTDIbaudRate);if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));close();return false;}ret = ftdi_set_latency_timer(ftdi, FTDI_LATENCY);if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));close();return false;}ret = ftdi_read_data_set_chunksize(ftdi, FTDI_MTU);if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));close();return false;}/* Now read some FT232R specifics* Doesnt mater if error, that will* be handled by user if read data* makes non-sense*/ftdi_read_chipid(ftdi, &FTDIchipID);if (ftdi_read_eeprom(ftdi) == 0)ftdi_eeprom_decode(ftdi, 0);productName = QString(ftdi->eeprom->product);serialNmb = QString(ftdi->eeprom->serial).toUpper().toUtf8();manufacturerName = QString(ftdi->eeprom->manufacturer);version = ftdi_get_library_version();libraryVersion = version.version_str;/* Clear buffers */ftdi_tciflush(ftdi);/* Notify parent class we are open*/QIODevice::open(mode);/* Create new thread */auto thread = new QThread;connect(this, &FT232::destroyed, thread, &QThread::quit);connect(thread, &QThread::finished, thread, &QThread::deleteLater);/* Creater worker */worker = new FT232Poll(ftdi, &ftdiMutex);connect(this, &FT232::destroyed, worker, &FT232Poll::deleteLater);/* Move worker to thread */worker->moveToThread(thread);/* Clean connects */connect(worker, &FT232Poll::finished, this, &FT232::close);connect(worker, &FT232Poll::finished, thread, &QThread::quit);/* Sync connects */connect(thread, &QThread::started, worker, &FT232Poll::poll);connect(worker, &FT232Poll::readError, this, &FT232::on_FTDIerror);connect(worker, &FT232Poll::modemError, this, &FT232::on_FTDImodemError);connect(worker, &FT232Poll::dataReady, this, &FT232::on_FTDIreceive);thread->start();return true;}/* Stop working thread and* close port*/void FT232::close(){/* If worker still working,* stop it*/if (worker)worker->stop();/* Close FTDI** Use mutex here to avoid polling* thread to read while closing port*/ftdiMutex.lock();ftdi_usb_close(ftdi);ftdiMutex.unlock();setOpenMode(NotOpen);/* Signal we are closing */emit aboutToClose();}/* This function is called by QIODevice::read()*/qint64 FT232::readData(char *data, qint64 maxSize){/* Check bounds */qint64 n = qMin(maxSize, (qint64)FTDIreadBuffer.size());/* No data, return immediately */if (!n)return 0;/* Try to catch n bytes */if (!sem.tryAcquire(n))return 0;/* Copy data to QIODevice provided buffer */memcpy(data, FTDIreadBuffer.data(), n);/* Erase data from my buffer */FTDIreadBuffer.remove(0, n);return n;}/* This function is called by QIODevice::write()*/qint64 FT232::writeData(const char *data, qint64 maxSize){int ret;/* Write to FTDI** Use mutex here to avoid writing* while polling thread is reading*/ftdiMutex.lock();ret = ftdi_write_data(ftdi, (const unsigned char *)data, maxSize);ftdiMutex.unlock();/* If error, setErrorString */if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));return -1;}return ret;}bool FT232::setBaudRate(qint32 baud){int ret;FTDIbaudRate = baud;/* Change baud rate** Use mutex here to avoid changing* while polling thread is reading*/ftdiMutex.lock();ret = ftdi_set_baudrate(ftdi, baud);ftdiMutex.unlock();/* If error, setErrorString */if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));return false;}/* Signal baudrate has changed */emit baudRateChanged(baud);return true;}bool FT232::setLineProperty(LineProperty line){int ret;ftdi_bits_type bits;ftdi_stopbits_type sbit;ftdi_parity_type parity;bits = BITS_8;FTDIlineProperty = line;switch (line) {case SERIAL_8N1: parity = NONE; sbit = STOP_BIT_1; break;case SERIAL_8N2: parity = NONE; sbit = STOP_BIT_2; break;case SERIAL_8N15: parity = NONE; sbit = STOP_BIT_15; break;case SERIAL_8E1: parity = EVEN; sbit = STOP_BIT_1; break;case SERIAL_8E2: parity = EVEN; sbit = STOP_BIT_2; break;case SERIAL_8E15: parity = EVEN; sbit = STOP_BIT_15; break;case SERIAL_8O1: parity = ODD; sbit = STOP_BIT_1; break;case SERIAL_8O2: parity = ODD; sbit = STOP_BIT_2; break;case SERIAL_8O15: parity = ODD; sbit = STOP_BIT_15; break;case SERIAL_8M1: parity = MARK; sbit = STOP_BIT_1; break;case SERIAL_8M2: parity = MARK; sbit = STOP_BIT_2; break;case SERIAL_8M15: parity = MARK; sbit = STOP_BIT_15; break;case SERIAL_8S1: parity = SPACE; sbit = STOP_BIT_1; break;case SERIAL_8S2: parity = SPACE; sbit = STOP_BIT_2; break;case SERIAL_8S15: parity = SPACE; sbit = STOP_BIT_15; break;default: parity = NONE; sbit = STOP_BIT_1; break;}/* Change line properties** Use mutex here to avoid changing* while polling thread is reading*/ftdiMutex.lock();ret = ftdi_set_line_property(ftdi, bits, sbit, parity);ftdiMutex.unlock();/* If error, setErrorString */if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));return false;}/* Signal line property has changed */emit linePropertyChanged(line);return true;}bool FT232::setFlowControl(FlowControl flow){int ret;int flowctrl;switch (flow) {case NoFlowControl: flowctrl = SIO_DISABLE_FLOW_CTRL; break;case HardwareControl: flowctrl = SIO_RTS_CTS_HS; break;case SoftwareControl: flowctrl = SIO_XON_XOFF_HS; break;case DTR_DSR_FlowControl: flowctrl = SIO_DTR_DSR_HS; break;default: flowctrl = SIO_DISABLE_FLOW_CTRL;}/* Change flow control** Use mutex here to avoid changing* while polling thread is reading*/ftdiMutex.lock();ret = ftdi_setflowctrl(ftdi, flowctrl);ftdiMutex.unlock();/* If error, setErrorString */if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));return false;}FTDIflowControl = flow;/* Signal flow control has changed */emit flowControlChanged(flow);return true;}bool FT232::setDataTerminalReady(bool set){int ret;/* DTR can only be set while port* is open*/if (!isOpen())return false;/* Set DTR** Use mutex here to avoid changing* while polling thread is reading*/ftdiMutex.lock();ret = ftdi_setdtr(ftdi, set);ftdiMutex.unlock();/* If error, setErrorString */if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));return false;}FTDIdtr = set;/* Signal DTR has changed */emit dataTerminalReadyChanged(set);return true;}bool FT232::setRequestToSend(bool set){int ret;/* RTS can only be set while port* is open*/if (!isOpen())return false;/* Set RTS** Use mutex here to avoid changing* while polling thread is reading*/ftdiMutex.lock();ret = ftdi_setrts(ftdi, set);ftdiMutex.unlock();/* If error, setErrorString */if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));return false;}FTDIrts = set;/* Signal RTS has changed */emit requestToSendChanged(set);return true;}/* Parses first byte of modemStatus and returns* active data lines*/FT232::PinoutSignals FT232::pinoutSignals(){/* Line status bit field* B0..B3 - must be 0* B4 Clear to send (CTS) 0 = inactive; 1 = active* B5 Data set ready (DTS) 0 = inactive; 1 = active* B6 Ring indicator (RI) 0 = inactive; 1 = active* B7 Receive line signal detect (RLSD) 0 = inactive; 1 = active*/int ret;/* Signals can only be* read while port is open*/if (!isOpen())return NoSignal;unsigned short modemStatus;/* Read modem status** Use mutex here to avoid reading status* while polling thread is reading data*/ftdiMutex.lock();ret = ftdi_poll_modem_status(ftdi, &modemStatus);ftdiMutex.unlock();/* If error, setErrorString */if (ret < 0) {setErrorString(ftdi_get_error_string(ftdi));return NoSignal;}FT232::PinoutSignals retval = NoSignal;if (modemStatus & 0x0080)retval |= ReceivedDataSignal;if (modemStatus & 0x0040)retval |= RingIndicatorSignal;if (modemStatus & 0x0020)retval |= DataSetReadySignal;if (modemStatus & 0x0010)retval |= ClearToSendSignal;return retval;}/* Parses second byte of modemStatus and returns* active error flags*/void FT232::on_FTDImodemError(unsigned short modemStatus){/* modemStatus Bit field* B8 Data Ready (DR)* B9 Overrun Error (OE)* B10 Parity Error (PE)* B11 Framing Error (FE)* B12 Break Interrupt (BI)* B13 Transmitter Holding Register (THRE)* B14 Transmitter Empty (TEMT)* B15 Error in RCVR FIFO*/FT232::PortErrors retval = NoError;if (modemStatus & 0x8000)retval |= FIFOError;if (modemStatus & 0x1000)retval |= BreakConditionError;if (modemStatus & 0x0800)retval |= FramingError;if (modemStatus & 0x0400)retval |= ParityError;if (modemStatus & 0x0200)retval |= OverrunError;errFlag = retval;//Modem errors are quite frequent, so not emited// emit errorOccurred();}/* Returns the number of bytes that are available for reading.* Subclasses that reimplement this function must call* the base implementation in order to include the size of the buffer of QIODevice*/qint64 FT232::bytesAvailable() const{qint64 my_size = FTDIreadBuffer.size();qint64 builtin_size = QIODevice::bytesAvailable();return (my_size + builtin_size);}/* Write received data to the end of internal* intermediate buffer*/void FT232::on_FTDIreceive(QByteArray data){/* Append new data */FTDIreadBuffer.append(data);/* Release n bytes */sem.release(data.size());/* Emit signals */emit readyRead();emit QIODevice::readyRead();}/* Slot for thread to inform read error*/void FT232::on_FTDIerror(){/* setErrorString */setErrorString(ftdi_get_error_string(ftdi));if (!isOpen())errFlag = NotOpenError;elseerrFlag = ReadError;emit errorOccurred();}/* Timeout blocking function that waits* for bytes available on buffer to read*/bool FT232::waitForReadyRead(int msecs){if (!isOpen())return false;/* Create a timer and an* Event Loop*/QEventLoop loop;QTimer timer;timer.setSingleShot(true);connect(this, &FT232::readyRead, &loop, &QEventLoop::quit);connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);/* Trigger timer and run* event loop*/timer.start(msecs);loop.exec();/* If timer timed out, means* we had a timeout!*/if (!timer.isActive()) {setErrorString(tr("Read timeout"));return false;}/* Otherwise, we exit event loop because* we catch a readyRead signal*/return true;}/* FTDI reader worker thread */void FT232Poll::poll(){/* modemStatus Bit field (*):serious errors* B8 Data Ready (DR)* B9* Overrun Error (OE)* B10* Parity Error (PE)* B11* Framing Error (FE)* B12 Break Interrupt (BI)* B13 Transmitter Holding Register (THRE)* B14 Transmitter Empty (TEMT)* B15* Error in RCVR FIFO */unsigned short modemStatus;char buff[FTDI_MTU];int ret;pauseCond = false;stopCond = false;while (!stopCond) {/* Get mutex before reading */mutexptr->lock();ret = ftdi_poll_modem_status(ftptr, &modemStatus);mutexptr->unlock();/* Very serious error, stop thread */if (ret < 0) {emit readError();stop();continue;}/* Mask out serious errors */if (modemStatus & 0b1000111000000000) {emit modemError(modemStatus);/* Get mutex before flushing buffers */mutexptr->lock();ftdi_tciflush(ftptr);mutexptr->unlock();continue;}/* Get mutex before reading */mutexptr->lock();ret = ftdi_read_data(ftptr, (unsigned char *)buff, sizeof (buff));mutexptr->unlock();/* FTDI buffer overflow */if (ret == LIBUSB_ERROR_OVERFLOW) {emit readError();/* Get mutex before flushing buffers */mutexptr->lock();ftdi_tciflush(ftptr);mutexptr->unlock();continue;}/* Very serious error, stop thread */if (ret < 0) {emit readError();stop();continue;}/* Read OK, emit data */if (ret > 0)emit dataReady(QByteArray(buff, ret));/* Check if a pause was requested */pauseMutex.lock();if (pauseCond)resumeCond.wait(&pauseMutex);pauseMutex.unlock();} //End loopemit finished();}/* Static function that finds all available FTDI* ports and returns a list to FT232Info items*/QList<FT232Info> FT232Info::availablePorts(int VID, int PID){QList<FT232Info> list;list.clear();FT232Info device;struct ftdi_context ftdic;struct ftdi_device_list *devlist, *curdev;char description[32], serialNum[32], manufacturer[32];/* If cannot init ftdi,* return empty list*/if (ftdi_init(&ftdic) < 0)return list;/* Find FTDI devices using selected USB VID and PID */if (ftdi_usb_find_all(&ftdic, &devlist, VID, PID) > 0) {/* Iterate thru list */for (curdev = devlist; curdev != nullptr; curdev = curdev->next) {/* Try to read FTDI data */if (ftdi_usb_get_strings(&ftdic, curdev->dev, manufacturer, sizeof(manufacturer),description, sizeof(description), serialNum, sizeof(serialNum)) < 0)/* Return if fail*/return list;device.manuf = manufacturer;device.descr = description;device.serialN = serialNum;device.vid = VID;device.pid = PID;/* Append data to list */list.append(device);}}/* Release resources */ftdi_list_free(&devlist);ftdi_deinit(&ftdic);return list;}