kopia lustrzana https://github.com/k3ng/k3ng_cw_keyer
897 wiersze
32 KiB
C
897 wiersze
32 KiB
C
/* ***************************************************************************
|
|
|
|
pico pico pico pico pico pico pico pico pico pico pico pico pico pico pico pico
|
|
pico pico pico pico pico pico pico pico pico pico pico pico pico pico pico pico
|
|
pico pico pico pico pico pico pico pico pico pico pico pico pico pico pico pico
|
|
pico pico pico pico pico pico pico pico pico pico pico pico pico pico pico pico
|
|
|
|
M M OOOOO RRRR TTTTTT TTTTTT Y Y RRRR TTTTTT TTTTTT Y Y
|
|
M M M M O O R RR TT TT Y Y R RR TT TT Y Y
|
|
M M M O O RRR TT TT YY RRR TT TT YY
|
|
M M O O R R TT TT YY R R TT TT YY
|
|
M M OOOOO R R TT TT YY R R TT TT YY
|
|
|
|
TinyFSK sketch for a Mortty v5 Keyer on RPi Pico-W platform by K8UT 2023-09-22
|
|
|
|
TinyFSK Version 1.1.0
|
|
Copyright (C) 2013-2015 Andrew T. Flowers K0SM
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is furnished
|
|
to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
IN THE SOFTWARE.
|
|
****************************************************************************
|
|
Revisions:
|
|
1.1.0: Make "Robust UnShift On Space" transmission to be compatible with MMTTY's
|
|
non-USOS default receiver. It is effectively non-USOS transmission
|
|
with extra FIGS shifts if a figs character appears after a space. This should
|
|
always print properly on by USOS and non-USOS demodulators at the expense of
|
|
having to send a few extra symbols in some contest exchages.
|
|
|
|
Version information displayed at beginning of configuration screen so people
|
|
can tell what version of firmware they have.
|
|
|
|
1.0.1: Swap FSK and PTT pins to make pin-compatible with K3NG "nanokeyer"
|
|
1.0.0: Initial release
|
|
*********************************************************************************** */
|
|
|
|
|
|
// original source version number from K0SM
|
|
#define VERSION "1.1.0"
|
|
// ported to Raspberry Pi Pico-W 2023-09-25 by Larry Gauthier K8UT
|
|
#define VERSION_pico "0.0.1"
|
|
|
|
|
|
|
|
// TEST troubleshooting parms - these define's must be placed at the beginning before #include "TimerInterrupt_Generic.h"
|
|
// _TIMERINTERRUPT_LOGLEVEL_ from 0 to 4
|
|
// Don't define _TIMERINTERRUPT_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system.
|
|
// #define TIMER_INTERRUPT_DEBUG 1
|
|
// #define _TIMERINTERRUPT_LOGLEVEL_ 4
|
|
|
|
// Can be included as many times as necessary, without `Multiple Definitions` Linker Error
|
|
#include "RPi_Pico_TimerInterrupt.h"
|
|
// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error
|
|
#include "RPi_Pico_ISR_Timer.h"
|
|
// define the RPI_PICO_Timer
|
|
RPI_PICO_Timer FSKTimer(0);
|
|
|
|
// Pico-W - do not use on-board LED
|
|
// #if !defined(LED_BUILTIN)
|
|
// #define LED_BUILTIN 25
|
|
// #endif
|
|
|
|
//Arduino pins for Radio 1 and Radio 2 PTT and FSK to control transmitter
|
|
#define R1_FSK_PIN 11
|
|
#define R1_PTT_PIN 13
|
|
#define R2_FSK_PIN 6
|
|
#define R2_PTT_PIN 8
|
|
|
|
|
|
|
|
|
|
//Special Baudot symbols for shift
|
|
#define LTRS_SHIFT 0x1F //baudot letter shift byte
|
|
#define FIGS_SHIFT 0x1B //baudot figs shift byte
|
|
|
|
#define SHIFT_UNKNOWN 0 //Undefined shift--used at TX start to force shift state
|
|
|
|
//Special ASCII SYMBOLS (8 bit)
|
|
#define ASCII_NULL 0x00
|
|
#define ASCII_LF 0x0A
|
|
#define ASCII_CR 0x0D
|
|
|
|
//BUFFER SETTINGS
|
|
#define SEND_BUFFER_SIZE 500 // Allow up to 500 chars in the buffer \
|
|
// before overrunning (wrapping around). \
|
|
// This can be increased on most boards \
|
|
// with more RAM.
|
|
|
|
#define TX_END_FLAG 0xFF // Used in Baudot stream to indicate EOT
|
|
|
|
//References used in banging out the bits for 5-bit baudot. These
|
|
//are relative to the first data bit in the frame.
|
|
#define START_BIT_POS -1
|
|
#define STOP_BIT_POS 5
|
|
|
|
//Commands that control transmitter sequencing
|
|
#define TX_ON '[' // TX now => {TX} in N1MM
|
|
#define TX_END ']' // Buffered switch to RX => {END} in N1MM
|
|
#define TX_ABORT '\\' // (Backslash) Immediate switch to RX and clear buffer => {ESC} in N1MM
|
|
|
|
//Configuration commands. These are also the values saved in the EEPROM.
|
|
#define COMMAND_ESCAPE '~'
|
|
#define COMMAND_POLARITY_MARK_HIGH '0'
|
|
#define COMMAND_POLARITY_MARK_LOW '1'
|
|
#define COMMAND_45BAUD '4'
|
|
#define COMMAND_50BAUD '5'
|
|
#define COMMAND_75BAUD '7'
|
|
#define COMMAND_DUMP_CONFIG '?'
|
|
|
|
// Stop bit settings
|
|
#define STOP_BITS_1 1 // 1 stop bit
|
|
#define STOP_BITS_1R5 2 // 1.5 stop bits
|
|
#define STOP_BITS_2 3 // 2 stop bits
|
|
|
|
// TX USOS settings
|
|
#define USOS_OFF 1 //Assumes that RX will not reset to LTRS shift after space \
|
|
//All shift symbols are explicit and spaces do not change \
|
|
//shift state: \
|
|
// K0SM 599 05 NY NY \
|
|
// --> <LTR>K<FIG>0<LTR>SM <FIG>599 05 <LTR>NY NY
|
|
|
|
#define USOS_ON 2 //Space is an implicit LTRS shift character. "Ham standard" \
|
|
//demodulators operate in this mode. \
|
|
// K0SM 599 05 NY NY \
|
|
// --> <LTR>K<FIG>0<LTR>SM <FIG>599 <FIG>05 NY NY
|
|
|
|
#define USOS_MMTTY_HACK 3 //Essentially USOS OFF plus extra FIGS shifts for all words \
|
|
//starting with numbers. This is what MMTTY somewhat misleadingly \
|
|
//calls "USOS transmission." This settings makes many contest exchanges \
|
|
//longer (and slightly more prone to bit errors) all to be \
|
|
//compatible with MMTTY's non-USOS RX default \
|
|
// K0SM 599 05 NY NY \
|
|
// --> <LTR>K<FIG>0<LTR>SM <FIG>599 <FIG>05 <LTR>NY NY
|
|
|
|
void initTimer();
|
|
void handleConfigurationCommand(byte oneByte);
|
|
void eeLoad();
|
|
void initTimer();
|
|
bool timerISR(struct repeating_timer* t);
|
|
void displayConfigurationPrompt();
|
|
void displayConfiguration();
|
|
void processHalfBit();
|
|
void resetChar();
|
|
void resetSendBuffer();
|
|
void addToSendBuffer(byte newByte);
|
|
byte getNextSendChar();
|
|
boolean requiresLetters(byte asciiByte);
|
|
boolean requiresFigures(byte asciiByte);
|
|
void setPTT(byte isPTT);
|
|
void echo(byte b);
|
|
|
|
/******************************************************
|
|
Variable declarations
|
|
*******************************************************/
|
|
|
|
//EEPROM addresses to persist configuration
|
|
int ee_speed_addr;
|
|
int ee_polarity_addr;
|
|
|
|
|
|
// Mapping of ascii to baudot symbols. This is the
|
|
// translation table that maps an incoming ASCII byte
|
|
// on the serial interface to a equivalent (or reasonable
|
|
// substitute) that exists the ITA2 or US 5-bit code.
|
|
// In general, any ASCII control character will be mapped
|
|
// to a Baudot NULL. Punctuation will be mapped to '?' if
|
|
// there is no equivalent in the Baudot set. Note that
|
|
// some punctuation such as '[', ']' and '\' are used
|
|
// to control the PTT behavior. Tilda (~) is used
|
|
// to enter the configuration menu. You can use
|
|
// your imagination to add other control functions here.
|
|
int asciiToBaudot[127] = {
|
|
|
|
//// ASCII ASCII IDX (decimal)
|
|
0, // Null character // 0
|
|
0, // Start of Header // 1
|
|
0, // Start of Text // 2
|
|
0, // End of Text // 3
|
|
0, // End of Transmission // 4
|
|
0, // Enquiry // 5
|
|
0, // Acknowledgment // 6
|
|
5, // Bell // 7
|
|
0, // Backspace // 8
|
|
0, // Horizontal Tab // 9
|
|
2, // Line feed // 10
|
|
0, // Vertical Tab // 11
|
|
0, // Form feed // 12
|
|
8, // Carriage return // 13
|
|
0, // Shift Out // 14
|
|
0, // Shift In // 15
|
|
0, // Data Link Escape // 16
|
|
0, // Device Control 1 // 17
|
|
0, // Device Control 2 // 18
|
|
0, // Device Control 3 // 19
|
|
0, // Device Control 4 // 20
|
|
0, // Negative Acknowledgement // 21
|
|
0, // Synchronous idle // 22
|
|
0, // End of Transmission Block // 23
|
|
0, // Cancel // 24
|
|
0, // End of Medium // 25
|
|
0, // Substitute // 26
|
|
0, // Escape // 27
|
|
0, // File Separator // 28
|
|
0, // Group Separator // 29
|
|
0, // Record Separator // 30
|
|
0, // Unit Separator // 31
|
|
4, // space // 32
|
|
13, // ! // 33
|
|
17, // " // 34
|
|
20, // # // 35
|
|
9, // $ // 36
|
|
25, // % // 37
|
|
26, // & // 38
|
|
11, // ' // 39
|
|
15, // ( // 40
|
|
18, // ) // 41
|
|
25, // * // 42
|
|
17, // + // 43 //ITA2
|
|
12, // , // 44
|
|
3, // - // 45
|
|
28, // . // 46
|
|
29, // / // 47
|
|
22, // 0 // 48
|
|
23, // 1 // 49
|
|
19, // 2 // 50
|
|
1, // 3 // 51
|
|
10, // 4 // 52
|
|
16, // 5 // 53
|
|
21, // 6 // 54
|
|
7, // 7 // 55
|
|
6, // 8 // 56
|
|
24, // 9 // 57
|
|
14, // : // 58
|
|
30, // ; // 59
|
|
25, // < // 60
|
|
30, // = // 61 //ITA2
|
|
25, // > // 62
|
|
25, // ? // 63
|
|
25, // @ // 64
|
|
3, // A // 65
|
|
25, // B // 66
|
|
14, // C // 67
|
|
9, // D // 68
|
|
1, // E // 69
|
|
13, // F // 70
|
|
26, // G // 71
|
|
20, // H // 72
|
|
6, // I // 73
|
|
11, // J // 74
|
|
15, // K // 75
|
|
18, // L // 76
|
|
28, // M // 77
|
|
12, // N // 78
|
|
24, // O // 79
|
|
22, // P // 80
|
|
23, // Q // 81
|
|
10, // R // 82
|
|
5, // S // 83
|
|
16, // T // 84
|
|
7, // U // 85
|
|
30, // V // 86
|
|
19, // W // 87
|
|
29, // X // 88
|
|
21, // Y // 89
|
|
17, // Z // 90
|
|
15, // [ Used to start TX // 91
|
|
20, // \ Used to escape TX // 92
|
|
18, // ] Buffered end TX // 93
|
|
25, // ^ // 94
|
|
4, // _ // 95
|
|
25, // ` // 96
|
|
3, // a // 97
|
|
25, // b // 98
|
|
14, // c // 99
|
|
9, // d // 100
|
|
1, // e // 101
|
|
13, // f // 102
|
|
26, // g // 103
|
|
20, // h // 104
|
|
6, // i // 105
|
|
11, // j // 106
|
|
15, // k // 107
|
|
18, // l // 108
|
|
28, // m // 109
|
|
12, // n // 110
|
|
24, // o // 111
|
|
22, // p // 112
|
|
23, // q // 113
|
|
10, // r // 114
|
|
5, // s // 115
|
|
16, // t // 116
|
|
7, // u // 117
|
|
30, // v // 118
|
|
19, // w // 119
|
|
29, // x // 120
|
|
21, // y // 121
|
|
17, // z // 122
|
|
15, // { // 123
|
|
20, // | // 124
|
|
18, // } // 125
|
|
25 // ~ Command escape char // 126
|
|
};
|
|
|
|
|
|
/*******************************************************
|
|
This section defines static runtime variables that affect
|
|
RTTY transmission. They are NOT directly changeable by user
|
|
commands because they can get ops into trouble. They are
|
|
here for the tinkerer/experimenter in case you want access
|
|
to them at runtime.
|
|
********************************************************/
|
|
|
|
long serialSpeed = 9600; //This is the speed for the serial
|
|
//(more likely USB) connection, 8-N-1
|
|
|
|
// Not user selectable, but USOS behavior can be changed here.
|
|
// We set this to TX extra shifts to be compatible with silly
|
|
// MMTTY default reciever
|
|
int usos = USOS_MMTTY_HACK;
|
|
|
|
int stopBits = STOP_BITS_1R5; // TX 1.5 stop bits
|
|
|
|
/***************************************
|
|
Dynamic runtime variables these are minipulated with
|
|
user commands or during normal TX operation.
|
|
*****************************************/
|
|
float baudrate = 45.45; //default--can be changed by user command
|
|
|
|
int pttLeadMillis = 150; //time before first start bit
|
|
int pttTailMillis = 25; //time after last stop bit
|
|
|
|
|
|
// Polarity--changed with user commands and stored in EEPROM
|
|
//boolean mark = LOW; //High indicates +V on the FSK pin
|
|
//boolean space = HIGH; //Low indicates 0V on the FSK pin
|
|
boolean mark = HIGH; //High indicates +V on the FSK pin
|
|
boolean space = LOW; //Low indicates 0V on the FSK pin
|
|
|
|
// Buffer management variables to handle TX text input
|
|
byte sendBufferArray[SEND_BUFFER_SIZE]; // size of TX buffer
|
|
byte sendBufferBytes = 0; // number of bytes unsent in TX buffer
|
|
byte lastAsciiByteSent = 0; // needed to echo back sent characters to terminal
|
|
boolean endWhenBufferEmpty = true; //flag to kill TX when buffer empty (']')
|
|
|
|
byte currentShiftState = SHIFT_UNKNOWN; //Keeps track of Letter/Figs state to determine
|
|
//if we need to send shift chars
|
|
|
|
// boolean ptt = false; // old one radio parm - Keeps track of PTT state (true = Transmitter is on)
|
|
boolean isR1_PTT = false; // Keeps track of Radio 1 PTT state (true = Transmitter is on)
|
|
boolean isR2_PTT = false; // Keeps track of Radio 2 PTT state (true = Transmitter is on)
|
|
|
|
volatile boolean isRxFlag = false; //set by timer interrupt. Set high every 1/2 bit
|
|
//to indicate when we should exectute the bit-banging
|
|
//routine. This is handled in the main loop function.
|
|
|
|
boolean configurationMode = false; //flag indicates if we are in the menu system or
|
|
//in normal operation.
|
|
|
|
// timer interval must be defined as a Unsigned Long integer "L"
|
|
unsigned long FSKTIMER_INTERVAL_US = 11001L; // default to 45.45 baud: 11001, 22002 == 45 baud 10000, 20000 = 50 baud 6666, 13332 == 75 baud
|
|
|
|
|
|
|
|
/**
|
|
* Handles configuration change commands by changing variables
|
|
* and writing new values to EEPROM.
|
|
*/
|
|
void handleConfigurationCommand(byte oneByte) {
|
|
switch (oneByte) {
|
|
case (COMMAND_POLARITY_MARK_HIGH):
|
|
{
|
|
mark = HIGH;
|
|
space = LOW;
|
|
EEPROM.write(ee_polarity_addr, COMMAND_POLARITY_MARK_HIGH);
|
|
break;
|
|
}
|
|
case (COMMAND_POLARITY_MARK_LOW):
|
|
{
|
|
mark = LOW;
|
|
space = HIGH;
|
|
EEPROM.write(ee_polarity_addr, COMMAND_POLARITY_MARK_LOW);
|
|
break;
|
|
}
|
|
case (COMMAND_45BAUD):
|
|
{
|
|
baudrate = 45.45;
|
|
FSKTIMER_INTERVAL_US = 11001;
|
|
initTimer();
|
|
EEPROM.write(ee_speed_addr, COMMAND_45BAUD);
|
|
break;
|
|
}
|
|
case (COMMAND_50BAUD):
|
|
{
|
|
baudrate = 50.0;
|
|
FSKTIMER_INTERVAL_US = 10000L;
|
|
initTimer();
|
|
EEPROM.write(ee_speed_addr, COMMAND_50BAUD);
|
|
break;
|
|
}
|
|
case (COMMAND_75BAUD):
|
|
{
|
|
baudrate = 75.0;
|
|
FSKTIMER_INTERVAL_US = 6666L;
|
|
initTimer();
|
|
EEPROM.write(ee_speed_addr, COMMAND_75BAUD);
|
|
break;
|
|
}
|
|
case (COMMAND_DUMP_CONFIG):
|
|
{
|
|
// we dump it out below
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
Serial.write("\nNot a recognized command. Exiting configuration mode.\n");
|
|
}
|
|
}
|
|
#if defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_RASPBERRY_PI_PICO)
|
|
EEPROM.commit();
|
|
#endif
|
|
displayConfiguration();
|
|
configurationMode = false;
|
|
}
|
|
|
|
/**
|
|
* Loads speed and polarity from EEPROM
|
|
*/
|
|
void eeLoad() {
|
|
|
|
byte speedChar = EEPROM.read(ee_speed_addr);
|
|
byte polarity = EEPROM.read(ee_polarity_addr);
|
|
|
|
if (polarity == COMMAND_POLARITY_MARK_LOW) {
|
|
mark = LOW;
|
|
} else {
|
|
mark = HIGH;
|
|
}
|
|
space = !mark;
|
|
|
|
switch (speedChar) {
|
|
case (COMMAND_50BAUD):
|
|
{
|
|
baudrate = 50.0;
|
|
break;
|
|
}
|
|
case (COMMAND_75BAUD):
|
|
{
|
|
baudrate = 75.0;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
baudrate = 45.45;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Init the timer to fire every *half* bit period. This allows us
|
|
* to have 1.5 stop bits if we want.
|
|
*/
|
|
void initTimer() {
|
|
|
|
FSKTimer.stopTimer(); // if timer is running, stop it
|
|
if (!FSKTimer.attachInterruptInterval(FSKTIMER_INTERVAL_US, timerISR)) { // if init fails, print this to console
|
|
Serial.println(F("Can't set FSKTimer. Select another freq. or timer"));
|
|
}
|
|
} // end of Init Timer() --------------------------------------------------------------------------
|
|
|
|
bool timerISR(struct repeating_timer* t) { // -----------------------------------------------------
|
|
(void)t;
|
|
isRxFlag = true;
|
|
|
|
/* TEST stuff from examples ino file
|
|
static bool toggle1 = false;
|
|
|
|
#if (TIMER_INTERRUPT_DEBUG > 0)
|
|
//timer interrupt toggles pin outputPin1
|
|
Serial.print("R1 Pin ");
|
|
Serial.print(outputPin1);
|
|
Serial.println(toggle1 ? F(" ON") : F(" OFF"));
|
|
#endif
|
|
|
|
digitalWrite(outputPin1, toggle1);
|
|
toggle1 = !toggle1;
|
|
*/
|
|
return true;
|
|
} // end of timerISR() FSK Timer Handler --------------------------------------------------------------------
|
|
|
|
/**
|
|
* Displays the configuration options on the console. keystroke ~?
|
|
*/
|
|
void displayConfigurationPrompt() {
|
|
|
|
Serial.write("\nEnter configuration command. Valid commands are:\n");
|
|
Serial.write(" 0 Set FSK polarity mark = HIGH\n");
|
|
Serial.write(" 1 Set FSK polarity mark = LOW\n");
|
|
Serial.write(" 4 Set 45.45 baud\n");
|
|
Serial.write(" 5 Set 50.0 baud\n");
|
|
Serial.write(" 7 Set 75.0 baud\n");
|
|
Serial.write("\n ? Show current configuration\n");
|
|
}
|
|
|
|
/**
|
|
* Prints the current configuration the console
|
|
*/
|
|
void displayConfiguration() {
|
|
Serial.write("\nBased on TinyFSK by K0SM v");
|
|
Serial.write(VERSION);
|
|
Serial.write("\nPorted to Pico-W by K8UT v");
|
|
Serial.write(VERSION_pico);
|
|
|
|
Serial.write("\nCurrent configuration:\n");
|
|
Serial.write(" Speed (baud): ");
|
|
Serial.print(baudrate);
|
|
Serial.write("\n");
|
|
|
|
Serial.write(" Polarity ");
|
|
if (mark == LOW) {
|
|
Serial.write(" mark = logical LOW");
|
|
} else {
|
|
Serial.write(" mark = logical HIGH");
|
|
}
|
|
Serial.write("\n");
|
|
}
|
|
|
|
/******************************************************************
|
|
* This called every half-bit period to figure out what to bit-bang
|
|
* out the FSK pin. It is basically an incremental counter that
|
|
* counts half bit periods and toggles the bits of the baudot character
|
|
* as needed. It bangs out the start bit, five symbol bits, and the
|
|
* stop bit, which is 1.5 bits long (hence the need to have a timer
|
|
* counting half bits).
|
|
* The 5 bit RTTY character frame looks like this:
|
|
*
|
|
* ||Start | LSB | X | X | X | MSB | Stop ||
|
|
* bitPos: -1 0 1 2 3 4 5
|
|
******************************************************************/
|
|
int sendingChar = LTRS_SHIFT; // default--this is the "diddle character"
|
|
int stopBitCounter = 0; // counts half-bits for stop bit
|
|
int bitPos = START_BIT_POS; // -1 = Start bit
|
|
bool midBit = false; // used as like an ignore flag--we usually don't
|
|
// toggle state in the middle of a bit. The exception
|
|
// is the stop bit, which is often 1.5 bits long.
|
|
void processHalfBit() {
|
|
|
|
//=====if (!ptt) // old one radio parm - not transmitting, so just return--there's nothing to send.
|
|
if (!isR1_PTT) //Radio 1 not transmitting, so just return--there's nothing to send.
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (midBit) {
|
|
midBit = false; // reset the flag. Next time we need to send the next bit.
|
|
return;
|
|
}
|
|
|
|
// it's time to bang out the next bit. We check for the special cases
|
|
// first. If it's a start bit we always sent SPACE and if its a STOP bit
|
|
// we always send MARK.
|
|
if (bitPos == START_BIT_POS) { // we have to send a start bit
|
|
|
|
// If it is time to send a start bit, we grab the next character to send so
|
|
// that it is ready the next time through the loop. The next character
|
|
// might be the TX_END_FLAG, in which case we need to turn off the transmitter.
|
|
sendingChar = getNextSendChar();
|
|
|
|
//===== need to expand R1 R2 if/when TinyFSK SO2R capable if (ptt) {
|
|
if (isR1_PTT) {
|
|
if (sendingChar == TX_END_FLAG) //end of data to send
|
|
{
|
|
setPTT(false);
|
|
return;
|
|
} else {
|
|
digitalWrite(R1_FSK_PIN, space); //start bit is always space
|
|
bitPos++;
|
|
midBit = true;
|
|
}
|
|
}
|
|
} else if (bitPos == STOP_BIT_POS) // we have to send a stop bit
|
|
{
|
|
//if stopBitCounter == 0 we are at the beginning of a stop bit
|
|
if (stopBitCounter == 0) {
|
|
digitalWrite(R1_FSK_PIN, mark);
|
|
stopBitCounter = stopBits; //this determines # of half-bit periods we stay in stop bit
|
|
} else // already in stop bit, just decrement
|
|
{
|
|
// stopBitCounter counts half-bit periods. 2 ==> one stop bit
|
|
// 3 ==> 1.5 stop bits
|
|
// 4 ==> two stop bits
|
|
stopBitCounter--;
|
|
if (stopBitCounter == 0) // end of stop bit period
|
|
{
|
|
bitPos = START_BIT_POS; // move on to start bit of next char
|
|
|
|
// If we just sent an explicit LTRS or FIGS shift, obviously we are in that state. If USOS is turned on and we
|
|
// have sent a space character, we are implicitly in LTRS shift.
|
|
if (sendingChar == LTRS_SHIFT || (usos == USOS_ON && sendingChar == 0x04)) //0x04 = Baudot space
|
|
{
|
|
currentShiftState = LTRS_SHIFT;
|
|
} else if (sendingChar == FIGS_SHIFT) {
|
|
currentShiftState = FIGS_SHIFT;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// We are not sending a stop/start bit, so we send the next bit of the
|
|
// of the character.
|
|
bool b = (sendingChar & (0x01 << bitPos)); //LSB first
|
|
if (b) {
|
|
digitalWrite(R1_FSK_PIN, mark);
|
|
} else {
|
|
digitalWrite(R1_FSK_PIN, space);
|
|
}
|
|
bitPos++;
|
|
midBit = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset character buffer. This is a helper routine when stop the
|
|
* transmitter so that everything is back to initial states ready
|
|
* to bang out the first character.
|
|
*/
|
|
void resetChar() {
|
|
sendingChar = LTRS_SHIFT;
|
|
stopBitCounter = 0;
|
|
bitPos = START_BIT_POS;
|
|
midBit = false;
|
|
}
|
|
|
|
/**
|
|
*Wipes the send buffer. Helper function for aborting
|
|
* a transmission.
|
|
*/
|
|
void resetSendBuffer() {
|
|
for (int i = 0; i < SEND_BUFFER_SIZE; i++) {
|
|
sendBufferArray[i] = 0;
|
|
}
|
|
sendBufferBytes = 0;
|
|
}
|
|
|
|
/**
|
|
* Adds a new byte to the transmit text buffer. These
|
|
* are *ASCII* bytes from the terminal, not Baudot.
|
|
*/
|
|
void addToSendBuffer(byte newByte) {
|
|
if (sendBufferBytes < SEND_BUFFER_SIZE) {
|
|
sendBufferBytes++;
|
|
sendBufferArray[sendBufferBytes - 1] = newByte;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the next Baudot (5-bit) char from the buffer. This
|
|
* function will return LTRS or FIGS shift characters when
|
|
* needed depending on the current shift state and USOS setting.
|
|
*/
|
|
byte getNextSendChar() {
|
|
|
|
byte rVal = LTRS_SHIFT; //default "idle" or "diddles" when nothing to send
|
|
|
|
if (sendBufferBytes > 0) // there is still data in buffer to send
|
|
{
|
|
byte asciiByte = sendBufferArray[0];
|
|
|
|
if (currentShiftState != LTRS_SHIFT && requiresLetters(asciiByte)) {
|
|
//echo('_');
|
|
return LTRS_SHIFT;
|
|
} else if (currentShiftState != FIGS_SHIFT && requiresFigures(asciiByte)) {
|
|
//echo('^');
|
|
return FIGS_SHIFT;
|
|
}
|
|
// Special "robust" USOS case--send FIGS after a space even if already in FIGS state and next
|
|
// character requires FIGS shift. Note: when this is called
|
|
// sendingChar is the char we just *finished* sending
|
|
else if (usos == USOS_MMTTY_HACK && currentShiftState != LTRS_SHIFT && requiresFigures(asciiByte) && sendingChar == 0x04) {
|
|
//echo('^');
|
|
return FIGS_SHIFT;
|
|
} else //we don't need to send a shift character. Just find the baudot equiv of the ascii symbol and return it.
|
|
{
|
|
rVal = asciiToBaudot[asciiByte];
|
|
lastAsciiByteSent = asciiByte;
|
|
sendBufferBytes--;
|
|
if (sendBufferBytes > 0) {
|
|
for (int i = 0; i < sendBufferBytes; i++) {
|
|
sendBufferArray[i] = sendBufferArray[i + 1];
|
|
}
|
|
}
|
|
echo(asciiByte);
|
|
}
|
|
} else // the buffer is empty
|
|
{
|
|
if (endWhenBufferEmpty) {
|
|
rVal = TX_END_FLAG; // signals to stop the TX
|
|
} else // slow typist?
|
|
{
|
|
if (currentShiftState == SHIFT_UNKNOWN) {
|
|
rVal = LTRS_SHIFT; //send LTRS idle if we haven't sent anything on this TX
|
|
} else {
|
|
rVal = currentShiftState; // idle on LTRS or FIGS depending on what state we are in
|
|
}
|
|
}
|
|
}
|
|
return rVal;
|
|
}
|
|
|
|
/*
|
|
* returns whether or not this is a "letter". Letters require LTRS
|
|
* shift preceding the byte if currently in FIGS mode.
|
|
*/
|
|
boolean requiresLetters(byte asciiByte) {
|
|
return (asciiByte >= 'A' && asciiByte <= 'Z')
|
|
|| (asciiByte >= 'a' && asciiByte <= 'z');
|
|
}
|
|
|
|
/**
|
|
* Helper function to find out whether a particular byte
|
|
* needs the FIGS shift preceeding it.
|
|
*/
|
|
boolean requiresFigures(byte asciiByte) {
|
|
return !requiresLetters(asciiByte)
|
|
&& (asciiByte != ASCII_NULL) //null
|
|
&& (asciiByte != ASCII_LF) //LF
|
|
&& (asciiByte != ASCII_CR)
|
|
&& (asciiByte != ' ');
|
|
}
|
|
|
|
/*
|
|
* Turns the PTT on or off and applies any delays that might exist.
|
|
*/
|
|
void setPTT(byte isPTT) {
|
|
if (isPTT) { // PTT ON
|
|
digitalWrite(R1_FSK_PIN, mark); //always start in mark state
|
|
digitalWrite(R1_PTT_PIN, HIGH);
|
|
// we will stay in the mark state for some amount of time
|
|
// before sending the first start bit of the first character
|
|
delay(pttLeadMillis);
|
|
} else { // PTT OFF
|
|
digitalWrite(R1_PTT_PIN, LOW); // drop PTT
|
|
digitalWrite(R1_FSK_PIN, space);
|
|
delay(pttTailMillis);
|
|
stopBitCounter = 0;
|
|
bitPos = -1;
|
|
currentShiftState = SHIFT_UNKNOWN;
|
|
|
|
lastAsciiByteSent = 0;
|
|
Serial.write("\ncmd:\n"); // Tells N1MM that TX is finished
|
|
}
|
|
isR1_PTT = isPTT;
|
|
}
|
|
|
|
/**
|
|
* Echo to the serial port. This will show up in the user's terminal
|
|
* if he or she is watching.
|
|
*/
|
|
void echo(byte b) {
|
|
Serial.write(b);
|
|
}
|
|
|
|
/*********************************************************************
|
|
Main execution
|
|
***********************************************************************/
|
|
|
|
/**
|
|
* Exectutes *once* at program start (when power applied or
|
|
* reset button pressed. Note that many Arudino devices have a
|
|
* "software reset" option that will reset the processor when
|
|
* the serial port is opened.
|
|
* It opens the port, configures the output pins, and loads
|
|
* configuration from EEPROM.
|
|
*/
|
|
void TinyFSKsetup() { // ==============================================================================================
|
|
|
|
|
|
|
|
|
|
#ifdef __LGT8FX8P__
|
|
/* LGT chip emulates EEPROM at the cost of giving up twice the space in program flash memory.
|
|
* Unortunately, the last 4 bytes of every 1KB block are read-only. Therefore
|
|
* EEPROM.length() would return 1024 (readable EEPROM size), while EEPROM.size() returns 1020
|
|
* (writable EEPROM size). The following line will give the right figure for LGT.
|
|
*/
|
|
ee_speed_addr = EEPROM.size() - 1;
|
|
ee_polarity_addr = EEPROM.size() - 2;
|
|
#elif (!defined(ARDUINO_SAM_DUE) && !defined(ARDUINO_ARCH_MBED) && !defined(ARDUINO_RASPBERRY_PI_PICO_W) && !defined(ARDUINO_RASPBERRY_PI_PICO) && !defined(HARDWARE_GENERIC_STM32F103C)) || (defined(ARDUINO_SAM_DUE) && defined(FEATURE_EEPROM_E24C1024))
|
|
ee_speed_addr = EEPROM.length() - 1;
|
|
ee_polarity_addr = EEPROM.length() - 2;
|
|
#elif defined(HARDWARE_GENERIC_STM32F103C)
|
|
ee_speed_addr = 252;
|
|
ee_polarity_addr = 253;
|
|
#elif defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_RASPBERRY_PI_PICO)
|
|
ee_speed_addr = 4094;
|
|
ee_polarity_addr = 4095;
|
|
EEPROM.begin(4096);
|
|
#else
|
|
// assume eeprom size of 1024 and hope for the best
|
|
ee_speed_addr = 1022;
|
|
ee_polarity_addr = 1023;
|
|
#endif
|
|
|
|
|
|
Serial.begin(serialSpeed);
|
|
while (!Serial) {
|
|
; // wait for serial port to connect. Needed for Leonardo only
|
|
}
|
|
|
|
// configure Radio 1 and Radio 2 FSK and PTT pins as outputs
|
|
pinMode(R1_FSK_PIN, OUTPUT);
|
|
pinMode(R1_PTT_PIN, OUTPUT);
|
|
pinMode(R2_FSK_PIN, OUTPUT);
|
|
pinMode(R2_PTT_PIN, OUTPUT);
|
|
|
|
eeLoad();
|
|
displayConfiguration();
|
|
|
|
// start the half-bit timer.
|
|
initTimer();
|
|
|
|
Serial.write("\ncmd:\n"); // Tell N1MM we are in "RX" mode. This will be sent
|
|
// at the end of transmission.
|
|
} // end of SETUP() ===============================================================================
|
|
|
|
/**
|
|
* Main loop. This loop does two things:
|
|
* (1) Process any input from the serial connection one byte at a time.
|
|
*
|
|
* (2) If the half-bit timer interrupt fired we need to execute the bit-banging
|
|
* routine to keep clocking out RTTY.
|
|
*/
|
|
void TinyFSKloop() { // =================================================================================
|
|
|
|
// (1) Now read *one* byte from serial port if anything is there.
|
|
// We only read one byte so as not to bog down the processor if
|
|
// hundreds of bytes arrive all at once. If there is more to read
|
|
// it will be picked up once each time through the loop.
|
|
if (Serial.available() > 0) {
|
|
// get incoming byte:
|
|
byte oneByte = Serial.read();
|
|
|
|
// if we are in configuration mode, this byte is likely intended to change
|
|
// a configuration setting.
|
|
if (configurationMode) {
|
|
handleConfigurationCommand(oneByte);
|
|
} else //not in configuration mode
|
|
{
|
|
// check for TX abort character. This immediately kills the
|
|
// transmitter and dumps anything remaining in the buffer.
|
|
if (oneByte == TX_ABORT) {
|
|
setPTT(false);
|
|
resetSendBuffer();
|
|
endWhenBufferEmpty = true;
|
|
return;
|
|
} else if (oneByte == TX_ON) {
|
|
// The set PTT method has a delay
|
|
// in it so that there is a chance for another character to
|
|
// arrive in the input buffer. We return immediately here
|
|
// so that we can pick it up at the top of this loop. If we
|
|
// didn't do this, we would like continue on, see the buffer
|
|
//is empty, and transmit a diddle before the first real character.
|
|
endWhenBufferEmpty = false;
|
|
setPTT(true);
|
|
return; //return to beginning of loop to pick up first char if any
|
|
} else if (oneByte == TX_END) {
|
|
endWhenBufferEmpty = true;
|
|
} else if (oneByte == COMMAND_ESCAPE) {
|
|
configurationMode = true;
|
|
displayConfigurationPrompt();
|
|
} else // character to TX, so add to send buffer
|
|
{
|
|
addToSendBuffer(oneByte);
|
|
}
|
|
}
|
|
}
|
|
|
|
// (2) if the ISR fired we need may need to bit-bang something out the the FSK port
|
|
if (isRxFlag) {
|
|
processHalfBit();
|
|
isRxFlag = false;
|
|
}
|
|
} // end of program LOOP() =======================================================================
|