/* * Hamlib Kenwood backend - Elecraft K2 description * Copyright (c) 2002-2009 by Stephane Fillod * Copyright (c) 2010 by Nate Bargmann, n0nb@arrl.net * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * See the file 'COPYING.LIB' in the main Hamlib distribution directory for * the complete text of the GNU Lesser Public License version 2.1. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "kenwood.h" #include "elecraft.h" #define K2_MODES (RIG_MODE_CW|RIG_MODE_CWR|RIG_MODE_SSB|RIG_MODE_PKTLSB|RIG_MODE_PKTUSB) #define K2_FUNC_ALL (RIG_FUNC_NB|RIG_FUNC_LOCK) #define K2_LEVEL_ALL (RIG_LEVEL_ATT|RIG_LEVEL_PREAMP|RIG_LEVEL_AGC|RIG_LEVEL_SQL|\ RIG_LEVEL_STRENGTH|RIG_LEVEL_RFPOWER|RIG_LEVEL_KEYSPD) #define K2_VFO (RIG_VFO_A|RIG_VFO_B) #define K2_VFO_OP (RIG_OP_UP|RIG_OP_DOWN) #define K2_ANTS (RIG_ANT_1|RIG_ANT_2) static rmode_t k2_mode_table[KENWOOD_MODE_TABLE_MAX] = { [0] = RIG_MODE_NONE, [1] = RIG_MODE_LSB, [2] = RIG_MODE_USB, [3] = RIG_MODE_CW, [4] = RIG_MODE_NONE, [5] = RIG_MODE_NONE, [6] = RIG_MODE_PKTLSB, /* AFSK */ [7] = RIG_MODE_CWR, [8] = RIG_MODE_NONE, /* TUNE mode */ [9] = RIG_MODE_PKTUSB /* AFSK */ }; /* kenwood_transaction() will add this to command strings * sent to the rig and remove it from strings returned from * the rig, so no need to append ';' manually to command strings. */ static struct kenwood_priv_caps k2_priv_caps = { .cmdtrm = EOM_KEN, .mode_table = k2_mode_table, }; /* K2 Filter list, four per mode */ struct k2_filt_s { shortfreq_t width; /* Filter width in Hz */ char fslot; /* Crystal filter slot number--1-4 */ char afslot; /* AF filter slot number--0-2 */ }; /* Number of filter slot arrays to allocate (TNX Diane, VA3DB) */ #define K2_FILT_NUM 4 /* K2 Filter List * * This struct will be populated as modes are queried or in response * to a request to set a given mode. This way a cache can be maintained * of the installed filters and an appropriate filter can be selected * for a requested bandwidth. Each mode has up to four filter slots available. */ struct k2_filt_lst_s { struct k2_filt_s filt_list[K2_FILT_NUM]; }; struct k2_filt_lst_s k2_fwmd_ssb; struct k2_filt_lst_s k2_fwmd_cw; struct k2_filt_lst_s k2_fwmd_rtty; /* K2 specific rig_caps API function declarations */ int k2_open(RIG *rig); int k2_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width); int k2_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width); int k2_get_ext_level(RIG *rig, vfo_t vfo, token_t token, value_t *val); /* Private function declarations */ int k2_probe_mdfw(RIG *rig, struct kenwood_priv_data *priv); int k2_mdfw_rest(RIG *rig, const char *mode, const char *fw); int k2_pop_fw_lst(RIG *rig, const char *cmd); /* * KIO2 rig capabilities. * This kit can recognize a large subset of TS-570 commands. * * Part of info comes from http://www.elecraft.com/K2_Manual_Download_Page.htm#K2 * look for KIO2 Programmer's Reference PDF */ const struct rig_caps k2_caps = { RIG_MODEL(RIG_MODEL_K2), .model_name = "K2", .mfg_name = "Elecraft", .version = "20200107", .copyright = "LGPL", .status = RIG_STATUS_BETA, .rig_type = RIG_TYPE_TRANSCEIVER, .ptt_type = RIG_PTT_RIG, .dcd_type = RIG_DCD_RIG, .port_type = RIG_PORT_SERIAL, .serial_rate_min = 4800, .serial_rate_max = 4800, .serial_data_bits = 8, .serial_stop_bits = 2, .serial_parity = RIG_PARITY_NONE, .serial_handshake = RIG_HANDSHAKE_NONE, .write_delay = 0, /* Timing between bytes */ .post_write_delay = 100, /* Timing between command strings */ // Note that 2000 timeout exceeds usleep but hl_usleep handles it .timeout = 2000, /* FA and FB make take up to 500 ms on band change */ .retry = 10, .has_get_func = K2_FUNC_ALL, .has_set_func = K2_FUNC_ALL, .has_get_level = K2_LEVEL_ALL, .has_set_level = RIG_LEVEL_SET(K2_LEVEL_ALL), .has_get_parm = RIG_PARM_NONE, .has_set_parm = RIG_PARM_NONE, /* FIXME: parms */ .level_gran = {}, /* FIXME: granularity */ .parm_gran = {}, .extlevels = elecraft_ext_levels, .extparms = kenwood_cfg_params, .preamp = { 14, RIG_DBLST_END, }, .attenuator = { 10, RIG_DBLST_END, }, .max_rit = Hz(9990), .max_xit = Hz(9990), .max_ifshift = Hz(0), .vfo_ops = K2_VFO_OP, .targetable_vfo = RIG_TARGETABLE_FREQ, .transceive = RIG_TRN_RIG, .bank_qty = 0, .chan_desc_sz = 0, .chan_list = { RIG_CHAN_END }, .rx_range_list1 = { {kHz(500), MHz(30), K2_MODES, -1, -1, K2_VFO, K2_ANTS}, RIG_FRNG_END, }, /* rx range */ .tx_range_list1 = { {kHz(1810), kHz(1850) - 1, K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, /* 15W class */ {kHz(3500), kHz(3800) - 1, K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {MHz(7), kHz(7100), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {kHz(10100), kHz(10150), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {MHz(14), kHz(14350), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {kHz(18068), kHz(18168), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {MHz(21), kHz(21450), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {kHz(24890), kHz(24990), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {MHz(28), kHz(29700), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, RIG_FRNG_END, }, /* tx range */ .rx_range_list2 = { {kHz(500), MHz(30), K2_MODES, -1, -1, K2_VFO, K2_ANTS}, RIG_FRNG_END, }, /* rx range */ .tx_range_list2 = { {kHz(1800), MHz(2) - 1, K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, /* 15W class */ {kHz(3500), MHz(4) - 1, K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {MHz(7), kHz(7300), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {kHz(10100), kHz(10150), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {MHz(14), kHz(14350), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {kHz(18068), kHz(18168), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {MHz(21), kHz(21450), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {kHz(24890), kHz(24990), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, {MHz(28), kHz(29700), K2_MODES, 10, W(15), K2_VFO, K2_ANTS}, RIG_FRNG_END, }, /* tx range */ .tuning_steps = { {K2_MODES, 10}, RIG_TS_END, }, /* mode/filter list, remember: order matters! */ .filters = { {RIG_MODE_SSB, kHz(2.5)}, {RIG_MODE_CW | RIG_MODE_CWR, Hz(500)}, {RIG_MODE_PKTLSB | RIG_MODE_PKTUSB, kHz(2.5)}, RIG_FLT_END, }, .priv = (void *)& k2_priv_caps, .rig_init = kenwood_init, .rig_cleanup = kenwood_cleanup, .rig_open = k2_open, .rig_close = kenwood_close, .set_freq = kenwood_set_freq, .get_freq = kenwood_get_freq, .set_mode = k2_set_mode, .get_mode = k2_get_mode, .set_vfo = kenwood_set_vfo, .get_vfo = kenwood_get_vfo_if, .set_split_vfo = kenwood_set_split_vfo, .get_split_vfo = kenwood_get_split_vfo_if, .set_rit = kenwood_set_rit, .get_rit = kenwood_get_rit, .set_xit = kenwood_set_xit, .get_xit = kenwood_get_xit, .get_ptt = kenwood_get_ptt, .set_ptt = kenwood_set_ptt, .get_dcd = kenwood_get_dcd, .set_func = kenwood_set_func, .get_func = kenwood_get_func, .set_ext_parm = kenwood_set_ext_parm, .get_ext_parm = kenwood_get_ext_parm, .set_level = kenwood_set_level, .get_level = kenwood_get_level, .get_ext_level = k2_get_ext_level, .vfo_op = kenwood_vfo_op, .set_trn = kenwood_set_trn, .get_powerstat = kenwood_get_powerstat, .get_trn = kenwood_get_trn, .set_ant = kenwood_set_ant, .get_ant = kenwood_get_ant, .send_morse = kenwood_send_morse, }; /* * K2 extension function definitions follow */ /* k2_open() * */ int k2_open(RIG *rig) { int err; struct kenwood_priv_data *priv = rig->state.priv; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); err = elecraft_open(rig); if (err != RIG_OK) { return err; } err = k2_probe_mdfw(rig, priv); if (err != RIG_OK) { return err; } return RIG_OK; } /* k2_set_mode() * * Based on the passed in bandwidth, looks up the nearest bandwidth filter * wider than the passed value and sets the radio accordingly. */ int k2_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width) { int err; char f; struct k2_filt_lst_s *flt; struct kenwood_priv_data *priv = rig->state.priv; shortfreq_t freq = 0; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); /* Select the filter array per mode. */ switch (mode) { case RIG_MODE_LSB: case RIG_MODE_USB: flt = &k2_fwmd_ssb; break; case RIG_MODE_CW: case RIG_MODE_CWR: flt = &k2_fwmd_cw; break; case RIG_MODE_PKTLSB: case RIG_MODE_PKTUSB: if (priv->k2_md_rtty == 0) { return -RIG_EINVAL; /* RTTY module not installed */ } else { flt = &k2_fwmd_rtty; } break; default: return -RIG_EINVAL; } if (width != RIG_PASSBAND_NOCHANGE) { if (width < 0) { width = labs(width); } /* Step through the filter list looking for the best match * for the passed in width. The choice is to select the filter * that is wide enough for the width without being too narrow * if possible. */ if (width == RIG_PASSBAND_NORMAL) { width = rig_passband_normal(rig, mode); } if ((width > flt->filt_list[0].width) || (width > flt->filt_list[1].width)) { width = flt->filt_list[0].width; f = '1'; } else if ((flt->filt_list[1].width >= width) && (width > flt->filt_list[2].width)) { width = flt->filt_list[1].width; f = '2'; } else if ((flt->filt_list[2].width >= width) && (width > flt->filt_list[3].width)) { width = flt->filt_list[2].width; f = '3'; } else if ((flt->filt_list[3].width >= width) && (width >= freq)) { width = flt->filt_list[3].width; f = '4'; } else { return -RIG_EINVAL; } } /* kenwood_set_mode() ignores width value for K2/K3/TS-570 */ err = kenwood_set_mode(rig, vfo, mode, width); if (err != RIG_OK) { return err; } if (width != RIG_PASSBAND_NOCHANGE) { char fcmd[16]; err = kenwood_transaction(rig, "K22", NULL, 0); if (err != RIG_OK) { return err; } /* Construct the filter command and set the radio mode and width*/ snprintf(fcmd, 8, "FW0000%c", f); /* Set the filter slot */ err = kenwood_transaction(rig, fcmd, NULL, 0); if (err != RIG_OK) { return err; } err = kenwood_transaction(rig, "K20", NULL, 0); if (err != RIG_OK) { return err; } } return RIG_OK; } /* k2_get_mode() * * Uses the FW command in K22 mode to query the filter bandwidth reported * by the radio and returns it to the caller. */ int k2_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width) { int err; char buf[KENWOOD_MAX_BUF_LEN]; char tmp[16]; char *bufptr; pbwidth_t temp_w; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); if (!mode || !width) { return -RIG_EINVAL; } err = kenwood_get_mode(rig, vfo, mode, &temp_w); if (err != RIG_OK) { return err; } err = kenwood_transaction(rig, "K22", NULL, 0); if (err != RIG_OK) { return err; } err = kenwood_safe_transaction(rig, "FW", buf, KENWOOD_MAX_BUF_LEN, 8); if (err != RIG_OK) { return err; } err = kenwood_transaction(rig, "K20", NULL, 0); if (err != RIG_OK) { return err; } /* Convert received filter string value's first four digits to width */ bufptr = buf; strncpy(tmp, bufptr + 2, 4); tmp[4] = '\0'; *width = atoi(tmp); rig_debug(RIG_DEBUG_VERBOSE, "%s: Mode: %s, Width: %d\n", __func__, rig_strrmode(*mode), (int)*width); return RIG_OK; } /* TQ command is a quick transmit status query--K2/K3 only. * * token Defined in elecraft.h or this file * val Type depends on token type from confparams structure: * NUMERIC: val.f * COMBO: val.i, starting from 0 Index to a string table. * STRING: val.cs for set, val.s for get * CHECKBUTTON: val.i 0/1 */ int k2_get_ext_level(RIG *rig, vfo_t vfo, token_t token, value_t *val) { char buf[KENWOOD_MAX_BUF_LEN]; int err; const struct confparams *cfp; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); if (!val) { return -RIG_EINVAL; } cfp = rig_ext_lookup_tok(rig, token); switch (token) { case TOK_TX_STAT: err = kenwood_safe_transaction(rig, "TQ", buf, KENWOOD_MAX_BUF_LEN, 3); if (err != RIG_OK) { return err; } if (cfp->type == RIG_CONF_CHECKBUTTON) { val->i = atoi(&buf[2]); } else { rig_debug(RIG_DEBUG_ERR, "%s: protocol error, invalid token type\n", __func__); return -RIG_EPROTO; } break; default: rig_debug(RIG_DEBUG_WARN, "%s: Unsupported get_ext_level %s\n", __func__, rig_strlevel(token)); return -RIG_EINVAL; } return RIG_OK; } /* K2 private helper functions follow */ /* Probes for mode and filter settings, based on information * by Chris Bryant, G3WIE. */ int k2_probe_mdfw(RIG *rig, struct kenwood_priv_data *priv) { int err, i, c; char buf[KENWOOD_MAX_BUF_LEN]; char mode[16]; char fw[16]; char cmd[16]; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); if (!priv) { return -RIG_EINVAL; } /* The K2 extension level has been stored by elecraft_open(). Now set rig * to K22 for detailed query of mode and filter width values... */ err = kenwood_transaction(rig, "K22", NULL, 0); if (err != RIG_OK) { return err; } /* Check for mode and store it for later. */ err = kenwood_safe_transaction(rig, "MD", buf, KENWOOD_MAX_BUF_LEN, 3); if (err != RIG_OK) { return err; } strcpy(mode, buf); /* Check for filter width and store it for later. */ err = kenwood_safe_transaction(rig, "FW", buf, KENWOOD_MAX_BUF_LEN, 8); if (err != RIG_OK) { return err; } strcpy(fw, buf); rig_debug(RIG_DEBUG_VERBOSE, "%s: Mode value: %s, Filter Width value: %s\n", __func__, mode, fw); /* Now begin the process of querying the available modes and filters. */ /* First try to put the K2 into RTTY mode and check if it's available. */ priv->k2_md_rtty = 0; /* Assume RTTY module not installed */ err = kenwood_transaction(rig, "MD6", NULL, 0); if (err != RIG_OK && err != -RIG_ERJCTED) { return err; } if (RIG_OK == err) { /* Read back mode and test to see if K2 reports RTTY. */ err = kenwood_safe_transaction(rig, "MD", buf, KENWOOD_MAX_BUF_LEN, 3); if (err != RIG_OK) { return err; } if (!strcmp("MD6", buf)) { priv->k2_md_rtty = 1; /* set flag for RTTY mode enabled */ } } rig_debug(RIG_DEBUG_VERBOSE, "%s: RTTY flag is: %d\n", __func__, priv->k2_md_rtty); i = (priv->k2_md_rtty == 1) ? 2 : 1; /* Now loop through the modes checking for installed filters. */ for (c = 0; i > -1; i--, c++) { if (c == 0) { strcpy(cmd, "MD1"); /* SSB */ } else if (c == 1) { strcpy(cmd, "MD3"); /* CW */ } else if (c == 2) { strcpy(cmd, "MD6"); /* RTTY */ } else /* Oops! */ { err = k2_mdfw_rest(rig, mode, fw); if (err != RIG_OK) { return err; } return -RIG_EINVAL; } /* Now populate the Filter arrays */ err = k2_pop_fw_lst(rig, cmd); if (err != RIG_OK) { return err; } } /* Restore mode, filter, extension level */ if (strlen(fw) == 8) { fw[7] = '\0'; /* Truncate AFSlot to set filter slot */ } err = k2_mdfw_rest(rig, mode, fw); if (err != RIG_OK) { return err; } return RIG_OK; } /* Restore mode, filter, and ext_lvl to original values */ int k2_mdfw_rest(RIG *rig, const char *mode, const char *fw) { int err; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); if (!mode || !fw) { return -RIG_EINVAL; } if (strlen(mode) != 3 || strlen(fw) != 7) { return -RIG_EINVAL; } err = kenwood_transaction(rig, mode, NULL, 0); if (err != RIG_OK) { return err; } err = kenwood_transaction(rig, fw, NULL, 0); if (err != RIG_OK) { return err; } err = kenwood_transaction(rig, "K20", NULL, 0); if (err != RIG_OK) { return err; } return RIG_OK; } /* Populate k2_filt_lst_s structure for each mode */ int k2_pop_fw_lst(RIG *rig, const char *cmd) { int err, f; char fcmd[16]; char buf[KENWOOD_MAX_BUF_LEN]; char tmp[16]; struct k2_filt_lst_s *flt; rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); if (!cmd) { return -RIG_EINVAL; } /* Store filter data in the correct structure depending on mode */ if (strcmp(cmd, "MD1") == 0) { flt = &k2_fwmd_ssb; } else if (strcmp(cmd, "MD3") == 0) { flt = &k2_fwmd_cw; } else if (strcmp(cmd, "MD6") == 0) { flt = &k2_fwmd_rtty; } else { return -RIG_EINVAL; } /* Set the mode */ err = kenwood_transaction(rig, cmd, NULL, 0); if (err != RIG_OK) { return err; } for (f = 1; f < 5; f++) { char *bufptr = buf; snprintf(fcmd, 8, "FW0000%d", f); err = kenwood_transaction(rig, fcmd, NULL, 0); if (err != RIG_OK) { return err; } err = kenwood_safe_transaction(rig, "FW", buf, KENWOOD_MAX_BUF_LEN, 8); if (err != RIG_OK) { return err; } /* buf should contain a string "FWxxxxfa;" which corresponds to: * xxxx = filter width in Hz * f = crystal filter slot number--1-4 * a = audio filter slot number--0-2 */ strncpy(tmp, bufptr + 2, 4); tmp[4] = '\0'; flt->filt_list[f - 1].width = atoi(tmp); strncpy(tmp, bufptr + 6, 1); tmp[1] = '\0'; flt->filt_list[f - 1].fslot = atoi(tmp); strncpy(tmp, bufptr + 7, 1); tmp[1] = '\0'; flt->filt_list[f - 1].afslot = atoi(tmp); rig_debug(RIG_DEBUG_VERBOSE, "%s: Width: %04li, FSlot: %i, AFSlot %i\n", __func__, flt->filt_list[f - 1].width, flt->filt_list[f - 1].fslot, flt->filt_list[f - 1].afslot); } return RIG_OK; }