2024艾迈斯欧司朗竞赛 - 基于tmf8821&rp2040实现折射率测量与液体鉴别
该项目使用了tmf8821和rp2040,实现了折射率测量装置的设计,它的主要功能为:测量折射率并识别液体种类。
标签
RP2040
ToF
游泳的鸟儿
更新2025-03-06
福州大学
54

一.项目描述

项目需求:

      设计实验,利用ToF的原理,在传感器前放置透明水槽,并装入水/油等液体,并尝试测量其折射率,最后根据折射率分辨液体种类

实现功能:

在LCD上显示被测液体的折射率,并根据折射率识别被测液体的种类。

二.原理及实现方法

   1.dtof原理

TOF(Time-of-Flight,飞行时间)型激光传感器是一种基于激光脉冲飞行时间测量原理的传感器,它主要用于测量物体与传感器之间的距离。

dTOF传感器上有一枚940nm波长的红外激光发射管,从传感器发射,经过被测物体反射,传感器上的单光子雪崩二极管SPAD(single photon avalanche diode)会感受到光子,从而可以计算出发射到接收的时间差Δt,由d=Δt*c/2可以算出dTOF传感器到物体的距离。

   2.折射率测量原理

根据定义,折射率是光在真空中的传播速度与光在该介质中的传播速度之比,即n=c/v。dTOF寄存器会直接传出距离d(mm),这个距离d是按照空气中的光速c算出来的。在不同的液体中,同样的距离由于光在介质中的速度不一样了,所以寄存器读出的距离会不同。

在空气中,d=c*Δt

在待测液体中,d=v*Δt‘

由此可得 c*Δt=v*Δt’=c/n*Δt‘

所以d=d’/n即n=d‘/d

在实际测量中,需要先将容器放空,测量空气中的d,再装入待测液体测出d’,进而算出折射率n。

三、tmf8821的驱动程序编写

根据手册,按照步骤一步一步编写micropython程序。

image.png

冷启动上电后,先读APPID,若是0x80,需要烧录固件APPID;之后变为0x03后就可以开始测量了

按照上图,1、上电;2、将Enable引脚拉高;3、在0xE0寄存器写入0x01;4、读0x41寄存器直到返回0x41;5、若读出APPID为0x00说明应用程序在运行,若是0x80说明需要烧录bootload,若是0x03说明应用程序在运行;

	def enable(self, auto_load_firmware: bool = True) -> None:
"""Enable the device."""
self.i2c.writeto_mem(0x41, 0xE0, b'\x01')#上电使能给时钟
self.i2c.writeto_mem(0x41, 0xE0, b'\x21')
time.sleep_ms(self.poll_delay)
for _ in range(100):
if self.mode == 0x41:
break
time.sleep_ms(self.poll_delay)
if self.mode != 0x41:
print(f"Failed to set mode to enabled. Mode is: {self.mode}.")
if self.app_id() == b'\x80' and auto_load_firmware:
self._load_firmware()

下一步是需要编写烧录bootload的程序,根据数据手册如下图

image.png

image.png

image.png

根据数据手册,这里每个发的指令都需要校验,需要发送寄存器地址+数据数量+数据+校验码,校验码为前面所有数据加起来异或0xff。开始烧录bootload,先向寄存器0x08开始发送指令0x14数据0x29,这里按照校验的方式为向0x08发送0x14,0x01,0x29,0xc1,发送完就是读0x08寄存器直到返回0x00,0x00,0xff三个字节,代表成功。后文我就不再赘述校验和等待回复的过程,只说指令和数据了。之后烧录ram起始地址,发送指令0x43数据0x00,0x00。之后就需要烧录bootload了,由于每次最大烧录20个,所以需要拆分后依次烧进去。烧录完发送指令0x11,数据为空,进行重置RAM。到此基本上烧录bootload结束,读APPID会显示0x03了。micropython代码如下:

    def _load_firmware(self):
"""Load the firmware into the device."""
# DOWNLOAD INIT
self._send_bootloader_command(0x14, [0x29])
# SET ADDR
self._send_bootloader_command(0x43, [0x00, 0x00])
# W RAM
for chunk in _chunks(firmware):
self._send_bootloader_command(0x41, chunk)
# RAMREMAP RESET
self._send_bootloader_command(0x11, [])
time.sleep_ms(self.poll_delay)
def _send_bootloader_command(self, command: int, data: list[int]):
"""Send a command to the bootloader."""
message = [command, len(data)] + data
checksum = (sum(message)&0xFF)^0xFF
message.append(checksum)
message2 = bytes([x for x in message])
self.i2c.writeto_mem(0x41, 0x08, message2)
for i in range(10):
if self._bootloader_status() == 0x00:
return
printf(f"Bootloader error {self._bootloader_status()}")
def _bootloader_status(self) -> int:
"""Get the status of the bootloader."""
read = self.i2c.readfrom_mem(0x41, 0x08, 3)
# Returns three fields: [value, size=0, checksum]
return list(read)[0]

接下来进行测量前的配置环节

image.png

按照数据手册步骤1、向寄存器0x08发送0x16开启配置页面;2、等待寄存器0x08内数据变为0x00,代表OK;3、寄存器0x41读出4个字节,应该是0x16,第二个字节无所谓,之后是0xbc,0x00 。4、写测量周期,向0x24寄存器发送两个字节的数据,数据为测量周期。5、发送测量点数的模式(SPAD),向0x34寄存器发送一个字节数据,这个数据就是预设的SPAD的代码,见数据手册。6、后面有些配置都是可有可无的,按照需要,我没配置中断。7、退出配置页面,向0x08寄存器发送0x15数据,并读取直到返回0x00。根据这些步骤可以编写micropython程序如下

def _configuration_mode(self,map_id: int,measurement_period:int,kilo_iterations:int):
# LOAD_CONFIG_PAGE
self._send_command(b'\x16')
data = self.i2c.readfrom_mem(0x41, 0x20, 4)
if data[0] != 0x16 or data[2] != 0xBC or data[3] != 0x00:
print("Configuration not loaded as expected.")

message=[measurement_period&0xFF,measurement_period//256]
message2 = bytes([x for x in message])
self.i2c.writeto_mem(0x41, 0x24, message2)#配置测量周期

message=[map_id]
message2 = bytes([x for x in message])
self.i2c.writeto_mem(0x41, 0x34, message2)#配置spad

message=[kilo_iterations&0xFF,kilo_iterations//256]
message2 = bytes([x for x in message])
self.i2c.writeto_mem(0x41, 0x26, message2)#配置迭代次数

self.i2c.writeto_mem(0x41, 0x31, b'\x03')#配置LED

# WRITE_CONFIG_PAGE
self._send_command(b'\x15')

def _send_command(self, command: bytes) -> None:
self.i2c.writeto_mem(0x41, 0x08, command)
for i in range(100):
read = self.i2c.readfrom_mem(0x41, 0x08, 1)
number=int.from_bytes(read, "little")
if number==0x00:
break
time.sleep_ms(self.poll_delay)

在测量前还需要进行校准,校准需要读出内部出厂校准数据再写入。数据手册读出校准数据步骤如下

image.png

按照手册:1、先向0x08寄存器写入0x20数据,并读取0x08寄存器直到变为0x00代表成功。2、再向0x08寄存器写入0x19 数据,并读取0x08寄存器直到变为0x00代表成功。3、从0x20寄存器开始读出192个字节数据,但只有后188个数据是校准需要写入的数据。4、向0x08寄存器写入0x15数据,并读取0x08寄存器直到变为0x00代表成功。编写micropython代码如下:

def calibrate(self) -> bytes:
"""
校准时需要保证40cm以内没有障碍物,每次切换SPAD需要重新校准
"""
# Perform calibration
self._send_command(b'\x20')
# Load calibration page
self._send_command(b'\x19')
# Read calibration data
data = _block_read(self,0x41, 0x20, 192)
# Write config page
self._send_command(b'\x15')
# Reset the # of iterations
return bytes(data[4:])

下面是数据手册关于写入的步骤:

image.png

按照手册:1、先向0x08寄存器写入0x19数据,并读取0x08寄存器直到变为0x00代表成功。2、再向0x24寄存器写入188个字节的校准数据。3、向0x08寄存器写入0x15数据,并读取0x08寄存器直到变为0x00代表成功。编写micropython代码如下:

def write_calibration(self, data: bytes) -> None:
"""
Write calibration data to the device.
"""
if len(data) != 188:
print("Calibration data must be 188 bytes long.")
# Load calibration page
self._send_command(b'\x19')
_block_write(self, 0x41, 0x24, list(data))
# WRITE_CONFIG_PAGE
self._send_command(b'\x15')

配置基本结束接着就可以开始发送指令进行测量了

image.png

image.png

按照手册:1、先向0xE1寄存器写入0xff数据清除所有中断标志。2、再向0x08寄存器写入0x10数据,并读取0x08寄存器直到变为0x00代表成功。3、之后可以从0x20寄存器读出132个字节数据,这些数据就是测量结果了。4、若需要停止测量,向0x08寄存器发送0xff。编写micropython代码如下:

    def measure(self) -> TMF882xMeasurement:
# Clear interrupts
self.i2c.writeto_mem(0x41, 0xE1, b'\xFF')
# MEASURE
self.i2c.writeto_mem(0x41, 0x08, b'\x10')
time.sleep_ms(self.poll_delay)
data = self.i2c.readfrom_mem(0x41, 0x20, 132)
# STOP
self.i2c.writeto_mem(0x41, 0x08, b'\xFF')
return data;

至此,所有的tmf882x基础测量的代码全部写完了。直方图部分我也写好了程序,在附件中,感兴趣的可以自行研究,本项目没用到,所以不再赘述。

四、应用层程序编写

应用中涉及屏幕,屏幕的驱动程序直接使用的是硬禾学堂的例程,就不再赘述了。屏幕截图 2025-02-19 152826.png

软件流程图如上

EN=Pin(22,Pin.OUT)
EN.value(0)#重启一下
time.sleep_ms(100)
EN.value(1)# 将引脚电平设置为高电平
time.sleep_ms(100)

tof=tmf882x.tmf882x(i2c)
tof.enable()
tof._configuration_mode(0x06,65535,4000)
data=tof.calibrate()
tof.write_calibration(data)
tof._configuration_mode(0x06,65535,250)

tof的初始化如上,就是将前面的配置过程走一遍,这里需要注意,校准时需要设置迭代次数为4000k,手册里有写。

basic=0;
def button_isr(pin):
global basic
basic=distance1

button=Pin(6,Pin.IN,Pin.PULL_UP)
button.irq(trigger=Pin.IRQ_FALLING,handler=button_isr)

while True:
i2c=machine.I2C(0,sda=sda,scl=scl,freq=400000)
data=tof.measure()

distance=[0,0,0,0,0,0,0,0,0]

for i in range(9):
distance[i]=int.from_bytes(data[25 + 3 * i : 27 + 3 * i], "little")

distance1=distance[4]
if 0==basic:
n=0
else:
n=distance1/basic
print(basic)

display.text(font2, "{:>4}".format(distance1), 96, 32)
display.text(font2, "{:>4}".format(basic), 96, 112)
display.text(font2, "{:.3f}".format(n), 90, 160)

if n<1.05:
display.text(font2, " Air ", 50, 200)
else:
if n<1.4:
display.text(font2, "Water", 50, 200)
else:
display.text(font2, " Oil ", 50, 200)

剩余代码相对比较简单,如第二点所说,需要先测d,作为基准,再去测d‘,从而算出折射率n。所以容器不装液体先测出一个值,按下A键,这时会存为程序中basic的值。后面装上液体再测量d‘,最终可算出折射率n。根据折射率n识别液体种类。

六、功能展示图及说明

使用方法:

1、开机上电,需要确保40cm以内无障碍物,会进行自动校准。

2、先紧贴容器壁,容器确保空的,这时按下A键。

3、装入待测液体,此时就会显示出折射率n值了。

测水和测油的效果图如下图所示

684143039931105862.jpg

607102484999277261.jpg


七、项目中遇到的难题和解决方法

本项目中所遇到的难题基本上就是tmf8821的驱动底层代码编写,tof的功能很多,操作步骤繁杂,一开始看给出的arduino或python例程会无从下手。最后我还是回归数据手册,并参照别人的例程https://github.com/rogiervandergeer/tmf882x-driver/tree/main,按照手册上的步骤一步一步进行配置改写,最终配置成功。

我原来都是使用C语言,python其实是第一次接触,刚开始写需要不断地修改语法错误,不断地搜索python语句,特别是bytes、list、int这些数据转换。

八、对本次竞赛的心得体会

通过本次竞赛,初步学习了python语言,学习了dTOF的原理,掌握了tmf8821的使用方法,为以后其他有意思的小项目提供了更多可选择的传感器。


附件下载
程序.zip
团队介绍
福州大学 电气工程及其自动化专业 郑凯文
团队成员
游泳的鸟儿
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号