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
Solved! Go to Solution.
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
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
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
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
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.
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
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
}
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] <<
y =((unsigned short)( (tmp[1] <<
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
|| 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".
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
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