BMA421 Accelerometer

Posted on
  • I have been using Espruino on an Gokoo M11 watch, with broadly similar specs to a Pinetime or older model of Colmi P8.
    It includes a BMA421 accelerometer and an HRS3300 heart rate monitor. The former also offers built in step counter as seen at https://www.youtube.com/watch?v=pqnowZd2­_KQ

    I can use this C++ code library successfully (on the ATCWatch Arduino firmware) to step count and to wake the watch.
    Is there any way to implement this for Espruino - or do I need to create my own build and compile the driver into it?

  • Is there any way to implement this for Espruino - or do I need to create my own build and compile the driver into it?

    yes, both ways are possible. if you want to use that library 'as is' then linking it to your espruino build may be much easier. otherwise it can be done from inline C or even JS if it is not too complicated - just read/write registers according to datasheet and setting a watch on interrupt pin

  • Thanks for that (sorry for the duplicate DM, thought it better to ask in forum). There is some simpler code from Daniel Thompson which might be easier to include. I think the BMA421 needs priming with a binary blob before initialising, which takes more steps.
    I'm more nervous about modifying the 'firmware' Espruino build at the moment, because I have no way to recover a bricked device. Rewriting in JS would be time consuming, and also make it harder to keep up with any changes upstream.

  • nice, looks like he removed the 423 from that. but still it is quite a lot of code. when one would start from scratch with datasheet and just write code for needed features it could be quite simple and short but it needs understanding the HW more so not sure it is worth it for you

    I'm more nervous about modifying the 'firmware' Espruino build at the moment, because I have no way to recover a bricked device.

    You actually did not mention how you update it, does it have nordic bootloader ? or was it using Da Fit app? which exact espruino build and bootloader you have in the watch now? you built it yourself already?

    to help with this I have a bootloader that vibrates after poweron from dead battery and you have some time to touch it and enter bootloader so you can replace even completely broken build. For less broken builds using watchdog helps. also normal espruino bootloader build for your device checks if BTN1 is pressed at boot time and stays in bootloader. However capacitive sensors do not work well when already held before powering on so that's why I added code so it vibrates and you need to touch it later.

    Rewriting in JS would be time consuming, and also make it harder to keep up with any changes upstream.

    which upstream you mean, the BMA driver or espruino? the driver won't probably change for this old HW, they will possibly just add newer devices to it.

  • It's a bit of mix at the moment. I am using your P8 SDK12 bootloader and the 2.09 firmware from @jeffmer. I update with the DaFlasher, but it does use the nordic bootloader, SD3.0. Is there an Espruino bootloader for this device? Would there be any advantage?
    Unfortunately, the M11 button is on D20, not D17, so the P8 bootloader would need rebuilding for this device if I want to use this strategy.
    I might spend some time reading the driver as you suggest. I have a background in C, but not much javascript - most of it is definitions and interfaces, there is not much actual code because the magic happens inside the black box. I would be happy to submit a module at the end of the process if it works.
    I'm not going to risk rebuilding the firmware unless it becomes essential.

  • I am using your P8 SDK12 bootloader .... Is there an Espruino bootloader for this device?

    The one you have is Espruino bootloader however it is build for P8 so button BTN1 is expected on pin 17. And that pin is inverted and set as input with pull up at boot time, which may cause you some troubles if you have something else on that pin as even when changing mode later the value is still inverted.

    Would there be any advantage?

    yes, by default the button allows you to enter bootloader and skip running your saved code when it is held over 3 seconds at boot time - helps if you save broken js code (e.g. like calling NRF.setServices(xx,{uart:false}) at startup, people are quite creative in this)

  • I have tried... and failed. The watch still works, but my efforts to send config bytes to the BMA421 have somehow put it the chip into an unreponsive state. I suspect I have overwritten its NVRAM somehow, as it won't even respond to a reset command.

    Even powering down the watch doesn't seem to bring it back, and any reads from the BMA421 registers just return 0xFF.

    Any ideas?

  • Even powering down the watch doesn't seem to bring it back

    Did you actually power it down by removing the battery? In these devices everything is always powered even when the watch is off, so if you'd configured the accelerometer you wouldn't expect it to reset until the battery was removed (or totally flattened).

    any reads from the BMA421 registers just return 0xFF

    Could be the I2C address has been reconfigured? Or it's been put into SPI mode?

    You could try accessing it by any other I2C addresses it might have

  • what you mean by 'powering down' the watch, you took it apart and disconnected battery? or let the battery die so it boots from scratch when charger is attached?

    as for all FFs it may be some issue with i2c, Espruino has both HW i2c - the I2C1 object or software i2c - new I2C() so you can try the other one, possibly after running E.reboot() just in case. also there is i2c scan code you can try to see it still responds, see e.g. code here http://forum.espruino.com/conversations/­278556/#comment13444645

    EDIT: oh gordon already responded with similar hints

  • Thanks for the hints - I will explore further. It looks like SPI mode could be the reason. Writing to bit 1 of register 0x70 disables I2C. There are many 0x70 bytes in the config file, so I could easily have done that.

    It looks quite hard to overwrite the NVRAM, you need a distinct sequence of bytes (or a byte and a bit) that I can't find anywhere in my config file.
    I don't think it has changed I2C address - that would require rewiring.
    I have tried to flatten the battery by running a firmware (no sleep or shutdown) until it dies and won't power on. However, it is possible that there is enough power in the battery to keep the sensor alive. I haven't taken the watch apart.
    I found a poster on the Borsch forum that had a similar problem, and solve it by a power cycle, so I will try running down the battery again and leaving for a few days (or weeks?) to see if that helps to flatten it totally!

  • Thank you both - it looks like that has worked. I think that I accidentally wrote to the register which switched the sensor to SPI mode, which is why I couldn't see it again.
    Fortunately, a battery discharge followed by 48 hours in a drawer has reset the sensor, and it now appears to be working again.

  • Ok, going back to my original question:

    I've taken the suggestion from @fanoush and just written a short module in javascript for the BMA421 from scratch. All it does it load the binary code to set up the step counter and then reads steps and xyz acceleration. It seems to all work, for not many lines of code, so I'm happy with that as an approach.
    My eventual solution was just to turn the bytes from the config file into a binary file that I upload to Storage. I then read it in chunks (I don't think this really uses memory?) and write each chunk to the sensor. Everything else is very similar to the example code in the ATCWatcH project. The datasheet has several errors / omissions in it, but it helped to get started.
    BMA421 doesn't have twist interrupts, so I will have to check that on an interval if I want to turn on the screen that way.

  • I then read it in chunks (I don't think this really uses memory?) and write each chunk to the sensor.

    file from storage does not take extra memory, the read just gets direct pointer to bufferbacked by the flash storage, so you can get whole file, no need to split into blocks due to memory. not sure how big blocks i2c can write in one call, maybe there is no limit either, so depends what is reasonable for the sensor to receive in one write.

    BTW, maybe you can copy your BMA421 code here or link it, could be helpful for other people in future

  • Thanks. Tried to open whole file with Storage.readArrayBuffer but ran out of heap memory, so chunked instead. I will try a larger chunk to see what works, now I can see if I am successful.

    I will upload code when I have tweaked it, and maybe submit somewhere as well. I am trying to write as a proper module, but I have zero JavaScript. It would help Pinetime owners, at least. I think only older P8s have BMA421.

  • strange, the comment about not using RAM is only in this description https://www.espruino.com/Reference#l_Sto­rage_read , not in readArrayBuffer, but they should be similar. if not, try read() and convert string via E.toArrayBuffer. I always used read() for some reason.

  • Would I have a a problem with terminators (0xFF) in the string? There are a few in the data.

  • not an issue with Storage api, it is the StorageFile api that has this issue (but allows appending to/extending the file - 0xff marks EOF in such files).

  • This is current state of code - just enough to demonstrate working. Various things still need adding, e.g. events and scaling the accel vector.

    /*
     Espruino module for BMA421/BMA423 motion sensor
     */
    
    
    //delete the next line for module
    var exports={};
    
    //create an instance - initialise if necessary (or just reconnect)
    function BMA421(_i2c) {
      this.i2c = _i2c;
      this.enabled = (this.checkstatus()==1);
      if (!this.enabled) this.initialise();
    }
    
    
    BMA421.prototype.initialise = function() {
    //reset sensor
      this.writeReg(0x7E,0xB6); 
    //disable power save
      this.writeReg(0x7C,0x00); 
      this.loadConfig();
    //Accl Enable
      this.writeReg(0x7D, 0x04);
    //Acc Config
      this.writeReg(0x40, 0b00101000);
    //Enable and Reset Step Counter - this will also enable power save
      this.resetSteps();
      this.enabled = (this.checkstatus()==1);
    }
    
    //x and y are swapped - could remap in hardware
    BMA421.prototype.getAccel = function() {
      var acc = { x: 0, y: 0, z: 0 };
      if (this.enabled) {
        var data = this.readBytes(0x12,6);
        acc.x = (data[3] << 8) | data[2];
        if (acc.x > 32767) acc.x -= 65536;
        acc.y = (data[1] << 8) | data[0]; 
        if (acc.y > 32767) acc.y -= 65536;
        acc.z = (data[5] << 8) | data[4];
        if (acc.z > 32767) acc.z -= 65536;
      }
      return acc;
    }
    
    BMA421.prototype.getSteps = function() {
      if (this.enabled) {
        var steps = this.readBytes(0x1E,4);
        return (steps[3] << 24) + (steps[2] <<16) + (steps[1] << 8) + steps[0];
      }
      else return 0;
    }
    
    //Temperature always seems to be 25
    BMA421.prototype.getTemp = function() {
        if (this.enabled) {
          return this.readBytes(0x22,1)[0] +23;
      }
      else return -300;
    }
    
    //enables the step counter, disables the step detector
    BMA421.prototype.resetSteps = function() {
      this.writeReg(0x7C, 0x00);//Sleep disable
      var feature_config = new Uint8Array(70);
      feature_config = this.readFeatures(70);
      feature_config[0x3A + 1] =  0x34; 
      this.writeFeatures(feature_config, 70);
    //Sleep Enable
      this.writeReg(0x7C, 0x03);
    }
    
    //burst write data to a register    
    BMA421.prototype.writeReg = function(r,d) {
        this.i2c.writeTo(0x18,r,d);
    }
    
    //read a given number of bytes from a register
    BMA421.prototype.readBytes = function(r,l) {
      this.i2c.writeTo(0x18,r);
      return this.i2c.readFrom(0x18,l);
    }
    
    //read a single byte from a register
    BMA421.prototype.readReg = function(reg) {
        this.i2c.writeTo(0x18,reg);
        return this.i2c.readFrom(0x18,1)[0];
    }
    
    //burst read bytes from the feature config
    BMA421.prototype.readFeatures = function(l) {
      this.i2c.writeTo(0x18,0x5E);
      return this.i2c.readFrom(0x18,l);
    }
    
    //burst write bytes to the feature config
    BMA421.prototype.writeFeatures = function(config) {
      this.i2c.writeTo(0x18,0x5E,config);
    }
    
    //Config file as a binary blob - write in chunks
    BMA421.prototype.loadConfig = function () {
      var buffer_size = 64;
      var config =  new Uint8Array(buffer_size);
    //initialise config
      this.writeReg(0x59,0x00); 
      for (var i=0;i<6144; i+=buffer_size) {
        config = require("Storage").read("bma421_config.b­in",i,buffer_size);
        this.i2c.writeTo(0x18,0x5B,  (i / 2) & 0x0F);
        this.i2c.writeTo(0x18,0x5C, (i / 2) >> 4);
        this.i2c.writeTo(0x18,0x5E,config);
      }
    //enable sensor features
      this.writeReg(0x59,0x01); 
    }
    
    //LSB of status register is 1 for working
    BMA421.prototype.checkstatus = function() {
        return (this.readReg(0x2A) & 0x1F);
    }
    
    exports.connect = function(_i2c) {
      return new BMA421(_i2c);
    }
    

    BTW, I have no experience of JavaScript, only C/Pascal/Delphi (and a little python), so I'm not convinced by the object model here at all.


    1 Attachment

  • Thanks. In fact I do have one P8 with BMA421 so will test. I am not good at JS so cannot comment style, just few optimization tips

    • byte/sign conversions can be done also via various JS array types, with KX023 I used something like

      var coords=new Int16Array(accRead(0x06,6).buffer);
      return {x:coords[0],y:coords[1],z:coords[2]};
      

      which does same thing as your lines 36-42. The array constructor with buffer shares same underlying data with no copy so you just get different view on same data (also any write to such arrays is shared so conversion can work in both directions). Of course it works only if you are sure about endianness and it matches what sensor uses. Or there is DataView https://www.espruino.com/Reference#DataV­iew

    • line 103 (var config = new ...) is not needed, you replace it with the read result anyway (and you could possibly read whole file at once as discussed but it doesn't matter)

  • Thanks. I was just reading https://developer.mozilla.org/en-US/docs­/Web/JavaScript/Typed_arrays when you replied! Much neater.
    I suspect 103 was redundant, as it is really a pointer assignment, not a copy.

  • I should also divide x y and z by 16 as only 12 bits are used.

  • At the moment it is all working, but I am polling the sensor 10 times a second to look for wrist twist, which is putting a bit of a strain on battery life, even when the watch isn't being worn. I'll look into the 'movement' interrupt, but am unsure if this is specific to BMA423. It won't hurt to try. The datasheet implies you can set a threshold movement in each axis to trigger the interrupt.

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

BMA421 Accelerometer

Posted by Avatar for Mark_wllms @Mark_wllms

Actions