1. Introduction
This document describes how to collect data from your Muse device using the Bluetooth Low Energy (BLE) functionalities provided by MATLAB®.
This example is based on the standard Muse communication protocol [A] and MATLAB® official documentation, as described in our quick start guide [B]:
2. Collect Data from your Muse
This example shows how to collect and plot inertial data (i.e., angular velocity, and accelerations) from your Muse device using the BLE communication support provided by MATLAB® functions.
First, check that the BLE device is powered on and support connections by finding it in MATLAB® using the blelist function. After the device has been found in MATLAB®, connect to it by calling the ble function. Specify the name of the device if the name is unique or specify the device address.
%% Discover and connect to Muse device devlist = blelist; muse = ble("muse_roberto");
Access the Muse v3 Custom Service and relative command and data characteristics, to get the corresponding UUIDs.
%% Access custom service and characteristics of interest cmd = characteristic(muse, "C8C0A708-E361-4B5E-A365-98FA6B0A836F", ... "D5913036-2D8A-41EE-85B9-4E361AA5C8A7"); dat = characteristic(muse, "C8C0A708-E361-4B5E-A365-98FA6B0A836F", ... "09BF2C52-D1D9-C0B7-4145-475964544307");
Next, enable subscription to both command and data characteristics.
%% Subscribe to characteristic notify subscribe(cmd); subscribe(dat);
Check device status and get its configuration, before starting acquisition.
%% Get device status (i.e., just to check setup consistency) % GET_STATE command code (hex): 0x82 write(cmd, [130 0]); cmd_response = read(cmd); state = displayDeviceState(cmd_response); %% Get device configuration (necessary to manage decoding during stream) % GET_FULL_SCALES command code (hex): 0xC0 write(cmd, [192 0]); cmd_response = read(cmd); [gyr_fs, gyr_sens, axl_fs, axl_sens, hdr_fs, hdr_sens, mag_fs, ... mag_sens] = displayDeviceFullScales(cmd_response);
If the device configuration is correct, we can activate the acquisition as follows.
%% Start data acquisition in STREAMING mode % To stop acquisition at runtime, write the following command on Command % Window: write(cmd, [2 1 2]). It set the device in SYS_IDLE state. if (state == 2) % SET_STATE command code (hex): 0x02 % streaming type: 0x08 (continuous streaming) % acquisition mode: 0x02 (accelerometer only) % acquisition freq: 0x01 (25 Hz) write(cmd, [2 5 8 2 0 0 1]); else disp('Acquisition already running.'); end
The data acquisition is managed through a dedicated callback function assigned to the DataAvailableFcn property of the data characteristic.
%% Manage data acquisition, decoding and visualization % Setup figure for real time plot figure; hold all; nSamples = 1000; ll = xline(0,'color','k'); buff = nan(nSamples,1); axbuff = plot(0:nSamples-1,buff,'b'); aybuff = plot(0:nSamples-1,buff,'g'); azbuff = plot(0:nSamples-1,buff,'r'); xlim([0 nSamples]); s = 0; % Assign data callback dat.DataAvailableFcn = @displayCharacteristicData;
The result should be something like those represented in Figure 1.
FIGURE 1: PLOT OF ACCELEROMETER DATA EXAMPLE.
3. Insights
As regard the message decoding, the use of dedicated standard function based on specific commands as well as callback functions are the most straight forward approach that can be adopted. As an example, we provide in the following some code snippets taking the device state, configuration, and accelerometer readings as reference to show a working implementation.
%% displayDeviceState(arg) function [state] = displayDeviceState(arg) % Check response consistency and positive acknowledge if (arg(1) == 0 && arg(3) == 130 && arg(4) == 0) % Check buffer at cell index number 5 to get the state code switch(arg(5)) case 2 % SYS_IDLE: 0x02 state = 2; disp('Current state: SYS_IDLE'); case 4 % SYS_LOG: 0x04 state = 4; disp('Current state: '); case 6 % SYS_TX: 0x06 (streaming 128 Byte) state = 6; disp('Current state: '); case 8 % SYS_TX: 0x08 (streaming 20 Byte) state = 8; disp('Current state: '); otherwise state = 0; disp('Device state not available.'); end else disp('Error on retrieving device state!'); end end
In the case of device state, the conversion is from a numeric value to the corresponding string. The aim is to make the status label accessible to the user in a simple way. On the other side, regarding the configuration parameters, the full scales values and the corresponding sensitivity coefficients play a key role in the subsequent decoding of the data stream during an acquisition, particularly during real-time streaming.
Among others, it is important to observe that a bit mask for each sensor of interest (i.e., typically gyroscope, accelerometer, magnetometer, and accelerometer HDR) must be defined:
gyr_mask = 3; % bit mask (hex): 0x0003 axl_mask = 12; % bit mask (hex): 0x000c hdr_mask = 48; % bit mask (hex): 0x0030 mag_mask = 192; % bit mask (hex): 0x00c0
Each mask will be applied to the uint16 value, notified by the device, to get the corresponding full-scale code of each sensor.
% Apply bit-mask to get gyr, axl, mag, hdr full scales gyr_code = bitand(y,gyr_mask); switch(gyr_code) case 0 gyr_fs = 245; gyr_sens = 0.00875; % dps/LSB disp('Gyroscope FS: 245 dps'); case 1 gyr_fs = 500; gyr_sens = 0.0175; disp('Gyroscope FS: 500 dps'); case 2 gyr_fs = 1000; gyr_sens = 0.035; disp('Gyroscope FS: 1000 dps'); case 3 gyr_fs = 2000; gyr_sens = 0.070; disp('Gyroscope FS: 2000 dps'); otherwise gyr_fs = 0; gyr_sens = 1; disp('Gyroscope FS not available.'); end
The sensitivity coefficients, together with a proper cast operation allows to retrieve the floating-point value of each axis as follows.
% Decode data (in this example we ignore the first 8 bytes that % represents the timestamp) x = [data(9) data(10)]; x_val = typecast(uint8(x),'int16'); x_val = x_val * axl_sens; y = [data(11) data(12)]; y_val = typecast(uint8(y),'int16'); y_val = y_val * axl_sens; z = [data(13) data(14)]; z_val = typecast(uint8(z),'int16'); z_val = z_val * axl_sens;