Data Conversion

Enter Your Electronics & Design Project for a chance to win a $200 shopping cart!

Submit an EntrySubmit an Entry  Back to homepage
Project14 Home
Monthly Themes
Monthly Theme Poll

 

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:

Data Conversion

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

FT2232 SPI example

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

Tekscan product page

Tekscan User Manual