Реализация интерфейса I2C с помощью библиотеки CH341DLL на VB.NET
автор:
aave
Для работы нам понадобится:
- датчик 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;
- демонстрационная программа, переведённая на английский язык;
- исходники разных демонстрационных программ.
Download attachments:
Поблагодарить автора:
Поделиться
Related items
48 comments
-
Aneg Воскреснье, 10 Март 2019 08:50 Ссылка на комментарийСофт на русском языке не запускается, на китайском хоть и запускается но выглядит это ужасно ((
-
aave1 Понедельник, 11 Март 2019 20:02 Ссылка на комментарийAneg, эти программы написаны китайцами для устаревшей технологии Windows Forms, и конечно, выглядят устаревшими. Но их назначение, по сути, только продемонстрировать базовые возможности микросхемы CH341. Вряд ли кто-то будет с ними серьёзно работать. Но мой код можно адаптировать под свои задачи и нарисовать такой пользовательский интерфейс, как Вам будет удобнее и красивее. А какой именно файл у вас не запустился? У меня всё работает, но я перепроверю.
-
-
aave1 Понедельник, 21 Октябрь 2019 10:26 Ссылка на комментарийAneg, значит, какого-то системного компонента не хватает. Какое сообщение появляется при запуске CH341PAR_EN.exe? Возможно, это связано с библиотекой tabctl32.ocx. Я добавил её в архив и приложил там же инструкцию. Заново скачайте архив и попробуйте сделать, как там написано.
-
Aneg Вторник, 22 Октябрь 2019 09:48 Ссылка на комментарийПосле выполнения третьей команды выдает это:
---------------------------
RegSvr32
---------------------------
Модуль "tabctl32.ocx" загружен, но не удалось выполнить вызов DllRegisterServer, код ошибки: 0x8002801c.
Для получения дополнительных сведений об этой ошибке выполните поиск в Интернете, указав код ошибки как аргумент поиска. -
Aneg Вторник, 22 Октябрь 2019 10:06 Ссылка на комментарийУра! Все таки получилось с помощью Windows PowerShell (администратор).
Нужно нажать правой кнопкой мыши на Пуск и выбрать из контекстного меню пункт «Windows PowerShell (администратор)». А там уже выполнить три команды из readme.txt.
Спасибо!!! -
Aneg Среда, 23 Октябрь 2019 07:24 Ссылка на комментарийЭкспортируемые функции, классы, абстракция, наследование, диаграмма классов??? А могли бы вы объяснить все это для людей владеющих только языком С. В какой среде это компилируется, как подключить DLL, как изменять интерфейс приложения (добавление возможности выбора скорости при работе в режиме I2C)?
-
aave1 Среда, 23 Октябрь 2019 08:07 Ссылка на комментарийВсё это описано для платформы .NET. Соответственно, для разработки используется среда Visual Studio (или более новые легковесные Visual Studio Code, Monodevelop или другие). Библиотека CH341DLL.dll размещается в директории с разрабатываемым приложением, а далее с помощью директивы Dllimport сообщается компилятору, к какой функции библиотеки обращаться. Вообще, тема чрезвычайно обширная, её не расскажешь так в двух предложениях. Что касается изменения интерфейса - так это можно сделать, используя китайские исходники. Я обновил архив ещё раз, добавил папку "CH341EVT\CH341VB\", в ней лежат исходники на VB6, а также папку "CH341EVT\CH341PAR\CH341PAR\", в которой исходники на C++.
-
maxic81 Понедельник, 11 Ноябрь 2019 08:44 Ссылка на комментарийСпасибо за программку, пользуюсь почти год, так же плату использую для скриптера MSP430.
Так как сам я не программист, много не понятно. -
-
maxic81 Вторник, 12 Ноябрь 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 Ноябрь 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 Ноябрь 2019 17:35 Ссылка на комментарийТочно не знаю, но есть подозрение на Большую Совковую Лопату, ))) рыть ей очень не удобно (к удалению )
-
maxic81 Суббота, 23 Ноябрь 2019 14:36 Ссылка на комментарийПрошивка контроллеров MSP430 с помощью Bootstrap Loader
https://levap.ru/proshivka-kontrollierov-msp430-s-pomoshchiu-bootstrap-loader/ -
Peter Суббота, 30 May 2020 07:57 Ссылка на комментарийА есть возможность использовать 10-бит адресацию с помощью этой библиотеки? если да, то как это сделать?
-
aave1 Воскреснье, 31 May 2020 08:44 Ссылка на комментарийPeter, поясните свой вопрос. Использовать 10-битную адресацию чего? Где?
-
Роман Среда, 10 Март 2021 21:58 Ссылка на комментарийдобрый день
при попытке запустить ваше приложение CH341PAR_EN.exe
виндовс 10(х64 версия 1909) ругается на отсутствие msstdfmt.dll -
Алекс Среда, 15 Сентябрь 2021 07:53 Ссылка на комментарийОтличная статья, спасибо! Не понятно, библиотека позволяет отследить наличие устройства на линии или нет, чтобы можно было в Вашу программу добавить сканер i2c. Если Вам известно о программной возможности отследить ответ устройства, поделитесь пожалуйста.
-
aave1 Среда, 15 Сентябрь 2021 19:08 Ссылка на комментарийАлекс, спасибо! Возможность сканирования устройств на шине - это не возможность библиотеки. Вы можете самостоятельно быстро опросить все адреса I2C на шине, и при наличии на ней устройства с данным адресом оно должно ответить на запрос. Правда, есть и исключения. Во-первых, не все устройства отвечают или не на любой скорости, а во-вторых, по одному адресу может быть несколько устройств. Вы должны примерно понимать, что у вас подключено к шине.
-
kalobyte Воскреснье, 29 Январь 2023 02:34 Ссылка на комментарийа какое значение надо ввести в SetOutput, чтобы выводы д0-д7 были выходами и установить их в 1
я использовал chip.Set_D5_D0(0xff, 0x02); и у меня сразу горит 2й светодиод, а все остальные не горят, кроме 2х последних, т.к. они входы с подтяжкой по дефолту настроены
перечитал кучу всяких док, но нигде толком не описывается режим гпио -
Aave1 Понедельник, 30 Январь 2023 07:46 Ссылка на комментарийKalobyte, посмотрите документ https://cloud.mail.ru/public/LFix/1b42LgWTU, раздел "Programming I/O individually" на стр.12.
-
kalobyte Среда, 08 Февраль 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 Февраль 2023 07:36 Ссылка на комментарийДа. Только обратите внимание, что в iEnable биты идут не по порядку. За направление битов отвечают биты 1 и 3, за состояние биты 0 и 2.
-
kalobyte Понедельник, 13 Февраль 2023 09:40 Ссылка на комментарийтут еще вот что всплыло
если сделать выводы выходами и установить в 1, то при закрытии устройства и даже при вызове функции сброса - выводы не переходят в дефолтное состояние входов
однако вызов сброса все таки переключает с синего на красный светодиод
но я запускал небольшую прогу из архива китайцев в папке SPEED341
она тестирует какую-то скорость и вот там вызывается просто функция закрытия устройства и выводы приходят в исходное состояние входов
как так? -
Aave1 Среда, 15 Февраль 2023 13:50 Ссылка на комментарийKalobyte,
Не знаю, если мне надо все выводы D7..D0 оставить при отключении в каком-то состоянии, я просто делаю это явно, всё работает, вот пример:
https://youtube.com/shorts/GKs6R5ISf4g?feature=share -
Александр Воскреснье, 30 Апрель 2023 12:37 Ссылка на комментарийЗдравствуйте. Не могу понять, функцией CH341OpenDevice получаем что, дескриптор устройства при помощи хендла драйвера. И в дальнейших функциях его используем или во всех API используем хендл драйвера. Извините с высокоуровневыми языками не знаком,- немножко ассемблера
-
-
Александр Понедельник, 01 May 2023 04:51 Ссылка на комментарийА ещё вопрос можно? Для режима только ЕРР Без всяких заморочек, каких функций будет достаточно, чтобы считать с устройства и сохранить в файл и передать на устройство из файла к примеру с расширением .bin. То есть без всякой инициализации внешнего устройства и прочими излишествами. Насколько возможно упростить процедуры, для использования с одним и тем же устройством соответствующей програмки?
-
Aave1 Вторник, 02 May 2023 06:22 Ссылка на комментарийАлександр, как минимум, необходимы Ch341OpenDevice(), Ch341InitParallel(), Ch341EppWriteData(), Ch341EppWriteAddr(), Ch341EppReadData() и Ch351EppReadAddr().
-
Александр Среда, 03 May 2023 13:36 Ссылка на комментарийБез этих само собой не получится связи. Я имею в виду CH341GetVersion, CH341GetDrvVersion, CH341SetExclusive, CH341GetVerIC, CH341GetDeviceName, а также не понятна необходимость CH341AbortRead и CH341AbortWrite. Ведь предполагается, что питание непрерывно у ПК и внешнего устройства, или здесь что-то другое предусматривается. А CH341SetExclusive мне кажется совсем лишним ведь драйвер открываю с помощью CreateFile, а в неё уже заложена монополия использования. Как считаете, из перечисленных функций какие всё таки необходимо оставить?
-
aave1 Среда, 03 May 2023 18:00 Ссылка на комментарийАлександр, это в основном всё вспомогательные функции, предоставляющие информацию о версии драйвера, библиотеки и чипа, и т.п. Только CH341AbortRead() и CH341AbortWrite() предназначены для прерывания текущей операции чтения и записи, соответственно. Функция CH341SetExclusive() позволяет предотвратить совместную работу через dll другим приложениям или вашему же приложению. Смотрите, нужны ли они вам. В принципе, это всё можно пропустить.
-
Александр Четверг, 04 May 2023 04:26 Ссылка на комментарийОгромное СПАСИБО! Конечно, у меня ещё много "глупых" вопросов но, думаю в процессе написания программы некоторые отвалятся автоматом.Ещё раз благодарю. Добавьте карту "МИР".
-
aave1 Четверг, 04 May 2023 19:00 Ссылка на комментарийАлександр, не бывает "глупых" вопросов, есть глупые ответы :) Надеюсь, мои вам помогли
-
Александр Суббота, 06 May 2023 05:04 Ссылка на комментарийЕщё как помогли! Сейчас подкрадываюсь к структурам передачи команд управления; struct _USB_SETUP_PKT и struct _WIN32_COMMAND. Пока пробую самостоятельно прожевать. Если я правильно понимаю, то они должны располагаться где-то в iBuffer, а вот каким образом организовать размещение? Я представляю себе буфер одной строкой как в .bin файле и в каком месте должна быть вся структура управления не пойму никак.Да и, с Праздником победы!
-
Александр Суббота, 06 May 2023 05:31 Ссылка на комментарийИли я не с того конца зашёл. Вот к примеру команда: CH341EppReadAddr (iIndex, oBuffer, ioLength); она уже содержит структуру _WIN32_COMMAND или её туда нужно ещё разместить? А эта CH341EppWriteData? Пытаюсь разобраться в Вашем ch341_vb-master что за чем, почему и для чего да ещё и как это всё стыкуется. Получается пока не очень.
-
aave1 Суббота, 06 May 2023 19:20 Ссылка на комментарийАлександр, вы слишком глубоко копаете. Тут используется динамически загружаемая библиотека ch341dll.dll, которую предоставляет разработчик чипа CH341. Всю низкоуровневую работу библиотека берёт на себя. Всё, что вам нужно сделать в своём коде - это правильно объявить функцию, соблюдая соответствие сигнатуры и типов (аргументов и возвращаемых). При вызове функции операционная система ищет точку входа для данной функции в библиотеке, и выполняет её (по сути библиотека DLL - это исполняемый файл, как и EXE). Попробуйте поискать в интернете как работать с DLL на том высокоуровневом языке программирования, на котором вы пишете.
PS. Спасибо за поздравления, вас тоже с праздником! -
Александр Воскреснье, 07 May 2023 13:23 Ссылка на комментарийВполне может быть. Тогда подскажите в CH341SetDeviceNotify iIndex - это хендл драйвера или устройства, а iDeviceID - это ИД устройства в ПК который можно определить открыв панель управления?
-
aave1 Воскреснье, 07 May 2023 20:52 Ссылка на комментарийИндекс - это номер устройства, начиная с "0". А идентификатор - это необязательный параметр, представляющий указатель на строку, содержащую ID устройства. Я этот параметр никогда не использовал, не скажу для чего он нужен. Обычно хватает индекса.
-
Александр Понедельник, 08 May 2023 05:00 Ссылка на комментарийПонял. Значит перед CH341OpenDevice нужно использовать CH341GetVerIC или CH341GetDrvVersion. Извините за назойливость, но в заголовочном файле везде одно и тоже название iIndex, а в .asm важна последовательность. По этому у меня устройство и не открывается из-за того, что не могу определить что за чем следует и что из чего получаем. На форумах даже близко подобной инфы нет. У всех проблемы посерьёзней типа как вставить микросхему в программатор или где находится HEX файл и т.д.. А толковых учебников не нашёл, литература больше похожа на хвастовство,- скачками от одного к другому и ни о чём конкретно, предполагая , что читатель уже умеет программировать. Если бы я знал как программировать, то зачем мне ихняя литература. Извините наболело. Ещё раз извините. В Ассемблере приходится учитывать все тонкости. Запись текста программы в разнобой не канает как в Си. Вот и приходится допытываться что и как. Библиотеку использую статически через библиотеку импорта CH341DLL.lib, в дизассемблерном файле видно, что обращение к библиотеке идёт, но результат один и тот-же INVALID_HANDLE_VALUE что делать с этим ума не приложу. Что не так?
-
Александр Понедельник, 08 May 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.
-
-
Александр Понедельник, 08 May 2023 13:34 Ссылка на комментарийАссемблер, но суть у всех языков одинакова. Я так думаю.
-
aave1 Понедельник, 08 May 2023 21:38 Ссылка на комментарийК сожалению, я с ассемблером знаком совершенно поверхностно. Но у меня вызывает некоторое сомнение, что вы хендл драйвера записываете в iIndex. Попробуйте записать ноль и вызвать CH341OpenDevice. Будет тоже возвращаться INVALID_HANDLE_VALUE?
-
Александр Вторник, 09 May 2023 04:50 Ссылка на комментарийУРА! Заработало! Ноль вместо iIndex. CH341OpenDevice, 0.Странно, а почему так? Шучу конечно. Вот что значит опыт.
-
Александр Вторник, 09 May 2023 05:33 Ссылка на комментарийВсего-то нужно было организовать автоподбор номера. Всё гениальное просто. СПАСИБО.
-
