Keyglove Serial Communication Protocol Draft

One of my main goals for the Keyglove is to allow it to be used on as many devices and operating systems as possible with no complicated driver installation or configuration every time you set it up. Using a USB connection and Human Interface Device (HID) profiles, along with the Bluegiga WT12 Bluetooth module that also supports HID profiles, the Keyglove can appear to the host devices as a standard keyboard, mouse, or joystick, none of which require drivers. This allows for basic usage with almost no work.

But what about special types of usage? What about reconfiguring the Keyglove’s behavior, even if most of the time you do just want to use it as a keyboard and mouse? What about extending the capabilities beyond what I imagined, or implementing your own special driver for your particular hardware or software application? Obviously a HID-only approach is too limited. So, we turn to basic serial communication, which is one of the easiest methods to communicate with a hardware device in a way that pretty much anyone can work with, regardless of the platform. It works the same over Bluetooth as it does over a USB virtual serial port (which is built into the main Keyglove processor), and it’s also easy to adapt to a direct connection to another microcontroller, should anyone want to do that.

Communication Requirements

The Keyglove is not an especially complicated device, really, but there are quite a few different things to control, both incoming and outgoing. Here’s my best attempt at an outline-style list of all current or immediately planned functionality:

  • Touch sensors
    • Sensor logic status
    • Sensor base combination touch status
  • Motion sensors
    • Accelerometer status
    • Accelerometer raw measurements
    • Accelerometer filtered measurements
    • Gyroscope status
    • Gyroscope raw measurements
    • Gyroscope filtered measurements
    • Filtered orientation data
    • Filtered movement data
    • Motion detected
    • Gesture detected
  • Feedback
    • Blink LED status
    • RGB LED status
    • Piezo buzzer status
    • Vibration motor status
  • Configuration
    • Device status
      • Communication protocol version
      • Device software version
      • Controller board hardware version
      • Motion sensor hardware present
      • Touch sensor hardware present
      • Wireless hardware present
      • Feedback hardware present
      • EEPROM usage
      • SRAM usage
      • Motion data output format
      • Motion sensor calibration values
      • Battery status
    • Touchset definition
    • Motion control scaling

There’s a lot of stuff here, and I can guarantee it will have more added to it in the future, either through new developments or when I remember features that aren’t coming to mind at the moment. This list will serve as a basis for further development though, since I know I’ll want to have at least this much functionality.

Human-Readable vs Binary Language Considerations

Once we have a list of what data and commands must be supported, we need some kind of language to communicate them. There are two obvious choices: a language that is human-readable, and one that is not human-readable. The advantage to the human-readable language is that it is very handy for testing, debugging, and overall obviousness, since…well…humans can read it. The disadvantage is that it usually takes far, far more digital information than is strictly necessary. In contrast, the non-human-readable language can be very efficient and concise, but it has to be translated from its original form into something that is easily readable.

Let’s say the text form of the accelerometer measurement report is like this:

a -18 32 104 -10 28 65 10 22 80 -221 504 1822
(46 bytes)

This represents x/y/z axis raw values, filtered values, velocity values, and position values. This format requires 46 bytes total (45 data and one terminating indicator, such as a NULL or carriage return). This is an average case sample made up of a variety of readings, which may each be between 1 and 6 digits depending on their actual value (16-bit signed integers, which range from -32768 to +32767). A best case sample would be:

a 0 0 0 0 0 0 0 0 0 0 0 0
(26 bytes)

And a worst case sample would be:

a -10000 -10000 -10000 -10000 -10000 -10000 -10000 -10000 -10000 -10000 -10000 -10000
(86 bytes)

A 16-bit signed integer value requires exactly two bytes of data to store and transfer. If we don’t need to display the human-readable decimal value, then we can leave it in this 2-byte format. Assuming we use one special byte to indicate the command packet start code, one to indicate the command type, one to indicate the length (0-255), and the rest for the actual data, that means we need [3 + (data length)] bytes for each report.

For each accelerometer reading, which reports 12 2-byte values, that means a grand total of [3 + 24] = 27 bytes. This is actually beat by our extremely rare best-case sample of human-readable data, but only 59% as large as the average sample, and only 31% as large as the worst sample. Additionally, it can be beneficial to know that the quantity of data to send will be exactly the same every time, since it makes it easier to know how much time you need to allot to that part of the code, or for how long some other process should be temporarily put on hold.

Sending as little data as possible is a good goal, since it means there is more time for the processor to be busy with other things. If your device is wireless (as the Keyglove is), that also may translate into longer battery life. Unless there is a clear reason why having human-readable language is important, then using a more efficient binary language should at least be an option.

Serial Port Limitations

One really important requirement here is to make sure that whatever protocol you choose, the transceiver (serial/UART in this case) can actually accommodate the throughput you need to use it. Let’s say your serial port is running at 115,200 bps, 8 data bits, no parity, 1 stop bit. Serial communications also include a start bit for each byte of data, which means that typically settings require 10 bits per byte of data sent. We can therefore calculate:

115,200 bps / 10 bits/byte = 11,520 bytes per second

This “11,520 bytes per second” value is the absolute theoretical maximum, and there are other factors which often make the realistic maximum notably lower than this. However, now let’s say (in the case of the Keyglove) that the microcontroller can’t push data out that fast over the serial port. The Keyglove runs an AT90USB1287 chip at 3.3v, and Atmel says the maximum clock speed at that voltage is 8 MHz. An MCU running at 8 MHz produces too much error for 115.2K (I know, I’ve tried). The fastest we can reliably send with an 8 MHz MCU is actually 38,400 BPS.

This means, by quick calculation, that the maximum theoretical limit for sending data is 3,840 bytes per second, and the realistic maximum is less. How will that work out for us?

Let’s say the accelerometer is taking measurements at 100Hz (i.e. 100 times per second), and we need to report every one of those measurements. That means, by another quick calculation, that we’d be sending 2700 bytes per second if we use the binary protocol. Aha! That should work just fine. What if we send it using the human-readable protocol? That works out to be 4600 bytes per second using the average sample case, which clearly won’t work. Uh oh.

Of course, in many cases, we may not need to actually send all of that data all the time. We might be able to cut the resolution of the final values in half (8-bit integers instead of 16-bit), which would drop the packet size down to 15 bytes instead of 27 (and throughput requirement to 1500 bytes per second). We might be able to transmit only the filtered acceleration readings, and leave out the raw values and velocity and position calculations. This would drop the packet size and throughput down to 9 bytes and 900 bytes per second, respectively. We could do both and just send 8-bit accelerometer values, giving us 6-byte packets and 600 bytes-per-second throughput. We could drop the frequency down to 50 Hz, and cut the throughput in half even though the packet size remains the same. There are quite a few options. In the case of accelerometer data, it is unlikely for my needs that the continuous measurements will need to be sent at all. Instead, I will only be sending filtered and smooth movement data (e.g. mouse control), and only when movement is actually happening.

On the other hand, depending on your project, you might also be able to run the MCU at 5v, allowing 16 MHz operation and 115.2K port speed. That gives you a lot more room to play. You can actually run the AT90USB1287 at 16 MHz even at 3.3v, and it does still work (I’ve tried this too). It’s just running it a bit above spec, so watch out for excess heat, and I assume no responsibility if you let all of the magic smoke out of your precious processor.

Binary Packet Structure

I figure that I’ll use binary data going from the Keyglove to the host (except in rare debug cases), and parse both binary and plain text commands coming from the host to the Keyglove. Due to the fact that control and reconfiguration commands from the host will likely be very, very rare in general, bandwidth should not be a problem. Supporting both will make it easier for other developers to do quick testing with serial terminal software, for example, without requiring them to either look up or be familiar with each binary command they want to use. Each binary command should have a human-readable counterpart.

If you’re using a terminal program, you might type:

set rgb red solid

…while the binary command might look something like the following sequence of hex values:

table.packet { border: 1px solid #CCC; border-collapse: collapse; }
table.packet th { font-weight: bold; font-family: Consolas, “Courier New”, monospace; padding: 4px 6px; border: 1px solid #CCC; background-color: #EEE; }
table.packet td { vertical-align: middle; padding: 4px 6px; border: 1px solid #CCC; }

0xB8 0x02 0x85 0x01

What does this sequence of bytes mean? Well, the basic packet structure starts with “trigger” byte (0xB8). I chose B8 because it’s kind of like the word “bait,” and I’m funny that way. This indicates to the Keyglove (or the host, if it’s going the other direction) that a special Keyglove packet is coming. The next byte indicates the length of the packet data (not including the bait or length bytes themselves), and all following bytes are the actual data.

[0] 0xB8 (the bait byte)
[1] 0x00-0xFF (data length, 0-255)
Data bytes

It’s pretty straightforward. Also, notice that you can’t have a packet with more than 255 data bytes. That’s the limit with this version of the protocol. The only thing I can imagine that would require sending or receiving more data than that at one time is loading or saving touchset definitions, but that (and other future stuff) should be easily accomplished by splitting the data into multiple packets.

The actual payload of the packet, contained in the data bytes, can be anything, as long as both ends are aware of the format. In this simple example (which may or may not end up being incorporated exactly into the protocol), there is a command byte (0x85) and a value byte (0x01). I have arbitrarily assigned the “set rgb red” command to be indicated by 0x85, and the setting “solid” to be indicated by 0x01.

Again, this is only an example, but it demonstrates the basic concept. After I actually work through all of the command details, I may decide to increase the length and/or command code to use two bytes (65,536 possibilities instead of only 256). The full details of the protocol will be readily available as soon as it’s completed (or even partially completed).

  1. Jeff,

    I too am looking for a simple human readable way of sharing a common serial line with different devices. If you know of one, please lay it on me, and our product can work together!

    I work at Cisco built a few hundred devices based on the Mega168 for internal IR settop testing fixed at 115.2KBaud. Never had a complaint about the serial port communication. I used 7.3728 MHz (115.2 KHz x 64) crystals on the megas, seemed to work well.

    Enjoy, Mark

    • Thanks Mark!

      That is a brilliant idea. Having 115.2k available would be a great help, at least for the time being. With external IMU functions handled by the MPU-6050 (not totally working yet, but promising), the MCU is running at only about 20% duty cycle at 8MHz. I’m sure I can afford dropping that down to 7.3728, or possibly even half that for increased battery life.

      I never would have thought of just tweaking the clock speed. Duh. Thanks again!

Leave a Reply