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