CH341A is the USB interface chip capable of I2C, SPI and serial communication. See how to use it to control SPI devices with the C/C++ API. Check out the correlation between API functions and bus signal sampled with a logic analyzer.
CH341A is used by some cheap memory programmers. The IC is somehow limited in this configuration, because the programmer makes use only of the SPI and I2C interface. A popular device is the so-called "CH341A MiniProgrammer" that you can buy for 2 to 5 USD. And this is probably the cheapest device using CH341A.
If you got a "MiniProgrammer", you may want to use for more than memory chips programming. The device can actually be used as USB to SPI converter (not only SPI, but this article will focus only on SPI function). Let's see how to use the included library and header to communicate with SPI peripherals.
Set up
You need the CH341PAR.ZIP file. This contains the C header and linker .LIB. It also contains the library CH341DLL.DLL which becomes a dependency for programs linked with CH341DLL.LIB. So, the files you need are:- CH341DLL.H - header; add #include directive to it in your source code;
- CH341DLL.LIB - linker library; add it to linker with -lCH341DLL argument;
- CH341DLL.DLL - library; place it in the same folder with the compiled executable or System32 folder.
- CH341OpenDevice(0); - opens the first CH341 device; cast its return value to int and if it's < 0, then an error occurred.
- CH341ResetDevice(0); - resets the device; I don't know if it's really necessary; returns true if succeeded.
- CH341SetStream(0, iMode); - configures number of I/O ports and bit order. See below
- Functions to perform SPI transfers - are detailed in the next sections.
- CH341CloseDevice(0); - closes the first device.
SPI Transfers
The API has two main SPI stream functions. There is one for each port setup (one for single I/O and the other for double port I/O). The chip (slave) select pin can be passed as a parameter to each of these functions. The two port mode uses a common clock and common chip select pin, so its usability is somehow limited.CH341A MiniProgrammer connected to logic analyzer |
CH341StreamSPI4
I will not talk about CH341StreamSPI5 too. It works in the same mode, except a additional I/O buffer that holds data from the second port. Let's see the API definition:BOOL WINAPI CH341StreamSPI4( // Processing the SPI data stream, 4-wire interface, the clock line for the DCK / D3 pin, the output data line DOUT / D5 pin, the input data line for the DIN / D7 pin, chip line for the D0 / D1 / D2, the speed of about 68K bytes /* SPI Timing: The DCK / D3 pin is clocked and defaults to the low level. The DOUT / D5 pin is output during the low period before the rising edge of the clock. The DIN / D7 pin is at a high level before the falling edge of the clock enter */ ULONG iIndex, // Specify the CH341 device serial number ULONG iChipSelect, // Chip select control, bit 7 is 0 is ignored chip select control, bit 7 is 1 parameter is valid: bit 1 bit 0 is 00/01/10 select D0 / D1 / D2 pin as low active chip select ULONG iLength, // The number of bytes of data to be transferred PVOID ioBuffer ); // Point to a buffer, place the data to be written from DOUT, and return the data read from DINLet's issue this command and check its output. I set iMode to 0.
The parameter iChipSelect should be set to 0 if CS is not used. Otherwise 0x80 if D0 is CS, 0x81 for D1 or 0x82 for D2. Clock frequency seems to be approximately 1.7 MHz, with no way of changing it. I don't understand why CS remains active for a long while after transfer is done. Specifically it stays low about 4 ms.
This function performs read and write too. The read data can be found in the same array that was passed as write buffer. The protocol looks rather strange. Data is sampled when clock goes low and you can't change this. While clock is low, data defaults to idle state (high), although next bit is low and this shouldn't be necessary. When streaming an 8-bit byte, clock makes 4 cycles followed by a small pause before next 4 cycles.
CH341BitStreamSPI
This function streams data in a bit-banging approach. Basically it writes directly to CH341A data port. Before calling it, port needs to be configured using CH341Set_D5_D0 to set data direction and initial state. D6 and D7 are always inputs and cannot be changed. This kind of approach does not allow other SPI modes but we can have up to three distinct slave select pins. Let's see API definitions.BOOL WINAPI CH341Set_D5_D0( // Set the I / O direction of the D5-D0 pin of CH341 and output data directly through the D5-D0 pin of CH341, which is higher than CH341SetOutput /* ***** Use this API with caution to prevent the I / O direction from changing the input pin into an output pin that causes a short circuit between the output pins and other output pins ***** */ ULONG iIndex, // Specify the CH341 device serial number ULONG iSetDirOut, // Set the D5-D0 pin I / O direction, a clear 0 is the corresponding pin for the input, a position of the corresponding pin for the output, parallel port mode default value of 0x00 all input ULONG iSetDataOut ); // Set the output data of each pin of D5-D0. If the I / O direction is output, the corresponding pin output is low when a bit is cleared to 0, and the pin output is high when a bit is set // The bits 5 to 0 of the above data correspond to the D5-D0 pin of CH341, respectively BOOL WINAPI CH341BitStreamSPI( // Processing the SPI bit data stream, 4 line / 5 line interface, the clock line for the DCK / D3 pin, the output data line DOUT / DOUT2 pin, the input data line for the DIN / DIN2 pin, chip select line D0 / D1 / D2, the speed of about 8K bit * 2 ULONG iIndex, // Specify the CH341 device serial number ULONG iLength, // Ready to transfer the number of data bits, up to 896 at a time, it is recommended not to exceed 256 PVOID ioBuffer ); // Point to a buffer, place the data to be written from DOUT / DOUT2 / D2-D0, and return the data read from DIN / DIN2 /* SPI Timing: The DCK / D3 pin is clocked and defaults to the low level. The DOUT / D5 and DOUT2 / D4 pins are output during the low level before the rising edge of the clock. The DIN / D7 and DIN2 / D6 pins are clocked The falling edge of the previous high period is entered */ /* A bit in the ioBuffer is 8 bits corresponding to the D7-D0 pin, bit 5 is output to DOUT, bit 4 is output to DOUT2, bit 2-bit 0 is output to D2-D0, bit 7 is input from DIN, bit 6 from DIN2 Input, bit 3 data ignored */ /* Before calling the API, you should call CH341Set_D5_D0 to set the I / O direction of the D5-D0 pin of CH341 and set the default level of the pin */You have no control of the clock. It is generated automatically and its pin corresponding bit is ignored. Let's see how it works.
The first byte I wrote to CH341A (0b000001) sets CS to high (D0 is bit 0, set to 1). All the remaining bits are 0, meaning bit 0 is transferred. The third byte has bit 5 set to 1. This corresponds to D5, MOSI. Therefore a high bit will be transferred. I'm writing a ninth byte just to clear the CS (D0), but CH341A performs an extra clock cycle. I should have used again CH341Set_D5_D0 to clear CS, without generating an extra clock cycle.
This approach is rather unusual and it mixes low level bit-banging with higher level automatic clock generation. Clock speed is about 300 kHz! The eight clock cycle is delayed.
These are CH341A API functions for SPI transfers. The API is available only for Windows. For Linux, there is a driver, but libusb is probably a better and easier choice.
Any idea if these CH341A based boards can be used with something like avrdude.exe to program AVR MCU's like Attiny85 using SPI ISP function?
ReplyDeleteIn theory it should be possible to program AVR MCUs but I don't know about avrdude support for CH341A. I could find a similar tool at https://github.com/Trel725/chavrprog. Also, WCH offers a Chinese executable with ISP programming support in archive CH341DP.zip.
DeleteIs it possible to find CH314DLL.H in English? I would appreciate if you share it. The comments in the same file from archive CH341DP.zip seem to be in Chinese.
ReplyDeleteCH341DLL.H with English comments
DeletePlease help me to run this library in codeblcoks.
ReplyDeleteMy code is compiled correclty but it doesn't work ( nothing happens on programmer )
#include
#include
#include
#include
#include "CH341DLL.H"
void write_test(void)
{
uint32_t i;
uint8_t buff[] = { 0x30, 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0x00 };
for(i=0; i<0xFF; i++)
{
Sleep(5);
CH341StreamSPI4(0, 1, 7, buff);
}
}
int main()
{
if(CH341OpenDevice(0) < 0)
{
printf("\nCan't open device");
return -1;
}
if(CH341ResetDevice(0)!=1)
{
printf("\nCan't reset device");
return -1;
}
printf("\n CH341GetVersion %lu", CH341GetVersion());
printf("\n CH341GetDrvVersion %lu", CH341GetDrvVersion());
printf("\n CH341GetDeviceName %s", (char*)CH341GetDeviceName(0));
printf("\n CH341GetVerIC %lu", CH341GetVerIC(0));
CH341SetStream(0, 0x80);
write_test();
CH341CloseDevice(0);
return 0;
}
Console output is :
CH341GetVersion 33
CH341GetDrvVersion 34
CH341GetDeviceName \\?\usb#vid_1a86&pid_5512#6&4be4af4&0&3#{5446f048-98b4-4ef0-96e8-27994bac0d00}
CH341GetVerIC 48
Process returned 0 (0x0) execution time : 1.534 s
Press any key to continue.
Change CH341StreamSPI4(0, 1, 7, buff);
Deleteto CH341StreamSPI4(0, 0x81, 7, &buff[0]);
and try again
#include
ReplyDelete#include
#include
#include
#include
#include
#include
#include
void write_test(void)
{
uint32_t i;
uint16_t buff[] = {0x0400};
for(i=0; i<0xFF; i++)
{
Sleep(5);
CH341StreamSPI4(0, 1, 2, &buff[0]);
}
}
int main() {
CH341OpenDevice(0);
std::cout << CH341GetVersion() << std::endl;
std::cout << (char*)CH341GetDeviceName(0) << std::endl;
std::cout << CH341GetDrvVersion() << std::endl;
CH341SetStream(0, 1);
write_test();
CH341CloseDevice(0);
return 0;
}
why is nothing happens in programmer
Do some error checks in the code. For example, what is the return value of CH341OpenDevice(0); ?
Deleteit works fine, all I have to do is set iChipSelect in function CH341StreamSPI4 to 0x80
Delete