# 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)));
}

TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN); // start data reception, transmit ACK
while (!(TWCR & (1 << TWINT)));
return TWDR;
}

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
}

{
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_stop();

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

i2c_start();
i2c_write_byte(0x3C); // write mag
i2c_write_byte(0x03); // OUTXH_M
i2c_start();		  // repeated start
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;

// 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

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

// 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
...
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