购买了 维特智能九轴加速度计、陀螺仪模块 ,型号WT901 。
红色圈中的就是传感器模块,约为1角硬币大小。图片上面部分是厂家原配的USB转TTL的模块,连线实在是太短了,用起来一点都不方便。我换成了自已以前用过的转接模块,连线要长得多,差不多是原配线的3倍长。
下图是传感器模块放大后的照片。只需要接 GND,VCC,RX,TX四根线就可以和串口通信了。
在厂家提供的的示例程序中,只找到了VC++ 与 C#的示例程序,没有找到我想要的Python示例程序。
好在厂家提供的说明书中对模块所使用的串口协议做了说明。参照厂家的说明书与C#示例程序,本人用Python实现了与C#功能一样的示例程序。
附厂家提供的C#示例程序的运行界面:
观察一下,可以注意到,传感器读数会有微小的动态变化。此外经过检查,厂家提供的C#程序在显示GPS地速,航向数据时,显示位置有些错位了。在用Python采集数据时,对这个问题也做了处理。当然,由于实际上未接GPS,上面显示的GPS数据都是0,没有实际意义。
UARTTest.py
# -*- coding:utf-8 -*-
# 维特的WT901传感器 数据读取实例程序,
# 串口协议规定11个字节一个数据包,包头两个字节,包尾一个字节的检验码,中间8个字节存放回传的实际数据
# 从现有的资料来看,JY901的串口协议与WT901的串口协议应该是一样的
import time
import struct
import serial
import binascii
import threading
import tkinter as tk
from datetime import datetime
#配置类
class Config:
# 端口号
serialPort = 'COM8'
# 波特率
baudRate = 115200
#在传感器数据中,最小的包长度是11个字节
minPackageLen = 11
#传感器数据读取类
class SensorReader:
def __init__(self):
self.port = serial.Serial(Config.serialPort, Config.baudRate)
self.port.close()
if not self.port.isOpen():
self.port.open()
self.receiveBuffer = bytearray()
self.working = False
#打开
def open(self):
if not self.port.isOpen():
self.port.open()
#关闭
def close(self):
self.port.close()
#发送数据
def send(self,data):
self.port.write(data)
#接收数据
def receive(self):
while self.working:
#休眠一个微小的时间,可以避免无谓的CPU占用,在windows上实验时直接从12%下降到接近于0%
time.sleep(0.001)
count = self.port.inWaiting()
if count > 0:
s = self.port.read(count)
self.receiveBuffer+= s
#开始工作
def start(self):
#开始数据读取线程
t = threading.Thread(target=self.receive)
#将当前线程设为子线程t的守护线程,这样一来,当前线程结束时会强制子线程结束
t.setDaemon(True)
self.working = True
t.start()
#停止工作
def stop(self):
self.working = False
#数据解析类
class DataParser:
def __init__(self,sensorReader,myUI):
self.r = sensorReader
self.u = myUI
self.working = False
self.TimeStart = datetime.now()
self.iniVariable()
#流逝的毫秒数,返回浮点数
def elapseMilliSeconds(self):
now = datetime.now()
seconds = time.mktime(now.timetuple()) - time.mktime(self.TimeStart.timetuple())
#微秒数的差值
microSeconds = now.microsecond - self.TimeStart.microsecond
return seconds* 1000+ microSeconds/1000.0
#流逝的秒数,返回浮点数
def elapseSeconds(self):
return self.elapseMilliSeconds()/1000
#在缓冲数据中找到第一个包的起始位置
def findFirstPackage(self,buffer):
i = 0
while True:
if buffer[i] == 0x55 and (buffer[i+1] & 0x50) == 0x50 :
return i
if i+2 >= len(buffer):
return -1
i+=1
#处理数据
def handle(self):
#处理接收到的数据
while self.working:
text = ''
#显示当前收到的数据
dataLen = len(self.r.receiveBuffer)
if dataLen >= Config.minPackageLen:
#去掉第1个包头前的数据
headerPos = self.findFirstPackage(self.r.receiveBuffer)
text += '包头偏移:'+str(headerPos) +'\r\n'
if headerPos >= 0 :
if headerPos > 0:
self.r.receiveBuffer[0:headerPos]=b''
#取 Config.minPackageLen 整数倍长度的数据
if dataLen-headerPos >= Config.minPackageLen :
packageCount = int((dataLen-headerPos)/Config.minPackageLen)
if packageCount > 0:
cutLen = packageCount*Config.minPackageLen
text += '当前收到包数:'+str(packageCount) +'\r\n'
temp = self.r.receiveBuffer[0:cutLen]
#按16进制字符串的形式显示收到的内容
hexStr = str(binascii.b2a_hex(temp))
text += '16进制原始据:'+hexStr[2:len(hexStr)-1]
self.r.receiveBuffer[0:cutLen]=b''
#在窗口的文本框中显示数据
self.u.showData(text)
#解析数据,逐个数据包进行解析
for i in range(packageCount):
beginIdx =int(i*Config.minPackageLen)
endIdx =int(i*Config.minPackageLen+Config.minPackageLen)
byteTemp = temp[beginIdx:endIdx]
#校验和通过了的数据包才进行解析
if self.sbSumCheck(byteTemp):
self.decodeData(byteTemp)
time.sleep(0.005)
#初始化解析丰关的变量
def iniVariable(self):
self.ChipTime = [0,0,0,0,0,0,0]
self.a = [0,0,0,0]
self.w = [0,0,0,0]
self.Angle = [0,0,0,0]
self.h = [0,0,0,0]
self.Port = [0,0,0,0]
self.LastTime = [0,0,0,0,0,0,0,0,0,0]
self.Temperature = 0
self.Pressure = 0
self.Altitude = 0
self.GroundVelocity = 0
self.GPSYaw = 0
self.GPSHeight = 0
self.Longitude = 0
self.Latitude = 0
#解码包中的数据
def decodeData(self,byteTemp):
#记录当前的相对时间
TimeElapse = self.elapseSeconds();
#将8个字节的数据解析成4个短整型
Data = list(struct.unpack("hhhh", byteTemp[2:10]) )
if byteTemp[1] == 0x50:
self.ChipTime[0] = (2000 + byteTemp[2])
self.ChipTime[1] = byteTemp[3]
self.ChipTime[2] = byteTemp[4]
self.ChipTime[3] = byteTemp[5]
self.ChipTime[4] = byteTemp[6]
self.ChipTime[5] = byteTemp[7]
self.ChipTime[6] = struct.unpack("h", byteTemp[8:10])[0]
if byteTemp[1] == 0x51:
self.Temperature = Data[3] / 100.0
Data[0] = Data[0] / 32768.0 * 16
Data[1] = Data[1] / 32768.0 * 16
Data[2] = Data[2] / 32768.0 * 16
self.a[0] = Data[0]
self.a[1] = Data[1]
self.a[2] = Data[2]
self.a[3] = Data[3]
if ((TimeElapse - self.LastTime[1]) < 0.1):
return
self.LastTime[1] = TimeElapse
if byteTemp[1] == 0x52:
self.Temperature = Data[3] / 100.0
Data[0] = Data[0] / 32768.0 * 2000
Data[1] = Data[1] / 32768.0 * 2000
Data[2] = Data[2] / 32768.0 * 2000
self.w[0] = Data[0]
self.w[1] = Data[1]
self.w[2] = Data[2]
self.w[3] = Data[3]
if ((TimeElapse - self.LastTime[2]) < 0.1):
return
self.LastTime[2] = TimeElapse
if byteTemp[1] == 0x53:
self.Temperature = Data[3] / 100.0
Data[0] = Data[0] / 32768.0 * 180
Data[1] = Data[1] / 32768.0 * 180
Data[2] = Data[2] / 32768.0 * 180
self.Angle[0] = Data[0]
self.Angle[1] = Data[1]
self.Angle[2] = Data[2]
self.Angle[3] = Data[3]
if ((TimeElapse - self.LastTime[3]) < 0.1):
return
self.LastTime[3] = TimeElapse
if byteTemp[1] == 0x54:
self.Temperature = Data[3] / 100.0
self.h[0] = Data[0]
self.h[1] = Data[1]
self.h[2] = Data[2]
self.h[3] = Data[3]
if ((TimeElapse - self.LastTime[4]) < 0.1):
return
self.LastTime[4] = TimeElapse
if byteTemp[1] == 0x55:
self.Port[0] = Data[0]
self.Port[1] = Data[1]
self.Port[2] = Data[2]
self.Port[3] = Data[3]
if byteTemp[1] == 0x56:
self.Pressure = struct.unpack("i", byteTemp[2:6])[0]
self.Altitude = struct.unpack("i", byteTemp[6:10])[0] / 100.0
if byteTemp[1] == 0x57:
self.Longitude = struct.unpack("i", byteTemp[2:6])[0]
self.Latitude = struct.unpack("i", byteTemp[6:10])[0]
if byteTemp[1] == 0x58:
self.GPSHeight = struct.unpack("h", byteTemp[2:4])[0] / 10.0
self.GPSYaw = struct.unpack("h", byteTemp[4:6])[0] / 10.0
self.GroundVelocity = struct.unpack("h", byteTemp[6:8])[0] / 1e3
#内嵌的输出函数,可以直接引用方法内部的各种变量,比如 TimeElapse 等
def output():
text ='系统时间:'+datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\r\n"
text+='片上时间:'+str(self.ChipTime[0]) + "-" + str(self.ChipTime[1]) + "-" + str(self.ChipTime[2]) + "\r\n "
text+= str(self.ChipTime[3]) + ":" + str(self.ChipTime[4]) + ":" + str(self.ChipTime[5]) + "." + str(self.ChipTime[6]) + "\r\n"
text+='相对时间:'+"%.3f" %TimeElapse + "\r\n\r\n"
text+='x轴加速度:'+"%.2f g" %self.a[0] + "\r\n"
text+='y轴加速度:'+"%.2f g" %self.a[1] + "\r\n"
text+='z轴加速度:'+"%.2f g" %self.a[2] + "\r\n\r\n"
text+='x轴角速度:'+"%.2f °/s" %self.w[0] + "\r\n"
text+='y轴角速度:'+"%.2f °/s" %self.w[1] + "\r\n"
text+='z轴角速度:'+"%.2f °/s" %self.w[2] + "\r\n\r\n"
text+='x轴角度: '+"%.2f °" %self.Angle[0] + "\r\n"
text+='y轴角度: '+"%.2f °" %self.Angle[1] + "\r\n"
text+='z轴角度: '+"%.2f °" %self.Angle[2] + "\r\n\r\n"
text+='x轴磁场: '+"%.0f mG" %self.h[0] + "\r\n"
text+='y轴磁场: '+"%.0f mG" %self.h[1] + "\r\n"
text+='z轴磁场: '+"%.0f mG" %self.h[2] + "\r\n\r\n"
text+='温 度:'+"%.2f ℃" %self.Temperature + "\r\n"
text+='气 压:'+"%.0f Pa" %self.Pressure + "\r\n"
text+='高 度:'+"%.2f m" %self.Altitude + "\r\n\r\n"
text+='经 度:'+"%.0f°" %(self.Longitude /10000000) + "%.5f\'" %((self.Longitude % 10000000)/1e5) +"\r\n"
text+='纬 度:'+"%.0f°" %(self.Latitude / 10000000) + "%.5f\'" %((self.Latitude % 10000000)/1e5) +"\r\n"
text+='GPS高度:'+"%.1f m" %self.GPSHeight + "\r\n"
text+='GPS航向:'+"%.1f °" %self.GPSYaw + "\r\n"
text+='GPS地速:'+"%.3f km/h" %self.GroundVelocity + "\r\n\r\n"
self.u.showText(text)
#输出解析得到的内容
output()
#检查校验和
def sbSumCheck(self,byteTemp):
if (((byteTemp[0]+byteTemp[1]+byteTemp[2]+byteTemp[3]+byteTemp[4]+byteTemp[5]+byteTemp[6]+byteTemp[7]+byteTemp[8]+byteTemp[9])&0xff)==byteTemp[10]) :
#print('sum check ok!')
return True
else:
print('sum check false!')
return False
#开始工作
def start(self):
#开启数据解析线程
t = threading.Thread(target=self.handle)
#将当前线程设为子线程t的守护线程,这样一来,当前线程结束时会强制子线程结束
t.setDaemon(True)
self.working = True
t.start()
#停止工作
def stop(self):
self.working = False
#图形界面类
class MyUI:
def __init__(self):
#创建显示传感器数据的窗口
self.window = tk.Tk()
self.window.title('WT901(JY901)传感器')
self.window.geometry('760x840')
self.frameTop = tk.Frame(self.window)
self.frameTop.config( height=100, width=780)
self.frameTop.place(x=10, y=0)
self.frameBottom = tk.Frame(self.window)
self.frameBottom.config( height=720, width=780)
self.frameBottom.place(x=10, y=110)
#创建显示数据的文本框
self.dataBox = tk.Text(self.frameTop, bg='white', font=('Arial', 12))
self.dataBox.place(x=4, y=4)
self.textBox = tk.Text(self.frameBottom, height=700,bg='white', font=('Arial', 12))
self.textBox.place(x=4, y=4)
#开启UI
def start(self):
#开启窗口主循环
self.window.mainloop()
#显示数据
def showData(self,data):
self.dataBox.delete(0.0,tk.END)
self.dataBox.insert(tk.INSERT, data)
#显示文本
def showText(self,text):
self.textBox.delete(0.0,tk.END)
self.textBox.insert(tk.INSERT, text)
#主线程
if __name__ == '__main__':
#创建串口操作对象
r= SensorReader()
#开始读取数据
r.start()
#创建UI对象
u = MyUI()
#创建数据解析对象
p= DataParser(r,u)
#开始解析数据
p.start()
#启动UI
u.start()
有了上述的Python示例程序,稍加修改,就可以用于加速度计、陀螺仪的二次开发。
在实际使用时,请修改python文件开头的参数配置,主要是串口号与波特率要与你实际使用模块的设置要一致。
可以点这里直接下载Python源码
提取码:swa2