AltIMU-10 v4 Problem with calibration and orientation

Hello everyone!

I’m trying for long time to understand and run compass by using LSM303D.
First of all I have problem with calibration. Everytime I run calibration from examples I get different values, for example -12421, -8234 and +2210, +28086 in second attempt for the same axis. Do I need to calibrate sensor before every runnig?

Also I can’t find proper orientation to get angle from north is serial monitor. I have tried different vectors in heading code (from examples) without success.

Could you help me?

Regards,
Maciej

Calibration of magnetometers is always necessary, and if you want your IMU to be as accurate as possible, careful calibration of both the accelerometer and magnetometer is absolutely required. The procedure suggested by the Pololu engineers is not the best, and prone to error if not carefully applied.

A much better and more reliable procedure is described here: sailboatinstruments.blogspot.com … ation.html The programs magcal and magneto are easy to use, but you need to write a small program to capture the data from the sensor, and output it as a .csv file on a larger computer. I use an Orangutan for that purpose, and the following code was compiled using Atmel Studio IV. It works for the older LSM303DLH and may need a few minor changes.

The procedure is the same for both the accelerometer and magnetometer, but for accurate results with the accelerometer, the sensor must be held motionless while each data point is collected. This is tedious!

/*
 * compass calibration: for the Orangutan LV, SV, SVP, and X2.
 *
 * This example program demonstrates how to read data from the LSM303DLH 3D compass and
 * accelerometer carrier with an Orangutan robot controller.
 *
 * modified from the Pololu example code for external calibration software.
 * S. James Remington 9/2012
 *
 * The LSM303DLH carrier should be connected to the Orangutan's I2C pins; on the
 * LV and SV, these are PC5 and PC4 for SCL and SDA, respectively, and on the
 * SVP and X2, these are PC0 and PC1, respectively. (PC0 and PC1 are LCD data
 * lines on the X2, but this code will work on it if the LCD is not used, or
 * used in 4-bit mode.)
 *
 * https://www.pololu.com
 * https://forum.pololu.com
 */

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

/* CPU frequency */
#define F_CPU 20000000UL

/* UART baud rate */
#define UART_BAUD  9600

#include "uart.c"

FILE uart_str = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);

/*
 * This program assumes that the LSM303DLH carrier is oriented with X pointing
 * to the right, Y pointing backward, and Z pointing down (toward the ground).
 * The code compensates for tilts of up to 90 degrees away from horizontal.
 * Vector p should be defined as pointing forward, parallel to the ground,
 * with coordinates {X, Y, Z}.
 */
vector p = {0, -1, 0};


/*
 * m_max and m_min are calibration values for the maximum and minimum
 * measurements recorded on each magnetic axis, which can vary for each
 * LSM303DLH. You should replace the values below with max and min readings from
 * your particular device.
 *
 * To obtain the max and min values, you can use this program's
 * calibration mode, which is enabled by pressing one of the pushbuttons. While
 * calibration mode is active, point each of the axes of the LSM303DLH toward
 * and away from the earth's North Magnetic Pole. Due to space constraints on an
 * 8x2 LCD, only one axis is displayed at a time; each button selects an axis to
 * display (top = X, middle = Y, bottom = Z), and pressing any button a second
 * time exits calibration mode and returns to normal operation.
 */
vector m_max = {340, 517, 416};
vector m_min = {-642, -496, -462};


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
}

// Returns a set of acceleration and raw magnetic readings from the cmp01a.
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();

	// 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 myh = i2c_read_byte();
	unsigned char myl = i2c_read_byte();
	unsigned char mzh = i2c_read_byte();
	unsigned char mzl = i2c_read_last_byte();
	i2c_stop();

	a->x = axh << 8 | axl;
	a->y = ayh << 8 | ayl;
	a->z = azh << 8 | azl;
	m->x = mxh << 8 | mxl;
	m->y = myh << 8 | myl;
	m->z = mzh << 8 | mzl;
}

// prints a set of acceleration and adjusted magnetic readings on the uart
// for subsequent input to magcal or magneto (calibration software)


int main()
{
	vector a, m;
	int ax,ay,az,mx,my,mz;
	unsigned char button;

	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

	clear();


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

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

	uart_init();
	stdout = stdin = &uart_str;
	print("button:");

	while (1)  //delay start until button press
	{
		button = get_single_debounced_button_press(ANY_BUTTON);
		if(button) break;
	}
	clear();

	while (1)
	{
		read_data_raw(&a, &m);
		ax=a.x;
		ay=a.y;
		az=a.z;
		mx=m.x;
		my=m.y;
		mz=m.z;
		printf ("%d,%d,%d,%d,%d,%d,\n",mx,my,mz,ax,ay,az);
		delay_ms(200);
	}
}

//uart.c

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
* <joerg@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.        Joerg Wunsch
 * $Id: uart.c,v 1.1.2.1 2005/12/28 22:35:08 joerg_wunsch Exp $
  * ----------------------------------------------------------------------------
*/


#include <stdint.h>
#include <stdio.h>

#include <avr/io.h>

#include "uart.h"

/*
 * Initialize the UART to 9600 Bd, tx/rx, 8N1.
 */
void uart_init(void)
{
  UBRR0H = 0;
  UBRR0L = (F_CPU / (16UL * UART_BAUD)) - 1;
  UCSR0B = _BV(TXEN0) | _BV(RXEN0); /* tx/rx enable */
}

/*
 * Send character c to the UART Tx, wait until tx holding register
 * is empty.
 */
int uart_putchar(char c, FILE *stream)
{
  if (c == '\n')
    uart_putchar('\r', stream);
  loop_until_bit_is_set(UCSR0A, UDRE0);
  UDR0 = c;

  return 0;
}

/*
 * Receive a character from the UART Rx.
 *
 * This features a simple line-editor that allows to delete and
 * re-edit the characters entered, until either CR or NL is entered.
 * Printable characters entered will be echoed using uart_putchar().
 *
 * Editing characters:
 *
 * . \b (BS) or \177 (DEL) delete the previous character
 * . ^u kills the entire input buffer
 * . ^w deletes the previous word
 * . ^r sends a newline, and then reprints the buffer for continued input
 * . \t will be replaced by a single space
 *
 * All other control characters will be ignored.
 *
 * The internal line buffer is RX_BUFSIZE (80) characters long, which
 * includes the terminating \n (but no terminating \0).  If the buffer
 * is full (i. e., at RX_BUFSIZE-1 characters in order to keep space for
 * the trailing \n), any further input attempts will send a \a to
 * uart_putchar() (BEL character), although line editing is still
 * allowed.
 *
 * Input errors while talking to the UART will cause an immediate
 * return of -1 (error indication).  Notably, this will be caused by a
 * framing error (e. g. serial line "break" condition), by an input
 * overrun, and by a parity error (if parity was enabled and automatic
 * parity recognition is supported by hardware).
 *
 * Successive calls to uart_getchar() will be satisfied from the
 * internal buffer until that buffer is emptied again.
 */
int uart_getchar(FILE *stream)
{
  uint8_t c;
  char *cp, *cp2;
  static char b[RX_BUFSIZE];
  static char *rxp;

  if (rxp == 0)
    for (cp = b;;)
      {
	loop_until_bit_is_set(UCSR0A, RXC0);
	if (UCSR0A & _BV(FE0))
	  return _FDEV_EOF;
	if (UCSR0A & _BV(DOR0))
	  return _FDEV_ERR;
	c = UDR0;
	/* behaviour similar to Unix stty ICRNL */
	if (c == '\r')
	  c = '\n';
	if (c == '\n')
	  {
	    *cp = c;
	    uart_putchar(c, stream);
	    rxp = b;
	    break;
	  }
	else if (c == '\t')
	  c = ' ';

	if ((c >= (uint8_t)' ' && c <= (uint8_t)'\x7e') ||
	    c >= (uint8_t)'\xa0')
	  {
	    if (cp == b + RX_BUFSIZE - 1)
	      uart_putchar('\a', stream);
	    else
	      {
		*cp++ = c;
		uart_putchar(c, stream);
	      }
	    continue;
	  }

	switch (c)
	  {
	  case 'c' & 0x1f:
	    return -1;

	  case '\b':
	  case '\x7f':
	    if (cp > b)
	      {
		uart_putchar('\b', stream);
		uart_putchar(' ', stream);
		uart_putchar('\b', stream);
		cp--;
	      }
	    break;

	  case 'r' & 0x1f:
	    uart_putchar('\n', stream);
	    for (cp2 = b; cp2 < cp; cp2++)
	      uart_putchar(*cp2, stream);
	    break;

	  case 'u' & 0x1f:
	    while (cp > b)
	      {
		uart_putchar('\b', stream);
		uart_putchar(' ', stream);
		uart_putchar('\b', stream);
		cp--;
	      }
	    break;

	  case 'w' & 0x1f:
	    while (cp > b && cp[-1] != ' ')
	      {
		uart_putchar('\b', stream);
		uart_putchar(' ', stream);
		uart_putchar('\b', stream);
		cp--;
	      }
	    break;
	  }
      }

  c = *rxp++;
  if (c == '\n')
    rxp = 0;

  return c;
}

//uart.h

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <joerg@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.        Joerg Wunsch
 * ----------------------------------------------------------------------------
 *
 * Stdio demo, UART declarations
 *
 * $Id: uart.h,v 1.1.2.1 2005/12/28 22:35:08 joerg_wunsch Exp $
 */

/*
 * Perform UART startup initialization.
 */
void	uart_init(void);

/*
 * Send one character to the UART.
 */
int	uart_putchar(char c, FILE *stream);

/*
 * Size of internal line buffer used by uart_getchar().
 */
#define RX_BUFSIZE 60

/*
 * Receive one character from the UART.  The actual reception is
 * line-buffered, and one character is returned from the buffer at
 * each invokation.
 */
int	uart_getchar(FILE *stream);

Thank you, Jim!

I’ll try your solution. When I calibrate sensors once it will work well every time, am I right?
Actually I don’t need perfect heading. Procedure described in Pololu example don’t give even inaccurate results for me. They are totaly unpredictable…

Maciej

Magnetometers often need to be recalibrated, especially if the immediate environment is changed or there are iron or magnetized objects in the vicinity. See this recent post: Watch out for LSM303D sensor board magnetization

Thank you for this topic! I have exhausting week… Today I realized that on the bread board I have buzzer next to AltIMU. :confused: Now it works very well even with simple calibration.
Thank you for your help once again!

Maciej