direct MATLAB serial interface vs LSL ?

edited April 2015 in Software
Hello all,

I'm currently developing a Matlab interface to operate with my openBCI. I use openBCI in the context of a EEG-based brain-machine interface. I created this thread because I thought it would be nice to share my progress with the openBCI community and maybe find some help whenever I get stuck. I will be publishing my code once it works.

So far, things have been progressing well. I found most of the information I needed here: http://docs.openbci.com/software/02-OpenBCI_Streaming_Data_Format

Very quickly, I was able to reset the device and read the returning status packet.

--------------------------------------------------------------
% constants I'm using
OPENBCI_PORT = 'COM8';
OPENBCI_BAUDRATE = 115200;
START_STREAMING_MSG = char('b');
STOP_STREAMING_MSG = char('s');
RESET_MSG = char('v');

% init the port
s_bci = serial(OPENBCI_PORT,'BaudRate',OPENBCI_BAUDRATE);
fopen(s_bci);

% write a reset
fwrite(s_bci,RESET_MSG,'uchar');
% read the status data
status_chunk = fread(s_bci,STATUS_PACKET_LENGTH);
--------------------------------------------------------------

Then, I was able to put the device in streaming mode and capture a certain number of packets.

--------------------------------------------------------------
% go in streaming mode
fwrite(s_bci,START_STREAMING_MSG,'uchar');

% read 100x33 bytes packets and pack them in an array
nb_samples = 100;
eeg_data = zeros(nb_samples,8);

for ii=1:nb_samples
    temp = fread(s_bci,DATA_PACKET_LENGTH);
    eeg_data(ii,:) = unpack_openbci_eeg(temp);
end

% stop streaming
fwrite(s_bci,STOP_STREAMING_MSG,'uchar');
--------------------------------------------------------------

What, I'm currently struggling with. however, is that I would like to be able to go back in streaming mode and capture another series of packets. On paper, it's not a problem, but it appears that Matlab serial buffer is filled with samples that came in after the last fread and before the fwrite was effective. I thought I could simply empty the buffer using the line:

fread(s_bci,s_bci.BytesAvailable);

but that doesn't do it. In fact, I got my best result using:

--------------------------------------------------------------
while s_bci.BytesAvailable>0
    fread(s_bci,s_bci.BytesAvailable);
    pause(0.1)
end

--------------------------------------------------------------

And then, I was able to do a second run. This method, however, is not very stable and because I read fixed packet length, whenever the buffer wraps around, my program lose track of packet beginning.

Also, even though the code above work fairly correctly, whenever I try to do something with the packets, such as displaying them, my interface begins too slow and the buffer gets overrun. 

I don't have a precise question, but I'm thinking of ways to improve the reliability of my program, by investigating the following possibilities, feel free to suggest other ideas or comment on these:

- Read more than one packet at a time from the buffer. Right now, I read only one packet, but maybe reading an integer number of packets at a time would optimize the time spent at extracting the packets.
- Dynamically find the beginning of the packets and then extract the data. Right now, I can't miss any packet, I'm depending on their alignment in the buffer.

Fred

PS: I built a function to extract eeg samples from packets, it includes a function to translate from 24bits to 32bits. It took me an afternoon to get it to work, but let me know if you need it and l'll give it to you.

Comments

  • wjcroftwjcroft Mount Shasta, CA
    Fred, hi.

    A direct serial port MATLAB interface might be helpful. And... The LSL interface via @jfrey 's Python plugin may also be another route you may want to consider. LSL can bridge between many different packages.

    http://openbci.com/forum/index.php?p=/discussion/334/labstreaminglayer-into-bcilab-eeglab-matlab

    William
  • wjcroftwjcroft Mount Shasta, CA
    Another point to consider is that you can't really stop / start the streaming repeatedly to gain some element of flow control. After the stream is started some form of buffering or parallel threading will be needed. LSL handles all this.
  • Hi William, thank you, a very pertinent comment.

    The reason I'm developing my own interface, however, is because I'm working toward implementing a EEG-BMI software for small computers such as the Raspberry Pi. I'm designing my EEG-BMI paradigm with Matlab, but that's only the proof of concept. Therefore, I need to take the time to understand what's going on under the hood, so that I can redo it in C. I also recommend LSL for anyone who just want it to work.

    By the way, I now have a better way to flush the buffer.

    % stop streaming
    fwrite(s_bci,STOP_STREAMING_MSG,'uchar');
    % wait for the command to be effective
    pause(1);
    % flush the buffer
    flushinput(s_bci)

    I'm not sure what the minimum amount of waiting time needs to be, but you do need to wait a bit, otherwise more data keep on coming in after after you flushed the buffer.
  • In regard to your second comment. Yes, this is something I'm starting to thinking about... It might be more efficient to just let it stream continuously and tap in whenever I need some data.

    Matlab is not a great tool for creating independent threads and I might run into a wall with this, but I want to give it a try. Worst case, I'll go with LSL.
  • wjcroftwjcroft Mount Shasta, CA
    Fred, take a look at the C language protocol machine in BrainBay, at about line 750 here.

    Complete BrainBay tree is here.



  • Nice! Yes, that's what I'm looking for.

    Apparently, it won't be that much work after all.
  • William's comments were very pertinent and I will most probably be converging toward the solutions described above. Still, for those interested in understanding or getting an openBCI Matlab interface to work. This script works reliably.

    Given an initialized serial port (see top of the thread), this script sets the device in streaming mode, extract the required number of samples, sets back the device in non-streaming mode and flush the buffer. It can be called repetitively, I tried it with large values of nb_samples_desired and the buffer never gets overunned.

    ----------------------------------------------------------------------------------------------------

    % go in streaming mode
    fwrite(s_bci,START_STREAMING_MSG,'uchar');

    % nb of samples desired
    nb_samples_desired = 1000;
    % running count of number of samples acquired
    nb_samples_obtained = 0;

    % nb of packets to read at once
    nb_packets_to_read = 0;

    % list of packet numbers (used to make sure we don't drop any)
    packet_nb = zeros(nb_samples_desired,1);
    % eeg data
    eeg_data = zeros(nb_samples_desired,8);

    % loop until we got all our samples
    while nb_samples_obtained < nb_samples_desired

        % check how many samples are in the buffer
        nb_packets_to_read = floor(s_bci.BytesAvailable/DATA_PACKET_LENGTH);
        
        % adjust so we only get the number of samples we asked for
        if nb_samples_obtained+nb_packets_to_read > nb_samples_desired
            nb_packets_to_read = nb_samples_desired-nb_samples_obtained;
        end
        
        % if we have packets to read
        if nb_packets_to_read>0
            
            % show infos (for validation purpose only)
            fprintf('nb_packets: %i\npacket_array size: %i\n',nb_packets_to_read,numel(packets_array));
            
            % read them from the buffer
            packets_array = fread(s_bci,DATA_PACKET_LENGTH*nb_packets_to_read);
            
            % extract the eeg samples
            [eeg_data_array, packet_nb_array] = unpack_openbci_eeg(packets_array,nb_packets_to_read);
            
            % copy them in the eeg_data array
            for ii=1:nb_packets_to_read
                eeg_data(nb_samples_obtained+ii,:) = eeg_data_array(ii,:);
                packet_nb(nb_samples_obtained+ii) = packet_nb_array(ii);
            end
            
            % update the samples count
            nb_samples_obtained = nb_samples_obtained+nb_packets_to_read;
        end
    end

    % stop streaming
    fwrite(s_bci,STOP_STREAMING_MSG,'uchar');

    % wait for the command to be effective
    pause(1);

    % flush the buffer
    flushinput(s_bci)

    % show that the buffer is empty
    s_bci.BytesAvailable

    ----------------------------------------------------------------------------------------------------

    You will need two other functions:

    ----------------------------------------------------------------------------------------------------

    %
    % Function that unpacks the eeg samples from the openbci packets
    %
    function [ eeg_data, packet_numbers] = unpack_openbci_eeg( packet, nb_packets )

    openbci_constants;

    % initialize output buffers
    eeg_data = zeros(nb_packets,NB_CHANNELS);
    packet_numbers = zeros(nb_packets,1);

    for ii=1:nb_packets

        offset = (ii-1)*DATA_PACKET_LENGTH;

        % check if the first byte correspond to the
        % standard
        if packet(1+offset) ~= PACKET_FIRST_WORD

            % show the packet for debuggin purpose
            packet
            warning('Invalid packet format');
            return;
        end
        
        % save the packet number
        packet_numbers(ii) = packet(2+offset);
        
        % extract eeg samples (24 bits) and interpret them as signed integer 32
        eeg_data(ii,1) = int24_to_int32(packet((3:5)+offset));
        eeg_data(ii,2) = int24_to_int32(packet((6:8)+offset));
        eeg_data(ii,3) = int24_to_int32(packet((9:11)+offset));
        eeg_data(ii,4) = int24_to_int32(packet((12:14)+offset));
        eeg_data(ii,5) = int24_to_int32(packet((15:17)+offset));
        eeg_data(ii,6) = int24_to_int32(packet((18:20)+offset));
        eeg_data(ii,7) = int24_to_int32(packet((21:23)+offset));
        eeg_data(ii,8) = int24_to_int32(packet((24:26)+offset));
    end

    end


    ----------------------------------------------------------------------------------------------------

    %
    % Function that interpret a 24 bits integer as a 32 bits integer. Two's complement encoding
    %
    function [int32_data] = int24_to_int32(uint24_data)

    % cast and order the bytes
    msb_byte_a = uint8(0);
    msb_byte_b = uint8(uint24_data(1));
    msb_byte_c = uint8(uint24_data(2));
    msb_byte_d = uint8(uint24_data(3));

    % check if it is a negative number
    if bitand(uint8(msb_byte_b),uint8(128),'uint8')
        % extend the sign bit, we are dealing with two's complement
        msb_byte_a = bitor(msb_byte_a,uint8(255),'uint8');
    end

    % build the 32 bits integer
    int32_data = swapbytes(typecast([msb_byte_a msb_byte_b msb_byte_c msb_byte_d],'int32'));

    end

    ----------------------------------------------------------------------------------------------------

    And these constants:

    ----------------------------------------------------------------------------------------------------
    DATA_PACKET_LENGTH = 33;

    NB_CHANNELS = 8;
    PACKET_FIRST_WORD = 160;

  • wjcroftwjcroft Mount Shasta, CA
    Fred, you would likely be introducing EEG artifacts into the data stream (at the start stop points) by this method. If that is ok in your application, that's fine. Most EEG scenarios do everything possible to avoid artifacts. The way to avoid the artifacts is to just sample the data continuously without stopping the stream. Buffering or threading of some sort is usually how this is done.

    Also consider posting your code to a Github site, rather than inserting it inline.

  • You are right.

    I will upgrade my interface to tap in the continuous stream. Anyway, this method leads to a 0.6 seconds delay between the start streaming command and the arrival of the first packets, which is obviously undesirable.

    Currently, it is not a problem in my experimental paradigm, it is trial-based and I already need to pad some time at the beginning of each trial to give the subject time to trigger the required state of mind, but very shortly I will need to sample continuously.

    Thanks for the matlab threading example, I knew it was possible, I already saw it at work, but this tutorial is going to give me a good start in making my own.

    I'm curious though, is the openbci ADC stopping when in non-streaming mode? Or openbci only stops sending packets, while keeping the ADC running?
  • wjcroftwjcroft Mount Shasta, CA
    When you send the command to stop the stream, the ADS1299 is also halted.

    Another example you could consider looking at is the MATLAB library code in LSL. That might show you how to do the buffering / threading.


  • Thank you so much, you are providing me information at a rate faster than I can pick it up ;)

    I'm on source forge. I'm also developing a machine learning library for Raspberry Pi, with the first application being this EEG-BMI project, but my repo is a bit messy right now, but a spring clean up is planned: 


    In accordance with your comments, the next steps in this project will be to upgrade the interface to include:

    - continuous sampling, with an independent thread that handles the buffering
    - maybe a display for online signal monitoring

    I'll keep posting in this thread.
  • wjcroftwjcroft Mount Shasta, CA
    edited April 2015
    Hey Fred thanks.

    Found your blog and website, some cool stuff!


  • Yes, I guess I'm too humble to post it myself. I did take a look at your own website: http://www.lightfield.com/nf.htm

    My guess is that we will be keeping in touch ;)
  • Hi @atom2626,

    I really would like to try your Matlab source code. However, the sourceforge link you provided is unavailable now. Could you share the code with me?

    Thanks,
    Anh
Sign In or Register to comment.