ubitx-v5x/TeensyDSP/RigState.cpp

448 lines
13 KiB
C++

/*!
* @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 <Wire.h>
#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<unsigned long>(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<uint16_t>(sideTone)) {
setSidetone(static_cast<uint16_t>(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 <i2c_t3.h>
/*!
* @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
**********************************************************************/