Merge remote-tracking branch 'ubitx-iop/master'. This incorporates the

IOP (Teensy 3.2) code.
This commit is contained in:
Rob French 2020-06-14 23:19:35 -05:00
commit 649784d285
19 changed files with 3473 additions and 0 deletions

472
iopcomm/iopcomm.cpp Normal file
View File

@ -0,0 +1,472 @@
//======================================================================
// iopcomm.cpp
//======================================================================
#include <Arduino.h>
#include "iopcomm.h"
#if defined(TEENSYDUINO)
// Compiling for the Teensy, so assuming this is IOP code.
#define MYSERIAL Serial1
#else
// Not compiling for the Teensy, so assuming this is Raduino code.
#define MYSERIAL Serial
#endif
//======================================================================
void sendCATMessage(const uint8_t* buf)
{
MYSERIAL.write(prefixAndLengthToByte(CAT_PREFIX, 5));
for (int i = 0; i < 5; i++) {
MYSERIAL.write(buf[i]);
}
}
//======================================================================
void sendIOPMessage(IOPMessage const& msg)
{
MYSERIAL.write(prefixAndLengthToByte(IOP_PREFIX, msg.len + 1));
MYSERIAL.write(msg.id);
for (int i = 0; i < msg.len; i++) {
MYSERIAL.write(msg.data[i]);
}
}
//======================================================================
void recvIOPMessage(IOPMessage& msg, const uint8_t* buf, int len)
{
msg.id = buf[0];
msg.len = len - 1;
for (int i = 1; i < len; i++) {
msg.data[i-1] = buf[i];
}
}
//======================================================================
void sendIOPModeCommand(rig_mode mode)
{
IOPMessage m;
m.id = IOP_MODE_COMMAND;
m.len = 1;
m.data[0] = static_cast<uint8_t>(mode);
sendIOPMessage(m);
}
//======================================================================
void sendIOPStartTxCommand()
{
IOPMessage m;
m.id = IOP_START_TX_COMMAND;
m.len = 0;
sendIOPMessage(m);
}
//======================================================================
void sendIOPStopTxCommand()
{
IOPMessage m;
m.id = IOP_STOP_TX_COMMAND;
m.len = 0;
sendIOPMessage(m);
}
//======================================================================
void sendIOPModeRequest()
{
IOPMessage m;
m.id = IOP_MODE_REQUEST;
m.len = 4; // NOTE: LEN = 4 for padding only... temporary
sendIOPMessage(m);
}
//======================================================================
void sendIOPDebugMessage(const char* text)
{
IOPMessage m;
m.id = IOP_DEBUG_MSG;
m.len = strlen(text);
if (m.len > IOP_MESSAGE_MAX_LEN) {
m.len = IOP_MESSAGE_MAX_LEN;
}
strncpy(m.data, text, m.len);
sendIOPMessage(m);
};
////======================================================================
//// SSB STATUS MESSAGE
////======================================================================
//void sendIOPSSBStatus(SSBConfig const &c)
//{
//IOPMessage m;
//m.id = IOP_SSB_STATUS_MSG;
//m.len = 4;
//m.data[0] = 'S'; // current mode; redundant w/ Raduino mode, but maybe useful for debugging
//m.data[1] = '-'; // placeholder for transmit filter/compressor
//m.data[2] = RX_FILTER_LETTER[c.filter];
//m.data[3] = '\0';
//sendIOPMessage(m);
//}
////======================================================================
//// DGT STATUS MESSAGE
////======================================================================
//void sendIOPDGTStatus(DGTConfig const &c)
//{
//IOPMessage m;
//m.id = IOP_DGT_STATUS_MSG;
//m.len = 4;
//m.data[0] = 'D'; // current mode; redundant w/ Raduino mode, but maybe useful for debugging
//m.data[1] = '-'; // placeholder for future digital submodes?
//m.data[2] = RX_FILTER_LETTER[c.filter];
//m.data[3] = '\0';
//sendIOPMessage(m);
//}
////======================================================================
//// CW STATUS MESSAGE
////======================================================================
//void sendIOPCWStatus(CWConfig const &c)
//{
//IOPMessage m;
//m.id = IOP_CW_STATUS_MSG;
//m.len = 4;
//m.data[0] = 'C'; // current mode; redundant w/ Raduino mode, but maybe useful for debugging
//m.data[1] = KEYER_MODE_LETTER[c.mode];
//m.data[2] = RX_FILTER_LETTER[c.filter];
//m.data[3] = '\0';
//sendIOPMessage(m);
//}
////======================================================================
//// TEST STATUS MESSAGE
////======================================================================
//void sendIOPTestStatus()
//{
//IOPMessage m;
//m.id = IOP_TEST_STATUS_MSG;
//m.len = 4;
//m.data[0] = ' ';
//m.data[1] = 'T';
//m.data[2] = 'T';
//m.data[3] = '\0';
//sendIOPMessage(m);
//}
//======================================================================
// MENU DISPLAY MESSAGE
//======================================================================
void sendIOPMenuDisplay(const char* text)
{
IOPMessage m;
int l = strlen(text);
m.id = IOP_MENU_DISPLAY_MSG;
m.len = 16;
for (int i = 0; i < 16; i++) {
if (i < l) {
m.data[i] = text[i];
} else {
m.data[i] = ' ';
}
}
m.data[16] = '\0';
sendIOPMessage(m);
}
//======================================================================
// MENU INACTIVE MESSAGE
//======================================================================
void sendIOPMenuInactive()
{
IOPMessage m;
m.id = IOP_MENU_INACTIVE_MSG;
m.len = 4; // NOTE: LEN = 4 for padding only... temporary
sendIOPMessage(m);
}
//======================================================================
/*
enum SerialState {
NORMAL = 0,
CAT_MODE,
IOP_MODE,
EEPROM_READ,
EEPROM_WRITE,
};
Translator::Translator(RigMode& m, CWConfig& c, Stream& s = SERIAL):
mode(m), cwConfig(c), serial(s)
{
for (int i = 0; i < NUM_PREFIX_IDS; i++) {
prefixCb[i] = NULL;
}
for (int i = 0; i < NUM_MESSAGE_IDS; i++) {
messageCb[i] = NULL;
}
}
void Translator::sendACK()
{
serial.write(prefixAndLengthToByte(ACK, 0));
}
void Translator::sendCATMessage(byte b, bool continued=false)
{
if (!continued) { // we're starting a CAT transmission sequence
} else { // we're continuing a CAT transmission sequence
}
}
void Translator::sendIOPMessage(IOPMessage const& m)
{
serial.write(prefixAndLengthToByte(IOP_PREFIX, m.len+1));
serial.write(m.id);
for (int i = 0; i < m.len; i++) {
serial.write(m.data[i]);
}
}
static void Translator::receive()
{
static SerialState state;
static PrefixID readPrefix = 0;
static uint8_t readLength = 0;
static IOPMessage iopMsg;
static uint8_t cmdLength = 0;
static byte cmdBuffer[16];
static char cmdString[17]; // added a char for null termination when required
static uint16_t eepromStartIndex;
static uint16_t eepromReadLength;
static int eepromMagicFlag = 0;
int incomingByte;
for (int i = 0; i < serial.available(); i++) {
incomingByte = serial.read();
switch(state) {
case NORMAL:
if (incomingByte == ACK) {
prefixCb[ACK](NULL);
} else {
readPrefix = byteToPrefix(incomingbyte);
readLength = byteToLength(incomingbyte);
if (readLength > 0) {
switch(readPrefix) {
case CAT_PREFIX:
case RAD_EEPROM_WRITE_PREFIX:
state = CAT_MODE;
break;
case IOP_PREFIX:
state = IOP_MODE;
iopMsg.len = readLength - 1;
cmdLength = 0;
break;
case RAD_EEPROM_READ_PREFIX:
state = EEPROM_READ;
readLength = 5;
eepromMagicFlag = 0;
break;
default:
// should never happen
break;
}
}
}
break;
case CAT_MODE:
// In CAT mode, we just pass thru the remaining bytes to the PC.
if
USBSERIAL.write(incomingByte);
readLength--;
if (readLength == 0) {
state = NORMAL;
}
break;
case IOP_MODE:
cmdBuffer[cmdLength] = incomingByte;
cmdLength++;
readLength--;
if (readLength == 0) {
processIOPCommand(cmdBuffer, cmdLength);
state = NORMAL;
}
break;
case EEPROM_READ:
readLength--;
switch(readLength) {
case 4:
eepromStartIndex = incomingByte;
if (incomingByte == 0x16) {
magicFlag++;
}
break;
case 3:
eepromStartIndex += (256 * incomingByte);
if (incomingByte == 0xe8) {
magicFlag++;
}
break;
case 2:
eepromReadLength = incomingByte;
break;
case 1:
eepromReadLength += (256 * incomingByte);
break;
case 0:
USBSERIAL.write(incomingByte);
if (magicFlag == 2) {
readLength = 126 + 2;
} else {
readLength = eepromReadLength + 2;
}
state = CAT_MODE;
break;
default:
// should never happen
break;
}
break;
case EEPROM_WRITE:
// TODO
break;
default:
// should never happen...
break;
}
}
}
void Translator::registerPrefixCb(PrefixID id, void (*func)(void*))
{
if (id >= 0 && id < NUM_PREFIX_IDS) {
prefixCb[id] = func;
}
void Translator::registerMessageCb(MessageID id, void (*func)(void*))
{
if (id >= 0 && id < NUM_MESSAGE_IDS) {
messageCb[id] = func;
}
}
void Translator::pack_ModeCommand(IOPMessage& m, RigMode r)
{
m.id = IOP_MODE_COMMAND;
m.len = 1;
m.data[0] = r;
}
void Translator::unpack_ModeCommand(IOPMessage const& m)
{
*mode = RigMode(m.data[0]);
}
void Translator::pack_CWConfig(IOPMessage &m, CWConfig const& c)
{
m.id = IOP_CW_CONFIG_MSG;
m.len = 6;
// mode
m.data[0] = byte(c.mode);
// flags
m.data[1] = NO_FLAGS;
m.data[1] |= (c.reversed ? REVERSED : 0);
// parameters
m.data[2] = byte(c.wpm);
m.data[3] = byte(c.weight * 10.0);
m.data[4] = byte(c.sidetone >> 8);
m.data[5] = byte(c.sidetone | 0x0F);
}
void Translator::unpack_CWConfig(IOPMessage const &m)
{
//if (m.id != IOP_CW_CONFIG_MSG || m.len != 6) {
// // do some error thing...
//}
//mode
cwConfig->mode = KeyMode(m.data[0]);
// flags
cwConfig->reversed = bool(m.data[1] & REVERSED);
// parameters
cwConfig->wpm = uint8_t(m.data[2]);
cwConfig->weight = float(m.data[3]) / 10.0;
cwConfig->sidetone = (m.data[4] << 8) | m.data[5];
}
void Translator::pack_ModeRequest(IOPMessage& m)
{
m.id = IOP_MODE_REQUEST;
m.len = 0;
}
void Translator::unpack_ModeRequest(IOPMessage const& m)
{
}
void Translator::unpack(IOPMessage const& m)
{
switch (m.id) {
case IOP_MODE_COMMAND:
unpack_ModeCommand(m);
messageCb[m.id](mode);
break;
case IOP_START_TX_COMMAND:
break;
case IOP_STOP_TX_COMMAND:
break;
case IOP_CW_CONFIG_MSG:
unpack_CWConfig(m);
messageCb[m.id](cwConfig);
break;
case IOP_MODE_REQUEST:
unpack_ModeRequest(m);
messageCb[m.id](NULL);
break;
case IOP_CW_STATUS_MSG:
break;
}
}
*/
//======================================================================
// EOF
//======================================================================

263
iopcomm/iopcomm.h Normal file
View File

@ -0,0 +1,263 @@
//======================================================================
// iopcomm.h
//======================================================================
#ifndef __iopcomm_h__
#define __iopcomm_h__
#include <stdint.h>
/* Message prefixes, starting with the ACK message (0). Message format
* consists of 1 or more bytes, where:
* b[0] - leftmost 3 bits == prefix
* - rightmost 5 bits == length of data (0-31)
* b[1]...b[31] (if present) - data bits
*/
enum PrefixID {
ACK = 0,
CAT_PREFIX,
IOP_PREFIX,
RAD_EEPROM_READ_PREFIX,
RAD_EEPROM_WRITE_PREFIX,
IOP_EEPROM_READ_PREFIX,
IOP_EEPROM_WRITE_PREFIX,
NUM_PREFIX_IDS
};
/* Masks for the leftmost 3 bits (prefix) and rightmost 5 bits (length)
* of a message.
*/
#define PREFIX_MASK 0xE0
#define LENGTH_MASK 0x1F
/* Convert a prefix and length to a byte, by shifting the prefix left
* 5 bits and OR-ing with the 5 bits of length.
*/
inline uint8_t prefixAndLengthToByte(PrefixID id, uint8_t len)
{
return (uint8_t(id) << 5) | (len & LENGTH_MASK);
}
/* Extract the prefix (leftmost 3 bits) from a byte.
*/
inline PrefixID byteToPrefix(uint8_t b)
{
return PrefixID(b >> 5);
}
/* Extract the length (rightmost 5 bits) from a byte.
*/
inline uint8_t byteToLength(byte b)
{
return uint8_t(b & LENGTH_MASK);
}
/* Message IDs/types, for IOP messages (messages between the Raduino and
* the IOP, not including CAT messages to be passed through).
*/
enum MessageID {
// Commands
IOP_MODE_COMMAND = 0,
IOP_START_TX_COMMAND,
IOP_STOP_TX_COMMAND,
IOP_CW_CONFIG_MSG,
IOP_DEBUG_MSG,
// Requests
IOP_MODE_REQUEST,
IOP_SSB_STATUS_MSG,
IOP_DGT_STATUS_MSG,
IOP_CW_STATUS_MSG,
IOP_TEST_STATUS_MSG,
IOP_MENU_DISPLAY_MSG,
IOP_MENU_INACTIVE_MSG,
// add any new elements here
NUM_MESSAGE_IDS
};
/* Max length of an IOP message payload (data), based on the following
* message format:
*
* byte 0: prefix (3 bits) + length (5 bits)
* byte 1: message ID (type)
* bytes 2-31: data bytes
*/
#define IOP_MESSAGE_MAX_LEN 30
/* Rig modes. Currently just defined as what the IOP cares about, but
* maybe these need to be expanded to encompass all of the rig modes.
* (e.g. USB, LSB, etc.)
*/
enum struct rig_mode {
ssb = 0,
cw,
digi,
// add new items here
count
};
/* Keyer modes.
*/
enum struct keyer_mode {
straight = 0,
iambic_a,
iambic_b,
//ultimatic,
//bug,
// add any new items here
count
};
enum struct rx_filter {
wide = 0,
medium,
narrow,
count
};
enum struct digi_submode {
rtty = 0,
psk31,
user_defined,
count
};
//const unsigned char RIG_MODE_LETTER[num_rig_modes] = {'s', 'S', 'c', 'C', 'd', 'D', 't', 'T'};
//const unsigned char KEYER_MODE_LETTER[num_keyer_modes] = {'S', 'A', 'B'};
//const unsigned char RX_FILTER_LETTER[num_rx_filters] = {'W', 'M', 'N'};
const uint8_t NO_FLAGS = 0;
const uint8_t REVERSED = 1;
struct IOPMessage {
IOPMessage() { memset(data, 0, IOP_MESSAGE_MAX_LEN); }
uint8_t id;
uint8_t len;
uint8_t data[IOP_MESSAGE_MAX_LEN];
};
//======================================================================
// IConfig
//
// Interface to a configuration object.
//======================================================================
struct bpf_config {
float lo_freq;
float hi_freq;
float gain;
};
struct mode_config {
mode_config(bool usb, rx_filter f, const bpf_config *fc) : is_usb(usb), filter(f) {
for (int i = 0; i < static_cast<int>(rx_filter::count); i++) {
filter_cfg[i] = fc[i];
}
}
bool is_usb;
rx_filter filter;
bpf_config filter_cfg[static_cast<size_t>(rx_filter::count)];
};
//======================================================================
// SSB CONFIGURATION
//======================================================================
const bpf_config ssb_filter_config[] =
{bpf_config{ 300.0, 3100.0, 1.0},
bpf_config{ 500.0, 2900.0, 1.0},
bpf_config{ 700.0, 2500.0, 1.0}};
struct comp_config {
bool enabled = false;
float threshold = 0.1;
float ratio = 20.0;
float gain = 2.0;
};
struct ssb_config : public mode_config {
ssb_config() : mode_config(true, rx_filter::wide, ssb_filter_config) {}
comp_config comp;
};
//======================================================================
// DIGI CONFIGURATION
//======================================================================
const bpf_config digi_filter_config[] =
{bpf_config{ 300.0, 3100.0, 1.0},
bpf_config{ 500.0, 2900.0, 1.0},
bpf_config{1250.0, 1750.0, 1.0}};
struct digi_config : public mode_config {
digi_config() : mode_config(true, rx_filter::wide, digi_filter_config) {}
digi_submode submode = digi_submode::user_defined;
};
//======================================================================
// CW CONFIGURATION
//======================================================================
const bpf_config cw_filter_config[] =
{bpf_config{ 300.0, 1300.0, 1.0},
bpf_config{ 450.0, 950.0, 1.0},
bpf_config{ 575.0, 825.0, 1.0}};
struct cw_config : public mode_config {
cw_config() : mode_config(true, rx_filter::wide, cw_filter_config) {}
keyer_mode mode = keyer_mode::iambic_a;
// flags
bool reversed = false;
// parameters
uint8_t wpm = 15;
float weight = 3.0;
uint16_t sidetone = 700;
};
//======================================================================
// TT CONFIGURATION
//======================================================================
/*
struct TTConfig : public IConfig {
public:
TTConfig(RxFilter f): filter(f) {}
// parameters
RxFilter filter = RX_FILTER_MEDIUM;
};
*/
//======================================================================
// FUNCTION PROTOTYPES
//======================================================================
void sendCATMessage(const uint8_t*);
void sendIOPMessage(IOPMessage const&);
void recvIOPMessage(IOPMessage&, const uint8_t*, int);
void sendIOPModeCommand(rig_mode);
void sendIOPStartTxCommand();
void sendIOPStopTxCommand();
void sendIOPDebugMessage(const char*);
void sendIOPModeRequest();
//void sendIOPSSBStatus(SSBConfig const&);
//void sendIOPDGTStatus(DGTConfig const&);
//void sendIOPCWStatus(CWConfig const&);
//void sendIOPTestStatus();
void sendIOPMenuDisplay(const char*);
void sendIOPMenuInactive();
#endif
//======================================================================
// EOF
//======================================================================

293
ubitx_iop/RigMode.h Normal file
View File

@ -0,0 +1,293 @@
//======================================================================
// RigMode.h
//======================================================================
#ifndef __RigMode_h__
#define __RigMode_h__
#include "audio.h"
//======================================================================
// basic_mode
//
// A basic rig mode. It can perform actions on_entry() and on_exit(),
// as well as on_tx() and on_rx(). These actions are defined in an
// appropriate subclass. In addition, the basic mode support an audio
// audio band pass filter with wide, medium, and narrow modes.
//======================================================================
class basic_mode {
public:
basic_mode(mode_config& c, RigAudio& a, bp_filter& f) :
config_(c), audio_(a), filter_(f), active_(false), transmitting_(false)
{
}
virtual ~basic_mode() {}
inline mode_config& config() { return config_; }
inline RigAudio& audio() { return audio_; }
inline bp_filter& filter() { return filter_; }
// Called upon mode entry. Should assume that the rig's state is
// "clean", i.e. that it still needs to enable anything it needs for
// use in the mode.
virtual void on_entry() = 0;
// Called upon mode exit. Should clean everything up that it used,
// and not make assumptions about which mode is being entered next.
virtual void on_exit() = 0;
// Called when transmitting.
virtual void on_tx() = 0;
// Called when receiving.
virtual void on_rx() = 0;
inline void enter() {
if (active_) {
// Do nothing. Exceptions not active (for Arduino, I assume?).
//throw MODE_ALREADY_ACTIVE;
USBDEBUG("ERROR - tried to enter mode, but it's already active");
} else {
active_ = true;
on_entry();
}
}
inline void exit() {
if (transmitting_) {
// Do nothing. Exceptions not active (for Arduino, I assume?).
//throw MODE_STILL_TRANSMITTING;
USBDEBUG("ERROR - tried to exit mode, but it's transmitting");
} else {
active_ = false;
on_exit();
}
}
inline bool is_active() { return active_; }
inline bool is_inactive() { return !active_; }
inline void tx() {
if (transmitting_) {
// Do nothing. Exceptions not active (for Arduino, I assume?).
//throw MODE_ALREADY_TRANSMITTING;
USBDEBUG("ERROR - tried to start transmitting, but mode already is transmitting");
} else {
transmitting_ = true;
on_tx();
}
}
inline void rx() {
if (!transmitting_) {
// Do nothing. Exceptions not active (for Arduino, I assume?).
//throw MODE_NOT_TRANSMITTING;
USBDEBUG("ERROR - tried to start receiving, but mode already is receiving");
} else {
transmitting_ = false;
on_rx();
}
}
inline bool is_tx() const { return transmitting_; }
inline bool is_rx() const { return !transmitting_; }
inline void set_rx_filter(rx_filter f) {
filter_.disable();
config_.filter = f;
filter_.init(config_.filter_cfg[static_cast<int>(config_.filter)]);
filter_.enable();
#if defined(DEBUG)
switch(config_.filter) {
case rx_filter::wide:
USBDEBUG("selected wide filter");
break;
case rx_filter::medium:
USBDEBUG("selected medium filter");
break;
case rx_filter::narrow:
USBDEBUG("selected narrow filter");
break;
}
#endif
}
private:
mode_config& config_;
RigAudio& audio_;
bp_filter& filter_;
bool active_;
bool transmitting_;
};
//======================================================================
// ssb_mode
//======================================================================
class ssb_mode : public basic_mode
{
public:
ssb_mode(ssb_config& c, RigAudio& a, bp_filter& f, speech_comp& s, bool default_mic=true) :
basic_mode(c, a, f), use_mic_(default_mic), comp_(s) {}
virtual void on_entry()
{
set_rx_filter(config().filter);
if (comp_.is_enabled()) {
enable_comp();
}
audio().unmuteRx();
audio().muteAllTx();
USBDEBUG("SSB mode entered");
}
virtual void on_exit() {
audio().muteAllTx();
audio().muteRx();
disable_comp();
USBDEBUG("SSB mode exited");
}
virtual void on_tx()
{
audio().muteRx();
if (use_mic_) {
audio().unmuteMicIn();
} else {
audio().unmuteLineIn();
}
USBDEBUG("SSB mode transmitting");
}
virtual void on_rx()
{
if (use_mic_) {
audio().muteMicIn();
} else {
audio().muteLineIn();
}
audio().unmuteRx();
USBDEBUG("SSB mode receiving");
}
void set_mic_in()
{
if (is_rx()) {
// can't switch inputs while already transmitting
use_mic_ = true;
}
USBDEBUG("SSB mode - Mic In set");
}
void set_line_in()
{
if (is_rx()) {
// can't switch inputs while already transmitting
use_mic_ = false;
}
USBDEBUG("SSB mode - Line In set");
}
inline void enable_comp() { comp_.enable(); }
inline void disable_comp() { comp_.disable(); }
private:
bool use_mic_;
speech_comp& comp_;
};
//======================================================================
// digi_mode
//======================================================================
class digi_mode : public basic_mode
{
public:
digi_mode(digi_config& c, RigAudio& a, bp_filter& f) :
basic_mode(c, a, f) {}
virtual void on_entry()
{
set_rx_filter(config().filter);
audio().unmuteRx();
audio().muteAllTx();
USBDEBUG("Digi mode entered");
}
virtual void on_exit() {
audio().muteAllTx();
audio().muteRx();
USBDEBUG("Digi mode exited");
}
virtual void on_tx()
{
audio().muteRx();
audio().unmuteUSBIn();
USBDEBUG("Digi mode transmitting");
}
virtual void on_rx()
{
audio().muteUSBIn();
audio().unmuteRx();
USBDEBUG("Digi mode receiving");
}
};
//======================================================================
// cw_mode
//======================================================================
class cw_mode : public basic_mode
{
public:
cw_mode(cw_config& c, RigAudio& a, bp_filter& f):
basic_mode(c, a, f) {}
virtual void on_entry()
{
set_rx_filter(config().filter);
audio().unmuteRx();
audio().muteAllTx();
USBDEBUG("CW mode entered");
}
virtual void on_exit() {
audio().muteAllTx();
audio().muteRx();
USBDEBUG("CW mode exited");
}
virtual void on_tx()
{
// Currently not muting Rx, since the uBITX produces it's own
// sidetone... but I'm probably going to replace that with a S/W-
// generated sidetone.
USBDEBUG("CW mode transmitting");
}
virtual void on_rx()
{
USBDEBUG("CW mode receiving");
}
};
#endif
//======================================================================
// EOF
//======================================================================

165
ubitx_iop/TxSwitch.h Normal file
View File

@ -0,0 +1,165 @@
//======================================================================
// TxSwitch.h
//======================================================================
#ifndef __TxSwitch_h__
#define __TxSwitch_h__
#include <Bounce2.h>
#define BOUNCE_WITH_PROMPT_DETECTION
#include "ubitx_iop.h"
#include "rig.h"
#define MIC_PTT_PIN 21
#define LINE_PTT_PIN 20
//----------------------------------------------------------------------
// ITxSwitch
//
// Interface for transmit (PTT, Key) switches. onPress() is called
// before transmission begins, and should return true if transmission
// should start. onRelease() is called before transmission ends, and
// should return true if transmission should in fact end.
//----------------------------------------------------------------------
class ITxSwitch
{
public:
virtual ~ITxSwitch() {}
// Called before beginning transmit; if false, transmit aborts before starting.
virtual bool onPress(basic_mode* m) = 0;
// Called before stopping tranmit; if false, transmit continues.
virtual bool onRelease(basic_mode* m) = 0;
void press(basic_mode* m, bool output_enable=true) {
if (onPress(m)) {
USBDEBUG("PTT pressed");
m->tx();
if (output_enable) {
setKeyDown(); // NOTE: could still make this more configurable...
}
}
}
void release(basic_mode* m, bool output_enable=true) {
if (onRelease(m)) {
USBDEBUG("PTT released");
if (output_enable) {
setKeyUp(); // NOTE: could still make this more configurable...
}
m->rx();
}
}
};
//----------------------------------------------------------------------
// CATSwitch
//
// Implementation of the ITxSwitch interface for the CAT control. In
// general, CAT cannot override any existing transmission, and cannot
// terminate an existing transmission.
//----------------------------------------------------------------------
class CATSwitch : public ITxSwitch
{
public:
CATSwitch(): _transmitting(false) {}
virtual bool onPress(basic_mode* m) {
// If another transmission is already occuring, abort... CAT can't
// interrupt transmissions already ongoing.
if (m->is_rx()) {
USBDEBUG("CAT PTT pressed");
_transmitting = true;
return true;
} else {
return false;
}
}
virtual bool onRelease(basic_mode* m) {
// If CAT transmission is not occurring, abort... CAT can't stop
// transmissions initiated by other sources. We don't check if
// the mode is already transmitting, because it could be
// transmitting because of CAT.
if (_transmitting) {
USBDEBUG("CAT PTT released");
_transmitting = false;
return true;
} else {
return false;
}
}
private:
bool _transmitting; // CAT-specific transmission
};
//----------------------------------------------------------------------
// GPIOSwitch
//
// Class used to implement the physical transmit switches (i.e. the
// Mic and Line input PTTs which are connected to GPIO pins). Takes
// an object implementing the ITxSwitch interface, as well as info for
// setting up a debounced pin.
//----------------------------------------------------------------------
class GPIOSwitch : public ITxSwitch
{
public:
GPIOSwitch(bool is_mic, int pin, int msec=25): _is_mic(is_mic), _ssb_mode(false), _bounce() {
_bounce.attach(pin, INPUT_PULLUP);
_bounce.interval(msec);
}
inline void setSSBMode(bool flag) { _ssb_mode = flag; }
virtual bool onPress(basic_mode* m) {
if (m->is_rx()) {
if (_ssb_mode) {
if (_is_mic) {
USBDEBUG("Mic PTT pressed");
((ssb_mode*)m)->set_mic_in();
} else {
USBDEBUG("Line PTT pressed");
((ssb_mode*)m)->set_line_in();
}
}
return true;
} else {
return false;
}
}
virtual bool onRelease(basic_mode* m) {
if (m->is_tx()) {
return true;
} else {
return false;
}
}
void update(basic_mode* m, bool output_enable=true) {
_bounce.update();
if (_bounce.fell()) {
press(m, output_enable);
} else if (_bounce.rose()) {
release(m, output_enable);
}
}
private:
bool _is_mic;
bool _ssb_mode;
Bounce _bounce;
};
#endif
//======================================================================
// EOF
//======================================================================

121
ubitx_iop/audio.h Normal file
View File

@ -0,0 +1,121 @@
//======================================================================
// audio.h
//
// NOTE: Let's change the name of this file to RigAudio.h.
//======================================================================
#ifndef __iop_audio_h__
#define __iop_audio_h__
#include <Audio.h>
#include <dynamicFilters.h>
#include <effect_compressor_fb.h>
#include "config.h"
class RigAudio
{
public:
RigAudio(AudioConfig& c): _config(c) {}
void init() const;
void muteRx() const;
void unmuteRx() const;
void muteAllTx() const;
void muteMicIn() const;
void unmuteMicIn() const;
void muteLineIn() const;
void unmuteLineIn() const;
void muteUSBIn() const;
void unmuteUSBIn() const;
void muteTTIn() const;
void unmuteTTIn() const;
void muteSpkrOut() const;
void unmuteSpkrOut() const;
void muteLineOut() const;
void unmuteLineOut() const;
void muteUSBOut() const;
void unmuteUSBOut() const;
private:
AudioConfig _config;
};
//======================================================================
class bp_filter {
public:
//bp_filter(double f1, double f2, bool use_center, short int window=-1, short int coeff=-1);
bp_filter();
bp_filter(AudioFilterFIR& f, AudioAmplifier& a);
void init(const bpf_config& cfg);
//void init(AudioFilterFIR* filter=NULL, short* coefficients=NULL);
void set_band(double f1, double f2);
void set_freq_lo(double f);
void set_freq_hi(double f);
void set_center_and_width(double c, double w);
void set_center(double c);
void set_width(double w);
void set_gain(double g);
void enable();
void disable();
private:
double freq_lo;
double freq_hi;
short int window;
short int coeff;
float recovery; // recovery amplifier value
AudioFilterFIR& filter; // = &filterRX;
AudioAmplifier& amp;
short coefficients[NUM_COEFFICIENTS];
bool setup_complete;
};
//======================================================================
class speech_comp
{
public:
speech_comp(comp_config* cfg);
// speech_comp(AudioEffectCompressor&, AudioAmplifier&, AudioAnalyzeRMS&);
void update();
void enable();
void disable();
inline bool is_enabled() const { return config_->enabled; }
private:
comp_config* config_;
AudioEffectCompressor& comp_;
AudioAmplifier& amp_;
AudioAnalyzeRMS& rms_;
float env_ = 1.0;
float alpha_ = 0.8;
};
//======================================================================
/*
void audioInit();
void audioSelectTxInput(TxInput);
void audioTransmit();
void audioReceive();
void audioCalibrate(AudioConfig *, char, char, char, float, bool);
*/
#endif
//======================================================================
// EOF
//======================================================================

370
ubitx_iop/audio.ino Normal file
View File

@ -0,0 +1,370 @@
//======================================================================
// audio.ino
//
// NOTE: Let's change the name of this file to Rigconfig.cc. Will need
// to ensure that "Arduino-isms" are resolved if it's converted to .cc
// from .ino, however.
//======================================================================
#include <dynamicFilters.h>
#include <effect_compressor_fb.h>
#include "audio.h"
//short firActive[NUM_COEFFICIENTS];
#define RX_RIG_IN 0
#define RX_USB_IN 1
#define RX_ST_IN 2 // sidetone
#define TX_MIC_IN 0
#define TX_LINE_IN 0
#define TX_USB_IN 1
#define TX_TEST_IN 2
//extern RigConfig rigConfig;
/*
#define DEFAULT_RX_INPUT RIG
#define DEFAULT_RX_OUTPUT SPKR
#define DEFAULT_TX_INPUT MIC
#define DEFAULT_TX_OUTPUT RIG
*/
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
// GUItool: begin automatically generated code
AudioInputI2S inLine; //xy=134,131
AudioInputUSB inUSB; //xy=134,303
AudioSynthWaveformSine sideTone; //xy=136,214
AudioSynthWaveformSine sine2; //xy=323,482
AudioSynthWaveformSine sine1; //xy=324,428
AudioAnalyzeRMS rmsRX; //xy=362,20
AudioAnalyzePeak peakRX; //xy=362,65
AudioEffectCompressor compTX; //xy=383,256
AudioAnalyzeRMS compRMS; //xy=388,224
AudioMixer4 mixRX; //xy=444,163
AudioAmplifier compAmp; //xy=514,257
AudioFilterFIR filterRX; //xy=577,161
AudioMixer4 mixTX; //xy=652,321
AudioAmplifier filterAmp; //xy=700,160
AudioAnalyzeRMS rmsTX; //xy=841,449
AudioAnalyzePeak peakTX; //xy=843,393
AudioAmplifier calRxUSB; //xy=873,172
AudioAmplifier calRxSpkr; //xy=877,62
AudioAmplifier calRxLine; //xy=880,119
AudioAmplifier calTxLine; //xy=892,255
AudioAmplifier calTxUSB; //xy=893,312
AudioOutputAnalog outSpkr; //xy=1053,62
AudioOutputUSB outUSB; //xy=1066,307
AudioOutputI2S outLine; //xy=1070,192
AudioConnection patchCord1(inLine, 0, rmsRX, 0);
AudioConnection patchCord2(inLine, 0, peakRX, 0);
AudioConnection patchCord3(inLine, 0, mixRX, 0);
AudioConnection patchCord4(inLine, 1, compTX, 0);
AudioConnection patchCord5(inLine, 1, compRMS, 0);
AudioConnection patchCord6(inUSB, 0, mixRX, 1);
AudioConnection patchCord7(inUSB, 1, mixTX, 1);
AudioConnection patchCord8(sideTone, 0, mixRX, 2);
AudioConnection patchCord9(sine2, 0, mixTX, 3);
AudioConnection patchCord10(sine1, 0, mixTX, 2);
AudioConnection patchCord11(compTX, compAmp);
AudioConnection patchCord12(mixRX, filterRX);
AudioConnection patchCord13(compAmp, 0, mixTX, 0);
AudioConnection patchCord14(filterRX, filterAmp);
AudioConnection patchCord15(mixTX, calTxLine);
AudioConnection patchCord16(mixTX, calTxUSB);
AudioConnection patchCord17(mixTX, peakTX);
AudioConnection patchCord18(mixTX, rmsTX);
AudioConnection patchCord19(filterAmp, calRxSpkr);
AudioConnection patchCord20(filterAmp, calRxLine);
AudioConnection patchCord21(filterAmp, calRxUSB);
AudioConnection patchCord22(calRxUSB, 0, outUSB, 0);
AudioConnection patchCord23(calRxSpkr, outSpkr);
AudioConnection patchCord24(calRxLine, 0, outLine, 0);
AudioConnection patchCord25(calTxLine, 0, outLine, 1);
AudioConnection patchCord26(calTxUSB, 0, outUSB, 1);
AudioControlSGTL5000 audioCtrl; //xy=648,517
// GUItool: end automatically generated code
void RigAudio::init() const {
USBDEBUG("audio initialization started");
audioCtrl.enable();
audioCtrl.adcHighPassFilterEnable(); // default
audioCtrl.dacVolume(1.0); // default
audioCtrl.dacVolumeRampDisable(); // not default; just trying to cull out culprits for my high freq hiss
audioCtrl.audioProcessorDisable();
audioCtrl.autoVolumeDisable();
audioCtrl.surroundSoundDisable();
audioCtrl.enhanceBassDisable();
audioCtrl.eqSelect(FLAT_FREQUENCY); // eventually, use this to smooth out the normal uBITX passband
audioCtrl.muteHeadphone(); // not using the headphone output
audioCtrl.volume(0.0); // not using the headphone output
audioCtrl.inputSelect(AUDIO_INPUT_LINEIN); // required for RX audio
audioCtrl.unmuteLineout(); // required for RX audio
audioCtrl.lineInLevel(_config.rxRigInLevel, _config.txLineInLevel); // NOTE: need to see if this persists through input changes (see mic gain...)
audioCtrl.lineOutLevel(_config.rxLineOutLevel, _config.txRigOutLevel); // NOTE: need to see if this persists through input changes (see mic gain...)
audioCtrl.micGain(_config.txMicInGain); // superfluous, as I have to do this anytime I switch to mic for some reason
// configure line input
audioCtrl.lineInLevel(_config.rxRigInLevel, _config.txLineInLevel);
// configure line output
calTxLine.gain(_config.txRigOutCal);
audioCtrl.lineOutLevel(_config.rxLineOutLevel, _config.txRigOutLevel);
// configure "receive" of USB audio input (debug only)
if (_config.rxUSBInEnable) {
mixRX.gain(RX_USB_IN, _config.rxUSBInVol * _config.rxUSBInCal);
} else {
mixRX.gain(RX_USB_IN, 0.0);
}
// configure USB audio output of transmit audio (useful for debug)
if (_config.txUSBOutEnable) {
calTxUSB.gain(_config.txUSBOutCal);
} else {
calTxUSB.gain(0.0);
}
// setup the two-tone generator
sine1.frequency(700);
sine2.frequency(1900);
sine1.amplitude(0);
sine2.amplitude(0);
//audioFilter(firActive, NUM_COEFFICIENTS, ID_BANDPASS, W_HAMMING, 300.0, 3100.0); // 2.8 kHz filter
//filterRX.begin(firActive, NUM_COEFFICIENTS);
filterAmp.gain(1.0);
// for now, just pass through the compressor
compTX.disable();
compAmp.gain(1.0);
// Hardware should be all setup... now we're going to mute everything
// and let the modes take care of enabling/disabling what they should.
for (int i = 0; i < 4; i++) {
mixRX.gain(i, 0.0);
mixTX.gain(i, 0.0);
}
audioEqualizer();
USBDEBUG("audio initialization completed");
}
void RigAudio::muteRx() const {
mixRX.gain(RX_RIG_IN, 0.0);
USBDEBUG("RX audio muted");
}
void RigAudio::unmuteRx() const {
audioCtrl.inputSelect(AUDIO_INPUT_LINEIN);
mixRX.gain(RX_RIG_IN, _config.rxRigInVol * _config.rxRigInCal);
USBDEBUG("RX audio unmuted");
}
void RigAudio::muteAllTx() const {
muteMicIn();
muteLineIn();
muteUSBIn();
muteTTIn();
USBDEBUG("all TX audio muted");
}
void RigAudio::muteMicIn() const {
mixTX.gain(TX_LINE_IN, 0.0);
USBDEBUG("Mic In audio muted");
}
void RigAudio::unmuteMicIn() const {
audioCtrl.inputSelect(AUDIO_INPUT_MIC);
audioCtrl.micGain(_config.txMicInGain);
mixTX.gain(TX_LINE_IN, _config.txMicInVol * _config.txMicInCal);
USBDEBUG("Mic In audio unmuted");
}
void RigAudio::muteLineIn() const {
mixTX.gain(TX_LINE_IN, 0.0);
USBDEBUG("Line In audio muted");
}
void RigAudio::unmuteLineIn() const {
audioCtrl.inputSelect(AUDIO_INPUT_LINEIN);
mixTX.gain(TX_LINE_IN, _config.txLineInVol * _config.txLineInCal);
USBDEBUG("Line In audio unmuted");
}
void RigAudio::muteUSBIn() const {
mixTX.gain(TX_USB_IN, 0.0);
USBDEBUG("USB In audio muted");
}
void RigAudio::unmuteUSBIn() const {
mixTX.gain(TX_USB_IN, _config.txUSBInVol * _config.txUSBInCal);
USBDEBUG("USB In audio unmuted");
}
void RigAudio::muteTTIn() const {
mixTX.gain(TX_TEST_IN, 0.0);
mixTX.gain(TX_TEST_IN + 1, 0.0);
sine1.amplitude(0.0);
sine2.amplitude(0.0);
USBDEBUG("Two Tone audio muted");
}
void RigAudio::unmuteTTIn() const {
sine1.amplitude(0.5);
sine2.amplitude(0.5);
mixTX.gain(TX_TEST_IN, _config.txSine1Vol);
mixTX.gain(TX_TEST_IN + 1, _config.txSine2Vol);
USBDEBUG("Two Tone audio unmuted");
}
void RigAudio::muteSpkrOut() const {
calRxSpkr.gain(0.0);
USBDEBUG("Speaker Out audio muted");
}
void RigAudio::unmuteSpkrOut() const {
calRxSpkr.gain(_config.rxSpkrOutCal);
USBDEBUG("Speaker Out audio unmuted");
}
void RigAudio::muteLineOut() const {
calRxLine.gain(0.0);
USBDEBUG("Line Out audio muted");
}
void RigAudio::unmuteLineOut() const {
calRxLine.gain(_config.rxLineOutCal);
USBDEBUG("Line Out audio unmuted");
}
void RigAudio::muteUSBOut() const {
calRxUSB.gain(0.0);
USBDEBUG("USB Out audio muted");
}
void RigAudio::unmuteUSBOut() const {
calRxUSB.gain(_config.rxUSBOutCal);
USBDEBUG("USB Out audio unmuted");
}
/*
RxInput audioRxInput;
RxOutput audioRxOutput;
TxInput audioTxInput;
TxOutput audioTxOutput;
*/
//======================================================================
// bp_filter
//
// Band Pass Filter methods.
//======================================================================
bp_filter::bp_filter(): filter(filterRX), amp(filterAmp) {}
bp_filter::bp_filter(AudioFilterFIR& f, AudioAmplifier& a): filter(f), amp(a) {}
void bp_filter::init(const bpf_config& cfg) {
set_band(cfg.lo_freq, cfg.hi_freq);
set_gain(cfg.gain);
}
void bp_filter::set_band(double f1, double f2) {
freq_lo = f1;
freq_hi = f2;
}
void bp_filter::set_freq_lo(double f) { freq_lo = f; }
void bp_filter::set_freq_hi(double f) { freq_hi = f; }
void bp_filter::set_center_and_width(double c, double w) {
freq_lo = c - (0.5 * w);
freq_hi = c + (0.5 * w);
}
void bp_filter::set_center(double c) {
double w = freq_hi - freq_lo;
set_center_and_width(c, w);
}
void bp_filter::set_width(double w) {
double c = (freq_lo + freq_hi) * 0.5;
set_center_and_width(c, w);
}
void bp_filter::set_gain(double g) { recovery = g; }
void bp_filter::enable() {
audioFilter(coefficients, NUM_COEFFICIENTS, ID_BANDPASS, W_HAMMING, freq_lo, freq_hi);
filter.begin(coefficients, NUM_COEFFICIENTS);
amp.gain(recovery);
}
void bp_filter::disable() {
filter.begin(FIR_PASSTHRU, NUM_COEFFICIENTS);
amp.gain(1.0);
}
//======================================================================
speech_comp::speech_comp(comp_config* cfg) :
config_(cfg), comp_(compTX), amp_(compAmp), rms_(compRMS) {}
void speech_comp::enable()
{
config_->enabled = true;
comp_.begin(1, config_->threshold, config_->ratio); // Need to make configurable
amp_.gain(config_->gain);
}
void speech_comp::disable()
{
config_->enabled = false;
comp_.disable();
amp_.gain(1.0);
}
// Speech compressor code based on post by 'hyperdyne': https://forum.pjrc.com/threads/36245-Changing-Pitch-of-Voice
void speech_comp::update()
{
float rms_cur;
if (config_->enabled && rms_.available()) {
rms_cur = rms_.read();
env_ = rms_cur + (alpha_ * (env_ - rms_cur));
comp_.update_pwr(env_);
}
}
//======================================================================
int eqFilter1[5];
void audioEqualizer()
{
audioCtrl.audioPreProcessorEnable();
audioCtrl.eqSelect(PARAMETRIC_EQUALIZER);
// calcBiquad(FilterType,FrequencyC,dBgain,Q,QuantizationUnit,SampleRate,int*);
calcBiquad(FILTER_PARAEQ, 2700, 6, 0.707, 524288, 44100, eqFilter1);
// calcBiquad(FILTER_HIPASS, 100, 0, 0.707, 524288, 44100, hpFilter);
audioCtrl.eqFilter(0, eqFilter1);
// audioCtrl.eqFilter(1, hpFilter);
// audioCtrl.eqFilter(2, lpFilter);
// audioCtrl.eqFilter(3, hpFilter);
// audioCtrl.eqFilter(4, lpFilter);
// audioCtrl.eqFilter(5, hpFilter);
}
//======================================================================
// EOF
//======================================================================

27
ubitx_iop/cat.h Normal file
View File

@ -0,0 +1,27 @@
//======================================================================
// cat.h
//======================================================================
#ifndef __iop_cat_h__
#define __iop_cat_h__
//#define ACK 0
//#define CAT_PREFIX 0xC0
//#define IOP_PREFIX 0xD0
//#define EEPROM_READ_PREFIX 0xE0
//#define EEPROM_WRITE_PREFIX 0xF0
#include <iopcomm.h>
#include "config.h"
#define USBSERIAL Serial
#define HWSERIAL Serial1
void initCAT(long, int);
void serviceCAT();
#endif
//======================================================================
// EOF
//======================================================================

354
ubitx_iop/cat.ino Normal file
View File

@ -0,0 +1,354 @@
//======================================================================
// cat.ino
//======================================================================
#include "cat.h"
#include "TxSwitch.h"
// make these messages static inside a function
IOPMessage inBuf; // input message buffer
IOPMessage outBuf; // output message buffer
extern CATSwitch catPTT;
extern basic_rig rig;
int received_mode = 0;
//======================================================================
// CAT from PC-to-IOP
//
// The general concept for IOP use of CAT is for the IOP to pass thru
// all incoming CAT data (from the PC) to the Raduino.
//
// This might change in the future, if we want to grab CAT data straight
// from the PC. That might apply to things like specific audio filter
// settings or something, but since the Raduino modes are an important
// part of the mix, I think the commands really need to come from the
// Raduino... and besides, what if a PC is not connected?
//
//
// For data coming from the Raduino, the IOP does have to do a minimal
// processing to extra any Raduino-to-IOP commands.
//======================================================================
void initCAT(long baud, int portConfig)
{
// CAT with PC via USB
USBSERIAL.begin(baud);
USBSERIAL.flush();
USBDEBUG("opened USB serial port (to PC)");
// Comm (including CAT passthru) with Raduino via UART
HWSERIAL.begin(baud, portConfig);
HWSERIAL.flush();
USBDEBUG("opened H/W serial port (to Raduino)");
//sendIOPModeRequest(); // Raduino doesn't support this yet
}
//======================================================================
void processIOPCommand(IOPMessage const& m)
{
switch(inBuf.id) {
case IOP_MODE_COMMAND:
received_mode = 2;
USBDEBUG("IOP_MODE_COMMAND received (from Raduino)");
if (m.len < 1) {
return;
} else {
rig.set_rig_mode(static_cast<rig_mode>(m.data[0]));
#if defined(DEBUG)
switch(rig.get_rig_mode()) {
case rig_mode::cw:
USBDEBUG("new mode - CW");
break;
case rig_mode::ssb:
USBDEBUG("new mode - SSB");
break;
case rig_mode::digi:
USBDEBUG("new mode - DIG");
break;
default:
char errormessage[32];
sprintf(errormessage, "unknown mode command - %3d", rig.get_rig_mode());
USBDEBUG("mode command not recognized");
}
#endif
}
break;
case IOP_START_TX_COMMAND:
catPTT.press(rig.mode());
break;
case IOP_STOP_TX_COMMAND:
catPTT.release(rig.mode());
break;
case IOP_CW_CONFIG_MSG:
break;
case IOP_DEBUG_MSG:
USBDEBUG("IOP_DEBUG_MSG");
USBDEBUG((const char*)m.data);
break;
}
}
//======================================================================
void processCalCommand(const char* buf)
{
int count;
char cmd;
char subcmd;
char parm;
float value;
count = sscanf(buf, "%1c %1c %1c %f", &cmd, &subcmd, &parm, &value);
if (count < 3) {
USBSERIAL.println("Calibration: invalid command");
} else {
switch(cmd) {
case 'r':
case 'R':
case 't':
case 'T':
//audioCalibrate(&rigConfig.audio, cmd, subcmd, parm, value, (count == 4));
break;
default:
USBSERIAL.println("Calibration: invalid command");
}
}
}
//======================================================================
enum SerialState {
NORMAL = 0,
CAT_MODE,
IOP_MODE,
EEPROM_READ,
EEPROM_WRITE,
} serialState = NORMAL;
PrefixID readPrefix;
uint8_t readLength;
int cmdLength = 0;
byte cmdBuffer[16];
char cmdString[17]; // added a char for null termination when required
uint16_t eepromStartIndex;
uint16_t eepromReadLength;
int magicFlag = 0;
//----------------------------------------------------------------------
uint8_t usbCatLength = 0;
byte usbCatBuffer[5];
elapsedMillis usbCatTimer;
#define CAT_RECEIVE_TIMEOUT 500
void serviceCAT()
{
uint8_t incomingByte;
// First, if we've never received or requested a mode command from the Raduino, send a request.
if (received_mode == 0) {
USBDEBUG("requesting mode");
sendIOPModeRequest();
received_mode = 1;
}
if ((usbCatLength > 0) && (usbCatTimer > CAT_RECEIVE_TIMEOUT)) {
// timeout... clear the buffer and start over
usbCatLength = 0;
usbCatTimer = 0;
}
// read from the USB serial, pass through to UART serial
for (int i = 0; i < USBSERIAL.available(); i++) {
incomingByte = USBSERIAL.read();
usbCatTimer = 0;
#if not defined(FACTORY_CALIBRATION)
usbCatBuffer[usbCatLength++] = incomingByte;
if (usbCatLength == 5) {
sendCATMessage(usbCatBuffer);
usbCatLength = 0;
}
// NOTE: This code does NOT handle any interrupts in COMMS from the PC...
#endif
#if defined(FACTORY_CALIBRATION)
// unless we're in factory calibration mode, in which case we're going
// to process calibration commands...
switch(incomingByte) {
case ';':
cmdString[cmdLength] = '\0';
if (cmdLength > 0) {
processCalCommand(cmdString);
cmdLength = 0;
}
break;
case '\n':
case '\r':
cmdString[0] = '\0';
cmdLength = 0;
break;
default:
cmdString[cmdLength++] = char(incomingByte);
if (cmdLength == 16) {
cmdString[cmdLength] = '\0';
processCalCommand(cmdString);
cmdLength = 0;
}
}
#else
// Don't pass CAT commands through if in DEBUG mode.
#if not defined(DEBUG)
//HWSERIAL.write(incomingByte);
#endif
}
// read from the UART serial, see what we need to do with it
for (int i = 0; i < HWSERIAL.available(); i++) {
incomingByte = HWSERIAL.read();
#if defined(DEBUG)
char ibBuff[20];
itoa(serialState, ibBuff, 10);
USBSERIAL.print("IOP: serialState = ");
USBSERIAL.print(ibBuff);
itoa(incomingByte, ibBuff, 10);
USBSERIAL.print("; incomingByte = ");
USBSERIAL.println(ibBuff);
#endif
switch(serialState) {
case NORMAL:
if (incomingByte == ACK) {
USBSERIAL.write(incomingByte);
} else {
readPrefix = byteToPrefix(incomingByte);
readLength = byteToLength(incomingByte);
#if defined(DEBUG)
itoa(readPrefix, ibBuff, 10);
USBSERIAL.print("IOP: readPrefix = ");
USBSERIAL.print(ibBuff);
itoa(readLength, ibBuff, 10);
USBSERIAL.print("; readLength = ");
USBSERIAL.println(ibBuff);
#endif
if (readLength > 0) {
USBDEBUG("readLength > 0");
switch(readPrefix) {
case CAT_PREFIX:
case RAD_EEPROM_WRITE_PREFIX:
serialState = CAT_MODE;
break;
case IOP_PREFIX:
USBDEBUG("entering IOP_MODE");
serialState = IOP_MODE;
cmdLength = 0;
break;
case RAD_EEPROM_READ_PREFIX:
serialState = EEPROM_READ;
readLength = 5;
magicFlag = 0;
break;
default:
// should never happen
break;
}
}
}
break;
case CAT_MODE:
// In CAT mode, we just pass thru the remaining bytes to the PC.
USBSERIAL.write(incomingByte);
readLength--;
if (readLength == 0) {
serialState = NORMAL;
}
break;
case IOP_MODE:
cmdBuffer[cmdLength] = incomingByte;
cmdLength++;
readLength--;
if (readLength == 0) {
recvIOPMessage(inBuf, cmdBuffer, cmdLength);
processIOPCommand(inBuf);
serialState = NORMAL;
}
break;
case EEPROM_READ:
readLength--;
switch(readLength) {
case 4:
eepromStartIndex = incomingByte;
if (incomingByte == 0x16) {
magicFlag++;
}
break;
case 3:
eepromStartIndex += (256 * incomingByte);
if (incomingByte == 0xe8) {
magicFlag++;
}
break;
case 2:
eepromReadLength = incomingByte;
break;
case 1:
eepromReadLength += (256 * incomingByte);
break;
case 0:
USBSERIAL.write(incomingByte);
if (magicFlag == 2) {
readLength = 126 + 2;
} else {
readLength = eepromReadLength + 2;
}
serialState = CAT_MODE;
break;
default:
// should never happen
break;
}
break;
case EEPROM_WRITE:
// TODO
break;
default:
// should never happen...
break;
}
#endif
}
}
//======================================================================
// EOF
//======================================================================

137
ubitx_iop/config.h Normal file
View File

@ -0,0 +1,137 @@
//======================================================================
// config.h
//
// NOTE: Let's change the name of this file to RigConfig.h.
//======================================================================
#ifndef __iop_config_h__
#define __iop_config_h__
#include <iopcomm.h>
//#include "rig.h"
#define KEYER_LEFT_PADDLE_PIN 16
#define KEYER_RIGHT_PADDLE_PIN 17
// Uncomment to use the "factory" calibration mode. This is intended to
// allow calibration of IOP settings, separately from the uBITX/Raduino.
// There will be no pass-thru of any CAT.
//#define FACTORY_CALIBRATION
//======================================================================
// AudioConfig
//======================================================================
class AudioConfig {
public:
//--------------------------------------------------------------------
// RECEIVE PARAMETERS
//--------------------------------------------------------------------
// rig-in parameters (RX)
uint8_t rxRigInLevel = 8; //5;
float rxRigInVol = 1.0; //0.7;
float rxRigInCal = 8.0; //1.0;
// USB-in parameters (RX) - debug/monitor use only
bool rxUSBInEnable = false;
float rxUSBInVol = 1.0;
float rxUSBInCal = 1.0;
// speaker-out (DAC) parameters (RX)
float rxSpkrOutCal = 1.0;
// line-out parameters (RX)
uint8_t rxLineOutLevel = 29;
float rxLineOutCal = 1.0;
// USB-out parameters (RX)
float rxUSBOutCal = 1.0;
// sidetone parameters (really a TX thing, but "RX" side of audio)
float sideToneVol = 1.0;
//--------------------------------------------------------------------
// TRANSMIT PARAMETERS
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// NOTE: Default rig-out parameters are based on trying to hit a
// 25mVrms output to the rig. This is based on some discussion at the
// following URL:
//
// https://groups.io/g/BITX20/message/58951
//
// This may or may not be totally applicable. I believe it was for
// IMD/spurs on a pre-V5 version of the uBITX. So it may be OBE for
// my V5. It also alludes to modifications to the transmit audio
// chain, which may be something I need to look into (i.e. increasing
// the gain on the initial mic pre-amp).
//
// Specifically, for the rig-out parameters:
// line out level 31 = 1.16 V p-p
// = 0.58 V peak
// = 0.41 V rms = 410 mV rms
// so output calibration needs to reduce that to 17.5 mV rms (70%
// of 25 mV rms)
// txMicInCal = 0.043 ... seems pretty low...
//
// Likewise, default line-in (TX) parameters assume consumer line
// level input, but with the default audio adaptor line-in level
// setting (5 = 1.33V p-p).
// line in level 5 = 1.33 V p-p
// signal in = 0.894 V p-p
// = 0.447 V peak
// = 0.316 V rms = 316 mV rms
// so input calibration needs to convert that to the 410 mV noted
// above (equivalent for the microphone), so that when that signal
// gets to the rig-out, it gets treated the same as the mic input.
// txLineInCal = 1.30
//--------------------------------------------------------------------
// microphone-in parameters (TX)
uint8_t txMicInGain = 12;
float txMicInVol = 1.0; //0.7;
float txMicInCal = 1.0;
// line-in parameters (TX)
uint8_t txLineInLevel = 5;
float txLineInVol = 1.0; //0.7;
float txLineInCal = 1.30;
// USB-in parameters (TX)
float txUSBInVol = 1.0; //0.7;
float txUSBInCal = 1.0;
// two-tone parameters (TX)
float txSine1Vol = 1.0;
float txSine2Vol = 1.0;
// rig-out parameters (TX) - default settings are based on hitting 70% of 25mVrms w/ mic
uint8_t txRigOutLevel = 31;
float txRigOutCal = 0.1; // 0.061;
// USB-out parameters (TX)- debug/monitor use only
bool txUSBOutEnable = true;
float txUSBOutCal = 1.0;
};
//======================================================================
// RigConfig
// Used to store configuration parameters during runtime, as well as to
// save them to EEPROM.
//======================================================================
class RigConfig {
public:
RigConfig() {}
// audio configuration
AudioConfig audio;
// mode configuration
ssb_config ssb;
digi_config digi;
cw_config cw;
// General rig configuration entries.
rig_mode mode = rig_mode::ssb;
};
extern RigConfig rigConfig;
#endif
//======================================================================
// EOF
//======================================================================

9
ubitx_iop/config.ino Normal file
View File

@ -0,0 +1,9 @@
//======================================================================
// config.ino
//======================================================================
#include "config.h"
//======================================================================
// EOF
//======================================================================

14
ubitx_iop/eeprom.h Normal file
View File

@ -0,0 +1,14 @@
//======================================================================
// eeprom.h
//======================================================================
#ifndef __iop_eeprom_h__
#define __iop_eeprom_h__
#include "config.h"
#endif
//======================================================================
// EOF
//======================================================================

9
ubitx_iop/eeprom.ino Normal file
View File

@ -0,0 +1,9 @@
//======================================================================
// eeprom.ino
//======================================================================
#include "eeprom.h"
//======================================================================
// EOF
//======================================================================

81
ubitx_iop/keyer.h Normal file
View File

@ -0,0 +1,81 @@
//**********************************************************************
//
// Keyer, a part of nanoIO
//
// nanoIO paddle keyer (c) 2018, David Freese, W1HKJ
//
// based on code from Iambic Keyer Code Keyer Sketch
// Copyright (c) 2009 Steven T. Elliott
//
// nanoIO is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// nanoIO is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with fldigi. If not, see <http://www.gnu.org/licenses/>.
//
//Revisions:
//
//1.0.0: Initial release
//
//**********************************************************************
#ifndef __iop_keyer_h__
#define __iop_keyer_h__
//#include "Arduino.h"
//#include "config.h"
#define IAMBICA 0
#define IAMBICB 1
#define STRAIGHT 2
class Keyer
{
private:
//int cw_pin_;
//int ptt_pin_;
bool key_down;
long ktimer;
int _speed;
int _dashlen; // Length of dash
int _dotlen; // Length of dot
int _space_len; // Length of space
float _weight;
char keyerControl;
char keyerState;
int key_mode;
void calc_ratio();
void update_PaddleLatch();
public:
Keyer(int wpm, float _weight);
//void cw_pin(int pin);
//void ptt_pin(int pin);
void wpm(int spd);
void set_mode(int md);
int get_mode() { return key_mode; }
bool is_down() { return key_down; }
// void set__weight();
bool do_paddles();
};
#endif
//======================================================================
// EOF
//======================================================================

214
ubitx_iop/keyer.ino Normal file
View File

@ -0,0 +1,214 @@
//======================================================================
//
// nanoIO paddle keyer (c) 2018, David Freese, W1HKJ
//
// based on code from Iambic Keyer Code Keyer Sketch
// Copyright (c) 2009 Steven T. Elliott
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details:
//
// Free Software Foundation, Inc., 59 Temple Place, Suite 330,
// Boston, MA 02111-1307 USA
//
//======================================================================
//#include "Arduino.h"
//#include "TimerOne.h"
#include "config.h"
#include "keyer.h"
const uint8_t LP_in = KEYER_LEFT_PADDLE_PIN;
const uint8_t RP_in = KEYER_RIGHT_PADDLE_PIN;
//#define ST_Freq 600 // Set the Sidetone Frequency to 600 Hz
//======================================================================
// keyerControl bit definitions
//
#define DIT_L 0x01 // Dit latch
#define DAH_L 0x02 // Dah latch
#define DIT_PROC 0x04 // Dit is being processed
#define PDLSWAP 0x08 // 0 for normal, 1 for swap
//======================================================================
//
// State Machine Defines
enum KSTYPE {IDLE, CHK_DIT, CHK_DAH, KEYED_PREP, KEYED, INTER_ELEMENT };
Keyer::Keyer(int wpm, float weight)
{
//ptt_pin_ = PTT_PIN;
//cw_pin_ = CW_PIN;
// Setup outputs
pinMode(LP_in, INPUT_PULLUP); // sets Left Paddle digital pin as input
pinMode(RP_in, INPUT_PULLUP); // sets Right Paddle digital pin as input
// pinMode(ST_Pin, OUTPUT); // Sets the Sidetone digital pin as output
// digitalWrite(LP_in, HIGH); // Enable pullup resistor on Left Paddle Input Pin
// digitalWrite(RP_in, HIGH); // Enable pullup resistor on Right Paddle Input Pin
keyerState = IDLE;
keyerControl = 0;
key_mode = IAMBICA;
key_down = false;
_weight = weight;
_speed = wpm;
calc_ratio();
}
// Calculate the length of dot, dash and silence
void Keyer::calc_ratio()
{
float w = (1 + _weight) / (_weight -1);
_space_len = (1200 / _speed);
_dotlen = _space_len * (w - 1);
_dashlen = (1 + w) * _space_len;
}
//void Keyer::cw_pin(int pin)
//{
// ptt_pin_ = pin;
//}
//void Keyer::ptt_pin(int pin)
//{
// cw_pin_ = pin;
//}
void Keyer::set_mode(int md)
{
key_mode = md;
}
void Keyer::wpm(int wpm)
{
_speed = wpm;
calc_ratio();
}
//======================================================================
// Latch paddle press
//======================================================================
void Keyer::update_PaddleLatch()
{
if (digitalRead(LP_in) == LOW) {
keyerControl |= DIT_L;
}
if (digitalRead(RP_in) == LOW) {
keyerControl |= DAH_L;
}
}
bool Keyer::do_paddles()
{
if (key_mode == STRAIGHT) { // Straight Key
if ((digitalRead(LP_in) == LOW) || (digitalRead(RP_in) == LOW)) {
// Key from either paddle
// digitalWrite(ptt_pin_, HIGH);
// digitalWrite(cw_pin_, HIGH);
// tone(ST_Pin, 600);
key_down = true;
return true;
} else {
// digitalWrite(ptt_pin_, LOW);
// digitalWrite(cw_pin_, LOW);
// noTone(ST_Pin);
key_down = false;
}
return false;
}
// keyerControl contains processing flags and keyer mode bits
// Supports Iambic A and B
// State machine based, uses calls to millis() for timing.
switch (keyerState) {
case IDLE: // Wait for direct or latched paddle press
if ((digitalRead(LP_in) == LOW) || (digitalRead(RP_in) == LOW) || (keyerControl & 0x03)) {
update_PaddleLatch();
keyerState = CHK_DIT;
// letting this fall through // return true;
} else {
return false;
}
// break;
case CHK_DIT: // See if the dit paddle was pressed
if (keyerControl & DIT_L) {
keyerControl |= DIT_PROC;
ktimer = _dotlen;
keyerState = KEYED_PREP;
return true;
} else { // fall through
keyerState = CHK_DAH;
}
case CHK_DAH: // See if dah paddle was pressed
if (keyerControl & DAH_L) {
ktimer = _dashlen;
keyerState = KEYED_PREP;
// letting this fall through // return true;
} else {
keyerState = IDLE;
return false;
}
// break;
case KEYED_PREP: // Assert key down, start timing
// state shared for dit or dah
// digitalWrite(ptt_pin_, HIGH); // Enable PTT
// tone(ST_Pin, ST_Freq); // Turn the Sidetone on
// digitalWrite(cw_pin_, HIGH); // Key the CW line
key_down = true;
ktimer += millis(); // set ktimer to interval end time
keyerControl &= ~(DIT_L + DAH_L); // clear both paddle latch bits
keyerState = KEYED; // next state
// letting this fall through // return true;
// break;
case KEYED: // Wait for timer to expire
if (millis() > ktimer) { // are we at end of key down ?
// digitalWrite(ptt_pin_, LOW); // Disable PTT
// noTone(ST_Pin); // Turn the Sidetone off
// digitalWrite(cw_pin_, LOW); // Unkey the CW line
key_down = false;
ktimer = millis() + _space_len; // inter-element time
keyerState = INTER_ELEMENT; // next state
// letting this fall through // return true;
} else if (key_mode == IAMBICB) { // Iambic B Mode ?
update_PaddleLatch(); // yes, early paddle latch in Iambic B mode
} else {
return true;
}
// break;
case INTER_ELEMENT: // Insert time between dits/dahs
update_PaddleLatch(); // latch paddle state
if (millis() > ktimer) { // are we at end of inter-space ?
if (keyerControl & DIT_PROC) { // was it a dit or dah ?
keyerControl &= ~(DIT_L + DIT_PROC); // clear two bits
keyerState = CHK_DAH; // dit done, check for dah
return true;
} else {
keyerControl &= ~(DAH_L); // clear dah latch
keyerState = IDLE; // go idle
return false;
}
} else {
return true;
}
// break;
}
return false; // resolve compiler warning; do we ever get here?
}
//======================================================================
// EOF
//======================================================================

299
ubitx_iop/menu.h Normal file
View File

@ -0,0 +1,299 @@
//======================================================================
// menu.h
//======================================================================
#ifndef __menu_h__
#define __menu_h__
//#include "etl_profile.h"
//#include <ArduinoSTL.h>
#include "etl/array.h"
#include "etl/cstring.h"
#include "etl/delegate.h"
//#include <vector>
#include <initializer_list>
#include "rig.h"
// 16 characters on display
const int MAX_TEXT_LEN = 16;
const int max_text_len = 16;
const char MENU_SELECTED_CHAR = '>';
const char menu_selection_char = '>';
const char blankLine[17] = " ";
typedef etl::string<max_text_len> Menu_string;
//======================================================================
// Menu_item
//======================================================================
class Menu_item {
public:
Menu_item(): parent_link(nullptr) {}
virtual ~Menu_item() {}
virtual const Menu_string& title() const = 0;
virtual void get_title(Menu_string& outstr) const = 0;
virtual void get_text(Menu_string& outstr) const = 0;
virtual Menu_item* select() = 0;
virtual Menu_item* altSelect() = 0;
virtual Menu_item* exit() = 0;
virtual Menu_item* prev() = 0;
virtual Menu_item* next() = 0;
void set_parent(Menu_item* item) {
parent_link = item;
}
const Menu_item* parent() const {
return parent_link;
}
private:
Menu_item* parent_link;
};
//======================================================================
// List_menu
//======================================================================
const int MAX_LISTMENU_TITLE_LEN = 15;
//const int max_size_of_list_menu = 20; // arbitrary...
template <const size_t SIZE>
class List_menu : public Menu_item {
public:
List_menu(const char* title, etl::array<const Menu_item*, SIZE> items):
Menu_item(), list_title(title), list_items(items), index(0) {
for (auto element : list_items) {
element->set_parent(this);
}
}
virtual const Menu_string& title() const {
return list_title;
}
virtual void get_title(Menu_string& outstr) const {
outstr.assign(list_title);
}
virtual void get_text(Menu_string& outstr) const {
list_items[index]->get_text(outstr);
}
virtual Menu_item* select()
{
return list_items[index];
}
virtual Menu_item* altSelect()
{
return this;
}
virtual Menu_item* exit()
{
return parent();
}
virtual Menu_item* prev()
{
if (--index < 0) {
index += list_items.size();
}
return this;
}
virtual Menu_item* next()
{
index = ++index % list_items.size();
return this;
}
private:
Menu_string list_title;
etl::array<const Menu_item*, SIZE> list_items;
int index;
};
//======================================================================
// Config_parm
//======================================================================
typedef etl::delegate<void(void)> Update_func;
template<typename T>
class Config_parm : public Menu_item {
public:
Config_parm(const char* title, T& parm, T min, T max, T step, Update_func f):
Menu_item(), parm_title(title), parameter(parm), min_val(min), max_val(max), step_val(step), on_update(f) {}
virtual const Menu_string& title() const {
return parm_title;
}
virtual void get_title(Menu_string& outstr) const {
outstr.assign(parm_title);
}
virtual void get_text(Menu_string& outstr) const = 0;
virtual Menu_item* select() {
on_update();
return parent();
}
virtual Menu_item* altSelect() {
on_update();
return this;
}
virtual Menu_item* exit() {
return parent();
}
virtual Menu_item* prev() {
parameter -= step_val;
if (parameter < min_val) {
parameter = min_val;
}
return this;
}
virtual Menu_item* next() {
parameter += step_val;
if (parameter > max_val) {
parameter = max_val;
}
return this;
}
const T& value() const {
return parameter;
}
private:
Menu_string parm_title;
T& parameter;
T min_val;
T max_val;
T step_val;
Update_func on_update;
};
class Parm_uint8 : public Config_parm<uint8_t> {
public:
Parm_uint8(const char* title, uint8_t& parm, uint8_t min, uint8_t max, uint8_t step, Update_func f):
Config_parm<uint8_t>(title, parm, min, max, step, f) {}
virtual void get_text(Menu_string& outstr) const {
snprintf(outstr.data(), max_text_len+1, "%-*.*s %3d", max_text_len-4, max_text_len-4, title().data(), value());
}
};
class Parm_float : public Config_parm<float> {
public:
Parm_float(const char* title, float& parm, float min, float max, float step, Update_func f):
Config_parm<float>(title, parm, min, max, step, f) {}
virtual void get_text(Menu_string& outstr) const {
snprintf(outstr.data(), max_text_len+1, "%-*.*s %5.2f", max_text_len-6, max_text_len-6, title().data(), value());
}
};
//======================================================================
const char modeID[] = {'s', 'S', 'c', 'C', 'd', 'D', 't', 'T'};
const char* const filterID[static_cast<int>(rig_mode::count)][static_cast<int>(rx_filter::count)] = {
{"2.8", "2.4", "1.8"}, // SSB
{"1.0", "500", "250"}, // CW
{"2.8", "2.4", "500"}, // DIG
};
//======================================================================
// Top_menu
//======================================================================
class Main_menu : public Menu_item {
public:
Main_menu(basic_rig& rig): menu_title("Main Menu"), rig_(rig), adjust_tx(false) {}
virtual const Menu_string& title() const {
return menu_title;
}
virtual void get_title(Menu_string& outstr) const {
outstr.assign(menu_title);
}
virtual void get_text(Menu_string& outstr) const {
char text[max_text_len+1];
sprintf(text, "%1cR:%3s %1cT:%3s ",
adjust_tx ? ' ' : menu_selection_char,
filterID[static_cast<size_t>(rig_.get_rig_mode())][static_cast<size_t>(rig_.get_rx_filter())],
adjust_tx ? menu_selection_char : ' ',
rig_.is_ssb_mode() && rig_.config().ssb.comp.enabled ? "CMP" : " ");
outstr.assign(text);
}
virtual Menu_item* select() {
adjust_tx = !adjust_tx;
return this;
}
virtual Menu_item* altSelect() {
return this;
}
virtual Menu_item* exit() {
return nullptr;
}
virtual Menu_item* prev() {
if (adjust_tx) {
if (rig_.is_ssb_mode()) {
rig_.config().ssb.comp.enabled = !rig_.config().ssb.comp.enabled;
if (rig_.config().ssb.comp.enabled)
rig_.enable_comp();
else
rig_.disable_comp();
}
} else {
rig_.prev_rx_filter();
}
return this;
}
virtual Menu_item* next() {
if (adjust_tx) {
if (rig_.is_ssb_mode()) {
rig_.config().ssb.comp.enabled = !rig_.config().ssb.comp.enabled;
if (rig_.config().ssb.comp.enabled)
rig_.enable_comp();
else
rig_.disable_comp();
}
} else {
rig_.next_rx_filter();
}
return this;
}
private:
Menu_string menu_title;
basic_rig& rig_;
bool adjust_tx;
};
extern const Menu_item* audio_config_menu;
#endif
//======================================================================
// EOF
//======================================================================

95
ubitx_iop/menu.ino Normal file
View File

@ -0,0 +1,95 @@
//======================================================================
// menu.ino
//======================================================================
#include <iopcomm.h>
#include "config.h"
#include "menu.h"
void do_nothing_func() {}
Update_func do_nothing = Update_func::create<do_nothing_func>();
List_menu<7> audio_config_menu_("Audio Config", {
new Parm_uint8 ("RX ADC Lvl", rigConfig.audio.rxRigInLevel, 0, 15, 1, do_nothing),
new Parm_uint8 ("RX DAC Lvl", rigConfig.audio.rxLineOutLevel, 13, 31, 1, do_nothing),
new Parm_float ("RX Inp Lvl", rigConfig.audio.rxRigInVol, 0.0, 1.0, 0.1, do_nothing),
new Parm_float ("RX Inp Cal", rigConfig.audio.rxRigInCal, 0.0, 99.9, 0.1, do_nothing),
new Parm_float ("RX Spkr Cal", rigConfig.audio.rxSpkrOutCal, 0.0, 99.9, 0.1, do_nothing),
new Parm_float ("RX Line Cal", rigConfig.audio.rxLineOutCal, 0.0, 99.9, 0.1, do_nothing),
new Parm_float ("RX USB Cal", rigConfig.audio.rxUSBOutCal, 0.0, 99.9, 0.1, do_nothing),
});
const Menu_item* audio_config_menu = &audio_config_menu_;
/* commented out til I go back and fix SSBConfig class
List_menu<3> ssb_config_menu("SSB Config", {
Parm_float("Comp Thresh", rigConfig.lsb.comp_threshold, 0.0, 1.0, 0.1, do_nothing),
Parm_float("Comp Ratio", rigConfig.lsb.comp_ratio, 0.0, 50.0, 1.0, do_nothing),
Parm_float("Comp Gain", rigConfig.lsb.comp_gain, 0.0, 99.9, 0.1, do_nothing)
});
*/
/*
ListMenu("Configuration",
ListMenu("LSB Mode",
ParameterFilter("Filter", c.lsb.filter),
ParameterOnOff ("Compressor", c.lsb.comp_enable),
ParameterFloat ("Comp Thresh", c.lsb.comp_threshold, 0.0, 1.0, 0.1),
ParameterFloat ("Comp Ratio", c.lsb.comp_ratio, 0.0, 50.0, 1.0),
ParameterFloat ("Comp Gain", c.lsb.comp_gain, 0.0, 99.9, 0.1)),
ListMenu("USB Mode",
ParameterFilter("Filter", c.usb.filter),
ParameterOnOff ("Compressor", c.usb.comp_enable),
ParameterFloat ("Comp Thresh", c.usb.comp_threshold, 0.0, 1.0, 0.1),
ParameterFloat ("Comp Ratio", c.usb.comp_ratio, 0.0, 50.0, 1.0),
ParameterFloat ("Comp Gain", c.usb.comp_gain, 0.0, 99.9, 0.1)),
ListMenu("CWL Mode",
ParameterFilter("Filter", c.cwl.filter),
ParameterKeyer ("Keyer Mode", c.cwl.mode),
ParameterOnOff ("Keyer Rev?", c.cwl.reversed),
ParameterInt ("Keyer WPM", c.cwl.wpm, 0, 60, 1),
ParameterFloat ("Keyer Weight", c.cwl.weight, 0.0, 9.9, 0.1),
ParameterInt ("ST Freq", c.sidetone, 300, 3000, 10)),
ListMenu("CWU Mode",
ParameterFilter("Filter", c.cwl.filter),
ParameterKeyer ("Keyer Mode", c.cwl.mode),
ParameterOnOff ("Keyer Rev?", c.cwl.reversed),
ParameterInt ("Keyer WPM", c.cwl.wpm, 0, 60, 1),
ParameterFloat ("Keyer Weight", c.cwl.weight, 0.0, 9.9, 0.1),
ParameterInt ("ST Freq", c.sidetone, 300, 3000, 10)),
ListMenu("DGL Mode",
ParameterFilter("Filter", c.dgl.filter)),
ListMenu("DGU Mode",
ParameterFilter("Filter", c.dgu.filter)),
ListMenu("TTL Mode",
ParameterFilter("Filter", c.dgl.filter)),
ListMenu("TTU Mode",
ParameterFilter("Filter", c.dgu.filter)),
ListMenu("Audio",
ParameterInt ("RX ADC Lvl", c.rxRigInLevel, 0, 15, 1),
ParameterInt ("RX DAC Lvl", c.rxLineOutLevel, 13, 31, 1),
ParameterFloat ("RX Inp Lvl", c.rxRigInVol, 0.0, 1.0, 0.1),
ParameterFloat ("RX Inp Cal", c.rxRigInCal, 0.0, 99.9, 0.1),
ParameterFloat ("RX Spkr Cal", c.rxSpkrOutCal, 0.0, 99.9, 0.1),
ParameterFloat ("RX Line Cal", c.rxLineOutCal, 0.0, 99.9, 0.1),
ParameterFloat ("RX USB Cal", c.rxUSBOutCal, 0.0, 99.9, 0.1),
ParameterOnOff ("RX USB Dbg", c.rxUSBInEnable),
ParameterInt ("TX Mic Gain", c.txMicInGain, 0, 63, 1),
ParameterInt ("TX ADC Lvl", c.txLineInLevel, 0, 15, 1),
ParameterInt ("TX DAC Lvl", c.txRigOutLevel, 13, 31, 1),
ParameterFloat ("TX Out Cal", c.txRigOutCal, 0.0, 99.9, 0.1),
ParameterFloat ("TX Mic Lvl", c.txMicInVol, 0.0, 1.0, 0.1),
ParameterFloat ("TX Mic Cal", c.txMicInCal, 0.0, 99.9, 0.1),
ParameterFloat ("TX Line Lvl", c.txLineInVol, 0.0, 1.0, 0.1),
ParameterFloat ("TX Line Cal", c.txLineInCal, 0.0, 99.9, 0.1),
ParameterFloat ("TX USB Lvl", c.txUSBInVol, 0.0, 1.0, 0.1),
ParameterFloat ("TX USB Cal", c.txUSBInCal, 0.0, 99.9, 0.1),
ParameterOnOff ("TX USB Dbg", c.txUSBOutEnable),
ParameterFloat ("Tone 1 Lvl", c.txSine1Vol, 0.0, 1.0, 0.1),
ParameterFloat ("Tone 2 Lvl", c.txSine2Vol, 0.0, 1.0, 0.1),
ParameterFloat ("ST Lvl", c.sideToneVol, 0.0, 1.0, 0.1)));
*/
//======================================================================
// EOF
//======================================================================

240
ubitx_iop/rig.h Normal file
View File

@ -0,0 +1,240 @@
//======================================================================
// rig.h
//======================================================================
#ifndef __rig_h__
#define __rig_h__
#include <iopcomm.h>
#include "audio.h"
#include "RigMode.h"
//======================================================================
// basic_rig
//======================================================================
class basic_rig {
public:
//--------------------------------------------------------------------
// Constructor/destructor.
//--------------------------------------------------------------------
basic_rig(RigConfig& c, RigAudio& a) :
config_(c), audio_(a),
bpf_(),
comp_(&config_.ssb.comp),
ssb_ (config_.ssb, audio_, bpf_, comp_),
cw_ (config_.cw, audio_, bpf_),
digi_(config_.digi, audio_, bpf_) { set_rig_mode(config_.mode); }
void init() {}
RigConfig& config() { return config_; }
//--------------------------------------------------------------------
// Mode control.
//--------------------------------------------------------------------
inline bool is_ssb_mode() const { return (config_.mode == rig_mode::ssb ); }
inline bool is_digi_mode() const { return (config_.mode == rig_mode::digi); }
inline bool is_cw_mode() const { return (config_.mode == rig_mode::cw ); }
// Switch to the specified mode. Returns a pointer to the new mode.
basic_mode* set_rig_mode(rig_mode m) {
// exit the previous mode
current_mode_->exit(); // NOTE: This could currently occur during TX, which is NOT desirable.
// select the new mode
config_.mode = m;
switch(config_.mode) {
case rig_mode::ssb:
current_mode_ = &ssb_;
break;
case rig_mode::cw:
current_mode_ = &cw_;
break;
case rig_mode::digi:
current_mode_ = &digi_;
break;
}
//enter the new mode
current_mode_->enter();
// return a pointer to the new mode
return current_mode_;
}
// Returns the rig_mode (enum) of the current mode object.
inline rig_mode get_rig_mode() const { return config_.mode; }
// Returns a pointer to the current mode object.
inline basic_mode* mode() const { return current_mode_; }
//--------------------------------------------------------------------
// Transmit/Receive (T/R) control.
//--------------------------------------------------------------------
inline bool is_tx() const { return mode()->is_tx(); }
inline bool is_rx() const { return mode()->is_rx(); }
// Enter the transmit state. This is delegated to the mode.
inline void tx() { mode()->tx(); }
// Enter the receive state. This is delegated to the mode.
inline void rx() { mode()->rx(); }
//--------------------------------------------------------------------
// Transmit processor control.
//--------------------------------------------------------------------
inline void enable_comp() {
if (is_ssb_mode()) ((ssb_mode*)mode())->enable_comp();
}
inline void disable_comp() {
if (is_ssb_mode()) ((ssb_mode*)mode())->disable_comp();
}
//--------------------------------------------------------------------
// RX filter control.
//--------------------------------------------------------------------
// Set the RX filter. This is delegated to the mode.
inline void set_rx_filter(rx_filter f) { mode()->set_rx_filter(f); }
// Returns the rx_filter (enum) of the RX filter currently being used.
inline rx_filter get_rx_filter() const { return mode()->config().filter; }
void next_rx_filter() {
switch(mode()->config().filter) {
case rx_filter::wide:
set_rx_filter(rx_filter::medium);
break;
case rx_filter::medium:
set_rx_filter(rx_filter::narrow);
break;
case rx_filter::narrow:
set_rx_filter(rx_filter::wide);
break;
}
}
void prev_rx_filter() {
switch(mode()->config().filter) {
case rx_filter::wide:
set_rx_filter(rx_filter::narrow);
break;
case rx_filter::medium:
set_rx_filter(rx_filter::wide);
break;
case rx_filter::narrow:
set_rx_filter(rx_filter::medium);
break;
}
}
//--------------------------------------------------------------------
// Audio output control.
//--------------------------------------------------------------------
inline void muteSpkrOut() const {
audio_.muteSpkrOut();
}
inline void unmuteSpkrOut() const {
audio_.unmuteSpkrOut();
}
inline void muteLineOut() const {
audio_.muteLineOut();
}
inline void unmuteLineOut() const {
audio_.unmuteLineOut();
}
inline void muteUSBOut() const {
audio_.muteUSBOut();
}
inline void unmuteUSBOut() const {
audio_.unmuteUSBOut();
}
//--------------------------------------------------------------------
// Audio input control.
//--------------------------------------------------------------------
inline void muteAllTx() const {
audio_.muteAllTx();
}
inline void muteMicIn() const {
audio_.muteMicIn();
}
inline void unmuteMicIn() const {
audio_.unmuteMicIn();
}
inline void muteLineIn() const {
audio_.muteLineIn();
}
inline void unmuteLineIn() const {
audio_.unmuteLineIn();
}
inline void muteUSBIn() const {
audio_.muteUSBIn();
}
inline void unmuteUSBIn() const {
audio_.unmuteUSBOut();
}
inline void muteTTIn() const {
audio_.muteTTIn();
}
inline void unmuteTTIn() const {
audio_.unmuteTTIn();
}
// Update the rig state. This should be called once each time through
// the main loop.
void update()
{
comp_.update(); // It checks if it's enabled on its own.
}
private:
RigConfig& config_;
RigAudio& audio_;
bp_filter bpf_;
speech_comp comp_;
ssb_mode ssb_;
cw_mode cw_;
digi_mode digi_;
basic_mode* current_mode_;
};
#endif
//======================================================================
// EOF
//======================================================================

75
ubitx_iop/ubitx_iop.h Normal file
View File

@ -0,0 +1,75 @@
//======================================================================
// ubitx_iop.h
//======================================================================
#ifndef __ubitx_iop_h__
#define __ubitx_iop_h__
#include "config.h"
#include "audio.h"
#include "cat.h"
#include "eeprom.h"
#include "keyer.h"
#define PTT_KEY_OUT_PIN 2
#define ENCODER_A_PIN 5
#define ENCODER_B_PIN 4
#define ENCODER_SW_PIN 3
// comment this out to disable debugging code
#define DEBUG
#if defined(DEBUG)
#define USBDEBUG(x) USBSERIAL.print("IOP: "); USBSERIAL.println(x);
#else
#define USBDEBUG(x)
#endif
//enum RigMode {
// MODE_SSB = 0,
// MODE_DIGI = 1,
// MODE_CW = 2,
//};
enum TxState {
TX_OFF = 0,
TX_MIC,
TX_LINE,
TX_CAT,
TX_KEYER,
};
//extern RigMode rigMode;
extern bool keyerKeyDown;
//======================================================================
// Keying functions.
//
// These are simple functions to assert the key line from the IOP to the
// Raduino.
//======================================================================
inline void initKeyLine()
{
pinMode(PTT_KEY_OUT_PIN, OUTPUT);
digitalWrite(PTT_KEY_OUT_PIN, HIGH);
}
inline void setKeyDown()
{
digitalWrite(PTT_KEY_OUT_PIN, LOW);
}
inline void setKeyUp()
{
digitalWrite(PTT_KEY_OUT_PIN, HIGH);
}
//======================================================================
#endif
//======================================================================
// EOF
//======================================================================

235
ubitx_iop/ubitx_iop.ino Normal file
View File

@ -0,0 +1,235 @@
//======================================================================
// ubitx_iop.ino
//======================================================================
#include <Encoder.h>
#include <iopcomm.h>
#include "audio.h"
#include "config.h"
#include "ubitx_iop.h"
#include "keyer.h"
#include "menu.h"
#include "rig.h"
#include "TxSwitch.h"
Keyer keyer{15, 3.0}; // NOTE: make configurable
RigConfig rigConfig;
RigAudio rigAudio{rigConfig.audio};
basic_rig rig{rigConfig, rigAudio};
CATSwitch catPTT;
//MicSwitch micPTTHelper;
GPIOSwitch micPTT(true, MIC_PTT_PIN);
//LineSwitch linePTTHelper;
GPIOSwitch linePTT(false, LINE_PTT_PIN);
Encoder knob(ENCODER_A_PIN, ENCODER_B_PIN);
long knobPos = 0;
Bounce btn;
elapsedMillis btnMillis;
Main_menu main_menu(rig);
Menu_item* menu_item = &main_menu;
elapsedMillis frameMillis;
unsigned frameTime;
unsigned frameCounter;
unsigned audioProcUsage;
unsigned audioMemUsage;
//======================================================================
void setup() {
// put your setup code here, to run once:
initCAT(38400, SERIAL_8N1);
USBDEBUG("setup started");
AudioMemory(16); // NOTE: Need to fine tune this. Have had errors due to this being too low.
initKeyLine();
rigAudio.init();
frameCounter = 0;
frameMillis = 0;
knob.write(0);
btn.attach(ENCODER_SW_PIN, INPUT_PULLUP);
btn.interval(25);
rig.init();
USBDEBUG("setup completed");
}
//======================================================================
void update_io_menu(Menu_item* item, bool is_active)
{
Menu_string text;
if (!is_active || (is_active && item == nullptr)) {
sendIOPMenuInactive();
} else {
item->get_text(text);
sendIOPMenuDisplay(text.data());
}
}
//======================================================================
void loop()
{
static bool menu_is_active = false;
static bool menu_updated = false;
static char frame_status[100];
static char old_mode_text[17] = " ";
static bool paddle_loop = false;
// long oldPos = knobPos;
rig_mode oldRigMode;
frameCounter++;
if (rig.is_cw_mode()) {
if (keyer.do_paddles()) {
// Checking for T/R separately from the paddle loop, because it's
// possible we're already transmitting (PTT/Key being held), and
// we don't want to run the tx() actions if we're already in TX.
if (rig.is_rx()) {
USBDEBUG("entered TX via paddles");
rig.tx();
}
paddle_loop = true;
if (keyer.is_down()) {
setKeyDown();
} else {
setKeyUp();
}
return; // return early for paddle responsiveness
} else {
if (paddle_loop) {
// If we exit the paddle loop (i.e. keyer completes its keying
// sequence), then we'll go back to receive, even if one of the
// PTT/Key lines is still held separately. General principle is
// that if "something" stops transmitting, then the rig will
// stop transmitting.
paddle_loop = false;
rig.rx();
USBDEBUG("exited TX from paddles");
}
}
}
rig.update();
oldRigMode = rig.get_rig_mode();
// Update the mic PTT. We need to tell it if we're in SSB mode, so that
// it knows if it should switch to the mic input if pressed. We also
// need to make it inactive if we're in DGT mode, since only CAT will be
// used to start transmitting in that case.
micPTT.setSSBMode(rig.is_ssb_mode());
micPTT.update(rig.mode(), !rig.is_digi_mode());
// Update the line PTT. We need to tell it if we're in SSB mode, so that
// it knows if it should switch to the line input if pressed. We also
// need to make it inactive if we're in DGT mode, since only CAT will be
// used to start transmitting in that case.
linePTT.setSSBMode(rig.is_ssb_mode());
linePTT.update(rig.mode(), !rig.is_digi_mode());
serviceCAT();
menu_updated = false;
btn.update();
knobPos = knob.read();
if (btn.fell()) {
btnMillis = 0;
USBDEBUG("button pressed");
// cancel out any knob rotation that occurred during in conjunction with press/release
knob.write(0);
} else if (btn.rose()) {
menu_updated = true;
if (btnMillis > 1000) {
// long press - exit
menu_item = menu_item->exit();
USBDEBUG("button released - long (exit)");
} else if (btnMillis > 500) {
// medium press - altSelect
if (menu_is_active) {
menu_item = menu_item->altSelect();
} else {
menu_item = audio_config_menu;
menu_is_active = true;
}
USBDEBUG("button released - medium (altSelect)");
} else {
// short press - select
if (menu_is_active) {
menu_item = menu_item->select();
} else {
menu_item = &main_menu;
menu_is_active = true;
}
USBDEBUG("button released - short (select)");
}
// cancel out any knob rotation that occurred during in conjunction with press/release
knob.write(0);
} else if (knobPos > 3 && menu_is_active) {
// left
menu_item = menu_item->prev();
knob.write(0);
menu_updated = true;
USBDEBUG("knob turned left");
} else if (knobPos < -3 && menu_is_active) {
// right
menu_item = menu_item->next();
knob.write(0);
menu_updated = true;
USBDEBUG("knob turned right");
}
if (menu_item == nullptr) {
menu_item = &main_menu;
menu_is_active = false;
menu_updated = true;
USBDEBUG("null menu - reset to main menu");
}
if (menu_updated) {
update_io_menu(menu_item, menu_is_active);
USBDEBUG("updated main menu");
}
rig.update();
if (frameMillis > 5000) {
#if defined(DEBUG)
frameTime = frameMillis;
audioProcUsage = AudioProcessorUsageMax();
audioMemUsage = AudioMemoryUsageMax();
sprintf(frame_status, "update: %u ms, %u frames, %d %% CPU max, %d %% mem max\n", frameTime, frameCounter, audioProcUsage, audioMemUsage);
USBDEBUG(frame_status);
#endif
frameMillis = 0;
frameCounter = 0;
}
//audioUpdate(); // was used to update the speech compressor
}
//======================================================================
// EOF
//======================================================================