【问题描述】有些控制系统中采用独立的方波或脉冲信号协调各设备的动作,这种信号称为“时统信号”,整个系统在时统信号的协调下工作。时统信号的电平幅度(峰峰值)一般在10V左右。 这时,系统开发就需要检测时统信号的周期(频率)。解决这个问题通用方法是采用计数/定时器卡对时统脉冲计数,同时计时,然后根据检测时间和检测到的计时值计算出信号的周期(频率)。如果对软件对时统信号精度要求不高,例如绝对精度为0.1毫秒(100微秒),这时可以利用串行通信端口检测时统信号。
【解决方法】串行通信接口检测时统信号有两种方法:(1)时统信号通过串行接口CTS输入,通过检测CTS引脚的状态变化得出时统信号的周期 (2)时统信号通过串行接口的RxD输入,通过检测串口的Break状态变化得出时统信号的周期。
【递进式均值计算】
利用串口检测时统信号时均使用了SerialPort.PinChanged事件。按MSDN说明,SerialPort类封装了一个内部线程,通过这个线程触发诸如DataReceived、PinChanged事件。众所周知,Windows系统线程调度是非实时性的。例如,使用Thread.Sleep(10)从线程休眠到唤醒的时间并非严格等于10毫秒,而是一个基于10毫秒的随机值。因此,SerialPort内部线程触发DataReceived、PinChanged事件时也会有随机误差。递进式均值计算就是为了消除这个误差。
/// <summary>
/// 周期均值滤波器
/// </summary>
public class CycleMeanValueFilter
{
private double meanValue;
private int index;
public CycleMeanValueFilter()
{
Reset();
}
/// <summary>
/// 均值计算的点数。默认20个点
/// </summary>
public int MaxCount { get; set; } = 20;
/// <summary>
/// 输入,递进方式求均值
/// </summary>
/// <param name="cycleInMicrosecond">微秒为单位的周期</param>
public void Input(long cycleInMicrosecond)
{
index++;
if (index > MaxCount)
{
// 保留上次均值!
index = 2;
}
// 以递进方式求均值
meanValue += (cycleInMicrosecond - meanValue) / index;
}
/// <summary>
/// 获取以微秒为单位的周期均值
/// </summary>
public double Output { get { return meanValue; } }
public void Reset()
{
meanValue = 0;
index = 0;
}
}
【微秒级计时器】
这个计时器用于计算时统信号的周期
using System.Diagnostics;
public class MicrosecondStopwatch
{
static readonly double MicrosecondPerTick = 1000000D / Stopwatch.Frequency;
private readonly Stopwatch stopwatch = new Stopwatch();
public void Start()
{
stopwatch.Start();
}
public void Stop()
{
stopwatch.Stop();
}
public long ElapsedMicroseconds
{
get { return (long)(stopwatch.ElapsedTicks * MicrosecondPerTick); }
}
}
【利用CTS检测时统信号】
using System;
using System.IO.Ports;
class Program
{
static SerialPort Port;
static MicrosecondStopwatch Stopwatch;
static CycleMeanValueFilter Filter;
static long t0 = 0;
static void Main(string[] args)
{
Filter = new CycleMeanValueFilter();
Port = new SerialPort("COM1");
Port.PingChanged += Port_PinChanged;
Port.Open();
StopWatch.Start();
Consoel.ReadLine();
StopWatcher.Stop();
Port.Close();
}
private void Port_PinChanged(object sender, SerialPinChangedEventArgs e)
{
// 只检测CTS信号有效
if (e.EventType == SerialPinChange.CtsChanged && Port.CtsHolding)
{
var t = Stopwatch.ElapsedMicroseconds;
Filter.Input(t - t0);
t0 = t;
Console.WriteLine($"周期:{Filter.Output / 1000:0.00} 毫秒");
}
}
}
【利用BreakState检测时统信号】
using System;
using System.IO.Ports;
class Program
{
static SerialPort Port;
static MicrosecondStopwatch Stopwatch;
static CycleMeanValueFilter Filter;
static long t0 = 0;
static void Main(string[] args)
{
Filter = new CycleMeanValueFilter();
Port = new SerialPort("COM1");
Port.PingChanged += Port_PinChanged;
Port.Open();
StopWatch.Start();
Consoel.ReadLine();
StopWatcher.Stop();
Port.Close();
}
private void Port_PinChanged(object sender, SerialPinChangedEventArgs e)
{
if (e.EventType == SerialPinChange.Break)
{
var t = Stopwatch.ElapsedMicroseconds;
Filter.Input(t - t0);
t0 = t;
Console.WriteLine($"周期:{Filter.Output / 1000:0.00} 毫秒");
}
}
}
【补充电平规范】计算机串口电平规范如下:
- “1”:[-3V, -15V]
- “0”:[+3V, +15V]