REFLEXES MACHINE — A Reaction Game That Starts When It Sees You (Arduino UNO Q & 12 Modulinos)
An arcade reaction game that challenges you the moment you walk up to it. A USB camera detects your face and the countdown begins on its own — or you hit Start, if you prefer. Four illuminated arcade buttons, eight Modulino LED Matrix displays running a live stopwatch, and sound effects on the Linux side. Built entirely on the Arduino UNO Q, using both of its brains at once.
Devices & Components
1
Modulino™ Thermo
1
Arduino® UNO™ Q 4GB
8
Modulino™ LED Matrix
4
Modulino™ Pixels
32
Led 3.3V 20mA
4
Transistor NPN S8050
4
Resistor 470Ω
16
Tactile Switch 6x6x4.3mm
1
Speaker USB
1
LiPo battery 11.1V 3S 2200mAh
Hardware & Tools
1
3D printer
Software & Tools
Arduino App Lab
Project description
Code
AddressChanger
cpp
1/* 2 * Modulino - Address Changer 3 * 4 * This utility allows you to change the I2C addresses of Modulino modules. 5 * This is essential when you want to use multiple modules of the same type 6 * on the same I2C bus (e.g., multiple encoders or buttons). 7 * 8 * By default, each Modulino type has a fixed default I2C address. If you connect 9 * two modules of the same type, they will conflict. This tool lets you change 10 * the address to avoid conflicts. 11 * 12 * How to use: 13 * 1. Connect the Arduino and open the Serial Monitor (115200 baud) 14 * 2. The tool will show all detected Modulino devices with their addresses 15 * 3. Enter commands in this format: "current_address new_address" 16 * Examples: 17 * - "0x3E 0x3F" - Changes device at 0x3E to address 0x3F 18 * - "0x3E 0" - Resets device at 0x3E to its default address 19 * - "0 0" - Resets ALL devices to their default addresses (broadcast) 20 * 21 * IMPORTANT NOTES: 22 * - Valid I2C addresses range from 0x08 to 0x77 23 * - Some devices have fixed addresses and cannot be changed (Distance, Thermo, Movement) 24 * - The new address is stored in the module's memory permanently 25 * - After changing addresses, power cycle the modules to ensure changes take effect 26 * 27 * Default addresses by module type: 28 * - Buzzer: 0x1E (pinstrap 0x3C) 29 * - Joystick: 0x2C (pinstrap 0x58) 30 * - Buttons: 0x3E (pinstrap 0x7C) 31 * - Opto Relay: 0x14 (pinstrap 0x28) 32 * - Encoder: 0x3B or 0x3A (pinstrap 0x76 or 0x74) 33 * - Smartleds: 0x36 (pinstrap 0x6C) 34 * - Vibro: 0x38 (pinstrap 0x70) 35 * - Distance: 0x29 (fixed, cannot change) 36 * - Thermo: 0x44 (fixed, cannot change) 37 * - Movement: 0x6A or 0x6B (fixed, cannot change) 38 * 39 * This example code is in the public domain. 40 * Copyright (c) 2025 Arduino 41 * SPDX-License-Identifier: MPL-2.0 42 */ 43 44#include "Wire.h" 45 46// Structure to store information about detected Modulino devices 47struct DetectedModulino { 48 uint8_t addr; // Current I2C address 49 String modulinoType; // Type of module (e.g., "Buzzer", "Encoder") 50 String pinstrap; // Pinstrap value (identifies device type) 51 String defaultAddr; // Default address for this module type 52}; 53 54#define MAX_DEVICES 16 55DetectedModulino rows[MAX_DEVICES]; // Array to store detected devices 56int numRows = 0; // Number of devices currently detected 57 58 59void setup() { 60 // Initialize I2C communication on Wire1 interface 61 Wire1.begin(); 62 // Initialize serial communication at 115200 baud 63 Serial.begin(115200); 64 65 // Wait for serial port to initialize 66 while (!Serial) {}; 67 68 // Scan I2C bus and display all detected Modulino devices 69 discoverDevices(); 70} 71 72bool waitingInput = false; 73 74void loop() { 75 // If no devices detected, nothing to do 76 if (numRows == 0) return; 77 // If waiting for input and nothing available, keep waiting 78 if (Serial.available() == 0 && waitingInput) return; 79 80 // Process user input when available 81 if (Serial.available() > 0) { 82 // Read two hexadecimal values separated by space 83 String hex1 = Serial.readStringUntil(' '); // Current address 84 String hex2 = Serial.readStringUntil('\n'); // New address 85 // Echo back what user entered 86 Serial.println("> " + hex1 + " " + hex2); 87 88 // Parse the hexadecimal strings to integer values 89 int num1 = parseHex(hex1); // Current address 90 int num2 = parseHex(hex2); // New address 91 92 // Validate input 93 if (num1 == -1 || num2 == -1) { 94 Serial.println("Error: Incomplete or invalid input. Please enter two hexadecimal numbers"); 95 return; 96 } 97 98 // Attempt to update the I2C address 99 bool success = updateI2cAddress(num1, num2); 100 if (!success) return; // If update failed, wait for new input 101 102 // Re-scan devices to show updated addresses 103 discoverDevices(); 104 waitingInput = false; 105 } 106 107 // Display instructions for the user 108 Serial.println("Enter the current address, space, and new address (ex. \"0x20 0x30\" or \"20 2A\"):"); 109 Serial.println(" - Enter \"<addr> 0\" to reset the device at <addr> to its default address."); 110 Serial.println(" - Enter \"0 0\" to reset all devices to the default address."); 111 waitingInput = true; 112} 113 114// Updates the device at current address to new address. Supports broadcasting and setting default address (0). 115// Returns true if the update was successful, false otherwise. 116bool updateI2cAddress(int curAddress, int newAddress) { 117 uint8_t data[48] = { 'C', 'F', newAddress * 2 }; 118 memset(data + 3, 0, sizeof(data) - 3); // Zero the rest of the buffer. 119 120 // Validate the current address, it must match a detected device. 121 if (curAddress != 0 && !findRow(curAddress)) { 122 Serial.println("Error: current address 0x" + String(curAddress, HEX) + " not found in the devices list\n"); 123 return false; 124 } 125 126 if (curAddress != 0 && isFixedAddrDevice(curAddress)) { 127 Serial.println("Error: address 0x" + String(curAddress, HEX) + " is a non configurable device\n"); 128 return false; 129 } 130 131 // Validate the new address. 132 if (newAddress != 0 && (newAddress < 8 || newAddress > 0x77)) { 133 Serial.println("Error: new address 0x" + String(newAddress, HEX) + " must be from 0x08 to 0x77\n"); 134 return false; 135 } 136 137 if (curAddress == 0) { 138 Serial.print("Updating all devices (broadcast 0x00) to 0x" + String(newAddress, HEX)); 139 } else { 140 Serial.print("Updating the device address from 0x" + String(curAddress, HEX) + " to 0x" + String(newAddress, HEX)); 141 } 142 if (newAddress == 0) Serial.print(" (default address)"); 143 Serial.print("..."); 144 145 Wire1.beginTransmission(curAddress); 146 Wire1.write(data, 40); 147 Wire1.endTransmission(); 148 149 delay(500); 150 151 if (newAddress == 0) { 152 Serial.println(" done\n"); 153 return true; 154 } else { 155 Wire1.requestFrom(newAddress, 1); 156 if (Wire1.available()) { 157 Serial.println(" done\n"); 158 return true; 159 } else { 160 Serial.println(" error\n"); 161 return false; 162 } 163 } 164} 165 166// Function to parse hex number (with or without 0x prefix) 167int parseHex(String hexStr) { 168 hexStr.trim(); 169 170 if (hexStr.length() == 0) { 171 return -1; 172 } 173 174 if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) { 175 hexStr = hexStr.substring(2); // Remove the "0x" prefix 176 } 177 178 // Validate that the remaining string contains only valid hexadecimal characters (0-9, A-F, a-f) 179 for (int i = 0; i < hexStr.length(); i++) { 180 if (!isHexDigit(hexStr.charAt(i))) { 181 return -1; 182 } 183 } 184 185 return strtol(hexStr.c_str(), NULL, 16); 186} 187 188bool isHexDigit(char c) { 189 return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); 190} 191 192void discoverDevices() { 193 char buffer[64]; 194 Serial.println("ADDR\tMODULINO\tPINSTRAP\tDEFAULT ADDR"); // Table heading. 195 196 numRows = 0; 197 198 // Discover all modulino devices connected to the I2C bus. 199 for (int addr = 0; addr < 128; addr++) { 200 Wire1.beginTransmission(addr); 201 if (Wire1.endTransmission() != 0) continue; 202 203 if (numRows >= MAX_DEVICES) { 204 Serial.println("Too many devices connected, maximum supported is" + String(MAX_DEVICES)); 205 return; 206 } 207 208 // Some addresses represent non configurable devices (no MCU on it). Handle them as a special case. 209 if (isFixedAddrDevice(addr)) { 210 snprintf(buffer, 64, "0x%02X (cannot change)", addr); 211 addRow(addr, fixedAddrToName(addr), "-", String(buffer)); 212 213 continue; // Stop here, do not try to communicate with this device. 214 } 215 216 { 217 uint8_t pinstrap = 0; // Variable to store the pinstrap (device type) 218 Wire1.beginTransmission(addr); // Begin I2C transmission to the current address 219 Wire1.write(0x00); // Send a request to the device (assuming 0x00 is the register for device type) 220 Wire1.endTransmission(); // End transmission 221 222 delay(50); // Delay to allow for the device to respond 223 224 Wire1.requestFrom(addr, 1); // Request 1 byte from the device at the current address 225 if (Wire1.available()) { 226 pinstrap = Wire1.read(); // Read the device type (pinstrap) 227 } else { 228 // If an error happens in the range 0x78 to 0x7F, ignore it. 229 if (addr >= 0x78) continue; 230 Serial.println("Failed to read device type at address 0x" + String(addr, HEX)); 231 } 232 233 snprintf(buffer, 64, "0x%02X", pinstrap); 234 auto hexPinstrap = String(buffer); 235 236 snprintf(buffer, 64, "0x%02X", pinstrap / 2); // Default address is half pinstrap. 237 auto defaultAddr = String(buffer); 238 if (addr != pinstrap / 2) defaultAddr += " *"; // Mark devices with modified address. 239 240 addRow(addr, pinstrapToName(pinstrap), hexPinstrap, defaultAddr); 241 } 242 } 243 244 // Print the results. 245 for (int i = 0; i < numRows; i++) { 246 char buffer[16]; 247 snprintf(buffer, 16, "0x%02X", rows[i].addr); 248 249 Serial.print(fixedWidth(buffer, 8)); 250 Serial.print(fixedWidth(rows[i].modulinoType, 16)); 251 Serial.print(fixedWidth(rows[i].pinstrap, 16)); 252 Serial.println(fixedWidth(rows[i].defaultAddr, 12)); 253 } 254} 255 256void addRow(uint8_t address, String modulinoType, String pinstrap, String defaultAddr) { 257 if (numRows >= MAX_DEVICES) return; 258 259 rows[numRows].addr = address; 260 rows[numRows].modulinoType = modulinoType; 261 rows[numRows].pinstrap = pinstrap; 262 rows[numRows].defaultAddr = defaultAddr; 263 numRows++; // Increment the row counter 264} 265 266bool findRow(uint8_t address) { 267 for (int i = 0; i < numRows; i++) { 268 if (rows[i].addr == address) return true; 269 } 270 return false; 271} 272 273 274// Function to add padding to the right to ensure each field has a fixed width 275String fixedWidth(String str, int width) { 276 for (int i = str.length(); i < width; i++) str += ' '; 277 return str; 278} 279 280String pinstrapToName(uint8_t pinstrap) { 281 switch (pinstrap) { 282 case 0x04: 283 return "Latch Relay"; 284 case 0x3C: 285 return "Buzzer"; 286 case 0x58: 287 return "Joystick"; 288 case 0x7C: 289 return "Buttons"; 290 case 0x28: 291 return "Opto Relay"; 292 case 0x76: 293 case 0x74: 294 return "Encoder"; 295 case 0x6C: 296 return "Smartleds"; 297 case 0x70: 298 return "Vibro"; 299 case 0x48: 300 return "Motors"; 301 } 302 return "UNKNOWN"; 303} 304 305String fixedAddrToName(uint8_t address) { 306 switch (address) { 307 case 0x29: 308 return "Distance"; 309 case 0x44: 310 return "Thermo"; 311 case 0x6A: 312 case 0x6B: 313 return "Movement"; 314 } 315 return "UNKNOWN"; 316} 317 318bool isFixedAddrDevice(uint8_t addr) { 319 // List of non-configurable devices, recognized by their fixed I2C address. 320 const uint8_t fixedAddr[] = { 0x29, 0x44, 0x6A, 0x6B }; 321 322 for (int i = 0; i < sizeof(fixedAddr) / sizeof(fixedAddr[0]); i++) { 323 if (addr == fixedAddr[i]) return true; 324 } 325 return false; 326}
TestReflexesMachine
1/* 2 * Modulino Bus Test — Arduino UNO Q 3 * --------------------------------------------------------------- 4 * Verifica tutti i moduli sul bus Qwiic (Wire1): 5 * - 4x Pixels @ 0x50 0x51 0x52 0x53 (punto mobile, colore diverso per strip) 6 * - 8x LED Matrix @ 0x54 ... 0x5B (ogni matrice mostra il proprio address) 7 * - 1x Thermo @ 0x44 (fisso) (temp + umidita' su Serial) 8 * 9 * Il check di presenza reale e' un ping I2C diretto su Wire1: 10 * begin() con address esplicito non scansiona il bus. 11 * 12 * Serial Monitor @ 115200. 13 */ 14 15#include <Wire.h> 16#include <Arduino_Modulino.h> 17#include <Modulino_LED_Matrix.h> 18 19// ---------------- moduli ---------------- 20ModulinoPixels px[4] = { 21 ModulinoPixels(0x50), ModulinoPixels(0x51), 22 ModulinoPixels(0x52), ModulinoPixels(0x53) 23}; 24const uint8_t pxAddr[4] = { 0x50, 0x51, 0x52, 0x53 }; 25 26ModulinoLEDMatrix mtx[8] = { 27 ModulinoLEDMatrix(0x54), ModulinoLEDMatrix(0x55), 28 ModulinoLEDMatrix(0x56), ModulinoLEDMatrix(0x57), 29 ModulinoLEDMatrix(0x58), ModulinoLEDMatrix(0x59), 30 ModulinoLEDMatrix(0x5A), ModulinoLEDMatrix(0x5B) 31}; 32const uint8_t mtxAddr[8] = { 0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B }; 33 34ModulinoThermo thermo; // 0x44 fisso, nessun address nel costruttore 35 36// colore distinto per ognuna delle 4 strip Pixels 37const ModulinoColor pxColor[4] = { 38 ModulinoColor(255, 0, 0), // 0x50 rosso 39 ModulinoColor( 0, 255, 0), // 0x51 verde 40 ModulinoColor( 0, 0, 255), // 0x52 blu 41 ModulinoColor(255, 160, 0) // 0x53 ambra 42}; 43 44// ------------- font hex 3x5 (bit2 = colonna sinistra) ------------- 45const uint8_t FONT[16][5] = { 46 {0b111,0b101,0b101,0b101,0b111}, // 0 47 {0b010,0b110,0b010,0b010,0b111}, // 1 48 {0b111,0b001,0b111,0b100,0b111}, // 2 49 {0b111,0b001,0b111,0b001,0b111}, // 3 50 {0b101,0b101,0b111,0b001,0b001}, // 4 51 {0b111,0b100,0b111,0b001,0b111}, // 5 52 {0b111,0b100,0b111,0b101,0b111}, // 6 53 {0b111,0b001,0b010,0b010,0b010}, // 7 54 {0b111,0b101,0b111,0b101,0b111}, // 8 55 {0b111,0b101,0b111,0b001,0b111}, // 9 56 {0b111,0b101,0b111,0b101,0b101}, // A 57 {0b110,0b101,0b110,0b101,0b110}, // B 58 {0b111,0b100,0b100,0b100,0b111}, // C 59 {0b110,0b101,0b101,0b101,0b110}, // D 60 {0b111,0b100,0b111,0b100,0b111}, // E 61 {0b111,0b100,0b111,0b100,0b100} // F 62}; 63 64// scrive una cifra hex (3x5) nella griglia [riga][colonna] a partire da (xOff, yOff) 65// grid[y][x] = 1 -> LED acceso 66void drawGlyph(uint8_t grid[8][12], uint8_t nibble, uint8_t xOff, uint8_t yOff) { 67 for (uint8_t r = 0; r < 5; r++) { 68 uint8_t bits = FONT[nibble & 0x0F][r]; 69 for (uint8_t c = 0; c < 3; c++) { 70 if (bits & (1 << (2 - c))) grid[yOff + r][xOff + c] = 1; 71 } 72 } 73} 74 75// mostra il byte come 2 cifre hex, centrato sulla matrice 12x8 76void showAddr(ModulinoLEDMatrix &m, uint8_t value) { 77 uint8_t grid[8][12] = {{0}}; 78 drawGlyph(grid, value >> 4, 2, 1); // cifra alta -> colonne 2..4 79 drawGlyph(grid, value & 0x0F, 6, 1); // cifra bassa -> colonne 6..8 80 81 // converti in frame da 12 byte, layout MonochromaticVertical (default libreria): 82 // per ogni pixel acceso (riga y, colonna x) -> frame[x] |= (1 << y) 83 uint8_t frame[12] = {0}; 84 for (uint8_t y = 0; y < 8; y++) 85 for (uint8_t x = 0; x < 12; x++) 86 if (grid[y][x]) frame[x] |= (1 << y); 87 88 m.setFrame(frame); 89} 90 91// ping I2C diretto: check di presenza affidabile sul bus Wire1 92bool present(uint8_t addr) { 93 Wire1.beginTransmission(addr); 94 return (Wire1.endTransmission() == 0); 95} 96 97void report(const char* label, uint8_t addr, bool ok) { 98 Serial.print(label); 99 Serial.print(" 0x"); 100 if (addr < 0x10) Serial.print('0'); 101 Serial.print(addr, HEX); 102 Serial.println(ok ? " [OK]" : " [--- ASSENTE]"); 103} 104 105void setup() { 106 Serial.begin(115200); 107 while (!Serial) {} 108 Modulino.begin(); // inizializza Wire1 (bus Modulino su UNO Q) 109 110 Serial.println("\n===== Modulino bus test ====="); 111 112 // Pixels 113 for (int i = 0; i < 4; i++) { 114 px[i].begin(); 115 report("Pixels", pxAddr[i], present(pxAddr[i])); 116 } 117 118 // LED Matrix: init + scrivi l'address sulla matrice se presente 119 for (int i = 0; i < 8; i++) { 120 mtx[i].begin(); 121 bool ok = present(mtxAddr[i]); 122 report("Matrix", mtxAddr[i], ok); 123 if (ok) showAddr(mtx[i], mtxAddr[i]); 124 } 125 126 // Thermo (begin() init davvero il sensore) 127 bool okT = thermo.begin(); 128 report("Thermo", 0x44, okT); 129 130 Serial.println("=============================\n"); 131} 132 133void loop() { 134 static uint8_t dot = 0; 135 136 // punto mobile su ogni strip Pixels, colore per strip 137 for (int i = 0; i < 4; i++) { 138 px[i].clear(); 139 px[i].set(dot % 8, pxColor[i], 40); // brightness 0..100 140 px[i].show(); 141 } 142 dot++; 143 144 // lettura Thermo (solo se init ok) 145 if (thermo) { 146 Serial.print("T: "); Serial.print(thermo.getTemperature(), 1); 147 Serial.print(" C RH: "); Serial.print(thermo.getHumidity(), 0); 148 Serial.println(" %"); 149 } 150 151 delay(200); 152}
Arduino App Lab
reflexes
😀
reflexes
Downloadable files
reflexes_machine
reflexes_machine.zip
Documentation
REFLEXES MACHINE BUILD
REFLEXES MACHINE BUILD.pdf
reflexes_bom
reflexes_bom.pdf
Comments
Only logged in users can leave comments