Давным-давно, настолько что уже кажется неправдой, masterkit рассказывали про чудесный встраиваемый MP3-плеер, которым можно оснащать все, что угодно, даже MP3-плееры, если из них сначала вынуть собственный, а потом поставить этот. Короче, полезная вещь. Особенно, если хочется сделать детскую игрушку во-первых, своими руками, а, во-вторых — правильно, а не так, как думают те, кто их делает в промышленном масштабе.

Однако в представленном товаре меня устраивало не только лишь все, поэтому я подумал: не может быть такого, чтобы настолько по-китайски выглядящей вещи не было на Aliexpress. И действительно, там было какое-то количество таких плееров, причем ознакомление с модельным рядом показало, что есть варианты более привлекательные, нежели модели с фиксированным объемом памяти. Именно — со слотом для сменных карточек microSD.

И понеслось.



Чтобы было понятно что и куда понеслось, демо ежа в различных режимах.

Развлекательные фразы при ожидании:



Переключение сказок, громкости и выключение карточками и встряхиванием:



Беспроводная зарядка:



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

Итак, после недолгих поисков и сравнений я переключился на Ebay, где и приобрел то, что хотел — плеер с чипом-декодером JQ6500, 4 мегабитами встроенной памяти и слотом для microSD.



Почему именно этот плеер? Потому, что тогда мне казалось, что это идеальный вариант для самоделки. Поскольку все при нем. Хочешь — используй отдельно: для этого есть пины воспроизведения/паузы, переключения треков, громкости, быстрого воспроизведения пяти композиций и моноусилитель для подключения динамика напрямую к плате (есть и стереовыход, но ему уже нужен внешний усилитель). То есть, по минимуму достаточно батарейки, динамика, нескольких кнопок и карточки памяти с музыкой.

image

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

Итак, два компонента уже известны: плеер и Arduino. Но лично я не хотел останавливаться на достигнутом, потому что глупо же глупо имитировать кнопки целым микроконтроллером. Надо что-то особенное, чтобы с Емелей, так сказать, и щуками.

Поэтому сюда же добавил простейший вибродатчик SW-18010P и всем известный считыватель карточек RC522.

image

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

image

Здесь же получается как: вибродатчик служит для включения игрушки, когда ребенок берет ее в руки. Тот же датчик не даст игрушке выключиться, пока ребенок ее не положит на достаточно длительное время. Что касается читалки карточек Mifare, то эта штука, на мой взгляд, ��райне удобна для переключения сказок. Например, карточки можно прикрепить к книжкам, и тогда ребенок сможет послушать сказку, поднеся игрушку к книжке.

А еще карточки можно прикрепить к различным предметам, и тогда ребенок сможет послушать их описание и правила использования. Например, что вот эта белая фиговина — холодильник, и что мы там храним продукты, чтобы они не испортились, и что по этой причине открывать его почем зря не стоит. Или что вот то — духовка, и она может быть горячей, поэтому не нужно висеть на ее ручке и прикладывать ладошки к стеклу. Много чего можно придумать, тем более, что карточками служат использованные билеты метро (да, мне повезло, я в Москве), которые можно запросто насобирать в нужном количестве.

Поэтому пока я думал, то между делом достал из запасов контроллер ATmega328p, напаял его на макетную платку и прошил загрузчик Adruino через Arduino Mega 2560.

image

Для удобства вывел последовательный порт, сброс и землю на отдельный разъем для быстрого перепрограммирования. А то знаю себя — залью скетч, а потом одно не то, другое не так.

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

image

Решение использовать такой пауэрбанк, а не типичный плоский аккумулятор может показаться нелогичным. Но я подумал, что так как игрушка будет потреблять довольно приличный ток (только контроллер и плеер в режиме ожидания кушают около 40 мА), то возможность быстро заменить пустую батарейку на полную весьма кстати.

А еще мне пришлось добавить пьезокерамическую пищалку для звуковой индикации некоторых событий (чтение карточки, например). Это, скажете, вообще смешно — есть же динамик, так? Ну так, да не так. Динамик ведь подключен к плееру, а не к контроллеру. И еще добавил транзистор в качестве ключа, который отключает MP3-плеер во время сна, чтобы снизить потребление энергии.

Внимательный читатель может заметить, что плеер можно было бы запитать от цифрового пина контроллера, который прекрасно справился бы с включением и выключением. Я бы сам того хотел, но это только в режиме ожидания плеер потребляет 16 мА. А когда музыка, то он легко забирает больше 100 мА, что уже как минимум вдвое превышает возможности ATmega. Поэтому я взял «любой» npn-транзистор с током коллектора 300 мА и подвел его к цифровому пину контроллера через резистор около 200 Ом.

Зато картридер потребляет в пределах 40 мА, поэтому питающий его стабилизатор можно подключать к цифровому пину контроллера. Так и сделал, но все равно не получилось, о чем — в конце.

Еще такой момент: плееру требуется динамическая головка сопротивлением не ниже 8 Ом. У меня такая была (динамик из системного блока), но звук у нее не очень. Еще были динамики сопротивлением 4 Ом (от типичной китайской колонки). В общем, соединил оба последовательно: один дает больше высоких, другой — низких, а вместе они просто классно звучат.

Осталась мелочь, т.е. зеркало души. Которое проще всего смастерить из пары соединенных последовательно светодиодов. Брутально-красные брать не стал — очень уж страшно. А вот янтарно-желтые глаза — самое оно.

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

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

image

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



Тяжелее всего, должен признать, далась хирургия ежа. Во-первых, я не люблю шить. А пришлось прилично: сначала распорол, потом скрепил края, затем пришил шесть кнопок. Потом отпорол шесть кнопок и пришил четыре кнопки. Все почему? Потому что сначала пришил маленькие и неправильно, так что еж расстегивался.



Во-вторых, глаза. Светодиоды я, конечно, предусмотрительно приобрел диаметром 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-колонки я в курсе, это немного не то, даже если поместить такого милого ежа.