diff --git a/TeensyDSP/Rig.cpp b/TeensyDSP/Rig.cpp new file mode 100644 index 0000000..a4d822c --- /dev/null +++ b/TeensyDSP/Rig.cpp @@ -0,0 +1,3 @@ +#include "Rig.h" + +UBitxRig Rig; diff --git a/TeensyDSP/Rig.h b/TeensyDSP/Rig.h new file mode 100644 index 0000000..a34f1c0 --- /dev/null +++ b/TeensyDSP/Rig.h @@ -0,0 +1,132 @@ +#ifndef __Rig_h__ +#define __Rig_h__ + +#include "RigState.h" + +enum UpdateSource { + NoSource, + RaduinoSource, + CATSource +}; + +class UBitxRig { + public: + inline unsigned long getFreqA() const { return state.vfo[0]; } + inline unsigned long getFreqB() const { return state.vfo[1]; } + inline short getRIT() const { return state.rit; } + inline short getXIT() const { return state.xit; } + inline bool isVFOA() const { return (state.flags & UBITX_VFOB_FLAG) != UBITX_VFOB_FLAG; } + inline bool isVFOB() const { return (state.flags & UBITX_VFOB_FLAG) == UBITX_VFOB_FLAG; } + inline bool isSplit() const { return (state.flags & UBITX_SPLIT_FLAG) == UBITX_SPLIT_FLAG; } + inline bool isRITOn() const { return (state.flags & UBITX_RIT_FLAG) == UBITX_RIT_FLAG; } + inline bool isXITOn() const { return (state.flags & UBITX_XIT_FLAG) == UBITX_XIT_FLAG; } + inline bool isCW() const { return (state.flags & UBITX_CW_FLAG) == UBITX_CW_FLAG; } + inline bool isLSB() const { return (state.flags & UBITX_USB_FLAG) != UBITX_USB_FLAG; } + inline bool isUSB() const { return (state.flags & UBITX_USB_FLAG) == UBITX_USB_FLAG; } + inline bool getAI() const { return autoInfo; } + inline bool updatedByCAT() const { return lastUpdatedBy == CATSource; } + inline bool updatedByRaduino() const { return lastUpdatedBy == RaduinoSource; } + + inline void clearUpdate() { lastUpdatedBy = NoSource; } + inline void setRaduinoUpdate() { lastUpdatedBy = RaduinoSource; } + inline void setCATUpdate() { lastUpdatedBy = CATSource; } + + inline void setFreqA(unsigned long freq, bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.vfo[0] = freq; + } + + inline void setFreqB(unsigned long freq, bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.vfo[1] = freq; + } + + inline void setRIT(short offset, bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.rit = offset; + } + + inline void setXIT(short offset, bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.xit = offset; + } + + inline void selectVFOA(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags &= ~UBITX_VFOB_FLAG; + } + + inline void selectVFOB(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags |= UBITX_VFOB_FLAG; + } + + inline void toggleVFO(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags ^= UBITX_VFOB_FLAG; + } + + inline void splitOn(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags |= UBITX_SPLIT_FLAG; + } + + inline void splitOff(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags &= ~UBITX_SPLIT_FLAG; + } + + inline void ritOn(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags |= UBITX_RIT_FLAG; + } + + inline void ritOff(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags &= ~UBITX_RIT_FLAG; + } + + inline void xitOn(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags |= UBITX_XIT_FLAG; + } + + inline void xitOff(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags &= ~UBITX_XIT_FLAG; + } + + inline void cwOn(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags |= UBITX_CW_FLAG; + } + + inline void cwOff(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags &= ~UBITX_CW_FLAG; + } + + inline void selectLSB(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags &= ~UBITX_USB_FLAG; + } + + inline void selectUSB(bool isCAT = false) { + lastUpdatedBy = isCAT ? CATSource : RaduinoSource; + state.flags |= UBITX_USB_FLAG; + } + + inline void aiOn() { autoInfo = true; } + inline void aiOff() { autoInfo = false; } + + uint8_t* const stateAsBytes() const { return (uint8_t* const)&state; } + + private: + bool autoInfo = false; + UpdateSource lastUpdatedBy = NoSource; + UBitxRigState state; +}; + +extern UBitxRig Rig; + +#endif diff --git a/TeensyDSP/TS590.cpp b/TeensyDSP/TS590.cpp new file mode 100644 index 0000000..dba45ae --- /dev/null +++ b/TeensyDSP/TS590.cpp @@ -0,0 +1,266 @@ +#include +#include "TS590.h" + +/**********************************************************************/ + +/*! + * @brief Send a command to the PC via CAT. Note that the command + * should not include the trailing terminator (;). That will + * be automatically added. + * @param format + * A printf-style format string. + * @param args + * Zero or more arguments to include in the command. + */ +void ts590SendCommand(const char* format, ...) { + static char outBuf[ts590CommandMaxLength]; + va_list args; + va_start(args, format); + vsprintf(outBuf, format, args); + va_end(args); + Serial.print(outBuf); + Serial.print(";"); +} + +/**********************************************************************/ + +/*! + * @brief Create a new CAT command. It should be initialized with + * a 2-character command prefix. + * @param pre + * A 2-character command prefix. If more than 2 characters + * are supplied, only the first two will be used. If less + * than two are supplied, then the command will be + * initialized with a null prefix. + */ +TS590Command::TS590Command(const char* pre) { + if (strlen(pre) >= 2) { + myPrefix[0] = pre[0]; + myPrefix[1] = pre[1]; + } +} + +TS590Command::~TS590Command() {} + +/*! + * @brief Determine whether this is a Read command or not. by + * default, if it's a 2-letter command, it's a Read. + * @return True if a Read command; false otherwise. + */ +bool TS590Command::isReadCommand(const char* cmd) const { + if (strlen(cmd) == 2) { + return true; + } else { + return false; + } +} + +/*! + * @brief Process the provided command. If the command is a Set + * command, it calls handleCommand(). If Auto Information + * is eet (by the rig), sendResponse() is called at the end. + * If the command is a Read command, it also calls + * sendResponse(). Finally, if necessary, it will return + * any error codes to the PC. + * @param cmd + * The current command string received from the PC via CAT. + * It should be null-terminated, and should no longer have + * the terminator (;). + */ +void TS590Command::process(const char* cmd) { + theError = NoError; + + if (isReadCommand(cmd)) { + sendResponse(cmd); + } else { + handleCommand(cmd); + switch(theError) { + case NoError: + if (theRig->getAI()) { + sendResponse(cmd); + } + break; + + case SyntaxError: + ts590SyntaxError(); + break; + + case CommError: + ts590CommError(); + break; + + case ProcessError: + ts590ProcessError(); + break; + } + } +} + +/*! + * @brief Set the syntax error flag. This is cleared at the + * beginning of each call to process(). + */ +void TS590Command::setSyntaxError() { + theError = SyntaxError; +} + +/*! + * @brief Set the comms error flag. This is cleared at the + * beginning of each call to process(). + */ +void TS590Command::setCommError() { + theError = CommError; +} + +/*! + * @brief Set the process error flag. This is cleared at the + * beginning of each call to process(). + */ +void TS590Command::setProcessError() { + theError = ProcessError; +} + +/*! + * @brief Set the rig that will be used to process commands. + * @param r + * Pointer to the UBitxRig object. + */ +void TS590Command::setRig(UBitxRig* r) { + theRig = r; +} + +UBitxRig* TS590Command::theRig = &Rig; +TS590Error TS590Command::theError = NoError; + +/**********************************************************************/ + +void TS590_FR::handleCommand(const char* cmd) { + if (strlen(cmd) == 3) { + switch (cmd[2]) { + case 0: + rig()->selectVFOA(true); + rig()->splitOff(true); + break; + + case 1: + rig()->selectVFOB(true); + rig()->splitOff(true); + break; + + case 2: + // TODO: Need to add something for channel mode. + break; + + default: + setSyntaxError(); + } + } else { + setSyntaxError(); + } +} + +void TS590_FR::sendResponse(const char* cmd) { + if (rig()->isVFOA()) { + ts590SendCommand("FR0"); + } else if (rig()->isVFOB()) { + ts590SendCommand("FR1"); + } else { + ts590SendCommand("FR2"); + } +} + +/**********************************************************************/ + +void TS590_FT::handleCommand(const char* cmd) { + if (strlen(cmd) == 3) { + switch (cmd[2]) { + case 0: + if (rig()->isVFOA()) { + rig()->splitOff(); + } else if (rig()->isVFOB()) { + rig()->splitOn(); + } else { + setSyntaxError(); + } + break; + + case 1: + if (rig()->isVFOA()) { + rig()->splitOn(); + } else if (rig()->isVFOB()) { + rig()->splitOff(); + } else { + setSyntaxError(); + } + break; + + default: + setSyntaxError(); + } + } else { + setSyntaxError(); + } +} + +void TS590_FT::sendResponse(const char* cmd) { + if (rig()->isVFOA()) { + ts590SendCommand(rig()->isSplit() ? "FT1" : "FT0"); + } else if (rig()->isVFOB()) { + ts590SendCommand(rig()->isSplit() ? "FT0" : "FT1"); + } else { + ts590SendCommand("FT2"); + } +} + +/**********************************************************************/ + +TS590_FA cmdFA; +TS590_FB cmdFB; +TS590_FR cmdFR; +TS590_FT cmdFT; + +TS590Command* catCommands[] = { + &cmdFA, + &cmdFB, + &cmdFR, + &cmdFT +}; + +/**********************************************************************/ + +void UBitxTS590::update() { + char incomingChar; + + while (Serial.available()) { + if (bufLen < ts590CommandMaxLength) { + incomingChar = Serial.read(); + if (incomingChar == ';') { + buf[bufLen++] = '\0'; + processCommand(); + } else { + buf[bufLen++] = incomingChar; + } + } else { + // too long... we're going to bail on this. + ts590SyntaxError(); + bufLen = 0; + } + } +} + +int compareCATCommands(const void* a, const void* b) { + return strcmp(((TS590Command*)a)->prefix(), ((TS590Command*)b)->prefix()); +} + +void UBitxTS590::processCommand() { + TS590Command* cmd = (TS590Command*)bsearch((void*)buf, (void*)commands, sizeof(commands) / sizeof(commands[0]), sizeof(commands[0]), compareCATCommands); + if (cmd == NULL) { + ts590SyntaxError(); + } else { + cmd->process(buf); + } +} + +UBitxTS590 TS590(catCommands); + +/**********************************************************************/ diff --git a/TeensyDSP/TS590.h b/TeensyDSP/TS590.h new file mode 100644 index 0000000..58b2abb --- /dev/null +++ b/TeensyDSP/TS590.h @@ -0,0 +1,173 @@ +#ifndef __TS590_h__ +#define __TS590_h__ + +#include +#include "Rig.h" + +/**********************************************************************/ + +#define TS590_COMMAND_MAX_LENGTH 50 // including terminator (which will get converted to null) + +const int ts590CommandMaxLength = TS590_COMMAND_MAX_LENGTH; + +void ts590SendCommand(const char*, ...); + +/*! + * @brief Send a syntax error response to the PC via CAT. + */ +inline void ts590SyntaxError() { ts590SendCommand("?"); } + +/*! + * @brief Send a communications error response to the PC via CAT. + */ +inline void ts590CommError() { ts590SendCommand("E"); } + +/*! + * @brief Send a processing error response to the PC via CAT. + */ +inline void ts590ProcessError() { ts590SendCommand("O"); } + +enum TS590Error { + NoError, + SyntaxError, + CommError, + ProcessError +}; + +/**********************************************************************/ + +/*! + * @brief A TS590S/SG "CAT" command. This is the base class for all + * CAT commands. + */ +class TS590Command { + public: + TS590Command(const char* pre); + virtual ~TS590Command() = 0; + + /*! + * @brief Return the 2-character prefix for the command. + * @return The 2-character prefix for the command. + */ + inline const char* prefix() const { return &myPrefix[0]; } + + /*! + * @brief Return the rig that this command will be used to control. + */ + inline UBitxRig* rig() const { return theRig; } + + /*! + * @brief Handle the provided Set command. If the Set command + * results in an error, then set the appropriate flag with + * setSyntaxError(), setCommError(), or setProcessError(). + * @param cmd + * The current command string received from the PC via CAT. + * It should be null-terminated, and should no longer have + * the terminator (;). + */ + virtual void handleCommand(const char* cmd) = 0; + + /*! + * @brief Send a response back to the PC. This assumes a + * successful command (no errors). + */ + virtual void sendResponse(const char* cmd) = 0; + + virtual bool isReadCommand(const char* cmd) const; + + void process(const char* cmd); + + static void setSyntaxError(); + static void setCommError(); + static void setProcessError(); + static void setRig(UBitxRig* r); + + private: + char myPrefix[3] = "\0\0"; + static TS590Error theError; + static UBitxRig* theRig; +}; + +/**********************************************************************/ + +/*! + * @brief CAT command for setting or reading the VFO A/B frequency. + */ +template +class TS590_FAB : public TS590Command { + public: + TS590_FAB(): TS590Command(VFOA ? "FA" : "FB") {} + + virtual void handleCommand(const char* cmd) { + if (strlen(cmd) == 13) { + unsigned long freq = strtoul(&cmd[2], NULL, 10); + if (VFOA) { + rig()->setFreqA(freq, true); + } else { + rig()->setFreqB(freq, true); + } + } else { + setSyntaxError(); + } + } + + virtual void sendResponse(const char* cmd) { + ts590SendCommand(VFOA ? "FA%011u" : "FB%011u", VFOA ? rig()->getFreqA() : rig()->getFreqB()); + } +}; + +typedef TS590_FAB TS590_FA; +typedef TS590_FAB TS590_FB; + +/**********************************************************************/ + +/*! + * @brief CAT command for setting the receiver VFO. This will always + * disable split mode, if it was previously enabled. + */ +class TS590_FR : public TS590Command { + public: + TS590_FR(): TS590Command("FR") {} + virtual void handleCommand(const char* cmd); + virtual void sendResponse(const char* cmd); +}; + +/**********************************************************************/ + +/*! + * @brief CAT command for setting the transmitter VFO. If it is + * different than the receiver VFO, then split mode will be + * automatically enabled. + */ +class TS590_FT : public TS590Command { + public: + TS590_FT(): TS590Command("FT") {} + virtual void handleCommand(const char* cmd); + virtual void sendResponse(const char* cmd); +}; + +/**********************************************************************/ + +class UBitxTS590 { + public: + UBitxTS590(TS590Command** cmds): commands(cmds) {} + + inline void begin() { + Serial.begin(9600); // USB is always 12 Mbit/sec + } + + void update(); + + private: + void processCommand(); + + char buf[ts590CommandMaxLength] = {0}; + int bufLen = 0; + TS590Command** commands; +}; + +extern UBitxTS590 TS590; + +#endif + +/**********************************************************************/ diff --git a/TeensyDSP/TeensyDSP.ino b/TeensyDSP/TeensyDSP.ino index e0dca3c..76cfce7 100644 --- a/TeensyDSP/TeensyDSP.ino +++ b/TeensyDSP/TeensyDSP.ino @@ -313,9 +313,6 @@ void sendMeterData(uint8_t isSend) void setup() { -#ifdef DEBUG - Serial.begin(38400); -#endif DBGCMD( DSP.begin() ); DBGCMD( TR.begin() );