Skip to content

Instantly share code, notes, and snippets.

@ericfont
Forked from bjornvaktaren/minimal_spi.cpp
Last active March 27, 2025 06:59
Show Gist options
  • Save ericfont/67709eff564948c27aaf2bee0455538f to your computer and use it in GitHub Desktop.
Save ericfont/67709eff564948c27aaf2bee0455538f to your computer and use it in GitHub Desktop.
Simple SPI example with libftdi and FTDI UM232H
// Quite minimal example showing how to configure MPSSE for SPI using libftdi
// compile like this: g++ minimal_spi.cpp -o minimal_spi -lftdi1 -Wno-deprecated
#include <libftdi1/ftdi.h>
#include <libftdi1/ftdi.hpp>
#include <usb.h>
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>
// UM232H development module
#define VENDOR 0x0403
#define PRODUCT 0x6014
using namespace Ftdi;
namespace Pin {
// enumerate the AD bus for conveniance.
enum bus_t {
SK = 0x01, // ADBUS0, SPI data clock
DO = 0x02, // ADBUS1, SPI data out
DI = 0x04, // ADBUS2, SPI data in
CS = 0x08, // ADBUS3, SPI chip select
L0 = 0x10, // ADBUS4, general-ourpose i/o, GPIOL0
L1 = 0x20, // ADBUS5, general-ourpose i/o, GPIOL1
L2 = 0x40, // ADBUS6, general-ourpose i/o, GPIOL2
l3 = 0x80 // ADBUS7, general-ourpose i/o, GPIOL3
};
}
// Set these pins high
const unsigned char pinInitialState = Pin::CS|Pin::L0|Pin::L1;
// Use these pins as outputs
const unsigned char pinDirection = Pin::SK|Pin::DO|Pin::CS|Pin::L0|Pin::L1;
uint64_t getRealtimeTick() {
struct timespec ts;
uint64_t theTick = 0U;
clock_gettime( CLOCK_REALTIME, &ts );
theTick = ts.tv_nsec;
theTick += ts.tv_sec * 1000000000;
return theTick;
}
uint64_t startTick = 0;
uint64_t getRealtimeTick_sinceStart() {
return getRealtimeTick() - startTick;
}
int main(void)
{
startTick = getRealtimeTick();
printf("Starting nanosecond: %ld\n", startTick);
struct ftdi_version_info ftdi_version_info_return = ftdi_get_library_version();
std::cout << "FTDI Version info: " << ftdi_version_info_return.version_str << '\n';
// initialize
struct ftdi_context ftdi;
int ftdi_status = 0;
ftdi_status = ftdi_init(&ftdi);
if ( ftdi_status != 0 ) {
std::cout << "Failed to initialize device\n";
return 1;
}
ftdi_status = ftdi_usb_open(&ftdi, VENDOR, PRODUCT);
if ( ftdi_status != 0 ) {
std::cout << "Can't open device. Got error\n"
<< ftdi_get_error_string(&ftdi) << '\n';
return 1;
}
ftdi_usb_reset(&ftdi);
ftdi_set_interface(&ftdi, INTERFACE_ANY);
const int nBitsPerFrame = 16;
const int nFramesPerTransfer = 20;
const int nBitsPerTransfer = nBitsPerFrame * nFramesPerTransfer;
const int nBytesPerBuffer = nBitsPerTransfer * 10;
int ftdi_set_latency_timer_return = ftdi_set_latency_timer(&ftdi, 1); // read timeout in milliseconds
if (ftdi_set_latency_timer_return == -1 ) std::cout << "latency out of range";
if (ftdi_set_latency_timer_return == -2 ) std::cout << "unable to set latency timer";
if (ftdi_set_latency_timer_return == -3 ) std::cout << "USB device unavailable";
// ftdi_write_data_set_chunksize(&ftdi, nBytesPerBuffer*10);//nBitsPerTransfer*10);
// ftdi_read_data_set_chunksize(&ftdi, nBytesPerBuffer);//nBitsPerTransfer*10);
ftdi_set_bitmode(&ftdi, 0, 0); // reset
ftdi_set_bitmode(&ftdi, 0, BITMODE_MPSSE); // enable mpsse on all bits
int ftdi_tcioflush_return = ftdi_tcioflush(&ftdi);
if (ftdi_tcioflush_return == -1 ) std::cout << "read buffer purge failed";
if (ftdi_tcioflush_return == -2 ) std::cout << "write buffer purge failed";
if (ftdi_tcioflush_return == -3 ) std::cout << "USB device unavailable";
usleep(50000); // sleep 50 ms for setup to complete
// Setup MPSSE; Operation code followed by 0 or more arguments.
unsigned char writeBuf[nBytesPerBuffer] = {0};
unsigned int icmd = 0;
writeBuf[icmd++] = DIS_DIV_5; // opcode: set clk divisor
writeBuf[icmd++] = TCK_DIVISOR; // opcode: set clk divisor according to next two bytes, with equation: 30 MHz / (divisor+1)
writeBuf[icmd++] = 0x02; // divisor: low bit.
writeBuf[icmd++] = 0x00; // divisor: high bit.
writeBuf[icmd++] = DIS_ADAPTIVE; // opcode: disable adaptive clocking
writeBuf[icmd++] = DIS_3_PHASE; // opcode: disable 3-phase clocking
writeBuf[icmd++] = SET_BITS_LOW; // opcode: set low bits (ADBUS[0-7])
writeBuf[icmd++] = pinInitialState; // argument: inital pin states
writeBuf[icmd++] = pinDirection; // argument: pin direction
// Write the setup to the chip.
if ( ftdi_write_data(&ftdi, writeBuf, icmd) != icmd ) {
std::cout << "Write failed\n";
}
// need to tciflush before reading
int ftdi_tciflush_return = ftdi_tciflush(&ftdi);
if (ftdi_tciflush_return == -1) std::cout << "read buffer purge failed\n";
if (ftdi_tciflush_return == -2) std::cout << "USB device unavailable\n";
// zero the buffer for good measure
memset(writeBuf, 0, sizeof(writeBuf));
icmd = 0;
for(int frame_counter = 0; frame_counter < nFramesPerTransfer; frame_counter++ ) {
for (int i=0; i<nBitsPerFrame; i++ ) {
// Next three commands sets the GPIOL0 pin low. Pulling CS low.
writeBuf[icmd++] = SET_BITS_LOW;
writeBuf[icmd++] = pinInitialState & ~Pin::CS;
writeBuf[icmd++] = pinDirection;
// commands to write and read one byte in SPI0 (polarity = phase = 0) mode
writeBuf[icmd++] = MPSSE_DO_WRITE | MPSSE_WRITE_NEG | MPSSE_DO_READ;
writeBuf[icmd++] = 0x00; // length-1 low byte
writeBuf[icmd++] = 0x00; // length-1 high byte
writeBuf[icmd++] = i; // byte to send
// Next three commands sets the GPIOL0 pin high. Pulling CS high.
writeBuf[icmd++] = SET_BITS_LOW;
writeBuf[icmd++] = pinInitialState | Pin::CS;
writeBuf[icmd++] = pinDirection;
}
}
unsigned char read1_buf[nBytesPerBuffer] = {0};
unsigned char read2_buf[nBytesPerBuffer] = {0};
unsigned char read3_buf[nBytesPerBuffer] = {0};
// submit a new async write
struct ftdi_transfer_control *tc_write1 = ftdi_write_data_submit(&ftdi, writeBuf, icmd);
// submit a new async write
struct ftdi_transfer_control *tc_write2 = ftdi_write_data_submit(&ftdi, writeBuf, icmd);
// submit a new async write
struct ftdi_transfer_control *tc_write3 = ftdi_write_data_submit(&ftdi, writeBuf, icmd);
uint64_t totalBytesRead = 0;
while (true) {
// wait for write1 to complete
/* int write1_transfer_done_return = ftdi_transfer_data_done(tc_write1);
if (write1_transfer_done_return < 0 ) {
printf("Async Write1 failed with ERROR code: %d\n", write1_transfer_done_return);
break;
}*/
// submit a new async write1
tc_write1 = ftdi_write_data_submit(&ftdi, writeBuf, icmd);
// read the previous write1's data
int ftdi_read1_data_return = ftdi_read_data(&ftdi, read1_buf, nBitsPerTransfer);
if (ftdi_read1_data_return < 0 ) {
printf("Read1 failed with ERROR code: %d\n", ftdi_read1_data_return);
break;
}
totalBytesRead += ftdi_read1_data_return;
std::cout << "Read1 " << std::dec << ftdi_read1_data_return << " bytes: 0x" << std::hex << (unsigned int)read1_buf[0] << " 0x" << std::hex << (unsigned int)read1_buf[1] << " ... totalBytesRead: " << std::dec << totalBytesRead << ".\n";
// wait for write2 to complete
/* int write2_transfer_done_return = ftdi_transfer_data_done(tc_write2);
if (write2_transfer_done_return < 0 ) {
printf("Async Write2 failed with ERROR code: %d\n", write2_transfer_done_return);
break;
}*/
// submit a new async write2
tc_write2 = ftdi_write_data_submit(&ftdi, writeBuf, icmd);
// read the previous write2's data
int ftdi_read2_data_return = ftdi_read_data(&ftdi, read2_buf, nBitsPerTransfer);
if (ftdi_read2_data_return < 0 ) {
printf("Read2 failed with ERROR code: %d\n", ftdi_read2_data_return);
break;
}
totalBytesRead += ftdi_read2_data_return;
std::cout << "Read2 " << std::dec << ftdi_read2_data_return << " bytes: 0x" << std::hex << (unsigned int)read2_buf[0] << " 0x" << std::hex << (unsigned int)read2_buf[1] << " ... totalBytesRead: " << std::dec << totalBytesRead << ".\n";
// wait for write3 to complete
/* int write3_transfer_done_return = ftdi_transfer_data_done(tc_write3);
if (write3_transfer_done_return < 0 ) {
printf("Async Write3 failed with ERROR code: %d\n", write3_transfer_done_return);
break;
}*/
// submit a new async write3
tc_write3 = ftdi_write_data_submit(&ftdi, writeBuf, icmd);
// read the previous write1's data
int ftdi_read3_data_return = ftdi_read_data(&ftdi, read3_buf, nBitsPerTransfer);
if (ftdi_read3_data_return < 0 ) {
printf("Read3 failed with ERROR code: %d\n", ftdi_read3_data_return);
break;
}
totalBytesRead += ftdi_read3_data_return;
std::cout << "Read3 " << std::dec << ftdi_read3_data_return << " bytes: 0x" << std::hex << (unsigned int)read3_buf[0] << " 0x" << std::hex << (unsigned int)read3_buf[1] << " ... totalBytesRead: " << std::dec << totalBytesRead << ".\n";
}
// close ftdi
ftdi_usb_reset(&ftdi);
ftdi_usb_close(&ftdi);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment