Compare commits

22 Commits

Author SHA1 Message Date
fea6b7d435 just cleaning up changes 2025-02-15 11:56:33 -06:00
Rob French
bd97aa0545 Added README.md 2020-10-27 14:25:13 -05:00
Rob French
110918079b Late add. Note that I have not used the uBITX IOP in a while due to a hardware malfunction. 2020-10-27 14:03:36 -05:00
Rob French
926b8d3600 It was compiled, and seemed like it might work. But then I managed to
kill my Teensy 3.2.  I unplugged the Teensy-to-Raduino comm lines in
order to program the Raduino.  When I plugged them back in, I didn't
realize that I was off by one pin.  So I believe something that
should've only gotten 3.3V, got more.  No light on the Teensy.

This development effort, while it has been fruitful, is definitely
complicated.  I don't think I have the bandwidth to continue it.  I plan
on merging all the branches back to master, collecting the pieces-parts
of uBITX V5D, and calling it "done".  I will rebuild my uBITX V5 back
into a relatively stock configuration, and use an external digital
interface (see KK5JY).
2020-06-14 22:51:59 -05:00
Rob French
6581a5f2a4 Insanely, this also compiles.
Numerous changes updating modes, rig, config, everything... don't think
this ones gonna work first time through...
2020-06-14 00:11:18 -05:00
Rob French
2da162f7c2 Somehow this compiled... 2020-06-13 09:59:11 -05:00
Rob French
28cbb0363f Semi-functional audio config menu. Correctly links to the audio config
variables.  No update functions currently.  No indication that you're in
the "edit" mode for a given variable.
2020-06-12 14:51:48 -05:00
Rob French
0a8e91c4a7 Revamped the fledgling menu system using the Embedded Template Library.
It works!

Started building up configuration menus.  Compiles, haven't tested yet.
Still learning about how to use the ETL/STL.
2020-06-11 23:50:50 -05:00
Rob French
2ea9c96e47 Created ListMenu class, which uses std::initializer_list to (hopefully)
facilitate creating a large menu.  Compiles, but hasn't been tested.
2020-06-10 23:58:15 -05:00
Rob French
66d1924685 Added speech compressor. Works.
Added some menu code.  But this needs to be redesigned.  Commented out.
Work this on a branch.
2020-06-10 23:23:15 -05:00
Rob French
2e115e363b Fixes associated with the Raduino display issues. 2020-06-07 15:27:27 -05:00
Rob French
bebd5ad78e Fixed the TopMenu class to only allow sending the next/prev presses if
the menu is visible.
2020-06-07 08:55:21 -05:00
Rob French
c27e2eddd6 More robust menu system, slightly. But still doesn't get correctly
displayed on the Raduino.  See most recently Raduino (ubitx-v5d) commit.
2020-06-06 22:28:11 -05:00
Rob French
e27c15d56a Multiple updates.
Support for full 16-char status line to be sent to the Raduino.

Added rotary encoder support.

Everything above has been tested and works.

Added rudimentary top-level menu.  Compiles, but not tested yet.
2020-06-06 00:06:45 -05:00
Rob French
c5a7592346 Added equalizer to smooth out RX audio passband. 2020-06-05 12:11:38 -05:00
Rob French
519a208508 Fixes to get it running, working for the most part (currently using DGU
mode on 40 meter FT8, so it can't be all bad).  Filters added, although
not currently selectable (til I get the rotary encoder added).
2020-05-27 22:45:37 -05:00
Rob French
5e16a49859 Some DEBUG updates. Compiles, runs; not comprehensively tested. 2020-05-26 10:49:15 -05:00
Rob French
322909c6f2 Added missing files to commit. 2020-05-25 23:11:36 -05:00
Rob French
564c35f397 Modifications, mainly based on a code walkthrough as well as adding some
debugging output.  Compiles, but untested.
2020-05-25 23:08:55 -05:00
Rob French
9b7095a6e3 Added some filters and speech compressor. Compiles, but not tested.
About to try some extreme re-architecting, so...
2020-05-21 08:16:35 -05:00
Rob French
7aa2cbc1c4 Added some basic FIR filter code (2.8 khz SSB filter).
Biggest thing, discovered that I needed to increase AudioMemory to 16 to
fix some "motorboating" that seemed to startup anytime I transmitted
through USB.
2020-05-19 21:39:35 -05:00
Rob French
9c1a490963 Fixed comms between the Raduino and the IOP. Implemented the wrapper
that allows messages to go back from the IOP to Raduino.  I don't know
if actual CAT from the PC works currently, however.
2020-05-18 08:16:54 -05:00
17 changed files with 2061 additions and 1179 deletions

11
README.md Normal file
View File

@@ -0,0 +1,11 @@
NOTE: This project is no longer maintained. I do not have a uBITX configured to use this code, and so am unable to provide any assistance regarding use of the code. I will note that this code is intended for use with a Teensy 3.2 microcontroller, although there are probably good things to be had for other microcontrollers as well. Also, at the point where this project left off, it might or might not have been functional... a hardware failure precluded further testing.
This project implements an additional processor in the uBITX V5 transceiver, which was used to implement the following functions:
- Wide, medium, and narrow audio filters for each mode (SSB, CW, digital)
- A new CW keyer based on the NanoIO codebase (I think... would have to double check the source markings)
- A single USB interface providing both serial CAT control as well as digital audio I/O
Communication between the Teensy and the Raduino occurs over serial (UART) communication.
This Teensy code depends on a modified version of the KD8CEC firmware for the Raduino; reference the ubitx-v5d repository.

View File

@@ -16,6 +16,16 @@
//======================================================================
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));
@@ -38,12 +48,12 @@ void recvIOPMessage(IOPMessage& msg, const uint8_t* buf, int len)
//======================================================================
void sendIOPModeCommand(RigMode mode)
void sendIOPModeCommand(rig_mode mode)
{
IOPMessage m;
m.id = IOP_MODE_COMMAND;
m.len = 1;
m.data[0] = uint8_t(mode);
m.data[0] = static_cast<uint8_t>(mode);
sendIOPMessage(m);
}
@@ -73,7 +83,118 @@ void sendIOPModeRequest()
{
IOPMessage m;
m.id = IOP_MODE_REQUEST;
m.len = 0;
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);
}

View File

@@ -67,10 +67,16 @@ enum MessageID {
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
@@ -91,118 +97,164 @@ enum MessageID {
* (e.g. USB, LSB, etc.)
*/
enum RigMode {
MODE_SSB = 0,
MODE_DIGI,
MODE_CW,
MODE_TEST,
// add any new elements here
NUM_RIG_MODES
enum struct rig_mode {
ssb = 0,
cw,
digi,
// add new items here
count
};
/* Keyer modes.
*/
enum KeyMode {
STRAIGHT = 0,
IAMBIC_A,
IAMBIC_B,
//ULTIMATIC,
//BUG,
// add any new elements here
NUM_KEY_MODES
enum struct keyer_mode {
straight = 0,
iambic_a,
iambic_b,
//ultimatic,
//bug,
// add any new items here
count
};
const unsigned char MODE_LETTER[3] = {'S', 'A', 'B'};
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 {
uint8_t id;
uint8_t len;
uint8_t data[IOP_MESSAGE_MAX_LEN];
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.
//======================================================================
void sendIOPMessage(IOPMessage const&);
void recvIOPMessage(IOPMessage&, const uint8_t*, int);
struct bpf_config {
float lo_freq;
float hi_freq;
float gain;
};
void sendIOPModeCommand(RigMode);
void sendIOPStartTxCommand();
void sendIOPStopTxCommand();
void sendIOPModeRequest();
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
//======================================================================
struct CWConfig {
// mode
KeyMode mode = IAMBIC_A;
// flags
bool reversed = false;
// parameters
uint8_t wpm = 15;
float weight = 3.0;
uint16_t sidetone = 700;
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;
};
//======================================================================
// TRANSLATOR
// TT CONFIGURATION
//======================================================================
/*
class Translator
{
struct TTConfig : public IConfig {
public:
Translator(RigMode&, CWConfig&, Stream&);
void registerPrefixCb(PrefixID id, void (*func)(void*));
void registerMessageCb(MessageID id, void (*func)(void*));
void sendACK();
void sendIOPMessage(IOPMessage const&);
void receive();
void pack_ModeCommand(IOPMessage&, RigMode);
void pack_CWConfig(IOPMessage&, CWConfig const&);
void pack_ModeRequest(IOPMessage&);
void unpack(IOPMessage const&);
protected:
void unpack_ModeCommand(IOPMessage const&);
void unpack_CWConfig(IOPMessage const&);
void unpack_ModeRequest(IOPMessage const&);
private:
RigMode& mode;
CWConfig& cwConfig;
Stream& serial;
void (*prefixCb[NUM_PREFIX_IDS])(void*);
void (*messageCb[NUM_MESSAGE_IDS])(void*);
TTConfig(RxFilter f): filter(f) {}
// parameters
RxFilter filter = RX_FILTER_MEDIUM;
};
*/
//======================================================================
// CW STATUS MESSAGE
// FUNCTION PROTOTYPES
//======================================================================
//void packT_DisplayText(TMessage &m, CWConfig const &c)
//{
// m.id = IOP_CW_STATUS_MSG;
// m.len = 3;
// m.data[0] = ' '; // growth
// m.data[1] = MODE_LETTER[c.mode];
// m.data[2] = ' '; // TODO: RX filter width
//}
void sendCATMessage(const uint8_t*);
void sendIOPMessage(IOPMessage const&);
void recvIOPMessage(IOPMessage&, const uint8_t*, int);
// No unpack required: this is a string to put on the display.
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

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
//======================================================================

View File

@@ -1,40 +1,118 @@
//======================================================================
// 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"
enum RxInput {
RX_RIG_IN = 0,
RX_USB_IN = 1,
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;
};
enum RxOutput {
RX_SPEAKER_OUT = 0,
RX_LINE_OUT = 1,
RX_USB_OUT = 2,
//======================================================================
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;
};
enum TxInput {
TX_MIC_IN = -1,
TX_LINE_IN = 0,
TX_USB_IN = 1,
TX_TEST_IN = 2,
//======================================================================
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;
};
enum TxOutput {
TX_RIG_OUT = 0,
TX_USB_OUT = 1,
};
//======================================================================
/*
void audioInit();
void audioSelectTxInput(TxInput);
void audioTransmit();
void audioReceive();
void audioCalibrate(IOPConfig *, char, char, char, float, bool);
void audioCalibrate(AudioConfig *, char, char, char, float, bool);
*/
#endif

View File

@@ -1,15 +1,34 @@
//======================================================================
// 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"
extern IOPConfig iopConfig;
//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>
@@ -18,674 +37,332 @@ extern IOPConfig iopConfig;
#include <SerialFlash.h>
// GUItool: begin automatically generated code
AudioSynthWaveformSine sideTone; //xy=204,221
AudioInputI2S inLine; //xy=208,170
AudioSynthWaveformSine sine2; //xy=207,423
AudioInputUSB inUSB; //xy=208,279
AudioSynthWaveformSine sine1; //xy=208,382
AudioAnalyzeRMS rmsRX; //xy=383,47
AudioAnalyzePeak peakRX; //xy=387,98
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
AudioMixer4 mixTX; //xy=444,279
AudioAmplifier calRxUSB; //xy=634,165
AudioAmplifier calRxSpkr; //xy=638,55
AudioAmplifier calRxLine; //xy=641,112
AudioAmplifier calTxLine; //xy=653,248
AudioAmplifier calTxUSB; //xy=654,305
AudioAnalyzePeak peakTX; //xy=669,378
AudioAnalyzeRMS rmsTX; //xy=683,433
AudioOutputAnalog outSpkr; //xy=814,55
AudioOutputUSB outUSB; //xy=823,255
AudioOutputI2S outLine; //xy=826,204
AudioConnection patchCord1(sideTone, 0, mixRX, 2);
AudioConnection patchCord2(inLine, 0, rmsRX, 0);
AudioConnection patchCord3(inLine, 0, peakRX, 0);
AudioConnection patchCord4(inLine, 0, mixRX, 0);
AudioConnection patchCord5(inLine, 1, mixTX, 0);
AudioConnection patchCord6(sine2, 0, mixTX, 3);
AudioConnection patchCord7(inUSB, 0, mixRX, 1);
AudioConnection patchCord8(inUSB, 1, mixTX, 1);
AudioConnection patchCord9(sine1, 0, mixTX, 2);
AudioConnection patchCord10(mixRX, calRxSpkr);
AudioConnection patchCord11(mixRX, calRxLine);
AudioConnection patchCord12(mixRX, calRxUSB);
AudioConnection patchCord13(mixTX, calTxLine);
AudioConnection patchCord14(mixTX, calTxUSB);
AudioConnection patchCord15(mixTX, peakTX);
AudioConnection patchCord16(mixTX, rmsTX);
AudioConnection patchCord17(calRxUSB, 0, outUSB, 0);
AudioConnection patchCord18(calRxSpkr, outSpkr);
AudioConnection patchCord19(calRxLine, 0, outLine, 0);
AudioConnection patchCord20(calTxLine, 0, outLine, 1);
AudioConnection patchCord21(calTxUSB, 0, outUSB, 1);
AudioControlSGTL5000 audioCtrl; //xy=391,443
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
RxInput audioRxInput;
RxOutput audioRxOutput;
TxInput audioTxInput;
TxOutput audioTxOutput;
// audioInit()
// Setup the audio subsystem.
void audioInit()
{
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.inputSelect(AUDIO_INPUT_LINEIN); // required for RX audio
audioCtrl.unmuteLineout(); // required for RX audio
audioCtrl.lineInLevel(iopConfig.rxRigInLevel, iopConfig.txLineInLevel); // NOTE: need to see if this persists through input changes (see mic gain...)
audioCtrl.lineOutLevel(iopConfig.rxLineOutLevel, iopConfig.txRigOutLevel); // NOTE: need to see if this persists through input changes (see mic gain...)
audioCtrl.micGain(iopConfig.txMicInGain); // superfluous, as I have to do this anytime I switch to mic for some reason
//audioCtrl.dacVolumeRamp(); // if this seems too slow, might try dacVolumeRampLinear().
//audioCtrl.dacVolume(1.0, 0.0); // we're going to mute TX audio via the DAC unless we're transmitting
updateRxRigIn();
updateRxSpkrOut();
updateRxLineOut();
updateRxUSBOut();
// Note - these really just need to be init functions; keep things muted while setting them up.
updateTxMicIn();
updateTxLineIn();
updateTxUSBIn();
updateTxTwoToneIn();
updateTxRigOut();
audioSelectRxInput(RX_RIG_IN);
audioSelectTxInput(TX_MIC_IN); // superfluous I think
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
//audioCtrl.adcHighPassFilterDisable();
audioCtrl.audioPreProcessorEnable();
// 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);
}
inline void updateRxRigIn()
{
audioCtrl.lineInLevel(iopConfig.rxRigInLevel, iopConfig.txLineInLevel);
mixRX.gain(RX_RIG_IN, iopConfig.rxRigInVol * iopConfig.rxRigInCal);
}
//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);
inline void muteRxRigIn()
{
mixRX.gain(RX_RIG_IN, 0.0);
}
// for now, just pass through the compressor
compTX.disable();
compAmp.gain(1.0);
inline void restoreRxRigIn()
{
mixRX.gain(RX_RIG_IN, iopConfig.rxRigInVol * iopConfig.rxRigInCal);
}
inline void updateRxUSBIn()
{
if (iopConfig.rxUSBInEnable) {
mixRX.gain(RX_USB_IN, iopConfig.rxUSBInVol * iopConfig.rxUSBInCal);
} else {
mixRX.gain(RX_USB_IN, 0.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");
}
inline void muteRxUSBIn()
{
mixRX.gain(RX_USB_IN, 0.0);
void RigAudio::muteRx() const {
mixRX.gain(RX_RIG_IN, 0.0);
USBDEBUG("RX audio muted");
}
inline void restoreRxUSBIn()
{
updateRxUSBIn();
void RigAudio::unmuteRx() const {
audioCtrl.inputSelect(AUDIO_INPUT_LINEIN);
mixRX.gain(RX_RIG_IN, _config.rxRigInVol * _config.rxRigInCal);
USBDEBUG("RX audio unmuted");
}
inline void updateRxSpkrOut()
{
calRxSpkr.gain(iopConfig.rxSpkrOutCal);
void RigAudio::muteAllTx() const {
muteMicIn();
muteLineIn();
muteUSBIn();
muteTTIn();
USBDEBUG("all TX audio muted");
}
inline void updateRxLineOut()
{
calRxLine.gain(iopConfig.rxLineOutCal);
audioCtrl.lineOutLevel(iopConfig.rxLineOutLevel, iopConfig.txRigOutLevel);
}
inline void updateRxUSBOut()
{
calRxUSB.gain(iopConfig.rxUSBOutCal);
}
inline void updateTxMicIn()
{
audioCtrl.micGain(iopConfig.txMicInGain);
mixTX.gain(TX_LINE_IN, iopConfig.txMicInVol * iopConfig.txMicInCal);
}
inline void muteTxMicIn()
{
void RigAudio::muteMicIn() const {
mixTX.gain(TX_LINE_IN, 0.0);
USBDEBUG("Mic In audio muted");
}
inline void restoreTxMicIn()
{
mixTX.gain(TX_LINE_IN, iopConfig.txMicInVol * iopConfig.txMicInCal);
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");
}
inline void updateTxLineIn()
{
audioCtrl.lineInLevel(iopConfig.rxRigInLevel, iopConfig.txLineInLevel);
mixTX.gain(TX_LINE_IN, iopConfig.txLineInVol * iopConfig.txLineInCal);
}
inline void muteTxLineIn()
{
void RigAudio::muteLineIn() const {
mixTX.gain(TX_LINE_IN, 0.0);
USBDEBUG("Line In audio muted");
}
inline void restoreTxLineIn()
{
mixTX.gain(TX_LINE_IN, iopConfig.txLineInVol * iopConfig.txLineInCal);
void RigAudio::unmuteLineIn() const {
audioCtrl.inputSelect(AUDIO_INPUT_LINEIN);
mixTX.gain(TX_LINE_IN, _config.txLineInVol * _config.txLineInCal);
USBDEBUG("Line In audio unmuted");
}
inline void updateTxUSBIn()
{
mixTX.gain(TX_USB_IN, iopConfig.txUSBInVol * iopConfig.txUSBInCal);
}
inline void muteTxUSBIn()
{
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");
}
inline void restoreTxUSBIn()
{
mixTX.gain(TX_USB_IN, iopConfig.txUSBInVol * iopConfig.txUSBInCal);
}
inline void updateTxTwoToneIn()
{
sine1.amplitude(0.5);
sine2.amplitude(0.5);
mixTX.gain(TX_TEST_IN, iopConfig.txSine1Vol);
mixTX.gain(TX_TEST_IN + 1, iopConfig.txSine2Vol);
}
inline void muteTxTwoToneIn()
{
void RigAudio::muteTTIn() const {
mixTX.gain(TX_TEST_IN, 0.0);
mixTX.gain(TX_TEST_IN + 1, 0.0);
sine1.amplitude(0);
sine2.amplitude(0);
sine1.amplitude(0.0);
sine2.amplitude(0.0);
USBDEBUG("Two Tone audio muted");
}
inline void restoreTxTwoToneIn()
{
void RigAudio::unmuteTTIn() const {
sine1.amplitude(0.5);
sine2.amplitude(0.5);
mixTX.gain(TX_TEST_IN, iopConfig.txSine1Vol);
mixTX.gain(TX_TEST_IN + 1, iopConfig.txSine2Vol);
mixTX.gain(TX_TEST_IN, _config.txSine1Vol);
mixTX.gain(TX_TEST_IN + 1, _config.txSine2Vol);
USBDEBUG("Two Tone audio unmuted");
}
inline void updateTxRigOut()
{
calTxLine.gain(iopConfig.txRigOutCal);
audioCtrl.lineOutLevel(iopConfig.rxLineOutLevel, iopConfig.txRigOutLevel);
void RigAudio::muteSpkrOut() const {
calRxSpkr.gain(0.0);
USBDEBUG("Speaker Out audio muted");
}
inline void updateTxUSBOut()
{
if (iopConfig.txUSBOutEnable) {
calTxUSB.gain(iopConfig.txUSBOutCal);
} else {
calTxUSB.gain(0.0);
}
void RigAudio::unmuteSpkrOut() const {
calRxSpkr.gain(_config.rxSpkrOutCal);
USBDEBUG("Speaker Out audio unmuted");
}
void audioSelectRxInput(RxInput input)
{
if (audioRxInput != input) {
audioRxInput = input;
switch(input) {
case RX_RIG_IN:
muteRxUSBIn();
restoreRxRigIn();
mixRX.gain(2, 0);
mixRX.gain(3, 0);
break;
case RX_USB_IN:
muteRxRigIn();
restoreRxUSBIn();
mixRX.gain(2, 0);
mixRX.gain(3, 0);
break;
}
}
void RigAudio::muteLineOut() const {
calRxLine.gain(0.0);
USBDEBUG("Line Out audio muted");
}
void audioSelectTxInput(TxInput input)
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()
{
if (audioTxInput != input) {
audioTxInput = input;
//muteTxMicIn(); // redundant w/ Line-In
muteTxLineIn();
muteTxUSBIn();
muteTxTwoToneIn();
/* switch(input) {
case TX_MIC_IN:
muteTxUSBIn();
restoreTxMicIn();
break;
case TX_LINE_IN:
muteTxUSBIn();
restoreTxLineIn();
break;
case TX_USB_IN:
muteTxLineIn();
restoreTxUSBIn();
break;
}*/
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_);
}
}
//======================================================================
// audioTransmit()
// This should be called anytime transmit mode is entered. It should
// in theory be called BEFORE the actual transmit signal (key/PTT) is
// sent to the Raduino, in order to ensure that any audio source
// transitions occur before transmission begins.
void audioTransmit()
{
switch(rigMode) {
// Nothing special for CW, TX audio inputs are already muted.
//case MODE_CW:
//break;
case MODE_SSB:
// Mute the incoming RX audio. Can't think of a good reason
// to let RX audio in while we're transmitting.
muteRxRigIn();
if (audioTxInput == TX_MIC_IN) {
audioCtrl.inputSelect(AUDIO_INPUT_MIC);
updateTxMicIn();
#if defined(FACTORY_CALIBRATION)
USBSERIAL.println("==============================");
USBSERIAL.println("Transmitting with Mic input");
USBSERIAL.print("Mic gain: ");
USBSERIAL.println(iopConfig.txMicInGain);
USBSERIAL.print("Mic volume: ");
USBSERIAL.println(iopConfig.txMicInVol);
USBSERIAL.print("Mic calibration: ");
USBSERIAL.println(iopConfig.txMicInCal);
USBSERIAL.print("TX audio level: ");
USBSERIAL.println(iopConfig.txRigOutLevel);
USBSERIAL.print("TX audio calibration: ");
USBSERIAL.println(iopConfig.txRigOutCal);
USBSERIAL.println("==============================");
#endif
} else if (audioTxInput == TX_LINE_IN) {
updateTxLineIn();
}
break;
case MODE_DIGI:
// Mute the incoming RX audio. Can't think of a good reason
// to let RX audio in while we're transmitting.
muteRxRigIn();
updateTxUSBIn();
break;
case MODE_TEST:
muteRxRigIn();
updateTxTwoToneIn();
break;
}
}
//======================================================================
// audioReceive()
// This should be called anytime receive mode is entered. It should
// in theory be called AFTER the actual transmit signal (key/PTT) is
// removed from the Raduino, in order to ensure that any audio source
// transitions occur before receive begins.
void audioReceive()
{
switch(rigMode) {
//case MODE_CW:
//break;
case MODE_SSB:
if (audioTxInput == TX_MIC_IN) {
muteTxMicIn();
audioCtrl.inputSelect(AUDIO_INPUT_LINEIN);
} else if (audioTxInput == TX_LINE_IN) {
muteTxLineIn();
}
restoreRxRigIn();
break;
case MODE_DIGI:
muteTxUSBIn();
restoreRxRigIn();
break;
case MODE_TEST:
muteTxTwoToneIn();
restoreRxRigIn();
break;
}
}
//======================================================================
void audioCalibrate(IOPConfig* c, char cmd, char subcmd, char parm, float value, bool set_value=true)
{
switch(cmd) {
case 'r':
case 'R':
// RX audio parameters
switch(subcmd) {
case 'r':
case 'R':
// Rig input
switch(parm) {
case 'l':
case 'L':
// level
if (set_value) {
c->rxRigInLevel = int(value);
updateRxRigIn();
}
USBSERIAL.print("rxRigInLevel: ");
USBSERIAL.println(c->rxRigInLevel);
break;
case 'v':
case 'V':
// volume
if (set_value) {
c->rxRigInVol = value;
updateRxRigIn();
}
USBSERIAL.print("rxRigInVol: ");
USBSERIAL.println(c->rxRigInVol);
break;
case 'c':
case 'C':
// calibration
if (set_value) {
c->rxRigInCal = value;
updateRxRigIn();
}
USBSERIAL.print("rxRigInCal: ");
USBSERIAL.println(c->rxRigInCal);
break;
}
break;
case 's':
case 'S':
// Speaker output
switch(parm) {
case 'c':
case 'C':
// calibration
if (set_value) {
c->rxSpkrOutCal = value;
updateRxSpkrOut();
}
USBSERIAL.print("rxSpkrOutCal: ");
USBSERIAL.println(c->rxSpkrOutCal);
break;
}
break;
case 'l':
case 'L':
// Line output
switch(parm) {
case 'l':
case 'L':
// level
if (set_value) {
c->rxLineOutLevel = int(value);
updateRxLineOut();
}
USBSERIAL.print("rxLineOutLevel: ");
USBSERIAL.println(c->rxLineOutLevel);
break;
case 'c':
case 'C':
// calibration
if (set_value) {
c->rxLineOutCal = value;
updateRxLineOut();
}
USBSERIAL.print("rxLineOutCal: ");
USBSERIAL.println(c->rxLineOutCal);
break;
}
break;
case 'u':
case 'U':
// USB output
switch(parm) {
case 'c':
case 'C':
// calibration
if (set_value) {
c->rxUSBOutCal = value;
updateRxUSBOut();
}
USBSERIAL.print("rxUSBOutCal: ");
USBSERIAL.println(c->rxUSBOutCal);
break;
}
break;
}
break;
case 't':
case 'T':
//TX audio parameters
switch(subcmd) {
case 'm':
case 'M':
// Mic input
switch(parm) {
case 'g':
case 'G':
// mic gain
if (set_value) {
c->txMicInGain = int(value);
updateTxMicIn();
}
USBSERIAL.print("txMicInGain: ");
USBSERIAL.println(c->txMicInGain);
break;
case 'v':
case 'V':
// volume
if (set_value) {
c->txMicInVol = value;
updateTxMicIn();
}
USBSERIAL.print("txMicInVol: ");
USBSERIAL.println(c->txMicInVol);
break;
case 'c':
case 'C':
// calibration
if (set_value) {
c->txMicInCal = value;
updateTxMicIn();
}
USBSERIAL.print("txMicInCal: ");
USBSERIAL.println(c->txMicInCal);
break;
}
break;
case 'l':
case 'L':
// Line input
switch(parm) {
case 'l':
case 'L':
// level
if (set_value) {
c->txLineInLevel = int(value);
updateTxLineIn();
}
USBSERIAL.print("txLineInLevel: ");
USBSERIAL.println(c->txLineInLevel);
break;
case 'v':
case 'V':
// volume
if (set_value) {
c->txLineInVol = value;
updateTxLineIn();
}
USBSERIAL.print("txLineInVol: ");
USBSERIAL.println(c->txLineInVol);
break;
case 'c':
case 'C':
// calibration
if (set_value) {
c->txLineInCal = value;
updateTxLineIn();
}
USBSERIAL.print("txLineInCal: ");
USBSERIAL.println(c->txLineInCal);
break;
}
break;
case 'u':
case 'U':
// USB input
switch(parm) {
case 'v':
case 'V':
// volume
if (set_value) {
c->txUSBInVol = value;
updateTxUSBIn();
}
USBSERIAL.print("txUSBInVol: ");
USBSERIAL.println(c->txUSBInVol);
break;
case 'c':
case 'C':
// calibration
if (set_value) {
c->txUSBInCal = value;
updateTxUSBIn();
}
USBSERIAL.print("txUSBInCal: ");
USBSERIAL.println(c->txUSBInCal);
break;
}
break;
case 'r':
case 'R':
// Rig output
switch(parm) {
case 'l':
case 'L':
// level
if (set_value) {
c->txRigOutLevel = int(value);
updateTxRigOut();
}
USBSERIAL.print("txRigOutLevel: ");
USBSERIAL.println(c->txRigOutLevel);
break;
case 'c':
case 'C':
// calibration
if (set_value) {
c->txRigOutCal = value;
updateTxRigOut();
}
USBSERIAL.print("txRigOutCal: ");
USBSERIAL.println(c->txRigOutCal);
break;
}
break;
}
break;
}
}
int lpFilter[5];
int hpFilter[5];
void audioSSBFilter()
int eqFilter1[5];
void audioEqualizer()
{
audioCtrl.audioPreProcessorEnable();
audioCtrl.eqSelect(PARAMETRIC_EQUALIZER);
// calcBiquad(FilterType,FrequencyC,dBgain,Q,QuantizationUnit,SampleRate,int*);
calcBiquad(FILTER_LOPASS, 3300, 0, 0.707, 524288, 44100, lpFilter);
calcBiquad(FILTER_HIPASS, 100, 0, 0.707, 524288, 44100, hpFilter);
audioCtrl.eqFilter(0, lpFilter);
audioCtrl.eqFilter(1, hpFilter);
audioCtrl.eqFilter(2, lpFilter);
audioCtrl.eqFilter(3, hpFilter);
audioCtrl.eqFilter(4, lpFilter);
audioCtrl.eqFilter(5, hpFilter);
}
void audioCWFilter()
{
// calcBiquad(FilterType,FrequencyC,dBgain,Q,QuantizationUnit,SampleRate,int*);
calcBiquad(FILTER_LOPASS, 1300, 0, 0.707, 524288, 44100, lpFilter);
calcBiquad(FILTER_HIPASS, 100, 0, 0.707, 524288, 44100, hpFilter);
audioCtrl.eqFilter(0, lpFilter);
audioCtrl.eqFilter(1, hpFilter);
audioCtrl.eqFilter(2, lpFilter);
audioCtrl.eqFilter(3, hpFilter);
audioCtrl.eqFilter(4, lpFilter);
audioCtrl.eqFilter(5, hpFilter);
}
//======================================================================
// audioDigiFilter()
// Create a very wide filter for digital modes... wider than the uBITX
// needs, but just want to make sure we leave the full audio bandwidth
// open for digimodes, while ensuring we've cut down any extraneous
// noise outside the normal band.
//======================================================================
void audioDigiFilter()
{
// calcBiquad(FilterType,FrequencyC,dBgain,Q,QuantizationUnit,SampleRate,int*);
calcBiquad(FILTER_LOPASS, 5000, 0, 0.707, 524288, 44100, lpFilter);
calcBiquad(FILTER_HIPASS, 100, 0, 0.707, 524288, 44100, hpFilter);
audioCtrl.eqFilter(0, lpFilter);
audioCtrl.eqFilter(1, hpFilter);
audioCtrl.eqFilter(2, lpFilter);
audioCtrl.eqFilter(3, hpFilter);
audioCtrl.eqFilter(4, lpFilter);
audioCtrl.eqFilter(5, hpFilter);
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);
}
//======================================================================

View File

@@ -3,11 +3,17 @@
//======================================================================
#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
//
@@ -30,16 +36,12 @@ void initCAT(long baud, int portConfig)
// CAT with PC via USB
USBSERIAL.begin(baud);
USBSERIAL.flush();
#if defined(DEBUG)
USBSERIAL.println("IOP: opened USB serial port (to PC)");
#endif
USBDEBUG("opened USB serial port (to PC)");
// Comm (including CAT passthru) with Raduino via UART
HWSERIAL.begin(baud, portConfig);
HWSERIAL.flush();
#if defined(DEBUG)
USBSERIAL.println("IOP: opened H/W serial port (to Raduino)");
#endif
USBDEBUG("opened H/W serial port (to Raduino)");
//sendIOPModeRequest(); // Raduino doesn't support this yet
}
@@ -50,40 +52,47 @@ 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 {
setRigMode(m.data[0]);
rig.set_rig_mode(static_cast<rig_mode>(m.data[0]));
#if defined(DEBUG)
switch(rigMode) {
case MODE_CW:
switch(rig.get_rig_mode()) {
case rig_mode::cw:
USBDEBUG("new mode - CW");
break;
case MODE_SSB:
case rig_mode::ssb:
USBDEBUG("new mode - SSB");
break;
case MODE_DIGI:
USBDEBUG("new mode - DIGI");
break;
case MODE_TEST:
USBDEBUG("new mode - TEST");
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:
catPTTOn();
catPTT.press(rig.mode());
break;
case IOP_STOP_TX_COMMAND:
catPTTOff();
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;
}
}
@@ -107,7 +116,7 @@ void processCalCommand(const char* buf)
case 'R':
case 't':
case 'T':
audioCalibrate(&iopConfig, cmd, subcmd, parm, value, (count == 4));
//audioCalibrate(&rigConfig.audio, cmd, subcmd, parm, value, (count == 4));
break;
default:
@@ -126,8 +135,8 @@ enum SerialState {
EEPROM_WRITE,
} serialState = NORMAL;
uint8_t readPrefix = 0;
uint8_t readLength = 0;
PrefixID readPrefix;
uint8_t readLength;
int cmdLength = 0;
byte cmdBuffer[16];
@@ -139,13 +148,44 @@ 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++) {
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
@@ -176,7 +216,7 @@ void serviceCAT()
#else
// Don't pass CAT commands through if in DEBUG mode.
#if not defined(DEBUG)
HWSERIAL.write(incomingByte);
//HWSERIAL.write(incomingByte);
#endif
}

View File

@@ -1,11 +1,14 @@
//======================================================================
// 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
@@ -13,19 +16,23 @@
// 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
//#define FACTORY_CALIBRATION
// IOPConfig
// Used to store configuration parameters during runtime, as well as to
// save them to EEPROM.
struct IOPConfig {
//======================================================================
// AudioConfig
//======================================================================
class AudioConfig {
public:
//--------------------------------------------------------------------
// RECEIVE PARAMETERS
//--------------------------------------------------------------------
// rig-in parameters (RX)
uint8_t rxRigInLevel = 5;
uint8_t rxRigInLevel = 8; //5;
float rxRigInVol = 1.0; //0.7;
float rxRigInCal = 1.0;
float rxRigInCal = 8.0; //1.0;
// USB-in parameters (RX) - debug/monitor use only
bool rxUSBInEnable = false;
float rxUSBInVol = 1.0;
@@ -37,36 +44,46 @@ struct IOPConfig {
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:
//--------------------------------------------------------------------
// 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
// 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)
// 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).
// 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.
// 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;
@@ -84,16 +101,34 @@ struct IOPConfig {
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.043;
float txRigOutCal = 0.1; // 0.061;
// USB-out parameters (TX)- debug/monitor use only
bool txUSBOutEnable = true;
float txUSBOutCal = 1.0;
// CW configuration
CWConfig cw;
};
extern IOPConfig iopConfig;
//======================================================================
// 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

9
ubitx_iop/config.ino Normal file
View File

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

0
ubitx_iop/etl Executable file
View File

11
ubitx_iop/etl_profile.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef __ETL_PROFILE_H__
#define __ETL_PROFILE_H__
//#define ETL_THROW_EXCEPTIONS
//#define ETL_VERBOSE_ERRORS
#define ETL_CHECK_PUSH_POP
#define ETL_TARGET_OS_FREERTOS // Placeholder. Not currently utilised in the ETL
#define ETL_TARGET_DEVICE_ARM // Placeholder. Not currently utilised in the ETL
#endif

View File

@@ -5,46 +5,293 @@
#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
#define MAX_TEXT_LEN 16
#define MENU_SELECTED_CHAR '>'
/*
public class MenuItem {
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:
MenuItem(bool active = true, int timeout = 0): _active(active), _timeout(timeout), _elapsed(0) {}
void update() {
if ((_timeout > 0) && (_elapsed > _timeout)) {
_active = false;
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);
}
}
inline void activate() { _active = true; _elapsed = 0; }
inline void deactivate() { _active = false; }
virtual MenuItem* accept();
virtual MenuItem* reject();
virtual MenuItem* next();
virtual MenuItem* prev();
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:
bool _active;
int _timeout;
elapsedMillis _elapsed;
Menu_string list_title;
etl::array<const Menu_item*, SIZE> list_items;
int index;
};
public class SSBMenu {
//======================================================================
// 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;
};
public class DigiMenu {
class Parm_uint8 : public Config_parm<uint8_t> {
public:
private:
}
public class CWMenu {
public:
private:
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
//======================================================================

View File

@@ -2,137 +2,93 @@
// menu.ino
//======================================================================
#include <iopcomm.h>
#include "config.h"
#include "menu.h"
/*
CW mode:
WPM (although we can get this from Raduino)
WPM: # (L/R, select/abort)
FILT
WIDE NORM NARR
void do_nothing_func() {}
Update_func do_nothing = Update_func::create<do_nothing_func>();
SSB mode:
TX LVL
COMP
FILT
WIDE NORM NARR
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),
});
Digi mode:
TX LVL
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)
});
*/
class MenuItem {
public:
MenuItem() {}
virtual MenuItem* next() = 0;
virtual MenuItem* prev() = 0;
char text[MAX_TEXT_LEN + 1];
};
class TextMenu : public MenuItem {
public:
TextMenu(int num_entries, int entry_len): MenuItem(), _num_entries(num_entries), _entry_len(entry_len), _selected(0), _leftmost(0) {
text[0] = '\0';
_entries = new MenuItem*[_num_entries];
_labels = new char*[_num_entries];
for (int i = 0; i < _num_entries; i++) {
_labels[i] = NULL;
}
}
~TextMenu() {
for (int i = 0; i < _num_entries; i++) {
if (_labels[i] != NULL) {
delete _labels[i];
}
}
delete _labels;
delete _entries;
}
virtual MenuItem* next() {
bool dirty = false;
if (++_selected == _num_entries) {
_selected = 0;
}
if (_selected < _leftmost) {
_leftmost = _selected;
dirty = true;
} else {
while ((_selected - _leftmost) * _entry_len >= MAX_TEXT_LEN) {
_leftmost++;
dirty = true;
}
}
if (dirty) {
for (int i = _leftmost; i < _num_entries; i++) {
strncpy(&text[i - _leftmost], _labels[i], _entry_len);
}
}
text[_selected - _leftmost] = MENU_SELECTED_CHAR;
return this;
}
virtual MenuItem* prev() {
bool dirty = false;
if (_selected-- == 0) {
_selected += _num_entries;
}
if (_selected < _leftmost) {
_leftmost = _selected;
dirty = true;
} else {
while ((_selected - _leftmost) * _entry_len >= MAX_TEXT_LEN) {
_leftmost++;
dirty = true;
}
}
if (dirty) {
updateText();
} else {
updateCursor();
}
return this;
}
void addEntry(int i, const char* label, MenuItem* entry) {
if (i < _num_entries) {
_labels[i] = new char[strlen(label) + 1]; // I need to learn to do strings the C++ way.
strcpy(_labels[i], label); // Ditto.
_entries[i] = entry;
}
}
void updateText() {
for (int i = _leftmost; i < _num_entries; i++) {
strncpy(&text[i - _leftmost], _labels[i], _entry_len);
}
text[_selected - _leftmost] = MENU_SELECTED_CHAR;
}
void updateCursor() {
text[_selected - _leftmost] = MENU_SELECTED_CHAR;
}
/*MenuItem* operator[](int i) {
return this->entries[i];
}*/
private:
int _num_entries;
int _entry_len;
int _selected;
int _leftmost;
char** _labels;
MenuItem** _entries;
};
/*
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
//======================================================================

View File

@@ -11,8 +11,13 @@
#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
#define DEBUG
#if defined(DEBUG)
#define USBDEBUG(x) USBSERIAL.print("IOP: "); USBSERIAL.println(x);
@@ -34,10 +39,35 @@ enum TxState {
TX_KEYER,
};
extern RigMode rigMode;
//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
//======================================================================

View File

@@ -2,315 +2,232 @@
// 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"
#include <Bounce2.h>
#define BOUNCE_WITH_PROMPT_DETECTION
Keyer keyer{15, 3.0}; // NOTE: make configurable
RigMode rigMode = MODE_SSB;
IOPConfig iopConfig;
RigConfig rigConfig;
RigAudio rigAudio{rigConfig.audio};
basic_rig rig{rigConfig, rigAudio};
#define MIC_PTT_PIN 21
#define LINE_PTT_PIN 20
#define PTT_KEY_OUT_PIN 2
CATSwitch catPTT;
//MicSwitch micPTTHelper;
GPIOSwitch micPTT(true, MIC_PTT_PIN);
//LineSwitch linePTTHelper;
GPIOSwitch linePTT(false, LINE_PTT_PIN);
Bounce micPTT = Bounce();
Bounce linePTT = Bounce();
Encoder knob(ENCODER_A_PIN, ENCODER_B_PIN);
long knobPos = 0;
TxState txState = TX_OFF;
Bounce btn;
elapsedMillis btnMillis;
Keyer keyer(15, 3.0); // need to make configurable
Main_menu main_menu(rig);
Menu_item* menu_item = &main_menu;
//SSBMenu ssbMenu();
//DigiMenu digiMenu();
//CWMenu cwMenu();
//MenuItem* dspMenu = &ssbMenu;
//======================================================================
// catPTTOn()
//
// NOTE: Should probably move this to cat.ino.
//======================================================================
void catPTTOn()
{
// Check if we're already transmitting. If so, then this command
// does nothing: existing transmission, from existing source and PTT,
// will continue.
if (txState != TX_OFF) return;
switch(rigMode) {
// CAT should not start or stop TX in CW mode... we really should
// not even get this command. But maybe the rig control software is
// doing it (but then, we should inhibit this in the Raduino).
//case MODE_CW:
//break;
case MODE_SSB:
// In SSB mode, CAT-PTT will always (de-)activate the Line-In TX
// audio source.
txState = TX_CAT;
audioSelectTxInput(TX_LINE_IN); // in case Mic-In is selected
audioTransmit();
digitalWrite(PTT_KEY_OUT_PIN, LOW);
break;
case MODE_DIGI:
// In Digital (USB) mode, CAT-PTT will always (de-)activate the
// USB-In TX audio source.
txState = TX_CAT;
audioTransmit();
digitalWrite(PTT_KEY_OUT_PIN, LOW);
break;
}
}
//======================================================================
// catPTTOff()
//
// NOTE: Should probably move this to cat.ino.
//======================================================================
void catPTTOff()
{
// If we're not transmitting, or the active PTT is not CAT, then this
// command does nothing: CAT cannot interrupt other transmissions.
if (txState != TX_CAT) return;
switch(rigMode) {
// CAT should not start or stop TX in CW mode... we really should
// not even get this command. But maybe the rig control software is
// doing it (but then, we should inhibit this in the Raduino).
//case MODE_CW:
//break;
case MODE_SSB:
// In SSB mode, CAT-PTT will always (de-)activate the Line-In TX
// audio source.
digitalWrite(PTT_KEY_OUT_PIN, HIGH);
audioReceive();
txState = TX_OFF;
break;
case MODE_DIGI:
// In Digital (USB) mode, CAT-PTT will always (de-)activate the
// USB-In TX audio source.
digitalWrite(PTT_KEY_OUT_PIN, HIGH);
audioReceive();
txState = TX_OFF;
break;
}
}
//======================================================================
// checkMicPTT()
//======================================================================
void checkMicPTT()
{
micPTT.update();
// If we're transmitting, then we're just going to check if Mic-PTT
// was released--the Mic-PTT can always be used to terminate a
// transmission from another source.
if ((txState != TX_OFF) && micPTT.rose()) {
#if defined(DEBUG)
USBSERIAL.println("DEBUG: mic PTT released");
#endif
digitalWrite(PTT_KEY_OUT_PIN, HIGH);
// In CW mode, we get a sidetone from the uBITX, so we don't mute
// the receive audio.
if (rigMode != MODE_CW) audioReceive();
txState = TX_OFF;
return;
}
if ((txState == TX_OFF) && micPTT.fell()) {
#if defined(DEBUG)
USBSERIAL.println("DEBUG: mic PTT depressed");
#endif
switch(rigMode) {
case MODE_CW:
txState = TX_MIC;
digitalWrite(PTT_KEY_OUT_PIN, LOW);
break;
case MODE_SSB:
txState = TX_MIC;
audioSelectTxInput(TX_MIC_IN);
audioTransmit();
digitalWrite(PTT_KEY_OUT_PIN, LOW);
break;
case MODE_DIGI:
// Mic PTT actuation during Digital does nothing.
break;
case MODE_TEST:
txState = TX_MIC;
audioSelectTxInput(TX_TEST_IN);
audioTransmit();
digitalWrite(PTT_KEY_OUT_PIN, LOW);
break;
}
}
}
//======================================================================
// checkLinePTT()
//======================================================================
void checkLinePTT()
{
linePTT.update();
// If we're transmitting, then we're just going to check if Line-PTT
// was released--the Line-PTT can always be used to terminate a
// transmission from another source.
if ((txState != TX_OFF) && linePTT.rose()) {
digitalWrite(PTT_KEY_OUT_PIN, HIGH);
// In CW mode, we get a sidetone from the uBITX, so we don't mute
// the receive audio.
if (rigMode != MODE_CW) audioReceive();
txState = TX_OFF;
return;
}
if ((txState == TX_OFF) && linePTT.fell()) {
switch(rigMode) {
case MODE_CW:
txState = TX_LINE;
digitalWrite(PTT_KEY_OUT_PIN, LOW);
break;
case MODE_SSB:
txState = TX_LINE;
audioSelectTxInput(TX_LINE_IN);
audioTransmit();
digitalWrite(PTT_KEY_OUT_PIN, LOW);
break;
case MODE_DIGI:
// Line PTT actuation during Digital does nothing.
break;
case MODE_TEST:
txState = TX_LINE;
audioSelectTxInput(TX_TEST_IN);
audioTransmit();
digitalWrite(PTT_KEY_OUT_PIN, LOW);
break;
}
}
}
//======================================================================
void setRigMode(RigMode m)
{
rigMode = m;
switch(rigMode) {
case MODE_SSB:
case MODE_TEST:
// SSB sets the TX audio input to line-in. Note that this will be
// automatically overridden by mic-in, if the mic PTT is pressed.
audioSelectTxInput(TX_LINE_IN);
audioSSBFilter();
// dspMenu = &ssbMenu;
break;
case MODE_DIGI:
// Digi sets the TX audio input to USB-in. Keying in this case must
// be via CAT control. Digital modes can also be used through the
// line-in, but the rig mode should be set to SSB in that case, and
// the rig could be keyed either via the line-in PTT, or via CAT.
// Digimodes could also be used through the mic-in, but in that case,
// the mic PTT line would need to be keyed by the computer rather
// than using CAT for PTT control.
audioSelectTxInput(TX_USB_IN);
audioDigiFilter();
// dspMenu = &digiMenu;
break;
case MODE_CW:
// CW just gets the radio off of Mic-In; but it won't use Line-In.
audioSelectTxInput(TX_LINE_IN);
audioCWFilter();
// dspMenu = &cwMenu;
break;
}
}
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(12);
AudioMemory(16); // NOTE: Need to fine tune this. Have had errors due to this being too low.
micPTT.attach(MIC_PTT_PIN, INPUT_PULLUP);
micPTT.interval(25);
linePTT.attach(LINE_PTT_PIN, INPUT_PULLUP);
linePTT.interval(25);
initKeyLine();
rigAudio.init();
pinMode(PTT_KEY_OUT_PIN, OUTPUT);
digitalWrite(PTT_KEY_OUT_PIN, HIGH);
frameCounter = 0;
frameMillis = 0;
audioInit();
#if defined(FACTORY_CALIBRATION)
setRigMode(MODE_TEST);
#else
setRigMode(MODE_SSB);
#endif
knob.write(0);
btn.attach(ENCODER_SW_PIN, INPUT_PULLUP);
btn.interval(25);
rig.init();
USBDEBUG("setup completed");
}
//======================================================================
void loop() {
elapsedMillis elapsed = 0;
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());
}
}
switch(rigMode) {
case MODE_CW:
//======================================================================
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()) {
if (keyer.is_down()) {
digitalWrite(PTT_KEY_OUT_PIN, LOW);
} else {
digitalWrite(PTT_KEY_OUT_PIN, HIGH);
// 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();
}
// No break... if the paddle is not active, I want this to fall
// through to checkMicPTT etc... but return early if the paddle is
// active, to maximize responsiveness. Probably could just use
// 'if' statements here instead of the 'switch'.
return;
}
default:
checkMicPTT();
checkLinePTT();
serviceCAT();
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");
}
}
}
//dspMenu->update();
rig.update();
/*
#if defined(DEBUG)
int frame_skews = 0; // for debugging; see how often we skew frames
#endif
oldRigMode = rig.get_rig_mode();
// put your main code here, to run repeatedly:
// 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());
// Run at a nominal 20 Hz frame rate.
#if defined(DEBUG)
if (frame_timer > 50) {
frame_skews += 1;
}
#endif
while (frame_timer < 50) { // this doesn't seem like a good way to do this
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
}
//======================================================================