yaesufc-40control
This is an old revision of the document!
Yaesu FC-40 Remote ATU Control
Operating the Yaesu FC-40 remote ATU without a Yaesu radio connected to it is something I am interested in doing. This Arduino sketch helps determine the feasibility of that.
- fc40diagnostics.ino
/* * FC-40 / Yaesu-protocol tuner probe & listener * Target: Arduino Uno (ATmega328P), 5V logic (direct-connect to tuner 5V TTL UART) * * Hardware UART (pins 0/1) -> USB console @ 115200 (Arduino IDE Serial Monitor) * SoftwareSerial -> tuner UART @ 4800 8N1 * * WIRING (uses FC-40 SERVICE MANUAL pin numbers): * Tuner pin 5 DATA OUT (tuner TX) -> Arduino D10 (SoftSerial RX) * Tuner pin 4 DATA IN (tuner RX) <- Arduino D11 (SoftSerial TX) * Tuner pin 3 GND -- Arduino GND (MUST be common) * Tuner pin 1 +13.5V -> Arduino Vin (feeds onboard 7805) [optional] * * ***DOUBLE-CHECK PIN NUMBERING PHYSICALLY.*** The MFJ results doc numbers * this same connector in REVERSE of the FC-40 service manual. Signals agree, * counting does not. Verify DATA OUT with a scope before trusting anything. * * INDICATORS / CONTROL (optional, matches your front-panel idea): * D3 Green LED - lights briefly on each A1 (tuning activity / see notes) * D4 Red LED - reserved for a FAIL byte you have not yet discovered * D2 Button - forces a command-mode probe sequence (INPUT_PULLUP) * * CONSOLE COMMANDS (type in Serial Monitor, newline to send): * h help * w send wakeup (0xFF) * f1 send tuner-disable (0xFF 0xF1 0x00 0x00) * f0 <MHz> send enable+freq (0xFF 0xF0 <bcd hi> <bcd lo>) * f2 <MHz> send start-tune (0xFF 0xF2 <bcd hi> <bcd lo>) * probe <MHz> f1 ... (gap) ... f2 <MHz> (mimics radio commanded tune) * raw <hh..> send arbitrary hex bytes, e.g. raw FF E0 01 00 * x reset the timestamp clock (t0) * * All received (tuner) bytes print in [brackets] with a ms timestamp, mirroring * WA2FZW's log format. Bytes we transmit print without brackets. */ #include <SoftwareSerial.h> // ---- pins ---- const uint8_t PIN_TUNER_RX = 10; // <- tuner DATA OUT const uint8_t PIN_TUNER_TX = 11; // -> tuner DATA IN const uint8_t PIN_GREEN = 3; const uint8_t PIN_RED = 4; const uint8_t PIN_BUTTON = 2; SoftwareSerial tuner(PIN_TUNER_RX, PIN_TUNER_TX); // ---- protocol bytes ---- const uint8_t WAKEUP = 0xFF; const uint8_t OP_ENABLE = 0xF0; // enable + set freq const uint8_t OP_DISABLE = 0xF1; // "set freq to zero" / bypass const uint8_t OP_TUNE = 0xF2; // start tune at freq const uint8_t RESP_A0 = 0xA0; // freq-ack / tuning-motion start const uint8_t RESP_A1 = 0xA1; // TX gate (cmd mode, x2) OR single-shot (auto) // const uint8_t RESP_FAIL = 0x??; // UNKNOWN - fill in once you provoke a failure const uint16_t WAKE_GAP_MS = 50; // observed ~50ms between 0xFF and command body // ---- timestamp clock (mirrors the WA2FZW logger) ---- uint32_t t0 = 0; uint32_t lastActivity = 0; const uint32_t IDLE_RESET_MS = 14000; // ---- A1 tracking to help distinguish auto vs command mode ---- uint8_t a1Count = 0; uint32_t lastA1Time = 0; // ---- green LED one-shot ---- uint32_t greenOffAt = 0; char lineBuf[48]; uint8_t lineLen = 0; // ------------------------------------------------------------------- void setup() { Serial.begin(115200); tuner.begin(4800); // 8N1 is SoftwareSerial's only framing anyway pinMode(PIN_GREEN, OUTPUT); digitalWrite(PIN_GREEN, LOW); pinMode(PIN_RED, OUTPUT); digitalWrite(PIN_RED, LOW); pinMode(PIN_BUTTON, INPUT_PULLUP); Serial.println(F("\n=== FC-40 tuner probe / listener ===")); Serial.println(F("Listening on tuner UART @ 4800 8N1.")); Serial.println(F("Type 'h' for commands. Power-cycle the tuner now to catch boot chatter.")); resetClock(); } // ------------------------------------------------------------------- void loop() { drainTuner(); // capture + report anything from the tuner handleConsole(); // parse commands from the IDE console handleButton(); // physical button -> command-mode probe serviceLeds(); // green one-shot timeout idleReset(); // reset t0 after long silence } // ------------------------------------------------------------------- // Stamp helper: prints "%05lu\t" ms since t0, starting the clock on first event. uint32_t stamp() { uint32_t now = millis(); if (t0 == 0) t0 = now; lastActivity = now; return now - t0; } void resetClock() { t0 = 0; a1Count = 0; Serial.println(F("\n-- t0 reset --")); } void idleReset() { if (t0 != 0 && (millis() - lastActivity) > IDLE_RESET_MS) { t0 = 0; a1Count = 0; Serial.println(F("\n-- t0 reset (idle) --")); } } // ------------------------------------------------------------------- // Read everything the tuner sends. All such bytes are printed in [brackets]. void drainTuner() { while (tuner.available()) { uint8_t b = tuner.read(); uint32_t ts = stamp(); char buf[24]; snprintf(buf, sizeof(buf), "%05lu\t[0x%02X]", (unsigned long)ts, b); Serial.print(buf); switch (b) { case RESP_A0: Serial.print(F(" A0 freq-ack / tuning motion start")); break; case RESP_A1: a1Count++; lastA1Time = millis(); // Green blip on every A1. NOTE: in AUTO mode a single A1 means // "cycle STARTED", not "succeeded" - do not read green as "done". greenOn(1000); if (a1Count == 1) Serial.print(F(" A1 #1 (auto: cycle running / cmd: KEY radio now)")); else Serial.print(F(" A1 #2 (cmd mode: tune complete, unkey)")); break; // case RESP_FAIL: // digitalWrite(PIN_RED, HIGH); // Serial.print(F(" FAIL")); // break; default: break; } Serial.println(); } } // ------------------------------------------------------------------- void greenOn(uint32_t ms) { digitalWrite(PIN_GREEN, HIGH); greenOffAt = millis() + ms; } void serviceLeds() { if (greenOffAt && millis() >= greenOffAt) { digitalWrite(PIN_GREEN, LOW); greenOffAt = 0; } } // ------------------------------------------------------------------- // Transmit helpers. Bytes we send are echoed to console WITHOUT brackets. void txByte(uint8_t b) { tuner.write(b); uint32_t ts = stamp(); char buf[24]; snprintf(buf, sizeof(buf), "%05lu\t0x%02X (tx)", (unsigned long)ts, b); Serial.println(buf); } // Send 0xFF, wait the observed gap, then a 3-byte command body. void sendCommand(uint8_t op, uint8_t hi, uint8_t lo) { txByte(WAKEUP); tuner.flush(); delay(WAKE_GAP_MS); txByte(op); txByte(hi); txByte(lo); tuner.flush(); } // MHz (float) -> two BCD bytes at 10 kHz resolution. // 3.57 -> 357 -> 0x03 0x57 // 7.22 -> 722 -> 0x07 0x22 // 14.25 -> 1425 -> 0x14 0x25 bool mhzToBcd(float mhz, uint8_t &hi, uint8_t &lo) { long units = lround(mhz * 100.0); // 10 kHz units if (units < 0 || units > 9999) return false; uint8_t d3 = (units / 1000) % 10; uint8_t d2 = (units / 100) % 10; uint8_t d1 = (units / 10) % 10; uint8_t d0 = units % 10; hi = (d3 << 4) | d2; lo = (d1 << 4) | d0; return true; } // ------------------------------------------------------------------- void handleButton() { static uint32_t lastPress = 0; static bool prev = HIGH; bool now = digitalRead(PIN_BUTTON); if (prev == HIGH && now == LOW && (millis() - lastPress) > 250) { lastPress = millis(); Serial.println(F("\n[button] commanded-tune probe @ 14.25 MHz")); commandedProbe(14.25); } prev = now; } // Mimic the radio's Tests-1/5 commanded sequence (minus actually keying RF). // You watch for: [A1] (key here) ... [A0] ... [A1] (done). void commandedProbe(float mhz) { uint8_t hi, lo; if (!mhzToBcd(mhz, hi, lo)) { Serial.println(F("bad freq")); return; } a1Count = 0; sendCommand(OP_DISABLE, 0x00, 0x00); // FF F1 00 00 delay(400); // radio waited ~450ms before F2 sendCommand(OP_TUNE, hi, lo); // FF F2 <bcd> } // ------------------------------------------------------------------- void handleConsole() { while (Serial.available()) { char c = Serial.read(); if (c == '\r') continue; if (c == '\n') { lineBuf[lineLen] = 0; parseLine(lineBuf); lineLen = 0; } else if (lineLen < sizeof(lineBuf) - 1) { lineBuf[lineLen++] = c; } } } void parseLine(char *s) { while (*s == ' ') s++; if (*s == 0) return; if (!strcmp(s, "h")) { printHelp(); return; } if (!strcmp(s, "x")) { resetClock(); return; } if (!strcmp(s, "w")) { txByte(WAKEUP); return; } if (!strcmp(s, "f1")) { sendCommand(OP_DISABLE, 0x00, 0x00); return; } if (!strncmp(s, "f0", 2) || !strncmp(s, "f2", 2)) { uint8_t op = (s[1] == '0') ? OP_ENABLE : OP_TUNE; float mhz = atof(s + 2); uint8_t hi, lo; if (mhz > 0 && mhzToBcd(mhz, hi, lo)) sendCommand(op, hi, lo); else Serial.println(F("usage: f0 <MHz> | f2 <MHz>")); return; } if (!strncmp(s, "probe", 5)) { float mhz = atof(s + 5); if (mhz > 0) commandedProbe(mhz); else Serial.println(F("usage: probe <MHz>")); return; } if (!strncmp(s, "raw", 3)) { char *p = s + 3; while (*p) { while (*p == ' ') p++; if (!*p) break; uint8_t b = (uint8_t) strtol(p, &p, 16); txByte(b); } return; } Serial.println(F("? unknown - type 'h'")); } void printHelp() { Serial.println(F("\n commands:")); Serial.println(F(" h help")); Serial.println(F(" w wakeup 0xFF")); Serial.println(F(" f1 disable (FF F1 00 00)")); Serial.println(F(" f0 <MHz> enable+freq (FF F0 <bcd>)")); Serial.println(F(" f2 <MHz> start tune (FF F2 <bcd>)")); Serial.println(F(" probe <MHz> f1 then f2 (commanded sequence)")); Serial.println(F(" raw <hh..> send raw hex, e.g. raw FF E0 01 00")); Serial.println(F(" x reset timestamp clock")); }
yaesufc-40control.1784086556.txt.gz ยท Last modified: by admin
