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;
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
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
+ }
}
}
} 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;
}
}
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;
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) {
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);
}
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;
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
// 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) {
// 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);
}
}
// 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;
}
sprintf(msg,"ZZFB%011lld;",vfo[VFO_B].frequency);
}
}
- fprintf(stderr,"RIGCTL: FB=%s\n",msg);
send_resp(client_sock,msg);
}
}
// - 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?
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;
}
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);
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
}
}
}
}
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 ) {
}
} 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)) { //
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
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
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 {
}
}
-// 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;j<tx->output_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;
}
}
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
// 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;
}
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