LSM303 acceleration scale

For what now appears to be no real reason (well, perhaps based on some comment I found somewhere on the net and/or misreading the data sheet), I expected the LSM303DLM acceleration values to be directly in mg (i.e. 10e-3 x g).

Observations of an LSM303DLM (Pololu #1273) suggest the values are simply scaled in the sensor to fit the 16-bit range, that is “full scale” == +/- 2^15, where the actual value of full scale depends on the FS bits (+/- 2g, 4g or 8g).

i.e. to convert to an acceleration measure a.x to g’s, use:

gx = (float)a.x / FS_G

where:

FS  Range   Divisor (FS_G)
00  2       2^14
01  4       2^13
11  8       2^12

e.g., for FS = 00

gx = (float)a.x / (1<<14)

Ok, kind of obvious in hindsight.

The datasheet does note a procedure in section 4.1 to determine the sensitivity “calibration” by measuring the maxima on rotating about each axis and assuming g to be 1.0; I wish they could have simply said “scale by 2^14 (FS=00), 2^13 (FS=01) or 2^12 (FS=11)”.

Ben


Below are my notes on this, if anyone’s interested.

The data sheet Sensor Characteristics lists LA_So ‘Linear acceleration sensitivity’ as a “12-bit representation”, which I initially interpreted to mean a range of 12 bits, or -2048 to 2047 - however it means simply that the range is 12 bits; the least 4 bits of the 16 bit value are meaningless (zero, it seems).

It notes the sensitivities for each range as:

FS      range  sensitivity, mg/digit
00        2       1
01        4       2
10        8       3.9

With the FS bits of CTRL_REG4_A set to 00 (the default) which the data sheet says is a full scale range of +/-2G, and the chip (board) mounted with Z up, I’d presumed raw measurements from the accelerometer of around (0,0,1000).

Here’s what I actually get (code is at the end):
(fields are acceleration x, y, z followed by magnetometer x, y, z)

Init FS 00
+0000 +0000 +0000	-0168 -0263 +0590
+2320 -1552 +14496	-0169 -0262 +0591
+1440 -0320 +16176	-0167 -0262 +0592
+1504 -0464 +16272	-0164 -0264 +0591
+1552 -0464 +16080	-0167 -0264 +0588
+1408 -0656 +16016	-0167 -0262 +0590
+1408 -0304 +16080	-0164 -0266 +0594
+1280 -0672 +16160	-0164 -0267 +0592
+1600 -0656 +16192	-0167 -0260 +0587
+1312 -0432 +16256	-0164 -0263 +0590
+1488 -0688 +16016	-0165 -0267 +0590
+1456 -0560 +16288	-0167 -0262 +0588

on rotating the board, the X and Y values increase depending on the rotation axis, Z decreases, as expected. Magnitudes are about the same as above.

Vigorously shaking the board gives peaks of around 32000 (i.e. max signed int):

+1344 -0688 +16192	-0153 -0268 +0589
+1808 -0496 +16480	-0154 -0268 +0592
+1376 -0464 +16528	-0152 -0267 +0592
+6416 -4816 +24320	-0122 -0292 +0588
-0320 -2752 +15936	-0151 -0248 +0629
+21072 -1536 +32752	-0235 -0275 +0640
-0848 +0064 -8464	-0193 -0255 +0634
-1344 -2192 +16448	-0202 -0276 +0620
+0784 +0960 +16720	-0164 -0263 +0612
+3776 -1296 +15552	-0085 -0292 +0578
+22960 -5920 +32752	-0116 -0289 +0589
-9808 +3136 -32704	-0167 -0288 +0616

FS = 10 (+/-4g)

Init FS 10
+0688 -0736 +15008	-0148 -0255 +0565
+0784 -0240 +8128	-0150 -0257 +0571
+0672 -0256 +7888	-0145 -0255 +0567
+0816 -0368 +8032	-0149 -0258 +0571
+0656 -0272 +8032	-0147 -0251 +0567
+0736 -0480 +7872	-0147 -0253 +0567
+0688 -0352 +7984	-0148 -0254 +0567
+0800 -0272 +7888	-0149 -0255 +0568
+0912 -0352 +7888	-0148 -0256 +0570
+0656 -0448 +7984	-0144 -0257 +0569

shaking:

+8144 -3584 +10400	-0047 -0295 +0588
-3952 -1136 -13552	-0047 -0282 +0589
+2224 -0400 +7280	-0038 -0282 +0577
+4144 -0720 +8592	-0050 -0278 +0578
-11712 +2704 -0208	-0039 -0265 +0612
-27728 +0576 +2832	-0045 -0305 +0584
+18640 -1840 +20704	-0088 -0277 +0639
+1840 +0608 +8560	-0026 -0214 +0597
+0704 -1888 +5344	-0117 -0251 +0580
+0576 -0336 +7792	-0157 -0262 +0590
+0768 -0208 +7952	-0155 -0260 +0589
+0736 -0400 +8112	-0155 -0259 +0588

FS = 11 (+/- 8g)

Init FS 30
+0000 +0000 +0000	-0156 -0256 +0587
+0240 -0192 +4144	-0156 -0256 +0587
+0400 -0144 +3968	-0156 -0256 +0587
+0448 -0208 +3936	-0156 -0256 +0587
+0352 -0080 +3920	-0156 -0256 +0587
+0416 -0288 +4048	-0156 -0256 +0587
+0320 -0208 +4032	-0156 -0256 +0587
+0576 -0176 +4032	-0156 -0256 +0587
+0416 -0208 +3856	-0156 -0256 +0587
+0384 -0176 +3968	-0156 -0256 +0587
+0272 -0192 +3968	-0156 -0256 +0587
+0288 -0304 +3936	-0156 -0256 +0587
+0352 -0160 +3968	-0156 -0256 +0587
+0384 -0192 +3920	-0156 -0256 +0587
+0224 -0336 +3936	-0156 -0256 +0587
+0432 -0288 +3888	-0156 -0256 +0587
+0384 -0304 +4032	-0156 -0256 +0587

shaking:

+0096 +4080 -0144	-0156 -0256 +0587
+1008 +2304 +3424	-0156 -0256 +0587
-1008 -0016 -7184	-0156 -0256 +0587
+2224 -0720 +17296	-0156 -0256 +0587
+3456 +2144 +7104	-0156 -0256 +0587
-1104 +1792 -7232	-0156 -0256 +0587
+4832 -0352 +1520	-0156 -0256 +0587
+4688 -0672 +14416	-0156 -0256 +0587
+0848 -0224 +3824	-0156 -0256 +0587
/*
 * minimal? test of LSM303
 */

#include <avr/io.h>  
#include <pololu/orangutan.h>  
#include <math.h>
#include <stdlib.h>
#include <string.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

typedef struct vector
{
  int16_t x, y, z;
} vector;


/*
acceleration sensor ranges and scale
- same for all axes
FS  Range     Divisor (FS_G)
00  +/- 2g    2^14
01  +/- 4g    2^13
11  +/- 8g    2^12
*/

#define ACC_FS 0b01
#define ACC_FS_SCALE (1<<13)

/*
magnetic field sensor ranges and scale
- Earth's magnetic field varies from ~0.25–0.65 Gauss; http://en.wikipedia.org/wiki/Earth's_magnetic_field
- same for x,y axes, z is slightly less sensitive
- ref. datasheet for other ranges
GN    Range       Divisor(x,y)  Divisor(z)
001   +/- 1.3G    1100          980
*/

#define MAG_GN 0b001
#define MAG_GN_SCALE_XY 1100
#define MAG_GN_SCALE_Z  980

// debug_msg_buffer: A buffer for sending bytes on PD1/TXD.
char debug_msg_buffer[128];


void i2c_start() {  
	TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); // send start condition  
	while (!(TWCR & (1 << TWINT)));  
}  
  
void i2c_write_byte(char byte) {  
	TWDR = byte;              
	TWCR = (1 << TWINT) | (1 << TWEN); // start address transmission  
	while (!(TWCR & (1 << TWINT)));  
}  
  
char i2c_read_byte() {  
	TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN); // start data reception, transmit ACK  
	while (!(TWCR & (1 << TWINT)));  
	return TWDR;  
}  

char i2c_read_last_byte() {  
	TWCR = (1 << TWINT) | (1 << TWEN); // start data reception
	while (!(TWCR & (1 << TWINT)));  
	return TWDR;  
}  
  
void i2c_stop() {  
	  TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); // send stop condition  
}  

void read_data_raw(vector *a, vector *m)
{
	// read accelerometer values
	i2c_start();
	i2c_write_byte(0x30); // write acc
	i2c_write_byte(0xa8); // OUT_X_L_A, MSB set to enable auto-increment
	i2c_start();		  // repeated start
	i2c_write_byte(0x31); // read acc
	unsigned char axl = i2c_read_byte();
	unsigned char axh = i2c_read_byte();
	unsigned char ayl = i2c_read_byte();
	unsigned char ayh = i2c_read_byte();
	unsigned char azl = i2c_read_byte();
	unsigned char azh = i2c_read_last_byte();
	i2c_stop();

	a->x = axh << 8 | axl;
	a->y = ayh << 8 | ayl;
	a->z = azh << 8 | azl;

	// read magnetometer values
	i2c_start(); 
	i2c_write_byte(0x3C); // write mag
	i2c_write_byte(0x03); // OUTXH_M
	i2c_start();		  // repeated start
	i2c_write_byte(0x3D); // read mag
	unsigned char mxh = i2c_read_byte();
	unsigned char mxl = i2c_read_byte();
	unsigned char mzh = i2c_read_byte();
	unsigned char mzl = i2c_read_byte();
	unsigned char myh = i2c_read_byte();
	unsigned char myl = i2c_read_last_byte();
	i2c_stop();

	m->x = mxh << 8 | mxl;
	m->y = myh << 8 | myl;
	m->z = mzh << 8 | mzl;
}


int main()
{
	DDRC = 0;                              // all inputs
	PORTC = (1 << PORTC4) | (1 << PORTC5); // enable pull-ups on SDA and SCL, respectively

	TWSR = 0;  // clear bit-rate prescale bits
	TWBR = 17; // produces an SCL frequency of 400 kHz with a 20 MHz CPU clock speed

	// configure and enable accelerometer
	i2c_start();
	i2c_write_byte(0x30); // write acc
	i2c_write_byte(0x23); // CTRL_REG4_A
	i2c_write_byte(ACC_FS<<4); // FS = bits 5,4
	i2c_stop();

	i2c_start();
	i2c_write_byte(0x30); // write acc
	i2c_write_byte(0x20); // CTRL_REG1_A
	i2c_write_byte(0b00100111); // normal power mode, 50 Hz data rate, all axes enabled
	i2c_stop();


	// configure and enable magnetometer
	i2c_start(); 
	i2c_write_byte(0x3C); // write mag
	i2c_write_byte(0x01); // CR_REG_M
	i2c_write_byte(MAG_GN<<5); // GN = bits 7,6,5
	i2c_stop();

	i2c_start(); 
	i2c_write_byte(0x3C); // write mag
	i2c_write_byte(0x02); // MR_REG_M
	i2c_write_byte(0b00000000); // continuous conversion mode
	i2c_stop();


  // set up serial port and send init message
  serial_set_baud_rate(9600); // approx. 960 characters / second
  snprintf(debug_msg_buffer, sizeof(debug_msg_buffer)-1,
    "Init ACC_FS=%02x  MAG_GN=%02x\n", ACC_FS, MAG_GN
  );
  serial_send_blocking(debug_msg_buffer, strlen(debug_msg_buffer));

	while(1)
	{
  	vector acc, mag;

    read_data_raw(&acc, &mag);

    // acceleration magnitude (should be ~1.0 at rest)
    float ax, ay, az, a;
    ax = (double)acc.x/ACC_FS_SCALE;
    ay = (double)acc.y/ACC_FS_SCALE;
    az = (double)acc.z/ACC_FS_SCALE;
    a = sqrt(ax*ax + ay*ay + az*az);

    // magnetic field magnitude (should be 0.25–0.65 depending on where you are / what's nearby)
    float mx, my, mz, m;
    mx = (double)mag.x/MAG_GN_SCALE_XY;
    my = (double)mag.y/MAG_GN_SCALE_XY;
    mz = (double)mag.z/MAG_GN_SCALE_Z;
    m = sqrt(mx*mx + my*my + mz*mz);

    snprintf(debug_msg_buffer, sizeof(debug_msg_buffer)-1,
      "%+5.2f\t%+05d %+05d %+05d\t%+5.2f\t%+05d %+05d %+05d\n",
      (double)a, acc.x, acc.y, acc.z,
      (double)m, mag.x, mag.y, mag.z
    );
    serial_send(debug_msg_buffer, strlen(debug_msg_buffer));

		delay_ms(500);
	}
}

Hello, Ben.

Here is the table from the LSM303DLM datasheet:


It looks like it is off from your findings by roughly a factor of 16: For example, at FS=11, you’re saying to convert from raw units to gs by dividing by 2^12. That’s equivalent to multiplying by 0.000244, but the datasheet says you’re just supposed to multiply 0.0039.

The factor of 16 is explained because the lower four bits of the register are meaningless; in our example code we shift right by 4 bits immediately after reading the data, but I noticed you are not doing that so your numbers will be 16 times bigger. However, there is still a slight difference between your findings and the datasheet because of the difference between 3.9 mg and 4.0 mg.

–David

edited to fix bit shift and scaling: 2^10 != 1000 :slight_smile:

Yep, essentially that table was what got me confused - at least until I noticed that the data is left-justified in a 16-bit field.

Here’s probably a clearer way of looking at it:

  raw_value = raw_value >> 4  // shift out "padding" bits
  (double)scaled_value = (double)raw_value/1000.0 * sensitivity

Made easier with some defines:

// change ACC_LA_FS,  ACC_LA_SO as reqd per the datasheet table 3
#define ACC_LA_FS   0b00
#define ACC_LA_SO   1
#define ACC_SCALE (1000 / ACC_LA_SO)

...
    // when initialising the sensor:
    i2c_write_byte(0x80 | ACC_LA_FS<<4); // Block data update; FS = bits 5,4
...
    // when reading the data:
    ax = (axh << 8 | axl) >> 4;
    ay = (ayh << 8 | ayl) >> 4;
    az = (azh << 8 | azl) >> 4;
...
    // and scaling:
    acc.x = (double)raw_acc.x / ACC_SCALE;
    acc.y = (double)raw_acc.y / ACC_SCALE;
    acc.z = (double)raw_acc.z / ACC_SCALE;
 ...

BTW: I had a look at the code at https://www.pololu.com/catalog/product/1273/resources, however didn’t and can’t see the raw data right shift. The “LSM303DLM Orangutan example project” doesn’t care about the magnitude of the direction vectors, so doesn’t do any scaling.

Thanks,
Ben

Hello. The right shift can be seen in the readAcc() function in our LSM303 Arduino Library:

github.com/pololu/LSM303/blob/m … 3.cpp#L200

–David