If I only knew of the lack of documentation I wouldn't have bought the chip maybe - but I wanted a simple solution to connect I2C devices to the PC. There's a board available with this chip for $10 to $15 incl. shipping usually (ebay or aliexpress, "usb i2c"). It's
often called "ALL IN 1 Multifunction USB to SPI/I2C/IIC/UART/TTL" and supports both 3.3V and 5V. It comes without any driver or documentation. The manufacturer
WCN/
Winchiphead offers a lot of downloads including drivers, basic documentation (only available in Chinese) and sample code (not very well documented, all comments are Chinese again) - enough to get started.
It works as single master on the I2C bus, that's OK for over 99% of the use cases. Slave mode is not supported and it's save to assume that a multi-master setup isn't supported as well, so if you need these functions you'll have to find another solution.
|
The USB to SPI/I2C/IIC/UART/TTL/ISP Serial Adapter Module |
|
On the top there are a 12 MHz quartz, a CH314A converter chip, two
LEDs, four jumpers a a lot of connectors, and on the bottom are a few
resistors and capacitors and a AMS1117-3.3 voltage regulator chip to
supply 3.3 volts.
|
Who needs more documentation? |
The jumpers are more or less self-explaining. It's important to set the signal voltage level select jumpers as a pair, not individually. The external supply voltage jumper sets the voltage supplied at the VCC pin to either to 5V fix or to the voltage that is selected by the signal voltage level select jumpes, i.e. 3.3V or 5V depending on the jumper positions.
Windows driver with 64bit installer:
ch341a.zip (Source: http://www.abacom-online.de/div/ch341a.zip)
Folder: .\Driver\CH341_EPP_MEM_I2C_IO\
The 64 bit installer should show:
WCH.CN
|__ USB-EPP/I2C... CH341A
|__ 06/05/2009, 2.2.2009.06
Windows 32 bit installer:
CH341SER.ZIP (Source: http://wch.cn/downfile/5) with 32bit and 64bit installer
Download this file only if you need a 32 bit installer, but do not install the included driver. Use the driver supplied with the 64bit installer linked above.
API documentation: CH341DS2.PDF (Source: http://wch.cn/downfile/24)
A mirror for these files: https://drive.google.com/drive/folders/0B1xSSg6lZlzaR1BlSVNKLUhwRDQ?resourcekey=0-D_lXKzjEfqGHlsmPNmZ5aw&usp=sharing
After installing the driver and plugging in the device configured as IIC/SPI and you'll see it in the device manager:
|
A screenshot of the Device Manager with the device. |
When configured as IIC/SPI device it's a unique interface device, so you'll have
to write your own program
to use this device. Here's a short list of the necessary functions for I2C only, based on a translation of the API documentation and corrected based on the provided examples and my own tests. Basically you'll need only four functions:
- CH341OpenDevice - initialize the device
- CH341SetStream - set the I2C speed
- CH341StreamI2C - read and/or write data
- CH341CloseDevice - close the device
The functions CH341ReadI2C and CH341WriteI2C are very limited in their functionality, so I suggest to always use CH341StreamI2C instead. I have them here in the list just for the sake of completeness. Here's the API description:
//************************
//CH341OpenDevice
//
/*
Call the function CH341OpenDevice once before calling other functions.
*/
HANDLE WINAPI CH341OpenDevice( // open CH341 device, return a handle
ULONG iIndex // id of the USB device
);
/*
If the returned number is smaller then zero the device couldn't be opened.
*/
//************************
//CH341CloseDevice
//
/*
Call the function CH341CloseDevice to close the device.
*/
VOID CH341CloseDevice( // close the CH341 device
ULONG iIndex // id of the USB device
);
//************************
//CH341SetStream
//
/*
Call the function CH341SetStream once to set the data rate.
*/
BOOL WINAPI CH341SetStream( // configure the serial flow mode
ULONG iIndex, // id of the USB device
ULONG iMode // specify the mode, see below
);
/*
// Bit 1,0: I2C interface speed / SCL frequency
// 00 = low / 20KHz
// 01 = standard / 100KHz (default)
// 10 = fast / 400KHz
// 11 = High Speed / 750KHz
// Bit 2: SPI's I / O Number / IO pin
// 0 = single-input single-output (D3 clock / D5 out / D7 into) (default)
// 1 = double into a double (D3 clock / D5 out D4 a / D7 D6 enter into)
// Bit 7: SPI byte bit order
// 0 = LSB first
// 1 = MSB first
// Other Reserved, must be 0
// To sum this up: Set iMode to 0, 1, 2 or 3 for 20, 100, 400 or 750 kHz.
*/
//************************
//CH341ReadI2C
//
/*
Call this function to write and read one byte from a slave device on
the I2C bus.
*/
BOOL WINAPI CH341ReadI2C( // read data
ULONG iIndex, // id of the USB device
UCHAR iDevice, // I2C device address (7 bits)
UCHAR iAddr, // I2C device register (writes one byte)
PUCHAR oByte // I2C data (reads one byte)
);
//************************
//CH341WriteI2C
//
/*
Call this function to write two bytes of data on the I2C bus.
*/
BOOL WINAPI CH341WriteI2C( // write data
ULONG iIndex, // id of the USB device
UCHAR iDevice, // I2C device address (7 bits)
UCHAR iAddr, // I2C device register (writes first byte)
UCHAR iByte // I2C data (writes second byte)
);
//************************
//CH341StreamI2C
//
/*
Call this function to read, write, or write and read a specified amount of
bytes on the I2C bus. Read length or write length can be set to zero
BOOL WINAPI CH341StreamI2C( // read/write data
ULONG iIndex, // id of the USB device
ULONG iWriteLength, // length of the data (bytes to write)
PVOID iWriteBuffer, // pointer to the data
ULONG iReadLength, // length of the data (bytes to read)
PVOID oReadBuffer // pointer to the data
);
/*
This function provides all functionality of CH341ReadI2C and CH341WriteI2C
plus much more flexibility. It's not that elegant because you have to prepare
the iWriteBuffer with the device address that is shifted by one bit to the
left by yourself (the least significant bit will be controlled by the chip
for read (1) and write (0) access automatically).
*/
Putting this all together in an undocumented test script for AutoIT:
#include <WinAPISys.au3>
;device ID
$id = 0
;open the dll
$dll = DllOpen("C:\Windows\System32\CH341DLL.DLL")
If $dll <> -1 Then
ConsoleWrite ("DllOpen: OK" & @CRLF)
Else
ConsoleWrite ("DllOpen: Error, could not open the dll." & @CRLF)
Beep(1000, 5000)
Exit
EndIf
;open the device
$aResult = DllCall($dll, "BOOL", "CH341OpenDevice", "ULONG", $id)
If $aResult[0] <> -1 Then
ConsoleWrite ("CH341OpenDevice: OK" & @CRLF)
Else
ConsoleWrite ("CH341OpenDevice: Error: Could not open device " & @CRLF)
Beep(1000, 5000)
Exit
EndIf
;set the I2C speed
$iMode = 0
$aResult = DllCall($dll, "BOOL", "CH341SetStream", "ULONG", $id, "ULONG", $iMode)
If $aResult[0] Then
ConsoleWrite("CH341SetStream: OK" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $iMode=" & $iMode & @CRLF)
Else
ConsoleWrite("CH341SetStream: Error" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $iMode=" & $iMode & @CRLF)
Beep(1000, 5000)
Exit
EndIf
;Read and Write data
;to be used with CH341ReadI2C and CH341WriteI2C:
$i2c_addr = 85 ;b: 0101'0101 (I2C: 1010'101*, *=R/W bit)
;to be used with CH341ReadI2C and CH341WriteI2C:
$i2c_B1 = 0 ;b: 0000'0000
;to be used with CH341ReadI2C:
$sBYTE_r4 = DllStructCreate ("BYTE[4]" )
$pBYTE_r4 = DllStructGetPtr($sBYTE_r4)
;to be used with CH341WriteI2C:
$i2c_B2 = 231 ;b: 1110'0111
;to be used with CH341StreamI2C
$rlen = 0
$sBYTE_r = DllStructCreate ("BYTE[8]" )
$pBYTE_r = DllStructGetPtr($sBYTE_r)
$wlen = 3
$sBYTE_w = DllStructCreate ("BYTE;BYTE;BYTE;BYTE;BYTE;BYTE;BYTE;BYTE" )
DllStructSetData ( $sBYTE_w, 1, 170 ) ;b: 1010'1010 (I2C: 1010'101*, *=R/W bit) - I2C slave address
DllStructSetData ( $sBYTE_w, 2, 0 ) ;b: 0000'0000
DllStructSetData ( $sBYTE_w, 3, 231 ) ;b: 1110'0111
DllStructSetData ( $sBYTE_w, 4, 0 )
DllStructSetData ( $sBYTE_w, 5, 0 )
DllStructSetData ( $sBYTE_w, 6, 0 )
DllStructSetData ( $sBYTE_w, 7, 0 )
DllStructSetData ( $sBYTE_w, 8, 0 )
$pBYTE_w = DllStructGetPtr($sBYTE_w)
MsgBox(0, "PAUSED", "Click OK to continue with the tests")
ConsoleWrite("WRITE TEST: START" & @CRLF)
;CH341WriteI2C
$aResult = DllCall($dll, "BOOL", "CH341WriteI2C", "BYTE", $id, "BYTE", $i2c_addr, "BYTE", $i2c_B1, "BYTE", $i2c_B2)
If $aResult[0] Then
ConsoleWrite("CH341WriteI2C: OK" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $i2c_addr=" & $i2c_addr & ", $i2c_B1=" & $i2c_B1 & ", $i2c_B2=" & $i2c_B2 & @CRLF)
Else
ConsoleWrite("CH341WriteI2C: Error" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $i2c_addr=" & $i2c_addr & ", $i2c_B1=" & $i2c_B1 & ", =$i2c_B2" & $i2c_B2 & @CRLF)
Beep(1000, 5000)
Exit
EndIf
MsgBox(0, "PAUSED", "Click OK to continue with the tests")
;CH341StreamI2C writing
$rlen = 0
$wlen = 3
$aResult = DllCall($dll, "BOOL", "CH341StreamI2C", "BYTE", $id, "BYTE", $wlen, "ptr", $pBYTE_w, "BYTE", $rlen, "ptr", $pBYTE_r)
If $aResult[0] Then
ConsoleWrite("CH341StreamI2C: OK" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $wlen=" & $wlen & ", $rlen=" & $rlen & _
", $sBYTE_w1,2,3,4=" & DllStructGetData($sBYTE_w, 1) & _
"," & DllStructGetData($sBYTE_w, 2) & _
"," & DllStructGetData($sBYTE_w, 3) & _
"," & DllStructGetData($sBYTE_w, 4) & _
", $sBYTE_r=" & DllStructGetData($sBYTE_r, 1) & @CRLF)
Else
ConsoleWrite("CH341StreamI2C: Error" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $wlen=" & $wlen & ", $rlen=" & $rlen & _
", $sBYTE_w1,2,3,4=" & DllStructGetData($sBYTE_w, 1) & _
"," & DllStructGetData($sBYTE_w, 2) & _
"," & DllStructGetData($sBYTE_w, 3) & _
"," & DllStructGetData($sBYTE_w, 4) & _
", $sBYTE_r=" & DllStructGetData($sBYTE_r, 1) & @CRLF)
Beep(1000, 5000)
Exit
EndIf
MsgBox(0, "PAUSED", "Click OK to continue with the tests")
ConsoleWrite("WRITE TEST COMLETE" & @CRLF)
ConsoleWrite("READ TEST: START" & @CRLF)
;CH341ReadI2C
$aResult = DllCall($dll, "BOOL", "CH341ReadI2C", "BYTE", $id, "BYTE", $i2c_addr, "BYTE", $i2c_B1, "PTR", $pBYTE_r4)
If $aResult[0] Then
ConsoleWrite("CH341ReadI2C: OK" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $i2c_addr=" & $i2c_addr & _
", $i2c_B1=" & $i2c_B1 & _
", $sBYTE_r4=" & DllStructGetData($sBYTE_r4, 1) & @CRLF)
Else
ConsoleWrite("CH341ReadI2C: Error" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $i2c_addr=" & $i2c_addr & _
", $i2c_B1=" & $i2c_B1 & _
", $sBYTE_r4=" & DllStructGetData($sBYTE_r4, 1) & @CRLF)
Beep(1000, 5000)
Exit
EndIf
MsgBox(0, "PAUSED", "Click OK to continue with the tests")
;CH341StreamI2C reading similar to CH341ReadI2C
$rlen = 1
$wlen = 2 ;I2C slave address and one byte
$aResult = DllCall($dll, "BOOL", "CH341StreamI2C", "BYTE", $id, "BYTE", $wlen, "ptr", $pBYTE_w, "BYTE", $rlen, "ptr", $pBYTE_r)
If $aResult[0] Then
ConsoleWrite("CH341StreamI2C: OK" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $wlen=" & $wlen & ", $rlen=" & $rlen & _
", $sBYTE_w1,2,3,4=" & DllStructGetData($sBYTE_w, 1) & _
"," & DllStructGetData($sBYTE_w, 2) & _
"," & DllStructGetData($sBYTE_w, 3) & _
"," & DllStructGetData($sBYTE_w, 4) & _
", $sBYTE_r=" & DllStructGetData($sBYTE_r, 1) & @CRLF)
Else
ConsoleWrite("CH341StreamI2C: Error" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $wlen=" & $wlen & ", $rlen=" & $rlen & _
", $sBYTE_w1,2,3,4=" & DllStructGetData($sBYTE_w, 1) & _
"," & DllStructGetData($sBYTE_w, 2) & _
"," & DllStructGetData($sBYTE_w, 3) & _
"," & DllStructGetData($sBYTE_w, 4) & _
", $sBYTE_r=" & DllStructGetData($sBYTE_r, 1) & @CRLF)
Beep(1000, 5000)
Exit
EndIf
MsgBox(0, "PAUSED", "Click OK to continue with the tests")
;CH341StreamI2C reading
$rlen = 8
$wlen = 1
$aResult = DllCall($dll, "BOOL", "CH341StreamI2C", "BYTE", $id, "BYTE", $wlen, "ptr", $pBYTE_w, "BYTE", $rlen, "ptr", $pBYTE_r)
If $aResult[0] Then
ConsoleWrite("CH341StreamI2C: OK" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $wlen=" & $wlen & ", $rlen=" & $rlen & _
", $sBYTE_w1,2,3,4=" & DllStructGetData($sBYTE_w, 1) & _
"," & DllStructGetData($sBYTE_w, 2) & _
"," & DllStructGetData($sBYTE_w, 3) & _
"," & DllStructGetData($sBYTE_w, 4) & _
", $sBYTE_r=" & DllStructGetData($sBYTE_r, 1) & @CRLF)
Else
ConsoleWrite("CH341StreamI2C: Error" & @CRLF)
ConsoleWrite(" Param: $id="& $id & ", $wlen=" & $wlen & ", $rlen=" & $rlen & _
", $sBYTE_w1,2,3,4=" & DllStructGetData($sBYTE_w, 1) & _
"," & DllStructGetData($sBYTE_w, 2) & _
"," & DllStructGetData($sBYTE_w, 3) & _
"," & DllStructGetData($sBYTE_w, 4) & _
", $sBYTE_r=" & DllStructGetData($sBYTE_r, 1) & @CRLF)
Beep(1000, 5000)
Exit
EndIf
MsgBox(0, "PAUSED", "Click OK to finish")
ConsoleWrite("READ TEST COMLETE" & @CRLF)
The console output should like this if everything works fine (no I2c devices connected):
DllOpen: OK
CH341OpenDevice: OK
CH341SetStream: OK
Param: $id=0, $iMode=0
WRITE TEST: START
CH341WriteI2C: OK
Param: $id=0, $i2c_addr=85, $i2c_B1=0, $i2c_B2=231
CH341StreamI2C: OK
Param: $id=0, $wlen=3, $rlen=0, $sBYTE_w1,2,3,4=170,0,231,0, $sBYTE_r=0x0000000000000000
WRITE TEST COMLETE
READ TEST: START
CH341ReadI2C: OK
Param: $id=0, $i2c_addr=85, $i2c_B1=0, $sBYTE_r4=0xFF000000
CH341StreamI2C: OK
Param: $id=0, $wlen=2, $rlen=1, $sBYTE_w1,2,3,4=170,0,231,0, $sBYTE_r=0xFF00000000000000
CH341StreamI2C: OK
Param: $id=0, $wlen=1, $rlen=8, $sBYTE_w1,2,3,4=170,0,231,0, $sBYTE_r=0xFFFFFFFFFFFFFFFF
READ TEST COMLETE
And here are the pictures showing what's happening on the wire:
|
First write test (CH341WriteI2C) |
|
Second write test (CH341StreamI2C) |
|
First read test (CH341ReadI2C) |
|
Second read test (CH341StreamI2C) |
|
Third read test (CH341StreamI2C) |
Settings for all pictures (RIGOL DS1052E):
Analog Ch State Scale Position Coupling BW Limit Invert
CH1 On 2.00V/ -4.00V DC Off Off
CH2 On 2.00V/ 0.00uV DC Off Off
Analog Ch Impedance Probe
CH1 1M Ohm 1X
CH2 1M Ohm 1X
Time Time Ref Main Scale Delay
Main Center 200.0us/ 840.0000us
Trigger Source Slope Mode Coupling Level Holdoff
Edge CH1 Falling Normal DC 1.68V 500ns
Acquisition Sampling Memory Depth Sample Rate
Normal Realtime Normal 1.000MSa