КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

Урок 46. Цветовой замок с квадратным ключом (Замок калейдоскоп)

В этом уроке мы дополним наш кодовый замок из акселерометра ( Урок 45 ) модулем Trema Bluetooth HC-05 и создадим беспроводную связь между «ключом» и «замком» нашего устройства. Кроме того, чтобы усложнить задачу, «замок» всякий раз будет генерировать псевдослучайную последовательность поворотов, которую нужно будет повторить с помощью «ключа». Для индикации требуемого положения мы воспользуемся RGB светодиодным модулем.


Нам понадобится:

«Замок»:

«Ключ»

Помимо этого, нам нужно будет установить несколько библиотек:

О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki – Установка библиотек в Arduino IDE.


Схема подключения:

«Ключ»:

Модули Trema Set Shield
Trema Зуммер 1 площадка (выводы 7-G-V-6-10)
Trema IMU 9 DOF 3 площадка (выводы G-V-DA-CL)
Trema Кнопка 4 площадка (выводы A1-G-V-2-A2)
Trema Bluetooth 5 площадка (выводы 7-12-G-V-11-13-10)

«Замок»:

Модули Trema Set Shield
Trema Силовой ключ 1 площадка (выводы 7-G-V-6-10)
NeoPixel 3 площадка (выводы 8-G-V-3-9)
Trema Bluetooth 5 площадка (выводы 7-12-G-V-11-13-10)
Trema Кнопка 6 площадка (выводы 4-G-V-A3-5)

При желании все установленные на Trema Set Shield модули можно закрепить, используя нейлоновые винты и стойки.

Код программы для «ключа»:

#define BMX055_DISABLE_BMG            // Не использовать гироскоп
#define BMX055_DISABLE_BMM            // Не использовать магнитометр
                                      //
#include <SoftwareSerial.h>           // Подключаем библиотеку SoftwareSerial
#include <iarduino_Bluetooth_HC05.h>  // Подключаем библиотеку iarduino_Bluetooth_HC05
#include <iarduino_Position_BMX055.h> // Подключаем библиотеку iarduino_Position_BMX055
                                      //
enum pos                              // Перечисление для различных положений
{                                     // модуля в пространстве: NONE - не определено, 
  NONE, UP, FRONT, RIGHT,             // UP -  вверх, FRONT - вперед, 
  BACK, LEFT, DOWN                    // RIGHT - вправо, BACK - назад, 
};                                    // LEFT - влево, DOWN - вниз
                                      //
const int BTN_PIN = 2;                // Вывод для подключения кнопки
const int BUZ_PIN = 6;                // Вывод для подключения зуммера
const int RX_PIN  = 13;               // Вывод RX для SoftwareSerial
const int TX_PIN  = 12;               // Вывод TX для SoftwareSerial
const int K_PIN   = 11;               // Вывод для управления режимом BT модуля
const float MAX_VALUE = 8.00;         // Максимальное значение по оси измерений для 
                                      // положительного результата
const float MIN_VALUE = -8.00;        // Минимальное значение по оси измерений для 
                                      // положительного результата
bool BTN_var;                         // Переменная для состояния кнопки
                                      //
SoftwareSerial            BTSerial(RX_PIN, TX_PIN); // Объект для программного UART
iarduino_Bluetooth_HC05   BTHC05(K_PIN);  // Создаем объект BTHC05 для BT модуля 
iarduino_Position_BMX055  sensor(BMA);// Создаем объект sensor, указывая что ему
                                      // требуется работать только с акселерометром
void setup() 
{
  pinMode(BTN_PIN, INPUT);            // Настраиваем вывод для кнопки как входной
  pinMode(BUZ_PIN, OUTPUT);           // Настраиваем вывод для зуммера как выходной
  digitalWrite(BUZ_PIN, LOW);         // Низкий уровень на выходе
  sensor.begin();                     // Инициируем работу с акселерометром, так как 
                                      // именно для работы с ним создан объект sensor
  sensor.setScale(BMA_4G);            // Указываем акселерометру производить измерения 
                                      // в новом диапазоне ±4g, где g=9.81 м/с²
  /* Настраиваем BT-соединение */
  BTN_var = digitalRead(BTN_PIN);
  while (!BTHC05.begin(BTSerial)) {}
  /* Для сопряжения устройств */
  if (BTN_var)
  {
    while(!BTHC05.createMaster("IMU9DOF", "1234")){}
  }
  /* Проверяем соединение */
  while(!BTHC05.checkConnect())
  {
    delay(1000);
  }
}
void loop() 
{
  /* Ждем пока есть движение */
  while (isMoving())
  {
    delay(1500);
  }
  /* Проверяем положение */
  pos new_pos = checkPosition();
  /* Отсылаем данные, до успешной попытки */
  while(!BTHC05.send(new_pos));
  /* Сообщаем звуковым сигналом о новом положении */
  digitalWrite(BUZ_PIN, HIGH);
  delay(200);
  digitalWrite(BUZ_PIN, LOW);
  /* Проверяем, не нажата ли кнопка сопряжения устройств */
  if (digitalRead(BTN_PIN))
  {
    BTHC05.createMaster("IMU9DOF", "1234");
    while (digitalRead(BTN_PIN)) {}
  }
  delay(800);
}
/* Функция для определения положения модуля */
pos checkPosition()
{
  pos result = NONE;                  // Возвращаемое значение положения модуля
  sensor.read();                      // Читаем данные акселерометра
  if (sensor.axisZ >= MAX_VALUE)      // Проверяем данные по оси Z
    result = UP;
  else if (sensor.axisZ <= MIN_VALUE)
    result = DOWN;
  if (sensor.axisY >= MAX_VALUE)      // Проверяем данные по оси Y
    result = BACK;
  else if (sensor.axisY <= MIN_VALUE)
    result = FRONT;
  if (sensor.axisX >= MAX_VALUE)      // Проверяем данные по оси X
    result = LEFT;
  else if (sensor.axisX <= MIN_VALUE)
    result = RIGHT;
  return result;                      // Возвращаем результат
}
/* Функция для определения движения модуля */
bool isMoving()
{
  static pos old_pos = NONE;          // Старое положение модуля
  pos new_pos = checkPosition();      // Новое положение модуля
  /* Если новое положение не равно старому */
  if(old_pos != new_pos && new_pos != NONE)
  {
    /* Обновляем старое положение */
    old_pos = new_pos;
    return false;
  }
  return true;
}

Знакомые с предыдущим уроком смогут найти здесь много общего. Основные компоненты - функции checkPosition() и isMoving() - остались на своих местах. В этом и преимущество выделения самостоятельных участков кода в отдельные функции — их всегда можно использовать повторно в следующем проекте, если необходима та же функциональность. Больше всего изменений произошло на начальном этапе: нам нужно не только настроить акселерометр, но и модуль Bluetooth. Обратите внимание, что в данном примере «ключ» является ведущим устройством (Master), тогда как «замок» является ведомым (Slave). В целом, устройство может инициировать обмен сообщениями в любом качестве, но ведущий чуть быстрее восстанавливает связь при потере соединения или отсутствии питания, поэтому мы решили настроить их именно таким образом. Так как «ключ» непосредственно не проверяет совпадение положений (это функциональность «замка»), его задача состоят только в том, чтобы распознать текущее положение и отправить нужное сообщение.


Код программы для «замка»:

#include <SoftwareSerial.h>                 // Подключаем библиотеку SoftwareSerial
#include <iarduino_Bluetooth_HC05.h>        // Подключаем библиотеку iarduino_Bluetooth_HC05
#include <iarduino_NeoPixel.h>              // Подключаем библиотеку iarduino_NeoPixel
                                            //            
enum pos                                    // Перечисление для различных положений
{                                           // модуля в пространстве: NONE - не определено,
  NONE, UP, FRONT, RIGHT,                   // UP -  вверх, FRONT - вперед,
  BACK, LEFT, DOWN                          // RIGHT - вправо, BACK - назад,
};                                          // LEFT - влево, DOWN - вниз
enum color                                  // Перечисление для различных цветов RGB
{                                           // модуля: NOCLR - не определено,
  NOCLR, RED, GREEN, BLUE,                  // RED - красный, GREEN - зеленый,
  YELLOW, PURPLE, WHITE                     // BLUE - синий, YELLOW - желтый,
};                                          // PURPLE - фиолетовый, WHITE - белый
                                            //
const int POS_LEN     = 3;                  // Длина массива положений
const int RGB_PIN     = 3;                  // Вывод RGB модуля
const int RGB_COUNT   = 4;                  // Количество светодиодов на RGB модуле
const int PWR_PIN     = 6;                  // Вывод для подключения силового ключа
const int BTN_PIN     = A3;                 // Вывод для подключения кнопки
const int RX_PIN      = 13;                 // Вывод RX для SoftwareSerial
const int TX_PIN      = 12;                 // Вывод TX для SoftwareSerial
const int K_PIN       = 11;                 // Вывод для управления режимом BT модуля
const int LED_DELAY   = 5000;               // Задержка анимации
const int RX_DELAY    = 10000;              // Задержка при отсутствии обновления
                                            //
bool BTN_var;                               // Переменная для состояния кнопки
bool DC_flag;                               // Флаг отсутствия соединения
int num = 0;                                // Количество положений, которые совпали
unsigned long timeAtRX = 0;                 // Время получения сообщения
pos current[POS_LEN] = {NONE, NONE, NONE};  // Массив для записи текущих значений положения
                                            // модуля в пространстве. Может быть одним из
                                            // значений перечисления pos. None по умолчанию
pos target[POS_LEN] = {NONE, NONE, NONE};   // Массив требуемых значений положения модуля
                                            // в пространстве для сравнения с текущими
                                            // Может быть одним из значений перечисления pos
pos RX_pos;                                 // Принятое значение положения
SoftwareSerial          BTSerial(RX_PIN, TX_PIN); // Объект для программного UART
iarduino_Bluetooth_HC05 BTHC05(K_PIN);            // Объект для BT модуля
iarduino_NeoPixel       RGB(RGB_PIN, RGB_COUNT);  // Объект для RGB модуля
void setup()
{
  pinMode(PWR_PIN, OUTPUT);                 // Настраиваем вывод для силового ключа как выходной
  pinMode(BTN_PIN, INPUT);                  // Настраиваем вывод для кнопки как входной
  digitalWrite(PWR_PIN, LOW);               // Низкий уровень на выходе
  /* Настраиваем светодиодный модуль */
  RGB.begin();
  RGB.setColor(NeoPixelAll, 0);
  RGB.write();
  /* Настраиваем BT-соединение */
  BTN_var = digitalRead(BTN_PIN);
  while (!BTHC05.begin(BTSerial)) {}
  /* Для сопряжения устройств */  
  if (BTN_var)
  {
    while(!BTHC05.createSlave("IMU9DOF", "1234"));
  }
  /* Проверяем соединение */
  while (!BTHC05.checkConnect()) 
  {
    delay(1000);
  }
  /* Инициализируем массив случайными значениями */
  randomSeed(analogRead(A0));
  genRandom();
}
void loop()
{
  /* Если не вышло время с момента последнего соединения */
  if (millis() - timeAtRX < RX_DELAY)
  {
    /* Сбрасывам флаговую переменную */
    DC_flag = false;
    /* Выставляем цвет на светодиодах */
    switch (target[num])
    {
      case RED:     RGB.setColor(NeoPixelAll, 255, 0, 0); RGB.write(); break;    // Красный
      case GREEN:   RGB.setColor(NeoPixelAll, 0, 255, 0); RGB.write(); break;    // Зеленый
      case BLUE:    RGB.setColor(NeoPixelAll, 0, 0, 255); RGB.write(); break;    // Синий
      case YELLOW:  RGB.setColor(NeoPixelAll, 255, 255, 0); RGB.write(); break;  // Желтый
      case PURPLE:  RGB.setColor(NeoPixelAll, 255, 0, 255); RGB.write(); break;  // Фиолетовый
      case WHITE:   RGB.setColor(NeoPixelAll, 0, 255, 255); RGB.write(); break;  // Белый
    }
  }
  /* Иначе */
  else
  {
    /* Только что пропало соединение */
    if (DC_flag == false)
    {
      /* Выставляем флаговую переменную */
      DC_flag = true;
      /* Забываем старые позиции */
      for (int i = 0; i < POS_LEN; ++i)
        current[i] = NONE;
      num = 0;
      /* Генерируем новый случайный ключ */
      genRandom();
      /* Гасим светодиоды */
      RGB.setColor(NeoPixelAll, 0);
      RGB.write();
    }
  }
  /* Проверяем BT-модуль */
  if (BTHC05.available())
  {
    /* Читаем полученное положение */
    BTHC05.read(RX_pos);
    timeAtRX = millis();
    /* Если есть совпадение, то обновляем массив текущих положений новыми данными */
    if (target[num] == RX_pos)
    {
      current[num] = RX_pos;
      /* Если дошли до крайнего элемента в массиве, сбрасываем счетчик в ноль*/
      (num == POS_LEN - 1) ? num = 0 : num++;
    }
  }
  /* Если значения в массиве текущих положений совпадают с требуемыми */
  if (current[0] == target[0] && current[1] == target[1] &&
      current[2] == target[2])
  {
    /* Забываем старые позиции */
    for (int i = 0; i < POS_LEN; ++i)
        current[i] = NONE;
    num = 0;
    /* Генерируем новый случайный ключ */
    genRandom();
    /* Выполняем полезное действие */
    digitalWrite(PWR_PIN, HIGH);
    displayProgress();
    digitalWrite(PWR_PIN, LOW);
  }
  /* Проверяем, не нажата ли кнопка сопряжения устройств */
  if (digitalRead(BTN_PIN))
  {
    BTHC05.createSlave("IMU9DOF", "1234");
    while (digitalRead(BTN_PIN)) {}
  }
  delay(100);
}
/* Функция для генерации пароля из нескольких положений */
void genRandom()
{
  static pos old_pos = NONE;
  /* Первый цвет не должен быть равен старому */
  do target[0] = static_cast<pos>(random(UP, DOWN + 1));
  while (target[0] == old_pos);
  old_pos = target[0];
  /* Не должно быть два одинаковых цвета подряд */
  for (int i = 1; i < POS_LEN; ++i)
  {
    do target[i] = static_cast<pos>(random(UP, DOWN + 1));
    while (target[i] == target[i - 1]);
  }
}
/* Функция для индикации прогресса на RGB модуле */
/* Взято из примеров к библиотеке NeoPixel (running) */
void displayProgress()
{
  byte z = 5;                                        //  Определяем константу указывающую задержку в мс (чем выше значение, тем медленнее перелив цветов)
  byte j;                                            //  Объявляем переменную для хранения значения сдвига спектра цветов для всех светодиодов (от 0 до 255)
  byte k;                                            //  Объявляем переменную для хранения положения сдвига спектра цвета для каждого светодиода на спектре j (зависит от количества светодиодов)
  byte r, g, b;                                      //  Объявляем переменную для хранения цветов RGB для каждого светодиода                               
  unsigned long start = millis();                    //  Время начала анимации 
  while ((millis() - start) < LED_DELAY)             //  Анимация продолжается, пока не пройдет время LED_DELAY в миллисекундах
  {
    j++;                                             //  Смещаем спектр цветов для всех светодиодов
    for(word i = 0; i < RGB_COUNT; i++)              //  Проходим по всем светодиодам
    {                                                
        k=((word)(i*256/RGB_COUNT)+j);               //  Определяем положение очередного светодиода на смещённом спектре цветов
        if(k<85) {        b=0; r=k*3; g=255-r;}else  //  Перелив от зелёного к красному, через жёлтый
        if(k<170){k-=85;  g=0; b=k*3; r=255-b;}else  //  Перелив от красного к синему  , через фиолетовый
                 {k-=170; r=0; g=k*3; b=255-g;}      //  Перелив от синего   к зелёному, через голубой
        RGB.setColor(i, r,g,b);                      //  Устанавливаем выбранный цвет для очередного светодиода
    }   
    RGB.write();                                     //  Записываем цвета всех светодиодов
    delay(z);                                        //  Устанавливаем задержку
  }
}

Для того, чтобы сообщить пользователю о текущем необходимом положении, мы решили добавить в проект RGB светодиод. Каждому положению соответствует свой цвет. Всего цветов будет шесть, как и положений. Проще всего еще раз воспользоваться перечислением. Обратите внимание на следующий код:

/* Выставляем цвет на светодиодах */
switch (target[num])
{
  case RED:     RGB.setColor(NeoPixelAll, 255, 0, 0); RGB.write(); break;    // Красный
  case GREEN:   RGB.setColor(NeoPixelAll, 0, 255, 0); RGB.write(); break;    // Зеленый
  case BLUE:    RGB.setColor(NeoPixelAll, 0, 0, 255); RGB.write(); break;    // Синий
  case YELLOW:  RGB.setColor(NeoPixelAll, 255, 255, 0); RGB.write(); break;  // Желтый
  case PURPLE:  RGB.setColor(NeoPixelAll, 255, 0, 255); RGB.write(); break;  // Фиолетовый
  case WHITE:   RGB.setColor(NeoPixelAll, 0, 255, 255); RGB.write(); break;  // Белый
}

Здесь в операторе switch проверяется выражение, которое соответствует текущему заданному положению — перечисление типа pos, но при этом для конкретных случаев используются значения из перечисления типа color. Все дело в том, что для внутреннего представления значений перечислений компилятор использует числа, а они одинаково отображаются в элементы наших перечислений. По умолчанию, первому элементу перечисления соответствует 0, далее каждое значение на единицу больше предыдущего. Так и получается, что в нашем коде положению UP соответствует цвет RED, положению FRONT – цвет GREEN и так далее.

Еще одно подтверждение взаимосвязи между перечисляемыми типами данных и числами можно обнаружить в функции genRandom(), которая инициализирует значениями массив требуемых положений. Так как эти положения должны быть случайными, нам нужно воспользоваться функцией random(), которая возвращает псевдослучайные числа. Эти числа мы приводим к типу перечисления с помощью вызова static_cast<pos>. Обратите внимание, что в аргументах функции указывается нижняя и верхняя границы диапазона для получаемого числа, но при этом верхняя граница не включается, поэтому в качестве второго аргумента функции мы используем значение DOWN + 1. Таким образом, максимально возможное значение на выходе будет равно DOWN, что нам и требуется. Чтобы не возникло ситуации, когда два соседних положения оказались одинаковы, мы сперва генерируем значение, а затем сравниваем с предыдущим: для этого очень удачно подходит редко используемый цикл do … while. Если положения совпадают, мы просто повторяем попытку генерации.

Алгоритм работы:

При первом включении устройств сперва необходимо выполнить сопряжение модулей Bluetooth HC-05. В дальнейшем это поможет им находить друг друга за гораздо меньшее время. Модули запоминают созданную пару в своей внутренней памяти и после подачи питания будут соединяться друг с другом.

  • Отключите питание «замка» (если оно было подано), нажмите на кнопку и подайте питание. При этом Bluetooth модулю будет назначена роль ведомого (Slave) с именем «IMU9DOF»и PIN-кодом «1234». Отпустите кнопку.
  • Отключите питание «ключа» (если оно было подано), нажмите на кнопку и подайте питание. При этом Bluetooth модулю будет назначена роль ведущего (Master) и он начнет поиск ведомого с именем «IMU9DOF» и PIN-кодом «1234». Отпустите кнопку.
  • Для выполнения повторного сопряжения (если оно потребуется) нужно выполнить те же операции как для «замка», так и для «ключа».
  • Как только связь будет установлена, на «замке» загорится RGB светодиод, а «ключ» просигналит зуммером о первом распознанном положении.

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

Примечание:

Оба модуля Bluetooth работают с программными реализациями последовательного порта на Arduino (выводы 13 и 12), поэтому для загрузки и обновления скетча в памяти контроллера вам не нужно отсоединять их от устройства. Как и прежде, кодовая последовательность состоит из трех положений и длину этой последовательности можно задать с помощью переменной POS_LEN. Это изменит размеры массивов current и target, которые нужно будет инициализировать последовательностями NONE подходящей длины. Кроме того, в условии следует проверить совпадения каждой пары положений из массивов.

В скетче есть две основных временных константы, которые влияют на работу устройства. Значение LED_DELAY определяет длительность анимации на RGB светодиодах, во время которой замок останется открытым. По умолчанию оно выставлено на 5000 миллисекунд (5 секунд), но вы можете изменить его по своему усмотрению. Вторая константа, RX_DELAY указывает на время ожидания с момента предыдущего сообщения от «ключа», в течение которого «замок» ждет следующего сообщения и записывает его в текущую последовательность. Если этот интервал превышен, «замок» «забывает» прежние совпадения и генерирует новую последовательность требуемых положений. Эту константу тоже можно настроить, чтобы сброс введенных значений не происходил неожиданно для пользователей.





Обсуждение

Гарантии и возврат Используя сайт Вы соглашаетесь с условями