Рейтинг@Mail.ru

Реализация интерфейса I2C с помощью библиотеки libMPSSE на VB.NET

Print Friendly, PDF & Email
Продолжим рассмотрение библиотеки libMPSSE.dll фирмы FTDI Chip. На этот раз воспользуемся ей для общения с устройствами по интерфейсу I2C.

Об интерфейсе I2C (иногда называемом IIC) мы уже писали. Это простой двухпроводной интерфейс, которым можно объединить до 127 устройств. Причём все устройства будут «сидеть» на этих двух проводах, что составляет одно из главных преимуществ данного интерфейса. По одному проводу передаются тактовые импульсы, а по второму – данные. В сети должно быть хотя бы одно ведущее устройство, которое и инициирует обмен данными.

1Описание функционала работы с I2C библиотеки libMPSSE.dll

Устройство FTDI (устройство производства фирмы Future Technology Devices International – FTDI) в режиме I2C может быть только ведущим. Для работы с последовательным интерфейсом IIC в библиотеке libMPSSE.dll имеются методы, перечисленные в следующей таблице:

Название функцииОписание
I2C_GetNumChannels()Возвращает число I2C каналов, подключённых к компьютеру
I2C_GetChannelInfo()Возвращает информацию о заданном канале
I2C_OpenChannel()Открывает канал в режиме I2C
I2C_InitChannel()Инициализирует канал заданными настройками
I2C_CloseChannel()Закрывает канал
I2C_DeviceRead()Запрашивает у ведомого устройства заданное число байтов
I2C_DeviceWrite()Отправляет в ведомое устройство заданное число байтов

Как видим, ничего необычного. Минимальный «боевой» набор. Также библиотека содержит несколько методов для управления портами ввода-вывода общего назначения (GPIO). Эту возможность мы здесь не рассматриваем.

2Класс-обёртка для библиотеки libMPSSE.dll и интерфейса I2C

Библиотека может реализовывать режимы I2C, SPI и JTAG для устройств MPSSE (Multi Protocol Synchronous Serial Engine – механизм, позволяющий устройствам FTDI работать с различными последовательными интерфейсами). Но есть общие для всех режимов методы, которые я предлагаю реализовать в абстрактном классе:

Код абстрактного класса для работы с устройствами MPSSE (разворачивается)
Imports System.Runtime.InteropServices

Namespace Ftdi

    ''' <summary>
    ''' Базовый абстрактный класс для работы с устройствами FTDI с помощью библиотеки libMPSSE.dll.
    ''' </summary>
    Public MustInherit Class MpsseBase

#Region "Read-only свойства"

        Protected Const CLOSED_HANDLE As Integer = -1
        Public Const DLL_MPSSE_PATH As String = "libMPSSE.dll"

        ''' <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>
        Protected Sub SetDevHandle(index As Integer)
            _DeviceHandle = index
        End Sub

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

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

        ''' <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>
        ''' <param name="name">Описание устройства, считанное из ПЗУ.</param>
        Protected Sub SetNameReadable(name As String)
            _NameReadable = name
        End Sub

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

        ''' <summary>
        ''' Задаёт серийный номер устройства.
        ''' </summary>
        ''' <param name="sn"></param>
        Protected Sub SetSerialNumber(sn As String)
            _SerialNumber = sn
        End Sub

        ''' <summary>
        ''' Тип устройства.
        ''' </summary>
        Public ReadOnly Property DeviceType As DeviceEnum
            Get
                Return _DeviceType
            End Get
        End Property
        Private _DeviceType As DeviceEnum = DeviceEnum.DEVICE_UNKNOWN

        ''' <summary>
        ''' Задаёт тип устройства.
        ''' </summary>
        ''' <param name="devType">Тип устройства.</param>
        Protected Sub SetDeviceType(devType As DeviceEnum)
            _DeviceType = devType
        End Sub

        ''' <summary>
        ''' Режим работы устройства MPSSE.
        ''' </summary>
        Public MustOverride ReadOnly Property DeviceMode As DeviceWorkingMode

#End Region '/Read-only свойства

#Region "Конструкторы"

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

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

        ''' <summary>
        ''' Конструктор экземпляра типа.
        ''' </summary>
        ''' <param name="index">Номер устройства в системе, начиная с 0.</param>
        Public Sub New(ByVal index As Integer)
            SetDeviceIndex(index)
        End Sub

#End Region '/Конструкторы

#Region "Выгрузка DLL"

        <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 '/Выгрузка DLL

#Region "Инфо"

        Public MustOverride Function GetChannelInfo() As FT_DEVICE_LIST_INFO_NODE

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

        ''' <summary>
        ''' Возвращает количество подключённых SPI устройств.
        ''' </summary>
        Public Shared Function GetNumSpiChannels() 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 I2C_GetNumChannels(ByRef numChannels As UInt32) As Integer
        End Function

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

#End Region '/Инфо

#Region "Переопределяемые свойства"

        Public MustOverride Sub OpenChannel()
        Public MustOverride Sub CloseChannel()

#End Region '/Переопределяемые свойства

#Region "Структуры и перечисления"

        ''' <summary>
        ''' Режим, в котором работает устройство MPSSE.
        ''' </summary>
        Public Enum DeviceWorkingMode As Integer
            None
            Spi
            I2c
            Jtag
        End Enum

        ''' <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>
        Public Enum DeviceEnum As Integer
            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

#End Region '/Структуры и перечисления

#Region "Статус устройства"

        ''' <summary>
        ''' Проверяет статус устройства и вызывает исключение в зависимости от кода статуса.
        ''' </summary>
        ''' <param name="status">Код статуса.</param>
        Protected 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 StatusCode.FT_WRONG_READ_BUFFER_SIZE
                    '    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 '/Статус устройства 

    End Class 
End Namespace

Теперь создадим класс, наследуемый от класса MpsseBase и реализующий функциональность для работы микросхем FTDI по интерфейсу I2C.

Код класса для работы в режиме I2C (разворачивается)
Imports System.Runtime.InteropServices

Namespace Ftdi

    ''' <summary>
    ''' Работа в режиме IIC с помощью библиотеки LibMPSSE.
    ''' </summary>
    Public Class MpsseI2c
        Inherits MpsseBase


#Region "Read-only свойства, константы"

        Public Const MAX_I2C_SPEED As Integer = 3400000
        Public Const I2C_MAX_READ_DATA_BITS_BUFFER As Integer = 524288
        Public Const I2C_MAX_READ_DATA_BYTES_BUFFER As Integer = I2C_MAX_READ_DATA_BITS_BUFFER \ 8

        ''' <summary>
        ''' Режим работы устройства MPSSE – IIC.
        ''' </summary>
        Public Overrides ReadOnly Property DeviceMode As DeviceWorkingMode = DeviceWorkingMode.I2c

#End Region '/Read-only свойства, константы

#Region "Конструктор"

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

#End Region '/Конструктор

#Region "Инфо"

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Function I2C_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 Overloads 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 = I2C_GetChannelInfo(index, info)
            CheckStatus(r)
            Return info
        End Function

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

#End Region '/Инфо

#Region "Инициализация"

        ''' <summary>
        ''' Последняя заданная конфигурация.
        ''' </summary>
        Private LastConfig As I2cConfig

        <DllImport(DLL_MPSSE_PATH, SetLastError:=True, CallingConvention:=CallingConvention.Cdecl)>
        Private Shared Function I2C_InitChannel(ByVal handle As Integer, ByRef config As I2cConfig) 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 I2cConfig) As I2cConfig
            Dim r As Integer = I2C_InitChannel(devHandle, config)
            CheckStatus(r)
            Return config
        End Function

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

        ''' <summary>
        ''' Устанавливает последнюю заданную конфигурацию.
        ''' </summary>
        Public Function ReInitChannel() As I2cConfig
            Return InitChannel(DeviceHandle, LastConfig)
        End Function

#End Region '/Инициализация

#Region "Открытие, закрытие"

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

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

        ''' <summary>
        ''' Открывает канал в режиме I2C, если это не было сделано при создании экземпляра.
        ''' </summary>
        Public Overrides 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 I2C_CloseChannel(ByVal handle As Integer) As Integer
        End Function

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

#End Region '/Открытие, закрытие

#Region "Чтение, запись"

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

        ''' <summary>
        ''' Читает по I2C из заданного адреса заданное число байтов и возвращает массив реально прочитанных байтов.
        ''' </summary>
        ''' <param name="devHandle">Дескриптор устройства (канала).</param>
        ''' <param name="deviceAddress">Адрес I2C устройства, 7-битовое значение (всегда меньше 128).</param>
        ''' <param name="readLength">Число байтов для чтения.</param>
        ''' <param name="options">Опции чтения.</param>
        Public Shared Function Read(ByVal devHandle As Integer, ByVal deviceAddress As Integer, ByVal readLength As Integer, Optional ByVal options As I2C_TRANSFER_OPTIONS = I2C_TRANSFER_OPTIONS.START_BIT Or I2C_TRANSFER_OPTIONS.STOP_BIT) As Byte()
            Dim wasRedLength As Integer = 0
            Dim bufferToRead(readLength - 1) As Byte
            Dim r As Integer = I2C_DeviceRead(devHandle, deviceAddress, readLength, bufferToRead, wasRedLength, options)
            CheckStatus(r)
            ReDim Preserve bufferToRead(wasRedLength - 1)
            Return bufferToRead
        End Function

        ''' <summary>
        ''' Читает по I2C из заданного адреса заданное число байтов и возвращает массив реально прочитанных байтов.
        ''' </summary>
        ''' <param name="deviceAddress">Адрес I2C устройства, 7-битовое значение (всегда меньше 128).</param>
        ''' <param name="readLength">Число байтов для чтения.</param>
        ''' <param name="options">Опции чтения.</param>
        Public Function Read(ByVal deviceAddress As Integer, ByVal readLength As Integer, Optional ByVal options As I2C_TRANSFER_OPTIONS = I2C_TRANSFER_OPTIONS.START_BIT Or I2C_TRANSFER_OPTIONS.STOP_BIT) As Byte()
            Return Read(DeviceHandle, deviceAddress, readLength, options)
        End Function

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

        ''' <summary>
        ''' Записывает по I2C заданный массив и возвращает число переданных байтов.
        ''' </summary>
        ''' <param name="devHandle">Дескриптор устройства (канала).</param>
        ''' <param name="deviceAddress">Адрес I2C устройства.</param>
        ''' <param name="bufferToWrite">Массив для записи.</param>
        ''' <param name="options">Опции записи.</param>
        Public Shared Function Write(ByVal devHandle As Integer, ByVal deviceAddress As Integer, ByVal bufferToWrite As Byte(), Optional ByVal options As I2C_TRANSFER_OPTIONS = I2C_TRANSFER_OPTIONS.START_BIT Or I2C_TRANSFER_OPTIONS.STOP_BIT) As Integer
            Dim bytesTransferred As Integer = 0
            Dim writeLength As Integer = bufferToWrite.Length
            Dim r As Integer = I2C_DeviceWrite(devHandle, deviceAddress, writeLength, bufferToWrite, bytesTransferred, options)
            CheckStatus(r)
            Return bytesTransferred
        End Function

        ''' <summary>
        ''' Записывает по I2C заданный массив и возвращает число переданных байтов.
        ''' </summary>
        ''' <param name="deviceAddress">Адрес I2C устройства.</param>
        ''' <param name="bufferToWrite">Массив для записи.</param>
        ''' <param name="options">Опции записи.</param>
        Public Function Write(ByVal deviceAddress As Integer, ByVal bufferToWrite As Byte(), Optional ByVal options As I2C_TRANSFER_OPTIONS = I2C_TRANSFER_OPTIONS.START_BIT Or I2C_TRANSFER_OPTIONS.STOP_BIT) As Integer
            Return Write(DeviceHandle, deviceAddress, bufferToWrite, options)
        End Function

#End Region '/Чтение, запись

#Region "Структуры и перечисления"

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

            ''' <summary>
            ''' Значение тактовой частоты шины IIC, в Гц. 
            ''' Можно задать стандартные значения <see cref="I2cSpeed"/> 
            ''' или произвольные значения в диапазоне от 0 до 3400000.
            ''' </summary>
            <CLSCompliant(False)>
            Public ClockRate As I2cSpeed

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

            ''' <summary>
            ''' <list>
            ''' <item>Бит 0 - Задаёт 3-фазовое тактирование.</item>
            ''' <item>Бит 1 - Задаёт опцию Drive-Only-Zero.</item>
            ''' <item>Биты 2..31 - Резерв.</item>
            ''' </list>
            ''' </summary>
            Public ConfigOptions As I2C_CONFIG_OPTIONS

        End Structure

        ''' <summary>
        ''' Стандартные значения частоты шины данных, в Гц.
        ''' </summary>
        Public Enum I2cSpeed As Integer
            I2C_CLOCK_STANDARD_MODE = 100000
            I2C_CLOCK_FAST_MODE = 400000
            I2C_CLOCK_FAST_MODE_PLUS = 1000000
            I2C_CLOCK_HIGH_SPEED_MODE = 3400000
        End Enum

        ''' <summary>
        ''' Конфигурация IIC.
        ''' </summary>
        <Flags()>
        Public Enum I2C_CONFIG_OPTIONS As Integer
            ''' <summary>
            ''' Отключить 3-фазовое тактирование. По умолчанию включено.
            ''' </summary>
            DISABLE_3PHASE_CLOCKING = 1
            ''' <summary>
            ''' Задаёт опцию Drive-Only-Zero.
            ''' </summary>
            ENABLE_DRIVE_ONLY_ZERO = 2
        End Enum

        ''' <summary>
        ''' Параметры передачи I2C.
        ''' </summary>
        <Flags()>
        Public Enum I2C_TRANSFER_OPTIONS As Integer

            ''' <summary>
            ''' Генерировать стартовый бит до начала передачи.
            ''' </summary>
            START_BIT = &H1

            ''' <summary>
            ''' Генерировать стоповый бит после передачи.
            ''' </summary>
            STOP_BIT = &H2

            ''' <summary>
            ''' Будет ли прервана передача, если не получено подтверждение (NAK).
            ''' Используется только при записи.
            ''' </summary>
            BREAK_ON_NACK = &H4

            ''' <summary>
            ''' Некоторые I2C ведомые требуют чтобы мастер генерировал NAK после чтения последнего байта.
            ''' Эта опция позволяет работать с такими ведомыми.
            ''' Используется только при чтении.
            ''' </summary>
            NACK_LAST_BYTE = &H8

            ''' <summary>
            ''' Быстрая передача - без задержек между фазами START, ADDRESS, DATA и STOP.
            ''' </summary>
            FAST_TRANSFER_BYTES = &H10

            ''' <summary>
            ''' Быстрая передача - без задержек между фазами START, ADDRESS, DATA и STOP.
            ''' </summary>
            FAST_TRANSFER_BITS = &H20

            ''' <summary>
            ''' Быстрая передача - без задержки между фазами START, ADDRESS, DATA и STOP, без USB задержек.
            ''' </summary>
            FAST_TRANSFER = &H30

            ''' <summary>
            ''' Игнорирует IIC адрес ведомого.
            ''' Работает только когда включены опции <see cref="FAST_TRANSFER_BYTES"/> и <see cref="FAST_TRANSFER_BITS" /> (или <see cref="FAST_TRANSFER"/>).
            ''' </summary>
            NO_ADDRESS = &H40

        End Enum

#End Region '/Структуры и перечисления

    End Class
End Namespace

3Чтение данных датчика BMP280с помощью микросхемы FTDI FT2232H

Программная часть готова для того чтобы обменяться данными с нашим старым знакомым – датчиком температуры и давления BMP280 (приобретаем у китайцев). Только на этот раз мы будем «общаться» с ним по интерфейсу I2C. Но сначала нужно подключить датчик по схеме:

Схема подключения BMP280 к FT2232 по интерфейсу I2C
Схема подключения BMP280 к FT2232 по интерфейсу I2C

Я подключаю ко второму каналу (выводам 38 и 39 микросхемы FT2232). Если будете подключать к первому, это выводы 16 и 17. А вообще, лучше свериться с техническим описанием (datasheet) на микросхему, т.к. фирма FTDI Chip выпускает большое число различных микросхем, и назначение выводов может не совпадать.

Обратите внимание, что вывод SDO датчика BMP280 необходимо подключить к питанию или к земле, он не должен «висеть» в воздухе. Это влияет на его I2C адрес:

Схема соединенияАдрес I2C
SDO соединён с Vdd0x77
SDO соединён с GND0x76

Теперь всё готово. Прочитаем регистр ID датчика, в котором, как мы помним, хранится постоянный идентификатор, равный 0x58. Посмотрим в описании на датчик, как с ним должен происходить обмен по I2C:

Порядок записи и чтения BMP280 по интерфейсу I2C
Порядок записи и чтения BMP280 по интерфейсу I2C

Верхний рисунок показывает последовательность записи, а нижний – чтения.

Получается, что сначала нам нужно записать в I2C устройство с адресом 0x77 адрес регистра ID 0x0D, а затем прочитать из него же 1 байт. Используя только что написанные классы, сделаем это.

Первым делом импортируем пространства имён, в которых находятся наши классы для работы с устройствами FTDI:

Imports Ftdi
Imports Ftdi.MpsseI2c

Опишем параметры I2C, которыми мы хотим инициализировать канал (устройство). Используем, например, стандартные параметры:

Dim config As New I2cConfig() With {
    .ClockRate = I2cSpeed.I2C_CLOCK_STANDARD_MODE,
    .LatencyTimer = 2,
    .ConfigOptions = 0
}

Теперь подключимся ко второму каналу и инициализируем его только что созданными настройками:

Dim device As New MpsseI2c(1) 
device.InitChannel(config) 

Создадим переменную для хранения опций передачи. Потом запишем в устройство адрес регистра и прочитаем из него 1 байт с указанными опциями:

Dim options As I2C_TRANSFER_OPTIONS = I2C_TRANSFER_OPTIONS.START_BIT Or I2C_TRANSFER_OPTIONS.STOP_BIT Or I2C_TRANSFER_OPTIONS.FAST_TRANSFER
device.Write(&H77, {&HD0}, options)
Dim id As Byte() = device.Read(&H77, 1, options)

В первом (и единственном) байте ответа должно находиться число 0x58.

Вот как информационный обмен с сенсором bmp280 по интерфейсу IIC выглядит на временной диаграмме с логического анализатора:

Временная диаграмма обмена по I2C между BMP280 и FT2232
Временная диаграмма обмена по I2C между BMP280 и FT2232

Мы помним, что I2C адрес устройства передаётся в старших 7-ми битах первого байта, а в младшем бите находится признак чтения (1) или записи (0). То есть в двоичном виде адрес 0x77 и маркер записи 0x0 дают: 01110111_0 = 0xEE, а адрес 0x77 и маркер чтения 0x1 дают 01110111_1 = 0xEF.

Зелёные и красные кружки на рисунке – это маркеры начала и конца передачи (те самые опции I2C_TRANSFER_OPTIONS.START_BIT и I2C_TRANSFER_OPTIONS.STOP_BIT). Биты ACK (каждый 9-ый бит) – это подтверждение, что в I2C сети имеется ведомое устройство с запрошенным адресом, и оно готово принять сообщение. Последний бит NAK сигнализирует об окончании обмена.

Выводы

Мы рассмотрели возможную реализацию взаимодействия по последовательному интерфейсу I2C, реализуемую с помощью динамически подключаемой библиотеки libMPSSE.dll фирмы FTDI Chip на языке VB.NET.

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

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

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

Скачать вложения:

Последнее изменениеПонедельник, 05 Апрель 2021 21:17 Прочитано 6106 раз

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

Поделиться

Print Friendly, PDF & Email

Оставить комментарий