Secfest2026BadgeCode/hardware_test/src/main.cpp

357 lines
9.6 KiB
C++

// Secfest 2026 Badge — Hardware Test Sketch
// Tests: buttons, front LEDs, flashlight LED, IS31FL3731 LED matrix, I2C bus scan, scrolling text
#include <Arduino.h>
#include <Wire.h>
#include <string.h>
#include <Adafruit_IS31FL3731.h>
bool hidMode = false;
// --- Buttons (active LOW, internal pull-ups) ---
// Per schematic: BTN_A=GP8, B=GP9, UP=GP10, DWN=GP11, LFT=GP12, RGT=GP13
#define BTN_A 8
#define BTN_B 9
#define BTN_UP 10
#define BTN_DOWN 11
#define BTN_LEFT 12
#define BTN_RIGHT 13
const int BUTTONS[] = { BTN_A, BTN_B, BTN_UP, BTN_DOWN, BTN_LEFT, BTN_RIGHT };
const char* BTN_NAMES[] = { "A", "B", "Up", "Down", "Left", "Right" };
const int NUM_BUTTONS = 6;
// --- LEDs ---
#define LED_FLASHLIGHT 15 // MOSFET gate, HIGH = on
#define LED_FRONT_1 23
#define LED_FRONT_2 24
#define LED_FRONT_3 25
#define LED_FRONT_4 26
const int FRONT_LEDS[] = { LED_FRONT_1, LED_FRONT_2, LED_FRONT_3, LED_FRONT_4 };
const int NUM_LEDS = 4;
// Front LEDs are wired active-low (anode to 3V3, cathode to GPIO via series R).
// Drive LOW to light them; HIGH (or open-drain Hi-Z) to turn them off.
const int FRONT_LED_ON = LOW;
const int FRONT_LED_OFF = HIGH;
// --- IS31FL3731 LED matrix ---
// Dedicated I2C bus per schematic: SDA_LED=GPIO4, SCL_LED=GPIO5, address 0x74.
// GP4/GP5 are I2C0 pins on the RP2040, so the matrix uses `Wire`.
#define LED_MATRIX_SDA 4
#define LED_MATRIX_SCL 5
#define IS31_ADDR 0x74
// --- Main I2C bus (user / SAO) ---
// Per schematic: SDA=GPIO2, SCL=GPIO3.
// GP2/GP3 are I2C1 pins on the RP2040, so the SAO bus uses `Wire1`.
#define MAIN_SDA 2
#define MAIN_SCL 3
// Wire = LED matrix bus (GPIO4/5, I2C0)
// Wire1 = main / SAO bus (GPIO2/3, I2C1)
Adafruit_IS31FL3731* matrix = nullptr;
static const int MATRIX_W = 9;
static const int MATRIX_H = 16;
static inline bool matrixPixelIsDead(int x, int y) {
return x == y;
}
static inline void matrixSetPixel(int x, int y, uint8_t brightness) {
if (x < 0 || x >= MATRIX_W || y < 0 || y >= MATRIX_H) return;
matrix->drawPixel(x, (MATRIX_H - 1) - y, brightness);
}
static void matrixFill(uint8_t brightness) {
for (int y = 0; y < MATRIX_H; y++)
for (int x = 0; x < MATRIX_W; x++)
matrixSetPixel(x, y, brightness);
}
// ------------------------------------------------------------------ helpers --
void blinkFrontLeds(int times, int ms) {
for (int i = 0; i < times; i++) {
for (int p : FRONT_LEDS) digitalWrite(p, FRONT_LED_ON);
delay(ms);
for (int p : FRONT_LEDS) digitalWrite(p, FRONT_LED_OFF);
delay(ms);
}
}
void scanI2C(TwoWire& bus, const char* label) {
Serial.print("\nI2C scan — ");
Serial.print(label);
Serial.println(":");
int found = 0;
for (uint8_t addr = 1; addr < 127; addr++) {
bus.beginTransmission(addr);
if (bus.endTransmission() == 0) {
Serial.print(" Device at 0x");
if (addr < 16) Serial.print('0');
Serial.println(addr, HEX);
found++;
}
}
if (!found) Serial.println(" (none found)");
}
// ------------------------------------------------------------------- animation state ---
enum AnimState {
ANIM_ALL_ON,
ANIM_CORNERS,
ANIM_ROW_SWEEP,
ANIM_COL_SWEEP,
ANIM_CHEVRON,
ANIM_DRIVEABLE,
ANIM_TEXT_SCROLL,
ANIM_COUNT
};
AnimState animState = ANIM_ALL_ON;
uint32_t animStartTime = 0;
const uint32_t ANIM_DURATION = 2000;
int scrollPos = 0;
struct Point { int x; int y; };
const Point corners[4] = {
{0, 0},
{MATRIX_W - 1, 0},
{0, MATRIX_H - 1},
{MATRIX_W - 1, MATRIX_H - 1}
};
void runAnimation() {
if (!matrix) return;
uint32_t elapsed = millis() - animStartTime;
switch (animState) {
case ANIM_ALL_ON:
matrix->clear();
matrixFill(50);
if (elapsed > ANIM_DURATION) {
animState = ANIM_CORNERS;
animStartTime = millis();
}
break;
case ANIM_CORNERS: {
int corner = (elapsed / 500) % 4;
matrix->clear();
matrixSetPixel(corners[corner].x, corners[corner].y, 220);
if (elapsed > ANIM_DURATION) {
animState = ANIM_ROW_SWEEP;
animStartTime = millis();
}
break;
}
case ANIM_ROW_SWEEP: {
int y = (elapsed / 100) % MATRIX_H;
matrix->clear();
for (int x = 0; x < MATRIX_W; x++) matrixSetPixel(x, y, 180);
if (elapsed > ANIM_DURATION) {
animState = ANIM_COL_SWEEP;
animStartTime = millis();
}
break;
}
case ANIM_COL_SWEEP: {
int x = (elapsed / 100) % MATRIX_W;
matrix->clear();
for (int y = 0; y < MATRIX_H; y++) matrixSetPixel(x, y, 180);
if (elapsed > ANIM_DURATION) {
animState = ANIM_CHEVRON;
animStartTime = millis();
}
break;
}
case ANIM_CHEVRON: {
static const uint8_t chevron[9][9] = {
{0,0,0,0,0,0,0,1,0},
{0,0,0,0,0,0,1,0,1},
{0,0,0,0,0,1,0,1,0},
{0,0,0,0,1,0,1,0,0},
{0,0,0,1,0,1,0,0,0},
{0,0,1,0,1,0,0,0,0},
{0,1,0,1,0,0,0,0,0},
{1,0,1,0,0,0,0,0,0},
{0,1,0,0,0,0,0,0,0},
};
matrix->clear();
for (int y = 0; y < 9; y++)
for (int x = 0; x < MATRIX_W; x++)
matrixSetPixel(x, y, chevron[y][x] ? 200 : 0);
if (elapsed > ANIM_DURATION) {
animState = ANIM_DRIVEABLE;
animStartTime = millis();
}
break;
}
case ANIM_DRIVEABLE: {
matrix->clear();
for (int y = 0; y < MATRIX_H; y++)
for (int x = 0; x < MATRIX_W; x++)
if (!matrixPixelIsDead(x, y)) matrixSetPixel(x, y, 60);
if (elapsed > ANIM_DURATION) {
animState = ANIM_TEXT_SCROLL;
animStartTime = millis();
scrollPos = 16;
}
break;
}
case ANIM_TEXT_SCROLL: {
matrix->clear();
matrix->setTextSize(1);
matrix->setTextColor(255);
const char* scrollText = "Welcome to Securityfest 2026 ";
matrix->setCursor(16 - scrollPos, 1);
matrix->print(scrollText);
scrollPos++;
if (scrollPos > strlen(scrollText) * 6 + 16) {
animState = ANIM_ALL_ON;
}
delay(60);
break;
}
default:
animState = ANIM_ALL_ON;
break;
}
}
// ------------------------------------------------------------------- setup ---
void setup() {
pinMode(BTN_A, INPUT_PULLUP);
if (digitalRead(BTN_A) == LOW) {
hidMode = true;
Serial.end();
} else {
Serial.begin(115200);
delay(2000);
Serial.println("\n=== Secfest 2026 Badge Hardware Test ===\n");
}
for (int pin : BUTTONS) pinMode(pin, INPUT_PULLUP);
Serial.println("[Buttons] GP8-GP13 configured with pull-ups");
for (int pin : FRONT_LEDS) { pinMode(pin, OUTPUT); digitalWrite(pin, FRONT_LED_OFF); }
pinMode(LED_FLASHLIGHT, OUTPUT);
digitalWrite(LED_FLASHLIGHT, LOW);
Serial.println("[LEDs] GPIO 15, 23-26 configured");
Serial.println("\n[Test] Front LEDs — sweeping both polarities");
for (int i = 0; i < NUM_LEDS; i++) {
int pin = FRONT_LEDS[i];
Serial.print(" FRONT_LED_"); Serial.print(i + 1);
Serial.print(" on GP"); Serial.println(pin);
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
delay(50);
digitalWrite(pin, LOW);
delay(50);
pinMode(pin, INPUT);
delay(200);
pinMode(pin, OUTPUT);
digitalWrite(pin, FRONT_LED_OFF);
}
Serial.println("[Test] Flashlight LED — 3 pulses");
for (int i = 0; i < 3; i++) {
digitalWrite(LED_FLASHLIGHT, HIGH);
delay(150);
digitalWrite(LED_FLASHLIGHT, LOW);
delay(150);
}
Wire.setSDA(LED_MATRIX_SDA);
Wire.setSCL(LED_MATRIX_SCL);
Wire.begin();
Serial.println("\n[Test] IS31FL3731 LED matrix (9x9) on GP4/GP5");
matrix = new Adafruit_IS31FL3731(9, 16);
if (!matrix->begin(IS31_ADDR, &Wire)) {
Serial.println(" ERROR: IS31FL3731 not found at 0x74");
matrix = nullptr;
} else {
Serial.println(" Found! Starting animation loop...");
animStartTime = millis();
}
Wire1.setSDA(MAIN_SDA);
Wire1.setSCL(MAIN_SCL);
Wire1.begin();
scanI2C(Wire, "matrix bus (SDA=GP4, SCL=GP5)");
scanI2C(Wire1, "main bus (SDA=GP2, SCL=GP3)");
Serial.println("\n[Ready] Animation loop running. Hold Button A to reset.\n");
blinkFrontLeds(3, 100);
}
// -------------------------------------------------------------------- TinyUSB HID ---
extern "C" {
bool tud_hid_n_report_complete_cb(uint8_t itf, uint8_t const* report, uint16_t len) {
(void)itf; (void)report; (void)len;
return true;
}
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, uint8_t report_type, uint8_t const* buffer, uint16_t bufsize) {
(void)itf; (void)report_id; (void)report_type; (void)buffer; (void)bufsize;
}
uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, uint8_t report_type, uint8_t* buffer, uint16_t bufsize) {
(void)itf; (void)report_id; (void)report_type;
if (bufsize < 1) return 0;
uint8_t btn = 0;
for (int i = 0; i < NUM_BUTTONS; i++) {
if (digitalRead(BUTTONS[i]) == LOW) btn |= (1 << i);
}
buffer[0] = btn;
return 1;
}
}
// -------------------------------------------------------------------- loop ---
bool prevState[NUM_BUTTONS] = {};
void loop() {
if (digitalRead(BTN_A) == LOW && !prevState[0]) {
Serial.println("\n[Reset] Rebooting...");
delay(100);
rp2040.reboot();
}
if (matrix) {
runAnimation();
}
for (int i = 0; i < NUM_BUTTONS; i++) {
bool pressed = (digitalRead(BUTTONS[i]) == LOW);
if (pressed && !prevState[i]) {
Serial.print("[Button] ");
Serial.println(BTN_NAMES[i]);
for (int p : FRONT_LEDS) digitalWrite(p, HIGH);
delay(60);
for (int p : FRONT_LEDS) digitalWrite(p, LOW);
delay(60);
for (int p : FRONT_LEDS) digitalWrite(p, FRONT_LED_OFF);
}
prevState[i] = pressed;
}
delay(10);
}