budryerson / TFMini-Plus-I2C

Arduino library for the Benewake TFMini-Plus LiDAR distance sensor in I2C communication mode
27 stars 16 forks source link

Framerate 0Hz not valid #29

Open renaudfv opened 2 months ago

renaudfv commented 2 months ago

After discussions with a Benewake rep engineer, he made us aware that 0Hz could not be used in I2C mode. That being said, we used the setting multiple time. Is there a possibility the sensor keeps an internal polling but says the frame rate setting command was succesful, despite it not being "valid"?

budryerson commented 2 months ago

I'm not entirely clear about what you're asking, but I'll try to answer anyway. • In UART (serial) mode, data return is asynchronous and repetitive at a user set Data Frame Rate between 1 and 1000Hz. When the Rate is set to 0Hz, a user must trigger the device with a command to return data as needed. • In I2C mode, a command is always needed to return data. The Data Frame Rate is irrelevant. I presume the engineer meant that in I2C mode a setting of 0Hz is unnecessary or undefined, rather than invalid. Please let me know if I'm wrong, or if I can be of any further help.

renaudfv commented 2 months ago

The UART part is fine, confusion comes from the use of I2C and internal frame rate setting of the sensor.

For exemple: In your exemple you set the framerate to 20hz and then use the getData method. How is the frame rate irelevent?

In our use case we'll:

  1. use the SET_FRAME_RATE command with FRAME_0 as an argument
  2. then we would ask the sensor to trigger a reading by sending the TRIGGER_DETECTION command
  3. then we'll get data by using the getData method

The engineer says that sending SET_FRAME_RATE with FRAME_0 is not possible in I2C.

budryerson commented 2 months ago

Well, that is peculiar! I assume that the Frame Rate command (line 112-118) was carelessly not deleted when I ported the UART example code into an I2C example. I don't see that it does anything. I have not tried sending a 0 Frame Rate command. I expect it also would not do anything. What happens in your use case? Does Step 1 return an error? What happens if you skip Step 1? May I see your code?

renaudfv commented 2 months ago

Looping in my colleague @evsc as well

Using a sensor for which I just change UART to I2C mode, and set addr to 1.

Step 1 returns: READY 5A 06 03 00 00 63 00 00 The whole serial logs would look like this:

Show all I2C addresses in Decimal and Hex. Scanning... I2C device found at address 1 (0x01 Hex) ---Soft reset---: Success Status: READY Data: 00 00 00 00 00 00 00 00 00 Status: READY 00 00 00 00 00 00 00 00 --- Firmware version ---: Got firmware: v2.0.9 --- Set Poll mode - Framerate = 0 ---: Set framerate: 0 Status: READY 5A 06 03 00 00 63 00 00 --- Trigger internal measurements ---: e: 0 Status: READY 5A 06 03 00 00 63 00 00 --- Get measurements ---: 286,2317,41 --- Trigger internal measurements ---: e: 0 Status: READY 5A 06 03 00 00 63 00 00 --- Get measurements ---: 284,2333,41 --- Trigger internal measurements ---: e: 0 Status: READY 5A 06 03 00 00 63 00 00 --- Get measurements ---: 285,2335,40 ....

Here's a sample of how we typically use the library:

#include <Wire.h>     // Arduino standard I2C/Two-Wire Library
#include <TFMPI2C.h>

TFMPI2C tfmP;         // Create a TFMini-Plus I2C object

// Declare variables
int I2C_total, I2C_error;
uint8_t oldAddr, newAddr;

// TFMini measurements
int16_t tfDist = 0;    // Distance to object in centimeters
int16_t tfFlux = 0;    // Strength or quality of return signal
int16_t tfTemp = 0;    // Internal temperature of Lidar sensor chip

int ID = 1;

boolean debug = true;

boolean firmwarePass = true;
boolean frameratePass = true;

void setup()
{
  Wire.begin();            // Initialize two-wire interface
  Serial.begin(115200);   // Initialize terminal serial port
  delay(20);

  Serial.flush();          // Flush serial write buffer
  while ( Serial.available())Serial.read(); // flush serial read buffer

  // Validate some sensor is online
  scanAddr();

  setup_sensor();

  delay(1000);
}

void loop()
{
  poll_sensor();
}

bool scanAddr()
{
    Serial.println();
    Serial.println( "Show all I2C addresses in Decimal and Hex.");
    Serial.println( "Scanning...");
    I2C_total = 0;
    I2C_error = 0;
    oldAddr = 0x10; // default address
    for( uint8_t x = 1; x < 127; x++ )
    {
        Wire.beginTransmission( x);
        // Use return value of Write.endTransmisstion() to
        // see if a device did acknowledge the I2C address.
        I2C_error = Wire.endTransmission();

        if( I2C_error == 0)
        {
            Serial.print( "I2C device found at address ");
            printAddress( x);
            // Set ID automatically
            ID = x;
            ++I2C_total;   //  Increment for each address returned.
            if( I2C_total == 1) oldAddr = x;
        }
        else if( I2C_error == 4)
        {
            Serial.print( "Unknown I2C error at address ");
            Serial.println( x);
        }
    }
    //  Display results and return boolean value.
    if( I2C_total == 0)
    {
      Serial.println( "No I2C devices found.");
      return false;
    }
    else return true;
}

// Print address in decimal and HEX
void printAddress( uint8_t adr)
{
    Serial.print( adr);
    Serial.print( " (0x");
    Serial.print( adr < 16 ? "0" : ""); 
    Serial.print( adr, HEX);
    Serial.println( " Hex)");
}

/**

*/
void setup_sensor() {
  Serial.println("---Soft reset---: ");
  if ( tfmP.sendCommand( SOFT_RESET, 0, ID))
  {
    Serial.print("Success\n");
    tfmP.printFrame();
    tfmP.printReply();
  }
  else {
    Serial.print("Soft reset Failed\n");
    tfmP.printFrame();
    tfmP.printReply();
  }

  Serial.println("--- Firmware version ---: ");
  if ( tfmP.sendCommand( GET_FIRMWARE_VERSION, 1, ID))
  {
    Serial.print("Got firmware: v");
    Serial.print((int)tfmP.version[0]);
    Serial.print(".");
    Serial.print((int)tfmP.version[1]);
    Serial.print(".");
    Serial.print((int)tfmP.version[2]);
    Serial.println();
  }
  else {
    Serial.print("Get firmware Failed\n");
    tfmP.printFrame();
    tfmP.printReply();
    firmwarePass = false;
  }

  Serial.println("--- Set Poll mode - Framerate = 0 ---: ");
  if ( tfmP.sendCommand( SET_FRAME_RATE, FRAME_0, ID))
  {
    Serial.println("Set framerate: ");
    Serial.println(tfmP.status);
    tfmP.printReply();
  }
  else {
    Serial.print("Set framerate Failed\n");
    tfmP.printFrame();
    tfmP.printReply();
    frameratePass = false;
  }

  if(!firmwarePass && !frameratePass) {
    Serial.println("---Hard reset---: ");
    if ( tfmP.sendCommand( HARD_RESET, 0, ID))
    {
      Serial.print("HR Success\n");
      tfmP.printFrame();
      tfmP.printReply();
    }
    else {
      Serial.print("Hard reset Failed\n");
      tfmP.printFrame();
      tfmP.printReply();
    }
  }
}

/**

*/
void poll_sensor() {
  if (debug) Serial.println("--- Trigger internal measurements ---: ");
  if (tfmP.sendCommand( TRIGGER_DETECTION, 0, ID))
  {
    if (debug) {
      Serial.println("e: ");
      Serial.println(tfmP.status);
      tfmP.printReply();
    }

  }
  else {
    if(debug) {
      Serial.print("Failed\n");
      tfmP.printFrame();
      tfmP.printReply();
    }
  }

  delay(10);

  if (debug) Serial.println("--- Get measurements ---: ");
  if (tfmP.getData( tfDist, tfFlux, tfTemp, ID))
  { 
    if(debug) {
      Serial.print((int)tfDist);
      Serial.print(",");
      Serial.print((int)tfFlux);
      Serial.print(",");
      Serial.print((int)tfTemp);
      Serial.println();
    }
  }
  else                  // If the command fails...
  {
    if(debug) {
      Serial.print("Failed\n");
      tfmP.printFrame();
      tfmP.printReply();
    }

  }
  delay(10);
}
evsc commented 4 days ago

Hey Bud, just to confirm what Renaud posted above.

I ran a little test:

In an I2C setup, with your I2C library, when setting the framerate to 0 tfmP.sendCommand( SET_FRAME_RATE, FRAME_0, ID)

the get_data command returns successfully (status == TFMP_READY), but all values are zero. tfmP.getData( tfDist, tfFlux, tfTemp, ID)

It requires the trigger_detection command to be called before the getData command, for it to return actual non-zero values.

tfmP.sendCommand( TRIGGER_DETECTION, 0, ID)
tfmP.getData( tfDist, tfFlux, tfTemp, ID)

So, don't delete those lines from the library :)

budryerson commented 4 days ago

I am not a Benewake engineer. This is my understanding of how the damn thing works:

• An internal chip generates Lidar data at 4Khz. A mixed signal processor (MSP) continuously averages that data, which is then sampled by the user. Normal, asynchronous, serial mode output sample rates (frame-rate) are between 1 and 1000. Lower rates deliver more accurate results because the amount of data averaged into each sample is larger. The default rate is 100. My example code sets the frame-rate to 20.

Frame-rate 0 is used only for low-power applications because it shuts down the chip and the MSP. When a trigger is received, the chips wake up, send a programmed burst of data to the MSP, average it, and send it serially to the user.

• In I2C mode, any frame-rate setting should be irrelevant, except when it is set to zero. In that case a trigger command must be sent before data can be sampled and read off of the MSP.

I could be wrong. When I get the time, I will play around with it more and let you know what I find out. I apologize for any inconvenience.

evsc commented 3 days ago

There's no inconvenience! Really interesting to learn how the sensors work internally. And thank you for creating and maintaining all the tfmini-libraries in general!

For context, we are using the frame-rate-0 mode, because have setups with multiple sensors, where the sensors face each other, or face reflecting surfaces. Triggering them by code, we could avoid that light beams from one sensor could potentially reflect into and interfere with another sensor.