Самодиагностика МЕМС акселерометра, гироскопа и компаса (self test)

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

34053525642a4bd9a4e4c31323bf870d.png

Введение


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

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

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

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-мибитный):

82cee11d87694eebbbb76349be56eea4.png

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

9db3865990c445508b6f7d310423f422.png

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

4759f6145ee54a90b0c6c90b01d206c2.png

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

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

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

eeba20ccd8c34a40a0d516ef847633f1.png

или так

61e90761cd17429788ebb9294f76ace4.png

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

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

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


Допустимый диапазон может быть указан несколькими способами:

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


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

1422fc5e774341a295994ccea0b6033c.png

add5af5d76d04df1a1dd27b80185002f.png

В первом примере применена неизвестная мне единица измерения, кто знает как это перевести в mg, пишите в комментарии (то что в числителе микро-ж, это понятно, а что в знаменателе?). В последнем примере максимальный уровень шума = 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);

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

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

Разбег значений получился 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);

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

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

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

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

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

Согласно спецификации, он не проходит самопроверку — значения по оси 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);


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

В другом положении:
7fde165f70fe4900b4e4385f89f3634f.png

В этом датчике успешность прохождения самопроверки определяется допустимым отклонением от эталонного значения. Допуск отклонения равен ±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.

© Geektimes