
Давным-давно, настолько что уже кажется неправдой, masterkit рассказывали про чудесный встраиваемый MP3-плеер, которым можно оснащать все, что угодно, даже MP3-плееры, если из них сначала вынуть собственный, а потом поставить этот. Короче, полезная вещь. Особенно, если хочется сделать детскую игрушку во-первых, своими руками, а, во-вторых — правильно, а не так, как думают те, кто их делает в промышленном масштабе.
Однако в представленном товаре меня устраивало не только лишь все, поэтому я подумал: не может быть такого, чтобы настолько по-китайски выглядящей вещи не было на Aliexpress. И действительно, там было какое-то количество таких плееров, причем ознакомление с модельным рядом показало, что есть варианты более привлекательные, нежели модели с фиксированным объемом памяти. Именно — со слотом для сменных карточек microSD.
И понеслось.
Чтобы было понятно что и куда понеслось, демо ежа в различных режимах.
Развлекательные фразы при ожидании:
Переключение сказок, громкости и выключение карточками и встряхиванием:
Беспроводная зарядка:
Прежде чем продолжить рассказ, прошу прощения, что могу допустить некоторые неточности в описании и схемотехнике, поскольку в процессе ничего не документировал, все так сказать, по наитию. Используйте, пожалуйста, здравый смысл и практические материалы, чтобы потом не было мучительно больно.
Итак, после недолгих поисков и сравнений я переключился на Ebay, где и приобрел то, что хотел — плеер с чипом-декодером JQ6500, 4 мегабитами встроенной памяти и слотом для microSD.
Почему именно этот плеер? Потому, что тогда мне казалось, что это идеальный вариант для самоделки. Поскольку все при нем. Хочешь — используй отдельно: для этого есть пины воспроизведения/паузы, переключения треков, громкости, быстрого воспроизведения пяти композиций и моноусилитель для подключения динамика напрямую к плате (есть и стереовыход, но ему уже нужен внешний усилитель). То есть, по минимуму достаточно батарейки, динамика, нескольких кнопок и карточки памяти с музыкой.

Но гораздо интереснее полное управление плеером через некоторое подобие последовательного порта. Здесь можно творить вообще что угодно — играть, останавливать, менять громкость, переключать эквалайзер и режимы воспроизведения, запускать воспроизведение произвольных композиций как в «сквозном» порядке, так и из определенных папок. И, что немаловажно в моем случае, уже имелась готовая библиотека для Arduino.
Итак, два компонента уже известны: плеер и Arduino. Но лично я не хотел останавливаться на достигнутом, потому что глупо же глупо имитировать кнопки целым микроконтроллером. Надо что-то особенное, чтобы с Емелей, так сказать, и щуками.
Поэтому сюда же добавил простейший вибродатчик SW-18010P и всем известный считыватель карточек RC522.

Все только для того, чтобы полностью избавиться от кнопок, которые в развлекательной игрушке я посчитал лишними. Сами посудите: если кнопки есть, ребенок так или иначе будет их нажимать, причем чаще — случайно, чем сознательно. А сверхчастое переключение сказок все же не совсем правильно.

Здесь же получается как: вибродатчик служит для включения игрушки, когда ребенок берет ее в руки. Тот же датчик не даст игрушке выключиться, пока ребенок ее не положит на достаточно длительное время. Что касается читалки карточек Mifare, то эта штука, на мой взгляд, ��райне удобна для переключения сказок. Например, карточки можно прикрепить к книжкам, и тогда ребенок сможет послушать сказку, поднеся игрушку к книжке.
А еще карточки можно прикрепить к различным предметам, и тогда ребенок сможет послушать их описание и правила использования. Например, что вот эта белая фиговина — холодильник, и что мы там храним продукты, чтобы они не испортились, и что по этой причине открывать его почем зря не стоит. Или что вот то — духовка, и она может быть горячей, поэтому не нужно висеть на ее ручке и прикладывать ладошки к стеклу. Много чего можно придумать, тем более, что карточками служат использованные билеты метро (да, мне повезло, я в Москве), которые можно запросто насобирать в нужном количестве.
Поэтому пока я думал, то между делом достал из запасов контроллер ATmega328p, напаял его на макетную платку и прошил загрузчик Adruino через Arduino Mega 2560.

Для удобства вывел последовательный порт, сброс и землю на отдельный разъем для быстрого перепрограммирования. А то знаю себя — залью скетч, а потом одно не то, другое не так.
Рядом распаял еще и стабилизатор на 3.3В, так как RC522 по недоразумению питается именно от этого напряжения, тогда как остальные компоненты прекрасно чувствуют себя на универсальных 5В, которые я предполагал брать от простенького пауэрбанка на аккумуляторах типа 18650.

Решение использовать такой пауэрбанк, а не типичный плоский аккумулятор может показаться нелогичным. Но я подумал, что так как игрушка будет потреблять довольно приличный ток (только контроллер и плеер в режиме ожидания кушают около 40 мА), то возможность быстро заменить пустую батарейку на полную весьма кстати.
А еще мне пришлось добавить пьезокерамическую пищалку для звуковой индикации некоторых событий (чтение карточки, например). Это, скажете, вообще смешно — есть же динамик, так? Ну так, да не так. Динамик ведь подключен к плееру, а не к контроллеру. И еще добавил транзистор в качестве ключа, который отключает MP3-плеер во время сна, чтобы снизить потребление энергии.
Внимательный читатель может заметить, что плеер можно было бы запитать от цифрового пина контроллера, который прекрасно справился бы с включением и выключением. Я бы сам того хотел, но это только в режиме ожидания плеер потребляет 16 мА. А когда музыка, то он легко забирает больше 100 мА, что уже как минимум вдвое превышает возможности ATmega. Поэтому я взял «любой» npn-транзистор с током коллектора 300 мА и подвел его к цифровому пину контроллера через резистор около 200 Ом.
Зато картридер потребляет в пределах 40 мА, поэтому питающий его стабилизатор можно подключать к цифровому пину контроллера. Так и сделал, но все равно не получилось, о чем — в конце.
Еще такой момент: плееру требуется динамическая головка сопротивлением не ниже 8 Ом. У меня такая была (динамик из системного блока), но звук у нее не очень. Еще были динамики сопротивлением 4 Ом (от типичной китайской колонки). В общем, соединил оба последовательно: один дает больше высоких, другой — низких, а вместе они просто классно звучат.
Осталась мелочь, т.е. зеркало души. Которое проще всего смастерить из пары соединенных последовательно светодиодов. Брутально-красные брать не стал — очень уж страшно. А вот янтарно-желтые глаза — самое оно.
Итак, макет игрушки собран и отлажен. Теперь самое главное: нужен донор телесной оболочки. Вообще, мне очень хотелось птицу-говоруна, но судя по цене соседних игрушек, удовольствие не совсем бюджетное. Особенно если учесть, что интерес ребенка — вещь непрогнозируемая.
Поэтому для начала я стал искать более доступного кандидата на трансплантацию. И такой нашелся: очаровательный еж Ивлин, продающийся в Детском мире.

Конечно, пришлось практически целиком избавиться от богатого внутреннего мира ежа. И заменить его самодельным, упакованным в обычную мыльницу. Впрочем, не совсем обычную. Дело в том, что в отличие от многих, у этой мыльницы плоская задняя сторона, поэтому там удобно размещать считыватель карточек — получится минимальная дистанция. С другой же стороны у мыльницы что-то вроде массажной щетки и даже есть отверстия, т.е. там идеально размещается динамик: звук будет выходить через отверстия, которые не будут перекрыты благодаря массажным шипам. Отверстий, правда, оказалось маловато, но не беда — я еще насверлил.
Тяжелее всего, должен признать, далась хирургия ежа. Во-первых, я не люблю шить. А пришлось прилично: сначала распорол, потом скрепил края, затем пришил шесть кнопок. Потом отпорол шесть кнопок и пришил четыре кнопки. Все почему? Потому что сначала пришил маленькие и неправильно, так что еж расстегивался.
Во-вторых, глаза. Светодиоды я, конечно, предусмотрительно приобрел диаметром 3 мм, чтобы уж гарантированно можно было изобразить ими зрачки. Однако высверлить отверстия в имеющихся глазах ежа Ивлина оказалось не так-то просто. Казалось бы: берешь гравер, ставишь в него нужное сверло — и вперед. Но выяснилось, что во время сверления пластик превращается в вязкую массу, где сверло слабенького гравера вязнет намертво.
И, должен сказать, еж со сверлом, торчащим из глаза, зрелище инфернальное.
Кстати, по этому поводу даже не знаю, что посоветовать. Я как-то извернулся и все-таки проделал отверстия, вставил в них светодиоды и залил эпоксидкой, а сверху покрыл бесцветным лаком для ногтей. Глаза также приклеил к ежу эпоксидкой, поскольку родное крепление погибло в процессе сверления. Результат получился не идеальным, но вполне терпимым.
Как еж работает, вы уже видели. Поэтому могу только продублировать текстом его текущую логику. Изначально еж находится в режиме сна. Если его взять в руки (или просто пошевелить, или пошевелить поверхность, на которой он лежит и т.п.), он проснется по прерыванию, которое сгенерирует датчик вибраций.
После этого через заданные интервалы еж будет говорить заранее заданные фразы, и если в течение некоторого времени ежа не трогать, он снова уснет. А если трогать — не уснет. А если потрясти некоторое время — начнет рассказывать сказку, выбранную в случайном порядке.
Если же к ежу (когда он не спит) поднести карточку, то еж начнет рассказывать сказку, которая ассоциирована с этой карточкой. Жесткой привязки нет, порядок будет меняться при замене сказок. Фиксированы только две служебные карточки: для регулировки громкости и принудительного усыпления ежа. Громкость, к слову, настраивается последовательным переключением трех ступеней (тихо — средне — громко).
Что до янтарных глаз, то они мигают по два раза (в цикле), когда еж проснулся, но молчит и меняют яркость, когда еж рассказывает сказку. Мне это показалось оптимальным вариантом.
В процессе сборки попробовал еще одну новацию: так как у пауэрбанка два разъема (вход и выход), то ко входу подключил имеющийся у меня адаптер беспроводной зарядки Qi. И таким образом получилось, что для перезарядки ежа, его совсем не нужно расстегивать — достаточно просто положить на ночь на беспроводной зарядник. Впрочем, как раз эта функция пока что в режиме тестирования.
Вообще, несмотря на кажущуюся простоту, некоторые вещи меня озадачили. Например, из кода вы можете заметить, что некоторые команды я отправляю плееру несколько раз подряд, да еще ставлю в конце таймаут. А все потому, что на практике в моей конфигурации только так и можно.
Также видно, что я зачем-то контролирую окончание воспроизведения по аппаратному сигналу плеера вместо того, чтобы простой командой включить ему режим воспроизведения только одного файла. Это тоже не просто так: почему-то у меня этот режим не работает, поэтому при окончании одной композиции плеер начинает играть следующую.
Еще любопытным может показаться то, что функция random для воспроизведения случайной композиции постоянно крутится в loop, вместо того, чтобы вызываться лишь когда она действительно нужна. Но тут такое дело: если вызывать ее только когда она нужна, то она почему-то в подавляющем большинстве случаев возвращает одно и то же значение. Зато если поставить в loop, тогда генерируются действительно псевдослучайные значения. Собственно, это я тоже на практике выяснил, когда пытался понять неадекватное поведение ежа.
Наконец, что меня совсем поставило в тупик, так это неспособность справиться с выключением картридера с помощью цифрового пина контроллера, от которого ридер и питается. Почему-то выходит так, что если пин выставить в LOW и затем в INPUT, то ридер не выключается.
При этом, если просто выставить LOW, то светодиод картридера «горит» вполнакала, напряжение на выходе стабилизатора, питающего ридер — около вольта. Если затем на пине контроллера сделать INPUT, это напряжение вырастает примерно до 3В.
Еще интереснее, что если сначала выставить пины контроллера, подключенные к SS и RST ридера в LOW и INPUT, а затем в это же положение перевести питающий пин контроллера, то ридер выключается. И даже потом включается после сна, если питающий пин перевести в OUTPUT и HIGH.
Однако при этом случается что-то непоправимое с таймерами. То есть, это я так считаю, потому что после такого финта (сна с отключением ридера) неадекватно работают глаза и счетчик встряхиваний, а оба эти процесса завязаны на millis(). Что происходит и как восстановить работу таймера, я не знаю, поэтому пока оставил, как есть — картридер продолжает питаться даже во время сна.
Если старшие товарищи помогут найти выход — буду очень признателен. Хотя с трудом верю, что старшие товарищи дочитают до этого места.
С учетом вышесказанного, код ежа Ивлина совсем неидеален, но вы всегда можете причесать его (код или ежа — выбирайте сами), оптимизировать, дополнить, сократить или иным образом приспособить к своим потребностям. Именно поэтому я его и прилагаю. А чтобы было проще использовать то, что есть, максимум настроек (старался вообще все, но получилось как обычно) находится не в коде, а в секции определения переменных. Там и конфигурация пинов, и временные задержки, и количество треков.
В секции описания номеров карточек следует понимать, что последние две карточки всегда «зарезервированы» для внутренних функций — переключения громкости и режима сна.
Программа действий
/* A0 - проверка состояния MP3 (играет или стоп)
* pin 0, 1 - последовательный порт (перепрошивка)
* pin 2 - прерывание на проснуться
* pin 4 - включение питания MP3 (через транзистор)
* pin 5 - пищалка
* pin 6 - питание кардридера
* pin 7, 8 - управление MP3
* pin 9 - сброс кардридера
* pin 10 - выбор кардридера
* pin 11, 12, 13 - SPI кардридера
*/
#include <Arduino.h>
#include <SoftwareSerial.h>
#include <JQ6500_Serial.h>
#include <avr/pgmspace.h> // для PROGMEM
#include <SPI.h>
#include <MFRC522.h>
#include <avr/sleep.h>
#include <avr/power.h>
#define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off)
#define adc_enable() (ADCSRA |= (1<<ADEN)) // re-enable ADC
#define RST_PIN 9
#define SS_PIN 10
#define mp3Pin 4
#define mp3Busy A0
#define readerPin 6
#define tonePin 5
#define ledPin 3
#define offDelay 70000 // таймаут автовыключения
#define winkStep 1500 // пауза между морганиями
#define on 150 // глаза "открыты"
#define off 80 // глаза "закрыты"
#define tOut1 0 // таймауты для воспроизведения определенных фраз в режиме ожидания (x - начало/ x1 - конец интервала)
#define tOut11 500
#define tOut2 14000
#define tOut21 15000
#define tOut3 29000
#define tOut31 30000
#define tOut4 44000
#define tOut41 45000
#define tOut5 60000
#define tOut51 65000
#define tShake 2000 // время тряски для включения воспроизведения
#define nShakeQ 10 // количество встряхиваний для включения воспроизведения
#define introQ 5 // количество файлов-заставок
#define minVol 18 // низкая громкость
#define midVol 22 // средняя громкость
#define maxVol 25 // высокая громкость
unsigned long dimDelay, winkStepDelay, onDelay, ledOffDelay, tShakeDelay;
boolean ledOn, ledOff, eyes, pwm;
int wink;
int pwmVal;
boolean playON = false;
boolean pwmUp = false;
byte pwmStep = 1;
unsigned int playFile;
MFRC522 mfrc522(SS_PIN, RST_PIN); // объект MFRC522
unsigned long uidDec, uidDecTemp; // для отображения номера карточки в десятичном формате
byte bCounter, readBit, nShake, rnd;
byte vol = midVol; // уровень громкости при включении средний
unsigned long ticketNumber;
unsigned long offTimeOut = 0; // счетчик таймера автовыключения
boolean mp3ON = false; // флаг включенного плеера
boolean isInt = false; // флаг прерывания
byte ticketQ = 32; // количество карточек минус два (резерв на карточку-выключатель и громкость)
byte fileQ = 0; // счетчик MP3-файлов
// массив номеров карточек (в десятичном виде, написан на самой карточке), последние две служат для переключения громкости и режима сна. Заполните своими номерами, текущие только для примера
PROGMEM const uint32_t ticketSet[] = {2515217196, 2540548337, 2490970856, 2486466332, 2485920633, 35870611, 37836807, 37836806, 2377004330, 2522873668, 2514304566, 23472725, 2485702426, 2374853555, 2374391583, 2492957469, 2486467162, 2489280075, 2488031661, 2491726641, 2491720188, 2490968782, 2490968783, 2488900952, 2489969016, 2506562651, 2375447052, 2375449579, 2489276180, 2483389692, 2486466331, 2484789326};
JQ6500_Serial mp3(8,7); // объект плеера
void enterSleep()
{
mp3.playFileNumberInFolderNumber(01, 005); // файл-заставка
mp3.playFileNumberInFolderNumber(01, 005); // файл-заставка
delay(2500);
tone(tonePin, 800, 500);
delay(500);
digitalWrite(readerPin, LOW);
digitalWrite(mp3Pin, LOW);
digitalWrite(ledPin, LOW);
pinMode(ledPin, INPUT);
pinMode(readerPin, INPUT);
pinMode(mp3Pin, INPUT);
adc_disable();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
sleep_disable();
power_all_enable();
adc_enable();
tone(tonePin, 450, 500);
pinMode(ledPin, OUTPUT);
pinMode(readerPin, OUTPUT);
pinMode(mp3Pin, OUTPUT);
digitalWrite(ledPin, LOW);
digitalWrite(readerPin, HIGH);
digitalWrite(mp3Pin, HIGH);
SPI.begin(); // инициализация SPI
mfrc522.PCD_Init(); // инициализация MFRC522
mp3Init();
offTimeOut = millis();
ledOffDelay = millis();
mp3.playFileNumberInFolderNumber(01, 001); // файл-заставка
mp3.playFileNumberInFolderNumber(01, 001); // файл-заставка
delay(500);
playON = false;
mp3ON = true;
}
void wakeUp() {
detachInterrupt(0);
ledOffDelay = millis();
if (isInt == false) { // флаг прерывания
isInt = true;
}
attachInterrupt(0, wakeUp, LOW);
}
void setup() {
pinMode(2, INPUT_PULLUP);
attachInterrupt(0, wakeUp, LOW);
pinMode(readerPin, OUTPUT);
digitalWrite(readerPin, HIGH);
SPI.begin(); // инициализация SPI
mfrc522.PCD_Init(); // инициализация MFRC522
pinMode(mp3Pin, OUTPUT);
mp3Init();
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
eyes = false;
ledOn = false;
ledOff = false;
dimDelay = millis();
winkStepDelay = millis();
wink = 0;
pwmUp = true; // начинать с повышения яркости
pwmVal = 0;
ledOffDelay = millis();
offTimeOut = millis();
mp3.playFileNumberInFolderNumber(01, 001); // файл-заставка
mp3.playFileNumberInFolderNumber(01, 001); // файл-заставка
delay(500);
playON = false;
mp3ON = true;
}
void loop() {
rnd = random(1, fileQ-1);
if ((millis() - offTimeOut) > offDelay) { // таймер выключения
enterSleep();
} else {
ledWink(); // моргание при паузе
if (isInt == true) {
offTimeOut = millis(); // сброс таймера выключения - не выключаться, пока игрушка в руках
if (nShake == 0) {
tShakeDelay = millis();
}
if ((millis() - tShakeDelay) < tShake){
nShake = nShake + 1;
} else {
tShakeDelay = millis();
nShake = 0;
}
isInt = false;
}
if (nShake > nShakeQ) {
playRandom();
nShake = 0;
}
// КОГДА НЕ ИГРАЕТ МУЗЫКА
playPreset(); // воспроизведение заданных фраз при паузе
// КОГДА ИГРАЕТ МУЗЫКА
if (mp3ON == true) {
if (playON == true) { // включена сказка, а не фраза
offTimeOut = millis(); // сброс таймера выключения
}
nShake = 0;
eyesPWM(); // мерцание глаз
// Воспроизведение MP3
if(analogRead(mp3Busy) < 250) { // если на паузе - сброс флагов, выключение лампочек
mp3ON = false;
playON = false;
digitalWrite(ledPin, LOW);
}
}
scanPlay(); // воспроизведение по карточке
}
}
void setBitsForGood(byte daBeat) {
if (daBeat == 1) {
bitSet(ticketNumber, bCounter);
bCounter=bCounter+1;
}
else {
bitClear(ticketNumber, bCounter);
bCounter=bCounter+1;
}
}
// ВКЛЮЧЕНИЕ И ИНИЦИАЛИЗАЦИЯ MP3
void mp3Init() {
digitalWrite(mp3Pin, HIGH);
delay(100);
mp3.begin(9600);
mp3.reset();
mp3.setVolume(vol);
mp3.setLoopMode(MP3_LOOP_NONE);
fileQ = mp3.countFiles(MP3_SRC_SDCARD); // количество файлов в плеере
fileQ = fileQ - introQ; // минус заставки
}
// ВОСПРОИЗВЕДЕНИЕ СЛУЧАЙНОГО ФАЙЛА
void playRandom() {
tone(tonePin, 450, 500);
delay(500);
playFile = rnd;
mp3ON = true;
playON = true;
mp3.playFileByIndexNumber(playFile);
mp3.playFileByIndexNumber(playFile);
mp3.playFileByIndexNumber(playFile);
delay(500);
}
// МОРГАНИЕ
void ledWink() {
// Моргание диодами в режиме ожидания
if ((millis() - winkStepDelay) > winkStep) { // длинный таймер
// зажигание
if (eyes == true) { // если диоды включены
if (ledOn == false) {
onDelay = millis(); // заводим таймер
ledOn = true; // признак того, что диоды горели
}
if ((millis() - onDelay) > on) { // если таймер сработал
digitalWrite(ledPin, LOW); // выключение диодов
eyes = false; // признак выключенных диодов
}
}
// гашение
if (eyes == false) { // если диоды выключены
if (ledOff == false) {
ledOffDelay = millis(); // заводим таймер
ledOff = true; // признак того, что диоды горели
}
if ((millis() - ledOffDelay) > off) { // если таймер сработал
digitalWrite(ledPin, HIGH); // включение диодов
eyes = true; // признак включенных диодов
}
}
if (ledOn == true && ledOff == true) { // подсчет количества включений (каждая пара вкл/выкл)
wink = wink+1;
ledOn = false;
ledOff = false;
}
if (wink == 4) { // две пары вкл/выкл
winkStepDelay = millis();
wink = 0;
}
}
}
// ВОСПРОИЗВЕДЕНИЕ ЗАДАННЫХ ФРАЗ ПРИ ПАУЗЕ
void playPreset() {
if (mp3ON == false) {
if ((millis() - offTimeOut) > tOut2 && (millis() - offTimeOut) < tOut21) {
mp3.playFileNumberInFolderNumber(01, 002); // воспроизведение файла /001/002.mp3 если от включения прошло около tOut21 сек.
mp3ON = true;
delay(500);
}
if ((millis() - offTimeOut) > tOut3 && (millis() - offTimeOut) < tOut31) {
mp3.playFileNumberInFolderNumber(01, 003); // воспроизведение файла /001/003.mp3 если от включения прошло около tOut31 сек.
mp3ON = true;
delay(500);
}
if ((millis() - offTimeOut) > tOut4 && (millis() - offTimeOut) < tOut41) {
mp3.playFileNumberInFolderNumber(01, 004); // воспроизведение файла /001/004.mp3 если от включения прошло около tOut41 сек.
mp3ON = true;
delay(500);
}
}
}
// МЕРЦАНИЕ
void eyesPWM(){
if ((millis() - winkStepDelay) > (pwmStep)/4) {
// мерцание диодами пока играет MP3
if (pwmUp == true) {
if (pwmVal < 128) { // диапазон меньше 254 из-за крутой ВАХ светодиода (нет смысла крутить до 255, когда светодиод уже горит на полную)
analogWrite(ledPin, pwmVal);
pwmVal = pwmVal + 1;
pwmStep = pwmStep - 1;
winkStepDelay = millis();
} else {
pwmUp = false;
pwmStep = 1;
pwmVal = 128;
}
}
if (pwmUp == false) {
if (pwmVal > pwmStep) {
analogWrite(ledPin, pwmVal);
pwmVal = pwmVal - 1;
pwmStep = pwmStep +1 ;
winkStepDelay = millis();
} else {
pwmUp = true;
pwmStep = 128;
pwmVal = 1;
}
}
}
}
// ФУНКЦИИ ПО КАРТОЧКЕ
void scanPlay() {
if (fileQ > 0) {
// Поиск новой карточки
if ( ! mfrc522.PICC_IsNewCardPresent()) {
return;
}
// Выбор карточки
if ( ! mfrc522.PICC_ReadCardSerial()) {
return;
}
uidDec = 0;
// сюда мы приедем, если чип правильный
byte status;
byte byteCount;
byte buffer[18]; // длина массива (16 байт + 2 байта контрольная сумма)
byte pages[2]={4, 8}; // страницы с данными
byte pageByte; // счетчик байтов страницы
byteCount = sizeof(buffer);
byte bCount=0;
mfrc522.MIFARE_Read(4, buffer, &byteCount);
bCounter = 0; // 32-битный счетчик для номера
// биты 0-3
for (bCount=0; bCount<4; bCount++) {
readBit = bitRead(buffer[6], (bCount+4));
setBitsForGood(readBit);
}
// биты 4 - 27
for (pageByte=5; pageByte > 2; pageByte--) {
for (bCount=0; bCount<8; bCount++) {
readBit = bitRead(buffer[pageByte], bCount);
setBitsForGood(readBit);
}
}
// биты 28-31
for (bCount=0; bCount<4; bCount++) {
readBit = bitRead(buffer[2], bCount);
setBitsForGood(readBit);
}
for (byte ticketNum = 0; ticketNum < ticketQ; ticketNum++) {
unsigned long ticketTemp = pgm_read_dword_near(ticketSet + ticketNum);
if (ticketTemp == ticketNumber) {
tone(tonePin, 450, 500);
delay(500);
if (ticketNum < (ticketQ - 2)) {
if ((ticketNum+1) < fileQ) {
digitalWrite(ledPin, HIGH);
playFile = ticketNum+1;
mp3ON = true;
playON = true;
mp3.playFileByIndexNumber(playFile);
mp3.playFileByIndexNumber(playFile);
mp3.playFileByIndexNumber(playFile);
delay(500);
}
return;
} else {
if (ticketNum == ticketQ-1) {
enterSleep(); // сон
}
if (ticketNum == ticketQ-2) {
setVol(); // регулировка громкости
}
}
}
}
// }
// Halt PICC
mfrc522.PICC_HaltA();
}
}
// РЕГУЛИРОВКА ГРОМКОСТИ ПО КАРТОЧКЕ
void setVol() {
switch (vol) {
case maxVol:
vol = minVol;
break;
case midVol:
vol = maxVol;
break;
case minVol:
vol = midVol;
break;
}
mp3.setVolume(vol);
}
Схема, примерно восстановленная по коду должна выглядеть таким образом (прошу прощения, если где-то остались мои ошибки):


Здесь:
- U1 = ATmega328p
- UHT7333 = HT7333
- Конденсатор C1 = 0,1 мкФ
- Резистор R2 = ~50 Ом
- Резистор R3 = 220 Ом
- Резистор R4 = 1 кОм
- M1 = вибродатчик
- Резистор R4 = 1 кОм
- SP1 = динамик 8 Ом
- Piezo = пьезокерамический излучатель
- T1 = подходящий NPN-транзистор с током коллектора не менее 0,3А
- U2 = JQ6500 со следующей распиновкой: 1 — TX, 2 — RX, 3 — GND, 4 — VCC, 5 — BUSY, 6 — SPK+, 7 — SPK -
Хабраюзер Chigin, за что ему огромное спасибо, заметно улучшил и привел все это хозяйство в порядок. Но обратите внимание, что здесь уже PNP-транзистор, и управление питанием плеера необходимо инвертировать (т.е. при таком подключении плеер включается низким уровнем на пине контроллера и выключатся — высоким):

При текущей конфигурации аккумулятора емкостью порядка 2500 мАч хватает примерно на сутки использования ежа. Не сказать, чтобы много, но надо понимать, что график все время разный и большая часть энергии тратится, надеюсь, в активном режиме. Что в некоторой степени позволяет пренебречь несостоявшимся полным уводом в сон всей электроники ежа.
Если дать себе труд посчитать примерный бюджет, то получится что-то вроде этого (в USD):
- Плеер: 8,9
- ATmega328p: 1,1
- Макетная плата: 0,28
- Считыватель RC522: 2,21
- Динамическая головка: 0,99
- Пьезокерамический излучатель: 0,77
- Светодиоды: 0,12
- Транзистор: 0,14
- Стабилизатор: 0,13
- Вибродатчик: 0,13
- Мыльница: 0,99
- Пауэрбанк: 0,75
- Аккумулятор 18650: 3,9
- Зарядный адаптер Qi: 1,65
- Карта памяти: 3
- Еж Ивлин: 6
Итого 31,06, но на деле чуть больше, потому что еще нужен провод для соединений и другие мелочи вроде термоклея, двустороннего скотча и синей изоленты.
Наверное, должна быть какая-то особо важная заключительная фраза, но у меня в голове ее точно нет. Наверное, имеет смысл сказать, что ребенка игрушка вполне устраивает, и это точно лучше (а часто — и быстрее), чем включать ноутбук.
ps. про Bluetooth-колонки я в курсе, это немного не то, даже если поместить такого милого ежа.
