Изучая спецификацию (datasheet) на МЕМС-датчик (акселерометр, гироскоп и проч.) мы сталкиваемся с такой процедурой, как самопроверка (self-test) или самодиагностика. Обычно в спецификациях есть описание, как это делать. Кому интересно: что это и как это правильно делать? — добро пожаловать под кат.



Введение


Самопроверка позволяет определить, что данные с датчика показывают то, что должны показывать. Датчики не всегда имели режим самодиагностики. Чтобы определить соответствие измеряемых данных тому, что указано в спецификации, разработчик должен был проверять это специальными экспериментами. В случае акселерометра — совершать наклоны на определённые углы и снимать изменение показаний. Анализируя результаты, можно было оценить погрешность, выдаваемую датчиком. Если показатели не соответствовали спецификации, датчик признавался бракованным.

Это усложняло массовое производство и во все датчики стали включать процедуру самодиагностики. Замысел процедуры достаточно прост — создать условия, при которых мы будем знать, что должен показывать датчик, и сравнить с тем, что он показывает. Отклонения выше заданных забраковывают датчик. Для компасов делают элемент, создающий магнитное поле (прямо в корпусе датчика). Включение режима самодиагностики включает этот элемент — эталонный источник. Для акселерометра и гироскопа элемент генерирует электростатическое поле, которое отклоняет грузик (см. фото ниже) и имитирует физическое движение или вращение. Величина смещения известна, соответственно известна величина, которую должен показать датчик. Если погрешность снятых показаний в заявленных пределах — значит с датчиком всё в порядке и он работает в соответствии со спецификацией.


(статья из интернет-журнала, откуда я позаимствовал картинку содержит ещё много красивых увеличенных фоток, ссылки см. внизу)

1. Влияние на самопроверку внешних сил


Рассмотрим акселерометр. Как указано ранее, в режиме самодиагностики на микромеханический грузик воздействует электростатическая сила, отклоняющая его на определённое расстояние, и имитирующая тем самым ускорение. Однако ожидаемую величину на выходе датчика мы не увидим. Дело в том, что в спокойном состоянии на грузик действуют другие силы (напр. сила тяжести). Они дополнительно отклоняют грузик.

Для того, чтобы учесть внешние силы, в процедуре самопроверки снимаются показания датчика в обычном режиме. Затем включается источник отклонения (переход в режим «самопроверка») и снова снимаются показания датчика.

aнорм = aвнешние
aST = aвнешние + aвнутр. ист.

Разница показаний этих двух режимов должна показывать величину с эталонного источника. Если величина находится в допустимом спецификацией диапазоне, то это значит, что датчик работает должным образом.

aвнутр. ист. = aST — aнорм
MIN < aвнутр. ист. < MAX

Отсюда важный вывод — внешние силы в обоих режимах должны быть равны. Т. е. датчик должен быть неподвижен. Когда устройство в руках — оно подвижно. Когда вы что-то нажимаете (или заканчиваете нажимать) на устройстве — оно тоже подвижно. Если на устройство действует вибрация — оно подвижно. И так далее.

2. Что ещё нужно учитывать при проведении самопроверки?


При проведении самопроверки датчика также необходимо учитывать:
  • При переключении датчика в режим измерений, может потребоваться некоторое количество времени для того, чтобы акселерометр стал готов для сбора данных. Время уточнять в спецификации.
  • Проведение самодиагностики желательно проводить в различных измеряемых диапазонах датчика (2G, 4G, и т. п.).
  • Иногда возможно инверсионное включение эталонного источника (может тянуть в одну или в другую сторону). В спецификации тогда будет указано два режима самопроверки (например «Self-test P(ositive)» и «Self-test N(egative)»).


3. Как найти величину отклонения грузика эталонным источником?


Величины отклонения грузика эталонным источником по осям:
  • Могут быть указаны явно в спецификации.
  • Может быть указан широкий диапазон возможных значений. Принцип тот же — если полученная разница aвнутр. ист. попадает в этот диапазон, то датчик в порядке. Нужно только понимать, что такой подход практикуется не для самых точных датчиков.
  • Значения могут храниться в специальных регистрах (что-нибудь вроде «SELF_TEST_X_ACCEL»). Это значит производитель уточнил величины отклонения для каждого конкретного датчика и положил в его память. Производитель может сэкономить память и записать не значение, а коэффициент формулы, с помощью которой можно рассчитать значение. Сам метод расчёта тогда будет в спецификации или в одном из её приложений.

Величина отклонения обычно указывается в условных единицах датчика — LSB. Это то, что в выходном регистре лежит. Рассмотрим значения, которые даёт внутренний источник датчика LIS302DL (датчик 8-мибитный):



Он интересен тем, что как раз содержит два режима работы внутреннего источника. Глядя в таблицу можно подумать, что значения приведены для обоих направлений внутреннего источника. Однако на самом деле это расширенный диапазон для одного направления — «Self test P». Обычно направление одно и значения указывают так:



Либо могут указать в измеряемых единицах:



Чтобы из значений датчика получить измеряемую величину, нужно воспользоваться формулой:
Величина_в_единицах_измерения = Значение_в_LSB * Чувствительность.

Чтобы перевести измеряемую величину обратно в единицы датчика, нужно воспользоваться формулой:
Значение_в_LSB = Величина_в_единицах_измерения / Чувствительность.

Чувствительность в спецификациях пишут так:



или так



Можно чувствительность посчитать самостоятельно:
Чувствительность = Измеряемый_диапазон / разрешение

Пример: Величина_LSB = Величина_измеряемая * Разрешение / Измеряемый_диапазон = 8 * 0,001 * g * 65536 / 4 * g = 131,072, т. е. ±131.

4. Как понять, что величина находится в допустимом диапазоне?


Допустимый диапазон может быть указан несколькими способами:
  • Может быть явно указан минимум и максимум.
  • Может быть указан в виде допустимого процента отклонения (например где-нибудь в сноске).
  • По идее должен быть не больше уровня шума.

Уровень шума обычно указывают через среднеквадратичное отклонение (RMS). Диапазон погрешности будет тогда равен удвоенному среднеквадратичному отклонению (±). Примеры:





В первом примере указан корень из спектральной плотности мощности шумов. Чтобы получить СКО шума, следует значение умножить на корень из полосы фильтра, который у вас будет стоять по выходу датчика. Например, при полосе 25 Гц получите 750 ug в качестве СКО (спасибо Korogodin за подсказку). Если хотим получить значение без учёта фильтрации, берём частоту замеров. В данном случае при 100Гц получается 1,5mg. Во втором и третьем примере указан максимальный уровень шума. Например 8 * 0,001 * g = 0,0784 м/с2. Значит возможный разбег величины самопроверки = ±0,0784 м/с2. Здесь тоже нужно быть внимательным, т. к. иногда уровень шума указывают с включенным фильтром. Это значит, что замеры нужно делать с теми же настройками фильтра.

Примеры самодиагностики:
Пример кода самопроверки LSM303D
	uint8_t sign = lsm303d_read(LSM303D_ADDR_WHO_AM_I);
	if (sign != LSM303D_WHO_I_AM)
	{
		//on_fatal_error("Нет сигнала с LSM303D");
	}

	lsm303d_write(LSM303D_ADDR_CTRL_REG1,
			LSM303D_REG1_RATE_100HZ_A |
			LSM303D_REG1_X_ENABLE_A  |
			LSM303D_REG1_Y_ENABLE_A  |
			LSM303D_REG1_Z_ENABLE_A
			);

	delay_ms(100);

	uint8_t i;
	int32_t avg_x__norm = 0,
                avg_y__norm = 0,
		avg_z__norm = 0;
	vector v;

	// делаем 100 замеров в обычном режиме
	for (i = 0; i < 100; i++)
	{
		lsm303d_get_acc(&v);

		avg_x__norm += v.x;
		avg_y__norm += v.y;
		avg_z__norm += v.z;
	}

	avg_x__norm /= 100;
	avg_y__norm /= 100;
	avg_z__norm /= 100;

	// Включаем внутренний источник (режим "self-test")
	lsm303d_write(LSM303D_ADDR_CTRL_REG2,
			LSM303D_REG2_FULL_SCALE_2G_A |
			2
				);
	delay_ms(100);

	int32_t avg_x__st = 0,
		avg_y__st = 0,
		avg_z__st = 0;

	// делаем ещё 100 замеров
	for (i = 0; i < 100; i++)
	{
		lsm303d_get_acc(&v);

		avg_x__st += v.x;
		avg_y__st += v.y;
		avg_z__st += v.z;
	}

	avg_x__st /= 100;
	avg_y__st /= 100;
	avg_z__st /= 100;
        
        // Выключаем внутренний источник
	lsm303d_write(LSM303D_ADDR_CTRL_REG2, 0);
	delay_ms(100);

	float internal_source_x, internal_source_y, internal_source_z; // mg

	internal_source_x = (avg_x__st - avg_x__norm)*0.061;
	internal_source_y = (avg_y__st - avg_y__norm)*0.061;
	internal_source_z = (avg_z__st - avg_z__norm)*0.061;
        
        if ((internal_source_x < 70.0) ||
            (internal_source_x >1700.0) ||
            (internal_source_y < 70.0) ||
            (internal_source_y >1700.0) ||
            (internal_source_z < 70.0) ||
            (internal_source_z >1700.0))
        {
            // detect problem
            draw_EraseScreen();
            draw_TextOut(0,0,"problem..");
            draw_Show();
        }
        else
        {
            draw_EraseScreen();
            draw_TextOut(0,0,"ok");
            draw_Show();
        }

        while(1);


В одном положении получились такие результаты:


Затем плату с датчиком положил на бок и получил такие результаты:


Разбег значений получился 11, 2 и 5 g * 10-3. Если провести ещё несколько испытаний, то можно уточнить диапазон для конкретно этого датчика:
380 < X < 410
370 < Y < 390
680 < Z < 700

Пример кода самопроверки LIS302DL
	uint8_t dev_id = lis302dl_read(0x0F);
	if (dev_id != 0x3b)
	{
		while(1); // ошибка
	}
        
        lis302dl_write(0x20,
            7 | // включить все оси
            // 100 Гц и диапазон 2G 
            (1 << 6) // включить
                      );

	delay_ms(100);

	uint8_t i;
	int32_t avg_x__norm = 0,
                avg_y__norm = 0,
		avg_z__norm = 0;

	// делаем 100 замеров в обычном режиме
	for (i = 0; i < 100; i++)
	{
		lis302dl_get_acc(&v);

		avg_x__norm += v.x;
		avg_y__norm += v.y;
		avg_z__norm += v.z;
	}

	avg_x__norm /= 100;
	avg_y__norm /= 100;
	avg_z__norm /= 100;

	// Включаем внутренний источник (режим "self-test P")
	lis302dl_write(0x20,
            7 | // включить все оси
            (1 << 4) | // self-test P
            // 100 Гц и диапазон 2G 
            (1 << 6) // включить
                      );
	delay_ms(100);

	int32_t avg_x__st = 0,
		avg_y__st = 0,
		avg_z__st = 0;

	// делаем ещё 100 замеров
	for (i = 0; i < 100; i++)
	{
		lis302dl_get_acc(&v);

		avg_x__st += v.x;
		avg_y__st += v.y;
		avg_z__st += v.z;
	}

	avg_x__st /= 100;
	avg_y__st /= 100;
	avg_z__st /= 100;
        
        // Выключаем внутренний источник
	lis302dl_write(0x20,
            7 | // включить все оси
            // 100 Гц и диапазон 2G  
            (1 << 6) // включить
                      );
	delay_ms(100);

	int32_t internal_source_x, internal_source_y, internal_source_z; // LSB

	internal_source_x = avg_x__st - avg_x__norm;
	internal_source_y = avg_y__st - avg_y__norm;
	internal_source_z = avg_z__st - avg_z__norm;
        
        if ((internal_source_x < -32) ||
            (internal_source_x > -3) ||
            (internal_source_y < 3) ||
            (internal_source_y > 32) ||
            (internal_source_z < 3) ||
            (internal_source_z > 32))
        {
            // detect problem
            draw_EraseScreen();
            draw_TextOut(0,0,"problem..");
            draw_Show();
        }
        else
        {
            draw_EraseScreen();
            draw_TextOut(0,0,"ok");
            draw_Show();
        }

        while(1);


В одном положении получились такие результаты:


В другом положении такие результаты:


Разбег значений получился в районе 1 единицы. А это второй режим работы внутреннего источника, когда он тянет в другую сторону (Self-test M):


Здесь мы видим показания другого датчика («Self-test P»):


И второй режим с поворотом платы («Self-test M»):


Согласно спецификации, он не проходит самопроверку — значения по оси Z выступают за заявленный предел: максимум 32, а мы имеем 35.

Пример кода самопроверки MPU9250
	delay_ms(100);

	uint8_t i;
	int32_t avg_x__norm = 0,
                avg_y__norm = 0,
		avg_z__norm = 0;

	// делаем 100 замеров в обычном режиме
	for (i = 0; i < 100; i++)
	{
		mpu9250_get_acc(&v);

		avg_x__norm += v.x;
		avg_y__norm += v.y;
		avg_z__norm += v.z;
	}

	avg_x__norm /= 100;
	avg_y__norm /= 100;
	avg_z__norm /= 100;

	// Включаем внутренний источник (режим "self-test")
	mpu9250_selftest(1);
	delay_ms(100);

	int32_t avg_x__st = 0,
		avg_y__st = 0,
		avg_z__st = 0;

	// делаем ещё 100 замеров
	for (i = 0; i < 100; i++)
	{
		mpu9250_get_acc(&v);

		avg_x__st += v.x;
		avg_y__st += v.y;
		avg_z__st += v.z;
	}

	avg_x__st /= 100;
	avg_y__st /= 100;
	avg_z__st /= 100;
        
        mpu9250_selftest(0); // отключаем внутренний источник смещения
        delay_ms(100);

        // считываю коэффициенты, по которым определим значения смещения, определённые заводом
        uint8_t factory_acc_kx = spi_read_byte(SELF_TEST_X_ACCEL) - 1;
        uint8_t factory_acc_ky = spi_read_byte(SELF_TEST_Y_ACCEL) - 1;
        uint8_t factory_acc_kz = spi_read_byte(SELF_TEST_Z_ACCEL) - 1;
        
        // считаю значения смещения, которые получились сейчас
        int32_t now_acc_dx = avg_x__st - avg_x__norm; // internal_source
        int32_t now_acc_dy = avg_y__st - avg_y__norm;
        int32_t now_acc_dz = avg_z__st - avg_z__norm;
        
        // определяем заводские значения смещения
        int32_t full_scale_acc_k = 0; // 2G
        int32_t factory_acc_dx = (int32_t)((2620.0 / (1 << full_scale_acc_k)) * pow(1.01, factory_acc_kx));
        int32_t factory_acc_dy = (int32_t)((2620.0 / (1 << full_scale_acc_k)) * pow(1.01, factory_acc_ky));
        int32_t factory_acc_dz = (int32_t)((2620.0 / (1 << full_scale_acc_k)) * pow(1.01, factory_acc_kz));
        
        // определяю процент разницы между заводскими значениями и полученными сейчас
        double acc_err_x = abs_double(100.0 - (100.0 * now_acc_dx) / factory_acc_dx);
        double acc_err_y = abs_double(100.0 - (100.0 * now_acc_dy) / factory_acc_dy);
        double acc_err_z = abs_double(100.0 - (100.0 * now_acc_dz) / factory_acc_dz);
        
        // спецификация допускает 3% отклонение
        if ((acc_err_x > 3.0) ||
            (acc_err_y > 3.0) ||
            (acc_err_z > 3.0))
        {
            // detect problem
            draw_EraseScreen();
            draw_TextOut(0,0,"problem..");
            draw_Show();
        }
        else
        {
            draw_EraseScreen();
            draw_TextOut(0,0,"ok");
            draw_Show();
        }
        
        while(1);

В одном положении получились такие результаты:


В другом положении:


В этом датчике успешность прохождения самопроверки определяется допустимым отклонением от эталонного значения. Допуск отклонения равен ±3% (стр. 9 первой части спецификации). Т. е. этот датчик в порядке.


Ссылки


  1. Интернет-журнал «EDN Network». Статья «The embedded self-test feature in MEMS inertial sensors. Jay Esfandyari, Gang Xu, Marco Capovilla, Paolo Bendiscioli, Marco Bianco -July 22, 2012». Ссылка.
  2. Интернет-журнал «3D News». Статья «MEMS: микроэлектромеханические системы, часть 1». Ссылка.
  3. Спецификация (datasheet) на датчик LIS3DH. Ссылка.
  4. Спецификация (datasheet) на датчик LIS302DL. Ссылка.
  5. Спецификация (datasheet) на датчик LSM303D. Ссылка.
  6. Спецификация (datasheet) на датчик MPU9250.

Комментарии (4)


  1. Korogodin
    19.10.2015 22:40
    +4

    В предпоследней таблице указан корень из спектральной плотности мощности шумов. Чтобы получить СКО шума, следует значение умножить на корень из полосы фильтра, который у вас будет стоять по выходу датчика. Например, при полосе 25 Гц получите 750 мкg в качестве СКО.


    1. 1div0
      19.10.2015 22:54

      Спасибо.


  1. utya
    20.10.2015 09:36
    +1

    Скажите, self-test это не калибровка это просто проверка на «работоспособность»?


    1. 1div0
      20.10.2015 09:41

      Да. Это просто проверка на то, что датчик с достаточной точностью отражает происходящее.