Electronic – How to read data from this sensor

ci2cmbedsensorstm32

I am using the MLX90393 Magnetometer IC from Melexis. More specifically, I am using the breakout board by Sparkfun. I am still a beginner when it comes to programming, and this is my first time trying to read data via I2C. I have made sure to progress slowly (started by toggling LEDs, then writing to LCDs etc) and now want to try my hand at sensing. However, I am really struggling with this. I have read the I2C protocol many times to try and get my head around it and I believe I am starting to.

I am using a STM32F303K8 Nucleo board to communicate with the IC. I am using a standard 16×2 serial LDC to display information. The IDE I am using is mbed (it is free and I have used it before, and its not complicated, and doesn't require anything else other than a PC, Nucleo board and USB cable)

For now, all I want to do is be able to read the data. I have looked on the mbed website for example codes to start out with and found none. I can't even find a library, although their quick start guide (can't find a link, it just auto downloads a PDF) claims there is one on the mbed site. I have been able to find 2 elsewhere, one HERE from GitHub and one HERE for ESP32. The problem is, these are both examples for using different equipment. The ESP32 one is written for the Arduino, and I don't wan't to be stuck doing Arduino forever, so I'd rather not use that. I have attempted to convert it for use with mbed but obviously I have not been successful. I tried the same with the GitHub code, but was unsuccessful with that too.

Going through both of these examples, I have attempted to decipher them and convert them for use in mbed and have come up with the following code:

CODE UPDATED TO CURRENT VERSION USE.

#include "mbed.h"

int addr = 0x0C <<1; // 8bit I2C address

I2C i2c(PB_7 , PB_6);   //sda, scl

Serial pc(PA_9, PA_10); //Tx/Rx

int main()
{
    char config [4];
    char data[7] = {0};

    config[0] = 0x60;
    config[1] = 0x00;
    config[2] = 0x5C;
    config[3] = 0x00;

    i2c.write(addr, config, 4, false);

    i2c.read(addr, data, 1);

    config[0] = 0x60;
    config[1] = 0x02;
    config[2] = 0xB4;
    config[3] = 0x02;

    i2c.write(addr, config, 4, false);

    i2c.read(addr, data, 1);

    wait(0.25);

    while (1) {

        config[0] = 0x3E; // Single measurement mode, ZYX enabled

        i2c.write(addr, config, 1, false);
        i2c.read(addr, data, 1);

        wait(0.1);

        config[0] = 0x4E;

        i2c.write(addr, config, 1, false); // Read command, followed by ZYX bits set
        i2c.read(addr, data, 7); 

        if(i2c.read(addr, data, 7) == !7) {
            printf("ERROR \n");
        } else {
            int xMag = i2c.read(data[1] * 256 + data[2]);
            int yMag = i2c.read(data[3] * 256 + data[4]);
            int zMag = i2c.read(data[5] * 256 + data[6]);

            printf("X Axis = %d \n", xMag);
            printf("Y Axis = %d \n", yMag);
            printf("Z Axis = %d \n", zMag);
        }
        wait(5);
    }
}

I have ran the Arduino code on the STM32 hardware and have managed to get the data displayed on a serial monitor. When I run my code, I get -1 read on all 3 axis.

I have scoped the data lines, and I can confirm that the SDA and SCL lines match that of the working Arduino code, so the majority of this is working. It seems that I am deciphering the data incorrectly, or displaying it incorrectly. One of those 2 as the serial monitor is showing the wrong thing. This means it is all correct up untill here:

if(i2c.read(addr, data, 7) == !7) {
                printf("ERROR \n");
            } else {
                int xMag = i2c.read(data[1] * 256 + data[2]);
                int yMag = i2c.read(data[3] * 256 + data[4]);
                int zMag = i2c.read(data[5] * 256 + data[6]);

                printf("X Axis = %d \n", xMag);
                printf("Y Axis = %d \n", yMag);
                printf("Z Axis = %d \n", zMag);
            }

This bit doesn't seem to be doing what it should. Everything else is working as it should do, the correct registers are being read and written to, and the data is being sent back. I have confirmed this by comparing the signals against the Arduino code version that works.

Before asking 'Can you scope this bit' or 'Can you measure that' please read the full chat logs HERE and HERE which will show everything I have done so far to get up to this point.

I just need to know how to get that last bit working to display the information correctly.

Best Answer

Working with the OP and building on the investigation explained in my answer to this related question "I2C Scanner not working properly", the same two problems needed to be fixed first:

  • De-solder STM32F303K8 Nucleo-32 (NUCLEO-F303K8) board solder bridges SB16 and SB18 when using Mbed I2C functions.
  • Use the "extended" 4-parameter Mbed i2c.write() call with the STM32F303K8 Nucleo-32 board.

As noted on another answer, a logic analyser would be helpful in this situation (and should be used, if possible) the following problems were found and fixed without using one, within the limited time available:

  • The sensor's correct 7-bit I2C address is 0x0C, but the Mbed I2C calls want the 8-bit version.

    Earlier versions of the code were misled by a misprint in the SparkFun documents for their breakout board using this sensor, where they said the address was 0xC0.

    Also some code versions didn't left-shift that I2C address value, however the Mbed I2C functions want the address passed to them as an 8-bit version (e.g. 0x18 in this case). So the fix was either to pass it as 0x0C << 1 or 0x18 (same thing) to the Mbed I2C calls.

  • As noted in that other linked question, I2C access using this STM32F303K8 Nucleo board is only successful when using the Mbed i2c.write() and i2c.read() functions, with (up to) 4 parameters (3 required, 4th optional) and not using the lower level single parameter i2c.write() and i2c.read() calls instead.

  • At this point, comparing I2C bus activity on the scope between the Arduino code and the Mbed code, the OP could see that I2C bus activity was now identical, up to the point of triggering the measurement by the sensor. That left the final problem in the reading and display of the values from the sensor in the Mbed code.

    Looking in that part of the code, I removed the part which was reading from the sensor multiple times and checking for an incorrect return code. Therefore I changed part of the original code from:

config[0] = 0x4E;

i2c.write(addr, config, 1, false); // Read command, followed by ZYX bits set
i2c.read(addr, data, 7); 

if(i2c.read(addr, data, 7) == !7) {
printf("ERROR \n"); 
} else { 
int xMag = i2c.read(data[1] * 256 + data[2]); 
int yMag = i2c.read(data[3] * 256 + data[4]); 
int zMag = i2c.read(data[5] * 256 + data[6]); 

printf("X Axis = %d \n", xMag); 
printf("Y Axis = %d \n", yMag); 
printf("Z Axis = %d \n", zMag); 
}

to

config[0] = 0x4E; 

i2c.write(addr, config, 1, false); // Read command, followed by ZYX bits set 
i2c.read(addr, data, 7); 

int xMag = ((data[1] * 256) + data[2]); 
int yMag = ((data[3] * 256) + data[4]); 
int zMag = ((data[5] * 256) + data[6]); 

printf("X Axis = %d \n", xMag); 
printf("Y Axis = %d \n", yMag); 
printf("Z Axis = %d \n", zMag);

So the full version of the working code is:

#include "mbed.h" 

int addr = 0x0C <<1; // 8bit I2C address 

I2C i2c(PB_9 , PB_8); //sda, scl 

Serial pc(PA_2, PA_3); //Tx/Rx 

int main() 
{ 
char config [4]; 
char data[7] = {0}; 

config[0] = 0x60; 
config[1] = 0x00; 
config[2] = 0x5C; 
config[3] = 0x00; 

i2c.write(addr, config, 4, false); 

i2c.read(addr, data, 1); 

config[0] = 0x60; 
config[1] = 0x02; 
config[2] = 0xB4; 
config[3] = 0x02; 

i2c.write(addr, config, 4, false); 

i2c.read(addr, data, 1); 

wait(0.25); 

while (1) { 

config[0] = 0x3E; // Single measurement mode, ZYX enabled 

i2c.write(addr, config, 1, false); 
i2c.read(addr, data, 1); 

wait(0.1); 

config[0] = 0x4E; 

i2c.write(addr, config, 1, false); // Read command, followed by ZYX bits set 
i2c.read(addr, data, 7); 

int xMag = ((data[1] * 256) + data[2]); 
int yMag = ((data[3] * 256) + data[4]); 
int zMag = ((data[5] * 256) + data[6]); 

printf("X Axis = %d \n", xMag); 
printf("Y Axis = %d \n", yMag); 
printf("Z Axis = %d \n", zMag); 

wait(5); 
} 
}

With this Mbed code, the sensor produces correct output, similar to the working Arduino code, and the point of the question was to get to that stage. The code can then be used as a starting point and improved with error checking & status reporting etc.

Again, further investigations of the I2C behaviour of the Mbed libraries and the sensor can be performed with a logic analyser, when an analyser (and more time) is available.