Рейтинг@Mail.ru

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

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

Для работы нам понадобится:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Если при запуске программы CH341PAR_EN.exe появляется сообщение об ошибке tabctl32.ocx, то необходимо сделать следующее. Скачать приложенный к статье архив. Найти в нём файл tabctl32.ocx и разместить его в директории c:\Windows\syswow64. Затем запустить от имени Администратора командную строку или Windows PowerShell и ввести по порядку следующие команды:
cd c:\Windows\syswow64
regsvr32 /u tabctl32.ocx
regsvr32 tabctl32.ocx

Если при запуске CH341PAR_EN.exe возникает ошибка tabctl32.ocx, необходимо зарегистрировать его в системе с помощью утилиты regsvr32
Если при запуске CH341PAR_EN.exe возникает ошибка tabctl32.ocx, необходимо зарегистрировать его в системе с помощью утилиты regsvr32

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

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

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

Иерархия классов, поддерживающих работу микросхемы CH341 в различных режимах
Иерархия классов, поддерживающих работу микросхемы CH341 в различных режимах

Базовый абстрактный класс данной иерархии будет таким:

Класс с общими методами для работы с микросхемами 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 с помощью микросхемы CH341 по интерфейсу I2C
Чтение датчика BMP280 с помощью микросхемы CH341 по интерфейсу I2C

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

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

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

Чтение датчика BMP280 с помощью CH341A по I2C
Чтение датчика BMP280 с помощью CH341A по I2C

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

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

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

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

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

Чтобы прочитать эти регистры, мы должны запросить 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. Это случайное совпадение.

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

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

Кстати, данные регистры можно не только читать, в них можно записывать. Т.е., по сути, выставлять время. Например, чтобы выставить на часах дату и время 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;
  • демонстрационная программа, переведённая на английский язык;
  • исходники разных демонстрационных программ.
Last modified onСреда, 15 Февраль 2023 19:58 Read 15580 times
Ключевые слова: :

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

Поделиться

Print Friendly, PDF & Email

48 comments

  • Aneg
    Aneg Воскреснье, 10 Март 2019 08:50 Ссылка на комментарий

    Софт на русском языке не запускается, на китайском хоть и запускается но выглядит это ужасно ((

  • aave1
    aave1 Понедельник, 11 Март 2019 20:02 Ссылка на комментарий

    Aneg, эти программы написаны китайцами для устаревшей технологии Windows Forms, и конечно, выглядят устаревшими. Но их назначение, по сути, только продемонстрировать базовые возможности микросхемы CH341. Вряд ли кто-то будет с ними серьёзно работать. Но мой код можно адаптировать под свои задачи и нарисовать такой пользовательский интерфейс, как Вам будет удобнее и красивее. А какой именно файл у вас не запустился? У меня всё работает, но я перепроверю.

  • Aneg
    Aneg Пятница, 18 Октябрь 2019 10:37 Ссылка на комментарий

    Не запускается файл CH341PAR_EN.exe

  • aave1
    aave1 Понедельник, 21 Октябрь 2019 10:26 Ссылка на комментарий

    Aneg, значит, какого-то системного компонента не хватает. Какое сообщение появляется при запуске CH341PAR_EN.exe? Возможно, это связано с библиотекой tabctl32.ocx. Я добавил её в архив и приложил там же инструкцию. Заново скачайте архив и попробуйте сделать, как там написано.

  • Aneg
    Aneg Вторник, 22 Октябрь 2019 09:48 Ссылка на комментарий

    После выполнения третьей команды выдает это:
    ---------------------------
    RegSvr32
    ---------------------------
    Модуль "tabctl32.ocx" загружен, но не удалось выполнить вызов DllRegisterServer, код ошибки: 0x8002801c.

    Для получения дополнительных сведений об этой ошибке выполните поиск в Интернете, указав код ошибки как аргумент поиска.

  • Aneg
    Aneg Вторник, 22 Октябрь 2019 10:06 Ссылка на комментарий

    Ура! Все таки получилось с помощью Windows PowerShell (администратор).
    Нужно нажать правой кнопкой мыши на Пуск и выбрать из контекстного меню пункт «Windows PowerShell (администратор)». А там уже выполнить три команды из readme.txt.
    Спасибо!!!

  • Aneg
    Aneg Среда, 23 Октябрь 2019 07:24 Ссылка на комментарий

    Экспортируемые функции, классы, абстракция, наследование, диаграмма классов??? А могли бы вы объяснить все это для людей владеющих только языком С. В какой среде это компилируется, как подключить DLL, как изменять интерфейс приложения (добавление возможности выбора скорости при работе в режиме I2C)?

  • aave1
    aave1 Среда, 23 Октябрь 2019 08:07 Ссылка на комментарий

    Всё это описано для платформы .NET. Соответственно, для разработки используется среда Visual Studio (или более новые легковесные Visual Studio Code, Monodevelop или другие). Библиотека CH341DLL.dll размещается в директории с разрабатываемым приложением, а далее с помощью директивы Dllimport сообщается компилятору, к какой функции библиотеки обращаться. Вообще, тема чрезвычайно обширная, её не расскажешь так в двух предложениях. Что касается изменения интерфейса - так это можно сделать, используя китайские исходники. Я обновил архив ещё раз, добавил папку "CH341EVT\CH341VB\", в ней лежат исходники на VB6, а также папку "CH341EVT\CH341PAR\CH341PAR\", в которой исходники на C++.

  • maxic81
    maxic81 Понедельник, 11 Ноябрь 2019 08:44 Ссылка на комментарий

    Спасибо за программку, пользуюсь почти год, так же плату использую для скриптера MSP430.
    Так как сам я не программист, много не понятно.

  • Aneg
    Aneg Вторник, 12 Ноябрь 2019 07:16 Ссылка на комментарий

    Что такое скриптер MSP430?

  • maxic81
    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/

  • aave1
    aave1 Вторник, 12 Ноябрь 2019 17:15 Ссылка на комментарий

    maxic81, рад помочь!

  • maxic81
    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/

  • Aneg
    Aneg Четверг, 14 Ноябрь 2019 09:02 Ссылка на комментарий

    А как расшифровывается аббревиатура BSL?

  • maxic81
    maxic81 Четверг, 14 Ноябрь 2019 17:35 Ссылка на комментарий

    Точно не знаю, но есть подозрение на Большую Совковую Лопату, ))) рыть ей очень не удобно (к удалению )

  • maxic81
    maxic81 Суббота, 23 Ноябрь 2019 14:36 Ссылка на комментарий

    Прошивка контроллеров MSP430 с помощью Bootstrap Loader
    https://levap.ru/proshivka-kontrollierov-msp430-s-pomoshchiu-bootstrap-loader/

  • Peter
    Peter Суббота, 30 May 2020 07:57 Ссылка на комментарий

    А есть возможность использовать 10-бит адресацию с помощью этой библиотеки? если да, то как это сделать?

  • aave1
    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
    aave1 Среда, 15 Сентябрь 2021 19:08 Ссылка на комментарий

    Алекс, спасибо! Возможность сканирования устройств на шине - это не возможность библиотеки. Вы можете самостоятельно быстро опросить все адреса I2C на шине, и при наличии на ней устройства с данным адресом оно должно ответить на запрос. Правда, есть и исключения. Во-первых, не все устройства отвечают или не на любой скорости, а во-вторых, по одному адресу может быть несколько устройств. Вы должны примерно понимать, что у вас подключено к шине.

  • kalobyte
    kalobyte Воскреснье, 29 Январь 2023 02:34 Ссылка на комментарий

    а какое значение надо ввести в SetOutput, чтобы выводы д0-д7 были выходами и установить их в 1
    я использовал chip.Set_D5_D0(0xff, 0x02); и у меня сразу горит 2й светодиод, а все остальные не горят, кроме 2х последних, т.к. они входы с подтяжкой по дефолту настроены

    перечитал кучу всяких док, но нигде толком не описывается режим гпио

  • Aave1
    Aave1 Понедельник, 30 Январь 2023 07:46 Ссылка на комментарий

    Kalobyte, посмотрите документ https://cloud.mail.ru/public/LFix/1b42LgWTU, раздел "Programming I/O individually" на стр.12.

  • kalobyte
    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
    Aave1 Понедельник, 13 Февраль 2023 07:36 Ссылка на комментарий

    Да. Только обратите внимание, что в iEnable биты идут не по порядку. За направление битов отвечают биты 1 и 3, за состояние биты 0 и 2.

  • kalobyte
    kalobyte Понедельник, 13 Февраль 2023 09:40 Ссылка на комментарий

    тут еще вот что всплыло
    если сделать выводы выходами и установить в 1, то при закрытии устройства и даже при вызове функции сброса - выводы не переходят в дефолтное состояние входов

    однако вызов сброса все таки переключает с синего на красный светодиод

    но я запускал небольшую прогу из архива китайцев в папке SPEED341
    она тестирует какую-то скорость и вот там вызывается просто функция закрытия устройства и выводы приходят в исходное состояние входов

    как так?

  • Aave1
    Aave1 Среда, 15 Февраль 2023 13:50 Ссылка на комментарий

    Kalobyte,
    Не знаю, если мне надо все выводы D7..D0 оставить при отключении в каком-то состоянии, я просто делаю это явно, всё работает, вот пример:
    https://youtube.com/shorts/GKs6R5ISf4g?feature=share

  • Александр
    Александр Воскреснье, 30 Апрель 2023 12:37 Ссылка на комментарий

    Здравствуйте. Не могу понять, функцией CH341OpenDevice получаем что, дескриптор устройства при помощи хендла драйвера. И в дальнейших функциях его используем или во всех API используем хендл драйвера. Извините с высокоуровневыми языками не знаком,- немножко ассемблера

  • aave1
    aave1 Воскреснье, 30 Апрель 2023 17:34 Ссылка на комментарий

    Александр, да, всё верно.

  • Александр
    Александр Понедельник, 01 May 2023 04:51 Ссылка на комментарий

    А ещё вопрос можно? Для режима только ЕРР Без всяких заморочек, каких функций будет достаточно, чтобы считать с устройства и сохранить в файл и передать на устройство из файла к примеру с расширением .bin. То есть без всякой инициализации внешнего устройства и прочими излишествами. Насколько возможно упростить процедуры, для использования с одним и тем же устройством соответствующей програмки?

  • Aave1
    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
    aave1 Среда, 03 May 2023 18:00 Ссылка на комментарий

    Александр, это в основном всё вспомогательные функции, предоставляющие информацию о версии драйвера, библиотеки и чипа, и т.п. Только CH341AbortRead() и CH341AbortWrite() предназначены для прерывания текущей операции чтения и записи, соответственно. Функция CH341SetExclusive() позволяет предотвратить совместную работу через dll другим приложениям или вашему же приложению. Смотрите, нужны ли они вам. В принципе, это всё можно пропустить.

  • Александр
    Александр Четверг, 04 May 2023 04:26 Ссылка на комментарий

    Огромное СПАСИБО! Конечно, у меня ещё много "глупых" вопросов но, думаю в процессе написания программы некоторые отвалятся автоматом.Ещё раз благодарю. Добавьте карту "МИР".

  • aave1
    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
    aave1 Суббота, 06 May 2023 19:20 Ссылка на комментарий

    Александр, вы слишком глубоко копаете. Тут используется динамически загружаемая библиотека ch341dll.dll, которую предоставляет разработчик чипа CH341. Всю низкоуровневую работу библиотека берёт на себя. Всё, что вам нужно сделать в своём коде - это правильно объявить функцию, соблюдая соответствие сигнатуры и типов (аргументов и возвращаемых). При вызове функции операционная система ищет точку входа для данной функции в библиотеке, и выполняет её (по сути библиотека DLL - это исполняемый файл, как и EXE). Попробуйте поискать в интернете как работать с DLL на том высокоуровневом языке программирования, на котором вы пишете.

    PS. Спасибо за поздравления, вас тоже с праздником!

  • Александр
    Александр Воскреснье, 07 May 2023 13:23 Ссылка на комментарий

    Вполне может быть. Тогда подскажите в CH341SetDeviceNotify iIndex - это хендл драйвера или устройства, а iDeviceID - это ИД устройства в ПК который можно определить открыв панель управления?

  • aave1
    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.

  • Aave1
    Aave1 Понедельник, 08 May 2023 11:28 Ссылка на комментарий

    Вы на каком языке пишете?

  • Александр
    Александр Понедельник, 08 May 2023 13:34 Ссылка на комментарий

    Ассемблер, но суть у всех языков одинакова. Я так думаю.

  • aave1
    aave1 Понедельник, 08 May 2023 21:38 Ссылка на комментарий

    К сожалению, я с ассемблером знаком совершенно поверхностно. Но у меня вызывает некоторое сомнение, что вы хендл драйвера записываете в iIndex. Попробуйте записать ноль и вызвать CH341OpenDevice. Будет тоже возвращаться INVALID_HANDLE_VALUE?

  • Александр
    Александр Вторник, 09 May 2023 04:50 Ссылка на комментарий

    УРА! Заработало! Ноль вместо iIndex. CH341OpenDevice, 0.Странно, а почему так? Шучу конечно. Вот что значит опыт.

  • Александр
    Александр Вторник, 09 May 2023 05:33 Ссылка на комментарий

    Всего-то нужно было организовать автоподбор номера. Всё гениальное просто. СПАСИБО.

  • aave1
    aave1 Вторник, 09 May 2023 20:48 Ссылка на комментарий

    Ну наконец-то дело сдвинулось) Поздравляю!