private void oscTimer_Action(object arg) { if (!oscTimerLock) { oscTimerLock = true; oscDispatcher.Invoke( new Action(() => { try { if (adcDataValid) { oscDataCanvas.Children.Clear(); int xNmb = AdcData.GetLength(1); double xStep = contentWidth / (xNmb - 1); for (int i = 0; i < xNmb - 1; i++) { Line l = new Line(); l.X1 = i * (xStep); l.X2 = (i + 1) * xStep; l.Y1 = (0.5 - (AdcData[2, i] / (double)0x8000)) * contentHeight; l.Y2 = (0.5 - (AdcData[2, i + 1] / (double)0x8000)) * contentHeight; l.StrokeThickness = 2.0; l.Stroke = Brushes.Green; oscDataCanvas.Children.Add(l); } adcDataValid = false; } } catch { }; })); } oscTimerLock = false; }
double sampFreq; //Sampling Frequency #endregion Fields #region Methods public AdcData DoQACalculations(double[] fourierHisto, int channel) { int numPoints = 0; //Keeping track of pts in FFT int ithData = 0; //For help looping through FFT int numHarm = -1; //Counting how many harmonics found Dictionary<double, double> theData = new Dictionary<double, double>(); //Keeping track of level of each frequency double binCont; // Help with filling FFT double sum2 = 0.0; // For Calculating SINAD double bin1, binMax = 0.0; // For Manipulating output of FFT double[] aHarmFreq = new double[150]; //Aliased Harmonics double fourierFreq, tempFreq = 0; //Help with looping through dictionary double noDistSum2 = 0; AdcData qadata = new AdcData(); qadata.outFreq = new string[5] { "", "", "", "", "" }; //----Find the Second Largest Value----// bin1 = fourierHisto[0]; // bin1 has the largest value, however fourierHisto[0] = 0; // that isn't actually part of our data. binMax = fourierHisto.Max(); // The max value, our signal, is actually fourierHisto[0] = bin1; // the second largest value, now in binMax. chart1.Series[channel].Points.AddXY(0, 20 * Math.Log10(Math.Abs(fourierHisto[1]) / Math.Abs(binMax))); // Only added to make plot look nicer //----Normalize all the points to the maximum----// for (int i = 1; i < (sampLength / 2); i++) { binCont = Math.Abs(fourierHisto[i]) / Math.Abs(binMax); // Normalizing to the maximum theData[20 * Math.Log10(binCont)] = ((i) * sampFreq / sampLength); chart1.Series[channel].Points.AddXY((i) * sampFreq / sampLength, 20 * Math.Log10(binCont)); if (binCont != 1.0) { // This is all points except the signal numPoints++; sum2 += binCont * binCont; } } noDistSum2 += sum2; // This is so we can make parallel calculations subtracting harmonics //----Find Relevant Harmonics----// // Harmonics occur at |+/- (k*sampFreq) +/- (n*signalFreq)| // There will be overcounting if we let both k and n go +/- // Keeping k positive, and for k=0 keeping n positive,thedata there is no overcounting for (int k = 0; k <= 20; k++) { for (int ord = -55; ord <= 55; ord++) { if (k > 0 || ord >= 0) tempFreq = Math.Abs(k * sampFreq + ord * freq); if (tempFreq != freq && tempFreq < sampFreq / 2 && tempFreq > 0) { // Limits range of interesting harmonics numHarm++; aHarmFreq[numHarm] = tempFreq; } } } //----Printing the Highest Readings and Corresponding Freq----// var amps = theData.Keys.ToList(); amps.Sort(); foreach (double normAmp in amps) { fourierFreq = theData[normAmp]; if ((ithData > (numPoints - 6)) && (ithData < numPoints)) { qadata.outFreq[numPoints - 1 - ithData] = String.Format(" {0:F2}, {1:F2}", fourierFreq, normAmp); } if (ithData == numPoints - 1) { qadata.sfdr = normAmp; // The SFDR is distance from signal (normalized to zero) to second largest // value. This will be the normalized amplitude of the second last point // since the last point is the signal (at 0 dB). } // This will remove the relevant harmonics so we can calculate SNR for (int i = 0; i <= numHarm; i++) { if (Math.Abs(fourierFreq - aHarmFreq[i]) < 0.00001) { noDistSum2 -= Math.Pow(10, normAmp / 10); } } ithData++; } //----Calculating Characteristic Variables----// qadata.sinadNoHarm = 10 * Math.Log10(noDistSum2); qadata.sinad = 10 * Math.Log10(sum2); qadata.enob = (-qadata.sinad - 1.76) / 6.02; return qadata; }
private void AddDataToChart(AdcData[] adcData) { // Add QA information to the chart area for (int isig = 0; isig < numchannels; isig++) { var fftInfo = new Legend { BackColor = Color.Transparent, InsideChartArea = chart1.ChartAreas[isig].Name, LegendStyle = LegendStyle.Column, Name = "Info Legend " + (isig + 1) }; for (int i = 0; i < 11; i++) fftInfo.CustomItems.Add(new LegendItem()); fftInfo.CustomItems[0].Cells.Add(new LegendCell("")); fftInfo.CustomItems[1].Cells.Add(new LegendCell(String.Format("SFDR: {0:F2}", adcData[isig].sfdr))); fftInfo.CustomItems[2].Cells.Add(new LegendCell(String.Format("SINAD: {0:F2}", adcData[isig].sinad))); fftInfo.CustomItems[3].Cells.Add(new LegendCell(String.Format("SNR: {0:F2}", adcData[isig].sinadNoHarm))); fftInfo.CustomItems[4].Cells.Add(new LegendCell(String.Format("ENOB: {0:F2}", adcData[isig].enob))); fftInfo.CustomItems[5].Cells.Add(new LegendCell("Spur Freq. [MHz]:")); for (int i = 6; i < 11; i++) { fftInfo.CustomItems[i].Cells.Add(new LegendCell(String.Format("{0:F2}", adcData[isig].outFreq[i - 6]))); } Font infoFont = new Font(FontFamily.GenericSansSerif, 9, FontStyle.Italic); for (int i = 0; i < 11; i++) { fftInfo.CustomItems[i].Cells[0].Font = infoFont; fftInfo.CustomItems[i].Cells[0].Alignment = ContentAlignment.MiddleLeft; } // Add everything to the original chart chart1.Legends.Add(fftInfo); } }
public AdcData[] FFT3(double infreq, int channels = 4) { numchannels = channels; freq = infreq; chart1 = new Chart(); chart1.Size = new System.Drawing.Size(690, 595); //----Setting Sampling Frequency----// switch ((int)(5 * freq)) //To bypass C# rules about switching doubles { case 25: sampFreq = 40; freq = 5.0065104167; break; case 90: sampFreq = 40.113166485310122; break; case 50: sampFreq = 40; freq = 9.967447917; break; case 10: sampFreq = 40.0000000667; freq = 1.9986979167; break; case 1: sampFreq = 40.0; break; default: sampFreq = 40.0; break; } InitializeChart(); double[][] signalHisto = ReadData(); FormatChart("sig"); chart1.SaveImage(filePath + "signal.png", ChartImageFormat.Png); /*for (int i = 3; i > 0; i--) { chart1.Series.Remove(chart1.Series[i]); chart1.ChartAreas.Remove(chart1.ChartAreas[i]); chart1.Titles.Remove(chart1.Titles[i+1]); } chart1.Series[0].MarkerSize = 3; chart1.SaveImage(filePath + "signal_large.png", ChartImageFormat.Png);*/ //---- Start to Take FFTs----// AdcData[] adcData = new AdcData[4]; for (int i = 0; i < 4; i++) adcData[i].outFreq = new string[5] { "", "", "", "", "" }; // Reset Charts InitializeChart(); // Start doing FFTs for each channel for (int isig = 0; isig < numchannels; isig++) { double[] fourierHisto = DoFFT(signalHisto[isig]); adcData[isig] = DoQACalculations(fourierHisto, isig); } //End FFT FormatChart("FFT"); AddDataToChart(adcData); chart1.Size = new Size(1000, 1000); chart1.SaveImage(filePath + "fft.png", ChartImageFormat.Png); // Save the FFT Charts return adcData; }
private void WriteResult(AdcData[] adcData) { if (adcData.Length != 4) throw new Exception("Invalid input to WriteResult."); bool underperf = false; bool defect = false; for (int i = 0; i < 4; i++) { if (adcData[i].enob < (enobBound * 0.9) || (1 - chipControl1.adcs[i].dynamicRange / 4096.0) > calBound) defect = true; else if (adcData[i].enob < enobBound) underperf = true; resultBox.Update(() => resultBox.Text += "Channel " + (i + 1) + Environment.NewLine + " ENOB = " + Math.Round(adcData[i].enob,4) + Environment.NewLine + " Range = " + chipControl1.adcs[i].dynamicRange + Environment.NewLine, true); } if (!underperf && !defect) { resultBox.Update(() => { resultBox.BackColor = Color.Green; resultBox.Text += "Chip fully operational"; }); } else { if (defect){ resultBox.Update(() => { resultBox.BackColor = Color.Red; resultBox.Text += "Defective Chip"; }); }else{ resultBox.Update(() => { resultBox.BackColor = Color.Yellow; resultBox.Text += "Chip Underperforming"; }); } } }