USB Host Shield w/ Uno to poll CDC device [SOLVED]

Hello,

I am attempting to use an Arduino Uno with a SparkFun USB host shield to retrieve serial data from a USB CDC device (fluid infusion pump) by periodically polling it.

Harvard Apparatus Pump 11 Elite
Device manual

I have successfully retrieved USB description information (see attached .txt file) from the device using the host shield.

To poll the device I need to enter: status\r or status\r\n - they both seem to work. In response, the device gives its current infusion status:

During infusion

\n166666666666 71691 10976820318950 I..TI.\r\n>

Target volume reached

\n166666666666 71691 10976820318950 i..TIT\r\nT*

Not infusing

\n166666666666 71691 10976820318950 i..TI.\r\n:

Not infusing (stalled)

\n166666666666 71691 10976820318950 i..STI.\r\n*

Withdrawing

\n166666666666 71691 10976820318950 W..TI.\r\n<

166666666666 = infusion rate in femtoliters per second —> 10 mL/min
71691 = infusion time elapsed in milliseconds —> 71.691 s
10976820318950 = infused volume in femtoliters —> 10.976 mL

I have attached an example snapshot of a RealTerm output between my device and PC.

My end goal is to extract the infused volume, convert it to milliliters then output a PWM-filtered signal proportional to its output so that I can record the analog value with my data acquisition system.

At this moment I have been trying to modify the ‘acm_terminal’ example code for the data extraction portion of this, but I need a bit of help…

So far, I have modified the ‘acm_terminal’ code to send status\r\n instead of performing a Serial.read() if serial is available, and it is printing out a “1” each time.

This acm code uses 64 as the max packet size, while the USB description of the device uses 40. Wondering if there are other adaptations that will likely need to be made to get this working…

Thank you for your time!

infusion_pump_usb_desc.txt (1.32 KB)

infusion_pump_status_output2.PNG

The max packet length in the device descriptor listing is showing a hex value. 40 hexadecimal equals 64 decimal so I do not think the packet length in the sketch should be changed.

Post the code. If the sketch is sending the status command every time loop runs, there may not be enough
time for the device to send the response to come back. Or increase the delay between sending the command and checking for the response. In general, using large delays is not a good idea but I would use it to see if this sketch works at all. Note the response may not come back in a single USB packet so you may need to accumulate the response line in a buffer until the CR or LF is received.

I am using the acm_terminal script out of the box, with the necessary #defines and setup() commands needed to get my SparkFun USB host shield working.
acm_terminal_code

Also… it looks like there is a drop-down option in the Serial Monitor that lets you add carriage return and newline characters at the end of your serial input…

Good news is… I can replicate RealTerm in Arduino Serial Monitor by sending the “status\r\n” command and retrieve the devices status! That’s a relief!

Looks like I need to somehow change the following code below in the acm_terminal code attached so that I don’t have to manually enter “status\r\n” but instead the program will submit it automatically…

/* reading the keyboard */
       if(Serial.available()) {
         uint8_t data= Serial.read();

Seems like this reads each character byte by byte. Any ideas on how I can get this to automatically send the “status\r\n” instead of waiting for me to type it in?

Thank you!

I managed to create a character array and loop through each character in "status\r\n" and it kind of works... but the timing seems off and I'm missing output characters.

Try the elapsedMillis library to send the status command once every second (or other interval of your choice).

#include <elapsedMillis.h>

elapsedMillis statusMillis;

loop()
{
    if( Acm.isReady()) {
        uint8_t rcode;

        if (statusMillis >= 1000) {
            statusMillis = 0;
            rcode = Acm.SndData(8, "status\r\n");
            if (rcode)
                ErrorMessage<uint8_t>(PSTR("SndData"), rcode);
        }
    ... rest of program
    }

}

Hi gdsports, thank you again!

I implemented this and it works the first time the "status\r\n" command is sent, but after that it no longer detects any input serial from the CDC device.

I ended up getting this to work by modifying this back to sending a single character at a time with a for loop in a separate callable function 'SendMessage()' without the error message if statement, as per this thread:

For those interested, here is the ‘acm_terminal’ code that I managed to modify and get working for my purposes.

Not the cleanest implementation, but a good start.

#include <cdcacm.h>
#include <usbhub.h>
#include <elapsedMillis.h>

#include "pgmstrings.h"

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

#define MAX_RESET 7 //MAX3421E pin 12
#define MAX_GPX   8 //MAX3421E pin 17

class ACMAsyncOper : public CDCAsyncOper
{
public:
    uint8_t OnInit(ACM *pacm);
};

uint8_t ACMAsyncOper::OnInit(ACM *pacm)
{
    uint8_t rcode;
    // Set DTR = 1 RTS=1
    rcode = pacm->SetControlLineState(3);

    if (rcode)
    {
        ErrorMessage<uint8_t>(PSTR("SetControlLineState"), rcode);
        return rcode;
    }

    LINE_CODING	lc;
    lc.dwDTERate	= 115200;
    lc.bCharFormat	= 0;
    lc.bParityType	= 0;
    lc.bDataBits	= 8;

    rcode = pacm->SetLineCoding(&lc);

    if (rcode)
        ErrorMessage<uint8_t>(PSTR("SetLineCoding"), rcode);

    return rcode;
}

USB     Usb;
//USBHub     Hub(&Usb);
ACMAsyncOper  AsyncOper;
ACM           Acm(&Usb, &AsyncOper);
elapsedMillis statusMillis;
uint8_t status_sent = 0;
char message[] = "status\r\n";
int messagelen = strlen(message);
uint8_t rcode;
uint8_t cdc_output[64];
uint8_t cdc_output_count = 0;
char infused_vol_string[64];
char infused_vol_substring[64];
uint16_t infused_vol_int_ul;
double upper_infused_vol_ul = 30000;
int PWM_PIN = 9;

void setup()
{

  for (uint8_t i=0; i<64; i++)
    cdc_output[i] = 0;

  pinMode(PWM_PIN, OUTPUT);
  analogWrite(PWM_PIN, 0);

  pinMode(MAX_GPX, INPUT);
  pinMode(MAX_RESET, OUTPUT);
  digitalWrite(MAX_RESET, LOW);
  delay(20); //wait 20ms
  digitalWrite(MAX_RESET, HIGH);
  delay(20); //wait 20ms
  Serial.begin( 115200 );
#if !defined(__MIPSEL__)
  while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
  //Serial.println("Start");

  if (Usb.Init() == -1)
      Serial.println("OSCOKIRQ failed to assert");

  delay( 200 );
}

void loop()
{
    Usb.Task();

    if( Acm.isReady()) 
    {
       //uint8_t rcode;

       /* reading the keyboard */
       //if(Serial.available()) {
        // uint8_t data= Serial.read();
         /* sending to the phone */
         //rcode = Acm.SndData(1, &data);
         //if (rcode)
          //  ErrorMessage<uint8_t>(PSTR("SndData"), rcode);
       //}//if(Serial.available()...

       if (statusMillis >= 200) 
       {
            if (status_sent == 0)
            {
              Send_Message();
              statusMillis = 0;
              status_sent = 1;
              //Serial.print('\n');
            }
            else
            {
              status_sent = 0; //incase it gets hung up
            }
        }

        /* reading the phone */
        /* buffer size must be greater or equal to max.packet size */
        /* it it set to 64 (largest possible max.packet size) here, can be tuned down
        for particular endpoint */
        uint8_t  buf[64];
        for (uint8_t i=0; i<64; i++)
          buf[i] = 0;
        
        uint16_t rcvd = 64;
        rcode = Acm.RcvData(&rcvd, buf);
         if (rcode && rcode != hrNAK)
         {
            ErrorMessage<uint8_t>(PSTR("Ret"), rcode);
         }

            //Serial.print(rcvd);
            if( rcvd ) //more than zero bytes received
            { 
              for(uint16_t i=0; i < rcvd; i++ ) 
              {  
                Serial.print((char)buf[i]);
                /*Serial.print(" - ");
                Serial.print(buf[i]);
                Serial.print('\n');*/
                cdc_output[cdc_output_count] = buf[i];
                cdc_output_count = cdc_output_count + 1;
                uint8_t infused_vol[64];
                uint8_t start_index;
                uint8_t infused_vol_len;
                uint8_t space_count = 0;
                
                if (buf[i]==42 or buf[i]==58 or buf[i]==60 or buf[i]==62)  //':' or '>' or '*' or '<'
                {
                  for(uint8_t i=0; i<cdc_output_count; i++)
                  {
                    if (space_count == 2)
                    {
                      infused_vol[i-start_index] = cdc_output[i];
                      infused_vol_len = infused_vol_len + 1;
                    }
                      
                    
                    if (cdc_output[i] == 32) //' '
                    {
                      space_count = space_count + 1;
                      if (space_count == 2)
                      {
                        start_index = i+1;
                      }
                    }

                    if (space_count == 3)
                    {                      
                      space_count = 0;
                      break;
                    }
                  }
                
                  for (uint8_t i=0; i<infused_vol_len; i++)
                  {
                     infused_vol_string[i] = (char)infused_vol[i];
                     infused_vol[i] = 0;
                  }

                  if (infused_vol_len < 10)
                  {
                    infused_vol_int_ul = 0;
                    infused_vol_substring[0] = '0';
                  }
                  else
                  {
                    for (uint8_t i=0; i<infused_vol_len-10;i++)
                    {
                       infused_vol_substring[i] = infused_vol_string[i];
                    }
                  }

                  String randomString(infused_vol_substring);
                  infused_vol_int_ul = randomString.toInt();
                  double infused_vol_ratio = infused_vol_int_ul/upper_infused_vol_ul;
                  uint8_t pwm_val = (uint8_t)(infused_vol_ratio*255);
                  
                  //Serial.print(infused_vol_int_ul);
                  //Serial.print("\r\n");                  
                  analogWrite(PWM_PIN, pwm_val);

                  infused_vol_int_ul = 0;
                  start_index = 0;                
                  status_sent = 0;
                  for (uint8_t i=0; i<64; i++)
                  {
                    infused_vol_string[i] = 0;                    
                    infused_vol_substring[i] = 0;
                    cdc_output[i] = 0;
                    cdc_output_count = 0;
                  }
              }
            }
         }
        //delay(10);
    }//if( Usb.getUsbTaskState() == USB_STATE_RUNNING..
}

int Send_Message()
{
  for (int i = 0; i < messagelen; i++)
  {
    uint8_t data = message[i];
    rcode = Acm.SndData(1, &data);
  }
}

Nice job! The USB Host library is not the friendliest code to work with so give yourself a hearty pat on the back.

gdsports:
Nice job! The USB Host library is not the friendliest code to work with so give yourself a hearty pat on the back.

Thanks again for pointing me in the right direction

One issue that I seem to be having is that the pin 9 output high level of the pwm is around 500 mV, which is about 10x less than what I expect. Because of this the resolution is not great for small changes.

Is there anything that I may have missed that I should modify to get the pwm output larger?

Also think that I'm going to need to get a 10+ bit DAC as the 8 bit pwm is a little low resolution for my liking.

I ended up going with the Adafruit MCP4725 (12-bit) breakout board and their Arduino library. I had to notch out a piece of the USB Host Shield to gain access to the Arduino SDA/SCL pins, but otherwise it worked like a charm.

Here’s the updated code:

#include <cdcacm.h>
#include <usbhub.h>
#include <elapsedMillis.h>
#include <Adafruit_MCP4725.h>

#include "pgmstrings.h"

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

#define MAX_RESET 7 //MAX3421E pin 12
#define MAX_GPX   8 //MAX3421E pin 17

class ACMAsyncOper : public CDCAsyncOper
{
public:
    uint8_t OnInit(ACM *pacm);
};

uint8_t ACMAsyncOper::OnInit(ACM *pacm)
{
    uint8_t rcode;
    // Set DTR = 1 RTS=1
    rcode = pacm->SetControlLineState(3);

    if (rcode)
    {
        ErrorMessage<uint8_t>(PSTR("SetControlLineState"), rcode);
        return rcode;
    }

    LINE_CODING lc;
    lc.dwDTERate = 115200;
    lc.bCharFormat = 0;
    lc.bParityType = 0;
    lc.bDataBits = 8;

    rcode = pacm->SetLineCoding(&lc);

    if (rcode)
        ErrorMessage<uint8_t>(PSTR("SetLineCoding"), rcode);

    return rcode;
}

USB     Usb;
//USBHub     Hub(&Usb);
ACMAsyncOper  AsyncOper;
ACM           Acm(&Usb, &AsyncOper);
elapsedMillis statusMillis;
uint8_t status_sent = 0;
char message[] = "status\r\n";
int messagelen = strlen(message);
uint8_t rcode;
uint8_t cdc_output[64];
uint8_t cdc_output_count = 0;
char infused_vol_string[64];
char infused_vol_substring[64];
uint16_t infused_vol_int_ul;
double upper_infused_vol_ul = 30000;
//int PWM_PIN = 9;
Adafruit_MCP4725 dac;

void setup()
{
  dac.begin(0x62);
  delay(10);
  dac.setVoltage(0, false);

  for (uint8_t i=0; i<64; i++)
    cdc_output[i] = 0;

  //pinMode(PWM_PIN, OUTPUT);
  //analogWrite(PWM_PIN, 0);

  pinMode(MAX_GPX, INPUT);
  pinMode(MAX_RESET, OUTPUT);
  digitalWrite(MAX_RESET, LOW);
  delay(20); //wait 20ms
  digitalWrite(MAX_RESET, HIGH);
  delay(20); //wait 20ms
  Serial.begin( 115200 );
#if !defined(__MIPSEL__)
  while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
  //Serial.println("Start");

  if (Usb.Init() == -1)
      Serial.println("OSCOKIRQ failed to assert");

  delay( 200 );
}

void loop()
{
    Usb.Task();

    if( Acm.isReady()) 
    {
       //uint8_t rcode;

       /* reading the keyboard */
       //if(Serial.available()) {
        // uint8_t data= Serial.read();
         /* sending to the phone */
         //rcode = Acm.SndData(1, &data);
         //if (rcode)
          //  ErrorMessage<uint8_t>(PSTR("SndData"), rcode);
       //}//if(Serial.available()...

       if (statusMillis >= 200) 
       {
            if (status_sent == 0)
            {
              Send_Message();
              statusMillis = 0;
              status_sent = 1;
              //Serial.print('\n');
            }
            else
            {
              status_sent = 0; //incase it gets hung up
            }
        }

        /* reading the phone */
        /* buffer size must be greater or equal to max.packet size */
        /* it it set to 64 (largest possible max.packet size) here, can be tuned down
        for particular endpoint */
        uint8_t  buf[64];
        for (uint8_t i=0; i<64; i++)
          buf[i] = 0;
        
        uint16_t rcvd = 64;
        rcode = Acm.RcvData(&rcvd, buf);
         if (rcode && rcode != hrNAK)
         {
            ErrorMessage<uint8_t>(PSTR("Ret"), rcode);
         }

            //Serial.print(rcvd);
            if( rcvd ) //more than zero bytes received
            { 
              for(uint16_t i=0; i < rcvd; i++ ) 
              {  
                Serial.print((char)buf[i]);
                /*Serial.print(" - ");
                Serial.print(buf[i]);
                Serial.print('\n');*/
                cdc_output[cdc_output_count] = buf[i];
                cdc_output_count = cdc_output_count + 1;
                uint8_t infused_vol[64];
                uint8_t start_index;
                uint8_t infused_vol_len;
                uint8_t space_count = 0;
                
                if (buf[i]==42 or buf[i]==58 or buf[i]==60 or buf[i]==62)  //':' or '>' or '*' or '<'
                {
                  for(uint8_t i=0; i<cdc_output_count; i++)
                  {
                    if (space_count == 2)
                    {
                      infused_vol[i-start_index] = cdc_output[i];
                      infused_vol_len = infused_vol_len + 1;
                    }
                      
                    
                    if (cdc_output[i] == 32) //' '
                    {
                      space_count = space_count + 1;
                      if (space_count == 2)
                      {
                        start_index = i+1;
                      }
                    }

                    if (space_count == 3)
                    {                      
                      space_count = 0;
                      break;
                    }
                  }
                
                  for (uint8_t i=0; i<infused_vol_len; i++)
                  {
                     infused_vol_string[i] = (char)infused_vol[i];
                     infused_vol[i] = 0;
                  }

                  if (infused_vol_len < 10)
                  {
                    infused_vol_int_ul = 0;
                    infused_vol_substring[0] = '0';
                  }
                  else
                  {
                    for (uint8_t i=0; i<infused_vol_len-10;i++)
                    {
                       infused_vol_substring[i] = infused_vol_string[i];
                    }
                  }

                  String randomString(infused_vol_substring);
                  infused_vol_int_ul = randomString.toInt();
                  double infused_vol_ratio = infused_vol_int_ul/upper_infused_vol_ul;
                  //uint8_t pwm_val = (uint8_t)(infused_vol_ratio*255);
                  uint32_t dac_val = (uint32_t)(infused_vol_ratio*4095);

                  
                  //Serial.print(infused_vol_int_ul);
                  //Serial.print("\r\n");                  
                  //analogWrite(PWM_PIN, pwm_val);

                  dac.setVoltage(dac_val, false);

                  infused_vol_int_ul = 0;
                  start_index = 0;                
                  status_sent = 0;
                  for (uint8_t i=0; i<64; i++)
                  {
                    infused_vol_string[i] = 0;                    
                    infused_vol_substring[i] = 0;
                    cdc_output[i] = 0;
                    cdc_output_count = 0;
                  }
              }
            }
         }
        //delay(10);
    }//if( Usb.getUsbTaskState() == USB_STATE_RUNNING..
}

int Send_Message()
{
  for (int i = 0; i < messagelen; i++)
  {
    uint8_t data = message[i];
    rcode = Acm.SndData(1, &data);
  }
}

Hi I try to use the usb host for read a gps usb. the code work, but the i can read only when open the serial monitor. it’s possible to read only without open the serial monitor? because I need read from usb and save in sd.