Рейтинг@Mail.ru

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

Print Friendly, PDF & Email
Рассмотрим реализацию интерфейса I2C с помощью микросхемы CH341 и библиотеки CH341DLL на языке VB.NET.

1Описание микросхемы CH341

Китайская микросхема CH341 сейчас используется очень широко. Она представляет собой преобразователь из USB в порт принтера, параллельный порт в режиме EPP или MEM, последовательные интерфейсы UART/RS232/RS485/RS422, SPI и I2C, и другие. Также микросхема может принимать различные Vendor ID и Product ID. В общем, отличная вещь.

Для управления микросхемой CH341 необходим компьютер или иное устройство, обладающее USB.

Режимы работы микросхемы CH341
Режимы работы микросхемы CH341

Микросхема выполняется в корпусах типа SOP-28 и SSOP-20. Назначение выводов микросхемы в различных режимах меняется:

Назначение выводов микросхемы CH341 в различных режимах
Назначение выводов микросхемы CH341 в различных режимах

Приведённые рисунки взяты из технического описания (datasheet) на микросхему. Ссылка на скачивание datasheet – в конце статьи.

Для изучения возможностей микросхемы хорошо иметь какую-либо отладочную плату вроде такой, как показано на фотографии:

Отладочная плата с микросхемой CH341A
Отладочная плата с микросхемой CH341A

Это продуманная плата: на ней все выводы, относящиеся к одному режиму работы, собраны вместе, и к ним очень удобно подключаться. Все ножки обозначены на плате с помощью шелкографии. Есть перемычки для переключения между режимами UART и IIC/SPI, выбор напряжения логической единицы 3 или 5 вольт, выбор питания от внешнего источника или по USB. Присутствует кварцевый резонатор для генерирования тактовой частоты микросхемы. На плате присутствует разъём для подключения к порту USB компьютера. У китайцев такую плату можно приобрести по хорошей цене.

2Демонстрационная программа для работы с микросхемой CH341

Для работы с данной микросхемой имеется библиотека CH341DLL.dll от производителя. Она позволяет переключить микросхему в любой из поддерживаемых режимов и осуществлять с ней информационный обмен. Также имеются демонстрационные программы, которые используют данную библиотеку. К сожалению, они все на китайском языке :) Я перевёл одну из программ на английский и добавил возможность выбора скорости при работе в режиме I2C, благо исходники открытые. На рисунке мы прочитали регистр ID датчика давления и температуры bmp280 по интерфейсу IIC:

Демонстрационная программа для CH341 в режиме I2C
Демонстрационная программа для CH341 в режиме I2C

Итак, что мы видим на этом скриншоте? Программа содержит ряд вкладок. Каждая вкладка отвечает за тот режим работы, который отмечен в названии вкладки. В нашем случае открыта вкладка I2C. На вкладке имеется два больших поля – сверху и снизу. В верхнем поле задаются параметры записи, в нижнем – параметры чтения.

Поле SCL freq позволяет выбрать скорость обмена (это поле, которое было добавлено мной). По умолчанию стоит 100 кГц.

В поле Length вводится (в 16-ном формате) длина байтового массива, который мы будем записывать в ведомое устройство. Ниже, в поле Data, вводится сам массив, также в 16-ном формате. Здесь первый байт представляет собой I2C адрес ведомого устройства и маркер операции записи. В данном случае 0x77 – адрес, и 0 – признак записи. Если представить это в двоичном виде, то понятно, откуда получается 0xEE.

Более подробно мы рассматривали это в пункте 3 вот этой статьи.

Второй байт – это, собственно, записываемые данные. В показанном случае 0xD0 – это адрес регистра ID, содержимое которого мы собираемся запросить у ведомого с адресом 0x77.

В нижней части – поле чтения: Length – сколько байтов мы запрашиваем у ведомого, а в поле Data отобразятся принятые данные.

Кнопка Write/Read начинает запись и чтение по I2C. Именно в такой последовательности: сначала запись, потом чтение. Если нужно только записать данные, то в нижнем поле Length нужно поставить "0". Если нужно только прочитать, то, соответственно, в верхнем поле Length ставим "0". Ссылка на скачивание переведённой программы – в конце статьи.

Пришла пора познакомиться поближе с библиотекой.

3Библиотека CH341DLL.dll для работы с микросхемой CH341

Вообще говоря, библиотека CH341DLL содержит 64 экспортируемые функции, реализующие работу во всех поддерживаемых режимах, а также ряд функций общего назначения. Сейчас мы не будем рассматривать их все. Реализуем класс Ch341Device, в который включим общую для всех режимов функциональность: информационные методы, методы для открытия и закрытия, сброса устройства, а также работа с выводами, не привязанная к конкретным интерфейсам.

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

Namespace Ch341

    ''' <summary>
    ''' Работа с м/сх CH341.
    ''' </summary>
    Public Class Ch341Device


        ''' <summary>
        ''' Путь к библиотеке CH341DLL.DLL.
        ''' </summary>
        Public Const DLL_PATH As String = "CH341DLL.DLL"

        ''' <summary>
        ''' Неверный дескриптор.
        ''' </summary>
        Public Const INVALID_HANDLE_VALUE = -1

#Region "Перечисления"

        ''' <summary>
        ''' Версии микросхемы CH341.
        ''' </summary>
        Public Enum IcVersion As Integer
            InvalidIc = 0
            Ch341 = &H10
            Ch341a = &H20
            Ch341a_ = &H30
        End Enum

        ''' <summary>
        ''' Словарь типов м/сх.
        ''' </summary>
        Public ReadOnly Property IcDict As New Dictionary(Of IcVersion, String) From {
            {IcVersion.InvalidIc, "Недействительный тип"},
            {IcVersion.Ch341, "CH341"},
            {IcVersion.Ch341a, "CH341A"},
            {IcVersion.Ch341a_, "CH341A"}
        }

#End Region '/Перечисления

#Region "Свойства только для чтения"

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

        ''' <summary>
        ''' Дескриптор устройства.
        ''' </summary>
        Public ReadOnly Property Handle As IntPtr
            Get
                Return _Handle
            End Get
        End Property
        Private _Handle As IntPtr = IntPtr.Zero

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

        ''' <summary>
        ''' Состояние вывода ERR# (pin 5, Input) - IN0.
        ''' </summary>
        Public ReadOnly Property PinErrState As Boolean
            Get
                Return ((GetInput() >> 8 And 1) = 1)
            End Get
        End Property

        ''' <summary>
        ''' Состояние вывода PEMP (pin 6, Input) - IN1.
        ''' </summary>
        Public ReadOnly Property PinPempState As Boolean
            Get
                Return ((GetInput() >> 9 And 1) = 1)
            End Get
        End Property

        ''' <summary>
        ''' Состояние вывода INT# (pin 7, Input) - Interrupt request (по переднему фронту).
        ''' </summary>
        Public ReadOnly Property PinIntState As Boolean
            Get
                Return ((GetInput() >> 10 And 1) = 1)
            End Get
        End Property

        ''' <summary>
        ''' Состояние вывода SEL (pin 8, Input) - IN3.
        ''' </summary>
        Public ReadOnly Property PinSelState As Boolean
            Get
                Return ((GetInput() >> 11 And 1) = 1)
            End Get
        End Property

        ''' <summary>
        ''' Состояние вывода SDA (pin 23).
        ''' </summary>
        Public ReadOnly Property PinSdaState As Boolean
            Get
                Return ((GetInput() >> 23 And 1) = 1)
            End Get
        End Property

        ''' <summary>
        ''' Состояние вывода BUSY/WAIT# (pin 27, Input) - /Wait.
        ''' </summary>
        Public ReadOnly Property PinWaitState As Boolean
            Get
                Return ((GetInput() >> 13 And 1) = 1)
            End Get
        End Property

        ''' <summary>
        ''' Состояние вывода AUTO341#/DATAS# (pin 4, Output) - /Data Select.
        ''' </summary>
        Public ReadOnly Property PinDataStrobeState As Boolean
            Get
                Return ((GetInput() >> 14 And 1) = 1)
            End Get
        End Property

        ''' <summary>
        ''' Состояние вывода SLCTIN#/ADDRS# (pin 3, Output) - /Address select.
        ''' </summary>
        Public ReadOnly Property PinAddrStrobeState As Boolean
            Get
                Return ((GetInput() >> 15 And 1) = 1)
            End Get
        End Property

        ''' <summary>
        ''' Состояние линий D0…D7 (двунаправленные выводы 15..22) - 8-битная шина данных.
        ''' </summary>
        Public ReadOnly Property PinDataState As Boolean()
            Get
                Dim d As New BitArray({GetInput()})
                Return {d(0), d(1), d(2), d(3), d(4), d(5), d(6), d(7)}
            End Get
        End Property

#End Region '/свойства только для чтения

#Region "Информационные методы"

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341GetVersion() As Integer
        End Function

        ''' <summary>
        ''' Возвращает версию библиотеки.
        ''' </summary>
        Public Shared Function GetDllVersion() As Integer
            Dim dllVer As Integer = CH341GetVersion()
            Return dllVer
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341GetDrvVersion() As Integer
        End Function

        ''' <summary>
        ''' Возвращает версию драйвера.
        ''' </summary>
        Public Function GetDriverVersion() As Integer
            Dim drvVer As Integer = CH341GetDrvVersion()
            Return drvVer
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341GetVerIC(ByVal iIndex As Integer) As Integer
        End Function

        ''' <summary>
        ''' Возвращает тип микросхемы CH341.
        ''' </summary>
        Public Function GetIcVersion() As IcVersion
            Dim verIc As IcVersion = CType(CH341GetVerIC(DeviceIndex), IcVersion)
            Return verIc
        End Function

        ''' <summary>
        ''' Возвращает описание типа микросхемы CH341.
        ''' </summary>
        Public Function GetIcVersionReadable() As String
            Dim ver As IcVersion = GetIcVersion()
            Dim s As String = String.Empty
            IcDict.TryGetValue(ver, s)
            Return s
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341GetDeviceDescr(ByVal iIndex As Integer, ByVal oBuffer As Byte(), ByRef ioLength As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Возвращает описание устройства.
        ''' </summary>
        Public Function GetDeviceDescriptor() As String
            Dim len As Integer = &H400
            Dim descr(len - 1) As Byte
            Dim res As Boolean = CH341GetDeviceDescr(DeviceIndex, descr, len)
            Dim s As String = String.Empty
            If res AndAlso (len > 0) Then
                s = Encoding.Unicode.GetString(descr, 0, len)
            End If
            Return s
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341GetDeviceName(ByVal iIndex As Integer) As IntPtr
        End Function

        ''' <summary>
        ''' Возвращает название устройства.
        ''' </summary>
        Public Function GetDeviceName() As String
            Dim ptr As IntPtr = CH341GetDeviceName(DeviceIndex)
            Dim s As String = Marshal.PtrToStringAnsi(ptr)
            Return s
        End Function

#End Region '/Информационные методы

#Region "Открытие, закрытие, сброс устройства"

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341OpenDevice(ByVal iIndex As Integer) As IntPtr
        End Function

        ''' <summary>
        ''' Открывает устройство с заданным индексом.
        ''' </summary>
        ''' <param name="index">Индекс устройства в системе, начиная с 0.</param>
        ''' <param name="isExclusive">Открыть эксклюзивно или разрешить другим процессам использовать устройство.</param>
        Public Sub New(ByVal index As Integer, Optional isExclusive As Boolean = False)
            _DeviceIndex = index
            _Handle = CH341OpenDevice(index)
            If (Handle.ToInt32() = INVALID_HANDLE_VALUE) OrElse (Handle = IntPtr.Zero) Then
                Throw New SystemException(String.Format("Невозможно открыть устройство {0}.", index))
            End If
            SetExclusive(isExclusive)
        End Sub

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Sub CH341CloseDevice(ByVal iIndex As Integer)
        End Sub

        ''' <summary>
        ''' Закрывает устройство.
        ''' </summary>
        Public Sub CloseDevice()
            If IsOpened Then
                CH341CloseDevice(DeviceIndex)
                _DeviceIndex = INVALID_HANDLE_VALUE
                _Handle = IntPtr.Zero
            End If
        End Sub

        ''' <summary>
        ''' Закрывает указанное устройство.
        ''' </summary>
        ''' <param name="index">Индекс устройства в системе, начиная с 0.</param>
        Public Shared Sub CloseDevice(index As Integer)
            CH341CloseDevice(index)
        End Sub

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341ResetDevice(ByVal iIndex As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Сбрасывает устройство (возвращает к исходному состоянию после загрузки).
        ''' </summary>
        Public Sub ResetDevice()
            Dim res As Boolean = CH341ResetDevice(DeviceIndex)
            If res Then
                Return
            End If
            Throw New Exception("Ошибка сброса устройства.")
        End Sub

        ''' <summary>
        ''' Сбрасывает заданное устройство.
        ''' </summary>
        ''' <param name="index">Индекс устройства в системе, начиная с 0.</param>
        ''' <remarks>Статический метод добавлен, чтобы можно было сбросить устройство в ситуации "зависания".</remarks>
        Public Shared Sub ResetDevice(index As Integer)
            Dim res As Boolean = CH341ResetDevice(index)
            If res Then
                Return
            End If
            Throw New Exception("Ошибка сброса устройства.")
        End Sub

#End Region '/Открытие, закрытие, сброс устройства

#Region "Управление"

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341SetExclusive(ByVal iIndex As Integer, <MarshalAs(UnmanagedType.Bool)> ByVal iExclusive As Boolean) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Задаёт или снимает эксклюзивное использование устройства (разрешает/запрещает другим процессам подключаться к устройству, пока оно открыто).
        ''' </summary>
        ''' <param name="isExclusive">True - устройство используется эксклюзивно (не даёт переоткрывать себя), False - может быть открыто несколько экземпляров устройства.</param>
        Public Sub SetExclusive(ByVal isExclusive As Boolean)
            Dim res As Boolean = CH341SetExclusive(DeviceIndex, isExclusive)
            If res Then
                Return
            End If
            Throw New Exception("Ошибка выставления эксклюзивного режима работы с устройством.")
        End Sub

#End Region '/Управление

#Region "Выборочная конфигурация выводов"

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341GetStatus(ByVal iIndex As Integer, ByRef iStatus As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Возвращает состояния портов ввода-вывода данных. Лучше использовать более эффективный метод <see cref="GetInput"/>.
        ''' </summary>
        ''' <remarks>
        ''' Биты 0…7 отвечают за состояния пинов D0..D7,
        ''' бит 8 - ERR# - пин 5,
        ''' бит 9 - PEMP - пин 6,
        ''' бит 10 - INT# - пин 7,
        ''' бит 11 - SLCT - пин 8,
        ''' бит 13 - BUSY/WAIT# - пин 27,
        ''' бит 14 - AUTO341#/DATAS# - пин 4,
        ''' бит 15 - SLCTIN#/ADDRS# - пин 3,
        ''' бит _23_ - SDA - пин 23.
        ''' </remarks>
        Public Function GetPinsState() As Integer
            Dim stat As Integer = 0
            Dim res As Boolean = CH341GetStatus(DeviceIndex, stat)
            If res Then
                Return stat
            End If
            Throw New Exception("Невозможно получить состояние портов ввода-вывода.")
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341GetInput(ByVal iIndex As Integer, ByRef iStatus As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Возвращает состояния портов ввода-вывода данных и статуса устройства. Эффективность выше, чем у метода <see cref=" GetPinsState()"/>.
        ''' </summary>
        ''' <remarks>
        ''' Биты 0…7 отвечают за состояния пинов D0..D7,
        ''' бит 8 - ERR# - пин 5,
        ''' бит 9 - PEMP - пин 6,
        ''' бит 10 - INT# - пин 7,
        ''' бит 11 - SLCT - пин 8,
        ''' бит 13 - BUSY/WAIT# - пин 27,
        ''' бит 14 - AUTO341#/DATAS# - пин 4,
        ''' бит 15 - SLCTIN#/ADDRS# - пин 3,
        ''' бит _23_ - SDA - пин 23.
        ''' </remarks>
        Public Function GetInput() As Integer
            Dim status As Integer = 0
            Dim res As Boolean = CH341GetInput(DeviceIndex, status)
            If res Then
                Return status
            End If
            Throw New Exception("Невозможно получить состояние портов ввода-вывода.")
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341SetOutput(ByVal iIndex As Integer, ByVal iEnable As Integer, ByVal iSetDirOut As Integer, ByVal iSetDataOut As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Задаёт направление и состояния пинов ввода-вывода.
        ''' </summary>
        ''' <param name="dataValid">Состояния валидности данных:
        ''' - бит 0 - 1 показывает, что биты 15..8  <paramref name="pinsState"/> валидны, в противном случае игнорируются;
        '''	- бит 1 - 1 показывает, что биты 15..8 <paramref name="pinsDirection"/> валидны, в противном случае игнорируются;
        '''	- бит 2 - 1 показывает, что биты 7..0 <paramref name="pinsState"/> валидны, в противном случае игнорируются;
        '''	- бит 3 - 1 показывает, что биты 7..0 <paramref name="pinsDirection"/> валидны, в противном случае игнорируются;
        '''	- бит 4 - 1 показывает, что биты 23..16 <paramref name="pinsState"/> валидны, в противном случае игнорируются.
        ''' </param>
        ''' <param name="pinsDirection">Задаёт направления портов I/O. Бит 0 соответствует входу, бит 1 - выходу (осторожно!). Значение по умолчанию для параллельного порта 0x000FC000.</param>
        ''' <param name="pinsState">Задаёт состояния портов ввода-вывода. Бит 0 соответствует низкому уровню, 1 - высокому: 
        '''	- биты 7..0 соответствуют пинам D7..D0;
        '''	- бит 8  - ERR#; 
        '''	- бит 9  - PEMP; 
        '''	- бит 10 - INT#; 
        '''	- бит 11 - SLCT; 
        '''	- бит 13 - WAIT#; 
        '''	- бит 14 - DATAS#/READ#; 
        '''	- бит 15 - ADDRS#/ADDR/ALE.
        '''	Следующие пины могут быть только выходами, независимо от заданного направления: 
        '''	- бит 16 соответствует пину RESET#; 
        '''	- бит 17 - WRITE#; 
        '''	- бит 18 - SCL; 
        '''	- бит 29 - SDA.
        ''' </param>
        ''' <remarks>
        ''' ***** Использовать этот API с осторожностью, т.к. неверное задание состояний и направлений выводов может привести к короткому замыканию и выходу микросхемы из строя! *****
        ''' Пример: 
        ''' CH341SetOutput(0, $FF, $FF, $F0) задаёт пины D0..D7 как выходы. Остальные линии не затрагиваются. D0…D3 - в состоянии LOW, D4…D7 - в состоянии HIGH.
        ''' </remarks>
        <Obsolete("Использовать с осторожностью, т.к. неверное задание состояний и направлений выводов может привести к короткому замыканию и выходу микросхемы из строя!")>
        Public Sub SetIoPinsDirection(dataValid As Integer, pinsDirection As Integer, pinsState As Integer)
            Dim res As Boolean = CH341SetOutput(DeviceIndex, dataValid, pinsDirection, pinsState)
            If res Then
                Return
            End If
            Throw New Exception("Не удалось задать направления и состояния выводов.")
        End Sub

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341Set_D5_D0(ByVal iIndex As Integer, ByVal iSetDirOut As Integer, ByVal iSetDataOut As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Задаёт направления и состояния портов ввода-выода D0..D5. Эффективнее, чем <see cref="SetIOPinsDirection"/>.
        ''' </summary>
        ''' <param name = "pinsDirection">Задаёт направления портов D0..D5. Бит 0 соответствует входу, бит 1 - выходу (осторожно!). Значение по умолчанию для параллельного порта 0x00.</param>
        ''' <param name="pinsState">Биты 0..5 задают состояния портов D0..D5. Если выводы в режиме выхода, бит 0 соответствует низкому уровню, 1 - высокому.</param>
        <Obsolete("Использовать с осторожностью, т.к. неверное задание состояний и направлений выводов может привести к короткому замыканию и выходу микросхемы из строя!")>
        Public Sub SetDataPinsDirection(pinsDirection As Integer, pinsState As Integer)
            Dim res As Boolean = CH341Set_D5_D0(DeviceIndex, pinsDirection, pinsState)
            If res Then
                Return
            End If
            Throw New Exception("Не удалось задать направления и состояния выводов D0..D5")
        End Sub

#End Region '/Выборочная конфигурация выводов

    End Class
End Namespace

Микросхемы CH341 реализуют несколько последовательных и несколько параллельных режимов. Все последователььные и все параллельные имеют общие свойства и методы, поэтому можем их объединить. Создадим класс Serial, который будет наследоваться от класса Ch341Device и реализовывать общую функциональность последовательных режимов.

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

Namespace Ch341

    ''' <summary>
    ''' Работа CH341 в режиме Serial.
    ''' </summary>
    Public Class Serial
        Inherits Ch341Device

        Public Sub New(index As Integer, Optional isExclusive As Boolean = False)
            MyBase.New(index, isExclusive)
        End Sub

#Region "Перечисления"

        ''' <summary>
        ''' Режимы проверки данных в последовательном режиме.
        ''' </summary>
        Public Enum ParityModes As Integer
            None = 0
            Odd = 1
            Even = 2
            Mark = 3
            Space = 4
        End Enum

        ''' <summary>
        ''' Скорости передачи в последовательном режиме.
        ''' </summary>
        Public Enum BaudRates As Integer
            br50 = 50
            br75 = 75
            br100 = 100
            br110 = 110
            br134 = 134
            br150 = 150
            br300 = 300
            br600 = 600
            br900 = 900
            br1200 = 1200
            br1800 = 1800
            br2400 = 2400
            br3600 = 3600
            br4800 = 4800
            br9600 = 9600
            br14400 = 14400
            br19200 = 19200
            br28800 = 28800
            br33600 = 33600
            br38400 = 38400
            br56000 = 56000
            br57600 = 57600
            br76800 = 76800
            br115200 = 115200
            br128000 = 128000
            br153600 = 153600
            br230400 = 230400
            br460800 = 460800
            br921600 = 921600
            br1500000 = 1500000
            br2000000 = 2000000
        End Enum

#End Region '/Перечисления 

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341SetupSerial(ByVal iIndex As Integer, ByVal iParityMode As Integer, ByVal iBaudRate As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Задаёт параметры последовательного режима устройства. Работает только в последовательном режиме.
        ''' </summary>
        ''' <param name="parityMode">Задаёт режим проверки данных.</param>
        ''' <param name="baudRate">Скорость обмена, 50..3000000 бит/с.</param>
        Public Sub SetupSerialMode(ByVal parityMode As ParityModes, ByVal baudRate As BaudRates)
            If (baudRate >= 50) AndAlso (baudRate <= 3000000) Then
                Dim res As Boolean = CH341SetupSerial(DeviceIndex, parityMode, baudRate)
                If res Then
                    Return
                End If
            End If
            Throw New Exception("Невозможно установить параметры последовательного режима устройства.")
        End Sub

        ''' <summary>
        ''' Задаёт параметры потокового режима последовательного порта.
        ''' </summary>
        ''' <param name="iMode">Задаёт режим:
        ''' биты 1..0 - скорость интерфейса I2C / частоту SCL: 00 = low speed / 20KHz, 01 = standard / 100KHz, 10 = fast / 400KHz, 11 = high speed / 750KHz.
        ''' бит 2 - соответствие и состояния портов ввода-вывода SPI: 0 = стандартный (D5 выход / D7 вход), 1 = с двумя линиями передачи (D5, D4 выход / D7, D6 вход);
        ''' бит 7 - порядок передачи чисел: 0 = LSB первый, 1 = MSB первый (стандартный).
        ''' Остальные биты заразервированы и должны быть 0.
        ''' </param>
        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Protected Friend Shared Function CH341SetStream(ByVal iIndex As Integer, ByVal iMode As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341SetDelaymS(ByVal iIndex As Integer, ByVal iDelay As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Задаёт аппаратную асинхронную задержку для потоковых операций.
        ''' </summary>
        ''' <param name="delaMs">Задержка, мс.</param>
        Public Sub SetDalay(ByVal delaMs As Integer)
            Dim res As Boolean = CH341SetDelaymS(DeviceIndex, delaMs)
            If res Then
                Return
            End If
            Throw New Exception("Ошибка выставления задержки.")
        End Sub

    End Class
End Namespace

Наконец-то мы дошли до момента, когда можем реализовать работу непосредственно с интерфейсом I2C. Микросхема CH341 может выступать только в роли ведущего устройства шины. Поэтому назовём класс I2cMaster и сделаем его наследующим от класса Serial. Включим в него возможность чтения, записи единичного байта, а также потоковой чтения и записи (именно этот способ используется в китайской программе, которую мы рассматривали выше). Также, т.к. микросхема позволяет осуществлять чтение и запись в ПЗУ по шине I2C, включим сюда же раздел для работы с ПЗУ.

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

Namespace Ch341

    ''' <summary>
    ''' Работа CH341 в последовательном режиме IIC.
    ''' </summary>
    Public Class I2cMaster
        Inherits Serial

        ''' <summary>
        ''' Открывает I2C устройство с заданной скоростью шины.
        ''' </summary>
        ''' <param name="index"></param>
        ''' <param name="isExclusive"></param>
        ''' <param name="speed"></param>
        Public Sub New(index As Integer, Optional isExclusive As Boolean = False, Optional speed As I2cSpeed = I2cSpeed.Standard)
            MyBase.New(index, isExclusive)
            SetSpeed(speed)
        End Sub

#Region "Перечисления"

        ''' <summary>
        ''' Типы ПЗУ.
        ''' </summary>
        Public Enum EepromTypes As Integer
            ID_24C01 = 0
            ID_24C02 = 1
            ID_24C04 = 2
            ID_24C08 = 3
            ID_24C16 = 4
            ID_24C32 = 5
            ID_24C64 = 6
            ID_24C128 = 7
            ID_24C256 = 8
            ID_24C512 = 9
            ID_24C1024 = 10
            ID_24C2048 = 11
            ID_24C4096 = 12
        End Enum

        ''' <summary>
        ''' Скорости интерфейса I2C (частота линии SCL).
        ''' </summary>
        Public Enum I2cSpeed
            ''' <summary>
            ''' Частота SCL 20 кГц.
            ''' </summary>
            LowSpeed = 0
            ''' <summary>
            ''' Частота SCL 100 кГц.
            ''' </summary>
            Standard = 1
            ''' <summary>
            ''' Частота SCL 400 кГц.
            ''' </summary>
            Fast = 2
            ''' <summary>
            ''' Частота SCL 750 кГц.
            ''' </summary>
            HighSpeed = 3
        End Enum

#End Region '/Перечисления

#Region "I2C"

        ''' <summary>
        ''' Задаёт частоту линии SCL / скорость передачи.
        ''' </summary>
        ''' <param name="speed">Скорость шины I2C.</param>
        Public Sub SetSpeed(ByVal speed As I2cSpeed)
            Dim res As Boolean = CH341SetStream(DeviceIndex, speed)
            If res Then
                Return
            End If
            Throw New Exception("Ошибка установки скорости шины I2C.")
        End Sub

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341StreamI2C(ByVal iIndex As Integer, ByVal iWriteLength As Integer, ByVal iWriteBuffer As Byte(), ByVal iReadLength As Integer, ByVal oReadBuffer As Byte()) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Универсальный метод чтения и/или записи.
        ''' </summary>
        ''' <param name="writeBuffer">Массив для записи. Первый байт это обычно I2C адрес, сдвинутый на 1 влево (например, если адрес 0x40, то первый элемент массива 0x80). Если NULL или пустой массив, то только чтение.</param>
        ''' <param name="readLength">Сколько байтов прочитать. Если 0, то только запись.</param>
        Public Function I2cReadWrite(ByVal writeBuffer As Byte(), ByVal readLength As Integer) As Byte()
            Dim wLen As Integer = 0
            If (writeBuffer IsNot Nothing) Then
                wLen = writeBuffer.Length
            End If
            Dim readBuffer(readLength - 1) As Byte
            Dim res As Boolean = CH341StreamI2C(DeviceIndex, wLen, writeBuffer, readLength, readBuffer)
            If res Then
                Return readBuffer
            End If
            Throw New Exception("Ошибка чтения/записи по I2C.")
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341ReadI2C(ByVal iIndex As Integer, ByVal iDevice As Byte, ByVal iAddr As Byte, ByRef oByte As Byte) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Читает 1 байт по шине I2C.
        ''' </summary>
        ''' <param name="slaveAddress">Младшие 7 битов представляют i2c адрес ведомого устройства, старший бит - направление передачи (1 - чтение).</param>
        ''' <param name="register">Адрес регистра.</param>
        Public Function I2cRead(slaveAddress As Byte, register As Byte) As Byte
            slaveAddress = CByte(slaveAddress And &HFE)
            Dim b As Byte = 0
            Dim res As Boolean = CH341ReadI2C(DeviceIndex, slaveAddress, register, b)
            If res Then
                Return b
            End If
            Throw New Exception("Ошибка чтения по интерфейсу I2C.")
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341WriteI2C(ByVal iIndex As Integer, ByVal iDevice As Byte, ByVal iAddr As Byte, ByVal iByte As Byte) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Записывает 1 байт по шине I2C.
        ''' </summary>
        ''' <param name="slaveAddress">Младшие 7 битов представляют i2c адрес ведомого устройства, старший бит - направление передачи (0 - запись).</param>
        ''' <param name="register">Адрес регистра.</param>
        ''' <param name="dataToWrite">Байт для записи.</param>
        Public Sub I2cWrite(slaveAddress As Byte, register As Byte, dataToWrite As Byte)
            Dim res As Boolean = CH341WriteI2C(DeviceIndex, slaveAddress, register, dataToWrite)
            If res Then
                Return
            End If
            Throw New Exception("Ошибка записи по интерфейсу I2C.")
        End Sub

#End Region '/I2C

#Region "Чтение и запись ПЗУ"

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341ReadEEPROM(ByVal iIndex As Integer, ByVal iEepromID As EepromTypes, ByVal iAddr As Integer, ByVal iLength As Integer, ByVal oBuffer As Byte()) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Читает из I2C ПЗУ заданное число байтов на скорости примерно 56 кб/с.
        ''' </summary>
        ''' <param name="eepromType">Тип ПЗУ.</param>
        ''' <param name="address">Адрес чтения.</param>
        ''' <param name="length">Сколько байтов прочитать.</param>
        ''' <remarks>ПЗУ подключается по линиям: SDA - пин 23, SCL - пин 24.</remarks>
        Public Function EepromRead(eepromType As EepromTypes, address As Integer, length As Integer) As Byte()
            Dim readBuffer(length - 1) As Byte
            Dim res As Boolean = CH341ReadEEPROM(DeviceIndex, eepromType, address, length, readBuffer)
            If res Then
                Return readBuffer
            End If
            Throw New Exception("Невозможно прочитать данные из ПЗУ.")
        End Function

        <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
        Private Shared Function CH341WriteEEPROM(ByVal iIndex As Integer, ByVal iEepromID As EepromTypes, ByVal iAddr As Integer, ByVal iLength As Integer, ByVal iBuffer As Byte()) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

        ''' <summary>
        ''' Записывает 
        ''' </summary>
        ''' <param name="eepromType">Тип ПЗУ.</param>
        ''' <param name="address">Адрес записи.</param>
        ''' <param name="writeBuffer">Данные для записи в ПЗУ.</param>
        Public Sub EepromWrite(eepromType As EepromTypes, address As Integer, writeBuffer As Byte())
            Dim res As Boolean = CH341WriteEEPROM(DeviceIndex, eepromType, address, writeBuffer.Length, writeBuffer)
            If res Then
                Return
            End If
            Throw New Exception("Невозможно записать данные в ПЗУ.")
        End Sub

#End Region '/Чтение и запись ПЗУ

    End Class 
End Namespace

Для того чтобы использовать наши классы, сначала импортируем пространство имён Ch341:

Imports Ch341

Теперь необходимо создать экземпляр класса I2cMaster с заданными параметрами:

Dim ch341iic As New I2cMaster(0, True, I2cMaster.I2cSpeed.Standard)

Здесь "0" – индекс устройства в системе (нумерация с нуля). Второй параметр "True" означает, что мы хотим использовать устройство в эксклюзивном режиме (другие процессы или объекты не смогут открыть устройство, пока мы не «отпустим» его). Третий параметр – скорость передачи данных по I2C из списка стандартных (в данном случае 100 кГц).

К сожалению, библиотека CH341DLL не имеет методов, позволяющих определить количество подключённых устройств.

Помните, выше мы обсуждали, как прочитать из датчика BMP280 регистр с идентификатором? Сделаем теперь то же самое с помощью нашего только что созданного объекта ch341iic. Прочитаем регистр ID и выведем его значение на консоль:

Dim buf As Byte() = ch341iic.I2cReadWrite({&HEE, &HD0}, 1) 
For Each b As Byte In buf
    Console.Write(b.ToString("X")) 'в hex формате
    Console.Write(" ")
Next
Console.WriteLine(" ")

В конце работы не забудем закрыть устройство:

ch341iic.CloseDevice()

Ну, как-то так:

Чтение датчика BMP280 с помощью микросхемы CH341 по интерфейсу I2C
Чтение датчика BMP280 с помощью микросхемы CH341 по интерфейсу I2C

Вот как выглядит временная диаграмма, снятая с помощью логического анализатора:

Временная диаграмма обмена с датчиком BMP280 по интерфейсу I2C
Временная диаграмма обмена с датчиком BMP280 по интерфейсу I2C

Как всегда, датчик BMP280 купим на Али-экспресс, где он стоит сущие копейки.

4Чтение данных таймера DS3231 по I2C с помощью микросхемы CH341

Давайте в качестве второго эксперимента прочитаем по I2C данные счётчика реального времени (RTC – Real time clock) DS3231. Этот счётчик также работает по интерфейсу I2C, его адрес 0x68. Счётчик выполнен в виде микросхемы в корпусе SO16 и может быть в составе вот такого, готового к подключению, модуля, который можно приобрести здесь:

Модуль со счётчиком времени DS3231
Модуль со счётчиком времени DS3231

Карта регистров, которые хранят отсчёты времени, показана ниже:

Карта регистров счётчика времени DS3231
Карта регистров счётчика времени DS3231

Чтобы прочитать эти регистры, мы должны запросить 19 байт, начиная с адреса 0x00 и заканчивая адресом 0x12:

Dim buf As Byte() = ch341iic.I2cReadWrite({&HD0, &H0}, 19)
For Each b As Byte In buf
    Console.Write(b.ToString("X")) 
    Console.Write(" ")
Next

Здесь 0xD0 – это адрес 0x68 и бит записи = 01101000_0, а совсем не то число 0xD0, что было в прошлом примере с датчиком bmp280. Это случайное совпадение.

Если дополнить этот код разборщиком, который будет выделять в соответствии с таблицей регистров компоненты времени, и опрашивать устройство каждую секунду, то вот что покажет наша программа:

Результат чтения регистров времени счётчика DS3231
Результат чтения регистров времени счётчика DS3231
Результат чтения регистров времени счётчика DS3231
Результат чтения регистров времени счётчика DS3231

Кстати, данные регистры можно не только читать, в них можно записывать. Т.е., по сути, выставлять время. Например, чтобы выставить на часах дату и время 23 сентября 18 года (годы отображаются только до 99), 15:10:35, нужно послать такую команду:

ch341iic.I2cReadWrite({&HD0, &H0, &H35, &H10, &H15, &H0, &H23, &H09, &H18}, 0)

Скачать техническое описание на микросхему CH341 и библиотеку CH341DLL.dll

По ссылке ниже архив, в который включены:

  • техническое описание (datasheet) на микросхему CH341;
  • библиотека CH341DLL.dll;
  • USB драйвер для микросхемы CH341 под ОС Windows;
  • демонстрационная программа, переведённая на английский язык;
  • исходники разных демонстрационных программ.
Скачать архив с Depositfiles
Последнее изменениеСуббота, 06 Октябрь 2018 07:59 Прочитано 360 раз

Поделиться

Print Friendly, PDF & Email

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

Убедитесь, что вы вводите (*) необходимую информацию, где нужно
HTML-коды запрещены