So far I have inched my way to making a Mind-Mirror compatible EEG in a theoretical way, but to make it work in real life I need a way of getting signals into the machine. You can buy a board made by Olimex for a reasonable £50, you get optoisolation and everything, and it’s probably the most cost-effective way. Trouble is I don’t know that EEGmir works yet, so I want to do it cheaper, and also now. A Microchip PIC16F88 will do the job here, and I have a few 🙂
I tinkered using this SPBRG calculator to find a suitable crystal to run the PIC16F88 at to match both the 256Hz sampling rate and the baud rate. The first run of EEGmir showed me nothing at all.
Inquiring further it seems the Raspberry Pi gets shirty about a 3% baudrate error at 57600 baud. I set up a test PIC to pump out an endless string of As, and when I brought up minicom they showed up as Ps. This is not good.
I needed to go find a 3.6864 MHz crystal, which lets you get down to 0% error at 57.6k, and by a fortunate stroke of luck fc/4 divides down integer-wise to 256Hz. Nice. So I did that, sending a bunch of As in the data frames to the Pi, after padding down the 5V TTL signal from the PIC.
Mincom showed the As OK from the test PIC, but it wouldn’t let go of the TTY until I rebooted. EEGmir comes up and shows me a load of gobby stuff about data errors. Pressing F12 shows it is assessing jitter
and telling me I have a sampling rate of 325Hz. The nice thing about hardware is you can get a second opinion. Sometimes it’s the smoke pouring out of something, but here it’s in the frame rate of the signal, as I gave myself a sync pulse on a spare PIC pin to synchronise my scope to. So I appeal the outrageous assertion that I am running too fast
and get handed down the verdict of guilty as charged, I did screw up. And I didn’t wait for the camera to focus.
Let’s look on the bright side. This PIC is sending out data at the right baud rate, sort of the right number of frames, too damn fast. And EEGmir is reading from the Pi serial port and struggling manfully to make some sense of it. The (256Hz) on the jitter display even gives me hope it might adapt if I choose to run at 128Hz. Oh and I find that the escape key is the quit command in EEGmir, which saves having to go find the PID and do a kill-9 PID on it, which always feels a bit bush league.
The sampling rate error is because I failed to wait for the TMR1 to time out which I was using to define the frame rate, doing that fixed the sampling rate, it’s now 256.04 according to EEGmir. Still hollering about data errors, so I probably failed to understand the OpenEEG2 protocol somehow.
testing different slots and values
So I create myself a test PIC 16F88 to read out to me all the slots, swapping sync0 for ‘Y’, sync1 is already ‘Z’, ‘P’ for the packet counter, then ‘S’ ‘0’ ‘s’ ‘1’ and end up with a CR in the switches, and add a gratuitous LF 18th packet.
Then stuff that into Mincom, just to ask the question, have I got all this rubbish in the right order? The answer is yes. So I check EEGmir.cfg, to see what protocol they expect.
# Note that there are currently two modEEG protocols in use. The # well-established "fmt modEEG-P2", and a newer "fmt modEEG-P3". Most # people have P2 installed, because it is supported by BioExplorer and # ElectricGuru. [unix-dev] #rawdump; #audio-sync; port /dev/ttyS0 57600; fmt modEEG-P2; rate 256; chan 6;
You can’t really argue with that. Looks like P2 to me, though I have the option of P3 should I so fancy. Scratches head. There’s clearly something in here I’m missing. Signed numbers perhaps – so I hit up the Big G on ‘openeeg signed integer’ and read this PDF, and go WTF? Were these openEEG guys doing acid as well as neurofeedback?
The modular OpenEEG digital boards output 256 EEG samples from the 6 possible channels per second in a “Firmware 2” format (as defined in the Modular EEG “Firmware 2” [1]). The samples are signed 10-bit values stored as 2 byte words (such that the upper 6 bits of the MSB are unused). Each sample is contained within a 17 byte data frame which has a 4 byte header (comprising a two byte synchronization value, a version byte and a frame number byte), 12 bytes of EEG values (2 bytes for each of 6 channels) and followed by a single (unused) button state byte (allowing the connection of four external buttons or switches should an application require them).
Now don’t get me wrong, there’s nothing wrong in accepting the 10-bit limitations of your hardware because you’re a cheapskate bum using a single successive approximation ADC behind an analogue switch, after all that’s what’s in my 16F88 too. The obvious way to pack that in your data format is to stick the MSB (or sign bit now) in the MSB of the 16-bit slot and pad the 6 LSBs with zero. Which is exactly what I told my PIC to do. That way, when you win the lottery and can afford a 16 bit ADC you’re all set and ready to go.
The way OpenEEG seem to have done it is to pad the 6 MSBs with zero, so when you win the lottery and have 16 bit ADCs I guess you get to invent a new data format, perhaps openEEGp3? It’s barmy as hell.
So. let’s have another go. At the moment my PIC puts the data in the MSBs and pads the LSBs, so from EEGmir’s point of view it sees the MSBs polluted with all sorts of guff. Let me make a PIC generating digital silence. Sadly the references in that PDF point to the deceased wiki and mailing lists of openEEG. So let me guess that the signed method was two’s complement because it’s the most popular. fortunately two’s complement 0 is still 0000000000 in 10-bit representation, so I can simply write 0 to all my signal bytes. what i would like to see there is openEEG not carping about data errors 😉 However I could make it more useful if I send digital 1s in the active bits, which should be -1.
And I take stick about data errors again. I am heartened by the indication it is tracking the packet count – ok it missed a lot more than 98 packets because that was a hook PIC out of board, reflash and try again, but it’s nice that it’s tracking the modulo-256 diff. This implies that frame synchronisation is running fine if it can parse the counter.
Hmm. Try all zeros. That works dandy, or at least no data errors but it can see my frame rate, 256.04Hz. Try 0xFF in all the LSBs. Nope – data errors. Try 0xFF in the MSBs and 0x00 in the LSBs. Now I get away with that, and I start to wonder if I really should be sending that LSB first. Now I do that because I got the idea from BrainBay’s Developer manual, but I should have been paying attention because the university final year projects PDF has this to say
Byte 1: Sync Value 0xa5
Byte 2: Sync Value 0x5a
Byte 3: Version (2)
Byte 4: Frame Number
Byte 5: Channel 1 High Byte
Byte 6: Channel 1 Low Byte
Byte 7: Channel 2 High Byte
Byte 8: Channel 2 Low Byte
Byte 9: Channel 3 High Byte
Byte 10: Channel 3 Low Byte
Byte 11: Channel 4 High Byte
Byte 12: Channel 4 Low Byte
Byte 13: Channel 5 High Byte
Byte 14: Channel 5 Low Byte
Byte 15: Channel 6 High Byte
Byte 16: Channel 6 Low Byte
Byte 17: Button States (b1-b4)
and anyway, if I use the source then exactly what part of big-endian format do I not understand?
So I now need some magic to convert my values into a twos-complement result. And send it through big-endian like the guy said in the code.
Conclusion
I have a hardware implementation of something that has the right baud rate, packet structure and frame rate for EEGmir to stop grousing about data errors and sync up to. I have found that the BrainBay developer’s manual is wrong and they really mean big-endian. And I have found out that for some bizarre reason OpenEEG p2 is limited to 10 bits. I am OK with that because Max Cade was running a 4-bit resolution display. And presumably p3 may be compatible with 16-bit resolution. As it is my 16F88 is only good for 10 bits.
To get this into OpenEEG format I need to subtract 32767 from the 16-bit padded 10-bit values I have. And then rotate right this 16 bit number six times. But that’s a job for another day.
Or not. After a cup of tea I realised that I could do a 16-bit add of -32767 with the existing 16-bit values, and the RRF these pairs of bytes through STATUS,C 6 times. So I fired it up and saw the signal come through. The funky sinewave pattern along the top is an artefact of the slow shutter speed, the signals look like an unlocked scope sine wave in real life. The bars respond well as I tune the frequency, faster in the higher frequencies and slower at the lower ones. Hard to really say, though, since my oscillator has lots of bounce at low frequencies due to the old HP trick of using a small bulb as a gain control that everybody copied for awhile.
More about that HP trick
Richard,
You certainly never give up!
I wish that I had your tenacity. One way to solve problems is simply to try to add different items and observe what happens. You are trying this already!
Another method is too leave the problem for a while and let your subconsciousness do its best. You have started on that method already!
I looked for other comments and could see none; perhaps I am too “quick off the mark”. Perhaps other readers are out of their depth.
Good luck with your endeavours!
Gwill Jones
Richard,
Another thought came to my mind which might be useful to you. (I used to teach students how to solve problems creatively). I find it particularly useful to jerk my own memory into action, particularly at the age of 85; it is to try to write detailed notes as though I intend to teach students about the topic.
Gwill Jones
Hi Gwill – thanks for the kind words. I guess on this series I am doing that – working it out as I go along and noting it down. It’s almost like industrial archaeology with some of the material because I guess the original work was quite old and there are lots of bits missing. It’s good that the story has some interest, it’s a little bit stream-of-consciousness at times 😉
Re: Deceased web pages
I find the Internet Wayback machine ( https://archive.org/web/ ) to be very useful in these cases. The vanished wiki you refer to seems to be available at: https://web.archive.org/web/20081219143441/http://wiki.asiaquake.org/openeeg/published/HomePage
It doesn’t always work and not all of the media are always available on archived pages but it’s saved my bacon a few times 🙂
Thanks for that tip – I hadn’t gone back far enough it seems. The wiki was fairly chaotic as seems to be the nature of such things, but there were some snippets of information that were very interesting. The prospect of active electrodes getting rid of the slime is still tantalising, although I believe one of the reasons for using Ag/AgCl is to limit static DC shifts. The Mind Mirror was AC coupled, but it’s hard to do that in a diff amp without small differences in the LF time constants leading to massive lifts in the differential response.
Hey Richard,
thank you so much for sharing this blog post. This is exactly what I needed to know to get this thing working for my bachelors thesis.
But how exactly do you cut the bytes?
Do you cut the first 6 bits of the High or the last 6 bits of the Low byte? And what exaytly do you intend to do when adding -32767 to the value?
Thanks.
Best regards,
Lisa
Hi Lisa,
It’s been four years since I wrangled this. Bear in mind the PIC is a 10 bit ADC. I have a 16-bit representation in two 8 bit registers, adhi and adlo. The PIC ADC sensibly puts the MSB in the, well, MSB of adhi, and leaves the last few bits of adlo blank.
OpenEEG doesn’t do that. They shift everything six bits right, padding the MSBs with zero, and precluding ever using more resolution. I believe the addlsbminus… charade is to get twos complement, but as I said, it’s been a long time 😉
looking at the code I do six rrf instructions across both bytes having done the twos complement thing at the start, so the output is b’000000xxxxxxxxxx which then goes to the transmit routine
#define msbminus32767 b’10000000′
#define lsbminus32767 b’00000001′
and then in the MPLab ASM code
;
; ——————————————-
; Take 16 bit from adhi and adlo, subtract 32767
; and turn into MSB padded 10-bit word in the
; same registers
; does a 16-bit add of -32677 with adhi and adlo
; we know adding a positive to a -ve number
; never results in an overflow, no need to handle
; ——————————————-
munge:
movfw lsbminus32767
addwf adlo,f
btfsc STATUS,C
incf adhi,f
movfw msbminus32767
addwf adhi,f
rrf adhi,f ; C gets the lost LSB, there may be junk from C in MSB. Mask that at the end
rrf adlo,f ; lost LSB of hi byte enters from C. Junk left in C from LSB
rrf adhi,f
rrf adlo,f
rrf adhi,f
rrf adlo,f
rrf adhi,f
rrf adlo,f
rrf adhi,f
rrf adlo,f
rrf adhi,f
rrf adlo,f
movlw b’00000011′ ; mask out any trash rotated into MSB via STATUS,C
andwf adhi,f
movfw adhi ; big-endian
call send
movfw adlo
call send
return