Created
March 26, 2024 21:26
-
-
Save marelab/e72b1e4156e2ee541812a08f18518b67 to your computer and use it in GitHub Desktop.
USB2SPI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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