Hello,
I have been working with an application that plots real-time serial data using a FIFO buffer. I have started programming around the ECG-Monitor example as this is exactly what I am creating.
I have a device that broadcasts real-time ECG data via Bluetooth (HC-05), to be exact. I have paired the device and opened a SerialPort in my program to receive the data. My sampling rate is 256 Hz.
When I used a text file to simulate ECG data, it works perfectly well. However, when I use real-time data, there is a delay in charting that increases as the time increases. An easier way to understand this is, the chart continues plotting for a significant period of time even after I have switched off my hardware device.
I have then come to the conclusion that my data is being received at the rate that I want it to, but the plotting gets delayed at an increasing rate as the time increases.
I am currently using the Direct-X rendering type as this gives me a very smooth plot. I receive the data via SerialPort, write to an array and then to the FIFO buffer.
I’m attaching my code for the same.
namespace SciChart.Examples.Examples.SeeFeaturedApplication.ECGMonitor
{
public class ECGMonitorViewModel : BaseViewModel
{
private Timer _timer;
private IXyDataSeries<double, double> _series0;
public static double[] _sourceData = new double[50000];
private int _currentIndex = 0;
private int _totalIndex = 0;
private DoubleRange _yVisibleRange;
private bool _isBeat;
private int _heartRate;
private bool _lastBeat;
private DateTime _lastBeatTime;
private ICommand _startCommand;
private ICommand _stopCommand;
private const double WindowSize = 5.0;
private const int TimerInterval = 40;
public static int counter = 0;
public static SerialPort mySerialPort=new SerialPort("COM3",9600);
public ECGMonitorViewModel()
{
mySerialPort.Open();
ECGMonitorViewModel.mySerialPort.WriteLine("A");
ECGMonitorViewModel.mySerialPort.WriteLine("F");
_series0 = new XyDataSeries<double, double>() { FifoCapacity = 2460, AcceptsUnsortedData = true };
YVisibleRange = new DoubleRange(-20, 500);
_startCommand = new ActionCommand(OnExampleEnter);
_stopCommand = new ActionCommand(OnExampleExit);
}
public ICommand StartCommand
{
get
{
return _startCommand;
}
}
public ICommand StopCommand
{
get
{
return _stopCommand;
}
}
public IXyDataSeries<double, double> EcgDataSeries
{
get
{
return _series0;
}
set
{
_series0 = value;
OnPropertyChanged("EcgDataSeries");
}
}
public DoubleRange YVisibleRange
{
get
{
return _yVisibleRange;
}
set
{
_yVisibleRange = value;
OnPropertyChanged("YVisibleRange");
}
}
public bool IsBeat
{
get
{
return _isBeat;
}
set
{
if (_isBeat != value)
{
_isBeat = value;
OnPropertyChanged("IsBeat");
}
}
}
public int HeartRate
{
get { return _heartRate; }
set
{
_heartRate = value;
OnPropertyChanged("HeartRate");
}
}
public void OnExampleExit()
{
if (_timer != null)
{
_timer.Stop();
_timer.Elapsed -= TimerElapsed;
_timer = null;
}
}
public void OnExampleEnter()
{
_timer = new Timer(TimerInterval) { AutoReset = true };
_timer.Elapsed += TimerElapsed;
_timer.Start();
}
private void TimerElapsed(object sender, EventArgs e)
{
lock (this)
{
for (int i = 0; i < 10; i++)
{
AppendPoint(250);
}
if ((DateTime.Now - _lastBeatTime).TotalMilliseconds < 120) return;
IsBeat = _series0.YValues[_series0.Count - 3] > 120 ||
_series0.YValues[_series0.Count - 5] > 120 ||
_series0.YValues[_series0.Count - 8] > 120;
if (IsBeat && !_lastBeat)
{
HeartRate = (int)(60.0 / (DateTime.Now - _lastBeatTime).TotalSeconds);
_lastBeatTime = DateTime.Now;
}
}
}
private void AppendPoint(double sampleRate)
{
if (_currentIndex >= _sourceData.Length)
{
_currentIndex = 0;
}
double voltage = _sourceData[_currentIndex];
double time = _totalIndex / sampleRate %10;
if(time==0.00)
{
voltage = double.NaN;
}
_series0.Append(time, voltage);
_lastBeat = IsBeat;
_currentIndex++;
_totalIndex++;
}
public static void mySerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
while (mySerialPort.BytesToRead > 0)
{
int b;
b = mySerialPort.ReadByte();
_sourceData[counter] = b;
counter++;
}
}
}
}
namespace SciChart.Examples.Examples.SeeFeaturedApplication.ECGMonitor
{
public partial class ECGMonitorView : UserControl
{
public ECGMonitorView()
{
InitializeComponent();
ECGMonitorViewModel.mySerialPort.DataReceived += new SerialDataReceivedEventHandler(ECGMonitorViewModel.mySerialPort_DataReceived);
}
}
}
The xaml code is as follows,
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:SciChart.Examples.Examples.SeeFeaturedApplication.ECGMonitor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="http://schemas.abtsoftware.co.uk/scichart"
xmlns:s3D="http://schemas.abtsoftware.co.uk/scichart3D" xmlns:XamlRasterizer="clr-namespace:SciChart.Drawing.XamlRasterizer;assembly=SciChart.Drawing" x:Class="SciChart.Examples.Examples.SeeFeaturedApplication.ECGMonitor.ECGMonitorView"
d:DesignHeight="400"
d:DesignWidth="600"
mc:Ignorable="d">
<UserControl.Resources>
<Style TargetType="{x:Type s:RenderSurfaceBase}">
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="5"
ShadowDepth="0"
Color="#FFB3E8F6" />
</Setter.Value>
</Setter>
</Style>
<local:BeatToScaleConverter x:Key="BeatToScaleConverter" />
</UserControl.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding StartCommand}" />
</i:EventTrigger>
<i:EventTrigger EventName="Unoaded">
<i:InvokeCommandAction Command="{Binding StopCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<s:SciChartSurface RenderPriority="Low" MaxFrameRate="25" AutoRangeOnStartup="False">
<s:SciChartSurface.RenderableSeries>
<s:FastLineRenderableSeries DataSeries="{Binding EcgDataSeries}"
Stroke="#FFB3E8F6"
StrokeThickness="2" />
</s:SciChartSurface.RenderableSeries>
<s:SciChartSurface.YAxis>
<s:NumericAxis AxisTitle="Voltage (mV)"
DrawMinorGridLines="True"
MaxAutoTicks="5"
VisibleRange="{Binding YVisibleRange, Mode=TwoWay}" />
</s:SciChartSurface.YAxis>
<s:SciChartSurface.XAxis>
<s:NumericAxis AxisTitle="Seconds (s)" TextFormatting="0.000s" VisibleRange="0, 10" AutoRange="Never"/>
</s:SciChartSurface.XAxis>
<s:SciChartSurface.RenderSurface>
<s3D:Direct3D10RenderSurface/>
</s:SciChartSurface.RenderSurface>
</s:SciChartSurface>
<StackPanel Margin="30,30" Orientation="Horizontal">
<StackPanel.Effect>
<DropShadowEffect BlurRadius="5"
ShadowDepth="0"
Color="#FFB3E8F6" />
</StackPanel.Effect>
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas x:Name="layer1"
Width="20"
Height="20"
Margin="12,34,10,0">
<Canvas.RenderTransform>
<ScaleTransform CenterX="-6"
CenterY="-6"
ScaleX="{Binding IsBeat, Converter={StaticResource BeatToScaleConverter}}"
ScaleY="{Binding IsBeat, Converter={StaticResource BeatToScaleConverter}}" />
</Canvas.RenderTransform>
<Path Data="m 0 0 c -4 -4 -8.866933 -10.79431 -10 -15 0 0 0 -5 5 -5 5 0 5 5 5 5 0 0 0 -5 5 -5 5 0 5.242535 4.02986 5 5 -1 4 -6 11 -10 15 z" Fill="#FFB0E6F4" />
</Canvas>
</Grid>
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Top"
FontFamily="ArialBlack"
FontSize="36"
FontWeight="Bold"
Foreground="#FFB0E6F4"
Text="{Binding HeartRate}" />
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Top"
FontFamily="ArialBlack"
FontSize="36"
FontWeight="Bold"
Foreground="#FFB0E6F4"
Text="BPM" />
</StackPanel>
<TextBlock Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
FontSize="9"
FontStyle="Italic"
Foreground="#FFB0E6F4"/>
</Grid>
I’ve attached pictures of what my program currently does. I have also attached a file with sample data in case anybody wants to test the program. I have also tried building in release mode and that doesn’t help, either.
The only issue is the delay in plotting of real-time data. Otherwise, the graphing and rendering is really smooth. As I am a complete beginner to this, can somebody help with me with what I might have done wrong?
Thanks a ton,
Jaivignesh Jayakumar
- Jaivignesh Jayakumar asked 8 years ago
- last active 8 years ago