/*! * @file RigState.cpp * * @mainpage uBITX V5X Software - RigState * * @section introsec Introduction * * TBD * * @section dependencies Dependencies * * TBD * * @section author Author * * Written by Rob "Scrape" French, KC4UPR * * @section license License * * TBD */ #include "Debug.h" #include "RigState.h" /*********************************************************************** * COMMON FUNCTIONS * * The following are all common to RigState objects, whether on the * Raduino or on the TeensyDSP. **********************************************************************/ static uint32_t zeroes[1] = {0}; // used to transmit zeroes /*! * @brief Begin using the RigState object. In order to force an * initial update (i.e. sending current state to the remote * device), all fields are initially marked dirty. */ void UBitxRigState::begin() { setDirty(); } /*********************************************************************** * RADUINO FUNCTIONS * * The following are specific to the Raduino implementation. Note that * this depends on the use of the TEENSYDUINO #define, which may result * in a fragile implementation for other development environments (e.g. * if the normal Arduino IDE is not being used). **********************************************************************/ #ifndef TEENSYDUINO #include #include "ubitx.h" #include "ubitx_eemap.h" extern unsigned long frequency, ritRxFrequency, ritTxFrequency, sideTone; extern unsigned long vfoA; extern unsigned long vfoB; extern char cwMode; extern char isUSB; extern char vfoActive; extern char ritOn; extern char splitOn; extern char inTx; void setFrequency(unsigned long); /*! * @brief Send the RigState from the Raduino to the TeensyDSP. The * basic process is: (1) read in any updated (dirty) data * from the Raduino's state variables; (2) transmit the dirty * data to the TeensyDSP; (2a) for clean data, zeroes are * transmitted; (3) mark all data as clean. */ void UBitxRigState::send_RIGINF() { readDirty(); Wire.beginTransmission(I2CMETER_ADDR); Wire.write(I2CMETER_RIGINF); for (RigStateWord i = DIRTY_WORD; i < NUM_WORDS; i++) { if (i == DIRTY_WORD || isDirty(i)) { // always send the current dirty bits // or, bytes for updated (dirty) fields Wire.write((byte*)&data[i], sizeof(uint32_t)); } else { // otherwise, send out zeroes Wire.write((byte*)&zeroes, sizeof(uint32_t)); //---------------------------------------------------------------- // NOTE: I am sending these zeroed out fields under a possibly // mistaken assumption that in doing so, I will be sending a // constant voltage on the SDA line most of the time, i.e. no // bit changes, and so this will help reduce noise generated by // I2C traffic (since most of the time there will be no updates.) //---------------------------------------------------------------- } } Wire.endTransmission(); IFDEBUG( serialHexState("Sent") ); //IFDEBUG( serialPrettyState("Sent") ); setClean(); } // delay(1); // 1ms - some delay required between ending transmission and requesting? /*! * @brief Receive the RigState from the TeensyDSP. This generally * reflects changes due to CAT transmission to the TeensyDSP. * @param numBytes * Number of bytes received from the TeensyDSP. */ void UBitxRigState::receive_RIGINF(int numBytes) { // Retrieve all of the deltas. Mark any received fields as dirty. It // is assumed that send_RIGINF() was called immedaitely before this, // so the fields are already clean. byte* ptr = (byte*)&data; Wire.requestFrom(I2CMETER_ADDR, sizeof(data)); for (RigStateWord i = DIRTY_WORD; i < NUM_WORDS && Wire.available(); i++) { for (size_t j = 0; j < sizeof(uint32_t) && Wire.available(); j++) { byte incomingByte = Wire.read(); if (i == DIRTY_WORD || isDirty(i)) { // always overwrite the dirty bits // and, update bytes for fields marked dirty *ptr = incomingByte; } ptr++; } } writeDirty(); IFDEBUG( serialHexState("Rcvd") ); //IFDEBUG( serialPrettyState("Rcvd") ); setClean(); // They get marked dirty as req'd during readDirty(). } /*! * @brief Write dirty fields from the RigState out to the Raduino * variables. */ void UBitxRigState::writeDirty() { // VFO A frequency if (isDirty(VFOA_WORD)) { if (vfoActive == VFO_A) { setFrequency(getFreqA()); } else { vfoA = getFreqA(); } } // VFO B frequency if (isDirty(VFOB_WORD)) { if (vfoActive == VFO_B) { setFrequency(getFreqB()); } else { vfoB = getFreqB(); } } // RIT and XIT frequencies if (isDirty(OFFSETS_WORD)) { // RIT ritRxFrequency = getRIT() + ritTxFrequency; if (ritOn == 1) { if (inTx == 0) { setFrequency(ritRxFrequency); } else { setFrequency(ritTxFrequency); } } // XIT - TODO } // Various flags if (isDirty(FLAGS_WORD)) { // VFO A/B selection char prev = vfoActive; vfoActive = isVFOA() ? VFO_A : VFO_B; if (vfoActive != prev) { if (vfoActive == VFO_A) { if (vfoA != frequency) { setFrequency(vfoA); } } else if (vfoActive == VFO_B) { if (vfoB != frequency) { setFrequency(vfoB); } } } // Split on/off splitOn = isSplit() ? 1 : 0; // RIT on/off prev = ritOn; ritOn = isRIT() ? 1 : 0; if (ritOn != prev) { if ((ritOn == 1) && (inTx == 0)) { setFrequency(ritRxFrequency); } } // XIT on/off // TODO // Mode prev = (cwMode << 1) | isUSB; isUSB = isModeUSB() ? 1 : 0; if (isModeCW()) { cwMode = 2; // 2 = cwu } else if (isModeCWR()) { cwMode = 1; // 1 = cwl } else { cwMode = 0; // 0 = no cw } if ((cwMode << 1) | isUSB != prev) { setFrequency(frequency); } } // Keyer information if (isDirty(KEYER_WORD)) { // Sidetone frequency sideTone = static_cast(getSidetone()); } } /*! * @brief Read current Raduino variables into the RigState * (if they are changed) and set the appropriate dirty flags. * @param r * RigState reference to put the values into. */ void UBitxRigState::readDirty() { unsigned long freq; short offset; // VFO A frequency freq = (vfoActive == VFO_A) ? frequency : vfoA; if (getFreqA() != freq) { setFreqA(freq); } // VFO B frequency freq = (vfoActive == VFO_B) ? frequency : vfoB; if (getFreqB() != freq) { setFreqB(freq); } // RIT frequency if (inTx) { offset = ritRxFrequency - ritTxFrequency; } else { offset = frequency - ritTxFrequency; } if (getRIT() != offset) { setRIT(offset); } // XIT frequency offset = 0; // xitRxFrequency - frequency; if (getXIT() != offset) { setXIT(offset); } // VFO A/B selection if (isVFOA() && vfoActive == VFO_B) { setVFOB(); } else if (isVFOB() && vfoActive == VFO_A) { setVFOA(); } // Split selection if (isSplit() && splitOn == 0) { setSplitOff(); } else if (!isSplit() && splitOn != 0) { setSplitOn(); } // RIT selection if (isRIT() && ritOn == 0) { setRITOff(); } else if (!isRIT() && ritOn != 0) { setRITOn(); } // XIT selection //setXITOff(); // TODO // Mode char prev = (isModeCW() ? 4 : 0) | (isModeCWR() ? 2 : 0) | (isModeUSB() ? 1 : 0); char curr = (cwMode << 1) | isUSB; if (curr != prev) { if (cwMode == 2) { setModeCW(); } else if (cwMode == 1) { setModeCWR(); } else { if (isUSB) { setModeUSB(); } else { setModeLSB(); } } } // Sidetone if (getSidetone() != static_cast(sideTone)) { setSidetone(static_cast(sideTone)); } } /*********************************************************************** * TEENSYDSP FUNCTIONS * * The following are specific to the TeensyDSP implementation. Note * that this depends on the use of the TEENSYDUINO #define, which may * result in a fragile implementation for other development environments * (e.g. if the normal Arduino IDE is not being used). **********************************************************************/ #else #include /*! * @brief Receive RIGINF data from the Raduino. This method should * be called on the TeensyDSP 'radState' (Raduino state) * instance, when a RIGINF signal is received via I2C. It * receives the incoming data from the Raduino and updates the * state. */ void UBitxRigState::receive_RIGINF(int numBytes) { byte* ptr = (byte*)&data; setClean(); // we'll get new dirty bits via the I2C message for (RigStateWord i = DIRTY_WORD; i < NUM_WORDS && Wire1.available(); i++) { for (size_t j = 0; j < sizeof(uint32_t) && Wire1.available(); j++) { byte incomingByte = Wire1.read(); if (i == DIRTY_WORD || isDirty(i)) { // always overwrite the dirty bits // and, update bytes for fields marked dirty *ptr = incomingByte; } ptr++; } } //-------------------------------------------------------------------- // Do anything that needs to happen when something is updated by the // Raduino... this would be due to e.g. changes through the menu. // Current (as of 2/19/2021) things that DON'T need to have any // updates: frequency (those just get requested by CAT as needed). // Current things that do need to get updated: sidetone (used to // update CW filter values). //-------------------------------------------------------------------- processDirty(); IFDEBUG( serialHexState("Rcvd") ); IFDEBUG( serialPrettyState("Rcvd") ); } /**********************************************************************/ /*! * @brief Handle a RIGINF signal from the Raduino. This method should * be called on the TeensyDSP 'catState' (CAT state) * instance, when a RIGINF signal is received via I2C. It * sends a response to the Raduino via I2C, using the Wire1 * interface. */ void UBitxRigState::send_RIGINF() { for (RigStateWord i = DIRTY_WORD; i < NUM_WORDS; i++) { if (i == DIRTY_WORD || isDirty(i)) { // always send the current dirty bits // or, bytes for updated (dirty) fields Wire1.write((byte*)&data[i], sizeof(uint32_t)); } else { // otherwise, send out zeroes Wire1.write((byte*)&zeroes, sizeof(uint32_t)); //---------------------------------------------------------------- // NOTE: I am sending these zeroed out fields under a possibly // mistaken assumption that in doing so, I will be sending a // constant voltage on the SDA line most of the time, i.e. no // bit changes, and so this will help reduce noise generated by // I2C traffic (since most of the time there will be no updates.) //---------------------------------------------------------------- } } IFDEBUG( serialHexState("Sent") ); IFDEBUG( serialPrettyState("Sent") ); setClean(); // now that we've sent them, they're clean //-------------------------------------------------------------------- // TODO: Need to look at possibly merging the two states together at // this point. The purpose would be to minimize the turnaround time // for getting the most recent data to a CAT response. //-------------------------------------------------------------------- } /*! * @brief Perform required actions based on any dirty bits set. */ void UBitxRigState::processDirty() { } #endif #ifdef DEBUG char debugString[81] = {'\0'}; void UBitxRigState::serialHexState(const char* label = "RigState") { Serial.print(label); sprintf(debugString, ": %#010lx, %#010lx, %#010lx, %#010lx, %#010lx", data[DIRTY_WORD], data[VFOA_WORD], data[VFOB_WORD], data[OFFSETS_WORD], data[FLAGS_WORD]); Serial.println(debugString); } void UBitxRigState::serialPrettyState(const char* label = "RigState") { Serial.println(label); sprintf(debugString, "VFO A : %011ld %1c / VFO B : %011ld %1c", getFreqA(), isDirty(VFOA_WORD) ? 'D' : ' ', getFreqB(), isDirty(VFOB_WORD) ? 'D' : ' '); Serial.println(debugString); sprintf(debugString, "RIT : %011ld %1c / XIT : %011ld %1c", getRIT(), isDirty(OFFSETS_WORD) ? 'D' : ' ', getXIT(), isDirty(OFFSETS_WORD) ? 'D' : ' '); Serial.println(debugString); sprintf(debugString, "Split? %1c / VFO? %1c / RIT? %1c / XIT? %1c / Mode? %3s", isSplit() ? 'Y' : 'N', isVFOA() ? 'A' : 'B', isRIT() ? 'Y' : 'N', isXIT() ? 'Y' : 'N', isModeUSB() ? "USB" : (isModeLSB() ? "LSB" : (isModeCW() ? "CW " : (isModeCWR() ? "CWR" : " ")))); Serial.println(debugString); } #endif /**********************************************************************/ #ifndef TEENSYDUINO UBitxRigState _rigState; UBitxRigState& rigState = _rigState; #endif /*********************************************************************** * EOF **********************************************************************/