MindViewer-TGAM模块数据图形化软件

上上篇:TGAM开发手册翻译

上一篇:神念科技TGAM模块组装测试

将TGAM模块与蓝牙模块组装好,并测试可以从蓝牙或者串口接收数据,然后就是本文的重点:MindViewer,用于将接收到的数据解析并绘制折线图。

本文所涉及的所有关键词和概念等等都在前两篇文章中。

注意:TGAM的数据为大端,一般的Intel CPU为小端

图片来自:https://www.cnblogs.com/jiangyongyawen/p/4238160.html

data

原始数据

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-2^{24}22412^{24}-1 )。

官方文档中说明:一个包最小四个字节,最大179个字节。

整理一下:

名称范围
attention0-100
meditation0-100
8个EEG数据224-2^{24}22412^{24}-1
signal0-200
power0-128 3v
16位raw value-32768~32767
blink1-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)
{
//qDebug()<<"Parsering data";
raw=false;
eeg=false;

if(ba.isEmpty()) return -1;
int size=ba.size();
if(size>179) return -1;//官方说明最多179个字节

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){
//qDebug()<<"parser first aa "<<i<<(uchar)ba[i];
state=PARSER_STATE_SYNC_CHECK;
}
++i;
break;
case PARSER_STATE_SYNC_CHECK:
if((uchar)ba[i]==PARSER_SYNC_BYTE){
//qDebug()<<"parser second aa "<<i<<(uchar)ba[i];
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;
//qDebug()<<"parser payload length "<<i<<(uchar)ba[i];
state=PARSER_STATE_CHKSUM;//准备解析有效数据
}
++i;
break;
case PARSER_STATE_CHKSUM:
{
uchar z=i;
//首先校验数据是否有效
for(int j=0;j<payloadLength;j++){
//qDebug()<<(uchar)ba[z+j];
payloadSum+=(uchar)ba[z+j];
}
payloadSum &= 0xff;
payloadSum = ~payloadSum & 0xff;
z+=payloadLength;

if(payloadSum!=(uchar)ba[z]){
//如果与校验值不同就丢弃此包数据
return -1;
}/*else{
qDebug()<<"match";
}*/
//qDebug()<<"get data check sum is: "<<z<<(uchar)ba[z];
//qDebug()<<"parser check sum "<<i;
state=PARSER_STATE_PAYLOAD;
break;
}
case PARSER_STATE_PAYLOAD://解析数据
if((uchar)ba[i]==0x02){//数据信号强度值
//qDebug()<<"signal value "<<i<<(uchar)ba[i]<<(uchar)ba[i+1]<<(uint)ba[i+1];
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){//注意力值
//qDebug()<<"attention value "<<i<<(uchar)ba[i+1];
eeg=true;
pkt.attention=(uchar)ba[i+1];
state=PARSER_STATE_PAYLOAD;
i+=2;
}else if((uchar)ba[i]==0x05){//冥想值
//qDebug()<<"meditation value "<<i<<(uchar)ba[i+1];
eeg=true;
pkt.meditation=(uchar)ba[i+1];
state=PARSER_STATE_PAYLOAD;
i+=2;
//qDebug()<<"current i "<<i<<size;
}else if((uchar)ba[i]==0x06){//8bit raw value
}else if((uchar)ba[i]==0x07){
}else if((uchar)ba[i]==0x80){//16位原始数据
//qDebug()<<"parser raw value "<<i;
//qDebug()<<"parser a is: "<<(uchar)ba[i+2];
//qDebug()<<"parser b is: "<<(uchar)ba[i+3];
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数据部分
//0x83标志eeg部分开始,下一位表示为eeg部分程度默认为0x18
//qDebug()<<"parser eeg data "<<i<<ba[i];
//qDebug()<<"parser eeg length "<<(uchar)ba[i+1];
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;
//qDebug()<<(uchar)ba[i]<<i;
}else if((uchar)ba[i]==0x86){
break;
}
//ui->textHex->appendPlainText(QString::number((uchar)ba[z],16));
break;
case PARSER_STATE_NULL:
break;
default:
break;
}
}

//qDebug()<<"all data has been parsered";

return 0;
}

绘图效果:result

可以选择只显示某个值折线,也可以全部显示。

软件源码:Github

软件操作视频:神念科技TGAM模块数据采集解析并显示效果