项目介绍:
这个项目使用树莓派5做为主控板,使用TMF8821模块做为探测设备,完成了任务一 (角度测算)和任务二(基础手势识别)。
设计思路:
要实现本项目所需要的功能,要完成以下的工作:
- 了解TMF8821的功能
- 了解TMF8821的用法
- 在树莓派5上,与TMF8821模块通讯
- 通过从TMF8821获取的数据,进行计算
- 完成角度测算和手势识别
硬件介绍:
要实现本项目的功能,用到了下面的两款设备:
分别为:
- 树莓派5:左边的设备,树莓派5 4G版本,加装了SSD和风扇
- 硬禾TMF8821模块:右边的设备
实现步骤:
1. 了解TMF8821模块
通过活动页面https://www.eetree.cn/activity/115,可以获取到TMF8821模块的各项资料。
其中最主要的直播讲解,以及1.TMF882X 传感器数据手册_TMF882X_DS000693_8-00.pdf。
从其中,可以了解到TMF882X的用途,以及具体的技术细节,使用方式。
2. 使用TMF8821
通过官方提供的各种不同环境的代码,结合数据手册,能够详细了解到TMF8821的具体用法。
首先要和TMF8821通信,需要使用I2C接口,通过I2C接口控制TMF8821完成各种设定,以及进行测算返回数据。
基本的使用过程如下:
- 打开I2C接口,检测设备是否正常连接
- 获取app_id,可以用于识别实在启动阶段,还是已经加载firmware数据
- 加载firmware
- 设置spad_map_id,调用指定的配置
- 进行校准
- 启动测量
- 获取测量数据
- 通过计算数据,完成实际的任务
其中,app_id为0x03表示测量程序运行,也就是已经加载了firmware,0x80表示bootloader运行,需要加载firmware。
而spad_map_id用于加载预定的配置,从手册可以得知预定义的配置:
从手册可以得知,TMF8821支持3x3、4x4、3x6等多种配置。
另外,在校准的时候,需要先加上一个透明盖板,且上方40cm内没有物体遮挡,否则校准失败。
我放了一个打开的透明盒子上去进行校准:
校准完成后,拿走即可。
3. 树莓派5连接TMF8821模块
在树莓派5上,使用的是标准40Pin接口:
其中,默认的I2C1接口为GPIO02/SDA、GPIO03/SCL,可以用于与TMF8821模块对接:
要在树莓派5上启用I2C1,可以使用 sudo raspi-config 命令进行配置:
配置完成后,使用 sudo vim /boot/firmware/config.txt 配置I2C连接速度:
配置完成后,重启树莓派生效。
正确连接,并重启设备后,使用 sudo i2cdetect -y 1,可以用于检测是否连接好了:
其中,0x41即为TMF8821的I2C地址;如果没有出现,则说明链接不正确。
在树莓派5上,使用SMBUS2来完成I2C通讯,具体库为:https://pypi.org/project/smbus2/
参考下面的代码,可以从TMF8821的寄存器读取数据:
具体的寄存器说明,可以参考数据手册:
上面代码中读取的0x00,对应的就是APPID,具体功能如下:
从官方提供的文档、代码等,可以了解寄存器数据的具体用途和使用方法。
4. 任务一:角度测算
完成该任务,主逻辑部分代码如下:
from smbus2 import SMBus
from tmf8821 import TMF8821
import time
import sys
import numpy as np
import math
from config_calibrate import factory_calibrate
# 计算角度
def calculate_angle(depth_data):
mean_depth = np.mean(depth_data)
depth_deviation = depth_data - mean_depth
max_deviation = np.max(depth_deviation)
min_deviation = np.min(depth_deviation)
theta_rad = math.atan((max_deviation - min_deviation) / mean_depth)
theta_deg = math.degrees(theta_rad)
return theta_deg
with SMBus(1) as bus:
device = TMF8821(bus=bus)
print("app_id:", hex(device.app_id))
print("mode:", hex(device.mode))
print("enable->")
device.enable()
print("mode:", hex(device.mode))
spad_map_id = 1
print("spad_map->", spad_map_id)
device.spad_map = spad_map_id
time.sleep(0.5)
print("spad_map<-", device.spad_map)
print("calibrate->")
if False and spad_map_id in factory_calibrate:
device.write_calibration(factory_calibrate[spad_map_id])
time.sleep(0.1)
else:
times = 10
while times>0:
data = device.calibrate()
if device.calibration_ok:
# 校准成功
data_hex = ["\\x%02x" % i for i in data]
print("校准数据: b'%s'" % "".join(data_hex))
break
times -=1
time.sleep(1)
if not device.calibration_ok:
print("校准失败")
sys.exit()
print("measure->")
while True:
try:
times = 5
data_list = []
while times > 0:
measurement = device.measure(spad_map_id)
if True:
# print("result:")
for spad in measurement:
# print(["%d:%d" % (p["confidence"],p["distance"]) for p in spad])
pass
# print("")
data_list.append([[p["distance"] for p in spad] for spad in measurement])
times -= 1
time.sleep(0.05)
# continue
# 对每次采集的数据计算角度
angles = []
for depth_data in data_list:
angle = calculate_angle(np.array(depth_data))
angles.append(angle)
# 计算平均角度
average_angle = np.mean(angles)
print(f"每次计算的角度: {angles}")
print(f"平均角度: {average_angle:.2f}°")
except Exception as e:
print(e)
break
在上述代码中,tmf8821.py封装了在树莓派5下面与TMF8821通过I2C打交道的接口。
代码的主要逻辑,就是先使能设备,使能的时候会自动根据app_id自动加载firmware。
然后进行校准,并检测校准是否成功。校准的数据,可以保存下来,用于后续直接写入校准数据,而无需重新校准。
在测量部分,多次获取测量的数据,然后使用numpy进行角度计算。
当没有防止物体在TMF8821探测窗口上方时,测量的是天花板,基本水平。
当放置一个手机上去时,结果如下:
5. 任务二、手势识别
在任务一的基础上,获取到了数据,可以进一步,进行手势的识别。
这里仅做了基本的手势识别:接近、离开、挥动
主要逻辑代码如下:
from smbus2 import SMBus
from tmf8821 import TMF8821
import time
import numpy as np
import math
from config_calibrate import factory_calibrate
# 计算每次数据的平均深度
def calculate_average_depth(depth_data):
return np.mean(depth_data)
# 计算左右差异
def calculate_left_right_difference(depth_data):
left = np.mean(depth_data[:, 0]) # 左列
right = np.mean(depth_data[:, 2]) # 右列
return right - left
with SMBus(1) as bus:
device = TMF8821(bus=bus)
print("app_id:", hex(device.app_id))
print("mode:", hex(device.mode))
print("enable->")
device.enable()
print("mode:", hex(device.mode))
spad_map_id = 7
print("spad_map->", spad_map_id)
device.spad_map = spad_map_id
time.sleep(0.1)
print("spad_map<-", device.spad_map)
print("calibrate->")
if False and spad_map_id in factory_calibrate:
device.write_calibration(factory_calibrate[spad_map_id])
time.sleep(0.1)
else:
times = 10
while times>0:
data = device.calibrate()
if device.calibration_ok:
# 校准成功
data_hex = ["\\x%02x" % i for i in data]
print("校准数据: b'%s'" % "".join(data_hex))
break
times -=1
time.sleep(1)
if not device.calibration_ok:
print("校准失败")
sys.exit()
print("measure->")
data_list = []
while True:
try:
measurement = device.measure(spad_map_id)
print("result:")
for spad in measurement:
print(["%d:%d" % (p["confidence"],p["distance"]) for p in spad])
print("")
data_list.append([[p["distance"] for p in spad] for spad in measurement])
# print("")
time.sleep(0.1)
if len(data_list)>2:
data_list.pop(0)
if len(data_list)<2:
continue
# 初始化变量
previous_average = None
previous_difference = None
trend = []
left_right_trend = []
action_detected = False
# 设置阈值
DEPTH_THRESHOLD = 150 # 深度变化阈值
DIFFERENCE_THRESHOLD = 150 # 左右差异变化阈值
# 遍历每次采集的数据
for depth_data in data_list:
current_average = calculate_average_depth(np.array(depth_data))
current_difference = calculate_left_right_difference(np.array(depth_data))
if previous_average is not None:
# 判断深度变化趋势
difference_change = abs(current_average - previous_average)
if difference_change > DEPTH_THRESHOLD:
if current_average < previous_average:
trend.append("接近")
elif current_average > previous_average:
trend.append("远离")
else:
trend.append("无变化")
# 判断左右差异变化
difference_change = abs(current_difference - previous_difference)
if difference_change > DIFFERENCE_THRESHOLD:
if current_difference < previous_difference:
left_right_trend.append("左挥动")
else:
left_right_trend.append("右挥动")
action_detected = True
else:
left_right_trend.append("无左右动作")
action_detected = False
previous_average = current_average
previous_difference = current_difference
# 输出结果
for i, (t, lrt) in enumerate(zip(trend, left_right_trend)):
print(f"第 {i+1} 次与第 {i+2} 次比较: {t}, {lrt}")
if "挥动" in lrt and action_detected:
print("挥动检测: 挥动超过阈值")
elif "接近" in t:
print("动作检测: 接近")
elif "远离" in t:
print("动作检测: 远离")
else:
print("动作检测: 无动作")
except Exception as e:
print(e)
break
上述代码中,从开始到测量的部分,与任务一的代码基本相同,主要连续累计数据,然后和上一次进行对比,从而检测手势的状态。
当接近时:
当原理时:
当挥动时:
总结:
这次的TMF8821模块,因为时间原因,只是做了基础的摸索使用。
通过的基础的使用,已经能够充分了解到这款模块的强大功能。
后续会安排时间,进一步研究学习,充分了解和挖掘这款模块的功能。