上上篇:TGAM开发手册翻译
上一篇:神念科技TGAM模块组装测试
将TGAM模块与蓝牙模块组装好,并测试可以从蓝牙或者串口接收数据,然后就是本文的重点:MindViewer,用于将接收到的数据解析并绘制折线图。
本文所涉及的所有关键词和概念等等都在前两篇文章中。
注意:TGAM的数据为大端,一般的Intel CPU为小端
图片来自:蓝牙解析数据及文档解析说明 博客园
原始数据
TGAM芯片每秒钟会通过蓝牙发送513包数据,其中有512包的原始数据包和一个包含EEG数据的大包。
512个数据包为:
1
| AA AA 04 80 02 high low checksum
|
AA AA
通用的包头
接下来的04
表示在最后一位(即校验值)之前有四个字节的数据
接下来的80 02
,80为表示原始数据,02表示原始数据有两个字节
high low两个字节组成了原始数据,计算方法根据官方的手册为:
1
| short raw = (Value[0]<<8) | Value[1];
|
其中Value[0]是高阶字节,Value[1]是低阶字节。
而最后一位是校验位,将04(不包括)后面和校验值前面的数据加起来进行处理
1 2
| checksum &= 0xFF; checksum = ~checksum & 0xFF;
|
如果值和校验值一样就是有效数据,否则就丢弃。
EEG数据包
此数据包包括两部分,一部分是EEG数据,另一部分是其他数据(冥想值、注意力、眨眼强度、信号强度等等),有可能不同时出现,也有可能只出现一部分。顺序也不一定
典型数据包如下(官方说明文档里面的):
1 2 3 4 5 6 7 8 9 10 11 12 13
| byte: value // Explanation [ 0]: 0xAA // [SYNC] [ 1]: 0xAA // [SYNC] [ 2]: 0x08 // [PLENGTH] (payload length) of 8 bytes [ 3]: 0x02 // [CODE] POOR_SIGNAL Quality [ 4]: 0x20 // Some poor signal detected (32/255) [ 5]: 0x01 // [CODE] BATTERY Level [ 6]: 0x7E // Almost full 3V of battery (126/127) [ 7]: 0x04 // [CODE] ATTENTION eSense [ 8]: 0x12 // eSense Attention level of 18% [ 9]: 0x05 // [CODE] MEDITATION eSense [10]: 0x60 // eSense Meditation level of 96% [11]: 0xE3 // [CHKSUM] (1's comp inverse of 8-bit Payload sum of 0x1C)
|
还有一种可能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| AA 同步 AA 同步 20 是十进制的32,即有32个字节的payload,除掉20本身+两个AA同步+最后校验和 02 代表信号值Signal C8 信号的值 83 代表EEG Power开始了 18 是十进制的24,说明EEG Power是由24个字节组成的,以下每三个字节为一组 18 Delta 1/3 D4 Delta 2/3 8B Delta 3/3 13 Theta 1/3 D1 Theta 2/3 69 Theta 3/3 02 LowAlpha 1/3 58 LowAlpha 2/3 C1 LowAlpha 3/3 17 HighAlpha 1/3 3B HighAlpha 2/3 DC HighAlpha 3/3 02 LowBeta 1/3 50 LowBeta 2/3 00 LowBeta 3/3 03 HighBeta 1/3 CB HighBeta 2/3 9D HighBeta 3/3 03 LowGamma 1/3 6D LowGamma 2/3 3B LowGamma 3/3 03 MiddleGamma 1/3 7E MiddleGamma 2/3 89 MiddleGamma 3/3 04 代表专注度Attention 00 Attention的值(0到100之间) 05 代表放松度Meditation 00 Meditation的值(0到100之间) D5 校验和
|
其他的好理解,EEG数据由三个字节,每个字节八位,结果计算:
1
| delta=(payload[i]<<16) | (payload[(i+1)]<<8) | (payload[(i+2)])
|
得到一个有符号整数(signed int),可以看出有效值为三个字节( −224 至 224−1 )。
官方文档中说明:一个包最小四个字节,最大179个字节。
整理一下:
名称 | 范围 |
---|
attention | 0-100 |
meditation | 0-100 |
8个EEG数据 | −224 至 224−1 |
signal | 0-200 |
power | 0-128 3v |
16位raw value | -32768~32767 |
blink | 1-255 |
走神程度 | 0-10 |
根据值范围,我将8个EEG数据和原始数据放在一张图像上,其余的值在255以内放在一张图像上。
这些值没有单位,只有多个值一起比较时才有意义。
使用Qt作为界面,qwt来绘制折线图。
数据包解析部分(参考官方提供的实例代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| int MainWindow::parserData(QByteArray ba, bool &raw, short &rawValue, bool &eeg, struct _eegPkt &pkt) { raw=false; eeg=false;
if(ba.isEmpty()) return -1; int size=ba.size(); if(size>179) return -1;
int i=0; uchar state=PARSER_STATE_SYNC; uchar payloadLength; uchar payloadSum; while(i<size-1){ switch(state){ case PARSER_STATE_SYNC: if((uchar)ba[i]==PARSER_SYNC_BYTE){ state=PARSER_STATE_SYNC_CHECK; } ++i; break; case PARSER_STATE_SYNC_CHECK: if((uchar)ba[i]==PARSER_SYNC_BYTE){ state=PARSER_STATE_PAYLOAD_LENGTH; }else{ state=PARSER_STATE_SYNC; } ++i; break; case PARSER_STATE_PAYLOAD_LENGTH: payloadLength=(uchar)ba[i]; if(payloadLength>170){ state=PARSER_STATE_SYNC; return -3; }else if(payloadLength==170){ return -4; }else{ payloadSum=0; state=PARSER_STATE_CHKSUM; } ++i; break; case PARSER_STATE_CHKSUM: { uchar z=i; for(int j=0;j<payloadLength;j++){ payloadSum+=(uchar)ba[z+j]; } payloadSum &= 0xff; payloadSum = ~payloadSum & 0xff; z+=payloadLength;
if(payloadSum!=(uchar)ba[z]){ return -1; }
state=PARSER_STATE_PAYLOAD; break; } case PARSER_STATE_PAYLOAD: if((uchar)ba[i]==0x02){ eeg=true; pkt.signal=(uchar)ba[i+1]; state=PARSER_STATE_PAYLOAD; i+=2; }else if((uchar)ba[i]==0x03){ }else if((uchar)ba[i]==0x04){ eeg=true; pkt.attention=(uchar)ba[i+1]; state=PARSER_STATE_PAYLOAD; i+=2; }else if((uchar)ba[i]==0x05){ eeg=true; pkt.meditation=(uchar)ba[i+1]; state=PARSER_STATE_PAYLOAD; i+=2; }else if((uchar)ba[i]==0x06){ }else if((uchar)ba[i]==0x07){ }else if((uchar)ba[i]==0x80){ raw=true; rawValue=((uchar)ba[i+3]<<8)|(uchar)ba[i+2]; return 0; }else if((uchar)ba[i]==0x81){ }else if((uchar)ba[i]==0x83){ eeg=true; pkt.delta =((uint)ba[i+4]<<16)|((uint)ba[i+3]<<8)|((uint)ba[i+2]); pkt.theta =((uint)ba[i+7]<<16)|((uint)ba[i+6]<<8)|((uint)ba[i+5]); pkt.lowAlpha =((uint)ba[i+10]<<16)|((uint)ba[i+9]<<8)|((uint)ba[i+8]); pkt.highAlpha =((uint)ba[i+13]<<16)|((uint)ba[i+12]<<8)|((uint)ba[i+11]); pkt.lowBeta =((uint)ba[i+16]<<16)|((uint)ba[i+15]<<8)|((uint)ba[i+14]); pkt.highBeta =((uint)ba[i+19]<<16)|((uint)ba[i+18]<<8)|((uint)ba[i+17]); pkt.lowGamma =((uint)ba[i+22]<<16)|((uint)ba[i+21]<<8)|((uint)ba[i+20]); pkt.midGamma =((uint)ba[i+25]<<16)|((uint)ba[i+24]<<8)|((uint)ba[i+23]); state=PARSER_STATE_PAYLOAD; i+=26; }else if((uchar)ba[i]==0x86){ break; } break; case PARSER_STATE_NULL: break; default: break; } }
return 0; }
|
绘图效果:
可以选择只显示某个值折线,也可以全部显示。
软件源码:MindViewer中
软件操作视频