Продолжаем цикл статей по использованию возможностей набора «Цифровая лаборатория» NR05 для изучения программирования микроконтроллеров на примере Ардуино и конструированию полезных электронных устройств.
Наш материал не претендует на законченную конструкцию, но, как вы увидите, она вполне выполняет все основные функции кодового замка, и может послужить хорошей иллюстрацией возможностей микроконтроллеров и использования внешних подключаемых модулей. Программу для микроконтроллера можно переделывать по вашему усмотрению, добавляя или изменяя функции замка, и повышая при этом уровень ваших знаний в программировании.
Воспользуемся, прежде всего, тем, что на плате расширения, входящей в состав набора, установлен двухстрочный жидкокристаллический дисплей, а также 5 кнопок. Используем эти элементы для построения кодового замка.
Зададимся следующими требованиями:
1. Имеется 5 кнопок для ввода кода, открывающего замок;
2. Крайняя левая кнопка соответствует коду 1, далее слева направо — 2,3,4,5;
3. Количество цифр вводимого кода может быть любым (в разумных пределах) и просто устанавливаться в программе;
4. Набираемый код отображается звездочками;
5. При совпадении введенного кода с образцовым на исполнительный механизм подается положительный импульс задаваемой в программе длительности;
6. При ошибочном наборе кода появляется сообщение об ошибке;
7. Если код набрать частично, то через некоторое время набранные значения сбрасываются.
8. Используем дисплей, RGB-светодиод и звукоизлучатель, входящие в набор для отображения понятной для пользователя информации.
9. Будем открывать настоящий электромеханический замок, питающийся от 12 вольт.
Теперь подберем устройство, которое будет подавать на замок напряжение открывания. Это напряжение в соответствии с паспортом электромеханического замка, который мы и будем открывать, равно 12 вольтам при токе около 1 ампера. Плата расширения набора NR05 не может работать с такими напряжениями и токами, поэтому необходим дополнительный модуль коммутации. Такими модулями могут быть предлагаемые компанией Мастер Кит реле MP515, или блоки реле MP2211, MP4411 в зависимости от того, захотим ли мы управлять не только замком, но и другими устройствами, например, включить свет при открывании двери. Все эти устройства совместимы с уровнями управляющих сигналов Ардуино. В нашем конкретном случае используем MP2211 – модуль с двумя реле.
Учитывая сказанное выше, нарисуем схему соединений используемых устройств:
Если внимательно посмотреть на маркировку платы расширения, то мы увидим, что зеленый канал RGB-светодиода GREEN и выход на реле CH3 подключены к одному выводу D9 Arduino Nano. В данном случае это допустимо, так вход управляющей схемы реле имеет достаточно высокое входное сопротивление, а вывод D9 используется только как цифровой выход. В общем случае следует проверять, не подключены ли используемые вами выводы платы к одному и тому же выводу Ардуино, и не допускать такой ситуации.
Замок во время срабатывания потребляет довольно большой ток, поэтому запитываем его и управляющую схему отдельно.
Листинг скетча снабжен подробными комментариями, которые помогут вам разобраться в программе.
Тем не менее, обратим ваше внимание на некоторые ее особенности.
Как мы уже писали, в плате расширения применена схема подключение кнопок, использующий только один вывод Ардуино. Такая схема экономит выводы микропроцессора, но не позволяет обрабатывать одновременное нажатие нескольких кнопок одновременно, но в нашем случае это и не нужно. Обратите внимание на функцию get_key в конце скетча. Если ни одна кнопка не нажата, функция возвращает 0, если нажата, то номер нажатой кнопки.
Также посмотрите на реализацию сравнения двух массивов: эталонного и набранного кодов:
int compareResult = 1;
for (int i = 0; i < codeLength; i++)
if (codeOrigin[i] != codePressed[i])
compareResult = 0; // если хотя бы одна пара элементов не равна
Вопрос об алгоритме такого сравнения довольно часто обсуждается на форумах по программированию, но каждый раз сводится к поэлементному сравнению, что и использовано в нашем случае. Переменная compareResult остается равной 1 в случае, если элементы массивов с одинаковыми индексами равны, и становится равной 0 в случае, если хотя бы одна пара элементов не совпадает.
Для вывода на дисплей символов кириллицы используется библиотека LiquidCrystalRus, разработанная Ильей Даниловым. Для корректной работы библиотеки в заголовке нашего скетча обязательно должны присутствовать три строки:
#include <LiquidCrystalRus.h>
#include <LineDriver.h>
#include <LiquidCrystalExt.h>
А инициализация дисплея должна выглядеть так:
LiquidCrystalRus lcd(A1, A2, A3, 2, 4, 7);
Длина вводимого кода задается предопределенной константой codeLength, например для кода из шести нажатий
#define codeLength 6
Массив эталонных значений для кода длиной 6 нажатий задается следующей строкой:
const int codeOrigin[codeLength] = {1, 2, 3, 4, 5, 3};
Количество значений в фигурных скобках, должно быть равным codeLength. Если значений будет больше, компилятор выдаст ошибку, если меньше, ошибки не будет, но в качестве недостающих элементов будут использованы случайные значения, что не даст возможности набрать код, который откроет замок.
Каждые 5 секунд программа сбрасывает набранные элементы кода. Если кнопка была нажата, то время нажатия запоминается, и отсчет пятисекундного интервала начинается снова. Это реализовано с помощью встроенной функций millis(), которая возвращает число миллисекунд, прошедших с момента начала выполнения скетча, и переменных oldTime и currentTime.
Приведем небольшой видеоролик, демонстрирующий работу кодового замка:
Для любознательных и пытливых программистов-электронщиков можно подкинуть еще несколько идей для самостоятельной доработки скетча и включения в схему модулей, расширяющих возможности замка. Например, ввести в программу мастер-код, с помощью которого замок переводится в режим программирования и запоминает нажимаемые кнопки в качестве эталонного кода, чтобы не менять этот код в скетче. Программирование нового кода заканчивается, если промежуток между нажатиями превышает определенное время.
Или, опираясь на материал, посвященный взаимодействию Ардуино со смартфоном по Bluetooth, сделать замок, который открывается кодом, посылаемым этим самым смартфоном.
Можно также достаточно просто ввести в наше устройство беспроводной канал управления. Для этого достаточно воспользоваться двумя модулями: пультом-передатчиком MP910 и одноканальным приемником с релейным выходом MP911, работающими на частоте 433 МГц. Контакты реле модуля MP2211 подключаются при этом параллельно кнопке пульта, а реле модуля приемника – к замку. Дистанция управления может быть до 100 м.
Изучайте Ардуино, изучайте микроконтроллеры и их программирование – и вы сможете создать немало умных и полезных электронных устройств!
Наш материал не претендует на законченную конструкцию, но, как вы увидите, она вполне выполняет все основные функции кодового замка, и может послужить хорошей иллюстрацией возможностей микроконтроллеров и использования внешних подключаемых модулей. Программу для микроконтроллера можно переделывать по вашему усмотрению, добавляя или изменяя функции замка, и повышая при этом уровень ваших знаний в программировании.
Воспользуемся, прежде всего, тем, что на плате расширения, входящей в состав набора, установлен двухстрочный жидкокристаллический дисплей, а также 5 кнопок. Используем эти элементы для построения кодового замка.
Зададимся следующими требованиями:
1. Имеется 5 кнопок для ввода кода, открывающего замок;
2. Крайняя левая кнопка соответствует коду 1, далее слева направо — 2,3,4,5;
3. Количество цифр вводимого кода может быть любым (в разумных пределах) и просто устанавливаться в программе;
4. Набираемый код отображается звездочками;
5. При совпадении введенного кода с образцовым на исполнительный механизм подается положительный импульс задаваемой в программе длительности;
6. При ошибочном наборе кода появляется сообщение об ошибке;
7. Если код набрать частично, то через некоторое время набранные значения сбрасываются.
8. Используем дисплей, RGB-светодиод и звукоизлучатель, входящие в набор для отображения понятной для пользователя информации.
9. Будем открывать настоящий электромеханический замок, питающийся от 12 вольт.
Теперь подберем устройство, которое будет подавать на замок напряжение открывания. Это напряжение в соответствии с паспортом электромеханического замка, который мы и будем открывать, равно 12 вольтам при токе около 1 ампера. Плата расширения набора NR05 не может работать с такими напряжениями и токами, поэтому необходим дополнительный модуль коммутации. Такими модулями могут быть предлагаемые компанией Мастер Кит реле MP515, или блоки реле MP2211, MP4411 в зависимости от того, захотим ли мы управлять не только замком, но и другими устройствами, например, включить свет при открывании двери. Все эти устройства совместимы с уровнями управляющих сигналов Ардуино. В нашем конкретном случае используем MP2211 – модуль с двумя реле.
Учитывая сказанное выше, нарисуем схему соединений используемых устройств:
Если внимательно посмотреть на маркировку платы расширения, то мы увидим, что зеленый канал RGB-светодиода GREEN и выход на реле CH3 подключены к одному выводу D9 Arduino Nano. В данном случае это допустимо, так вход управляющей схемы реле имеет достаточно высокое входное сопротивление, а вывод D9 используется только как цифровой выход. В общем случае следует проверять, не подключены ли используемые вами выводы платы к одному и тому же выводу Ардуино, и не допускать такой ситуации.
Замок во время срабатывания потребляет довольно большой ток, поэтому запитываем его и управляющую схему отдельно.
Вот скетч, работающий в Ардуино, спойлер
// Кодовый замок на пяти кнопках с индикацией на ЖК и RGB-светодиоде
// на основе платы расширения из набора NR05
//-------------------------------------------------------------------
// подключаем библиотеки LiquidCrystalRus
#include <LiquidCrystalRus.h>
#include <LineDriver.h>
#include <LiquidCrystalExt.h>
// определяем выводы для RGB-светодиода и звукоизлучателя
#define red 5
#define blue 6
#define green 9
#define beep 12
// определяем, сколько кнопок у нас подключено
#define NUM_KEYS 5
// для каждой кнопки заносим калибровочные значения(выведены экспериментально)
int adcKeyVal[NUM_KEYS] = {30, 150, 360, 535, 760};
///////////////////////////////////////////////////////////
// длина кода, открывающего замок
#define codeLength 6
// массив, содержащий код, открывающий замок. Число элементов массива должно быть равным codeLength
const int codeOrigin[codeLength] = {1, 2, 3, 4, 5, 3};
// время разблокировки замка, миллисекунд
const int unlockTime = 400;
///////////////////////////////////////////////////////////
// массив для записи номеров нажатых клавиш
int codePressed[codeLength];
// счетчик нажатий (замок разблокируется при пятом нажатии)
int pressCount;
// переменные для счетчика времени неактивности набора кода
unsigned long oldTime;
unsigned long currentTime;
const int timeout = 5; // время таймаута при наборе кода, сек. После таймаута неполностью набранный код сбрасывается
//-----------------------------------------------------------------------
// инициализируем дисплей, объясняя программе куда подключены линии RS,EN,DB4,DB5,DB6,DB7
LiquidCrystalRus lcd(A1, A2, A3, 2, 4, 7);
//-----------------------------------------------------------------------
// Эта функция будет выполнена 1 раз в момент запуска программы Arduino
void setup()
{
// инициализируем LCD: 16 символов и 2 строки
lcd.begin(16, 2);
// курсор находится на первой строке (верхней) и первом слева символе
// напишем на дисплее «Мастер Кит»
lcd.print(«Мастер Кит»);
// установим курсора в первую позицию втрой строки
lcd.setCursor(0,1);
lcd.print(«tоткр.: „);
lcd.print(unlockTime);
lcd.print(“ мс»);
// выдержим паузу в 2000 миллисекунд = 2 секунды
delay(2000);
// очистим дисплей
lcd.clear();
// обнулим счетчик нажатий
pressCount = 0;
// зададим режим «на вывод» для подключения RGB-светодиода и звукоизлучателя
pinMode(red, OUTPUT);
pinMode(blue, OUTPUT);
pinMode(green, OUTPUT);
pinMode(beep, OUTPUT);
}
//-----------------------------------------------------------------------
// Эта функция будет выполнена после функции setup и будет бесконечное число раз повторятся после своего окончания.
void loop() {
// записываем текущее время (в миллисекундах), прошедшее с момента начала исполнения программы
currentTime = millis();
// проверяем, не достигнул ли таймаут на ввод кода
if (currentTime — oldTime <= timeout*1000){
// заводим переменную с именем key
int key;
// записываем в эту переменную номер нажатой кнопки, вызывая на исполнение нижеописанную функцию get_key
key = get_key();
lcd.setCursor(0, 0);
lcd.print(«Введите код:»);
// включаем синий светодиод
digitalWrite(blue, HIGH);
if (key > 0){ // если кнопка нажата
codePressed[pressCount] = key; // записываем номер нажатой кнопки в массив
// короткий сигнал звукоизлучателя (50 мс)
digitalWrite(beep, HIGH);
delay(50);
digitalWrite(beep, LOW);
// печатаем на втрой строке звездочки, мигая синим светодиодом
lcd.setCursor(pressCount, 1);
lcd.print('*');
digitalWrite(blue, LOW);
delay(200);
digitalWrite(blue, HIGH);
pressCount++; // увеличиваем счетчик нажатий
// сбрасываем счетчик времени таймаута
oldTime = currentTime;
}
}
// если достигнут таймаут, сбрасываем частично набранный код
else{
pressCount = 0;
lcd.clear();
oldTime = currentTime;
}
// если весь код введен, сравниваем поэлементно два массива: codeOrigin и codePressed
if (pressCount == codeLength){
int compareResult = 1;
for (int i = 0; i < codeLength; i++)
if (codeOrigin[i] != codePressed[i])
compareResult = 0; // если хотя бы одна пара элементов не равна
// если введен правильный код
if (compareResult == 1){ // если массивы совпадают
digitalWrite(blue, LOW);
digitalWrite(green, HIGH);
lcd.setCursor(0, 0);
lcd.print(«Открыто „);
delay(unlockTime);
digitalWrite(green, LOW);
pressCount = 0;
delay(1000);
lcd.clear();
digitalWrite(blue, HIGH);
}
// если введен неправильный код
else {
lcd.setCursor(0, 1);
lcd.print(“Неверный код»);
digitalWrite(blue, LOW);
digitalWrite(red, HIGH);
digitalWrite(beep, HIGH);
delay(2000);
pressCount = 0;
lcd.clear();
digitalWrite(beep, LOW);
digitalWrite(blue, HIGH);
digitalWrite(red, LOW);
}
}
}
//-----------------------------------------------------------------------
// Эта функция будет выполнена только когда ее вызвали из программы
// Функция читает значение с АЦП, куда подключена аналоговая клавиатура
// и сравнивает с калибровочными значениями, определяя номер нажатой кнопки
int get_key()
{
int input = analogRead(A6);
int k;
for(k = 0; k < NUM_KEYS; k++)
if(input < adcKeyVal[k])
return k + 1;
return 0;
}
// конец скетча
// на основе платы расширения из набора NR05
//-------------------------------------------------------------------
// подключаем библиотеки LiquidCrystalRus
#include <LiquidCrystalRus.h>
#include <LineDriver.h>
#include <LiquidCrystalExt.h>
// определяем выводы для RGB-светодиода и звукоизлучателя
#define red 5
#define blue 6
#define green 9
#define beep 12
// определяем, сколько кнопок у нас подключено
#define NUM_KEYS 5
// для каждой кнопки заносим калибровочные значения(выведены экспериментально)
int adcKeyVal[NUM_KEYS] = {30, 150, 360, 535, 760};
///////////////////////////////////////////////////////////
// длина кода, открывающего замок
#define codeLength 6
// массив, содержащий код, открывающий замок. Число элементов массива должно быть равным codeLength
const int codeOrigin[codeLength] = {1, 2, 3, 4, 5, 3};
// время разблокировки замка, миллисекунд
const int unlockTime = 400;
///////////////////////////////////////////////////////////
// массив для записи номеров нажатых клавиш
int codePressed[codeLength];
// счетчик нажатий (замок разблокируется при пятом нажатии)
int pressCount;
// переменные для счетчика времени неактивности набора кода
unsigned long oldTime;
unsigned long currentTime;
const int timeout = 5; // время таймаута при наборе кода, сек. После таймаута неполностью набранный код сбрасывается
//-----------------------------------------------------------------------
// инициализируем дисплей, объясняя программе куда подключены линии RS,EN,DB4,DB5,DB6,DB7
LiquidCrystalRus lcd(A1, A2, A3, 2, 4, 7);
//-----------------------------------------------------------------------
// Эта функция будет выполнена 1 раз в момент запуска программы Arduino
void setup()
{
// инициализируем LCD: 16 символов и 2 строки
lcd.begin(16, 2);
// курсор находится на первой строке (верхней) и первом слева символе
// напишем на дисплее «Мастер Кит»
lcd.print(«Мастер Кит»);
// установим курсора в первую позицию втрой строки
lcd.setCursor(0,1);
lcd.print(«tоткр.: „);
lcd.print(unlockTime);
lcd.print(“ мс»);
// выдержим паузу в 2000 миллисекунд = 2 секунды
delay(2000);
// очистим дисплей
lcd.clear();
// обнулим счетчик нажатий
pressCount = 0;
// зададим режим «на вывод» для подключения RGB-светодиода и звукоизлучателя
pinMode(red, OUTPUT);
pinMode(blue, OUTPUT);
pinMode(green, OUTPUT);
pinMode(beep, OUTPUT);
}
//-----------------------------------------------------------------------
// Эта функция будет выполнена после функции setup и будет бесконечное число раз повторятся после своего окончания.
void loop() {
// записываем текущее время (в миллисекундах), прошедшее с момента начала исполнения программы
currentTime = millis();
// проверяем, не достигнул ли таймаут на ввод кода
if (currentTime — oldTime <= timeout*1000){
// заводим переменную с именем key
int key;
// записываем в эту переменную номер нажатой кнопки, вызывая на исполнение нижеописанную функцию get_key
key = get_key();
lcd.setCursor(0, 0);
lcd.print(«Введите код:»);
// включаем синий светодиод
digitalWrite(blue, HIGH);
if (key > 0){ // если кнопка нажата
codePressed[pressCount] = key; // записываем номер нажатой кнопки в массив
// короткий сигнал звукоизлучателя (50 мс)
digitalWrite(beep, HIGH);
delay(50);
digitalWrite(beep, LOW);
// печатаем на втрой строке звездочки, мигая синим светодиодом
lcd.setCursor(pressCount, 1);
lcd.print('*');
digitalWrite(blue, LOW);
delay(200);
digitalWrite(blue, HIGH);
pressCount++; // увеличиваем счетчик нажатий
// сбрасываем счетчик времени таймаута
oldTime = currentTime;
}
}
// если достигнут таймаут, сбрасываем частично набранный код
else{
pressCount = 0;
lcd.clear();
oldTime = currentTime;
}
// если весь код введен, сравниваем поэлементно два массива: codeOrigin и codePressed
if (pressCount == codeLength){
int compareResult = 1;
for (int i = 0; i < codeLength; i++)
if (codeOrigin[i] != codePressed[i])
compareResult = 0; // если хотя бы одна пара элементов не равна
// если введен правильный код
if (compareResult == 1){ // если массивы совпадают
digitalWrite(blue, LOW);
digitalWrite(green, HIGH);
lcd.setCursor(0, 0);
lcd.print(«Открыто „);
delay(unlockTime);
digitalWrite(green, LOW);
pressCount = 0;
delay(1000);
lcd.clear();
digitalWrite(blue, HIGH);
}
// если введен неправильный код
else {
lcd.setCursor(0, 1);
lcd.print(“Неверный код»);
digitalWrite(blue, LOW);
digitalWrite(red, HIGH);
digitalWrite(beep, HIGH);
delay(2000);
pressCount = 0;
lcd.clear();
digitalWrite(beep, LOW);
digitalWrite(blue, HIGH);
digitalWrite(red, LOW);
}
}
}
//-----------------------------------------------------------------------
// Эта функция будет выполнена только когда ее вызвали из программы
// Функция читает значение с АЦП, куда подключена аналоговая клавиатура
// и сравнивает с калибровочными значениями, определяя номер нажатой кнопки
int get_key()
{
int input = analogRead(A6);
int k;
for(k = 0; k < NUM_KEYS; k++)
if(input < adcKeyVal[k])
return k + 1;
return 0;
}
// конец скетча
Листинг скетча снабжен подробными комментариями, которые помогут вам разобраться в программе.
Тем не менее, обратим ваше внимание на некоторые ее особенности.
Как мы уже писали, в плате расширения применена схема подключение кнопок, использующий только один вывод Ардуино. Такая схема экономит выводы микропроцессора, но не позволяет обрабатывать одновременное нажатие нескольких кнопок одновременно, но в нашем случае это и не нужно. Обратите внимание на функцию get_key в конце скетча. Если ни одна кнопка не нажата, функция возвращает 0, если нажата, то номер нажатой кнопки.
Также посмотрите на реализацию сравнения двух массивов: эталонного и набранного кодов:
int compareResult = 1;
for (int i = 0; i < codeLength; i++)
if (codeOrigin[i] != codePressed[i])
compareResult = 0; // если хотя бы одна пара элементов не равна
Вопрос об алгоритме такого сравнения довольно часто обсуждается на форумах по программированию, но каждый раз сводится к поэлементному сравнению, что и использовано в нашем случае. Переменная compareResult остается равной 1 в случае, если элементы массивов с одинаковыми индексами равны, и становится равной 0 в случае, если хотя бы одна пара элементов не совпадает.
Для вывода на дисплей символов кириллицы используется библиотека LiquidCrystalRus, разработанная Ильей Даниловым. Для корректной работы библиотеки в заголовке нашего скетча обязательно должны присутствовать три строки:
#include <LiquidCrystalRus.h>
#include <LineDriver.h>
#include <LiquidCrystalExt.h>
А инициализация дисплея должна выглядеть так:
LiquidCrystalRus lcd(A1, A2, A3, 2, 4, 7);
Длина вводимого кода задается предопределенной константой codeLength, например для кода из шести нажатий
#define codeLength 6
Массив эталонных значений для кода длиной 6 нажатий задается следующей строкой:
const int codeOrigin[codeLength] = {1, 2, 3, 4, 5, 3};
Количество значений в фигурных скобках, должно быть равным codeLength. Если значений будет больше, компилятор выдаст ошибку, если меньше, ошибки не будет, но в качестве недостающих элементов будут использованы случайные значения, что не даст возможности набрать код, который откроет замок.
Каждые 5 секунд программа сбрасывает набранные элементы кода. Если кнопка была нажата, то время нажатия запоминается, и отсчет пятисекундного интервала начинается снова. Это реализовано с помощью встроенной функций millis(), которая возвращает число миллисекунд, прошедших с момента начала выполнения скетча, и переменных oldTime и currentTime.
Приведем небольшой видеоролик, демонстрирующий работу кодового замка:
Для любознательных и пытливых программистов-электронщиков можно подкинуть еще несколько идей для самостоятельной доработки скетча и включения в схему модулей, расширяющих возможности замка. Например, ввести в программу мастер-код, с помощью которого замок переводится в режим программирования и запоминает нажимаемые кнопки в качестве эталонного кода, чтобы не менять этот код в скетче. Программирование нового кода заканчивается, если промежуток между нажатиями превышает определенное время.
Или, опираясь на материал, посвященный взаимодействию Ардуино со смартфоном по Bluetooth, сделать замок, который открывается кодом, посылаемым этим самым смартфоном.
Можно также достаточно просто ввести в наше устройство беспроводной канал управления. Для этого достаточно воспользоваться двумя модулями: пультом-передатчиком MP910 и одноканальным приемником с релейным выходом MP911, работающими на частоте 433 МГц. Контакты реле модуля MP2211 подключаются при этом параллельно кнопке пульта, а реле модуля приемника – к замку. Дистанция управления может быть до 100 м.
Изучайте Ардуино, изучайте микроконтроллеры и их программирование – и вы сможете создать немало умных и полезных электронных устройств!