目录
介绍
时间和幅度点
信号参数
数字信号
正弦和余弦信号
矩形信号
锯齿信号
三角形信号
白噪声信号
信号图
测试信号生成库
正弦信号值
使用代码
结论
- 下载 Util_Lib - 1.4 KB
- 下载 TestSignalGenLib - 1.4 KB
- 下载 SignalGenLib - 10.5 KB
数字信号通常用于压缩数据。在一定范围内将模拟信号的所有值存储在某种介质上实际上是不可能的(只需考虑两个实数之间的数字集,无论它们有多接近,都是无限的)。取而代之的是,以固定的时间间隔采集样本,然后存储以供进一步处理。生成和显示数字信号的C#库将按照需要其组成数据结构和功能的顺序进行描述。随附的ZIP文件包含所有代码。
时间和幅度点数字信号只是在时间(水平)维度上等间隔的时间幅度点序列。以下类将用于跟踪数据点。
/// Class to encapsulate one data point of a digital signal.
///
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参数对于正确生成用于进一步处理的数字信号数据点至关重要。
/// Class to encapsulate the parameters of a digital signal.
///
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.
/// Create an instance of signal parameters.
///
/// Amplitude of the signal in volts.
/// Frequency of the signal in hertz (cycles/second).
/// DC offset (in volts) to be added to the amplitude.
/// Number of "samples" to be generated for the signal.
/// Factor to multiply by {_frequency} for signal-processing.
///
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 sineSignalValues, cosineSignalValues,
squareSignalValues, sawtoothSignalValues,
triangleSignalValues, whiteNoiseValues;
public List zeroCrossings; // Signal crossings on the time (X) axis.
private SignalPlot sineSignal, cosineSignal, squareSignal,
sawtoothSignal, triangleSignal, whiteNoise;
构造函数创建该类的一个实例,初始化信号参数、将包含信号值的列表、过零列表和信号图:
/// Create an instance of a signal-generating function.
///
/// Amplitude of the signal in Volts.
/// Frequency of the signal in Hertz (cycles/second).
/// DC offset to be added to the magnitude of the signal.
/// Number of samples to generate for signal-processing.
/// Factor to multiply by the frequency.
///
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();
cosineSignalValues = new List();
squareSignalValues = new List();
sawtoothSignalValues = new List();
triangleSignalValues = new List();
whiteNoiseValues = new List();
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元素列表中收集幅度很方便,只需按如下方式完成:
/// Collect the magnitudes from a list of {TimeMagnitude} elements.
///
/// List of (time, magnitude) elements.
/// List of magnitudes.
///
public double[] Magnitudes( List 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元素,将其添加到相应的列表中并返回该元素的双倍大小。
正弦和余弦信号以下枚举器用于生成正弦信号的元素。
/// Generate the next sine-signal value.
///
/// Current magnitude of the sine signal.
///
public IEnumerator 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();
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以确定信号是否已穿过时间轴。
/// 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.
///
/// Previous {TimeMagnitude} element.
/// Current {TimeMagnitude} element.
///
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 ) 0.0 );
}// MagnitudeTransition
检查过零后,更新变量previousTM,函数离开try-catch子句并执行yield return 幅度以返回信号值。
下次调用该函数时,yield返回语句后继续执行,更新局部变量的时间,无限循环继续。观察到,实际上,作为枚举器的函数实现和yield return语句的使用使函数的局部变量表现得像古老的C和C++ static变量。这是非常了不起的,因为按照设计,C#不支持static变量。
如果执行到达try-catch子句的catch部分,则发生异常。该函数显示MessageBox异常消息,然后调用Abort函数终止执行。
/// Abort execution of the 'user'.
///
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)的一半时都会出现这种情况。
/// Generate the next square-signal value.
///
/// Current magnitude of the square signal.
///
public IEnumerator 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();
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纵坐标。除了处理垂直不连续性的部分之外,相应枚举器的实现也很简单。
/// Generate the next sawtooth-signal value.
///
/// Current magnitude of the sawtooth signal.
///
public IEnumerator 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();
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
观察锯齿信号的垂直不连续性出现在信号周期的倍数处。
三角形信号三角形信号可以看作是锯齿信号的两条镜像斜线,一条上升线用于周期的前半部分,下降线用于周期的后半部分。
/// Generate the next triangle-signal value.
///
/// Current magnitude of the triangle signal.
///
public IEnumerator 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();
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
白噪声是一种随机信号。它的数据点根据从随机数生成器获得的值随机出现。为了或多或少均匀地分布数据点,可以使用两个随机数生成器:一个用于幅度,另一个用于它们的符号。
/// Generate the next white-noise value.
///
/// Current value of white noise.
///
public IEnumerator 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(); // 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
///
/// 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}.
///
public void PlotTriangleSignal()
{
triangleSignal.Plot( parameters, triangleSignalValues );
}// PlotTriangleSignal
///
/// 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}.
///
public void PlotWhiteNoiseSignal()
{
whiteNoiseSignal.Plot( parameters, whiteNoiseValues );
}// PlotWhiteNoiseSignal
绘制信号(SignalPlot.Plot)的函数非常简单。它将信号的参数、时间幅度点列表和信号的连续性作为参数。
/// Plot a list of {TimeMagnitude} points.
///
/// Parameters of the signal to be plotted.
/// List containing the (time, magnitude) points.
/// Continuity of the signal.
///
public void Plot( SignalParameters parameters, List 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 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中的实用程序函数。
/// Determine whether a double is divisible by another double.
///
/// Numerator of division.
///
/// Denominator of division.
///
/// Whether {x} is divisible by {y}.
///
public static bool IsDivisible( double x, double y )
{
return Math.Abs( ( ( Math.Round( x / y ) * y ) - x ) ) = bmp.Width )
{
return;
}
int discLength = Math.Abs( currY - nextY );
if ( discLength > iScaleDIV2 )
{
int y;
if ( currY < nextY )
{
for ( y = currY; y = nextY; --y )
{
bmp.SetPixel( x, y, Color.Black );
}
}
}
}// CheckVerticalDiscontinuity
在矩形或锯齿信号的末端,无条件地绘制不连续性。
/// Draw the vertical discontinuity at the end of a square or sawtooth signal.
///
/// Position on the x axis (time).
/// Current position in the y dimension (units of magnitude).
///
private void DrawVerticalDiscontinuity( int x, int currY )
{
if ( x >= bmp.Width )
{
return;
}
int y;
if ( currY < sigMax_Y )
{
for ( y = currY; y sigMin_Y )
{
for ( y = sigMin_Y; y
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?