Hi bmillier,
Yes, you are right, the samples would be the same, when in MONO mode, leading to zero degrees phase difference, thus 90 degrees phase error. Therefore I believe that a MONO/STEREO issue is not the problem. What I observe if the phenomenon occurs -measured by the IQ correction code- are differing phase error differences, ranging from 0.15 to 0.39 (radians), so there is a range of 8 to 23 degrees phase error (determined by the correction algorithm, NOT directly measured, so the algorithm could introduce severe errors in this "measurement"). When the phenomenon does not occur, phase differences are well under 1 degree with my hardware (which has selected sampling capacitors)! By the way, the IQ correction code also works on band noise, you do not need a proper signal!
This is the IQ correction code that I developed from the paper by Moseley & Slump (very nice paper,
http://doc.utwente.nl/66726/1/moseley.pdf, the criterion for me to choose this algorithm was mainly the detailed description of the implementation in this paper rather than the efficiency of the correction. However I guess 65dB rejection is very well done and is well suited for this kind of SDR. Many commercial SDRs would be happy, if they achieved 50dB! And many (if not most) commercial >1000$-standalone-SDRs do not have automatic IQ correction ;-)):
Code:
// float_buffer_L = I
// float_buffer_R = Q
if(MOSELEY)
{ // Moseley, N.A. & C.H. Slump (2006): A low-complexity feed-forward I/Q imbalance compensation algorithm.
// in 17th Annual Workshop on Circuits, Nov. 2006, pp. 158–164.
// http://doc.utwente.nl/66726/1/moseley.pdf
if (twinpeaks_tested == 2)
{
twinpeaks_counter++;
Serial.print("twinpeaks counter = "); Serial.println(twinpeaks_counter);
}
if(twinpeaks_counter > 100) // wait for the system to settle
{
twinpeaks_tested = 0;
twinpeaks_counter = 0;
Serial.println("twinpeaks_counter ready to test IQ balance !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
for(i = 0; i < n_para; i++)
{
teta1 += sign(float_buffer_L[i]) * float_buffer_R[i]; // eq (34)
teta2 += sign(float_buffer_L[i]) * float_buffer_L[i]; // eq (35)
teta3 += sign (float_buffer_R[i]) * float_buffer_R[i]; // eq (36)
IQ_auto_counter++;
}
teta1 = -0.01 * (teta1 / n_para) + 0.99 * teta1_old; // eq (34) and first order lowpass
teta2 = 0.01 * (teta2 / n_para) + 0.99 * teta2_old; // eq (35) and first order lowpass
teta3 = 0.01 * (teta3 / n_para) + 0.99 * teta3_old; // eq (36) and first order lowpass
if(teta2 != 0.0)// prevent divide-by-zero
{
M_c1 = teta1 / teta2; // eq (30)
}
else
{
M_c1 = 0.0;
}
float32_t help = (teta2 * teta2);
if(help > 0.0)// prevent divide-by-zero
{
help = (teta3 * teta3 - teta1 * teta1) / help; // eq (31)
}
if (help > 0.0)// prevent sqrtf of negative value
{
M_c2 = sqrtf(help); // eq (31)
}
else
{
M_c2 = 1.0;
}
// Test and fix of the "twinpeak syndrome"
// which occurs sporadically and can -to our knowledge- only be fixed
// by a reset of the codec
// It can be identified by a non-existing mirror rejection,
// so I & Q have essentially the same phase
// We use this to identify the syndrome and reset the codec accordingly:
// calculate phase between I & Q
if(teta3 != 0.0 && twinpeaks_tested == 0) // prevent divide-by-zero
// twinpeak_tested = 2 --> wait for system to warm up
// twinpeak_tested = 0 --> go and test the IQ phase
// twinpeak_tested = 1 --> tested, verified, go and have a nice day!
{
Serial.println("HERE");
// Moseley & Slump (2006) eq. (33)
// this gives us the phase error between I & Q in radians
float32_t phase_IQ = asinf(teta1 / teta3);
Serial.print("asinf = "); Serial.println(phase_IQ);
if ((phase_IQ > 0.15 || phase_IQ < -0.15) && codec_restarts < 5)
// threshold lowered, so we can be really sure to have IQ phase balance OK
// threshold of 22.5 degrees phase shift == PI / 8 == 0.3926990817
// hopefully your hardware is not so bad, that its phase error is more than 22 degrees ;-)
// if it is that bad, adjust this threshold to maybe PI / 7 or PI / 6
{
reset_codec();
Serial.println("CODEC RESET");
twinpeaks_tested = 2;
codec_restarts++;
// TODO: we should set a maximum number of codec resets
// and print out a message, if twinpeaks remains after the
// 5th reset for example --> could then be a severe hardware error !
if(codec_restarts >= 4)
{
// PRINT OUT WARNING MESSAGE
Serial.println("Tried four times to reset your codec, but still IQ balance is very bad - hardware error ???");
}
}
else
{
twinpeaks_tested = 1;
Serial.println("IQ phase balance is OK, so enjoy radio reception !");
}
}
teta1_old = teta1;
teta2_old = teta2;
teta3_old = teta3;
IQ_auto_counter = 0;
// first correct Q and then correct I --> this order is crucially important!
for(i = 0; i < BUFFER_SIZE * N_BLOCKS; i++)
{ // see fig. 5
float_buffer_R[i] += M_c1 * float_buffer_L[i];
}
// see fig. 5
arm_scale_f32 (float_buffer_L, M_c2, float_buffer_L, BUFFER_SIZE * N_BLOCKS);
}
By the way, I do not use any additional interrupts apart from the audio library interrupts that account for acquisition of the audio samples for my audio queue-object! So, I do not think, interrupts can be an issue with your FT800 display. Encoders, buttons etc. are called while the audio samples are still being acquired for the next processing FFT-iFFT-step in the background by the queue object in the main loop, but without interrupts.
Yes, a bigger display would be great given the high amount of elements on the display. But my final goal is a low power application. My radio runs with a simple USB power bank of 10000mAh, which I opened and installed some filtering (common mode choke, caps etc.) and put it under a blank copper PCB. On the other side of the copper PCB I mounted the QSD and the Teensy plus audio board (preselector to be installed . . .). That simple USB power bank for 15 EUROs can keep the radio running for more than 24 hours, probably longer, power supply is 5V, 200-250milliamps including a small loudspeaker plus amp. I suspect your TFT would soak the power bank to zero in a few hours ;-). But it´s very nice to have a large screen, I certainly agree!
Have fun with the Teensy,
Frank DD4WH