用于生成和显示数字信号的C#库

目录

介绍

时间和幅度点

信号参数

数字信号

正弦和余弦信号

矩形信号

锯齿信号

三角形信号

白噪声信号

信号图

测试信号生成库

正弦信号值

使用代码

结论


介绍

数字信号通常用于压缩数据。在一定范围内将模拟信号的所有值存储在某种介质上实际上是不可能的(只需考虑两个实数之间的数字集,无论它们有多接近,都是无限的)。取而代之的是,以固定的时间间隔采集样本,然后存储以供进一步处理。生成和显示数字信号的C#库将按照需要其组成数据结构和功能的顺序进行描述。随附的ZIP文件包含所有代码。

时间和幅度点

数字信号只是在时间(水平)维度上等间隔的时间幅度点序列。以下类将用于跟踪数据点。

/// <summary>Class to encapsulate one data point of a digital signal.
/// </summary>
public class TimeMagnitude
{
   public double Time,      // Time (in seconds) of signal magnitude generation.
                 Magnitude; // Signal magnitude in volts.

   public TimeMagnitude( double time, double magnitude )
   {
      Time = time; Magnitude = magnitude;
   }

   public TimeMagnitude Copy()
   {
      return new TimeMagnitude( this.Time, this.Magnitude );
   }// Copy

}// TimeMagnitude (class)

在某些应用程序中,有必要创建现有TimeMagnitude实例的新副本。这就是函数Copy的目的。

信号参数

为了生成数字信号,必须假设一些样本是从相应模拟信号的幅度中以规则间隔的时间间隔获取的。为正确生成信号,必须指定几个参数,如以下类中所定义。大多数参数是不言自明的,有些参数,如频率和周期是多余的,因为如果一个已知,另一个可以从中计算出来。该samplingFactor参数对于正确生成用于进一步处理的数字信号数据点至关重要。

/// <summary>Class to encapsulate the parameters of a digital signal.
/// </summary>
public class SignalParameters
{
   public double amplitude, offset,   // Signal amplitude and DC offset in volts.
                 frequency,           // Signal frequency in Hertz.
                 period,              // Signal period in seconds.
                 halfPeriod,          // Half of the signal period.
                 samplingFrequency,   // Frequency (in Hertz) for signal generation.
                 frequencyResolution, // Spacing (in Hertz) between adjacent signal values.
                 timeStep;            // Spacing (in seconds) between two adjacent time points.

   public int samplingFactor,         // Factor to multiply by the signal frequency.
              nSamples;               // Number of signal "samples" to be generated.

   /// <summary>Create an instance of signal parameters.
   /// </summary>
   /// <param name="_amplitude">Amplitude of the signal in volts.</param>
   /// <param name="_frequency">Frequency of the signal in hertz (cycles/second).</param>
   /// <param name="_offset">DC offset (in volts) to be added to the amplitude.</param>
   /// <param name="_nSamples">Number of "samples" to be generated for the signal.</param>
   /// <param name="_samplingFactor">Factor to multiply by {_frequency} for signal-processing.
   /// </param>
   public SignalParameters( double _amplitude, double _frequency,                // Mandatory.
                            double _offset, int _nSamples, int _samplingFactor ) // Optional.
   {
      double one = (double)1.0;

      amplitude =_amplitude;
      frequency = _frequency;
      samplingFactor = _samplingFactor;
      nSamples = _nSamples;
      offset = _offset;

      period = one / frequency;
      halfPeriod = period / (double)2.0;
      samplingFrequency = (double)samplingFactor * frequency;
      frequencyResolution = samplingFrequency / (double)nSamples;

      timeStep = one / samplingFrequency;
   }
}// SignalParameters (class)

数字信号

数字信号的生成涉及在等间隔的时间点对其相应模拟信号的幅度进行采样的模拟。为了生成任意数量的连续数据点,信号生成函数可以实现为枚举器,从一个调用到下一个调用保持其状态。要生成的信号是正弦、余弦、方波、锯齿波、三角波和白噪声。

数字信号由GeneratingFn类的函数成员生成。该类的数据成员如下:

public SignalParameters parameters; // Parameters of a signal to be generated.

public List<TimeMagnitude> sineSignalValues, cosineSignalValues,
                                 squareSignalValues, sawtoothSignalValues,
                                 triangleSignalValues, whiteNoiseValues;

public List<double> zeroCrossings; // Signal crossings on the time (X) axis.

private SignalPlot sineSignal, cosineSignal, squareSignal,
                   sawtoothSignal, triangleSignal, whiteNoise;

构造函数创建该类的一个实例,初始化信号参数、将包含信号值的列表、过零列表和信号图:

/// <summary>Create an instance of a signal-generating function.
/// </summary>
/// <param name="_amplitude">Amplitude of the signal in Volts.</param>
/// <param name="_frequency">Frequency of the signal in Hertz (cycles/second).</param>
/// <param name="_offset">DC offset to be added to the magnitude of the signal.</param>
/// <param name="_nSamples">Number of samples to generate for signal-processing.</param>
/// <param name="_samplingFactor">Factor to multiply by the frequency.
/// </param>
public GeneratingFn( double _amplitude, double _frequency, double _offset = 0.0,
                     int _nSamples = 512, int _samplingFactor = 32 )
{
   parameters = new SignalParameters( _amplitude, _frequency,                // Mandatory 
                                                                             // arguments.
                                      _offset, _nSamples, _samplingFactor ); // Optional 
                                                                             // arguments.

   sineSignalValues = new List<TimeMagnitude>();
   cosineSignalValues = new List<TimeMagnitude>();
   squareSignalValues = new List<TimeMagnitude>();
   sawtoothSignalValues = new List<TimeMagnitude>();
   triangleSignalValues = new List<TimeMagnitude>();
   whiteNoiseValues = new List<TimeMagnitude>();

   sineSignal = new SignalPlot( "sine-signal", SignalShape.sine );
   sineSignal.Text = "Sine signal plot";
   cosineSignal = new SignalPlot( "cosine-signal", SignalShape.cosine );
   cosineSignal.Text = "Cosine signal plot";
   squareSignal = new SignalPlot( "square-signal", SignalShape.square,
                                   SignalContinuity.discontinuous );
   squareSignal.Text = "Square signal plot";
   sawtoothSignal = new SignalPlot( "sawtooth-signal", SignalShape.sawtooth,
                                     SignalContinuity.discontinuous );
   sawtoothSignal.Text = "Sawtooth signal plot";
   triangleSignal = new SignalPlot( "triangle-signal", SignalShape.triangle );
   triangleSignal.Text = "Triangle signal plot";
   whiteNoise = new SignalPlot( "white-noise signal", SignalShape.whiteNoise );
   whiteNoise.Text = "White noise plot";
}// GeneratingFn

在某些应用程序中,从TimeMagnitude元素列表中收集幅度很方便,只需按如下方式完成:

/// <summary>Collect the magnitudes from a list of {TimeMagnitude} elements.
/// </summary>
/// <param name="tmList">List of (time, magnitude) elements.</param>
/// <returns>List of magnitudes.
/// </returns>
public double[] Magnitudes( List<TimeMagnitude> tmList )
{
   double[] mags = null;

   if ( tmList != null )
   {
      int n = tmList.Count;

      mags = new double[ n ];
      for ( int i = 0; i < n; ++i )
      {
         mags[ i ] = tmList[ i ].Magnitude;
      }
   }
   return mags;
}// Magnitudes 

构造函数GeneratingFn及其public函数成员由驱动程序或用户程序调用。这将在本文后面通过测试控制台应用程序进行说明。信号生成函数被重复调用任意次数。每次调用它们时,这些函数都会创建一个新TimeMagnitude元素,将其添加到相应的列表中并返回该元素的双倍大小。

正弦和余弦信号

以下枚举器用于生成正弦信号的元素。

/// <summary>Generate the next sine-signal value.
/// </summary>
/// <returns>Current magnitude of the sine signal.
/// </returns>
public IEnumerator<double> NextSineSignalValue()
{
   double angularFreq = 2.0 * Math.PI * parameters.frequency; // w == 2 * pi * f
   double time = 0.0;
   double wt, sinOFwt, magnitude = 0.0;
   TimeMagnitude previousTM = null;
   
   zeroCrossings = new List<double>();
   
   while ( true )
   {
      try
      {
         wt = angularFreq * time;
         sinOFwt = Math.Sin( wt );
         magnitude = parameters.offset + ( parameters.amplitude * sinOFwt );
         
         TimeMagnitude tm = new TimeMagnitude( time, magnitude );
         
         sineSignalValues.Add( tm  );
         CheckZeroCrossing( previousTM, tm );
         previousTM = tm.Copy();
      }
      catch ( Exception exc )
      {
         MessageBox.Show( exc.Message );
         Abort();
      }
      yield return magnitude;
      
      time += parameters.timeStep;
   }
}// NextSineSignalValue

该函数定义为IEnumerator。第一次调用它时,它会初始化它的局部变量,然后进入一个无限循环。如果try-catch子句中的一切都按预期进行,则更新sineSignalValues列表并调用函数CheckZeroCrossing以确定信号是否已穿过时间轴。

/// <summary>If the {magnitude} of the current {TimeMagnitude} element is near 0.0,
///          or if there is a magnitude transition through the time axis from the
///          previous {TimeMagnitude} element to the current {TimeMagnitude}
///          element, then update the {zeroCrossings} list.
/// </summary>
/// <param name="previousTM">Previous {TimeMagnitude} element.</param>
/// <param name="tm">Current {TimeMagnitude} element.
/// </param>
private void CheckZeroCrossing( TimeMagnitude previousTM, TimeMagnitude tm )
{
   if ( UtilFn.NearZero( tm.Magnitude ) )
   {
      zeroCrossings.Add( tm.Time );
   }
   else if ( previousTM != null && MagnitudeTransition( previousTM, tm ) )
   {
      zeroCrossings.Add( previousTM.Time + ( ( tm.Time - previousTM.Time ) / 2.0 ) );
   }
}// CheckZeroCrossing

过零可能以三种可能的方式发生。在最好的情况下,信号幅度可能非常接近0.0。但是,由于双数的精确比较涉及的问题,文件Util_Lib.cs中定义的UtilFn类中的以下实用程序代码用于将double与零进行比较。

// Definitions to deal with zero-comparisons of {double}s.
//
// After Microsoft.Docs "Double.Equals Method".
// https://docs.microsoft.com/en-us/dotnet/api/system.double.equals?view=net-5.0

private static double fraction = (double)0.333333,
                      dTolerance = Math.Abs( fraction * (double)0.00001 ),
                      zero = (double)0.0;
//
// In comparisons, use Math.Abs( {double}1 - {double}2 ) <= {dTolerance} )
// (see function {EQdoubles}).

/// <summary>Determine whether two {double} numbers are "equal".
/// </summary>
/// <param name="d1">Double number.</param>
/// <param name="d2">Double number.
/// </param>
/// <returns>Whether {d1} "equals" {d2}.
/// </returns>
public static bool EQdoubles( double d1, double d2 )
{
   d1 = Math.Abs( d1 );
   d2 = Math.Abs( d2 );
   
   return Math.Abs( d1 - d2 ) <= dTolerance;
}// EQdoubles

/// <summary>Determine whether a {double} is close to {zero}.
/// </summary>
/// <param name="d">{double} to be tested.
/// </param>
/// <returns>Whether {d} is close to {zero}.
/// </returns>
public static bool NearZero( double d )
{
   return EQdoubles( d, zero );
}// NearZero

发生过零的另外两种情况是,当当前TimeMagnitude元素的幅度低于时间轴并且前一个元素的幅度高于该轴时,或者当前一个TimeMagnitude元素的幅度低于时间轴并且电流的大小在这样的轴之上。这些场景由以下函数检查:

/// <summary>Determine whether there is a magnitude transition through the time
///          axis from the previous {TimeMagnitude} element to the current element.
/// </summary>
/// <param name="previousTM">Previous {TimeMagnitude} element.</param>
/// <param name="currentTM">Current {TimeMagnitude} element.</param>
/// <returns>{true} if there was a transition, {false} otherwise.
/// </returns>
private bool MagnitudeTransition( TimeMagnitude previousTM, TimeMagnitude currentTM )
{
   return ( previousTM.Magnitude > 0.0 && currentTM.Magnitude < 0.0 )
          ||
          ( previousTM.Magnitude < 0.0 && currentTM.Magnitude > 0.0 );
}// MagnitudeTransition

检查过零后,更新变量previousTM,函数离开try-catch子句并执行yield return 幅度以返回信号值。

下次调用该函数时,yield返回语句后继续执行,更新局部变量的时间,无限循环继续。观察到,实际上,作为枚举器的函数实现和yield return语句的使用使函数的局部变量表现得像古老的CC++ static变量。这是非常了不起的,因为按照设计,C#不支持static变量。

如果执行到达try-catch子句的catch部分,则发生异常。该函数显示MessageBox异常消息,然后调用Abort函数终止执行。

/// <summary>Abort execution of the 'user'.
/// </summary>
private void Abort()
{
   if ( System.Windows.Forms.Application.MessageLoop )
   {
      // Windows application
      System.Windows.Forms.Application.Exit();
   }
   else
   {
      // Console application
      System.Environment.Exit( 1 );
   }
}// Abort

下一个余弦值的生成以类似的方式完成,通过调用Math.Cos而不是Math.Sin在这里显示。(同样,完整的代码在随附的ZIP文件中。)

矩形信号

矩形信号的生成几乎很简单。唯一的困难是处理垂直不连续性,每次辅助时间变量t接近信号周期(parameters.halfPeriod)的一半时都会出现这种情况。

/// <summary>Generate the next square-signal value.
/// </summary>
/// <returns>Current magnitude of the square signal.
/// </returns>
public IEnumerator<double> NextSquareSignalValue()
{
   double _amplitude = parameters.amplitude,
          magnitude = parameters.offset + _amplitude;
   double time = 0.0, t = 0.0;
   bool updateZeroCrossings = magnitude > (double)0.0;
   
   zeroCrossings = new List<double>();
   
   while ( true )
   {
      try
      {
         TimeMagnitude tm = new TimeMagnitude( time, magnitude );
         
         squareSignalValues.Add( new TimeMagnitude( time, magnitude ) );
      }
      catch ( Exception exc )
      {
         MessageBox.Show( exc.Message );
         Abort();
      }
      yield return magnitude;
      
      time += parameters.timeStep;
      t += parameters.timeStep;
      if ( UtilFn.NearZero( t - parameters.halfPeriod ) )
      {
         _amplitude = -_amplitude; // Vertical discontinuity.
         t = 0.0;
         if ( updateZeroCrossings )
         {
            zeroCrossings.Add( time );
         }
      }
      magnitude = parameters.offset + _amplitude;
   }
}// NextSquareSignalValue

除了处理垂直不连续性之外,矩形信号的枚举器遵循与正弦和余弦信号的枚举器相同的逻辑。

锯齿信号

锯齿信号是通过重复一条倾斜的直线产生的,其方程为:

其中m是斜率,by-axis纵坐标。除了处理垂直不连续性的部分之外,相应枚举器的实现也很简单。

/// <summary>Generate the next sawtooth-signal value.
/// </summary>
/// <returns>Current magnitude of the sawtooth signal.
/// </returns>
public IEnumerator<double> NextSawtoothSignalValue()
{
   /*
    * A sawtooth signal is generated by repeating a sloped straight
    * line, whose equation is
    *
    *                y = m * t + b
    *
    * where {m} is the slope and {b} is the y-axis ordinate.
   */
   double m = 10.0 / parameters.period,
          b = -parameters.amplitude;
   double time = 0.0, t = 0.0;
   double magnitude = 0.0;
   TimeMagnitude previousTM, tm;
   
   zeroCrossings = new List<double>();
   
   while ( true )
   {
      previousTM = tm = null;
      try
      {
         magnitude = parameters.offset + ( m * t + b );
         
         tm = new TimeMagnitude( time, magnitude );
         
         sawtoothSignalValues.Add( tm );
         CheckZeroCrossing( previousTM, tm );
         previousTM = tm.Copy();
      }
      catch ( Exception exc )
      {
         MessageBox.Show( exc.Message );
         Abort();
      }
      yield return magnitude;
      
      if ( UtilFn.NearZero( t - parameters.period ) )
      {
         t = 0.0;
         if ( tm.Magnitude > (double)0.0 )
         {
            zeroCrossings.Add( time ); // Vertical discontinuity.
         }
      }
      time += parameters.timeStep;
      t += parameters.timeStep;
   }
}// NextSawtoothSignalValue

观察锯齿信号的垂直不连续性出现在信号周期的倍数处。

三角形信号

三角形信号可以看作是锯齿信号的两条镜像斜线,一条上升线用于周期的前半部分,下降线用于周期的后半部分。

/// <summary>Generate the next triangle-signal value.
/// </summary>
/// <returns>Current magnitude of the triangle signal.
/// </returns>
public IEnumerator<double> NextTriangleSignalValue()
{
   /*
    * A triangle signal consists of mirrored sloped straight lines,
    * which can be obtained using part of the code for a sawtooth signal.
   */
   double m = 10.0 / parameters.period,
          b = -parameters.amplitude;
   double time = 0.0, t = 0.0;
   double magnitude = 0.0;
   int j = 0;
   TimeMagnitude previousTM, tm;
   
   tm = previousTM = null;
   bool mirror = false; // No mirroring.
   
   zeroCrossings = new List<double>();
   
   while ( true )
   {
      try
      {
         if ( !mirror ) // Line with ascending slope.
         {
            magnitude = parameters.offset + ( m * t + b );
            
            tm = new TimeMagnitude( time, magnitude );
            
            triangleSignalValues.Add( tm );
            ++j;
         }
         else // Mirroring: line with descending slope.
         {
            if ( j > 0 )
            {
               magnitude = triangleSignalValues[ --j ].Magnitude;
               
               tm = new TimeMagnitude( time, magnitude );
               
               triangleSignalValues.Add( tm );
            }
         }
      }
      catch ( Exception exc )
      {
         MessageBox.Show( exc.Message );
         Abort();
      }
      CheckZeroCrossing( previousTM, tm );
      previousTM = tm.Copy();
      yield return magnitude;
      
      if ( UtilFn.NearZero( t - parameters.halfPeriod ) )
      {
         mirror = true; // Start mirroring.
      }
      if ( UtilFn.NearZero( t - parameters.period ) )
      {
         t = 0.0;
         j = 0;
         mirror = false; // Stop mirroring.
      }
      time += parameters.timeStep;
      t += parameters.timeStep;
    }
}// NextTriangleSignalValue

白噪声信号

白噪声是一种随机信号。它的数据点根据从随机数生成器获得的值随机出现。为了或多或少均匀地分布数据点,可以使用两个随机数生成器:一个用于幅度,另一个用于它们的符号。

/// <summary>Generate the next white-noise value.
/// </summary>
/// <returns>Current value of white noise.
/// </returns>
public IEnumerator<double> NextWhiteNoiseSignalValue()
{
   double magnitude = 0.0, time = 0.0, sign;
   Random magRand, signRand;
   
   magRand = new Random();             // Magnitude random number generator.
   signRand = new Random();            // Random number generator for signal sign.
   
   zeroCrossings = new List<double>(); // This list will remain empty.
   
   while ( true )
   {
      sign = ( signRand.Next( 10 ) > 5 ) ? 1.0 : -1.0;
      magnitude = parameters.offset
                  + sign * ( magRand.NextDouble() * parameters.amplitude );
                  
      whiteNoiseValues.Add( new TimeMagnitude( time, magnitude ) );
      
      yield return magnitude;
      
      time += parameters.timeStep;
   }
}// NextWhiteNoiseSignalValue

此函数初始化零交叉列表,但从不向其中插入元素。原因是白噪声不能被认为对应于正弦、余弦、矩形、锯齿或三角形等函数,其幅度值遵循一定的趋势。

信号图

GeneratingFn类构造函数使用SignalPlot类实例初始化一些private数据成员,这些实例用于显示Windows窗体,显示生成信号的数据点。此类在文件SignalPlot.cs中定义。有两种枚举,一种用于指定信号的连续性,另一种用于指定信号的形状。

public enum SignalContinuity { continuous, discontinuous };
public enum SignalShape { sine, cosine, square, sawtooth, triangle, whiteNoise };

用于创建Windows窗体实例以绘制信号的类的数据成员和构造函数定义如下:

public partial class SignalPlot : Form
{
   private string description;         // Windows-form title.
   private SignalShape shape;
   private SignalContinuity continuity;
   private Bitmap bmp;
   private int xAxis_Y,                // Y coordinate of x-axis (middle of bmp.Height).
               sigMin_Y,               // Minimum Y coordinate of a signal
                                       // (has nothing to do with the actual signal value).
               sigMax_Y;               // Maximum Y coordinate of a signal
                                       // (has nothing to do with the actual signal value).
   private Graphics gr;
   private Font drawFont;
   private StringFormat drawFormat;
   private int iScale,                 // Scaling factor for plotting signals.
               iScaleDIV2;
   private double dScale;              // {double} of {iScale}.

   private int nextParameter_Y;        // Y coordinate to draw {deltaY}
                                       // and {timeStep} in function {DrawDelta_Y_X}.

   public SignalPlot( string _description, SignalShape _shape,
                      SignalContinuity _continuity = SignalContinuity.continuous )
   {
      InitializeComponent();

      bmp = new Bitmap( pictureBox1.Width, pictureBox1.Height );

      pictureBox1.Image = bmp;
      gr = Graphics.FromImage( bmp );
      gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
      gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
      gr.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
      gr.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
      gr.Clear( Color.Transparent );

      drawFont = new System.Drawing.Font( "Calibri", 10,
                                          FontStyle.Regular, GraphicsUnit.Point );
      drawFormat = new StringFormat();
      description = _description;
      shape = _shape;
      continuity = _continuity;
      xAxis_Y = bmp.Height / 2;        // Y coordinate of x-axis
      iScale = 10;                     // Arbitrary scaling factor.
      iScaleDIV2 = iScale / 2;
      dScale = (double)iScale;
   }// SignalPlot (constructor)

GeneratingFn类的构造函数使用SignalPlot类的实例初始化几个private数据成员(sineSignalcosineSignalsquareSignalsawtoothSignaltriangleSignalwhiteNoiseSignal 。这些实例可用于通过以下调用SignalPlot.Plot函数的函数绘制生成的信号。

public void PlotSineSignal()
{
   sineSignal.Plot( parameters, sineSignalValues );
}// PlotSineSignal

public void PlotCosineSignal()
{
   cosineSignal.Plot( parameters, cosineSignalValues );
}// PlotSineSignal

public void PlotSquareSignal()
{
   squareSignal.Plot( parameters, squareSignalValues, SignalContinuity.discontinuous );
}// PlotSquareSignal

public void PlotSawtoothSignal()
{
   sawtoothSignal.Plot
   ( parameters, sawtoothSignalValues, SignalContinuity.discontinuous );
}// PlotSawtoothSignal

/// <summary>
/// By definition, a triangle signal is discontinuous because the derivative (slope)
/// of the function at the peaks does not exist. However, the discontinuity at
/// the peaks is not as sharp (i.e., vertical) as in a square signal or a sawtooth
/// signal. Hence, the third argument to function {triangleSignal.Plot} is left as
/// {SignalContinuity.continuous}.
/// </summary>
public void PlotTriangleSignal()
{
   triangleSignal.Plot( parameters, triangleSignalValues );
}// PlotTriangleSignal

/// <summary>
/// By definition, a white noise signal is completely discontinuous because it is
/// made up by random points on the amplitude vs. time scales. However, the
/// discontinuities are not as sharp (i.e., vertical) as in a square signal or a
/// sawtooth signal. Therefore, the third argument to function {whiteNoise.Plot}
/// is left as {SignalContinuity.continuous}.
/// </summary>
public void PlotWhiteNoiseSignal()
{
   whiteNoiseSignal.Plot( parameters, whiteNoiseValues );
}// PlotWhiteNoiseSignal

绘制信号(SignalPlot.Plot)的函数非常简单。它将信号的参数、时间幅度点列表和信号的连续性作为参数。

/// <summary>Plot a list of {TimeMagnitude} points.
/// </summary>
/// <param name="parameters">Parameters of the signal to be plotted.</param>
/// <param name="list">List containing the (time, magnitude) points.</param>
/// <param name="continuity">Continuity of the signal.
/// </param>
public void Plot( SignalParameters parameters, List<TimeMagnitude> list,
                  SignalContinuity continuity = SignalContinuity.continuous )
{
   int n, m;
   
   if ( list == null || ( n = list.Count ) == 0 )
   {
      MessageBox.Show(
         String.Format( "No {0} values to plot", description ) );
   }
   else
   {
      int x, deltaX, currY, nextY;
      
      // Increasing signal-magnitude values are drawn from the
      // bottom of the {Bitmap} to its top.
      sigMax_Y = 0;
      sigMin_Y = bmp.Height;
      
      Draw_X_axis();
      Draw_Y_axis();
      
      drawFormat.FormatFlags = StringFormatFlags.DirectionRightToLeft;
      
      DrawParameters( parameters, shape );
      
      deltaX = this.Width / n;
      x = 0;
      m = n - 2;
      
      drawFormat.FormatFlags = StringFormatFlags.DirectionVertical;
      
      for ( int i = 0; i < n; ++i )
      {
         int iScaledMag = ScaledMagnitude( list[ i ], dScale );
         
         currY = xAxis_Y - iScaledMag;
         
         if ( currY > sigMax_Y )
         {
            sigMax_Y = currY;
         }
         if ( currY < sigMin_Y )
         {
            sigMin_Y = currY;
         }
         if ( x >= bmp.Width )
         {
            break;
         }
         bmp.SetPixel( x, currY, Color.Black );
         
         if ( UtilFn.IsDivisible( list[ i ].Time, parameters.period ) )
         {
            string label = String.Format( "___ {0:0.0000}", list[ i ].Time );
            SizeF size = gr.MeasureString( label, drawFont );
            
            gr.DrawString( label, drawFont, Brushes.Red,
                           new Point( x, bmp.Height - (int)size.Width ),
                           drawFormat );
         }
         if ( continuity == SignalContinuity.discontinuous && i <= m )
         {
            int iP1ScaledMag = ScaledMagnitude( list[ i + 1 ], dScale );
            
            nextY = xAxis_Y - iP1ScaledMag;
            
            if ( x > 0 && ( shape == SignalShape.square || 
                            shape == SignalShape.sawtooth ) )
            {
               if ( i < m )
               {
                  CheckVerticalDiscontinuity( x, currY, nextY );
               }
               else // i == m
               {
                  DrawVerticalDiscontinuity( x + deltaX, currY );
               }
            }
         }
         x += deltaX;
      }
      Draw_Y_axisNotches( parameters );
      this.ShowDialog();                // Display form in modal mode.
   }
}// Plot

该函数确定Y坐标的最大值和最小值,绘制信号参数,为缩放的幅度数据点设置像素,并在X(时间)坐标处绘制标签,这些坐标可以被信号的周期整除,如通过调用文件Util_Lib.cs中的实用程序函数。

/// <summary>Determine whether a double is divisible by another double.
/// </summary>
/// <param name="x">Numerator of division.
/// </param>
/// <param name="y">Denominator of division.
/// </param>
/// <returns>Whether {x} is divisible by {y}.
/// </returns>
public static bool IsDivisible( double x, double y )
{
   return Math.Abs( ( ( Math.Round( x / y ) * y ) - x ) ) <= ( 1.0E-9 * y );
}// IsDivisible

在文件Util_Lib.cs中使用(两次)代码的原因是作者在其他应用程序中使用了此类文件中的函数。该文件包含与数字信号的生成和绘图无关的附加功能。

SignalPlot.Plot函数调用的函数几乎是不言自明的。其中大部分(Draw_X_axisDraw_Y_axisDrawParametersScaledMagnitudeDraw_Y_axisNotchesDrawNotch)不会在文章中描述。

函数DrawDelta_Y_X负责绘制信号的幅度和时间步长。时间步长是采样频率的倒数,它是采样因子和信号频率的乘积。这些参数对于在其他应用中正确处理数字信号至关重要。

/// <summary>Draw the {amplitude} and the {timeStep} from the
///          parameters of a signal.
/// </summary>
/// <param name="parameters">Signal parameters.
/// </param>
private void DrawDelta_Y_X( SignalParameters parameters )
{
   // There are 11 notches on the Y axis.
   
   string delta_Y_X_str = String.Format( "deltaY: {0:00.000} V, time step: {1:0.00000} sec",
                                         parameters.amplitude / 5.0, parameters.timeStep );
                                         
   drawFormat.FormatFlags = StringFormatFlags.DirectionRightToLeft;
   SizeF size = gr.MeasureString( delta_Y_X_str, drawFont );
   
   int x = (int)size.Width + 8;
   
   Point point = new Point( x, nextParameter_Y );
   
   gr.DrawString( delta_Y_X_str, drawFont, Brushes.Red, point, drawFormat );
}// DrawDelta_Y_X

两个有趣的函数是绘制矩形和锯齿信号的垂直不连续性的函数。在到达矩形或锯齿信号的末端之前,有必要检查是否必须绘制不连续性。此外,在矩形信号的情况下,必须向上或向下绘制不连续性。对于锯齿信号,不连续性总是下降。

/// <summary>Conditionally draw the discontinuity of a square or sawtooth signal.
/// </summary>
/// <param name="x">Position on the x axis (time).</param>
/// <param name="currY">Current position in the y dimension (units of magnitude).</param>
/// <param name="nextY">Next position in the y dimension (units of magnitude).
/// </param>
private void CheckVerticalDiscontinuity( int x, int currY, int nextY )
{
   if ( x >= bmp.Width )
   {
      return;
   }
   int discLength = Math.Abs( currY - nextY );
   
   if ( discLength > iScaleDIV2 )
   {
      int y;
      
      if ( currY < nextY )
      {
         for ( y = currY; y <= nextY; ++y ) // Discontinuity going down.
         {
            bmp.SetPixel( x, y, Color.Black );
         }
      }
      else // nextY < currY, Discontinuity going up.
      {
         for ( y = currY; y >= nextY; --y )
         {
            bmp.SetPixel( x, y, Color.Black );
         }
      }
   }
}// CheckVerticalDiscontinuity

在矩形或锯齿信号的末端,无条件地绘制不连续性。

/// <summary>Draw the vertical discontinuity at the end of a square or sawtooth signal.
/// </summary>
/// <param name="x">Position on the x axis (time).</param>
/// <param name="currY">Current position in the y dimension (units of magnitude).
/// </param>
private void DrawVerticalDiscontinuity( int x, int currY )
{
   if ( x >= bmp.Width )
   {
      return;
   }
   int y;
   
   if ( currY < sigMax_Y )
   {
      for ( y = currY; y <= sigMax_Y; ++y )
      {
         bmp.SetPixel( x, y, Color.Black );
      }
   }
   else if ( currY > sigMin_Y )
   {
      for ( y = sigMin_Y; y <= currY; ++y )
      {
         bmp.SetPixel( x, y, Color.Black );
      }
   }
}// DrawVerticalDiscontinuity

测试信号生成库

可以编写一个简单的控制台应用程序来测试信号生成库。此应用程序的代码位于附件ZIP文件中的TestSignalGenLib目录中的Program.cs文件中。Program类定义了两个与文件相关的私有变量,用于将发送到控制台应用程序命令提示符窗口的输出写入。这些变量在应用程序的Main函数中初始化如下:

fs = new FileStream( @"..\..\_TXT\out.txt", FileMode.Create );
sw = new StreamWriter( fs );

唯一的public global变量是genFn。该Main函数将此变量绑定到GeneratingFn类的实例,然后定义枚举器以生成信号。

IEnumerator<double> Sine = genFn.NextSineSignalValue();
IEnumerator<double> Cosine = genFn.NextCosineSignalValue();
IEnumerator<double> Square = genFn.NextSquareSignalValue();
IEnumerator<double> Sawtooth = genFn.NextSawtoothSignalValue();
IEnumerator<double> Triangle = genFn.NextTriangleSignalValue();
IEnumerator<double> WhiteNoise = genFn.NextWhiteNoiseSignalValue();

所有信号发生器都被调用固定次数,以在命令提示符窗口中枚举和显示信号值。然后,显示时间步长和过零。最后,将信号值绘制在Windows窗体中。例如,以下代码对应于正弦信号的情况。

int n = 512;
string signalName;

signalName = "Sine";
EnumerateValues( signalName, Sine, genFn.sineSignalValues, n );
DisplayTimeStepAndZeroCrossings( genFn, signalName );
genFn.PlotSineSignal();

在执行结束时,文件@" ..\..\_TXT\out.txt "包含发送到控制台应用程序命令提示符窗口的所有文本输出。为简洁起见,此处仅显示信号的第一个和最后一个周期。为了便于参考,对TimeMagnitude数据点和零交叉点进行了编号。

正弦信号值

 

 0  0.0000   0.0000     1  0.0003   0.9755     2  0.0006   1.9134     3  0.0009   2.7779
  4  0.0013   3.5355     5  0.0016   4.1573     6  0.0019   4.6194     7  0.0022   4.9039
  8  0.0025   5.0000     9  0.0028   4.9039    10  0.0031   4.6194    11  0.0034   4.1573
 12  0.0038   3.5355    13  0.0041   2.7779    14  0.0044   1.9134    15  0.0047   0.9755
 16  0.0050   0.0000

. . .

496  0.1550   0.0000   497  0.1553  -0.9755   498  0.1556  -1.9134   499  0.1559  -2.7779
500  0.1562  -3.5355   501  0.1566  -4.1573   502  0.1569  -4.6194   503  0.1572  -4.9039
504  0.1575  -5.0000   505  0.1578  -4.9039   506  0.1581  -4.6194   507  0.1584  -4.1573
508  0.1587  -3.5355   509  0.1591  -2.7779   510  0.1594  -1.9134   511  0.1597  -0.9755   

构造函数GeneratingFn设置的时间步长:0.00031

GeneratingFn.NextSineSignalValue发现零交叉点:

 0  0.0000    1  0.0050    2  0.0052    3  0.0100    4  0.0150    5  0.0200    6  0.0250
 7  0.0300    8  0.0350    9  0.0400   10  0.0450   11  0.0500   12  0.0550   13  0.0600
14  0.0650   15  0.0652   16  0.0700   17  0.0702   18  0.0750   19  0.0752   20  0.0800
21  0.0802   22  0.0850   23  0.0852   24  0.0900   25  0.0902   26  0.0950   27  0.0952
28  0.1000   29  0.1002   30  0.1050   31  0.1052   32  0.1100   33  0.1102   34  0.1150
35  0.1152   36  0.1200   37  0.1202   38  0.1250   39  0.1252   40  0.1300   41  0.1302
42  0.1350   43  0.1352   44  0.1400   45  0.1402   46  0.1450   47  0.1452   48  0.1500

49 0.1502 50 0.1550 51 0.1552

在命令提示符窗口中显示信号值和过零后,应用程序将显示信号图,如下图所示:

表单以模态模式显示(通过调用函数SignalGenLib.Plot中的Form.ShowDialog)。作为第二个示例,下图显示了500-Hz正弦信号的图。下图列出了过零:

GeneratingFn.NextSineSignalValue发现零交叉点:

 0  0.0000    1  0.0010    2  0.0020    3  0.0030    4  0.0040    5  0.0050    6  0.0060
 7  0.0070    8  0.0080    9  0.0090   10  0.0100   11  0.0110   12  0.0120   13  0.0130
14  0.0140   15  0.0150   16  0.0160   17  0.0170   18  0.0180   19  0.0190   20  0.0200
21  0.0210   22  0.0220   23  0.0230   24  0.0240   25  0.0250   26  0.0260   27  0.0270
28  0.0280   29  0.0290   30  0.0300   31  0.0310

请注意,即使100 Hz500 Hz正弦信号看起来相同,但它们并不是因为时间轴标记具有不同的值。此外,由于它们的频率不同,第一个信号穿过时间轴52次,而第二个信号穿过时间轴32次。

当通过单击右上角的十字关闭Windows窗体时,程序会运行类似的代码来生成余弦、矩形波、锯齿波、三角行波和白噪声信号的图,其参数与用于生成正弦信号的参数相同。每次显示信号图时,必须关闭它才能生成并显示下一个。以下两幅图显示了方噪声和白噪声信号的图。

 

命令提示窗口指示在白噪声情况下没有过零。这是因为白噪声与所有其他离散信号不同,其相邻点遵循有规律的趋势。

作为另一个示例,下图显示了一个100Hz三角形信号,其幅度为6伏,直流偏移为2.5伏,由代码生成:

genFn = new GeneratingFn( 6.0, 100.0, 2.5 );

IEnumerator<double> Triangle = genFn.NextTriangleSignalValue();

signalName = "Triangle";
EnumerateValues( signalName, Triangle, genFn.triangleSignalValues, n );
DisplayTimeStepAndZeroCrossings( genFn, signalName );
genFn.PlotTriangleSignal();

  

使用代码

附加的ZIP文件包含三个目录中的八个文件。Util_Lib目录包含文件UtilFn.csSignalGenLib目录包含文件GeneratingFn.csSignalParameters.csSignalPlot.csSignalPlot.Designer.csSignalPlot.resxTimeMagnitude.csTestSignalGenLib目录包含文件Program.cs

创建一个目录Generation of Digital Signals。在Visual Studio中,单击文件,选择新建,然后单击项目。选择Class Library,指定创建的目录为LocationNameUtil_Lib。在解决方案资源管理器窗格中,右键单击Class1.cs,选择重命名并将类名更改为UtilFn.cs。将附件ZIP文件中的“UtilFn.cs”文件中代码复制到创建的UtilFn.cs文件中。单击构建,然后单击构建解决方案。构建应该成功。单击文件,然后单击关闭解决方案

重复前面的步骤以创建一个名为SignalGenLib的库。在解决方案资源管理器窗格中,右键单击Class1.cs,选择Rename并将类名更改为GeneratingFn.cs。右击References,选择Add Reference,点击.NET选项卡,选择System.Windows.Forms,点击OK;做同样的事情来添加对System.Drawing的引用。右键单击引用,选择添加引用,单击Browse选项卡,导航到Util_Lib\bin\Debug目录,选择Util_Lib.dll并点击确定。将刚刚创建的GeneratingFn.cs文件的全部内容替换为附加的GeneratingFn.cs文件的内容。选择文件并单击全部保存

将文件SignalParameters.csSignalPlot.csSignalPlot.Designer.csSignalPlot.resxTimeMagnitude.cs复制到SignalGenLib目录。对于每个复制的文件,在解决方案资源管理器窗格中,右键单击SignalGenLib,选择添加,单击现有项,选择要添加的文件并单击添加。错误列表窗格应指示0个错误、0个警告和0个消息。单击构建选项卡,然后单击文件,然后点击关闭解决方案

点击File,选择New,点击Project,选择Console Application,名称为TestSignalGenLib。在解决方案资源管理器中,右键单击References,单击Add Reference,单击Browse选项卡,导航到SignalGenLib\bin\Debug目录,选择SignalGenLib.dll并单击OK ”。单击File ”,然后单击Save All。添加对SignalGenLib.dll”的引用。将文件的全部内容替换为附加的Program.cs文件的全部内容。单击构建,然后单击构建解决方案。构建应该成功。在“TestSignalGenLib”目录下创建一个名为“_TXT”的目录。单击调试,然后单击不调试就开始。控制台应用程序应该生成信号值并为每个信号显示一个图。关闭当前Windows表单的绘图以生成下一个信号的值并显示其绘图。关闭白噪声图后,按任意键退出控制台应用程序。

结论

本文介绍了C#库的设计、实现和测试,以生成和显示一些常见的数字信号。信号生成函数被实现为枚举器,通过使用yield return,它实际上在连续调用之间维护其局部变量的状态。该库将再次用于测试数字双二阶带通滤波器的实现。

https://www.codeproject.com/Articles/5326502/A-Csharp-Library-to-Generate-and-Display-Digital-S

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值