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

Комментарии (24)


  1. diller61
    27.06.2016 12:32
    +1

    насчет невыключения плейера: вообще по такой «схеме» сложно понять что и как, но рискну предположить что дело возможно в том что у вас управление питанием плейера осуществлено отключением «массы», при этом низкоомный динамик всегда остается подключен к «массе», видимо, через выходные каскады осуществляется паразитное питание

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

    upd:
    посмотрел еще раз на схему, 6 нога плейера всегда на минусе, 4 на плюсе, те он никогда не обесточивается


    1. spc
      27.06.2016 12:44

      Спасибо за замечание — действительно не уследил за шестой ногой, сейчас исправил эту ошибку.

      Плеер как раз выключается без проблем. Речь идет о картридере, с которым я разобраться так и не смог. То есть, либо я его выключаю, но после выхода контроллера из сна он (контроллер) становится неадекватен; либо я картридер не выключаю и трачу лишнюю энергию, зато после выхода контроллера из сна все в порядке.

      ps. для выключения плеера просто взял первый попавшийся вариант, никакого злого умысла.


  1. spc
    27.06.2016 12:44

    --


  1. Find_the_truth
    27.06.2016 13:04
    +1

    Не очень понимаю в схемотехнике, но эта игрушка крутая. Гораздо круче всяких говорящих Ферби))


  1. qbertych
    27.06.2016 15:14

    Здорово! А на чем беспроводная зарядка сделана?


    1. spc
      27.06.2016 15:17

      Все готовое стандарта Qi, как продается для различных телефонов.

      Примерно такой (но немного получше) зарядник и адаптер с разъемом microUSB — сразу в портативный аккумулятор.


  1. safari2012
    27.06.2016 15:44

    Крутая игрушка вышла. Один вопрос: если RFID-метку воткнуть в корешок книги, насколько такие книги(метки) будут интерферировать между собой на одной полке?


    1. spc
      28.06.2016 00:05

      Если так, то как видите — без проблем. Но карточки должны располагаться параллельно спине ежа.



  1. ComodoHacker
    27.06.2016 20:22

    Недавно мне попалась подобная китайская игрушка на AA батарейках (батарейка потекла, разъела контакты и пришлось слегка чинить). Только корпус у нее из жесткого пластика, к которому изнутри приклеены в разных местах датчики прикосновения. По их срабатыванию воспроизводятся фразы, песенки и сказки. Мне интересно, как работает такой датчик. Выглядит он просто как кусок фольги, к которому припаян провод. На плате нет никаких аналоговых компонентов, середина залита компаундом, под которым, похоже, одна микросхема. Фоторгафий я, к сожалению, не делал, а игрушку уже отдал.

    Я еще подумал тогда, вот бы корпус сделать мягким, как у вас. Но будут ли тогда работать эти датчики?

    А еще у нее был микрофон и функция «передразнивания». Любая близко сказанная фраза записывается и воспроизводится с искажениями: с ускорением, повышением тона и еще чем-то.


    1. spc
      27.06.2016 21:38

      Я думаю, что с высокой долей вероятности датчики (сенсорные кнопки) продолжат работать и в новом корпусе. Обычно в такого типа сенсорах используется либо наводка, создаваемая телом, либо изменение емкостных характеристик за счет того же самого человеческого тела.


  1. hp6812er
    27.06.2016 22:23

    Я бы купил такого зверька ребёнку с удовольствием.


    1. spc
      27.06.2016 23:58

      К сожалению, этот экземпляр не на продажу, если я правильно понял.


  1. roman12rus
    28.06.2016 14:31

    Спасибо за статью. Тоже люблю детям мастерить игрушки.


  1. Chigin
    29.06.2016 15:29
    +2

    Мне очень понравилась ваша статья, очень в духе diy.
    А фраза про «богатый внутренний мир ежа» подняла настроение на весь день)
    Кстати, очень странная коммутация плеера, через минус, я думаю все глюки у вас из-за этого. И схема в конце статьи портит всю картину.
    Я тут перерисовал вашу схему в более читабельный вид и сделал включение плеера через плюс. Транзистор, любой пнп, хоть кт3107, и нужно будет изменить в скетче команду на включение плеера с high на low.
    drive.google.com/open?id=0B58OLL7O54-Vank1WS1YWlY5bDQ


    1. spc
      29.06.2016 15:34

      Спасибо! Просто шикарно у вас получилось, я сейчас с вашего разрешения заменю ей свое творчество. Что касается «глюков», то если это по поводу невозможности адекватного выключения RC522, то не думаю, что это связано с выбранным способом отключения плеера. Собственно, ридер даже с голым контроллером так же странно себя ведет. Т.е. его светодиод у меня светился даже без подключения питания.

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


    1. spc
      29.06.2016 16:02

      Кстати, я тут по не знанию всю голову поломал. Ведь когда отправляю контроллер спать, то как раз перевожу его пины в LOW, и тогда получится, что одновременно с переходом в сон плеер как раз и включится. А если пин оставить в HIGH, тогда контроллер продолжит кушать энергию. Я чего-то не понимаю?


      1. Chigin
        29.06.2016 16:25

        Я не особо силен в програмировани мк, нельзя отправить микроконтороллер спать с пином в high? Если можно, то все ок, там потребления не будет.
        Транзистор закрыт, плеер выключен.


        1. Chigin
          29.06.2016 16:44
          +1

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


          1. spc
            30.06.2016 16:53

            Понятно, спасибо. Я тоже поискал, но особо толкового найти ничего не успел, кроме того, что у AVR не всегда все очевидно с режимами пинов и сном.


  1. Chigin
    29.06.2016 16:20

    Я не против.
    В статье вы писали про то, что отправляете команды на плеер несколько раз и режим воспроизведения работает не так, как хотелось, ну и просто так правильней.
    Насчет считывателя, он на 3,3вольта и скорее всего ему нужны уровни в 3,3в на входах и выходах, отсюда и запитка с линий данных. Можно попробывать поставить резисторы небольшого номинала или использовать согласователь уровней 5<->3,3.


    1. spc
      30.06.2016 16:56

      Я почему-то подумал, что некоторые странные моменты работы плеера связаны больше с библиотекой, которая им управляет, нежели с питанием. В остальном согласен, плюс отрывать гораздо более правильно.

      Считывателю действительно лучше 3,3В, а текущее подключение номинально считается допустимым, поскольку говорят, что он толерантен к 5В уровням.


  1. Chigin
    30.06.2016 18:37
    +1

    Скачал даташит на MFRC522, чтобы глянуть про толерантность к 5в, ничего не нашел.((
    Зато на стр 33 описываются способы hard/soft power down.
    Первое достигается подачей low на пин NRSTPD, второе записью в регистр.
    Вот это, я думаю, классный и красивый вариант отключения считывателя.


    1. spc
      30.06.2016 23:14

      Согласен, в даташите только про максимальный уровень в районе 4,5В почти по всем пинам. Полагаю, толерантность к 5В по интерфейсу проверили опытным путем.

      За страничку 33 особое спасибо. Я заодно посмотрел на плату ридера, нагуглил примерную схему и выяснил, что в текущем виде тот самый NRSTPD — это шестой пин, который через резистор 10К подтянут к плюсу и заодно напрямую соединен с контактом RST на интерфейсной гребенке.

      Посмотрел мультиметром, что вообще происходит на ридере, подключенном только к источнику тока. При висящих в воздухе входах/выходах потребление на уровне 8 мА, а если RST посадить на минус (т.е. устроить ему hard power down), то потребление падает до 1,8 мА. При этом в обоих случаях горит светодиод на плате.

      Предположил, что он и кушает те самые 1,8 мА, перерезал его дорожку. Теперь светодиод не горит, но потребление все равно высокое — 350 мкА, тогда как в даташите пишется о 5 мкА. В принципе, все это подозрительно похоже на утечку через тот самый резистор 10 кОм, который соединяет NRSTPD с плюсом.

      Т.е. для чистоты эксперимента нужно отрезать от плюса светодиод и резистор, ведущий к NRSTPD, а затем подать питание и отдельным образом подать высокий уровень на NRSTPD. Пока этому препятствуют два обстоятельства: жалко ридер и, что важнее, позднее время.


    1. spc
      30.06.2016 23:21

      Не удержался, порезал еще дорожку. Т.е отрезал от NRSTPD подтягивающий 10 кОм резистор. Потребление теперь 14 мкА. Т.е. почему то все равно не 5 мкА, как в даташите, но зато теперь всего в два раза больше, чем там, а не на два порядка. Полагаю, скоро полезу внутрь ежа.

      Вот только с духом соберусь, и полезу.