Category: Electronics
Reverse Engineering the Dynamic Controls Shark Joystick
No, not a joystick that lets you drive a shark. It’s a joystick for a mobility scooter or powerchair, as is used by people with disabilities.
There are a lot of resources on the internet that claim that the joystick uses CAN-Bus. This is because the signal is differential (there’s a “high” and “low” data line, and they are inversions of each other). However, I don’t think that this is the case. The microcontroller used in the joystick is the ATMega8, which doesn’t have a CAN controller. There are no CAN controllers or tranceivers in the joystick. On top of that, my friend Seth‘s Saleae logic analyzer can’t make head nor tails of the protocol using the normal CAN analysis modes.
So if it’s not CAN, what is it? There is an LM339 in the joystick, and another one in the motor control unit. These are quad differential comparators, and would be pretty handy if you wanted to hack your own differential serial lines, for noise immunity.
The asynchronous serial decoder of the logic analyzer did manage to decode the serial bit stream at 40000bps.
If the joystick data lines are unplugged and it is powered up, the only output is
t '129' '137' '134' '128' '133' '138' '166' '130' '196' '15'
repeated every 20ms (19.96937, actually, but who’s counting?).
If the joystick is plugged in, the startup does this:
t '129' '137' '134' '128' '133' '138' '166' '130' '196' '15'
'5' '130' '248' '15'
` '192' '191' '192' '141' '128' '140' '128' '199' '15'
a '128' '128' '128' '128' '128' '128' '128' '158' '15'
` '192' '191' '192' '141' '128' '140' '128' '199' '15'
a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
` '192' '191' '192' '141' '128' '140' '128' '199' '15'
a '128' '192' '128' '128' '128' '128' '128' '222' '15'
` '192' '191' '192' '141' '128' '140' '128' '199' '15'
I wrote a little script that parses the CSV output of the logic analyzer software and just prints a newline after each ’15’.
The main thing to notice about this is that the same initialization value is sent, but then it falls into a sort of call and response, with every other line starting with ‘a’ or ‘`’.
My guess is that these are communications passing back and forth between the joystick and the motor controller. I logged 20 seconds of the wheelchair sitting still and then counted all the unique messages that passed between the joystick and the motor controller.
Assuming that the above startup sequence is call and response, the message starting with ‘t’ and the ones starting with ‘`’ are the joystick, and the ones starting with ‘a’ are from the motor driver.
I sorted the commands out and counted the unique messages. They break down like this:
1 ` '191' '190' '192' '189' '128' '132' '128' '161' '15'
1 ` '191' '191' '192' '186' '128' '132' '128' '163' '15'
1 ` '192' '191' '192' '128' '128' '140' '128' '212' '15'
1 ` '192' '191' '192' '130' '128' '132' '128' '218' '15'
1 '5' '130' '248' '15'
1 a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
1 t '129' '137' '134' '128' '133' '138' '166' '130' '196' '15'
2 ` '191' '190' '192' '174' '128' '132' '128' '176' '15'
3 ` '191' '191' '192' '184' '128' '140' '128' '157' '15'
10 a '128' '192' '128' '128' '128' '128' '128' '222' '15'
14 ` '192' '190' '192' '135' '128' '132' '128' '214' '15'
20 a '146' '128' '128' '128' '128' '128' '128' '140' '26' '133' '167' '185' '15'
21 a '128' '128' '128' '128' '128' '128' '128' '158' '15'
22 ` '192' '191' '192' '129' '128' '132' '128' '219' '15'
34 ` '191' '191' '192' '176' '128' '132' '128' '173' '15'
37 ` '192' '191' '192' '128' '128' '132' '128' '220' '15'
40 ` '191' '190' '192' '190' '128' '132' '128' '160' '15'
57 ` '191' '190' '192' '182' '128' '132' '128' '168' '15'
70 ` '191' '191' '192' '185' '128' '132' '128' '164' '15'
139 ` '191' '190' '192' '183' '128' '132' '128' '167' '15'
290 ` '191' '191' '192' '184' '128' '132' '128' '165' '15'
294 ` '191' '190' '192' '191' '128' '132' '128' '159' '15'
954 a '146' '128' '128' '128' '128' '128' '128' '140' '15'
Most of the unique messages are from the joystick, and almost all of the messages sent back are the one at the bottom, with 954 occurances.
Let’s compare the messages from the motor controller. Bear in mind that nothing is moving at this point.
1 '5' '130' '248' '15'
1 a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
10 a '128' '192' '128' '128' '128' '128' '128' '222' '15'
20 a '146' '128' '128' '128' '128' '128' '128' '140' '26' '133' '167' '185' '15'
21 a '128' '128' '128' '128' '128' '128' '128' '158' '15'
954 a '146' '128' '128' '128' '128' '128' '128' '140' '15'
The first one is the startup acknowledgement. It never occurs again.
The next one and the one that occurs 20 times look similar, in that they both have four extra numbers in them. The first, second, and eighth values are the only ones that vary between them.
The one that occurs ten times mtches the one that occurs 21 times, except for the second and eighth fields, and doesn’t have the extended part.
The third through seventh fields are always ‘128’.
The first and second fields are always ‘128’, ‘146’, or ‘192’. The eighth field is always ‘222’, ‘140’, or ‘158’. The nineth field is either 26 or the end of transmission marker ’15’.
So what does all this mean?
I suspect that at least one of the values has something to do with the battery. The battery connects to the motor driver, but there is a battery level display on the joystick, so the motor driver must communicate some battery level information to the joystick.
The messages from the joystick have a similar pattern.
1 ` '191' '190' '192' '189' '128' '132' '128' '161' '15'
1 ` '191' '191' '192' '186' '128' '132' '128' '163' '15'
1 ` '192' '191' '192' '128' '128' '140' '128' '212' '15'
1 ` '192' '191' '192' '130' '128' '132' '128' '218' '15'
2 ` '191' '190' '192' '174' '128' '132' '128' '176' '15'
3 ` '191' '191' '192' '184' '128' '140' '128' '157' '15
14 ` '192' '190' '192' '135' '128' '132' '128' '214' '15'
22 ` '192' '191' '192' '129' '128' '132' '128' '219' '15'
34 ` '191' '191' '192' '176' '128' '132' '128' '173' '15'
37 ` '192' '191' '192' '128' '128' '132' '128' '220' '15'
40 ` '191' '190' '192' '190' '128' '132' '128' '160' '15'
57 ` '191' '190' '192' '182' '128' '132' '128' '168' '15'
70 ` '191' '191' '192' '185' '128' '132' '128' '164' '15'
139 ` '191' '190' '192' '183' '128' '132' '128' '167' '15'
290 ` '191' '191' '192' '184' '128' '132' '128' '165' '15'
294 ` '191' '190' '192' '191' '128' '132' '128' '159' '15'
The first and second values vary, but only slightly. The third value does not vary. the fourth value varies over the range 128-191. The fifth value does not vary. The sixth value varies, the seventh does not, the eighth does.
The joystick is a 4-axis device, with three buttons and a pair of binary inputs. My hope is that the values reported are something like the four joystick axes and the buttons, but the numbers don’t quite line up, as there are more buttons (5, three buttons plus two input jacks) than values left over after subtracting the joystick axes. Unless the jacks are in parallel with the buttons, that’s not what’s going on.
Recording from the data lines while the joystick is held in the forward position gives these unique lines:
1 ` '128' '191' '192' '134' '128' '132' '128' '150' '15'
1 ` '128' '192' '192' '130' '128' '132' '128' '153' '15'
1 ` '128' '192' '192' '132' '128' '132' '128' '151' '15'
1 ` '128' '193' '192' '131' '128' '132' '128' '151' '15'
1 ` '129' '191' '192' '165' '128' '132' '128' '246' '15'
1 ` '131' '191' '192' '170' '128' '132' '128' '239' '15'
1 ` '133' '191' '192' '176' '128' '132' '128' '231' '15'
1 ` '135' '190' '192' '191' '128' '132' '128' '215' '15'
1 ` '137' '190' '192' '190' '128' '132' '128' '214' '15'
1 ` '140' '190' '192' '135' '128' '132' '128' '138' '15'
1 ` '142' '191' '192' '130' '128' '132' '128' '140' '15'
1 ` '144' '191' '192' '156' '128' '132' '128' '240' '15'
1 ` '146' '191' '192' '156' '128' '132' '128' '238' '15'
1 ` '148' '191' '192' '149' '128' '132' '128' '243' '15'
1 ` '150' '191' '192' '150' '128' '132' '128' '240' '15'
1 ` '152' '191' '192' '164' '128' '132' '128' '224' '15'
1 ` '154' '191' '192' '186' '128' '132' '128' '200' '15'
1 ` '157' '191' '192' '153' '128' '132' '128' '230' '15'
1 ` '160' '191' '192' '136' '128' '132' '128' '244' '15'
1 ` '163' '191' '192' '145' '128' '132' '128' '232' '15'
1 ` '166' '191' '192' '153' '128' '132' '128' '221' '15'
1 ` '169' '191' '192' '161' '128' '132' '128' '210' '15'
1 ` '172' '191' '192' '145' '128' '132' '128' '223' '15'
1 ` '174' '191' '192' '179' '128' '132' '128' '187' '15'
1 ` '177' '191' '192' '139' '128' '132' '128' '224' '15'
1 ` '179' '191' '192' '139' '128' '132' '128' '222' '15'
1 ` '180' '191' '192' '188' '128' '132' '128' '172' '15'
1 ` '182' '191' '192' '181' '128' '132' '128' '177' '15'
1 ` '184' '191' '192' '172' '128' '132' '128' '184' '15'
1 ` '186' '191' '192' '172' '128' '132' '128' '182' '15'
1 ` '188' '191' '192' '140' '128' '132' '128' '212' '15'
1 ` '189' '191' '192' '156' '128' '132' '128' '195' '15'
1 ` '190' '191' '192' '132' '128' '132' '128' '218' '15'
1 ` '190' '191' '192' '147' '128' '132' '128' '203' '15'
1 ` '190' '191' '192' '163' '128' '132' '128' '187' '15'
1 ` '190' '191' '192' '171' '128' '132' '128' '179' '15'
1 ` '190' '191' '192' '172' '128' '132' '128' '178' '15'
1 ` '190' '191' '192' '181' '128' '132' '128' '169' '15'
1 ` '190' '191' '192' '190' '128' '132' '128' '160' '15'
1 ` '191' '191' '192' '141' '128' '132' '128' '208' '15'
1 ` '191' '191' '192' '173' '128' '132' '128' '176' '15'
1 ` '192' '191' '192' '133' '128' '140' '128' '207' '15'
1 ` '192' '191' '192' '135' '128' '132' '128' '213' '15'
1 ` '192' '191' '192' '150' '128' '132' '128' '198' '15'
1 '5' '130' '248' '15'
1 a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '134' '230' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '141' '223' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '145' '219' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '129' '142' '222' '15'
1 a '146' '128' '128' '160' '132' '128' '135' '229' '15'
1 a '146' '128' '128' '176' '128' '128' '145' '219' '15'
1 a '146' '128' '132' '160' '128' '128' '146' '218' '15'
1 a '146' '129' '128' '160' '128' '128' '141' '223' '15'
1 a '146' '132' '128' '160' '128' '128' '143' '221' '15'
1 a '146' '132' '128' '160' '130' '128' '146' '218' '15'
1 t '129' '137' '134' '128' '133' '138' '166' '130' '196' '15'
2 ` '128' '191' '192' '133' '128' '132' '128' '151' '15'
2 ` '128' '191' '192' '135' '128' '132' '128' '149' '15'
2 ` '128' '192' '192' '134' '128' '132' '128' '149' '15'
2 ` '128' '194' '192' '130' '128' '132' '128' '151' '15'
2 ` '191' '191' '192' '188' '128' '132' '128' '161' '15'
2 ` '192' '191' '192' '131' '128' '132' '128' '217' '15'
2 ` '192' '191' '192' '140' '128' '132' '128' '208' '15'
2 a '146' '128' '128' '160' '128' '128' '146' '218' '26' '133' '167' '185' '15'
3 ` '128' '191' '192' '131' '128' '132' '128' '153' '15'
3 ` '192' '191' '192' '143' '128' '132' '128' '205' '15'
3 a '146' '128' '128' '128' '128' '128' '128' '140' '26' '133' '167' '185' '15'
4 a '146' '128' '128' '160' '128' '128' '130' '234' '15'
4 a '146' '128' '128' '160' '128' '128' '131' '233' '15'
4 a '146' '128' '128' '160' '128' '128' '136' '228' '15'
5 ` '128' '191' '192' '132' '128' '132' '128' '152' '15'
5 ` '128' '192' '192' '128' '128' '132' '128' '155' '15'
5 a '146' '128' '128' '160' '128' '128' '129' '235' '15'
5 a '146' '128' '128' '160' '128' '128' '133' '231' '15'
5 a '146' '128' '128' '160' '128' '128' '134' '230' '15'
5 a '146' '128' '128' '160' '128' '128' '138' '226' '15'
6 ` '128' '193' '192' '130' '128' '132' '128' '152' '15'
6 ` '192' '191' '192' '134' '128' '132' '128' '214' '15'
6 a '146' '128' '128' '160' '128' '128' '132' '232' '15'
6 a '146' '128' '128' '160' '128' '128' '137' '227' '15'
7 a '146' '128' '128' '160' '128' '128' '139' '225' '15'
7 a '146' '128' '128' '160' '128' '128' '141' '223' '15'
8 a '146' '128' '128' '160' '128' '128' '140' '224' '15'
9 ` '128' '193' '192' '129' '128' '132' '128' '153' '15'
9 ` '128' '193' '192' '132' '128' '132' '128' '150' '15'
9 ` '192' '191' '192' '141' '128' '140' '128' '199' '15'
9 a '146' '128' '128' '160' '128' '128' '145' '219' '15'
10 a '128' '192' '128' '128' '128' '128' '128' '222' '15'
10 a '146' '128' '128' '160' '128' '128' '135' '229' '15'
10 a '146' '128' '128' '160' '128' '128' '142' '222' '15'
10 a '146' '128' '128' '160' '128' '128' '143' '221' '15'
12 ` '128' '192' '192' '135' '128' '132' '128' '148' '15'
12 ` '128' '193' '192' '133' '128' '132' '128' '149' '15'
12 a '146' '128' '128' '160' '128' '128' '128' '236' '15'
12 a '146' '128' '128' '160' '128' '128' '144' '220' '15'
15 ` '128' '193' '192' '128' '128' '132' '128' '154' '15'
20 ` '128' '193' '192' '134' '128' '132' '128' '148' '15'
21 a '128' '128' '128' '128' '128' '128' '128' '158' '15'
24 ` '128' '194' '192' '129' '128' '132' '128' '152' '15'
25 ` '192' '191' '192' '141' '128' '132' '128' '207' '15'
26 ` '192' '191' '192' '142' '128' '132' '128' '206' '15'
30 ` '192' '191' '192' '133' '128' '132' '128' '215' '15'
33 ` '192' '191' '192' '132' '128' '132' '128' '216' '15'
63 ` '128' '194' '192' '128' '128' '132' '128' '153' '15'
64 ` '128' '193' '192' '135' '128' '132' '128' '147' '15'
126 a '146' '128' '128' '128' '128' '128' '128' '140' '15'
135 a '146' '128' '128' '160' '128' '128' '146' '218' '15'
Breaking out the motor driver packets gives:
1 a '128' '192' '128' '128' '128' '128' '128' '222' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '134' '230' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '141' '223' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '128' '145' '219' '26' '133' '167' '185' '15'
1 a '146' '128' '128' '160' '128' '129' '142' '222' '15'
1 a '146' '128' '128' '160' '132' '128' '135' '229' '15'
1 a '146' '128' '128' '176' '128' '128' '145' '219' '15'
1 a '146' '128' '132' '160' '128' '128' '146' '218' '15'
1 a '146' '129' '128' '160' '128' '128' '141' '223' '15'
1 a '146' '132' '128' '160' '128' '128' '143' '221' '15'
1 a '146' '132' '128' '160' '130' '128' '146' '218' '15'
2 a '146' '128' '128' '160' '128' '128' '146' '218' '26' '133' '167' '185' '15'
3 a '146' '128' '128' '128' '128' '128' '128' '140' '26' '133' '167' '185' '15'
4 a '146' '128' '128' '160' '128' '128' '130' '234' '15'
4 a '146' '128' '128' '160' '128' '128' '131' '233' '15'
4 a '146' '128' '128' '160' '128' '128' '136' '228' '15'
5 a '146' '128' '128' '160' '128' '128' '129' '235' '15'
5 a '146' '128' '128' '160' '128' '128' '133' '231' '15'
5 a '146' '128' '128' '160' '128' '128' '134' '230' '15'
5 a '146' '128' '128' '160' '128' '128' '138' '226' '15'
6 a '146' '128' '128' '160' '128' '128' '132' '232' '15'
6 a '146' '128' '128' '160' '128' '128' '137' '227' '15'
7 a '146' '128' '128' '160' '128' '128' '139' '225' '15'
7 a '146' '128' '128' '160' '128' '128' '141' '223' '15'
8 a '146' '128' '128' '160' '128' '128' '140' '224' '15'
9 a '146' '128' '128' '160' '128' '128' '145' '219' '15'
10 a '128' '192' '128' '128' '128' '128' '128' '222' '15'
10 a '146' '128' '128' '160' '128' '128' '135' '229' '15'
10 a '146' '128' '128' '160' '128' '128' '142' '222' '15'
10 a '146' '128' '128' '160' '128' '128' '143' '221' '15'
12 a '146' '128' '128' '160' '128' '128' '128' '236' '15'
12 a '146' '128' '128' '160' '128' '128' '144' '220' '15'
21 a '128' '128' '128' '128' '128' '128' '128' '158' '15'
126 a '146' '128' '128' '128' '128' '128' '128' '140' '15'
135 a '146' '128' '128' '160' '128' '128' '146' '218' '15'
Now it seems that every value varies at least once. I’m not sure yet what to make of this, but I feel like I’m on the right track.
USB parallel ports under Python on Ubuntu
I have this PCB designed to control four flame effects. Instead of running it on the Arduino, I’m doing an FFT on a laptop and trying to control the solenoid drivers through a USB parallel port adapter on the laptop.
Ubuntu recognizes the USB parallel port adapter, and gives me a port in /dev/usb/lp0
. I don’t have permissions to access it, because its user and group are root and lp, and I’m neither of those. The specific error is:
>>> p = parallel.Parallel('/dev/usb/lp0')
Traceback (most recent call last):
File "
File "/usr/lib/python2.7/dist-packages/parallel/parallelppdev.py", line 187, in __init__
self._fd = os.open(self.device, os.O_RDWR)
OSError: [Errno 13] Permission denied: '/dev/usb/lp0'
sudo chmod o+rw /dev/usb/lp0
doesn’t get me any closer, because whatever python-parallel does under the hood is not a legit operation on that dev entry.
>>> p = parallel.Parallel("/dev/usb/lp0")
Traceback (most recent call last):
File "
File "/usr/lib/python2.7/dist-packages/parallel/parallelppdev.py", line 189, in __init__
self.PPEXCL()
File "/usr/lib/python2.7/dist-packages/parallel/parallelppdev.py", line 241, in PPEXCL
fcntl.ioctl(self._fd, PPEXCL)
IOError: [Errno 25] Inappropriate ioctl for device
The /dev/usb/lp0
device entry appears to be created by the usblp module. I have a suspicion that what’s going on here is that the device entry created by usblp isn’t claimable the way one created by ppdev would be.
Using rmmod to get rid of usblp doesn’t work, it just gets restarted when I re-insert the USB connector for the adapter. Blacklisting it in /etc/modprobe.d/blacklist.conf just means that the /dev entry doesn’t get created, not that ppdev takes over.
Most reports online also indicate that USB parallel ports don’t really act like parallel ports, but only work for connecting parallel printers. Since I’ve already wasted enough time on this, it’s time to go with plan B. I’m going to fully populate the board, so that it has an Arduino on it, and then interface to that using serial commands and possibly Processing or OpenFrameworks.
I've been had!
I got a “5600mAh” power bank from Aliexpress. It’s an electronic item direct from China at low, low prices, so I assumed it was going to not measure up in some way or other, but until I got it, I didn’t know how.
Overall, the build quality isn’t bad. The case is molded plastic, and snaps together. It’s nice looking and feels solid. The power management PCB in it seems to have good quality solder joints. Not too shabby, and I don’t expect it to catch fire or anything.
The batteries, on the other hand, are where it falls down. The device has two cells in it, 18650 size, 1200mAh each (if their labels are to be believed). They are in parallel, which gets me a total of 2400mAh. That’s slightly more than half of the advertised capacity. Since 5600 isn’t an even multiple of 1200, there’s no way they could get 5600mAh using these batteries, even if they did want a product that could live up to their claims.
For ~$9, I don’t think it’s worth making a fuss over, but now I know what to expect from this device.
A New Contender!
As I mentioned back in this entry, I don’t think the further development of the ToyBrain controller is useful. The main use case is amply solved by the Pololu Baby Orangutan. However, there is a project that I want something similar for, where the Baby Orangutan won’t work.
The idea behind the project is to make cheap swarm robots out of small robot toys, using something like the ToyBrain or Baby Oranguntan, but with wireless. The Moteino has wireless, but no motor drivers. The Baby Orangutan has motor drivers, but no wireless. The solution, then, appears to be to make a motor driver shield for the Moteino or a wireless shield for the Baby Orangutan. Of the two, I’m more inclined to do the motor shield for the Moteino, but I haven’t done any really analysis.
ToyBrain Version 2 Firmware Troubles
I’ve started trying to load a bootloader onto the V2 hardware for the ToyBrains, as part of attempting to make a luminous fez for my girlfriend. I ran into a few problems.
The first problem I had was that the IC I’m using is the ATMega328, not the ATMega328P. So far, the only difference I can find between the two is their voltage range, but they have different ID strings. To get around this, I duplicated the chip definition for the 328P in avrdude.conf, and replaced the ID with the proper ID for a 328 (no P). This seemed to work for getting the Arduino IDE to burn the bootloader to my chip.
Unfortunately, I had made the Arduino boards.txt file description of my board by copying the Arduino Nano section. This includes a fuse setting for an external clock crystal. The toybrain boards don’t have a clock crystal, and use the internal RC oscillator of the Atmega328 instead. This meant that when the board was reflashed, it had no clock, and so not only could it not be programmed from the IDE, it also couldn’t be reflashed with a new bootloader using the USBTinyISP. The big symptom of this is that avrdude immediately fails to init, and if I used the -F (force, which you should never do) flag, it reported a part ID of 0x000000.
To fix that problem, I wired two small caps and a crystal to add an external oscillator to the board, tacked it to the appropriate pins, and reset the flags.
At this point, I think I have the bootloader installed, and the fuses at least mostly correct. The Lilypad Arduino with the ATMega328P uses these fuses: Low: 0xE2 High: 0xD8 Extended: 0x05, which mean that the clock is internal, and not divided by 8, so it runs at 8MHz. The bootloader settings set the size of the bootloader, which is the same as the one I am using (ATmegaBOOT_168_atmega328_pro_8MHz.hex), and the boot interrupt vector.
However, I still get a problem when I attempt to upload a program to the device. If I set the upload speed to 57600, avrdude beefs about the chip ID. If I set it to 19200, avrdude says the programmer is not responding.
My current guesses at the cause of the problem are these:
- My clock settings are slightly off, so the chip is running at the wrong speed for the bootloader. As a result, the serial data rate is bad, and the data that avrdude gets back is nonsense. This can be fixed by applying the above fuse settings via the USBTinyICSP.
- I have the reset vector or bootloader size setting wrong, and so the chip doesn’t start the bootloader correctly when it is reset. Again, this is a matter of fixing the fuses.
- Something else that I haven’t considered yet, e.g. there is something wrong with my hardware.
I’m going to work on it more tonight and see if I can get it going.
Mindflex EEG Hacking
I got a Mindflex Duel for Christmas. The Mindflex Duel is a toy that uses a pair of EEG headsets to read signals from the users, and then send those signals to a base unit that contains a blower and a little sliding cart to move the blower. The users try to concentrate to control the cart, moving a little ball suspended in the air jet from the blower into a goal.
Needless to say, I gutted it.
The base unit has a little PCB with a 2.4Ghz radio on it, and a little hardware to control the blower and cart motors. The headsets are the really interesting part. Each one has a single-channel EEG and a wireless radio. I took the radios out and replaced them with BlueSmiRF bluetooth-to-serial links so that I could connect them to my laptop. The hardware part of the replacement is below, the software part will be in another post.
The guts of one of the headsets. The 2.4 Ghz radio is the top daughter board, the EEG hardware is the bottom daughter board.
I desoldered the original radio. It works in the same band as Bluetooth, and consumes power, so there was no need to have it there.
The red and black wires supply power for the BlueSmiRF. It can take up to 5 or so volts, but the headset runs on 4.5v, so it is fine to hook it up like this. The red wire is connected to the power switch, rather than V+, so that the power switch also turns off the bluetooth radio.
The white wire goes from the pin labeled “T” on the EEG board to the RX pin on the BlueSmiRF. The T pin of the EEG board is a serial line, which transmits the EEG data to the BlueSmiRF.
Glue the bluetooth radio into place with hot glue. The LEDs on the BlueSmiRF are covered by black paint on the inside of the Mindflex headset, but I scractched away the paint in little circles so the BlueSmiRF status lights would shine through.
The finished product looks stock, until you turn it on. That red light on the side is not normally there.
Nuiteblaster fail
I aligned the mirrors and powered up the draft hardware for the Nuiteblaster, and it didn’t work. This kind of fails to surprise me, as I changed a lot of stuff in the design. I think my speakers are good, but that using MOSFETs was an error. I used MOSFETs because they have a very low resistance when turned on, so they don’t waste energy as heat.
However, the MOSFETs in the design are switching 12V, and are getting a base drive of 5V. This means the voltage from the gate to the grounded source (Vgs) is at most 5V, because that’s what the Arduino can source, voltage-wise. The voltage from the drain to the source (Vds) is 12V. Since Vgs is > Vth (the point at which the MOSFET turns on), but nowhere near 12V, the MOSFETs are probably not getting driven into saturation, even when the outputs are fully on. This is compounded by the fact that since the code runs the PWM of the Arduino very fast, it is unlikely that that gate capacitance gets fully charged, and so the MOSFETs probably see a much lower average voltage at the gate, and so are probably operating in ohmic mode most of the time.
Whatever the cause, the speaker motion was quite small, and the MOSFETs got so hot that when I licked my finger and touched one of them, it sizzled.
I’m contemplating two possible fixes for this. The first is to replace the MOSFETs with the TIP120 power darlingtons that the schematic calls for. The second is to build my own power darlington transistor out of a 2n2222 switching the gate of the MOSFET to the full 12V of the beefy power supply that I’m driving the whole thing with. This would almost certainly drive the MOSFET into saturation, and hopefully get me the gain that my current rig lacks.
The speakers might also not be the best speakers for the job, but they are the ones I have. If I can’t get the system to work by changing the electronics, I’ll try new speakers next.
Low Power Electronics
I am building a set of strings of lights to illuminate a labyrinth. As someone walks the labyrinth, the strings of lights will light up ahead of them to show the way, and fade out behind them as they pass. Instead of doing the build from the ground up, I’m starting with solar-powered garden lights that charge during the day, and light a string of lights at night.
My initial thought was that this would be a pretty simple task. I’d rig each light with a Sharp IR ranger, poll the ranger, and light the lights when something got close enough. Once it passed, I’d set a timer based on how long it takes to walk a strand of lights, and then shut the lights off when the timer timed out.
Unfortunately, that idea went away when I got the solar light. The light uses a single 1.2V battery, and runs the LED strand by having a simple boost converter double that to pulses of around 2.5V at a high enough rate that the LEDs don’t look like they are pulsing. I figured I would get around that by rectifying the pulses using a voltage doubler, which would get me 5V for my microcontroller and sensor. Unfortunately, voltage doublers get you voltage at the expense of current. The Sharp IR rangers can eat around 20mA, and the microcontroller is another 15mA or so. With that amount of load, the voltage on the voltage doubler rapidly falls back to ~2V. The Sharp IR rangers don’t work at anything less than about 4 volts, so I couldn’t use them.
I decided that since I don’t need range measurement, just the presence or absence of something in the range of the detector, I could get by with lighting the area up with 38kHz modulated IR, and picking it up with an IR detector module like the ones used in TVs to receive the remote signal. The microcontroller can generate the modulation signal to drive the IR LED. I got the code to do it here, I think, but that site is down now. In practice, this works just fine. I used my Arduino to do a quick sketch of the detection circuitry, and got it to blink an LED.
Unfortunately, the IR detectors I have also don’t work with less than 5V. However, unlike the Sharp IR rangers, there are a bunch of manufacturers that make the TV remote receivers, and some of them operate down to 2.4V. I ordered some of these, and set up my microcontroller, IR LED, and remote receiver so I could blink an LED by sending a IR pulse.
That worked just fine on battery power, but running from the voltage doubler still drained the caps too fast. Powering the IR LED at reasonable brightness just took too much current. In order to let the capacitors in the voltage doubler recharge, I shortened the IR LED on time to a 10th of a second, and put the microcontroller in a very low power (i.e. it runs on microamps, rather than milliamps) sleep mode when it was not firing the LED. Since the circuit spends most of its time off, the IR detector is the main draw on the voltage doubler. So far, this seems to work. If I want to save even more power, I can power the IR detector from a pin of the microcontroller, and shut it down when the microcontroller goes down.
Soon, I’m going to test the full circuit. I’ll post about it if I have to make any wild and crazy hardware changes.
Touch and pressure sensing with the Arduino
A friend of mine is working on a device that requires multiple soft fabric pressure sensors over a volume approximately equivalent to a human arm. I have more Arduino experience than her, so I’m helping out with the electronic implementation details.
Our first attempt was to examine some commercial force-sensing resistors. These consist of a plastic layer with a pattern of interleaved contacts printed on it, and a layer of carbon-impregnated rubbery material over the contacts. The more pressure there is on the rubber, the more it touches the contacts and the lower its resistance becomes. These devices are very stable, yielding repeatable resistance measurements with repeated contacts. Unfortunately, they are of a fixed size, and cannot be cut or reshaped. They also are flexible, but not exactly soft.
After discarding that approach, we tried making our own force sensitive resistors out of the conductive foam from IC packaging. This works, but has a couple of problems. The first is that the resulting device doesn’t have a simple response to force. Its resistance goes down when pressed, and goes back up when released, but it doesn’t always return to the same values, and the resulting sensor data is noisy. We also don’t have a good source for a lot of IC packaging foam.
Most recently, we’ve tried making a pressure sensor based on a capacitor. The Arduino CapSense library provides a simple way to turn two pins of an Arduino into a capacitance sensor. One plate of the capacitor is a sheet of conductive material, the other plate is the user, and is effectively connected to ground (or at least “away” as charges can leave the circuit that way). The page notes that you can use the capacitive sensing pad, covered with an insulator, as a pressure sensor with an approximately logarithmic response.
However, it also notes that putting a ground plane under the touch sensor makes the results more stable. Instead of doing that, I put down the sensing plate, made of copper-coated nylon, two layers of soft interfacing (A sewing material kind of like a sheet of stuffed toy stuffing) and a ground plate made of silver-coated spandex over the interfacing. Pressing on the ground pad compresses the interfacing and brings the ground plate closer to the sensing plate, increasing the capacitance, and registering as pressure to the sensor. Because the upper/interactive surface is the ground plate, it shields the sensing plate, so the capacitive pressure sensor does not also act as a proximity sensor and trigger before it is touched.
I taped the bottom layer down to my desk, and hooked a clip lead to it. This lead goes to pin 2 of my Arduino.
Then I put two layers of interfacing on top.
Then I put the ground layer on top. This is connected to a clip lead that goes to a ground connection on the Arduino.
In the finished device, there will probably be a stuffed fabric tube with rings of conductive fabric around it as sensors, surrounded by a layer of interfacing, and then by a conductive grounded layer. There will also likely be an outer layer of fabric to protect and decorate the whole thing. In order to determine if this is a good way to build the thing, I intend to use the technique to make a stuffed toy that can detect squeezing.
This is the code I used to read the values from the sensor:
#include/* * CapitiveSense Library Demo Sketch * Paul Badger 2008 * Modified by Abe Shultz 2012 * Uses a high value resistor e.g. 10 megohm between send pin and receive pin * Resistor effects sensitivity, experiment with values, 50 kilohm - 50 megohm. * Larger resistor values yield larger sensor values. * Receive pin is the sensor pin - try different amounts of foil/metal on this pin * Best results are obtained if sensor foil and wire is covered with an insulator * such as paper or plastic sheet */ CapSense cs_4_2= CapSense(4,2); // 1 megohm resistor between pins 4 & 2, pin 2 is sensor pin void setup() { Serial.begin(9600); cs_4_2.reset_CS_AutoCal(); } void loop() { long start = millis(); long total1 = cs_4_2.capSense(30); Serial.print(millis() - start); // check on performance in milliseconds Serial.print("t"); // tab character for debug window spacing Serial.println(total1); // print sensor output 1 delay(10); // arbitrary delay to limit data to serial port }
Scooped?
Pololu makes a controller (called the “Baby Orangutan”) very similar to the one I am building. It has an Arduino-compatable microcontroller, 1A motor driver, and user IO lines. It is also cheap. However, there are a few things my design has that theirs does not.
The ToyBrain motor driver is replaceable. If you accidentally short the output pins and destroy the chip with overcurrent, you can remove it from the socket and replace it. The motor driver of the Baby Orangutan is a fairly fine-pitch SMD package, and so is difficult for the average user to desolder and replace.
Connections to the ToyBrain are also a bit more convenient than those on the Baby Orangutan. The ToyBrain has headers designed for servos, and headers for sensors that carry power for those sensors. The Baby Orangutan breaks all the pins out as single pins, leaving the user to deal with power wiring.
The ToyBrain uses the serial port for programming with a $5 FTDI cable available from multiple vendors. This means that it integrates very easily with the Arduino development environment, and it can send serial data back to an attached computer. The Baby Orangutan uses a USB to ICSP adapter to program the chip. It has the ability to send data back to a computer via serial, but it would require a second cable.
This post isn’t intended to slam Pololu. They make useful products with incredibly high quality. I know this because I buy their stuff. The point isn’t that my device is better, it’s that it’s different, in ways that make it better for me.
Recent Comments