sb-ocr / diy-spacemouse

A DIY navigation device for Fusion360
Other
1.28k stars 171 forks source link

TLV493d - ADC hang up in Master Controlled or Fast Mode #21

Open Burke111-DEV opened 5 months ago

Burke111-DEV commented 5 months ago

How are you guys getting around the ADC hangup issue for the magnetometer?

I'm constantly getting lockups making it unusable.

geroulas commented 5 months ago

Same here.. works for a few minutes, then stops. Any suggestions?

geroulas commented 5 months ago

@Burke111-DEV This code (https://github.com/Launcherspider/3D-Spacemouse) worked for me on Windows.. No hangups. Still this projects feels a bit unusable, there is no smooth motion, no zoom.. It's a cool project but that's all, you cant work with this spacemouse.

Burke111-DEV commented 5 months ago

@geroulas Thanks for sharing that project. Unfortunately, that project just reposted precisely the same arduino sketch file that Salim created. Any performance improvements are likely purely coincidental. The hangup issue is unpredictably intermittent, so it may just be that you haven't experienced it yet... My device sometimes lasts over an hour without issue, sometimes not even 10s. It's completely random.

I've made a lot of changes to Salim's original diy-spacemouse.ino file. Key features I've added are

I'm quite happy with how it has turned out, and sounds like you might be interested in these features.

But I don't want to release my project details and files until I've got a solution to the intermittent hangup issue,,


For awareness, this is definitely a bug in the magnetometer, and not a quirk in the code. See:

Additionally, the datasheet even mentions that it is a known issue, and comments in the official Infineon library for this sensor make reference to it.

Supposedly, the fix is to try to "reset" the device. But none of my attempts to do so have been successful. Was hoping someone might know something.

geroulas commented 5 months ago

@Burke111-DEV woww man.. I was sure that was a different code.. but you are right! I was comparing it to the code I was running and I have altered some things compared to the original. I've been running it all moring without issues. Now that i got back from work, it hangs if I leave it alone for some minutes.. I also noticed that it wont work If Serial Monitor is not open. Íf I plug in the device, it will run for a few seconds then stops, if I open Arduino and Serial Monitor it stars working again. I close serial monitor and stops working. Maybe I've done something wrong, or maybe that helps you in some way...

In my case, I use the Waveshare RP2040-Zero, and in order to make it work I changes this Wire.begin(); mag.begin(Wire); Wire1 didn't work for me.

I will follow your links and try the reset method, see if I come up with something. I'm not really familiar with Arduino projects, but I will experiment.

I would love to see your project when you decide to release it! It will give life to this device for sure!

Burke111-DEV commented 5 months ago

@geroulas Regarding your issues with Wire vs Wire1:

Salim uses the Adafruit QT PY RP2040 in the original design. In particular, it is good for this project because it has the STEMMA connector which makes connecting up to the Adafruit TLV493d magnetometer module much simpler. It's not that either of those two are required, just that they're a good match for ease of use (i.e., no need to solder the I2C pins). So you're free to use any board, just you may need to do some more soldering.

The Adafruit QT PY RP2040 has two I2C interfaces - i2c0 and i2c1. Both are usable, just two separate busses. To communicate on i2c0 your code should use Wire. To communicate on i2c1 your code should use Wire1.

So presumably, the I2C bus you wired your magnetometer to on your RP2040uC board is the i2c0 one, so you need Wire instead of Wire1 :)


If I can't find a solution to the intermittent issue soon, I'll likely just publish the details of my project anyway and hope someone sees it and can help solve the I2C lockup on there.

geroulas commented 5 months ago

@Burke111-DEV Thanks for the input, I did read about all these, and now i'm in a good place now.

I think I have a something that might be working. I'm trying to re-initialize the sensor when it hangs.

I've added a code that compares the last values of the sensor with the previous ones. If xCurrent, yCurrent & zCurrent are exactly the same with xLast, yLast, zLast that must mean the sensor hang and reports the same values again and again. It did work some times, I saw the code reporting back.

I've also remapped the two buttons. One button is jsut running the code to initialise the sensor. So when I see the knob not responding, I press the button and it's live again.

I read somewhere that by sending a 0x00 singal to the I2C it resets the sensor, so that's what i did here. I think it might be working even wihtout this and you just have initialize....

Also the entire code is doing Keyboard.press() and Keyboard.release all the time, I thinks that's bad for the controller. So I changed that part as well and I think it helps. Especially the shift key was not behaving well. The program was freezing and the Shift button was still pressed. For now I have it running only in Orbit mode (Fusion360).. just to see how it behaves.

Check the code, see if you can replicate this.

#include <TinyUSB_Mouse_and_Keyboard.h>
#include <OneButton.h>
#include <Tlv493d.h>
#include <SimpleKalmanFilter.h>
#include <Wire.h>

Tlv493d mag = Tlv493d();
SimpleKalmanFilter xFilter(1, 1, 0.2), yFilter(1, 1, 0.2), zFilter(1, 1, 0.2);

// Setup buttons
OneButton button1(26, true);
OneButton button2(27, true);

float xOffset = 0, yOffset = 0, zOffset = 0;
float xCurrent = 0, yCurrent = 0, zCurrent = 0;
float xLast = 0, yLast = 0, zLast = 0;
int unchangedCount = 0;
const int maxUnchangedCount = 100;

int calSamples = 500;
int sensivity = 30;
int magRange = 3;
int outRange = 127;      // 127 Max allowed in HID report
float xyThreshold = 0.4; // Center threshold

int inRange = magRange * sensivity;
float zThreshold = 3;

bool isOrbit = false;

void setup()
{

  button1.attachClick(leftButton);
  button1.attachLongPressStop(leftButton);

  button2.attachClick(rightButton);
  button2.attachLongPressStop(rightButton);

  // mouse and keyboard init
  Mouse.begin();
  Keyboard.begin();

  Serial.begin(9600);
  Wire.begin();

  // mag sensor init
  mag.begin(Wire);
  mag.setAccessMode(mag.MASTERCONTROLLEDMODE);
  mag.disableTemp();

  // crude offset calibration on first boot
  for (int i = 1; i <= calSamples; i++)
  {

    delay(mag.getMeasurementDelay());
    mag.updateData();

    xOffset += mag.getX();
    yOffset += mag.getY();
    zOffset += mag.getZ();

    //Serial.print(".");
  }

  xOffset = xOffset / calSamples;
  yOffset = yOffset / calSamples;
  zOffset = zOffset / calSamples;

  xCurrent = xFilter.updateEstimate(mag.getX() - xOffset);
  yCurrent = yFilter.updateEstimate(mag.getY() - yOffset);
  zCurrent = zFilter.updateEstimate(mag.getZ() - zOffset);

  //Serial.println();
  Serial.println(xOffset);
  Serial.println(yOffset);
  Serial.println(zOffset);
}

void loop()
{

  // keep watching the push buttons
  button1.tick();
  button2.tick();

  xLast = mag.getX();
  yLast = mag.getY();
  zLast = mag.getZ();

  // get the mag data
  delay(mag.getMeasurementDelay());
  mag.updateData();

  // update the filters
  xCurrent = xFilter.updateEstimate(mag.getX() - xOffset);
  yCurrent = yFilter.updateEstimate(mag.getY() - yOffset);
  zCurrent = zFilter.updateEstimate(mag.getZ() - zOffset);

  //mReset I2C if Sensor Hangs
  //Compare current Mag values with previous values
  if(xLast == mag.getX() && yLast == mag.getY() && zLast == mag.getZ())
  {
    unchangedCount++;
  }
  else
  {
    unchangedCount = 0;
  }

  if (unchangedCount >= maxUnchangedCount)
  {
    Serial.println();
    Serial.print("RESETING");

    Mouse.release(MOUSE_MIDDLE);
    Keyboard.releaseAll();

    Mouse.begin();
    Keyboard.begin();

    Wire.beginTransmission(0x00);
    Wire.write(0x00);
    Wire.endTransmission();
    delay(1000);
    Wire.begin();
    mag.begin(Wire);
    mag.setAccessMode(mag.MASTERCONTROLLEDMODE);
    mag.disableTemp();

    Serial.println();
    Serial.print("RESET DONE");
  }

  // check the center threshold
  if (abs(xCurrent) > xyThreshold || abs(yCurrent) > xyThreshold)
  {
    int xMove = 0;
    int yMove = 0;

    // map the magnetometer xy to the allowed 127 range in HID repports
    xMove = map(xCurrent, -inRange, inRange, -outRange, outRange);
    yMove = map(yCurrent, -inRange, inRange, -outRange, outRange);

    // press shift to orbit in Fusion 360 if the pan threshold is not corssed (zAxis)

    /*
    if (abs(zCurrent) < zThreshold && !isOrbit)
    {
      Keyboard.press(KEY_LEFT_SHIFT);
      isOrbit = true;
    }
    */

    // pan or orbit by holding the middle mouse button and moving propotionaly to the xy axis
    if (!Mouse.isPressed(MOUSE_MIDDLE))
    {
      Keyboard.press(KEY_LEFT_SHIFT);
      Mouse.press(MOUSE_MIDDLE);
      Serial.println();
      Serial.print("Mouse_Middle PRESSED");
    }

    Mouse.move(yMove, xMove, 0);
  }
  else
  {
    if (Mouse.isPressed(MOUSE_MIDDLE))
    {
      Mouse.release(MOUSE_MIDDLE);
      Keyboard.releaseAll();
      Serial.println();
      Serial.print("Mouse_Middle RELEASED");
    }
  }

  Serial.println();
  Serial.print(mag.getX());
  Serial.print(",");
  Serial.print(mag.getY());
  Serial.print(",");
  Serial.print(mag.getZ());
}

// go to home view in Fusion 360 by pressing  (CMD + SHIFT + H) shortcut assigned to the custom Add-in command
void leftButton()
{
  Mouse.press(MOUSE_MIDDLE);
  Mouse.release(MOUSE_MIDDLE);
  Mouse.press(MOUSE_MIDDLE);
  Mouse.release(MOUSE_MIDDLE);
}

// fit to view by pressing the middle mouse button twice
void rightButton()
{
  Serial.println();
  Serial.print("RESETING");

  Mouse.release(MOUSE_MIDDLE);
  Keyboard.releaseAll();

  Mouse.begin();
  Keyboard.begin();

  Wire.beginTransmission(0x00);
  Wire.write(0x00);
  Wire.endTransmission();
  delay(1000);
  Wire.begin();
  mag.begin(Wire);
  mag.setAccessMode(mag.MASTERCONTROLLEDMODE);
  mag.disableTemp();

  Serial.println();
  Serial.print("RESET DONE");
}

EDIT: I Changed to code to compare Last Values but over a period of iterations maxUnchangedCount=100. So If the sensor report the exact same values for 100 times it will reset.

Burke111-DEV commented 4 months ago

@geroulas Not sure if that would work, but the general idea isn't wrong. Interested to know if it worked for you.

On my end, I think I may have solved it. It requires a change to the code inside the Tlv493d library files. I just put up a comment explaining it under an issue I made on the Infineon project's repo: https://github.com/Infineon/TLV493D-A1B6-3DMagnetic-Sensor/issues/25

Due to the completely random occurence of the bug, it's difficult to say with 100% certIainty that this is a definite fix. But I have used my sensor for extended periods of time (4+ hrs) without issue for over a week now.

So I will post my code and 3D files when my free time permits :)

Burke111-DEV commented 4 months ago

@geroulas

I made a fork of Infineon's lirbary, adding the required fix to get it working. https://github.com/Burke111-DEV/TLV493D-3DMagnetic-Sensor-With-Hangup-Recovery

Also find a basic usage example on the readme. Hope this helps :)

I'll also put the rest of my project code and files up when I get a chance to organise it. For now, swapping infineon's ltlv493d library for mine should be enough to fix @sb-ocr 's code.