Puck.js v2 Accelerometer/Gyroscope output...

Posted on
  • Hi Everybody,

    I've just recently begun working with the Puck.js v2 board. I updated the firmware to "espruino_2v07" immediately after powering it up. I'm starting a project hoping to count revolutions on a rotating spindle with the Puck's onboard LSM6DS3TR-C. Two questions:

    1) Is counting revolutions this way even possible (versus using a more traditional optical encoder or magnetic sensor approach)?

    2) When I manually execute "Puck.accel();" in the command line repeatedly (without moving the sensor at all), I get a huge range of output. Why is "Puck.accel();" returning such a variation in values?

    Sometimes I'll just get a dead response and have to cycle the power to fix:

    >Puck.accel()
    ={
      acc: { x: -16056, y: -5125, z: -151 },
      gyro: { x: 0, y: 0, z: 0 }
     }
    >Puck.accel()
    ={
      acc: { x: -16056, y: -5125, z: -151 },
      gyro: { x: 0, y: 0, z: 0 }
     }
    >Puck.accel()
    ={
      acc: { x: -16056, y: -5125, z: -151 },
      gyro: { x: 0, y: 0, z: 0 }
     }
    

    And sometimes the gyro output seems totally random (without physically moving the Puck at all):

    >Puck.accel()
    ={
      acc: { x: -8001, y: 2134, z: 107 },
      gyro: { x: 200, y: -9329, z: -14452 }
     }
    >Puck.accel()
    ={
      acc: { x: -7988, y: 2153, z: 101 },
      gyro: { x: -949, y: -5207, z: -12287 }
     }
    >Puck.accel()
    ={
      acc: { x: -7973, y: 2136, z: 115 },
      gyro: { x: -1532, y: -5256, z: -12355 }
     }
    >Puck.accel()
    ={
      acc: { x: -8023, y: 2158, z: 123 },
      gyro: { x: -4909, y: -13612, z: -11337 }
     }
    

    Thanks!

  • Sun 2020.10.04

    'output seems totally random'

    There is a note concerning the mag, presumably the gyro also:

    http://www.espruino.com/Puck.js#magnetom­eter

    Has E.getBattery() been used to determine the amount of battery charge remaining?


    http://www.espruino.com/Puck.js#accelero­meter-gyro

    Are the same flutuations observed when using the low power module?

    source:    http://www.espruino.com/modules/puckjsv2­-accel-movement.js

  • Thanks for the tip regarding the battery charge. However, E.getBattery() returns a value of 100. I actually have the Puck wired to two new 1.5v AAA batteries.

    Unfortunately, using the puckjsv2-accel-movement.js module also returns the same unpredictable results.

  • 1) Is counting revolutions this way even possible (versus using a more traditional optical encoder or magnetic sensor approach)?

    Yes, however I'd say only if what you're attaching the Puck to is moving slowly. Since you're unlikely to be able to get the accelerometer at the centre of rotation, at higher speeds the centrifugal force could cause issues.

    2) When I manually execute "Puck.accel();" in the command line repeatedly (without moving the sensor at all), I get a huge range of output. Why is "Puck.accel();" returning such a variation in values?

    You mean for the gyro? The accelerometer itself looks pretty good.

    In the gyro case I think it's an issue with the accelerometer - basically because Espruino turns the accelerometer on, then takes the first reading, then turns it off, the gyro doesn't get time to 'settle' before a reading is made.

    I bet if you run Puck.accelOn() first (to leave the accelerometer on), and then use Puck.accel() you'll get much better readings. What I'd suggest is you just respond as soon as you get accelerometer data (rather than polling):

    Puck.on('accel', function(d) {
     // ...
    });
    Puck.accelOn(12.5);
    

    The other thing you could try for counting revolutions is the magnetometer. For instance you could stick a magnet on whatever is rotating and then have the Puck next to it. You could use the XYZ values from the magnetometer to get a pretty good idea of absolute position.

  • Thanks Gordon, I was able to get better readings from the IMU after using Puck.accelOn()

    The application I'm working on is a bit unique. I need to count revolutions of a spindle with the Puck mounted inside the spindle itself. I can't use any external reference source like a magnet or light to count the revolutions. The spindle will spin infrequently, and at varying velocities (think of pulling a length of paper from a roll).

    I'm hoping the gyroscope might be a reliable source of data for this application (spinning on the z axis). If I can get the gyro to reliably output scaled values in degrees or revolutions per second, would it just be a matter of dividing the the output by the amount of time that it spun? Or is the math not that simple?

  • Ahh, interesting. Try this:

    Puck.accelOn();
    Puck.accelWr(0x11, 0b00011100); // scale to 2000dps
    
    var d = 0;
    Puck.on("accel", a=> d += a.gyro.z/64000);
    
    setInterval(function() {
      print(d.toFixed(2));
    }, 500);
    

    If you rotate the Puck around like it's a knob then it will do a pretty convincing job of counting rotations (I can't promise it's actually calibrated in any way though). However it will wander off over time because the gyro does have noise.

    How is the spindle mounted? Because if it's horizontal then you've also got gravity, so you could detect when the Puck hasn't moved for a while and then use gravity to get an 'absolute' reading for rotation...

    This one seems to work reasonably well - it uses a slightly higher data rate of 26Hz which I think works better:

    Puck.accelOn(26); // 26Hz
    Puck.accelWr(0x11, Puck.accelRd(0x11)|0b00001100); // scale to 2000dps
    
    var d = 0, lastAccel;
    var timeStationary = 0;
    Puck.on("accel", r=> {
      lastAccel = r;
      d -= r.gyro.z/128000;
      var a = r.acc;
      a.mag = a.x*a.x + a.y*a.y + a.z*a.z;
      a.ang = Math.atan2(a.y,a.x)/(2*Math.PI);
      if (a.mag < 66000000 || a.mag > 71000000) {
        timeStationary = 0;
      } else {
        if (timeStationary<100) timeStationary++;
        else {
          // if stable for a while, re-aligh turn count
          var nearest = Math.round(d)+a.ang;
          d = d*0.8 + nearest*0.2;
        }
      }
      
    });
    
    setInterval(function() {
      print("rotation",d.toFixed(2), 
            "abs",lastAccel.acc.ang.toFixed(2), 
            "stationary", timeStationary);
    }, 500);
    

    However with that kind of thing it's drawing around 1mA, so you could expect a battery life of around 10 days. You might also want to consider combining it with the movement detection on http://www.espruino.com/Puck.js#accelero­meter-gyro to get it to enter a low power sleep mode when nothing is happening.

  • Wow! Thanks, Gordon. Both of those examples seem to be working well. A couple questions:

    1) In both examples, the gyro z-axis data is added to itself, then divided by a constant (64,000 or 128,000). Could you help me understand where you got those numbers from?

    2) The second example uses a.mag and a.ang... I don't see these referenced in the API docs. Is 'mag' using the magnetometer? If so, what for? Where do I find more info about these two properties?

    3) The math in the second example gets over my head. Any insight into that would be great (but don't feel obligated if there is not a simple way to explain it.)

    Thanks again!

  • I actually just added this to the Espruino App loader as well: https://espruino.github.io/EspruinoApps/­#puckrotate

    You can get the code by clicking the GitHub icon but the code has some power saving built in too which will really increase the battery life.

    1) In both examples, the gyro z-axis data is added to itself, then divided by a constant (64,000 or 128,000). Could you help me understand where you got those numbers from?

    Honestly, I started out calculating it based on 2000dps, 26 samples per second, and 360 degrees but then I just rotated the Puck by 360 degrees looked at the value I got, and divided by that - so it's almost certainly not exactly right.

    2) The second example uses a.mag and a.ang... I don't see these referenced in the API docs. Is 'mag' using the magnetometer? If so, what for? Where do I find more info about these two properties?

    They're created in line 10/11. Just added to the acceleration object because then I could see what they were by looking at lastAccel in the console.

    • mag is the size of the acceleration (squared). If the Puck isn't moving it should be the same as gravity (8192*8192 because of the scaling)
    • ang is the angle based on X and Y

    3) The math in the second example gets over my head. Any insight into that would be great (but don't feel obligated if there is not a simple way to explain it.)

    Yes, sorry - it was a quick hack so I didn't document it too well...

    // work out the rotation from the gyro - this line is all you need
    // if you don't care alignment with gravity
    d -= r.gyro.z/128000;
    
      var a = r.acc;
    // work out the magnitude of acceleration squared 
      a.mag = a.x*a.x + a.y*a.y + a.z*a.z;
    // work out the angle (as a value between 0 and 1)
      a.ang = Math.atan2(a.y,a.x)/(2*Math.PI);
    // if magnitude is more or less than gravity we assume we've moved
      if (a.mag < 66000000 || a.mag > 71000000) {
        timeStationary = 0;
      } else {
    // otherwise we start incrementing a counter
        if (timeStationary<100) timeStationary++;
    // when we've been stationary for 100 samples (~4 seconds)
    // start re-adjusting the value based on gravity
        else {
    // work out the nearest rotation value that matches with the rotation
    // we're getting based on gravity. 
    // a.ang is the fractional amount of rotation (-0.5 .. 0.5)
    // Math.round(d) is the nearest whole rotation
          var nearest = Math.round(d)+a.ang;
    // then we do a simple average (we don't set the value right away)
          d = d*0.8 + nearest*0.2;
        }
      }
    
  • Hi Gordon,

    I've been playing around with the puckrotate code you have added to the Espruino App loader. I really like how you've added the sleep/awake functionality. However, I'm running into a bit of trouble after the Puck wakes (using the "puckjsv2-accel-movement" module) from the first time it has fallen asleep. The scaling that was initially, say 12.5Hz/2000dps, set seems to be erased (or overwritten) and where a single revolution might have outputted 1.00, now outputs as 4 or 5 revolutions.

    This only happens after the Puck is waken from its first sleep, and the only way I can seem to set the device straight again is by power cycling it. This makes me think that the issue is with the accel/gyro chip itself, perhaps? It's almost as if the 12.5Hz data rate gets stuck at 26Hz and can't be changed back by writing to that register again.

  • That's really odd. And this is using https://espruino.github.io/EspruinoApps/­#puckrotate unmodified?

    What firmware version do you have on your Puck?

  • Ok, after a bunch of messing around I figured out what's been giving me trouble. In the process, I've learned a few things, but now have a bunch more questions... hehehe.

    1) Is it true that the gyro is the more "power hungry" part of the IMU vs the accelerometer?

    2) The puckjsv2-accel-movement module puts the accel/gyro sensor in a low power mode mostly by turning off the gyro, correct? Why does it put CTRL1_XL to 12.5Hz?

    3) Would you be willing to make some more comments about the sleep() and wake() functions in the code?

    function wake() {
      digitalPulse(LED1,1,10); // indicate awake red
      timeStationary = 0;
      advCounter = 0;
      Puck.removeAllListeners('accel');
      Puck.on("accel", onAccel);
      Puck.accelOn(26); // 26Hz
      Puck.accelWr(0x11, Puck.accelRd(0x11)|0b00001100); // scale to 2000dps
    }
    
    function sleep() {
      digitalPulse(LED2,1,10); // indicate sleeping green
      var last = getTime()+2;
      Puck.removeAllListeners('accel');
      Puck.on('accel',function(a) {
        if (getTime()<last) return; // ignore events while accelerometer settles
        require("puckjsv2-accel-movement").off()­;
        wake();
      });
      require("puckjsv2-accel-movement").on();­
    }
    

    4) For instance, in the sleep() function, why run puckjsv2-accel-movement.off(), then wake(), then puckjsv2-accel-movement.on() in that order?

    5) I seem to be seeing some lag in the gyro output immediately after waking from sleep(). Is that to be expected? Does the gyro take a moment to turn on after being shut off with Puck.accelWr(0x11,0);?

  • 1) Is it true that the gyro is the more "power hungry" part of the IMU vs the accelerometer?

    Yes - I think mainly it's that for the gyro to be useful everything needs to be running more quickly - so more power.

    2) The puckjsv2-accel-movement module puts the accel/gyro sensor in a low power mode mostly by turning off the gyro, correct? Why does it put CTRL1_XL to 12.5Hz?

    Yes - otherwise it's drawing a lot more power. Putting the accelerometer into the 12.5Hz mode helps it get much lower power consumption.

    4) For instance, in the sleep() function, why run puckjsv2-accel-movement.off(), then wake(), then puckjsv2-accel-movement.on() in that order?

    The function is using a callback for Puck.on('accel' so the indented code doesn't execute immediately.

    So in reality it uses puckjsv2-accel-movement.on() to start listening for movement.

    Then when movement is detected it does puckjsv2-accel-movement.off() to turn the accelerometer off and hopefully turn off movement detection, then calls wake() to configure it properly with the gyro.

    5) I seem to be seeing some lag in the gyro output immediately after waking from sleep(). Is that to be expected? Does the gyro take a moment to turn on after being shut off with Puck.accelWr(0x11,0);?

    I think that's possible, yes. I'm not sure but it may take a while to calibrate itself - it shouldn't be long though.

  • On power usage - the gyroscopic effect requires motion, so that's why gyro is significantly higher power than accelerometer. Accelerometer is just a mass on a spring, I think MEMS gyros tend to be tuning-fork type arrangements that are induced to vibrate at a certain frequency.

    Therefore it's also likely there's a bit of a warm-up time, and the one-shot call will probably never return reasonable values for the gyro.

    @BrianH - if your spindle is fixed, ie there will never be linear motion of it, and you can mount the puck so the Z axis is aligned with the spindle axis, then using purely the accelerometer might be best. In that setup it should measure gravity without any other accelerations (if it's not on the axis you'd get some centripetal acceleration mixed in).

    For each rotation the accelerometer value (in either x or y) will basically be a sinusoid - so you can just count a rotation each time there's a sign change from -ve to +ve in acc.x for example. No drift to worry about, power will be lower (if you manually turn off gyro via register), and I suspect maximum supported rotational speed is higher (2000 dps max scale for gyro is still "only" 5.5 full rotations per second, so with an accelerometer at 12.5Hz or above you should be able to beat that).

  • Thanks so much for the insights, @Gordon & @SimonT. Your thorough explanations are most helpful!

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Puck.js v2 Accelerometer/Gyroscope output...

Posted by Avatar for BrianH @BrianH

Actions