Описание:
В данном проекте мы создадим аналог автодрома, на котором сдают первую часть экзамена для получения водительского удостоверения. В данном случае будем считать, что вы получите 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 преобразователь;
- Мультиметр;
- Робот "Малыш";

Обсуждение