1% =========================================================================== %
2%> @brief reads binary files of JUMP Sensors
4%>
this function reads the binary file provided by the JUMP Sensor and returns
5%> a
struct containing the data.
7%> @param filePath - full path to the binary file
8%> @param options.time_correction -
boolean to enable time correction (
default: true)
9%> @param options.plot_time_correction - boolean to plot the time correction (default: false)
10%> @param options.time_jump_correction - boolean to correct time jumps (default: true)
11%> @param options.plot_time_jump_correction - boolean to plot the time jump correction (default: false)
12%> @param options.header_only - boolean to only read the header of the file (default: false)
14%> @retval data - struct containing the data. the fields are defined in
18%> - This implementation is inspired by the implementation of Tobias Voegeli
20%> - This function also undoes timing errors made in the firmware. See @ref
30%> @copyright see the file @ref LICENSE in the root directory of the repository
31% =========================================================================== %
37 options.time_correction (1,1) logical =
true
38 options.plot_time_correction (1,1) logical =
false
39 options.time_jump_correction (1,1) logical =
true
40 options.plot_time_jump_correction (1,1) logical =
false
41 options.header_only (1,1) logical =
false
47fileID = fopen(filePath);
49 data = uint8(fread(fileID, FILE_HEADER,
'uint8'));
51 data = uint8(fread(fileID, inf,
'uint8'));
55%% check file consistency
56% check
for correct length (and correct
if necessary)
57len = (numel(data)-FILE_HEADER)/SAMPLE_SIZE;
60 data(SAMPLE_SIZE*len+1+FILE_HEADER:end) = []; % erase incomplete last block
63% check
if newline is at correct location
64cr = data(FILE_HEADER+58:SAMPLE_SIZE:end);
65lf = data(FILE_HEADER+59:SAMPLE_SIZE:end);
67err = [find(lf~=10) find(cr~=13)];
69 nerr = numel(unique(err));
70 warning([
'Error parsing file. Inconsistent block format. ' num2str(nerr)
' faulty block(s) found.'])
75ID = sprintf(strcat(
char(data(22:33))));
76UID = num2str(swapbytes(typecast(data(50:57),
'uint64')));
77name = sprintf(strcat(
char(data(58:73))));
78sw_version = strcat(num2str(typecast(data(74:77),
'uint32')),
'(', data(78),
')');
79hw_version = strcat(num2str(typecast(data(80:83),
'uint32')),
'(', data(79),
')');
80acc_range = double(data(84));
81gyro_range = double(uint32(data(85)) * 10);
82mpu_cal = double(data(86));
83act_thrs = double(data(87));
84inact_dur = double(data(88));
85mpu_rate = double(data(89));
86adxl_rate = double(data(90));
87pres_rate = double(data(91));
88magn_en = double(data(92));
89sync = double(data(93));
90ble_address = dec2hex(uint64(typecast(data(96:99),
'uint32'))*2^16 + uint64(data(95))*2^8 + uint64(data(94)));
91ble_address = dec2hex(swapbytes(uint64(hex2dec(ble_address))));
92ble_address = ble_address(1:end-4);
94if ~options.header_only
98 sec = double(typecast( reshape([data(FILE_HEADER+1:SAMPLE_SIZE:end), data(FILE_HEADER+2:SAMPLE_SIZE:end), data(FILE_HEADER+3:SAMPLE_SIZE:end), data(FILE_HEADER+4:SAMPLE_SIZE:end)]
',1,[]), 'uint32
'));
99 subsec = double(typecast( reshape([data(FILE_HEADER+5:SAMPLE_SIZE:end), data(FILE_HEADER+6:SAMPLE_SIZE:end)]',1,[]),
'uint16'));
100 t = (sec+subsec/2^15)';
101 t = datetime(1970,1,1,0,0,0) + seconds(t);
103 % acceleration (from Ivensense MPU-9250)
105 acc(:,1) = typecast( reshape([data((FILE_HEADER+7):SAMPLE_SIZE:end), data((FILE_HEADER+8):SAMPLE_SIZE:end)]',1,[]), 'int16');
106 acc(:,2) = typecast( reshape([data((FILE_HEADER+9):SAMPLE_SIZE:end), data((FILE_HEADER+10):SAMPLE_SIZE:end)]',1,[]), 'int16');
107 acc(:,3) = typecast( reshape([data((FILE_HEADER+11):SAMPLE_SIZE:end), data((FILE_HEADER+12):SAMPLE_SIZE:end)]',1,[]), 'int16');
108 acc =
double(acc) /
double(int32(0x10000) / (2 * int32(acc_range))); % rescale to unit 'g'
109 acc = acc * 9.80665; % standard gravity (BIPM, 1901)
111 % angular rate (from Ivensense MPU-9250)
113 gyro(:,1) = typecast( reshape([data(FILE_HEADER+13:SAMPLE_SIZE:end), data(FILE_HEADER+14:SAMPLE_SIZE:end)]',1,[]), 'int16');
114 gyro(:,2) = typecast( reshape([data(FILE_HEADER+15:SAMPLE_SIZE:end), data(FILE_HEADER+16:SAMPLE_SIZE:end)]',1,[]), 'int16');
115 gyro(:,3) = typecast( reshape([data(FILE_HEADER+17:SAMPLE_SIZE:end), data(FILE_HEADER+18:SAMPLE_SIZE:end)]',1,[]), 'int16');
116 gyro =
double(gyro) / (
double(int32(0x10000)) /
double(2 * int32(gyro_range))); % rescale to unit '°/s'
118 % high G acceleration (from Analog Devices ADXL375)
119 accHiG = zeros(len,3);
120 accHiG(:,1) =
double(typecast( reshape([data(FILE_HEADER+35:SAMPLE_SIZE:end), data(FILE_HEADER+36:SAMPLE_SIZE:end)]',1,[]), 'int16'));
121 accHiG(:,2) =
double(typecast( reshape([data(FILE_HEADER+37:SAMPLE_SIZE:end), data(FILE_HEADER+38:SAMPLE_SIZE:end)]',1,[]), 'int16'));
122 accHiG(:,3) =
double(typecast( reshape([data(FILE_HEADER+39:SAMPLE_SIZE:end), data(FILE_HEADER+40:SAMPLE_SIZE:end)]',1,[]), 'int16'));
123 accHiG = accHiG / 20.5; % rescale to unit 'g'
124 accHiG = accHiG * 9.80665; % standard gravity (BIPM, 1901)
126 % Magnetometer (from Ivensense MPU-9250)
128 magn(:,1) =
double(typecast( reshape([data(FILE_HEADER+41:SAMPLE_SIZE:end), data(FILE_HEADER+42:SAMPLE_SIZE:end)]',1,[]), 'int16'));
129 magn(:,2) =
double(typecast( reshape([data(FILE_HEADER+43:SAMPLE_SIZE:end), data(FILE_HEADER+44:SAMPLE_SIZE:end)]',1,[]), 'int16'));
130 magn(:,3) =
double(typecast( reshape([data(FILE_HEADER+45:SAMPLE_SIZE:end), data(FILE_HEADER+46:SAMPLE_SIZE:end)]',1,[]), 'int16'));
131 magn = magn / 0.6; % rescale to unit 'uT'
133 % pressure (from TE Connectivity MS5611-01BA03)
134 pressure =
double(typecast( reshape([data(FILE_HEADER+47:SAMPLE_SIZE:end), data(FILE_HEADER+48:SAMPLE_SIZE:end), data(FILE_HEADER+49:SAMPLE_SIZE:end), data(FILE_HEADER+50:SAMPLE_SIZE:end)]',1,[]), 'int32'))';
135 pressure = pressure / 100; % rescale to unit 'mbar'
137 % temp (from TE Connectivity MS5611-01BA03)
138 temp =
double(typecast( reshape([data(FILE_HEADER+51:SAMPLE_SIZE:end), data(FILE_HEADER+52:SAMPLE_SIZE:end), data(FILE_HEADER+53:SAMPLE_SIZE:end), data(FILE_HEADER+54:SAMPLE_SIZE:end)]',1,[]), 'int32'))';
139 temp = temp / 100; % rescale to unit '°C'
148data.sw_version = sw_version;
149data.hw_version = hw_version;
150data.acc_range = acc_range;
151data.gyro_range = gyro_range;
152data.mpu_cal = mpu_cal;
153data.act_thrs = act_thrs;
154data.inact_dur = inact_dur;
155data.mpu_rate = mpu_rate;
156data.adxl_rate = adxl_rate;
157data.pres_rate = pres_rate;
158data.magn_en = magn_en;
160data.ble_address = ble_address;
162if options.header_only
166data.tt = timetable(t,acc,gyro,accHiG,magn,pressure,temp);
167data.tt.Properties.VariableNames = {
'acc',
'gyr',
'hig',
'mag',
'prs',
'tmp'};
168data.tt.Properties.VariableUnits = {
'm/s^2',
'°/s',
'm/s^2',
'uT',
'mbar',
'°C'};
169data.tt.t.Format =
"dd.MM.uuuu HH:mm:ss.SSS";
170data.t_start = data.tt.t(1);
172% correct time jumps in the data
173% there are binary files that have jumps in the full second timestamps. The
174% subsecond timestamps, however, are correct. therefore
this section corrects
175% the full second timestamps to be continuous.
178t_diff = diff(data.tt.t);
180% check
for jumps (a jump is defined as a difference of more than 1 second)
181if any(abs(t_diff) > seconds(1))
182 assert(options.time_jump_correction, 'Time jumps detected. Cannot correct. Set option time_jump_correction to true.')
186 % split the difference into full seconds and subseconds
187 t_diff_sec = round(seconds(t_diff));
188 t_diff_subsec = seconds(t_diff) - t_diff_sec;
190 % check iff there are jumps in the subseconds (less that 3 ms or more than 7 ms)
191 assert(all(abs(t_diff_subsec) > 3e-3) && all(abs(t_diff_subsec) < 7e-3), 'Subsecond jumps detected. Cannot correct.')
193 % check if the seconds difference sums up to zero
194 assert(sum(t_diff_sec) == 0, 'Seconds difference does not sum up to zero. Cannot correct.')
196 % create the correction time vector
197 t_corr = -cumsum(t_diff_sec);
200 data.tt.t(2:end) = data.tt.t(2:end) + seconds(t_corr);
202 % check if the correction was successful
203 assert(all(abs(diff(data.tt.t)) < seconds(1)), 'Time correction failed.')
205 % plot the correction
206 if options.plot_time_jump_correction
209 plot(diff(data.tt.t), '.', DisplayName="Corrected time difference [s]")
210 ylabel('Corrected time difference [s]')
212 stem(t_diff_old, '.', MarkerSize=15, DisplayName="Original time difference [s]")
213 ylabel('Time difference [s]')
215 title('Time jump correction')
216 legend(Location="best")
221% bring the timetable to regular sampling rate
222if options.time_correction
227 data.tt = retime(data.tt, 'regular', 'linear', 'TimeStep', seconds(1/data.mpu_rate));
229 if options.plot_time_correction
230 warning('Time correction is disabled. Cannot plot time correction.')
238 error('Data check failed. Check the data struct.')
function jumpCheckData(in data)
function jumpCorrectData(in tt, in options)
function jumpReadData(in filePath, in options)