User Tools

Site Tools


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

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki