В процессе работы некоторые платы были переделаны в свете изменившихся нюансов работы мишени. Ну и никуда без спиртования плат для очистки от флюса:
По окончании сборки было сделано основание для макетной платы, дабы спрятать мешанину проводов.
Была собрана стойка для галетного выключателя и всё было прикинуто, в плане компоновки, на листе пластика 21х13 см. Итог на фото ниже.
Ну а теперь программная часть мишени.
Программный код
Код будет приведён не весь, дабы дать реализоваться творческому потенциалу тех, кто решит сделать нечто подобное.
При запуске мишень озвучивает то, что она была включена и в дополнение пару раз моргает индикатором попадания.
Вот тело программы, в котором прописываются все глобальные переменные, указывается назначение портов ввода\вывода и вызываются нужные процедуры.
Код:
/* Скетч для активной мишени "Орудийный дот"
* ---------------------------------------
* Используемые пины:
* Пины с ШИМ у arduino nano: 3, 5, 6, 9, 10, 11
* 2 - Имитация вспышки
* 3 - ИК-диод
* 4 - ИК-приёмник
* 5 - сервопривод отката
* 6 - серво блокировки ИК-диода
* 7 - Serial TX для MP3 плеера
* 8 - Serial RX для MP3 плеера
* 9 -
* 10 -
* 11 -
* 12 - Проверка состояния плеера LOW когда играет и HIGH
* A0 - пин настройки режима работы
* A1 - световой индикатор попадания
* A2 - тригер УЗ-дальномера
* A3 - Эхо УЗ-дальномера
* A4 - Кнопка ручного выстрела
* A5 -
* A6 -
* A7 -
*/
#define Led_Vistrela 2 // световая имитация вспышки - пин D2
#define IRpin 3 // ИК-диод - пин D3
#define txPin 7 // tx для плеера - пин D7
#define rxPin 8 // rx для плеера - пин D8
#define MP3_svoboden 12 //пин проверки занятости плеера проигрыванием - пин D12
#define Rej_raboti A0 // пин выбора режима работы
#define G_LedPin A1 // световой индикатор попадания - пин A1
#define trigUZI A2 // тригер для УЗ-дальномера - пин A2
#define ehoUZI A3 // эхо для УЗ- дальномера - пин A3
#define Butt_Fire A4 // пин кнопки ручного огня испоьзовать INPUT_PULLUP, который ПРИ НАЖАТОЙ КНОПКЕ ВЫДАЁТ 0, а при ненажатой 1
#include <avr/pgmspace.h>
#include <IRLibDecodeBase.h>
#include <IRLibSendBase.h>
#include <IRLib_P01_NEC.h>
#include <IRLib_P02_Sony.h>
#include <IRLib_HashRaw.h>
#include <IRLibCombo.h>
#include <IRLibRecv.h>
#include <Servo.h>
#include <Ultrasonic.h>
#include <SoftwareSerial.h>
#include <DFPlayer_Mini_Mp3.h>
// -------------- Прописывание сервоприводов ---------------
Servo servoOtkat; // сервопривод отката
Servo servoBlok; // сервопривод резервный
// ---------------------------------------------------------
// -------------- Прописывание программного серийного порта -----------------
SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin); //закрепление за программным серийным портом mySerial пинов: 7 - tx, 8 - rx
// --------------------------------------------------------------------------
// -------------- Прописывание ИК-приймника и ИК-переменных (отправки и декодирования) ---------------
IRsendRaw mySender; //объявляем переменную mySender типа IRsendRaw (3 PIN)
IRdecode myDecoder; //объявляем переменную myDecoder типа IRdecode
IRrecv myReceiver(4); //Присваиваем ИК-приёмнику пин D4
// ---------------------------------------------------------------------------------------------------
// ---------- Иницилизация Ик боя ---------------
// эта часть кода скрыта \\
// ------------------------------------------------
// ------------------ Переменные для разрешения стрельбы и переменные для паузы --------------------
unsigned long LastShotTime; // время последнего выстрела от начала работы программы
byte vremyaPausi = 5; // переменная, содержащая длительность паузы В СЕКУНДАХ
unsigned long StartPause; // переменная с временем начала паузы
bool EndPause = true; //флаг окончания паузы
bool needRepair = false;
// ---------------------------------------------------------------------------------------------
// ---------- настройка подключения УЗ-дальномера --------------
Ultrasonic ultrasonic(trigUZI,ehoUZI, 17400); // назначаем триггер - А2 пин, эхо - А3 пин. ограничение расстояния в 300 см посредством частоты 17400 = 300 см * 58
// -------------------------------------------------------------
// ----------- переменные настройки работы мишени --------------
int DistanciyaDefault = 0; // переменная, содержащая зарегистрированную дистанцию до препятствия при калибровке УЗ-дальномера в сантиметрах
byte RejimRaboti = 15; // переменная, содержащая номер режима работы мишени: 1 - режим ожидания (не стреляем и не принимаем сигнал), 2 - пассивный режим (не стреляем, но принимаеи ИК-сигнал), 3 - работа в автоматическом режиме по дальномеру, 4-6 - работа в автоматическом режиме по таймеру, 7 - режим автономной мишени на 9 жизней, 8 - работа в ручном режиме. Значение 15 (выходящее за пределы 1-8) присвоена для последующего определения режима работы
bool NaydenTank = false; //флаг обнаружения танка: false - танк не обнаружен, true - танк обнаружен
byte countHit = 0; //число попаданий в мишень (максимум 9) для режима автономной мишени на 9 жизней
// -------------------------------------------------------------
void setup() {
LastShotTime = millis(); // присваиваем времени последнего выстрела значение текущего времени (прошедшего с момента запуска программы)
pinMode (Led_Vistrela, OUTPUT); // Пин имитации вспышки при выстреле D2 в режим входа
pinMode (IRpin, OUTPUT); // Пин ИК-диода D3 в режиме выхода
pinMode (4, INPUT); //Пин Ик-приёмника D4 в режим входа
digitalWrite (4, LOW); // гасим напряжение (если оно есть) на ИК-приёмнике, чтобы не мешал получению ИК-сигнала (D4)
servoOtkat.attach(5); // привязываем сервопривод отката к аналоговому выходу 5
// servoBlok.attach(6); // привязываем сервопривод механической блокировки ИК-диода к аналоговому выходу 6
pinMode (txPin, OUTPUT); // Пин передачи данных плееру D7 в режим выхода
pinMode (rxPin, INPUT); // Пин получения данных от плеера D8 в режим входа
pinMode (MP3_svoboden, INPUT); //пин проверки занятости плеера проигрыванием D12
digitalWrite (MP3_svoboden, HIGH); // поднимаем флаг, что плеер изначально свободен (D12)
pinMode (Rej_raboti, INPUT); // Пин выбора режима работы A0 в режим входа
pinMode (G_LedPin, OUTPUT); // Пин светового индикатора попадания A1 в режим выхода
pinMode (trigUZI, OUTPUT); // Пин траггера для УЗ- дальномера A2 в режим выхода
pinMode (ehoUZI, INPUT); // Пин эхо для УЗ- дальномера A3 а режим вход
pinMode (Butt_Fire, INPUT_PULLUP); //Пин кнопки ручного выстрела A4 в режим входа, который ПРИ НАЖАТОЙ КНОПКЕ ВЫДАЁТ 0, а при ненажатой 1
digitalWrite (Butt_Fire, HIGH); // указываем, что кнопка ручного выстрела изначально не нажата (A4)
Serial.begin(115200);
delay(2000); while (!Serial); //delay for Leonardo
mySerial.begin(9600); // выставление скорости программного серийного порта
mp3_set_serial(mySerial); //установка softwareSerial для модуля DFPlayer-mini mp3
mp3_set_volume(15); // уровень громкости из диапазоно 0~30
ZAPUSK (); //функция индикации о включении мишени
servoOtkat.write(90); // ставим качалку сервы отката в положение 90 градусов
myReceiver.enableIRIn(); // Start the receiver
Serial.println(F("Ready to receive IR signals"));
randomSeed(millis());
}
void loop() {
if (RejimRaboti > 1)
{
HITS_CT(); //функцмя приёма ИК-сигнала и декодирования его
};
REJIMI_RABOTI ();
if ((EndPause == true) && (needRepair == true))
{needRepair = false;
PLAY_SOUND (17); //проигрываем звук команды для наводки
delay (40);
while (digitalRead (MP3_svoboden) == LOW) //ждём, пока проигрывание закончится
{
delay (100); //даём ардуине чуток "отдышаться"
}
}
if (EndPause == false )
{
PAUSA (vremyaPausi); //функция паузы, в точение которой мишень не стреляет (перезарядка, или пауза после попаданиея)
}
}
Код процедуры выстрела
Код:
void FIRE ()
{
// ----- объявление локальных переменных, которые используются только в этой функции -------
byte UgolServiOtkata = 90; //переменная положения качалки сервы отката (в градусах)
// -----------------------------------------------------------------------------------------
//myReceiver.disableIRIn(); // запрещаем принимаем следующую команду
PLAY_SOUND(15); //проигрываем звук выстрела
digitalWrite (Led_Vistrela, HIGH); //включение вспышки выстрела
delay (10);
// ------------------- отката ствола ------------------
servoOtkat.write(180);
while (servoOtkat.read() != 180) //ставим ствол в положение максимального отката
{
};
UgolServiOtkata = 180;
delay (270);
// ------------------- возврат ствола обратно после отката -----------------------
while (servoOtkat.read() != 90) //медленно возвращаем ствол назад
{
UgolServiOtkata=UgolServiOtkata-1 ;
if ((UgolServiOtkata >= 2) && (digitalRead(Led_Vistrela) == HIGH))
{
digitalWrite (Led_Vistrela, LOW); //выключение вспышки выстрела
};
servoOtkat.write(UgolServiOtkata);
delay (8); //задержка при возврате после отката
};
// ----------------------- отправка ИК-сигнала ------------------------
// эта часть кода скрыта \\
// ---------------------------------------------------------------------
LastShotTime = millis (); // призваиваем времени последнего выстрела текущее время
}
Код процедуры приёма ИК-сигнала
Код:
void HITS_CT()
{
if (needRepair) //проверка на необходимость восстановления мишени после попадания в неё
{
myReceiver.disableIRIn(); // запрещаем принимаем следующую команду
} else myReceiver.enableIRIn();// принимаем следующую команду
if (!needRepair)
{
if (myReceiver.getResults() && EndPause && (countHit < 9)) //если что-то получили на ИК-приёмник и пауза закончилась и число попаданий меньше 9, то:
{
if (myDecoder.decode()) //если декодировали то, что получили, то:
{
// эта часть кода скрыта \\
for (int i = 0; i <= len; i++) //цикл для сравнения кода ИК-боя с полученным декодированным значением
{
if // эта часть кода скрыта \\
{//если декодированное значение = одному из кодов ИК-боя и флаг обнаружения совпадения = 0, то выводится индикация попадания.
flag = 1; // поднимаем флаг опознания кола ИК-боя
Err = 0; // сбрасываем флаг ошибки совпадения кода с базой ИК-боёв
PLAY_SOUND(18);
delay (400);
HIT (); // выводим индикацию попадания
StartPause = millis(); // запись времени начала паузы
EndPause = false;
if (RejimRaboti == 7) // для режима автономной мишени с 9-ю жизнями
{
countHit++; //увеличиваем число попаданий на 1
vremyaPausi = 10; // пауза после попадания в мишень, в течение которой мишень полностью не активна
LastShotTime = StartPause + 10000;
} else
{
vremyaPausi = 20; // пауза после попадания в мишень, в течение которой мишень полностью не активна
LastShotTime = StartPause + 20000;
}
PAUSA(vremyaPausi);
needRepair = true;
}
}
//-----------------------------
if (Err == 1) // если поднят флаг ошибки совпадения кода с базой ИК-боёв, то:
{
Serial.println( "...not found..." ); // выводим в com-порт сообщение об отсутствии совпадения с ИК-боем из базы
}
}
//-----------------------------
myReceiver.enableIRIn();// принимаем следующую команду
};
flag = 0;
}
}
Код опроса на предмет выбранного режима работы
Код:
void OPROS_NASTROEK ()
{
/* Если - То
* map(analogRead(Rej_raboti), 0, 1023, 1, 9) = 1 - Режим ожидания
* map(analogRead(Rej_raboti), 0, 1023, 1, 9) = 2 - Пассивный режим работы
* map(analogRead(Rej_raboti), 0, 1023, 1, 9) = 3 - Режим стрельбы по дальномеру
* map(analogRead(Rej_raboti), 0, 1023, 1, 9) = 4 - Режим автоматической стрельбы с паузой 5 секунд
* map(analogRead(Rej_raboti), 0, 1023, 1, 9) = 5 - Режим автоматической стрельбы с паузой 10 секунд
* map(analogRead(Rej_raboti), 0, 1023, 1, 9) = 6 - Режим автоматической стрельбы с паузой 15 секунд
* map(analogRead(Rej_raboti), 0, 1023, 1, 9) = 7 - Режим автономной мишени с 9-ю жизнями
* map(analogRead(Rej_raboti), 0, 1023, 1, 9) = 8 - Режим ручной стрельбы
*/
byte CurrRejimRaboti = map(analogRead(Rej_raboti), 0, 1023, 1, 9); //считываем режим работы на текущий момент
if (RejimRaboti != CurrRejimRaboti) //если изменился режим работы то проигрываем звуковое предупреждение о выбранном режиме и запоминаем этот режим
{
if (CurrRejimRaboti == 1)
{
PLAY_SOUND (3); //проигрываем озвучивание режима
}
if (CurrRejimRaboti == 2)
{
PLAY_SOUND (4); //проигрываем озвучивание режима
}
if (CurrRejimRaboti == 3)
{
// ----- рассчитываем среднее расстояние до эталонного препятствия исходя из среднего по пяти проведённым измерениям -----
// эта часть кода скрыта \\
// -----------------------------------------
PLAY_SOUND (5); //проигрываем озвучивание режима
}
if (CurrRejimRaboti == 4)
{
PLAY_SOUND (6); //проигрываем озвучивание режима
}
if (CurrRejimRaboti == 5)
{
PLAY_SOUND (7); //проигрываем озвучивание режима
}
if (CurrRejimRaboti == 6)
{
PLAY_SOUND (8); //проигрываем озвучивание режима
}
if (CurrRejimRaboti == 7)
{
PLAY_SOUND (9); //проигрываем озвучивание режима
}
if (CurrRejimRaboti == 8)
{
PLAY_SOUND (10); //проигрываем озвучивание режима
}
RejimRaboti = CurrRejimRaboti;
delay(40);
while (digitalRead (MP3_svoboden) == LOW) //ждём, пока проигрывание закончится
{
delay (100); //даём ардуине чуток "отдышаться"
}
LastShotTime = millis();
EndPause = true;
needRepair = false;
vremyaPausi = 5;
countHit = 0; //обнуляем число попаданий в мишень для режима автономной мишени с 9-ю жизнями при смене режима
if (RejimRaboti < 2) //проверка на необходимость восстановления мишени после попадания в неё для режимов 2-8
{
myReceiver.disableIRIn(); // запрещаем принимаем следующую команду
} else myReceiver.enableIRIn();// принимаем следующую команду
}
}
Сам код с реализацией алгоритма работы, в зависимости от выбранного режима, я озвучивать не буду. Думаю, что вы вполне сможете написать его сами и именно под своё виденье того, как должна работать ваша мишень.
Скажу сразу - т.к. работа над мишенью ещё не завершена, то программный код постоянно меняется, что-то в него добавляется, что-то удаляется. Именно поэтому есть расхождения между указанными выше характеристиками и текущим результатом. А он таков:
Режимы работы мишени:
1) Режим ожидания, в течение которого мишень проигрывает несколько старых военных песен в случайном порядке.
2) Пассивный режим работы, в котором мишень только принимает выстрела по протоколам Хенг Лонг, Тамия, Тайген, Де Агостини.
3) Режим стрельбы по обнаружению танка ультразвуковым дальномером (режим пока тестовый)
4) Режим активной мишени с автоматической стрельбой с интервалом 5, 10, 15 секунд на выбор.
5) Режим автономной мишени на 9-ть жизней с автоматической стрельбой через каждые 5 секунд
6) Ручной режим стрельбы.
Мишень озвучивает:
1) Рандомно выбранные старые военные песни в режиме ожидания
2) Выбранный режим работы
3) Выстрел
4) Попадание в мишень
5) Восстановление работоспособности мишени после попадания.
6) Также в режиме стрельбы по дальномеру озвучивается обнаружение и потеря танка из видимости.