USB Host Shield with Bulk Transfer

Hi all,

First of all, I am new to Arduino and also new to this forum, hopefully I post this question in the right place.

I have got a Duemilanoves (ATmega168) board and a USB Host Shield from Sparkfun SparkFun USB-C Host Shield - DEV-21247 - SparkFun Electronics. I used the libraries from http://www.circuitsathome.com/category/mcu/arduino/usb-shield and have changed the .h file according to the Sparkfun website. I tried the example sketch http://www.circuitsathome.com/mcu/programming/arduino-usb-host-part-4-peripherals and it works fine. So I start to work on my project.

My project is to use the Arduino to talk to a power monitoring device called the "WattsUp Pro".

This device has a USB port and I can get information through my computer, by using pyserial with python. In general, you issue a command such as "#D,R,0;" to the device and it will feed back the measured data, eg. "#D,-,34, ... ;". I use USEB_View and get the device's USB information as follow:

By using the USBView utility, we can get the USB connection tree of the end device.
Device Descriptor:
?      bcdUSB:            0x0200
?      bDeviceClass:            0x00
?      bDeviceSubClass:      0x00
?      bDeviceProtocol:      0x00
?      bMaxPacketSize0:      0x08 (8)
?      idVendor:            0x0403 (Future Technology Devices International Limited)
?      idProduct:            0x6001
?      bcdDevice:            0x0600
?      iManufacturer:            0x01
?      0x0409:                  "FTDI"
?      iProduct:            0x02
?      0x0409:                  "FT232R USB UART"
?      0x0409:                  "FT232R USB UART"
?      iSerialNumber:            0x03
?      0x0409:                  "A7005chz"
?      bNumConfigurations:       0x01
?      ConnectionStatus:      DeviceConnected
?      Current Config Value:      0x01
?      Device Bus Speed:      Full
?      Device Address:            0x01
?      Open Pipes:            2
Endpoint Descriptor:
?      bEndpointAddress:      0x81  IN
?      Transfer Type:            Bulk
?      wMaxPacketSize:      0x0040 (64)
?      bInterval:            0x00
Endpoint Descriptor:
?      bEndpointAddress:      0x02  OUT
?      Transfer Type:            Bulk
?      wMaxPacketSize:      0x0040 (64)
?      bInterval:            0x00

Configuration Descriptor:
?      wTotalLength:             0x0020
?      bNumInterfaces:      0x01
?      bConfigurationValue:      0x01
?      iConfiguration:             0x00
?      bmAttributes:             0xA0 (Bus Powered Remote Wakeup)
?      MaxPower:             0x2D (90 Ma)
Interface Descriptor:
?      bInterfaceNumber:       0x00
?      bAlternateSetting:       0x00
?      bNumEndpoints:       0x02      
?      bInterfaceClass:             0xFF
?      bInterfaceSubClass:       0xFF
?      bInterfaceProtocol:      0xFF
?      iInterface:            0x02
?      0x0409:                  "FT232R USB UART"
?      0x0409:                  "FT232R USB UART"
Endpoint Descriptor:
?      bEndpointAddress:      0x81  IN
?      Transfer Type:            Bulk (Attr 02)
?      wMaxPacketSize:      0x0040 (64)
?      bInterval:            0x00
Endpoint Descriptor:
?      bEndpointAddress:      0x02  OUT
?      Transfer Type:            Bulk (Attr 02)
?      wMaxPacketSize:      0x0040 (64)
?      bInterval:            0x00

And I tried to write a program to ask the device for data and the code is as follow:

#include <Spi.h>
#include <Max3421e.h>
#include <Max3421e_constants.h>
#include <Usb.h>
#include <avr/pgmspace.h>

#define WATTSUP_CONFIGURATION  1
#define WATTSUP_ADDR    0x01
#define WATTSUP_VID    0x0403
#define WATTSUP_PID    0x6001
#define WATTSUP_NUM_EP    3
#define EP_MAXPKTSIZE    64
#define EP_BULK    0x02
#define EP_POLL    0x01
#define CONTROL_PIPE    0
#define INPUT_PIPE    1
#define OUTPUT_PIPE    2
#define DEV_DESCR_LEN    32

#define WATTSUP_CLEAR_COMMAND_LEN    7
#define WATTSUP_HEADER_COMMAND_LEN    7


#define statusDeviceConnected 0x01
#define statusUSBConfigured 0x02
#define statusWATTSUPConnected 0x04
#define statusReportReceived 0x08

char buf[64] = {0};    //Gemeral purpose buffer for USB data
static unsigned char wattsup_status;

prog_char clear_command[] PROGMEM = {0x23,0x52,0x2C,0x57,0x2C,0x30,0x3B};
prog_char header_command[] PROGMEM = {0x23,0x44,0x2C,0x52,0x2C,0x30,0x3B};


void setup();
void loop();

EP_RECORD ep_record[WATTSUP_NUM_EP];
MAX3421E Max;
USB Usb;


void setup(){
  byte tmpdata = 0;
  Serial.begin(115200);
  Serial.println("Start");
  Max.powerOn();
  delay(200);
}

void loop(){
  Max.Task();
  Usb.Task();
  if(Usb.getUsbTaskState() == USB_DETACHED_SUBSTATE_INITIALIZE){
    Serial.println("USB_DETACHED_SUBSTATE_INITIALIZE");
    wattsup_status = 0;  
  }
  if(Usb.getUsbTaskState() == USB_STATE_CONFIGURING) {  
    Serial.println("START TO INIT");
    WATTSUP_init();
    if(wattsup_status & statusWATTSUPConnected){
      Serial.println(wattsup_status & statusWATTSUPConnected, HEX);
      Usb.setUsbTaskState(USB_STATE_RUNNING);
    }
  }
  
  if(Usb.getUsbTaskState() == USB_STATE_RUNNING){
    Serial.println("Polling");
    WATTSUP_poll();
  }
}

void WATTSUP_init(void){
  Serial.println("INSIDE THE INIT LOOP");
  byte rcode = 0;
  byte i;
  USB_DEVICE_DESCRIPTOR* device_descriptor;
  wattsup_status = statusDeviceConnected;
  
  ep_record[CONTROL_PIPE] = *(Usb.getDevTableEntry(0,0));  //copy Endpoint Zero
  //make sure if the ep_record[Control Pipe] is good
  
  ep_record[OUTPUT_PIPE].epAddr = 0x02;
  ep_record[OUTPUT_PIPE].Attr = EP_BULK;
  ep_record[OUTPUT_PIPE].MaxPktSize = EP_MAXPKTSIZE;
  ep_record[OUTPUT_PIPE].Interval = EP_POLL;
  ep_record[OUTPUT_PIPE].sndToggle = bmSNDTOG0;
  ep_record[OUTPUT_PIPE].rcvToggle = bmRCVTOG0;

  ep_record[INPUT_PIPE].epAddr = 0x81;
  ep_record[INPUT_PIPE].Attr = EP_BULK;
  ep_record[INPUT_PIPE].MaxPktSize = EP_MAXPKTSIZE;
  ep_record[INPUT_PIPE].Interval = EP_POLL;
  ep_record[INPUT_PIPE].sndToggle = bmSNDTOG0;
  ep_record[INPUT_PIPE].rcvToggle = bmRCVTOG0;
  
  Usb.setDevTableEntry(WATTSUP_ADDR, ep_record);
   
  rcode = Usb.getDevDescr(WATTSUP_ADDR, ep_record[CONTROL_PIPE].epAddr, DEV_DESCR_LEN, (char *)&buf);
  if(rcode){
    Serial.println("Cannot get Descriptor");
    return;
  }
  device_descriptor = (USB_DEVICE_DESCRIPTOR *) &buf;
  if((device_descriptor->idVendor != WATTSUP_VID) || (device_descriptor->idProduct != WATTSUP_PID)){
    Serial.println("The End Device Is Unknown.");
    return;
  }
  Serial.println("Succeed");
    
  rcode = Usb.setConf(WATTSUP_ADDR, ep_record[CONTROL_PIPE].epAddr, WATTSUP_CONFIGURATION );
  if( rcode ) return;
  wattsup_status |= statusUSBConfigured;
  Serial.println("Configured");
    
  WATTSUP_request();
  Serial.println("Connected");
  
  delay(200);  
}

void WATTSUP_request(void){
  byte rcode = 0;
  byte i = 0;
  char buf[64] = {0};
  for (i=0; i<WATTSUP_CLEAR_COMMAND_LEN; i++) {
    buf[i] = pgm_read_byte_near(clear_command+i);
  }
  rcode = Usb.outTransfer(WATTSUP_ADDR, ep_record[OUTPUT_PIPE].epAddr, WATTSUP_CLEAR_COMMAND_LEN, buf);
  for( i = 0; i < WATTSUP_CLEAR_COMMAND_LEN; i++ ) {
    Serial.print( buf[i]);
  }
  if(rcode){
    Serial.println("Failed");
    return;
  }
  Serial.println("Issued the clear command.");
  
  delay(200);
  
  for (i=0; i<WATTSUP_HEADER_COMMAND_LEN; i++) {
    buf[i] = pgm_read_byte_near(header_command+i);
  }
  rcode = Usb.outTransfer(WATTSUP_ADDR, ep_record[OUTPUT_PIPE].epAddr, WATTSUP_HEADER_COMMAND_LEN, buf);
    for( i = 0; i < WATTSUP_CLEAR_COMMAND_LEN; i++ ) {
    Serial.print( buf[i]);
  }
  if(rcode){
    Serial.println("Failed");
    return;
  }
  Serial.println("Issued the header command.");

  wattsup_status |= statusWATTSUPConnected;
  delay(200);
  return;
}


void WATTSUP_poll(void){
  char buf[0x0040] = { 0 };      //keyboard buffer
  byte i = 0;
  byte rcode = 0;
  
  rcode = Usb.inTransfer(WATTSUP_ADDR, ep_record[INPUT_PIPE].epAddr, 8, buf);
  
  if(rcode){
    Serial.println("Failed polling");
    return;
  }
  
  for( i = 0; i < 0x0040; i++ ) {
       Serial.print( buf[i]);
  }
  
  return;
}

Now, first of all, I dont know if I am doing the correct thing to start a bulk out transfer, furthermore, I dont know if I have the setting or procedure correct. The bottom line is, I cannot get any data from the device. Wondering if anyone has any idea what I have done wrong. Thanks so much.

-klo

First of all, I am new to Arduino

Welcome.
However I think you have chosen a mighty challenging project. Until recently making a USB host was considered not possible. Now it is but the arduino is not a very easy platform to implement one due to the small memory both program and storage.
From Skarkfuns website:-

I think it is unlikely you will see a large library of device drivers. They take a significant time to create, and there is usually no support from the USB device manufacturer.
The arduino is memory challenged, which makes drivers for memory sticks and cameras most difficult.
I personally use the USB Host shield, when other solutions are not available to support a specific device or interface. As a first choice I avoid USB completely, because it does put constraints on the whole arduino code operation to be non blocking, so for bluetooth serial profile, would still use a bluetooth serial module. When USB Host is needed, I still look if Vinculum or GHI, can already do what I need already. then I use the shield.
With more chips having USB Host ports (PIC32, AVR8, AVR32, ARM, ..) more drivers will come available and easier to port to arduino. Especially when free stacks like the ones from Igor and LUFA are available.

Your USB product "WATTSUP" has enumerated as an FT232R device, so your communication will be to support this serial converter first, then any serial protocol on top.

Do you have the serial protocol and parameters, if not you will have to figure them from watching the serial port on a working PC interaction.

So first you need to set up the FTDI baud rate through EP0 and also any control lines. Then you can in and out through the bulk endpoints as a pseudo serial port

I have some working code for driving these FTDI devices. Post your problem on circuits@home or Sparkfun under USB host.

Hi,

Thanks for the replies.

Your USB product "WATTSUP" has enumerated as an FT232R device, so your communication will be to support this serial converter first, then any serial protocol on top.

Do you have the serial protocol and parameters, if not you will have to figure them from watching the serial port on a working PC interaction.

i-Bot, I actually modified the code from the PS3 controller example from circuit@home. It seems to me to talk to this WattsUp device shouldnt be that hard, the only difference is the Bulk Transfer, where I have no examples and not sure if I implemented correctly. I will also post the question on circuit@home and Sparkfun soon.

-klo

Several bulk transfer examples are posted. Data stage of control transfer is a bulk transfer, so you can look right at the code of control transfer function in the library. Also, interrupt transfer is the same as bulk; I have some keyboard polling code on my site. Also, digital camera control code works on bulk transfers, you will find plenty of outTransfer calls in Transaction function in ptp.cpp.

I looked at the code you've posted and it seems to be correct, so if Usb.outTransfer returns zero then your device is happy and problem is not in the transfer itself but in the data transferred.

I-Bot, if you'd like to share your FTDI code, I will be happy to host it.

Oleg
Circuits@Home

I looked at the code you've posted and it seems to be correct, so if Usb.outTransfer returns zero then your device is happy and problem is not in the transfer itself but in the data transferred.

Hi Felis,

Thanks for the information. I have checked my outTransfer returns zero, so I am assuming it is happy. I agree the problem is probably in the inTransfer part, however, I have no idea if I have set my endpoints (eg. address, etc) or other settings correctly corresponding to the USB information of the WattsupPro. Can you kindly have a look and let me know if I have them right?

Moreover, I have read the keyboard polling code and it uses set protocol or other code that use the setup report which I dont know if they can be used as the Bulk Out transfer. Sorry for my little knowledge on USB.

-klo

As I said earlier, you are talking to an FT232R device over USB, which is a USB to serial converter.

You must first set the baud rate at which the FT232R talks to the WATTSUP. Until you do this you will not get anything sensible.

Second talking to the FTDI chip requires a modification to the USB libraries from Oleg. This is required to make the receive function return the length received. The first two bytes returned by the chip are the status and the received data follows ( or not !).

The updated drivers and the fact I made the FTDI interrupt driven so as to be non blocking is why I did not post yet.

Do you have documentation on the protocol and baud rate ?

Hi iBot,

The protocol can be found here https://www.wattsupmeters.com/secure/downloads/CommunicationsProtocol090824.pdf

Where it said:

  1. Serial Format. Data is transmitted and received as standard RS232
    data at 115200 baud, 8 data bits, 1 stop bit and no parity bit.
  2. Character Format. Data consists of standard ASCII characters. Numbers are represented as strings of ASCII characters with no binary numbers.
  3. Packets. All information is contained in packets. All characters
    outside of a packet are ignored. Packets begin with a pound sign "#" and end with a semicolon ";".

Hmm.. I didnt know that I need to modify Oleg's library... maybe I should look into that.

-klo

Great, so we need to set the baud to 115200. The other thing to be careful of is that when they talk about packets, these may not correspond to USB packets, there is a serial data stream in between, so WATTSUP data packets may be over multiple USB packets.

My FTDI routines are basically the same as the serial.read() and serial.write(char) and a ftdi_baud(long) to set the baud rate.

The library change is quite simple and uses a pointer to the length for a version of Usb.inTransfer, so the actual length can be returned.

I also made the whole USB interrupt driven, so no need to write non blocking loop() routines.

Drop me a PM with your email, and I will send example.

Hi,
I also have the usb shield. How exactly did you connect it to the arduino?
Is there some type of documentation?
Best,
Ingo

Hi all,

Im doing something similar but with a HUAWEI E160, and trying to send AT commands and read responses (this is working via python and my laptop currently, but i'm having troubling getting the arduino to talk to it through the usb shield).

I'm trying a slightly modified version of the code posted above, with device settings adjusted for this device.

For some reason, the code is getting stuck before the outTransfer in the request function. I've tried a number of different basic AT commands, but it always holds up at the same place.

Any tips on what might be happening?

Many thanks,
Jeremy

#include <Spi.h>
#include <Max3421e.h>
#include <Max3421e_constants.h>
#include <Usb.h>
#include <avr/pgmspace.h>

#define HUAWEI_CONFIGURATION  1
#define HUAWEI_ADDR    0x01
#define HUAWEI_VID    0x12D1
#define HUAWEI_PID    0x1003
#define HUAWEI_NUM_EP    2
#define EP_MAXPKTSIZE    512
#define EP_BULK    0x02
#define EP_POLL    0x01 // Not sure
#define CONTROL_PIPE    0 // Not sure
#define INPUT_PIPE    1 // Not sure
#define OUTPUT_PIPE    2 // Not sure
#define DEV_DESCR_LEN    12

#define HUAWEI_MFG_COMMAND_LEN    6
#define HUAWEI_MODEL_COMMAND_LEN    6

#define statusDeviceConnected 0x01
#define statusUSBConfigured 0x02
#define statusHUAWEIConnected 0x04
#define statusReportReceived 0x08

char buf[64] = {0};    //Gemeral purpose buffer for USB data
static unsigned char huawei_status;

//prog_char clear_command[] PROGMEM = {0x23,0x52,0x2C,0x57,0x2C,0x30,0x3B};
//prog_char header_command[] PROGMEM = {0x23,0x44,0x2C,0x52,0x2C,0x30,0x3B};
prog_char mfg_command[] PROGMEM = {0x41,0x54,0x2b,0x47,0x4d,0x49}; // AT+GMI
prog_char model_command[] PROGMEM = {0x41,0x54,0x2b,0x47,0x4d,0x4d}; // AT+GMM

void setup();
void loop();

EP_RECORD ep_record[HUAWEI_NUM_EP];
MAX3421E Max;
USB Usb;


void setup(){
  byte tmpdata = 0;
  Serial.begin(115200); 
  Serial.println("Start");
  Max.powerOn();
  delay(4000);
}

void loop(){
  Max.Task();
  Usb.Task();
  if(Usb.getUsbTaskState() == USB_DETACHED_SUBSTATE_INITIALIZE){
    Serial.println("USB_DETACHED_SUBSTATE_INITIALIZE");
    huawei_status = 0;  
  }
  if(Usb.getUsbTaskState() == USB_STATE_CONFIGURING) {  
    Serial.println("START TO INIT");
    HUAWEI_init();
    if(huawei_status & statusHUAWEIConnected){
      Serial.println(huawei_status & statusHUAWEIConnected, HEX);
      Usb.setUsbTaskState(USB_STATE_RUNNING);
    }
  }
  
  if(Usb.getUsbTaskState() == USB_STATE_RUNNING){
    Serial.println("Polling");
    HUAWEI_poll();
  }
}

void HUAWEI_init(void){
  Serial.println("INSIDE THE INIT LOOP");
  byte rcode = 0;
  byte i;
  USB_DEVICE_DESCRIPTOR* device_descriptor;
  huawei_status = statusDeviceConnected;
  
  ep_record[CONTROL_PIPE] = *(Usb.getDevTableEntry(0,0));  //copy Endpoint Zero
  //make sure if the ep_record[Control Pipe] is good
  
  ep_record[OUTPUT_PIPE].epAddr = 0x01;
  ep_record[OUTPUT_PIPE].Attr = EP_BULK;
  ep_record[OUTPUT_PIPE].MaxPktSize = EP_MAXPKTSIZE;
  ep_record[OUTPUT_PIPE].Interval = EP_POLL;
  ep_record[OUTPUT_PIPE].sndToggle = bmSNDTOG0;
  ep_record[OUTPUT_PIPE].rcvToggle = bmRCVTOG0;

  ep_record[INPUT_PIPE].epAddr = 0x81;
  ep_record[INPUT_PIPE].Attr = EP_BULK;
  ep_record[INPUT_PIPE].MaxPktSize = EP_MAXPKTSIZE;
  ep_record[INPUT_PIPE].Interval = EP_POLL;
  ep_record[INPUT_PIPE].sndToggle = bmSNDTOG0;
  ep_record[INPUT_PIPE].rcvToggle = bmRCVTOG0;
  
  Usb.setDevTableEntry(HUAWEI_ADDR, ep_record);
  
  rcode = Usb.getDevDescr(HUAWEI_ADDR, ep_record[CONTROL_PIPE].epAddr, DEV_DESCR_LEN, (char *)&buf);
  if(rcode){
    Serial.println("Cannot get Descriptor");
    return;
  }
  device_descriptor = (USB_DEVICE_DESCRIPTOR *) &buf;
  if((device_descriptor->idVendor != HUAWEI_VID) || (device_descriptor->idProduct != HUAWEI_PID)){
    Serial.println("The End Device Is Unknown.");
    return;
  }
  Serial.println("Succeed");
    
  rcode = Usb.setConf(HUAWEI_ADDR, ep_record[CONTROL_PIPE].epAddr, HUAWEI_CONFIGURATION );
  if( rcode ) return;
  huawei_status |= statusUSBConfigured;
  Serial.println("Configured");
    
  HUAWEI_request();
  Serial.println("Connected");
  
  delay(200);  
}

void HUAWEI_request(void){
  byte rcode = 0;
  byte i = 0;
  char buf[64] = {0};
  for (i=0; i<HUAWEI_MFG_COMMAND_LEN; i++) {
    buf[i] = pgm_read_byte_near(mfg_command+i);
  }
//  Serial.println("Before outTransfer");
  rcode = Usb.outTransfer(HUAWEI_ADDR, ep_record[OUTPUT_PIPE].epAddr, HUAWEI_MFG_COMMAND_LEN, buf);
  Serial.println("After outTransfer");
  for( i = 0; i < HUAWEI_MFG_COMMAND_LEN; i++ ) {
    Serial.print( buf[i]); 
  }
  if(rcode){
    Serial.println("Failed");
    return;
  }
  Serial.println("Issued the mfg command.");
  
  delay(200);
  
  huawei_status |= statusHUAWEIConnected;
  delay(200);
  return;
}


void HUAWEI_poll(void){
  char buf[0x0040] = { 0 };      //keyboard buffer
  byte i = 0;
  byte rcode = 0;
  
  rcode = Usb.inTransfer(HUAWEI_ADDR, ep_record[INPUT_PIPE].epAddr, 8, buf);
  
  if(rcode){
    Serial.println("Failed polling");
    return;
  }
  
  for( i = 0; i < 0x0040; i++ ) {
       Serial.print( buf[i]);
  }
  
  return;
}
Device Descriptor:
bcdUSB:             0x0200
bDeviceClass:         0x00
bDeviceSubClass:      0x00
bDeviceProtocol:      0x00
bMaxPacketSize0:      0x40 (64)
idVendor:           0x12D1
idProduct:          0x1003
bcdDevice:          0x0000
iManufacturer:        0x03
0x0409: "HUAWEI Technology"
iProduct:             0x02
0x0409: "HUAWEI Mobile"
iSerialNumber:        0x00
bNumConfigurations:   0x01

ConnectionStatus: DeviceConnected
Current Config Value: 0x01
Device Bus Speed:     High
Device Address:       0x01
Open Pipes:              2

Endpoint Descriptor:
bEndpointAddress:     0x81  IN
Transfer Type:        Bulk
wMaxPacketSize:     0x0200 (512)
bInterval:            0x00

Endpoint Descriptor:
bEndpointAddress:     0x01  OUT
Transfer Type:        Bulk
wMaxPacketSize:     0x0200 (512)
bInterval:            0x00

Configuration Descriptor:
wTotalLength:       0x0020
bNumInterfaces:       0x01
bConfigurationValue:  0x01
iConfiguration:       0x01
0x0409: "Qualcomm Configuration"
bmAttributes:         0xE0 (Bus Powered Self Powered Remote Wakeup)
MaxPower:             0xFA (500 Ma)

Interface Descriptor:
bInterfaceNumber:     0x00
bAlternateSetting:    0x00
bNumEndpoints:        0x02
bInterfaceClass:      0x08
bInterfaceSubClass:   0x06
bInterfaceProtocol:   0x50
iInterface:           0x00

Endpoint Descriptor:
bEndpointAddress:     0x81  IN
Transfer Type:        Bulk
wMaxPacketSize:     0x0200 (512)
bInterval:            0x00

Endpoint Descriptor:
bEndpointAddress:     0x01  OUT
Transfer Type:        Bulk
wMaxPacketSize:     0x0200 (512)
bInterval:            0x00