2019-12-18 07:32:44 +01:00
# include <Arduino.h>
# include "nano_gui.h"
2020-04-22 06:10:24 +02:00
# include "scratch_space.h"
# include "settings.h"
# include "tuner.h"
2019-12-18 07:32:44 +01:00
/**
* The CAT protocol is used by many radios to provide remote control to comptuers through
* the serial port .
2020-05-04 00:34:13 +02:00
*
2019-12-18 07:32:44 +01:00
* This is very much a work in progress . Parts of this code have been liberally
* borrowed from other GPLicensed works like hamlib .
2020-05-04 00:34:13 +02:00
*
* WARNING : This is an unstable version and it has worked with fldigi ,
* it gives time out error with WSJTX 1.8 .0
2019-12-18 07:32:44 +01:00
*/
2020-05-04 00:32:06 +02:00
static const uint8_t FT817_MESSAGE_SIZE = 5 ;
2020-05-03 23:46:42 +02:00
2020-05-04 09:58:49 +02:00
static const uint8_t ACK = 0x00 ;
static const uint8_t RACK = 0xF0 ; //Re-Acknowledge sent when the state requests is already active
2020-05-04 00:32:06 +02:00
//Data is ordered parameters 1-4, then command code last
2020-05-03 23:46:42 +02:00
enum CatDataIndex_e : uint8_t {
P1 = 0 ,
P2 = 1 ,
P3 = 2 ,
P4 = 3 ,
CMD = 4
} ;
2020-05-04 01:36:27 +02:00
enum Ft817Command_e : uint8_t {
//Listed in the order presented by FT-817ND_OM_ENG_E13771011.pdf
OffBit = 0x80 ,
LockOn = 0x00 ,
LockOff = LockOn | OffBit ,
PttOn = 0x08 ,
PttOff = PttOn | OffBit ,
SetFrequency = 0x01 , //P1-P4 are BCD, 0x01 0x42 0x34 0x56 = 14.23456MHz
OperatingMode = 0x07 , //See OperatingMode_e for P1 decode
ClarOn = 0x05 ,
ClarOff = ClarOn | OffBit ,
ClarFrequency = 0xF5 , //P1 is sign/direction (0x00 = +, - otherwise), P3-P4 are BCD, 0x12 0x34 = 12.34kHz
VfoToggle = 0x81 ,
SplitOn = 0x02 ,
SplitOff = SplitOn | OffBit ,
RepeaterMode = 0x09 , //See RepeaterMode_e for P1 decode
RepeaterOffset = 0xF9 , //P1-P4 are BCD
CtcssDcsMode = 0x0A , //See CtcssDcsMode_e for P1 decode
CtcssTone = 0x0B , //P1-P2 are BCD, 0x08 0x85 = 88.5MHz
DcsTone = 0x0C , //P1-P2 are BCD, 0x00 0x23 = code 023
ReadRxStatus = 0xE7 , //Returns ReadRxStatus_t
ReadTxStatus = 0xF7 , //Returns ReadTxStatus_t
ReadFreqAndMode = 0x03 , //Returns current frequency (BCD, 4 bytes), then mode (OperatingMode_e)
PowerOn = 0x0F ,
PowerOff = PowerOn | OffBit ,
2020-05-04 08:54:38 +02:00
//Unofficial commands
ReadEeprom = 0xBB ,
2020-05-04 01:36:27 +02:00
} ;
enum OperatingMode_e : uint8_t {
LSB = 0x00 ,
USB = 0x01 ,
CW = 0x02 ,
2020-05-04 02:45:43 +02:00
CWR = 0x03 , //CW-reverse aka LSB CW
2020-05-04 01:36:27 +02:00
AM = 0x04 ,
FM = 0x08 ,
DIG = 0x0A ,
PKT = 0x0C ,
} ;
enum RepeaterMode_e : uint8_t {
ShiftMinus = 0x09 ,
ShiftPlus = 0x49 ,
Simplex = 0x89 ,
} ;
enum CtcssDcsMode_e : uint8_t {
DcsOn = 0x0A ,
CtcssOn = 0x2A ,
EncoderOn = 0x4A ,
Off = 0x8A ,
} ;
struct ReadRxStatus_t {
//Bitfields are not defined by the standard to be portable, which is unfortunate
2020-05-04 02:45:43 +02:00
uint8_t Smeter : 4 ; //0x00 = S0, 0x09 = S9, etc.
2020-05-04 01:36:27 +02:00
uint8_t Dummy : 1 ;
uint8_t DiscriminatorCenteringOff : 1 ;
uint8_t CodeUnmatched : 1 ;
uint8_t SquelchSuppressionActive : 1 ;
} ;
struct ReadTxStatus_t {
2020-05-04 02:45:43 +02:00
//Bitfields are not defined by the standard to be portable, which is unfortunate
2020-05-04 01:36:27 +02:00
uint8_t PowerOutputMeter : 4 ;
uint8_t Dummy : 1 ;
uint8_t SplitOff : 1 ;
uint8_t HighSwrDetected : 1 ;
uint8_t PttOff : 1 ;
} ;
2020-05-04 06:35:44 +02:00
//Values based on http://www.ka7oei.com/ft817_memmap.html
2020-05-04 09:43:38 +02:00
//hamlib likes to read addresses 0x0065 (read as 0x0064) and 0x007A, but including support for some others
2020-05-04 06:35:44 +02:00
enum Ft817Eeprom_e : uint16_t {
VfoAndBankSelect = 0x0055 ,
TuningModes = 0x0057 ,
KeyerStatus = 0x0058 ,
BandSelect = 0x0059 ,
BeepVolume = 0x005C ,
CwPitch = 0x005E ,
CwWeight = 0x005F ,
CwDelay = 0x0060 ,
SidetoneVolume = 0x0061 ,
CwSpeed = 0x0062 ,
VoxGain = 0x0063 ,
CatBaudRate = 0x0064 ,
SsbMicVolume = 0x0067 ,
AmMicVolume = 0x0068 ,
FmMicVolume = 0x0069 ,
TxPower = 0x0079 ,
AntennaSelectAndSplit = 0x007A ,
2020-05-04 08:02:10 +02:00
VfoAPhantomMode = 0x01E9 ,
2020-05-04 06:35:44 +02:00
} ;
2019-12-18 07:32:44 +01:00
//for broken protocol
2020-05-04 00:32:06 +02:00
static const uint16_t CAT_RECEIVE_TIMEOUT_MS = 500 ;
2019-12-18 07:32:44 +01:00
2020-05-03 23:48:11 +02:00
uint8_t setHighNibble ( uint8_t b , uint8_t v ) {
2019-12-18 07:32:44 +01:00
// Clear the high nibble
b & = 0x0f ;
// Set the high nibble
return b | ( ( v & 0x0f ) < < 4 ) ;
}
2020-05-03 23:48:11 +02:00
uint8_t setLowNibble ( uint8_t b , uint8_t v ) {
2019-12-18 07:32:44 +01:00
// Clear the low nibble
b & = 0xf0 ;
// Set the low nibble
return b | ( v & 0x0f ) ;
}
2020-05-03 23:48:11 +02:00
uint8_t getHighNibble ( uint8_t b ) {
2019-12-18 07:32:44 +01:00
return ( b > > 4 ) & 0x0f ;
}
2020-05-03 23:48:11 +02:00
uint8_t getLowNibble ( uint8_t b ) {
2019-12-18 07:32:44 +01:00
return b & 0x0f ;
}
// Takes a number and produces the requested number of decimal digits, staring
2020-05-04 00:34:13 +02:00
// from the least significant digit.
2019-12-18 07:32:44 +01:00
//
2020-05-03 23:48:11 +02:00
void getDecimalDigits ( unsigned long number , uint8_t * result , int digits ) {
2019-12-18 07:32:44 +01:00
for ( int i = 0 ; i < digits ; i + + ) {
// "Mask off" (in a decimal sense) the LSD and return it
result [ i ] = number % 10 ;
// "Shift right" (in a decimal sense)
number / = 10 ;
}
}
// Takes a frequency and writes it into the CAT command buffer in BCD form.
//
2020-05-03 23:48:11 +02:00
void writeFreq ( unsigned long freq , uint8_t * cmd ) {
2019-12-18 07:32:44 +01:00
// Convert the frequency to a set of decimal digits. We are taking 9 digits
// so that we can get up to 999 MHz. But the protocol doesn't care about the
// LSD (1's place), so we ignore that digit.
2020-05-03 23:48:11 +02:00
uint8_t digits [ 9 ] ;
2019-12-18 07:32:44 +01:00
getDecimalDigits ( freq , digits , 9 ) ;
2020-05-04 00:34:13 +02:00
// Start from the LSB and get each nibble
2020-05-04 00:36:37 +02:00
cmd [ P4 ] = setLowNibble ( cmd [ P4 ] , digits [ 1 ] ) ;
cmd [ P4 ] = setHighNibble ( cmd [ P4 ] , digits [ 2 ] ) ;
cmd [ P3 ] = setLowNibble ( cmd [ P3 ] , digits [ 3 ] ) ;
cmd [ P3 ] = setHighNibble ( cmd [ P3 ] , digits [ 4 ] ) ;
cmd [ P2 ] = setLowNibble ( cmd [ P2 ] , digits [ 5 ] ) ;
cmd [ P2 ] = setHighNibble ( cmd [ P2 ] , digits [ 6 ] ) ;
cmd [ P1 ] = setLowNibble ( cmd [ P1 ] , digits [ 7 ] ) ;
cmd [ P1 ] = setHighNibble ( cmd [ P1 ] , digits [ 8 ] ) ;
2019-12-18 07:32:44 +01:00
}
2020-05-03 23:48:11 +02:00
// This function takes a frquency that is encoded using 4 uint8_ts of BCD
2019-12-18 07:32:44 +01:00
// representation and turns it into an long measured in Hz.
//
// [12][34][56][78] = 123.45678? Mhz
//
2020-05-04 02:45:43 +02:00
uint32_t readFreq ( uint8_t * cmd ) {
2019-12-18 07:32:44 +01:00
// Pull off each of the digits
2020-04-27 09:17:41 +02:00
unsigned long ret = 0 ;
for ( uint8_t i = 0 ; i < 4 ; + + i ) {
const uint8_t d1 = getHighNibble ( cmd [ i ] ) ;
const uint8_t d0 = getLowNibble ( cmd [ i ] ) ;
ret * = 100 ;
ret + = 10 * d1 + d0 ;
}
return ret * 10 ;
2019-12-18 07:32:44 +01:00
}
2020-05-04 06:35:44 +02:00
void catGetEeprom ( const uint16_t read_address , uint8_t * response )
2019-12-18 07:32:44 +01:00
{
2020-05-04 06:35:44 +02:00
switch ( read_address )
2019-12-18 07:32:44 +01:00
{
2020-05-04 06:35:44 +02:00
case Ft817Eeprom_e : : VfoAndBankSelect :
2019-12-18 07:32:44 +01:00
//0 : VFO A/B 0 = VFO-A, 1 = VFO-B
//1 : MTQMB Select 0 = (Not MTQMB), 1 = MTQMB ("Memory Tune Quick Memory Bank")
//2 : QMB Select 0 = (Not QMB), 1 = QMB ("Quick Memory Bank")
//3 :
//4 : Home Select 0 = (Not HOME), 1 = HOME memory
//5 : Memory/MTUNE select 0 = Memory, 1 = MTUNE
//6 :
//7 : MEM/VFO Select 0 = Memory, 1 = VFO (A or B - see bit 0)
2020-05-04 06:35:44 +02:00
* response = 0x80 //always report VFO mode
| ( ( VFO_B = = globalSettings . activeVfo ) ? 0x01 : 0x00 ) ;
2019-12-18 07:32:44 +01:00
break ;
2020-05-04 06:35:44 +02:00
case Ft817Eeprom_e : : CwPitch :
2019-12-18 07:32:44 +01:00
//3-0 : CW Pitch (300-1000 Hz) (#20) From 0 to E (HEX) with 0 = 300 Hz and each step representing 50 Hz
//5-4 : Lock Mode (#32) 00 = Dial, 01 = Freq, 10 = Panel
//7-6 : Op Filter (#38) 00 = Off, 01 = SSB, 10 = CW
2020-05-04 06:35:44 +02:00
* response = ( globalSettings . cwSideToneFreq - 300 ) / 50 ;
2019-12-18 07:32:44 +01:00
break ;
2020-05-04 06:35:44 +02:00
case Ft817Eeprom_e : : SidetoneVolume :
2020-05-04 08:02:10 +02:00
//Sidetone (Volume) (#44) 0-100
* response = globalSettings . cwSideToneFreq / 100 ;
2019-12-18 07:32:44 +01:00
break ;
2020-05-04 06:35:44 +02:00
case Ft817Eeprom_e : : CwDelay :
//CW Delay (10-2500 ms) (#17) From 1 to 250 (decimal) with each step representing 10 ms
* response = globalSettings . cwActiveTimeoutMs / 10 ;
2019-12-18 07:32:44 +01:00
break ;
2020-05-04 06:35:44 +02:00
case Ft817Eeprom_e : : CwSpeed :
2019-12-18 07:32:44 +01:00
//5-0 CW Speed (4-60 WPM) (#21) From 0 to 38 (HEX) with 0 = 4 WPM and 38 = 60 WPM (1 WPM steps)
//7-6 Batt-Chg (6/8/10 Hours (#11) 00 = 6 Hours, 01 = 8 Hours, 10 = 10 Hours
2020-05-04 06:35:44 +02:00
* response = ( 1200 / globalSettings . cwDitDurationMs ) - 4 ;
2019-12-18 07:32:44 +01:00
break ;
2020-05-04 06:35:44 +02:00
case Ft817Eeprom_e : : CatBaudRate :
//4-0 : VOX Delay (#50) 0 = 100 Ms with each step representing 100 Ms. 24 = 2500 Ms
//5 : Emergency (#28) 0 = Off, 1 = On
//7-6 : CAT Rate (4800, 9600, 38400) (#14) 00 = 4800, 01 = 9600, 10 = 38400 Baud
* response = 0xA5 ;
2019-12-18 07:32:44 +01:00
break ;
2020-05-04 08:02:10 +02:00
case Ft817Eeprom_e : : VfoAPhantomMode :
//2-0 : 000 = LSB, 001 = USB, 010 = CW, 011 = CWR, 100 = AM, 101 = FM, 110 = DIG, 111 = PKT
//7-3 : ?
2020-05-04 06:35:44 +02:00
if ( VfoMode_e : : VFO_MODE_USB = = GetActiveVfoMode ( ) ) {
2020-05-04 08:02:10 +02:00
* response = OperatingMode_e : : USB ;
2020-05-04 06:35:44 +02:00
}
else {
2020-05-04 08:02:10 +02:00
* response = OperatingMode_e : : LSB ;
2020-05-04 06:35:44 +02:00
}
break ;
case Ft817Eeprom_e : : AntennaSelectAndSplit :
//0 : HF Antenna Select 0 = Front, 1 = Rear
//1 : 6 M Antenna Select 0 = Front, 1 = Rear
//2 : FM BCB Antenna Select 0 = Front, 1 = Rear
//3 : Air Antenna Select 0 = Front, 1 = Rear
//4 : 2 M Antenna Select 0 = Front, 1 = Rear
//5 : UHF Antenna Select 0 = Front, 1 = Rear
//6 : ? ?
//7 : SPL On/Off 0 = Off, 1 = On
* response = ( globalSettings . splitOn ? 0xFF : 0x7F ) ;
2019-12-18 07:32:44 +01:00
break ;
}
2020-05-04 06:35:44 +02:00
}
//Maps some of the fixed memory layout of the FT817's EEPROM
void catReadEEPRom ( uint8_t * cmd , uint8_t * response )
{
const uint16_t read_address = cmd [ P1 ] < < 8 | cmd [ P2 ] ;
2019-12-18 07:32:44 +01:00
2020-05-04 06:35:44 +02:00
catGetEeprom ( read_address , response ) ;
catGetEeprom ( read_address + 1 , response + 1 ) ;
2019-12-18 07:32:44 +01:00
}
2020-05-04 00:34:13 +02:00
void processCatCommand ( uint8_t * cmd ) {
2020-05-04 09:58:49 +02:00
//A response of a single byte, 0x00, is an ACK, so default to that
uint8_t response [ FT817_MESSAGE_SIZE ] = { ACK } ;
uint8_t response_length = 1 ;
2020-05-04 00:34:13 +02:00
2020-05-04 00:36:37 +02:00
switch ( cmd [ CMD ] ) {
2020-05-04 09:34:15 +02:00
case Ft817Command_e : : SetFrequency :
{
uint32_t f = readFreq ( cmd ) ;
setFrequency ( f ) ;
break ;
2020-05-04 02:45:43 +02:00
}
2020-05-04 00:34:13 +02:00
2020-05-04 09:34:15 +02:00
case Ft817Command_e : : SplitOn :
2020-05-04 09:58:49 +02:00
if ( globalSettings . splitOn ) {
response [ 0 ] = RACK ;
}
2020-05-04 09:34:15 +02:00
globalSettings . splitOn = true ;
break ;
case Ft817Command_e : : SplitOff :
2020-05-04 09:58:49 +02:00
if ( ! globalSettings . splitOn ) {
response [ 0 ] = RACK ;
}
2020-05-04 09:34:15 +02:00
globalSettings . splitOn = false ;
break ;
2020-05-04 02:45:43 +02:00
2020-05-04 09:34:15 +02:00
case Ft817Command_e : : ReadFreqAndMode :
//First 4 bytes are the frequency
writeFreq ( GetActiveVfoFreq ( ) , response ) ; //bytes 0-3
//Last byte is the mode
if ( VfoMode_e : : VFO_MODE_USB = = GetActiveVfoMode ( ) ) {
response [ 4 ] = OperatingMode_e : : USB ;
}
else {
response [ 4 ] = OperatingMode_e : : LSB ;
}
2020-05-04 09:58:49 +02:00
response_length = 5 ;
2020-05-04 09:34:15 +02:00
break ;
2020-05-04 00:34:13 +02:00
2020-05-04 09:34:15 +02:00
case Ft817Command_e : : OperatingMode :
if ( OperatingMode_e : : LSB = = cmd [ P1 ] | | OperatingMode_e : : CWR = = cmd [ P1 ] ) {
SetActiveVfoMode ( VfoMode_e : : VFO_MODE_LSB ) ;
}
else {
SetActiveVfoMode ( VfoMode_e : : VFO_MODE_USB ) ;
}
2019-12-18 07:32:44 +01:00
2020-05-04 09:34:15 +02:00
setFrequency ( GetActiveVfoFreq ( ) ) ; //Refresh frequency to get new mode to take effect
break ;
case Ft817Command_e : : PttOn :
if ( ! globalSettings . txActive ) {
globalSettings . txCatActive = true ;
startTx ( globalSettings . tuningMode ) ;
}
else {
2020-05-04 09:58:49 +02:00
response [ 0 ] = RACK ;
2020-05-04 09:34:15 +02:00
}
break ;
case Ft817Command_e : : PttOff :
if ( globalSettings . txActive ) {
stopTx ( ) ;
}
2020-05-04 09:58:49 +02:00
else {
response [ 0 ] = RACK ;
}
2020-05-04 09:34:15 +02:00
globalSettings . txCatActive = false ;
break ;
case Ft817Command_e : : VfoToggle :
if ( Vfo_e : : VFO_A = = globalSettings . activeVfo ) {
globalSettings . activeVfo = Vfo_e : : VFO_B ;
}
else {
globalSettings . activeVfo = Vfo_e : : VFO_A ;
}
break ;
case Ft817Command_e : : ReadEeprom :
catReadEEPRom ( cmd , response ) ;
response_length = 2 ;
break ;
case Ft817Command_e : : ReadRxStatus :
//We don't have visibility into these values, so just hard code stuff
ReadRxStatus_t reply_status ;
reply_status . Dummy = 0 ;
reply_status . Smeter = 9 ; //S9
reply_status . SquelchSuppressionActive = 0 ;
reply_status . DiscriminatorCenteringOff = 1 ;
reply_status . CodeUnmatched = 0 ;
response [ 0 ] = * ( uint8_t * ) & reply_status ;
break ;
case Ft817Command_e : : ReadTxStatus :
{
//We don't have visibility into some of these values, so just hard code stuff
ReadTxStatus_t reply_status ;
reply_status . Dummy = 0 ;
reply_status . HighSwrDetected = 0 ;
reply_status . PowerOutputMeter = 0xF ;
reply_status . PttOff = ! globalSettings . txActive ;
reply_status . SplitOff = globalSettings . splitOn ; //Yaesu's documentation says that 1 = split off, but as of 2020-05-04 hamlib reads (*split = (p->tx_status & 0x20) ? RIG_SPLIT_ON : RIG_SPLIT_OFF), so do what hamlib wants
response [ 0 ] = * ( uint8_t * ) & reply_status ;
break ;
2020-04-22 06:10:24 +02:00
}
2020-05-04 00:34:13 +02:00
2020-05-04 09:34:15 +02:00
default :
2020-05-04 09:58:49 +02:00
//Do something?
2020-05-04 09:34:15 +02:00
break ;
2019-12-18 07:32:44 +01:00
}
2020-05-04 02:55:23 +02:00
Serial . write ( response , response_length ) ;
2019-12-18 07:32:44 +01:00
}
void checkCAT ( ) {
2020-05-04 00:32:06 +02:00
static uint8_t rx_buffer [ FT817_MESSAGE_SIZE ] ;
static uint8_t current_index = 0 ;
static uint32_t timeout = 0 ;
2020-05-04 09:34:15 +02:00
2019-12-18 07:32:44 +01:00
//Check Serial Port Buffer
if ( Serial . available ( ) = = 0 ) { //Set Buffer Clear status
2020-05-04 00:32:06 +02:00
if ( timeout < millis ( ) ) {
current_index = 0 ;
timeout = 0 ;
}
2019-12-18 07:32:44 +01:00
return ;
}
2020-05-04 00:32:06 +02:00
else {
if ( 0 = = current_index ) {
timeout = millis ( ) + CAT_RECEIVE_TIMEOUT_MS ;
2019-12-18 07:32:44 +01:00
}
2020-05-04 00:32:06 +02:00
rx_buffer [ current_index ] = Serial . read ( ) ;
+ + current_index ;
if ( current_index < FT817_MESSAGE_SIZE ) {
return ;
2019-12-18 07:32:44 +01:00
}
2020-05-04 09:34:15 +02:00
}
2019-12-18 07:32:44 +01:00
2020-05-04 00:34:13 +02:00
processCatCommand ( rx_buffer ) ;
2020-05-04 00:32:06 +02:00
current_index = 0 ;
timeout = 0 ;
2019-12-18 07:32:44 +01:00
}