Skip to content

Instantly share code, notes, and snippets.

@marelab
Created March 26, 2024 21:26
Show Gist options
  • Save marelab/e72b1e4156e2ee541812a08f18518b67 to your computer and use it in GitHub Desktop.
Save marelab/e72b1e4156e2ee541812a08f18518b67 to your computer and use it in GitHub Desktop.
USB2SPI
// Quite minimal example showing how to configure MPSSE for SPI using libftdi
// compile like this: g++ minimal_spi.cpp -o minimal_spi -lftdipp -lftdi
//#include "../ftdipp/ftdi.hpp"
#include "ftdi.h"
#include <usb.h>
#include <stdio.h>
#include <iostream>
#include <string.h>
// FT232H development module
#define VENDOR 0x0403
#define PRODUCT 0x6014
// MSB FIRST
// 2MHz clock
class SPI {
private:
struct ftdi_context ftdi;
struct settings {
uint8_t bitOrder;
uint8_t dataMode;
uint8_t clockDiv;
};
// Enum to represent bit order
typedef enum {
MSB_FIRST,
LSB_FIRST
} bit_order_t;
settings SPISettings;
//struct ftdi_context ftdi;
int ftdi_channel;
unsigned char inData[256]={};
int inDataSize=0;
int SPI_Reset(){
// Reset the MPSSE controller
unsigned char buf[3];
int ftdi_status;
// Command to disable loopback and reset MPSSE controller
buf[0] = 0x85; // Disable loopback command
buf[1] = 0xAB; // Send a bad command to ensure synchronization
// Send the commands to the device
ftdi_status = ftdi_write_data(&ftdi, buf, 2);
if (ftdi_status < 0)
{
fprintf(stderr, "Failed to write to device: %s\n", ftdi_get_error_string(&ftdi));
return EXIT_FAILURE;
}
// Purge USB receive buffer to remove any data still left
ftdi_status = ftdi_usb_purge_rx_buffer(&ftdi);
if (ftdi_status < 0)
{
fprintf(stderr, "Failed to purge RX buffer: %s\n", ftdi_get_error_string(&ftdi));
return EXIT_FAILURE;
}
printf("MPSSE engine reset successfully.\n");
return 0; // Success
};
int SPI_SetBitOrder(bit_order_t bitOrder)
{
// Function to set MPSSE bit order
unsigned char buf[3];
int ftdi_status;
// Determine the command based on the desired bit order
switch (bitOrder)
{
case MSB_FIRST:
buf[0] = 0x8A; // Command to set MSB first for data out
buf[1] = 0x8B; // Command to set MSB first for data in
break;
case LSB_FIRST:
buf[0] = 0x8C; // Command to set LSB first for data out
buf[1] = 0x8D; // Command to set LSB first for data in
break;
default:
fprintf(stderr, "Invalid bit order specified.\n");
return EXIT_FAILURE;
}
// Send the commands to the device
ftdi_status = ftdi_write_data(&ftdi, buf, 2);
if (ftdi_status < 0)
{
fprintf(stderr, "Failed to write to device: %s\n", ftdi_get_error_string(&ftdi));
return EXIT_FAILURE;
}
printf("MPSSE bit order set successfully.\n");
return 0; // Success
};
int SPI_SetDataMode(uint8_t dataMode)
{
unsigned char buf[3];
int ftdi_status;
// SPI mode configuration
// SPI modes determine CPOL and CPHA, setting up the appropriate pins and clock phase
switch (dataMode)
{
case 0:
// Mode 0: CPOL = 0, CPHA = 0
// Clock idle low, data captured on rising edge, output on falling edge
buf[0] = 0x8A; // Enable clock divide by 5 for 60MHz master clock
buf[1] = 0x97; // Ensure adaptive clocking is off
buf[2] = 0x8D; // Disable three-phase clocking
break;
case 1:
// Mode 1: CPOL = 0, CPHA = 1
// Clock idle low, data captured on falling edge, output on rising edge
buf[0] = 0x8A;
buf[1] = 0x97;
buf[2] = 0x8C; // Enable three-phase clocking
break;
case 2:
// Mode 2: CPOL = 1, CPHA = 0
// Clock idle high, data captured on falling edge, output on rising edge
buf[0] = 0x8A;
buf[1] = 0x97;
buf[2] = 0x8D; // Disable three-phase clocking
break;
case 3:
// Mode 3: CPOL = 1, CPHA = 1
// Clock idle high, data captured on rising edge, output on falling edge
buf[0] = 0x8A;
buf[1] = 0x97;
buf[2] = 0x8C; // Enable three-phase clocking
break;
default:
fprintf(stderr, "Invalid SPI mode: %d\n", dataMode);
return EXIT_FAILURE;
}
ftdi_status = ftdi_write_data(&ftdi, buf, 3);
if (ftdi_status < 0)
{
fprintf(stderr, "Failed to configure SPI mode: %s\n", ftdi_get_error_string(&ftdi));
return EXIT_FAILURE;
}
printf("SPI mode %d configured successfully.\n", dataMode);
return 0; // Success
};
int SPI_SetClockDivider(int clock_rate_hz)
{
// Set the clock divider
unsigned char buf[3];
int ftdi_status;
int divisor;
// Calculate divisor for the desired clock rate (assuming a 12MHz base clock for FT232H)
divisor = (12000000 / clock_rate_hz) - 1;
// Command to set clock divisor
buf[0] = 0x86; // Set TCK/SK divisor command
buf[1] = (unsigned char)(divisor & 0xFF); // Divisor low byte
buf[2] = (unsigned char)((divisor >> 8) & 0xFF); // Divisor high byte
ftdi_status = ftdi_write_data(&ftdi, buf, 3);
if (ftdi_status < 0)
{
fprintf(stderr, "Failed to set clock divisor: %s\n", ftdi_get_error_string(&ftdi));
return EXIT_FAILURE;
}
};
public:
SPI(int channel) : ftdi_channel(channel) {
// initi
int ftdi_status = 0;
ftdi_status = ftdi_init(&ftdi);
if ( ftdi_status != 0 ) {
std::cout << "Failed to initialize device\n";
//return EXIT_FAILURE;
}
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 EXIT_FAILURE;
}
unsigned int chipid;
ftdi_read_chipid(&ftdi, &chipid);
fprintf( stdout, "FTDI chipid: %X\n", chipid);
ftdi_usb_reset( &ftdi);
// Set MPSSE mode
ftdi_set_interface(&ftdi, INTERFACE_ANY);
ftdi_set_bitmode(&ftdi, 0, 0); // reset
ftdi_set_bitmode(&ftdi, 0, BITMODE_MPSSE); // enable mpsse on all bits
ftdi_usb_purge_buffers(&ftdi);
usleep(50000); // sleep 50 ms for setup to complete
// Initialize the SPI bus
// SPI_Init();
}
~SPI() {
ftdi_usb_reset(&ftdi);
ftdi_usb_close(&ftdi);
ftdi_deinit(&ftdi);
}
ftdi_context* GetFTDI () {
return &ftdi;
}
void begin() {
SPI_Reset();
}
void end() {
// Perform cleanup if necessary
}
void beginTransaction() {
// Set up SPI parameters
SPI_SetBitOrder(MSB_FIRST);
SPI_SetDataMode(0);
SPI_SetClockDivider(2);
}
void endTransaction() {
// Perform transaction end tasks if necessary
}
int transfer(const unsigned char *sendBuf, unsigned char *recvBuf, size_t size)
{
int ret;
// Sende die Bytes
ret = ftdi_write_data(&ftdi, sendBuf, size);
if (ret < 0)
{
std::cerr << "Unable to send data: " << ftdi_get_error_string(&ftdi) << std::endl;
return ret;
}
// Empfange die Bytes
ret = ftdi_read_data(&ftdi, recvBuf, size);
if (ret < 0)
{
std::cerr << "Unable to receive data: " << ftdi_get_error_string(&ftdi) << std::endl;
return ret;
}
return 0; // Erfolg
}
void transfer(uint8_t *data,int size ) {
uint8_t in;
if( ftdi_write_data(&ftdi, data, size)!= size ) {
std::cout << "Write transfer failed\n";
}else{
std::cout << "Transfer Write Data size = " << std::dec << size << " OK" << std::endl;
}
//ftdi_write_data(&ftdi, data, sizeof(data));
//ftdi_read_data(&ftdi, data, size);
//if ( ftdi_read_data(&ftdi, &in, sizeof(in)) != sizeof(in) ) {
// std::cout << "Read transfer failed\n";
//}
}
void setBitOrder(uint8_t bitOrder) {
// Provide implementation
}
void setDataMode(uint8_t dataMode) {
// Provide implementation
}
void setClockDivider(uint8_t clockDiv) {
// Provide implementation
}
};
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;
int main(void)
{
SPI spi(0);
// 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 EXIT_FAILURE;
}
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 EXIT_FAILURE;
}
unsigned int chipid;
ftdi_read_chipid(&ftdi, &chipid);
fprintf( stdout, "FTDI chipid: %X\n", chipid);
// Set MPSSE mode
ftdi_usb_reset(&ftdi);
ftdi_set_interface(&ftdi, INTERFACE_ANY);
ftdi_set_bitmode(&ftdi, 0, 0); // reset
ftdi_set_bitmode(&ftdi, 0, BITMODE_MPSSE); // enable mpsse on all bits
ftdi_usb_purge_buffers(&ftdi);
usleep(50000); // sleep 50 ms for setup to complete
*/
// Setup MPSSE; Operation code followed by 0 or more arguments.
unsigned int icmd = 0;
uint8_t buf[256] = {0};
buf[icmd++] = TCK_DIVISOR; // opcode: set clk divisor
buf[icmd++] = 0x05; // argument: low bit. 60 MHz / (5+1) = 1 MHz
buf[icmd++] = 0x00; // argument: high bit.
buf[icmd++] = DIS_ADAPTIVE; // opcode: disable adaptive clocking
buf[icmd++] = DIS_3_PHASE; // opcode: disable 3-phase clocking
buf[icmd++] = SET_BITS_LOW; // opcode: set low bits (ADBUS[0-7])
buf[icmd++] = pinInitialState; // argument: inital pin states
buf[icmd++] = pinDirection; // argument: pin direction
// Write the setup to the chip.
spi.beginTransaction();
spi.transfer(buf,buf,icmd);
//spi.transfer(buf, icmd);
//if ( ftdi_write_data(spi.GetFTDI(), buf, icmd) != icmd ) {
// std::cout << "Write setup failed\n";
//}
// zero the buffer for good measure
memset(buf, 0, sizeof(buf));
icmd = 0;
// Now we will write and read 1 byte.
// The DO and DI pins should be physically connected on the breadboard.
// Next three commands sets the GPIOL0 pin low. Pulling CS low.
buf[icmd++] = SET_BITS_LOW;
buf[icmd++] = pinInitialState & ~Pin::CS;
buf[icmd++] = pinDirection;
// commands to write and read one byte in SPI0 (polarity = phase = 0) mode
buf[icmd++] = MPSSE_DO_WRITE | MPSSE_WRITE_NEG | MPSSE_DO_READ;
buf[icmd++] = 0x00; // length low byte, 0x0000 ==> 1 byte
buf[icmd++] = 0x00; // length high byte
buf[icmd++] = 0x12; // byte to send
// Next three commands sets the GPIOL0 pin high. Pulling CS high.
buf[icmd++] = SET_BITS_LOW;
buf[icmd++] = pinInitialState | Pin::CS;
buf[icmd++] = pinDirection;
std::cout << "Writing: ";
for ( int i = 0; i < icmd; ++i ) {
std::cout << std::hex << (unsigned int)buf[i] << ' ';
}
std::cout << '\n';
// need to purge tx when reading for some etherial reason
ftdi_usb_purge_tx_buffer( spi.GetFTDI());
//spi.beginTransaction();
//spi.transfer(buf);
spi.transfer(buf,buf,icmd);
//spi.transfer(buf, icmd);
//if ( spi.transfer(buf, icmd) != icmd ) {
// std::cout << "Write purge failed\n";
//}
//if ( ftdi_write_data(spi.GetFTDI(), buf, icmd) != icmd ) {
// std::cout << "Write purge failed\n";
//}
// zero the buffer for good measure
///memset(buf, 0, sizeof(buf));
///icmd = 0;
// now get the data we read just read from the chip
///unsigned char readBuf[256] = {0};
//spi.
//spi.transfer(readBuf,1);
///ftdi_read_data(spi.GetFTDI(), readBuf, 1);
std::cout << "Answer: " << std::hex << (unsigned int)buf[0] << std::endl;
// close ftdi
ftdi_usb_reset(spi.GetFTDI());
ftdi_usb_close(spi.GetFTDI());
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment