About to make a bunch of changes (really not all keyer related), so

committing for safety...
This commit is contained in:
Rob French 2020-05-14 20:49:54 -05:00
parent a039740466
commit e9d021835a
9 changed files with 663 additions and 18 deletions

View File

@ -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();

View File

@ -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

View File

@ -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.

81
ubitx_iop/keyer.h Normal file
View File

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

214
ubitx_iop/keyer.ino Normal file
View File

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

52
ubitx_iop/menu.h Normal file
View File

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

139
ubitx_iop/menu.ino Normal file
View File

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

View File

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

View File

@ -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