Описание:
В данном проекте мы создадим аналог автодрома, на котором сдают первую часть экзамена для получения водительского удостоверения. В данном случае будем считать, что вы получите Arduino-права категории UNO! :)
Видео:
Логика:
Данное устройство создано для того, чтобы развлечь вас и ваших друзей, а наличие термо-принтера и дополнительного дисплея для отображения лучшего времени позволит устроить настоящий чемпионат!
Логика работы следующая:
- Во время ожидания начала игры стенд находится в демо-режиме. В этот момент горят все стрелки и надписи автодрома;
- Как только Малыш проезжает датчик линии, который находится на линии СТАРТа, начинается игра. Об этом подскажут загоревшиеся стрелки направления движения, а так же начнется отсчёт времени на 4-разрядном индикаторе, ведь время экзамена должно фиксироваться;
- Датчики линии фиксируют положение Малыша на поле, тем самым меняя режим игры, выключая старые (испытание пройдено) и включая новые стрелки и надписи;
- После того, как все датчики будут пройдены, необходимо пересечь линию ФИНИШа, после чего игра будет закончена, а отсчёт времени остановлен. Термо-принтер начнёт печатать чек с результатами вашего заезда;
- Если ваше время превзошло лучшее время, которое было установлено до этого, то оно будет установлено на втором дисплее до тех пор, пока его кто-то не побьёт или на веки вечные!
Нам понадобится:
- 1x Piranha UNO;
- 1х Trema Shield;
- 1х Trema-модуль Кнопка;
- 5х I2C-FLASH Модуль силовых ключей (4N) с измерением тока;
- 2х 4-сегментный индикатор;
- 1х Trema MP3-плеер;
- 1х Карта памяти MicroSD;
- 1х Усилитель;
- 1х Динамик 3W;
- 1х Аудио штекер 3,5 мм с клеммником;
- 1х I2C-FLASH Trema Expander;
- 1х Термо-принтер;
- 11х Аналоговый датчик линии;
- 1х Коннектор питания типа "МАМА" с клеммником;
- 1х Светодиодная лента;
- 2х Провод красный 20AWG ;
- 2х Провод чёрный 20AWG;
- 1х Провод красный 16AWG;
- 1х Провод чёрный 16AWG;
- 12х 3-й шлейф;
- 8х 4-й шлейф;
- 4х ПВХ-пластина большая;
- 2х ПВХ-пластина средняя;
- 1х ПВХ-пластина с креплениями;
- 1х Источник питания (12В);
- 3х Понижающий DC-DC преобразователь;
- 1х Мультиметр;
- 2х Фанера 10мм х 750мм х 750мм;
- 2х Рейка деревянная 12мм х 2000мм;
- 1х Коробка саморезов 3мм х 10мм;
- 1х Коробка саморезов 3мм х 30мм;
- 1х Эпоксидная смола (0.5л);
Дополнительно может понадобиться:
- 1х Робот "Малыш";
Для реализации проекта необходимо установить следующие библиотеки:
- Библиотека iarduino_4LED;
- Библиотека iarduino_I2C_Relay;
- Библиотека iarduino_I2C_Expander;
- Библиотека DFRobotDFPlayerMini;
- Библиотека Adafruit_Thermal;
- Библиотека EEPROM (входит в пакет Arduino IDE);
- Библиотека SoftwareSerial (входит в пакет Arduino IDE);
- Библиотека Wire (входит в пакет Arduino IDE);
О том, как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.
Подключение:
Для удобства подключения всех датчиков и модулей к Piranha UNO мы воспользуемся Trema Shield.
Сперва подключим питание и понижающий DC-DC преобразователь к Piranha UNO:
С помощью мультиметра, щупы которого должны быть приложены к выходным контактам преобразователя, настройте величину выходного напряжения равным 7В.
Изображение кликабельно!
Внешнее питание 12В | DC-DC преобразователь (вход) |
---|---|
+ | IN+ |
- | IN- |
DC-DC преобразователь (выход) | Питание Piranha UNO 7В |
---|---|
OUT+ | + |
OUT- | - |
Подключим Trema Shield к Piranha UNO:
Подключим I2C-FLASH Модуль силовых ключей (4N-канала) с измерением тока к Trema Shield:
По умолчанию все модули FLASH-I2C имеют установленный адрес 0х09.
— Перед подключением 1 модуля к шине I2C настоятельно рекомендуется изменить адрес модуля.
— При подключении 2 и более FLASH-I2C модулей к шине необходимо в обязательном порядке предварительно изменить адрес каждого модуля, после чего уже подключать их к шине.
Более подробно о том, как это сделать, а так же о многом другом, что касается работы FLASH-I2C модулей, вы можете прочесть в этой статье.
I2C Силовой ключ (4N) с измерением тока | Trema Shield |
---|---|
SCL | SCL |
SDA | SDA |
Vcc | Vcc |
GND | GND |
Подключим к I2C-FLASH Силовому ключу (4N) остальные I2C-FLASH Силовые ключи (4N):
По умолчанию все модули FLASH-I2C имеют установленный адрес 0х09.
— Перед подключением 1 модуля к шине I2C настоятельно рекомендуется изменить адрес модуля.
— При подключении 2 и более FLASH-I2C модулей к шине необходимо в обязательном порядке предварительно изменить адрес каждого модуля, после чего уже подключать их к шине.
Более подробно о том, как это сделать, а так же о многом другом, что касается работы FLASH-I2C модулей, вы можете прочесть в этой статье.
I2C Силовой ключ (4N) с измерением тока | I2C Силовой ключ (4N) с измерением тока |
---|---|
SCL | SCL |
SDA | SDA |
Vcc | Vcc |
GND | Gnd |
Подключим светодиодную ленту и питание 12В к I2C-FLASH Силовым ключам (4N):
Светодиодная лента | Колодка силового ключа (4N) |
---|---|
Красный провод | + |
Чёрный провод | - |
Подключим Trema Кнопку к Trema Shield:
Trema Кнопка | Trema Shield |
---|---|
G | GND |
V | Vcc |
S | D5 |
Подключим Trema MP3-плеер к Trema Shield:
Trema MP3-плеер | Trema Shield |
---|---|
TX | D8 |
G | GND |
V | Vcc |
RX | D9 |
Подключим Динамик и Усилитель к Trema MP3-плееру:
Изображение кликабельно!
Trema MP3-плеер | Усилитель | Динамик | Питание |
---|---|---|---|
L | L | ----- | ----- |
R | R | ----- | ----- |
G | G | ----- | ----- |
----- | ROUT- | Красный провод | ----- |
----- | ROUT+ | Чёрный провод | ----- |
----- | POWER- | ----- | - |
----- | POWER+ | ----- | + |
Подключим Питание 12В и понижающий DC-DC преобразователь к Усилителю:
Изображение кликабельно!
Внешнее питание 12В | DC-DC преобразователь (вход) |
---|---|
+ | IN+ |
- | IN- |
DC-DC преобразователь (выход) | Питание Усилителя 5В |
---|---|
OUT+ | + |
OUT- | - |
Подключим Термо-принтер и Питание к Trema Shield:
Из-за того, что термо-принтер подключен к аппаратному UART платы Piranha UNO (выводы D0 и D1), то в будущем вам придётся сначала загрузить скетч в плату, а уже потом подключить к ней термо-принтер. В противном случае в процессе загрузки скетча будет возникать ошибка!
Изображение кликабельно!
Trema Shield | Термо-принтер | Питание 9В |
---|---|---|
D0 (RX) | Зелёный провод (TX) | ----- |
D1 (TX) | Синий провод (RX) | ----- |
GND | Чёрный провод | ----- |
GND | ----- | - |
----- | Красный провод | + |
Подключим Питание 12В и понижающий DC-DC преобразователь к Термо-принтеру:
Изображение кликабельно!
Внешнее питание 12В | DC-DC преобразователь (вход) |
---|---|
+ | IN+ |
- | IN- |
DC-DC преобразователь (выход) | Питание Термо-принтера 9В |
---|---|
OUT+ | + |
OUT- | - |
Подключим 4-сегментный индикатор (1)
к Trema Shield:
4-сегментный индикатор | Trema Shield |
---|---|
G | GND |
V | VCC |
D | D12 |
C | D13 |
Подключим 4-сегментный индикатор (2) к Trema Shield:
4-сегментный индикатор | Trema Shield |
---|---|
G | GND |
V | VCC |
D | D6 |
C | D7 |
Подключим Датчики линии к Trema Shield:
Аналоговый датчик линии (A0) | Trema Shield |
---|---|
G | GND |
V | VCC |
S | A0 |
Аналоговый датчик линии (A1) | Trema Shield |
---|---|
G | GND |
V | VCC |
S | A1 |
Аналоговый датчик линии (A2) | Trema Shield |
---|---|
G | GND |
V | VCC |
S | A2 |
Подключим I2C-FLASH Trema Expander к I2C-FLASH Силовому ключу (4N):
По умолчанию все модули FLASH-I2C имеют установленный адрес 0х09.
— Перед подключением 1 модуля к шине I2C настоятельно рекомендуется изменить адрес модуля.
— При подключении 2 и более FLASH-I2C модулей к шине необходимо в обязательном порядке предварительно изменить адрес каждого модуля, после чего уже подключать их к шине.
Более подробно о том, как это сделать, а так же о многом другом, что касается работы FLASH-I2C модулей, вы можете прочесть в этой статье.
I2C-FLASH Силовой Ключ (4N) | I2C-FLSH Trema Expander |
---|---|
SCL | SCL |
SDA | SDA |
Vcc | Vcc |
GND | GND |
Подключим Датчики линии к I2C-FLASH Trema Expander:
Аналоговый датчик линии (A0) | I2C-FLASH Trema Expander |
---|---|
G | GND |
V | VCC |
S | A0 |
Аналоговый датчик линии (A1) | I2C-FLASH Trema Expander |
---|---|
G | GND |
V | VCC |
S | A1 |
Аналоговый датчик линии (A2) | I2C-FLASH Trema Expander |
---|---|
G | GND |
V | VCC |
S | A2 |
Аналоговый датчик линии (A3) | I2C-FLASH Trema Expander |
---|---|
G | GND |
V | VCC |
S | A3 |
Аналоговый датчик линии (A4) | I2C-FLASH Trema Expander |
---|---|
G | GND |
V | VCC |
S | A4 |
Аналоговый датчик линии (A5) | I2C-FLASH Trema Expander |
---|---|
G | GND |
V | VCC |
S | A5 |
Аналоговый датчик линии (A6) | I2C-FLASH Trema Expander |
---|---|
G | GND |
V | VCC |
S | A6 |
Аналоговый датчик линии (A7) | I2C-FLASH Trema Expander |
---|---|
G | GND |
V | VCC |
S | A7 |
Скетч проекта:
//----------------------------------------------------------------------// // ПОЗИЦИИ ИГРОКА НА ПОЛЕ: //----------------------------------------------------------------------// #define READY_TO_START 0 // Позиция готовности трассы к старту. #define START_RACE 1 // Позиция после пересечения датчика СТАРТ. #define ESTAKADA_WAITING 2 // Позиция нахождения на эстакаде. #define GO_FROM_ESTAKADA 3 // Позиция после эстакады. #define STOP_BEFORE_PARKING 4 // Позиция перед началом парковки задним ходом #define GO_TO_PARKING 5 // Позиция движения на парковку. #define STOP_BEFORE_GO_TO_ANGLE 6 // Позиция перед движением с парков к повороту на 90 градусов #define GO_TO_ANGLE 7 // Позиция движения к повороту на 90 градусов #define GO_TO_SNAKE_FIRST 8 // Позиция движения к первому датчику змейки #define GO_TO_SNAKE_SECOND 9 // Позиция движения к второму датчику змейки #define GO_TO_SNAKE_THIRD 10 // Позиция движения к третьему датчику змейки #define GO_TO_FINISH 11 // Позиция движения к финишу. #define CROSS_FINISH_LINE 12 // Позиция после пересечения финишной линии #define GAME_OVER 13 // Позиция наезда на бордюр или окончания заезда //----------------------------------------------------------------------// // УСТАНОВКИ МОДУЛЕЙ И ДАТЧИКОВ: //----------------------------------------------------------------------// #define NUMBER_OF_SENS_ON_EXPANDER 8 // Количество датчиков, подключенных к Trema Expander'у (расширитель выводов) #define DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD 0.07f // Коэффициент задания нижней границы значений относительно верхней границы (при автокалибровке) #define FIRST_SNAKE_SENS A0 // Вывод, к которому подключен первый аналоговый датчик линии, расположенный на змейке #define SECOND_SNAKE_SENS A1 // Вывод, к которому подключен второй аналоговый датчик линии, расположенный на змейке #define THIRD_SNAKE_SENS A2 // Вывод, к которому подключен третий аналоговый датчик линии, расположенный на змейке #define EEPROM_ADRESS 0 // Адрес ячейки EEPROM, куда будет записан последний рекорд #define RESET 5 // Вывод, к которому подключена кнопка, отвечающая за сброс рекорда #define WAITING_STOP_TIME 2000 // Время ожидания до начала движения на трассе (в разных местах) #define ESTAKADA_WAITING_TIME 5000 // Время ожидания на эстакаде. #define RESTART_GAME_TIME 7000 // Время включения надписи СТАРТ после финиша (перезапуск игры) #define NUMBER_OF_DECIMAL_PLACES 1 // Количество знаков после запятой #define MAX 4095 // Максимальное значение ШИМ для силового ключа #define MIDDLE 1024 // Среднее значение ШИМ для силового ключа //----------------------------------------------------------------------// // НАСТРОЙКИ ПЛЕЕРА: //----------------------------------------------------------------------// #define SOUND_BEFORE_START 1 // Звук стадиона #define SOUND_OF_START_THE_ENGINE 2 // Звук запуска двигателя #define SOUND_GO_PARKING_BY_BACK 3 // Звук парковки задним ходом #define SOUND_BEEP 4 // Звук сирены #define SOUND_NEW_RECORD 5 // Звук нового рекорда времени #define SOUND_ESTAKADA_WAITING 6 // Звук ожидания на эстакаде #define SOUND_SNAKE_LINE_MUSIC 7 // Звук финишной прямой #define SOUND_OF_WORKING_MOTOR 8 // Звук работающего мотора #define SOUND_LOSING_GAME 9 // Звук проигрыша //----------------------------------------------------------------------// // ПОДКЛЮЧЕНИЕ БИБЛИОТЕК И ОБЪЯВЛЕНИЕ ОБЪЕКТОВ: //----------------------------------------------------------------------// #include "Wire.h" // Подключаем библиотеку для работы с аппаратной шиной I2C. #include "EEPROM.h" // Подключаем библиотеку для работы с энергонезависимой памятью #include "iarduino_4LED.h" // Подключаем библиотеку iarduino_4LED iarduino_4LED DISP_COUNT(13, 12); // Объявляем объект для работы с функциями библиотеки iarduino_4LED, с указанием выводов дисплея ( CLK , DIO ) iarduino_4LED DISP_RECORD(7, 6); // Объявляем объект для работы с функциями библиотеки iarduino_4LED, с указанием выводов дисплея ( CLK , DIO ) //----------------------------------------------------------------------// #include "iarduino_I2C_Relay.h" // Подключаем библиотеку для работы с реле и силовыми ключами. iarduino_I2C_Relay PWRKEY_A(0x0A); // Объявляем объект PWRKEY_A для работы с функциями и методами библиотеки iarduino_I2C_Relay, указывая адрес модуля на шине I2C. iarduino_I2C_Relay PWRKEY_B(0x0B); // Объявляем объект PWRKEY_B для работы с функциями и методами библиотеки iarduino_I2C_Relay, указывая адрес модуля на шине I2C. iarduino_I2C_Relay PWRKEY_C(0x0C); // Объявляем объект PWRKEY_C для работы с функциями и методами библиотеки iarduino_I2C_Relay, указывая адрес модуля на шине I2C. iarduino_I2C_Relay PWRKEY_D(0x0D); // Объявляем объект PWRKEY_D для работы с функциями и методами библиотеки iarduino_I2C_Relay, указывая адрес модуля на шине I2C. iarduino_I2C_Relay PWRKEY_E(0x0E); // Объявляем объект PWRKEY_E для работы с функциями и методами библиотеки iarduino_I2C_Relay, указывая адрес модуля на шине I2C. //----------------------------------------------------------------------// #include "iarduino_I2C_Expander.h" // Подключаем библиотеку для работы с расширителем выводов. iarduino_I2C_Expander EXPAND_F(0x0F); // Объявляем объект EXPAND_F для работы с функциями и методами библиотеки iarduino_I2C_Expander, указывая адрес модуля на шине I2C. //----------------------------------------------------------------------// #include "SoftwareSerial.h" // Подключаем библиотеку для работы с программным последовательным портом #include "DFRobotDFPlayerMini.h" // Подключаем библиотеку для работы с MP3-плеером SoftwareSerial MY_SOFTWARE_SERIAL(8, 9); // Объявляем объект MY_SOFTWARE_SERIAL (RX, TX) для работы с программным последовательным портом, указав выводы подключения модуля DFRobotDFPlayerMini MP3_PLAYER; // Объявляем объект MP3_PLAYER для работы с MP3-плеером //----------------------------------------------------------------------// #include "Adafruit_Thermal.h" // Подключаем библиотеку для работы с термопринтером Adafruit_Thermal PRINTER(&Serial); // Объявляем объект PRINTER библиотеки Adafruit_Thermal, указывая ссылку на класс Serial, или Serial1, или Serial2 ... //----------------------------------------------------------------------// // СОЗДАНИЕ ПЕРЕМЕННЫХ: //----------------------------------------------------------------------// float FullGameTime; // Создаём переменную для хранения значения времени заезда float Highscore = 999.9; // Создаём переменную для хранения рекордного времени заезда bool Mp3DemoFlag = true; // Создаём флаг для включения MP3-плеера на работу в демо-режиме bool StartRaceFlag = true; // Создаём флаг для начала отсчёта времени с начала гонки bool NewRecordFlag = false; // Создаём флаг для печати текста о новом рекорде времени на принтере uint8_t DemoCount = 0; // Создаём переменную-счётчик для смены режима работы светодиодной ленты в демо-режиме uint8_t ArrowState = 0; // Создаём переменную-счётчик для смены режима работы группы стрелок на отдельных участках трассы uint8_t TrackPositionCode; // Создаём переменную для хранения кода позиции игрока на трассе. //----------------------------------------------------------------------// uint16_t StartSens, EstakadatSens, StartParkingSens; // Создаём переменные для хранения аналоговых значений датчиков линии uint16_t StopParkingSens, FinishSens, BoardSensOne; // Создаём переменные для хранения аналоговых значений датчиков линии uint16_t BoardSensTwo, BoardSensThree, SnakeSensOne; // Создаём переменные для хранения аналоговых значений датчиков линии uint16_t SnakeSensTwo, SnakeSensThree; // Создаём переменные для хранения аналоговых значений датчиков линии //----------------------------------------------------------------------// uint16_t LowValStartSens, LowValEstakadaSens, LowValStartParkingSens; // Создаём переменные для хранения нижней границы значений датчиков линии uint16_t LowValStopParkingSens, LowValFinishSens, LowValBoardSensOne; // Создаём переменные для хранения нижней границы значений датчиков линии uint16_t LowValBoardSensTwo, LowValBoardSensThree, LowValSnakeSensOne; // Создаём переменные для хранения нижней границы значений датчиков линии uint16_t LowValSnakeSensTwo, LowValSnakeSensThree; // Создаём переменные для хранения нижней границы значений датчиков линии //----------------------------------------------------------------------// uint32_t WaitingCount; // Время ожидания до перехода к следующему действию uint32_t StartGameTime; // Время начала гонки (с момента пересечения линии СТАРТа) //----------------------------------------------------------------------// //----------------------------------------------------------------------// void setup() { MY_SOFTWARE_SERIAL.begin(9600); // Инициируем связь с программным последовательным портом на скорости 9600 бит/сек. Serial.begin(9600); // Инициируем связь с аппаратным последовательным портом на скорости 9600 бит/сек. pinMode(RESET, INPUT); // Конфигурируем вывод RESET на работу в качестве входа. if (digitalRead(RESET)) { // Если кнопка нажата при старте скетча, то EEPROM.put(EEPROM_ADRESS, Highscore); // сбросить рекорд в значение по умолчанию delay(100); // Задержка в 100мс для окончания записи } DISP_COUNT.begin(); // Инициируем работу с четырехразрядным индикатором для вывода времени заезда DISP_RECORD.begin(); // Инициируем работу с четырехразрядным индикатором для вывода лучшего времени EEPROM.get(EEPROM_ADRESS, Highscore); // Считываем значение рекорда из энергонезависимой памяти в переменную Highscore MP3_PLAYER.begin(MY_SOFTWARE_SERIAL); // Инициируем работу с MP3-плеером MP3_PLAYER.volume(30); // Устанавливаем уровень громкости плеера (от 10 до 30) PRINTER.begin(255); // Инициируем работу с термопринтером, установив максимальное значение параметра предварительного нагрева PRINTER.setSize('L'); // Устанавливаем крупный размер шрифта 'L' (Large) PRINTER.justify('C'); // Устанавливаем выравнивание текста по центру 'C' (Center) PRINTER.setLineHeight(20); // Устанавливаем межстрочный интервал в 2,0 мм. PRINTER.println(F("-----------------")); // Печатаем тестовую линию отрыва чека PRINTER.feed(3); // Прокручиваем ленту на 3 строки PWRKEY_A.begin(); // Инициируем работу с модулем реле PWRKEY_B.begin(); // Инициируем работу с модулем реле PWRKEY_C.begin(); // Инициируем работу с модулем реле PWRKEY_D.begin(); // Инициируем работу с модулем реле PWRKEY_E.begin(); // Инициируем работу с модулем реле EXPAND_F.begin(); // Инициируем работу с расширителем выводов PWRKEY_A.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля реле PWRKEY_B.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля реле PWRKEY_C.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля реле PWRKEY_D.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля реле PWRKEY_E.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля реле pinMode(FIRST_SNAKE_SENS, INPUT); // Конфигурируем вывод FIRST_SNAKE_SENS на работу в качестве входа. pinMode(SECOND_SNAKE_SENS, INPUT); // Конфигурируем вывод SECOND_SNAKE_SENS на работу в качестве входа. pinMode(THIRD_SNAKE_SENS, INPUT); // Конфигурируем вывод THIRD_SNAKE_SENS на работу в качестве входа. for (int i = 0; i < NUMBER_OF_SENS_ON_EXPANDER; i++) { // Проходим циклом по всем аналоговым датчикам, подключенным к Trema Expander'у и EXPAND_F.pinMode(i, INPUT, ANALOG); // Конфигурируем все выводы на работу в качестве аналогового входа. } TrackPositionCode = GAME_OVER; // Переходим к позиции GAME_OVER (это потушит все стрелки и приведёт к переходу в позицию READY_TO_START). delay(2000); // Устанавливаем задержку для того, чтобы АЦП Expander'а настроился //--------------------------------------------------------// // Переопределяем границы сработки, для чего сначала считываем текущее значение с датчиков: //--------------------------------------------------------// StartSens = EXPAND_F.analogRead(6); // Датчик начала движения. EstakadatSens = EXPAND_F.analogRead(7); // Датчик эстакады. StartParkingSens = EXPAND_F.analogRead(0); // Датчик указывающий совершить парковку. StopParkingSens = EXPAND_F.analogRead(4); // Датчик парковки. FinishSens = EXPAND_F.analogRead(3); // Датчик завершения трассы. BoardSensOne = EXPAND_F.analogRead(1); // Датчик бордюра. BoardSensTwo = EXPAND_F.analogRead(2); // Датчик бордюра. BoardSensThree = EXPAND_F.analogRead(5); // Датчик бордюра. SnakeSensOne = analogRead(FIRST_SNAKE_SENS); // Первый датчик змейки. SnakeSensTwo = analogRead(SECOND_SNAKE_SENS); // Второй датчик змейки. SnakeSensThree = analogRead(THIRD_SNAKE_SENS); // Третий датчик змейки. //--------------------------------------------------------// // Нижняя граница датчика начала движения: LowValStartSens = StartSens - (StartSens * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница датчика эстакады: LowValEstakadaSens = EstakadatSens - (EstakadatSens * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница датчика, указывающего совершить парковку: LowValStartParkingSens = StartParkingSens - (StartParkingSens * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница датчика парковки: LowValStopParkingSens = StopParkingSens - (StopParkingSens * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница датчика завершения трассы: LowValFinishSens = FinishSens - (FinishSens * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница датчика бордюра: LowValBoardSensOne = BoardSensOne - (BoardSensOne * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница датчика бордюра: LowValBoardSensTwo = BoardSensTwo - (BoardSensTwo * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница датчика бордюра: LowValBoardSensThree = BoardSensThree - (BoardSensThree * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница 1 датчика змейки: LowValSnakeSensOne = SnakeSensOne - (SnakeSensOne * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница 2 датчика змейки: LowValSnakeSensTwo = SnakeSensTwo - (SnakeSensTwo * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); //--------------------------------------------------------// // Нижняя граница 3 датчика змейки: LowValSnakeSensThree = SnakeSensThree - (SnakeSensThree * DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD); } //------------------------------------------------------------------// //------------------------------------------------------------------// void loop() { //----------------------------------------------------------------// // Считываем значения с датчиков линии: //----------------------------------------------------------------// CLOSE / OPEN StartSens = EXPAND_F.analogRead(6); // Датчик начала движения. ( 2400 / 4094 ) EstakadatSens = EXPAND_F.analogRead(7); // Датчик эстакады. ( 1900 / 4094 ) StartParkingSens = EXPAND_F.analogRead(0); // Датчик указывающий совершить парковку. ( 1200 / 2537 ) StopParkingSens = EXPAND_F.analogRead(4); // Датчик парковки. ( 3300 / 4094 ) FinishSens = EXPAND_F.analogRead(3); // Датчик завершения трассы. ( 2330 / 4094 ) BoardSensOne = EXPAND_F.analogRead(1); // Датчик бордюра. ( 1650 / 3060 ) BoardSensTwo = EXPAND_F.analogRead(2); // Датчик бордюра. ( 2500 / 4094 ) BoardSensThree = EXPAND_F.analogRead(5); // Датчик бордюра. ( 3300 / 4094 ) SnakeSensOne = analogRead(FIRST_SNAKE_SENS); // Первый датчик змейки. ( 500 / 1009 ) SnakeSensTwo = analogRead(SECOND_SNAKE_SENS); // Второй датчик змейки. ( 400 / 1000 ) SnakeSensThree = analogRead(THIRD_SNAKE_SENS); // Третий датчик змейки. ( 500 / 1000 ) //----------------------------------------------------------------// // Наезд на бордюр: //----------------------------------------------------------------// // Если был совершён наезд на один из датчиков бордюра И установлен любой другой режим, кроме GAME_OVER, то if (TrackPositionCode != GAME_OVER && (BoardSensOne < LowValBoardSensOne || BoardSensTwo < LowValBoardSensTwo || BoardSensThree < LowValBoardSensThree)) { MP3_PLAYER.play(SOUND_LOSING_GAME); // Включаем указанный трек TrackPositionCode = GAME_OVER; // Переходим к позиции GAME_OVER. Mp3DemoFlag = true; // Устанавливаем флаг включения предстартового трека StartRaceFlag = true; // Устанавливаем флаг готовности к новому заезду delay(3500); // Задержка для того, чтобы мелодия проигрыша была проиграна до конца } //----------------------------------------------------------------// // Подсчёт игрового времени: //----------------------------------------------------------------// if (!StartRaceFlag) { // Если флаг ожидания начала гонки сброшен, то FullGameTime = (float)(millis() - StartGameTime) / 1000; // вычисляем время прошедшее с момента пересечения датчика СТАРТ в секундах с плавающей точкой DISP_COUNT.print(FullGameTime, NUMBER_OF_DECIMAL_PLACES); // Выводим время прошедшее с момента пересечения датчика СТАРТ с 1 знаком после запятой. } //----------------------------------------------------------------// // ПЕРЕКЛЮЧАЕМ ПОЗИЦИИ ДВИЖЕНИЯ: //----------------------------------------------------------------// // Позиция ожидания начала заезда: //----------------------------------------------------------------// if (TrackPositionCode == READY_TO_START) { PWRKEY_E.digitalWrite(1, HIGH); // Включаем надпись СТАРТ. if (StartSens < LowValStartSens) { // Если зафиксирован наезд на датчик в линии старта, то ... PWRKEY_E.digitalWrite(1, LOW); // Выключаем надпись СТАРТ. TrackPositionCode = START_RACE; // Переходим к позиции START_RACE. MP3_PLAYER.play(SOUND_OF_START_THE_ENGINE); // Включаем указанный трек StartGameTime = millis(); // Фиксируем время пересечения датчика СТАРТ дл начала отсчёта времени StartRaceFlag = false; // Сбрасываем флаг ожидания начала гонки, чтобы начать считать время } } //----------------------------------------------------------------// // Позиция после пересечения датчика СТАРТ: //----------------------------------------------------------------// else if (TrackPositionCode == START_RACE) { switch (ArrowState) { // Переключаем стрелки направления движения, чтобы они включались змейкой, показывая направление движения case 0: PWRKEY_E.analogWrite(2, MAX); PWRKEY_E.analogWrite(3, MIDDLE); PWRKEY_A.analogWrite(4, MIDDLE); break; case 1: PWRKEY_E.analogWrite(2, MIDDLE); PWRKEY_E.analogWrite(3, MAX); PWRKEY_A.analogWrite(4, MIDDLE); break; case 2: PWRKEY_E.analogWrite(2, MIDDLE); PWRKEY_E.analogWrite(3, MIDDLE); PWRKEY_A.analogWrite(4, MAX); break; } ArrowState++; if (ArrowState > 2) ArrowState = 0; // Если счётчик переключения превысил количество переключаемых состояний, то сбрасываем его в 0 if (EstakadatSens < LowValEstakadaSens) { // Если зафиксирован наезд на датчик ЭСТАКАДЫ, то ... ArrowState = 0; // обнуляем счётчик включения элементов MP3_PLAYER.loop(SOUND_ESTAKADA_WAITING); // Включаем указанный трек PWRKEY_E.digitalWrite(2, LOW); PWRKEY_E.digitalWrite(3, LOW); // Отключаем первые стрелки после надписи старт. TrackPositionCode = ESTAKADA_WAITING; // Переходим к позиции ESTAKADA_WAITING. WaitingCount = millis() + ESTAKADA_WAITING_TIME; // Определяем время съезда с эстакады. } } //----------------------------------------------------------------// // Позиция нахождения на эстакаде: //----------------------------------------------------------------// else if (TrackPositionCode == ESTAKADA_WAITING) { PWRKEY_A.digitalWrite(4, HIGH); // Включаем надпись СТОП на ЭСТАКАДЕ. if (EstakadatSens < LowValEstakadaSens) { // Если машина находится на эстакаде, то ... if (millis() > WaitingCount) { // если истекло время нахождения на ЭСТАКАДЕ, то ... MP3_PLAYER.play(SOUND_OF_WORKING_MOTOR); // включаем указанный трек PWRKEY_A.digitalWrite(4, LOW); // Отключаем надпись СТОП на ЭСТАКАДЕ. TrackPositionCode = GO_FROM_ESTAKADA; // Переходим к позиции GO_FROM_ESTAKADA. } } else { // Если машина съехала с эстакады РАНЬШЕ времени, то ... WaitingCount = millis() + ESTAKADA_WAITING_TIME; // Переопределяем новое время съезда с эстакады. } } //----------------------------------------------------------------// // Позиция после эстакады: //----------------------------------------------------------------// else if (TrackPositionCode == GO_FROM_ESTAKADA) { switch (ArrowState) { // Переключаем стрелки направления движения, чтобы они включались змейкой, показывая направление движения case 0: PWRKEY_A.analogWrite(2, MAX); PWRKEY_A.analogWrite(1, MIDDLE); PWRKEY_B.analogWrite(3, MIDDLE); break; case 1: PWRKEY_A.analogWrite(2, MIDDLE); PWRKEY_A.analogWrite(1, MAX); PWRKEY_B.analogWrite(3, MIDDLE); break; case 2: PWRKEY_A.analogWrite(2, MIDDLE); PWRKEY_A.analogWrite(1, MIDDLE); PWRKEY_B.analogWrite(3, MAX); break; } ArrowState++; if (ArrowState > 2) ArrowState = 0; // Если счётчик переключения превысил количество переключаемых состояний, то сбрасываем его в 0 if (StartParkingSens < LowValStartParkingSens) { // Если зафиксирован наезд на датчик перед надписью СТОП в углу, то ... MP3_PLAYER.play(SOUND_BEEP); // Включаем указанный трек ArrowState = 0; // обнуляем счётчик включения элементов PWRKEY_A.digitalWrite(1, LOW); PWRKEY_A.digitalWrite(2, LOW); // Отключаем первые стрелки после эстакады. TrackPositionCode = STOP_BEFORE_PARKING; // Переходим к позиции STOP_BEFORE_PARKING. WaitingCount = millis() + WAITING_STOP_TIME; // Определяем время работы блока в следующей позиции } } //----------------------------------------------------------------// // Позиция ожидания начала движения на парковку: //----------------------------------------------------------------// else if (TrackPositionCode == STOP_BEFORE_PARKING) { PWRKEY_B.digitalWrite(3, HIGH); // Включаем надпись СТОП в углу if (millis() > WaitingCount) { // Если время работы блока истекло, то... PWRKEY_B.digitalWrite(3, LOW); // выключаем надпись СТОП в углу MP3_PLAYER.play(SOUND_GO_PARKING_BY_BACK); // Включаем указанный трек TrackPositionCode = GO_TO_PARKING; // Переходим к позиции GO_TO_PARKING. } } //----------------------------------------------------------------// // Позиция движения на парковку: //----------------------------------------------------------------// else if (TrackPositionCode == GO_TO_PARKING) { switch (ArrowState) { // Переключаем стрелки направления движения, чтобы они включались змейкой, показывая направление движения case 0: PWRKEY_A.analogWrite(3, MAX); PWRKEY_D.analogWrite(2, MIDDLE); break; case 1: PWRKEY_A.analogWrite(3, MIDDLE); PWRKEY_D.analogWrite(2, MAX); break; } ArrowState++; if (ArrowState > 1) ArrowState = 0; // Если счётчик переключения превысил количество переключаемых состояний, то сбрасываем его в 0 if (StopParkingSens < LowValStopParkingSens) { // Если зафиксирован наезд на датчик парковки, то ... ArrowState = 0; // обнуляем счётчик включения элементов MP3_PLAYER.play(SOUND_BEEP); // Включаем указанный трек PWRKEY_D.digitalWrite(2, LOW); // Отключаем надпись стоп и стрелку на парковку. TrackPositionCode = STOP_BEFORE_GO_TO_ANGLE; // Переходим к позиции STOP_BEFORE_GO_TO_ANGLE. WaitingCount = millis() + WAITING_STOP_TIME; // Определяем время работы блока в следующей позиции } } //----------------------------------------------------------------// // Позиция ожидания начала движения с парковки к датчику в углу: //----------------------------------------------------------------// else if (TrackPositionCode == STOP_BEFORE_GO_TO_ANGLE) { PWRKEY_A.digitalWrite(3, HIGH); // Включаем надпись СТОП на ПАРКОВКЕ if (millis() > WaitingCount) { // Если время работы блока истекло, то... ArrowState = 0; // обнуляем счётчик включения элементов PWRKEY_A.digitalWrite(3, LOW); // выключаем надпись СТОП на ПАРКОВКЕ MP3_PLAYER.play(SOUND_OF_WORKING_MOTOR); // Включаем указанный трек TrackPositionCode = GO_TO_ANGLE; // Переходим к позиции GO_TO_ANGLE. } } //----------------------------------------------------------------// // Позиция движения к первому датчику в углу: //----------------------------------------------------------------// else if (TrackPositionCode == GO_TO_ANGLE) { switch (ArrowState) { // Переключаем стрелку направления движения, чтобы она включалась змейкой, показывая направление движения case 0: PWRKEY_D.analogWrite(1, MAX); break; case 1: PWRKEY_D.analogWrite(1, MIDDLE); break; } ArrowState++; if (ArrowState > 1) ArrowState = 0; // Если счётчик переключения превысил количество переключаемых состояний, то сбрасываем его в 0 if (StartParkingSens < LowValStartParkingSens) { // Если зафиксирован наезд на датчик перед поротом в 90 градусов, то ... ArrowState = 0; // обнуляем счётчик включения элементов PWRKEY_D.digitalWrite(1, LOW); // Отключаем стрелку в углу TrackPositionCode = GO_TO_SNAKE_FIRST; // Переходим к позиции GO_TO_SNAKE_FIRST. } } //----------------------------------------------------------------// // Позиция движения к первому датчику змейки: //----------------------------------------------------------------// else if (TrackPositionCode == GO_TO_SNAKE_FIRST) { switch (ArrowState) { // Переключаем стрелки направления движения, чтобы они включались змейкой, показывая направление движения case 0: PWRKEY_B.analogWrite(1, MAX); PWRKEY_B.analogWrite(2, MIDDLE); break; case 1: PWRKEY_B.analogWrite(1, MIDDLE); PWRKEY_B.analogWrite(2, MAX); break; } ArrowState++; if (ArrowState > 1) ArrowState = 0; // Если счётчик переключения превысил количество переключаемых состояний, то сбрасываем его в 0 if (SnakeSensOne < LowValSnakeSensOne) { // Если зафиксирован наезд на первый датчик змейки, то ... ArrowState = 0; // обнуляем счётчик включения элементов PWRKEY_B.digitalWrite(1, LOW); PWRKEY_B.digitalWrite(2, LOW); // Отключаем стрелки направления к ЗМЕЙКЕ MP3_PLAYER.play(SOUND_SNAKE_LINE_MUSIC); // Включаем указанный трек TrackPositionCode = GO_TO_SNAKE_SECOND; // Переходим к позиции GO_TO_SNAKE_SECOND. } } //----------------------------------------------------------------// // Позиция движения ко второму датчику змейки: //----------------------------------------------------------------// else if (TrackPositionCode == GO_TO_SNAKE_SECOND) { switch (ArrowState) { // Переключаем стрелки направления движения, чтобы они включались змейкой, показывая направление движения case 0: PWRKEY_D.analogWrite(3, MAX); PWRKEY_E.analogWrite(4, MIDDLE); break; case 1: PWRKEY_D.analogWrite(3, MIDDLE); PWRKEY_E.analogWrite(4, MAX); break; } ArrowState++; if (ArrowState > 1) ArrowState = 0; // Если счётчик переключения превысил количество переключаемых состояний, то сбрасываем его в 0 if (SnakeSensTwo < LowValSnakeSensTwo) { // Если зафиксирован наезд на второй датчик змейки, то ... ArrowState = 0; // обнуляем счётчик включения элементов PWRKEY_D.digitalWrite(3, LOW); PWRKEY_E.digitalWrite(4, LOW); // Отключаем стрелки TrackPositionCode = GO_TO_SNAKE_THIRD; // Переходим к позиции GO_TO_SNAKE_THIRD. } } //----------------------------------------------------------------// // Позиция движения к третьему датчику змейки: //----------------------------------------------------------------// else if (TrackPositionCode == GO_TO_SNAKE_THIRD) { switch (ArrowState) { // Переключаем стрелки направления движения, чтобы они включались змейкой, показывая направление движения case 0: PWRKEY_D.analogWrite(4, MAX); PWRKEY_C.analogWrite(3, MIDDLE); break; case 1: PWRKEY_D.analogWrite(4, MIDDLE); PWRKEY_C.analogWrite(3, MAX); break; } ArrowState++; if (ArrowState > 1) ArrowState = 0; // Если счётчик переключения превысил количество переключаемых состояний, то сбрасываем его в 0 if (SnakeSensThree < LowValSnakeSensThree) { // Если зафиксирован наезд на третий датчик змейки, то ... ArrowState = 0; // обнуляем счётчик включения элементов PWRKEY_D.digitalWrite(4, LOW); PWRKEY_C.digitalWrite(3, LOW); // Отключаем стрелки TrackPositionCode = GO_TO_FINISH; // Переходим к позиции GO_TO_FINISH. } } //----------------------------------------------------------------// // Позиция движения к финишу: //----------------------------------------------------------------// else if (TrackPositionCode == GO_TO_FINISH) { switch (ArrowState) { // Переключаем стрелку и надпись ФИНИШ, чтобы они включались змейкой, показывая направление движения case 0: PWRKEY_C.analogWrite(2, MAX); PWRKEY_C.analogWrite(1, MIDDLE); break; case 1: PWRKEY_C.analogWrite(2, MIDDLE); PWRKEY_C.analogWrite(1, MAX); break; } ArrowState++; if (ArrowState > 1) ArrowState = 0; // Если счётчик переключения превысил количество переключаемых состояний, то сбрасываем его в 0 if (FinishSens < LowValFinishSens) { // Если зафиксирован наезд на ФИНИШ, то ... if (FullGameTime < Highscore) { // если время заезда оказалось МЕНЬШЕ текущего рекорда времени, то... NewRecordFlag = true; // Устанавливаем флаг нового рекорда для дальнейшей печати термо-принтера MP3_PLAYER.play(SOUND_NEW_RECORD); // Включаем указанный трек Highscore = FullGameTime; // Обновляем рекорд времени и EEPROM.put(EEPROM_ADRESS, Highscore); // записываем его в энергонезависимую память DISP_RECORD.print(Highscore, NUMBER_OF_DECIMAL_PLACES); // Обновляем значение рекорда на индикаторе рекордов } else { // Если же время заезда БОЛЬШЕ времени рекорда, то MP3_PLAYER.play(SOUND_BEEP); // включаем указанный трек } StartRaceFlag = true; // Устанавливаем флаг готовности трассы к новому заезду ArrowState = 0; // Обнуляем счётчик включения элементов PRINTER.setSize('L'); // Устанавливаем крупный размер шрифта 'L' (Large) PRINTER.justify('C'); // Устанавливаем выравнивание текста по центру 'C' (Center) PRINTER.println(F("iArduino Racing")); // Выводим текст PRINTER.setLineHeight(60); // Устанавливаем межстрочный интервал в 6,0 мм. PRINTER.println(F("2020")); // Выводим текст PRINTER.println(F("")); // Выводим текст PRINTER.setLineHeight(80); // Устанавливаем межстрочный интервал в 8,0 мм. PRINTER.setSize('M'); // Устанавливаем средний размер шрифта 'M' (Middle) PRINTER.println(F("Your time:")); // Выводим текст PRINTER.setSize('L'); // Устанавливаем крупный размер шрифта 'L' (Large) PRINTER.println(FullGameTime + (String)" seconds"); // Выводим текст if (NewRecordFlag) { // Если установлен флаг нового рекорда скорости, то... NewRecordFlag = false; // Сбрасываем флаг нового рекорда PRINTER.setLineHeight(60); // Устанавливаем межстрочный интервал в 6,0 мм. PRINTER.println(F(" ")); // Выводим текст PRINTER.println(F("A new record!")); // Выводим текст о новом рекорде PRINTER.println(F(" ")); // Выводим текст } PRINTER.setLineHeight(70); // Устанавливаем межстрочный интервал в 7,0 мм. PRINTER.setSize('M'); // Устанавливаем средний размер шрифта 'M' (Middle) PRINTER.justify('L'); // Устанавливаем выравнивание текста по левому краю 'L' (Left) PRINTER.println(F("Your name:__________________")); // Выводим текст PRINTER.println(F("Your tel:__________________")); // Выводим текст PRINTER.feed(2); // Прокручиваем ленту на 2 строки PWRKEY_C.digitalWrite(2, LOW); // Выключаем элементы перед финишной линией TrackPositionCode = CROSS_FINISH_LINE; // Переходим к позиции CROSS_FINISH_LINE. WaitingCount = millis() + RESTART_GAME_TIME; // Определяем время работы блока в следующей позиции } } //----------------------------------------------------------------// // Позиция нахождения на финише: //----------------------------------------------------------------// else if (TrackPositionCode == CROSS_FINISH_LINE) { PWRKEY_C.digitalWrite(1, HIGH); // Включаем надпись ФИНИШ if (millis() > WaitingCount) { // Если время работы блока истекло, то... PWRKEY_C.digitalWrite(1, LOW); // выключаем надпись ФИНИШ TrackPositionCode = GAME_OVER; // Переходим к позиции GAME_OVER Mp3DemoFlag = true; // Устанавливаем флаг включения предстартового трека } } //----------------------------------------------------------------// // Позиция окончания игры или проигрыша: //----------------------------------------------------------------// else if (TrackPositionCode == GAME_OVER) { if (Mp3DemoFlag == true) { // Если флаг включения предстартового трека установлен, то Mp3DemoFlag = false; // сбрасываем флаг включения предстартового трека и MP3_PLAYER.loop(SOUND_BEFORE_START); // включаем нужный трек в бесконечном повторе } DISP_COUNT.print( "----" ); // Вывод текста ожидания начала гонки на индикатор DISP_RECORD.print(Highscore, NUMBER_OF_DECIMAL_PLACES); // Выводим на индикатор значение текущего рекорда времени // Если зафиксирован наезд на датчик в линии ФИНИША и установлен режим GAME_OVER, то ... if (FinishSens < LowValFinishSens && TrackPositionCode == GAME_OVER) { PWRKEY_A.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля. PWRKEY_B.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля. PWRKEY_C.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля. PWRKEY_D.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля. PWRKEY_E.digitalWrite(ALL_CHANNEL, LOW); // Выключаем все каналы модуля. TrackPositionCode = READY_TO_START; // Переходим к позиции READY_TO_START } else { demo_resume(); // Включаем демонстрационный режим работы платформы } } } //------------------------------------------------------------// // Демо-режим работы стенда: void demo_resume() { for (DemoCount = 0; DemoCount < 19; DemoCount ++) { // Проходимся в цикле по всем элементам поля и зажигаем при каждом проходе следующий элемент PWRKEY_E.analogWrite(1, DemoCount == 0 ? MAX : MIDDLE); // Если переменная DemoCount равна 0, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_E.analogWrite(2, DemoCount == 1 ? MAX : MIDDLE); // Если переменная DemoCount равна 1, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_E.analogWrite(3, DemoCount == 2 ? MAX : MIDDLE); // Если переменная DemoCount равна 2, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_A.analogWrite(4, DemoCount == 3 ? MAX : MIDDLE); // Если переменная DemoCount равна 3, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_A.analogWrite(2, DemoCount == 4 ? MAX : MIDDLE); // Если переменная DemoCount равна 4, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_A.analogWrite(1, DemoCount == 5 ? MAX : MIDDLE); // Если переменная DemoCount равна 5, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_B.analogWrite(3, DemoCount == 6 ? MAX : MIDDLE); // Если переменная DemoCount равна 6, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_D.analogWrite(2, DemoCount == 7 ? MAX : MIDDLE); // Если переменная DemoCount равна 7, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_A.analogWrite(3, DemoCount == 8 ? MAX : MIDDLE); // Если переменная DemoCount равна 8, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_D.analogWrite(1, DemoCount == 9 ? MAX : MIDDLE); // Если переменная DemoCount равна 9, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_B.analogWrite(2, DemoCount == 10 ? MAX : MIDDLE); // Если переменная DemoCount равна 10, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_B.analogWrite(1, DemoCount == 11 ? MAX : MIDDLE); // Если переменная DemoCount равна 11, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_D.analogWrite(3, DemoCount == 12 ? MAX : MIDDLE); // Если переменная DemoCount равна 12, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_E.analogWrite(4, DemoCount == 13 ? MAX : MIDDLE); // Если переменная DemoCount равна 13, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_D.analogWrite(4, DemoCount == 14 ? MAX : MIDDLE); // Если переменная DemoCount равна 14, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_C.analogWrite(3, DemoCount == 15 ? MAX : MIDDLE); // Если переменная DemoCount равна 15, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_C.analogWrite(2, DemoCount == 16 ? MAX : MIDDLE); // Если переменная DemoCount равна 16, то установить ШИМ в значение MAX, иначе в MIDDLE PWRKEY_C.analogWrite(1, DemoCount == 17 ? MAX : MIDDLE); // Если переменная DemoCount равна 17, то установить ШИМ в значение MAX, иначе в MIDDLE } }
Принцип работы:
Есть игровое поле. На нём расположены датчики линии, которые переключают режимы работы модулей игрового поля. Игроку необходимо проехать по датчикам линии в правильном порядке так, чтобы при наезде на очередной датчик включалась следующая группа указателей и целей.
Заезд считается успешно выполненным, если все датчики линии были пройдены в правильном порядке, а в конце была пересечена финишная линия.
При успешном заезде в момент пересечения финишной прямой на термо-принтере будет напечатан текст с временем заезда. Если же при пересечении финишной линии время заезда игрока оказалось меньшим, чем рекордное, то дополнительно к основному тексту будет добавлено уведомление о том, что игрок установил новый рекорд, а так же заиграет победная мелодия.
Теперь что касается работы самого скетча. Условно, разделим его на следующие блоки:
- Блок до Setup;
- Блок Setup;
- Блок Loop;
- Блок Demo;
и разберём каждый из них подробнее.
Блок до Setup.
В данном блоке задаются константы и указываются выводы, к которым подключены модули, задаётся коэффициент, с помощью которого происходит автокалибровка нижней границы датчиков линии, подключаются библиотеки и объявляются объекты для работы с ними, создаются переменные.
Блок Setup.
В данном блоке выполняется предварительная настройка работы:
- настраивается режим работы силовых ключей, MP3-плеера, термо-принтера, 4-сегментных дисплеев;
- настраиваются режимы работы выводов, к которым подключены кнопка и датчики линии;
- из энергонезависимой памяти считывается значение рекорда скорости, который был установлен ранее. Если же при запуске платформы была нажата кнопка, то значение рекорда будет сброшено в значение по умолчанию (999.9 секунд);
- устанавливается режим работы всей платформы в значение "ожидание начала заезда"
- выполняется калибровка границы сработки датчиков линии, для чего сначала выполняется опрос всех датчиков, а затем из этих значений ) с помощью коэффициента понижения
DECREASE_COEFFICIENT_FOR_SET_SENS_BOARD
) они преобразуются в нижнюю границу чувствительности;
Блок Loop.
В данном блоке в цикле выполняется основное тело программы:
- каждый цикл опрашиваются датчики линии;
- с момента пересечения линии СТАРТ и до момента пересечения линии ФИНИШ ведётся отсчёт игрового времени
FullGameTime
, которое выводится на 4-разрядный индикатор; - каждый цикл отслеживается, чтобы на датчики линии, расположенные на бордюре, не было совершено наезда;
- Далее, в зависимости от позиции Малыша на поле, выполняется один из следующих режимов:
READY_TO_START
- позиция ожидания начала заезда:- Включается надпись "СТАРТ"
- Идёт ожидание наезда на датчик линии, расположенный на линии старта:
- Пока датчик не пересечён, надпись "СТАРТ" будет гореть;
- После пересечения датчика код позиции будет изменён на
START_RACE
, трек на плеере будет переключен, а надпись будет погашена;
START_RACE
- позиция старта гонки после пересечения линии СТАРТ:- Включаются стрелки направления движения;
- Идёт ожидание наезда на датчик линии, расположенный на эстакаде:
- Пока датчик не будет пересечён, стрелки будут гореть;
- После пересечения датчика эстакады код позиции будет изменён на
ESTAKADA_WAITING
, трек на плеере будет переключен, а стрелки будут погашены;
ESTAKADA_WAITING
- позиция стоянки на эстакаде:- С момента наезда на датчик линии на эстакаде начинается отсчёт времени (длительность указана в
ESTAKADA_WAITING_TIME
), по истечении которого режим будет изменён на следующий,GO_FROM_ESTAKADA
. - Если же с эстакады был совершён съезд раньше истечения времени
ESTAKADA_WAITING_TIME
, то отсчёт необходимого времени будет запущен заново;
- С момента наезда на датчик линии на эстакаде начинается отсчёт времени (длительность указана в
GO_FROM_ESTAKADA
- позиция съезда с эстакады:- Включаются стрелки направления дальнейшего движения;
- Идёт ожидание наезда на датчик линии, расположенный в дальнем углу:
- Пока датчик не будет пересечён, стрелку будут гореть;
- После пересечения датчика стрелки будут погашены, будет включена надпись СТОП, а режим будет изменён на следующий,
STOP_BEFORE_PARKING
;
STOP_BEFORE_PARKING
- позиция ожидания до начала парковки:- Надпись СТОП горит до истечения времени
WaitingCount
, после чего надпись будет погашена, на плеере будет включен следующий трек, а режим будет сменён на следующий -GO_TO_PARKING
;
- Надпись СТОП горит до истечения времени
GO_TO_PARKING
- позиция парковки задним ходом:- Будут включены стрелка и надпись СТОП, которые обозначат место парковки задним ходом;
- Идёт ожидание наезда на датчик линии, расположенный на месте парковки:
- До тех пор, пока он не будет пересечён, стрелки будут гореть, а плеер будет играть специальный трек;
- После того, как датчик будет пересечён, стрелка погаснет, а надпись СТОП загорится постоянным светом; будет установлен таймер работы следующего режима, а сам режим работы будет изменён на следующий -
STOP_BEFORE_GO_TO_ANGLE
;
STOP_BEFORE_GO_TO_ANGLE
- позиция ожидания до начала движения к датчику в углу:- Надпись СТОП горит до истечения времени
WaitingCount
, после чего надпись будет погашена, на плеере будет включен следующий трек, а режим будет сменён на следующий -GO_TO_ANGLE
;
- Надпись СТОП горит до истечения времени
GO_TO_ANGLE
- позиция движения к датчику в углу:- Будет включена стрелка, которая обозначит направление движения;
- Идёт ожидание наезда на датчик линии, расположенный в углу, на повороте на 90 градусов:
- До тех пор, пока он не будет пересечён, стрелка будет гореть;
- После того, как датчик будет пересечён, стрелка погаснет, а сам режим работы будет изменён на следующий -
GO_TO_SNAKE_FIRST
;
GO_TO_SNAKE_FIRST
- позиция движения к первому датчику змейки:- Будут включены стрелки, которые обозначат направление движения;
- Идёт ожидание наезда на датчик линии, расположенный в самом начале "змейки":
- До тех пор, пока он не будет пересечён, стрелки будут гореть;
- После того, как датчик будет пересечён, стрелки погаснут, на плеере будет сменён трек, а сам режим работы будет изменён на следующий -
GO_TO_SNAKE_SECOND
;
GO_TO_SNAKE_SECOND
- позиция движения ко второму датчику змейки:- Будут включены стрелки, которые обозначат направление движения;
- Идёт ожидание наезда на датчик линии, расположенный в середине "змейки":
- До тех пор, пока он не будет пересечён, стрелки будут гореть;
- После того, как датчик будет пересечён, стрелки погаснут, а сам режим работы будет изменён на следующий -
GO_TO_SNAKE_THIRD
;
GO_TO_SNAKE_THIRD
- позиция движения к третьему датчику змейки:- Будут включены стрелки, которые обозначат направление движения;
- Идёт ожидание наезда на датчик линии, расположенный в конце "змейки":
- До тех пор, пока он не будет пересечён, стрелки будут гореть;
- После того, как датчик будет пересечён, стрелки погаснут, а сам режим работы будет изменён на следующий -
GO_TO_FINISH
;
GO_TO_FINISH
- позиция движения к финишной линии:- Будут включены стрелка и надпись "ФИНИШ", которые обозначат направление движения;
- Идёт ожидание наезда на датчик линии, расположенный в финишной линии:
- До тех пор, пока он не будет пересечён, стрелка и надпись будут гореть;
- После того, как датчик будет пересечён будут выполнены следующие действия:
- Будет выполнена проверка времени прохождения трассы
FullGameTime
с рекордным временем прохожденияHighscore
и:- если время меньше рекордного, то будет установлен флаг нового рекорда
NewRecordFlag
, на плеере будет включён трек нового рекорда, а сам рекорд будет перезаписан в переменнуюHighscore
и в энергонезависимую память, после чего на дисплее с рекордом так же будет обновлено значение; - если время прохождения больше рекордного, то будет включён трек завершения трасы;
- если время меньше рекордного, то будет установлен флаг нового рекорда
- Будет установлен флаг готовности трассы к новому заезду, что остановит отсчёт времени заезда;
- Будет напечатан чек на термо-принтере с результатами гонки, и если ранее был установлен флаг нового рекорда, то в чек будет добавлена строка с уведомлением об этом;
- Будет установлен флаг перевода трассы в демо-режим;
- стрелка погаснет, а надпись будет гореть постоянно, а режим работы будет изменён на следующий -
CROSS_FINISH_LINE
;
- Будет выполнена проверка времени прохождения трассы
CROSS_FINISH_LINE
- позиция пересечения финишной линии:- Будет включена надпись "ФИНИШ";
- После того, как истечёт время
WaitingCount
, надпись будет погашена, а режим будет изменён наGAME_OVER
;
GAME_OVER
- позиция проигрыша или окончания заезда:- Будет включен трек ожидания нового заезда;
- Время на дисплее отсчёта времени будет сброшено;
- Будет включен демо-режим работы стенда;
- Идёт ожидание наезда на датчик линии, расположенный в финишной линии:
- До тех пор, пока он не будет пересечён, стенд будет работать в демо-режиме;
- После того, как датчик пересечён, все стрелки на стенде будут погашены, а режим будет изменён на
READY_TO_START
;
Блок Demo.
В данном блоке в цикле выполняется смена режима работы всех световых элементов на игровом поле таким образом, чтобы каждый проход цикла один из элементов горел ярче остальных, тем самым создавая эффект "бегущей" дорожки.
Ссылки:
- Piranha UNO;
- Trema Shield;
- Trema-модуль Кнопка;
- I2C-FLASH Модуль силовых ключей (4N) с измерением тока;
- 4-сегментный индикатор;
- Trema MP3-плеер;
- Карта памяти MicroSD;
- Усилитель;
- Динамик 3W;
- Аудио штекер 3,5 мм с клеммником;
- I2C-FLASH Trema Expander;
- Термо-принтер;
- Аналоговый датчик линии;
- Коннектор питания типа "МАМА" с клеммником;
- Светодиодная лента;
- Провод красный 20AWG ;
- Провод чёрный 20AWG;
- Провод красный 16AWG;
- Провод чёрный 16AWG;
- 3-й шлейф;
- 4-й шлейф;
- ПВХ-пластина большая;
- ПВХ-пластина средняя;
- ПВХ-пластина с креплениями;
- Источник питания (12В);
- Понижающий DC-DC преобразователь;
- Мультиметр;
- Робот "Малыш";
Обсуждение