HCS12 SPI to Digital Compass HM55B

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

HCS12 SPI to Digital Compass HM55B

Jump to solution
4,667 Views
alejo_dg
Contributor I

Hello everyone!

 

I am trying to communicate to a digital compass HM55B using SPI. My microcontroller is a CMS12C32. I am very confused because the way of reading back the bits from the compass.

I have written several times the code below and no results yet. I even replaced the compass for a new one.

Sent to Compass:

0b0000 to reset 

0b1000 to start meausurement

0b1100 to check status flag

 

Receive from Compass:

0b1100 everything is ok

After 0b1100, it receives 11-bits x component followed by 11-bit y component.

 

I have attached a graphic showing the compass information.

 

#define EN_N PTT = 0x00 //Enable LOW
#define EN_Y PTT = 0x01 //Enable HIGH

int x_angle = 0;
int y_angle = 0;
unsigned char buf_reset[4] = {0,0,0,0};
unsigned char buf_start[4] = {1,0,0,0};
unsigned char buf_check[4] = {1,1,0,0};

unsigned char staus_flag;

void main(void) {
  int i;  

  //setup PPT
  DDRT_DDRT0=1; //configure PORT T[0] for output
 
  //Init SPI
  SPICR1= 0x5e; //0x22 low clk
  SPICR2= 0x10;
  SPIBR = 0x70; //1MHz
 
  for(;;){
   
    do{
     
      //Reset Compass
      EN_Y;
      waitms(10);
      EN_N;
      for(i=0; i<4; i++) {
        while ((SPISR & 0x20) == 0);
        SPIDR = buf_reset[i];
      }
      EN_Y;
      waitms(50);
     
      //Start Compass
      EN_N;
      for(i=0; i<4; i++){
        while ((SPISR & 0x20) == 0);
        SPIDR = buf_start[i];
      }

      //Check Measurement
      waitms(50);
      EN_Y;
      waitms(10);
      EN_N;
      for(i=0; i<4; i++){
        while ((SPISR & 0x20) == 0);
        SPIDR = buf_check[i];
      }

      //Get Status Flag
      staus_flag = getcspi();
     
    } while (staus_flag != 0b1100); //exit if status is 0b1100


    x_angle = getcspi(); //I never get to this point
    y_angle = getcspi();

  }
}
/**********************************
*  Subroutines          
***********************************/
int getcspi(void)
{
  while(!SPISR_SPTEF);// wait until write is permissible
  SPIDR = 0x00;       // trigger 8 SCK pulses to shift in data
  while(!SPISR_SPIF); // wait until a byte has been shifted in
  return SPIDR;       // return the character
}

 

 

Thanks for you ay help!

 

Pedro

Labels (1)
0 Kudos
1 Solution
802 Views
bigmac
Specialist III

Hello Pedro,

 

Some further observations and issues with the code so far.

 

The read of the compass status appears quite "tricky".  The upper nybble of the send byte represents the command, and the lower nybble of the simultaneously returned byte represents the status. 

 

A further complication is that, in the event that the reading is incomplete, the enable line must be raised and then lowered again for the next status command.  However, if the reading has been completed, the enable line remains low while the return of the reading takes place.  The enable line is then raised again after the value is returned.

 

Note that in the following code, I have changed the naming of the macros to EN_L and EN_H (in lieu of EN_N and EN_Y), which is more meaningful to me, especially since the active state of the enable line is low.  For the handling of the individual bytes, I have created a union, that seems to make the eventual processing of the data a little less cumbersome.

 

Finally, a point that so far seems to have been overlooked is that the two 11-bit quantities are signed values.  So when the 11-bit values are converted to 16-bits, they will need to be sign extended.

 

// Compass commands:
#define RESET  0x00
#define START  0x88
#define STATUS 0xC0

#define NEG_MASK  0xF800
#define SIGN_MASK 0x0400

typedef union {
   long val32;
   int  w[2];
   byte b[4];
} DWORD;

int Xdata, Ydata;

void HM55B_proc( void)
{
   DWORD rdg;
   byte i;
   
   EN_L;
   (void)getcspi( RESET);
   EN_H;
   EN_L;
   (void)getcspi( START);  // Commence conversion
   EN_H;
   EN_L;

 

   // Wait until measurement is complete
   while ((getcspi( STATUS) & 0x0C) == 0) {
      EN_H;
      EN_L;
   }

 

   rdg.b[0] = 0;
   for (i = 1; i < 4; i++)
      rdg.b[i] = getcspi( 0);
   EN_H;

   rdg.val32 >>= 2;            // Right justify 22 data bits

   Ydata = rdg.w[1] & 0x07FF;  // 11-bit signed Y-value
   if (Ydata & SIGN_MASK)
      Ydata |= NEG_MASK;       // Sign extend 16-bit value
      
   rdg.val32 >>= 11;           // Right justify X-value

   Xdata = rdg.w[1];           // 11-bit signed X-value
   if (Xdata & SIGN_MASK)
      Xdata |= NEG_MASK;       // Sign extend 16-bit value
}

 

As Kef has already suggested, the settings of CPHA and CPOL bits will need to match the device requirements.

 

Regards,

Mac

 

View solution in original post

0 Kudos
9 Replies
802 Views
bigmac
Specialist III

Hello Pedro, and welcome to the forum.

 

This post should really have been to the 16-bit forum.  However, since the query is about the SPI module,  I think  I may be able to help.

 

Within the  do-while loop in your code, you are sending multiple bytes to the compass, but you do not wait until each transfer is complete, nor clear the SPIF flag, before sending the next byte.  This will result in an over-run error that will prevent the transfer of further data, until the SPIF flag is cleared.  It is necessary to test and clear the flag, even when the return data is not used.

 

You already do this correctly with the getcspi() function.  Why don't you adapt this function so can also be used to send a non-zero value?  For example - 

 

unsigned char getcspi( unsigned char val)
{
  while(!SPISR_SPTEF); // wait until write is permissible
  SPIDR = val;         // trigger 8 SCK pulses to shift in data
  while(!SPISR_SPIF);  // wait until a byte has been shifted in
  return SPIDR;        // return the character
}

Note that the return should be an 8-bit value, rather than 16 bits, for more efficient code.  Once you wait until each transfer is complete, some of the additional delays may be unnecessary.

 

Check the compass datasheet to see whether the enable signal should be a pulse at the beginning of each transfer, or whether the enable should remain low for the duration of each transfer.  The latter would be more usual, but will depend on the requirements of the device. 

 

The following example shows the use of the function to send data.  The cast should avoid a compiler warning that the return value is not used.

 

for (i = 0; i < 4; i++)

   (void)getcspi( buf_reset[i]);

 

Regards,

Mac

0 Kudos
802 Views
alejo_dg
Contributor I

Mac,

Thanks for your replay. I made some changes to the code based on your help, but I still don't get the bits back. What it confuse me is that the values I sent to the compass are always 4 bits at the time. Not a byte. Did you see the attachment? It shows how the bits have to be sent.

 

I also deleted some delays and changed them, but nothing back.

 

This is what I changed!

 

 

#define EN_N PTT = 0x00 //Enable LOW#define EN_Y PTT = 0x01 //Enable HIGHunsigned char getcspi( unsigned char val);unsigned char x_angle;unsigned char y_angle;unsigned char buf_reset[4] = {0,0,0,0};unsigned char buf_start[4] = {1,0,0,0};unsigned char buf_check[4] = {1,1,0,0};unsigned char status_flag;void main(void) {  int i;     //setup PPT  DDRT_DDRT0=1; //configure PORT T[0] for output    //Init SPI  SPICR1= 0x5e; //high clk  SPICR2= 0x10;  SPIBR = 0x70; //1MHz    for(;;){    do{            //Reset Compass      EN_Y;      waitms(10);      EN_N;      for(i=0; i<4; i++) {        (void)getcspi(buf_reset[i]);      }      EN_Y;      waitms(10);            //Start Compass      EN_N;      for(i=0; i<4; i++){        (void)getcspi(buf_start[i]);      }      //Check Measurement      waitms(50);      EN_Y;      waitms(10);      EN_N;      for(i=0; i<4; i++){        (void)getcspi(buf_check[i]);      }      //Get Status Flag      status_flag = getcspi(0);          } while (status_flag != 0b1100); //exit if status is 0b1100    x_angle = getcspi(0); //I never get to this point    y_angle = getcspi(0);   }}/***********************************  Subroutines           ***********************************/unsigned char getcspi( unsigned char val){  while(!SPISR_SPTEF); // wait until write is permissible  SPIDR = val;         // trigger 8 SCK pulses to shift in data  while(!SPISR_SPIF);  // wait until a byte has been shifted in  return SPIDR;        // return the character}

 

I am posting this message in the 16-bit forum as well. Im sorry for that.

 

Thanks

Pedro 

 

0 Kudos
802 Views
kef
Specialist I

alejo_dg,

 

your picture from module manual differs quite well from pictures from HM55B IC datasheet:

http://www.parallax.com/Portals/0/Downloads/docs/prod/compshop/HM55BDatasheet.pdf

from here

http://www.parallax.com/StoreSearchResults/tabid/768/txtSearch/29123/List/0/SortField/4/ProductID/98...

 

IC datasheet is also fuzzy about what will happen if you send 8 bits instead of 4 for reset or start command. But I think it should be safe to send 0x00 for reset command, 0x88 for start command and 0xCC for read command.

 

Code for reset should be something like

 EN_N;

 getcspi(0x00);

 EN_Y;

 

Start

 EN_N;

 getcspi(0x88);

 EN_Y;

 

Read

 

char status_bits;

short X;

short Y;

char tmp[3];

 

 EN_N;

 status_bits = getcspi(0xCC);

 tmp[0] = getcspi(0);

 tmp[1] = getcspi(0);

 tmp[2] = getcspi(0);

 EN_Y;

 X = (unsigned short)(tmp[0] << 8) || tmp[1]) >> 5;

 Y = ( (unsigned short)(tmp[1] << 8) || tmp[2]) >> 2 ) & 0x7FF;

 

Also make sure CPOL, CPHA and bitrate settings are correct.

0 Kudos
802 Views
alejo_dg
Contributor I

Kef,

Thanks for your help. Finally I see data comming in from the compass, but the x and y are always 0 or 1.

 

This 2 equations have missing paranthesis.

 

X = (unsigned short)(tmp[0] << 8) || tmp[1]) >> 5;

Y = ( (unsigned short)(tmp[1] << 8) || tmp[2]) >> 2 ) & 0x7FF;

 

I added the missing paranthesis.

x = ((unsigned short)(tmp[0] << 8) || tmp[1] >> 5);
y = ((unsigned short)(tmp[1] << 8) || tmp[2] >> 2 ) & 0x7FF; 

 

 

Do I need to check if the reading is 1100? so i can start calculating the x and y components?

 

Thanks,

Pedro

0 Kudos
802 Views
kef
Specialist I

Oh, didn't verify it. But I think you added parenthesis in the wrong places. I meant this:

 

x = (unsigned short)( (tmp[0] << 8) || tmp[1] ) >> 5;
y = ( (unsigned short)( (tmp[1] << 8) || tmp[2] ) >> 2 ) & 0x7FF; 

 

Still not tested, but seems OK

 

 

Yes, I forgot about status flags:

 

#define ENDFLAG_MASK 0xC

#define ERRFLAG_MASK 0x3

 

Code to read X and Y should be then

 

 EN_N;

 status_bits = getcspi(0xCC);

 if(status_bits & ENDFLAG_MASK)

 {

    tmp[0] = getcspi(0);

    tmp[1] = getcspi(0);

    tmp[2] = getcspi(0);

 

    x =   (unsigned short)( (tmp[0] << 8) || tmp[1] ) >> 5;
    y = ( (unsigned short)( (tmp[1] << 8) || tmp[2] ) >> 2 ) & 0x7FF; 

 }

 EN_Y;

 

 if(status_bits & ERRFLAG_MASK)

 {

     // overflow error

  }

Message Edited by kef on 2009-05-07 09:01 PM
0 Kudos
802 Views
alejo_dg
Contributor I

Kef,

 

I get bits back for x and y if I change "||" for both equations, but when I put it into an equation to get the angle, the result does not change that much. 

 

x = (unsigned short)( (tmp[0] << || tmp[1] ) >> 5;
y =((unsigned short)( (tmp[1] << || tmp[2] ) >> 2 ) & 0x7FF;

 

It looks like I always get back 3 or 4 sets of bits for x and y.

 

This is what I have after the changes

if(status_bits & ENDFLAG_MASK) {
     
      tmp[0] = getcspi(0);
      tmp[1] = getcspi(0);
      tmp[2] = getcspi(0);
     
      x = (unsigned short)( (tmp[0] << 8) | tmp[1] ) >> 5;
      y =((unsigned short)( (tmp[1] << 8) | tmp[2] ) >> 2 ) & 0x7FF;
   
      angle = atan2(-y, x);   //angle is float
      if (angle < 0)
   angle += 360;

      angle += 90;    //Add 90 degrees
      if (angle > 360)
         angle -= 360; //ensure that angle is not larger than 360
   
}

Thanks,

Pedro

 

0 Kudos
802 Views
kef
Specialist I

|| is logical or operator. (a || b) expression means if a or b aren't zero, then result is not zero (1).

 

| is bitwise or operator. 0b0010 | 0b1000 equals 0b1010

 

Did you chek CPHA and CPOL settings like I suggested? You didn't! If you would check HM55 datasheet, you would notice that data is shifted out from HM55 on rising CLK edge and data is sampled in on falling CLK edge. And your code is setting both CPOL and CPHA to ones. Now check if it is what you need studying S12 / SPimodule documentation, see chapters "CPHA = 0 Transfer Format" and "CPHA = 1 Transfer Format".

0 Kudos
803 Views
bigmac
Specialist III

Hello Pedro,

 

Some further observations and issues with the code so far.

 

The read of the compass status appears quite "tricky".  The upper nybble of the send byte represents the command, and the lower nybble of the simultaneously returned byte represents the status. 

 

A further complication is that, in the event that the reading is incomplete, the enable line must be raised and then lowered again for the next status command.  However, if the reading has been completed, the enable line remains low while the return of the reading takes place.  The enable line is then raised again after the value is returned.

 

Note that in the following code, I have changed the naming of the macros to EN_L and EN_H (in lieu of EN_N and EN_Y), which is more meaningful to me, especially since the active state of the enable line is low.  For the handling of the individual bytes, I have created a union, that seems to make the eventual processing of the data a little less cumbersome.

 

Finally, a point that so far seems to have been overlooked is that the two 11-bit quantities are signed values.  So when the 11-bit values are converted to 16-bits, they will need to be sign extended.

 

// Compass commands:
#define RESET  0x00
#define START  0x88
#define STATUS 0xC0

#define NEG_MASK  0xF800
#define SIGN_MASK 0x0400

typedef union {
   long val32;
   int  w[2];
   byte b[4];
} DWORD;

int Xdata, Ydata;

void HM55B_proc( void)
{
   DWORD rdg;
   byte i;
   
   EN_L;
   (void)getcspi( RESET);
   EN_H;
   EN_L;
   (void)getcspi( START);  // Commence conversion
   EN_H;
   EN_L;

 

   // Wait until measurement is complete
   while ((getcspi( STATUS) & 0x0C) == 0) {
      EN_H;
      EN_L;
   }

 

   rdg.b[0] = 0;
   for (i = 1; i < 4; i++)
      rdg.b[i] = getcspi( 0);
   EN_H;

   rdg.val32 >>= 2;            // Right justify 22 data bits

   Ydata = rdg.w[1] & 0x07FF;  // 11-bit signed Y-value
   if (Ydata & SIGN_MASK)
      Ydata |= NEG_MASK;       // Sign extend 16-bit value
      
   rdg.val32 >>= 11;           // Right justify X-value

   Xdata = rdg.w[1];           // 11-bit signed X-value
   if (Xdata & SIGN_MASK)
      Xdata |= NEG_MASK;       // Sign extend 16-bit value
}

 

As Kef has already suggested, the settings of CPHA and CPOL bits will need to match the device requirements.

 

Regards,

Mac

 

0 Kudos
802 Views
alejo_dg
Contributor I

Kef and Mac,

 

Thanksfor everything! It is working now!

 

Kef,

I did study the chapter for SPI before. I set the CPOL and CPHA for rising edge before, and actually didn't get anything back. The only way I got it working is with CPOL=1 ad CPHA=1. I tried all the combinations and didn't work. It is wierd because the documentation says the opposite.

 

So I had to add a delay before reading from the compass, and I had to change the order of LSB and MSB when shifting in and out.

 

Then, after getting my x and y components, Mac suggested converting the 11 bits to 16.

 

 

#define EN_LOW  PTT_PTT7 = 0 //Enable LOW#define EN_HIGH PTT_PTT7 = 1  //Enable HIGH#define ENDFLAG_MASK 0xC// variable declarationschar status_bits;char tmp[3];int x; int y;float angle = 0;unsigned char getcspi( unsigned char val);float GetAngle(void);void main(void) {  //setup PPT  DDRT_DDRT7=1; //configure PORT T[7] for output    //Init SPI  SPICR1= 0x5c;  SPICR2= 0x00;  SPIBR = 0x03; //2MHz    for(;;){          SPICR1_LSBFE= 1;      ////LSB first        //Reset Compass    EN_LOW;    (void)getcspi(0x00);    EN_HIGH;        //Start Compass    EN_LOW;    (void)getcspi(0x88);      EN_HIGH;        //Read    EN_LOW;    status_bits = getcspi(0xCC);      waitms(40);                SPICR1_LSBFE= 0;       //MSB first    if(status_bits & ENDFLAG_MASK) {            tmp[0] = getcspi(0);      tmp[1] = getcspi(0);      tmp[2] = getcspi(0);            x = (unsigned short)( (tmp[0] << 8) | tmp[1] ) >> 5;      y =((unsigned short)( (tmp[1] << 8) | tmp[2] ) >> 2 ) & 0x7FF;           if ((x & 0x0400) != 0) x |= (short)0xF800;//convert 11bit signed integer      if ((y & 0x0400) != 0) y |= (short)0xF800;//to 16bit signed integer            angle = atan2(-y, x) * 57.2957795;      if (angle < 0)       angle += 360;          }    EN_HIGH;    waitms(1000);  //debugging purposes  }}/***********************************  Subroutines           ***********************************/unsigned char getcspi( unsigned char val){  while(!SPISR_SPTEF); // wait until write is permissible  SPIDR = val;         // trigger 8 SCK pulses to shift in data  while(!SPISR_SPIF);  // wait until a byte has been shifted in  return SPIDR;        // return the character}

 

 

 

I heartily thank both of you for your time!

Pedro

 

 

 

0 Kudos