195 lines
5.1 KiB
Arduino
195 lines
5.1 KiB
Arduino
// ===========================
|
||||
|
|
// 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 <Arduino.h>
|
|||
|
|
#include <WiFi.h>
|
|||
|
|
#include <esp_now.h>
|
|||
|
|
#include <esp_task_wdt.h>
|
|||
|
|
#include <ArduinoOTA.h>
|
|||
|
|
#include <Adafruit_SSD1306.h>
|
|||
|
|
#include <Wire.h>
|
|||
|
|
|
|||
|
|
// ======= 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();
|
|||
|
|
}
|
|||
|
|
}
|