Реализация интерфейса I2C с помощью библиотеки CH341DLL на VB.NET
Для работы нам понадобится:
- датчик BMP280;
- отладочная плата с микросхемой CH341A;
- real-time clock DS3231;
- макетная плата (breadboard);
- соединительные провода.
1Описание микросхемы CH341
Китайская микросхема CH341 сейчас используется очень широко. Она представляет собой преобразователь из USB в порт принтера, параллельный порт в режиме EPP или MEM, последовательные интерфейсы UART/RS232/RS485/RS422, SPI и I2C, и другие. Также микросхема может принимать различные Vendor ID и Product ID. В общем, отличная вещь.
Для управления микросхемой CH341 необходим компьютер или иное устройство, обладающее USB.

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

Приведённые рисунки взяты из технического описания (datasheet) на микросхему. Ссылка на скачивание datasheet – в конце статьи.
Для изучения возможностей микросхемы хорошо иметь какую-либо отладочную плату вроде такой, как показано на фотографии:


Это продуманная плата: на ней все выводы, относящиеся к одному режиму работы, собраны вместе, и к ним очень удобно подключаться. Все ножки обозначены на плате с помощью шелкографии. Есть перемычки для переключения между режимами UART и IIC/SPI, выбор напряжения логической единицы 3 или 5 вольт, выбор питания от внешнего источника или по USB. Присутствует кварцевый резонатор для генерирования тактовой частоты микросхемы. На плате присутствует разъём для подключения к порту USB компьютера. У китайцев такую плату можно приобрести по хорошей цене.
2Демонстрационная программа для работы с микросхемой CH341
Для работы с данной микросхемой имеется библиотека CH341DLL.dll от производителя. Она позволяет переключить микросхему в любой из поддерживаемых режимов и осуществлять с ней информационный обмен. Также имеются демонстрационные программы, которые используют данную библиотеку. К сожалению, они все на китайском языке :) Я перевёл одну из программ на английский и добавил возможность выбора скорости при работе в режиме I2C, благо исходники открытые. На рисунке мы прочитали регистр ID датчика давления и температуры bmp280 по интерфейсу IIC:

Итак, что мы видим на этом скриншоте? Программа содержит ряд вкладок. Каждая вкладка отвечает за тот режим работы, который отмечен в названии вкладки. В нашем случае открыта вкладка 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". Ссылка на скачивание переведённой программы – в конце статьи.
Если при запуске программы CH341PAR_EN.exe появляется сообщение об ошибке tabctl32.ocx, то необходимо сделать следующее. Скачать приложенный к статье архив. Найти в нём файл tabctl32.ocx и разместить его в директории c:\Windows\syswow64. Затем запустить от имени Администратора командную строку или Windows PowerShell и ввести по порядку следующие команды:
cd c:\Windows\syswow64
regsvr32 /u tabctl32.ocx
regsvr32 tabctl32.ocx

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

Базовый абстрактный класс данной иерархии будет таким:
Класс с общими методами для работы с микросхемами CH341 (разворачивается)
Imports System.Runtime.InteropServices Imports System.Text Namespace Ch341 ''' <summary> ''' Базовый класс для работы с микросхемой CH341A. ''' </summary> Public MustInherit Class Ch341Device ''' <summary> ''' Путь к библиотеке CH341DLL.DLL. ''' </summary> Public Const DLL_PATH As String = "CH341DLL.DLL" ''' <summary> ''' Неверный дескриптор. ''' </summary> Protected 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> Protected Sub New(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(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(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 ch341 As New I2cMaster(0, True, I2cMaster.I2cSpeed.Standard)
Здесь "0" – индекс устройства в системе (нумерация с нуля). Второй параметр "True" означает, что мы хотим использовать устройство в эксклюзивном режиме (другие процессы или объекты не смогут открыть устройство, пока мы не «отпустим» его). Третий параметр – скорость передачи данных по I2C из списка стандартных (в данном случае 100 кГц).
К сожалению, библиотека CH341DLL не имеет методов, позволяющих определить количество подключённых устройств.
Помните, выше мы обсуждали, как прочитать из датчика BMP280 регистр с идентификатором? Сделаем теперь то же самое с помощью нашего только что созданного объекта ch341. Прочитаем регистр ID и выведем его значение на консоль:
Dim buf As Byte() = ch341.I2cReadWrite({&HEE, &HD0}, 1) For Each b As Byte In buf Console.Write(b.ToString("X")) 'в hex формате Console.Write(" ") Next Console.WriteLine(" ")
В конце работы не забудем закрыть устройство:
ch341.CloseDevice()
Полную и самую последнюю версию кода для работы с микросхемой CH341 можно скачать с репозитория на GitHub.
Результат работы программы, выведенный на консоль, будет выглядеть примерно так:

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

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

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

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

Чтобы прочитать эти регистры, мы должны запросить 19 байт, начиная с адреса 0x00 и заканчивая адресом 0x12:
Dim buf As Byte() = ch341.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. Это случайное совпадение.
Если дополнить этот код разборщиком, который будет выделять в соответствии с таблицей регистров компоненты времени, и опрашивать устройство каждую секунду, то вот что покажет наша программа:


Кстати, данные регистры можно не только читать, в них можно записывать. Т.е., по сути, выставлять время. Например, чтобы выставить на часах дату и время 23 сентября 18 года (годы отображаются только от 0 до 99), 15:10:35, нужно послать такую команду:
ch341.I2cReadWrite({&HD0, &H0, &H35, &H10, &H15, &H0, &H23, &H09, &H18}, 0)
Скачать техническое описание на микросхему CH341 и библиотеку CH341DLL.dll
По ссылке ниже архив, в который включены:
- техническое описание (datasheet) на микросхему CH341;
- библиотека CH341DLL.dll, через которую осуществляется вся работа с микросхемой;
- USB драйвер для микросхемы CH341 под ОС Windows;
- демонстрационная программа, переведённая на английский язык;
- исходники разных демонстрационных программ.
Скачать вложения:
Поблагодарить автора:
Поделиться
Похожие материалы (по тегу)
48 комментарии
-
Aneg 10.03.2019 08:50 Комментировать
Софт на русском языке не запускается, на китайском хоть и запускается но выглядит это ужасно ((
-
aave1 11.03.2019 20:02 Комментировать
Aneg, эти программы написаны китайцами для устаревшей технологии Windows Forms, и конечно, выглядят устаревшими. Но их назначение, по сути, только продемонстрировать базовые возможности микросхемы CH341. Вряд ли кто-то будет с ними серьёзно работать. Но мой код можно адаптировать под свои задачи и нарисовать такой пользовательский интерфейс, как Вам будет удобнее и красивее. А какой именно файл у вас не запустился? У меня всё работает, но я перепроверю.
-
-
aave1 21.10.2019 10:26 Комментировать
Aneg, значит, какого-то системного компонента не хватает. Какое сообщение появляется при запуске CH341PAR_EN.exe? Возможно, это связано с библиотекой tabctl32.ocx. Я добавил её в архив и приложил там же инструкцию. Заново скачайте архив и попробуйте сделать, как там написано.
-
Aneg 22.10.2019 09:48 Комментировать
После выполнения третьей команды выдает это:
---------------------------
RegSvr32
---------------------------
Модуль "tabctl32.ocx" загружен, но не удалось выполнить вызов DllRegisterServer, код ошибки: 0x8002801c.
Для получения дополнительных сведений об этой ошибке выполните поиск в Интернете, указав код ошибки как аргумент поиска. -
Aneg 22.10.2019 10:06 Комментировать
Ура! Все таки получилось с помощью Windows PowerShell (администратор).
Нужно нажать правой кнопкой мыши на Пуск и выбрать из контекстного меню пункт «Windows PowerShell (администратор)». А там уже выполнить три команды из readme.txt.
Спасибо!!! -
Aneg 23.10.2019 07:24 Комментировать
Экспортируемые функции, классы, абстракция, наследование, диаграмма классов??? А могли бы вы объяснить все это для людей владеющих только языком С. В какой среде это компилируется, как подключить DLL, как изменять интерфейс приложения (добавление возможности выбора скорости при работе в режиме I2C)?
-
aave1 23.10.2019 08:07 Комментировать
Всё это описано для платформы .NET. Соответственно, для разработки используется среда Visual Studio (или более новые легковесные Visual Studio Code, Monodevelop или другие). Библиотека CH341DLL.dll размещается в директории с разрабатываемым приложением, а далее с помощью директивы Dllimport сообщается компилятору, к какой функции библиотеки обращаться. Вообще, тема чрезвычайно обширная, её не расскажешь так в двух предложениях. Что касается изменения интерфейса - так это можно сделать, используя китайские исходники. Я обновил архив ещё раз, добавил папку "CH341EVT\CH341VB\", в ней лежат исходники на VB6, а также папку "CH341EVT\CH341PAR\CH341PAR\", в которой исходники на C++.
-
maxic81 11.11.2019 08:44 Комментировать
Спасибо за программку, пользуюсь почти год, так же плату использую для скриптера MSP430.
Так как сам я не программист, много не понятно. -
-
maxic81 12.11.2019 17:04 Комментировать
Скриптер MSP430:
https://www.eevblog.com/forum/microcontrollers/(msp430)-modified-bsl-scripter-for-windows-now-works-with-usb-to-uart-adapters/ -
-
maxic81 12.11.2019 18:25 Комментировать
BSL-Scripter
https://www.eevblog.com/forum/microcontrollers/(msp430)-modified-bsl-scripter-for-windows-now-works-with-usb-to-uart-adapters/ -
-
maxic81 14.11.2019 17:35 Комментировать
Точно не знаю, но есть подозрение на Большую Совковую Лопату, ))) рыть ей очень не удобно (к удалению )
-
maxic81 23.11.2019 14:36 Комментировать
Прошивка контроллеров MSP430 с помощью Bootstrap Loader
https://levap.ru/proshivka-kontrollierov-msp430-s-pomoshchiu-bootstrap-loader/ -
Peter 30.05.2020 07:57 Комментировать
А есть возможность использовать 10-бит адресацию с помощью этой библиотеки? если да, то как это сделать?
-
aave1 31.05.2020 08:44 Комментировать
Peter, поясните свой вопрос. Использовать 10-битную адресацию чего? Где?
-
Роман 10.03.2021 21:58 Комментировать
добрый день
при попытке запустить ваше приложение CH341PAR_EN.exe
виндовс 10(х64 версия 1909) ругается на отсутствие msstdfmt.dll -
Алекс 15.09.2021 07:53 Комментировать
Отличная статья, спасибо! Не понятно, библиотека позволяет отследить наличие устройства на линии или нет, чтобы можно было в Вашу программу добавить сканер i2c. Если Вам известно о программной возможности отследить ответ устройства, поделитесь пожалуйста.
-
aave1 15.09.2021 19:08 Комментировать
Алекс, спасибо! Возможность сканирования устройств на шине - это не возможность библиотеки. Вы можете самостоятельно быстро опросить все адреса I2C на шине, и при наличии на ней устройства с данным адресом оно должно ответить на запрос. Правда, есть и исключения. Во-первых, не все устройства отвечают или не на любой скорости, а во-вторых, по одному адресу может быть несколько устройств. Вы должны примерно понимать, что у вас подключено к шине.
-
kalobyte 29.01.2023 02:34 Комментировать
а какое значение надо ввести в SetOutput, чтобы выводы д0-д7 были выходами и установить их в 1
я использовал chip.Set_D5_D0(0xff, 0x02); и у меня сразу горит 2й светодиод, а все остальные не горят, кроме 2х последних, т.к. они входы с подтяжкой по дефолту настроены
перечитал кучу всяких док, но нигде толком не описывается режим гпио -
Aave1 30.01.2023 07:46 Комментировать
Kalobyte, посмотрите документ https://cloud.mail.ru/public/LFix/1b42LgWTU, раздел "Programming I/O individually" на стр.12.
-
kalobyte 08.02.2023 07:27 Комментировать
Aave1
я так боле менее понял уже
public bool SetOutput(uint iEnable, uint iSetDirOut, uint iSetDataOut)
iEnable это 32 бит переменная и в ней надо выставить биты в 1, чтобы эти же биты можно было в iSetDirOut выставить как выходы, т.е. 1
если какой-то бит iEnable не выставлен в 1, то iSetDirOut этот бит даже если выставить в 1 как выход, то это будет проигнорированно в целях защиты от повреждения выходов?
у меня этот твой пдф есть, я его читал и даже на немецком
я уже кстати выставил д0-7 как выходы и зажигал светодиоды -
Aave1 13.02.2023 07:36 Комментировать
Да. Только обратите внимание, что в iEnable биты идут не по порядку. За направление битов отвечают биты 1 и 3, за состояние биты 0 и 2.
-
kalobyte 13.02.2023 09:40 Комментировать
тут еще вот что всплыло
если сделать выводы выходами и установить в 1, то при закрытии устройства и даже при вызове функции сброса - выводы не переходят в дефолтное состояние входов
однако вызов сброса все таки переключает с синего на красный светодиод
но я запускал небольшую прогу из архива китайцев в папке SPEED341
она тестирует какую-то скорость и вот там вызывается просто функция закрытия устройства и выводы приходят в исходное состояние входов
как так? -
Aave1 15.02.2023 13:50 Комментировать
Kalobyte,
Не знаю, если мне надо все выводы D7..D0 оставить при отключении в каком-то состоянии, я просто делаю это явно, всё работает, вот пример:
https://youtube.com/shorts/GKs6R5ISf4g?feature=share -
Александр 30.04.2023 12:37 Комментировать
Здравствуйте. Не могу понять, функцией CH341OpenDevice получаем что, дескриптор устройства при помощи хендла драйвера. И в дальнейших функциях его используем или во всех API используем хендл драйвера. Извините с высокоуровневыми языками не знаком,- немножко ассемблера
-
-
Александр 01.05.2023 04:51 Комментировать
А ещё вопрос можно? Для режима только ЕРР Без всяких заморочек, каких функций будет достаточно, чтобы считать с устройства и сохранить в файл и передать на устройство из файла к примеру с расширением .bin. То есть без всякой инициализации внешнего устройства и прочими излишествами. Насколько возможно упростить процедуры, для использования с одним и тем же устройством соответствующей програмки?
-
Aave1 02.05.2023 06:22 Комментировать
Александр, как минимум, необходимы Ch341OpenDevice(), Ch341InitParallel(), Ch341EppWriteData(), Ch341EppWriteAddr(), Ch341EppReadData() и Ch351EppReadAddr().
-
Александр 03.05.2023 13:36 Комментировать
Без этих само собой не получится связи. Я имею в виду CH341GetVersion, CH341GetDrvVersion, CH341SetExclusive, CH341GetVerIC, CH341GetDeviceName, а также не понятна необходимость CH341AbortRead и CH341AbortWrite. Ведь предполагается, что питание непрерывно у ПК и внешнего устройства, или здесь что-то другое предусматривается. А CH341SetExclusive мне кажется совсем лишним ведь драйвер открываю с помощью CreateFile, а в неё уже заложена монополия использования. Как считаете, из перечисленных функций какие всё таки необходимо оставить?
-
aave1 03.05.2023 18:00 Комментировать
Александр, это в основном всё вспомогательные функции, предоставляющие информацию о версии драйвера, библиотеки и чипа, и т.п. Только CH341AbortRead() и CH341AbortWrite() предназначены для прерывания текущей операции чтения и записи, соответственно. Функция CH341SetExclusive() позволяет предотвратить совместную работу через dll другим приложениям или вашему же приложению. Смотрите, нужны ли они вам. В принципе, это всё можно пропустить.
-
Александр 04.05.2023 04:26 Комментировать
Огромное СПАСИБО! Конечно, у меня ещё много "глупых" вопросов но, думаю в процессе написания программы некоторые отвалятся автоматом.Ещё раз благодарю. Добавьте карту "МИР".
-
aave1 04.05.2023 19:00 Комментировать
Александр, не бывает "глупых" вопросов, есть глупые ответы :) Надеюсь, мои вам помогли
-
Александр 06.05.2023 05:04 Комментировать
Ещё как помогли! Сейчас подкрадываюсь к структурам передачи команд управления; struct _USB_SETUP_PKT и struct _WIN32_COMMAND. Пока пробую самостоятельно прожевать. Если я правильно понимаю, то они должны располагаться где-то в iBuffer, а вот каким образом организовать размещение? Я представляю себе буфер одной строкой как в .bin файле и в каком месте должна быть вся структура управления не пойму никак.Да и, с Праздником победы!
-
Александр 06.05.2023 05:31 Комментировать
Или я не с того конца зашёл. Вот к примеру команда: CH341EppReadAddr (iIndex, oBuffer, ioLength); она уже содержит структуру _WIN32_COMMAND или её туда нужно ещё разместить? А эта CH341EppWriteData? Пытаюсь разобраться в Вашем ch341_vb-master что за чем, почему и для чего да ещё и как это всё стыкуется. Получается пока не очень.
-
aave1 06.05.2023 19:20 Комментировать
Александр, вы слишком глубоко копаете. Тут используется динамически загружаемая библиотека ch341dll.dll, которую предоставляет разработчик чипа CH341. Всю низкоуровневую работу библиотека берёт на себя. Всё, что вам нужно сделать в своём коде - это правильно объявить функцию, соблюдая соответствие сигнатуры и типов (аргументов и возвращаемых). При вызове функции операционная система ищет точку входа для данной функции в библиотеке, и выполняет её (по сути библиотека DLL - это исполняемый файл, как и EXE). Попробуйте поискать в интернете как работать с DLL на том высокоуровневом языке программирования, на котором вы пишете.
PS. Спасибо за поздравления, вас тоже с праздником! -
Александр 07.05.2023 13:23 Комментировать
Вполне может быть. Тогда подскажите в CH341SetDeviceNotify iIndex - это хендл драйвера или устройства, а iDeviceID - это ИД устройства в ПК который можно определить открыв панель управления?
-
aave1 07.05.2023 20:52 Комментировать
Индекс - это номер устройства, начиная с "0". А идентификатор - это необязательный параметр, представляющий указатель на строку, содержащую ID устройства. Я этот параметр никогда не использовал, не скажу для чего он нужен. Обычно хватает индекса.
-
Александр 08.05.2023 05:00 Комментировать
Понял. Значит перед CH341OpenDevice нужно использовать CH341GetVerIC или CH341GetDrvVersion. Извините за назойливость, но в заголовочном файле везде одно и тоже название iIndex, а в .asm важна последовательность. По этому у меня устройство и не открывается из-за того, что не могу определить что за чем следует и что из чего получаем. На форумах даже близко подобной инфы нет. У всех проблемы посерьёзней типа как вставить микросхему в программатор или где находится HEX файл и т.д.. А толковых учебников не нашёл, литература больше похожа на хвастовство,- скачками от одного к другому и ни о чём конкретно, предполагая , что читатель уже умеет программировать. Если бы я знал как программировать, то зачем мне ихняя литература. Извините наболело. Ещё раз извините. В Ассемблере приходится учитывать все тонкости. Запись текста программы в разнобой не канает как в Си. Вот и приходится допытываться что и как. Библиотеку использую статически через библиотеку импорта CH341DLL.lib, в дизассемблерном файле видно, что обращение к библиотеке идёт, но результат один и тот-же INVALID_HANDLE_VALUE что делать с этим ума не приложу. Что не так?
-
Александр 08.05.2023 05:28 Комментировать
С помощью invoke CreateFile, OFFSET DeviceName, GENERIC_READ+GENERIC_WRITE, FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0 получаю хендл драйвера и записываю его в iIndex, затем вызываю CH341OpenDevice, iIndex -получаю INVALID_HANDLE_VALUE, что ещё упустил? Может кто из гостей подскажет? А то я уже наверно до стал aave1.
-
-
-
aave1 08.05.2023 21:38 Комментировать
К сожалению, я с ассемблером знаком совершенно поверхностно. Но у меня вызывает некоторое сомнение, что вы хендл драйвера записываете в iIndex. Попробуйте записать ноль и вызвать CH341OpenDevice. Будет тоже возвращаться INVALID_HANDLE_VALUE?
-
Александр 09.05.2023 04:50 Комментировать
УРА! Заработало! Ноль вместо iIndex. CH341OpenDevice, 0.Странно, а почему так? Шучу конечно. Вот что значит опыт.
-
Александр 09.05.2023 05:33 Комментировать
Всего-то нужно было организовать автоподбор номера. Всё гениальное просто. СПАСИБО.
-