Does an Arduino host driver for the CP210x USB-to-serial family exist?

I'm looking for anyone who might have written an Arduino driver for the Silicon Labs CP210x USB-to-serial converter.

I understand that the normal way to use this device is to download the driver for your favorite OS, install it, and then it's plug-and-play.

What I desire to do, however, is to program a MAX4321E device to serve as the host talking to this device. This will allow us to communicate with a Xilinx FPGA board via its built-in USB port (which is where the CP2103 comes in.)

Having never attempted to write anything more than a simple LCD driver before, I'm not sure where to start to go about something like this. USB is obviously quite complicated and a bit overwhelming, and it would help me a lot to be able to see an example of how someone else might have gone about this.

Thank you!

I should add that I'm interested in having someone develop this driver under a contract basis. If you are an experienced developer, please PM me.

Bryan

Not finding any previous work that has been done to create a USB host driver for the Silicon Labs CP2103, I'm undertaking this task myself and have made some very limited headway.

My hardware setup is an Arduino Uno with Oleg's USB host shield. On the other end of the USB cable is a Silicon Labs CP2103 development board.

My starting point is Oleg's board_test code from his rev1 usb_host_shield-master. To that I'm attempting to add some CP2103-specific commands, starting with trying to set the baud rate and flow control.

The code:

/* USB related */
//#include <Spi.h>
#include <Max3421e.h>
#include <Max3421e_constants.h>
#include <Usb.h>


#include "board_test.h" /* Board test messages */

#define CP2103_VendID               0x10C4
#define CP2103_ProdID               0xEA60
#define CP2103_RevID                0x0100
#define REQTYPE_HOST_TO_DEVICE      0x40
#define REQTYPE_HOST_TO_INTERFACE   0x41
#define REQTYPE_DEVICE_TO_HOST      0xc0

#define CP210X_IFC_ENABLE           0x00
#define CP210X_GET_BAUDRATE         0x1D
#define CP210X_SET_BAUDRATE         0x1E
#define CP210X_SET_FLOW             0x13
#define CP210X_GET_FLOW             0x14
#define USB_CTRL_SET_TIMEOUT        5000
#define UART_ENABLE                 0x0001

typedef void (*PARSE)( uint8_t bytes );

//#define MAX_SS 10

void setup();
void loop();

MAX3421E Max;
USB Usb;

byte result;
byte tmp;
char data[16];
byte rcv_bytes;


void setup()
{
  Serial.begin( 115200 );
  Max.powerOn();
  printProgStr( startBanner );
  printProgStr( anykey_msg );
  //Serial.print( Max.getvar(), DEC);
}

void loop()
{
  DEV_RECORD devtable[ USB_NUMDEVICES + 1 ];
  /* start tests */
  Serial.println("revregcheck...");
  if (!revregcheck()) test_halted();

  Serial.println("now osc test...");
  if (!osctest()) printProgStr(PSTR("OSCOK test failed. Check the oscillator"));
  if (!usbtest()) printProgStr(PSTR("USB connection test failed. Check traces from USB connector to MAX3421E, as well as VBUS"));  //never gets here
    /* All tests passed */
  Serial.println("\nLink is ready.\n\n");

  Serial.println("Calling Usb.task()...");
  Usb.Task();
  
  // unsure if we need to first "open" the CP210x...
  Serial.println("\nAttempting to open device...");
  //clearData();
  result = Usb.ctrlReq( 1, 0, REQTYPE_HOST_TO_DEVICE, CP210X_IFC_ENABLE, UART_ENABLE, 0x00, 0x0000, 0x00, NULL, USB_NAK_LIMIT);
  if(result) {
    Serial.print("Error: result of command is: 0x");
    Serial.println(result);
  }
  //printData();


  Serial.println("\nRead initial baud rate setting...");
  clearData();
  result = Usb.ctrlReq( 1, 0, REQTYPE_DEVICE_TO_HOST, CP210X_GET_BAUDRATE, 0x00, 0x00, 0x0000, 0x08, data, USB_NAK_LIMIT);
  if(result) {
    Serial.print("Error: result of command is: 0x");
    Serial.println(result);
  }
  printData();
  
  Serial.println("\nAttempting to set baud rate to 57600");
  result = Usb.ctrlReq( 1, 0, REQTYPE_HOST_TO_DEVICE, CP210X_SET_BAUDRATE, 0xE1, 0x00, 0x0000, 0x00, NULL, USB_NAK_LIMIT);
  if(result) {
    Serial.print("Error: result of command is: 0x");
    Serial.println(result);
  }
  else Serial.println("Success.");
  
  Serial.println("Read back baud rate...");
  clearData();
  result = Usb.ctrlReq( 1, 0, REQTYPE_DEVICE_TO_HOST, CP210X_GET_BAUDRATE, 0x00, 0x00, 0x0000, 0x04, data, USB_NAK_LIMIT);
  if(result) {
    Serial.print("Error: result of command is: 0x");
    Serial.println(result);
  }
  printData();

  Serial.println("\nAttempting to set flow control...");
  result = Usb.ctrlReq( 1, 0, REQTYPE_HOST_TO_DEVICE, CP210X_SET_FLOW, 0x41, 0x00, 0x00, 0x00, NULL, USB_NAK_LIMIT);
  if(result) {
    Serial.print("Error: result of command is: 0x");
    Serial.println(result);
  }

  Serial.println("Read back flow control settings...");
  clearData();
  result = Usb.ctrlReq( 1, 0, REQTYPE_DEVICE_TO_HOST, CP210X_GET_FLOW, 0x00, 0x00, 0x0000, 0x10, data, USB_NAK_LIMIT);
  if(result) {
    Serial.print("Error: result of command is: 0x");
    Serial.println(result);
  }
  printLongData();

  Serial.println("\nAttempting to set baud rate to 115200:");
  result = Usb.ctrlReq( 1, 0, REQTYPE_HOST_TO_DEVICE, CP210X_SET_BAUDRATE, 0xC2, 0x01, 0x00, 0x00, NULL, USB_NAK_LIMIT);
  if(result) {
    Serial.print("Error: result of command is: 0x");
    Serial.println(result);
  }

  Serial.println("Now read back baud rate again to confirm change...");
  clearData();
  result = Usb.ctrlReq( 1, 0, REQTYPE_DEVICE_TO_HOST, CP210X_GET_BAUDRATE, 0x00, 0x00, 0x0000, 0x04, data, USB_NAK_LIMIT);
  if(result) {
    Serial.print("Error: result of command is: 0x");
    Serial.println(result);
  }
  printData();

  while(1); // stop here for now....
  
  // issue transfer_IN commands at 8 bytes at a time... forever...
  for(byte i=0; i<2; i++) {
    Serial.println("\n\nAttempting to receive data...");

    //byte USB::inTransfer( addr, ep, unsigned int nbytes, char* data, unsigned int nak_limit )
    result = BCinTransfer( 1, 0, 8, data, USB_NAK_LIMIT);

    Serial.print("Result of inTransfer command: 0x0");
    Serial.println(result, HEX);
        
    if(result == 0) {
      for(byte i=0; i<8; i++) {
         Serial.print(data[i]);
      }
      Serial.println(" ");
    }
    delay(1000);
  }
  while(1); // stop here for now....
}

Current challenges:

  1. I can't seem to set the baud rate. I'm reading a baud rate value of 0x01C200, I think, which corresponds to 115200. But nothing I've done seems to be able to set that to another value, in this case half of that, 57600, or 0x00E100.

  2. I don't think I'm having an effect on flow control. I'm trying to leverage a command from the Linux driver for this device, a snippet of which is:

if ((cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
 cp210x_get_config(port, REQTYPE_INTERFACE_TO_HOST,
 CP210X_GET_FLOW, 0, modem_ctl, 16);
 dev_dbg(dev, "%s - read modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x\n",
 __func__, modem_ctl[0], modem_ctl[1],
 modem_ctl[2], modem_ctl[3]);

 if (cflag & CRTSCTS) {
 modem_ctl[0] &= ~0x7B;
 modem_ctl[0] |= 0x09;
 modem_ctl[1] = 0x80;
 dev_dbg(dev, "%s - flow control = CRTSCTS\n", __func__);
 } else {
 modem_ctl[0] &= ~0x7B;
 modem_ctl[0] |= 0x01;
 modem_ctl[1] |= 0x40;
 dev_dbg(dev, "%s - flow control = NONE\n", __func__);
 }

 dev_dbg(dev, "%s - write modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x\n",
 __func__, modem_ctl[0], modem_ctl[1],
 modem_ctl[2], modem_ctl[3]);
 cp210x_set_config(port, REQTYPE_HOST_TO_INTERFACE,
 CP210X_SET_FLOW, 0, modem_ctl, 16);
 }

which is where I'm getting the value of 0x41.

Not finding much in the way of documentation for this device, I'm left guessing as to where to put values in the USB packets.

Until I can see that I'm successfully able to set the baud rate and flow control, I'm not able to make much progress toward the ultimate goal, which is to issue BulkIN transfers from the Silicon Labs chip. (The end application for all this is to be able to pull data, across USB, from a Xilinx FPGA development card. This card has a USB console port with this device on it.)

If anyone has suggestions about how to move this forward, I could really, really use your help.

Thanks!
Bryan

The console output wouldn't fit in the 9000 character limit, so here it is:

Circuits At Home 2010
USB Host Shield QC test routine

Press any key to continue...revregcheck...

Reading REVISION register...Die revision 03
Test PASSEDnow osc test...

Oscillator start/stop test. Oscillator state is ON
Setting CHIP RESET. Oscillator state is OFF
Clearing CHIP RESET. PLL is stable. Time to stabilize - 136 cycles
Test PASSED
USB Connectivity test. Waiting for device connection... 



Device connected. Resetting


Reset complete. Waiting for the first SOF...

SOF generation started. Enumerating device.

pktsize = 8

Setting device address

Getting device descriptor
pktsize = 18

Device descriptor: 
Descriptor Length:	12
Descriptor type:	01
USB version:	0110
Device class:	00
Device Subclass:	00
Device Protocol:	00
Max.packet size:	40
Vendor ID:	10C4
Product ID:	EA60
Revision ID:	0100
Mfg.string index:	01
Prod.string index:	02
Serial number index:	03
Number of conf.:	01

All tests passed.
Link is ready.


Calling Usb.task()...


Attempting to open device...

Read initial baud rate setting...

pktsize = 4
data buffer contents:
byte 0   value = 0x0
byte 1   value = 0xC2
byte 2   value = 0x1
byte 3   value = 0x0
end of data printout


Attempting to set baud rate to 57600
Success.
Read back baud rate...

pktsize = 4
data buffer contents:
byte 0   value = 0x0
byte 1   value = 0xC2
byte 2   value = 0x1
byte 3   value = 0x0
end of data printout


Attempting to set flow control...
Read back flow control settings...

pktsize = 16
data buffer contents:
byte 0   value = 0x0
byte 1   value = 0x0
byte 2   value = 0x0
byte 3   value = 0x0
byte 4   value = 0x0
byte 5   value = 0x0
byte 6   value = 0x0
byte 7   value = 0x0
byte 8   value = 0x0
byte 9   value = 0x0
byte 10   value = 0x0
byte 11   value = 0x0
byte 12   value = 0x0
byte 13   value = 0x0
byte 14   value = 0x0
byte 15   value = 0x0
end of data printout


Attempting to set baud rate to 115200:
Now read back baud rate again to confirm change...

pktsize = 4
data buffer contents:
byte 0   value = 0x0
byte 1   value = 0xC2
byte 2   value = 0x1
byte 3   value = 0x0
end of data printout

It's a little bit lonely on this thread, but I'll continue to post stuff in case this helps someone down the road or someone might be able to help me.

After finally receiving the programming guide for this device (Silicon Labs CP2103), I'm making progress.

  1. I'm now able to successfully change the baud rate of the device; it turns out that the baud rate needs to be placed into the data payload portion of the SET_BAUDRATE packet, versus be part of the packet itself (e.g. wValue). So, setting the baud rate to 57600 is done with:
  data[0] = 0x00;
  data[1] = 0xE1;
  data[2] = 0x00;
  data[3] = 0x00;
  
  Usb.ctrlReq( 1, 0, REQTYPE_HOST_TO_DEVICE, CP210X_SET_BAUDRATE, 0x00, 0x00, 0x0000, 0x04, data, USB_NAK_LIMIT);

or just set whatever value you like (direction conversion from decimal to hex) into the 4 data bytes.

  1. I was also able to capture a Wireshark trace of this device when I plug it directly into one of my PC's USB ports -- what I wanted to see what how the Windows VCP driver for the device configures it and eventually receives data from it.

I won't clutter up this thread with that capture, but essentially it does the following:

a) Go through the GET_DESCRIPTOR process (about 35 packets)
b) Set things like flow control, baud rate, modem handshaking, all done with HOST_TO_INTERFACE (about another 20 packets)
c) Get things like modem status, comm status, again all to the INTERFACE (about another 20 packets)
d) Then all of a sudden two BULK_IN transfers flow from the DEVICE to the host.

These packets contain the serial data that I'm injecting into the serial port side of the CP2103. These are the first packets to or from the DEVICE (address 16.1), as opposed to the INTERFACE (16.0).

So it is good to see my serial data coming up on the Windows terminal, but I never see a command from the host which initiates the BULK_IN transfer. I thought that USB always counted on the host initiating all transfers, so on this point I remain confused about how to get these BULK_IN transfers to happen in my own embedded driver....

I've continued to make progress on my CP2103 driver, but am now stuck at the USB Standard Request phase, specifically the SET_CONFIGURATION command.

My code for that command:

 Serial.println("\nSet Configuration command to device...\n");
  //result = Usb.setConf( 1, 0, 0x01, USB_NAK_LIMIT);
  result = Usb.ctrlReq( 1, 0, 0x00, USB_REQUEST_SET_CONFIGURATION, 0x01, 0x00, 0x0000, 0x0000, NULL, USB_NAK_LIMIT);

result = 0, so the device appears to be accepting the command. (I've tried both calls above with equal results.)

I thought that would be my last hurdle, but then when I follow that with a GET_CONFIGURATION command, as such:

 Serial.println("sending USB_REQUEST_GET_CONFIGURATION");
  result = Usb.ctrlReq( 1, 0, 0x80, USB_REQUEST_GET_CONFIGURATION, 0x00, 0x00, 0x0000, 0x0001, data, USB_NAK_LIMIT);

the device is returning a single byte of 0x00, which I believe means that it does not yet have a configuration, it's still in Addressing mode, and then of course it won't / can't respond to a subsequent BULK_IN transfer command, resulting in the 0x0D error.

It all makes sense to me except for the fact that the device won't activate its Configuration.

If someone could help me out on why that might be happening, it would be a huge help for me.

Thanks,
Bryan

Problem solved. It ended up being something very simple... I was issuing the BULK_IN transfer request to endpoint 0, and of course it should be to endpoint 1.

Thanks to everyone for making me suffer through this by myself and figure it out!! : )

I just saw this thread.

Maybe I misunderstand what kind of connections you're trying to make to the FPGA board, but wouldn't it be easier to use the Arduino's native TTL serial port than to talk to a USB-to-serial converter that does virtually the same thing? Granted, the Arduino's serial port has no hardware flow control, but it should be relatively easy (certainly easier than programming a USB controller!) to add flow control with some of the digital output pins.

A diagram of the connections between each device would help.

christop, you are correct -- simply establishing a serial connection would have been a LOT easier. However, it would have involved some hardware changes to the FPGA card and at $5000 each, we don't want to be cutting traces / soldering / adding additional I/O connectors / etc. Nor do we want to be doing these mods to a dozen or so of these setups that we may need to produce.

Now that the USB driver is working, it takes advantage of an existing USB (console) port on the FPGA card and it's a simple plug-and-play connection that's electrically and mechanically robust.

I see. I just read up on the Xlinix FPGA development board, and it appears to have a CP210x USB-to-serial converter built into it, so the only way to talk to its UART interface is via the CP210x. I have no experience with programming a driver for the CP210x, so I wouldn't have been able to help much with it.

Anyway, good job on getting an Arduino talking to the CP210x!

I am also looking for a driver for CP210x... I tried to use the USB host shield library GitHub - felis/USB_Host_Shield_2.0: Revision 2.0 of USB Host Library for Arduino. but came to the conclusion that a driver was missing.

cowger: I'm not as fluent in Arduino and c programming as you yet... Great job getting it to work!
Would it be possible for you to publish the full driver with easy to understand instructions how to use it?

Thanks,
Henrik

Hi Henrik,

PM me your e-mail address and I'll share with you my sketch and the documentation I found on the CP210x. That should be a good start for you.

Bryan

Thanks, I have sent you a PM.

Hey cowger,

I've found myself in a similar situation, and wouldn't mind shaving some time off my development. Would you be willing to send the info to me, as well?

To cowger: I also need the driver for CP210x,could you send your code to me? I 've send a PM to you right now. Thank you very much!

Thanks you cowger! You are so kindly! I also sent PM to chazzy84 and hope he can help me.

Thanks to Cowger, you are so kindly!
And also want to get help from chazzy84, waiting......

Hello cowger,

I have a similar problem with products already on the field using Silicon Labs' CP2102 chip. That is why I need of CP210x class support on any MCU with USB host. I have already investigated possibility to start with following solutions:

  • Atmels AVR & LUFA USB stack - it has CDC host but lacks of CP210x class driver
  • STM32 MCU & ST USB Host Library - the same as above
  • LPC1768 (mbed) & BlueUSB project - I add CP210x customization but project is too complex
  • LPC1768 (mbed) & USB Host library - succeeded to recognize CP2102 but fail to communicate with

I have just seen your work and would like to try your solution.
Could you share with me your project (I will PM my e-mail)?

Do you have some performance measuring?

Thanks in advance
Chris

In fact, from the beginning of last year, programmers of our company have been troubled for a long time because of this problem too.
Later, through some hard coding and testing, and we finally solved it successfully. Now, I find that still have so many people troubled with it, I can provide it with some paid.
If you will get it with some paid, please contact me with this email
dslwork [AT] sina [DOT] com

note that [AT] = @, [DOT] = .

It's a good news, that our driver is also work well with arduino mega ADK board, so you no need to buy an USB Host Shield to work with an UNO or a mega board.