基于TMF8821 dToF传感器和树莓派RP2040游戏机构建生命游戏
项目介绍
本项目使用了 TMF8821 dToF 传感器和树莓派 RP2040 开发了一款基于手势控制的生命游戏。通过检测手势动作,用户可以与游戏进行交互,控制游戏的进程和状态。项目的核心是利用 dToF 传感器检测交互方式并将其转换为游戏控制信号。具体来说,靠近传感器将清空屏幕,远离传感器将随机化当检测到挥动时,会在随机位置生成一个滑翔机(生命游戏的特殊结构,能够做到不断移动)。
整体框架如下:
开发平台
dToF模块是基于 TMF8821 设计的直接飞行时间 (dToF) 传感器模块,TMF8821采用单个模块化封装,带有相关的 VCSEL(垂直腔面发射激光器)。dToF 设备基于 SPAD、TDC 和直方图技术,可实现 5000 mm 的检测范围。由于它的镜头位于 SPAD 上,它支持 3x3、4x4 和 3x6 多区域输出数据以及宽广的、动态可调的视野。VCSEL 上方的封装内的多透镜阵列 (MLA) 拓宽了 FoI(照明场)。原始数据的所有处理都在片上进行,TMF8821在其 I2C 接口上提供距离信息和置信度值。
RP2040 Game Kit是基于树莓派RP2040的嵌入式系统学习平台,USB Type-C供电,采用RP2040作为主控,支持MicroPython、C/C++编程。
生命游戏简介
生命游戏(Conway's Game of Life)是由英国数学家约翰·康威(John Horton Conway)在1970年发明的细胞自动机。它是一个零玩家游戏,模拟了细胞在网格上的生存和繁殖。游戏在一个二维网格上进行,每个格子可以是活的或死的。根据简单的规则,细胞会在每个时间步长中更新状态:一个活细胞如果有两个或三个活邻居则继续存活,否则死亡;一个死细胞如果有三个活邻居则复活。尽管规则简单,生命游戏可以产生复杂的图案和行为,被广泛用于研究复杂系统和自组织现象。
在生命游戏(Conway's Game of Life)中,滑翔机(Glider)是一种非常有趣的结构。滑翔机是一个由五个细胞组成的模式,它会在每个时间步长中按照特定的方向移动。滑翔机的独特之处在于它能够在网格上不断移动,而不会消失或变形。这种自我复制和移动的能力使得滑翔机成为生命游戏中研究复杂系统和自组织现象的重要工具。
使用 Micro Python,我们定义了以下函数来实现生命游戏的更新和绘制,更新机制在 update_grid
中展示。
def draw_grid():
"""Draw the grid and cells on the display."""
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
if grid[y][x] != prev_grid[y][x]:
color = ALIVE_COLOR if grid[y][x] == 1 else DEAD_COLOR
display.fill_rect(x * CELL_SIZE, (y + 4) * CELL_SIZE, CELL_SIZE, CELL_SIZE, color)
for x in range(0, WIDTH+1, CELL_SIZE):
display.vline(x, 40, HEIGHT - 40, GRID_COLOR)
for y in range(40, HEIGHT+1, CELL_SIZE):
display.hline(0, y, WIDTH, GRID_COLOR)
def update_grid():
"""Update the grid based on Conway's Game of Life rules."""
new_grid = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
alive_neighbors = sum([
grid[(y-1) % GRID_HEIGHT][(x-1) % GRID_WIDTH],
grid[(y-1) % GRID_HEIGHT][x],
grid[(y-1) % GRID_HEIGHT][(x+1) % GRID_WIDTH],
grid[y][(x-1) % GRID_WIDTH],
grid[y][(x+1) % GRID_WIDTH],
grid[(y+1) % GRID_HEIGHT][(x-1) % GRID_WIDTH],
grid[(y+1) % GRID_HEIGHT][x],
grid[(y+1) % GRID_HEIGHT][(x+1) % GRID_WIDTH]
])
if grid[y][x] == 1 and alive_neighbors in [2, 3]:
new_grid[y][x] = 1
elif grid[y][x] == 0 and alive_neighbors == 3:
new_grid[y][x] = 1
else:
new_grid[y][x] = 0
return new_grid
def add_glider(x, y, direction='right', color=GLIDER_COLOR):
"""Add a glider at the specified position and direction with a specific color."""
glider_patterns = {
'right': [(0, 1), (1, 2), (2, 0), (2, 1), (2, 2)],
'left': [(0, 1), (1, 0), (2, 2), (2, 1), (2, 0)],
'up': [(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)],
'down': [(1, 2), (2, 1), (0, 0), (1, 0), (2, 0)]
}
# print("Adding glider at ({}, {}) in {} direction".format(x, y, direction))
for dx, dy in glider_patterns[direction]:
grid[(y + dy) % GRID_HEIGHT][(x + dx) % GRID_WIDTH] = 1
display.fill_rect((x + dx) * CELL_SIZE, (y + dy + 4) * CELL_SIZE, CELL_SIZE, CELL_SIZE, color)
# display.text(font1, "{} ".format(direction), 0, 0, TITLE_COLOR)
# 清空point
def clear_screen():
prev_grid = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
grid = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
display.fill(st7789.BLACK)
for x in range(0, WIDTH+1, CELL_SIZE):
display.vline(x, 40, HEIGHT - 40, GRID_COLOR)
for y in range(40, HEIGHT+1, CELL_SIZE):
display.hline(0, y, WIDTH, GRID_COLOR)
驱动移植
官方的驱动写的很完善,但是阅读起来需要考虑的东西有点多,不能轻易梳理一条主线。我在 Github 上找到了另外使用 Python 编写的驱动项目并进行了移植:https://github.com/rogiervandergeer/tmf882x-driver/tree/main,进行的主要修改是将通信库替换成了 MPY 的 I2C,并对其中非必要的功能进行了删减。调试输出如下:
这里的APPID、MINOR显示结果与手册中一致
手势检测
在硬件端,使用了树莓派 RP2040 和 TMF8821 dToF 传感器进行手势检测。手势检测部分通过不断读取传感器数据实现用户手势的检测功能,当检测到特定手势时会发送对应的控制信号。这一过程中存在的问题是,如何准确识别手势并避免误判。
class SwipeDetector:
def __init__(self, bus, address=0x41, enable_pin=22, interrupt_pin=21):
self.device = TMF882x(bus=bus, address=address, enable_pin=enable_pin, interrupt_pin=interrupt_pin)
self.previous_distances = []
self.low_value_count = 0
self.default_distance = 250 # 常态高度下的默认值
self.sample_length = (1 / tof_sample_time) + 2
def detect_gesture(self):
measurement = self.device.measure()
if measurement.n_valid_results > 0:
primary_distances = measurement.primary_grid
avg_distance = self.calculate_average_distance(primary_distances)
# print(avg_distance)
if avg_distance < self.default_distance:
self.previous_distances.append(avg_distance)
if len(self.previous_distances) > self.sample_length: # 增加检测窗口长度
self.previous_distances.pop(0)
gesture = self.detect_gesture_type()
if gesture:
# print(gesture)
self.trigger_glider(gesture)
return True
return False
def calculate_average_distance(self, distances):
total_distance = sum(sum(row) for row in distances)
num_elements = sum(len(row) for row in distances)
return total_distance / num_elements
def detect_gesture_type(self):
if len(self.previous_distances) < self.sample_length: # 确保有足够的数据点进行判断
return None
# 计算距离变化趋势
trends = [self.previous_distances[i+1] - self.previous_distances[i] for i in range(len(self.previous_distances) - 1)]
for i in range(len(trends) -1):
if trends[i] < -50 and trends[i+1] > 50:
self.previous_distances = []
return 'wave'
# 靠近 区间内持续降低且每次的幅度小于20
if all(trend < 0 for trend in trends[-3:]) and all(trend < -15 for trend in trends[-3:]):
self.previous_distances = []
return 'approach'
# 远离 区间内持续升高且每次的幅度小于20
if all(trend > 0 for trend in trends[-3:]) and all(trend > 10 for trend in trends[-3:]):
self.previous_distances = []
return 'depart'
return None
detect_gesture
方法通过传感器获取数据并计算平均距离。如果平均距离小于默认值,则将其添加到 previous_distances
列表中,然后检测手势类型。如果检测到手势,则触发相应的动作。calculate_average_distance
方法用于计算传感器测量数据的平均值。detect_gesture_type
方法通过分析 previous_distances
列表中的距离变化趋势来判断手势类型。主要检测三种手势:
- wave:快速的距离变化(先减小后增大)。
- approach:持续减小的距离变化。
- depart:持续增大的距离变化。
通过这些逻辑,系统可以准确检测到用户的手势,并将手势转换为游戏控制信号,实现手势控制游戏的功能。
在下图中,我们展示了三种手势检测的效果:
wave 手势:快速的距离变化(先减小后增大),触发滑翔机生成。
approac手势:持续减小的距离变化,触发清空屏幕。
depart手势:持续增大的距离变化,触发随机化屏幕。
总结
目前 TMF8821 dToF 传感器的手势检测功能已经实现,并成功应用于生命游戏的控制中。通过手势检测,用户可以与游戏进行交互,控制游戏的进程和状态。项目的实现过程中遇到了一些挑战,但最终通过不断的调试和优化,成功实现了预期的功能。
参考链接
- RP2040 Game Kit on Gitee
- See the datasheet and the communication note for more details.
- EETree Platform