Changing I2C address of TMF8801 time of flight sensor

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.

Words like "arduino i2c multiplexer" may be of help with your research.

Thanks for the idea. I will probably use something like that if I can't figure out about changing the address. It is possible according to the datasheet, so I would ideally like to get that working.

Idahowalker has the answer, the sensors address is factory set and does not allow it to be changed. SparkFun has one as do many others.

I did not know that. This is what the host driver communication manual and the datasheet say:



These screenshots are taken directly from the datasheet and the host driver communication manual. So either it can be changed, or this information is just misleading.

I don't think you can change the address as seen by a normal scan. As I read this the two methods allow access to more than one device through a combination of wiring and software.

The wiring changes you need are different for each different method. You do not mention any wiring changes you have made.

So first choose the method you want to use and then see how you need to wire these devices up.

1 Like

As of right now there's only one sensor connected. Correct me if I'm wrong, but from what I understand the extra wiring is only necessary if you have multiple sensors connected:

It appears to me that the purpose of the extra wiring is to make sure that only one device is receiving the commands to change the address. Unless I overlooked something else. I have not used any extra wires other than VCC, GND, SDA, and SCL because I have only one sensor connected at the moment.

From what I have read if you only have one sensor you don't need to / can't change the address of the one you have. That is my reading of the document.

To me, the datasheet and that other communications document are saying that you can change the i2c address, even if you only have 1 device attached to the bus. But you will need to control the ENable pin in order to change the address. I did not see anything that implies the address change is permanent, so after a reset or loss of power the address will change back to the default. This makes it necessary to change the addresses of 2 of your 3 devices each time the circuit is powered on/reset.

The other library I checked, from Sparkfun, had no function to change the address, and the process sounds quite complex, so it may be worth persevering with that other library you found for a while longer.

What errors do you get compiling one of the example sketches that come with the library?

In file included from C:\Users\avsor\Documents\Arduino\libraries\107-Arduino-TMF8801-1.3.0\examples\TMF8801-Basic\TMF8801-Basic.ino:11:0:
C:\Users\avsor\Documents\Arduino\libraries\107-Arduino-TMF8801-1.3.0\src/107-Arduino-TMF8801.h:15:10: fatal error: 107-Arduino-Sensor.hpp: No such file or directory
 #include <107-Arduino-Sensor.hpp>
          ^~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
exit status 1
Error compiling for board Arduino Pro or Pro Mini.

It's a problem with the source code. They're trying to #include a file that doesn't actually exist.

I see. I assumed that I would be able to set it using the Arduino library and then use the new address in my Micropython code.

I'm using the Sparkfun board, and they have a resistor pulling ENable high by default.

The only steps that I'm not doing, as explained by the host driver communication manual, are steps 4 and 5:
image

but I don't know what the "patch" is referring to, so I don't know what it wants me to download to the device. The 107-Arduino library has a file called main_app_3v3_k2.h which is basically a huge array of hex codes, maybe you need a custom application to be loaded in order for it to let you change the address? But if that is the case, then this line from the datasheet doesn't make much sense:
image

Under which is the command register:
image

Which is the register that you need to send the address change command to. App0 is the distance measurement app. So basically, you can't change the I2C address unless the distance measurement app is running?

Didn't work with two sensors on the same bus.

I don't think so. Some i²c sensors have a small amount of eeprom memory and updated address is stored there permanently. There's no mention of anything like that I could see in the data sheet. So your python code will also have to change the address of 2 out of 3 sensors at startup.

Yes, I see. Try deleting that line from 107-Arduino-TMF8801.h Or create an empty file with the name 107-Arduino-Sensor.hpp in the library's folder.

It's good that the pull-up resistor is on the board, but that's not enough. The arduino has to control the ENable pin, setting it LOW and later HIGH in order to change the sensor address.

Yeah, I didn't understand that part either. That's why I said it seems complicated, and getting that library to compile, if possible, might be the best solution.

So kind of you to supply the wiring details that accompanied that experiment. Or did you not make any?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.