Реализация интерфейса SPI с помощью библиотеки CH341DLL на VB.NET
Для работы нам понадобится:
- отладочная плата с микросхемой CH341A;
- датчик BMP280;
- макетная плата (breadboard);
- соединительные провода.
1Работа с микросхемой CH341в режиме SPI
Напомню, ранее мы уже рассматривали микросхему CH341, её возможности, а также отладочную плату с микросхемой CH341A. Также мы написали базовый класс для общих методов работы с данными микросхемами и общий (базовый) для последовательных режимов класс. Поэтому дублировать эту часть кода не буду. Приведу лишь то, что относится непосредственно к режиму SPI.
Класс для работы с микросхемами CH341 в режиме SPI (разворачивается)
Imports System.Runtime.InteropServices Namespace Ch341 ''' <summary> ''' Работа с CH341 в последовательном режиме SPI. ''' </summary> Public Class SpiMaster Inherits Serial #Region "CTOR" ''' <summary> ''' Открывает устройство в режиме SPI. ''' </summary> ''' <param name="index">Индекс устройства в системе, начиная с 0.</param> ''' <param name="isExclusive">Включить ли эксклюзивное использование чипа.</param> ''' <param name="isDoubleInOut">Число входов-выходов SPI, 0 = single-input single-out (D5 out / D7 in), 1 = double-in and double-out (D4,D5 out / D6,D7 in)</param> ''' <param name="isMsbFirst">MSB первым (стандартный режим).</param> ''' <param name="wires">Число проводников режима SPI.</param> Public Sub New(index As Integer, Optional isExclusive As Boolean = False, Optional wires As WiresCountEnum = WiresCountEnum.Wires4, Optional isMsbFirst As Boolean = True, Optional isDoubleInOut As Boolean = False) MyBase.New(index, isExclusive) Me.WiresCount = wires SetInOutCount(isMsbFirst, isDoubleInOut) End Sub #End Region '/CTOR #Region "ENUMS" ''' <summary> ''' Число проводников режима SPI. ''' </summary> Public Property WiresCount As WiresCountEnum = WiresCountEnum.Wires4 ''' <summary> ''' Число проводов, используемых в SPI. ''' </summary> Public Enum WiresCountEnum As Integer ''' <summary> ''' 3-проводной интерфейс SPI: тактовая частота на выводе DCK2/SCL, данные на DIO/SDA (квази-двунаправленный порт), выбор чипа D0/D1/D2. ''' Скорость примерно 51 кслов/с. ''' </summary> Wires3 ''' <summary> ''' 4-проводной интерфейс SPI: тактовая частота на выводе DCK/D3, выходные данные DOUT/D5, входные данные DIN/D7, выбор чипа D0/D1/D2. ''' Скорость примерно 68 кбайт/с. ''' </summary> Wires4 ''' <summary> ''' 5-проводной интерфейс SPI: Тактовая частота на выводе DCK/D3, выходные данные DOUT/D5 и DOUT2/D4, вхродные DIN/D7 b DIN2/D6, выбор чипа D0/D1/D2. ''' Скорость примерно 30 кбайт*2/с. ''' </summary> Wires5 End Enum #End Region '/ENUMS ''' <summary> ''' Задаёт число входов-выходов SPI, 0 = single-input single-out (D5 out / D7 in), 1 = double-in and double-out (D4,D5 out / D6,D7 in). ''' </summary> ''' <param name="isMsbFirst"></param> ''' <param name="isDoubleInOut"></param> Public Sub SetInOutCount(ByVal isMsbFirst As Boolean, ByVal isDoubleInOut As Boolean) Dim res As Boolean = False Dim mode As Integer = 0 If isDoubleInOut Then mode = 3 End If If isMsbFirst Then mode = mode Or &H80 End If res = CH341SetStream(DeviceIndex, mode) If res Then Return End If Throw New Exception("Ошибка выставления режима SPI.") End Sub #Region "ЧТЕНИЕ И ЗАПИСЬ" ''' <summary> ''' Реализует обмен по 3-проводному интефрейсу SPI. ''' Тактовая частота на выводе DCK2/SCL, данные на DIO/SDA (квази-двунаправленный ввод-вывод), выбор чипа D0/D1/D2, скорость примерно 51 кслов/с. ''' </summary> ''' <param name="iChipSelect">Выбор ведомого: ''' бит 7 = 0 - выбор чипа игнорируется, бит 7 = 1 - данные валидны; ''' биты 1..0 = 00/01/10 соответственно активируют D0/D1/D2 низким уровнем.</param> ''' <param name="iLength">Число байтов для передачи.</param> ''' <param name="ioBuffer">Буфер для записи в DIO, и сюда же сохраняются прочитанные с DIO данные.</param> <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)> Private Shared Function CH341StreamSPI3(ByVal iIndex As Integer, ByVal iChipSelect As Integer, ByVal iLength As Integer, ByVal ioBuffer As Byte()) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function ''' <summary> ''' Реализует обмен по 4-проводному интефрейсу SPI. ''' Тактовая частота на выводе DCK/D3, выходные данные на DOUT/D5, входные данные на DIN/D7, выбор чипа D0/D1/D2, скорость примерно 68 кбайт/с. ''' </summary> ''' <param name="iChipSelect">Выбор ведомого: ''' бит 7 = 0 - выбор чипа игнорируется, бит 7 = 1 - данные валидны; ''' биты 1..0 = 00/01/10 соответственно активируют D0/D1/D2 низким уровнем.</param> ''' <param name="iLength">Число байтов для передачи.</param> ''' <param name="ioBuffer">Буфер для записи в DOUT, и сюда же сохраняются прочитанные с DIN данные.</param> <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)> Private Shared Function CH341StreamSPI4(ByVal iIndex As Integer, ByVal iChipSelect As Integer, ByVal iLength As Integer, ByVal ioBuffer As Byte()) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function ''' <summary> ''' Реализует обмен по 5-проводному интефрейсу SPI. ''' Тактовая частота на выводе DCK/D3, выходные данные на DOUT/D5 и DOUT2/D4, входные на DIN/D7 и DIN2/D6, выбор чипа D0/D1/D2, скорость примерно 30*2 кбайт/с. ''' </summary> ''' <param name="iChipSelect">Выбор ведомого: ''' бит 7 = 0, вывод CS игнорируется, бит 7 = 1 данные валидны; ''' биты 1..0 = 00/01/10 соответственно активируют D0/D1/D2 низким уровнем.</param> ''' <param name="iLength">Число байтов для передачи.</param> ''' <param name="ioBuffer">Буфер для записи в DOUT, и сюда же сохраняются прочитанные с DIN данные.</param> ''' <param name="ioBuffer2">Буфер для записи в DOUT2, и сюда же сохраняются прочитанные с DIN2 данные.</param> <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)> Private Shared Function CH341StreamSPI5(ByVal iIndex As Integer, ByVal iChipSelect As Integer, ByVal iLength As Integer, ByVal ioBuffer As Byte(), ByVal ioBuffer2 As Byte()) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function ''' <summary> ''' Записывает заданный массив и возвращает считанный массив по SPI. ''' </summary> ''' <param name="writeBuffer">Буфер для записи.</param> ''' <param name="readLength">Число байтов для чтения.</param> Public Function StreamSpi(ByVal writeBuffer As Byte(), ByVal readLength As Integer) As Byte() 'TODO "Забил гвоздями" вывод CS. Надо реализовать возможность управлять им. Dim res As Boolean = False Dim writeLength As Integer = writeBuffer.Length ReDim Preserve writeBuffer(writeLength + readLength - 1) Select Case WiresCount Case WiresCountEnum.Wires3 res = CH341StreamSPI3(DeviceIndex, &H80, writeBuffer.Length, writeBuffer) Case WiresCountEnum.Wires4 res = CH341StreamSPI4(DeviceIndex, &H80, writeBuffer.Length, writeBuffer) Case WiresCountEnum.Wires5 Dim readBuffer2(readLength - 1) As Byte res = CH341StreamSPI5(DeviceIndex, &H80, writeBuffer.Length, writeBuffer, readBuffer2) End Select If res Then Return writeBuffer.Skip(writeLength).Take(readLength).ToArray() End If Throw New Exception("Ошибка чтения или записи массива байтов по SPI.") End Function <DllImport(DLL_PATH, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)> Private Shared Function CH341BitStreamSPI(ByVal iIndex As Integer, ByVal iLength As Integer, ByVal ioBuffer As Byte()) As <MarshalAs(UnmanagedType.Bool)> Boolean End Function ''' <summary> ''' Управляет битовой передачей по SPI в 4- и 5-проводном режимах. ''' Тактовая частота на выводе DCK/D3, по умолчанию LOW, DOUT/D5 и DOUT2/D4 - выходы по срезу, DIN/D7 и DIN2/D6 по спаду. */ ''' One byte in ioBuffer corresponds to D7-D0 pin, bit 5 is output to DOUT, bit 4 is output to DOUT2, bit 2..0 is output to D2-D0, bit 7 is input from DIN, bit 6 input from DIN2, bit 3 data ignored */ ''' </summary> ''' <param name="writeBuffer"></param> ''' <param name="bitsToWrite">Число битов, которые следует передать, до 896 за раз. Не рекомендуется передавать более 256.</param> ''' <param name="readLength"></param> <Obsolete("Before calling this API, CH341Set_D5_D0 should be called to set the I/O direction of the D5-D0 pin of CH341 and set the default level of the pin")> Public Function BitSreamSpi(ByVal writeBuffer As Byte(), ByVal bitsToWrite As Integer, ByVal readLength As Integer) As Byte() If (readLength > writeBuffer.Length) Then ReDim Preserve writeBuffer(readLength - 1) End If Dim res As Boolean = CH341BitStreamSPI(DeviceIndex, bitsToWrite, writeBuffer) If res Then Return writeBuffer End If Throw New Exception("Ошибка чтения или записи массива битов по SPI.") End Function #End Region '/чтение и запись End Class End Namespace
Полную и самую последнюю версию кода для работы с микросхемой CH341 можно скачать с репозитория на GitHub, где я буду его обновлять при появлении доработок/коррекции ошибок/добавлении новых возможностей.
2Использование CH341 в режиме SPI на примере работы с датчиком BMP280
Давайте протестируем работоспособность нашего кода с библиотекой CH341.dll на реальном «железе». Для этого нам нужно какое-либо ведомое устройство. У меня под рукой имеется уже многократно использованный нами в предыдущих статьях сенсор давления и температуры BMP280. Его-то мы и возьмём как первого подопытного.
Подключим датчик к отладочной плате по стандартной для SPI схеме. То есть, SCL датчика подключаем к SCK платы, SDA – к MOSI, SDO – к MISO, CSB – к CSO, VCC – к VDD, GND – к GND. Должно получиться что-то вроде этого:
Также обратите внимание, что перемычка режима работы (зелёная на фото) должна находиться в режиме IIC/SPI, а не UART.
Приведу пример использования класса для работы с нашей dll-кой. Этот кусок кода читает 1 байт из регистра ID датчика BMP280. Как мы знаем, в этом регистре должно храниться постоянное значение идентификатора, равное 0x58.
Пример использования класса для работы с микросхемами CH341 в режиме SPI (разворачивается)
Imports Ch341 ''' <summary> ''' Тестирование библиотеки CH341DLL.DLL. ''' </summary> Module Module1 Sub Main() Console.ForegroundColor = ConsoleColor.Cyan Console.WriteLine("Работа с библиотекой CH341.dll") Console.WriteLine() Do Try CheckBmp280_spi() Catch ex As Exception Console.ForegroundColor = ConsoleColor.Red Console.WriteLine(ex.Message) Finally Console.ForegroundColor = ConsoleColor.DarkGray Console.WriteLine("Нажмите любую клавишу чтобы повторить...") Console.ReadKey(True) Console.WriteLine() End Try Loop End Sub ''' <summary> ''' Чтение регистра ID датчика BMP280 по интерфейсу SPI. ''' </summary> Private Sub CheckBmp280_spi() Console.ForegroundColor = ConsoleColor.Yellow Console.WriteLine("Чтение регистра ID датчика BMP280 по интерфейсу SPI: ") Console.ForegroundColor = ConsoleColor.Gray Dim ch341 As New SpiMaster(0, True) Dim buf As Byte() = ch341.StreamSpi({&HD0}, 1) For Each b As Byte In buf Console.Write(b.ToString("X2")) Console.Write(" ") Next Console.WriteLine() ch341.CloseDevice() Console.WriteLine() End Sub End Module
Вот как будет выглядеть вывод нашей консольной программы, читающей и выводящей значение жёстко «зашитого» в ПЗУ идентификатора датчика BMP280.
Интересно посмотреть, как это будет выглядеть на уровне передаваемых сигналов. А тут, впрочем, всё стандартно для интерфейса SPI: линия ENABLE активирует обмен, линия CLOCK – для синхронизации, на линии MOSI данные от ведущего устройства (в данном случае микросхемы CH341), на линии MISO – данные от ведомого (датчика BMP280).
Первым байтом по линии ведущего устройства MOSI передаём команду 0xD0. На неё приходит один байт ответа от ведомого (по линии MISO) с постоянным значением идентификатора датчика BMP280 – 0x58.