Added cessbProcessing control. Tnx KF5N

master
boblark 1 month ago
parent 5113b5394b
commit 8fbac3562b
  1. 119
      radioCESSB_Z_transmit_F32.cpp
  2. 14
      radioCESSB_Z_transmit_F32.h
  3. 184
      radioCESSBtransmit_F32.cpp
  4. 14
      radioCESSBtransmit_F32.h

@ -122,7 +122,7 @@ void radioCESSB_Z_transmit_F32::update(void) {
workingDataQ[k] *= gainFactor; workingDataQ[k] *= gainFactor;
} }
// Mesaure input power and peak envelope, SSB before any CESSB processing // Measure input power and peak envelope, SSB before any CESSB processing
for(int k=0; k<nW; k++) for(int k=0; k<nW; k++)
{ {
float32_t pwrWorkingData = workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k]; float32_t pwrWorkingData = workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k];
@ -147,73 +147,78 @@ void radioCESSB_Z_transmit_F32::update(void) {
// LPF with gain of 2 built into coefficients, correct for added zeros. // LPF with gain of 2 built into coefficients, correct for added zeros.
arm_fir_f32(&firInstInterpolate1I, workingDataI, workingDataI, nC); arm_fir_f32(&firInstInterpolate1I, workingDataI, workingDataI, nC);
arm_fir_f32(&firInstInterpolate1Q, workingDataQ, workingDataQ, nC); arm_fir_f32(&firInstInterpolate1Q, workingDataQ, workingDataQ, nC);
// WorkingDataI and Q are now at 24 ksps and ready for clipping // WorkingDataI and Q are now at 24 ksps and ready for clipping
// For input 48 ksps this produces 64 numbers // For input 48 ksps this produces 64 numbers
for(int kk=0; kk<nC; kk++) // Optional skip CESSB processing. Filtering & SSB generation unchanged. Thanks to Greg KF5N
if(cessbProcessing) // Not skipping
{ {
float32_t power = workingDataI[kk]*workingDataI[kk] + workingDataQ[kk]*workingDataQ[kk]; for(int kk=0; kk<nC; kk++)
float32_t mag = sqrtf(power);
if(mag > 1.0f) // This the clipping, scaled to 1.0, desired max
{ {
workingDataI[kk] /= mag; float32_t power = workingDataI[kk]*workingDataI[kk] + workingDataQ[kk]*workingDataQ[kk];
workingDataQ[kk] /= mag; float32_t mag = sqrtf(power);
if(mag > 1.0f) // This the clipping, scaled to 1.0, desired max
{
workingDataI[kk] /= mag;
workingDataQ[kk] /= mag;
}
} }
}
// clipperIn needs spectrum control, so LP filter it. // clipperIn needs spectrum control, so LP filter it.
// Both BW of the signal and the sample rate have been doubled. // Both BW of the signal and the sample rate have been doubled.
arm_fir_f32(&firInstClipperI, workingDataI, workingDataI, nC); arm_fir_f32(&firInstClipperI, workingDataI, workingDataI, nC);
arm_fir_f32(&firInstClipperQ, workingDataQ, workingDataQ, nC); arm_fir_f32(&firInstClipperQ, workingDataQ, workingDataQ, nC);
// Ready to compensate for filter overshoots // Ready to compensate for filter overshoots
for (int k=0; k<nC; k++) for (int k=0; k<nC; k++)
{ {
// Circular delay line for signal to align data with FIR output // Circular delay line for signal to align data with FIR output
// Put I & Q data points into the delay arrays // Put I & Q data points into the delay arrays
osDelayI[indexOsDelay & 0X3F] = workingDataI[k]; osDelayI[indexOsDelay & 0X3F] = workingDataI[k];
osDelayQ[indexOsDelay & 0X3F] = workingDataQ[k]; osDelayQ[indexOsDelay & 0X3F] = workingDataQ[k];
// Remove 64 points delayed data from line and save for later // Remove 64 points delayed data from line and save for later
delayedDataI[k] = osDelayI[(indexOsDelay - 63) & 0X3F]; delayedDataI[k] = osDelayI[(indexOsDelay - 63) & 0X3F];
delayedDataQ[k] = osDelayQ[(indexOsDelay - 63) & 0X3F]; delayedDataQ[k] = osDelayQ[(indexOsDelay - 63) & 0X3F];
indexOsDelay++; indexOsDelay++;
// Delay line to allow strongest envelope to be used for compensation // Delay line to allow strongest envelope to be used for compensation
// We only need to look ahead 1 or behind 1, so delay line of 4 is OK. // We only need to look ahead 1 or behind 1, so delay line of 4 is OK.
// Enter latest envelope to delay array // Enter latest envelope to delay array
osEnv[indexOsEnv & 0X03] = sqrtf( osEnv[indexOsEnv & 0X03] = sqrtf(
workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k]); workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k]);
// look over the envelope curve to find the max // look over the envelope curve to find the max
float32_t eMax = 0.0f; float32_t eMax = 0.0f;
if(osEnv[(indexOsEnv) & 0X03] > eMax) // Data point just entered if(osEnv[(indexOsEnv) & 0X03] > eMax) // Data point just entered
eMax = osEnv[(indexOsEnv) & 0X03]; eMax = osEnv[(indexOsEnv) & 0X03];
if(osEnv[(indexOsEnv-1) & 0X03] > eMax) // Entered one before if(osEnv[(indexOsEnv-1) & 0X03] > eMax) // Entered one before
eMax = osEnv[(indexOsEnv-1) & 0X03]; eMax = osEnv[(indexOsEnv-1) & 0X03];
if(osEnv[(indexOsEnv-2) & 0X03] > eMax) // Entered one before that if(osEnv[(indexOsEnv-2) & 0X03] > eMax) // Entered one before that
eMax = osEnv[(indexOsEnv-2) & 0X03]; eMax = osEnv[(indexOsEnv-2) & 0X03];
if(eMax < 1.0f) if(eMax < 1.0f)
eMax = 1.0f; // Below clipping region eMax = 1.0f; // Below clipping region
indexOsEnv++; indexOsEnv++;
// Clip the signal to 1.0. -2 allows 1 look ahead on signal. // Clip the signal to 1.0. -2 allows 1 look ahead on signal.
float32_t eCorrectedI = osDelayI[(indexOsDelay - 2) & 0X3F] / eMax; float32_t eCorrectedI = osDelayI[(indexOsDelay - 2) & 0X3F] / eMax;
float32_t eCorrectedQ = osDelayQ[(indexOsDelay - 2) & 0X3F] / eMax; float32_t eCorrectedQ = osDelayQ[(indexOsDelay - 2) & 0X3F] / eMax;
// Filtering is linear, so we only need to filter the difference between // Filtering is linear, so we only need to filter the difference between
// the signal and the clipper output. This needs less filtering, as the // the signal and the clipper output. This needs less filtering, as the
// difference is many dB below the signal to begin with. Hershberger 2014 // difference is many dB below the signal to begin with. Hershberger 2014
diffI[k] = osDelayI[(indexOsDelay - 2) & 0X3F] - eCorrectedI; diffI[k] = osDelayI[(indexOsDelay - 2) & 0X3F] - eCorrectedI;
diffQ[k] = osDelayQ[(indexOsDelay - 2) & 0X3F] - eCorrectedQ; diffQ[k] = osDelayQ[(indexOsDelay - 2) & 0X3F] - eCorrectedQ;
} // End, for k=0 to 63 } // End, for k=0 to 63
// Filter the differences, osFilter has 123 taps and 61 delay // Filter the differences, osFilter has 123 taps and 61 delay
arm_fir_f32(&firInstOShootI, diffI, diffI, nC); arm_fir_f32(&firInstOShootI, diffI, diffI, nC);
arm_fir_f32(&firInstOShootQ, diffQ, diffQ, nC); arm_fir_f32(&firInstOShootQ, diffQ, diffQ, nC);
// Do the overshoot compensation // Do the overshoot compensation
for(int k=0; k<nC; k++) for(int k=0; k<nC; k++)
{ {
workingDataI[k] = delayedDataI[k] - gainCompensate*diffI[k]; workingDataI[k] = delayedDataI[k] - gainCompensate*diffI[k];
workingDataQ[k] = delayedDataQ[k] - gainCompensate*diffQ[k]; workingDataQ[k] = delayedDataQ[k] - gainCompensate*diffQ[k];
} }
} // end CESSB processing
// Measure average output power and peak envelope, after CESSB // Measure average output power and peak envelope, after CESSB
// but before gainOut // but before gainOut

@ -105,6 +105,7 @@
* such as IIR. Minimize any linear-phase filtering such as FIR. * such as IIR. Minimize any linear-phase filtering such as FIR.
* Such activities enhance the overshoots and defeat the purpose of CESSB. * Such activities enhance the overshoots and defeat the purpose of CESSB.
*/ */
// Rev 14Oct24 Added on/off via cessbProcessing. Tnx KF5N.
#ifndef _radioCESSB_Z_transmit_f32_h #ifndef _radioCESSB_Z_transmit_f32_h
#define _radioCESSB_Z_transmit_f32_h #define _radioCESSB_Z_transmit_f32_h
@ -158,6 +159,16 @@ public:
//setBlockLength(128); Always default 128 //setBlockLength(128); Always default 128
} }
// A "setter" and "getter" methods. If cessbProcessing==false, CESSB processing is bypassed.
// This is intended for digital modes. Greg KF5N August 16 2024
void setProcessing(bool cessbActive) {
cessbProcessing = cessbActive;
}
bool getProcessing(void) {
return cessbProcessing;
}
// Sample rate starts at default 44.1 ksps. That will work. Filters // Sample rate starts at default 44.1 ksps. That will work. Filters
// are designed for 48 and 96 ksps, however. This is a *required* // are designed for 48 and 96 ksps, however. This is a *required*
// function at setup(). // function at setup().
@ -278,7 +289,8 @@ private:
struct levelsZ levelData; struct levelsZ levelData;
audio_block_f32_t *inputQueueArray_f32[1]; audio_block_f32_t *inputQueueArray_f32[1];
uint32_t jjj = 0; // Used for diagnostic printing uint32_t jjj = 0; // Used for diagnostic printing
bool cessbProcessing = true; // If false, CESSB processing is bypassed.
// Greg KF5N August 16 2024
// Input/Output is at 48 or 96 ksps. Hilbert generation is at 12 ksps. // Input/Output is at 48 or 96 ksps. Hilbert generation is at 12 ksps.
// Clipping and overshoot processing is at 24 ksps. // Clipping and overshoot processing is at 24 ksps.
// Next line is to indicate that setSampleRateHz() has not executed // Next line is to indicate that setSampleRateHz() has not executed

@ -117,7 +117,7 @@ void radioCESSBtransmit_F32::update(void) {
arm_fir_f32(&firInstWeaverQ, weaverMQ, workingDataQ, nW); arm_fir_f32(&firInstWeaverQ, weaverMQ, workingDataQ, nW);
// Note: Sine wave envelope gain from blockIn->data[kk] to here is gainIn // Note: Sine wave envelope gain from blockIn->data[kk] to here is gainIn
// Mesaure input power and peak envelope, SSB before any CESSB processing // Measure input power and peak envelope, SSB before any CESSB processing
for(int k=0; k<nW; k++) for(int k=0; k<nW; k++)
{ {
float32_t pwrWorkingData = workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k]; float32_t pwrWorkingData = workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k];
@ -142,102 +142,110 @@ void radioCESSBtransmit_F32::update(void) {
// LPF with gain of 2 built into coefficients, correct for zeros. // LPF with gain of 2 built into coefficients, correct for zeros.
arm_fir_f32(&firInstInterpolate1I, workingDataI, workingDataI, nC); arm_fir_f32(&firInstInterpolate1I, workingDataI, workingDataI, nC);
arm_fir_f32(&firInstInterpolate1Q, workingDataQ, workingDataQ, nC); arm_fir_f32(&firInstInterpolate1Q, workingDataQ, workingDataQ, nC);
// WorkingDataI and Q are now at 24 ksps and ready for clipping // WorkingDataI and Q are now at 24 ksps and ready for clipping
// For input 48 ksps this produces 64 numbers // For input 48 ksps this produces 64 numbers
// Voltage gain from blockIn->data to here for small sine wave is 1.0 // Voltage gain from blockIn->data to here for small sine wave is 1.0
for(int kk=0; kk<nC; kk++)
// Optional skip of CESSB processing. Filtering & SSB generation unchanged. Thanks to Greg KF5N
if(cessbProcessing) // Not skipping
{ {
float32_t power = workingDataI[kk]*workingDataI[kk] + workingDataQ[kk]*workingDataQ[kk]; for(int kk=0; kk<nC; kk++)
float32_t mag = sqrtf(power);
if(mag > 1.0f) // This the clipping, scaled to 1.0, desired max
{ {
workingDataI[kk] /= mag; float32_t power = workingDataI[kk]*workingDataI[kk] + workingDataQ[kk]*workingDataQ[kk];
workingDataQ[kk] /= mag; float32_t mag = sqrtf(power);
if(mag > 1.0f) // This the clipping, scaled to 1.0, desired max
{
workingDataI[kk] /= mag;
workingDataQ[kk] /= mag;
}
powerSum0 += power; // For measuring amount of clipping
if(mag > maxMag0)
maxMag0 = mag;
} }
powerSum0 += power; // For measuring amount of clipping
if(mag > maxMag0)
maxMag0 = mag;
}
// clipperIn needs spectrum control, so LP filter it. Same filter coeffs as Weaver. // clipperIn needs spectrum control, so LP filter it. Same filter coeffs as Weaver.
// Both BW of the signal and the sample rate have been doubled. // Both BW of the signal and the sample rate have been doubled.
arm_fir_f32(&firInstClipperI, workingDataI, workingDataI, nC); arm_fir_f32(&firInstClipperI, workingDataI, workingDataI, nC);
arm_fir_f32(&firInstClipperQ, workingDataQ, workingDataQ, nC); arm_fir_f32(&firInstClipperQ, workingDataQ, workingDataQ, nC);
// Ready to compensate for filter overshoots // Ready to compensate for filter overshoots
for (int k=0; k<64; k++) for (int k=0; k<64; k++)
{ {
/* ======= Sidebar: Circular 2^n length delay arrays ======== /* ======= Sidebar: Circular 2^n length delay arrays ========
* *
* The length of the array, N, * The length of the array, N,
* must be a power of 2. For example N=2^6 = 64. The minimum * must be a power of 2. For example N=2^6 = 64. The minimum
* delay possible is the trivial case of 0 up to N-1. * delay possible is the trivial case of 0 up to N-1.
* As in C, let i be the index of the N array elements which * As in C, let i be the index of the N array elements which
* would range from 0 to N-1. If p is an integer, that is a power * would range from 0 to N-1. If p is an integer, that is a power
* of 2 also, with p >= n, it can serve as an index to the * of 2 also, with p >= n, it can serve as an index to the
* delay array by "ANDing" it with (N-1). That is, * delay array by "ANDing" it with (N-1). That is,
* i = p & (N-1). It can be convenient if the largest * i = p & (N-1). It can be convenient if the largest
* possible value of the integer p, plus 1, is an integer multiple * possible value of the integer p, plus 1, is an integer multiple
* of the arrray size N, as then the rollover of p will not cause * of the arrray size N, as then the rollover of p will not cause
* a jump in i. For instance, if p is an uint8_t with a maximum * a jump in i. For instance, if p is an uint8_t with a maximum
* value of pmax=255, (pmax+1)/N = (255+1)/64 = 4, which is an * value of pmax=255, (pmax+1)/N = (255+1)/64 = 4, which is an
* integer. This combination will have no problems from rollover * integer. This combination will have no problems from rollover
* of p. * of p.
* *
* The new data point is entered at index p & (N - 1). To * The new data point is entered at index p & (N - 1). To
* achieve a delay of d, the output of the delay array is taken * achieve a delay of d, the output of the delay array is taken
* at index ((p-d) & (N-1)). The index is then incremented by 1. * at index ((p-d) & (N-1)). The index is then incremented by 1.
* ========================================================== */ * ========================================================== */
// Circular delay line for signal to align data with FIR output // Circular delay line for signal to align data with FIR output
// Put I & Q data points into the delay arrays // Put I & Q data points into the delay arrays
osDelayI[indexOsDelay & 0X3F] = workingDataI[k]; osDelayI[indexOsDelay & 0X3F] = workingDataI[k];
osDelayQ[indexOsDelay & 0X3F] = workingDataQ[k]; osDelayQ[indexOsDelay & 0X3F] = workingDataQ[k];
// Remove 64 points delayed data from line and save for later // Remove 64 points delayed data from line and save for later
delayedDataI[k] = osDelayI[(indexOsDelay - 63) & 0X3F]; delayedDataI[k] = osDelayI[(indexOsDelay - 63) & 0X3F];
delayedDataQ[k] = osDelayQ[(indexOsDelay - 63) & 0X3F]; delayedDataQ[k] = osDelayQ[(indexOsDelay - 63) & 0X3F];
indexOsDelay++; indexOsDelay++;
// Delay line to allow strongest envelope to be used for compensation // Delay line to allow strongest envelope to be used for compensation
// We only need to look ahead 1 or behind 1, so delay line of 4 is OK. // We only need to look ahead 1 or behind 1, so delay line of 4 is OK.
// Enter latest envelope to delay array // Enter latest envelope to delay array
osEnv[indexOsEnv & 0X03] = sqrtf( osEnv[indexOsEnv & 0X03] = sqrtf(
workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k]); workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k]);
// look over the envelope curve to find the max // look over the envelope curve to find the max
float32_t eMax = 0.0f; float32_t eMax = 0.0f;
if(osEnv[(indexOsEnv) & 0X03] > eMax) // One just entered if(osEnv[(indexOsEnv) & 0X03] > eMax) // One just entered
eMax = osEnv[(indexOsEnv) & 0X03]; eMax = osEnv[(indexOsEnv) & 0X03];
if(osEnv[(indexOsEnv-1) & 0X03] > eMax) // Entered one before if(osEnv[(indexOsEnv-1) & 0X03] > eMax) // Entered one before
eMax = osEnv[(indexOsEnv-1) & 0X03]; eMax = osEnv[(indexOsEnv-1) & 0X03];
if(osEnv[(indexOsEnv-2) & 0X03] > eMax) // Entered one before that if(osEnv[(indexOsEnv-2) & 0X03] > eMax) // Entered one before that
eMax = osEnv[(indexOsEnv-2) & 0X03]; eMax = osEnv[(indexOsEnv-2) & 0X03];
if(eMax < 1.0f) if(eMax < 1.0f)
eMax = 1.0f; // Below clipping region eMax = 1.0f; // Below clipping region
indexOsEnv++; indexOsEnv++;
// Clip the signal to 1.0. -2 allows 1 look ahead on signal. // Clip the signal to 1.0. -2 allows 1 look ahead on signal.
float32_t eCorrectedI = osDelayI[(indexOsDelay - 2) & 0X3F] / eMax; float32_t eCorrectedI = osDelayI[(indexOsDelay - 2) & 0X3F] / eMax;
float32_t eCorrectedQ = osDelayQ[(indexOsDelay - 2) & 0X3F] / eMax; float32_t eCorrectedQ = osDelayQ[(indexOsDelay - 2) & 0X3F] / eMax;
// Filtering is linear, so we only need to filter the difference between // Filtering is linear, so we only need to filter the difference between
// the signal and the clipper output. This needs less filtering, as the // the signal and the clipper output. This needs less filtering, as the
// difference is many dB below the signal to begin with. Hershberger 2014 // difference is many dB below the signal to begin with. Hershberger 2014
diffI[k] = osDelayI[(indexOsDelay - 2) & 0X3F] - eCorrectedI; diffI[k] = osDelayI[(indexOsDelay - 2) & 0X3F] - eCorrectedI;
diffQ[k] = osDelayQ[(indexOsDelay - 2) & 0X3F] - eCorrectedQ; diffQ[k] = osDelayQ[(indexOsDelay - 2) & 0X3F] - eCorrectedQ;
} // End, for k=0 to 63 } // End, for k=0 to 63
// Filter the differences, osFilter has 129 taps and 64 delay // Filter the differences, osFilter has 129 taps and 64 delay
arm_fir_f32(&firInstOShootI, diffI, diffI, nC); arm_fir_f32(&firInstOShootI, diffI, diffI, nC);
arm_fir_f32(&firInstOShootQ, diffQ, diffQ, nC); arm_fir_f32(&firInstOShootQ, diffQ, diffQ, nC);
// Do the overshoot compensation // Do the overshoot compensation
for(int k=0; k<64; k++) for(int k=0; k<64; k++)
{ {
workingDataI[k] = delayedDataI[k] - gainCompensate*diffI[k]; workingDataI[k] = delayedDataI[k] - gainCompensate*diffI[k];
workingDataQ[k] = delayedDataQ[k] - gainCompensate*diffQ[k]; workingDataQ[k] = delayedDataQ[k] - gainCompensate*diffQ[k];
} }
} // End CESSB processing
// Finally interpolate to 48 or 96 ksps. Data is in workingDataI[k] // Finally interpolate to 48 or 96 ksps. Data is in workingDataI[k]
// and is 64 samples for audio 48 ksps. // and is 64 samples for audio 48 ksps.

@ -39,6 +39,7 @@
* These times are for a 48 ksps rate, for which about 2667 microseconds * These times are for a 48 ksps rate, for which about 2667 microseconds
* are available. * are available.
*/ */
// Rev 14Oct24 Added on/off via cessbProcessing. Tnx KF5N.
#ifndef _radioCESSBtransmit_f32_h #ifndef _radioCESSBtransmit_f32_h
#define _radioCESSBtransmit_f32_h #define _radioCESSBtransmit_f32_h
@ -92,6 +93,16 @@ public:
//setBlockLength(128); Always default 128 //setBlockLength(128); Always default 128
} }
// A "setter" and "getter" methods. If cessbProcessing==false, CESSB processing is bypassed.
// This is intended for digital modes. Greg KF5N August 16 2024
void setProcessing(bool cessbActive) {
cessbProcessing = cessbActive;
}
bool getProcessing(void) {
return cessbProcessing;
}
// Sample rate starts at default 44.1 ksps. That will work. Filters // Sample rate starts at default 44.1 ksps. That will work. Filters
// are designed for 48 and 96 ksps, however. This is a *required* // are designed for 48 and 96 ksps, however. This is a *required*
// function at setup(). // function at setup().
@ -200,7 +211,8 @@ private:
struct levels levelData; struct levels levelData;
audio_block_f32_t *inputQueueArray_f32[1]; audio_block_f32_t *inputQueueArray_f32[1];
float32_t freqW = 1350.0f; // Set here and not changed float32_t freqW = 1350.0f; // Set here and not changed
bool cessbProcessing = true; // If false, CESSB processing is bypassed.
// Greg KF5N August 16 2024
// Input/Output is at 48 (or later 96 ksps). Weaver generation is at 12 ksps. // Input/Output is at 48 (or later 96 ksps). Weaver generation is at 12 ksps.
// Clipping and overshoot processing is at 24 ksps. // Clipping and overshoot processing is at 24 ksps.
// Next line is to indicate that setSampleRateHz() has not executed // Next line is to indicate that setSampleRateHz() has not executed

Loading…
Cancel
Save