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

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

回答(2 个)

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 个评论

Fortunately, the datasheet says that 0xFF is an unambiguous start code (i.e. by design, no other byte in the data stream can have a value of 0xFF).
Your line of code and my line do the exact same thing and give the exact same results.
The lines of code below demonstrates this.
Thanks for taking the time to reply though. Any other suggestions?
rawD = ([255;3;40;140;195;255;4;40;160;203;255;5;48;138;249])
starts = find(rawD == 255)
bytes = rawD([starts+2 starts+3 starts+4])
ADC_value = bytes(:,1)*256^2 + bytes(:,2)*256 + bytes(:,3)
ADC_value_2 = rawD(starts+2)*256^2 + rawD(starts+3)*256 + rawD(starts+4)
If you post the data as text and not as a screenshot, the readers could use it by copy&paste for experiments.
If all 8 bits of the bytes are used for data, I do not see how you can avoid 255 as byte value "by design". Perhaps the bytes contain 7 bits only?
Thanks, reply now edited to show code as text.
The datasheet states explicitly what I said above. Not quite sure how its achieved, but in all my testing of the device, I have not yet seen a 255 besides as the start byte.
Perhaps they have included some hardware/software which limits/clips the voltage, for example:
if ADC = 255
ADC = 254
end
Puh. Yes, such dirty tricks exist. The packet number as 2nd byte is meaningless. This data format is a crap.
Usually an offset is subtracted from the ADC values before scaling. Don't you expect values distributed around 0V?
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).
Perhaps:
ADC_value = typecast(uint32(rawData(startpackets+2)*16777216 + ...
rawData(startpackets+3)*131072 + ...
rawData(startpackets+4)*1024), 'int32');

请先登录,再进行评论。

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');

类别

帮助中心File Exchange 中查找有关 EEG/MEG/ECoG 的更多信息

产品

Community Treasure Hunt

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

Start Hunting!

Translated by