
В этом уроке мы создадим кодовый замок, который открывается определенной последовательностью поворотов. Благодаря новому датчику Trema IMU 9 DOF этот замок сможет запоминать свое положение в пространстве. Подбирать комбинацию, не зная нашего ключа, горе - взломщику будет непросто!
Нам понадобится:
- Arduino Uno x 1 шт.
- Trema Set Shield x 1 шт.
- Trema IMU 9 DOF x 1 шт.
- Trema Зуммер х 1 шт.
- Trema Силовой ключ х 1 шт.
- Замок электромагнитный х 1 шт.
- Источник питания 12В х 1 шт.
- Коннектор Power Jack х 1 шт.
Для нашей идеи нам также нужно будет установить библиотеку для модуля положения:
О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.
Схема подключения:
- Установите Trema Set Shield на Arduino Uno.
- Установите Trema Зуммер на 1 посадочную площадку Trema Set Shield.
- Установите Trema Силовой ключ на 3 посадочную площадку Trema Set Shield.
- Установите Trema IMU 9 DOF на 5 посадочную площадку Trema Set Shield.
- При желании все установленные на Trema-Set Shield модули можно закрепить, используя нейлоновые винты и стойки.
| Модули | Trema Set Shield |
| Trema Зуммер | 1 площадка (выводы 7-G-V-6-10) |
| Trema Силовой ключ | 3 площадка (выводы 8-G-V-3-9) |
| Trema IMU 9 DOF | 5 площадка (выводы G-V-DA-CL) |

Код программы:
/* Кодовый замок из акселерометра */
#define BMX055_DISABLE_BMG // Не использовать гироскоп
#define BMX055_DISABLE_BMM // Не использовать магнитометр
#define MAX_VALUE 8.00 // Максимальное значение по оси измерений для
// положительного результата
#define MIN_VALUE -8.00 // Минимальное значение по оси измерений для
// положительного результата
#define PWR_PIN 3 // Вывод для подключения силового ключа
#define BUZ_PIN 6 // Вывод для подключения зуммера
#include <Wire.h> // Подключаем библиотеку для работы с аппаратной шиной I2C, до подключения библиотеки iarduino_Position_BMX055.
#include <iarduino_Position_BMX055.h> // Подключаем библиотеку iarduino_Position_BMX055
// для работы с Trema-модулем IMU 9 DOF
enum pos // Перечисление для различных положений
{ // модуля в пространстве: NONE - не определено,
NONE, UP, FRONT, RIGHT, // UP - вверх, FRONT - вперед,
BACK, LEFT, DOWN // RIGHT - вправо, BACK - назад,
}; // LEFT - влево, DOWN - вниз
iarduino_Position_BMX055 sensor(BMA); // Создаем объект sensor, указывая что ему
// требуется работать только с акселерометром
pos current[3] = {NONE, NONE, NONE}; // Массив для записи текущих значений положения
// модуля в пространстве. Может быть одним из
// значений перечисления pos. None по умолчанию
pos target[3] = {RIGHT, FRONT, LEFT}; // Массив требуемых значений положения модуля
// в пространстве для сравнения с текущими
// Может быть одним из значений перечисления pos
void setup()
{
Serial.begin(9600); // Инициируем передачу данных в монитор
// последовательного порта на скорости 9600 бит/сек
// для отладки
pinMode(PWR_PIN, OUTPUT); // Настраиваем вывод для силового ключа как выходной
digitalWrite(PWR_PIN, LOW); // Низкий уровень на выходе
pinMode(BUZ_PIN, OUTPUT); // Настраиваем вывод для зуммера как выходной
digitalWrite(BUZ_PIN, LOW); // Низкий уровень на выходе
sensor.begin(); // Инициируем работу с акселерометром, так как
// именно для работы с ним создан объект sensor
sensor.setScale(BMA_4G); // Указываем акселерометру производить измерения
// в новом диапазоне ±4g, где g=9.81 м/с²
}
void loop()
{
/* Ждем пока есть движение */
while (isMoving())
{
delay(1500);
}
/* Обновляем массив текущих положений новыми данными */
current[0] = current[1];
current[1] = current[2];
current[2] = checkPosition();
/* Сообщаем звуковым сигналом о записи нового положения */
digitalWrite(BUZ_PIN, HIGH);
delay(200);
digitalWrite(BUZ_PIN, LOW);
/* Если значения в массиве текущих положений совпадают с требуемыми */
if (current[0] == target[0] && current[1] == target[1] &&
current[2] == target[2])
{
/* Выполняем полезное действие */
/* Активируем силовой ключ */
digitalWrite(PWR_PIN, HIGH);
}
/* Иначе */
else
{
/* Отменяем выполнение полезного действия */
/* Деактивируем силовой ключ */
digitalWrite(PWR_PIN, LOW);
}
}
/* Функция для определения положения модуля */
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;
}
Алгоритм работы:
При включении устройства сперва происходит настройка сенсора, а также конфигурируются выходы для управления силовым ключом и зуммером. Обратите внимание, что из всех трех устройств на модуле (гироскоп, акселерометр и магнитометр) нам понадобится только акселерометр. Лишние части кода из библиотеки мы не включаем, чтобы экономить память микроконтроллера.
Затем в основном цикле сперва проверяем наличия движения у модуля. Для этого написана функция isMoving(). Она возвращает значение false если движения нет и true если замечено движение. Основной компонент этой функции — статическая переменная old_pos нашего перечисления pos. Именно она запоминает предыдущее положение модуля и хранит его между вызовами функции isMoving(). Чтобы понять, изменилось ли положение модуля мы вызываем другую функцию — checkPosition(). Оставим пока на время детали реализации этой функции и посмотрим, что она возвращает. Результатом выполнения этой функции будет текущее положение модуля в пространстве. Это может быть одно из значений нашего перечисления: NONE (если положение не определено), UP (вверх), FRONT (вперед), RIGHT (вправо), BACK (назад), LEFT (влево), DOWN (вниз).
Если новое положение аналогично старому, значит модуль никуда не двигался и можно продолжать ждать движения. Если новое положение оказалось NONE, значит модуль находится в каком-то промежуточном состоянии и однозначно судить о его положении нельзя. Опять воздержимся от каких-либо действий и подождем. Наконец, если из функции checkPosition() получено новое положение, то логично предположить, что модуль действительно переместился и было движение. В таком случае возвращаемое значение функции isMoving() будет true (но надо не забыть при этом обновить значение переменной old_pos!).
Новое положение нужно добавить в конец нашего массива текущих положений. При этом сначала обновляются первое (меняется на второе) и второе (меняется на третье) места в массиве, а третьим значением будет результат нового вызова функции checkPosition(). Эта функция обращается непосредственно к модулю, чтобы получить новые значения от акселерометра и вернуть в качестве результата одно из возможных положений. Чувствительность настроек можно задать с помощью директив препроцессора #define MAX_VALUE и #define MIN_VALUE (по умолчанию равны 8.00 и -8.00). Чтобы пользователь знал, что новое положение распознано нашим устройством, подадим короткий звуковой сигнал на зуммер.
В заключение следует проверить, соответствуют ли текущие положения из массива current значениям из массива target, которые необходимы для открытия замка. И хотя в данном примере мы управляем силовым ключом, в вашем случае это может быть что угодно: зажечь лампочку, светодиод, повернуть сервопривод, проиграть мелодию или даже все вместе. Затем программа переходит к ожиданию нового положения и все повторяется.
Примечание:
В данном примере паролем служат три последовательных положения: RIGHT > FRONT > LEFT. Пароль очень легко изменить, указав, например, значения UP > LEFT > DOWN. Изменить длину пароля несколько сложнее, но тоже возможно. Для этого нужно поменять размеры массивов (значения должны совпадать!) и не забыть инициализировать их последовательностью подходящей длины. Кроме того, условие проверки соответствия должно обрабатывать каждую пару значений из массива.

Обсуждение