C语言的也有,但是是绘图不方便,就先拿Python写了,我直接接了一个串口来解析的数据,第一部分是电路焊接。
板子的接口定义
这个就是串口的全套
在这里可以更改波特率
焊盘
这个板子上面焊接了一块蓝牙的穿透模块:
左上的位置和下面的位置都是联通的
TTL电平是传输级逻辑(Transistor-Transistor Logic)电路的信号电平。TTL电路使用双极性晶体管实现逻辑功能,所以TTL电平也称为双极性逻辑电平。TTL电平有两种,高电平(HIGH)和低电平(LOW):-
高电平:大于2.4V,通常是5V。这代表逻辑"1"。
低电平:小于0.8V,通常是0V。这代表逻辑"0"。
在TTL电路中,电平在2.4V到0.8V之间是不确定的,属于无效区域。所以为了稳定和可靠地区分高低电平,都会选用远离无效区域的电压,通常是0V和5V。除了高低电平的幅值之外,TTL电平还有以下一些要点:
TTL电平的变化速率不能太快,通常小于1V/ns,否则会产生噪声干扰TTL电
TTL输入电平的阈值约为1.4V,超过该阈值会被识别为高电平,低于该阈值会被识别为低电平。
TTL输出电平会受温度的影响。通常会设计足够大的边缘区域(2.4V-0.8V)以适应温度变化的影响。
TTL电平不够稳定,输出电平可能会发生assaults,导致接收端误判电平。常用抑制assaults的方法是上拉或下拉电阻。
由于TTL电路功耗较大,输出会有一定的驱动能力,一般可以驱动10个TTL输入。
如果需要驱动更多输入,可以用OpenCollector输出来提高驱动能力。所以TTL电平是典型的双极性逻辑电平,使用0V和5V代表低电平和高电平。
脑电采集+TGAM脑电模块
我不记得上面的文章里面有没有写具体的解析协议,Python也是一样的就是来解析串口的协议。
一开始的程序可以写成这样
1. EEGThread:这是读取脑电波设备数据的线程。会通过串口读取原始数据,解析并存储在data, data2和data3列表中。这些数据分别代表脑电波值,放松值和专注值。
这个就是一个对现在来处理程序的前置情况的处理
2.ShowThread:这是显示数据的线程。它会创建一个PyQtGrah窗口,包含两个plot。第一个plot显示脑电波值,第二个plot同时显示放松值(绿色)和专注值(蓝色)。该线程会不断从data, data2和data3列表中读取最新数据,更新plot的显示。
3.程序入口:这里会启动上述两个线程,并调用QtGui.QApplication.instance().exec_()进入Qt事件循环。
4.checkEeg():这是一个帮助方法,用于检查脑电波是否异常。通过检测old_data和delta_data列表中超出阈值的数据数量,判断是否异常。
5. serial和threading模块用于串口通信和多线程。
6. pyqtgraph模块用于完成数据的可视化显示。
这个程序的工作流程如下:
1. EEGThread线程启动,开始读取串口数据。
2. 将解析后的脑电波,放松值和专注值数据 append 到 data, data2 和 data3 列表。
3. ShowThread线程启动,创建PyQtGraph窗口和两个plot。
4. ShowThread线程定期从上述3个列表读取最新数据,更新plot的显示。
5. 主程序进入Qt事件循环,ShowThread线程能定期更新显示。
6. EEGThread线程持续读取串口数据,不断更新列表内容。这样,通过两个线程协同工作,实现了从脑电波设备获取数据并实时显示的功能。PyQtGraph提供了比较简洁的API来完成数据可视化,相比matplotlib更适合这个实时显示的场景。
这个方法checkList的参数是:
- list: 要检查的列表
- num: 阈值它的功能是:通过遍历list中的所有值,统计大于num的元素的数量,并返回这个数量。
举例:
python a = [1, 3, 2, 5, 4] checkList(a, 3) # 返回2,因为a中有两个值大于3 b = [1, 2, 1, 2] checkList(b, 3) # 返回0
基本的实现代码如下:
python def checkList(self, list, num): count = 0 for i in list: if i > num: count += 1 return count
它定义了一个count变量,遍历list中的每个元素i,如果i大于num,则count加1。遍历完成后返回count的值,这个值就是大于num的元素数量。
这个方法的作用是提供一个列表值异常判断的手段。通过设置一个阈值num,可以轻松统计列表中异常大的元素数量,从而判断该列表的值是否异常。
在脑电波数据分析中,可以设置某个通道数据或差分数据的阈值,通过这个方法判断脑电波数据是否出现异常的突增,这在脑电波监测中比较重要。
这个方法checkEeg用于判断脑电波数据是否异常。
它的参数是:
- old_data: 原始脑电波数据的历史记录,包含多个数据列表
- delta_data: 差分脑电波数据的历史记录,
包含多个数据它的功能是:
通过checkList方法,统计old_data中的每个数据列表中大于200的值的数量。如果有3个数据列表的大于200的值的数量大于5,则old_num加1。
通过checkList方法,统计delta_data中的值大于50000的值的数量,赋值给delta_num。
如果old_num大于3,并且delta_num大于4,则返回True,说明脑电波数据异常。否则返回False,数据正常。
这实现了通过检测原始数据和差分数据的异常值来判断脑电波数据是否异常的目的。异常数据可能意味着受试者的脑电波出现突变,需要注意。示例:
python old_data = [[1, 2, 3, 210, 5], [3, 4, 5, 220, 6], [100, 2, 3, 4, 5]] delta_data = [1, 2, 3, 60000, 4, 5] checkEeg(old_data, delta_data) # 返回True,old_num=2,delta_num=1,所以数据异常
这个方法为脑电波数据的实时监测提供了重要的支持。通过定期调用这个方法,并检查其返回值,可以实时判断受试者的脑电波数据是否出现异常,有助于及时发现问题。
代码中还可以继续优化:
可以这样的来设计脑电的数据
read这个函数是最重要的
那么就是可以变成重要的使用模式
这样通过将数据读取和解析的过程封装在EEGData类中,外部程序只需要关注从该类获取数据并进行显示或其它处理,代码会更清晰简洁。这个封装也使得EEGData类具有更高的复用性,如果有其它需要读取和解析同样串口数据的场景,可以直接复用这个类,而不是重新编写读取和解析的过程。
可视化的代码再第一个版本的时候写的就能显示一个通道的
我想要更多的通道,那么可以这样设计:
这样通过参数就可以让我弹性的获得脑电可视化的数据
这个类EEGPlot的功能是:
1. 根据传入的通道名称ch_names,初始化一个图形窗口和多个绘图区域,每个区域对应显示一路脑电波数据。
2. show_data方法用于刷新所有绘图区的显示,它接收所有的通道数据,并设置给相应的曲线来更新显示。
3. start方法启动一个定时器timer来周期调用show_data方法,实现数据的动态显示。
4. 外部通过采集好的多通道数据调用show_data方法,EEGPlot内部会自动将数据映射到对应的绘图区显示。
这样,通过这个封装,可以很方便的支持任意数量的通道,并且内部自动处理数据如何映射到对应通道的显示,外部只需要将整理好的最新数据传入即可,简化了多通道数据处理与显示的难度。
在这段代码中,t = serial.Serial(self.com, self.bps) 这一行打开了串口并初始化了Serial对象t。之后,代码会进入一个死循环,持续的从串口读取数据。主要的读取流程如下:
1. 先读取3个字节的数据b,用于检测设备是否连上。如果b符合要求(b[0] == b[1] == 170 and b[2] == 4),则认为已经连接上设备。
成功
2. 然后再读取5个字节的数据a。如果a符合要求(a[0] == 170 and a[1] == 170 and a[2] == 4 and a[3] == 128 and a[4] == 2),则继续读取。
3. 读取8个字节的数据a,用于获取实际的脑电波数据。
4. 从a中解析出高8位high和低8位low,构成16位的原始数据rawdata。rawdata会存储在self.vaul列表中。
三和四的代码太早了,还有一段串口重试。
1. 首先读取8个字节的数据a。
2. 计算a中的第6和第7字节(数据部分)的校验和sum。
3. 判断a的前3个字节是否是170,170,32。如果是,设置y=1,否则y=0。
4. 判断a的前5个字节是否是170,170,4,128,2。如果是,设置p=1,否则p=0。
5. 如果sum校验失败,并且y!=1和p!=1,则继续读取3个字节的数据b。
6. 从b中解析出c,d,e三个字节,并循环读取直到c=170,d=170和e=4。
7.如果循环出的c,d,e符合要求,再读取5个字节的数据g。如果g符合要求(g[0]=128和g[1]=2),则再次读取8个字节的数据a。
8. 这样重复3-7步,直到读取一组校验和sum正确的数据a为止。这个过程是数据读取的重试机制。由于串口通信可能存在噪声或帧错误,导致读取的数据校验失败。这段代码实现了这样的重试机制:如果读取的数据a校验失败,它不会直接丢弃这组数据。
而是继续读取,判断下一组数据b是否为起始帧(170,170,4),如果是则继续判断g是否为头两字节(128,2),如果仍然符合则重新读取一组完整的数据a。这样通过在校验失败后继续“捡漏”,增加了数据正确读取的几率。有效避免由于偶尔的通信错误导致丢失有效数据的问题。这在构建稳定性高的数据读取机制时是很有用的方式。尤其是在通信环境较差的情况下,这段“重试”逻辑可以显著提高数据的正确采集率。
上面的代码不好看,让我来重构一下:
这样写
这样用
这个DataRetry类实现了数据读取的重试逻辑,其功能是:
1. read方法读取8字节数据a,并校验数据校验和。如果失败,继续读取以判断是否为起始帧和包头。如果通过,则重新读取8字节数据a。
2. 通过这种方式,内部实现了在校验失败后继续读取从而重新获取完整数据的重试机制。
3. 使用简单的try/except,外部程序可以轻松调用retry.read(),不需要关心内部的重试细节。读取失败由retry内部捕获并重试,成功则返回数据。
4. 通过这个类,外部可以像读取正常数据一样简单调用,但相比直接读取,会显著提高数据正确读取的几率,增强程序的健壮性。这个封装使复杂的重试读取逻辑和外部数据读取接口解耦, outwardly具有简单读取的表象, inwardly却具备重试的能力,体现了很好的封装思想。
这里才是接上面的代码继续
5. 如果校验和sum校验失败,会重新读取数据,直到获取一组正确的数据。
6. 如果读取到的a数据第1,2字节为170,第3字节为32,则认为这是28字节的数据帧c。从c中可以解析出更多的信息,比如放松值和专注值,存储在data2和data3列表。
7. 每10组数据,会对old_data和delta_data列表中的数据进行检查,看是否异常。
8. 最后会清空self.vaul列表,准备读取下一组数据。这样,通过持续循环读取串口数据,并解析存储在不同列表中,实现了对脑电波原始数据,放松值和专注值的采集。这些数据会在其他线程ShowThread中读取和显示。
这里已经重构过了
这段代码的功能是:
1. 读取8字节的数据a,判断a的前5字节是否是170, 170, 4, 128, 2。如果是,继续解析a。
2. 从a中取出第5和第6字节(high和low)。
3. 计算high<<8 | low得到原始数据rawdata。如果rawdata大于32768,减去65536。
4.计算a的校验和sum,如果等于a的第8字节,则将rawdata添加到列表self.vaul。
5. 如果校验失败,进入重试机制(省略)。如果读取失败,跳出。
6. 判断a的前3字节是否是170, 170, 32。如果是,继续读取28字节的数据c。
7. 从c中解析出delta = c[7]<<16 | c[8]<<8 | c[9]。8. 显示delta。
这个过程实现了:
1. 对读取的8字节数据a进行校验,如果通过则解析出原始脑电波采样数据rawdata并添加到列表。
2. 在校验失败的情况下,进行重试读取以尽量不丢失有效数据。
3. 如果a的数据标识为170,170,32,则继续读取28字节数据c,并从中解析出放松值或专注值delta的信息。
4. 显示解析出的delta值。这个过程对应了从串口读取一帧完整的脑电波数据,解析出原始采样值rawdata,放松/专注值delta,并进行必要的校验和重试机制来提高数据正确率。
external调用此过程,即可从串口解析和获取脑电波的采样数据、放松值与专注值,并判断采集是否正常进行。通过定期调用,可实现对整个采集过程的监控。
这个类EEGData的功能是:
1. read方法读取8字节和28字节的数据,并进行校验与解析。
2. 如果通过校验,将解析出的原始数据添加到data列表,放松/专注值添加到relax/focus列表。
3. 在校验失败时,进行数据重试读取。读取错误时,打印错误信息。
4. 提供data,relax和focus三个列表,分别存储原始数据,放松值和专注值信息。5. get_sum方法用于计算校验和。
ser = serial.Serial('COM11', 57600) # 打开串口 eeg = EEGData(ser) # 创建EEGData对象 while True: eeg.read() # 读取并解析一组数据 # 使用eeg.data, eeg.relax和eeg.focus访问解析出的数据 ...
使用代码
1. EEGData类实现数据解析和采集,提供eeg.data, eeg.relax和eeg.focus三个列表存取解析后的数据。
2.ShowThread类实现一个显示线程,从eeg对象中获取最新数据并实时显示。3. 主程序从串口read()读取数据,并通过eeg对象解析。
4.show_thread启动一个定时器周期调用show_data方法来显示最新数据。
5. 这样通过 ShowThread 的实时显示和 EEGData的数据解析,实现了对整个脑电波采集过程的监视与反馈。
这个程序通过将数据采集、解析和显示过程解耦到不同线程中的不同对象,使得整体逻辑清晰且专注。EEGData专注于数据解析,ShowThread专注于数据显示,主程序只关注数据读取本身。这体现了较好的逻辑划分和职责分配。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !