Домофон с голосовым индивидуальным приветствием и голосовым меню
Вступление
Я уже давно слежу за системами автоматизации домофонов. На первый взгляд, может показаться, что в домофонах сделано все что можно было придумать, запись видео, связь с хозяином через мобильный телефон, управление открыванием дверей голосовым ассистентом. Но постойте что это такое? Что это за бибиканье зуммером которое изобрели еще в далеком 1831 году, мы же живем в современном мире, в веке высоких технологий, а тут такая примитивная пищалка. Решил избавиться от этого анахронизма. И сделать приятное и понятное для нашего слуха голосовое сопровождение. Речь пойдет не о самом домофоне, а об его неотъемлемой части, системы управления доступом (СКУД). Или если сказать по простому, то это бесконтактный считыватель меток для открытия дверей.
Я решил собрать систему управления доступом и добавить пару функций которые сделают эту систему более современной.
Схема
Вкратце о функционале. Система контроля доступом имеет несколько режимов работы.
- Режим контроля доступа. Считывание меток и открывание дверей
- Мастер режим для добавления новых ключей
- Режим обнуления ключей
Особенности режима добавления ключей. Нельзя добавить один и тот же ключ два раза. К любому имени можно привязать любое количество ключей в пределах доступной для них памяти.
У меня светодиод с общим анодом, по этому в коде он включается сигналом "LOW"
Питание 12 В для устройства берется с вызывной панели домофона и подключается через понижающий преобразователь на выходе которого напряжение 5 В. Открывание двери происходит стандартным для домофона способом. Методом замыкания линии "Audio" c "+12V". Для этого в схеме имеется оптрон. При подключении которого важно соблюдать полярность.

Необходимые комплектующие
- Ардуино нано - 1 шт.
- MP3 плеер - 1 шт.
- Считыватель меток RFID RC522 - 1 шт.
- Понижающий преобразователь Mini560 - 1 шт.
- Оптопара PC817 - 1 шт.
- RGB светодиод - 1 шт.
- Динамик от игрушки или от телефона - 1 шт.

RFID RC522
В представленном проекте используется самый популярный модуль бесконтактной идентификации RFID Mifare RC522. Устройство предназначено для бесконтактного считывания RFID меток. Считыватель MFRC-522 работает на частоте 13.56 МГц. Для подключения к ардуино буду использовать SPI интерфейс, так как он надежнее чем UART и I2C. Поддерживаемые считывателем типы карт: MIFARE S50, MIFARE S70, MIFARE UltraLight, MIFARE Pro, MIFARE DESfire.
Питание на модуль я взял с пина 3,3V ардуины. Это питание в свою очередь приходит от микросхемы CH340. У нее есть встроенный преобразователь. Из недостатков максимальный ток всего 80 мА. Алекс Гайвер жаловался у себя в блоге, что с этим питанием считыватель нестабильно работает. Но причина скорее всего кроется в недостаточном токе. Тем более он для большего радиуса действия передатчика, включил максимальное усиление. К тому же ни на ардуино ни на модуле RC-522 нет ни одного электролита. Электролитической конденсатор нивелировал бы все пиковые токи, которые как я понимаю и подвешивают модуль. Я сразу в эту цепь подключил конденсатор на 47 мкФ и все работает безотказно. Тестировал модуль неделю, никаких подвисаний не наблюдается. Но так как для СКУД важна отказоустойчивость, то я все же использую идею Гайвера периодического сброса модуля.

MP3 проигрыватель
DFPlayer - это маленький, но многофункциональный звуковой модуль который может считывать звуковые файлы с TF или микро SD карт и воспроизводить их на динамик, через встроенный в него усилитель. Управлять Arduino MP3 плеером можно автономно, подключив к его выводам кнопки. А так же MP3 модулем можно управлять через интерфейс UART подключив его к ардуино или к другому микроконтроллеру. DFPlayer производят на разных чипах, мной было насчитано 5 модификаций. В данном проекте используется плеер на микросхеме MH2024K-24SS. Вы можете скачать рабочую и проверенную библиотеку для этой платы

Борьба с наводками и цифровыми шумами.
Во время остановки проигрывания треков в колонке отчетливо прослушиваются цифровые шумы создаваемые встроенным в плеер контроллером. Фильтрация питания LC фильтрами и установка линейного стабилизатора почти не помогли. Так как в плеере нет отдельного питания для усилителя и ЦАП, то вылечить эту проблему хардверным способом невозможно. Потому как скорее всего между внутренним контроллером, ЦАП-ом и УНЧ нет встроенных LC или RC фильтров по питанию. А если и есть, то они не справляются со своей задачей.
Я нашел способ побороть эту проблему программно. Для этого написал отдельную функцию воспроизведения треков. Как работает эта функция? После воспроизведения трека я убавляю громкость до нуля, а перед проигрыванием она восстанавливается на требуемый пользователем уровень. Этот трюк существенно решил проблему, но в звуковом тракте в паузах все равно оставался слабый жужжащий звук. Но эту проблему мне помог решить знакомый ардуинщик Герман, за, что ему большое спасибо. Как оказалось это жужжание исходило от UART и что бы от него избавиться достаточно было перепрограммировать порт TX Ардуины с выхода на вход, при этом подтянув его внутренним сопротивлением к плюсу питания.
Теперь, после применения этих костылей, когда проигрыватель находится в режиме ожидания, никакие посторонние шумы не слышны. Если приложить ухо к динамику, можно услышать только едва уловимый белый шум, создаваемый переходными процессами в полупроводниках на первичных стадиях усиления. И то, что сначала запускается воспроизведение, а потом устанавливается громкость, это не ошибка, а специфика этого модуля.
Код этой функции.
void mp3play(uint16_t track, uint16_t time) { pinMode(TX, OUTPUT); // восстанавливаем выход TX mp3_play(track); // проиграть трек mp3_set_volume (volume); // устанавливаем заданную громкость delay(time); // длительность проигрывания трека mp3_set_volume (0); // выключаем звук, что бы не было цифрового шума в динамике pinMode(TX, INPUT_PULLUP); // Отключаем TX, что бы в динамике исчезло жужжание }
Проблема со статусом Busy
Для отслеживания в DFPlayer статуса проигрывания трека, я сначала использовал сигнал BUSY, но как оказалось он живет своей жизнью. На выходе BUSY в зависимости от фазы луны. После завершения воспроизведения трека на этом выводе может не появиться ожидаемая логическая единица и плеер уходит в зависание, так как для ожидания завершения воспроизведения используется цикл while (!digitalRead(BUSY_PIN)). По этой причине для ожидания завершения проигрывания трека пришлось использовать delay().
Проблема не полного воспроизведения
Плеер без особых на то причин может воспроизводить треки не до конца и обрывать фразы на полу слове. Хотя при этом проигрыватель продолжает исправно принимать команды по UART. По этому мной было решено сделать сброс плеера через каждые 10 минут.
Динамик
Очень хорошо себя показал громкоговоритель от мягкой игрушки, но для моего корпуса он оказался немножко большеват в размерах, по этому пришлось использовать динамическую головку от мобильного телефона.
Для максимальной эффективности динамика желательно исключить короткое акустическое замыкание. Оно происходит, когда давление воздуха одной стороной головки поглощается разрежением с противоположной стороны диффузора. Или проще говоря акустические волны нейтрализуют друг друга в противофазе. Для исключения этого недостатка нужно обеспечить хорошую герметичность громкоговорителя в корпусе. К счастью динамик от телефона уже хорошо герметизирован и не имеет выше описанных проблем.
При монтаже модулей в корпусе считывателя нужно учесть, что плохо закрепленные платы могут дребезжать от вибрации громкоговорителя. Использование силикона или термоклея должно решить эту проблему.

Аудио файлы
Наиболее приемлемым TTS для создания озвучки, я считаю голос Яндекс Алисы, но это мое сугубо личное мнение. Для записи фраз от Алисы я использовал колонку Яндекс Станция Мини-2, у этой колонки есть разъем линейного аудио выхода. Наличие которого дает возможность записывать любую звуковую информацию с этой колонки. Для этого нужно подключить аудио выход от яндекс.станции, к линейному входу звуковой карты, аудио кабелем.
Для создания голосовых фраз я использовал программу audacity. Что бы заставить колонку произносить требуемые фразы нужно в мессенджере телеграм добавить бота Алиса(alice_speaker_bot). После чего в меню бота сначала выбираем пункт "сказать колонкой что нибудь" и после этого отправляем ему сообщении с подготовленным текстом, который Алиса покорно произнесет.
Для звучания фраз как в фантастических фильмах, нужно в аудио редакторе к звуковому файлу добавить эффект "Эхо". Настройки которого видны на скриншоте ниже.

Корпус считывателя
Корпус оказался немножко маловат в размерах. По этому вместо стандартного RFID ридера пришлось заказать в Китае компактную версию устройства. Который к великому сожалению приехал в неисправном состоянии.

В корпусе нет штатной подсветки. И использовать светодиод светящий внутри коробочки не получится, так как свечение графического индикатора получается блеклым и неравномерным, к тому же в этом месте должен быть установлен модуль RFID и светодиод будет ему мешать. По этому я вынул подсветку из старого разбитого экрана от тестера LCR-T4. Размер которой мне идеально подошел.

В итоге получилось очень круто. Камера не передает реального цвета, в реальности гораздо красивее.

Сборка
Для отладки устройства собрал проект на макетной плате. После успешного тестирования, спаял все платы между собой проводами(навесным монтажом). Признаюсь, что это не самый лучший вариант из существующих, но кросс плату рисовать, а потом травить у меня нет ни времени ни желания. На фото снизу пример того как будет размещены платы, но они еще пока не закреплены в корпусе, так как RFID модуль будет заменен на исправный, когда приедет.

Код для Ардуино
По скетчу особо нечего сказать. Весь код детально прокомментирован.
Нажмите, что бы посмотреть
#include <avr/wdt.h> #include <MFRC522.h> #include <DFPlayer_Mini_Mp3.h> #define BUSY_PIN 4 // Пин BUSY для MP3 пдеера #define RST_PIN 9 // Пин RST для rfid модуля #define SS_PIN 10 // Пин SS для rfid модуля #define RX 2 // назначить пин RX SoftSerial #define TX 3 // назначить пин TX SoftSerial #define modeRes 11 // пин для сброса памяти ключей #define look_PIN 5 // номер пина для подключения к нему реле открытия замка #define blue_PIN 8 #define green_PIN 7 #define red_PIN 6 #define interval 2000 // интервал сброса RFID модуля в мкс #define time_reset_mp3 600000 // интервал сброса MP3 плеера #define volume 25 // громкость MP3 от 0 до 30 #define equalizer 0 // эквалайзер(POP=1) от 0 до 5 Normal/Pop/Rock/Jazz/Classic/Bass #define sumName 7 // количество имен пользователей записанных на флешке начинаются с 0001.mp3, 0002.mp3 ... и т.д. uint32_t rebootTimer=0, oldTime=0; // Переменная для таймера MFRC522 rfid(SS_PIN, RST_PIN); // Назначить пины SS и RST SoftwareSerial mySerial(RX, TX); // назначить пины RX, TX SoftSerial void setup() { // pinMode(look_PIN, OUTPUT); // пин для подключения реле открытия замка // digitalWrite(look_PIN, LOW); pinMode(red_PIN, OUTPUT); pinMode(green_PIN, OUTPUT); pinMode(blue_PIN, OUTPUT); pinMode(modeRes, INPUT_PULLUP); // пин сброса памяти ключей mySerial.begin (9600); // Инициализация Soft Serial mp3_set_serial (mySerial); delay (1000); mp3Reset(); // инициализация плеера uint8_t memClear = digitalRead(modeRes); // проверяем состояние пина сброса памяти if(!memClear) // Если установлена перемычка на GND и пин D11 { digitalWrite (green_PIN, HIGH); digitalWrite (red_PIN, HIGH); eeprom_write_byte(0, 255); // то обнуляем счетчик ключей mp3play (113, 13000); // память обнулена while(true); // зависаем и ждем нажатия reset } SPI.begin(); // Инициализация SPI firstStart(); // проверка наличия Мастер ключа и добавление его mp3play (109, 9500); // система активирована //Serial.begin(115200); // Инициализация Serial //wdt_enable(WDTO_8S); } void loop() { digitalWrite (green_PIN, HIGH); digitalWrite (red_PIN, HIGH); digitalWrite (blue_PIN, HIGH); uint8_t sumKey = eeprom_read_byte(0); // считываем из EEPROM с адреса 0 информацию о количестве сохраненных ключей uint32_t master = ReadEEPROM_Long(1); // читаем из EEPROM uid мастер ключа uint32_t uid_tmp = ReadUID(); // ждем когда приложат ключ к считывателю и Читаем UID ключа //Serial.println(sumKey); //Serial.println(master); //Serial.println(uid_tmp); if(uid_tmp == master) { digitalWrite (red_PIN, LOW); digitalWrite (blue_PIN, LOW); addKey(sumKey); // если к считывателю поднесли мастер ключ, то переходим в режим добавления новых ключей } if(sumKey>=0 && uid_tmp != master) // если в EEPROM больше 1 ключа и UID прочитан с RFID то сравниваем все имеющиеся в EEPROM ключи со считанным 1 { uint8_t nameKey = searchKey(sumKey, uid_tmp); // считываем все ключи по очереди из EEPROM и сравниваем их с UID приложенного к считывателю ключа if(nameKey > 0) // в nameKey номер(индекс) ключа { sumKey = eeprom_read_byte(nameKey*5); // читаем из EEPROM привязанное к uid имя пользователя //digitalWrite (look_PIN, HIGH); // открываем замок digitalWrite (green_PIN, LOW); mp3play (106, 1480); // фраза "Здравствуйте" mp3play (sumKey, 1200); // проигрываем привязанное имя пользователя к uid mp3play (107, 5000); // фраза "Добро пожаловать" //digitalWrite (look_PIN, LOW); // закрываем замок } else { digitalWrite (red_PIN, LOW); mp3play(104, 6500); } // фраза "Доступ запрещен, Ключ не найден" } if (millis() - oldTime > time_reset_mp3) // интервал таймера для сброс плеера { oldTime = millis(); } } //********************************************* uint32_t ReadUID(void) // опрашиваем считыватель каждую 0,2сек { for (;;) // бесконечный цикл { ResetRFID(); // перезагружаем RFID модуль каждые 2 секунды delay(100); if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) // Если метка найдена и она прочитана { return uidTolong(); // прервать цикл и возвратить UID } } } //----------------------------------------- uint8_t searchKey( uint8_t sumKey, uint32_t uid ) // поиск ключа в EEPROM. sumKey-количество сохраненных ключей вEEPROM { for (uint8_t i = 1; i <= sumKey; i++) { uint32_t tmp=ReadEEPROM_Long(i*5+1); //читаем по очереди все сохраненные UID и сравниваем их с UID полученным c RFID if(tmp == uid) { return i; } // сравниваем считанный из памяти UID с прочитанным UID и если найден, то возвращаем номер совпавшего ключа } return 0; // если в базе нет такого ключа то возвращаем 0 } //--------------------------------------- void addKey(uint8_t sumKey) // sumKey-количество сохраненных ключей в EEPROM { mp3play(101, 15000); // для добавления нового UID после того как услышите имя которое нужно привязать к UID поднесите ключ к считывателю RFID for (uint8_t i = 1; i <= sumName; i++) // перебираем все имена по очереди { mp3play(110, 1000); // проигрывание звука next ResetRFID(); pinMode(TX, OUTPUT); // восстанавливаем выход для TX mp3_play (i); // проигрываем в фоновом режиме в порядке возрастания все записанные на флешку имена mp3_set_volume (volume); // устанавливаем заданную громкость for (uint8_t ti = 0; ti < 20; ti++) // цикл таймер, 20*100мс=2 сек на опрос считывателя RFID пока проигрывается имя пользователя { if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) // Если определена метка и она прочитана { uint32_t uid = uidTolong(); // преобразуем uid из массива в ulong uint8_t dub = searchKey(sumKey, uid); // ищем в EEPROM совпадение, проверяем ключ на дубли if(dub == 0) // если такого ключа нет в памяти то добавляем его { // или если ключ есть то в dub номер ключа sumKey++; // инкрементируем счетчик ключей eeprom_write_byte(0, sumKey); // счетчик ключей в EEPROM eeprom_write_byte(sumKey*5, i); // сохраняем в EEPROM привязанное к ключу имя WriteEEPROM_Long (sumKey*5+1, uid); // сохраняем UID ключа в EEPROM delay(500); // для того,что бы имя проигралось до конца mp3play(103, 4500); // фраза "новый ключ сохранен" mp3play(112, 9000); // фраза "мастер режим завершен" return; } else { digitalWrite (blue_PIN, HIGH); delay(1000); mp3play(111, 9000); // прочитанный ключ уже имеется в системе digitalWrite (red_PIN, HIGH); mp3play(112, 9000); // фраза "мастер режим завершен" return; } } delay(100); } } mp3play(108, 9000); // Время ожидание ключа истекло. } //---------------------------------------- void firstStart(void) { uint8_t tmp = eeprom_read_byte(0); // проверяем записан ли мастер ключ в EEPROM if(tmp==255) // если его там нет, то нужно добавить { mp3play(100, 12500); // фраза добавить мастер ключ uint32_t uid_tmp = ReadUID(); // прочитать UID мастер ключа со считывателя RFID if( uid_tmp > 0 ) // если ключ прочитан то записываем его в EEPROM { eeprom_write_byte (0, 0); // по адресу 000 записываем счетчик ключей не более 51 шт.------------------ WriteEEPROM_Long (1, uid_tmp); // по адресу 001 может быть записан только мастер ключ mp3play(102, 3500); // фраза мастер ключ добавлен } } } //---------------------------------------- void ResetRFID(void) // Таймер сброса RFID защита от зависания { if (millis() - rebootTimer > interval) // интервал таймера { rebootTimer = millis(); // Обновляем таймер digitalWrite(RST_PIN, HIGH); // Сбрасываем модуль delayMicroseconds(100); // длительность сброса 100 мкс digitalWrite(RST_PIN, LOW); // Отпускаем сброс rfid.PCD_Init(); // Инициализируем RFID rfid.PCD_AntennaOn(); // Включаем антенну } } //--------------------------------------- void mp3Reset(void) // можно так же использовать как остановку трека { mp3_reset (); delay (1000); // mp3_set_device(2); // 2-работа с SD Card. 1/2/3/4/5 U/SD/AUX/SLEEP/FLASH // mp3_DAC (true); mp3_set_EQ (equalizer); // эквалайзер Normal/Pop/Rock/Jazz/Classic/Bass delay (100); mp3_set_volume (volume); // устанавливаем громкость delay (200); pinMode(TX, INPUT_PULLUP); // Отключаем TX, что бы в динамике исчез шум/наводка } void mp3play(uint16_t track, uint16_t time) { pinMode(TX, OUTPUT); // восстанавливаем выход TX mp3_play(track); // проиграть трек // mp3_set_volume (volume); // устанавливаем заданную громкость //wdt_reset(); delay(time); // длительность проигрывания // mp3_set_volume (0); // выключаем звук, что бы не было шума в динамике pinMode(TX, INPUT_PULLUP); // Отключаем TX, что бы в динамике исчез шум/наводка } //--------------------------------------- uint32_t uidTolong(void) { uint32_t uid_long=0; uid_long = rfid.uid.uidByte[3]; // преобразуем uid массив в тип uint32_t uid_long = ((uid_long << 8) | rfid.uid.uidByte[2]); uid_long = ((uid_long << 8) | rfid.uid.uidByte[1]); return (uid_long = ((uid_long << 8) | rfid.uid.uidByte[0])); //прервать цикл и передать UID } void WriteEEPROM_Long(uint8_t addr, uint32_t data) //преобразовать из long и сохранить в EEPROM { eeprom_write_byte((uint8_t*)addr, data & 0xFF); eeprom_write_byte((uint8_t*)addr+1, (data & 0xFF00) >> 8); eeprom_write_byte((uint8_t*)addr+2, (data & 0xFF0000) >> 16); eeprom_write_byte((uint8_t*)addr+3, (data & 0xFF000000) >> 24); } uint32_t ReadEEPROM_Long(uint8_t addr) // считываем значение из EEPROM и преобразуем в long { uint32_t ir_code = eeprom_read_byte((uint8_t*)addr+3); ir_code = (ir_code << 8) | eeprom_read_byte((uint8_t*)addr+2); ir_code = (ir_code << 8) | eeprom_read_byte((uint8_t*)addr+1); return ((ir_code << 8) | eeprom_read_byte((uint8_t*)addr)); }
Фразы
Для отладки кода, выкладываю фразы которые нужно скачать, распаковать и скопировать на флешку, а ниже прикладываю их текстовый вариант.
Нажмите, что бы посмотреть
1 - 50 Имена пользователей 100 - В памяти устройства нет сохраненных ключей. Для начала добавьте мастер ключ. Для этого поднесите к считывателю любой ключ который вы хотите сделать основным. 101 - Вы зашли в мастер режим добавления ключей. Что бы добавить новый ключ поднесите его к считывателю. 102 - Мастер ключ добавлен! 103 - Новый ключ добавлен! 104 - Доступ заблокирован! Используемый Вами ключ отсутствует в системе. 105 - Доступ разрешен! 106 - Добрый день! 107 - Добро пожаловать! 108 - Время ожидание ключа истекло. Система бесконтактной идентификации переходит в стандартный режим работы. 109 - Система бесконтактной идентификации готова к работе. 110 - Звуковой сигнал next 111 - Прочитанный ключ уже используется в системе идентификации, попробуйте привязать другую метку. 112 - Мастер режим завершен! 113 - Включен режим обнуления памяти ключей. Все ключи удалены из памяти. Для возврата в стандартный режим не забудьте удалить перемычку и нажать кнопку сброса.
Заключение
К сожалению довести проект до логического конца так и не получилось. Так как компактный RFID RC522 модуль оказался неисправным. Я заказал в этот раз два таких же модуля у разных продавцов, надеюсь хоть один из них приедет исправным и заработает. Но сколько дней будут ехать посылки пока неизвестно, так как на текущий момент они еще даже не отправлены. Ориентировочные сроки 5 ноября. Соответственно когда смонтирую считыватель на дверь и испытаю его в боевых условия, то я сниму короткий видео отчет.

Спасибо, что дочитали до конца! Надеюсь, что эта статья вам понравилась и вы сможете воспользоваться моей наработкой в своих проектах.
Если у Вас остались вопросы и замечания, то можете задать их в комментариях. Я с удовольствием на них отвечу.
21 комментарий
Неточности в нарисованной схеме,
пины замка и ног светодиода на схеме аналоговые, А0, А2, А3, А4, а в скетче D5, D6, D7, D8.
Строки отвечающие за работу замка look_PIN закоментированы.
Время фразы 106 "Здравствуйте" слишком мало, успевал произносить только "Здраст" поставил немного больше 1880 вместо 1480
А так вроде все работает.
Спасибо.
C:\Arduino\sketch_domofon\sketch_domofon.ino: In function 'void loop()':
C:\Arduino\sketch_domofon\sketch_domofon.ino:77:12: warning: comparison is always true due to limited range of data type [-Wtype-limits]
~~~~~~^~~
C:\Arduino\sketch_domofon\sketch_domofon.ino:82:40: warning: invalid conversion from 'int' to 'const uint8_t* {aka const unsigned char*}' [-fpermissive]