Рейтинг@Mail.ru

Как сделать электронный компас на датчике MPU-9250 и Arduino

автор:
Be the first to comment! Arduino
Print Friendly, PDF & Email
Сделаем электронный компас на датчике MPU-9250 (или MPU-9255). В качестве микроконтроллера будем использовать Arduino Nano (Atmega 328). Показания компаса с дочностью до 1 градуса будем выводить на трёхразрядный светодиодный 7-сегментный индикатор. Добавим компасу возможность отслеживать азимут по нажатию на кнопку. А при отклонении от азимута будем издавать звуковой сигнал.

Разобьём всю задачу на более мелкие подзадачи. Так сказать, проведём декомпозицию. И по шагам опишем весь процесс создания электронного компаса. Для проекта нам понадобятся:

1Подключение датчика MPU-9250к Arduino

Датчик MPU-9250 – это по сути несколько датчиков, расположенных на одном чипе. Так, он реализует функции 3-осевого гироскопа, 3-осевого акселерометра и 3-осевого магнитометра. В данном проекте мы будем использовать только магнитометр. Остальные датчики можно отключить в целях уменьшения потребляемого тока, т.к. будем делать портативное устройство, питающееся от батареи «Крона».

Внешний вид модуля с датчиком MPU-9250
Внешний вид модуля с датчиком MPU-9250

Модуль имеет 10 выводов. Вот их назначение:

ВыводНазначение вывода модуля с MPU-9250 (MPU-9255)
VCCВнешнее питание 3.3 В.
GNDОбщий.
SCLЛиния тактовых импульсов I2C и SPI.
SDAЛиния данных для I2C или SPI.
EDAЛиния данных при подключении внешних датчиков по шине I2C.
ECLЛиния тактов при подключении внешних датчиков по шине I2C.
AD0Для выставления адреса I2C в режиме I2C. В режиме SPI это линия данных от датчика.
INTЛиния прерываний. Срабатывание настраивается при конфигурировании датчика MPU-9250.
NCSВ режиме SPI – линия выбора ведомого (chip select).
FSYNCЗависит от конфигурации.

Прежде чем подключать датчик MPU-9250 к Arduino, проверим его работоспособность с помощью моей любимой платы с микросхемой FT2232H. Для самого простого теста прочитаем содержимое регистра датчика, в котором содержится постоянное значение. Таким регистром может служить, например, регистр, в котором хранится идентификатор магнитометра, равный 0x48. Подключаться будем по интерфейсу I2C (для сокращения числа проводников). Как и большинство датчиков, MPU-9250 является ведомым на I2C шине. Схема подключения предельно проста: питание +3.3 В подаётся на вывод VCC, земля – GND, тактовая частота приходит на вывод SCL с ножки ADBUS0 микросхемы FT2232, линия данных SDA подключается одновременно к выводам ADBUS1 и ADBUS2 микросхемы FT2232.

Подключение датчика MPU-9250 по I2C в качестве ведомого к микросхеме FT2232H
Подключение датчика MPU-9250 по I2C в качестве ведомого к микросхеме FT2232H

Теперь, когда всё подключено, запускаем в режиме I2C программу SPI via FTDI, многократно описанную нами ранее. Оставляем настройки по умолчанию и сканируем шину I2C. Мы увидим, что программа обнаружила на шине одно устройство по адресу 0x68. Если прочитать из него 127 байтов (именно столько регистров имеет датчик MPU9250, техническое описание можно скачать в приложении к статье), то увидим следующее:

Чтение всех регистров датчика MPU-9250 по I2C с помощью FT2232
Чтение всех регистров датчика MPU-9250 по I2C с помощью FT2232

Здесь нет показаний магнитометра. Магнитометр (он называется AK8963) – это отдельное устройство на кристалле, которое не активно при подаче питания на датчик MPU-9250. Его нужно активировать явно. Для этого необходимо в регистр под номером 0x37 (INT_PIN_CFG) записать значение 0x02. Для этого в программе в поле записи укажем команду "37 02", как на рисунке, и нажмём кнопку «Записать». Полное описание регистра приводится далее.

Включение мангитометра датчика MPU-9250 через регистр I2C_SLV0_ADDR
Включение мангитометра датчика MPU-9250 через регистр I2C_SLV0_ADDR

Карта регистров магнитометра AK8963 представляет собой довольно короткую таблицу, состоящую всего из 13-ти байтов:

Карта регистров магнитометра MPU-9250
Карта регистров магнитометра MPU-9250
Чтение регистров магнитометра MPU-9250 по I2C
Чтение регистров магнитометра MPU-9250 по I2C

Как видно, по адресу 0x00 размещается постоянный Device ID, который должен быть равен 0x48. В нашем случае так и есть. Это хороший признак. Значит, магнитометр как минимум отвечает осмысленные данные, а мы можем их читать.

Схема подключений датчика MPU-9250 к Arduino остаётся предельно простой:

Вывод датчика MPU-9250Вывод Arduino
SCLA5
SDAA4
VCC+3.3V
GNDGND

Для того чтобы использовать датчик MPU-9250 в режиме магнитометра, следует придерживаться такой последовательности действий:

  • настроить регистр управления питанием PWR_MGMT_1 по адресу 0x6B;
    Структура регистра PWR_MGMT_1 датчика MPU-9255
    Структура регистра PWR_MGMT_1 датчика MPU-9255
  • настроить пользовательский регистр контроля USER_CTRL по адресу 0x6A чтобы отключить мастер шины I2C (не будем его использовать);
    Структура регистра USER_CTRL датчика MPU-9255
    Структура регистра USER_CTRL датчика MPU-9255
  • настроить регистр конфигурации прерываний INT_PIN_CFG по адресу 0x37 чтобы включить магнитометр;
    Структура регистра INT_PIN_CFG датчика MPU-9255
    Структура регистра INT_PIN_CFG датчика MPU-9255
  • настроить регистр управления магнитометра CNTL1 по адресу 0x0A на непрерывное проведение измерений.
    Структура регистра CNTL1 магнитометра AK8963
    Структура регистра CNTL1 магнитометра AK8963

Релизуем это в следующем коде.

Скетч для чтения показаний датчика MPU-9255 (разворачивается)
#include <Wire.h>

const byte MPU9255_I2C_ADDR = 0x68; // I2C адрес датчика MPU-9255
const byte MPU9255_MAG_I2C_ADDR = 0x0C; // I2C адрес магнитометра

// Регистры датчика MPU-9255:
const byte USER_CTRL = 0x6A; // { - | FIFO_EN | I2C_MST_EN | I2C_IF_DIS | - | FIFO_RST | I2C_MST_RST | SIG_COND_RST }
const byte PWR_MGMT_1 = 0x6B; // { H_RESET | SLEEP | CYCLE | GYRO_STANDBY | PD_PTAT | CLKSEL[2:0] }
const byte INT_PIN_CFG = 0x37; // { ACTL | OPEN | LATCH_INT_EN | INT_ANYRD_2CLEAR | ACTL_FSYNC | FSYNC_INT_MODE_EN | BYPASS_EN | - }
const byte CNTL1 = 0x0A; // { 0 | 0 | 0 | BIT | MODE3 | MODE2 | MODE1 | MODE0 }

void setup()
{
    Serial.begin(115200);
    Wire.begin();
    Write(PWR_MGMT_1, 0x8); // Отключаем гироскоп 
    Write(USER_CTRL, 0);  // Отключаем I2C мастер 
    Write(INT_PIN_CFG, 0x02); // включаем режим bypass
    WriteMag(CNTL1, 0x12); // задаём непрерывный режим измерений с точностью 16 разрядов
    int mag_id = ReadMag(00); // читаем идентификатор датчика MPU-9255
    Serial.print("Magnetometer ID=");
    Serial.println(mag_id, HEX);
}

void loop()
{
    int xh = ReadMag(0x04); // читаем показания оси X, старший байт
    int xl = ReadMag(0x03); // читаем показания оси X, младший байт
    int yh = ReadMag(0x06); // аналогично для осей Y и Z
    int yl = ReadMag(0x05);
    int zh = ReadMag(0x08);
    int zl = ReadMag(0x07);
    int status = ReadMag(0x09); // сообщаем датчику, что прочитали значения, и можно продолжать измерения
    
    // Собираем значения:
    int x = word(xh, xl); // или так: (xh << 8) | (xl & 0xFF); 
    int y = word(yh, yl); // или так: (yh << 8) | (yl & 0xFF);
    int z = word(zh, zl); // или так: (zh << 8) | (zl & 0xFF);
    
    // Выводим показания в мкТл:
    Serial.print("x=");
    Serial.print(GetMagneticFlux(x));
    Serial.print(", y=");
    Serial.print(GetMagneticFlux(y));
    Serial.print(", z=");
    Serial.print(GetMagneticFlux(z));
    Serial.println(" uT");
    delay(200);
}

// Читает по шине I2C из заданного регистра датчика MPU-9255
byte Read(int reg)
{
    Wire.beginTransmission(MPU9255_I2C_ADDR); // начинаем обмен по I2C
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU9255_I2C_ADDR, 1, false); // запрашиваем 1 байт от MPU-9255
    byte val = Wire.read();
    Wire.endTransmission(true);
    return val;
}

// Записывает по шине I2C в заданный регистр датчика MPU-9255
void Write(int reg, int data)
{
    Wire.beginTransmission(MPU9255_I2C_ADDR); // начинаем обмен по I2C
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission(true);
}

// Читает значение регистра магнитометра
byte ReadMag(int reg)
{
    Wire.beginTransmission(MPU9255_MAG_I2C_ADDR);
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU9255_MAG_I2C_ADDR, 1, false); // запрашиваем у магнитометра 1 байт
    byte val = Wire.read();
    Wire.endTransmission(true);
    return val;
}

// Записывает значение в регистр магнитометра
void WriteMag(int reg, int data)
{
    Wire.beginTransmission(MPU9255_MAG_I2C_ADDR);
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission(true);
}

// Преобразует показания с датчика MPU-9255 в значения магнитного потока в микротеслах
float GetMagneticFlux(int value){
    return (value * 0.15);
}

Теперь остаётся перевести показания индукции магнитного поля в азимут. Для этого воспользуемся следующей формулой:

float GetAzimuth(int x, int y) {
  float azimuth = atan2(x, y) * 180 / PI;
  return azimuth;
}

Будем двигаться к следующей задаче.

2Подключение 7-сегментного светодиодногоиндиактора к Arduino

В качестве индикатора для вывода показаний компаса будем использовать семисегментный индикатор 3361AS-1. Он построен по принципу индикатора с общим катодом.

Светодиодный индикатор с общим катодом – это тип индикатора, состоящий из нескольких светодиодов в одном корпусе, у которых общая земля, а питание на каждый светодиод подаётся отдельно.

Напомню, что 7-сегментным индикатор называется из-за того, что он состоит из 7-ми светодиодов, которые расположены в форме цифры "8". Зажигая определённые сегменты, можно изображать разные цифры. Это похоже на цифры индекса на почтовом конверте: закрашивая определённые участки, мы пишем разные индексы. Зачастую дополнительно к 7-ми сегментам, индикатор содержит десятичную точку. Также индикатор может иметь несколько цифр – разрядов. Сегменты индикатора обозначаются латинскими буквами от A до G, как на рисунке.

Обозначение сегментов индикатора латинскими буквами
Обозначение сегментов индикатора латинскими буквами

Нет проблем подключить индикатор прямо к выводам Arduino. Но тогда придётся задействовать сразу 7 ножек (или 8, если нужна десятичная точка). Это слишком расточительно. Поэтому обычно на практике используются различные решения, которые сокращают число задействованных выводов.

Индикатор 3361AS не имеет токоограничивающих резисторов, поэтому необходимо озаботиться этим самому, подключая индикатор. При напряжении питания 5 В сопротивление на каждый сегмент должно быть около 200 Ом.

Например, популярным способом управления 7-сегментным индикатором является применение микросхемы CD4511 – двоично-десятичного преобразователя. Он переводит двоичный код числа в напряжение на соответствующих цифре сегментах индикатора. Такой преобразователь будет использовать всего 4 ножки Arduino. То есть, например, если необходимо отобразить на индикаторе десятичное число 7, необходимо выставить на входе преобразователя двоичное 0111 ("LOW HIGH HIGH HIGH"). Микросхема CD4511 выполняется в разных типах корпусов. Назначение выводов в исполнении с 16-тью ножками, такое:

Выводы двоично-десятичного преобразователя CD4511
Выводы двоично-десятичного преобразователя CD4511

Отечественными аналогами данного преобразователя являются микросхемы серий ИД1…ИД7. Кстати, отечественные преобразователи изображают цифры "6" и "9", используя 6 сегментов, а зарубежные CD4511 – только 5 сегментов.

При подключении двоичного декодера будем руководствоваться следующей таблицей:

Вывод CD4511НазначениеПримечание
A0...A3Входы двоичного преобразователяСоответствуют разрядам двоичного числа.
a...gВыходы на сегменты индикатораПодключаются через токоограничивающие резисторы к соответствующим сегментам светодиодного индикатора.
Lamp Test#Тест индикатора (включает все сегменты)Подключим к питанию, не использовать его.
Blanking#Очистка индикатора (отключает все сегменты)Подключим к питанию, чтобы не использовать его.
Latch Enabled#Выход активенБудет подключен к земле, чтобы выход был всегда активен.
VDDПитание микросхемы и индикатораОт 3 до 15 В.
GNDЗемляОбщая у CD4511, Arduino, 7-сегментного индикатора.

Желательно также подключить керамический конденсатор ёмкостью примерно 1 мкФ между землёй и питанием микросхемы CD4511.

Подключение 7-сегментного индикатора к Arduino через двоичный декодер CD4511B
Подключение 7-сегментного индикатора к Arduino через двоичный декодер CD4511B

Теперь напишем первый простой скетч, чтобы проверить работоспобность 7-сегментного индикатора 3361AS-1, а также получить опыт работы с ним. Данный скетч будет поочерёдно перебирать числа от 0 до 9, перемещаясь по циклу от одного разряда индикатора к следующему.

Скетч для управления 7-сегментным индикатором (светится 1 разряд) (разворачивается)
// выводы Arduino для управления двоичным кодом на входе декодера CD4511:
const byte D_0 = 11;
const byte D_1 = 10;
const byte D_2 = 9;
const byte D_3 = 8;

// выводы Arduino для выбора десятичного разряда индикатора:
const byte B_0 = 7;
const byte B_1 = 6;
const byte B_2 = 5;

void setup() {
  pinMode(D_0, OUTPUT);
  pinMode(D_1, OUTPUT);
  pinMode(D_2, OUTPUT);
  pinMode(D_3, OUTPUT);
  pinMode(B_0, OUTPUT);
  pinMode(B_1, OUTPUT);
  pinMode(B_2, OUTPUT);
}

void loop() {
  for (int i=0; i<3; i++){ // перебираем разряды с 0 по 2-ой
    setDigit(i);
    for (int n=0; n<10; n++){ // перебираем числа от 0 до 9
      printNumber(n);
      delay(200);
    }    
  }
}

// выбирает разряд десятичного числа на счётчике:
void setDigit(byte b){
  switch (b) {
    case 0:
      digitalWrite(B_0, LOW);
      digitalWrite(B_1, HIGH);
      digitalWrite(B_2, HIGH);
      break;
    case 1:
      digitalWrite(B_0, HIGH);
      digitalWrite(B_1, LOW);
      digitalWrite(B_2, HIGH);
      break;
    case 2:
      digitalWrite(B_0, HIGH);
      digitalWrite(B_1, HIGH);
      digitalWrite(B_2, LOW);
      break;
  }
}

// зажигает заданную цифру 7-сегментного индикатора
void printNumber(byte n){
  switch(n){
    case 0:
      digitalWrite(D_0, LOW);
      digitalWrite(D_1, LOW);
      digitalWrite(D_2, LOW);
      digitalWrite(D_3, LOW);
      break;
    case 1:
      digitalWrite(D_0, HIGH);
      digitalWrite(D_1, LOW);
      digitalWrite(D_2, LOW);
      digitalWrite(D_3, LOW);
      break;
    case 2:
      digitalWrite(D_0, LOW);
      digitalWrite(D_1, HIGH);
      digitalWrite(D_2, LOW);
      digitalWrite(D_3, LOW);
      break;
    case 3:
      digitalWrite(D_0, HIGH);
      digitalWrite(D_1, HIGH);
      digitalWrite(D_2, LOW);
      digitalWrite(D_3, LOW);
      break;
    case 4:
      digitalWrite(D_0, LOW);
      digitalWrite(D_1, LOW);
      digitalWrite(D_2, HIGH);
      digitalWrite(D_3, LOW);
      break;
    case 5:
      digitalWrite(D_0, HIGH);
      digitalWrite(D_1, LOW);
      digitalWrite(D_2, HIGH);
      digitalWrite(D_3, LOW);
      break;
    case 6:
      digitalWrite(D_0, LOW);
      digitalWrite(D_1, HIGH);
      digitalWrite(D_2, HIGH);
      digitalWrite(D_3, LOW);
      break;
    case 7:
      digitalWrite(D_0, HIGH);
      digitalWrite(D_1, HIGH);
      digitalWrite(D_2, HIGH);
      digitalWrite(D_3, LOW);
      break;
    case 8:
      digitalWrite(D_0, LOW);
      digitalWrite(D_1, LOW);
      digitalWrite(D_2, LOW);
      digitalWrite(D_3, HIGH);
      break;
    case 9:
      digitalWrite(D_0, HIGH);
      digitalWrite(D_1, LOW);
      digitalWrite(D_2, LOW);
      digitalWrite(D_3, HIGH);
      break;
  }
}

Загрузим скетч в Arduino и посмотрим результат.

В один момент времени светится только один разряд индикатора. Как же задействовать одновременно сразу три разряда индикатора? Это будет немного сложнее. Сложность заключается в том, что нам одновременно нужно управлять тремя разрядами десятичного число, используя только один преобразователь CD4511. Но чисто физически это невозможно. Однако можно добиться иллюзии постоянного свечения всех разрядов светодиодного индикатора. Для этого придётся быстро переключаться между разрядами, постоянно обновляя показание каждого разряда. Мы будем поочерёдно активировать каждый из разрядов индикатора 3361AS, выставлять на нём с помощью двоичного преобразователя CD4511 нужную цифру, а затем переключаться на следующий разряд.

Для человеческого глаза такое переключение между разрядами будет незаметно, но если результат снять на видео, то его можно увидеть.

Также перепишем функцию setNumber() отправки двоичного кода на вход микросхемы преобразователя CD4511. Вместо использования оператора switch, используем массив массивов.

Скетч для управления трёхразрядным 7-сегментным индикатором (разворачивается)
// Выводы Arduino для управления двоичным конвертером CD4511:
const byte bit0 = 11;
const byte bit1 = 10;
const byte bit2 = 9;
const byte bit3 = 8;

// Выводы Arduino для выбора десятичных разрядов индикатора 3361AS:
const byte B_0 = 5;
const byte B_1 = 6;
const byte B_2 = 7;

#define seconds() (millis()/1000) // макрос определения секунд, прошедших с начала работы скетча

void setup()
{
  pinMode(bit0, OUTPUT);
  pinMode(bit1, OUTPUT);
  pinMode(bit2, OUTPUT);
  pinMode(bit3, OUTPUT);
  
  pinMode(B_0, OUTPUT);
  pinMode(B_1, OUTPUT);
  pinMode(B_2, OUTPUT);

  digitalWrite(B_0, HIGH);
  digitalWrite(B_1, HIGH);
  digitalWrite(B_2, HIGH);
}

void loop()
{
  // Каждую секунду увеличиваем показания индикатора на 1:
  int sec = seconds();
  for (int i=0; i<1000; i++) {
    while (sec == seconds()) {
      printNumber(i);
    }
    sec = seconds();
  }
}

// Выводит 3-разрядное число на 7-сегментный индикатор.
void printNumber(int n) {
  setDigit(B_0, n/100); // выводим сотни десятичного числа
  setDigit(B_1, n/10 ); // выводим десятки
  setDigit(B_2, n/1  ); // выводим единицы
}

// Выводит заданное число на заданный разряд индикатора.
void setDigit(byte digit, int value) {
  digitalWrite(digit, LOW); // выбираем разряд индикатора 3361AS-1
  setNumber(value); // выводим на этот разряд число
  delay(4);
  digitalWrite(digit, HIGH); // снимаем выбор разряда индикатора
}

// Выставляет двоичный код на входе преобразователя CD4511
void setNumber(int n) {
  static const struct number {
    byte b3;
    byte b2;
    byte b1;
    byte b0;
  }
  
  numbers[] = {
    {0, 0, 0, 0}, // 0 
    {0, 0, 0, 1}, // 1 
    {0, 0, 1, 0}, // 2 
    {0, 0, 1, 1}, // 3 
    {0, 1, 0, 0}, // 4 
    {0, 1, 0, 1}, // 5 
    {0, 1, 1, 0}, // 6 
    {0, 1, 1, 1}, // 7 
    {1, 0, 0, 0}, // 8 
    {1, 0, 0, 1}, // 9 
  };

  digitalWrite(bit0, numbers[n%10].b0);
  digitalWrite(bit1, numbers[n%10].b1);
  digitalWrite(bit2, numbers[n%10].b2);
  digitalWrite(bit3, numbers[n%10].b3);
}

Получится вот такая картина.

Управление трёхразрядным семисегментным индикатором с помощью преобразователя CD4511 и Arduino
Управление трёхразрядным семисегментным индикатором с помощью преобразователя CD4511 и Arduino

В динамике это выглядит так. Тут как раз временами видны мерцания сегментов светодиодного индикатора.

Можно попробовать поиграть значением задержек в функции setDigit(). Если сделать задержки меньше, то мерцание станет меньше заметно. Но начнут сильнее засвечиваться соседние сегменты на выбранном разряде индикатора. Тут придётся выбрать какое-то компромиссное решение.

3Подключение динамика / пьезоизлучателяк Arduino

Для сигнализировании об отклонении от азимута, как было решено, будем использовать звуковой пьезоизлучатель. Мы уже обсуждали в отдельной статье, как подключить пьезоизлучатель к Arduino. Поэтому останавливаться подробно здесь не будем. Напомню ключевые моменты.

Схема подключения излучателя к Arduino очень простая: объединяем земли, а в цепь питания ставим резистор сопротивлением около 100 Ом (для защиты порта Ardunio).

Схема подключения пьезоизлучателя к Arduino
Схема подключения пьезоизлучателя к Arduino

Для Arduino есть специальные функции tone() и noTone(), которые используются для извлечения звука заданной частоты:

    tone(piezoPin, freq); // подача звука с частотой freq (Гц)
    tone(piezoPin, freq, duration); // подача звука с частотой freq (Гц) длительностью duration (мс)
    noTone(piezoPin); // остановка звука

Здесь piezoPin – номер вывода Arduino, к которой подключён звуковой извещатель. Давайте изменим в предыдущем скетче функцию loop() таким образом (изменения выделены жирным):

void loop()
{
  // Каждую секунду увеличиваем показания индикатора на 1:
  int sec = seconds();
  for (int i=0; i<1000; i++) {
    while (sec == seconds()) {
      print_number(i);
      if (sec % 60 == 0){ // каждые 60 секунд
        tone(piezoPin, 1000, 500); // издавать звуковой сигнал
      }
    }
    sec = seconds();
  }
}

И конечно же, не забудем объявить в начале скетча piezoPin и задать ему режим работы OUTPUT. Теперь каждую минуту излучатель будет подавать звуковой сигнал продолжительностью 500 мс и частотой 1000 Гц.

4Собираем всё вместе:компас на Arduino

Схема нашего устройства будет такой:

Схема электронного компаса на MPU-9255 и Arduino
Схема электронного компаса на MPU-9255 и Arduino

Здесь ARD1 – это Arduino Nano, CD4511 – драйвер управления 7-сегментным дисплеем 3361AS, MPU-9255 – собственно, сам модуль с магнитным датчиком, SW1 – кнопка для запуска и останова отслеживания азимута, BUZ – звуковой извещатель, а PWR – клемма для подачи внешнего питания от батареи «Крона» на устройство.

Продолжение следует...

Last modified onВторник, 07 Апрель 2020 18:42 Read 448 times

Поделиться

Print Friendly, PDF & Email

Leave a comment