I finally am well enough to get down to my workshop, so I figured I had better try to get a project done before I get incapacitated again.
Project Description
This project is an exploration of implementing an analog to digital converter without involving a microcontroller development system.
The project will use an MCP3208MCP3208 (SPI 8 channel ADC) connected to an FT2232FT2232 (USB to SPI interface). This will allow all the programming to be performed on the host PC.
The project will incorporate at least 3 custom PCBs (1 for the ADC and several for sensors).
Sensors
The sensors I will be using are force sensitive resistors (FSR). I have a large collection of different types of FSR. This picture shows a few examples:
Before getting into the ADC interface, I want to first explore force sensitive resistors and their interface requirements...
Sensor Interfacing
Here is a discussion of sensor characteristics and now they can be interfaced:
Here is a typical sensor interface schematic:
This circuit provides a nice linear output that operates from a single supply and goes from zero volts at zero force all the way to the high rail at full force.
It even provides some low pass filtering.
I have developed several different printed circuits to interface these sensors to analog MCU inputs - here are a few examples:
The following video shows how a typical sensor and circuit works using a DVM and oscilloscope:
Now I will discuss the A/D and how it is interfaced.
Hardware
The FT2232 translates USB to SPI, which connects to the MCP3208 A/D converter. The A/D is an 8 channel 12 bit SAR. I am running 4 sensor channels through low-pass filters to four of the A/D inputs and the same 4 sensor channels to 4 different A/D inputs, except with no filtering.
Here is the schematic:
The PCB looks like this:
The assembled CCA looks like this:
I am using 3.5mm stereo cables to connect to sensors (+5V, signal and GND).
Packaging
I happened to have a Serpac C10Serpac C10 case of the correct size, (as in - it was designed to fit) so I used that to house the A/D CCA. The hole in the middle of the PCB matches up to a screw post in the middle of the case.
Communications Protocol
The result of an A/D conversion is obtained by sending a series of bits to the MCP3208 using SPI MOSI, which tell the chip which channel to convert and when to start. After the successive approximation conversion is complete, extra bits need to be sent to clock the result out of the chip using SPI MISO.
However, to send SPI data to the MCP3208 we are using an FT2232 (USB-to-SPI) chip. This chip needs to be configured to handle SPI properly, so the first 6 bytes sent over USB are used by the FT2232 to configure it to use SPI.
Here is the entire string of bits needed to complete an A/D transaction, only the last 3 bytes get passed on to the ADC, which passes 3 bytes back with the conversion data (in blue below):
As far as USB is concerned, we just send 9 bytes of data and receive 3 bytes back, but we need to extract the 12 bit conversion result from the 3 bytes as shown in the last line of the table. It is a bit strange that some of the 12 bits of interest come from each of the 3 bytes, but it isn't hard to extract the reading, once you know which bits to use.
Software
I wrote a VB6 program to do exactly that. It also displays 4 channels of data and plots a 4 channels.
Here is the program in action:
Unfortunately the screen capture software I was using was dropping frames which made the plotted traces look discontinuous sometimes. Hopefully it captured enough to get an idea of what the program does.
Software
Event Driven Code
' USB DAQ by Doug Wong - a program to read an 8 channel A/D converter and display the voltages.
' This program was developed in VB6 and uses FTDI DLLs
' The program controls an MCP3208 SPI A/D chip via USB using an FTDI FT2232 in MPSSE mode
' The MCP3208 requires 3 control bytes to be sent via SPI while simultaneously reading back the last 12 bits as data
Option Explicit
Private Sub cmdContinuous_Click()
' continuous readings
Dim Point(1001, 4) As Integer 'array to store plot points for 4 traces
Dim T, chan As Integer 'T is time (=point count), Chan is channel number
Dim Buf(4) As Integer 'Buf stores point to allow detection of data glitches
Dim dR As Integer 'delta reading is the difference between successive readings
Dim Glitch(4) As Boolean 'glitch flags for each channel
Dim Colour(4) As Long 'trace colours
Dim etStart As Long 'elapsed time start
Dim etEnd As Long 'elapsed time end
Dim et As Long 'elapsed time
Dim rate As Single 'sample rate
If Not (PortAIsOpen) Then Exit Sub
StopReading = False
Colour(0) = vbBlue 'channel 0 is blue
Colour(1) = vbRed 'channel 1 is red
Colour(2) = vbGreen 'channel 2 is green
Colour(3) = vbCyan 'channel 3 is cyan
etStart = GetTickCount 'initiallize start time
Plot.Line (0, 0)-(2000, 1000), vbWhite, BF 'erase screen
For chan = 0 To 3 'initiallize glitch flags
Glitch(chan) = False
Next chan
Do
etEnd = GetTickCount 'get millisecond time to calculate sample rate
et = etEnd - etStart 'calculate elapsed time for one screen redraw
etStart = etEnd 'save start time for next calc
et = et / 1000 'convert ms to seconds
If et < 0.1 Then et = 4000 'prevent divide by zero on first pass
rate = 4000 / et 'calc sample rate - 4000 samples per screen
' RateLbl.Caption = Format(rate, "###0.0") 'display rate
TakeReading 'take a set of readings - one from each channel
T = 1 'set time to ist point
For chan = 0 To 3 'erase first segment of each trace because a new one is being drawn
Plot.Line (T, Point(T, chan))-(T + 1, Point(T + 1, chan)), vbWhite 'erase trace segment
Point(T, chan) = 1470 - Reading(chan) / 2.1 'calc 1st point
Buf(chan) = Point(T, chan) / 10 'save old point
Glitch(chan) = False 'initiallize glitch flag
Next chan
For T = 2 To 1000 'read 4 channels and plot 4 traces
For chan = 0 To 3 'erase next trace segment ready to draw again
Plot.Line (T, Point(T, chan))-(T + 1, Point(T + 1, chan)), vbWhite
Next chan
TakeReading 'take a set of readings - one from each channe
For chan = 3 To 0 Step -1 'draw last channel first so chan 0 will be in front
Buf(chan) = 1470 - Reading(chan) / 2.1 'get reading
dR = Point(T - 1, chan) - Buf(chan) 'calc difference between successive readings
If Abs(dR) > 40 Then 'if trace jumps too much, it is a data glitch
If Glitch(chan) = False Then
Buf(chan) = Point(T - 1, chan) 'if it is a new glitch, use last point
Glitch(chan) = True
TakeReading 'take a set of readings to clear the glitch
Else 'if last point was a glitch, start to move
' Buf(chan) = 40 * dR / Abs(dR) + Buf(chan) 'move 40 pixels towards new reading
Glitch(chan) = False
End If
End If
Point(T, chan) = Buf(chan) 'save point so it can be erased next time through
Plot.Line (T - 1, Point(T - 1, chan))-(T, Point(T, chan)), Colour(chan) 'draw trace segment
DoEvents
If StopReading Then Exit Do 'if stop button was pushed, stop drawing traces
Next chan
Next T
Loop Until StopReading
End Sub
Private Sub cmdOpen_Click()
' open the USB A/D module
OpenDevice
End Sub
Private Sub cmdStop_Click()
' stop scope
StopReading = True ' say we want to stop
DoEvents
End Sub
Private Sub Command1_Click()
Timer1.Enabled = True
End Sub
Private Sub Command2_Click()
Timer1.Enabled = False
End Sub
Private Sub Form_Load()
' initialise the program
InitialiseVariables ' init variables
OpenDevice ' open the USB A/D
DoEvents
End Sub
Private Sub Form_Unload(Cancel As Integer)
' unload form tidy up
Dim Res As Long
If PortAIsOpen Then
Res = Close_USB_Device
If FT_Result <> FT_OK Then
PortAIsOpen = False
Form1.shpOK.BackColor = Red
StopReading = True
Form1.lblStatus.Caption = "Close device failed in procedure Form Unload."
Exit Sub
End If
End If
End Sub
Private Sub mnuExit_Click()
' end program
cmdStop_Click
DoEvents
Timer1.Enabled = False
DoEvents
Unload Me
' Stop
End
End Sub
Private Sub Timer1_Timer()
Dim chan As Integer
Dim voltage As Single
TakeReading
For chan = 0 To 3
voltage = Reading(chan) / 200
voltage = voltage - 10
If voltage < 0 Then voltage = 0
Channel(chan).Text = FormatNumber(voltage - 0.24, 3)
' Channel(chan).DataFormat = ("#.##")
' Channel(chan).Text = voltage
Next chan
End Sub
Basic Code
Option Explicit
Public RegKey As String ' name of registry key
Public Const Green = &HFF00&
Public Const Red = &HFF&
Public Const White = &HFFFFFF
Public Const Yellow = &HFFFF&
Public Const ButtonFace = &H8000000F
Public NumberOfReadings ' number of readings taken when continuous
Public StopReading As Boolean ' true = stop continuous readings
Public OurDevice As String ' the name of our USB A/D device
Public Reading(4) As Single ' actual value of reading from ADC
Public Saved_Port_Value As Byte ' the setting of the first 8 data lines
Public OutIndex As Long ' position within the output buffer
Public PortAIsOpen As Boolean ' true = the USB A/D chan A is open
'==============================
'CLASSIC INTERFACE DECLARATIONS
'==============================
Public Declare Function FT_ListDevices Lib "FTD2XX.DLL" ( _
ByVal arg1 As Long, _
ByVal arg2 As String, _
ByVal dwFlags As Long) As Long
Public Declare Function FT_GetNumDevices Lib "FTD2XX.DLL" Alias "FT_ListDevices" ( _
ByRef arg1 As Long, _
ByVal arg2 As String, _
ByVal dwFlags As Long) As Long
Public Declare Function FT_Open Lib "FTD2XX.DLL" ( _
ByVal intDeviceNumber As Integer, _
ByRef lngHandle As Long) As Long
Public Declare Function FT_OpenEx Lib "FTD2XX.DLL" ( _
ByVal arg1 As String, _
ByVal arg2 As Long, _
ByRef lngHandle As Long) As Long
Public Declare Function FT_Close Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long) As Long
Public Declare Function FT_Read Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal lpszBuffer As String, _
ByVal lngBufferSize As Long, _
ByRef lngBytesReturned As Long) As Long
Public Declare Function FT_Write Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal lpszBuffer As String, _
ByVal lngBufferSize As Long, _
ByRef lngBytesWritten As Long) As Long
Public Declare Function FT_WriteByte Lib "FTD2XX.DLL" Alias "FT_Write" ( _
ByVal lngHandle As Long, _
ByRef lpszBuffer As Any, _
ByVal lngBufferSize As Long, _
ByRef lngBytesWritten As Long) As Long
Public Declare Function FT_SetBaudRate Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal lngBaudRate As Long) As Long
Public Declare Function FT_SetDataCharacteristics Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal byWordLength As Byte, _
ByVal byStopBits As Byte, _
ByVal byParity As Byte) As Long
Public Declare Function FT_SetFlowControl Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal intFlowControl As Integer, _
ByVal byXonChar As Byte, _
ByVal byXoffChar As Byte) As Long
Public Declare Function FT_SetDtr Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long) As Long
Public Declare Function FT_ClrDtr Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long) As Long
Public Declare Function FT_SetRts Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long) As Long
Public Declare Function FT_ClrRts Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long) As Long
Public Declare Function FT_GetModemStatus Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByRef lngModemStatus As Long) As Long
Public Declare Function FT_SetChars Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal byEventChar As Byte, _
ByVal byEventCharEnabled As Byte, _
ByVal byErrorChar As Byte, _
ByVal byErrorCharEnabled As Byte) As Long
Public Declare Function FT_Purge Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal lngMask As Long) As Long
Public Declare Function FT_SetTimeouts Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal lngReadTimeout As Long, _
ByVal lngWriteTimeout As Long) As Long
Public Declare Function FT_GetQueueStatus Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByRef lngRxBytes As Long) As Long
Public Declare Function FT_SetBreakOn Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long) As Long
Public Declare Function FT_SetBreakOff Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long) As Long
Public Declare Function FT_GetStatus Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByRef lngRxBytes As Long, _
ByRef lngTxBytes As Long, _
ByRef lngEventsDWord As Long) As Long
Public Declare Function FT_SetEventNotification Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal dwEventMask As Long, _
ByVal pVoid As Long) As Long
Public Declare Function FT_ResetDevice Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long) As Long
Public Declare Function FT_GetBitMode Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByRef intData As Any) As Long
Public Declare Function FT_SetBitMode Lib "FTD2XX.DLL" ( _
ByVal lngHandle As Long, _
ByVal intMask As Byte, _
ByVal intMode As Byte) As Long
Public Declare Function FT_SetLatencyTimer Lib "FTD2XX.DLL" ( _
ByVal Handle As Long, _
ByVal pucTimer As Byte) As Long
Public Declare Function FT_GetLatencyTimer Lib "FTD2XX.DLL" ( _
ByVal Handle As Long, _
ByRef ucTimer As Long) As Long
Public Declare Function GetTickCount Lib "kernel32" () As Long
' Return codes
Public Const FT_OK = 0
Public Const FT_INVALID_HANDLE = 1
Public Const FT_DEVICE_NOT_FOUND = 2
Public Const FT_DEVICE_NOT_OPENED = 3
Public Const FT_IO_ERROR = 4
Public Const FT_INSUFFICIENT_RESOURCES = 5
Public Const FT_INVALID_PARAMETER = 6
Public Const FT_INVALID_BAUD_RATE = 7
Public Const FT_DEVICE_NOT_OPENED_FOR_ERASE = 8
Public Const FT_DEVICE_NOT_OPENED_FOR_WRITE = 9
Public Const FT_FAILED_TO_WRITE_DEVICE = 10
Public Const FT_EEPROM_READ_FAILED = 11
Public Const FT_EEPROM_WRITE_FAILED = 12
Public Const FT_EEPROM_ERASE_FAILED = 13
Public Const FT_EEPROM_NOT_PRESENT = 14
Public Const FT_EEPROM_NOT_PROGRAMMED = 15
Public Const FT_INVALID_ARGS = 16
Public Const FT_NOT_SUPPORTED = 17
Public Const FT_OTHER_ERROR = 18
' Flags for FT_OpenEx
Public Const FT_OPEN_BY_SERIAL_NUMBER = 1
Public Const FT_OPEN_BY_DESCRIPTION = 2
' Flags for FT_ListDevices
Public Const FT_LIST_NUMBER_ONLY = &H80000000
Public Const FT_LIST_BY_INDEX = &H40000000
Public Const FT_LIST_ALL = &H20000000
' IO buffer sizes
Public Const FT_In_Buffer_Size = 1024
Public Const FT_Out_Buffer_Size = 1024
Public FT_In_Buffer As String * FT_In_Buffer_Size
Public FT_Out_Buffer As String * FT_Out_Buffer_Size
Public FT_IO_Status As Long
Public FT_Result As Long
Public FT_Device_Count As Long
Public FT_Device_String_Buffer As String * 50
Public FT_Device_String As String
Global lngHandle As Long
Public FT_HANDLE As Long
Public PV_Device As Integer
Public FT_Q_Bytes As Long
Public Sub InitialiseVariables()
' initialise variables
RegKey = "RFT Sensor"
OurDevice = "RFT Sensor A" ' set the name of our USB A/D
End Sub
Public Sub AddToBuffer(I As Long)
' add a character to the output buffer
Mid(FT_Out_Buffer, OutIndex + 1, 1) = Chr(I)
OutIndex = OutIndex + 1
End Sub
Public Function Close_USB_Device() As Long
' close the module
FT_Result = FT_Close(FT_HANDLE)
If FT_Result <> FT_OK Then
FT_Error_Report "FT_Close", FT_Result
End If
Close_USB_Device = FT_Result
End Function
Public Sub FT_Error_Report(ErrStr As String, PortStatus As Long)
' show an error message
Dim Str As String
Select Case PortStatus
Case FT_INVALID_HANDLE
Str = ErrStr & " - Invalid Handle"
Case FT_DEVICE_NOT_FOUND
Str = ErrStr & " - Device Not Found"
Case FT_DEVICE_NOT_OPENED
Str = ErrStr & " - Device Not Opened"
Case FT_IO_ERROR
Str = ErrStr & " - General IO Error"
Case FT_INSUFFICIENT_RESOURCES
Str = ErrStr & " - Insufficient Resources"
Case FT_INVALID_PARAMETER
Str = ErrStr & " - Invalid Parameter"
Case FT_INVALID_BAUD_RATE
Str = ErrStr & " - Invalid Baud Rate"
Case FT_DEVICE_NOT_OPENED_FOR_ERASE
Str = ErrStr & " - Device not opened for Erase"
Case FT_DEVICE_NOT_OPENED_FOR_WRITE
Str = ErrStr & " - Device not opened for Write"
Case FT_FAILED_TO_WRITE_DEVICE
Str = ErrStr & " - Failed to write Device"
Case FT_EEPROM_READ_FAILED
Str = ErrStr & " - EEPROM read failed"
Case FT_EEPROM_WRITE_FAILED
Str = ErrStr & " - EEPROM write failed"
Case FT_EEPROM_ERASE_FAILED
Str = ErrStr & " - EEPROM erase failed"
Case FT_EEPROM_NOT_PRESENT
Str = ErrStr & " - EEPROM not present"
Case FT_EEPROM_NOT_PROGRAMMED
Str = ErrStr & " - EEPROM not programmed"
Case FT_INVALID_ARGS
Str = ErrStr & " - Invalid Arguments"
Case FT_NOT_SUPPORTED
Str = ErrStr & " - not supported"
Case FT_OTHER_ERROR
Str = ErrStr & " - other error"
End Select
Form1.shpOK.BackColor = Red ' turn status indicator red
StopReading = True ' turn off continuous readings
Form1.lblStatus.Caption = Str ' show the message in the status area
MsgBox Str ' display the message
End Sub
Public Function Get_USB_Device_QueueStatus() As Long
' return the number of bytes waiting to be read
FT_Result = FT_GetQueueStatus(FT_HANDLE, FT_Q_Bytes)
If FT_Result <> FT_OK Then
FT_Error_Report "FT_GetQueueStatus", FT_Result
End If
Get_USB_Device_QueueStatus = FT_Result
End Function
Public Function GetDeviceString() As String
' get the device name
GetDeviceString = Left(FT_Device_String_Buffer, InStr(FT_Device_String_Buffer, Chr(0)) - 1)
End Function
Public Function GetFTDeviceCount() As Long
' get the number of connected devices
FT_Result = FT_GetNumDevices(FT_Device_Count, 0, FT_LIST_NUMBER_ONLY)
If FT_Result = FT_OK Then
GetFTDeviceCount = FT_Device_Count ' return the number of devices
Else
FT_Error_Report "GetFTDeviceCount", FT_Result ' show error message
GetFTDeviceCount = 0 ' return 0 devices
End If
End Function
Public Function GetFTDeviceDescription(DeviceIndex As Long) As String
' get the device description of a specific device
FT_Result = FT_ListDevices(DeviceIndex, FT_Device_String_Buffer, (FT_OPEN_BY_DESCRIPTION Or FT_LIST_BY_INDEX))
If FT_Result = FT_OK Then
FT_Device_String = GetDeviceString ' strip off trailing nulls
GetFTDeviceDescription = FT_Device_String ' return the character part
Else
FT_Error_Report "GetFTDeviceDescription", FT_Result
GetFTDeviceDescription = "" ' init to null
End If
End Function
Public Function GetFTDeviceSerialNo(DeviceIndex As Long) As String
' get the serial number of a specific device
FT_Result = FT_ListDevices(DeviceIndex, FT_Device_String_Buffer, (FT_OPEN_BY_SERIAL_NUMBER Or FT_LIST_BY_INDEX))
If FT_Result = FT_OK Then
FT_Device_String = GetDeviceString ' strip off trailing nulls
GetFTDeviceSerialNo = FT_Device_String ' return the character part
Else
FT_Error_Report "GetFTDeviceSerialNo", FT_Result
GetFTDeviceSerialNo = "" ' init to null
End If
End Function
Public Function Init_Controller(DName As String) As Boolean
' initialise the controller on port DName
Init_Controller = OpenPort(DName) ' open the port
End Function
Public Function Open_USB_Device_By_Description(Device_Description As String) As Long
SetDeviceString Device_Description
FT_Result = FT_OpenEx(FT_Device_String_Buffer, FT_OPEN_BY_DESCRIPTION, FT_HANDLE)
If FT_Result <> FT_OK Then
FT_Error_Report "Open_USB_Device_By_Description", FT_Result
End If
End Function
Public Sub OpenDevice()
' open the USB A/D module by name. The A port is the only one that can be used for MPSSE SPI
' communications.
Dim I As Long
Dim X As Long
Dim DeviceDescription As String
Dim FoundDevice As Boolean
Dim Res As Long
' if the port is already open then close it
If PortAIsOpen Then
Res = Close_USB_Device
If FT_Result <> FT_OK Then
PortAIsOpen = False
Form1.shpOK.BackColor = Red
Form1.lblStatus.Caption = "Attempt to close USB A/D failed."
StopReading = True
Exit Sub
End If
End If
' set port A not open
PortAIsOpen = False
' see if anything connected
X = GetFTDeviceCount
If X = 0 Then
Form1.shpOK.BackColor = Yellow
Form1.lblStatus.Caption = "No FTDI devices found. Please connect the meter and re-try"
Exit Sub
End If
' get the descriptions and look for DLP module channel A
For I = 0 To FT_Device_Count - 1
DeviceDescription = GetFTDeviceDescription(I)
If FT_Result = FT_OK Then
If DeviceDescription = OurDevice Then
FoundDevice = True
Exit For
End If
End If
Next
' check we have a DLP A module found
If Not (FoundDevice) Then
Form1.shpOK.BackColor = Yellow
Form1.lblStatus.Caption = "No USB A/D A device found. Please re-connect the meter and re-try"
Exit Sub
End If
' open by the device description
Open_USB_Device_By_Description DeviceDescription
If FT_Result <> FT_OK Then
Form1.shpOK.BackColor = Red
StopReading = True
Form1.lblStatus.Caption = "The open for the meter did not complete successfully."
Exit Sub
End If
' try a command
Res = Get_USB_Device_QueueStatus
If FT_Result <> FT_OK Then
Form1.shpOK.BackColor = Red
StopReading = True
Form1.lblStatus.Caption = "Get USB Device QueuStatus command failed in procedure OpenDevice"
Exit Sub
End If
PortAIsOpen = True
' set the latency
FT_Result = Set_USB_Device_LatencyTimer(16)
If FT_Result <> FT_OK Then
Form1.shpOK.BackColor = Red
StopReading = True
Form1.lblStatus.Caption = "Set USB Device Latency Timer failed"
Exit Sub
End If
' reset the controller
FT_Result = Set_USB_Device_BitMode(&H0, &H0) ' reset the controller
If FT_Result <> FT_OK Then
Form1.shpOK.BackColor = Red
StopReading = True
Form1.lblStatus.Caption = "Device reset failed in procedure OpenDevice."
Exit Sub
End If
' set the module to MPSSE mode
FT_Result = Set_USB_Device_BitMode(&H0, &H2) ' set to MPSSE mode
If FT_Result <> FT_OK Then
Form1.shpOK.BackColor = Red
StopReading = True
Form1.lblStatus.Caption = "Set to MPSSE mode failed in procedure OpenDevice."
Exit Sub
End If
' sync MPSSE mode
If Not (Sync_To_MPSSE) Then
Form1.shpOK.BackColor = Red
StopReading = True
Form1.lblStatus.Caption = "Unable to synchronise the MPSSE write/read cycle in procedure OpenDevice."
Exit Sub
End If
' initialise the port
OutIndex = 0 ' point to the start of output buffer
Saved_Port_Value = &H8 ' set the initial state of the first 8 lines
' set the low byte
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer &H8 ' set CS=high, DI=low, DO=low, SK=low
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
' set the clock divisor
AddToBuffer &H86 ' set clock divisor command to 1MHz
AddToBuffer &H5 ' low byte
AddToBuffer &H0 ' high byte
AddToBuffer &H85 ' turn off loopback
SendBytes OutIndex ' send to command processor
' check for a bad command being echoed back
Res = Get_USB_Device_QueueStatus
If FT_Q_Bytes > 0 Or Res <> 0 Then
Form1.shpOK.BackColor = Yellow
Form1.lblStatus.Caption = "Possible bad command detected in procedure OpenDevice."
Exit Sub
End If
Form1.shpOK.BackColor = Green ' set status to green
Form1.lblStatus.Caption = "AVI Device Detected" ' set OK
End Sub
Public Function OpenPort(PortName As String) As Boolean
' to open the port named PortName
Dim Res As Long
Dim NoOfDevs As Long
Dim I As Long
Dim Name As String
Dim DualName As String
PortAIsOpen = False ' init to port not open
OpenPort = False ' init to failure to open port
Name = "" ' set name to null
DualName = PortName ' set which port we want to open
NoOfDevs = GetFTDeviceCount ' get the number of devices
If FT_Result <> FT_OK Then Exit Function ' exit if failure
' try to find the requested port
For I = 0 To NoOfDevs - 1
Name = GetFTDeviceDescription(I) ' get the device desctiption
If Name = DualName Then Exit For ' exit if this is the one
Next
If Name <> DualName Then Exit Function ' exit if not found
Res = Open_USB_Device_By_Description(DualName) ' open the device by its description
If FT_Result <> FT_OK Then Exit Function ' exit if failure
Res = Get_USB_Device_QueueStatus ' perform a test function on the port
If FT_Result <> FT_OK Then Exit Function ' exit if failure
PortAIsOpen = True ' flag port as open
OpenPort = True ' return open OK
End Function
Public Function Read_USB_Device_Buffer(Read_Count As Long) As Long
' Reads Read_Count bytes or less from the USB device to the FT_In_Buffer
' The function returns the number of bytes actually received which may range from zero
' to the actual number of bytes requested, depending on how many have been received
' at the time of the request + the read timeout value.
Dim Read_Result As Long
If Read_Count = 1 Then Read_Result = Read_Count
FT_IO_Status = FT_Read(FT_HANDLE, FT_In_Buffer, Read_Count, Read_Result)
If FT_IO_Status <> FT_OK Then
FT_Error_Report "FT_Read", FT_IO_Status
End If
Read_USB_Device_Buffer = Read_Result
End Function
Public Sub SendBytes(NumberOfBytes As Long)
Dim I As Long
I = Write_USB_Device_Buffer(NumberOfBytes)
OutIndex = OutIndex - I
End Sub
Public Function Set_USB_Device_BitMode(ucMask As Byte, ucEnable As Byte) As Long
Set_USB_Device_BitMode = FT_SetBitMode(FT_HANDLE, ucMask, ucEnable)
End Function
Public Function Set_USB_Device_LatencyTimer(ucLatency As Byte) As Long
Set_USB_Device_LatencyTimer = FT_SetLatencyTimer(FT_HANDLE, ucLatency)
End Function
Public Sub SetDeviceString(S As String)
' set the device name
FT_Device_String_Buffer = S & Chr(0)
End Sub
Public Function Sync_To_MPSSE() As Boolean
' uses &HAA and &HAB commands which are invalid so that the MPSSE processor should
' echo these back to use preceded with &HFA
Dim Res As Long
Dim I As Long
Dim J As Long
Sync_To_MPSSE = False
' clear anything in the input buffer
Res = Get_USB_Device_QueueStatus
If Res <> FT_OK Then Exit Function
If FT_Q_Bytes > 0 Then
' read chunks of 'input buffer size'
Do While FT_Q_Bytes > FT_In_Buffer_Size
I = Read_USB_Device_Buffer(FT_In_Buffer_Size) ' read a chunk
FT_Q_Bytes = FT_Q_Bytes - FT_In_Buffer_Size ' calculate bytes left
Loop
I = Read_USB_Device_Buffer(FT_Q_Bytes) ' read the final bytes
End If
' put a bad command to the command processor
OutIndex = 0 ' point to start of buffer
AddToBuffer &HAA ' add a bad command
SendBytes OutIndex ' send to command processor
' wait for a response
Do
Res = Get_USB_Device_QueueStatus
Loop Until (FT_Q_Bytes > 0) Or (Res <> FT_OK)
If Res <> FT_OK Then Exit Function
' read the input queue
I = Read_USB_Device_Buffer(FT_Q_Bytes) ' read the bytes
For J = 1 To I
If Mid(FT_In_Buffer, J, 1) = Chr(&HAA) Then
Sync_To_MPSSE = True
Exit Function
End If
Next
End Function
Public Sub TakeReading()
' take a single read of the ADC
Dim BitTest As Byte
Dim Res As Long
Dim Byte0 As Byte
Dim Byte1 As Byte
Dim Byte2 As Byte
Dim I As Long
Dim IB, IBC, LoopLimit As Integer
' set CS low to initiate a conversion in the MCP3208 ADC
Saved_Port_Value = Saved_Port_Value And &HF7 ' set CS=low
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
SendBytes OutIndex ' send to command processor
' check for bad command
Res = Get_USB_Device_QueueStatus
If FT_Q_Bytes > 0 Or Res <> 0 Then
Form1.shpOK.BackColor = Yellow
Form1.lblStatus.Caption = "Possible bad command detected in procedure TakeReading when initiating an ADC conversion."
End If
' Clock data in. 3 bytes on -ve clock MSB first, no write
AddToBuffer &H31 ' write on -ve edges, read on +ve edges MSB 1st
AddToBuffer &H2 ' LSB # bytes to write & read - counting from 0
AddToBuffer &H0 ' MSB # bytes to write & read
AddToBuffer &HC0 ' MSByte bit 0 is A/D start, bit 1 is SE mode, bit 2 is Chan # MSB, bit 4 is Chan # LSB (Chan 0 = 11000000)
AddToBuffer &H0 ' Middle byte is a dummy value to get enough read clocks
AddToBuffer &H0 ' LSByte dummy value to get enough read clocks
Saved_Port_Value = Saved_Port_Value Or &H8 ' set CS=high
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
Saved_Port_Value = Saved_Port_Value And &HF7 ' set CS=low
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
AddToBuffer &H31 ' write on -ve edges, read on +ve edges MSB 1st
AddToBuffer &H2 ' LSB # bytes to write & read - counting from 0
AddToBuffer &H0 ' MSB # bytes to write & read
AddToBuffer &HC8 ' Chan 1 = 11001000
AddToBuffer &H0 ' Middle byte is a dummy value to get enough read clocks
AddToBuffer &H0 ' LSByte dummy value to get enough read clocks
Saved_Port_Value = Saved_Port_Value Or &H8 ' set CS=high
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
Saved_Port_Value = Saved_Port_Value And &HF7 ' set CS=low
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
AddToBuffer &H31 ' write on -ve edges, read on +ve edges MSB 1st
AddToBuffer &H2 ' LSB # bytes to write & read - counting from 0
AddToBuffer &H0 ' MSB # bytes to write & read
AddToBuffer &HD0 ' Chan 2 = 11010000
AddToBuffer &H0 ' Middle byte is a dummy value to get enough read clocks
AddToBuffer &H0 ' LSByte dummy value to get enough read clocks
Saved_Port_Value = Saved_Port_Value Or &H8 ' set CS=high
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
Saved_Port_Value = Saved_Port_Value And &HF7 ' set CS=low
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
AddToBuffer &H31 ' write on -ve edges, read on +ve edges MSB 1st
AddToBuffer &H2 ' LSB # bytes to write & read - counting from 0
AddToBuffer &H0 ' MSB # bytes to write & read
AddToBuffer &HD8 ' Chan 3 = 11011000
AddToBuffer &H0 ' Middle byte is a dummy value to get enough read clocks
AddToBuffer &H0 ' LSByte dummy value to get enough read clocks
Saved_Port_Value = Saved_Port_Value Or &H8 ' set CS=high
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
Saved_Port_Value = Saved_Port_Value And &HF7 ' set CS=low
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
AddToBuffer &H87 ' flush read buffer to USB
SendBytes OutIndex
' wait for data to become available
Do
Res = Get_USB_Device_QueueStatus '
Loop Until (FT_Q_Bytes > 5) Or (Res <> FT_OK) ' wait for answer to be available
If Res <> FT_OK Then
Form1.shpOK.BackColor = Red
StopReading = True
Form1.lblStatus.Caption = "Get USB device queue status failed while waiting to read an ADC conversion."
Exit Sub
End If
' read the input queue
I = Read_USB_Device_Buffer(FT_Q_Bytes) ' read the bytes
' the MCP3202 sends a null bit on the 5th clock falling edge of the first byte followed by 3 data bits, then the
' 8 data bits in the 2nd byte and 1 bit in the 3rd byte. We must combine just the data ...
For IB = 0 To 3
IBC = IB * 3
Byte0 = CByte(Asc(Mid(FT_In_Buffer, 1 + IBC, 1))) ' convert to byte format
Byte1 = CByte(Asc(Mid(FT_In_Buffer, 2 + IBC, 1))) ' convert to byte format
Byte2 = CByte(Asc(Mid(FT_In_Buffer, 3 + IBC, 1))) ' convert to byte format
Byte0 = Byte0 And &H7 ' zero the 1st 5 bits of the MSByte
Reading(IB) = (Byte0 And 7) * 512 + Byte1 * 2 + Byte2 / 128 ' combine to integer, mask = 00000111 11111111 10000000
Next IB
' turn CS high
Saved_Port_Value = Saved_Port_Value Or &H8 ' set CS=high
AddToBuffer &H80 ' Set data bits low byte command
AddToBuffer CLng(Saved_Port_Value)
AddToBuffer &HB ' CS=output, DI=input, DO=output, SK=output
SendBytes OutIndex ' send to command processor
' check got a reading
' If Reading = 0 Then
' Form1.shpOK.BackColor = Yellow
' StopReading = True
' Form1.lblStatus.Caption = "No reading received - please check the ADC power is turned on."
' Exit Sub
' Else
Form1.shpOK.BackColor = Green
Form1.lblStatus.Caption = "OK"
' End If
End Sub
Public Function Write_USB_Device_Buffer(Write_Count As Long) As Long
Dim Write_Result As Long
FT_IO_Status = FT_Write(FT_HANDLE, FT_Out_Buffer, Write_Count, Write_Result)
If FT_IO_Status <> FT_OK Then FT_Error_Report "FT-Write", FT_IO_Status
Write_USB_Device_Buffer = Write_Result
End Function
Applications
Force sensitive resistors are thin and flexible and can have pretty much any 2 dimensional shape. Because they are flexible they can also conform to curved surfaces. These properties make them useful in a wide range of applications. Here are just a few:
- touch sensing
- foot steps
- grip force
- applied pressure
- analog joysticks
- equipment and padding forces on a body
- forces generated by muscles
- impact forces
- forces on protective pads
Conclusions
This project provides information that could be useful in getting started with force sensitive resistors and SPI A/D converters. It also provides some insight into how other SPI or serial interface chips can be controlled via USB from a host computer without adding an MCU to the system. The MCP3208 is a pretty handy 8 channel A/D converted chip that can connect to any SPI port.
I had fun programming the app in VB6. Even though the last release of VB6 from Microsoft was 23 years ago, I still haven't found a language that could create an app like this quicker.
This is important because I need to get this project completed before I get incapacitated again.
If you have any questions, please ask away in the comments below.
Relevant links:
Project14 | Winners Announcement: Data Conversion: A-D or D-A Data Conversion Techniques!
Project14 | Data Conversion: From ADCs to DACs, Explore A-D or D-A Techniques!
MCP3208 (8 channel 12 bit A/D) datasheet
FT2232D (USB to SPI interface) datasheet
Omite FSR01CE (force sensitive resistor)Omite FSR01CE (force sensitive resistor)
Omite FRS06BE (force sensitive resistor)Omite FRS06BE (force sensitive resistor)
Interlink FSR 406 (force sensitive resistor)Interlink FSR 406 (force sensitive resistor)
Interlink FSR 402 (force sensitive resistor)Interlink FSR 402 (force sensitive resistor)
Tekscan Flexiforce A301 (force sensitive resistor) datasheet








Top Comments