目录
介绍
数字信号通常用于压缩数据。在一定范围内将模拟信号的所有值存储在某种介质上实际上是不可能的(只需考虑两个实数之间的数字集,无论它们有多接近,都是无限的)。取而代之的是,以固定的时间间隔采集样本,然后存储以供进一步处理。生成和显示数字信号的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语句的使用使函数的局部变量表现得像古老的C和C++ 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是斜率,b是y-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数据成员(sineSignal、cosineSignal、squareSignal、sawtoothSignal、triangleSignal和whiteNoiseSignal) 。这些实例可用于通过以下调用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_axis、Draw_Y_axis、DrawParameters、ScaledMagnitude、Draw_Y_axisNotches和DrawNotch)不会在文章中描述。
函数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 Hz和500 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.cs。SignalGenLib目录包含文件GeneratingFn.cs、SignalParameters.cs、SignalPlot.cs、SignalPlot.Designer.cs、SignalPlot.resx和TimeMagnitude.cs。TestSignalGenLib目录包含文件Program.cs。
创建一个目录“Generation of Digital Signals”。在Visual Studio中,单击“文件”,选择“新建”,然后单击“项目”。选择“Class Library”,指定创建的目录为“Location”,“Name”为“Util_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.cs”、“SignalPlot.cs”、“SignalPlot.Designer.cs”、“SignalPlot.resx”和“TimeMagnitude.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