Convert 24-bit ADC serial read data from 3-byte format to signed integer (int32)

40 views (last 30 days)
I am receiving EEG data from a 24 bit ADC over serial. The ADC data is transmitting in 3 bytes from MSB to LSB. The full packet is 21 bytes:
  • The first byte is the start byte - 0xFF (255 in decimal)
  • Then packet number byte.
  • Then the next 3 bytes are the 24 bit ADC value broken into MSB LSB2 LSB1
I can parse the data fine, but re-constructing a 2's complement signed int32 number is causing issues. The values I am getting out certainly don't reflect what the ADC should be giving out.
Below are the lines to read and parse the 504 samples (which gives me 24 ADC values (504samples/21bytes = 24 values)). I have tried uint8 instead of uchar with similar results (when I try int8 I get a invalid specified precision error).
comEEGSMT = serial(com,'BaudRate',3000000);
fopen(comEEGSMT);
rawData(1:504) = fread(comEEGSMT, 504, 'uchar');
fclose(comEEGSMT);
startPackets = find(rawData == 255);
bytes = rawData([startpackets+2 startpackets+3 startpackets+4]);
I have tried the following method to reconstruct the value:
ADC_value = bytes(:,1)*256^2 + bytes(:,2)*256 + bytes(:,3);
and the following line is the formula to convert the above number to volts:
ADC_value_volts = ADC_value*(5/3)*(1/(2^32));
The values are in the range of 4000 - 8000 microvolts with large jumps in value. The values SHOULD be in the range of 200 - 600 microvolts with small changes.
I have found other questions relating to similar issues, but have had no success trying the proposed solutions such as in the link below: https://uk.mathworks.com/matlabcentral/answers/137965-concatenate-3-bytes-array-of-real-time-serial-data-into-single-precision
Any help would be very much appreciated as I've been stuck on this for quite long.
Thanks Mark

Answers (2)

Jan
Jan on 6 Dec 2016
Isn't this insecure:
startPackets = find(rawData == 255);
What happens, if a 255 appears in the data?
This replies the wrong order, as far as I can see:
bytes = rawData([startpackets+2 startpackets+3 startpackets+4]);
Try:
ADC_value = rawData(startpackets+2)*256^2 + rawData(startpackets+3)*256 + ...
rawData(startpackets+4);
  6 Comments
Mark O'Sullivan
Mark O'Sullivan on 9 Dec 2016
I don't understand why the packet number is meaningless? Presume it's just a handy way to show if you're dropping packets or whatever.
The documentation states:
The formula for reconstructing to a 2's compliment, signed int32 is:
data = (MSB<<24 + LSB2<<17 + LSB1<<10).
The conversion factor for EEG channels to volts is:
EEG = data*(5/3)*(1/2^32)
I haven't been able to get meaningful data using the parsing method above with these formula. The expected values are in the hundreds of microvolts range, so I should be getting values around 0.0005V (5e-4 V).
Jan
Jan on 9 Dec 2016
Perhaps:
ADC_value = typecast(uint32(rawData(startpackets+2)*16777216 + ...
rawData(startpackets+3)*131072 + ...
rawData(startpackets+4)*1024), 'int32');

Sign in to comment.


David Mellinger
David Mellinger on 29 Jun 2023
Edited: David Mellinger on 29 Jun 2023
There's an additional problem if the file has signed 3-byte numbers. After a LOT of trial and error, I ended up doing it like this.
ch = fread(fp, [3 n], 'uint8'); % n is the number of data values to read
val = int32(ch(3,:)*2^16 + ch(2,:)*2^8 + ch(1,:));
ix = (ch(3,:) >= 0x80); % indices of negative values
val(ix) = typecast(bitor(val(ix), -(2^24)), 'int32');
This is for little-endian data in the file (fp) being read from. For big-endian data, which the person asking the question has, use this:
ch = fread(fp, [3 n], 'uint8'); % n is the number of data values to read
val = int32(ch(1,:)*2^16 + ch(2,:)*2^8 + ch(3,:));
ix = (ch(1,:) >= 0x80); % indices of negative values
val(ix) = typecast(bitor(val(ix), -(2^24)), 'int32');

Products

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!