IC746CAT/IC746.cpp

707 wiersze
22 KiB
C++

/*************************************************************************
IC746 CAT Library, by KK4DAS, Dean Souleles
Inspired by: ft857d CAT Library, by Pavel Milanes, CO7WT, pavelmc@gmail.com
Emulates an ICOM 746 CAT functionality to work with all ham radio software that include CAT control
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
***************************************************************************/
#include "Arduino.h"
#include "IC746.h"
//#define DEBUG_CAT
#ifdef DEBUG_CAT
//#define DEBUG_CAT_DETAIL
//#define DEBUG_CAT_SMETER
#include <SoftwareSerial.h>
SoftwareSerial catDebug(6, 7); // RX, TX
String dbg;
#endif
// User supplied calback function work variables, must be static
static FuncPtrBoolean catSplit;
static FuncPtrBoolean catSetPtt;
static FuncPtrVoidBoolean catGetPtt;
static FuncPtrVoidLong catGetFreq;
static FuncPtrLong catSetFreq;
static FuncPtrVoidByte catGetMode;
static FuncPtrByte catSetMode;
static FuncPtrVoidByte catGetSmeter;
static FuncPtrByte catSetVFO;
static FuncPtrVoid catAtoB;
static FuncPtrVoid catSwapVfo;
// Command indices
//
// Command structure after preamble and EOM have been discarded
// |56|E0|cmd|sub-cmd|data
// 56 - fixed transceiver address for IC746
// E0 - fixed cat xontroller address
// The sub-command field varies by command. For somme commands the sub-cmd field is not supplied and the data
// begins immediatedly follwing the command.
//
// The following are the array indedes within the command buffer for command elements that are sent with the command
// or are where we put data to return to the conroller
//
#define CAT_IX_XCVR_ADDR 0
#define CAT_IX_CTRL_ADDR 1
#define CAT_IX_CMD 2
#define CAT_IX_SUB_CMD 3
#define CAT_IX_FREQ 3 // Set Freq has no sub-command
#define CAT_IX_MODE 3 // Get mode has no sub-command
#define CAT_IX_PTT 4 // PTT RX/TX indicator
#define CAT_IX_IF_FILTER 4 // IF Filter value
#define CAT_IX_SMETER 4 // S Meter 0-255
#define CAT_IX_SQUELCH 4 // Squelch 0=close, 1= open
#define CAT_IX_ID 5
#define CAT_IX_DATA 4 // Data following sub-comand
/*
Contructor, simple constructor, it initiates the serial port in the
default mode for the radio: 9600 @ 8N2
*/
void IC746::begin() {
Serial.begin(9600, SERIAL_8N2);
Serial.flush();
#ifdef DEBUG_CAT
catDebug.begin(9600);
dbg = "CAT Debug Ready:";
catDebug.println(dbg.c_str());
#endif
}
// Alternative initializer with a custom baudrate and mode
void IC746::begin(long br, int mode) {
/*
Allowed Arduino modes for the serial:
SERIAL_5N1; SERIAL_6N1; SERIAL_7N1; SERIAL_8N1; SERIAL_5N2; SERIAL_6N2;
SERIAL_7N2; SERIAL_8N2; SERIAL_5E1; SERIAL_6E1; SERIAL_7E1; SERIAL_8E1;
SERIAL_5E2; SERIAL_6E2; SERIAL_7E2; SERIAL_8E2; SERIAL_5O1; SERIAL_6O1;
SERIAL_7O1; SERIAL_8O1; SERIAL_5O2; SERIAL_6O2; SERIAL_7O2; SERIAL_8O2
*/
Serial.begin(br, mode);
Serial.flush();
#ifdef DEBUG_CAT
catDebug.begin(9600);
dbg = "CAT Debug Ready";
catDebug.println(dbg.c_str());
#endif
}
/*
Linking user supplied callback functions
*/
// PTT
void IC746::addCATPtt(void (*userFunc)(boolean)) {
catSetPtt = userFunc;
}
// Split
void IC746::addCATsplit(void (*userFunc)(boolean)) {
catSplit = userFunc;
}
// VFO A=B - set both VFOs to be the same as the active VFO
void IC746::addCATAtoB(void (*userFunc)(void)) {
catAtoB = userFunc;
}
// Swap Active VFO
void IC746::addCATSwapVfo(void (*userFunc)(void)) {
catSwapVfo = userFunc;
}
// Get the freq of operation, the function must return the freq
void IC746::addCATGetFreq(long (*userFunc)(void)) {
catGetFreq = userFunc;
}
// Get the mode of operation, the function must return the mode
void IC746::addCATGetMode(byte (*userFunc)(void)) {
catGetMode = userFunc;
}
// GetPTT - function must return true for Tx and false for Rx
void IC746::addCATGetPtt(boolean (*userFunc)(void)) {
catGetPtt = userFunc;
}
// S meter (user function must return 0-15)
void IC746::addCATSMeter(byte (*userFunc)(void)) {
catGetSmeter = userFunc;
}
// Set Frequency - user function must accept a long as the freq in hz
void IC746::addCATFSet(void (*userFunc)(long)) {
catSetFreq = userFunc;
}
// Set Mode - user function must interpret MODE per the constants in IC746.h
void IC746::addCATMSet(void (*userFunc)(byte)) {
catSetMode = userFunc;
}
// SEt VFOA or B - user function must interpret VFO per the constants in IC746.h
void IC746::addCATVSet(void (*userFunc)(byte)) {
catSetVFO = userFunc;
}
////////////////////////////////////////////////////////////////////////////////
// Protocol Message Handling
////////////////////////////////////////////////////////////////////////////////
//
// Send a message back to CAT controller
// Format PREAMBLE, PREAMBLE, MSG, EOM
//
void IC746::send(byte *buf, int len) {
int i;
Serial.write(CAT_PREAMBLE);
Serial.write(CAT_PREAMBLE);
for (i = 0; i < len; i++) {
Serial.write(buf[i]);
}
Serial.write(CAT_EOM);
}
//
// sendAck() - send back hard-code acknowledge message
//
void IC746::sendAck() {
byte ack[] = {CAT_CTRL_ADDR, CAT_RIG_ADDR, CAT_ACK};
send(ack, 3);
}
//
// sendNack() - send back hard-code negative-acknowledge message
//
void IC746::sendNack() {
byte nack[] = {CAT_CTRL_ADDR, CAT_RIG_ADDR, CAT_NACK};
send(nack, 3);
}
/*
readCMD - state machine to receive a command from the controller
States:
CAT_RCV_WAITING - scan incoming serial data for first preamble byte
CAT_RCV_INIT - second premable byte confirms start of message
CAT_RCV_RECEIVING - fill command buffer until EOM received
Command format
|FE|FE|56|E0|cmd|sub-cmd|data|FD|
FE FE = preamble, FD = end of command
56 = transceiver default address for IC746 (unused)
E0 = CAT controller default address (unused)
Upon successful receipt of EOM, protocol requires echo back of enitre message
On interrupted preamble or buffer overflow (no EOM received), send NAK
On successful receipt of a command the global array cmdBuf
will have the received CAT command (without the preamble and EOM)
*/
boolean IC746::readCmd() {
byte bt;
boolean cmdRcvd = false;
while (Serial.available() && !cmdRcvd) {
bt = byte(Serial.read());
switch (rcvState) {
case CAT_RCV_WAITING: // scan for start of new command
if (bt == CAT_PREAMBLE) {
rcvState = CAT_RCV_INIT;
}
break;
case CAT_RCV_INIT: // check for second preamble byte
if (bt == CAT_PREAMBLE) {
rcvState = CAT_RCV_RECEIVING;
} else { // error - should not happen, reset and report
rcvState = CAT_RCV_WAITING;
bytesRcvd = 0;
sendNack();
}
break;
case CAT_RCV_RECEIVING:
switch (bt) {
case CAT_EOM: // end of message received, return for processing, reset state
send(cmdBuf, bytesRcvd); // echo received packet for protocol
cmdRcvd = true;
rcvState = CAT_RCV_WAITING;
cmdLength = bytesRcvd;
bytesRcvd = 0;
break;
default: // fill command buffer
if (bytesRcvd <= CAT_CMD_BUF_LENGTH) {
cmdBuf[bytesRcvd] = bt;
bytesRcvd++;
} else { // overflow - should not happen reset for new comand
rcvState = CAT_RCV_WAITING;
bytesRcvd = 0;
sendNack(); // report error
}
break;
}
break;
}
}
return cmdRcvd;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//
// COMMAND PROCESSORS
//
///////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doSmeter() - process the CAT_READ_SMETER Command
// This command has two sub-commands, SMETER and SQUELCH, only SMETER is fully implemented
//
// The SMETER sub command requests the current Return S-meter readingscaled for IC 746 appropriate values
// It calls a user function returns 0-15 S0-9, +10, +20, +30, +40, +50 +60
// The mapping of s-units to IC746 equivalents done imperically using the CatBkt cat test program
//
// The second sub-command, SQUELCH request wheterh Squelch is open or closed. Return a fixed value
// of "Open" to keep the protocol happly
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doSmeter() {
switch (cmdBuf[CAT_IX_SUB_CMD]) {
case CAT_READ_SUB_SMETER:
if (catGetSmeter) {
const byte smap[] = {0, 20, 50, 70, 90, 110, 140, 150, 170, 180, 197, 201, 211, 216, 220, 230};
byte s;
s = catGetSmeter();
cmdBuf[CAT_IX_SMETER] = smap[s];
#ifdef DEBUG_CAT_SMETER
dbg = "doSmeter- s: ";
dbg += String(s);
dbg += " map: ";
dbg += String(smap[s]);
catDebug.println(dbg.c_str());
#endif
} else {
cmdBuf[CAT_IX_SMETER] = 0;
}
send(cmdBuf, 5);
break;
case CAT_READ_SUB_SQL: // Squelch condition 0=closed, 1=open
cmdBuf[CAT_IX_SQUELCH] = 1;
send(cmdBuf, 5);
break;
}
}
/*
BCD Version
void IC746::doSmeter() {
switch (cmdBuf[CAT_IX_SUB_CMD]) {
case CAT_READ_SUB_SMETER:
if (catGetSmeter) {
const byte smap[] = {0, 20, 50, 70, 90, 110, 140, 150, 170, 180, 197, 201, 211, 216, 220, 230};
byte s = catGetSmeter();
SmetertoBCD(smap[s]);
#ifdef DEBUG_CAT_DETAIL
dbg = "doSmeter- s:";
dbg += String(s);
dbg += " map: ";
dbg += String(smap[s]);
dbg += " BCD: ";
for (int i = CAT_IX_SMETER; i < CAT_IX_SMETER+2; i++) {
dbg += String(cmdBuf[i], HEX);
dbg += " ";
}
catDebug.println(dbg.c_str());
#endif
} else {
cmdBuf[CAT_IX_SMETER] = 0; // user has not supplied S Meter function - keep the protocol happy
cmdBuf[CAT_IX_SMETER + 1] = 0;
}
send(cmdBuf, 6);
break;
case CAT_READ_SUB_SQL: // Squelch condition 0=closed, 1=open
cmdBuf[CAT_IX_SQUELCH] = 1;
send(cmdBuf, 5);
break;
}
}
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doPtt() - process the CAT_PTT Command
// CAT_PTT calls the user supplied functions to either put the rig in to Tx or Rx or to request the rigs
// current Tx/Rx state
//
// A "read ptt" request does not have a data byte and is therefore one byte shorter
// than a "set ptt" request.
// 56 | E0 | 1C | 00 - Read request 1C is PTT command, sub-command 0 is uused, no data byte
// 56 | E0 | 1C | 00 | 01 - Set Tx, trailing data bit 1 for Tx, 0 for Rx
////////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doPtt() {
if (cmdLength == 4) { // Read request
if (catGetPtt) {
cmdBuf[CAT_IX_PTT] = catGetPtt();
send(cmdBuf, 5);
}
} else { // Set request
if (catSetPtt) {
if (cmdBuf[CAT_IX_PTT] == CAT_PTT_TX) {
catSetPtt(true);
} else {
catSetPtt(false);
}
}
sendAck(); // always acknowledge "set" commands
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doSplit() - process the CAT_SPLIT Command
// Call user supplied function to turn split on or off
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doSplit() {
switch (cmdBuf[CAT_IX_SUB_CMD]) {
case CAT_SPLIT_OFF:
if (catSplit) {
catSplit(false);
}
break;
case CAT_SPLIT_ON:
if (catSplit) {
catSplit(true);
}
default:
break;
}
sendAck();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doSetVfo() - process the CAT_SET_VFO Command, calls user supplied functions
// SET_VFO with no sub-command selects VFO Tuning vice Memory Tuning (memory tuning is not implemented)
// SET_VFO has four sub-commands:
// VFOA or VFOB - directs the rig to make the selected VFO the active VFO
// VFO_A_TO_B - directs the rig to copy the make both VFO frequencies the same as the Active VFO
// VFO_SWAP - directs the rig to exchange VFOA and VFOB
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doSetVfo() {
if (cmdLength == 4) { // No sub-command - sets VFO Tuning vice memory tuning
sendAck(); // Memory tuning is not implemented so send ack to keep protocol happy
}
switch (cmdBuf[CAT_IX_SUB_CMD]) {
case CAT_VFO_A:
case CAT_VFO_B:
if (catSetVFO) {
catSetVFO(cmdBuf[CAT_IX_SUB_CMD]);
}
break;
case CAT_VFO_A_TO_B:
if (catAtoB) {
catAtoB();
}
break;
case CAT_VFO_SWAP:
if (catSwapVfo) {
catSwapVfo();
}
break;
}
sendAck();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doSetFreq() - proces CAT_SET_FREQ command
// Call the user supplied function to set the righ frequency
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doSetFreq() {
if (catSetFreq) {
catSetFreq(BCDtoFreq()); // Convert the frequency BCD to Long and call the user function
}
sendAck();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doReadFreq() - process the CAT_READ_FREQ command
// Call the user supplied function to read set the rig frequency
// Frequecies are sent and received in BCD - call the support functions to do the conversion
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doReadFreq() {
if (catGetFreq) {
FreqtoBCD(catGetFreq()); // get the frequency, convert to BCD and stuff it in the response buffer
send(cmdBuf, 8);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doSetMode() - process the CAT_SET_MODE command
// Call the user function to put the rig into the requested mode. Only USB or LSB are supported.
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doSetMode() {
if (catSetMode) {
switch (cmdBuf[CAT_IX_SUB_CMD]) {
case CAT_MODE_LSB:
case CAT_MODE_USB:
catSetMode(cmdBuf[CAT_IX_SUB_CMD]);
break;
}
}
sendAck();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doReadMode() - process the CAT_READ_MODE command
// Call the user function to query the rig's current mode
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doReadMode() {
if (catGetMode) {
cmdBuf[CAT_IX_MODE] = catGetMode();
send(cmdBuf, 4);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doMisc() - process the CAT_MISC command
//
// The MISC command has several unrelated sub-commands
// The only one implemented here is the sub-command to read the IF Filter Setting
// The code sends a hard-coded response since most homebrew rigs won't have such a setting
// but programs like WSJTX and FLDIGI request it
//
// The value of 29 was determined by reviewing the IC-7600 CI-V documentation is more complete than the IC-746 doc.
// The value of 29 equates to 2.5KHz which should be a reasonable value.
//
// Commands that "set" values are replied to with an ACK message
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doMisc() {
switch (cmdBuf[CAT_IX_SUB_CMD]) {
case CAT_READ_IF_FILTER:
// cmdBuf[CAT_IX_IF_FILTER] = 29; // Return a valid value - 29 represents 2.5K filter for SSB
cmdBuf[CAT_IX_IF_FILTER] = 0;
send(cmdBuf, 5);
break;
// Not implemented
// Reply with ACK to keep the protocol happy
case CAT_SET_MEM_CHAN:
case CAT_SET_BANDSTACK:
case CAT_SET_MEM_KEYER:
sendAck();
break;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// doUnimplemented() - reasonable processing for features that are not fully implemented
//
// Unimplimented command handling
//
// These following commands are used to both set and read various parameters in the IC-746 that are not
// typically implemented in a homebrew transceiver.
// Commands requesting the state of various parameters require a data byte returned.
// We return zero in all cases which typically means the requsted feature is OFF - eg AGC, NB, VOX, etc.
// Command that "set" various parameters only require an ACK reply
// A "read" request has no data byte and is one byte shorter than a set request
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::doUnimplemented() {
if (cmdLength == 4) { // Read request
cmdBuf[CAT_IX_PTT] = 0; // return 0 for all read requests
send(cmdBuf, 5);
} else { // Set parameter request
sendAck(); // Send an acknowledgement to keep the protocol happy
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// check() - process commands from CAT controller, should be called from the sketch main loop
///////////////////////////////////////////////////////////////////////////////////////////////////////
void IC746::check() {
// do nothing if it was disabled by software
if (!enabled) return;
// Receive a CAT Command
if (!readCmd()) return;
#ifdef DEBUG_CAT_DETAIL
dbg = "rcvd: ";
dbg += String(cmdLength);
dbg += ": ";
for (int i = 0; i < cmdLength; i++) {
dbg += String(cmdBuf[i], HEX);
dbg += " ";
}
catDebug.println(dbg.c_str());
#endif
// Process the command - command opcode is at CAT_IX_CMD location in command buffer
switch (cmdBuf[CAT_IX_CMD]) {
case CAT_PTT:
doPtt();
break;
case CAT_SPLIT:
doSplit();
break;
case CAT_SET_VFO:
doSetVfo();
break;
case CAT_SET_FREQ:
doSetFreq();
break;
case CAT_SET_MODE:
doSetMode();
break;
case CAT_READ_MODE:
doReadMode();
break;
case CAT_READ_FREQ:
doReadFreq();
break;
case CAT_READ_SMETER:
doSmeter();
break;
case CAT_MISC:
doMisc();
break;
case CAT_READ_ID:
cmdBuf[CAT_IX_ID] = CAT_RIG_ADDR; // Send back the transmitter ID
send(cmdBuf, 5);
break;
// Unimplemented commands that request data from the rig - keep the protocol happy
case CAT_ATT:
case CAT_SET_PARAMS2:
case CAT_READ_OFFSET:
doUnimplemented();
break;
default: // For all other commands respond with an ACK
#ifdef DEBUG_CAT
dbg = "unimp cmd: ";
dbg += String(cmdBuf[CAT_IX_CMD], HEX);
catDebug.println(dbg.c_str());
#endif
sendAck();
break;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//
// UTILITY FUNCTIONS
//
///////////////////////////////////////////////////////////////////////////////////////////////////////
//
// BCD FrequencyConversion Routines
// Convert BCD frequency to/from command buffer to/from long
// Format (beginning at first data byte in buffer is
// Byte 0 10Hz | 1Hz
// Byte 1 1KHz | 100Hz
// Byte 2 100KHz | 10KHz
// Byte 3 10MHz | 1MHz
// Example: 7,123,456 is encoded 56 | 34 | 12 | 07
//
long IC746::BCDtoFreq() {
long freq;
freq = cmdBuf[CAT_IX_FREQ] & 0xf; // lower 4 bits
freq += 10L * (cmdBuf[CAT_IX_FREQ] >> 4); // upper 4 bits
freq += 100L * (cmdBuf[CAT_IX_FREQ + 1] & 0xf);
freq += 1000L * (cmdBuf[CAT_IX_FREQ + 1] >> 4);
freq += 10000L * (cmdBuf[CAT_IX_FREQ + 2] & 0xf);
freq += 100000L * (cmdBuf[CAT_IX_FREQ + 2] >> 4);
freq += 1000000L * (cmdBuf[CAT_IX_FREQ + 3] & 0xf);
freq += 10000000L * (cmdBuf[CAT_IX_FREQ + 3] >> 4);
return freq;
}
void IC746::FreqtoBCD(long freq) {
byte ones, tens, hund, thou, ten_thou, hund_thou, mil, ten_mil;
ones = byte(freq % 10);
tens = byte((freq / 10L) % 10);
cmdBuf[CAT_IX_FREQ] = byte((tens << 4)) | ones;
hund = byte((freq / 100L) % 10);
thou = byte((freq / 1000L) % 10);
cmdBuf[CAT_IX_FREQ + 1] = byte((thou << 4)) | hund;
ten_thou = byte((freq / 10000L) % 10);
hund_thou = byte((freq / 100000L) % 10);
cmdBuf[CAT_IX_FREQ + 2] = byte((hund_thou << 4)) | ten_thou;
mil = byte((freq / 1000000L) % 10);
ten_mil = byte(freq / 10000000L);
cmdBuf[CAT_IX_FREQ + 3] = byte((ten_mil << 4)) | mil;
cmdBuf[CAT_IX_FREQ + 4] = 0; // fixed
}
void IC746::SmetertoBCD(byte s) {
byte ones, tens, hund;
ones = byte(s % 10);
tens = byte((s / byte(10)) % 10);
cmdBuf[CAT_IX_SMETER] = byte((tens << 4)) | ones;
hund = byte((s / byte(100)) % 10);
cmdBuf[CAT_IX_SMETER + 1] = hund;
}