Friday, 14 September 2012

I am lost.. and i have a compass...

I haven't made a post in a while, been rather busy with different projects, went to Portugal for work bit of a whirl wind trip.. but back to the electronics fun and games..

For one of my many side projects it needs a compass to know where it is. this device is to be aware of its location and environment to some degree. I need to know which way is up :)

I purchased from ebay a HMC5883L breakout board for about $15 delivered. once it arrived i located a library and some example code and it worked as advertised when i soldered it to a prototype shield and compiled the code and uploaded it to the mega2560. I thought sweet this will be great. i proceeded to load the code on my MAX32 chipkit arduino clone, i am planning to use this as my main dev platform as its a beast it rips through code at 80MIPs which is ideal for my application as i need the speed to update a graphical LCD at a nice rate a MEGA2560 doesn't have the grunt i need, i like the arduino IDE.

I loaded up the MAX32 with the example code and opened up the serial monitor to see whats doin.
things looked good, i was getting a similar output to the mega, then I did a full rotation of the compass and was not happy with the full results..

The mega gave me a nice 0-360' degree output as i rotated it about.
The MAX32 gave me 0-90' degrees then 0-90' (four times) and it had big holes where the output would jump from 45-75 with no steps between.
This is with the same code using the same compass wired to the same two pins.

I searched around on the net and found that there is a page that states the current status of various library's for the MAX32 and UNO32 platforms. this page didn't look like it had been updated in some time, i i took note of what they were saying. they report that the wire library produces bad stop bits when using the max32 hardware. it works with a uno32 (i tested it), so i then searched the net for any glimmer of hope that i could use the hardware i2c port on the mega, only to find no real info or help. that said i haven't hassled the manufacturer yet.

 Seeing as thou i couldn't find a answer to why the wire library wasn't working without reading all the header files and arduino dev bugs and stuff like that, i decided to bit bang me a IIc I2c I^c port on the max32.  now that i have completed this, i will dump the code at the end of this post. its a simple bit of code that is broken up in to the essential sections, i hope its not too confusing i have been modifying this for the last two weeks trying different things to explain my results, so there could be a bit of left over crap from previous methods i have tried..

So I seem to have replicated the hardware port 100% exactly the same in my bit banging code. I say this because i get exactly the same results as the example code produces.. that is, on the MEGA2560 i get nice results 0-360' degrees as i rotate the compass chip. with exactly the same code on the MAX32 i get the same results as the example code, 0-90' degrees four times with a hole in the results.. YAY i can copy something that doesn't work..

I have taken both boards to work and connected up the CRO probes to the mega and looked at a bit of the waveform that works.. then repeated the operation on the MAX32 and looked at the waveforms they looked ok until... what i  think is the result of a read command the voltage is a little lower than the rest of the transaction.. it was at 3.0v the chip and bus is referenced to the 3v rail when connected to both the mega and max32.. i was worried that the bus voltage might be a little low for the mega as its a 5v device and a Voh of 3.0v might not be read as a high on the mega. but testing has proven that the mega is happy with this for a Vih.



Code....


#define SDA_PIN  13  //20  //76
#define SCL_PIN  12  //21  //75
#define DRDY_PIN 74

#define HMC5883_W_add 0x1E
#define HMC5883_R_add 0x1E
#define HMC5883_A_add 0x1E      

#define CONFA 0x00
#define CONFB 0x01
#define MODER 0x02
#define OUTXH 0x03
#define OUTXL 0x04
#define OUTZH 0x05
#define OUTZL 0x06
#define OUTYH 0x07
#define OUTYL 0x08
#define STATU 0x09         // bit 1 of result 0 = data has been read
// bit 0 of result 1 = data is read to be read/
#define IDRA  0x0A
#define IDRB  0x0B
#define IDRC  0x0C
//  CONFIG_B value bits 765
#define scale088 0.73          //  000
#define scale130 0.92          //  001
#define scale190 1.22          //  010
#define scale250 1.52          //  011
#define scale400 2.27          //  100
#define scale470 2.56          //  101
#define scale560 3.03          //  110
#define scale810 4.35          //  111


//                   SShhhBB        // hhh = hz setting
#define CONFIG_A  0b01111000      // 
#define CONFIG_B  0b00000000      // 001 = 1.3Ga 000 = 0.88Ga
#define CONFIG_M  0b00000000      // continous read mode

float Scale_Factor = scale088;
int compassXYZ[6];
char temps[10];
char data;
char x;




void IIC_Init(void);          // sets up the hardware for idle state
void IIC_Startbit(void);         // sets the bus into start_bit cond.
void IIC_Stopbit(void);          // sets the bus into stop_bit cond.
unsigned char IIC_Send_Byte(unsigned char data);
void IIC_Send_Add_RW(unsigned char add,unsigned char rw);
unsigned char IIC_ReadByte(void);
char w;      // used to waste time.

float compass_read(int);
float raw_to_gs(int);

char count = 0;
char t,temp1,temp2,temp3;



//------------------------------------------------------------------------------
void setup(void)
{
  pinMode(SDA_PIN,OUTPUT);   // data
  pinMode(SCL_PIN,OUTPUT);   // clock
  digitalWrite(74,LOW);
  pinMode(DRDY_PIN,INPUT);   // DRDY_PIN data ready for reading INTor flag.

  Serial.begin(9600);
  Serial.println("Done");
  IIC_Init();  // sets up pins for I2c chat
  compass_init();
//  while(1);
}


void loop(void)
{
  if (digitalRead(DRDY_PIN) == 1)
  { 
    compass_read(compassXYZ);
    Serial.print(" X ");
    Serial.print(compassXYZ[0]);
    Serial.print(" Z ");
    Serial.print(compassXYZ[1]);
    Serial.print(" Y ");
    Serial.println (compassXYZ[2]);

    raw_to_gs(compassXYZ);
    Serial.print("          Heading -----> ");
    float head = atan2(compassXYZ[2],compassXYZ[0]);
    if (head < 0)
        head += 2 * PI;
   
    if (head > 2 * PI)
        head -= 2 * PI;
       
        head = head * 180/M_PI;
    Serial.println(head);
  }
  else
    Serial.print("*");

}


void compass_init(void)
{
  unsigned char IIC_t0 = 0;
  unsigned char IIC_t1 = 0;
  unsigned char IIC_t2 = 0;
  unsigned char IIC_t3 = 0;
  unsigned char IIC_t4 = 0;
  unsigned char IIC_t5 = 0;
  unsigned char IIC_temp0 = 0;
  unsigned char IIC_temp1 = 0;
  unsigned char IIC_temp2 = 0;
  unsigned char IIC_tempL=0;
  IIC_StartBit();
  IIC_Send_Add_RW(HMC5883_A_add,0);      // select the chip and command
  IIC_t0= IIC_SendByte(CONFA);      //  send address to write
  IIC_t1= IIC_SendByte(CONFIG_A);   // write value to register
  IIC_StopBit();
  delay(100);
  IIC_StartBit();
  IIC_Send_Add_RW(HMC5883_A_add,0);      // select the chip and command
  IIC_t0= IIC_SendByte(CONFB);      //  send address to write
  IIC_t3= IIC_SendByte(CONFIG_B);     // Write value to register
  IIC_StopBit();
  delay(100);
  IIC_StartBit();
  IIC_Send_Add_RW(HMC5883_A_add,0);      // select the chip and command
  IIC_t0= IIC_SendByte(MODER);      //  send address to write
  IIC_t5= IIC_SendByte(CONFIG_M);
  IIC_StopBit();
  delay(100);
  IIC_StartBit();
  IIC_Send_Add_RW(HMC5883_A_add,0);      // select the chip and command
  IIC_SendByte(CONFA);          // addressing CONFIG_A reg
  IIC_StopBit();

  IIC_StartBit();
  IIC_Send_Add_RW(HMC5883_A_add,1);      // select the chip and command
  IIC_temp0 = IIC_ReadByte();      // read CONFIG_A
  IIC_StopBit();

  IIC_StartBit();
  IIC_Send_Add_RW(HMC5883_A_add,0);      // select the chip and command
  IIC_SendByte(CONFB);          // addressing CONFIG_A reg
  IIC_StopBit();

  IIC_StartBit();
  IIC_Send_Add_RW(HMC5883_A_add,1);      // select the chip and command
  IIC_temp1 = IIC_ReadByte();      // read CONFIG_B (address auto increment)
  IIC_StopBit();

  IIC_StartBit();
  IIC_Send_Add_RW(HMC5883_A_add,0);      // select the chip and command
  IIC_SendByte(MODER);          // addressing CONFIG_A reg
  IIC_StopBit();

  IIC_StartBit();
  IIC_Send_Add_RW(HMC5883_A_add,1);      // select the chip and command
  IIC_temp2 = IIC_ReadByte();      // read MODE register
  IIC_StopBit();

//   Debug info

   Serial.println(IIC_t0,HEX);
   Serial.println(IIC_t1,HEX);
   Serial.println(IIC_t2,HEX);
   Serial.println(IIC_t3,HEX);
   Serial.println(IIC_t4,HEX);
   Serial.println(IIC_t5,HEX);
   Serial.print("CONFIG_A =");
   Serial.println(CONFIG_A,HEX);
   Serial.println(IIC_temp0,HEX);
   Serial.print("CONFIG_B =");
   Serial.println(CONFIG_B,HEX);
   Serial.println(IIC_temp1,HEX);
   Serial.print("CONFIG_M =");
   Serial.println(CONFIG_M,HEX);
   Serial.println(IIC_temp2,HEX);
  
}


int compass_read(int comp[3])
{
  //  this attempts to read 6 bytes out of the compass from 0x03 to 0x09
  //
  unsigned char XH=0,XL=0,ZH=0,ZL=0,YH=0,YL=0;
  //  unsigned char temp_data;

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,0);  // send the address with Write mode
  IIC_SendByte(0x03);                // send the value of the address we want to read
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,1);  // send the address with Write mode  
  XH = IIC_ReadByte();          // X high read from chip
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,0);  // send the address with Write mode
  IIC_SendByte(0x04);                // send the value of the address we want to read
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,1);  // send the address with Write mode  
  XL = IIC_ReadByte();          // X Low  next byte
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,0);  // send the address with Write mode
  IIC_SendByte(0x05);                // send the value of the address we want to read
  IIC_StopBit();
 
  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,1);  // send the address with Write mode  
  ZH = IIC_ReadByte();          // Z high
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,0);  // send the address with Write mode
  IIC_SendByte(0x06);                // send the value of the address we want to read
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,1);  // send the address with Write mode  
  ZL = IIC_ReadByte();          // Z Low
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,0);  // send the address with Write mode
  IIC_SendByte(0x07);                // send the value of the address we want to read
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,1);  // send the address with Write mode  
  YH = IIC_ReadByte();          // Y high
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883_R_add,0);  // send the address with Write mode
  IIC_SendByte(0x08);                // send the value of the address we want to read
  IIC_StopBit();

  IIC_StartBit();                    // send a start condition on the bus
  IIC_Send_Add_RW(HMC5883  _R_add,1);  // send the address with Write mode  
  YL = IIC_ReadByte();          // Y Low
  IIC_StopBit();          // we have read all the bytes we need and finish the conversation

  comp[0] = (XH << 8) + XL;
  comp[1] = (ZH << 8) + ZL;
  comp[2] = (YH << 8) + YL;

  return *comp;          // tell everyone what we heard

}


float raw_to_gs(int Scaled[3])
{
  float RESULTS[3];
  RESULTS[0] = float(Scaled[0]) * Scale_Factor;
  RESULTS[1] = float(Scaled[1]) * Scale_Factor;
  RESULTS[2] = float(Scaled[2]) * Scale_Factor;
  return *RESULTS;
}

// I2c IIC  I^c interface core below here

void IIC_Init(void)          // sets up intial pin state
{
  pinMode(SCL_PIN,OUTPUT); 
  pinMode(SDA_PIN,OUTPUT); 
  digitalWrite(SDA_PIN,HIGH);  // master idle mode   
  digitalWrite(SCL_PIN,HIGH);  // master idle mode   
}


void IIC_StartBit(void)      // sends a start condition on the IIC bus
{  // data changes while clock high
  pinMode(SDA_PIN,OUTPUT);   
  delayMicroseconds(21); 
  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21); 
  digitalWrite(SDA_PIN,LOW);
}

void IIC_StopBit(void)        // sends/creates a stop condition on the IIc bus
{       // data changes while clock high
  pinMode(SDA_PIN,OUTPUT);   
  digitalWrite(SCL_PIN,HIGH); 
  delayMicroseconds(21); 
  digitalWrite(SDA_PIN,HIGH);
}

unsigned char IIC_SendByte(unsigned char data)        // sends  byte bit by bit
{  // data changes while clock low
  //  char dla;
  //  data = 0xFF;
  unsigned char ack_result;
 
  pinMode(SDA_PIN,OUTPUT);
  delayMicroseconds(21);          
  // Start of bit 8 MSB 
  digitalWrite(SCL_PIN,LOW);     
  delayMicroseconds(21);             //waits a little bit of time to wait for clock to stablize
  if ((data & 0b10000000)==0)
    digitalWrite(SDA_PIN,LOW);
  else
    digitalWrite(SDA_PIN,HIGH);      // sets or clears the data line according to the bit 8
  delayMicroseconds(21);            // lets data settle on the bus
  digitalWrite(SCL_PIN,HIGH);      // tells slave to read the pin
  delayMicroseconds(21);            // gives the slave time to read the pin
 // FINSIH of bit 7 START of bit 6  // and repeats for the rest of the byte
  digitalWrite(SCL_PIN,LOW);   
  if ((data & 0b01000000)==0)
    digitalWrite(SDA_PIN,LOW);
  else
    digitalWrite(SDA_PIN,HIGH);
  delayMicroseconds(21);
  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
 // FINSIH of bit 6 START of bit 5
  digitalWrite(SCL_PIN,LOW);
  if ((data & 0b00100000)==0)
    digitalWrite(SDA_PIN,LOW);
  else
    digitalWrite(SDA_PIN,HIGH);
  delayMicroseconds(21);
  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
 // FINSIH of bit 5 START of bit 4
  digitalWrite(SCL_PIN,LOW);
  if ((data & 0b00010000)==0)
    digitalWrite(SDA_PIN,LOW);
  else   digitalWrite(SDA_PIN,HIGH);
  delayMicroseconds(21);
  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
 // FINSIH of bit 4 START of bit 3
  digitalWrite(SCL_PIN,LOW);
  if ((data & 0b00001000)==0)
    digitalWrite(SDA_PIN,LOW);
  else
    digitalWrite(SDA_PIN,HIGH);
  delayMicroseconds(21);
  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
 // FINSIH of bit 3 START of bit 2
  digitalWrite(SCL_PIN,LOW);
  if ((data & 0b00000100)==0)
    digitalWrite(SDA_PIN,LOW);
  else
    digitalWrite(SDA_PIN,HIGH);
  delayMicroseconds(21);
  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
 // FINSIH of bit 2 START of bit 1
  digitalWrite(SCL_PIN,LOW);
  if ((data & 0b00000010)==0)
    digitalWrite(SDA_PIN,LOW);
  else
    digitalWrite(SDA_PIN,HIGH);
  delayMicroseconds(21);
  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
 // FINSIH of bit 1 START of bit 0
  digitalWrite(SCL_PIN,LOW);
  if ((data & 0b00000001)==0)
    digitalWrite(SDA_PIN,LOW);
  else
    digitalWrite(SDA_PIN,HIGH);
  delayMicroseconds(21);
  digitalWrite(SCL_PIN,HIGH); 
  delayMicroseconds(21);
  // FINSIH of bit 0 START of ACK bit
  digitalWrite(SCL_PIN,LOW);  
  pinMode(SDA_PIN,INPUT);        // set data pin to read
  delayMicroseconds(25);
  digitalWrite(SCL_PIN,HIGH);    // clock in the ack signal
  delayMicroseconds(25);
  ack_result = digitalRead(SDA_PIN);   
  digitalWrite(SCL_PIN,LOW);   
  digitalWrite(SDA_PIN,LOW);     // could allow clock streatching here wait for ack (if high wait for low)    
  pinMode(SDA_PIN,OUTPUT);        // set data pin to write
  delayMicroseconds(21);
//added above two lines since scoped. seems to work.
  return ack_result;
}

unsigned char IIC_ReadByte(void)        // receives a byte bit by bit.   
{
  unsigned char b,result = 0,var;   // index bit
//  digitalWrite(SDA_PIN,LOW);
  pinMode(SDA_PIN,INPUT);      // done after clock is low to avoid stop/start cond

  digitalWrite(SCL_PIN,LOW);   // makes sure the bus is low
  delayMicroseconds(21);
  digitalWrite(SCL_PIN,HIGH);  // pull clock high
  delayMicroseconds(21);        // wait for data to be valid
  var = digitalRead(SDA_PIN);  // read the pin
  if (var==1)                 
    result = (result | 0b10000000);    // load up the result
  digitalWrite(SCL_PIN,LOW);   // could move up three line to above the if and use the if time for delay
  delayMicroseconds(21);

  digitalWrite(SCL_PIN,HIGH);    // set the clock
  delayMicroseconds(21);
  var = digitalRead(SDA_PIN);    // read the data pin
  if (var==1)
    result = (result | 0b01000000);  // store result
  digitalWrite(SCL_PIN,LOW); 
  delayMicroseconds(21);          // start next bit

  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
  var = digitalRead(SDA_PIN);
  if (var==1)
    result = (result | 0b00100000);
  digitalWrite(SCL_PIN,LOW); 
  delayMicroseconds(21);

  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
  var = digitalRead(SDA_PIN);
  if (var==1)
    result = (result | 0b00010000);
  digitalWrite(SCL_PIN,LOW); 
  delayMicroseconds(21);

  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
  var = digitalRead(SDA_PIN);
  if (var==1)
    result = (result | 0b00001000);
  digitalWrite(SCL_PIN,LOW); 
  delayMicroseconds(21);

  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
  var = digitalRead(SDA_PIN);
  if (var==1)
    result = (result | 0b00000100);
  digitalWrite(SCL_PIN,LOW); 
  delayMicroseconds(21);

  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
  var = digitalRead(SDA_PIN);
  if (var==1)
    result = (result | 0b00000010);
  digitalWrite(SCL_PIN,LOW); 

  delayMicroseconds(21);
  digitalWrite(SCL_PIN,HIGH);
  delayMicroseconds(21);
  var = digitalRead(SDA_PIN);
  if (var==1)
    result = (result | 0b00000001);
  digitalWrite(SCL_PIN,LOW); 
  delayMicroseconds(21);
 
  pinMode(SDA_PIN,INPUT);
  delayMicroseconds(21);       
  digitalWrite(SCL_PIN,HIGH);   
  delayMicroseconds(25);       
  var = digitalRead(SDA_PIN);
  digitalWrite(SCL_PIN,LOW);
  pinMode(SDA_PIN,OUTPUT);
  delayMicroseconds(21);       
//  digitalWrite(SDA_PIN,HIGH);
//  result = 0;
  return result;
}


void IIC_Send_Add_RW(unsigned char IIC_add,unsigned char IIC_rw)
{
  unsigned char IIC_data = 0;
//  Serial.print("in add :");
//  Serial.print(add,HEX);
 IIC_data = (IIC_add <<1);      // shifts address to MSB's
  IIC_data = (IIC_data | IIC_rw);    // adds rw bit in LSB 0=write 1 = read as per datasheet.
//  Serial.print(" out data :");
//  Serial.println(data,HEX);
  IIC_SendByte(IIC_data);

}







What the above code attempts to do is..


setup
prep the SDA, SCL pins for I2c operation

Init_Compass
init the compass chip.
         this sends CONFIG_A,B,M to register 0,1,2 it then reads these registers and might print them on the serial port for debugging.



Loop
this reads registers 3-8 of the compass and does a little maths on it and produces the heading value depending on how the compass chip is rotated.

raw_to_gs
    scales the results of the compass_read values

the main loop then does the maths on the result to get the heading.

This code (for me) gives the same results as the HMC5883 example code i found.

If anyone can point out where i have gone wrong please publicly ridicule me in the comments section, just be sure to show where i went wrong..

i am sure someone will tell me that my problems are speed related, but i get the same results at 10khz as 370khz.. as measured on the CRO at work. i have tired slowing things down and speeding up but all gives the same results.. pull up resistors are 2k2.. i cant explain the small sections of 3.0v on the bus when using the MAX32..

2 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. i have a solution.. see the post on April 19th. 2013. happy days.

    ReplyDelete