diff --git a/ubitx_iop/cat.h b/ubitx_iop/cat.h index c25ad0a..1283eb4 100644 --- a/ubitx_iop/cat.h +++ b/ubitx_iop/cat.h @@ -5,11 +5,134 @@ #ifndef __iop_cat_h__ #define __iop_cat_h__ +#define ACK 0 +#define CAT_PREFIX 0xC0 +#define IOP_PREFIX 0xD0 +#define EEPROM_READ_PREFIX 0xE0 +#define EEPROM_WRITE_PREFIX 0xF0 + +#define IOP_MODE_COMMAND 0x00 +#define IOP_START_TX_COMMAND 0x01 +#define IOP_STOP_TX_COMMAND 0x02 +#define IOP_CW_CONFIG_MSG 0x03 + +#define IOP_MODE_REQUEST 0x00 +#define IOP_CW_STATUS_MSG 0x03 + +#define IOP_MODE_SSB 0x00 +#define IOP_MODE_DIGI 0x01 +#define IOP_MODE_CW 0x02 + #include "config.h" #define USBSERIAL Serial #define HWSERIAL Serial1 +/*struct IOPTMsg { +}; +*/ + +enum KeyMode { + STRAIGHT = 0, + IAMBIC_A, + IAMBIC_B, + //ULTIMATIC, + //BUG, +}; + +const byte MODE_LETTER[3] = {'S', 'A', 'B'}; + +const byte NO_FLAGS = 0; +const byte REVERSED = 1; + +#define R_MESSAGE_MAX_LEN 32 +#define T_MESSAGE_MAX_LEN 32 + +struct RMessage { + uint8_t id; + uint8_t len; + byte data[R_MESSAGE_MAX_LEN]; +}; + +struct TMessage { + uint8_t id; + uint8_t len; + byte data[T_MESSAGE_MAX_LEN]; +}; + +//====================================================================== +// CW CONFIGURATION MESSAGE +//====================================================================== + +struct CWConfig { + // mode + KeyMode mode = IAMBIC_A; + // flags + bool reversed = false; + // parameters + uint8_t wpm = 15; + float weight = 3.0; + uint16_t sidetone = 700; +}; + +void packR_CWConfig(RMessage &m, CWConfig const &c) +{ + m.id = IOP_CW_CONFIG_MSG; + m.len = 6; + // mode + m.data[0] = byte(c.mode); + // flags + m.data[1] = NO_FLAGS; + m.data[1] |= (c.reversed ? REVERSED : 0); + // parameters + m.data[2] = byte(c.wpm); + m.data[3] = byte(c.weight * 10.0); + m.data[4] = byte(c.sidetone >> 8); + m.data[5] = byte(c.sidetone | 0x0F); +} + +void unpackR_CWConfig(CWConfig &c, RMessage const &m) +{ + //if (m.id != IOP_CW_CONFIG_MSG || m.len != 6) { + // // do some error thing... + //} + //mode + c.mode = KeyMode(m.data[0]); + // flags + c.reversed = bool(m.data[1] & REVERSED); + // parameters + c.wpm = uint8_t(m.data[2]); + c.weight = float(m.data[3]) / 10.0; + c.sidetone = (m.data[4] << 8) | m.data[5]; +} + +//====================================================================== +// MODE REQUEST MESSAGE +//====================================================================== + +void packT_ModeRequest(TMessage &m) +{ + m.id = IOP_MODE_REQUEST; + m.len = 0; +} + +//====================================================================== +// CW STATUS MESSAGE +//====================================================================== + +//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 +//} + +// No unpack required: this is a string to put on the display. + +//====================================================================== + void initCAT(long, int); void serviceCAT(); diff --git a/ubitx_iop/cat.ino b/ubitx_iop/cat.ino index a07978b..abe1ea6 100644 --- a/ubitx_iop/cat.ino +++ b/ubitx_iop/cat.ino @@ -4,18 +4,18 @@ #include "cat.h" -#define ACK 0 -#define CAT_PREFIX 0xC0 -#define IOP_PREFIX 0xD0 -#define EEPROM_READ_PREFIX 0xE0 -#define EEPROM_WRITE_PREFIX 0xF0 - -#define IOP_MODE_COMMAND 0x00 -#define IOP_START_TX_COMMAND 0x01 -#define IOP_STOP_TX_COMMAND 0x02 -#define IOP_MODE_SSB 0x00 -#define IOP_MODE_DIGI 0x01 -#define IOP_MODE_CW 0x02 +//#define ACK 0 +//#define CAT_PREFIX 0xC0 +//#define IOP_PREFIX 0xD0 +//#define EEPROM_READ_PREFIX 0xE0 +//#define EEPROM_WRITE_PREFIX 0xF0 +// +//#define IOP_MODE_COMMAND 0x00 +//#define IOP_START_TX_COMMAND 0x01 +//#define IOP_STOP_TX_COMMAND 0x02 +//#define IOP_MODE_SSB 0x00 +//#define IOP_MODE_DIGI 0x01 +//#define IOP_MODE_CW 0x02 //====================================================================== // CAT from PC-to-IOP diff --git a/ubitx_iop/config.h b/ubitx_iop/config.h index 5b1833a..008a51b 100644 --- a/ubitx_iop/config.h +++ b/ubitx_iop/config.h @@ -5,6 +5,9 @@ #ifndef __iop_config_h__ #define __iop_config_h__ +#define KEYER_LEFT_PADDLE_PIN 16 +#define KEYER_RIGHT_PADDLE_PIN 17 + // Uncomment to use the "factory" calibration mode. This is intended to // allow calibration of IOP settings, separately from the uBITX/Raduino. // There will be no pass-thru of any CAT. diff --git a/ubitx_iop/keyer.h b/ubitx_iop/keyer.h new file mode 100644 index 0000000..36e6007 --- /dev/null +++ b/ubitx_iop/keyer.h @@ -0,0 +1,81 @@ +//********************************************************************** +// +// Keyer, a part of nanoIO +// +// nanoIO paddle keyer (c) 2018, David Freese, W1HKJ +// +// based on code from Iambic Keyer Code Keyer Sketch +// Copyright (c) 2009 Steven T. Elliott +// +// nanoIO is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// nanoIO is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with fldigi. If not, see . +// +//Revisions: +// +//1.0.0: Initial release +// +//********************************************************************** + + +#ifndef __iop_keyer_h__ +#define __iop_keyer_h__ + +//#include "Arduino.h" + +//#include "config.h" + +#define IAMBICA 0 +#define IAMBICB 1 +#define STRAIGHT 2 + +class Keyer +{ +private: + //int cw_pin_; + //int ptt_pin_; + bool key_down; + + long ktimer; + + int _speed; + int _dashlen; // Length of dash + int _dotlen; // Length of dot + int _space_len; // Length of space + float _weight; + + char keyerControl; + char keyerState; + int key_mode; + + void calc_ratio(); + void update_PaddleLatch(); + +public: + Keyer(int wpm, float _weight); + //void cw_pin(int pin); + //void ptt_pin(int pin); + void wpm(int spd); + void set_mode(int md); + int get_mode() { return key_mode; } + bool is_down() { return key_down; } +// void set__weight(); + + bool do_paddles(); + +}; + +#endif + +//====================================================================== +// EOF +//====================================================================== diff --git a/ubitx_iop/keyer.ino b/ubitx_iop/keyer.ino new file mode 100644 index 0000000..f1cc9e4 --- /dev/null +++ b/ubitx_iop/keyer.ino @@ -0,0 +1,214 @@ +//====================================================================== +// +// nanoIO paddle keyer (c) 2018, David Freese, W1HKJ +// +// based on code from Iambic Keyer Code Keyer Sketch +// Copyright (c) 2009 Steven T. Elliott +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details: +// +// Free Software Foundation, Inc., 59 Temple Place, Suite 330, +// Boston, MA 02111-1307 USA +// +//====================================================================== + +//#include "Arduino.h" +//#include "TimerOne.h" +#include "config.h" +#include "keyer.h" + +const uint8_t LP_in = KEYER_LEFT_PADDLE_PIN; +const uint8_t RP_in = KEYER_RIGHT_PADDLE_PIN; + +//#define ST_Freq 600 // Set the Sidetone Frequency to 600 Hz + +//====================================================================== +// keyerControl bit definitions +// +#define DIT_L 0x01 // Dit latch +#define DAH_L 0x02 // Dah latch +#define DIT_PROC 0x04 // Dit is being processed +#define PDLSWAP 0x08 // 0 for normal, 1 for swap +//====================================================================== +// +// State Machine Defines + +enum KSTYPE {IDLE, CHK_DIT, CHK_DAH, KEYED_PREP, KEYED, INTER_ELEMENT }; + +Keyer::Keyer(int wpm, float weight) +{ + //ptt_pin_ = PTT_PIN; + //cw_pin_ = CW_PIN; +// Setup outputs + pinMode(LP_in, INPUT_PULLUP); // sets Left Paddle digital pin as input + pinMode(RP_in, INPUT_PULLUP); // sets Right Paddle digital pin as input + +// pinMode(ST_Pin, OUTPUT); // Sets the Sidetone digital pin as output + +// digitalWrite(LP_in, HIGH); // Enable pullup resistor on Left Paddle Input Pin +// digitalWrite(RP_in, HIGH); // Enable pullup resistor on Right Paddle Input Pin + + keyerState = IDLE; + keyerControl = 0; + key_mode = IAMBICA; + key_down = false; + _weight = weight; + + _speed = wpm; + calc_ratio(); +} + +// Calculate the length of dot, dash and silence +void Keyer::calc_ratio() +{ + float w = (1 + _weight) / (_weight -1); + _space_len = (1200 / _speed); + _dotlen = _space_len * (w - 1); + _dashlen = (1 + w) * _space_len; +} + +//void Keyer::cw_pin(int pin) +//{ +// ptt_pin_ = pin; +//} + +//void Keyer::ptt_pin(int pin) +//{ +// cw_pin_ = pin; +//} + +void Keyer::set_mode(int md) +{ + key_mode = md; +} + +void Keyer::wpm(int wpm) +{ + _speed = wpm; + calc_ratio(); +} + +//====================================================================== +// Latch paddle press +//====================================================================== + +void Keyer::update_PaddleLatch() +{ + if (digitalRead(LP_in) == LOW) { + keyerControl |= DIT_L; + } + if (digitalRead(RP_in) == LOW) { + keyerControl |= DAH_L; + } +} + +bool Keyer::do_paddles() +{ + if (key_mode == STRAIGHT) { // Straight Key + if ((digitalRead(LP_in) == LOW) || (digitalRead(RP_in) == LOW)) { +// Key from either paddle +// digitalWrite(ptt_pin_, HIGH); +// digitalWrite(cw_pin_, HIGH); +// tone(ST_Pin, 600); + key_down = true; + return true; + } else { +// digitalWrite(ptt_pin_, LOW); +// digitalWrite(cw_pin_, LOW); +// noTone(ST_Pin); + key_down = false; + } + return false; + } + +// keyerControl contains processing flags and keyer mode bits +// Supports Iambic A and B +// State machine based, uses calls to millis() for timing. + switch (keyerState) { + case IDLE: // Wait for direct or latched paddle press + if ((digitalRead(LP_in) == LOW) || (digitalRead(RP_in) == LOW) || (keyerControl & 0x03)) { + update_PaddleLatch(); + keyerState = CHK_DIT; + // letting this fall through // return true; + } else { + return false; + } +// break; + case CHK_DIT: // See if the dit paddle was pressed + if (keyerControl & DIT_L) { + keyerControl |= DIT_PROC; + ktimer = _dotlen; + keyerState = KEYED_PREP; + return true; + } else { // fall through + keyerState = CHK_DAH; + } + case CHK_DAH: // See if dah paddle was pressed + if (keyerControl & DAH_L) { + ktimer = _dashlen; + keyerState = KEYED_PREP; + // letting this fall through // return true; + } else { + keyerState = IDLE; + return false; + } +// break; + case KEYED_PREP: // Assert key down, start timing + // state shared for dit or dah +// digitalWrite(ptt_pin_, HIGH); // Enable PTT +// tone(ST_Pin, ST_Freq); // Turn the Sidetone on +// digitalWrite(cw_pin_, HIGH); // Key the CW line + key_down = true; + ktimer += millis(); // set ktimer to interval end time + keyerControl &= ~(DIT_L + DAH_L); // clear both paddle latch bits + keyerState = KEYED; // next state + // letting this fall through // return true; +// break; + case KEYED: // Wait for timer to expire + if (millis() > ktimer) { // are we at end of key down ? +// digitalWrite(ptt_pin_, LOW); // Disable PTT +// noTone(ST_Pin); // Turn the Sidetone off +// digitalWrite(cw_pin_, LOW); // Unkey the CW line + key_down = false; + ktimer = millis() + _space_len; // inter-element time + keyerState = INTER_ELEMENT; // next state + // letting this fall through // return true; + } else if (key_mode == IAMBICB) { // Iambic B Mode ? + update_PaddleLatch(); // yes, early paddle latch in Iambic B mode + } else { + return true; + } +// break; + + case INTER_ELEMENT: // Insert time between dits/dahs + update_PaddleLatch(); // latch paddle state + if (millis() > ktimer) { // are we at end of inter-space ? + if (keyerControl & DIT_PROC) { // was it a dit or dah ? + keyerControl &= ~(DIT_L + DIT_PROC); // clear two bits + keyerState = CHK_DAH; // dit done, check for dah + return true; + } else { + keyerControl &= ~(DAH_L); // clear dah latch + keyerState = IDLE; // go idle + return false; + } + } else { + return true; + } +// break; + } + + return false; // resolve compiler warning; do we ever get here? +} + +//====================================================================== +// EOF +//====================================================================== diff --git a/ubitx_iop/menu.h b/ubitx_iop/menu.h new file mode 100644 index 0000000..cc409cb --- /dev/null +++ b/ubitx_iop/menu.h @@ -0,0 +1,52 @@ +//====================================================================== +// menu.h +//====================================================================== + +#ifndef __menu_h__ +#define __menu_h__ + +// 16 characters on display +#define MAX_TEXT_LEN 16 +#define MENU_SELECTED_CHAR '>' +/* +public class MenuItem { + public: + MenuItem(bool active = true, int timeout = 0): _active(active), _timeout(timeout), _elapsed(0) {} + void update() { + if ((_timeout > 0) && (_elapsed > _timeout)) { + _active = false; + } + } + inline void activate() { _active = true; _elapsed = 0; } + inline void deactivate() { _active = false; } + virtual MenuItem* accept(); + virtual MenuItem* reject(); + virtual MenuItem* next(); + virtual MenuItem* prev(); + + private: + bool _active; + int _timeout; + elapsedMillis _elapsed; +}; + +public class SSBMenu { + public: + private: +}; + +public class DigiMenu { + public: + private: +} + +public class CWMenu { + public: + private: +}; +*/ +#endif + +//====================================================================== +// EOF +//====================================================================== diff --git a/ubitx_iop/menu.ino b/ubitx_iop/menu.ino new file mode 100644 index 0000000..8b7ad15 --- /dev/null +++ b/ubitx_iop/menu.ino @@ -0,0 +1,139 @@ +//====================================================================== +// menu.ino +//====================================================================== + +#include "menu.h" + +/* + CW mode: + WPM (although we can get this from Raduino) + WPM: # (L/R, select/abort) + FILT + WIDE NORM NARR + + SSB mode: + TX LVL + COMP + FILT + WIDE NORM NARR + + Digi mode: + TX LVL +*/ + +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; +}; + + +//====================================================================== +// EOF +//====================================================================== diff --git a/ubitx_iop/ubitx_iop.h b/ubitx_iop/ubitx_iop.h index 018e2d1..7adc9e3 100644 --- a/ubitx_iop/ubitx_iop.h +++ b/ubitx_iop/ubitx_iop.h @@ -9,6 +9,7 @@ #include "audio.h" #include "cat.h" #include "eeprom.h" +#include "keyer.h" // comment this out to disable debugging code //#define DEBUG @@ -29,6 +30,8 @@ enum TxState { extern RigMode rigMode; +extern bool keyerKeyDown; + #endif //====================================================================== diff --git a/ubitx_iop/ubitx_iop.ino b/ubitx_iop/ubitx_iop.ino index af884d0..bd29de2 100644 --- a/ubitx_iop/ubitx_iop.ino +++ b/ubitx_iop/ubitx_iop.ino @@ -19,6 +19,13 @@ Bounce linePTT = Bounce(); TxState txState = TX_OFF; +Keyer keyer(15, 3.0); // need to make configurable + +//SSBMenu ssbMenu(); +//DigiMenu digiMenu(); +//CWMenu cwMenu(); +//MenuItem* dspMenu = &ssbMenu; + //====================================================================== // catPTTOn() // @@ -195,6 +202,7 @@ void setRigMode(RigMode m) // automatically overridden by mic-in, if the mic PTT is pressed. audioSelectTxInput(TX_LINE_IN); audioSSBFilter(); +// dspMenu = &ssbMenu; break; case MODE_DIGI: @@ -207,12 +215,14 @@ void setRigMode(RigMode m) // 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(); + audioCWFilter(); +// dspMenu = &cwMenu; break; } } @@ -239,12 +249,32 @@ void setup() { //====================================================================== -void loop() { - elapsedMillis frame_timer = 0; +void loop() { + elapsedMillis elapsed = 0; + + switch(rigMode) { + case MODE_CW: + if (keyer.do_paddles()) { + if (keyer.is_down()) { + digitalWrite(PTT_KEY_OUT_PIN, LOW); + } else { + digitalWrite(PTT_KEY_OUT_PIN, HIGH); + } + // 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(); + } + + //dspMenu->update(); - checkMicPTT(); - checkLinePTT(); - serviceCAT(); /* #if defined(DEBUG) int frame_skews = 0; // for debugging; see how often we skew frames