understanding FIR SRRC filter with memory implementation in C

I translated the Matlab QPSK receiver/transmitter into real-time C code that I have running on a PlutoSDR platform. QPSK receiver/transmitter

I wrote the square root raised cosine filter for the TX chain and it worked without any issues. I was able to successfully encode my message data and continuously transmit it then receive it and successfully decode it. The code for the root raised cosine filter in the TX chain is below:

static const double filterCoeff[21] = { -0.00045472051487622253, 0.0035368955557498576,
-0.0071456080909122616, 0.0075790619051782785, 0.0021436824272736714,
-0.010610686667249576, 0.0300115539818315, -0.053053433336247971,
-0.075028884954578726, 0.40916871463405252, 0.80373860039798006,
0.40916871463405252, -0.075028884954578726, -0.053053433336247971,
0.0300115539818315, -0.010610686667249576, 0.0021436824272736714,
0.0075790619051782785, -0.0071456080909122616, 0.0035368955557498576,
-0.00045472051487622253 };

void use_rrc_filter(int taps, int siglen, double complex* input, double complex* output, double*
rrc_filter_taps)
{
double complex sum = 0;

for(int i = 0; i < siglen; i++)
{
for(int j = 0; j <= i && j < taps; j++)
{
sum += input[i-j]*rrc_filter_taps[j];
}

output[i] = sum;
sum = 0 + 0*I;
}
}


The number of taps is given by: 10*2 + 1 = 21, where 10 is the filter span in symbols, 2 is the number of samples per symbol. The filter taps were generated by Matlab.

The problem arose when I tried to use this same code for the filter in the RX chain. It did not work. I realized I needed a filter that has "memory" and takes the previously received values into account (non causal). I was able to get MATLAB to generate C code for this filter which worked for me. The code is below:

typedef struct rcRXfilter
{
int PhaseIdx;
int CoeffIdx;
int TapDelayIndex;
double complex Sum;
double complex StatesBuffer[RRC_TAPS-1];
double complex output[FRAME_SIZE*2];
bool isInitialized;
}rcRXfilter;

void initialize_rrc_rx_filt(rcRXfilter* obj)
{
if (obj->isInitialized != 1)
{
obj->CoeffIdx = 0;
obj->PhaseIdx = 0;
obj->TapDelayIndex = 0;
obj->Sum = 0.0;
for (int i = 0; i < 20; i++)
{
obj->StatesBuffer[i] = 0.0;
}

obj->isInitialized = 1;
}
}

void rrc_filt(double complex* input, double complex* output, rcRXfilter* obj)
{
if (obj->isInitialized != 1)
{
initialize_rrc_rx_filt(obj);
}

int i = 0;
int iIdx = 0;
int jIdx = 0;
int outBufIdx = 0;
int curTapIdx = obj->TapDelayIndex;
int phaseIdx = obj->PhaseIdx;
int cffIdx = obj->CoeffIdx;
int maxWindow = (phaseIdx + 1) * 20;

for (iIdx = 0; iIdx < FRAME_SIZE*2; iIdx++)
{
obj->Sum += input[i] * filterCoeff[cffIdx];
cffIdx++;

for (jIdx = curTapIdx + 1; jIdx < maxWindow; jIdx++)
{
obj->Sum += obj->StatesBuffer[jIdx] * filterCoeff[cffIdx];
cffIdx++;
}

for (jIdx = maxWindow - 20; jIdx <= curTapIdx; jIdx++)
{
obj->Sum += obj->StatesBuffer[jIdx] * filterCoeff[cffIdx];
cffIdx++;
}

obj->StatesBuffer[curTapIdx] = input[i];
i++;
curTapIdx += 20;

if (curTapIdx >= 20)
{
curTapIdx -= 20;
}

phaseIdx++;

if (phaseIdx < 1)
{
maxWindow += 20;
}
else
{
obj->output[outBufIdx] = obj->Sum;
outBufIdx++;
obj->Sum = 0.0;
phaseIdx = 0;
cffIdx = 0;
curTapIdx--;

if (curTapIdx < 0)
{
curTapIdx += 20;
}
maxWindow = 20;
}
}

obj->TapDelayIndex = curTapIdx;
obj->CoeffIdx = cffIdx;
obj->PhaseIdx = phaseIdx;

for (i = 0; i < FRAME_SIZE*2; i++)
{
output[i] = obj->output[i];
}
}


Can someone help me understand this code? I know it is performing downsampling but I'm having trouble understanding exactly what it is doing. Any equations illustrating what is happening and/or a block diagram would be ideal. Thank you!

• I don't understand why you say the same filter would not work in the receiver. Both filters have memory, so both will have a start-up transient but should equally work. It is the same filter but you could easily have a different number of samples per symbol between transmitter and receiver (which is fine, and then the coefficients are adjusted accordingly such that the impulse response of the filters match for actual sample time). Debugging code is not something we typically do, but if you write out the equations and block diagram that would be helpful / productive. – Dan Boschen Mar 5 at 23:37
• You are correct. It does indeed work. I must've inadequately tested it before. Thank you for your insight! – yellow_watermelon Mar 6 at 17:31