// =========================== // Wireless Rocket Launch System v2 // Implements: MOSFET output, interrupts, state machine, non-blocking scheduler, // ESP-NOW encryption, CRC16, sequence numbering, OTA updates, watchdog, heartbeat, // RSSI monitoring, Adafruit SSD1306 OLED UI. // =========================== #include #include #include #include #include #include #include // ======= Pin Definitions ======= // Transmitter pins #define TX_PIN_ARM_SWITCH 14 // toggle, interrupt #define TX_PIN_LAUNCH_BTN 15 // push, interrupt #define TX_PIN_POWER_BTN 16 // push, interrupt #define TX_LED_PWR 26 #define TX_LED_ARMED 12 #define TX_LED_CONN 27 #define TX_LED_PULSE 13 #define TX_OLED_SDA SDA #define TX_OLED_SCL SCL // Receiver pins #define RX_PIN_MOSFET1 14 // MOSFET channel 1 #define RX_PIN_MOSFET2 25 // MOSFET channel 2 (redundant) #define RX_PIN_CONTINUITY 15 // analog sense via ADC #define RX_PIN_PWR_BTN 17 // momentary, interrupt #define RX_LED_PWR 26 #define RX_LED_CONN 27 #define RX_OLED_SDA SDA #define RX_OLED_SCL SCL // OLED settings #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 32 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); // ======= ESP-NOW Encryption Key ======= uint8_t espnow_key[16] = { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08, 0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10 }; // ======= Packet Structure ======= struct __attribute__((packed)) CommandMessage { bool arm; bool launch; uint16_t seq; uint16_t crc; }; // ======= Globals ======= volatile bool armRequested = false; volatile bool launchRequested = false; volatile bool powerOn = false; uint16_t txSeq = 0; unsigned long lastSend = 0; // Transmitter MAC (to be filled with receiver MAC) uint8_t peerMac[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; // ======= CRC16-CCITT ======= uint16_t crc16_ccitt(const uint8_t *data, size_t len) { uint16_t crc = 0xFFFF; while (len--) { crc ^= (uint16_t)*data++ << 8; for (uint8_t i = 0; i < 8; i++) { if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; else crc <<= 1; } } return crc; } // ======= Interrupt Handlers ======= void IRAM_ATTR handleArmSwitch() { armRequested = (digitalRead(TX_PIN_ARM_SWITCH) == LOW); } void IRAM_ATTR handleLaunchBtn() { if (armRequested && digitalRead(TX_PIN_LAUNCH_BTN) == LOW) { launchRequested = true; } } void IRAM_ATTR handlePowerBtn() { powerOn = (digitalRead(TX_PIN_POWER_BTN) == LOW); } // ======= ESP-NOW Send Callback ======= void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { digitalWrite(TX_LED_CONN, status == ESP_NOW_SEND_SUCCESS); } // ======= Setup ======= void setup() { // Serial & Watchdog Serial.begin(115200); esp_task_wdt_init(5, true); esp_task_wdt_add(NULL); // Pins pinMode(TX_PIN_ARM_SWITCH, INPUT_PULLUP); pinMode(TX_PIN_LAUNCH_BTN, INPUT_PULLUP); pinMode(TX_PIN_POWER_BTN, INPUT_PULLUP); pinMode(TX_LED_PWR, OUTPUT); pinMode(TX_LED_ARMED, OUTPUT); pinMode(TX_LED_CONN, OUTPUT); pinMode(TX_LED_PULSE, OUTPUT); // Attach interrupts attachInterrupt(TX_PIN_ARM_SWITCH, handleArmSwitch, CHANGE); attachInterrupt(TX_PIN_LAUNCH_BTN, handleLaunchBtn, FALLING); attachInterrupt(TX_PIN_POWER_BTN, handlePowerBtn, CHANGE); // OLED Wire.begin(TX_OLED_SDA, TX_OLED_SCL); if (display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.println("Transmitter v2 ready"); display.display(); } // Power LED if (!powerOn) { Serial.println("Power button not pressed. Halting."); while (!powerOn) esp_task_wdt_reset(); } digitalWrite(TX_LED_PWR, HIGH); // Wi‑Fi & ESP-NOW WiFi.mode(WIFI_STA); esp_now_init(); esp_now_register_send_cb(onDataSent); esp_now_peer_info_t peer = {}; memcpy(peer.peer_addr, peerMac, 6); peer.channel = 0; peer.encrypt = true; memcpy(peer.lmk, espnow_key, 16); esp_now_add_peer(&peer); // OTA ArduinoOTA.begin(); } // ======= Main Loop ======= void loop() { esp_task_wdt_reset(); // OTA handling ArduinoOTA.handle(); // State updates digitalWrite(TX_LED_ARMED, armRequested); // Send heartbeat/command every 100ms if (millis() - lastSend >= 100) { lastSend = millis(); CommandMessage msg; msg.arm = armRequested; msg.launch = launchRequested; msg.seq = txSeq++; msg.crc = crc16_ccitt((uint8_t*)&msg, sizeof(msg) - sizeof(msg.crc)); esp_now_send(peerMac, (uint8_t*)&msg, sizeof(msg)); launchRequested = false; // Pulse LED on launch request if (msg.launch) { analogWrite(TX_LED_PULSE, 255); } else { analogWrite(TX_LED_PULSE, 0); } // Optionally read and display RSSI int8_t rssi; esp_now_fetch_peer(peerMac, nullptr); esp_now_get_peer_rssi(peerMac, &rssi); display.clearDisplay(); display.setCursor(0,0); display.printf("ARM:%d LAUNCH:%d", msg.arm, msg.launch); display.setCursor(0,10); display.printf("RSSI:%ddBm", rssi); display.display(); } }