Shopping Cart

Posts

Adding a display to a digital scale using Arduino and USB Host shield

Arduino reading digital scale

Arduino reading digital scale


I am the proud owner of Stamps.com Model 510 5lb digital scale. It is a nice little scale which works very well (much better than Stamps.com service itself) while attached to my workstation. The scale doesn’t have a display making any kind of standalone use difficult. However, since the scale is a USB HID device reading data from it should be as easy as from a joystick and Arduino board should be adequate to provide a display function for it. To test this theory I made a simple setup consisting of Arduino UNO, USB Host shield and HD44780-compatible LCD display. I also wrote a small sketch which polls the scale and outputs the weight. The secondary objective of this project was to demonstrate LCD support in USB Host shield library.

For this project I used the following:

  1. An Arduino board. Standard size board, such as UNO, Duemilanove or Leonardo, will work
  2. USB Host Shield
  3. Toshiba HD44780-compatible LCD display, in 16×1 or 16×2 configuration. If you’re planning to use this sketch for something else, like data logging, the display is optional – all output from the scale is repeated to the serial port
  4. Stamps.com 5lb digital scale. Scales are standard HID devices with usage table 0x8d, therefore, scales from other brands may work as well with no or minimal modifications to the code
  5. USB Host library

The example code is also hosted at github, as well as in ‘examples’ section of the library under ‘HID’. It has been tested with Arduino IDE version 1.0.5.

In this project, the LCD is connected to the shield’s GPOUT pins, as documented in max_LCD.h header file. In addition to data lines, 5V and ground must also be connected to the shield’s 5V and GND terminals; the RW pin must be grounded – I do it on the LCD itself. In order to see the characters, the display must be biased – a 5K-10K pot with wiper on Vo and other two pins on 5V and ground will provide contrast adjustment.

Now it’s time to load a sketch, connect the scale to the USB port of the shield and open a terminal. If everything is good, the following will be printed:

Start
Weight: 0.00 oz

If a LCD is (properly) connected, similar information will be output to it also.

In addition to the weight, several diagnostic messages will be printed, indicating, for example, excessive weight. Below, I’m placing several books on a a scale, one by one, until a limit is reached:

Weight: 41.90 oz
Weight: 43.40 oz
Weight: 43.50 oz
Weight: 70.40 oz
Weight: 95.60 oz
Max.weight reached

As I said before, the scale is a HID device and it works similarly to any other HID device – after initialization it starts responding to requests from the host reporting its state. The code is very similar to one written to poll a Logitech joystick, the main differences being (obviously) report data structure and parsing as well as using an LCD for the output along with the terminal.

I recently started a github repository containing USB device traces. If you’re curious about the protocol details, this trace contains scale initialization performed by a Windows 7 PC, as well as report polling. For the rest, the HID support in the library is designed in such a way that almost everything happens automatically, the only device-specific piece of code being the parser. Let’s go through the steps to change a joystick parser into a scale parser.

The scale report is 6 bytes. Below is a report returned by empty scale. The meaning of the bytes, from left to right, is as follows:

03 04 0B FF 00 00
  • 03 – this is the report ID, always 3
  • 04 – status, in this case meaning the report containing correct weight
  • 0B – unit of measurement, in this case ounces
  • FF – exponent to the base of 10 to make a multiplier for the weight value contained in the next 2 bytes. It is a signed integer, in this case -1, which means the weight needs to be divided by 10
  • 00 00 – weight, in this case zero

Now let’s place something on a scale – like a 4.5oz phone. The report changes – the weight is now 0x2D or decimal 45.

03 04 0B FF 2D 00

Let’s now put a really big weight thus overloading the scale. We can see that the status has changed to 0x06 meaning overweight condition. In this case, the reported weight is invalid.

03 06 0B FF D5 04

More information about scale report format is available at usb.org website in HID section.

For our parser, this data structure is defined in scale_rptparser.h file and looks like this:

/* input data report */
struct ScaleEventData
{
  uint8_t reportID;	//must be 3
  uint8_t status;
  uint8_t unit;		
  int8_t exp;			//scale factor for the weight
  uint16_t weight;	//
};

The method which gets called every time a report has changed due to change in weight or status, is called ScaleEvents::OnScaleChanged(). This is how it looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
void ScaleEvents::OnScaleChanged(const ScaleEventData *evt)
{
 
	pLcd->clear();
  pLcd->home();
  pLcd->setCursor(0,0);
 
	if( evt->reportID != 3 ) {
 
		const char inv_report[]="Invalid report!";
 
		Serial.println(inv_report);
		LcdPrint(inv_report);
 
		return;
 
	}//if( evt->reportID != 3...
 
	switch( evt->status ) {
 
		case REPORT_FAULT: 
			Serial.println(F("Report fault"));
			break;
 
		case ZEROED:
			Serial.println(F("Scale zero set"));
			break;
 
		case WEIGHING: {
 
			const char progress[] = "Weighing...";
			Serial.println(progress);
			LcdPrint(progress);
			break;
		}
 
		case WEIGHT_VALID: {
 
			char buf[10];
      double weight = evt->weight * pow( 10, evt->exp );
 
 
 
      	Serial.print(F("Weight: "));
				Serial.print( weight );
				Serial.print(F(" "));
				Serial.println( UNITS[ evt->unit ]);
 
				LcdPrint("Weight: ");
				dtostrf( weight, 4, 2, buf );
				LcdPrint( buf ); 
				LcdPrint( UNITS[ evt->unit ]);
 
			break;
 
		}//case WEIGHT_VALID...
 
		case WEIGHT_NEGATIVE: {
 
			const char negweight[] = "Negative weight";
			Serial.println(negweight);
			LcdPrint(negweight);
			break;
		}
 
		case OVERWEIGHT: {
 
			const char overweight[] = "Max.weight reached";
			Serial.println(overweight);
			LcdPrint( overweight );
			break;
		}
 
		case CALIBRATE_ME:
 
			Serial.println(F("Scale calibration required"));
			break;
 
		case ZERO_ME:
 
			Serial.println(F("Scale zeroing required"));
			break;
 
		default:
 
			Serial.print(F("Undefined status code: "));
			Serial.println( evt->status );
			break;	
 
	}//switch( evt->status...
 
}
  • Lines 4-6 – here I’m clearing the LCD screen to prepare for the new output
  • Line 8 – I’m checking the report ID and return an error if the ID is wrong
  • Line 19 – the switch statement handling status field of the report. Almost every status means some kind of error so I just print the error message and return
  • Line 37 – this status means the valid weight is present in the report. I first calculate the real weight (line 40) and then output it

Now, Where does this method actually gets called? In the same file several lines earlier from a method called Parse(). HID driver periodically calls this name when it polls the HID device.

If you don’t need to write your own parser but just want to change the behaviour of this example, the only place which needs to be modified is OnScaleChanged() method.

Next I want to show how these classes are instantiated in the main sketch. Here’s the fragment of the definition section:

1
2
3
Max_LCD                                       LCD(&Usb);
ScaleEvents                                  ScaleEvents(&LCD);
ScaleReportParser                            Scale(&ScaleEvents);

The first line instantiates a LCD class and it is always defined like that. Second line instantiates ScaleEvents – the class where the most important method OnScaleChanged() resides. A pointer to LCD is passed to it. The last line define a report parser instance and pointer to ScaleEvents is passed to it.

The following statement inside a setup() links report parser to the HID driver. It is the last link in the chain – from this point, everything just starts working.

  if (!Hid.SetReportParser(0, &Scale))
      ErrorMessage<uint8_t>(PSTR("SetReportParser"), 1  );

For better understanding I suggest opening the joystick code mentioned in the beginning of the article and compare it to the scale example. Don’t try to understand every line, just pay attention to code elements explained above. If anything in the code is not clear please leave a comment and I’ll try to explain it better.

Oleg.

No related posts.

12 comments to Adding a display to a digital scale using Arduino and USB Host shield

  • Hz

    Hi, Oleg
    Have you ever provided some sketches that support more than 1 HID devices at same time? E.g. 1 Barcode scanner and 1 keyboard, which works great individually, but not works normally when together.

  • Edwin Edgerly

    Great write up! Thanks for doing this work. I think I will make one two :-)

  • Brandon

    Hi There Oleg,
    Is it possible to ‘poll’ the scale? How quickly can you get changes in weight from the scale?

    • The Usb.Task() does the polling, OnScaleChanged() will be called if scale state changes. In general, scales are slow devices so one reading every 1-2 seconds is probably the fastest rate you can expect.

  • Jonathan

    Hello
    Could the USB host shield talk with more then one scale?
    Looking to make a scale system with 4 scales to measure cross weight of an RC car like they do in full scale cars racing.
    Thanks

  • This should be possible, just use a hub.

  • wklee

    I had just purchased a USB Host Shield 2.0 via Citibank credit card. Your webpage keep complaining that my billing address does not match billing address of cardholder, which is definitely not the case. I received SMS from Citibank informing me the transaction is successful, so I am not sure what to do now, did I get the item purchased successfully?

    I already sent an email with details of transaction but no reply after 5 days. Sorry to post it here.

    • I’m sorry you’re having troubles. I have 2 questions:
      a) Was it a US credit card? International credit card transactions don’t work very well, try using Paypal instead. If you believe your card has been charged, get the transaction number from your bank so I can investigate it on my side
      b) What was the e-mail address you’ve sent the details to? Try one you received the registration info from or one listed in the “About” section.

  • wklee

    I am using an international credit card, and the email was sent to general@circuitsathome.com

    Anyway, so far the credit card doesn’t show any sign of being charged, I just received the SMS but does not see it get billed. I will contact you if it really get charged. I had purchased another set via TKJ electronics, and it works well. Thank you very much for the reply.

  • houlala

    Thank you for your work.

    I think it’s one of the simplest an cheapest way to get a precise weight from a scale (amplify the wheastone bridge signal easy but not precise, hacking lcd precise but you need time and oscilloscope)

    Just for the records your code is also compatible with dymo M25.
    I have bought one from ebay
    I haven’t modified a single line of code and it works!

    An other remark it’s not possible to get negative weight as the scale does not send negative weight through the usb.

    You saved me lot of time!

    thank you

Leave a Reply

  

  

  



You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">