TGAM开发手册翻译

本文英文版文档在MDT开发包的ThinkGear Serial Stream SDK文件夹下,名称为ThinkGearSerialStreamGuide.pdf

ThinkGear Serial Stream引导

介绍

ThinkGear™是每个神念产品或合作产品中的技术,使设备能够与佩戴者的脑电波接口。它包括触摸前额的传感器、位于耳垫上的接触点和参考点,以及处理所有数据并以数字形式向软件和应用程序提供这些数据的机载芯片。原始脑电波和eSense Meters(注意力和冥想)都是在ThinkGear芯片上计算的。

Think Gear Serial Stream SDK文档(以前称为Think Gear Communications Protocol)详细定义了如何与Think Gear模块通信。特别是,它描述了:

  • 如何解析字节的串行数据流来重建ThinkGear发送的各种类型的脑电波数据
  • 如何解释和使用在BCI应用程序中从ThinkGear发送的各种类型的脑电波数据(包括注意力、冥想和信号质量数据)
  • 如何向ThinkGear发送重构命令字节,以便对模块的行为和输出进行动态定制

Think Gear数据值一章定义了Think Gear可以报告的数据值类型。强烈建议您阅读本节,以熟悉哪些类型的数据值(并且不是)可从ThinkGear获得,然后再继续到后面的章节。

Think Gear Packets一章描述了用于传递Think Gear数据值的Think Gear Packet格式。

Think Gear Command Bytes一章是为高级用户编写的,涉及如何向Think Gear发送Command Bytes以便自定义其配置(更改波特率、启用/禁用某些数据值输出等)。

ThinkGear 数据值

POOR_SIGNAL Quality

这个无符号的一字节整数值描述了ThinkGear测量的信号有多差。它的价值范围从0到200。任何非零值都表示检测到某种噪声污染。数越高,检测到的噪声越多。一个值200有一个特殊的含义,特别是ThinkGear接触不接触用户的皮肤。

此值通常每秒输出一次,并表示最近测量的差。

信号不佳可能是由许多不同的事情引起的。严重程度依次为:

  • 不在人的头上、地面或参考联系人的传感器。(比如,当没有人穿ThinkGear的时候)。
  • 传感器、地面或参考触点与人的皮肤接触不良(即:头发,或耳机,不适合一个人的头,或耳机没有正确地放置在头上)。
  • 佩戴者的过度运动(即。过度移动头部或身体,推挤耳机)。
  • 过度的环境静电噪声(一些环境中有强烈的电信号或静电积聚在佩戴传感器的人身上)。
  • 过度的非EEG生物识别噪声(即。EMG,EKG/ECG,EOG等)

在ThinkGear的正常使用中,一定数量的噪声是不可避免的,NeuroSky的滤波技术和eSenseTM算法都被设计用来检测、纠正、补偿、解释和容忍多种类型的非EEG噪声。大多数只对使用eSense值感兴趣的典型用户,如注意力和冥想,不需要太担心POOR_IGNA L质量值,只需注意,在检测POOR_IGNA L时,注意力和冥想值不会被更新。对于一些需要对噪声更敏感的应用(如某些医学或研究应用),或当检测到甚至是轻微的噪声时需要立即知道的应用,POOR_SIGNAL质量值更有用。

默认情况下,启用此数据值的输出。它通常每秒输出一次。

eSense™计量器

对于所有不同类型的eSenses(即:注意,冥想),仪表值是在1到100的相对密度尺度上报告的。在这一尺度上,在任何给定的时间时刻,40至60之间的值被认为是“中性的”,在概念上类似于传统的脑电图测量技术中确定的“基线”(尽管确定ThinkGear基线的方法是专有的,可能与传统的脑电图不同)。从60到80的值被认为是“略有升高”,可以解释为可能高于正常水平(对某人来说可能高于正常的注意力或冥想水平)。从80到100的值被认为是“升高”的,这意味着它们强烈地表明了该eSense的高度。

同样,在比额表的另一端,20至40之间的数值表示eSense的“降低”水平,1至20之间的数值表示eSense的“强烈降低”水平。这些水平可能表示分心、激动或异常状态,根据每个eSense的相反情况。一个eSense米值为0是一个特殊的值,表明ThinkGear无法以合理的可靠性计算一个eSense级别。这可能是(而且通常是)由于过多的噪音,如上面POOR_signal质量部分所描述的。

每种解释的范围略宽的原因是,eSense算法的一些部分是动态学习的,有时使用一些“慢适应”算法来适应每个用户的自然变化和趋势,从而考虑和补偿人脑中的脑电图受到正常方差和变化范围的影响。这是为什么ThinkGear传感器能够在非常广泛的个人和环境条件下对广泛的个人进行操作,同时仍然提供良好的准确性和可靠性。

鼓励开发人员进一步解释和调整这些准则范围,以便对其应用程序进行微调(例如,应用程序可以忽略低于60的值,并且只对60-100之间的值作出反应,将其解释为开始提高注意力水平)。

ATTENTION eSense

这个无符号的一字节值报告用户当前的eSense Attention值,它表示用户的精神“专注”或“注意”水平的强度,例如在强烈集中和定向(但稳定)的精神活动期间发生的强度。其值从0到100不等,分心、走神、注意力不集中或焦虑可能会降低注意力的水平。有关解释一般的eSense级别的详细信息,请参阅上面的eSense™ Meter。

默认情况下,启用此数据值的输出。它通常每秒输出一次。

MEDITATION eSense

这个无符号的一字节值报告用户当前的eSense冥想仪,它表示用户的心理“平静”或“放松”的水平。其值从0到100不等…请注意,冥想是衡量一个人的心理水平,而不是身体水平,所以简单地放松身体的所有肌肉可能不会立即导致冥想水平的提高。然而,对于大多数正常情况下的人来说,放松身体往往也有助于大脑放松。

冥想与大脑中活跃的心理过程减少活动有关,长期以来一直观察到的一种效果是,闭上眼睛会关闭处理眼睛图像的心理活动,因此闭上眼睛往往是提高冥想仪水平的有效方法。

分心、走神、焦虑、激动和感官刺激可能降低冥想仪的水平。有关解释一般eSense水平的详细信息,请参阅上面的“eSense Meters”。

默认情况下,启用此数据值的输出。它通常每秒输出一次。

8位源波值(8BIT_RAW Wave Value)

此无符号单字节值相当于下面描述的有符号RAW波值(16位),除了它被缩放为无符号,并且只有最重要的8位被输出(其中“大多数重要“是基于特定的ThinkGear硬件定义的)。 不输出最低几位精度的成本下,这使得在串行通信9600波特率下的带宽限制原始输出成为可能。对于许多应用程序(如实时显示原始波的图形),显示8位精度是足够的,因为人的眼睛通常不能快速识别像素,这些像素可能对应于较低的精度位。如果需要更高的精度,考虑使用正常有符号的RAW波值(16位)(下面描述)以较高的波特率输出。

虽然8BIT_RAW输出启用时,只有最重要的8位是输出的,所有计算仍然在ThinkGear中根据原始波的最大精度进行,可用于ThinkGear硬件的信息,因此内部不会丢弃任何信息。只有那个输出的原始值减少到8位,以节省串行带宽。

默认情况下,此数据值的输出将被禁用。就像常规的16位RAW波值一样8BIT_RAW波值通常每秒输出128次,或大约每78ms输出一次。

此数据值仅在ThinkGear模块中可用。它无法从ThinkGear ASIC(比如:MindWave和MindWave Mobile)获取。

RAW_MARKER部分开始

这不是一个真正的数据值,主要对调试非常精确的时间和原始波的同步,或研究目的有用。目前,值将始终为0x00。

默认情况下,此数据值的输出将被禁用。它通常每秒输出一次。

此数据值仅在ThinkGear模块中可用。 它无法从ThinkGear ASIC(比如:MindWave和MindWave Mobile)获取。

RAW Wave Value (16-bit)

此数据值由两个字节组成,并表示单个原始波样本。它的值是一个有符号的16位整数,范围从-32768到32767…值的第一个字节表示两个运算值的高阶位,而第二个字节表示低阶位。要重建完整的原始波值,只需将第一个字节向左移动8位,然后和第二个字节按位与:

1
short raw = (Value[0]<<8) | Value[1];

其中Value[0]是高阶字节,Value[1]是低阶字节。

在位操作不方便的系统或语言中,可以替换以下算术操作:

1
2
raw = Value[0]*256 + Value[1];
if( raw >= 32768 ) raw = raw - 65536;

其中RAW是语言中的任何有符号数字类型,可以表示从-32768到32767的所有数字。

然而,在Java中,您使用以下代码来代替,以避免出现(原始波数据)的解析时问题代码0x80。这是由于Java没有无符号数据类型,在组合这两个字节时必须仔细处理。

1
2
3
4
5
private int[] highlow = new int[2];
highlow[0] = (int)(payload[index] & 0xFF);
highlow[1] = (int)(payload[index+1] & 0xFF);
raw = (highlow[0] * 256) + highlow[1];
if( raw > 32768 ) raw -= 65536;

每个ThinkGear模型只在完整的-32768到32767的范围内某些区域报告其原始波信息。例如,ThinkGear ASIC可能只报告介于大约-2048到2047两者之间的原始波,而ThinkGear 模块可能只报告原始波之间大约0到1023。欲知更多信息,请查阅您的特定ThinkGear硬件的文档。

默认情况下,此数据值的输出未被启用。如果启用,ThinkGear模块每秒输出128次,或者大约每7.8ms输出一次。ThinkGear ASIC(比如MindWave和MindWave Mobile)每秒输出512次,大约2ms一次。

注意:由于输出这个值的速率很高,以及所涉及的数据字节数,所以它是只能在串行通信流上输出16位RAW波值57600波特及以上。如果需要9600波特的原始波信息,请考虑8BIT_RAW波值输出代替(如上所述)。

EEG_POWER

这个数据值代表了当前8种公认类型的脑电波(脑波)的频率。它由8个四字节浮点数以以下顺序组成:
delta δ(0.5-2.75Hz),
theta θ(3.5-6.75Hz),
低α(7.5-9.25Hz),
高α(10-11.75Hz),
低β(13-16.75Hz),
高β(18-29.75Hz),
低伽马(31-39.75Hz),
中伽马(41-49.75Hz)。

这些值没有任何单位,当与彼此和自己相比,考虑相对的数量和时间变化,才有意义的。浮点数是标准的大端IEEE 754,所以在C中32位值可以直接转换为float*(大端环境下)以用作浮点数组。

默认情况下,此数据值的输出是为启用的。启用时,通常每秒输出一次。

此版本的EEG_POWER,使用浮点数,只在ThinkGear模块中可用,并且不是以ASIC。 关于ASIC当量,见ASIC_EEG_POWER_INT。对于ThinkGear固件v1.7.8,ASIC版本是读取脑电波段功率的标准和首选格式,并且这种浮点格式只被弃用到向后兼容的目的

ASIC_EEG_POWER_INT

此数据值表示当前8种常见的脑电图类型的大小(脑波)。它是ASIC等价于EEG_POWER,主要区别在于这个数据值作为8个3字节无符号整数的序列输出,而不是4字节浮点数。这些3字节无符号整数是大endian格式的。

八个脑电功率的输出顺序如下:δ(0.5-2.75Hz)、θ(3.5-6.75Hz),
低α(7.5-9.25Hz)、高α(10-11.75Hz)、低β(13-16.75Hz)、高β(18-29.75Hz),
低伽马(31-39.75Hz)和中伽马(41-49.75Hz)。这些值没有单位,因此与对方和自己相比,才有意义,才能考虑相对数量和时间波动。

默认情况下,启用此数据值的输出,通常每秒输出一次。

从ThinkGear固件v1.7.8开始,这种形式的EEG_POWER是EEG带的标准输出格式,而EEG_POWER中描述的权力只是为了向后兼容性而保留的,通过命令开关访问。在v1.7.8之前,EEG_POWER是标准。

眨眼强度

这个无符号的一个字节值报告用户最近眨眼的强度。它的值范围从1到255,每当检测到眨眼时就会报告。该值表示眨眼的相对强度,没有单位。

注:此数据值目前无法通过TGCD和TGC API获得。不能从任何当前的ThinkGear硬件直接作为输出。有关TGCD,查看TG_GetValueStatus()和TG_GetValue()的TG_DATA_BLINK_STRENGTH数据类型。

走神程度(Mind-wandering Level)

这个无符号的一个字节值报告了用户走神级别的强度。它的值范围从0到10。值为0表示级别为N/A。1-10的值表示级别(带较高的值表示较高的走神水平)。

ThinkGear数据包

ThinkGear组件将其数字数据作为字节的异步串行流传送。串行流必须被解析和解释为ThinkGear分组,以便正确地提取和解释上面一章中描述的ThinkGear数据值。

ThinkGear分组是一种分组格式,由三部分组成:

  • Packet Header
  • Packet Payload
  • Payload Checksum

使用ThinkGear分组将数据值(在上一章中描述)从ThinkGear模块传递到任意接收器(PC、另一个微处理器或任何其他可以接收字节流的设备)。由于串行I/O编程API在每个平台、操作系统和语言上都是不同的,因此它超出了本文档的范围(参见您平台的串行I/O编程文档)。本章将只讨论如何将字节流解释为ThinkGear分组、Payload,最后解释为上一章中描述的有意义的数据值。

数据包格式的设计主要是为了健壮和易用:结合起来,Header和Check-sum提供数据流同步和数据完整性检查,而Data Payload的格式则确保在未来可以将新的数据字段添加到(或从数据包中删除的现有数据字段)中,而不会破坏任何现有应用程序/设备中的数据包解析器。这意味着,任何正确实现ThinkGear分组解析器的应用程序都将能够使用较新的ThinkGear模块模型,而无需更改它们的解析器或应用程序,即使较新的ThinkGear硬件包含新的数据字段或重新排列数据字段的顺序。

数据包结构

数据包以字节的异步串行流的形式发送。传输介质可以是UART、串行COM、USB、蓝牙、文件或任何其他可以流字节的机制。

每个数据包从其报头开始,然后是其数据有效负载,最后是有效负载的校验和字节,如下:

1
2
3
[SYNC] [SYNC] [PLENGTH] [PAYLOAD...] [CHKSUM]
_______________________ _____________ ____________
^^^^^^^^(Header)^^^^^^^ ^^(Payload)^^ ^(Checksum)^

[PAYLOAD…]节允许长达169个字节长,而[SYNC]、[PLENGTH]和[CHKSUM]中的每一个都是一个字节。这意味着一个完整的、有效的数据包至少有4个字节长(当数据有效负载为零字节长,可能为空)和最大173字节长(如果数据有效负载是最大169字节长,则可能)。

下面在逐步分析数据包的指南中给出了一个正确解析ThinkGear分组的过程。

数据包头

数据包的报头由3个字节组成:两个同步[SYNC]字节(0xAA 0xAA),然后是[PLENGTH](有效负载长度)字节:

1
2
3
[SYNC] [SYNC] [PLENGTH]
_______________________
^^^^^^^^(Header)^^^^^^^

这两个[SYNC]字节用于发出新到达数据包的开始信号,并且是具有0xAA(十进制170)值的字节。同步是两个字节长,而不是只有一个字节长,以减少发生在分组中的[SYNC](0xAA)字节可能被误认为是分组的开头的机会。虽然仍然有可能有两个连续的[同步]字节出现在数据包中(导致分析器试图开始将数据包的中间解析为数据包的开头),但[PLENGTH]和[CHKSUM]组合确保这样的“错误同步”数据包“永远不会意外地被解释为有效数据包(有关更多细节,请参见下面的Payload Checksum)。

[PLENGTH]字节表示数据包的数据有效载[PAYLOAD…]的长度,以字节为单位,可以是从0到169的任何值。任何较高的值都表示错误(PLENGTH TOO大小)。

请注意,[PLENGTH]是数据包的数据有效负载的长度,而不是整个数据包的长度。包的完整长度将始终为[PLENGTH]+4。

数据负载

数据包的数据有效负载只是一系列字节。数据包中的数据有效载字节数由来自分组报头的[PLENGTH]字节给出。第1章中描述的将数据有效负载字节解释为ThinkGear数据值的解释在下面的数据有效负载结构部分中详细定义。请注意,数据有效负载的解析通常不应该尝试,直到有效负载校验和字节[CHKSUM]被验证后,如下节所述。

负载校验

必须使用[CHKSUM]字节来验证数据包的数据有效负载的完整性。有效载荷的校验和定义为:

1.求和数据包的数据有效负载的所有字节
2.取和的最低8位
3.在最低的8位上执行位逆(补码取反)

接收数据包的接收机必须使用这3个步骤来计算它们接收到的数据有效负载的校验和,然后将其与与分组接收的[CHKSUM]校验和字节进行比较。如果计算的有效载荷校验和与接收的[CHKSUM]值不匹配,则整个数据包应作为无效丢弃。如果它们是匹配的,那么接收方可以按照下面的“数据有效负载结构”部分的描述来解析数据有效负载。

数据负载结构

一旦验证了数据包的校验和,就可以解析数据有效负载的字节。数据有效负载本身由一系列连续的数据值组成,每个数据值包含在一系列称为DataRow的字节中。每个DataRow包含有关数据值代表什么、数据值的长度和数据值本身的字节的信息。因此,要解析数据有效负载,必须从数据有效负载中解析每个DataRow,直到数据有效负载的所有字节都被解析为止。

源数据格式

DataRow由以下格式的字节组成:

1
2
3
([EXCODE]...) [CODE] ([VLENGTH]) [VALUE...]
____________________ ____________ ___________
^^^^(Value Type)^^^^ ^^(length)^^ ^^(value)^^

注意:括号中的字节是有条件的,这意味着它们只出现在一些DataRow中,而不是出现在另一些DataRow中。详见以下描述。

数据路可以以零或更多[EXCODE](扩展代码)字节开头,这些字节是具有0x55值的字节。[EXCODE]字节数表示扩展代码级别.扩展代码级别反过来与[CODE]字节一起使用,以确定此DataRow包含什么类型的数据值。因此,分析器应该总是通过计数[EXCODE](0x55)字节数来开始解析DataRow,这些字节数似乎决定了DataRow[CODE]的扩展代码级别。

[CODE]字节与扩展代码级别一起指示DataRow中编码的数据值的类型。例如,在扩展代码级别0,0x04的[CODE]表示DataRow包含一个eSenseAttention值。有关定义[代码]含义的列表,请参阅下面的代码定义表。请注意,0x55的[EXCODE]字节永远不会用作[CODE](顺便说一句,0xAA的[SYNC]字节也永远不会用作[CODE])。

如果[CODE]字节在0x00和0x7F之间,则[Value…]被暗示为1字节长(称为单字节值)。在这种情况下,没有[V LENGTH]字节,因此单个[V ALUE]字节将立即出现在[CODE]字节之后。‘

但是,如果[CODE]大于0x7F,则[VLENGTH](“值长度”)字节紧跟在[CODE]字节之后,这是[Value…]中的字节数(称为多字节值)。这些较高的代码对于传输不能装入单个字节的值或值的数组是有用的。

以这种方式定义DataRow格式,以便任何正确实现的解析器在将来不会中断,如果新的代码表示任意长的DATA…值被添加(它们只是忽略未识别的代码,但在解析中不中断),代码的顺序在分组中被重新排列,或者如果一些代码并不总是在每个分组中传输。

下面分别给出了一个正确解析分组和数据流的过程,分别在分组有效负载中解析分组和数据流的步骤指南中给出。

编码定义表

单字节编码(Single-Byte CODEs)

Extended(Byte)
Code Level[Code][LENGTH]Data Value Meaning
00x02-POOR_SIGNAL Quality (0-255)
00x03-HEART_REATE (0-255) 1次/s EGO上
00x04-ATTENTION eSense (0 to 100)
00x05-MEDITATION eSense (0 to 100)
00x06-8BIT_RAW 波值(0-255)
00x07-RAW_MARKER部分开始(0)

(多字节编码)Multi-Byte CODEs

Extended(Byte)
Code Level[Code][LENGTH]Data Value Meaning
00x802RAW Wave Value: a single big-endian 16-bit two's-compliment signed value(high-order byte followed by low-order byte) (-32768 to 32767)
00x8132EEG_POWER: eight big-endian 4-byte IEEE 754 floating point values representing delta, theta, low-alpha,high-alpha, low-beta, high-beta,low-gamma, and mid-gamma EEG band power values
00x8324ASIC_EEG_POWER: eight big-endian 3-byte unsigned integer values representing delta, theta, low-alpha,high-alpha, low-beta, high-beta,low-gamma, and mid-gamma EEG band power values
00x862RRINTERVAL: two byte big-endian unsigned integer representing the milliseconds between two R-peaks
Any0x55-NEVER USED (reserved for [EXCODE])
Any0xAA-NEVER USED (reserved for [SYNC])

(上表中未列出的任何扩展代码级别/CODE组合尚未定义,但今后可随时添加)

有关每种数据值的含义的详细解释,请参阅关于ThinkGear数据值的一章。

每当ThinkGear模块启动时,它总是以标准配置启动,其中默认情况下,只有上面列出的一些数据值将被输出。 以启用或禁用的类型数据值由ThinkGear输出,请参阅高级章节ThinkGear命令字节。

示例包

以下是一个典型的数据包。除了[SYNC]、[PLENGTH]和[CHKSUM]字节之外,所有其他字节(字节[3]到[10])都是分组数据有效负载的一部分。请注意,有效负载中的DataRow不保证出现在每个分组中,也不保证出现在分组规范中的任何DataRow以任何特定顺序出现。

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、继续从流中读取字节,直到遇到[SYNC]字节(0xAA)。
2、读取下一个字节,并确保它也是[SYNC]字节

  • 如果不是[SYNC]字节, 回到第一步;
  • 否则,继续第3步

3、从流中读取下一个字节为[PLENGTH]。

  • 如果[PLENGTH]为170([SYNC]),则重复步骤3。
  • 如果[PLENGTH]大于170,则返回到步骤1(PLENGTH TOOLARGE)。
  • 否则继续执行步骤4

4、从流中读取[PAYLOAD…]的下一个[PLENGTH]字节,将它们保存到存储区域中(例如无符号char有效载荷[256]数组)。当读取每个字节时,通过增加校验和累加器(checksum+=byte)来计算它。

5、取校验和累加器的最低8位并倒置它们。以下是C代码:

1
2
checksum &= 0xFF;
checksum = ~checksum & 0xFF;

6、从流中读取下一个字节作为[CHKSUM]字节。

  • 如果[CHKSUM]与您计算的CHKSUM(CHKSUM失败)不匹配。
  • 否则,您现在可以将有效负载的内容解析为DataRow以获得数据值,如下所述。
  • 在任何一种情况下,返回到步骤1。

数据包有效负载中数据路的分步解析指南

重复以下步骤解析DataRow,直到考虑并解析了有效载荷数组中的所有字节([PLENGTH]字节):

1、解析并计数可能位于当前DataRow开头的[EXCODE](0x55)字节数。

2、解析当前DataRow的[CODE]字节。

3、如果[CODE]>=0x80,则将下一个字节解析为当前DataRow的[VLENGTH]字节。

4、解析并处理当前DataRow的[Value…]字节,基于DataRow的[EX-CODE]级别、[CODE]和[VLength](请参阅代码定义表)。

5、如果不是所有字节都已从payload[]数组中解析,请返回到步骤1,继续解析下一个DataRow。

数据包解析样本C代码

下面是一个程序的例子,在C中实现,它从流读取,并(正确)连续解析数据包。为两个部分搜索TODO这个词,这两个部分需要修改,以适合您的应用程序。

注意:为了简单起见,省略了标准库函数调用的错误检查和处理。一个真正的应用程序可能应该优雅地检测和处理所有错误。

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
#include <stdio.h>
#define SYNC 0xAA
#define EXCODE 0x55

int parsePayload( unsigned char *payload, unsigned char pLength ) {
unsigned char bytesParsed = 0;
unsigned char code;
unsigned char length;
unsigned char extendedCodeLevel;
int i;

/* Loop until all bytes are parsed from the payload[] array... */
while( bytesParsed < pLength ) {
/* Parse the extendedCodeLevel, code, and length */
extendedCodeLevel = 0;
while( payload[bytesParsed] == EXCODE ) {
extendedCodeLevel++;
bytesParsed++;
}
code = payload[bytesParsed++];
if( code & 0x80 ) length = payload[bytesParsed++];
else length = 1;
/* TODO: Based on the extendedCodeLevel, code, length,
* and the [CODE] Definitions Table, handle the next
* "length" bytes of data from the payload as
* appropriate for your application.
*/
printf( "EXCODE level: %d CODE: 0x%02X length: %d\n",
extendedCodeLevel, code, length );
printf( "Data value(s):" );
for( i=0; i<length; i++ ) {
printf( " %02X", payload[bytesParsed+i] & 0xFF );
}
printf( "\n" );
/* Increment the bytesParsed by the length of the Data Value */
bytesParsed += length;
}

return( 0 );
}

int main( int argc, char **argv )
{
int checksum;
unsigned char payload[256];
unsigned char pLength;
unsigned char c;
unsigned char i;
/* TODO: Initialize 'stream' here to read from a serial data
* stream, or whatever stream source is appropriate for your
* application. See documentation for "Serial I/O" for your
* platform for details.
*/
FILE *stream = 0;
stream = fopen( "COM4", "r" );
/* Loop forever, parsing one Packet per loop... */
while( 1 ) {
/* Synchronize on [SYNC] bytes */
fread( &c, 1, 1, stream );
if( c != SYNC ) continue;
fread( &c, 1, 1, stream );
if( c != SYNC ) continue;
/* Parse [PLENGTH] byte */
while( true ) {
fread( &pLength, 1, 1, stream );
if( pLength ~= 170 ) break;
}
if( pLength > 169 ) continue;
/* Collect [PAYLOAD...] bytes */
fread( payload, 1, pLength, stream );
/* Calculate [PAYLOAD...] checksum */
checksum = 0;
for( i=0; i<pLength; i++ ) checksum += payload[i];
checksum &= 0xFF;
checksum = ~checksum & 0xFF;
/* Parse [CKSUM] byte */
fread( &c, 1, 1, stream );
/* Verify [CKSUM] byte against calculated [PAYLOAD...] checksum */
if( c != checksum ) continue;
/* Since [CKSUM] is OK, parse the Data Payload */
parsePayload( payload, pLength );
}
return( 0 );
}

ThinkGearStreamParser C API

这个ThinkGear Stream ParserAPI是一个库,它实现了上面描述的解析过程,并将其抽象成两个简单的函数,这样程序员就不需要担心解析数据包和DataRow了。剩下的就是程序员从数据流中获取字节,将它们放入解析器中,然后定义它们的程序对Value[]的处理来自接收和解析的每个DataRow的字节。

源代码是作为Mind Set开发工具(MDT)(就是文章最开始下载的工具包)的一部分提供的,由.h头文件和.c源文件组成。它是在纯ANSI C中实现的,以最大限度地向所有平台(包括微处理器)可移植性。

使用API包括三个步骤:

1、定义一个数据处理程序(callback)函数,它处理(立刻)接收和解析数据值。

2、通过调用THINKGEAR_initParser()函数初始化ThinkGear Stream Parser结构。

3、当从数据流接收到每个字节时,程序将其传递给THINKGEAR_parseByte()函数。每当解析数据值时,此函数将自动调用1)中定义的数据处理程序函数。

4、下面的小节是ThinkGear Stream Parser.h头文件的摘录,该文件提供服务作为API文档。

常量

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
/* Parser types */
#define PARSER_TYPE_NULL 0x00
#define PARSER_TYPE_PACKETS 0x01 /* Stream bytes as ThinkGear Packets */
#define PARSER_TYPE_2BYTERAW 0x02 /* Stream bytes as 2-byte raw data */
/* Data CODE definitions */
#define PARSER_BATTERY_CODE 0x01
#define PARSER_POOR_SIGNAL_CODE 0x02
#define PARSER_ATTENTION_CODE 0x04
#define PARSER_MEDITATION_CODE 0x05
#define PARSER_RAW_CODE 0x80
THINKGEAR_initParser()
/**
* @param parser Pointer to a ThinkGearStreamParser object.
* @param parserType One of the PARSER_TYPE_* constants defined
* above: PARSER_TYPE_PACKETS or
* PARSER_TYPE_2BYTERAW.
* @param handleDataValueFunc A user-defined callback function that will
* be called whenever a data value is parsed
* from a Packet.
* @param customData A pointer to any arbitrary data that will
* also be passed to the handleDataValueFunc
* whenever a data value is parsed from a
* Packet.
*
* @return -1 if @c parser is NULL.
* @return -2 if @c parserType is invalid.
* @return 0 on success.
*/
int
THINKGEAR_initParser( ThinkGearStreamParser *parser, unsigned char parserType,
void (*handleDataValueFunc)(
unsigned char extendedCodeLevel,
unsigned char code,
unsigned char numBytes,
const unsigned char *value,
void *customData),
void *customData );
THINKGEAR_parseByte()
/**
* @param parser Pointer to an initialized ThinkGearDataParser object.
* @param byte The next byte of the data stream.
*
* @return -1 if @c parser is NULL.
* @return -2 if a complete Packet was received, but the checksum failed.
* @return 0 if the @c byte did not yet complete a Packet.
* @return 1 if a Packet was received and parsed successfully.
*
*/
int
THINKGEAR_parseByte( ThinkGearStreamParser *parser, unsigned char byte );

示例

下面是一个使用ThinkGear Stream Parser API的示例程序。它和这个例子非常相似上述程序,只需将接收到的数据值打印到stdout:

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
#include <stdio.h>
#include "ThinkGearStreamParser.h"
/**
* 1) Function which acts on the value[] bytes of each ThinkGear DataRow as it is received.
*/
void
handleDataValueFunc( unsigned char extendedCodeLevel,
unsigned char code,
unsigned char valueLength,
const unsigned char *value,
void *customData )
{
if( extendedCodeLevel == 0 ) {
switch( code ) {
/* [CODE]: ATTENTION eSense */
case( 0x04 ):
printf( "Attention Level: %d\n", value[0] & 0xFF );
break;
/* [CODE]: MEDITATION eSense */
case( 0x05 ):
printf( "Meditation Level: %d\n", value[0] & 0xFF );
break;
/* Other [CODE]s */
default:
printf( "EXCODE level: %d CODE: 0x%02X vLength: %d\n",
extendedCodeLevel, code, valueLength );
printf( "Data value(s):" );
for( i=0; i<valueLength; i++ ) printf( " %02X", value[i] & 0xFF );
printf( "\n" );
}
}
}
/**
* Program which reads ThinkGear Data Values from a COM port.
*/
int
main( int argc, char **argv ) {
/* 2) Initialize ThinkGear stream parser */
ThinkGearStreamParser parser;
THINKGEAR_initParser( &parser, PARSER_TYPE_PACKETS,handleDataValueFunc, NULL );
/* TODO: Initialize 'stream' here to read from a serial data
* stream, or whatever stream source is appropriate for your
* application. See documentation for "Serial I/O" for your
* platform for details.
*/
FILE *stream = fopen( "COM4", "r" );
/* 3) Stuff each byte from the stream into the parser. Every time
* a Data Value is received, handleDataValueFunc() is called.
*/
unsigned char streamByte;
while( 1 ) {
fread( &streamByte, 1, stream );
THINKGEAR_parseByte( &parser, streamByte );
}
}

注意事项:

  • handleDataValueFunc() 回调函数应该实现快速执行,以免阻塞从数据流读取的线程。一个更健壮(也更有用)的程序可能会剥离从数据流读取的线程并调用handleDataValueFunc(),并定义handleDataValueFunc()来简单地保存它接收到的数据值,而主线程实际上使用保存的值来显示屏幕、控制游戏等。线程不在本手册的范围内。
  • 用于读取的串行通信端口数据流的打开代码因操作系统和平台的不同而不同。通常,它非常类似于打开一个正常的文件进行读取。串行通信不在本手册的范围内,因此请查阅“串行I/O”的文档,以了解您的平台的详细信息。作为一种选择,你可以使用ThinkGear
  • 通信驱动程序(TGCD)API可以为您处理从某些平台上的串行I/O流打开和读取。该接口的使用在developer_tools_2.1_development_guide开发指南和TGCD API文档中描述。
  • 为了清晰起见,从上面的代码中省略了大多数错误处理。正确编写的程序应该检查函数返回的所有错误代码。有关函数参数和返回值的详细信息,请参阅ThinkGear Stream Parser.h头文件。

ThinkGear Command Bytes

启动时,ThinkGear模块/AISC总是以工厂编程的默认状态启动。例如,使用FWv1.7.13和更早的ThinkGear模块总是从9600波特开始,并输出只有电池,信号差,SIC_EEG,注意力和冥想值。此配置旨在对于大多数玩具、游戏和演示应用程序来说都是足够的。然而,在启动后,ThinkGear模块可以发送命令字节,以便更改其配置,例如切换到57.6k波特,或启用原始波值输出。THinkGear Command Bytes是一个高级的用于执行为ThinkGear硬件的一些定制的特性。

ThinkGear命令字节通过相同用于接收分组字节的UART接口发送到ThinkGear硬件。命令字节是一个具有特定位集的单字节(8位)值。这部分描述要设置哪些位以更改ThinkGear模块的配置。

请注意,当电源循环后,ThinkGear模块返回到以上所描述的默认设置。

还请注意,在应用程序向ThinkGear发送任何命令字节之前,它应该首先确保它已经从ThinkGear读取了至少一个完整的、有效的数据包,以确保它发送命令字节在适当的波特率。发送错误的命令字节可能会导致ThinkGear是不可操作的,直到它是电源循环(重置回默认配置)。

Command Bytes Syntax

命令字节由8位组成,每个位要么设置,要么未设置。 最低的(最不重要)四位用于控制模式,如9600对57.6k波特模式,注意输出启用或禁用模式等。上面(最重要的)四位,称为“命令页”,定义较低的四位的含义。

例如,0x0E的命令字节具有00001110的位模式。 高(最显著)四位是0000,它指的是命令页零。查看下表中的命令第0页(对于1.6固件),我们看到较低的4位1110用于控制设置分别用于波特率、原始波输出、冥想输出和注意力输出。因为那个波特率位为1,模块的波特率将设置为57.6k模式。因为原始波输出冥想输出位是每个1,这些类型的输出将被启用。 因为那个注意输出位为0,注意输出变为禁用。因此,将0x0E的字节发送到ThinkGear模块指示它在57.6k波特模式下工作,输出原始值和冥想值在数据包中,但没有注意值。

请注意,命令页的顺序在版本1.6和模块固件的1.7版本之间发生了很大的变化。还请注意Think Gear ASIC(如Mind Wave和Mind Wave Mobile)只识别第0页上的4个命令字节为1.7固件;所有其他命令字节可以放置ThinkGear ASIC进入一个不可操作的状态,直到它是电源循环。请参阅您的ThinkGear硬件文件,以及下面的适当表格一起来确定哪一个命令字节对硬件有效。

Firmware 1.6 Command Byte Table

注意:ThinkGear ASIC只识别命令字节从第0页下面。任何其他命令字节可以把它置于不可操作的状态,直到它被动力循环。

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
Page 0 (0000____) (0x0_): STANDARD/ASIC CONFIG COMMANDS* **
00000000 (0x00): 9600 baud, normal output mode
00000001 (0x01): 1200 baud, normal output mode
00000010 (0x02): 57.6k baud, normal+raw output mode
00000011 (0x03): 57.6k baud, FFT output mode
Page 1 (0001____) (0x1_): RAW WAVE OUTPUT
bit[0] (____0001): Set/unset to enable/disable raw wave output
bit[1] (____0010): Set/unset to use 10-bit/8-bit raw wave output
bit[2] (____0100): Set/unset to enable/disable raw marker output
bit[3] (____1000): Ignored
Page 2 (0010____) (0x2_): MEASUREMENTS OUTPUTS
bit[0] (____0001): Set/unset to enable/disable poor quality output
bit[1] (____0010): Set/unset to enable/disable EEG powers (int) output
bit[2] (____0100): Set/unset to enable/disable EEG powers
(legacy/floats) output
bit[3] (____1000): Set/unset to enable/disable battery output***
Page 3 (0011____) (0x3_): ESENSE OUTPUTS
bit[0] (____0001): Set/unset to enable/disable attention output
bit[1] (____0010): Set/unset to enable/disable meditation output
bit[2] (____0100): Ignored
bit[3] (____1000): Ignored
Page 6 (0110____) (0x6_): BAUD RATE SELECTION* **
01100000 (0x60): No change
01100001 (0x61): 1200 baud
01100010 (0x62): 9600 baud
01100011 (0x63): 57.6k baud

*:注意,第0页和第6页与大多数命令页有点不同。而大多数页面使用每个4位作为单独设置的启用/禁用开关命令,第0页和第6页使用整个命令值作为单个命令。

**:在发送此Page byte后,应用程序本身必须更改自己以开始通信新请求的波特率,然后在新请求的波特率下等待一个完整的、有效的波特率在尝试向ThinkGear发送任何其他命令字节之前,要从ThinkGear接收数据包想想齿轮。在执行此检查之前发送另一个命令字节可能会放置ThinkGear模块进入不确定(和不可操作)状态,直到它是功率循环。

***:一些ThinkGear模型上没有电池级采样。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!