From d0ef4ee9f9a33d8619cac85e3a55900ae44edb90 Mon Sep 17 00:00:00 2001 From: Youen Toupin Date: Tue, 14 Apr 2015 00:16:48 +0200 Subject: [PATCH] finished basic oscilloscope functionalities (max sampling rate seems to be 38KHz) --- OneWireIO.ino | 27 ++-- SerialMonitor/SerialMonitor/App.xaml.cs | 2 +- SerialMonitor/SerialMonitor/MainWindow.xaml | 15 +- .../SerialMonitor/MainWindow.xaml.cs | 31 ++++ .../SerialMonitor/MainWindowContext.cs | 152 ++++++++++++++---- SerialMonitor/SerialMonitor/MathEx.cs | 21 +++ .../SerialMonitor/SerialMonitor.csproj | 1 + 7 files changed, 204 insertions(+), 45 deletions(-) create mode 100644 SerialMonitor/SerialMonitor/MathEx.cs diff --git a/OneWireIO.ino b/OneWireIO.ino index 925f199..c6bf7b7 100644 --- a/OneWireIO.ino +++ b/OneWireIO.ino @@ -5,12 +5,14 @@ #define OWPin 2 #define InterruptNumber 0 // Must correspond to the OWPin to correctly detect state changes. On Arduino Uno, interrupt 0 is for digital pin 2 -const int SkipSamples = 8; // how many samples we want to skip between two samples we keep (can be used to lower the sampling frequency) -const int BufferSize = 128; +// how many samples we want to skip between two samples we keep (can be used to lower the sampling frequency) +#define SkipSamples 0 + +const int BufferSize = 512; byte buffer1[BufferSize]; byte buffer2[BufferSize]; byte* backBuffer = buffer1; -volatile byte backBufferPos = 0; +volatile short backBufferPos = 0; byte samplesSkipped = SkipSamples; unsigned long backBufferStartTime = micros(); @@ -24,7 +26,7 @@ void setup() digitalWrite(LEDPin, LOW); - attachInterrupt(InterruptNumber,onewireInterrupt,CHANGE); + //attachInterrupt(InterruptNumber,onewireInterrupt,CHANGE); cli();//disable interrupts @@ -36,7 +38,7 @@ void setup() ADMUX |= (1 << REFS0); //set reference voltage ADMUX |= (1 << ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only - ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); //set ADC clock with 128 prescaler- 16mHz/128=125kHz ; 13 cycles for a conversion which means 9600 samples per second + ADCSRA |= (1 << ADPS2) | (0 << ADPS1) | (1 << ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500KHz ; 13 cycles for a conversion which means 38000 samples per second ADCSRA |= (1 << ADATE); //enabble auto trigger ADCSRA |= (1 << ADIE); //enable interrupts when measurement complete ADCSRA |= (1 << ADEN); //enable ADC @@ -44,7 +46,7 @@ void setup() sei();//enable interrupts - Serial.begin(200000); + Serial.begin(400000); } void loop() @@ -52,27 +54,32 @@ void loop() while(backBufferPos < BufferSize / 2) ; cli();//disable interrupts byte* currentBuffer = backBuffer; - unsigned long currentBufferStartTime = backBufferStartTime; - byte currentBufferSize = backBufferPos; + short currentBufferSize = backBufferPos; backBuffer = (backBuffer == buffer1 ? buffer2 : buffer1); backBufferPos = 0; - backBufferStartTime = micros(); sei();//enable interrupts + unsigned long currentBufferStartTime = backBufferStartTime; + backBufferStartTime = micros(); + digitalWrite(LEDPin, LOW); + //Serial.write(currentBuffer, currentBufferSize); oscilloscope.write(currentBuffer, currentBufferSize, currentBufferStartTime); } ISR(ADC_vect) {//when new ADC value ready byte sample = ADCH; //store 8 bit value from analog pin 0 - + + #if SkipSamples > 0 if(samplesSkipped++ < SkipSamples) return; samplesSkipped = 0; + #endif backBuffer[backBufferPos++] = sample; if(backBufferPos >= BufferSize) { // overflow of back buffer, we loose the current sample + digitalWrite(LEDPin, HIGH); backBufferPos = BufferSize - 1; } } diff --git a/SerialMonitor/SerialMonitor/App.xaml.cs b/SerialMonitor/SerialMonitor/App.xaml.cs index ac44671..74db570 100644 --- a/SerialMonitor/SerialMonitor/App.xaml.cs +++ b/SerialMonitor/SerialMonitor/App.xaml.cs @@ -28,7 +28,7 @@ namespace SerialMonitor Serial = new SerialPort(); Serial.PortName = "COM4"; - Serial.BaudRate = 200000; + Serial.BaudRate = 400000; Serial.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(OnDataReceived); diff --git a/SerialMonitor/SerialMonitor/MainWindow.xaml b/SerialMonitor/SerialMonitor/MainWindow.xaml index 98617aa..7cb7936 100644 --- a/SerialMonitor/SerialMonitor/MainWindow.xaml +++ b/SerialMonitor/SerialMonitor/MainWindow.xaml @@ -1,11 +1,18 @@  + Title="Serial Monitor" Height="490.75" Width="923"> - - - + + + + + + + + + + diff --git a/SerialMonitor/SerialMonitor/MainWindow.xaml.cs b/SerialMonitor/SerialMonitor/MainWindow.xaml.cs index 9a6318d..82c9d9f 100644 --- a/SerialMonitor/SerialMonitor/MainWindow.xaml.cs +++ b/SerialMonitor/SerialMonitor/MainWindow.xaml.cs @@ -26,6 +26,37 @@ namespace SerialMonitor var context = new MainWindowContext(this); context.WriteLine("Connecting..."); this.DataContext = context; + + this.MouseWheel += OnMouseWheel; + } + + private void OnMouseWheel(object sender, MouseWheelEventArgs e) + { + MainWindowContext context = (MainWindowContext)this.DataContext; + Point cursorPos = e.GetPosition(context.OscilloscopeCanvas); + if (cursorPos.X < 0 || cursorPos.X > context.OscilloscopeCanvas.ActualWidth || cursorPos.Y < 0 || cursorPos.Y > context.OscilloscopeCanvas.ActualHeight) + return; + + double cursorPosRatio = cursorPos.X / context.OscilloscopeCanvas.ActualWidth; + double cursorTime = context.ViewportStartTime + cursorPosRatio * context.ViewportTimeWidth; + + double newTimeWidth = context.ViewportTimeWidth; + if (e.Delta > 0) + newTimeWidth /= e.Delta * 0.01; + else if (e.Delta < 0) + newTimeWidth *= -e.Delta * 0.01; + + double totalTimeWidth = Math.Max(0.1, context.MaxTime - context.MinTime); + if (newTimeWidth > totalTimeWidth) + newTimeWidth = totalTimeWidth; + + double newStartTime = cursorTime - cursorPosRatio * newTimeWidth; + if (newStartTime < context.MinTime) + newStartTime = context.MinTime; + if (newStartTime + newTimeWidth > context.MaxTime) + newStartTime = context.MaxTime - newTimeWidth; + + context.SetViewport(newStartTime, newTimeWidth); } } } diff --git a/SerialMonitor/SerialMonitor/MainWindowContext.cs b/SerialMonitor/SerialMonitor/MainWindowContext.cs index e329a90..33d40ec 100644 --- a/SerialMonitor/SerialMonitor/MainWindowContext.cs +++ b/SerialMonitor/SerialMonitor/MainWindowContext.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; using System.Windows.Shapes; namespace SerialMonitor @@ -28,11 +30,14 @@ namespace SerialMonitor public void WriteLine(string line) { Lines.Add(line); - if (Lines.Count > 9) + if (Lines.Count > 100) Lines.RemoveAt(0); OnPropertyChanged("ConsoleText"); } + public double SampleDelay { get { return 0.000026; } } + public int MaxSequenceSize { get { return 2048; } } + private class Sequence { public ulong StartTime { get; set; } @@ -46,7 +51,11 @@ namespace SerialMonitor } } } - double ValueToHeight(byte value) { return (double)value + 10; } + double ValueToHeight(double time, byte value) + { + //value = (byte)(Math.Sin(time * 100.0) * 127.0 + 128.0); + return 256.0 - (double)value + 10; + } private SortedSet Sequences = new SortedSet(new Sequence.Comparer()); public IEnumerable Oscilloscope { @@ -54,64 +63,147 @@ namespace SerialMonitor { if(!Sequences.Any()) yield break; - - ulong startTime = Sequences.ElementAt(0).StartTime; foreach (var sequence in Sequences) { - foreach (var line in ConvertToLines(sequence, startTime)) - yield return line; + double seqStartTime = (double)sequence.StartTime/1000000.0; + if (seqStartTime + (double)sequence.Data.Length * SampleDelay > ViewportStartTime && seqStartTime < ViewportStartTime + ViewportTimeWidth) + { + foreach (var line in ConvertToLines(sequence)) + yield return line; + } } } } - private IEnumerable ConvertToLines(Sequence sequence, ulong displayStartTime) + private IEnumerable ConvertToLines(Sequence sequence) { - double sampleDelay = 0.000936; // in seconds - double scale = 10; // in pixels per second + double viewportWidth = OscilloscopeCanvas.ActualWidth; + double scale = viewportWidth / ViewportTimeWidth; // in pixels per second - double pos = (sequence.StartTime - displayStartTime) / 1000000.0 * scale; + ulong displayStartTime = (ulong)(viewportStartTime_ * 1000000.0); + + double pos = ((double)sequence.StartTime - (double)displayStartTime) / 1000000.0 * scale; if (pos > 1000) yield break; - double prevHeight = ValueToHeight(sequence.Data[0]); + double prevPos = pos; + byte minValue = sequence.Data[0]; + byte maxValue = minValue; + int prevIdx = 0; + double prevHeight = ValueToHeight(sequence.StartTime / 1000000.0, minValue); for (int idx = 1; idx < sequence.Data.Length; ++idx) { byte value = sequence.Data[idx]; - var line = new Line(); - line.Stroke = System.Windows.Media.Brushes.LightSteelBlue; - line.X1 = pos; - pos += sampleDelay * scale; - line.X2 = pos; - line.Y1 = prevHeight; - prevHeight = ValueToHeight(value); - line.Y2 = prevHeight; - line.HorizontalAlignment = HorizontalAlignment.Left; - line.VerticalAlignment = VerticalAlignment.Center; - line.StrokeThickness = 1; - yield return line; + pos += SampleDelay * scale; + if (value > maxValue) maxValue = value; + if (value < minValue) minValue = value; + + if (pos > 0 && pos < viewportWidth && pos - prevPos >= 5 || idx == sequence.Data.Length - 1) + { + var line = new Line(); + line.Stroke = System.Windows.Media.Brushes.LightSteelBlue; + line.HorizontalAlignment = HorizontalAlignment.Left; + line.VerticalAlignment = VerticalAlignment.Center; + line.StrokeThickness = 1; + + line.X1 = prevPos; + prevPos = pos; + line.X2 = pos; + + double time = (double)sequence.StartTime / 1000000.0 + (double)idx * SampleDelay; + double lastHeight = ValueToHeight(time, value); + + if (idx == prevIdx + 1) + { + line.Y1 = prevHeight; + line.Y2 = lastHeight; + } + else + { + if (value - minValue > maxValue - value) + { + line.Y1 = ValueToHeight(time, minValue); + line.Y2 = ValueToHeight(time, maxValue); + } + else + { + line.Y1 = ValueToHeight(time, maxValue); + line.Y2 = ValueToHeight(time, minValue); + } + } + + prevHeight = lastHeight; + minValue = value; + maxValue = value; + prevIdx = idx; + + yield return line; + } } } public void AddSequence(ulong startTime, byte[] data) { + // TODO: merge sequences if total size is lower than MaxSequenceSize var sequence = new Sequence { StartTime = startTime, Data = data }; Sequences.Add(sequence); OnPropertyChanged("Oscilloscope"); - var canvas = (Canvas)Window.FindName("Oscilloscope"); - /*canvas.Children.Clear(); + OnPropertyChanged("MinTime"); + OnPropertyChanged("MaxTime"); + if (Sequences.Count == 1) + { + ViewportStartTime = MinTime; + } + + var canvas = OscilloscopeCanvas; + foreach (var line in ConvertToLines(sequence)) + canvas.Children.Add(line); + } + + void RefreshOscilloscope() + { + var canvas = OscilloscopeCanvas; + canvas.Children.Clear(); foreach (var line in Oscilloscope) - canvas.Children.Add(line);*/ - foreach (var line in ConvertToLines(sequence, Sequences.ElementAt(0).StartTime)) canvas.Children.Add(line); } + private Canvas oscilloscopeCanvas_; + public Canvas OscilloscopeCanvas { get { if (oscilloscopeCanvas_ == null) oscilloscopeCanvas_ = (Canvas)Window.FindName("Oscilloscope"); return oscilloscopeCanvas_; } } + + public double MinTime { get { return Sequences.Any() ? (double)Sequences.First().StartTime / 1000000.0 : 0.0; } } + public double MaxTime { get { return Sequences.Any() ? Math.Max(MinTime + 0.1, (double)Sequences.Last().StartTime / 1000000.0) : 0.1; } } + private double viewportTimeWidth_ = 0.1; + public double ViewportTimeWidth + { + get { return viewportTimeWidth_; } + set { viewportTimeWidth_ = value; OnPropertyChanged("ViewportTimeWidth"); RefreshOscilloscope(); } + } + + private double viewportStartTime_ = 0; + public double ViewportStartTime + { + get { return viewportStartTime_; } + set { viewportStartTime_ = value; OnPropertyChanged("ViewportStartTime"); RefreshOscilloscope(); } + } + public double ScrollValue + { + get { return MathEx.Unproject(MathEx.Project(ViewportStartTime, MinTime, MaxTime - ViewportTimeWidth), MinTime, MaxTime); } + set { ViewportStartTime = MathEx.Unproject(MathEx.Project(value, MinTime, MaxTime), MinTime, MaxTime - ViewportTimeWidth); } + } + + public void SetViewport(double startTime, double timeWidth) + { + viewportStartTime_ = startTime; + ViewportTimeWidth = timeWidth; + } + public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) { - PropertyChangedEventHandler handler = PropertyChanged; - if (handler != null) + if (PropertyChanged != null) { - handler(this, new PropertyChangedEventArgs(name)); + PropertyChanged(this, new PropertyChangedEventArgs(name)); } } } diff --git a/SerialMonitor/SerialMonitor/MathEx.cs b/SerialMonitor/SerialMonitor/MathEx.cs new file mode 100644 index 0000000..4d6e1e6 --- /dev/null +++ b/SerialMonitor/SerialMonitor/MathEx.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SerialMonitor +{ + public static class MathEx + { + public static double Project(double value, double rangeMin, double rangeMax) + { + return (value - rangeMin) / (rangeMax - rangeMin); + } + + public static double Unproject(double ratio, double rangeMin, double rangeMax) + { + return rangeMin + ratio * (rangeMax - rangeMin); + } + } +} diff --git a/SerialMonitor/SerialMonitor/SerialMonitor.csproj b/SerialMonitor/SerialMonitor/SerialMonitor.csproj index 1638f28..6f58ec6 100644 --- a/SerialMonitor/SerialMonitor/SerialMonitor.csproj +++ b/SerialMonitor/SerialMonitor/SerialMonitor.csproj @@ -54,6 +54,7 @@ Designer +