I have been doing a project involving three TMF8801 sensors for distance measurement. These sensors use I2C, and since the board I was using had only one I2C port exposed, I was putting one sensor on the hardware I2C bus and the other two on software buses. The software buses seemed to be giving some issues, so I switched to the Teensy 4.0 which has three hardware I2C buses. However, now all three sensors are giving me a distance of 7162mm, which from past testing I know is a junk value (also, this is way outside the advertised maximum range of the sensor, 2500mm). Moving my hand in front of the sensor doesn't change the distance like it normally would, but it instead continues to read 7162. Any ideas what could be going wrong?
My Micropython code for interfacing with the sensor:
Code: Select all
from micropython import const
from time import sleep_ms
from event_handler import EventHandler
# Constants
_DEFAULT_ADDR = const(0x41)
_CPU_TIMEOUT = const(200)
_APP_TIMEOUT = const(200)
_COMMAND_RESULT = const(0x55)
_APP_DIST = const(0xC0)
_APP_BOOT = const(0x80)
_CHIP_ID = const(0x07)
_CALIBRATION = bytearray([0xC1, 0x22, 0x0, 0x1C, 0x9, 0x40, 0x8C, 0x98, 0xA, 0x15, 0xCE, 0x9C, 0x1, 0xFC])
_ALG_STATE = bytearray([0xB1, 0xA9, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
_CMD_DATA_VALUES = bytearray([0x03, 0x23, 0x44, 0x00, 0x00, 0x64, 0xD8, 0xA4])
# Commands
_CMD_SET_ADDR = const(0x49)
_CMD_CAL = const(0x0B)
_CMD_MEASURE = const(0x02)
_CMD_STOP = const(0xFF)
_CMD_DATA0 = const(0x0F)
_CMD_DATA1 = const(0x0E)
_CMD_RAM_REMAP_RST = const(0x11)
# CPU status
_CPU_RESET = const(0x07)
_CPU_READY = const(0x06)
# Registers
_REG_COMMAND = const(0x10)
_REG_DATA = const(0x20)
_REG_ENABLE = const(0xE0)
_REG_ID = const(0xE3)
_REG_APPID = const(0x00)
_REG_APPREQID = const(0x02)
_REG_FACTORY_CAL = const(0x20)
_REG_STATE_DATA = const(0x2E)
_REG_CMD_DATA7 = const(0x08)
_REG_REGISTER_CONTENTS = const(0x1E)
_REG_BL_CMD_STAT = const(0x08)
_REG_BL_SIZE = const(0x09)
_REG_BL_CSUM = const(0x8B)
_REG_STATUS = const(0x0D)
class TMF8801:
"""A class to manage the TOF distance sensor."""
def __init__(self, i2c: object, close_dist = 4, inches = True, address = _DEFAULT_ADDR, enable = None):
"""Initialize the sensor."""
self.i2c = i2c
self.inches = inches
self.address = _DEFAULT_ADDR
self.enable = enable
self.close = EventHandler()
self.c_trig = False
self.block = EventHandler()
self.b_trig = False
self.path_detected = EventHandler()
self.p_trig = False
self.close_dist = close_dist
if int.from_bytes(self.i2c.readfrom_mem(self.address, _REG_APPID, 1), "big") != 0xC0:
self.begin()
@property
def measurement(self) -> bytearray:
"""Get the result number, result info, and distance."""
buffer = bytearray(4)
self.i2c.readfrom_mem_into(self.address, _REG_DATA, buffer)
return buffer
def get_distance(self) -> int:
"""Get the distance."""
distance = self.measurement[3] << 8
distance += self.measurement[2]
converted_distance = ((distance / 25.4) if self.inches else distance)
if converted_distance <= 1 and not self.b_trig:
self.block()
self.b_trig = True
elif converted_distance > 1 and self.b_trig:
self.block(1)
self.b_trig = False
if converted_distance <= self.close_dist and not self.c_trig:
self.close()
self.c_trig = True
elif converted_distance > self.close_dist and self.c_trig:
self.close(1)
self.c_trig = False
if (converted_distance >= 7 or converted_distance == 0) and not self.p_trig:
self.path_detected()
self.p_trig = True
elif (converted_distance < 7 and converted_distance != 0) and self.p_trig:
self.path_detected(1)
self.p_trig = False
return converted_distance
def cpu_ready(self) -> bool:
"""Make sure the CPU is ready."""
counter = 0
if not self.__bit_set(_REG_ENABLE, _CPU_READY):
counter += 1
sleep_ms(100)
else:
return True
while counter <= _CPU_TIMEOUT:
if not self.__bit_set(_REG_ENABLE, _CPU_READY):
counter += 1
sleep_ms(100)
else:
return True
else:
return False
def app_ready(self, app: int) -> bool:
"""Make sure the measurement app is ready."""
counter = 0
if not (int.from_bytes(self.i2c.readfrom_mem(self.address, _REG_APPID, 1), "big") == app):
counter += 1
sleep_ms(100)
else:
return True
while counter <= _APP_TIMEOUT:
if not (int.from_bytes(self.i2c.readfrom_mem(self.address, _REG_APPID, 1), "big") == app):
counter += 1
sleep_ms(100)
else:
return True
else:
return False
def data_ready(self) -> bool:
"""Check if new data is ready."""
return int.from_bytes(self.i2c.readfrom_mem(self.address, _REG_REGISTER_CONTENTS, 1), "big") == _COMMAND_RESULT
def set_address(self, new_address: int): # FIXME: Does not work.
"""Set the i2c address of the sensor."""
print(self.i2c.readfrom_mem(self.address, _REG_STATUS, 1))
self.i2c.writeto_mem(self.address, _REG_APPREQID, bytearray([_APP_BOOT]))
if not self.app_ready(_APP_BOOT):
raise RuntimeError("Unable to load bootloader.")
self.i2c.writeto_mem(self.address, _REG_BL_CMD_STAT, bytearray([_CMD_RAM_REMAP_RST]))
self.i2c.writeto_mem(self.address, _REG_BL_SIZE, bytearray([0x00]))
self.i2c.writeto_mem(self.address, _REG_BL_CSUM, bytearray([0xEE]))
__new_address = new_address << 1
self.i2c.writeto_mem(self.address, _CMD_DATA1, bytearray([__new_address]))
self.i2c.writeto_mem(self.address, _CMD_DATA0, bytearray([0x00]))
self.i2c.writeto_mem(self.address, _REG_COMMAND, bytearray([_CMD_SET_ADDR]))
print(self.i2c.readfrom_mem(self.address, _REG_STATUS, 1))
self.i2c.writeto_mem(self.address, _REG_COMMAND, bytearray([_CMD_STOP]))
print(self.i2c.scan())
print(self.i2c.readfrom_mem(self.address, _REG_STATUS, 1))
self.address = new_address
def wake(self) -> bool:
"""Wake the sensor."""
self.i2c.writeto_mem(self.address, _REG_ENABLE, bytearray([0x01]))
result = self.i2c.readfrom_mem(self.address, _REG_ENABLE, 1)
sleep_ms(100)
while result != 0x41:
self.i2c.writeto_mem(self.address, _REG_ENABLE, bytearray([0x01]))
result = self.i2c.readfrom_mem(self.address, _REG_ENABLE, 1)
sleep_ms(100)
return True
def __bit_set(self, register_address: int, bit_position: int) -> bool:
"""Check if a particular bit is set."""
value = int.from_bytes(self.i2c.readfrom_mem(self.address, register_address, 1), "big")
mask = int(1 << bit_position)
if (value & mask) != 0:
return True
else:
return False
def is_connected(self):
"""Check if the device is connected."""
if self.address not in self.i2c.scan():
return False
else:
return True
def begin(self):
"""Initialize the sensor."""
if self.address not in self.i2c.scan():
raise RuntimeError("Failed to get response from device.")
# Reset the CPU and make sure it is ready
self.i2c.writeto_mem(self.address, _REG_ENABLE, bytearray([_CPU_RESET]))
if not self.cpu_ready():
raise RuntimeError("CPU timed out.")
# Make sure the device is a TMF8801
if int.from_bytes(self.i2c.readfrom_mem(self.address, _REG_ID, 1), "big") != _CHIP_ID:
raise RuntimeError("Wrong chip ID.")
if self.address not in self.i2c.scan():
raise RuntimeError("Failed to get response from device.")
# Load the distance measurement application
self.i2c.writeto_mem(self.address, _REG_APPREQID, bytearray([_APP_DIST]))
if not self.app_ready(_APP_DIST):
raise RuntimeError("Unable to load measurement application.")
# Set the calibration data
self.i2c.writeto_mem(self.address, _REG_COMMAND, bytearray([_CMD_CAL]))
self.i2c.writeto_mem(self.address, _REG_FACTORY_CAL, _CALIBRATION)
self.i2c.writeto_mem(self.address, _REG_STATE_DATA, _ALG_STATE)
self.i2c.writeto_mem(self.address, _REG_CMD_DATA7, _CMD_DATA_VALUES)
# Start the measurement application
self.i2c.writeto_mem(self.address, _REG_COMMAND, bytearray([_CMD_MEASURE]))