XBee modules have a built in ADC, so why not sample an analog accelerometer directly? This will allow me to make a smaller wireless accelerometer that I can strap to my participants for testing with. Long term I want a microcontroller in the system for onboard signal processing. But for initial data collection, the smaller and simpler the better. Make it work. Make it fast. Make it right.. I am using the ADXL335 analog output 3-axis accelerometer connected to D0, D1 and D2 of an XBee series 1. This idea is nothing new, I got the idea for this build from a website made by Dr. Eric Ayars, Associate Professor of Physics at the California State University, Chico here. Thanks Eric! Initially I tried lashing up his design with the series 2 XBees that I had to hand. The issues with this are the two main differences that I found between the Series 1 and the Series 2 XBee ADC (analog to digital converter).

from xbee import XBee import serial import time PORT = '/dev/ttyUSB0' BAUD_RATE = 115200 # Open serial port ser = serial.Serial(PORT, BAUD_RATE) # Create XBee Series 1 object xbee = XBee(ser, escaped=True) print('created xbee at {} with baud {}'.format(PORT, BAUD_RATE)) print('listening for data...') dt_old = time.perf_counter() # Continuously read and print packets while True: dt_new = time.perf_counter() response = xbee.wait_read_frame() adc_dict=response['samples'][0] delta_millis = (dt_new-dt_old)*1000 dt_old = dt_new try: print('{:.2f} {:.2f}'.format(delta_millis, 1000/delta_millis)) except ZeroDivisionError as e: continue print(adc_dict['adc-0'], adc_dict['adc-1'], adc_dict['adc-2']) ser.close()
output:
created xbee at /dev/ttyUSB0 with baud 115200 listening for data... 0.00 1428571.69 526 409 502 10.67 93.73 526 409 503 0.25 4058.74 526 411 503 10.62 94.19 522 406 500 0.40 2474.43 523 409 502 11.26 88.85 516 412 505 0.62 1604.76 523 408 502 10.65 93.86 522 407 498 0.39 2591.94 522 403 500 10.64 94.02
Ignore the first line of data, I expected that to be garbage. The lines of data should be:
adc-0, adc-1, adc-2 # which looks about right time in ms since the last sample, resulting frequency = 1000/time in ms since last sample # these don't look about right
We should be seeing a uniform sample and frequency. But it oscillates between about 11ms and 0.5ms. Which averages to be about 6ms. For all three channels. So the ADC is working at a sample rate of around 2ms.
I modified the code to include a 100 sample averaging calculation. This is implemented using a deque data container, initialised in line 13. The sample times are added in line 24. Prior to that, the oldest one is removed in line 13. The values are averaged and printed in line 26. The try, except clause around this line are necessary as the 'None' values that the deque is intialised with cause the np.mean function to crash with a TypeError.
from collections import deque import numpy as np from xbee import XBee import serial import time PORT = '/dev/ttyUSB0' BAUD_RATE = 115200 # Open serial port ser = serial.Serial(PORT, BAUD_RATE) # Create XBee Series 1 object xbee = XBee(ser, escaped=True) sample_deque = deque([None]*100, maxlen=100) print('created xbee at {} with baud {}'.format(PORT, BAUD_RATE)) print('listening for data...') dt_old = time.perf_counter() # Continuously read and print packets while True: dt_new = time.perf_counter() response = xbee.wait_read_frame() adc_dict=response['samples'][0] delta_millis = (dt_new-dt_old)*1000 sample_deque.pop() sample_deque.appendleft(delta_millis) try: print('{:.2f}'.format(np.mean(sample_deque))) except TypeError: continue dt_old = dt_new try: print('{:.2f} {:.2f}'.format(delta_millis, 1000/delta_millis)) except ZeroDivisionError as e: continue print(adc_dict['adc-0'], adc_dict['adc-1'], adc_dict['adc-2']) ser.close()
output after a few hundred samples:
5.44 13.52 73.96 521 404 500 5.45 2.06 485.59 523 408 502 5.44 0.91 1103.39 526 409 504 5.44 11.06 90.38 516 412 507 5.45
The data should be:
averaged interval in ms # looks about right last sample interval in ms, frequency calculated from last interval in Hz # still oscillating adc-0, adc-1, adc-2
The average of around 5.5ms is close enough to the programmed value of 5ms for my purposes. Why does the sample time fluctuate? Probably something to do with my code. If you have an answer, please leave it below.
The rigorous way to verify the accuracy and speed of this module is to plug in a function generator to the analog channels, record data then analyse that. How hard could that be? Errrr..... I think that what I have now is 'good enough' to try out shake gesture recognition.
The next step is to get an output in 'g' - that is units of gravity. As the sensitivity of the ADXL335 is 330mV/g with an input of 3.3V, the output is centred on half of the rail voltage and the ADC has a range of 0-1024:
g = (ADC_count-512)/102.5
I made a python lambda function to do the conversion:
g = lambda x: (x-512)/102.4
So I can output formatted accelerometer values in g by altering line 34 of the last listing to:
print('{:.2f} {:.2f} {:.2f}'.format(g(adc_dict['adc-0']), g(adc_dict['adc-1']), g(adc_dict['adc-2'])))