// ON4CRD RK-SWR R2. Nano classic / ATmega328P, 5 V, 16 MHz, Optiboot.
// Six measured knots per band/direction. R1 calibration is NOT compatible.
// This is a steady-carrier QRP meter, not PEP or a PA protection interlock.
#include <EEPROM.h>
#include <math.h>
#include <stddef.h>
#include <avr/wdt.h>

// Explicit forward declarations keep Arduino's generated prototypes valid.
struct Calibration;
struct Sample;

const uint8_t RS = 8, EN = 9, DATA[4] = {10, 11, 12, 13};
const uint8_t MEAS_ENABLE = 4;
const uint8_t KNOTS = 6, BANDS = 9, CHANNELS = 2, COMPLETE = 63;
const uint16_t powersMw[KNOTS] = {50, 100, 200, 1000, 5000, 10000};
const char *const bands[BANDS] = {"1.8", "3.5", "7", "10", "14", "18", "21", "24", "28"};
const uint32_t MAGIC = 0x524B5332UL;
const uint8_t SCHEMA = 2;

void watchdogOffAtStartup() __attribute__((naked, section(".init3")));
void watchdogOffAtStartup() { MCUSR = 0; wdt_disable(); }

void nibble(uint8_t value) {
  for (uint8_t i = 0; i < 4; i++) digitalWrite(DATA[i], (value >> i) & 1);
  digitalWrite(EN, HIGH); delayMicroseconds(1); digitalWrite(EN, LOW); delayMicroseconds(50);
}
void lcdByte(uint8_t value, bool data) { digitalWrite(RS, data); nibble(value >> 4); nibble(value & 15); }
void lcdLine(uint8_t row, const char *text) {
  lcdByte(row ? 0xC0 : 0x80, false);
  for (uint8_t i = 0; i < 16; i++) lcdByte(*text ? *text++ : ' ', true);
}
void lcdInit() {
  pinMode(RS, OUTPUT); pinMode(EN, OUTPUT);
  for (uint8_t pin : DATA) pinMode(pin, OUTPUT);
  delay(40); nibble(3); delay(5); nibble(3); delayMicroseconds(150); nibble(3); nibble(2);
  lcdByte(0x28, false); lcdByte(0x0C, false); lcdByte(0x06, false); lcdByte(1, false); delay(2);
}

struct Calibration {
  uint32_t magic;
  uint8_t schema, knots;
  uint32_t generation;
  uint16_t adc[BANDS][CHANNELS][KNOTS];
  uint8_t mask[BANDS][CHANNELS];
  uint16_t crc;
};
static_assert(sizeof(Calibration) <= 256, "EEPROM slot capacity");
Calibration active, draft;
bool dirty = false, unsaved = false, haveActive = false;
int8_t savedSlot = -1;
uint8_t selectedBand = 0;
bool bandReady = false;
bool referenceFault = true;

// Coarse missing/wrong-reference diagnostic only. Bandgap tolerance prevents
// this from being a precision 3.000 V measurement or a calibration reference.
uint16_t referenceMonitor() {
  uint8_t previous = ADMUX;
  ADMUX = 14; // External AREF, internal nominal 1.1 V bandgap input.
  delay(2);
  uint16_t total = 0;
  for (uint8_t i = 0; i < 9; i++) {
    ADCSRA |= _BV(ADSC);
    while (bit_is_set(ADCSRA, ADSC)) {}
    uint16_t value = ADC;
    if (i) total += value;
  }
  ADMUX = previous;
  return (total + 4) / 8;
}
bool referencePlausible(uint16_t raw) { return raw >= 300 && raw <= 470; }
void checkReference() {
  referenceFault = !referencePlausible(referenceMonitor());
  digitalWrite(MEAS_ENABLE, referenceFault ? LOW : HIGH);
  if (!referenceFault) delay(5);
}

uint16_t crc16(const uint8_t *data, size_t length) {
  uint16_t value = 0xffff;
  while (length--) {
    value ^= *data++;
    for (uint8_t i = 0; i < 8; i++) value = value & 1 ? (value >> 1) ^ 0xA001 : value >> 1;
  }
  return value;
}
uint16_t checksum(const Calibration &record) {
  return crc16(reinterpret_cast<const uint8_t *>(&record), offsetof(Calibration, crc));
}
bool curveValid(const Calibration &record, uint8_t band, uint8_t channel) {
  if (record.mask[band][channel] != COMPLETE) return false;
  for (uint8_t knot = 0; knot < KNOTS; knot++) {
    uint16_t value = record.adc[band][channel][knot];
    if (value < 3 || value > 1018) return false;
    if (knot && value <= record.adc[band][channel][knot - 1] + 2) return false;
  }
  return true;
}
bool tableValid(const Calibration &record) {
  bool any = false;
  for (uint8_t band = 0; band < BANDS; band++) {
    if (!record.mask[band][0] && !record.mask[band][1]) continue;
    if (!curveValid(record, band, 0) || !curveValid(record, band, 1)) return false;
    any = true;
  }
  return any;
}
bool storedValid(const Calibration &record) {
  return record.magic == MAGIC && record.schema == SCHEMA && record.knots == KNOTS
    && record.crc == checksum(record) && tableValid(record);
}
void emptyCalibration(Calibration &record) {
  memset(&record, 0, sizeof(record));
  record.magic = MAGIC; record.schema = SCHEMA; record.knots = KNOTS;
}
void loadCalibration() {
  Calibration other;
  EEPROM.get(0, active); EEPROM.get(256, other);
  bool first = storedValid(active), second = storedValid(other);
  if (second && (!first || int32_t(other.generation - active.generation) > 0)) {
    active = other; savedSlot = 1; haveActive = true;
  } else if (first) { savedSlot = 0; haveActive = true; }
  else { emptyCalibration(active); haveActive = false; }
  draft = active;
}
bool applyDraft() {
  if (referenceFault) { Serial.println(F("ERROR reference fault; REFCHECK")); return false; }
  if (!tableValid(draft)) { Serial.println(F("ERROR incomplete/nonmonotonic calibration")); return false; }
  if (dirty || !haveActive) {
    draft.generation = active.generation + 1;
    active = draft; active.crc = checksum(active);
    dirty = false; unsaved = true; haveActive = true; draft = active;
  }
  return true;
}
void saveCalibration() {
  if (!applyDraft()) return;
  if (!unsaved && savedSlot >= 0) { Serial.println(F("ALREADY SAVED; no EEPROM write")); return; }
  uint8_t slot = savedSlot == 0 ? 1 : 0;
  uint16_t base = uint16_t(slot) * 256;
  active.magic = MAGIC; active.schema = SCHEMA; active.knots = KNOTS; active.crc = checksum(active);
  lcdLine(0, "CAL EEPROM WRITE"); lcdLine(1, "RF display paused");
  // Invalidate only the target slot, write payload/CRC, publish magic LAST.
  // An interrupted save leaves the previous complete slot available.
  for (uint8_t i = 0; i < sizeof(active.magic); i++) EEPROM.update(base + i, 0);
  const uint8_t *bytes = reinterpret_cast<const uint8_t *>(&active);
  for (uint16_t i = sizeof(active.magic); i < sizeof(active); i++) {
    EEPROM.update(base + i, bytes[i]); wdt_reset();
  }
  for (uint8_t i = 0; i < sizeof(active.magic); i++) EEPROM.update(base + i, bytes[i]);
  savedSlot = slot; unsaved = false; draft = active;
  Serial.println(F("SAVED R2"));
}

struct Sample { uint16_t mean, minimum, maximum; };
Sample sample(uint8_t pin) {
  analogRead(pin);
  uint32_t total = 0;
  Sample result = {0, 1023, 0};
  for (uint8_t i = 0; i < 32; i++) {
    uint16_t value = analogRead(pin); total += value;
    result.minimum = min(result.minimum, value); result.maximum = max(result.maximum, value);
  }
  result.mean = (total + 16) / 32;
  return result;
}
float powerMw(uint8_t band, uint8_t channel, uint16_t raw) {
  const uint16_t *values = active.adc[band][channel];
  if (raw < values[0]) return -1;
  if (raw > values[KNOTS - 1]) return -2;
  // Preserve exact calibration anchors: sqrt(1000)^2 rounds below 1000 on AVR,
  // which otherwise incorrectly rejects the calibrated 1 W threshold.
  for (uint8_t knot = 0; knot < KNOTS; knot++) if (raw == values[knot]) return powersMw[knot];
  uint8_t index = 0;
  while (index < KNOTS - 2 && raw > values[index + 1]) index++;
  float fraction = float(raw - values[index]) / (values[index + 1] - values[index]);
  float a = sqrt(float(powersMw[index])), b = sqrt(float(powersMw[index + 1]));
  float amplitude = a + fraction * (b - a);
  return amplitude * amplitude;
}

char command[48];
uint8_t used = 0;
bool overflow = false;
uint32_t commandChanged = 0;
bool number(const char *&text, unsigned limit, unsigned &value) {
  while (*text == ' ') text++;
  if (*text < '0' || *text > '9') return false;
  value = 0;
  do {
    unsigned digit = *text++ - '0';
    if (digit > limit || value > (limit - digit) / 10) return false;
    value = value * 10 + digit;
  } while (*text >= '0' && *text <= '9');
  return !*text || *text == ' ';
}
bool parseSet(unsigned &band, char &channel, unsigned &knot, unsigned &value) {
  if (strncmp(command, "SET ", 4)) return false;
  const char *text = command + 4;
  if (!number(text, BANDS - 1, band)) return false;
  while (*text == ' ') text++;
  channel = *text;
  if (channel != 'F' && channel != 'R') return false;
  text++;
  if (*text != ' ' || !number(text, KNOTS - 1, knot) || *text != ' '
      || !number(text, 1018, value) || value < 3) return false;
  while (*text == ' ') text++;
  return !*text;
}
void dump(const Calibration &record) {
  for (uint8_t band = 0; band < BANDS; band++) for (uint8_t channel = 0; channel < CHANNELS; channel++) {
    Serial.print(band); Serial.print(' '); Serial.print(channel ? 'R' : 'F');
    for (uint8_t knot = 0; knot < KNOTS; knot++) { Serial.print(' '); Serial.print(record.adc[band][channel][knot]); }
    Serial.println();
  }
}
void execute() {
  command[used] = 0;
  if (!strcmp(command, "SAVE")) saveCalibration();
  else if (!strcmp(command, "APPLY")) {
    if (applyDraft()) Serial.println(unsaved ? F("APPLIED RAM; SAVE required") : F("ACTIVE calibration unchanged"));
  }
  else if (!strcmp(command, "DUMP")) dump(draft);
  else if (!strcmp(command, "ACTIVE")) dump(active);
  else if (!strcmp(command, "DISCARD")) { draft = active; dirty = false; Serial.println(F("DRAFT DISCARDED")); }
  else if (!strcmp(command, "STATUS")) {
    Serial.print(F("RK-SWR R2 SCHEMA=2 KNOTS=6 GENERATION=")); Serial.print(active.generation);
    Serial.print(F(" DIRTY=")); Serial.print(dirty ? 1 : 0);
    Serial.print(F(" STORAGE=")); Serial.println(!haveActive ? F("NONE") : unsaved ? F("RAM") : F("EEPROM"));
    Serial.print(F("REFERENCE=")); Serial.println(referenceFault ? F("FAULT") : F("COARSE CHECK OK; verify 3.000 V"));
  } else if (!strcmp(command, "REFCHECK")) {
    checkReference();
    Serial.println(referenceFault ? F("ERROR reference fault") : F("REFCHECK OK; verify 3.000 V with meter"));
  } else {
    unsigned band, knot, value; char channel;
    if (parseSet(band, channel, knot, value)) {
      uint8_t index = channel == 'R';
      draft.adc[band][index][knot] = value; draft.mask[band][index] |= 1 << knot; dirty = true;
      Serial.println(F("SET OK DRAFT; APPLY/SAVE required"));
    } else Serial.println(F("SET b[0..8] F|R k[0..5] adc[3..1018]; APPLY SAVE DUMP ACTIVE DISCARD STATUS REFCHECK"));
  }
}
void serialPoll(uint32_t now) {
  if ((used || overflow) && uint32_t(now - commandChanged) > 1000) {
    used = 0; overflow = false; Serial.println(F("ERROR command timeout"));
  }
  for (uint8_t count = 0; count < 16 && Serial.available(); count++) {
    char value = Serial.read(); commandChanged = now;
    if (value == '\r') continue;
    if (value == '\n') {
      if (!overflow && used) execute(); else if (overflow) Serial.println(F("ERROR line too long"));
      used = 0; overflow = false;
    } else if (!overflow && used < sizeof(command) - 1) command[used++] = value;
    else overflow = true;
  }
}
uint8_t readBand() {
  analogRead(A2); uint16_t raw = analogRead(A2);
  if (!bandReady) { selectedBand = min(8, uint32_t(raw) * 9 / 1024); bandReady = true; }
  else if (int(raw) < int((uint32_t(selectedBand) * 1024 + 8) / 9) - 12
        || int(raw) > int((uint32_t(selectedBand + 1) * 1024 + 8) / 9) + 12)
    selectedBand = min(8, uint32_t(raw) * 9 / 1024);
  return selectedBand;
}
void setup() {
  digitalWrite(MEAS_ENABLE, LOW); pinMode(MEAS_ENABLE, OUTPUT);
  analogReference(EXTERNAL); // R2 REF3330 3.0 V on AREF; band potentiometer uses it too.
  Serial.begin(115200); pinMode(3, INPUT_PULLUP); lcdInit();
  analogRead(A2); // Establish ADC prescaler/enable before the direct bandgap read.
  checkReference(); loadCalibration();
  Serial.println(F("RK-SWR R2; measured knots 50,100,200,1000,5000,10000 mW. No default calibration."));
  Serial.println(F("R1 EEPROM incompatible; SET edits draft; APPLY/SAVE validates complete F+R band pairs."));
  wdt_enable(WDTO_2S);
}
uint32_t lastUpdate = 0;
void loop() {
  wdt_reset(); uint32_t now = millis(); serialPoll(now);
  if (uint32_t(now - lastUpdate) < 250) return;
  lastUpdate = now;
  if (!referencePlausible(referenceMonitor())) referenceFault = true;
  if (referenceFault) {
    digitalWrite(MEAS_ENABLE, LOW);
    lcdLine(0, "REFERENCE FAULT"); lcdLine(1, "CHECK 3V / USB"); return;
  }
  uint8_t band = readBand(); Sample f = sample(A0), r = sample(A1);
  Serial.print(F("RAW ")); Serial.print(band); Serial.print(' '); Serial.print(f.mean); Serial.print(' '); Serial.println(r.mean);
  char line[17]; snprintf(line, sizeof(line), "%s F%u R%u", bands[band], f.mean, r.mean);
  if (digitalRead(3) == LOW) { lcdLine(0, line); lcdLine(1, "RAW ADC / USB"); return; }
  if (dirty) { lcdLine(0, line); lcdLine(1, "CAL EDIT / APPLY"); return; }
  if (!haveActive || !curveValid(active, band, 0) || !curveValid(active, band, 1)) {
    lcdLine(0, line); lcdLine(1, "CAL REQUIRED"); return;
  }
  if (f.maximum >= 1019 || r.maximum >= 1019) { lcdLine(0, line); lcdLine(1, "ADC SATURATION"); return; }
  if (f.maximum - f.minimum > 12 || r.maximum - r.minimum > 12) {
    lcdLine(0, line); lcdLine(1, "UNSTABLE / CHECK"); return;
  }
  float pf = powerMw(band, 0, f.mean), pr = powerMw(band, 1, r.mean);
  if (pf < 1000) { lcdLine(0, line); lcdLine(1, pf == -2 ? "OVER RANGE" : "FWD <1W / CHECK"); return; }
  if (pr == -2 || pr >= pf) { lcdLine(0, line); lcdLine(1, "REVERSE / RANGE"); return; }
  char watts[8]; dtostrf(pf / 1000, 5, 2, watts);
  snprintf(line, sizeof(line), "F %s W %sM", watts, bands[band]); lcdLine(0, line);
  // Below 50 mW reverse: nominal calibration-floor upper bound, NOT zero SWR.
  bool upperBound = pr < 0;
  float rho = sqrt((upperBound ? powersMw[0] : pr) / pf);
  float swr = (1 + rho) / (1 - rho);
  if (!isfinite(swr) || swr > 99.9) { lcdLine(1, "SWR >99.9"); return; }
  // The displayed nominal upper bound must not be rounded below its value.
  // This still excludes the real detector/calibration uncertainty.
  if (upperBound) swr = ceil(swr * 100.0f) / 100.0f;
  char result[8]; dtostrf(swr, 4, 2, result);
  snprintf(line, sizeof(line), "SWR %c%s%s", upperBound ? '<' : '=', result, unsaved ? " *RAM" : "");
  lcdLine(1, line);
}
