home‎ > ‎

xbee, zigbee and the pyboard

The circuit below shows how I set up wireless communication using an XBee module, between my pyboard v1.0 and a laptop (Lenovo X230) running Linux Mint 17. The data comes from an accelerometer on a MPU6050 accelerometer/gyroscope unit, which is being used as part of a project to help people communicate through gesture recognition - more details here. The Xbee module is the XBee S2 with a PCB antenna which I got from Cool Components. To learn how to set up the XBee I bought a copy of this book.

This diagram shows how the XBee S2 and an MPU6050 module (the blue board on the right) are connected to the pyboard (the green board in the middle).

pyboard, xbee

How to physically connect your XBee modules to the pyboard and laptop?
You can buy little adapter boards from the usual suspects of adafruit, sparkfun and coolcomponents. I got mine from eBay, from China, as I had the time to wait for them to arrive. They seem to work all right. The controller board plugs into the laptop with a mini USB cable. The router board is connected using socket and pin cables to the pyboard using the wiring diagram shown in the fritzing layout diagram above.

The pictures below show the XBee s2 modules in their adapter boards. The controller is connected to the laptop and the router to the pyboard, which has the mpu6050 accelerometer/gyroscope board connected to it and tie-wrapped in place.

 XBee controller in an adapter board    
 XBee router connected to a pyboard using an adapter board, top view
xbee router connected to a pyboard
XBee router connected to a pyboard using an adapter board, bottom view
xbee router in an adapter board, underside, connected to a pyboard

To set up the XBee modules, download and install the XCTU software from digi here. Click on the 'find module' symbol which is on the top left of the screen and you will see a screen like the one below. Update the firmware to the latest. You need to have one module configured as a controller and one as the router. The controller will have firmware which starts with a 20, e.g. 20A7. The router will have firmware which starts with a 22, e.g. 22A7. Beginner's mistake - having the same firmware on both modules. I did this... They must be different! 

The PAN (Physical Area Network) ID has to be the same for both modules. Each module needs the other module's address set in the DH (Destination High) and DL (Destination Low) boxes - this is printed on the back of the XBee as well as displayed in the XCTU software as SH and SL. 

digi xctu software

The pyboard is programmed with micropython. I set up a UART on the pyboard with this code snippet:

  1.         uart = UART(3115200)
  2.         uart.init(115200, bits=8, parity=None, stop=1)
uart 3 is used as the pins for uart 1 are in use for communicating with the MPU6050 module. 

xbee DIN goes to TX on the pyboard = Y9

xbee DOUT goes to RX on the pyboard = Y10

xbee VCC goes to 3.3V on the pyboard

xbee GND goes to GND on the pyboard - as a rule, I hook this wire up first.

I configured the port to have a baud of 115200, 8 bit data no parity and 1 stop bit. To understand more about uart communication, refer to wikipedia here. The data framing diagram from the wikipedia article is informative:

wikipedia, uart, data framing

So we can see that to send an 8 bit character, even with a single stop bit, takes 11 bits of serial data. Keep this in mind when calculating your maximum data throughput.

To write the data I simply use:


Where out_string is the data being sent.

How to display the data?
I use CoolTerm. More details of how I use this serial port monitor to interact with the pyboard can be found here.
The data should stream across on /dev/usb0. You need to set up the port details in CoolTerm and then click on 'connect'. It just works. No pairing necessary.
The coolterm display shows incoming data. The data is being truncated as I am sending data faster than the setup can handle. How to fix this?

coolterm display for xbee data

Time to look at the python class pack. Building on the example given here, I played around in the Jupyter notebook:

  1. import struct
  2. import binascii
  4. values = (b'START',28276890.16601230.074078710.9207859,b'END')
  5. packer = struct.Struct('2shhfff2s')
  6. packed_data = packer.pack(*values)
  8. print('Original values:', values)
  9. print('Format string  :', packer.format)
  10. print('Uses           :', packer.size'bytes')
  11. print('Packed Value   :'binascii.hexlify(packed_data))

Original values: (b'START', 282, 7689, 0.1660123, 0.07407871, 0.9207859, b'END')
Format string  : b'2shhfff2s'
Uses           : 22 bytes
Packed Value   : b'53541a01091e000021ff293e94b6973da0b86b3f454e'

So this takes a sample data string, set as 'values' in line 4 and produces a packed data structure which is 22 bytes long. Using this code snippet you can play around to see how changing the packer structure in line 5 alters the packed byte length. Plus using the struct class is the idiomatic way to go for data transmission. In this example 2shhfff2s means: 

2s = 2 characters ('ST') = 4 bytes
h = short (282) = 2 bytes
h = short (7689) = 2 bytes
f = float (0.1660123) = 4 bytes
f = float(0.07407871) = 4 bytes
f=float(0.9207859) = 4 bytes
2s = 2 characters ('EN') = 4 bytes

Line 5 has no spaces between the code formats. You could put packer = ('2s h h f f f 2s'), which works fine in python3, but micropython does not like the spaces. Not at all!
Truncating the accelerometer values to three decimal places, e.g. 0.1660123 to 0.166, makes no difference to the byte size of the packed data, as declaring the data type as a float allocates 4 bytes to it, regardless of precision.

So I knocked up some code in a Jupyter notebook to connect to the /dev/ttyUSB*, where '*' will be a number, typically 0 or 1, of the USB port created when you plug in your XBee module on the USB convert board. After playing with this for a while, I transferred it to a stand alone script. Please see the full listing here

line 55 is critical - without this sleep statement the script will tie up the CPU constantly. It set the maximum rate at which data will be looked for, in this case 500Hz:


Each time that the ttyUSB* port is checked, there may be no data, a partial scan, or a bunch of scans at once. This bunch of scans could start or end with a partial scan. Dealing with these cases takes a little fiddling.

Here is a python3 code snippet to write to the XBee connected to your laptop:

  1.     def write_pyboard(self, message):
  2.         ''' sends data to the pyboard '''
  3.         print('writing to pyboard: {}'.format(message))
  4.         self.serial.write(bytes(message, 'utf-8'))
  5.         self.serial.flush()
Here's a micropython code snippet to receiver the data. Note that the encoding should be the same for sending and receiving. I am using utf-8. I don't know what formats are built in to micropython, utf-8 is a safe one to use for pretty much anything that doesn't require fancy characters.
  1.     def read_uart(self):
  2.         ''' read uart port data '''
  3.         in_data = self.uart.readall().decode('utf-8')  
  4.         print('reading uart data: {}'.format(in_data))
  5.         return in_data

Final results - I can get data from the pyboard using the XBee reliably at 140Hz. This is a lot lower than the 400Hz I get over a USB cable between the pyboard and my laptop. By dropping one of the short values and bringing the byte count down to 18, I can get up to 200Hz. But I kind of need both of the short values - one is a count to check for missing scans and the other is the time in milliseconds since the last scan, which allows me to verify the sampling frequency. Dropping the start and end characters to a single character leads to pain, as inevitably one or other of the single characters will appear in an accelerometer data byte elsewhere, leading to a truncated scan being stripped out of the data.

In conclusion, the XBee is simple to set up to send data from the pyboard to the laptop. The data transfer rate is not as high as I can get over a USB cable, which is not a surprise. The final product - a hand gesture recogniser -  should do all of the gesture recognition on the pyboard, so the data transfer rate will be tiny, just a 'recognised hand gesture' signal. For testing it is easier to get the accelerometer data onto the laptop and play with it there.