From: c vw Date: Thu, 12 Jul 2018 14:02:06 +0000 (+0200) Subject: Finalized CW stuff. Local CW now should work. X-Git-Url: https://git.rkrishnan.org/pf/content/en/seg/status?a=commitdiff_plain;h=30d4a1919937fc133860cca6342c9c9733d7bd58;p=pihpsdr.git Finalized CW stuff. Local CW now should work. --- diff --git a/ext.c b/ext.c index a7cbb55..331883d 100644 --- a/ext.c +++ b/ext.c @@ -141,11 +141,15 @@ int ext_radio_change_sample_rate(void *data) { return 0; } +// DL1YCF: because of the new CW algorithm, +// this function is no longer used int ext_cw_setup() { radio_cw_setup(); return 0; } +// DL1YCF: because of the new CW algorithm, +// this function is no longer used int ext_cw_key(void *data) { radio_cw_key((uintptr_t)data); return 0; diff --git a/iambic.c b/iambic.c index 71f2502..5cd0307 100644 --- a/iambic.c +++ b/iambic.c @@ -158,6 +158,10 @@ void keyer_update() { void keyer_event(int gpio, int level) { int state = (level == 0); + // This is for aborting CAT CW messages if the key is hit. + if (state) { + cw_key_hit = 1; + } if (gpio == CWL_BUTTON) kcwl = state; else // CWR_BUTTON @@ -180,21 +184,26 @@ void clear_memory() { void set_keyer_out(int state) { if (keyer_out != state) { + // DL1YCF: Shouln't one activate PTT if not yet done? + // At the moment, it is required to *manually* activate + // MOX before starting local CW. keyer_out = state; if(protocol==NEW_PROTOCOL) schedule_high_priority(9); fprintf(stderr,"set_keyer_out keyer_out= %d\n", keyer_out); - if (state) - if (SIDETONE_GPIO) + if (state) { + // DL1YCF: we must call cw_hold_key in *any* case, else no + // CW signal will be produced. We certainly do not + // want to produce a side tone *only*. + if (SIDETONE_GPIO) { softToneWrite (SIDETONE_GPIO, cw_keyer_sidetone_frequency); - else { - cw_sidetone_mute(1); - } - else - if (SIDETONE_GPIO) + } + cw_hold_key(1); // this starts a CW pulse in transmitter.c + } else { + if (SIDETONE_GPIO) { softToneWrite (SIDETONE_GPIO, 0); - else { - cw_sidetone_mute(0); - } + } + cw_hold_key(0); // this stops a CW pulse in transmitter.c + } } } diff --git a/old_protocol.c b/old_protocol.c index 3c26cae..99814a0 100644 --- a/old_protocol.c +++ b/old_protocol.c @@ -1218,21 +1218,20 @@ void ozy_send_buffer() { } else { mode=vfo[0].mode; } - if(mode==modeCWU || mode==modeCWL) { + if (isTransmitting()) { + if(mode==modeCWU || mode==modeCWL) { // -// The default is doing 'external CW', that is, -// CW is entirely done on the HPSDR board. In this -// case, we should not set MOX, the PTT switching on -// the HPSDR board is done by the board itself. +// If CW is done on the HPSDR board, we should not set +// the MOX bit, everything is done in the FPGA. // -// However, if we are doing local CW or tuning, -// we must put the SDR into TX mode. +// However, if we are doing CAT CW, local CW or tuning, +// we must put the SDR into TX mode. // - if(isTransmitting() && (tune || local_cw_is_active)) { - output_buffer[C0]|=0x01; - } - } else { - if(isTransmitting()) { + if(tune || CAT_cw_is_active || !cw_keyer_internal) { + output_buffer[C0]|=0x01; + } + } else { + // not doing CW? set MOX in any case. output_buffer[C0]|=0x01; } } diff --git a/radio.c b/radio.c index 854f053..090092a 100644 --- a/radio.c +++ b/radio.c @@ -288,7 +288,7 @@ double vox_threshold=0.001; double vox_gain=10.0; double vox_hang=250.0; int vox=0; -int local_cw_is_active=0; +int CAT_cw_is_active=0; int cw_key_hit=0; int diversity_enabled=0; @@ -899,6 +899,8 @@ int getTune() { return tune; } +// DL1YCF: because of the new CW algorithm, +// this function is no longer used void radio_cw_setup() { int mode=vfo[VFO_A].mode;; if(split) { @@ -912,6 +914,8 @@ void radio_cw_setup() { SetTXAPostGenToneMag(transmitter->id,0.99999); } +// DL1YCF: because of the new CW algorithm, +// this function is no longer used void radio_cw_key(int state) { SetTXAPostGenRun(transmitter->id,state); } diff --git a/radio.h b/radio.h index b455aec..9421534 100644 --- a/radio.h +++ b/radio.h @@ -244,7 +244,7 @@ extern double vox_threshold; extern double vox_gain; extern double vox_hang; extern int vox; -extern int local_cw_is_active; +extern int CAT_cw_is_active; extern int cw_key_hit; extern int diversity_enabled; diff --git a/rigctl.c b/rigctl.c index 599b686..a3305a2 100644 --- a/rigctl.c +++ b/rigctl.c @@ -495,11 +495,10 @@ static gpointer rigctl_cw_thread(gpointer data) dashlen = (dotlen * 3 * cw_keyer_weight) / 50L; dotsamples = 57600 / cw_keyer_speed; dashsamples = (3456 * cw_keyer_weight) / cw_keyer_speed; - local_cw_is_active=1; + CAT_cw_is_active=1; if (!mox) { // activate PTT g_idle_add(ext_ptt_update ,(gpointer)1); - g_idle_add(ext_cw_key ,(gpointer)1); // have to wait until it is really there while (!mox) usleep(50000L); // some extra time to settle down @@ -514,8 +513,7 @@ static gpointer rigctl_cw_thread(gpointer data) // CW transmission has been aborted, either due to manually // removing MOX, changing the mode to non-CW, or because a CW key has been hit. // Do not remove PTT if we abort CAT CW because a CW key has been hit. - local_cw_is_active=0; - g_idle_add(ext_cw_key ,(gpointer)0); + CAT_cw_is_active=0; // If an external CW key has been hit, we continue in TX mode // doing CW manually. Otherwise, switch PTT off. if (!cw_key_hit) { @@ -536,8 +534,7 @@ static gpointer rigctl_cw_thread(gpointer data) // Otherwise remove PTT and wait for next CAT // CW command. if (cw_busy) continue; - local_cw_is_active=0; - g_idle_add(ext_cw_key ,(gpointer)0); + CAT_cw_is_active=0; g_idle_add(ext_ptt_update ,(gpointer)0); } } @@ -547,7 +544,6 @@ static gpointer rigctl_cw_thread(gpointer data) // of a transmission rigctl_cw_thread_id = NULL; cw_busy=0; - g_idle_add(ext_cw_key ,(gpointer)0); g_idle_add(ext_ptt_update ,(gpointer)0); return NULL; } @@ -1754,7 +1750,6 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { sprintf(msg,"ZZFB%011lld;",vfo[VFO_B].frequency); } } - fprintf(stderr,"RIGCTL: FB=%s\n",msg); send_resp(client_sock,msg); } } @@ -2175,11 +2170,7 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { // - Note further that the space immediately following "KY" is *not* // part of the message. if (!cw_busy && len > 3) { - // A CW text will be sent. We have to call cw_setup here because - // TUNE changes the WDSP side tone frequency, and in this case we have - // to re-adjust it. - g_idle_add(ext_cw_setup,NULL); // Initialize for external transmit - // Copy data to buffer + // A CW text will be sent. Copy incoming data to buffer strncpy(cw_buf, cmd_input+3, 29); // Kenwood protocol allows for at most 24 characters, so // this seems pure paranoia -- but who knows? @@ -3223,13 +3214,8 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { else if(strcmp(cmd_str,"SM")==0) { // PiHPSDR - ZZSM - Read SMeter - same range as TS 2000 // TI-2000 - SM - Read SMETER - // SMx; x=0 RX1, x=1 RX2 - // meter is in dbm - value will be 0<260 - // Translate to 00-30 for main, 0-15 fo sub - // Resp= SMxAABB; - // Received range from the SMeter can be -127 for S0, S9 is -73, and S9+60=-13.; - // PowerSDR returns S9=0015 code. - // Let's make S9 half scale or a value of 70. + // SM0: main receiver, SM1: sub receiver + // Resp= SM0xxxx or SM1yyyy; xxxx between 0000 and 0030, yyyy between 0000 and 0015. double level=0.0; int r=0; @@ -3242,23 +3228,38 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { } level = GetRXAMeter(receiver[r]->id, smeter); - // Determine how high above 127 we are..making a range of 114 from S0 to S9+60db - // 5 is a fugdge factor that shouldn't be there - but seems to get us to S9=SM015 - // DL1YCF replaced abs by fabs, and changed 127 to floating point constant - level = fabs(127.0+(level + (double) adc_attenuation[receiver[r]->adc]))+5; - - // Clip the value just in case - if(cmd_input[2] == '0') { - new_level = (int) ((level * 30.0)/114.0); - // Do saturation check - if(new_level < 0) { new_level = 0; } - if(new_level > 30) { new_level = 30;} - } else { //Assume we are using sub receiver where range is 0-15 - new_level = (int) ((level * 15.0)/114.0); - // Do saturation check - if(new_level < 0) { new_level = 0; } - if(new_level > 15) { new_level = 15;} - } + if(r == 0) { + // DL1YCF: In the "main" bar graph, each bar counts 4 dB. + // S9+60 has 30 bars, S9 has 15 bars, S7 has 12 bars, and so on + // since S9 is about -73 dBm, the correct formula for converting + // dBm to bars is 15 + (level+73)/4 or 0.25*level + 33.25. + // The problem is now that S1 has 3 bars, such that 0 bars would + // correspond to S0 - 6dB. If one assumes that S0 corresponds + // to 0 bars, then it follows that the slope is 2 dB per bar + // below S1. This assumption is used in hamlib, and thus followed here. + if (level <= -121.0) { + new_level = (int) round(0.50*level+63.50); // valid up to S1 = -48 dBm + } else { + new_level = (int) round(0.25*level+33.25); // valid if at least S1 + } + // Clip + if(new_level < 0) { new_level = 0; } + if(new_level > 30) { new_level = 30;} + } else { + // DL1YCF: studying the pictures in the TS2000 manual, + // for the "sub" display we have 0-9 small bars for S0 -S9, and in addition 1-6 + // large bars for S9+10, ..., S9+60. So the slope is 6 dB per par + // up to S9, and 10 dB per bar beyond. + if (level <= -73.0) { + new_level = (int) round(0.16667*level+21.16667); // valid up to S9 + } else { + new_level = (int) round(0.1*level+16.3); // valid if at least S9 + } + // Clip + if(new_level < 0) { new_level = 0; } + if(new_level > 15) { new_level = 15;} + } + if(zzid_flag == 0) { sprintf(msg,"SM%1c%04d;", cmd_input[2],new_level); @@ -3517,7 +3518,6 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { g_idle_add(ext_mox_update,(gpointer)(long)mox_state); g_idle_add(ext_vfo_update,NULL); } else { - g_idle_add(ext_cw_setup,NULL); // Initialize for external transmit g_idle_add(ext_mox_update,(gpointer)(long)1); // Turn on External MOX } } @@ -3682,17 +3682,19 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { } } else if((strcmp(cmd_str,"XC")==0) && (zzid_flag == 1)) { - // PiHPSDR - ZZXC - Turn off External MOX- - g_idle_add(ext_mox_update,(gpointer)(long)0); // Turn on xmitter (set Mox) + // PiHPSDR - ZZXC - Terminate "CW" via ZZXO/ZZXT (Key up and MOX off) + cw_hold_key(0); + g_idle_add(ext_mox_update,(gpointer)(long)0); + CAT_cw_is_active=0; } else if((strcmp(cmd_str,"XI")==0) && (zzid_flag == 1)) { - // PiHPSDR - ZZXI - Initialize the transmitter for external CW - g_idle_add(ext_cw_setup,NULL); // Initialize for external transmit - g_idle_add(ext_mox_update,(gpointer)(long)1); // Turn on External MOX + // PiHPSDR - ZZXI - Prepeare for "CW" via ZZXO/ZZXT (MOX on) + CAT_cw_is_active=1; + g_idle_add(ext_mox_update,(gpointer)(long)1); } else if((strcmp(cmd_str,"XO")==0) && (zzid_flag == 1)) { - // PiHPSDR - ZZXT - Turn CW Note off when in CW mode - g_idle_add(ext_cw_key, (gpointer)(long)0); + // PiHPSDR - ZZXO - Turn CW Note off when in CW mode + cw_hold_key(0); } else if(strcmp(cmd_str,"XT")==0) { if(zzid_flag == 0 ) { @@ -3703,7 +3705,7 @@ void parse_cmd ( char * cmd_input,int len,int client_sock) { } } else { // PiHPSDR - ZZXT - Turn CW Note on when in CW mode - g_idle_add(ext_cw_key, (gpointer)(long)1); + cw_hold_key(1); } } else if((strcmp(cmd_str,"XX")==0) && (zzid_flag == 0)) { // diff --git a/transmitter.c b/transmitter.c index f33b5c3..baf1453 100644 --- a/transmitter.c +++ b/transmitter.c @@ -741,11 +741,17 @@ static void full_tx_buffer(TRANSMITTER *tx) { long isample; long qsample; double gain, sidevol, ramp, fgain; + double *dp; int j; int error; - int mode; + int cwmode; int sidetone=0; + // It is important query tx->mode and tune only once, to assure that + // the two "if (cwmode)" queries give the same result. + + cwmode = (tx->mode == modeCWL || tx->mode == modeCWU) && !tune; + switch(protocol) { case ORIGINAL_PROTOCOL: gain=32767.0; // 16 bit @@ -755,16 +761,30 @@ static void full_tx_buffer(TRANSMITTER *tx) { break; } - mode=vfo[VFO_A].mode; - if(split) { - mode=vfo[VFO_B].mode; - } - - update_vox(tx); + if (cwmode) { + // + // do not update VOX in CW mode in case we have just switched to CW + // and tx->mic_input_buffer is non-empty. WDSP (fexchange0) is not + // needed because we directly produce the I/Q samples (see below). + // What we do, however, is to create the iq_output_buffer for the + // sole purpose to display the spectrum of our CW signal. Then, + // the difference between poorly-shaped and well-shaped CW pulses + // also becomes visible on *our* TX spectrum display. + // + dp=tx->iq_output_buffer; + // These are the I/Q samples that describe our CW signal + // The only use we make of it is displaying the spectrum. + for (j = 0; j < tx->output_samples; j++) { + *dp++ = cw_shape_buffer[j]; + *dp++ = 0.0; + } + } else { + update_vox(tx); - fexchange0(tx->id, tx->mic_input_buffer, tx->iq_output_buffer, &error); - if(error!=0) { - fprintf(stderr,"full_tx_buffer: id=%d fexchange0: error=%d\n",tx->id,error); + fexchange0(tx->id, tx->mic_input_buffer, tx->iq_output_buffer, &error); + if(error!=0) { + fprintf(stderr,"full_tx_buffer: id=%d fexchange0: error=%d\n",tx->id,error); + } } #ifdef PURESIGNAL @@ -778,6 +798,12 @@ static void full_tx_buffer(TRANSMITTER *tx) { if(isTransmitting()) { if(radio->device==NEW_DEVICE_ATLAS && atlas_penelope) { + // + // On these boards, drive level changes are performed by + // scaling the TX IQ samples. In the other cases, DriveLevel + // as sent in the C&C frames becomes effective and the IQ + // samples are sent with full amplitude. + // if(tune && !transmitter->tune_use_drive) { gain=gain*((double)transmitter->drive_level*100.0/(double)transmitter->tune_percent); } else { @@ -785,35 +811,42 @@ static void full_tx_buffer(TRANSMITTER *tx) { } } -// Two times essentially the same code: moved the check on "pulse shape" case -// out of the loops for computational efficiency +// +// When doing CW, we do not need WDSP since I(t) = cw_shape_buffer(t) and Q(t)=0 +// For the old protocol where the IQ and audio samples are tied together, we can +// easily generate a synchronous side tone (and use the function +// old_protocol_iq_samples_with_sidetone for this purpose). +// - if ((mode == modeCWL || mode == modeCWU) && !tune) { + if (cwmode) { // // "pulse shape case": - // shape the I/Q samples with the envelope stored in cw_shape_buffer. - // We also produce a side tone with same shape. + // directly produce the I/Q samples. For a continuous zero-frequency + // carrier (as needed for CW) I(t)=1 and Q(t)=0 everywhere. We shape I(t) + // with the pulse envelope. We also produce a side tone with same shape. + // Note that tx->iq_output_buffer is not used. Therefore, all the + // SetTXAPostGen functions are not needed for CW! // - fgain=gain; // will be multiplied with ramp function - sidevol= 258.0 * cw_keyer_sidetone_volume; // will be multiplied with ramp function + sidevol= 258.0 * cw_keyer_sidetone_volume; // between 0.0 and 32766.0 + qsample=0; // will be constantly zero for(j=0;joutput_samples;j++) { - ramp=cw_shape_buffer[j]; // between 0 and 1 - gain=fgain*ramp; - double is=tx->iq_output_buffer[j*2]; - double qs=tx->iq_output_buffer[(j*2)+1]; - isample=is>=0.0?(long)floor(is*gain+0.5):(long)ceil(is*gain-0.5); - qsample=qs>=0.0?(long)floor(qs*gain+0.5):(long)ceil(qs*gain-0.5); + ramp=cw_shape_buffer[j]; // between 0.0 and 1.0 + isample=floor(gain*ramp+0.5); // always non-negative, isample is just the pulse envelope switch(protocol) { case ORIGINAL_PROTOCOL: + // // produce a side tone for internal CW on the HPSDR board // since we may use getNextSidetoneSample for local audio, we need // an independent instance thereof here. To be nice to the CW // operator, the audio is shaped the same way as the RF + // The extra CPU to calculate the side tone is available here, + // because we have not called fexchange0 for this buffer. + // sidetone=sidevol * ramp * getNextInternalSideToneSample(); old_protocol_iq_samples_with_sidetone(isample,qsample,sidetone); break; case NEW_PROTOCOL: - // ToDo: how to produce side-tone on the HPSDR board? + // ToDo: how to produce synchronous side-tone on the HPSDR board? new_protocol_iq_samples(isample,qsample); break; } @@ -841,17 +874,11 @@ static void full_tx_buffer(TRANSMITTER *tx) { } void add_mic_sample(TRANSMITTER *tx,short mic_sample) { - int mode; + int mode=tx->mode; double sample; double mic_sample_double, ramp; int i,s; - if(split) { - mode=vfo[1].mode; - } else { - mode=vfo[0].mode; - } - // // silence TX audio if not transmitting, if tuning, or // when doing CW. Note: CW side tone added later on by a @@ -1033,9 +1060,9 @@ void tx_set_ps_sample_rate(TRANSMITTER *tx,int rate) { // This is the old key-down/key-up interface for iambic.c // but now it also smoothes (cw_shape) the signal // -void cw_sidetone_mute(int mute) { - if (mute) { - cw_key_down = 480000; // up to 10 sec, should be OK even for QRSS +void cw_hold_key(int state) { + if (state) { + cw_key_down = 4800000; // up to 100 sec } else { cw_key_down = 0; } diff --git a/transmitter.h b/transmitter.h index 0f847f5..3b0fb61 100644 --- a/transmitter.h +++ b/transmitter.h @@ -145,7 +145,7 @@ extern void transmitter_set_compressor(TRANSMITTER *tx,int state); extern void tx_set_ps_sample_rate(TRANSMITTER *tx,int rate); extern void add_ps_iq_samples(TRANSMITTER *tx, double i_sample_0,double q_sample_0, double i_sample_1, double q_sample_1); -extern void cw_sidetone_mute(int mute); +extern void cw_hold_key(int state); #endif