/* -*- mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8 -*- */ /************************************************************************************ ** ** ** mcHF QRP Transceiver ** ** K Atanassov - M0NKA 2014 ** ** ** **---------------------------------------------------------------------------------** ** ** ** File name: ** ** Description: ** ** Last Modified: ** ** Licence: GNU GPLv3 ** ************************************************************************************/ // Common #include "uhsdr_board.h" #include "cat_driver.h" #include "usb_device.h" #include "usbd_cdc_if.h" #include #include "audio_driver.h" #include "radio_management.h" #include "config_storage.h" uint8_t limit_4bits(uint32_t in) { return in > 15 ? 15 : in; } // CAT driver internal structure typedef struct CatDriver { bool cat_ptt_active; CatInterfaceState state; CatInterfaceProtocol protocol; uint32_t lastbufferadd_time; } CatDriver; // CAT driver state CatDriver cat_driver; static void CatDriver_CatEnableTX(bool enable) { if (enable) { if(RadioManagement_IsTxDisabled() == false) { RadioManagement_Request_TxOn(); cat_driver.cat_ptt_active = true; } } else { cat_driver.cat_ptt_active = false; RadioManagement_Request_TxOff(); } } /** * @brief returns true if the current TX state has been initiated by a CAT PTT command */ bool CatDriver_CatPttActive() { return cat_driver.cat_ptt_active; } bool CatDriver_CWKeyPressed() { return cdcvcp_ctrllines.dtr != 0; } bool CatDriver_PTTKeyPressed() { return ts.enable_ptt_rts? (cdcvcp_ctrllines.rts != 0) : false; } bool CatDriver_PTTKeyChangedState() { static bool ptt_key_last_state; bool curval = CatDriver_PTTKeyPressed(); bool retval = ptt_key_last_state != curval; ptt_key_last_state = curval; return retval; } CatInterfaceState CatDriver_GetInterfaceState() { return hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED?CAT_CONNECTED:CAT_DISCONNECTED; } #define CAT_BUFFER_SIZE 256 __IO uint8_t cat_buffer[CAT_BUFFER_SIZE]; __IO int32_t cat_head = 0; __IO int32_t cat_tail = 0; static uint8_t CatDriver_InterfaceBufferHasData(void) { int32_t len = cat_head - cat_tail; return len < 0?len+CAT_BUFFER_SIZE:len; } static int cat_buffer_remove(uint8_t* c_ptr) { int ret = 0; if (cat_head != cat_tail) { int c = cat_buffer[cat_tail]; cat_tail = (cat_tail + 1) % CAT_BUFFER_SIZE; *c_ptr = (uint8_t)c; ret++; } return ret; } /* no room left in the buffer returns 0 */ int CatDriver_InterfaceBufferAddData(uint8_t c) { int ret = 0; int32_t next_head = (cat_head + 1) % CAT_BUFFER_SIZE; if (next_head != cat_tail) { /* there is room */ cat_buffer[cat_head] = c; cat_head = next_head; cat_driver.lastbufferadd_time = ts.sysclock; ret ++; } return ret; } static void cat_buffer_reset(void) { cat_tail = cat_head; } #define CAT_DRIVER_TIMEOUT 30 // defined in increments of 10ms, needs to be longer than the longest running operation // the mcHF can do. It seems band switching is taking longest time. // According to the FT817 manual, timeout is 200ms, so we use that plus a little extra and it seems to work /** * Synchronize CAT data and CAT decoder mechanism. * As there is a chance that buffers have received erroneous data * at least at startup or if the external program stops without completely sending the * request data, we expire old data in the request data buffer. */ static void cat_driver_sync_data( void) { uint32_t bufsz = CatDriver_InterfaceBufferHasData(); // we now know how much data is available // this MUST BE DONE BEFORE find out how old the data is // otherwise a race condition can occur and we throw away new data. if (bufsz) { if ( ts.sysclock - CAT_DRIVER_TIMEOUT > cat_driver.lastbufferadd_time) { // if we are here the first bufsz bytes are older than 200ms // if in the meantime new bytes arrive, no problem, we keep them // since we remove the first bufsz "old data" bytes only from // the front of the buffer while(bufsz) { uint8_t c; cat_buffer_remove(&c); bufsz--; } } } } static uint8_t CatDriver_InterfaceBufferGetData(uint8_t* Buf,uint32_t Len) { uint8_t res = 0; if (CatDriver_InterfaceBufferHasData() >= Len) { for (int i = 0; i < Len; i++) { cat_buffer_remove(&Buf[i]); } res = 1; } return res; } static uint8_t CatDriver_InterfaceBufferPutData(uint8_t* Buf,uint32_t Len) { uint8_t res = 0; if (CatDriver_GetInterfaceState() == CAT_CONNECTED && Len > 0) { ; res = CDC_Transmit_FS(Buf,Len) == USBD_OK; } return res; } /* DUPLEX = ["", "-", "+", "split"] # narrow modes has to be at end MODES = ["LSB", "USB", "CW", "CWR", "AM", "FM", "DIG", "PKT", "NCW", "NCWR", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS"] STEPSFM = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0] STEPSAM = [2.5, 5.0, 9.0, 10.0, 12.5, 25.0] STEPSSSB = [1.0, 2.5, 5.0] # warning ranges has to be in this exact order VALID_BANDS = [ (100000, 33000000), 0 // USED (33000000, 56000000), 1 (76000000, 108000000), 2 // NOT USED (108000000, 137000000), 3 // NOT USED (137000000, 154000000), 4 (420000000, 470000000) 5 ] */ // based on CHIRP ft817.py, gcc needs reversal of allocations inside 8bit typedef struct { uint8_t mode:3, // LSB, USB, ... unknown1:3, tag_default:1, // do we show the automatically generate name CH-XXX tag_on_off:1; // do we show the stored name? uint8_t freq_range:3, is_fm_narrow:1, is_cwdig_narrow:1, is_duplex:1, // is a duplex memory duplex:2; // how to interpret the offset with relation to the freq entry // 0 -> no offset / second freq // 1 -> - RX is freq - offset // 2 -> + RX is freq + offset // 3 -> split frequency, offset is used to store second frequency uint8_t unknown3:4, att:1, // attenuator on, NOT USED ipo:1, // IPO attenuator on, NOT USED unknown2:1, skip:1; uint8_t fm_step:3, am_step:3, ssb_step:2; uint8_t tmode:2, unknown4:6; uint8_t tx_freq_range:3, // VFO B? tx_mode:3, // VFO B? unknown5:2; uint8_t tone:6, unknown_toneflag:1, unknown6:1; uint8_t dcs:7, unknown7:1; uint16_t rit; // ul16 uint32_t freq; // ul32 -> USED VFO A uint32_t offset; // ul32 -> USED, VFO B uint8_t name[8]; // USED } __attribute__((packed)) ft817_memory_t ; typedef struct { uint8_t fst:1, lock:1, nb:1, pbt:1, unknownb:1, dsp:1, agc:2; uint8_t vox:1, vlt:1, bk:1, kyr:1, unknown5:1, cw_paddle:1, pwr_meter_mode:2; uint8_t vfob_band_select:4, vfoa_band_select:4; uint8_t unknowna; uint8_t backlight:2, color:2, contrast:4; uint8_t beep_freq:1, beep_volume:7; uint8_t arts_beep:2, main_step:1, cw_id:1, scope:1, pkt_rate:1, resume_scan:2; uint8_t op_filter:2, lock_mode:2, cw_pitch:4; uint8_t sql_rf_gain:1, ars_144:1, ars_430:1, cw_weight:5; uint8_t cw_delay; uint8_t unknown8:1, sidetone:7; uint8_t batt_chg:2, cw_speed:6; uint8_t disable_amfm_dial:1, vox_gain:7; uint8_t cat_rate:2, emergency:1, vox_delay:5; uint8_t dig_mode:3, mem_group:1, unknown9:1, apo_time:3; uint8_t dcs_inv:2, unknown10:1, tot_time:5; uint8_t mic_scan:1, ssb_mic:7; uint8_t mic_key:1, am_mic:7; uint8_t unknown11:1, fm_mic:7; uint8_t unknown12:1, dig_mic:7; uint8_t extended_menu:1, pkt_mic:7; uint8_t unknown14:1, pkt9600_mic:7; int16_t dig_shift; // il16 int16_t dig_disp; // il16 int8_t r_lsb_car; int8_t r_usb_car; int8_t t_lsb_car; int8_t t_usb_car; uint8_t unknown15:2, menu_item:6; uint8_t unknown16:4, menu_sel:4; uint16_t unknown17; uint8_t art:1, scn_mode:2, dw:1, pri:1, unknown18:1, tx_power:2; uint8_t spl:1, unknown:1, uhf_antenna:1, vhf_antenna:1, air_antenna:1, bc_antenna:1, sixm_antenna:1, hf_antenna:1; } __attribute__((packed)) ft871_settings_t ; typedef struct { uint8_t len; uint8_t count; } ft817_block_t; // FT817 (not ND!) const ft817_block_t cloneblock_len[] = { {2,1}, //0 -> 2 {40,1}, // -> 42 {208,1}, // -> 250 {182,1}, // -> 432 {208,1}, // -> 640 {182,1}, // -> 822 {198,1}, // -> 1020 {53,1}, // -> 1073 {130,40}, // -> 6273 (+5200) {118,1}, // -> 6391 {118,1} // -> 6509 }; /* @0x4: ft817_settings_t settings; @0x2A: -> @42 -> block [2] struct mem_struct vfoa[15]; // block[2+3] struct mem_struct vfob[15]; // block[4+5] struct mem_struct home[4]; // block[6]... struct mem_struct qmb; struct mem_struct mtqmb; struct mem_struct mtune; // ...block[6] @0x3FD: @1021 // block[7]+1 uint8_t visible[25]; uint8_t pmsvisible; @0x417: @1047 uint8_t filled[25]; // block[7]+27 uint8_t pmsfilled; @0x431: @1073 struct mem_struct memory[200]; block[9]-block[48] struct mem_struct pms[2]; block[49] @0x18cf: @6351 uint8_t callsign[7]; @0x1979: @6521 struct mem_struct sixtymeterchannels[5]; // not FT817, only ND US */ typedef enum { CLONEOUT_INIT = 0, CLONEOUT_BLOCK_SEND, CLONEOUT_BLOCK_ACK_WAIT, CLONEOUT_BLOCK_ACK_NACK, CLONEOUT_DONE } ft817_clone_out_st; // #define DEBUG_FT817 typedef enum { CLONEIN_INIT = 0, CLONEIN_BLOCK_RECV, CLONEIN_BLOCK_RECV_START, CLONEIN_FINAL_PROCESSING, CLONEIN_DONE } ft817_clone_in_st; typedef enum { CAT_INIT = 0, CAT_CAT, CAT_CLONEOUT, CAT_CLONEIN } ft817_cat_st; struct FT817 { uint8_t req[5]; ft817_cat_st state; ft817_clone_out_st cloneout_state; ft817_clone_in_st clonein_state; // #define DEBUG_FT817 #ifdef DEBUG_FT817 #define FT817_MAX_CMD 100 uint8_t reqs[FT817_MAX_CMD*5]; uint32_t cmd_cntr; #endif }; #include "ui_driver.h" #include "uhsdr_board.h" // FT817 Emulation #if 0 // list of commands supported by hamlib static const yaesu_cmd_set_t ncmd[] = { { 1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, /* lock on */ { 1, { 0x00, 0x00, 0x00, 0x00, 0x80 } }, /* lock off */ +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x08 } }, /* ptt on */ +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x88 } }, /* ptt off */ +{ 0, { 0x00, 0x00, 0x00, 0x00, 0x01 } }, /* set freq */ +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main LSB */ +{ 1, { 0x01, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main USB */ +{ 1, { 0x02, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main CW */ +{ 1, { 0x03, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main CWR */ +{ 1, { 0x04, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main AM */ +{ 1, { 0x08, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main FM */ +{ 1, { 0x88, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main FM-N */ +{ 1, { 0x0a, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main DIG */ +{ 1, { 0x0c, 0x00, 0x00, 0x00, 0x07 } }, /* mode set main PKT */ { 1, { 0x00, 0x00, 0x00, 0x00, 0x05 } }, /* clar on */ { 1, { 0x00, 0x00, 0x00, 0x00, 0x85 } }, /* clar off */ { 0, { 0x00, 0x00, 0x00, 0x00, 0xf5 } }, /* set clar freq */ +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x81 } }, /* toggle vfo a/b */ +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x02 } }, /* split on */ +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x82 } }, /* split off */ { 1, { 0x09, 0x00, 0x00, 0x00, 0x09 } }, /* set RPT shift MINUS */ { 1, { 0x49, 0x00, 0x00, 0x00, 0x09 } }, /* set RPT shift PLUS */ { 1, { 0x89, 0x00, 0x00, 0x00, 0x09 } }, /* set RPT shift SIMPLEX */ { 0, { 0x00, 0x00, 0x00, 0x00, 0xf9 } }, /* set RPT offset freq */ { 1, { 0x0a, 0x00, 0x00, 0x00, 0x0a } }, /* set DCS on */ { 1, { 0x2a, 0x00, 0x00, 0x00, 0x0a } }, /* set CTCSS on */ { 1, { 0x4a, 0x00, 0x00, 0x00, 0x0a } }, /* set CTCSS encoder on */ { 1, { 0x8a, 0x00, 0x00, 0x00, 0x0a } }, /* set CTCSS/DCS off */ { 0, { 0x00, 0x00, 0x00, 0x00, 0x0b } }, /* set CTCSS tone */ { 0, { 0x00, 0x00, 0x00, 0x00, 0x0c } }, /* set DCS code */ +{ 1, { 0x00, 0x00, 0x00, 0x00, 0xe7 } }, /* get RX status */ +{ 1, { 0x00, 0x00, 0x00, 0x00, 0xf7 } }, /* get TX status */ { 1, { 0x00, 0x00, 0x00, 0x00, 0x03 } }, /* get FREQ and MODE status */ { 1, { 0x00, 0x00, 0x00, 0x00, 0x00 } }, /* pwr wakeup sequence */ +{ 1, { 0x00, 0x00, 0x00, 0x00, 0x0f } }, /* pwr on */ { 1, { 0x00, 0x00, 0x00, 0x00, 0x8f } }, /* pwr off */ ?{ 0, { 0x00, 0x00, 0x00, 0x00, 0xbb } }, /* eeprom read */ } #endif typedef enum { FT817EE_INVALID = 0, FT817EE_DATA, FT817EE_FUNC, FT817EE_STOP } ft817_ee_entry_t; typedef struct { ft817_ee_entry_t type; uint16_t start; uint16_t end; union { bool (*funcPtr)(bool read, uint16_t addr, uint8_t* data_p); uint8_t value; } content; } ft817_eeprom_emul_t; // here we defined the eeprom emulation // for some tools we may want to support either reading a fixed value // or even reading/writing actual information from/to mcHF // all other addresses will indicate failure bool CatDriver_Ft817_EEPROM_RW_Func(bool readEEPROM, uint16_t addr, uint8_t* data_p) { bool retval = false; if (readEEPROM == false) { switch(addr) { case 0x55: { uint8_t vfo_selection = data_p[0] & 0x81; if ( (vfo_selection == 0x81 && is_vfo_b() == false) || (vfo_selection == 0x80 && is_vfo_b() == true) ) { // we have to switch active vfo UiAction_ToggleVfoAB(); retval = true; } break; } } } else { switch(addr) { /* HRD: case 0x56: case 0x57: case 0x58: case 0x5F: case 0x60: case 0x80 case 0x7B: */ case 0x55: // Used by: HRD // Bit 0: 0 -> VFO A, 1 -> VFO B // Bit 5: 0 -> Memory, 1 -> MTUNE // Bit 6: Unknown // Bit 7: 0 -> Memory Mode, 1 -> VFO *data_p = is_vfo_b()? 0x81:0x80; // TODO: If memmode gets implmented, we need more complex handling here retval = true; break; case 0x65: // lower bits not relevant bits 5-7 // 000 -> RTTY // 001 -> PSK31-L // 010 -> PSK31-U // 011 -> USER-L // 100 -> USER-U // we always report USER-U/USER-L independent from currently selected digital mode // on the UHSDR TRX *data_p = RadioManagement_LSBActive(DEMOD_DIGI) ? 0b01100000 : 0b10000000; retval = true; break; case 0x79: { // Used by: HRD // Bit 0 - 1: TX Power 00 - Hi -> 11: L1 (lowest) // Bit 2: Unknown // Bit 3: PRI On? // Bit 4: DW On? // Bit 5-6: Scan Mode: 00 No, 10 Scan Up, 11 Scan Down // Bit 7: ART On? uint8_t powerlevel = 0; switch (ts.power_level) { case PA_LEVEL_MINIMAL: powerlevel = 3; break; case PA_LEVEL_LOW: powerlevel = 2; break; case PA_LEVEL_MEDIUM: powerlevel = 1; break; default: // we report 5W and FULL as High // TODO: or shall we merge two other power settings? powerlevel = 0; break; } *data_p = powerlevel; retval = true; break; } case 0x7A: { // Used by: HRD, N1MM // Bit 0 - 5: Antenna Select HF/6m/FM Broadcast/Airband/2m/70cm // Bit 6: Unknown // Bit 7 indicates split mode *data_p = is_splitmode() ? 0x80:00; retval = true; break; } } } return retval; } // WSJT-X: Wants to read from 0x64, no write static const ft817_eeprom_emul_t ft817_eeprom[] = { { .type = FT817EE_DATA, .start = 0, .end = 3, .content.value = 0 }, // checksum { .type = FT817EE_DATA, .start = 4, .end = 4, .content.value = 0xD8 }, // radio config { .type = FT817EE_DATA, .start = 5, .end = 5, .content.value = 0xBF }, // radio config { .type = FT817EE_DATA, .start = 0x54, .end = 0x54, .content.value = 0xFF }, // this is just to make HAMLIB happy when reading 0x55 by requesting 0x54 // (and ignoring this value). This might break things if an application really wants to read a useful value from 0x54 { .type = FT817EE_FUNC, .start = 0x55, .end = 0x55, .content.funcPtr = CatDriver_Ft817_EEPROM_RW_Func }, // VOX Delay and other stuff, fixed value, required by WSJT-X { .type = FT817EE_DATA, .start = 0x64, .end = 0x64, .content.value = 0x00 }, // VOX Delay and other stuff, fixed value, required by WSJT-X { .type = FT817EE_FUNC, .start = 0x65, .end = 0x65, .content.funcPtr = CatDriver_Ft817_EEPROM_RW_Func }, // APO and Dig Mode setting, fixed value, required by WSJT-X { .type = FT817EE_FUNC, .start = 0x79, .end = 0x79, .content.funcPtr = CatDriver_Ft817_EEPROM_RW_Func }, // VOX Delay and other stuff, fixed value, required by WSJT-X { .type = FT817EE_FUNC, .start = 0x7A, .end = 0x7A, .content.funcPtr = CatDriver_Ft817_EEPROM_RW_Func }, // VOX Delay and other stuff, fixed value, required by WSJT-X { .type = FT817EE_DATA, .start = 0x7B, .end = 0x7B, .content.value = 0x00 }, // Chg related, not supported { .type = FT817EE_STOP } }; static bool CatDriver_Ft817_EEPROM_Write(uint16_t addr,uint8_t* data_p) { bool retval = false; for(int idx = 0; ft817_eeprom[idx].type != FT817EE_STOP && addr >= ft817_eeprom[idx].start;idx++) { if (ft817_eeprom[idx].start <= addr && addr <= ft817_eeprom[idx].end) { switch(ft817_eeprom[idx].type) { case FT817EE_FUNC: retval = (*ft817_eeprom[idx].content.funcPtr)(false, addr,data_p); break; default: break; } break; } } return retval; } static bool CatDriver_Ft817_EEPROM_Read(uint16_t addr,uint8_t* data_p) { bool retval = false; for(int idx = 0; ft817_eeprom[idx].type != FT817EE_STOP && addr >= ft817_eeprom[idx].start;idx++) { if (ft817_eeprom[idx].start <= addr && addr <= ft817_eeprom[idx].end) { switch(ft817_eeprom[idx].type) { case FT817EE_DATA: *data_p = ft817_eeprom[idx].content.value; retval = true; break; case FT817EE_FUNC: retval = (*ft817_eeprom[idx].content.funcPtr)(true, addr,data_p); break; default: break; } break; } } return retval; } typedef enum { FT817_SET_FREQ = 0x01, FT817_GET_FREQ = 0x03, FT817_SPLIT_ON = 0x02, FT817_SPLIT_OFF = 0x82, FT817_PTT_ON = 0x08, FT817_PTT_OFF = 0x88, FT817_MODE_SET = 0x07, FT817_PWR_ON = 0x0f, FT817_TOGGLE_VFO = 0x81, FT817_A7 = 0xa7, FT817_EEPROM_READ = 0xbb, FT817_EEPROM_WRITE = 0xbc, FT817_READ_TX_STATE = 0xbd, FT817_READ_RX_STATE = 0xe7, FT817_PTT_STATE = 0xf7, FT817_NOOP = 0xff, UHSDR_ID = 0x42, // this command is not known to the FT817 so we can use this to identify a UHSDR } Ft817_CatCmd_t; struct FT817 ft817; uint8_t CatDriver_Clone_Checksum(uint8_t* buf, size_t len) { uint8_t retval = 0; for (int idx = 0; idx < len; idx++) { retval += buf[idx]; } return retval; } void CatDriver_BlockPrepare(uint8_t num, uint8_t idx, uint8_t rpt, uint8_t* buf, size_t maxlen) { buf[0] = num; if (num == 7) { buf[2] = 0x1; // Memory[0] visible; buf[2+26] = 0x1; // Memory[0] filled; } if (num == 8) { ft817_memory_t* mem = (ft817_memory_t*)&buf[1]; mem->freq=__builtin_bswap32(700100); // need to convert big / little endian!! mem->tag_on_off = 1; mem->freq_range = 0; mem->am_step = 1; mem->ssb_step = 1; mem->tone = 0x08; memcpy(&mem->name[0],"A1234567",8); } buf[cloneblock_len[idx ].len+1] = CatDriver_Clone_Checksum(&buf[1],cloneblock_len[idx].len); } bool CatDriver_BlockRecv(uint8_t num, uint8_t idx, uint8_t rpt, uint8_t* buf, size_t len) { bool retval = false; if (buf[0] == num && (buf[len - 1] == CatDriver_Clone_Checksum(&buf[1],len-2))) { if (num == 8) { // we simply set the dial frequency here just for the show! ft817_memory_t* mem = (ft817_memory_t*)&buf[1]; df.tune_new = __builtin_bswap32(mem[0].freq) * 10; } retval = true; } return retval; } #define CLONE_CMD_ACK (0x06) void CatDriver_CloneSendAck() { uint8_t cmd_ack = CLONE_CMD_ACK; CatDriver_InterfaceBufferPutData(&cmd_ack,1); } void CatDriver_BlockSend(uint8_t* buf, size_t len) { CatDriver_InterfaceBufferPutData(buf,len); } bool CatDriver_BlockAck() { bool retval = false; while(CatDriver_InterfaceBufferHasData()) { // uint8_t c; // CatDriver_InterfaceBufferGetData(&c,1); // if (c == CLONE_CMD_ACK) uint8_t c = 0; if (CatDriver_InterfaceBufferGetData(&c,1)) { // retval = true; retval = c == CLONE_CMD_ACK; break; } } return retval; } static void CatDriver_HandleCloneOut(void) { static uint16_t blockIdx = 0; static uint16_t blockRpt = 0; static uint16_t blockNum = 0; static uint8_t buf[256]; static uint32_t last_sysclk; switch (ft817.cloneout_state) { case CLONEOUT_INIT: { blockIdx = 0; blockRpt = 0; blockNum = 0; ft817.cloneout_state = CLONEOUT_BLOCK_SEND; break; } case CLONEOUT_BLOCK_SEND: { CatDriver_BlockPrepare(blockNum,blockIdx,blockRpt,buf,256); CatDriver_BlockSend(buf,cloneblock_len[blockIdx].len+2); // +2 since we added blocknum and checksum, each 8bit. ft817.cloneout_state = CLONEOUT_BLOCK_ACK_WAIT; last_sysclk = ts.sysclock + 100; break; } case CLONEOUT_BLOCK_ACK_WAIT: { if (CatDriver_BlockAck()) { blockNum++; blockRpt++; if (blockRpt == cloneblock_len[blockIdx].count) { blockIdx++; blockRpt = 0; } if (blockIdx == 11) { ft817.cloneout_state = CLONEOUT_DONE; } else { ft817.cloneout_state = CLONEOUT_BLOCK_SEND; } } else { if (last_sysclk < ts.sysclock) { // after a while we will give up ft817.cloneout_state = CLONEOUT_BLOCK_ACK_NACK; } } break; } case CLONEOUT_BLOCK_ACK_NACK: { ft817.cloneout_state = CLONEOUT_DONE; break; } case CLONEOUT_DONE: { // go back to normal CAT MODE and prepare for next round ft817.cloneout_state = CLONEOUT_INIT; ft817.state = CAT_INIT; break; } } } static void CatDriver_HandleCloneIn(void) { static uint16_t blockIdx = 0; static uint16_t blockRpt = 0; static uint16_t blockNum = 0; static uint8_t buf[256]; static uint8_t blockWant = 0; static uint32_t last_sysclk; switch (ft817.clonein_state) { case CLONEIN_INIT: { blockIdx = 0; blockRpt = 0; blockNum = 0; ft817.clonein_state = CLONEIN_BLOCK_RECV_START; break; } case CLONEIN_BLOCK_RECV_START: { last_sysclk = ts.sysclock + 600; // that is 6s, enough to start the CAT clone transmit on the PC blockWant = cloneblock_len[blockIdx].len + 2; // two more for blocknum and checksum ft817.clonein_state = CLONEIN_BLOCK_RECV; break; } case CLONEIN_BLOCK_RECV: { // we can ask for the full amount since our buffer will be able to hold all of the packets contents if (CatDriver_InterfaceBufferGetData(buf,blockWant)) { // analyse block if (CatDriver_BlockRecv(blockNum,blockIdx,blockRpt,buf,blockWant)) { // now continue CatDriver_CloneSendAck(); blockNum++; blockRpt++; if (blockRpt == cloneblock_len[blockIdx].count) { blockIdx++; blockRpt = 0; } if (blockIdx == 11) { // we are done receiving, so now lets do the final data processing ft817.clonein_state = CLONEIN_FINAL_PROCESSING; } else { ft817.clonein_state = CLONEIN_BLOCK_RECV_START; } } else { ft817.clonein_state = CLONEIN_DONE; } } else if (last_sysclk <= ts.sysclock) { // timeout ft817.clonein_state = CLONEIN_DONE; } break; } case CLONEIN_FINAL_PROCESSING: { // TODO: Now that all infos have been received, do the processing of it // TBW // once done, get back to normal CAT operation ft817.clonein_state = CLONEIN_DONE; break; } case CLONEIN_DONE: { // go back to normal CAT MODE and prepare for next round ft817.clonein_state = CLONEIN_INIT; ft817.state = CAT_INIT; break; } } } bool CatDriver_CloneOutStart() { bool retval = false; if (ft817.state == CAT_CAT) { retval = true; ft817.state = CAT_CLONEOUT; ft817.cloneout_state = CLONEOUT_INIT; } return retval; } bool CatDriver_CloneInStart() { bool retval = false; if (ft817.state == CAT_CAT) { retval = true; ft817.state = CAT_CLONEIN; ft817.clonein_state = CLONEIN_INIT; } return retval; } static void CatDriver_HandleCommands(void) { uint8_t bc = 0; uint8_t resp[32]; cat_driver_sync_data(); while (CatDriver_InterfaceBufferGetData(ft817.req,5)) { #ifdef DEBUG_FT817 int debug_idx; for (debug_idx = 0; debug_idx < 5 && ft817.cmd_cntr < FT817_MAX_CMD; debug_idx++ ) { ft817.reqs[ft817.cmd_cntr*5+debug_idx] = ft817.req[debug_idx]; } ft817.cmd_cntr++; #endif switch((Ft817_CatCmd_t)ft817.req[4]) { case FT817_SET_FREQ: { ulong f = 0; ulong fdelta; if(ts.xlat == 0) { fdelta = (ts.tx_audio_source == TX_AUDIO_DIGIQ)?AudioDriver_GetTranslateFreq():0; // If we are in DIGITAL IQ Output mode, use real tune frequency frequency instead // translated RX frequency } else { fdelta = 0; } int fidx; for (fidx = 0; fidx < 4; fidx++) { f *= 100; f += (ft817.req[fidx] >> 4) * 10 + (ft817.req[fidx] & 0x0f); } f *= 10; df.tune_new = f - fdelta; resp[0] = 0; bc = 1; if(ts.flags1 & FLAGS1_CAT_IN_SANDBOX) // if running in sandbox store active band { ts.cat_band_index = ts.band->band_mode; } } break; case FT817_GET_FREQ: { ulong fdelta; if(ts.xlat == 0) { fdelta = (ts.tx_audio_source == TX_AUDIO_DIGIQ)?AudioDriver_GetTranslateFreq():0; // If we are in DIGITAL IQ Output mode, send real tune frequency frequency instead // translated RX frequency } else { fdelta = 0; } ulong f = (df.tune_new + fdelta + 5)/ 10; ulong fbcd = 0; int fidx; for (fidx = 0; fidx < 8; fidx++) { fbcd >>= 4; fbcd |= (f % 10) << 28; f = f / 10; } resp[0] = (uint8_t)(fbcd >> 24); resp[1] = (uint8_t)(fbcd >> 16); resp[2] = (uint8_t)(fbcd >> 8); resp[3] = (uint8_t)fbcd; } switch(ts.dmod_mode) { case DEMOD_LSB: resp[4] = 0; break; case DEMOD_USB: resp[4] = 1; break; case DEMOD_CW: resp[4] = 2 + (ts.cw_lsb==true?1:0); break; // return 3 if CW in LSB aka CW-R case DEMOD_SAM: case DEMOD_AM: resp[4] = 4; break; case DEMOD_FM: resp[4] = 8; break; default: resp[4] = 1; } bc = 5; break; case FT817_MODE_SET: { resp[0] = 0; // ACK bc = 1; uint8_t new_mode = ts.dmod_mode; bool new_cwlsb = ts.cw_lsb; uint32_t new_fmdev5khz = RadioManagement_FmDevIs5khz(); switch (ft817.req[0]) { case 0: // LSB new_mode = DEMOD_LSB; break; case 1: // USB new_mode = DEMOD_USB; break; case 2: // CW new_cwlsb = false; new_mode = DEMOD_CW; break; case 3: // CW-R new_cwlsb = true; new_mode = DEMOD_CW; break; case 4: // AM new_mode = DEMOD_AM; break; case 8: // FM new_fmdev5khz = true; new_mode = DEMOD_FM; break; case 0x88: // FM-N new_fmdev5khz = false; new_mode = DEMOD_FM; break; case 0x0a: // DIG - SSB, side band controlled by some menu configuration in ft817, we use USB here new_mode = DEMOD_USB; break; case 0x0c: // PKT - FM, 9k6 new_mode = DEMOD_FM; break; default: resp[0] = 0xFF; // NACK } if (new_mode != ts.dmod_mode || new_cwlsb != ts.cw_lsb || new_fmdev5khz != RadioManagement_FmDevIs5khz() ) { if(ts.flags1 & FLAGS1_CAT_IN_SANDBOX) // if running in sandbox store active band { ts.cat_band_index = ts.band->band_mode; } RadioManagement_FmDevSet5khz(new_fmdev5khz); ts.cw_lsb = new_cwlsb; RadioManagement_SetDemodMode(new_mode); UiDriver_UpdateDisplayAfterParamChange(); } } break; case FT817_PTT_ON: resp[0] = cat_driver.cat_ptt_active?0xF0:0x00; /* 0xF0 if PTT was already on */ CatDriver_CatEnableTX(true); bc = 1; break; case FT817_PWR_ON: resp[0] = 0; bc = 1; break; case FT817_TOGGLE_VFO: UiAction_ToggleVfoAB(); resp[0] = 0; bc = 1; break; case FT817_SPLIT_ON: UiDriver_SetSplitMode(true); resp[0] = 0; bc = 1; break; case FT817_SPLIT_OFF: UiDriver_SetSplitMode(false); resp[0] = 0; bc = 1; break; case FT817_PTT_OFF: resp[0] = cat_driver.cat_ptt_active?0x00:0xF0; /* 0xF0 if PTT was already off */ bc = 1; CatDriver_CatEnableTX(false); break; case FT817_A7: /* A7 */ resp[0]=0xA7; resp[1]=0x02; resp[2]=0x00; resp[3]=0x04; resp[4]=0x67; resp[5]=0xD8; resp[6]=0xBF; resp[7]=0xD8; resp[8]=0xBF; bc = 9; break; case FT817_EEPROM_READ: // with a full simulation, we would return from 0 to 0x1925 two bytes: data@addr + data@(addr+1) // and above 0x1925 only 1 byte. So we call our function and see what it returns. { resp[0] = 0; resp[1] = 0; uint16_t ee_addr = (ft817.req[0] << 8) | ft817.req[1]; if (CatDriver_Ft817_EEPROM_Read(ee_addr,&resp[0]) && ee_addr < 0x1925) { // please note: in case of second addr being invalid // we still return success and two bytes, second data byte is 0 in this case CatDriver_Ft817_EEPROM_Read(ee_addr+1,&resp[1]); bc = 2; } // we now check if we are supposed to return config values else if (ee_addr > 0x7FFF) { ConfigStorage_ReadVariable(ee_addr & 0x7FFF, (uint16_t*)&resp[0]); bc = 2; } else { bc = 1; } break; } case FT817_EEPROM_WRITE: { // no op in most cases resp[0] = 0; bc = 1; uint16_t ee_addr = (ft817.req[0] << 8) | ft817.req[1]; if (ee_addr < 0x1925) { CatDriver_Ft817_EEPROM_Write(ee_addr,&ft817.req[2]); CatDriver_Ft817_EEPROM_Write(ee_addr+1,&ft817.req[3]); } else if (ee_addr > 0x7FFF) { uint16_t ee_value16 = (ft817.req[3] << 8) | ft817.req[2]; ConfigStorage_WriteVariable(ee_addr & 0x7FFF, ee_value16); } break; } case FT817_READ_TX_STATE: if(RadioManagement_IsTxDisabled()||(ts.txrx_mode != TRX_MODE_TX)) { resp[0] = 0; bc = 1; } else { // PWR / SWR resp[0] =(limit_4bits(roundf(swrm.fwd_pwr))<<4)+limit_4bits(roundf(swrm.vswr_dampened)); // ALC / MOD resp[1] = (limit_4bits(0)<<4)+limit_4bits(0); bc = 2; } break; case FT817_READ_RX_STATE: /* E7 */ resp[0] = (uint8_t)roundf(sm.s_count*0.5); //S-Meter signal bc = 1; break; case FT817_PTT_STATE: { uint8_t tx_state = limit_4bits(roundf(swrm.fwd_pwr)); tx_state |= is_splitmode() ? 0x20 : 0x00; // tx_state |= ts.txrx_mode == TRX_MODE_TX ? 0x00 : 0x80; tx_state |= swrm.vswr_dampened > 3.0 ? 0x40 : 0x00; // resp[0]= ts.txrx_mode == TRX_MODE_TX ? tx_state : 0x80; // the only correct response when in rx mode is 0xff, confirmed // with a real FT-817 resp[0]= ts.txrx_mode == TRX_MODE_TX ? tx_state : 0xff; if(RadioManagement_IsTxDisabled()) { resp[0] = 0xFF; } bc = 1; break; } case FT817_NOOP: /* FF sent out by HRD */ break; // default: // while (1); case UHSDR_ID: /* used to identify the UHSDR EXTEND CAT SUPPORT */ resp[0] = 'U'; resp[1] = 'H'; resp[2] = 'S'; resp[3] = 'D'; resp[4] = 'R'; bc = 5; break; // default: // while (1); } CatDriver_InterfaceBufferPutData(resp,bc); /* Return data back */ } } void CatDriver_HandleProtocol() { if (CatDriver_GetInterfaceState() == CAT_DISCONNECTED) { if (ft817.state != CAT_INIT) { cat_buffer_reset(); ft817.state = CAT_INIT; } } else { switch(ft817.state) { case CAT_CLONEOUT: CatDriver_HandleCloneOut(); break; case CAT_CLONEIN: CatDriver_HandleCloneIn(); break; case CAT_INIT: ft817.cloneout_state = CLONEOUT_INIT; ft817.clonein_state = CLONEIN_INIT; ft817.state = CAT_CAT; /* fall through */ // this is for the compiler, the following comment is for Eclipse /* no break */ case CAT_CAT: CatDriver_HandleCommands(); break; } } #if 1 // TODO: factor out // handle emulated serial PTT Pin Handling if (CatDriver_PTTKeyChangedState()) { if (CatDriver_PTTKeyPressed()) { if (CatDriver_CatPttActive() == false) { CatDriver_CatEnableTX(true); } } else { if (CatDriver_CatPttActive() == true) { CatDriver_CatEnableTX(false); } } } #endif }