pimoroni / icm20948-python

Python library for the Pimoroni ICM20948 breakout
https://shop.pimoroni.com/products/icm20948
MIT License
48 stars 22 forks source link

Read data from DMP #1

Open Takaklas opened 5 years ago

Takaklas commented 5 years ago

Hello pimoroni team,

I would like to ask if there are any plans to enable DMP and read orientation data from this device, using this library, if possible! It will be for me the most amazing feature and a take-my-money move!

Thank you in advance.

Gadgetoid commented 5 years ago

I'm looking into it. Right now I'm struggling to find any clear instrutions for this specific chip detailing how to upload and use the DMP firmware and any DMP-related registers - bar the one that has the DMP_EN bit - are simply missing from the ICM 20948 datasheet. Looking at the eMD SmartMotion code reveals a lot of registers which fit into the gaps left in said datasheet, so I feel like I'm on the right path. Unfortunately the SmartMotion code is an enterprisey spaghetti mess that's not easy to decypher.

The setup process seems to involve flashing some 16Kb proprietary binary firmware blob over i2c, which is a fun start.

Takaklas commented 5 years ago

Really glad to hear this! Had a feeling it would be a total mess... Keep us updated please! Last resort is to import this library into RTIMULib. You know, just to diss tdk and its secretive tactics :P

BTW are there any instructions concerning the EOL mpu9250 DMP?

ghost commented 4 years ago

Just wanted to share what I have learned after contacting Invensense. The DMP is an FPGA that comes up brain dead and must be programmed with a proprietary binary image before it is useable. Examples can be found here but you must register then get permission to perform the downloads. I downloaded several and the one labeled "ICM-20948 v1.0 for Nucleo Board" seems to be the easiest to understand. But be warned, this is not easy. I decided for my project I'm just going to read the raw Accel/Gyro/Mag data and compute my own fusion with the algorithm here.

laughingrice commented 4 years ago

Initializing the DMP is relatively easy, this is the code I have (part of a bigger piece of code, so hopefully it's clear enough), let me know if you want more info (note that dmp_img is just a list created by copying the code previous mentioned, I think that if you google icm20948_img_dmp3a you can find a few copies people made without having to register and download all the Invensense code)

I'm still working on offloading the data from the DMP, but I'm pretty sure that this Arduino code should be adaptable easily enough along with the documentation, it's for the previous version MPU-9250 that should be mostly compatible, other than the DMP being 6 degrees of freedom rather than 9 degrees of freedom SparkFun_MPU-9250-DMP_Arduino_Library

    def InitDPM(self) -> None:
        '''
        Load the DPM firmware
        '''

        # Reset memories, enable DMP and FIFO
        self._acc.WriteReg(USER_CTRL, 0xFE)
        time.sleep(0.1)

        from .icm20948_img_dmp3a import dmp_img

        mem_bank = 0
        start_address = DMP_LOAD_START
        data_pos = 0

        # Write dpm firmware to memory
        while data_pos < len(dmp_img):
            write_len = min((256 - start_address, len(dmp_img[data_pos:])))
            self.WriteMems(mem_bank, start_address, dmp_img[data_pos:data_pos + write_len])

            data_pos += write_len
            mem_bank += 1
            start_address = 0
    def ValidateDPM(self) -> None:
        '''
        Validate the DPM firmware
        '''

        from .icm20948_img_dmp3a import dmp_img  # So that we know how much to read  

        read_img = [0] * len(dmp_img)

        mem_bank = 0
        start_address = DMP_LOAD_START
        data_pos = 0

        # Write dpm firmware to memory
        while data_pos < len(dmp_img):
            read_len = min((256 - start_address, len(dmp_img[data_pos:])))
            read_img[data_pos:data_pos + read_len] = self.ReadMems(mem_bank, start_address, read_len)

            data_pos += read_len
            mem_bank += 1
            start_address = 0

        diff = 0
        for z in zip(dmp_img, read_img):
            diff += abs(z[1] - z[0])

        return diff == 0
    def WriteMems(self, bank: int, address: int, data: list) -> None:
        '''
        Write data to device memory

        :param bank: Memory bank to write to
        :param address: memory address to write to
        :param data: list of byte values to write
        '''

        self.SelectBank(bank)

        # TODO: it might be possiple to write INV_MAX_SERIAL_WRITE bytes at a time
        for d in data:
            self._acc.WriteReg(MEM_START_ADDR, address)
            self._acc.WriteReg(MEM_R_W, d)
            address += 1`

Where _acc is the bus interface, that for a I2C connection is defined as:

try:
    import smbus

    class I2C_Bus:
        def __init__(self):
            '''
            Setup I2C
            '''

            self._acc = smbus.SMBus(I2C_PORT)

        def ReadReg(self, reg: int) -> int:
            '''
            Read register value

            :param reg: register to read

            :returns: register value
            '''

            # TODO: Looks like READ_FLAG is not needed for I2C
            return self._acc.read_byte_data(ICM20948_I2C_ADDRESS, reg)

        def ReadRegs(self, reg: int, cnt: int) -> list:
            '''
            Read consecutive cnt registers

            :param reg: First register to read
            :param cnt: number of register values to read

            :returns: list of register values
            '''

            return self._acc.read_i2c_block_data(ICM20948_I2C_ADDRESS, reg, cnt)

        def WriteReg(self, reg: int, data: int) -> None:
            '''
            Write registers value

            :param reg: register to write to
            :oaram data: data byte to write
            '''

            self._acc.write_byte_data(ICM20948_I2C_ADDRESS, reg, data)

        def WriteRegs(self, reg: int, data: list) -> None:
            '''
            Write consecutive cnt registers

            :param reg: First register to write to
            :oaram data: list of data byte to write
            '''

            self._acc.write_i2c_block_data(ICM20948_I2C_ADDRESS, reg, data)

except ImportError:
    print("Warning: smbus not installed")
laughingrice commented 4 years ago

I don't have the bandwidth right now to push all the way to a working DMP, but if someone is willing to help with the next step, I can put in some work to merge my DMP initialization code

polymurph commented 4 years ago

I am currently writing a driver for the ICM20948. I've gotten to the point where all 9DOFs are working. The only big problem still remaining is the internal DMP for sensor fusion. I can confirm that the Datasheet is a complete mess and contains some errors. It seems to me that TDK InvenSense does not want to provide any detailed descriptions about how to use the internal DMP. I would be glad if someone could share their knowledge and experience with this blackbox DMP.

romatou18 commented 4 years ago

I guess that you have already downloaded the C SDK from the Tdk invensense website? It's not a very nice code to read but it allows using the DMP.

This repository is an implementation reusing this code, adapting it from the STM32 board used in the SDK to arduino like.

It works but is missing the output rate/ODR configuration in order to set the ODR to something else than the default 5HZ.

https://github.com/ericalbers/ICM20948_DMP_Arduino

romatou18 commented 4 years ago

The missing code to set the ODR should be along the lines of:

rc = inv_icm20948_set_sensor_period(&icm_device, idd_sensortype_conversion(INV_SENSOR_TYPE_LINEAR_ACCELERATION), );

romatou18 commented 4 years ago

The SDK code should be trusted for the logic of how to initialise this sensor, and then read values ou of the DMP, the datasheet only for the units, as quite vague and incomplete otherwise. If you compare it with the bosch hillcrest BNO080 in comparison, and also realise that the code is in github, really clear and regularly maintained, it makes one wonder why invensense is so lazy. They probably don't care about the makers market, only the big firms like phone manufacturers. I guess this is also why the BNO80 is twice the price. But as far as I'm concerned i wasted so much time trying to leverage the ICM20948 that I'd rather pay yhe price and then focus on writing my application logic rather than a driver that should be provided with the sensor.

polymurph commented 4 years ago

I don't use Arduino at all. The library I'm writing is a generic one which is platform independent and written in C++. I will take a closer look at the C SDK software and I's "arduinofied" version. Thank you @romatou18 for your input. If I manage to get it working will make it public.

romatou18 commented 4 years ago

The invensense code exposes a generic interface called Serif. An object that implements i2C or SPI bus hardware abstraction layer.

You're talking about a couple dozen lines of implementation at most.

So if wanting to implement a generic lib, i suggest to take a look at the SDK and the arduinoified version in ericalbers' github. This way you'll easily identify how to implement that object.

And yes a c++ lib would be great. Something maintainable and readable, something invensense is not excelling at obviously!

romatou18 commented 4 years ago

Last but not least, Silicon labs has done a c++ driver that's found in mbed OS. The code does not implement the DMP but implements the sensor init and config in a very clear way. It's help me understand the TDK SDK mess, and from there depve into the DMP stuff.

polymurph commented 4 years ago

Does anybody have an idea what this blob of data means? https://github.com/ericalbers/ICM20948_DMP_Arduino/blob/master/ICM20948/icm20948_img.dmp3a.h

romatou18 commented 4 years ago

Yup, this is the DMP firmware. It gets flashed into the chip on initialisation. Bith the MPU9250 and ICM20948 works this way. If you track the #include of that file it will appear clearly.

laughingrice commented 4 years ago

The blob is the firmware. The code I posted (python) shows how to load it into the sensor. The spec sheet barely mentioned the DMP and says nothing about how to use it. You have to dig through the mess of code in the SDK which has so many abstraction layers that it's nearly impossible to follow.

If I managed to follow it, you have to read the DMP through the FIFO, you cannot read it directly, and then there is a whole pile of math to translate what it outputs into quaternions.

I need to dig back into it at some point and see if there is some clear code for the MPU9250 with it's simpler DMP to see if that might be used for bootstrapping the code.

venumuz commented 4 years ago

I'm currently writing a library for the ICM20948 and had a meeting with a sales engineer from TDK. He basically said what pidloop commented, that the DMP is a 14 kB, very powerful and complex FPGA. I was also told to use the DK-20948 project from the TDK Developer's Corner since it's the most updated, even though I'm using a nucleo board (they have a specific nucleo library). It's still just a massive project with not great documentation, but I have found some things not listed on datasheet (such as the max FIFO burst is 16 bytes).

Gadgetoid commented 4 years ago

I'm glad people are still chipping away at this nightmare- thank you!

I have started using the icm20948 in an illuminated-chair project and am finding integrating gyro data to be an uncompromising pain. I may find that it's easier for me to spend time figuring out the DMP than it is figuring out how to compensate sensors manually.

polymurph commented 4 years ago

@Gadgetoid What microprocessor/dev. workbench are you using?

Gadgetoid commented 4 years ago

@polymurph in this case a full Pi Zero, code written in Python and using this very library. Actually right now I only have an old mpu9250 to hand so I wont be making any progress on this for a few weeks.

Gadgetoid commented 4 years ago

Probably the biggest sticking point for getting this up and running in a way that anyone has a hope of reproducing, is being able to ship the binary firmware that's flashed to the DMP. @venumuz assuming you intend to ship your library, did you get any idea how amenable TDK would be to distributing the blob?

venumuz commented 4 years ago

I think they'd be fine since you can download it from their website but I never asked. I eventually decided that creating my own library for the basic functionalities we needed was a lot easier than trying to understand it (we basically just needed gyro/accel/magnetometer raw data).

Gadgetoid commented 4 years ago

@oziqus icm20948_img_dmp3a.h is available in eMD-SmartMotion-ICM20948-1.1.0-MP.zip - if you're determined not to download these directly from TDK (use a disposable email address) then they are, indeed, not difficult to find. I'd stop short of sharing any more information than that since the legal ramifications aren't clear.

However this certainly hasn't stopped icm20948_img_dmp3a.h from appearing in multiple GitHub repositories and I can verify that the instance with commit hash 50f5150b70b98b84e726400136dd178a4ded0333 is binary identical to the one supplied by TDK.

I think @laughingrice has given pretty detailed instructions for the flashing process, but that doesn't get us meaningful data out. Changing the .h file to .py to work with their code would appear to be a simple matter of just wrapping it in dmp_img = []

Gadgetoid commented 4 years ago

If you're looking for a working DMP with ready to go code, you're in exactly the wrong place 😆 This whole thread is slow progress toward trying to make it work.

ArMouReR commented 4 years ago

@oziqus Can you share the examples of getting DMP data in python that you have found ?

laughingrice commented 3 years ago

If anyone can use this, got approval to make this piece of code public (wrote it some time ago for someone), it does not include reading DMP data yet, but does have the code for loading the DMP firmware.

The project might be picked up again so might be able to allocate some time to have another look

Currently supports reading out the raw data over either SPI or I2C, I did not explore utilizing the FIFO or DMP yet. It does have a main.py that should dump some basic data and is a good reference point. Feel free to pull code out of there if it's any help

https://github.com/laughingrice/ICM20948

bzoss commented 7 months ago

Stumbling upon this as I've recently purchased the ICM20948 for the DMP feature. Sadly, it seems needlessly difficult to gain access to. By posting this, I hoped to determine if any of you in this chat made progress. I'm able to upload the firmware, but it's not clear (1) if it was successful and (2) what registers to poke to retrieve the orientation data I'm interested in.

Thanks!