Рейтинг@Mail.ru
FT2232 и датчик давления BMP280
FT2232 и датчик давления BMP280

Реализация SPI: библиотека libMPSSE и .NET

автор:
Be the first to comment! Программирование
Print Friendly, PDF & Email
Рассмотрим программную реализацию класса для .NET, который использует динамически подключаемую библиотеку libMPSSE.dll фирмы FTDI Chip в режиме SPI. Для демонстрации работы библиотеки воспользуемся микросхемой FT2232H и датчиком давления и температуры BMP280.

Мы уже обсуждали работу с высокоскоростными микросхемами фирмы FTDI в режиме SPI и даже рассматривали программу, которая позволяет осуществлять приём и передачу массивов данных или файлов. Но я не описывал внутреннее устройство программы. Теперь рассмотрим этот вопрос более детально.

1Библиотеки для взаимодействия с микросхемами FTDI

В прошлой статье мы вскользь упоминали, что фирма FTDI Chip предоставляет достаточно широкий набор динамически подключаемых (*.dll) и статических (*.lib) библиотек: FTD2XX, FTD2XX_NET, FTCSPI, а также libMPSSE. Раз уж мы будем писать код для .NET, то статические библиотеки не для нас. Использовать будем последнюю из перечисленных выше динамических библиотек – libMPSSE.dll. Для тех, кто пишет на С++, существует аналогичная статическая библиотека libMPSSE.lib.

По своей функциональности библиотека libMPSSE – это урезанная версия библиотеки FTD2XX, которая позволяет по упрощённой схеме сконфигурировать микросхему для работы в режиме SPI или в режиме I2C. Это своеобразная «надстройка», которая позволяет легко использовать D2XX API. На картинке изображены уровни программного и аппаратного взаимодействия, через которые проходят потоки данных от устройства FTDI к пользователю и обратно.

Уровни взаимодействия между пользовательским приложением и микросхемой FT2232
Уровни взаимодействия между пользовательским приложением и микросхемой FT2232

Библиотека libMPSSE существует для x86 и для x64 операционных систем. Кроме того, существует несколько версий этой библиотеки. Я столкнулся с тем, что на Windows 7 x64 она работает неправильно, а на Windows XP и даже на Windows 10 x64 – та же самая библиотека работает корректно. Решение – использовать самую последнюю версию, на данный момент это версия 0.6 (Beta) от 03.09.2014. Библиотеку можно скачать по ссылкам в конце статьи.

Для получения опыта работы с микросхемами FTDI желательно приобрести (или сделать самому) отладочную плату типа такой, как на фото ниже.

Отладочная плата с FT2232H
Отладочная плата с FT2232H

Как видно, на плате очень грамотно расположены выводы, к которым легко подключаться. Кроме того, они сгруппированы по назначению. Например, один ряд – канал 1, второй – канал 2. Управляющие выводы располагаются вместе, питание отдельно. Имеются индикаторы-светодиоды. Питание от mini-USB порта. Приобрести отладочную плату с микросхемой FT2232H можно тут.

Вот электрическая схема подобной платы в 1-ой редакции. Конкретно у этой платы (в редакции 5) схема немного изменилась, но не существенно, поэтому может кому-то данная схема будет полезна.

Схема отладочной платы с микросхемой FT2232H
Схема отладочной платы с микросхемой FT2232H

2Использование динамически подключаемой библиотеки libMPSSE.dll

Далее приводится код класса, в котором мы импортируем из библиотеки необходимые нам функции, а затем добавляем «обёртку» из управляемого кода. Причём я предлагаю на выбор статический или экземплярный вариант каждого метода. Код довольно объёмный, поэтому свёрнут.

Код класса для работы с библиотекой libMPSSE.dll (разворачивается)
Imports System.Text

Namespace Ftdi

    ''' <summary>
    ''' Класс для работы с устройствами FTDI в режиме SPI с помощью библиотеки libMPSSE.dll.
    ''' </summary>
    Public Class Mpsse

#Region "READ-ONLY PROPS, CONST"

        Public Const DLL_MPSSE_PATH As String = "libMPSSE.dll"
        Public Const MAX_FREQUENCY As Integer = 30000000
        Public Const MAX_READ_DATA_BITS_BUFFER_SIZE As Integer = 524288
        Public Const MAX_READ_DATA_BYTES_BUFFER_SIZE As Integer = MAX_READ_DATA_BITS_BUFFER_SIZE \ 8
        Private Const CLOSED_HANDLE As Integer = -1

        ''' <summary>
        ''' Дескриптор устройства.
        ''' </summary>
        Public ReadOnly Property DeviceHandle As Integer
            Get
                Return _DeviceHandle
            End Get
        End Property
        Private _DeviceHandle As Integer = CLOSED_HANDLE

        ''' <summary>
        ''' Задаёт дескриптор устройства.
        ''' </summary>
        ''' <param name="index">Индекс устройства в системе.</param>
        Private Sub SetDevHandle(index As Integer)
            _DeviceHandle = index
        End Sub

        ''' <summary>
        ''' Индекс устройства в системе.
        ''' </summary>
        Public ReadOnly Property DeviceIndex As Integer = CLOSED_HANDLE

        ''' <summary>
        ''' Открыто ли устройство.
        ''' </summary>
        Public ReadOnly Property IsOpened As Boolean
            Get
                Return (DeviceHandle <> CLOSED_HANDLE)
            End Get
        End Property

        ''' <summary>
        ''' Описание устройства, считанное из ПЗУ.
        ''' </summary>
        Public ReadOnly Property NameReadable As String
            Get
                Return _NameReadable
            End Get
        End Property
        Private _NameReadable As String = String.Empty

        ''' <summary>
        ''' Серийный номер устрйоства.
        ''' </summary>
        Public ReadOnly Property SerialNumber As String
            Get
                Return _SerialNumber
            End Get
        End Property
        Private _SerialNumber As String

#End Region '/READ-ONLY PROPS, CONST

#Region "CTOR"

        ''' <summary>
        ''' Статический конструктор, инициализирующий библиотеку.
        ''' </summary>
        Shared Sub New()
            Init_libMPSSE()
        End Sub

        ''' <summary>
        ''' Подключается к устройству с заданным индексом.
        ''' </summary>
        ''' <param name="index">Индекс устройства в системе, начиная с 0.</param>
        ''' <param name="openNow">Открывать ли сразу устройство.</param>
        Public Sub New(ByVal index As Integer, Optional openNow As Boolean = True)
            DeviceIndex = index
            Dim ci As FT_DEVICE_LIST_INFO_NODE = GetChannelInfo(index)
            _NameReadable = ci.Description
            _SerialNumber = ci.SerialNumber
            If openNow Then
                SetDevHandle(OpenChannel(index))
            End If
        End Sub

#End Region '/CTOR

#Region "LOAD, UNLOAD DLL"

        ''' <summary>
        ''' Инициализирует библиотеку LibMPSSE.
        ''' </summary>
        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Sub Init_libMPSSE()
        End Sub

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Sub Cleanup_libMPSSE()
        End Sub

        ''' <summary>
        ''' Освобождает ресурсы, используемые библиотекой.
        ''' </summary>
        Public Shared Sub CleanupLibrary()
            Cleanup_libMPSSE()
        End Sub

#End Region '/LOAD, UNLOAD DLL

#Region "OPEN, CLOSE CHANNEL"

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Function SPI_OpenChannel(ByVal index As Integer, ByRef handle As Integer) As Integer
        End Function

        ''' <summary>
        ''' Открывает устройство и возвращает его дескриптор.
        ''' </summary>
        ''' <param name="index">Индекс устройства в системе.</param>
        Private Shared Function OpenChannel(ByVal index As Integer) As Integer
            Dim devH As Integer = 0
            Dim r As Integer = SPI_OpenChannel(index, devH)
            CheckStatus(r)
            If (devH = 0) Then
                Throw New Exception("Устройство не открыто.")
            End If
            Return devH
        End Function

        ''' <summary>
        ''' Открывает канал, если это не было сделано при создании экземпляра.
        ''' </summary>
        Public Sub OpenChannel()
            If (Not IsOpened) Then
                SetDevHandle(OpenChannel(DeviceIndex))
            End If
        End Sub

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Function SPI_CloseChannel(ByVal handle As Integer) As Integer
        End Function

        ''' <summary>
        ''' Закрывает устройство (если оно открыто).
        ''' </summary>
        Public Sub CloseChannel()
            If IsOpened Then
                Dim r As Integer = SPI_CloseChannel(DeviceHandle)
                CheckStatus(r)
                SetDevHandle(CLOSED_HANDLE)
            End If
        End Sub

#End Region '/OPEN, CLOSE CHANNEL

#Region "INFO"

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Function SPI_GetNumChannels(ByRef numChannels As UInt32) As Integer
        End Function

        ''' <summary>
        ''' Возвращает количество устройств (каналов), подключённых к системе.
        ''' </summary>
        Public Shared Function GetNumChannels() As Integer
            Dim n As UInt32 = 0
            Dim s As Integer = SPI_GetNumChannels(n)
            CheckStatus(s)
            Return CInt(n)
        End Function

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Function SPI_GetChannelInfo(ByVal index As Integer, ByRef chanInfo As FT_DEVICE_LIST_INFO_NODE) As Integer
        End Function

        ''' <summary>
        ''' Возвращает информацию об устройстве (канале) по его индексу в системе.
        ''' </summary>
        ''' <param name="index">Индекс канала, начиная с 0 до <see cref="GetNumChannels()"/> - 1.</param>
        Public Shared Function GetChannelInfo(ByVal index As Integer) As FT_DEVICE_LIST_INFO_NODE
            Dim info As New FT_DEVICE_LIST_INFO_NODE()
            Dim r As Integer = SPI_GetChannelInfo(index, info)
            CheckStatus(r)
            Return info
        End Function

        ''' <summary>
        ''' Возвращает информацию о текущем устройстве (канале).
        ''' </summary>
        Public Function GetChannelInfo() As FT_DEVICE_LIST_INFO_NODE
            Return GetChannelInfo(DeviceHandle)
        End Function

#End Region '/INFO

#Region "INIT"

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Function SPI_InitChannel(ByVal handle As Integer, ByRef config As ChannelConfig) As Integer
        End Function

        ''' <summary>
        ''' Инициализирует канал заданными параметрами
        ''' </summary>
        ''' <param name="devHandle">Дескриптор устройства.</param>
        ''' <param name="config">Конфигурация канала.</param>
        Public Shared Function InitChannel(ByVal devHandle As Integer, ByVal config As ChannelConfig) As ChannelConfig
            Dim r As Integer = SPI_InitChannel(devHandle, config)
            CheckStatus(r)
            Return config
        End Function

        ''' <summary>
        ''' Инициализирует канал заданными параметрами.
        ''' </summary>
        ''' <param name="config">Конфигурация канала.</param>
        Public Function InitChannel(ByVal config As ChannelConfig) As ChannelConfig
            Return InitChannel(DeviceHandle, config)
        End Function

#End Region '/INIT

#Region "READ, WRITE"

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Function SPI_Read(ByVal handle As Integer, ByVal buffer() As Byte, ByVal sizeToTransfer As Integer, ByRef sizeTransfered As Integer, ByVal options As Integer) As Integer
        End Function

        ''' <summary>
        ''' Запрашивает заданное число байтов (или битов - в зависимости от параметра options) из ведомого SPI.
        ''' </summary>
        ''' <param name="devHandle">Дескриптор устройства (канала).</param>
        ''' <param name="sizeToRead">Число байтов или битов для чтения.</param>
        ''' <param name="options">Параметры передачи.</param>
        Public Shared Function SpiRead(ByVal devHandle As Integer, ByVal sizeToRead As Integer,
                                       Optional ByVal options As SPI_TRANSFER_OPTIONS = SPI_TRANSFER_OPTIONS.SIZE_IN_BYTES Or SPI_TRANSFER_OPTIONS.CHIPSELECT_ENABLE Or SPI_TRANSFER_OPTIONS.CHIPSELECT_DISABLE) As Byte()
            Dim buffer() As Byte = New Byte(sizeToRead - 1) {}
            Dim received As Integer = 0
            Dim r As Integer = SPI_Read(devHandle, buffer, sizeToRead, received, options)
            CheckStatus(r)
            ReDim Preserve buffer(received - 1)
            Return buffer
        End Function

        ''' <summary>
        ''' Запрашивает заданное число байтов (или битов - в зависимости от параметра <paramref name="options"/>) из ведомого устройства.
        ''' </summary>
        ''' <param name="sizeToRead">Число байтов или битов для чтения.</param>
        ''' <param name="options">Параметры передачи.</param>
        Public Function SpiRead(ByVal sizeToRead As Integer, Optional ByVal options As SPI_TRANSFER_OPTIONS = SPI_TRANSFER_OPTIONS.SIZE_IN_BYTES Or SPI_TRANSFER_OPTIONS.CHIPSELECT_ENABLE Or SPI_TRANSFER_OPTIONS.CHIPSELECT_DISABLE) As Byte()
            Return SpiRead(DeviceHandle, sizeToRead, options)
        End Function

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Function SPI_Write(ByVal handle As Integer, ByVal buffer As Byte(), ByVal sizeToTransfer As Integer, ByRef sizeTransfered As Integer,
                                          Optional ByVal options As SPI_TRANSFER_OPTIONS = SPI_TRANSFER_OPTIONS.SIZE_IN_BYTES Or SPI_TRANSFER_OPTIONS.CHIPSELECT_ENABLE Or SPI_TRANSFER_OPTIONS.CHIPSELECT_DISABLE) As Integer
        End Function

        ''' <summary>
        ''' Передаёт в ведомое устройство заданный массив байтов.
        ''' </summary>
        ''' <param name="devHandle">Дескриптор устройства (канала).</param>
        ''' <param name="buffer">Массив байтов для записи.</param>
        ''' <param name="options">Параметры передачи.</param>
        Public Shared Sub SpiWrite(ByVal devHandle As Integer, ByVal buffer As Byte(), Optional ByVal options As SPI_TRANSFER_OPTIONS = SPI_TRANSFER_OPTIONS.SIZE_IN_BYTES Or SPI_TRANSFER_OPTIONS.CHIPSELECT_ENABLE Or SPI_TRANSFER_OPTIONS.CHIPSELECT_DISABLE)
            Dim buf As Byte() = buffer
            Dim transferred As Integer = 0
            Dim r As Integer = SPI_Write(devHandle, buf, buf.Length, transferred, options)
            CheckStatus(r)
        End Sub

        ''' <summary>
        ''' Передаёт в ведомое устройство заданный массив байтов.
        ''' </summary>
        ''' <param name="buffer">Массив байтов для записи.</param>
        ''' <param name="options">Параметры передачи.</param>
        Public Sub SpiWrite(ByVal buffer As Byte(), Optional ByVal options As SPI_TRANSFER_OPTIONS = SPI_TRANSFER_OPTIONS.SIZE_IN_BYTES Or SPI_TRANSFER_OPTIONS.CHIPSELECT_ENABLE Or SPI_TRANSFER_OPTIONS.CHIPSELECT_DISABLE)
            SpiWrite(DeviceHandle, buffer, options)
        End Sub

#End Region '/READ, WRITE

#Region "СТРУКТУРЫ, ПЕРЕЧИСЛЕНИЯ"

        Public Const DLL_MPSSE_PATH As String = "C:\Temp\libMPSSE.dll"

        ''' <summary>
        ''' Информация об устройстве.
        ''' </summary>
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
        Public Structure FT_DEVICE_LIST_INFO_NODE
            <CLSCompliant(False)>
            Public Flags As FlagsEnum
            <CLSCompliant(False)>
            Public Type As DeviceEnum
            <CLSCompliant(False)>
            Public ID As UInteger
            <CLSCompliant(False)>
            Public LocId As UInteger
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=16)> Public SerialNumber As String
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=64)> Public Description As String
            <CLSCompliant(False)>
            Public ftHandle As UInteger
        End Structure

        ''' <summary>
        ''' Флаги состояния устройства.
        ''' </summary>
        <CLSCompliant(False)>
        <Flags()>
        Public Enum FlagsEnum As UInteger
            DEVICE_OPENED = 1
            DEVICE_HISPEED = 2
        End Enum

        ''' <summary>
        ''' Типы устройств FTDI.
        ''' </summary>
        <CLSCompliant(False)>
        Public Enum DeviceEnum As UInteger
            FT_DEVICE_BM
            FT_DEVICE_AM
            FT100AX
            DEVICE_UNKNOWN
            FT2232C
            FT232R
            FT2232H
            FT4232H
            FT232H
            FTX_SERIES
            FT4222H_0
            FT4222H_1_2
            FT4222H_3
            FT4222_PROG
            FT900
        End Enum

        ''' <summary>
        ''' Конфигурация устройства (канала).
        ''' </summary>
        <StructLayout(LayoutKind.Sequential)>
        Public Structure ChannelConfig

            ''' <summary>
            ''' Значение тактовой частоты шины SPI, в Гц. 
            ''' Значения в диапазоне от 0 до 30 МГц.
            ''' </summary>
            Public ClockRate As UInteger

            ''' <summary>
            ''' Значение таймера задержек, в мс. 
            ''' </summary>
            ''' <remarks>
             ''' Рекомендуются диапазоны:
            ''' - для Full-speed устройств (FT2232D): 2...255;
            ''' - для Hi-speed устройств (FT232H, FT2232H, FT4232H): 1...255.
            ''' </remarks>
            Public LatencyTimer As Byte

            ''' <summary>
            ''' Биты:
            ''' <list type="">
            ''' <listheader>Номер бита # Описание </listheader>
            ''' <item>BIT1-BIT0 # Определяют режим SPI:
            ''' - 00 - SPI MODE0 - CLK активен в HIGH, данные защёлкиваются по переднему фронту;
            ''' - 01 - SPI MODE1 - CLK активен в HIGH, данные защёлкиваются по заднему фронту;
            ''' - 10 - SPI MODE2 - CLK активен в LOW, данные защёлкиваются по переднему фронту;
            ''' - 11 - SPI MODE3 - CLK активен в LOW, данные защёлкиваются по заднему фронту.
            ''' </item>
            ''' <item>BIT4-BIT2 # Определяет, какие из линий будут использоваться в качестве CS:
            ''' - 000 - xDBUS3 
            ''' - 001 - xDBUS4 
            ''' - 010 - xDBUS5 
            ''' - 011 - xDBUS6 
            ''' - 100 - xDBUS7 
            ''' </item>
            ''' <item>BIT5 # Определяет, каким уровнем активизируется линия CS: 
            ''' - 0 - выбор чипа высоким уровнем;
            ''' - 1 - выбор чипа низким уровнем.
            ''' </item>
            ''' <item>BIT6-BIT31 # Резерв </item>
            ''' </list>
            ''' </summary>
            ''' <remarks>
            ''' Примечание: обозначения xDBUS0...xDBUS7 соответствуют линиям ADBUS0...ADBUS7, 
            ''' если используется первый канал устройства MPSSE, 
            ''' линиям BDBUS0..BDBUS7 - если используется второй канал MPSSE (если он есть).
            ''' </remarks>
            Public ConfigOptions As UInteger  

            ''' <summary>
            ''' Определяет направления и значения выводов:
            ''' <list type="">
            ''' <listheader>Номер бита # Описание # Примечание</listheader>
            ''' <item>- BIT7-BIT0   # Направление линий после вызова SPI_InitChannel  # 1 = OUTPUT, 0 = INPUT</item>
            ''' <item>- BIT15-BIT8  # Состояния линий после вызова SPI_InitChannel  # 1 = HIGH, 0 = LOW</item>
            ''' <item>- BIT23-BIT16 # Направление линий после вызова SPI_CloseChannel # 1 = OUTPUT, 0 = INPUT</item>
            ''' <item>- BIT31-BIT24 # Состояния линий после вызова SPI_CloseChannel # 1 = HIGH, 0 = LOW</item>
            ''' </list>
            ''' </summary>
            ''' <remarks>
            ''' Note that the directions of the SCLK, MOSI and the specified chip select line will be overwritten to 1 
            ''' and the direction of the MISO like will be overwritten to 0 irrespective of the values passed by the user application.
            ''' </remarks>
            Public Pin As UInteger

            ''' <summary>
            ''' Этот параметр зарезервирован и не должен использоваться. 
            ''' </summary>
            Public Reserved As UShort

        End Structure

        ''' <summary>
        ''' Конфигурация SPI - режимы и CS.
        ''' </summary>
        <Flags()>
        Public Enum SPI_CONFIG_OPTION As UInteger

            ''' <summary>
            ''' BIT0-BIT1: MODE.
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_MODE0</remarks>
            MODE0 = &H0

            ''' <summary>
            ''' BIT0-BIT1: MODE.
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_MODE1</remarks>
            MODE1 = &H1

            ''' <summary>
            ''' BIT0-BIT1: MODE.
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_MODE2</remarks>
            MODE2 = &H2

            ''' <summary>
            ''' BIT0-BIT1: MODE.
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_MODE3</remarks>
            MODE3 = &H3

            ''' <summary>
            ''' BIT2-BIT4: CS.
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_CS_DBUS3</remarks>
            CS_DBUS3 = &H0

            ''' <summary>
            ''' BIT2-BIT4: CS.
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_CS_DBUS4</remarks>
            CS_DBUS4 = &H4

            ''' <summary>
            ''' BIT2-BIT4: CS.
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_CS_DBUS5</remarks>
            CS_DBUS5 = &H8

            ''' <summary>
            ''' BIT2-BIT4: CS.
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_CS_DBUS6</remarks>
            CS_DBUS6 = &HC

            ''' <summary>
            ''' BIT2-BIT4: CS.
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_CS_DBUS7</remarks>
            CS_DBUS7 = &H10

            ''' <summary>
            ''' BIT5: Линия CS активна в состоянии Active Low?
            ''' </summary>
            ''' <remarks>SPI_CONFIG_OPTION_CS_ACTIVELOW</remarks>
            CS_ACTIVELOW = &H20

        End Enum

        ''' <summary>
        ''' Настройки передачи.
        ''' </summary>
        <Flags()>
        Public Enum SPI_TRANSFER_OPTIONS As Integer

            ''' <summary>
            ''' BIT 0: Счёт передаваемых данных - в байтах.
            ''' </summary>
            ''' <remarks>SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES</remarks>
            SIZE_IN_BYTES = 0

            ''' <summary>
            ''' BIT 0: Счёт передаваемых данных - в битах.
            ''' </summary>
            ''' <remarks>SPI_TRANSFER_OPTIONS_SIZE_IN_BITS</remarks>
            SIZE_IN_BITS = 1

            ''' <summary>
            ''' BIT 1: Если задано, линия CS выставляется до начала передачи.
            ''' </summary>
            ''' <remarks>SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE</remarks>
            CHIPSELECT_ENABLE = 2

            ''' <summary>
            ''' BIT 2: Если задано, линия CS убирается после окончания передачи.
            ''' </summary>
            ''' <remarks>SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE</remarks>
            CHIPSELECT_DISABLE = 4

        End Enum

#End Region '/СТРУКТУРЫ, ПЕРЕЧИСЛЕНИЯ

#Region "СТАТУС УСТРОЙСТВА FTDI"

        ''' <summary>
        ''' Вызывает исключение по статусу устройства.
        ''' </summary>
        ''' <param name="status">Код статуса.</param>
        Private Shared Sub CheckStatus(ByVal status As Integer)
            Select Case status
                Case StatusCode.FT_OK
                    Exit Sub
                Case StatusCode.FT_INVALID_HANDLE
                    Throw New NullReferenceException("Ошибка: Неверный дескриптор устройства.")
                Case StatusCode.FT_DEVICE_NOT_FOUND
                    Throw New Exception("Ошибка: Устройство не найдено.")
                Case StatusCode.FT_DEVICE_NOT_OPENED
                    Throw New Exception("Ошибка: Устройство невозможно открыть.")
                Case StatusCode.FT_IO_ERROR
                    Throw New Exception("Ошибка: Ошибка чтения/записи.")
                Case StatusCode.FT_INSUFFICIENT_RESOURCES
                    Throw New Exception("Ошибка: Несуществующий ресурс.")
                Case StatusCode.FT_INVALID_PARAMETER
                    Throw New Exception("Ошибка: Неверный параметр.")
                Case StatusCode.FT_INVALID_BAUD_RATE
                    Throw New Exception("Ошибка: Неверный битрейт.")
                Case StatusCode.FT_DEVICE_NOT_OPENED_FOR_ERASE
                    Throw New Exception("Ошибка: Устройство не открыто для очистки.")
                Case StatusCode.FT_DEVICE_NOT_OPENED_FOR_WRITE
                    Throw New Exception("Ошибка: Устройство не открыто для записи.")
                Case StatusCode.FT_FAILED_TO_WRITE_DEVICE
                    Throw New Exception("Ошибка: Ошибка записи на устройство.")
                Case StatusCode.FT_EEPROM_READ_FAILED
                    Throw New Exception("Ошибка: Ошибка чтения EEPROM.")
                Case StatusCode.FT_EEPROM_WRITE_FAILED
                    Throw New Exception("Ошибка: Ошибка записи в EEPROM.")
                Case StatusCode.FT_EEPROM_ERASE_FAILED
                    Throw New Exception("Ошибка стирания EEPROM.")
                Case StatusCode.FT_EEPROM_NOT_PRESENT
                    Throw New Exception("Ошибка: EEPROM не представлено.")
                Case StatusCode.FT_EEPROM_NOT_PROGRAMMED
                    Throw New Exception("Ошибка: EEPROM не запрограммировано.")
                Case StatusCode.FT_INVALID_ARGS
                    Throw New Exception("Ошибка: Неверные аргументы.")
                Case StatusCode.FT_NOT_SUPPORTED
                    Throw New Exception("Ошибка: Не поддерживается.")
                Case StatusCode.FT_OTHER_ERROR
                    Throw New Exception("Ошибка: Иная ошибка.")
                Case StatusCode.FT_DEVICE_LIST_NOT_READY
                    Throw New Exception("Ошибка: Список устройств не готов.")

                Case StatusCode.FTC_FAILED_TO_COMPLETE_COMMAND
                    Throw New Exception("Ошибка: Невозможно завершить задачу.")
                Case StatusCode.FTC_FAILED_TO_SYNCHRONIZE_DEVICE_MPSSE
                    Throw New Exception("Ошибка: Невозможно синхронизировать устройство MPSSE.")
                Case StatusCode.FTC_INVALID_DEVICE_NAME_INDEX
                    Throw New Exception("Ошибка: Неверные имя или индекс устройства.")
                Case StatusCode.FTC_NULL_DEVICE_NAME_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель на имя устройства.")
                Case StatusCode.FTC_DEVICE_NAME_BUFFER_TOO_SMALL
                    Throw New Exception("Ошибка: Слишком маленький буфер для имени устройства.")
                Case StatusCode.FTC_INVALID_DEVICE_NAME
                    Throw New Exception("Ошибка: Неверное имя устройства.")
                Case StatusCode.FTC_INVALID_LOCATION_ID
                    Throw New Exception("Ошибка: Неверный Location ID.")
                Case StatusCode.FTC_DEVICE_IN_USE
                    Throw New Exception("Ошибка: Устройство занято.")
                Case StatusCode.FTC_TOO_MANY_DEVICES
                    Throw New Exception("Ошибка: Слишком много устройств.")
                Case StatusCode.FTC_NULL_CHANNEL_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель номера канала.")
                Case StatusCode.FTC_CHANNEL_BUFFER_TOO_SMALL
                    Throw New Exception("Ошибка: Слишком маленький буфер для канала устройства.")
                Case StatusCode.FTC_INVALID_CHANNEL
                    Throw New Exception("Ошибка: Неверный канал.")
                Case StatusCode.FTC_INVALID_TIMER_VALUE
                    Throw New Exception("Ошибка: Неверное значение таймера.")
                Case StatusCode.FTC_INVALID_CLOCK_DIVISOR
                    Throw New Exception("Ошибка: Неверное значение делителя частоты.")
                Case StatusCode.FTC_NULL_INPUT_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель входного буфера.")
                Case StatusCode.FTC_NULL_CHIP_SELECT_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера CS.")
                Case StatusCode.FTC_NULL_INPUT_OUTPUT_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера I/O.")
                Case StatusCode.FTC_NULL_OUTPUT_PINS_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель выходного буфера.")
                Case StatusCode.FTC_NULL_INITIAL_CONDITION_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера начального состояния.")
                Case StatusCode.FTC_NULL_WRITE_CONTROL_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера контроля при записи.")
                Case StatusCode.FTC_NULL_WRITE_DATA_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера данных при записи.")
                Case StatusCode.FTC_NULL_WAIT_DATA_WRITE_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера данных при записи.")
                Case StatusCode.FTC_NULL_READ_DATA_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера данных при чтении.")
                Case StatusCode.FTC_NULL_READ_CMDS_DATA_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера команд при чтении.")
                Case StatusCode.FTC_INVALID_NUMBER_CONTROL_BITS
                    Throw New Exception("Ошибка: Неверное число битов управления.")
                Case StatusCode.FTC_INVALID_NUMBER_CONTROL_BYTES
                    Throw New Exception("Ошибка: Неверное число байтов управления.")
                Case StatusCode.FTC_NUMBER_CONTROL_BYTES_TOO_SMALL
                    Throw New Exception("Ошибка: Слишком маленькое число байтов управления.")
                Case StatusCode.FTC_INVALID_NUMBER_WRITE_DATA_BITS
                    Throw New Exception("Ошибка: Неверное число битов данных при записи.")
                Case StatusCode.FTC_INVALID_NUMBER_WRITE_DATA_BYTES
                    Throw New Exception("Ошибка: Неверное число байтов данных при записи.")
                Case StatusCode.FTC_NUMBER_WRITE_DATA_BYTES_TOO_SMALL
                    Throw New Exception("Ошибка: Слишком маленькое число байтов данных при записи.")
                Case StatusCode.FTC_INVALID_NUMBER_READ_DATA_BITS
                    Throw New Exception("Ошибка: Неверное число битов данных при чтении.")
                Case StatusCode.FTC_INVALID_INIT_CLOCK_PIN_STATE
                    Throw New Exception("Ошибка: Неверное начальное состояние вывода тактовой частоты.")
                Case StatusCode.FTC_INVALID_FT2232C_CHIP_SELECT_PIN
                    Throw New Exception("Ошибка: Неверный вывод CS FT2232C.")
                Case StatusCode.FTC_INVALID_FT2232C_DATA_WRITE_COMPLETE_PIN
                    Throw New Exception("Ошибка: Неверный вывод завершения записи FT2232C.")
                Case StatusCode.FTC_DATA_WRITE_COMPLETE_TIMEOUT
                    Throw New Exception("Ошибка: Превышено время завершения записи.")
                Case StatusCode.FTC_INVALID_CONFIGURATION_HIGHER_GPIO_PIN
                    Throw New Exception("Ошибка: Неверный вывод конфигурации верхних GPIO.")
                Case StatusCode.FTC_COMMAND_SEQUENCE_BUFFER_FULL
                    Throw New Exception("Ошибка: Очередь команд полна.")
                Case StatusCode.FTC_NO_COMMAND_SEQUENCE
                    Throw New Exception("Ошибка: Нет очереди команд.")
                Case StatusCode.FTC_NULL_CLOSE_FINAL_STATE_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера закрытия финального состояния.")
                Case StatusCode.FTC_NULL_DLL_VERSION_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера версии DLL.")
                Case StatusCode.FTC_DLL_VERSION_BUFFER_TOO_SMALL
                    Throw New Exception("Ошибка: Слишком маленький буфер версии DLL.")
                Case StatusCode.FTC_NULL_LANGUAGE_CODE_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера кода языка.")
                Case StatusCode.FTC_NULL_ERROR_MESSAGE_BUFFER_POINTER
                    Throw New Exception("Ошибка: Несуществующий указатель буфера ошибки сообщения.")
                Case StatusCode.FTC_ERROR_MESSAGE_BUFFER_TOO_SMALL
                    Throw New Exception("Ошибка: Слишком маленький буфер ошибки сообщения.")
                Case StatusCode.FTC_INVALID_LANGUAGE_CODE
                    Throw New Exception("Ошибка: Неверный код языка.")
                Case StatusCode.FTC_INVALID_STATUS_CODE
                    Throw New Exception("Ошибка: Неверный код статуса.")
                Case Else
                    Throw New Exception("Ошибка: Неизвестная ошибка.")
            End Select
        End Sub

        ''' <summary>
        ''' Коды статуса устройства.
        ''' </summary>
        Public Enum StatusCode As Integer
            'Из библиотеки D2XX:
            FT_OK = 0
            FT_INVALID_HANDLE = 1
            FT_DEVICE_NOT_FOUND = 2
            FT_DEVICE_NOT_OPENED = 3
            FT_IO_ERROR = 4
            FT_INSUFFICIENT_RESOURCES = 5
            FT_INVALID_PARAMETER = 6
            FT_INVALID_BAUD_RATE = 7
            FT_DEVICE_NOT_OPENED_FOR_ERASE = 8
            FT_DEVICE_NOT_OPENED_FOR_WRITE = 9
            FT_FAILED_TO_WRITE_DEVICE = 10
            FT_EEPROM_READ_FAILED = 11
            FT_EEPROM_WRITE_FAILED = 12
            FT_EEPROM_ERASE_FAILED = 13
            FT_EEPROM_NOT_PRESENT = 14
            FT_EEPROM_NOT_PROGRAMMED = 15
            FT_INVALID_ARGS = 16
            FT_NOT_SUPPORTED = 17
            FT_OTHER_ERROR = 18
            FT_DEVICE_LIST_NOT_READY = 19
            FT_WRONG_READ_BUFFER_SIZE = 50
            'Из библиотеки FtcSPI:
            FTC_FAILED_TO_COMPLETE_COMMAND = 20
            FTC_FAILED_TO_SYNCHRONIZE_DEVICE_MPSSE = 21
            FTC_INVALID_DEVICE_NAME_INDEX = 22
            FTC_NULL_DEVICE_NAME_BUFFER_POINTER = 23
            FTC_DEVICE_NAME_BUFFER_TOO_SMALL = 24
            FTC_INVALID_DEVICE_NAME = 25
            FTC_INVALID_LOCATION_ID = 26
            FTC_DEVICE_IN_USE = 27
            FTC_TOO_MANY_DEVICES = 28
            FTC_NULL_CHANNEL_BUFFER_POINTER = 29
            FTC_CHANNEL_BUFFER_TOO_SMALL = 30
            FTC_INVALID_CHANNEL = 31
            FTC_INVALID_TIMER_VALUE = 32
            FTC_INVALID_CLOCK_DIVISOR = 33
            FTC_NULL_INPUT_BUFFER_POINTER = 34
            FTC_NULL_CHIP_SELECT_BUFFER_POINTER = 35
            FTC_NULL_INPUT_OUTPUT_BUFFER_POINTER = 36
            FTC_NULL_OUTPUT_PINS_BUFFER_POINTER = 37
            FTC_NULL_INITIAL_CONDITION_BUFFER_POINTER = 38
            FTC_NULL_WRITE_CONTROL_BUFFER_POINTER = 39
            FTC_NULL_WRITE_DATA_BUFFER_POINTER = 40
            FTC_NULL_WAIT_DATA_WRITE_BUFFER_POINTER = 41
            FTC_NULL_READ_DATA_BUFFER_POINTER = 42
            FTC_NULL_READ_CMDS_DATA_BUFFER_POINTER = 43
            FTC_INVALID_NUMBER_CONTROL_BITS = 44
            FTC_INVALID_NUMBER_CONTROL_BYTES = 45
            FTC_NUMBER_CONTROL_BYTES_TOO_SMALL = 46
            FTC_INVALID_NUMBER_WRITE_DATA_BITS = 47
            FTC_INVALID_NUMBER_WRITE_DATA_BYTES = 48
            FTC_NUMBER_WRITE_DATA_BYTES_TOO_SMALL = 49
            FTC_INVALID_NUMBER_READ_DATA_BITS = 50
            FTC_INVALID_INIT_CLOCK_PIN_STATE = 51
            FTC_INVALID_FT2232C_CHIP_SELECT_PIN = 52
            FTC_INVALID_FT2232C_DATA_WRITE_COMPLETE_PIN = 53
            FTC_DATA_WRITE_COMPLETE_TIMEOUT = 54
            FTC_INVALID_CONFIGURATION_HIGHER_GPIO_PIN = 55
            FTC_COMMAND_SEQUENCE_BUFFER_FULL = 56
            FTC_NO_COMMAND_SEQUENCE = 57
            FTC_NULL_CLOSE_FINAL_STATE_BUFFER_POINTER = 58
            FTC_NULL_DLL_VERSION_BUFFER_POINTER = 59
            FTC_DLL_VERSION_BUFFER_TOO_SMALL = 60
            FTC_NULL_LANGUAGE_CODE_BUFFER_POINTER = 61
            FTC_NULL_ERROR_MESSAGE_BUFFER_POINTER = 62
            FTC_ERROR_MESSAGE_BUFFER_TOO_SMALL = 63
            FTC_INVALID_LANGUAGE_CODE = 64
            FTC_INVALID_STATUS_CODE = 65
        End Enum

#End Region '/СТАТУС УСТРОЙСТВА FTDI

    End Class 

End Namespace

Алгоритм использования этого класса следующий:

  • определить количество подключённых поддерживаемых устройств FTDI, вызвав метод GetNumChannels();
    • при этом выполняется автоматическая инициализация библиотеки;
  • создать экземпляр класса Mpsse, вызвав конструктор и передав в него индекс устройства;
    • по умолчанию при вызове конструктора устройство открывается и сразу готово к приёму или передаче данных;
  • инициализировать устройство с помощью метода InitChannel() – сообщить ему настройки режима SPI, частоты, начальные состояния выводов;
  • осуществлять передачу и приём данных с помощью методов SpiWrite() и SpiRead();
  • при завершении работы с библиотекой необходимо вызвать статический метод CleanupLibrary(), чтобы освободить неуправляемые ресурсы.

Разумеется, этот код можно (и нужно) улучшать.

3 Работа с датчиком давления и температуры BMP280 по интерфейсу SPI

Микросхема FT2232 работает только в режиме ведущего (Master). Для того чтобы проверить наш код в действии, нам нужно ведомое устройство (Slave). Хорошим кандидатом на роль ведомого будет датчик давления и температуры BMP280, который может работать по интерфейсу I2C или SPI (3- или 4-проводной).

Модуль с датчиком температуры и давления BMP280
Модуль с датчиком температуры и давления BMP280

Если мы посмотрим на карту регистров данного датчика, то увидим один регистр, который содержит постоянное значение – это регистр по адресу 0xD0 с именем id. В нём хранится идентификатор датчика, равный 0x58. Именно это число мы должны прочитать, если обратимся к регистру id. Давайте проверим это.

Карта памяти датчика BMP280
Карта памяти датчика BMP280

Изображения взяты из технического описания (datasheet) на датчик BMP280. В конце статьи даются ссылки на скачивание datasheet BMP280.

Протокол обмена датчика BMP280 по SPI следующий:

Протокол SPI датчика давления BMP280
Протокол SPI датчика давления BMP280

Линия CS активна при низком уровне. Первый передаваемый бит (RW) определяет чтение или запись. В нашем случае он должен быть равен "1" (чтение). Далее следуют 7 бит адреса регистра (AD6...AD0), с которого начинаем чтение. Далее генерируется столько тактовых импульсов, сколько битов мы хотим прочитать из датчика. На следующем рисунке последовательность чтения по SPI представлена более детально на примере чтения двух байтов, начиная с регистра по адресу 0x76 (старший бит означает чтение, поэтому передаётся 0xF6):

Чтение последовательности байтов из датчика BMP280
Чтение последовательности байтов из датчика BMP280

Датчик сам следит за указателем на текущий регистр, поэтому если мы начали читать с адреса 0x76, следующий запрошенный байт будет прочитан из регистра 0x77, и т.д.

Подключим датчик BMP280 к первому каналу микросхемы FT2232H вот по такой схеме:

Схема соединения BMP280 с FT2232H
Вывод BMP280Вывод FT2232HПримечание
SCLADBUS0если используется первый канал
CSBADBUS3если используется первый канал
SDAADBUS1если используется первый канал
SDOADBUS2если используется первый канал
VCCнеттребуется отдельное питание +3.3 В
GNDGNDсоединить с GND источника +3.3 В

Если используется канал, отличный от первого, то соответствие выводов смотрите в техническом описании микросхемы. Например, у FT232 один канал, у FT2232 два канала, а у FT4232 – четыре.

Вывода с 3,3 вольтами у микросхемы FT2232 нет, поэтому для обеспечения питания датчику BMP280 придётся где-то его взять. Можно, к примеру, использовать плату Arduino, у которой имеется необходимое напряжение. Только не забудьте объединить GND датчика, Arduino и FT2232. Я, например, так и сделал:

Подключение датчика BMP280 к FT2232, питание подаётся от 3,3V Arduino Nano
Подключение датчика BMP280 к FT2232, питание подаётся от 3,3V Arduino Nano

Итак, давайте же прочитаем наш регистр. Для этого опускаем линию CS в LOW и записываем в BMP280 число 0xD0 – адрес регистра id (вызываем метод с параметрами SpiWrite(&HD, SPI_TRANSFER_OPTIONS.CHIPSELECT_ENABLE). Линию CS пока не поднимаем. Сразу же читаем 1 байт из датчика. Поднимаем линию CS (вызываем метод с параметрами SpiRead(1, SPI_TRANSFER_OPTIONS.CHIPSELECT_DISABLE). Вот как выглядит временная диаграмма обмена, полученная с помощью логического анализатора:

Временная диаграмма чтения регистра идентификатора датчика BMP280
Временная диаграмма чтения регистра идентификатора датчика BMP280

Такое же значение, как на диаграмме, возвращает метод SpiRead() – 0x58. Поздравляю, мы только что прочитали регистр идентификатора сенсора BMP280. А это значит, что наш код работает корректно.

Для желающих потренироваться в дальнейшем освоении интерфейса SPI – датчик BMP280 хороший «подопытный» :)

Выводы

Мы научились передавать и принимать данные по интерфейсу SPI с помощью семейства микросхем фирмы FTDI, используя библиотеку MPSSE.

Мы слегка ознакомились с датчиком давления и температуры BMP280 и прочитали его регистр id.

Скачать техническое описание BMP280 и библиотеку libMPSSE

В архиве по ссылке ниже – datasheet для датчика, последняя версия библиотеки libMPSSE для x86, x64 Windows и Linux.

Download attachments:

Last modified onВторник, 09 Июнь 2020 18:35 Read 6052 times

Поблагодарить автора:

Поделиться

Print Friendly, PDF & Email

Leave a comment