Решил я этим летом на свой морской старт «Земля Санникова» поставить корабельный гудок (тифон). Системы, типа - баллон со сжатым воздухом плюс раструб, рассматривать не стал, в силу громоздкости, трудоёмкости и тяжеловесности. Да и скучно, городить такую махину ради одного гудка. MP3 плеер – идеальный вариант для такой цели. В сети достаточно гудков на любой вкус, качай - не хочу. Но, тут уже другая проблема. Избыточность устройства. Плеер может больше, чем просто гудеть. В сообществах Ардуино часто встречаются «умные» часы, умеющие «проговаривать» текущее время. Да я и сам, тоже имею в активе проект «говорящей» ракеты «Искатель». Она умеет приветствовать капитана корабля и сообщать ему высоту последнего полёта словами. «А почему бы, не проигрывать на корабле музыку?» - подумал я, поражённый своей догадливостью. Так родился мини-проект «На теплоходе музыка играет». Что устройство должно было иметь и уметь по итогу? Набор сигналов (сейчас их 4 шт.) и возможность проигрывания конкретного сигнала по команде; Набор музыкальных треков (сейчас их 30шт) и проигрывание их в случайном порядке; Возможность включения конкретного трека (можно одного); Регулировка громкости (при настройке в домашних условиях, лучше делать потише); Если, во время проигрывания музыки, включался гудок, то по окончании его звучания, музыка должна была продолжать играть; Устройство должно быть достаточно громким. Светлые головы, которые изобрели плеер и написали библиотеки, во многом позаботились о нас. Первые пять пунктов, по отдельности, легко реализовывались через управление устройством, посредством библиотеки DFRobotDFPlayerMini. Зная номер файла варианта гудка или музыки, можно было запустить его командой с Ардуино. Встроенный рандом позволял проигрывать музыку в случайном порядке, но всё в месте, это не работало. Точнее работало, но не так как хотелось. Согласно DFRobotDFPlayerMini, у плеера есть две особые папки, которые имеют свои свойства, это – ADVERT и MP3, остальные именуются числами (01, 02, …). Есть специальная команда, которая позволяет при проигрывании музыки делать врезки из папки ADVERT. То есть достаточно вложить файлы гудков в ADVERT, и проблема решена? Музыка играет, прогудел, играет дальше? Да, музыка играет, но рандом дергает треки из всех папок подряд, в том числе и из ADVERT. Избежать этого методами библиотеки не получилось, изучение мануала плеера, тоже идей не подкинуло. Ну, с другой стороны, кодер я никакой, может просто не тот уровень, чтобы разобраться на уровне системных команд плеера. В результате сделал два метода, которые позволяют врезать сигналы гудков в музыку. При этом, музыка случайно проигрывается только из конкретной папки. В этих методах есть свой недостаток, который, возможно, и является причиной того, что похожих методов нет в библиотеке DFRobotDFPlayerMini. Для их работы нужно заранее знать время проигрывания трека. Для этого я использую два массива, в которые вручную забиты значения в миллисекундах для гудков Guti[] и музыкальных файлов trti[]. Еще один момент, первое значение массива trti[] должно быть небольшим (1000). Это, как-бы пустой трек - "тишина". На тот случай если, у кого то, возникнет похожая история, прилагаю код. Контроллер в схеме специализирован под плеер и работает, как ведомое устройство в сцепке I2C. Ведущая Ардуино получает команды по RC и передает их контроллеру плеера. В коде есть метод key_power(), он к функциональности плеера отношения не имеет. Используется для управления других устройств. Схема Внешний вид Оформление колонок Пример файлов на карте
Код (C++): /* ----------------------------------------- */ /* ---------------- гудок ------------------ */ /* -------- автор: Владимир Коранов -------- */ /* ----------------------------------------- */ /* ------------- pleer-Gudok --------------- */ /* ------- author: Vladimir Koranov -------- */ /* ----------------------------------------- */ /* scetch name: pleer-Gudok */ /* ----------------------------------------- */ #include "SoftwareSerial.h" #include "DFRobotDFPlayerMini.h" #include <Wire.h> // Дебаггер- раздефайнить или задефайнить первую строку для использования //#define DEBUG_ENABLE #ifdef DEBUG_ENABLE #define DEBUG(x) Serial.println(x) #else #define DEBUG(x) #endif /*Примеры ситаксиса сообщения дебаггера //DEBUG((String)"val_move = " + val_move + " val_turn = " + val_turn + " val_kran_rot = " + val_kran_rot); //DEBUG((String)"val_L = " + val_L); */ /* константы */ #define out1 7 // выход3 #define out2 8 // выход4 #define d_Rx 10 // плеер-RX #define d_Tx 11 // плеер-TX #define sizeTrTi 31 // размер массива с таймингом треков #define sizeGuTi 5 // размер массива с таймингом гудков unsigned long timeComanda; // время шага для таймаута по командам int volumeSp; // текущий уровень громкости музыки, ака плеера int volumeSp_is; // для контроля изменения громкости в методах int volGudok; // уровень громкости гудков boolean set_on; // признак того, что поступила команда boolean igrai; // флаг того, что играет музыка int comanda_id; // команда пришедшая по I2C int comanda_is; // для контроля команд I2C int numTrack; // номер звучащего трека unsigned long timeTrack_is; // глобальное время окончания звучания текущего трека long valGSCh; // значение ГСЧ // тайминг музыки unsigned long trti [sizeTrTi] { 1000, 198000, 111000, 167000, 260000, 169000, 108000, 156000, 148000, 162000, 169000, 155000, 118000, 104000, 157000, 169000, 164000, 85000, 216000, 107000, 102000, 20000, 265000, 195000, 82000, 109000, 139000, 265000, 559000, 242000, 204000 }; // тайминг гудков unsigned long Guti [sizeGuTi] { 1000, 7500, 2500, 7500, 6500 }; // реальное время + 500 /* объекты */ SoftwareSerial mySoftwareSerial(d_Rx, d_Tx); DFRobotDFPlayerMini myDFPlayer; /* --------------------------------------------------------------------------------*/ void setup() { Wire.begin(44); // подключаемся к шине I2C как ведомый Wire.onReceive(PrishlaComanda); /* выходы на электронные ключи доп.эффектов (не для плеера) */ pinMode (out1, OUTPUT); // выход1 (на электронный ключ) pinMode (out2, OUTPUT); // выход2 (-//-) digitalWrite (out1, LOW); digitalWrite (out2, LOW); /*Остальное*/ set_on = LOW; igrai = LOW; timeComanda = 0; timeTrack_is = 0; volumeSp = 2; volGudok = 2; //payer mySoftwareSerial.begin(9600); #ifdef DEBUG_ENABLE Serial.begin (9600); #endif if (!myDFPlayer.begin(mySoftwareSerial)) { //Use softwareSerial to communicate with mp3. Serial.println(F("Unable to begin:")); Serial.println(F("1.Please recheck the connection!")); Serial.println(F("2.Please insert the SD card!")); while (true) { delay(0); // Code to compatible with ESP8266 watch dog. } } } /* --------------------------------------------------------------------------------*/ void loop() { /* if ( volumeSp != analogRead(a_gromk) / 33 ) { // --- громкость в ручном режиме (для отладки) --- /// volumeSp = analogRead(a_gromk) / 33; /DEBUG((String)"volumeSp = " + volumeSp); }*/ if (comanda_is != comanda_id) { // --- прием команды по IIS --- /// comanda_is = comanda_id; comanda_id = 0; set_on = HIGH; } switch ( comanda_is ) { // --- функции f3 --- /// //case 10: break; // резерв (можно задействовать подо что-то) case 11: key_power( out1 ); break; // эл.ключ 1 case 12: key_power( out2 ); break; // эл.ключ 2 } switch ( comanda_is ) { // --- установка уровня громкости --- /// case 7: volumeSp++; volumeSp = constrain(volumeSp, 1, 30); volGudok = volumeSp; break; // тише case 8: volumeSp--; volumeSp = constrain(volumeSp, 1, 30); volGudok = volumeSp; break; // громч } //if ( comanda_is != 0 ) DEBUG((String)"comanda_is = " + comanda_is); if ( set_on == HIGH ) { set_on = LOW; // --- исполнение команд для плеера --- /// switch ( comanda_is ) { case 1: gogo_gudok( 2, igrai ); break; // ревун case 2: gogo_gudok( 1, igrai ); break; // гудок case 3: gogo_gudok( 3, igrai ); break; // склянки case 4: timeTrack_is = 0; numTrack = 0; gogo_track(&numTrack, 1, 0); igrai = HIGH; break; // музыка case 5: myDFPlayer.reset(); timeTrack_is = 0; igrai = LOW; break; // музыка выкл case 6: myDFPlayer.volume(volumeSp); myDFPlayer.playLargeFolder(3, 3); break; // воспроизвести трек № XX } } if (igrai == HIGH) { gogo_track(&numTrack, 1, 0); } } /* ------------------------------------------------------------------------- */ /* ------------------------- Подпрограммы и методы ------------------------- */ /* ------------------------------------------------------------------------- */ /* --- чтение команды IIC --------------------------------------*/ void PrishlaComanda() { comanda_id = Wire.read(); } /* --- оператор гудков -----------------------------------------*/ void gogo_gudok(int track, int igra ) { unsigned long timeReclam_on; myDFPlayer.volume(volGudok); if ( igra == HIGH ) { //врезка сигнала в музыкальый трек myDFPlayer.advertise(track); timeTrack_is = timeTrack_is + Guti[track]; //плюсуем к глобальному времени игры трека время сигнала delay( Guti[track] - 200 ); myDFPlayer.volume(volumeSp); //без задержки работает криво return; } DEBUG((String)"volumeSp = " + volumeSp + "; track = " + track); myDFPlayer.volume(volumeSp); myDFPlayer.playLargeFolder(1, track); //сигнал без проигрывания музыки } /* --- оператор треков -----------------------------------------*/ // по номеру трека берет его время проигрывания, и по прошествии времени, начинает новую композицию // использует третью папку на карте void gogo_track(int* track, int mode, int new_track) { DEBUG((String)"GOGO"); // --- проверка тайминга текущего трека --- // if ( millis() < timeTrack_is + trti[ *track ] ) { if ( volumeSp_is != volumeSp ) { // если время не вышло, то корректируем только громкость, если она изменилась volumeSp_is = volumeSp; myDFPlayer.volume(volumeSp); return; } return; } DEBUG((String)"GOGO2"); // --- Eсли время текущего трека закончилось, то: --- // switch (mode) { // --- запускается следующий трек --- // case 0: if ( *track < 1 || *track >= sizeTrTi ) *track = 1; else *track = *track + 1; break; // --- запускается случайный трек --- // case 1: randomSeed(micros()); valGSCh = random(1, sizeTrTi); while (valGSCh == *track) { // запрет на повторное воспроизведение предыдущего трека valGSCh = random(1, sizeTrTi); } DEBUG((String)"New_misic = On; valGSCh = " + valGSCh + "valGSCh = " + valGSCh); *track = valGSCh; break; // --- запускается указанный трек --- // case 2: *track = new_track; break; } timeTrack_is = millis(); DEBUG((String)"New_misic = " + numTrack + ";"); myDFPlayer.volume(volumeSp); myDFPlayer.playLargeFolder(3, numTrack); } /* --- управление электронным ключом ---------------------------*/ void key_power(int key_num ) { digitalWrite (key_num, HIGH); delay(2000); digitalWrite (key_num, LOW); }
Перекинул управление подъемником на контроллер бортового плеера, так как на основном контроллере кончились ноги.