NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » I2C Magnetic sensor Module

May 15, 2011
by Ralphxyz
Ralphxyz's Avatar

As part of my Weather Station I have a I2C Weather Vane i.e. Wind Direction.

The device I have is from SURE electronics.

The User Manual or instructions are quite lacking and there are only a few examples of it's use on the web.

There are a couple of examples using Arduino.

I was starting to write up some code when I questioned a I2C function so I thought I'd look at some working I2C code so I opened up Rick's I2C Nunchuck code.

Besides the DeviceID and the Nunchuck returning 6 registers versus 5 for the Compass Module it looks like I "should" be able just use Rick's code. Changing the LCD output of course.

Well that is what I thought at least.

I am getting a "2nd start bad: 207" error.

I'll be looking at the code but thought I'd get the ball rolling on using the DC-SS503 Compass sensor Module which I have not seen mentioned here in the Nerdkit's forums.

Comments please.

Oh this is using Peter Fleury's TWI library of course.

Ralph

May 16, 2011
by Ralphxyz
Ralphxyz's Avatar

I am sure my problem is with Slave device addressing.

The Slave 7 bit address is 0x60.

A confusion pont is:

// Setup the Nunchuk for first time providing address to read

    uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE);

So we have the 7 bit address 0x60 + 1 or 01100000 + 00000001 that gives me 01100001.

But this is supposed to be a 8 bit address 11000001.

See why I am confused? Then the fact that it works just makes it worse.

Ralph

May 16, 2011
by Noter
Noter's Avatar

It works? I thought you were getting an error - doesn't that mean it's not working?

May 16, 2011
by OliverM
OliverM's Avatar

Hello Ralph,

as I just got my nerdkit, this is my first time posting here.

I have no real experience on the I2C bus, but googeling a bit gave me an idea that might help you:

First the master sends the 7 address bits and then a bit which tells the slave whether the master wants to read or write. In effect one complete byte, consisting of 7bit address + direction.

The byte looks like this:

8 7 6 5 4 3 2 1
a a a a a a a d

if you just copy the 7bit address into a variable, the byte looks like this:

8 7 6 5 4 3 2 1
0 a a a a a a a

If you now add the direction bit you get

8 7 6 5 4 3 2 1
0 a a a a a a d

which is not what you want.

You have to shift the bits of the address one place to the left and then add direction. Shifting to the left can be achieved by multiplying with 2.

7 Bit address:

8 7 6 5 4 3 2 1
0 a a a a a a a

multiply with 2:

8 7 6 5 4 3 2 1
a a a a a a a 0

add direction:

8 7 6 5 4 3 2 1
a a a a a a a d

or in code :

uint8_t ret=i2c_start(ADR * 2 +I2C_WRITE);

I hope this helps

Oliver

May 16, 2011
by Ralphxyz
Ralphxyz's Avatar

Paul, "it works" as in it works in the Wii Nunchuck program (and else where).

Oliver thank you that would make more sense but I have

uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE);

i2c_start is a library function I do not believe it does a multiplication but I'll have to look.

That would make sense.

Just to be clear I am still getting the "2nd start bad: 207" error that is the specific problem.

The problem I have with uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE); is with my understanding of what is going on NOT with the program it comes from (which as I said works)!!

Ralph

May 16, 2011
by Ralphxyz
Ralphxyz's Avatar

Duh boy uint8_t ret=i2c_start(Nunchk_ADR+I2C_WRITE); had me going around in circles but I suppose that is good as I had to finally pay attention to what was going on.

The Slave address for the Nunchuck is 8 bit not 7 bit!!

#define Nunchk_ADR 0xA4

I am sure Rick had told me that but it had slipped away.

For my project when I tried using a 8 bit format with 0 for write. I immediately got the :

First start bad
Second start bad

error messages.

Well I am sure it is Slave address that is causing me problems so I'll keep messing around.

Now at least I know what I am doing and why, so that's progress.

Ralph

May 16, 2011
by Noter
Noter's Avatar

Ok, now I understand what you meant by it works.

There is only a 7 bit device address in I2C land. No such thing as an 8 bit address. Maybe reviewing the TWI doc in the ATmega datasheet will help you get a firm handle on it. When you put the 7 bit device address in bits 8:1 and then put a 1 in bit 0, the resulting byte is called the SLA-W which stands for "slave address - write".

Then for a device address of 0x66, depending on how you write your code it may look like any of the following:

SLA_W = ((0x06*0x22) + 1);
SLA_W = (0x66<<1 | 1);
SLA_W = (0x66<<1 + 1);
SLA_W = (0x66*2 + 1);
SLA_W = (0x66*2 | 1);
SLA_W = (0xCC + 1);
SLA_W = (0xCC | 1);
SLA_W = (0xCD);

They all result in exactly the same value in SLA_W. Seems the Nunchuck code you are looking over uses the (0xCC + 1) type style. I always us the (0x66<<1 | 1) type style because I like to see/recognize the device address and the logical 'or' makes more sense to me for setting a single bit. But they are all equivalent and all will work.

May 16, 2011
by OliverM
OliverM's Avatar
#define Nunchk_ADR 0xA4

0xA4 is 0b10100100

maybe Nunchk_ADR is already in the form

aaaaaaa0
May 17, 2011
by Ralphxyz
Ralphxyz's Avatar

Yes Paul, I like the (0x66<<1 | 1) format, it is definitely more comprehensible for me.

All of the specsheets specify the 7 bit address so it is a twist to use a 8 bit representation of the 7 bit address in the expression (Nunchk_ADR+I2C_WRITE). It works but tends to confuse poor soles like me.

I always say 8 bit address when what I really mean is "7 bit address left shifted 1 plus function bit" 8 bit address is just easier to say.

Actually if I had used your I2C EEPROM code instead of the Nunchuck code this probable would not have been a issue.

The Nunchuck code uses very similar 6 register reads and I need to read 5 at a time so I was using that.


Yes Oliver,

[quote]maybe Nunchk_ADR is already in the form aaaaaaa0[/quote]

I really have a hard time understanding your aaaaaaa0 syntax. That must come from a discipline I am not familiar with.

What exactly does the "a" represent? "a" = any is my guess. I can see where "d" could/would mean "direction" (read/write).

It's interesting to see new things so thank you again.

There will be more so stand by!!

Ralph

May 17, 2011
by OliverM
OliverM's Avatar

Hi Ralph,

with 'a' I mean one bit from the address, so the 7 bit address is aaaaaaa. If you shift the address one to the the left you get aaaaaaa0. When left shifted, the last bit on the rigt side gets the value 0. The last bit on the right can then used as the function bit.

I hope I made myself a bit clearer.

Oliver

May 17, 2011
by Ralphxyz
Ralphxyz's Avatar

Hi Oliver, I understand what you are doing just wondering where the "a" syntax came from. Actually "a" works as any format of the bit 1 or 0

Then I suppose you might have used eeeeeee0 as in "e" for either, that is "either 1 or 0".

Just another trivial thing to distract my none too focused mind.

Ralph

May 17, 2011
by Noter
Noter's Avatar

Bit pattern defs - "a" comes from the word address. "d" would be data. "x" means don't care. "1" means 1. "0" means 0. One place that you will see these used a lot is in avrdude device definitions.

May 17, 2011
by Ralphxyz
Ralphxyz's Avatar

Where does "a" address come from in "aaaaaaa0"?

I am familiar with "a" = address but not in this context.

Ralph

May 17, 2011
by Noter
Noter's Avatar

I don't understand what you're asking. Are you trying to find who used it first, where it first appeared, where it was first used? Or are you after something specifically realted to the SLA-R?

Take a look at your default avrdude config file (avrdude.conf.in) - it goes a step farther using "i" for input and "o" for output. It even adds position numbers to the "a" so you won't get them backwards.

May 18, 2011
by Ralphxyz
Ralphxyz's Avatar

I said:

[quote]Where does "a" address come from in "aaaaaaa0"?[/quote]

Noter said:

[quote]I don't understand what you're asking. [/quote]

As I had said I have never seen the "aaaaaaa0" nomenclature (syntax).

That is using "a" to represent a bit,

Oliver said:

[quote]with 'a' I mean one bit from the address[/quote]

I was just asking Oliver where he learned to use that syntax and then asked in general what the "a" stood for?

Now "i" for input and "o" for output I can understand but "a" for 1/0, on/off, hi/lo I do not see the logic of.

I could see some logic how he used "d".

Oliver said:

[quote]add direction:

8 7 6 5 4 3 2 1
a a a a a a a d

[/quote]

I just do not know where the usage of "a" comes from. I do not think of a bit as representing an address.

Possible there is an association that I am just missing it would not be the first time.

Oh, also my avrdude.conf.in does not use any input (i) or output (o) designations and certainly no "a".

And thanks again Paul for re-re-re-explaining I2C Slave device address I believe it has finally sunk in.

Your patience is knightworthy.

Ralph

May 18, 2011
by Noter
Noter's Avatar

How else would you represent an address if not with bit(s)? Look further into the avrdude configuration file. All the devices use the bitmap nomenclature as described. Here's an excerpt from the device "m328p". I would think it has to be in your config file too even though you're on an apple computer. If not then you surely will find it in the config file on your windows box.

read_hi = " 0 0 1 0 1 0 0 0",
      " 0 0 a13 a12 a11 a10 a9 a8",
      " a7 a6 a5 a4 a3 a2 a1 a0",
      " o o o o o o o o";

loadpage_lo = " 0 1 0 0 0 0 0 0",
          " 0 0 0 x x x x x",
          " x x a5 a4 a3 a2 a1 a0",
          " i i i i i i i i";

loadpage_hi = " 0 1 0 0 1 0 0 0",
          " 0 0 0 x x x x x",
          " x x a5 a4 a3 a2 a1 a0",
          " i i i i i i i i";

writepage = " 0 1 0 0 1 1 0 0",
        " 0 0 a13 a12 a11 a10 a9 a8",
        " a7 a6 x x x x x x",
        " x x x x x x x x";

Then near the top around line 150 you should find this description:

# INSTRUCTION FORMATS
#
#    Instruction formats are specified as a comma seperated list of
#    string values containing information (bit specifiers) about each
#    of the 32 bits of the instruction.  Bit specifiers may be one of
#    the following formats:
#
#       '1'  = the bit is always set on input as well as output
#
#       '0'  = the bit is always clear on input as well as output
#
#       'x'  = the bit is ignored on input and output
#
#       'a'  = the bit is an address bit, the bit-number matches this bit
#              specifier's position within the current instruction byte
#
#       'aN' = the bit is the Nth address bit, bit-number = N, i.e., a12
#              is address bit 12 on input, a0 is address bit 0.
#
#       'i'  = the bit is an input data bit
#
#       'o'  = the bit is an output data bit
#
#    Each instruction must be composed of 32 bit specifiers.  The
#    instruction specification closely follows the instruction data
#    provided in Atmel's data sheets for their parts.
#
# See below for some examples.
May 18, 2011
by Ralphxyz
Ralphxyz's Avatar

Wow Paul, ask and you shall receive.

Well I asked and you certainly delivered on that one. Thank you once again.

Now that is a detailed comprehensible answer.

Ralph

May 19, 2011
by Ralphxyz
Ralphxyz's Avatar

Ok now back to my actual programming problem.

I was getting the:

1nd start bad:
2nd start bad:

Errors (using Rick's modified Nunchck code).

So I have been meticulously taking the code apart to see where the "Bad Start" comes from.

Here is working code section!!

    uint8_t ret=i2c_start(DCSS503_ADR+I2C_WRITE);
    if(ret==1) 
        {
            fprintf_P(&lcd_stream, PSTR("1st start bad"));
        }
    i2c_write(0x00);
    i2c_write(0x01);
    i2c_stop();

    while(1)
        {
        lcd_home();
        lcd_write_string(PSTR("WORKS"));
        lcd_write_string(PSTR(" working   "));
        }

And here is code that does not work!!

    uint8_t ret=i2c_start(DCSS503_ADR+I2C_WRITE);
    if(ret==1) 
        {
            fprintf_P(&lcd_stream, PSTR("1st start bad"));
        }
    i2c_write(0x00);
    i2c_write(0x01);
    i2c_stop();

    while(1)
        {
        lcd_home();
        lcd_write_string(PSTR("WORKS"));
        //lcd_write_string(PSTR(" working   "));
        }

Let me qualify "does not work":

In fact the code does work but I get

"WORKStart bad"

On the LCD readout!

If I do anything else in the while(1) loop I get the "start bad" message.

I want to run a for loop to read the registers but keep getting "start bad"

By removing the comments "//lcd_write_string(PSTR(" working "));" I do not get the "start bad"

Now the "start bad" message is outside the while(1) loop so what does that mean?

I sure would appreciate some deliverance as this has had me going around in circles for a few days.

Ralph

May 19, 2011
by Noter
Noter's Avatar

You're overlaying the text from all prints on the same line. The start bad is first then it is overwritten with works. Move the works and working writes to the 2nd line on your lcd and you won't be overwriting the error message from i2c_start.

May 19, 2011
by Ralphxyz
Ralphxyz's Avatar

Yeah but what is causing the problem?

Ralph

May 19, 2011
by Noter
Noter's Avatar

The start bad error is likely due to a slave that cannot be found. Could be a wiring problem or a slave address problem. Do you have the pull-up resistors installed? What value do you have in DCSS503_ADR when calling i2c_start? What is the device address of your slave?

May 19, 2011
by Ralphxyz
Ralphxyz's Avatar

Ok I put together an entire program instead of trying to build upon working pieces, which never work.

I am actually getting some activity according to my Digital Analyzer:

Here is the start of a session it is a wide picture so I didn't inbed it.

If you went as far as downloading the ikalogic Digital analyzer application you could actually view the whole session with I2C decoding.

The documented code is here

This list the comments from the specsheet which I tried to emulate step (cycle) by step.

So the code runs!!

But I am not getting any output to the LCD!!

Here is a excerpt from the code: (I have excluded the leading comments)

#include <avr/io.h>
#include <inttypes.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/i2cmaster.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/io_328p.h"
#include "../libnerdkits/uart.h"

#define DCSS503_ADR 0xC0

int main()
{
    uint8_t C_data[6], i, Xmsb, Xlsb, Ymsb, Ylsb, reg1;
    uint16_t headingX, headingY;
    // initialize data array

    for(i=0;i<6;i++)
        {
        C_data[i]=0;
        }

    i2c_init(); 
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
    lcd_home();
    uart_init();
    FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
    stdin = stdout = &uart_stream;
    while(1)
        {   
            // START
                // First cycle:
                i2c_start_wait(DCSS503_ADR+I2C_WRITE);  //Slave ack
            // Second cycle:
                i2c_write(0x00);
            // Third cycle
                i2c_write(0x01);    // Wake Up call  Slave ack
                delay_ms(5);    
                i2c_stop();
            // Forth cycle:
                i2c_start_wait(DCSS503_ADR+I2C_READ);   // Slave ack
            // Fifth, Sixth, Seventh and Eight cycle:
            // read 5 bytes
                for(i = 0; i < 4; i++)
                    {
                        C_data[i]=i2c_readAck();
                    }

            // STOP
                i2c_readNak();

                i2c_stop();

            reg1 = C_data[0]; 
            Xmsb = C_data[1];
            Xlsb = C_data[2];
            Ymsb = C_data[3];
            Ylsb = C_data[4];

            lcd_home();
            lcd_line_one();
            lcd_write_string(PSTR("WORKS!!"));

            lcd_line_two();
            fprintf_P(&lcd_stream, PSTR("Register 1: %d"), reg1);
            lcd_line_three();
            fprintf_P(&lcd_stream, PSTR("Xmsb: %d  Xlsb %d"), Xmsb, Xlsb);
            lcd_line_four();
            fprintf_P(&lcd_stream, PSTR("Ymsb: %d  Ylsb %d"), Ymsb, Ylsb);

        }   
}

I "think" the program is actually running, I do not know what to do to get any LCD readout.

And of course "running" is a relative term, the program runs but I am not sure the Slave is actually being addressed or acknowledging any transactions.

I do not see any [ack] in the Digital Analyzer file! I do not see either the Slave or the Master issuing a [ack] though the SCL is pulled low at times.

Well any comments, they are more than welcome. Any insight you might have is certainly more than what I have at the moment.

Ralph

May 19, 2011
by Ralphxyz
Ralphxyz's Avatar

Further details:

If I comment out all of the I2C code I get the expected output to the LCD!!

    while(1)
        {   
        /*
            // START
                // First cycle:
                i2c_start_wait(DCSS503_ADR+I2C_WRITE);  //Slave ack
            // Second cycle:
                i2c_write(0x00);
            // Third cycle
                i2c_write(0x01);    // Wake Up call  Slave ack
                delay_ms(5);    
                i2c_stop();
            // Forth cycle:
                i2c_start_wait(DCSS503_ADR+I2C_READ);   // Slave ack
            // Fifth, Sixth, Seventh and Eight cycle:
            // read 5 bytes
                for(i = 0; i < 4; i++)
                    {
                        C_data[i]=i2c_readAck();
                    }

            // STOP
                i2c_readNak();

                i2c_stop();

            reg1 = C_data[0]; 
            Xmsb = C_data[1];
            Xlsb = C_data[2];
            Ymsb = C_data[3];
            Ylsb = C_data[4]; 
        //*/

            lcd_home();
            lcd_line_one();
            lcd_write_string(PSTR("WORKS!!"));

            lcd_line_two();
            fprintf_P(&lcd_stream, PSTR("Register 1: %d"), reg1);
            lcd_line_three();
            fprintf_P(&lcd_stream, PSTR("Xmsb: %d  Xlsb %d"), Xmsb, Xlsb);
            lcd_line_four();
            fprintf_P(&lcd_stream, PSTR("Ymsb: %d  Ylsb %d"), Ymsb, Ylsb);

        }

So what is it about the I2C code the LCD does not like?

Ralph

May 19, 2011
by Noter
Noter's Avatar

Seems you may be sticking in the i2c_start_wait(). Probably same issue as before. According to your code your device address is 0x60 - is that correct?

May 19, 2011
by Ralphxyz
Ralphxyz's Avatar

Yes the "7 bit address" is 0x60 from the specsheet *"a Magnetic Sensor Module with a 7-bit device address “[0110000]”. *

I posted the Digital Analyzer code on the Scanlogic forum to see what they have to say.

I'll try using i2c_start() tomorrow in fact I'll look at using your libNoter library instead of Peter Fluery's.

Though I do not know what the differences would be besides syntax. Both libraries have to use the "native" TWI registers.

I looked at the Wii Nunchuck with the Digital Analyzer and I do not see [ack]s there either.

I know the Scanlogic application has gone through at least two major revisions in the past two months so maybe they remove the acknowledgement from the I2C decode.

I think "sticking in i2c_start_wait()" is a logical place to start looking thank you.

Ralph

Ralph

May 19, 2011
by Noter
Noter's Avatar

“[0110000]” is 0x30. Shifted it would be 0x60. I would try defining DCSS503_ADR as 0x60.

May 20, 2011
by Ralphxyz
Ralphxyz's Avatar

Damm, I thought I had tried that. I need to find a new hex to binary converter or get back to the place where I can just look at a hex number and know the binary. I used to be able to do that, and now I am afflicted with the Age Virus.

Well I am getting the expected output well I do not actually know what the "expected" output would be but I am getting output and it changes as I revolve the breadboard!!

Now to right shift the X-MSB axis (8 places) and add the X-LSB axis to get heading X.

That is what I want to do isn't?

That would be:

headingX = (Xmsb >>8 + Xlsb);
headingY = (Ymsb >>8 + Ylsb);

Ralph

May 20, 2011
by Ralphxyz
Ralphxyz's Avatar

geesch, I think I want to left shift not right shift.

Ok here is what I currently have,

            reg1 = C_data[0]; 
            Xmsb = C_data[1];
            Xlsb = C_data[2];
            Ymsb = C_data[3];
            Ylsb = C_data[4];

            headingX = (Xmsb <<8 + Xlsb);
            headingY = (Ymsb <<8 + Ylsb);

            lcd_home();
            lcd_line_one();
            fprintf_P(&lcd_stream, PSTR("Register 1: %d      "), reg1);
            lcd_line_two();         
            fprintf_P(&lcd_stream, PSTR("Xmsb: %d Xlsb: %d   "), Xmsb,Xlsb);
            lcd_line_three();
            fprintf_P(&lcd_stream, PSTR("Ymsb: %d Ylsb: %d   "), Ymsb,Ylsb);
            lcd_line_four();
            lcd_write_string(PSTR("                    "));
            delay_ms(5000);
            lcd_line_one();

            lcd_init();
            fprintf_P(&lcd_stream, PSTR("X is %.2f"), headingX);
            lcd_line_two();
            lcd_write_string(PSTR("                    "));
            lcd_line_three();
            fprintf_P(&lcd_stream, PSTR("Y is %.2f"), headingY);
            lcd_line_four();
            lcd_write_string(PSTR("                    "));
            delay_ms(5000);
        }   
}

headingX and headingy are coming out as 0.00!!

    Xmsb = 47 Xlsb = 7
    Ymsb = 81 Ylsb = 21

There is something really wrong with my math.

I probable have to do some casting, but not sure what, where or how.

Ralph

May 20, 2011
by Noter
Noter's Avatar

When you try to print an int16 as a float you will show 0. Change the %.2f to %d and it might work.

May 20, 2011
by Ralphxyz
Ralphxyz's Avatar

Here is a terminal screen shot of output:

Register 1: 8 Xmsb: 21 Xlsb: 7 Ymsb: 184 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 20 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 21 Xlsb: 7 Ymsb: 184 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 20 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 21 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 20 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 21 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 21 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 21 Xlsb: 7 Ymsb: 184 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 19 Xlsb: 7 Ymsb: 186 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 20 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 21 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 21 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00 
Register 1: 8 Xmsb: 21 Xlsb: 7 Ymsb: 185 Ylsb: 21 X is 0.00 Y is -0.00

I changed to using decimal (d) instead of using float (f).

I get the above output on my pc while on the LCD I get "X is -32768".

Also if I move the compass I freeze all output to both the LCD and the Terminal.

Moving the compass essentially kills all functioning

It would help if I had/knew expected values.

If anyone would like further reading here are some of the IC compass manufacturer's literature.

AN-00MM-001_Magnetometer_Fundamentals_r1_2.pdf

AN-00MM-003_Magnetic_Sensor_Calibration_r1_1.pdf

AN-00MM-004_Electronic_Tilt_Compensation_r1_1.pdf

AN-00MM-005_Magnetic_Sensor_Placement_Guidelines_r1_1.pdf

MMC2120MG/pdf

SURE Electronics (breakout board manufacturer) in their literature do not mention having to calibrate the compass.

The whole documentation line is not good. I really spent a lot of time to come up with what I have.

How does the flow of the program look, do you think it is actually working?

Why does it freeze up when I move the compass? I must have a overflow somewhere.

Is my math correct? Why don't the LCD and the serial dump agree?

Thanks for the help,

Ralph

May 20, 2011
by Noter
Noter's Avatar

I don't see where you are sending anything over the usart. How's that happening?

May 20, 2011
by OliverM
OliverM's Avatar

Hello Ralph,

%d in printf function is for signed integer, but headingX and headingY are declared as unsigned integer.

try %u

Wikipedia printf

May 20, 2011
by Ralphxyz
Ralphxyz's Avatar

Sorry Paul I slipped the usart function in on you with out mentioning it in particular.

Oliver thank you, lets see what %u will do.

Using %d and removing any delays I had added in order to see output on the LCD I actually am getting a steady continuos output to the usart.

The LCD is flickering by to quickly but the Terminal output is readable. well it was until I removed all of the delays.

The %u and %d appear to be the same.

But I can see that I am returning:

headingX = 0
headingY = 0

with a occasional headingX = -32768

So my math definitely is not correct!

here it is again:

uint8_t C_data[6], i, Xmsb, Xlsb, Ymsb, Ylsb, reg1;
uint16_t headingX, headingY;

        reg1 = C_data[0]; 
        Xmsb = C_data[1];
        Xlsb = C_data[2];
        Ymsb = C_data[3];
        Ylsb = C_data[4];

        headingX = (Xmsb <<8 + Xlsb);
        headingY = (Ymsb <<8 + Ylsb);

Also Xlsb and Ylsb are always the same value no matter the heading of the compass so there must be something wrong with my I2C register processing.

So with the compass stationary I am getting readings of:

Xmsb = 81
Xlsb  = 07
Ymsb = 163
Ylsb  = 21

headingX = 00 on LCD -32768 on usart pc Terminal 2 out of 3 readings
headingY = 00 on both

Starting tomorrow I will be offline until Wednesday or Thursday so I might not have a quick response.

I sure appreciate all of the help.

I am starting to look at the Calibration instructions. I'll need steady consistant headingX and headingY readings before I can even think about calibration of course.

I am thinking about setting up a 360˚ servo motor to do the calibration instead of moving the compass 10˚ at a time by hand.

Well one of the calibration routines just uses Max/Min values and applied math so the servo might be overkill.

Without any delays if I move the compass slowly I do not freeze it up but any quick movements will likely freeze it.

Sometimes if it does freeze I can just move the compass a little bit and it will restore.

Ralph

May 20, 2011
by Noter
Noter's Avatar

Maybe it won't make a difference but I always use a logical or to put the lsb value into the word. Also try grouping with () to eliminate any misunderstanding as to the order of operation.

headingX = ((Xmsb <<8) | Xlsb);
May 21, 2011
by Ralphxyz
Ralphxyz's Avatar

Thanks Paul, that makes a difference!!

I am now getting:

Register 1: 07 Xmsb: 122 Xlsb: 07 Ymsb: 197 Ylsb: 20 X is 31239 Y is 50452 
Register 1: 07 Xmsb: 121 Xlsb: 07 Ymsb: 195 Ylsb: 20 X is 30983 Y is 49940 
Register 1: 07 Xmsb: 123 Xlsb: 07 Ymsb: 198 Ylsb: 20 X is 31495 Y is 50708 
Register 1: 07 Xmsb: 125 Xlsb: 07 Ymsb: 197 Ylsb: 20 X is 32007 Y is 50452 
Register 1: 07 Xmsb: 122 Xlsb: 07 Ymsb: 198 Ylsb: 20 X is 31239 Y is 50708 
Register 1: 07 Xmsb: 123 Xlsb: 07 Ymsb: 196 Ylsb: 20 X is 31495 Y is 50196 
Register 1: 07 Xmsb: 124 Xlsb: 07 Ymsb: 198 Ylsb: 20 X is 31751 Y is 50708 
Register 1: 07 Xmsb: 123 Xlsb: 07 Ymsb: 197 Ylsb: 20 X is 31495 Y is 50452 
Register 1: 07 Xmsb: 121 Xlsb: 07 Ymsb: 196 Ylsb: 20 X is 30983 Y is 50196 
Register 1: 07 Xmsb: 124 Xlsb: 07 Ymsb: 198 Ylsb: 20 X is 31751 Y is 50708 
Register 1: 07 Xmsb: 124 Xlsb: 07 Ymsb: 198 Ylsb: 20 X is 31751 Y is 50708 
Register 1: 07 Xmsb: 122 Xlsb: 07 Ymsb: 197 Ylsb: 20 X is 31239 Y is 50452

With the compass perfectly still.

At least I am not getting 0 for headingX and headingY so that's progress. I do not know why the X and Y valves are not consistant.

I also do not know if they are correct.

I wonder why the Xlsb and Ylsb are always they same (+-1 or 2)

Ralph

May 21, 2011
by Ralphxyz
Ralphxyz's Avatar

Please check my math: (from the first row)

Xmsb = 122 = 1111010
Xlsb =  07 =     111

1111010 <<8 = 111101000000000
+                         111
              _______________
              111101000000111 = 31239

X is 31239

Ymsb = 197 = 11000101
Ylsb =  20 =    10100

11000101 <<8 = 1100010100000000
+                         10100
               ----------------
               1100010100010100 = 50452

Y is 50452

Well that appears to be actually working, amazing. Thanks again Paul your <<8 | method did it.

Now why would Xlsb and Ylsb always be the same number when the compass is turned?

Can somebody explain why we need a X axis and Y axis?

Here supposedly is the formula to get Degrees!

atan(y/x)*180/3.14;

Ralph

May 21, 2011
by OliverM
OliverM's Avatar

Hello Ralph,

the register value from the magnetic module is 7. That means that the "Take Measurement" Bit is still set.

According to the usermanual:

The TM bit(Take Measurement bit in control register) will be automatically reset to "0" after data from A/D converter is ready.

The measurement process has not finished, when you read the values.

The usermanual also states A STOP command indicates the end of write operation. A minimal 5ms wait should be given to the Magnetic Sensor Module to finish a data acquisition and return a valid output.

In your code

delay_ms(5);   
i2c_stop();

You wait 5ms and then give the stop command. try revers the order and

i2c_stop();
delay_ms(5);

Oliver

May 21, 2011
by 6ofhalfdozen
6ofhalfdozen's Avatar

Congrats Ralph!

Getting stuff to work is always a good start to a weekend!

As for the "why x and y?" Sad to say, I know the answer, or perhaps it is better to say that I have had it explained to me enough times that some of it has stuck. It is a lot more simple and yet strangely also more complicated than you would think. One of my friends in college was a math grad/phD student and this was one of the things he obsessed about for his grad work. So he could go on and on and on, about long complicated calculations proving why this is this and so on about seemingly simple stuff. The first time its kinda cool, the 500th time its Snoresville. Anyhow, the short version is that if you only have X, you have a "bead on a string". A straight line value, that can only go back and forth along the string. By adding Y into the mix, it becomes "bead on a table", and you then can go left and right in addition to the back and forth. Since the bead has the extra room it can do more fun things like having the line make angles, curves, as well as rotate and all the other fun stuff. For your magnetic sensor, it is trying to tell how it was moved "on the table", which means it needs to know left/right and forward/back. For the most part, since the sensor is just rotating it actually uses the forward/back (x) to scale the right/left(y) (ie if it goes x 10 and y 10 it is much less of a turn than x 1 y 10 ) which is why the equation shows y/x.

As for your binary math, it looks ok but to be honest I stink at binary math with that many digits and shifts. Sorry. Also when I use the degrees calculation as given with your zero rotation data I get >360, which makes me wonder if there is some scaling factor missing or else the equation is misprinted. So for your line#1 data point, if you use the equation as typed "degrees = atan(y/x)180/Pi " gives 3336.62 which last I checked is a little out there. if you swap 180/Pi with /180*Pi it gives you 1.016 which while a little high, isn't a bad value for sitting still. Have you tried seeing what the data looks like when you rotate the sensor?? Perhaps the sensor doesn't like sitting still too well. Anyhow, just a little info and some thoughts. Hope it helps!

May 21, 2011
by OliverM
OliverM's Avatar

Hello Ralph,

as to why we need an X axis and a Y axis:

a magnetic field not only has a strenght, but also a direction. It is a vector. Thats why a compass can tell you where north and south is.

Usually this is expressed in a X/Y coordinate system with M(x,y).

Y-Axis
   ^
   |
 y |.......^ M
   |      /.
   |     / .
   |    /  .
   |   /   .
   |  /    .
   | /     .
   |/      .
   +------------> X Axis
           x

Strength of M is SQRT(y^2 + x^2)

Angle is atan(y/x)*180/3.14;

Oliver

May 21, 2011
by Noter
Noter's Avatar

That is the correct forumla to calculate the angle in radians and convert it to degrees. Using your values, the answer is 31.765 degrees. Here's the long answer as to why you need x and y to get direction - http://en.wikipedia.org/wiki/Trigonometric_functions

May 21, 2011
by bretm
bretm's Avatar

If lsb is staying the same and msb is wiggling slightly, then it very likely means you have lsb and msb swapped. Its probably xmsb=7 and xlsb=122, which gives x=256*7+122=1914.

May 21, 2011
by OliverM
OliverM's Avatar

Hello Ralph,

I spotted something else that might be a problem

// read 5 bytes
    for(i = 0; i < 4; i++)
        {
            C_data[i]=i2c_readAck();
        }

// STOP
    i2c_readNak();

    i2c_stop();

The for loop is executed four times, reading one byte and the sending an acknowledge signal; i2c_readAck(). The loop reads register, Xmsb, Xlsb and Ymsb

After the loop one byte is read without sending an acknowledge signal; i2c_readNak(). This reads Ylsb, but that value is not stored.

I think the line should look like this

C_data[4]=i2c_readNak();

Oliver

May 21, 2011
by bretm
bretm's Avatar

For angle calculation you should use atan2(y, x) instead of atan(y/x). atan2 handles the case where x is zero, and distinguishes between all four quadrants, e.g. x positive and y positive give different angle than x negative and y negative, whereas with atan(y/x) they would give the same result.

If the x and y values are unsigned and hence the angle is always in the first quadrant, it's still better use use atan2 because of the x==0 problem and numerical stability when x is small.

May 26, 2011
by Ralphxyz
Ralphxyz's Avatar

Unbelieveable, I do not know if I could buy such great support. Thank you all so much!!

bretm would I use atan2(y, x)180/3.14; instead of atan(y/x)180/3.14;

Oliver, I just used/modified Rick's Nunchuck code for querying the slave.

        // read 6 bytes

        for(i = 0; i < 5; i++)
        {
            nc_data[i]=i2c_readAck();
        }

        nc_data[5]= i2c_readNak();
        i2c_stop();

And then he does some decoding which I could not comprehend so i did not use:

        // Decode data

        for (i = 0; i < 6; i++)
        {
            nc_data[i] = (nc_data[i] ^ 0x17) + 0x17;

        }

Rick was reading 6 bytes while I need to read 5. I was hoping he would jump in here so that I could ask him about what the Decode was doing. It probable is essential.


Now Oliver said:

*[quote] the register value from the magnetic module is 7. That means that the "Take Measurement" Bit is still set.

According to the usermanual:

The TM bit(Take Measurement bit in control register) will be automatically reset to "0" after data from A/D converter is ready.

The measurement process has not finished, when you read the values.

[/quote]*

I didn't like the Register return value (07) I think I'll focus on this to see if I can get it to return 0 (read).

Ralph

May 26, 2011
by Ralphxyz
Ralphxyz's Avatar

Ok I made the recommended changes:

        // read 5 bytes
            for(i = 0; i < 5; i++)
                {
                    //C_data[i]=i2c_readAck();
                    C_data[4]=i2c_readNak();
                    delay_ms(5);
                }
//*/    
        // STOP
            i2c_readNak();

            i2c_stop();
            delay_ms(5);

Now Register is 0 BUT:

Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255 
Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255 
Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255 
Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255 
Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255 
Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255 
Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255 
Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255 
Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255 
Register 1: 00 Xmsb: 00 Xlsb: 00 Ymsb: 00 Ylsb: 255 X is 00 Y is 255

This is with a stationary compass.

Now rotating the compas does not change the readings!!

Ralph

May 26, 2011
by Noter
Noter's Avatar

Why would you set C_data[4]=i2c_readNak(); four times in a row? And now there's no readAck's? I think Oliver had something else in mind.

May 26, 2011
by Noter
Noter's Avatar

Maybe he means to set C_data[4]=i2c_readNak(); after the //STOP and leave the readAck's as they were.

May 27, 2011
by OliverM
OliverM's Avatar

Hello Ralph,

the code to read the values from the module should look like this:

// read 5 bytes
    for(i = 0; i < 4; i++)
        {
            C_data[i]=i2c_readAck();
        }

// STOP
    C_data[4]=i2c_readNak();  // <-- Only change this line

    i2c_stop();

Oliver

May 27, 2011
by OliverM
OliverM's Avatar

And the code to start the measurement should be:

    // Third cycle
        i2c_write(0x01);    // Wake Up call  Slave ack
        i2c_stop();
        delay_ms(5);   // <--- put the 5ms delay after i2c_stop()

Oliver

May 27, 2011
by Ralphxyz
Ralphxyz's Avatar

Sorry Oliver, I should have realized what you were saying.

Here is what I am getting after the changes (rotated 360˚):

Register 1: 07 Xmsb: 161 Xlsb: 07 Ymsb: 179 Ylsb: 22 X is 41223 Y is 45846 
Register 1: 07 Xmsb: 161 Xlsb: 07 Ymsb: 179 Ylsb: 22 X is 41223 Y is 45846 
Register 1: 07 Xmsb: 161 Xlsb: 07 Ymsb: 180 Ylsb: 22 X is 41223 Y is 46102 
Register 1: 07 Xmsb: 161 Xlsb: 07 Ymsb: 180 Ylsb: 22 X is 41223 Y is 46102 
Register 1: 07 Xmsb: 161 Xlsb: 07 Ymsb: 180 Ylsb: 22 X is 41223 Y is 46102 
Register 1: 07 Xmsb: 159 Xlsb: 07 Ymsb: 182 Ylsb: 22 X is 40711 Y is 46614 
Register 1: 07 Xmsb: 159 Xlsb: 07 Ymsb: 182 Ylsb: 22 X is 40711 Y is 46614 
Register 1: 07 Xmsb: 216 Xlsb: 07 Ymsb: 143 Ylsb: 22 X is 55303 Y is 36630 
Register 1: 07 Xmsb: 222 Xlsb: 07 Ymsb: 140 Ylsb: 22 X is 56839 Y is 35862 
Register 1: 07 Xmsb: 221 Xlsb: 07 Ymsb: 142 Ylsb: 22 X is 56583 Y is 36374 
Register 1: 07 Xmsb: 223 Xlsb: 07 Ymsb: 141 Ylsb: 22 X is 57095 Y is 36118 
Register 1: 07 Xmsb: 224 Xlsb: 07 Ymsb: 140 Ylsb: 22 X is 57351 Y is 35862 
Register 1: 08 Xmsb: 31 Xlsb: 07 Ymsb: 146 Ylsb: 22 X is 7943 Y is 37398 
Register 1: 08 Xmsb: 39 Xlsb: 07 Ymsb: 149 Ylsb: 22 X is 9991 Y is 38166 
Register 1: 08 Xmsb: 39 Xlsb: 07 Ymsb: 151 Ylsb: 22 X is 9991 Y is 38678 
Register 1: 08 Xmsb: 44 Xlsb: 07 Ymsb: 152 Ylsb: 22 X is 11271 Y is 38934 
Register 1: 08 Xmsb: 76 Xlsb: 07 Ymsb: 196 Ylsb: 22 X is 19463 Y is 50198 
Register 1: 08 Xmsb: 78 Xlsb: 08 Ymsb: 00 Ylsb: 22 X is 19976 Y is 22 
Register 1: 08 Xmsb: 79 Xlsb: 07 Ymsb: 252 Ylsb: 22 X is 20231 Y is 64534 
Register 1: 08 Xmsb: 80 Xlsb: 07 Ymsb: 253 Ylsb: 22 X is 20487 Y is 64790 
Register 1: 08 Xmsb: 61 Xlsb: 08 Ymsb: 41 Ylsb: 22 X is 15624 Y is 10518 
Register 1: 08 Xmsb: 38 Xlsb: 08 Ymsb: 82 Ylsb: 22 X is 9736 Y is 21014 
Register 1: 08 Xmsb: 38 Xlsb: 08 Ymsb: 82 Ylsb: 22 X is 9736 Y is 21014 
Register 1: 07 Xmsb: 219 Xlsb: 08 Ymsb: 89 Ylsb: 22 X is 56072 Y is 22806 
Register 1: 07 Xmsb: 169 Xlsb: 08 Ymsb: 59 Ylsb: 22 X is 43272 Y is 15126 
Register 1: 07 Xmsb: 159 Xlsb: 08 Ymsb: 47 Ylsb: 22 X is 40712 Y is 12054 
Register 1: 07 Xmsb: 133 Xlsb: 07 Ymsb: 231 Ylsb: 22 X is 34055 Y is 59158 
Register 1: 07 Xmsb: 154 Xlsb: 07 Ymsb: 165 Ylsb: 22 X is 39431 Y is 42262 
Register 1: 07 Xmsb: 158 Xlsb: 07 Ymsb: 160 Ylsb: 22 X is 40455 Y is 40982 
Register 1: 07 Xmsb: 203 Xlsb: 07 Ymsb: 122 Ylsb: 22 X is 51975 Y is 31254 
Register 1: 07 Xmsb: 231 Xlsb: 07 Ymsb: 114 Ylsb: 22 X is 59143 Y is 29206 
Register 1: 07 Xmsb: 206 Xlsb: 07 Ymsb: 122 Ylsb: 22 X is 52743 Y is 31254

Register 1, is either 7 or 8 never 0.

Xmsb and Ymsb are 07 and 22 which does not make any sense. If they are fixed values why bother to read them?

Is the problem with Register (I am going to change that to Status) that it is read first?

How would I reverse the read order?

Ralph

May 27, 2011
by Ralphxyz
Ralphxyz's Avatar

Hey bretm, how do I use atan2(y, x)?

I added #include <math.h>

Here is my code:

            X = ((Xmsb <<8) | Xlsb);
            Y = ((Ymsb <<8) | Ylsb);

            uint16_t degrees;

            degrees = atan2(Y, X)*180/3.14;
            //degrees = atan(Y/X)*180/3.14;

            //degrees = 1123;   // this works!!

            lcd_home();
            lcd_line_one();
            fprintf_P(&lcd_stream, PSTR("Degrees are: %.2u"), degrees);

Here is the error.

make -C ../libnerdkits
make[1]: Nothing to be done for `all'.
avr-gcc -g -Os -Wall -mmcu=atmega328p  -o ../libnerdkits/twimaster.c ../libnerdkits/twimaster.o -c 
avr-gcc: ../libnerdkits/twimaster.o: linker input file unused because linking not done
avr-gcc -g -Os -Wall -mmcu=atmega328p  -Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm -o Compass.o Compass.c ../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o ../libnerdkits/twimaster.o
/usr/local/CrossPack-AVR-20100115/lib/gcc/avr/4.3.3/../../../../avr/lib/avr5/libc.a(inverse.o):../../../libm/fplib/inverse.S:50: relocation truncated to fit: R_AVR_13_PCREL against symbol `__divsf3' defined in .text section in /usr/local/CrossPack-AVR-20100115/lib/gcc/avr/4.3.3/avr5/libgcc.a(_div_sf.o)
make: *** [Compass.hex] Error 1
miniMac:compass Me$

Ralph

May 29, 2011
by Ralphxyz
Ralphxyz's Avatar

Well clawson over at AVRfreaks had the answer.

It was the -lm flag in the Makefile!

Here is the modified Makefile:

GCCFLAGS=-g -Os -Wall -mmcu=atmega328p 
LINKFLAGS=-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt #-lm
AVRDUDEFLAGS= -vvv -c avr109 -p m328p -F -b 115200 -P /dev/cu.PL2303-0000101D # USB1
ProjectName = Accel

LINKOBJECTS=../libnerdkits/delay.o ../libnerdkits/lcd.o ../libnerdkits/uart.o -lm

all:    $(ProjectName)-upload

$(ProjectName).hex: $(ProjectName).c
    make -C ../libnerdkits
    avr-gcc ${GCCFLAGS} ${LINKFLAGS} -o $(ProjectName).o $(ProjectName).c ${LINKOBJECTS}
    avr-objcopy -j .text -O ihex $(ProjectName).o $(ProjectName).hex

$(ProjectName).ass: $(ProjectName).hex
    avr-objdump -S -d $(ProjectName).o > $(ProjectName).ass

$(ProjectName)-upload:  $(ProjectName).hex
    avrdude ${AVRDUDEFLAGS} -U flash:w:$(ProjectName).hex:a

Now to get the Xlsb and Ylsb figured out.

Ralph

Post a Reply

Please log in to post a reply.

Did you know that two resistors can be used to make a voltage divider? Learn more...