一、项目介绍
本项目创新性地开发了一套基于艾迈斯欧司朗TMF8821多区域直接飞行时间(dToF)传感器与RP2040微控制器平台的非接触式角度测量系统。通过充分发挥RP2040 Game Kit的卓越性能与dToF模块的高精度测距优势,系统实现了对被测平面与传感器模块之间空间夹角及最小垂直距离的精准实时计算。系统的核心在于对dToF传感器输出的多区域测距数据进行喜欢分析,通过优选特定检测区域数据构建高精度几何模型。在此基础上,运用余弦定理与三角函数关系,实现了空间参数的快速解算,确保了系统在测量精度与实时性方面的卓越表现。
二、设计思路与实现
1、目标
角度测算:将板卡组合后固定,并保证与面前的平面存在一定夹角,通过程序测算出板卡距离屏幕的夹角和垂直最小距离
2、任务分析
任务要求中可以看出,程序测算的输出参数是板卡与平面的夹角,和垂直最小距离,这里我们可以看到需要传感器的采集以及主控的显示要求;
结构上的要求是板卡组合后固定,这里分出两个任务一个是组合,一个是固定,空间上存在两个物体,一个是组合后的板卡,一个是平面。
3、硬件连接
咱们先实现一下结构要求,板卡的组合与固定非常简单,主控板和传感器板的接口是兼容的,我们将其安装在一起就可以:
接下来就是固定于垂直位置,并通过热熔胶固定:
4、主要器件
主控平台采用RP2040 Game Kit,这是一款基于树莓派RP2040的嵌入式系统学习平台。该平台通过USB Type-C供电,搭载RP2040主控芯片,支持MicroPython和C/C++编程,具备强大的性能。板上功能丰富,包括:240*240分辨率的彩色IPS LCD(SPI接口,ST7789控制器)、四向摇杆、2个轻触按键、三轴姿态传感器MMA7660作为输入控制。此外,板载2MB Flash,预刷MicroPython的UF2固件,配备红外接收管、红外发射管、三轴姿态传感器MMA7660、蜂鸣器以及双排16Pin连接器(支持SPI、I2C和2路模拟信号输入)。该游戏机可玩性极高,可移植多款复古游戏,同时可作为电赛的控制与显示接口平台,搭配传感器和模拟电路,能实现更多创意项目。本次作为TMF8821传感器模块的采集工具,通过适配的双排16Pin连接器进行数据采集,最终在240*240分辨率的彩色IPS LCD上显示结果。
接下来登场的是TMF8821传感器模块,这款模块采用一体化封装设计,集成VCSEL(垂直腔面发射激光器)。依托SPAD、TDC及直方图技术,模块可实现5000mm的精准测距。其镜头位于SPAD上方,支持3x3、4x4和3x6多区域数据输出,并具备宽幅动态可调视野。VCSEL上方的多透镜阵列(MLA)进一步提升了照明场(FoI)范围。所有原始数据均在片上完成处理,TMF8821通过I2C接口提供精确的距离信息与置信度值。该dToF传感器模块与RP2040游戏机管脚完全兼容,实现即插即用,并在侧面预留扩展接口,便于焊接、调试及数据采集。核心特性包括:采用高灵敏度SPAD检测的Direct ToF技术;支持4x4可配置多区域,实现多目标检测;最大63°对角线的可调视野;快速时间数字转换器(TDC)架构;亚纳秒级光脉冲;10 – 5000mm距离感应@30Hz;片上直方图处理;940nm VCSEL 1类人眼安全;高性能片上阳光阻隔滤波器与算法;小型模块化OLGA 2.0mm x 4.6mm x 1.4mm封装。本次采用的是3*3数据采集的经典模式。
5、主要算法
空间结构实际上也可以归到一个个面上的,然后在划分为线与点之间的关系进行计算,通过公用关系将空间问题转化为纯数学运算,以下是我解题的整体思路:
中间部分是一个空间变化的拆解图,实际上,你对传感器的平面动作都可以划分为线在X轴和Y轴的连续变化,在变化过程中有一些量是不变的,这就是我们解题的思路,接下来我们一起分析一下:
在得到的数据中我们选取a2,a5,a4组成空间模型,得到的数据长度分别为B,A,C,注意垂直于传感器的a5为A,A与B线的夹角是固定的,那么我们就可以得到图形中的b的长度,依据的是余弦定理:
得到了比以后通过两个拥有公用边的直角三角形的特性可以得到:
B*B-(b+a)*(b+a) = A*A-a*a,这里用到是勾股定理,可以计算出a的长度,然后同样适用勾股定理可以求出公用变的长度(即A1);
以上是X轴方向求出的量,同样的步骤用在Y轴,可以求出c的长度以及Y轴公用边的长度(即A2);
我们在一个轴上的转动可以保证位置的移动是相同的,这样我们就可以又构成了一个之间三角形,即c和A1组成了X轴夹角的直角三角形,a和A2组成了Y轴夹角的直角三角形,除了夹角,其他都是已经算出来的量,夹角就都可以计算出来了;
进而可以计算最小的垂直距离,也就是共同的直角边。
我们将上边的推到转化为程序中的算法如下:
return angle_x,angle_y,L_Hdef algorithm(A,B,C):
L_A = A
L_B = B
L_C = C
angle_F = 16
rad_F = math.radians(angle_F)
COS_F = math.cos(rad_F)
L_b2 = L_A*L_A+L_B*L_B-2*L_A*L_B*COS_F
L_b = math.sqrt(L_b2)
print("L_b: ")
print(L_b)
L_a = (L_B*L_B-L_A*L_A-L_b2)/2/L_b
print("L_a: ")
print(L_a)
L_d2 = L_A*L_A+L_C*L_C-2*L_A*L_C*COS_F
L_d = math.sqrt(L_d2)
L_c = (L_C*L_C-L_A*L_A-L_d2)/2/L_d
L_A1 = math.sqrt(L_A*L_A - L_a*L_a)
SIN_x = L_c/L_A1
SIN_x = max(-1,min(1,SIN_x))
rad_x = math.asin(SIN_x)
angle_x = math.degrees(rad_x)+25
print()
L_A2 = math.sqrt(L_A*L_A - L_c*L_c)
SIN_y = L_a/L_A2
SIN_y = max(-1,min(1,SIN_y))
rad_y = math.asin(SIN_y)
angle_y = math.degrees(rad_y)
print(L_A1*math.cos(rad_x))
print(L_A2*math.cos(rad_y))
L_H = (L_A1*math.cos(rad_x)+L_A2*math.cos(rad_y))/2
输入是三点构成的空间距离数据,注意A一定是a5点的数据,输出为X轴、Y轴以及最小距离的数据,也是我们显示的主要内容。
6、测试方法
通过结合不同测试方法的优缺点,最终选取了定传感器板卡,移动水平面的方式进行测试,这里的平面要有一定的要求,不能是吸光材料,最终我用笔记本的内页作为移动的平面,保证传感器的视场角时钟在平面内。
7、软件流程
8、其他关键代码
接口代码IIC:‘
class I2C_config:
def __init__(self, sda: int = 16, scl: int = 17, freq: int = 1000000):
self.sda_pin = Pin(sda)
self.scl_pin = Pin(scl)
self.freq = freq
class I2C_user:
def __init__(self):
self.i2c = None
#
def i2cOpen(self, i2c_pin_set: I2C_config):
try:
self.i2c = I2C(0, sda=i2c_pin_set.sda_pin, scl=i2c_pin_set.scl_pin, freq=i2c_pin_set.freq)
return 0
except OSError as e:
return -1
def i2cTxRx(self, device_addr: int, tx: list, rx_size: int) -> bytearray:
"""Function to transmit and receive bytes via I2C.
Args:
device_addr(int): the 7-bit I2C slave address (un-shifted).
tx(list): the list of bytes to be transmitted.
rx_size(int): the number of bytes to be received.
Returns:
bytearray: array of bytes received.
"""
self.i2c.writeto(device_addr, bytes(tx))
if rx_size == 0:
return bytearray(0)
data_buffer = bytearray(rx_size)
self.i2c.readfrom_into(device_addr, data_buffer)
return data_buffer
def i2cTx(self, device_addr: int, tx: list) -> int:
"""Function to transmit given bytes on I2C.
Args:
device_addr(int): the 7-bit I2C slave address (un-shifted).
tx(list): a list of bytes to be transmitted.
Returns:
int: status: 0 == ok, else error
"""
try:
self.i2c.writeto(device_addr, bytes(tx))
return 0 # Return 0 indicating success
except OSError as e:
print("I2C error:", e)
return -1 # Indicate an error occurred
传感器初始化代码:
class Tmf8821Utility(Tmf8821App):
def __init__(self, ic_com: I2C_user):
super().__init__(ic_com)
def open(self, i2c_config: I2C_config = I2C_config()):
return super().open(i2c_config)
def init_bootloader_check(self):
print("Started Bootloader check")
self.enable()
if not self.isAppRunning():
print("Image not found. Initiate Image Download")
self.downloadAndStartApp()
print("Application {} started".format(self.getAppId()))
def measure_frame(self, number_of_frames: int = 1):
frames = list()
status = self.startMeasure()
if status != Tmf8821Device.Status.OK:
return
read_frames = 0
while read_frames < number_of_frames:
if self.readAndClearInt(self.TMF8X2X_APP_I2C_RESULT_IRQ_MASK):
read_frames = read_frames + 1
frame = self.readResult()
frames.append(frame)
self.stopMeasure()
return frames
注意配置固件是每一次上电到要写的。
9、问题与思考
本次测试过程中还是会出现一些小问题,比如边界超限的判断,这个在转动开发板的时候总会遇到,这也是我通过改变平面的方法进行测试的主要原因;
传感器数据的精度问题,我们在看一半驱动流程的时候其中是包含校准步骤的,不过实际上这对于我们来说并不好操作,使用默认的校准数据肯定是存在一些误差的,上面的几何计算实际上是依据理想模型进行的,加入这个误差后会进一步增加边缘计算的不可信区域以及整体的误差值。
三、效果展示
主要的效果展示我们已经通过视频的方式进行了详细的讲解,大家可以查看相关视频,欢迎指正,下面是部分效果界面:
四、活动感悟
本次的驱动设计可以说是一个最大的坎,也可能是自己mpy的水平太差,最后还是通过资深工程师的帮助才实现了传感器的基本驱动,本次的数据解析是基于最基本的三角图形实现的,一步一步推理,环环相扣,这也是将感知数据照进现实的一次重要体验,感谢电子森林与艾迈斯欧司朗组织的本次活动,让我们了解更多的跟先进的传感器,更深刻的感受科技改变生活的魅力。