Hello,
I have been trying to change the I2C address of a TMF8801 time of flight sensor, since I need to have three of them connected to the same I2C bus for a project. I've looked everywhere for libraries that have a function to do this, but have not found anything. Closest I found to what I've been looking for is this library which doesn't compile because of an error in the SRC (I probably should have noticed earlier that there were 15 failing checks with the release that included the address change function.) I tried writing my own code to change the address based on information provided in the datasheet and in the host driver communication docs, but it still doesn't work.
This is the code that I'm using (written in Micropython, not C / C++):
from micropython import const
from time import sleep_ms
# 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, address = _DEFAULT_ADDR):
"""Initialize the sensor."""
self.i2c = i2c
self.address = _DEFAULT_ADDR
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
@property
def distance(self) -> int:
"""Get the distance."""
distance = self.measurement[3] << 8
distance += self.measurement[2]
return 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):
"""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]))
I'm trying to change the address to 0x51. When it prints the list of I2C addresses after the change, it still shows the default device address of 0x41 instead of 0x51, and I cannot use the new address to get data (the default address works, though.)
Any help would be appreciated. Suggestions for specific code can be in either programming language.