User Tools

Site Tools


yaesufc-40control


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 antenna tuner probe & listener
 * ------------------------------------------------------
 * Target: ATmega32U4 board (Leonardo / Micro / Pro Micro), 5V logic.
 *         Direct-connect to the tuner's 5V TTL UART (verify levels on a scope!).
 *
 * Serial   (USB CDC) -> IDE Serial Monitor console @ 115200
 * Serial1  (HW UART, pins 0=RX / 1=TX) -> tuner UART @ 4800 8N1
 *
 * WIRING (uses FC-40 SERVICE MANUAL pin numbers):
 *   Tuner pin 5  DATA OUT (tuner TX) -> board RX (D0)
 *   Tuner pin 4  DATA IN  (tuner RX) <- board TX (D1)
 *   Tuner pin 3  GND                 -- board GND  (MUST be common)
 *   Tuner pin 1  +13.5V -> board Vin/RAW (onboard regulator)  [optional]
 *
 *   *** VERIFY PIN NUMBERING AND LOGIC LEVELS ON A SCOPE FIRST. ***
 *   The mini-DIN can be numbered in either direction depending on whether
 *   you read the plug or the jack; signals are what matter, not the count.
 *
 * OPTIONAL: onboard user LED (D13) blips on each received A1. This is a
 *   convenience only -- see note below about what an A1 does and does not mean.
 *
 * 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 a radio commanded tune)
 *   raw <hh..>     send arbitrary hex bytes, e.g.  raw FF E0 01 00
 *   x              reset the timestamp clock
 *
 * Received (tuner) bytes print in [brackets] with a ms timestamp.
 * Bytes we transmit print without brackets.
 *
 * ------------------------------------------------------
 * Acknowledgement:
 *   This work builds directly on the prior reverse-engineering and
 *   documentation of the Yaesu tuner control protocol by John Price (WA2FZW),
 *   whose published test box, captures, and analysis of the FT-891 / tuner
 *   interface made this possible. Thank you for sharing your research openly.
 * ------------------------------------------------------
 */
 
// ---- optional indicator ----
#define USE_LED_A1  1              // set 0 to disable the D13 blip entirely
const uint8_t PIN_LED = 13;
 
// ---- 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 ----
uint32_t t0 = 0;
uint32_t lastActivity = 0;
const uint32_t IDLE_RESET_MS = 14000;
 
// ---- A1 tracking (helps distinguish auto vs command mode) ----
uint8_t  a1Count = 0;
 
// ---- LED one-shot ----
uint32_t ledOffAt = 0;
 
char lineBuf[48];
uint8_t lineLen = 0;
 
// -------------------------------------------------------------------
void setup() {
  Serial.begin(115200);              // USB CDC console
  while (!Serial && millis() < 3000) { }   // wait briefly for host (32U4)
  Serial1.begin(4800, SERIAL_8N1);   // tuner UART
 
#if USE_LED_A1
  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, LOW);
#endif
 
  Serial.println(F("\n=== FC-40 tuner probe / listener (ATmega32U4) ==="));
  Serial.println(F("Listening on Serial1 @ 4800 8N1."));
  Serial.println(F("Type 'h' for commands. Power-cycle the tuner now to catch boot chatter."));
  resetClock();
}
 
// -------------------------------------------------------------------
void loop() {
  drainTuner();
  handleConsole();
  serviceLed();
  idleReset();
}
 
// -------------------------------------------------------------------
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) --"));
  }
}
 
// -------------------------------------------------------------------
// Everything from the tuner prints in [brackets].
void drainTuner() {
  while (Serial1.available()) {
    uint8_t b = Serial1.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++;
        // AUTO mode: a single A1 means the cycle STARTED, not that it
        // succeeded. COMMAND mode: A1 #1 = key now, A1 #2 = done/unkey.
        ledBlip(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:
      //   Serial.print(F("  FAIL"));
      //   break;
      default:
        break;
    }
    Serial.println();
  }
}
 
// -------------------------------------------------------------------
void ledBlip(uint32_t ms) {
#if USE_LED_A1
  digitalWrite(PIN_LED, HIGH);
  ledOffAt = millis() + ms;
#else
  (void)ms;
#endif
}
void serviceLed() {
#if USE_LED_A1
  if (ledOffAt && millis() >= ledOffAt) {
    digitalWrite(PIN_LED, LOW);
    ledOffAt = 0;
  }
#endif
}
 
// -------------------------------------------------------------------
// Bytes we send echo to the console WITHOUT brackets.
void txByte(uint8_t b) {
  Serial1.write(b);
  uint32_t ts = stamp();
  char buf[24];
  snprintf(buf, sizeof(buf), "%05lu\t0x%02X  (tx)", (unsigned long)ts, b);
  Serial.println(buf);
}
 
// 0xFF, wait the observed gap, then a 3-byte command body.
void sendCommand(uint8_t op, uint8_t hi, uint8_t lo) {
  txByte(WAKEUP);
  Serial1.flush();
  delay(WAKE_GAP_MS);
  txByte(op);
  txByte(hi);
  txByte(lo);
  Serial1.flush();
}
 
// MHz -> two BCD bytes at 10 kHz resolution.
//   3.57  -> 0x03 0x57
//   7.22  -> 0x07 0x22
//   14.25 -> 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;
}
 
// Mimic a radio's commanded-tune sequence (minus actually keying RF).
// 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);
  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.txt ยท Last modified: by admin

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki