Search code examples
cmagnetometeruclinuxcubesat-protocol

Magnetometer data on MPU-9250 (uClinux)


I've found a few other people ask about this, but all of them are coding in Arduino, and I'm struggling to translate it into my project.

I'm on a research team developing a Cube Satellite for launch, and my role is to communicate with the peripherals, including the IMU (MPU-9250). I'm cross compiling with uClinux, coding in C.

So far, I can successfully read the accelerometer, gyroscope, and temperature, but cannot get the readings from the magnetometer. The magnetometer (AK8963) has its own address (0x0C), and I've been trying to communicate with it by writing to I2C_SLV0_ADDR(0x25), I2C_SLV0_REG(0x26), and I2C_SLV0_CTRL(0x27). Once I didn't appear to get any results, I tried to resolve it by writing to FIFO Enable(0x23) and I2C Master Control(0x24)

The datasheet and register map imply that the values taken from the magnetometer are stored in registers External Sensor Data(0x49-0x60), but I'm not getting anything in those registers when I try this.

Here's some code that shows the data I'm writing to the registers:

write_register(0x23, 0x04);
write_register(0x24, 0b11110000);
write_register(0x25,0x8c);
write_register(0x26,0x00);
write_register(0x27,0b11110001);

So my questions are: 1. Am I going about this process the right way, or am I completely off?

  1. If I am on the right track, am I reading from the correct registers?

Thanks for your help everyone! Let me know if there's anything I need to clarify!


Solution

  • Alright! I figured out my own question :)

    1) I was indeed going about the process the wrong way. There is a much easier way to do that and I will show how in detail.

    2) I was not reading from the correct registers. I'll show what to do below.

    So, connecting to the Magnetometer is super easy, but not intuitive at all. It does act like its own slave with its own slave address, but that address is inaccessible initially.

    Since I'm cross compiling with uClinux, I can use the bash command i2cdetect 0 (when screening into my SmartFusion2) to check all the slaves on my i2C 0 bus. When I execute this command after resetting my board, I get the following address map printed out:

         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 
    70: -- -- -- -- -- -- -- -- 
    

    So you can see that only the address for the IMU(0x68) is showing. To get the magnetometer showing up properly, you have to enable a bypass bit on INT_PIN_CFG(0x37). I've attached my code below in case anyone needs to replicate this.

    #include <stdio.h>
    //#include <linux/i2c-dev.h>
    
    #include <stdint.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/ioctl.h>
    #include <fcntl.h>
    
    #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
    #define IMU_ADDR 0x68 
    #define IMU_WHO_AM_I 0x75
    
    
    int file;
    
    void i2c_init();
    void write_register(uint8_t register_address, uint8_t value);
    uint8_t read_register(uint8_t register_address);
    
    void i2c_init(address){
        int adapter_nr = 0;
        char filename[20];
    
        snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
        file = open(filename, O_RDWR);
        if(file < 0)
            printf("Error: Failed to open file %s\n", filename);
        int success= (ioctl(file, I2C_SLAVE, address));
        if(file < 0) {
            printf("Error: IMU I2C Failed to Open\n");
            //return -1;
        }
    }
    void write_register(uint8_t register_address, uint8_t value){
        uint8_t data[]={register_address,value};
        write(file, data,ARRAY_SIZE(data));
    }
    uint8_t read_register(uint8_t register_address){
        uint8_t value;
        if(write(file, &register_address, sizeof(register_address)) !=1)
        {
            printf("%d\n",write(file, &register_address, sizeof(register_address)));
            printf("Failed to send data\n");
        }
        read(file, &value, sizeof(value));
        return value;
    }
    
    int main(){
        i2c_init(IMU_ADDR);
    
        printf("\nNow testing the 'Who am I?' IMU register. Output should be 0x71\n");
        printf("Register 0x75: 0x%02X \n", read_register(IMU_WHO_AM_I));
    
        write_register(0x37, 0x22);
        i2c_init(0x0C);
            printf("\nNow testing the 'Who am I?' Magnetometer register. Output should be 0x48\n");
    
        printf("Register 0x00: 0x%02x\n", read_register(0x00));
    
        return 0;
    }
    

    Once the code is compiled and run, the command i2cdetect 0 returns the following datamap.

         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    00:          -- -- -- -- -- -- -- -- -- 0c -- -- -- 
    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 
    70: -- -- -- -- -- -- -- -- 
    

    The program also returns 0x48, the correct value when reading from the magnetometer's 'who am I?' register. The magnetometer can now be read just like any other register. Hope this helps!!