Радио-кнопки для игры "Угадай мелодию".
- Войдите на сайт для отправки комментариев
В игру давно играют как в семейном кругу так и на вечеринках с друзьями. Напомню смысл игры: ведущий включает фрагмент мелодии, игроки нажимают кнопки. Кто угадал и быстрее нажал свою кнопку тому + N-количество очков. Если не правильно угадал - минус. Возможны вариации правил игры в зависимости от фантазии ведущего. Преимущество беспроводных кнопок для игроков очевидно - они не привязаны к определенному месту и позволяют располагаться вокруг ведущего в радиусе 10 и более метров. Предлагаемый вариант беспроводных кнопок состоит из трех кнопок для игроков и одной для ведущего и предназначен для домашнего использования. Кнопки построены на самых дешевых ардуинах Pro Mini ATmega168/328/8MHz/3,3V и трансиверах nRF24L01+.
Структурная схема системы.
Кнопка ведущего представляет собой HID - устройство, подключается к USB порту компьтера и позволяет эмулировать команды мм-клавиатуры для управления плеером. Данное устройство передает плееру только две команды - PLAY и STOP. Также к кнопке ведущего подключена лампа-сигнализатор, которая включатся соответствующим цветом при нажатии кнопки игрока. В режиме ожидания кнопки игроков заблокированы и ожидают команды от кнопки ведущего. Ведущий выбирает на компе файл мелодии и нажимает свою кнопку. "Кнопка посылает" кнопкам игроков сигнал разблокировки и запускает плеер с выбранной мелодией. Длительность проигрывания мелодии ограничена в данном варианте 10-ю секундами, по истечении которых плеер останавливается и вновь блокируются кнопки игроков. Если при проигрывании мелодии будет нажата кнопка одного из игроков - плеер останавливается, блокируются кнопки игроков, зажигается лампа-сигнализатор на 5 сек соответствующим цветом.
Принципиальные схемы и скетчи.
Схема кнопки ведущего.
Кнопка ведущего запитана от шины 5V USB порта. При выборе ардуины для этой кнопки следует ориентироваться на варианты клонов Pro-Mini 168/328 с "большим" стабилизатором и кварцем на борту.
Дело в том, что от внутреннего стабилизатора 3,3V запитывается не только чип мк ардуины, но и периферия в лице трансивера nRF24L01+, что требует от стабилизатора повышенной мощности. Что касается кварца, то его нужно поменять на 12МГц. Это требования USB-драйвера для ардуины. Для кнопок игроков стабилизаторы ардуин не учавствуют в питании, поэтому там могут быть любые модификации плат 8Мгц/3,3V.
Трансиверы nRF24L01+ могут быть обычными или мини. Следует учесть, что у платы-мини вывод N1 это VCC(3,3V).
В качестве сигнализатора нажатой кнопки игрока используется "пятачек" CJMCU-2812-7, с 7-ю адресными светодиодами WS2812B, помещенный под колпак сгоревшей сетевой лед-лампы.
Можно использовать огрызок адресной ленты или 7 отдельных WS2812B. При использовании длинной ленты, ее следует запитать от отдельного источника 5V и скорректировать скетч. Можно использовать обычные RGB светодиоды после минимальной доработки схемы и скетча.
Для заливки скетча в ардуины потребуется USB/UART конвертер. Рекомендую FTDI232 на чипе FT232RL, кроме USB/UART конвертера он может работать как SPI bit-bang программатор. SPI программатор потребуется для заливки загрузчика в ардуину после замены кварца с 8 на 12МГц или при заливке готового HEX файла. Для поддержки Arduino IDE плат с "нестандартной" частотой необходимо в IDE установить дополнение MiniCore Подробности установки дополнений и заливки загрузчика можно найти в интернете.
Скетч кнопки ведущего:
001 | /***************************LEADER (ВЕДУЩИЙ)*********************************** |
002 |
003 | * Плата: ATmega168 |
004 | * Clock: 12MHz external |
005 | * Variant: 168/168A |
006 | * LTO enabled |
007 | * Bootloader: Yes (UART0) |
008 |
009 |
010 | * VER 11.12.20 |
011 | ******************************************************************************/ |
012 |
013 | //#define DEBUG //Раскомментировать для отладки |
014 |
015 | #include <TrinketHidCombo.h> // see tutorial at <a href="http://learn.adafruit.com/trinket-usb-volume-knob" title="http://learn.adafruit.com/trinket-usb-volume-knob" rel="nofollow">http://learn.adafruit.com/trinket-usb-volume-knob</a> |
016 | #include <Adafruit_NeoPixel.h> |
017 | #include <RF24.h> // Подключаем библиотеку для работы с nRF24L01+ |
018 |
019 | #ifdef DEBUG |
020 | #include <printf.h> // для отладки |
021 | #endif |
022 |
023 | #define GAMER1 1 // 1 геймер красный |
024 | #define GAMER2 2 // 2 геймер зеленый |
025 | #define GAMER3 3 // 3 геймер синий |
026 |
027 | // CE and CSN are configurable for nRF24L01+ |
028 | #define CE_PIN 9 |
029 | #define CSN_PIN 10 |
030 |
031 | #define LED_PIN 5 // пин ленты |
032 | #define LED_COUNT 7 // кол-во светодиодов |
033 | #define LIGHT_TIME 5000 // Время свечения ленты (msec) |
034 |
035 | #define PIN_BUTTON 3 // Пин кнопки |
036 | #define LED 6 // Пин светодиода |
037 |
038 | //Время распознавания = FLASH_COUNT 20 * FREQ 500 = 10 000 msec (10 sec) |
039 | #define FLASH_COUNT 20 |
040 | #define FREQ 500 // LED blinking frequency (msec) |
041 |
042 | #define OFF 100 // Константа для блокировки геймеров |
043 | #define ON 101 // Для разблокировки геймеров |
044 |
045 | Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); |
046 |
047 | RF24 radio(CE_PIN, CSN_PIN); // Создаём объект radio для работы с библиотекой RF24, указывая номера выводов nRF24L01+ (CE, CSN) |
048 |
049 | uint8_t data_tx = ON; // Принятые данные |
050 | uint8_t data_rx; // Данные для передачи |
051 | uint8_t pipe; // Создаём переменную для хранения номера трубы, по которой пришли данные |
052 | bool ledState = LOW; //Status LED |
053 | unsigned long previousMillis = 0; |
054 | unsigned long previousMillis2 = 0; |
055 |
056 | uint8_t flashCounter = 0; |
057 | bool flash = false ; |
058 | bool led_show = false ; |
059 | byte addresses[][6] = { "1Node" , "2Node" , "3Node" , "4Node" }; |
060 | static char sw_was_pressed = 0; |
061 |
062 | //*************************************************************************** |
063 |
064 | void setup () |
065 | { |
066 | //****************************Для отладки *********************************** |
067 | #ifdef DEBUG |
068 | Serial .begin(57600); |
069 | printf_begin(); |
070 | Serial .println( "BASE UNIT" ); |
071 | #endif |
072 | //*************************************************************************** |
073 | |
074 | delay(1000); |
075 | pinMode(PIN_BUTTON, INPUT_PULLUP); |
076 | pinMode(LED, OUTPUT); |
077 | digitalWrite(LED, HIGH); |
078 | |
079 | strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) |
080 | strip.setBrightness(150); // яркость (0-255) |
081 | |
082 | for ( byte i = 1; i < LED_COUNT; i++) { //зажечь ленту белым на 2 с |
083 | strip.setPixelColor(i, strip.Color(127, 127, 127)); |
084 | } |
085 | strip.show(); // выводим изменения на ленту |
086 | |
087 | delay(2000); |
088 | |
089 | strip.clear(); // очищает буфер |
090 | strip.show(); // выводим изменения на ленту |
091 | |
092 | // Setup and configure rf radio |
093 | radio.begin(); // Инициируем работу nRF24L01+ |
094 | radio.setAutoAck(1); // Ensure autoACK is enabled |
095 | radio.setRetries(0,15); // Max delay between retries & number of retries |
096 | radio.setChannel(5); // Указываем канал приёма данных (от 0 до 127), 5 - значит приём данных осуществляется на частоте 2,405 ГГц (на одном канале может быть только 1 приёмник и до 6 передатчиков) |
097 | radio.setDataRate (RF24_1MBPS); // Указываем скорость передачи данных (RF24_250KBPS, RF24_1MBPS, RF24_2MBPS), RF24_1MBPS - 1Мбит/сек |
098 | radio.setPALevel (RF24_PA_HIGH); // Указываем мощность передатчика (RF24_PA_MIN=-18dBm, RF24_PA_LOW=-12dBm, RF24_PA_HIGH=-6dBm, RF24_PA_MAX=0dBm) |
099 | |
100 | radio.openWritingPipe(addresses[0]); |
101 | radio.openReadingPipe(GAMER1,addresses[GAMER1]); |
102 | radio.openReadingPipe(GAMER2,addresses[GAMER2]); |
103 | radio.openReadingPipe(GAMER3,addresses[GAMER3]); |
104 | radio.startListening (); // Включаем приемник, начинаем прослушивать открытые трубы |
105 |
106 | #ifdef DEBUG |
107 | radio.printDetails(); // Дамп конфигурации RF модуля в монитор для отладки. |
108 | #endif |
109 | |
110 | //USB d- (белый) PORT D4 задан в usbconfig.h |
111 | //USB d+ (зелёный) PORT D2 задан в usbconfig.h |
112 | TrinketHidCombo.begin(); // start the USB device engine and enumerate |
113 | |
114 | } |
115 | //**************************************************************************************** |
116 | void loop () |
117 | { |
118 | if (radio.available(&pipe)){ // Если в буфере имеются принятые данные, то получаем номер трубы, по которой они пришли, по ссылке на переменную pipe |
119 | radio.read(&data_rx, sizeof (data_rx)); // Читаем данные в массив data и указываем сколько байт читать |
120 |
121 | #ifdef DEBUG |
122 | Serial .println(pipe); |
123 | Serial .println(data_rx); |
124 | #endif |
125 | TrinketHidCombo.pressMultimediaKey(MMKEY_STOP); // Если данные пришли от геймеров, то тормозим плеер |
126 | |
127 | data_tx = OFF; // Готовим код для блокировки геймеров |
128 | radio.stopListening(); // First, stop listening so we can talk. |
129 | radio.write( &data_tx, 1); // Блокируем геймеров |
130 | radio.startListening(); // Now, continue listening |
131 | |
132 | flash = false ; // Тормоз мигания LED |
133 | |
134 | if (pipe == GAMER1){ // Если кнопку нажал красный геймер |
135 | for ( byte i = 1; i < LED_COUNT; i++) { // Зажигаем ленту красным |
136 | strip.setPixelColor(i, strip.Color(255, 0, 0)); // красный |
137 | } |
138 | } |
139 |
140 | if (pipe == GAMER2){ // Если кнопку нажал зеленый геймер |
141 | for ( byte i = 1; i < LED_COUNT; i++) { |
142 | strip.setPixelColor(i, strip.Color(0, 255, 0)); // зеленый |
143 | } |
144 | } |
145 |
146 | if (pipe == GAMER3){ // Если кнопку нажал синий геймер |
147 | for ( byte i = 1; i < LED_COUNT; i++) { |
148 | strip.setPixelColor(i, strip.Color(0, 0, 255)); // синий |
149 | } |
150 | } |
151 | |
152 | strip.show(); |
153 | pipe = 0; |
154 | previousMillis2 = millis(); |
155 | led_show = true ; |
156 | flashCounter = 0; |
157 | ledState = LOW; |
158 | digitalWrite(LED, HIGH); |
159 | } |
160 | |
161 | Delay_Show(); //Зажигаем ленту на 5с |
162 | |
163 | //----------------- Обработка кнопки PLAY/PAUSE------------------ |
164 | if (digitalRead(PIN_BUTTON) == LOW) |
165 | { |
166 | if (sw_was_pressed == 0) // only on initial press, so the keystroke is not repeated while the button is held down |
167 | { |
168 | TrinketHidCombo.pressMultimediaKey(MMKEY_PLAYPAUSE); //Запустить плеер с выбранным файлом |
169 | flash = true ; //!flash; |
170 | data_tx = ON; // Данные для разблокировки геймеров |
171 | previousMillis = millis(); |
172 | radio.stopListening(); // First, stop listening so we can talk. |
173 | radio.write( &data_tx, 1); // Разблокировать геймеров |
174 | radio.startListening(); // Now, continue listening |
175 | |
176 | delay(4); // debounce delay |
177 | } |
178 | sw_was_pressed = 1; |
179 | } |
180 | else |
181 | { |
182 | if (sw_was_pressed != 0) { |
183 | delay(4); // debounce delay |
184 | } |
185 | sw_was_pressed = 0; |
186 | } |
187 |
188 | TrinketHidCombo.poll(); // check if USB needs anything done |
189 | //---------------------------------------------------------------- |
190 | //Non-blocking blinking |
191 | if ((millis() - previousMillis >= FREQ)&& flash == true ) { |
192 | |
193 | previousMillis = millis(); // save the last time you blinked the LED |
194 | ledState = !ledState; //if the LED is off turn it on and vice-versa: |
195 | digitalWrite(LED, ledState); // set the LED with the ledState of the variable: |
196 | |
197 | if (flashCounter++ >= FLASH_COUNT) { |
198 | TrinketHidCombo.pressMultimediaKey(MMKEY_STOP); // Остановить плеер |
199 | data_tx = OFF; |
200 | |
201 | radio.stopListening(); // First, stop listening so we can talk. |
202 | radio.write( &data_tx, 1); // Блокировать геймеров, если время на распознавание вышло |
203 | radio.startListening(); // Now, continue listening |
204 | |
205 | flashCounter = 0; |
206 | flash = false ; |
207 |
208 | ledState = LOW; |
209 | digitalWrite(LED, HIGH); |
210 | } |
211 | |
212 | } |
213 | |
214 | } |
215 |
216 | void Delay_Show ( void ) { |
217 | if (((millis() - previousMillis2) >= LIGHT_TIME) && led_show == true ) { |
218 | previousMillis2 = millis(); // save the last time you blinked the LED |
219 | strip.clear(); // очищает буфер |
220 | strip.show(); // выводим изменения на ленту |
221 | led_show = false ; |
222 | } |
223 | } |
Схема кнопки игроков.
Для каждой кнопки игрока заливается свой скетч. В скетче нужно раскомментировать #define GAMER для соответствующей кнопки, это нужно чтобы у каждой кнопки был свой адрес трубы для передачи, необходимо для распознавания нажатой кнопки у ведущего.
Скетч кнопки игрока:
001 | //**********************GAMER (ИГРОК)********************************* |
002 |
003 | /* Плата: Arduino Pro or Pro mini |
004 | * Процессор: ATmega168 (3,3V 8MHz) |
005 | * |
006 | * VER 11.12.20 |
007 | */ |
008 |
009 | //#define DEBUG //Раскомментировать для отладки |
010 |
011 | #include <RF24.h> |
012 |
013 | #ifdef DEBUG |
014 | #include <printf.h> // для отладки |
015 | #endif |
016 |
017 |
018 | //Раскомментировать для соответствующего геймера |
019 | //#define GAMER 1 // 1 геймер красный |
020 | #define GAMER 2 // 2 геймер зеленый |
021 | //#define GAMER 3 // 3 геймер синий |
022 |
023 | // CE and CSN are configurable |
024 | #define CE_PIN 9 |
025 | #define CSN_PIN 10 |
026 |
027 | #define BUTTON 5 // пин кнопки |
028 |
029 | #define LED1 2 // пины светодиодов |
030 | #define LED2 3 |
031 | #define LED3 4 |
032 |
033 | #define OFF 100 |
034 | #define ON 101 |
035 |
036 | #define BRIGHNESS 100 // яркость LED в режиме ожидания |
037 |
038 | RF24 radio(CE_PIN, CSN_PIN); |
039 |
040 | const int ARRAY_SIZE = 3; |
041 | int ledPin[] = {LED1, LED2, LED3}; // Массив LED - пинов |
042 | int ledDelay = 100; // Скорость бегущего огня (msec) |
043 | int currentLed = 0; |
044 | unsigned long changeTime; |
045 | bool run_light = false ; // Флаг разрешения бегущего огня |
046 |
047 | static char sw_was_pressed = 0; |
048 | uint8_t data_tx = 111; // Данные передатчика |
049 | uint8_t data_rx; // Данные приемника |
050 | bool button_available = false ; // Флаг доступа к кнопке |
051 | byte addresses[][6] = { "1Node" , "2Node" , "3Node" , "4Node" }; //"2Node" - красный,"3Node" - зеленый, "4Node" - синий геймер |
052 |
053 | void setup () { |
054 | //****************************Для отладки *********************************** |
055 | #ifdef DEBUG |
056 | Serial .begin(57600); |
057 | printf_begin(); |
058 | Serial .println( "BASE UNIT" ); |
059 | #endif |
060 | //*************************************************************************** |
061 | |
062 | pinMode(BUTTON, INPUT_PULLUP); // Пин для кнопки на ввод с подтяжкой |
063 | |
064 | for ( int i = 0; i < ARRAY_SIZE; i++) { // Конфигурируем пины для LED на выход |
065 | pinMode(ledPin[i], OUTPUT); |
066 | } |
067 | delay(2000); |
068 | analogWrite(ledPin[1], BRIGHNESS); //режим ожидания LED |
069 | |
070 | // Setup and configure rf radio |
071 | radio.begin(); // Start up the radio |
072 | radio.setAutoAck(1); // Ensure autoACK is enabled |
073 | radio.setRetries(0,15); // Max delay between retries & number of retries |
074 | radio.setChannel(5); // Указываем канал приёма данных (от 0 до 127), 5 - значит приём данных осуществляется на частоте 2,405 ГГц (на одном канале может быть только 1 приёмник и до 6 передатчиков) |
075 | radio.setDataRate (RF24_1MBPS); // Указываем скорость передачи данных (RF24_250KBPS, RF24_1MBPS, RF24_2MBPS), RF24_1MBPS - 1Мбит/сек |
076 | radio.setPALevel (RF24_PA_HIGH); //(RF24_PA_MIN); // Указываем мощность передатчика (RF24_PA_MIN=-18dBm, RF24_PA_LOW=-12dBm, RF24_PA_HIGH=-6dBm, RF24_PA_MAX=0dBm) |
077 |
078 | radio.openWritingPipe(addresses[GAMER]); |
079 | radio.openReadingPipe(1,addresses[0]); |
080 |
081 | //****************************Для отладки *********************************** |
082 | #ifdef DEBUG |
083 | radio.printDetails(); // Дамп конфигурации RF модуля в монитор для отладки. |
084 | #endif |
085 | //*************************************************************************** |
086 | radio.startListening(); // Start listening |
087 |
088 | } |
089 |
090 | void loop ( void ){ |
091 | |
092 | if (radio.available()){ // Если в буфере имеются принятые данные |
093 | radio.read(&data_rx, 1); // Читаем данные в массив data и указываем сколько байт читать |
094 | if (data_rx == ON) { // Если приняли данные по разблокировке геймера |
095 |
096 | //****************************Для отладки *********************************** |
097 | #ifdef DEBUG |
098 | //Serial.println(pipe); |
099 | Serial .println(data_rx); |
100 | #endif |
101 | //*************************************************************************** |
102 | |
103 | button_available = true ; // Разблокировать кнопку |
104 | run_light = true ; // Запустить бегущий огонь |
105 | } |
106 | if (data_rx == OFF) { // Если приняли данные по блокировке геймера |
107 | button_available = false ; // Заблокировать кнопку |
108 | run_light = false ; // Остановить бегущий огонь |
109 | LED_off(); // Включить дежурный LED, остальные погасить |
110 | } |
111 | } |
112 | |
113 | if (button_available) { // Если кнопка разблокирована |
114 | //---------------- Обработка нажатия кнопки active-low----------------- |
115 | if (digitalRead(BUTTON) == LOW) |
116 | { |
117 | if (sw_was_pressed == 0) // only on initial press, so the keystroke is not repeated while the button is held down |
118 | { |
119 | radio.stopListening(); // First, stop listening so we can talk. |
120 | radio.write( &data_tx, 1); // Передать ведущему, что кнопку нажали |
121 | radio.startListening(); // Now, continue listening |
122 | |
123 | delay(5); // debounce delay |
124 | } |
125 | sw_was_pressed = 1; |
126 | } |
127 | else |
128 | { |
129 | if (sw_was_pressed != 0) { |
130 | delay(5); // debounce delay |
131 | } |
132 | sw_was_pressed = 0; |
133 | |
134 | } |
135 | //------------------------------------------------------------------------ |
136 | } |
137 | Running_Light(); // Бегущий огонь |
138 | } |
139 |
140 | //-------------------------Running Light-------------------------- |
141 | void Running_Light ( void ) { |
142 | if (((millis() - changeTime) > ledDelay) && run_light == true ) { |
143 | digitalWrite(ledPin[currentLed-1], LOW); //выключаем предыдущий LED |
144 | digitalWrite(ledPin[currentLed], HIGH); // включаем текущий LED |
145 | if (currentLed++ == ARRAY_SIZE) |
146 | currentLed = 0; |
147 | changeTime = millis(); |
148 | } |
149 | } |
150 | //--------------------------LED off-------------------------------- |
151 | void LED_off ( void ) { |
152 | for ( int i = 0; i < ARRAY_SIZE; i++) { |
153 | digitalWrite(ledPin[i], LOW); |
154 | } |
155 | analogWrite(LED2, BRIGHNESS); //включить режим ожидания LED2 |
156 | } |