Components and supplies
Resistor 221 ohm
Arduino UNO
Resistor 470 kOhm
Capacitor 100 nF
Rotary potentiometer (generic)
Standard LCD - 16x2 White on Blue
555 Timers
Capacitor 1 µF
5 mm LED: Red
Resistor 1k ohm
Project description
Code
ScopeOne.h
h
ScopeOne header file
1#ifndef _Scope_h_ 2#define _Scope_h_ 3 4/* 5 1-6 channel oscilloscope 6 7 Time base: 8 100, 200, 500 us 9 1, 2, 5 ms 10 10, 20, 50 ms 11 100, 200, 500 ms 12 1, 2, 5 s 13 10, 20, 50 s 14 15 Counter time bases: 16 1 period, clock divider 1024 17 1 period, clock divider 256 18 1 period, clock divider 64 19 10 periods, clock divider 1024 20 ... 21 10000 periods, clock divider 64 22*/ 23 24#include <Arduino.h> 25 26#define MaxChannels 6 27 28// Commands to the Arduino board 29#define Reset 'X' 30#define ScopeMode 'Y' 31#define CounterMode 'Z' 32#define Channel1 'A' 33#define ChannelMax 'F' 34#define TrigRising 'w' 35#define TrigFalling 'x' 36#define ContSweep 'y' 37#define SingleSweep 'z' 38#define TimeBaseMin 'a' 39#define TimeBaseMax 't' 40#define CounterBaseMin 'G' 41#define CounterBaseMax 'U' 42#define InitialMode 'Z' 43 44#define ScopeOneCommand 'X' 45 46// The trigger default is pin 2, unless user has defined it otherwise 47#ifndef TriggerPin 48#define TriggerPin 2 49#endif 50 51#define MaxSamples 500 52 53// Timer and interrupt settings 54#define INTBIT B00000001 55#define TRIGCLR B00000001 56#define TRIGRISE B00000011 57#define TIMERCTCA B00000000 58#define TIMERCTCB B10001000 59#define TIMERCNTA B00000000 60#define TIMERCNTB B00000000 61#define TIMERNOCLK B11111000 62#define TIMERPS0001 B00000001 63#define TIMERPS0256 B00000100 64#define TIMERPS1024 B00000101 65#define ADCINIT B10000111 66#define ADCSELECT B01100000 67#define ADCSTART B01000000 68#define ADCPSCLR B11111000 69#define ADCPS016 B00000100 70#define ADCPS032 B00000101 71#define ADCPS064 B00000110 72#define ADCPS128 B00000111 73#define ADCREADY B00010000 74#define CLEARADIF B10101111 75 76#define InitValue 123 77 78class ScopeOne 79{ 80 public: 81 static ScopeOne* install(byte channel1 = A0, byte nrChannels = MaxChannels); 82 static ScopeOne* create(byte channel1 = A0, byte nrChannels = MaxChannels); 83 void setup(); 84 85 static char getCommand(); 86 87 bool isScopeMode(); 88 byte getChannel1(); 89 byte getNrChannels(); 90 91 inline void trigger(); 92 inline void getSample(); 93 94 static ScopeOne* theScope; 95 96 protected: 97 // Constructor 98 ScopeOne(byte channel1, byte nrChannels); 99 100 // Command handler 101 char execute(char c); 102 103 // Scope functions 104 void setScope(); 105 void scopeReset(); 106 void setChannel(byte c); 107 void setSampleTime(byte c); 108 void setTriggerMode(byte c); 109 void setSweepMode(byte c); 110 void stopSweep(); 111 void writeData(); 112 113 // Counter functions 114 void setCounter(); 115 void counterReset(); 116 void setPeriods(byte c); 117 void writeCount(unsigned long cnt); 118 119 void init(); 120 void initAdc(); 121 void readAdc(); 122 123 bool scope; 124 bool continuousSweep; 125 byte maxChannels; 126 byte firstChannel; 127 byte currentChannel; 128 char currentBase; 129 130 // Sample variables 131 int samples; 132 volatile byte sample[MaxSamples]; 133 volatile int index; 134 volatile int writeIndex; 135 136 // Frequencey counter current ode variables 137 int periodCount; 138 volatile int periods; 139 volatile unsigned long count; 140 141 byte timerPrescaler; 142 byte intBit; 143 volatile byte counterDiv; 144 bool writeImmediate; 145 146 static byte _isInitialized; 147}; 148 149#endif 150
Oscilloscope.pde
processing
The code for the GUI of the oscilloscope running on your PC in the Processing IDE.
1/* 2 ** Oscilloscope V2.0.1 3 */ 4static boolean Debug = false; 5 6import processing.serial.*; 7 8boolean scope = false; 9 10static final String SerialPort ="COM3"; // Change this to match your Arduino port 11 12//static final String FontType = "System"; 13 14static final float fclk = 16e6; // Arduinos clock frequency 15 16/* Commands to the Arduino board */ 17static final char Reset = 'X'; 18static final char ScopeMode = 'Y'; 19static final char CounterMode = 'Z'; 20static final char Channel1 = 'A'; 21static final char ChannelMax = 'F'; 22static final char TrigRising = 'w'; 23static final char TrigFalling = 'x'; 24static final char ContSweep = 'y'; 25static final char SingleSweep = 'z'; 26static final char TimeBaseMin = 'a'; 27static final char TimeBaseMax = 't'; 28static final char CounterBaseMin = 'G'; 29static final char CounterBaseMax = 'U'; 30 31/* Text definitions */ 32static final String Sampling = "Sampling..."; 33static final String SampleFreqFmt = "[%1.1f Hz]"; 34static final String FreqFmt = "%1.2f Hz"; 35static final String PeriodFmt = "T = %dx (/%d)"; 36 37/* End markers when switching to scope */ 38static final int EndMarkers = 3; 39 40/* Trace definitions */ 41static final int MaxSample = 500; 42static final int SampleSize = 8; 43static final int SampleMax = (1 << SampleSize) - 1; 44static final int Channels = ChannelMax - Channel1 + 1; 45 46/* Screen size */ 47static final int MaxX = 1000; 48static final int MaxY = 550; 49/* Trace dimensions */ 50static final int Width = 800; 51static final int Height = 500; 52 53/* Time base parameters class */ 54class TimebaseSet 55{ 56 TimebaseSet(int f, float pwr, int s, float st) 57 { 58 factor = f; 59 p10 = pwr; 60 samples = s; 61 sampleTime = st; 62 } 63 64 int factor; 65 float p10; 66 int samples; 67 float sampleTime; 68}; 69 70/* Class to execute a button action, used in conjunction with Button class */ 71abstract class ButtonAction 72{ 73 public abstract void execute(); 74 75 public void setButton(Button b) 76 { 77 _button = b; 78 } 79 protected Button _button; 80}; 81 82/* Class to represent a button with an associated action */ 83public class Button 84{ 85 public Button(int centerx, int centery, int w, int h, String name, long col, ButtonAction action) 86 { 87 _cx = centerx; 88 _cy = centery; 89 _buttonWidth = w; 90 _buttonHeight = h; 91 text = name; 92 red = (int) (col >> 16); 93 green = (int) ((col >> 8) & 0xFF); 94 blue = (int) (col & 0xFF); 95 _action = action; 96 if (_action != null) 97 { 98 _action.setButton(this); 99 } 100 } 101 102 void enable(boolean on) 103 { 104 enabled = on; 105 } 106 107 /* Shows the button on the screen */ 108 public void draw() 109 { 110 if (enabled) 111 { 112 rectMode(CENTER); 113 fill(red, green, blue); 114 stroke(192, 192, 192); 115 rect(_cx, _cy, _buttonWidth, _buttonHeight); 116 textSize(20); 117 fill(255, 255, 255); 118 textAlign(CENTER, CENTER); 119 text(text, _cx, _cy - 3); 120 } 121 } 122 123 /* Checks if the button was clicked, executes the action if so */ 124 public void isClicked(int x, int y) 125 { 126 int bw = _buttonWidth / 2; 127 int bh = _buttonHeight / 2; 128 129 boolean result = enabled && ((x >= _cx - bw && x <= _cx + bw && y >= _cy - bh && y <= _cy + bh)); 130 131 if (Debug && result) 132 { 133 println(text + " clicked!"); 134 } 135 if (result) 136 { 137 _action.execute(); 138 } 139 } 140 141 protected int _cx; 142 protected int _cy; 143 protected int _buttonWidth; 144 protected int _buttonHeight; 145 public int red; 146 public int green; 147 public int blue; 148 public String text; 149 protected ButtonAction _action; 150 protected boolean enabled = true; 151}; 152 153/* Class for check box buttons */ 154class CheckButton extends Button 155{ 156 class Toggle extends ButtonAction 157 { 158 public Toggle(ButtonAction action) 159 { 160 _taction = action; 161 } 162 163 public void execute() 164 { 165 if (Debug) 166 { 167 println(text + " toggled"); 168 } 169 _state = !_state; 170 _taction.execute(); 171 } 172 173 protected ButtonAction _taction; 174 }; 175 176 public CheckButton(int centerx, int centery, int w, int h, String name, long col, ButtonAction action) 177 { 178 super(centerx, centery, w, h, name, col, null); 179 180 _action = new Toggle(action); 181 } 182 183 /* Shows the button on the screen */ 184 public void draw() 185 { 186 rectMode(CENTER); 187 if (_state) 188 { 189 fill(255 - red, 255 - green, 255 - blue); 190 } else 191 { 192 fill(red, green, blue); 193 } 194 stroke(192, 192, 192); 195 rect(_cx, _cy, _buttonWidth, _buttonHeight); 196 textSize(14); 197 fill(255, 255, 255); 198 textAlign(LEFT, CENTER); 199 text(text, _cx + _buttonWidth / 2 + 5, _cy - 3); 200 } 201 202 /* Returns the current state of the check box */ 203 public boolean getState() 204 { 205 return _state; 206 } 207 208 protected boolean _state = false; 209}; 210 211/* Class for channel selection actions */ 212class ChAction extends ButtonAction 213{ 214 ChAction(int ch) 215 { 216 _ch = ch; 217 } 218 219 public void execute() 220 { 221 if (_button.red == 0 & _button.green == 0 && _button.blue == 0) 222 { 223 setChannel(_ch); 224 } else 225 { 226 showChannel(_ch); 227 } 228 } 229 230 protected int _ch; 231}; 232 233int currentChannel = 0; 234 235TimebaseSet[] timebase = { 236 //new TimebaseSet(1, 0.0001, 77, 0.000013), // 0 237 //new TimebaseSet(2, 0.0001, 154, 0.000013), 238 //new TimebaseSet(5, 0.0001, 384, 0.000013), 239 //new TimebaseSet(1, 0.0001, 71, 0.000014), // 0 240 //new TimebaseSet(2, 0.0001, 142, 0.000014), 241 //new TimebaseSet(5, 0.0001, 357, 0.000014), 242 //new TimebaseSet(1, 0.0001, 66, 0.000015), // 0 243 //new TimebaseSet(2, 0.0001, 133, 0.000015), 244 //new TimebaseSet(5, 0.0001, 333, 0.000015), 245 //new TimebaseSet(1, 0.0001, 62, 0.000016), // 0 246 //new TimebaseSet(2, 0.0001, 125, 0.000016), 247 //new TimebaseSet(5, 0.0001, 312, 0.000016), 248 new TimebaseSet(1, 0.0001, 50, 0.000020), // 0 249 new TimebaseSet(2, 0.0001, 100, 0.000020), 250 new TimebaseSet(5, 0.0001, 250, 0.000020), 251 //new TimebaseSet(1, 0.0001, 48, 0.000021), // 0 252 //new TimebaseSet(2, 0.0001, 95, 0.000021), 253 //new TimebaseSet(5, 0.0001, 238, 0.000021), 254 new TimebaseSet(1, 0.001, 400, 0.000025), 255 new TimebaseSet(2, 0.001, 400, 0.000050), 256 new TimebaseSet(5, 0.001, 500, 0.000100), 257 new TimebaseSet(1, 0.01, 500, 0.000200), // 6 258 new TimebaseSet(2, 0.01, 500, 0.000400), 259 new TimebaseSet(5, 0.01, 500, 0.001), 260 new TimebaseSet(1, 0.1, 500, 0.002), 261 new TimebaseSet(2, 0.1, 500, 0.004), 262 new TimebaseSet(5, 0.1, 500, 0.01), 263 new TimebaseSet(1, 1, 500, 0.02), 264 new TimebaseSet(2, 1, 500, 0.04), 265 new TimebaseSet(5, 1, 500, 0.1), 266 new TimebaseSet(1, 10, 500, 0.2), 267 new TimebaseSet(2, 10, 500, 0.4), 268 new TimebaseSet(5, 10, 500, 1.0), 269 new TimebaseSet(1, 100, 500, 2.0), 270 new TimebaseSet(2, 100, 500, 4.0) 271}; 272 273int timebaseIndex = 7; 274float timediv; 275float sens; /* mV/div */ 276 277int samples; 278int channelSamples[] = { 0, 0, 0, 0, 0, 0 }; 279float sampleTime; 280float sample[][] = new float[Channels][MaxSample]; 281 282long channelColor[] = { 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFF0000, 0x00FF00, 0x0000FF }; 283float channelSampleTime[] = { 0, 0, 0, 0, 0, 0 }; 284boolean channelOn[] = { false, false, false, false, false, false }; 285boolean channelVisible[] = { false, false, false, false, false, false }; 286 287float periodCount; 288float divider = 1024.0; 289float count = 0.0; 290float countDigit = 1.0; 291float frequency = 0.0; 292boolean countingInd = false; 293char periodCountInd = CounterBaseMin; 294 295 296Serial port; 297PFont f; 298 299int set = 0; 300int index = 0; 301 302int x0 = 0, x1 = Width; 303int y0 = 25, y1 = Height + y0; 304int divx = Width / 10; 305int divy = Height / 10; 306 307float scalex; 308float scaley; 309float xcenter = (x0 + x1) / 2; 310float ycenter = (y0 + y1) / 2; 311float offset[] = { 0, 0, 0, 0, 0, 0 }; 312int sensFact[] = { 5, 5, 5, 5, 5, 5 }; 313int sens10[] = { 100, 100, 100, 100, 100, 100 }; 314 315boolean measuring = false; 316int flushingCountData = 0; 317boolean getChannelCount = false; 318 319ArrayList<Button> button = new ArrayList(); 320CheckButton sweepButton; 321CheckButton triggerButton; 322 323void scale() 324{ 325 scalex = Width / timediv / 10.0; 326 scaley = Height / (sens / 1000.0) / 10.0; 327} 328 329float plotX(float time) 330{ 331 return scalex * time; 332} 333 334float plotY(int channel, float voltage) 335{ 336 return y1 - scaley * (voltage + offset[channel]); 337} 338 339/* Switch between scope and frequency counter mode */ 340void toggleMode() 341{ 342 scope = !scope; 343 getChannelCount = scope; // Wait for the number of channels reported by Arduino 344 flushingCountData = (scope ? EndMarkers : 0); 345 port.write(scope ? ScopeMode : CounterMode); 346 port.clear(); 347 if (scope) 348 { 349 updateTimebase(); 350 } else 351 { 352 scope = false; 353 updatePeriodCount(); 354 } 355} 356 357/* Switch active channel for receiving samples */ 358void setChannel(int ch) 359{ 360 channelOn[currentChannel] = false; 361 currentChannel = ch; 362 for (int i = 0; i < MaxSample; i++) 363 { 364 sample[currentChannel][i] = 0; 365 } 366 channelSamples[currentChannel] = samples; 367 channelOn[currentChannel] = true; 368 channelVisible[currentChannel] = true; 369 sens = sensFact[currentChannel] * sens10[currentChannel]; 370 port.write((char) (currentChannel + Channel1)); // Send channel switch command to Arduino 371 index = 0; 372} 373 374/* Toggle a channel's trace visibility */ 375void showChannel(int ch) 376{ 377 channelVisible[ch] = !channelVisible[ch]; 378} 379 380/* Handle start/stop button press */ 381void startStop() 382{ 383 measuring = !measuring; 384 index = 0; 385 if (measuring && sweepButton.getState()) 386 { 387 if (!Debug) 388 { 389 port.write(Reset); // Send a reset command to Arduino 390 } 391 } 392} 393 394/* Toggle sweep mode between single and continuous */ 395void setSweep() 396{ 397 if (!Debug) 398 { 399 port.write(sweepButton.getState() ? SingleSweep : ContSweep); 400 } 401 index = 0; 402} 403 404/* Toggle trigger mode between rising and falling edge */ 405void setTriggerMode() 406{ 407 if (!Debug) 408 { 409 port.write(triggerButton.getState() ? TrigFalling : TrigRising); 410 } 411 index = 0; 412} 413 414/* Increase sensitivity */ 415void sensUp() 416{ 417 if (sens > 10.0) 418 { 419 sensFact[currentChannel] /= 2; 420 if (sensFact[currentChannel] == 0) 421 { 422 sens10[currentChannel] /= 10; 423 sensFact[currentChannel] = 5; 424 } 425 } 426} 427 428/* Decrease sensitivity */ 429void sensDn() 430{ 431 if (sens < 5000.0) 432 { 433 sensFact[currentChannel] *= 2; 434 if (sensFact[currentChannel] == 4) 435 { 436 sensFact[currentChannel] = 5; 437 } 438 if (sensFact[currentChannel] >= 10) 439 { 440 sens10[currentChannel] *= 10; 441 sensFact[currentChannel] = 1; 442 } 443 } 444} 445 446/* Update time base based on the value of timebaseIndex */ 447void updateTimebase() 448{ 449 timediv = (float) timebase[timebaseIndex].factor * timebase[timebaseIndex].p10; 450 samples = timebase[timebaseIndex].samples; 451 sampleTime = timebase[timebaseIndex].sampleTime; 452 channelSamples[currentChannel] = samples; 453 scale(); 454 if (!Debug) 455 { 456 port.write((char) (timebaseIndex + TimeBaseMin)); // Send command to Arduino 457 } 458 index = 0; 459} 460 461/* Increase time base (slower scan) */ 462void timeUp() 463{ 464 if (scope) 465 { 466 if (timebaseIndex < TimeBaseMax - TimeBaseMin) 467 { 468 timebaseIndex++; 469 updateTimebase(); 470 } 471 } else 472 { 473 if (periodCountInd < CounterBaseMax) 474 { 475 periodCountInd++; 476 updatePeriodCount(); 477 } 478 } 479} 480 481/* Decrease time base (faster scan) */ 482void timeDn() 483{ 484 if (scope) 485 { 486 if (timebaseIndex > 0) 487 { 488 timebaseIndex--; 489 updateTimebase(); 490 } 491 } else 492 { 493 if (periodCountInd > CounterBaseMin) 494 { 495 periodCountInd--; 496 updatePeriodCount(); 497 } 498 } 499} 500 501/* Update periods */ 502void updatePeriodCount() 503{ 504 periodCount = 1.0; 505 divider = 64.0; 506 int s = periodCountInd - CounterBaseMin; 507 int p = s / 3; 508 int d = 2 - (s % 3); 509 for (int div = 0; div < d; div++) 510 { 511 divider *= 4.0; 512 } 513 for (int per = 0; per < p; per++) 514 { 515 periodCount *= 10.0; 516 } 517 port.write(periodCountInd); 518 count = 0.0; 519 countDigit = 1.0; 520} 521 522/* Initiate */ 523void setup() 524{ 525 if (!Debug) 526 { 527 port = new Serial(this, SerialPort, 115200); 528 } else 529 { 530 // For testing simulate a sine wave 531 for (index = 0; index < MaxSample; index++) 532 { 533 sample[0][index] = 2.5*sin(2.0*PI*20.0*((float) (index * sampleTime / 1000.0)))+2.5; 534 } 535 } 536 537 // Screen 538 size(1000, 550); 539 frameRate(50); 540 background(0); 541 //f = createFont(FontType, 16); 542 543 // Define buttons 544 button.add(new Button(900, 50, 190, 50, "Scope / Count", 0x404040, new ButtonAction() { 545 public void execute() { 546 toggleMode(); 547 } 548 } 549 )); 550 button.add(new Button(900, 110, 190, 50, "Start / Stop", 0x404040, new ButtonAction() { 551 public void execute() { 552 startStop(); 553 } 554 } 555 )); 556 for (int ch = 0; ch < Channels; ch++) 557 { 558 button.add(new Button(825 + ch * 30, 205, 25, 25, "" + (char) ('1' + ch), 0, new ChAction(ch))); 559 } 560 for (int ch = 0; ch < Channels; ch++) 561 { 562 button.add(new Button(825 + ch * 30, 240, 25, 25, "", channelColor[ch], new ChAction(ch))); 563 } 564 button.add(sweepButton = new CheckButton(825, 280, 20, 20, "Single Sweep", 0x000000, new ButtonAction() { 565 public void execute() { 566 setSweep(); 567 } 568 } 569 )); 570 button.add(triggerButton = new CheckButton(825, 315, 20, 20, "Trigger on falling edge", 0x000000, new ButtonAction() { 571 public void execute() { 572 setTriggerMode(); 573 } 574 } 575 )); 576 button.add(new Button(850, 380, 90, 50, "Sens -", 0x404040, new ButtonAction() { 577 public void execute() { 578 sensDn(); 579 } 580 } 581 )); 582 button.add(new Button(950, 380, 90, 50, "Sens +", 0x404040, new ButtonAction() { 583 public void execute() { 584 sensUp(); 585 } 586 } 587 )); 588 button.add(new Button(850, 440, 90, 50, "Offset -", 0x404040, new ButtonAction() { 589 public void execute() 590 { 591 if (offset[currentChannel] > -15.0) 592 { 593 offset[currentChannel] -= 0.5; 594 } 595 } 596 } 597 )); 598 button.add(new Button(950, 440, 90, 50, "Offset +", 0x404040, new ButtonAction() { 599 public void execute() 600 { 601 if (offset[currentChannel] < 15.0) 602 { 603 offset[currentChannel] += 0.5; 604 } 605 } 606 } 607 )); 608 button.add(new Button(850, 500, 90, 50, "Time -", 0x404040, new ButtonAction() { 609 public void execute() { 610 timeDn(); 611 } 612 } 613 )); 614 button.add(new Button(950, 500, 90, 50, "Time +", 0x404040, new ButtonAction() { 615 public void execute() { 616 timeUp(); 617 } 618 } 619 )); 620 621 // Set initial configuration of the scope (matches Arduino defaults) 622 updateTimebase(); 623 updatePeriodCount(); 624 setChannel(currentChannel); 625 startStop(); 626} 627 628void draw() 629{ 630 clear(); 631 632 if (measuring & scope) 633 { 634 textSize(16); 635 fill(255, 255, 255); 636 text(Sampling, 900, 147); 637 text(String.format(SampleFreqFmt, 1.0 / sampleTime), 900, 170); 638 } 639 640 /* Gridlines */ 641 stroke(0, 128, 0); 642 643 /* Vertical */ 644 for (int x = 0; x <= x1; x += divx) 645 { 646 line(x, y1, x, y0); 647 } 648 /* Horizontal */ 649 for (int y = y0; y <= y1; y += divy) 650 { 651 line(x0, y, x1, y); 652 } 653 654 /* Emphasize horizontal and vertical center lines */ 655 stroke(0, 255, 0); 656 line(xcenter, y0, xcenter, y1); 657 line(x0, ycenter, x1, ycenter); 658 659 /* Show all buttons */ 660 for (int b = 0; b < button.size(); b++) 661 { 662 button.get(b).draw(); 663 } 664 665 /* Show active channel */ 666 stroke(255, 0, 0); 667 noFill(); 668 rect(825 + currentChannel * 30, 205, 25, 25); 669 670 /* Scaling info text */ 671 textSize(16); 672 fill(255, 255, 255); 673 textAlign(LEFT, CENTER); 674 //textFont(f); 675 fill(0, 255, 0); 676 sens = sensFact[currentChannel] * sens10[currentChannel]; 677 text(String.format("%d%cV/div", (int) (sens > 999 ? sens / 1000 : sens), 678 sens < 1000.0 ? 'm' : '\\0'), x0 + 4, y0 + divy / 4); 679 text(String.format("Offset: %+1.1fV", offset[currentChannel]), x0 + 4, y0 + 3 * divy / 4); 680 681 int tb = (int) (timediv); 682 char unit = ' '; 683 684 if (timediv < 0.001) 685 { 686 tb = (int) (timediv * 1000000.0 + 0.5); 687 unit = 'u'; 688 } else if (timediv < 1.0) 689 { 690 tb = (int) (timediv * 1000.0 + 0.5); 691 unit = 'm'; 692 } 693 694 textAlign(CENTER, CENTER); 695 text(String.format("%d%cs/div", tb, unit), x0 + 19 * divx / 2, y0 + 19 * divy / 2); 696 697 if (scope) 698 { 699 /* Display the sample traces */ 700 701 for (int ch = 0; ch < Channels; ch++) 702 { 703 if (channelVisible[ch]) 704 { 705 sens = sensFact[ch] * sens10[ch]; 706 scale(); 707 float prevx = plotX(0.0); 708 float prevy = plotY(ch, sample[ch][0]); 709 long c = channelColor[ch]; 710 711 prevy = max(prevy, y0); 712 prevy = min(prevy, y1); 713 714 stroke(c >> 16, (c >> 8) & 0xFF, c & 0xFF); 715 716 for (int i = 1; i < channelSamples[currentChannel] && prevx < x1; i++) 717 { 718 float x = plotX(i * sampleTime); 719 float y = plotY(ch, sample[ch][i]); 720 if (y <= y1 && y >= y0) 721 { 722 line(prevx, prevy, x, y); 723 prevx = x; 724 prevy = y; 725 } 726 } 727 } 728 } 729 } else 730 { 731 rectMode(CENTER); 732 stroke(255, 255, 255); 733 fill(0, 0, 0); 734 rect((x0 + x1) / 2, ycenter, 4 * divx, 2 * divy); 735 736 if (countingInd) 737 { 738 fill(255, 0, 0); 739 rect(xcenter - 7 * divx / 4, ycenter - 3 * divy / 4, 20, 20); 740 } 741 742 textAlign(CENTER, CENTER); 743 textSize(30); 744 fill(255, 255, 255); 745 text(String.format(FreqFmt, frequency), xcenter, ycenter - divy / 4); 746 textSize(18); 747 text(String.format(PeriodFmt, (int) periodCount, (int) divider), xcenter, ycenter + divy / 2); 748 } 749 sens = sensFact[currentChannel] * sens10[currentChannel]; 750} 751 752/* Handle button clicks */ 753void mouseClicked() 754{ 755 int mx = mouseX; 756 int my = mouseY; 757 758 for (int b = 0; b < button.size(); b++) 759 { 760 button.get(b).isClicked(mx, my); 761 } 762} 763 764void keyPressed() 765{ 766 if (!Character.isLetter(key)) 767 { 768 port.write(key); 769 } 770} 771 772/* Handle incoming sample data from Arduino */ 773void serialEvent(Serial port) 774{ 775 int s; 776 float v; 777 778 try 779 { 780 if (port.available() != 0) 781 { 782 s = port.read(); 783 v = s; 784 if (s == 0xFF) // End-of-sweep indicator 785 { 786 if (scope) 787 { 788 if (flushingCountData > 0) 789 { 790 //println("s==0xFF"); 791 //println ("flushCount=", flushingCountData); 792 flushingCountData--; 793 } 794 index = 0; 795 } else 796 { 797 if (countDigit >= 4294967296.0) 798 { 799 if (count > 0) 800 { 801 frequency = fclk * periodCount / (count * divider) ; 802 } 803 count = 0.0; 804 countDigit = 1; 805 countingInd = !countingInd; 806 } else 807 { 808 count += (v * countDigit); 809 countDigit *= 256; 810 } 811 } 812 } else 813 { 814 if (scope) 815 { 816 if (getChannelCount) 817 { 818 //println ("flushCount=", flushingCountData); 819 if (flushingCountData == 0) // If 3 consecutive 0xFF found, number of channels follows 820 { 821 getChannelCount = false; 822 //println ("s=", s); 823 s -= '0'; 824 //println (s, "Channels"); 825 for (int i = 0; i < 6; i++) 826 { 827 button.get(2 + i).enable(i < s); 828 button.get(8 + i).enable(i < s); 829 } 830 } else 831 { 832 //println("s==", s); 833 flushingCountData = EndMarkers; 834 } 835 } 836 if (measuring) 837 { 838 sample[currentChannel][index++] = map(s, 0.0, 255.0, 0.0, 5.0); 839 if (index >= channelSamples[currentChannel]) 840 { 841 index = 0; 842 if (sweepButton.getState()) 843 { 844 measuring = false; 845 } 846 } 847 } 848 } else 849 { 850 count += (v * countDigit); 851 countDigit *= 256; 852 } 853 } 854 } 855 } 856 857 catch(RuntimeException e) 858 { 859 e.printStackTrace(); 860 } 861} 862
library.properties
properties
ScopeOne library property file
1name=ScopeOne 2version=1.0.0 3author=Qixaz 4maintainer=Qixaz (www.qixaz.com) 5sentence=Digital sampling oscilloscope and frequency counter for diagnosing your circuits 6paragraph=Library to use your Arduino One as a digital sampling scope and frequency counter. You can create the scope with just a few commands in your project code and diagnose your circuit. 7category=Data Processing 8url=http://www.qixaz.com 9architectures=* 10dot_a_linkage=true 11
keywords.txt
text
Keyword file for the ScopeOne library
1####################################### 2# Syntax Coloring Map For ScopeOne 3####################################### 4 5####################################### 6# Class (KEYWORD1) 7####################################### 8 9ScopeOne KEYWORD1 ScopeOneLibrary 10 11####################################### 12# Methods and Functions (KEYWORD2) 13####################################### 14 15# method names in ScopeOne 16 17install KEYWORD2 18create KEYWORD2 19setup KEYWORD2 20isScopeMode KEYWORD2 21getChannel1 KEYWORD2 22getNrChannels KEYWORD2 23getCommand KEYWORD2 24 25####################################### 26# Constants (LITERAL1) 27####################################### 28 29DfltOnTime LITERAL1 30MinusPattern LITERAL1 31
Oscilloscope.ino
arduino
The minimum code to create an oscilloscope on your Arduino using the ScopeOne library
1/* 2 Oscilloscope using the ScopeOne library 3*/ 4 5// Uncomment the next line to use pin 3 instead of pin 2 as trigger input 6//#define TriggerPin 3 7 8#include <ScopeOne.h> 9 10void setup() 11{ 12 ScopeOne::install(); 13 14 // Other ways of configuring the scope: 15 // ScopeOne::install(A2); // Uses A2-A5 as inputs 16 // ScopeOne::install(A3, 2); // Uses A3 and A4 as inputs (2 channels) 17} 18 19void loop() 20{ 21 char ch = ScopeOne::getCommand(); 22 23 // ch will be 0 if no command was received 24 // ch will be ScopeOneCommand if a scope command was received and handled 25 // ch will be the character typed on the keyboard of your PC (not a letter) 26} 27
ScopeOne.cpp
c_cpp
ScopeOne C++ source code
1#include <ScopeOne.h> 2 3ScopeOne* ScopeOne::theScope = NULL; 4byte ScopeOne::_isInitialized = 0; 5 6// Create ad set-up a scope 7ScopeOne* ScopeOne::install(byte channel1, byte nrChannels) 8{ 9 create(channel1, nrChannels); 10 theScope->setup(); 11 return theScope; 12} 13 14// Create a scope object 15ScopeOne* ScopeOne::create(byte channel1, byte nrChannels) 16{ 17 if (_isInitialized != InitValue) 18 { 19 delete theScope; 20 } 21 theScope = new ScopeOne(channel1, nrChannels); 22 _isInitialized = InitValue; 23 return theScope; 24} 25 26// Constructor 27ScopeOne::ScopeOne(byte channel1, byte nrChannels) 28{ 29 if (channel1 < A0 || channel1 > A5) 30 { 31 firstChannel = A0; 32 maxChannels = 1; 33 } 34 else 35 { 36 if (channel1 + nrChannels - 1 >= A5) 37 { 38 maxChannels = A5 - channel1 + 1; 39 } 40 else 41 { 42 maxChannels = nrChannels <= MaxChannels ? nrChannels : MaxChannels; 43 } 44 firstChannel = channel1; 45 } 46 scope = true; 47 intBit = (INTBIT << (TriggerPin == 2 ? 0 : 1)); 48 currentChannel = 0; 49 counterDiv = TIMERPS1024; 50} 51 52byte ScopeOne::getChannel1() 53{ 54 return firstChannel; 55} 56 57byte ScopeOne::getNrChannels() 58{ 59 return maxChannels; 60} 61 62/* Return scope mode */ 63bool ScopeOne::isScopeMode() 64{ 65 return scope; 66} 67 68/* Initialize the Analog-Digital Converter */ 69void ScopeOne::initAdc() 70{ 71 ADCSRA = ADCINIT; 72 ADMUX = ADCSELECT; 73} 74 75/* Read a sample from the ADC */ 76void ScopeOne::readAdc() 77{ 78 sample[index++] = ADCH; // 8-bit sample size for speed 79 ADCSRA &= CLEARADIF; 80 ADCSRA |= ADCSTART; // Start conversion for the next sample 81} 82 83/* Handle the end of a sweep */ 84void ScopeOne::stopSweep() 85{ 86 TCCR1B &= TIMERNOCLK; // Set clock select to 0 (no clock) 87 index++; 88 writeData(); // Write sampled data to serial connection 89 if (continuousSweep) 90 { 91 scopeReset(); // Restart automatically in continuous sweep mode 92 } 93} 94 95/* Reset the scope for a new sweep */ 96void ScopeOne::scopeReset() 97{ 98 TCCR1B &= TIMERNOCLK; // Stop the timer by setting clock select to 0 (no clock) 99 100 Serial.print((char) 0xFF); // Mark end of sweep to console 101 102 index = 0; // Reset sweep data 103 writeIndex = 0; 104 105 EIFR |= intBit; // Reset trigger interrupt flag 106 EIMSK |= intBit; // Enable interrupts on trigger input 107 108 // Wait for trigger signal interrupt 109} 110 111/* Reset the frequency counter to start another measurement */ 112void ScopeOne::counterReset() 113{ 114 periods = 0; // Reset counted periods 115 count = 0UL; // Reset total timer counts 116 TCNT1 = 0; // Reset timer 117 EIFR != intBit; 118} 119 120/* Set the sample time for the selected time base. 121 * The selection is done with a single character 'a'-'t'. 122*/ 123void ScopeOne::setSampleTime(byte c) 124{ 125 unsigned int cnt; 126 127 ADCSRA &= ADCPSCLR; // Clear prescaler 128 // Set ADC prescaler 129 switch (c) 130 { 131 case 0: 132 case 1: 133 case 2: 134 case 3: 135 ADCSRA |= ADCPS016; 136 break; 137 case 4: 138 ADCSRA |= ADCPS032; 139 break; 140 case 5: 141 ADCSRA |= ADCPS064; 142 break; 143 default: 144 ADCSRA |= ADCPS128; 145 break; 146 } 147 148 // Set #samples 149 switch (c) 150 { 151 case 0: 152 // samples = 77; 153 // samples = 71; 154 // samples = 66; 155 // samples = 62; 156 samples = 50; 157 // samples = 48; 158 break; 159 case 1: 160 // samples = 154; 161 // samples = 142; 162 // samples = 133; 163 // samples = 125; 164 samples = 100; 165 // samples = 95; 166 break; 167 case 2: 168 // samples = 384; 169 // samples = 357; 170 // samples = 333; 171 // samples = 312; 172 samples = 250; 173 // samples = 238; 174 break; 175 case 3: 176 case 4: 177 samples = 400; 178 break; 179 default: 180 samples = 500; 181 break; 182 } 183 184 // Set timer prescaler 185 timerPrescaler = (c <= 9 ? TIMERPS0001 : (c <= 17 ? TIMERPS0256 : TIMERPS1024)); 186 187 // Set counter max value 188 switch (c) 189 { 190 case 0: 191 case 1: 192 case 2: 193 // cnt = 208; 194 // cnt = 224; 195 // cnt = 240; 196 // cnt = 256; 197 cnt = 320; 198 // cnt = 336; 199 break; 200 case 3: 201 case 4: 202 case 5: 203 case 6: 204 case 7: 205 cnt = 400 << (c - 3); 206 break; 207 case 8: 208 cnt = 16000; 209 break; 210 case 9: 211 cnt = 32000; 212 break; 213 case 10: 214 cnt = 250; 215 break; 216 case 11: 217 case 12: 218 case 13: 219 cnt = 625 << (c - 11); 220 break; 221 case 14: 222 case 15: 223 case 16: 224 cnt = 6250 << (c - 14); 225 break; 226 case 17: 227 case 19: 228 cnt = 62500; 229 break; 230 case 18: 231 cnt = 31250; 232 break; 233 } 234 OCR1A = cnt; 235 236 writeImmediate = (c >= 8); 237} 238 239/* Set trigger mode to falling or rising edge */ 240void ScopeOne::setTriggerMode(byte c) 241{ 242 if (c == 1) 243 { 244 EICRA &= ~(TRIGCLR << (TriggerPin == 2 ? 0 : 2)); // 1 is falling edge 245 } 246 else 247 { 248 EICRA |= TRIGRISE << (TriggerPin == 2 ? 0 : 2); // 0 is rising edge 249 } 250} 251 252/* Sweep mode (continuous or single) */ 253void ScopeOne::setSweepMode(byte c) 254{ 255 continuousSweep = (c == 0); // 'y' is continuous, 'z' is single 256} 257 258/* Set the channel 'A'-'F' */ 259void ScopeOne::setChannel(byte c) 260{ 261 if (c < maxChannels) 262 { 263 byte offset = firstChannel - A0; 264 currentChannel = c; 265 ADMUX &= B11110000; 266 ADMUX |= ((offset + currentChannel) & 0x7); // Switch the ADC multiplexer to the channel pin 267 } 268} 269 270/* Start oscilloscope mode */ 271void ScopeOne::setScope() 272{ 273 TCCR1B = 0; // ... Stop counter 274 scope = true; 275 276 TCCR1A = TIMERCTCA; // Use Timer1 in 'match OCR' mode for sampling 277 TCCR1B = TIMERCTCB; // No clock, so no interrupts yet 278 279 Serial.print((char) 0xFF); // Mark sending of channel number 280 Serial.print((char) 0xFF); 281 Serial.print((char) (getNrChannels() + '0')); // Send channel number to PC 282 283 TIMSK1 |= (1 << OCIE1A); // Enable timer1 compare interrupts 284 execute(currentBase); // Set the time base to the last used 285 scopeReset(); // Restart scope 286} 287 288/* Start frequency counter mode */ 289void ScopeOne::setCounter() 290{ 291 scope = false; 292 periodCount = 1; 293 TCCR1A = TIMERCNTA; // Use Timer1 in normal mode for counting 294 TCCR1B = 0; // Hold timer 295 TIMSK1 &= ~(1 << OCIE1A); // Disable timer1 compare interrupts 296 EIFR |= intBit; 297 EIMSK |= intBit; // Enable external interrupt 298 counterReset(); // Restart frequency counter 299} 300 301/* Set the number of periods to count for determining frequency */ 302void ScopeOne::setPeriods(byte c) 303{ 304 int p = c / 3; // Period count 1, 10, 100, 1000 or 10000 305 counterDiv = 5 - (c % 3); // Clock divider 64, 256 or 1024 for accuracy 306 periodCount = 1; 307 for (int per = 0; per < p; per++) 308 { 309 periodCount *= 10; 310 } 311} 312 313/* Handle command characters sent from the console */ 314char ScopeOne::execute(char c) 315{ 316 switch (c) 317 { 318 case Reset: 319 // Reset always happens at the end of execute 320 break; 321 case TrigRising: 322 case TrigFalling: 323 setTriggerMode(c - TrigRising); 324 break; 325 case ContSweep: 326 case SingleSweep: 327 setSweepMode(c - ContSweep); 328 break; 329 case CounterMode: 330 setCounter(); 331 break; 332 case ScopeMode: 333 setScope(); 334 break; 335 default: 336 if (c >= Channel1 && c <= ChannelMax) 337 { 338 setChannel(c - Channel1); 339 } 340 else if (c >= TimeBaseMin && c <= TimeBaseMax) 341 { 342 setSampleTime(c - TimeBaseMin); 343 // Store the time base as current 344 currentBase = c; 345 } 346 else if (c >= CounterBaseMin && c <= CounterBaseMax) 347 { 348 setPeriods(c - CounterBaseMin); 349 } 350 else 351 { 352 return c; // No scope command, pass on to user's program 353 } 354 } 355 if (scope) 356 { 357 scopeReset(); 358 } 359 else 360 { 361 counterReset(); 362 } 363 return Reset; // Indicates a scope command was processed 364} 365 366/* Send all available samples to the console */ 367void ScopeOne::writeData() 368{ 369 for (; writeIndex < index; writeIndex++) 370 { 371 Serial.print((char) sample[writeIndex]); 372 } 373} 374 375/* Writes the count value for the defined number of periods in 4 bytes, LSB first */ 376void ScopeOne::writeCount(unsigned long cnt) 377{ 378 unsigned long c = cnt; 379 380 for (int d = 0; d < 4; d++) 381 { 382 Serial.print((char) (c & 0xFF)); 383 c >>= 8; 384 } 385 Serial.print((char) (0xFF)); // Send all ones to mark end of transmission 386} 387 388/* Standard set-up */ 389void ScopeOne::setup() 390{ 391 byte pin = firstChannel; 392 393 Serial.begin(115200); // Fast serial connection 394 395 pinMode(TriggerPin, INPUT_PULLUP); // The trigger input 396 397 for (int i = 0; i < maxChannels; i++, pin++) // Channel inputs 398 { 399 pinMode(pin, INPUT); 400 } 401 402 TIMSK0 = 0; // Disable other timer interrupts 403 TIMSK2 = 0; 404 405 // External interrupt for trigger signal 406 EIMSK &= ~intBit; // Disable trigger interrupt first; 407 EIFR |= intBit; // Clear pending interrupts 408 EICRA = TRIGRISE << (TriggerPin == 2 ? 0 : 2); // Start with rising edge 409 410 initAdc(); // Set up the analog inputs and the ADC 411 412 // Set the default controls 413 execute(TrigRising); // Rising edge trigger 414 execute(ContSweep); // Continuous sweep 415 execute(Channel1); // Channel A0 416 execute(TimeBaseMin + 7); // Time base at 10ms/div 417 execute(CounterBaseMin); // Counter time base at 1x/1024 418 419 execute(InitialMode); // Start in selected initial mode 420} 421 422/* Command read */ 423char ScopeOne::getCommand() 424{ 425 if (Serial.available()) // If a command was sent from the console, ... 426 { 427 return theScope->execute(Serial.read()); // ...handle it here 428 } 429 return 0; 430} 431 432inline void ScopeOne::trigger() 433{ 434 if (scope) 435 { 436 EIMSK &= ~intBit; 437 EIFR |= intBit; 438 439// readAdc(); // Read first sample immediately 440 ADCSRA &= CLEARADIF; 441 ADCSRA |= ADCSTART; // Start conversion for the first sample 442 443 TCNT1 = 0; // Reset timer 444 TCCR1B |= timerPrescaler; // Start timer now 445 } 446 else 447 { 448 int c = TCNT1; 449 450 TCNT1 = 0; 451 TCCR1B = counterDiv; // Start counter 452 count += c; // Add current timer to total count 453 periods++; // Another period counted 454 if (periods > periodCount) // If all periods counted for a measurment... 455 { 456 TCCR1B = 0; // ... Stop counter 457 writeCount(count); // Report value to PC 458 counterReset(); // Reset counter for next measurement 459 } 460 } 461} 462 463inline void ScopeOne::getSample() 464{ 465 readAdc(); // Read next ADC sample and store it 466 if (index >= samples) 467 { 468 stopSweep(); // Got all samples for this sweep, so end it 469 } 470 if (writeImmediate) 471 { 472 writeData(); 473 } 474} 475 476// Trigger Interrupt Service Routine 477#if (TriggerPin == 2) 478ISR(INT0_vect) 479#else 480ISR(INT1_vect) 481#endif 482{ 483 ScopeOne::theScope->trigger(); 484} 485 486// Interrupt Service Routine for timer OCR compare match 487ISR(TIMER1_COMPA_vect) 488{ 489 ScopeOne::theScope->getSample(); 490} 491
ScopeLCD.ino
arduino
An example project that uses the ScopeOne library together with an LCD, an NE555 based wave generator and a blinking LED
1/* 2 * ScopeLCD demonstrates the use of the ScopeOne library used in 3 * combination with an LCD display 4*/ 5 6#include <ScopeOne.h> 7#include <LiquidCrystal.h> 8 9#define PwmFreq65k 0x1 10#define PwmFreq7k 0x2 11#define PwmFreq976 0x3 12#define PwmFreq244 0x4 13#define PwmFreq061 0x5 14 15// Associate LCD interface pins 16const int rs = 3, rw = 4, en = 5, d4 = 8, d5 = 9, d6 = 10, d7 = 11; 17// Associate display contrast and backlight control pins 18const int contrast = 6, backlight = 7; 19// An LED to play with 20const int Led = 12; 21 22LiquidCrystal lcd(rs, en, d4, d5, d6, d7); 23 24// The actual contrast value 25int co = 70; 26 27unsigned long blinkTime = 500; 28unsigned long blink = 0UL; 29 30void setup() 31{ 32 pinMode(rw, OUTPUT); 33 digitalWrite(rw, LOW); // Not used by LCD library, set to write 34 35 pinMode(contrast, OUTPUT); 36 pinMode(backlight, OUTPUT); 37 analogWrite(contrast, co); 38 digitalWrite(backlight, HIGH); 39 40 pinMode(Led, OUTPUT); 41 42 // Set up the LCD: 16 characters, 2 rows 43 lcd.begin(16, 2); 44 45 // Install the scope 46 ScopeOne::install(); 47 48 TCCR0B = (TCCR0B & 0xF8) | PwmFreq7k; 49// TCCR0B = (TCCR0B & 0xF8) | PwmFreq976; // Set pin 5/6 PWM frequency 50 51 // Print the scope configuration to the LCD 52 lcd.setCursor(0, 0); 53 lcd.print(ScopeOne::theScope->isScopeMode() ? "Scope" : "Counter"); 54 lcd.setCursor(0, 1); 55 lcd.print("Ch1=A"); 56 lcd.print(ScopeOne::theScope->getChannel1() - A0); 57 lcd.print(", #ch="); 58 lcd.print(ScopeOne::theScope->getNrChannels()); 59} 60 61void loop() 62{ 63 if (blink == 0) 64 { 65 digitalWrite(Led, HIGH + LOW - digitalRead(Led)); 66 blink = blinkTime; 67 } 68 blink--; 69 70 switch (ScopeOne::getCommand()) 71 { 72 case 0: 73 // No command received 74 break; 75 case '-': // Decrease contrast 76 if (co > 0) 77 { 78 co--; 79 analogWrite(contrast, co); 80 } 81 break; 82 case '+': // Increase contrast 83 if (co < 255) 84 { 85 co++; 86 analogWrite(contrast, co); 87 } 88 break; 89 case '.': // Toggle backlight 90 digitalWrite(backlight, HIGH + LOW - digitalRead(backlight)); 91 break; 92 case Reset: // Scope command received, update info 93 // Print a message to the LCD. 94 lcd.setCursor(0, 0); 95 lcd.print(ScopeOne::theScope->isScopeMode() ? "Scope " : "Counter"); 96 break; 97 default: 98 // Ignore other key strokes 99 break; 100 } 101 102 // Show the contrast value 103 lcd.setCursor(13, 0); 104 lcd.print(co); 105 lcd.print(" "); 106} 107
Oscilloscope.ino
arduino
The minimum code to create an oscilloscope on your Arduino using the ScopeOne library
1/* 2 Oscilloscope using the ScopeOne library 3*/ 4 5// Uncomment the next line to use pin 3 instead of pin 2 as trigger input 6//#define TriggerPin 3 7 8#include <ScopeOne.h> 9 10void setup() 11{ 12 ScopeOne::install(); 13 14 // Other ways of configuring the scope: 15 // ScopeOne::install(A2); // Uses A2-A5 as inputs 16 // ScopeOne::install(A3, 2); // Uses A3 and A4 as inputs (2 channels) 17} 18 19void loop() 20{ 21 char ch = ScopeOne::getCommand(); 22 23 // ch will be 0 if no command was received 24 // ch will be ScopeOneCommand if a scope command was received and handled 25 // ch will be the character typed on the keyboard of your PC (not a letter) 26} 27
ScopeOne.cpp
c_cpp
ScopeOne C++ source code
1#include <ScopeOne.h> 2 3ScopeOne* ScopeOne::theScope = NULL; 4byte ScopeOne::_isInitialized = 0; 5 6// Create ad set-up a scope 7ScopeOne* ScopeOne::install(byte channel1, byte nrChannels) 8{ 9 create(channel1, nrChannels); 10 theScope->setup(); 11 return theScope; 12} 13 14// Create a scope object 15ScopeOne* ScopeOne::create(byte channel1, byte nrChannels) 16{ 17 if (_isInitialized != InitValue) 18 { 19 delete theScope; 20 } 21 theScope = new ScopeOne(channel1, nrChannels); 22 _isInitialized = InitValue; 23 return theScope; 24} 25 26// Constructor 27ScopeOne::ScopeOne(byte channel1, byte nrChannels) 28{ 29 if (channel1 < A0 || channel1 > A5) 30 { 31 firstChannel = A0; 32 maxChannels = 1; 33 } 34 else 35 { 36 if (channel1 + nrChannels - 1 >= A5) 37 { 38 maxChannels = A5 - channel1 + 1; 39 } 40 else 41 { 42 maxChannels = nrChannels <= MaxChannels ? nrChannels : MaxChannels; 43 } 44 firstChannel = channel1; 45 } 46 scope = true; 47 intBit = (INTBIT << (TriggerPin == 2 ? 0 : 1)); 48 currentChannel = 0; 49 counterDiv = TIMERPS1024; 50} 51 52byte ScopeOne::getChannel1() 53{ 54 return firstChannel; 55} 56 57byte ScopeOne::getNrChannels() 58{ 59 return maxChannels; 60} 61 62/* Return scope mode */ 63bool ScopeOne::isScopeMode() 64{ 65 return scope; 66} 67 68/* Initialize the Analog-Digital Converter */ 69void ScopeOne::initAdc() 70{ 71 ADCSRA = ADCINIT; 72 ADMUX = ADCSELECT; 73} 74 75/* Read a sample from the ADC */ 76void ScopeOne::readAdc() 77{ 78 sample[index++] = ADCH; // 8-bit sample size for speed 79 ADCSRA &= CLEARADIF; 80 ADCSRA |= ADCSTART; // Start conversion for the next sample 81} 82 83/* Handle the end of a sweep */ 84void ScopeOne::stopSweep() 85{ 86 TCCR1B &= TIMERNOCLK; // Set clock select to 0 (no clock) 87 index++; 88 writeData(); // Write sampled data to serial connection 89 if (continuousSweep) 90 { 91 scopeReset(); // Restart automatically in continuous sweep mode 92 } 93} 94 95/* Reset the scope for a new sweep */ 96void ScopeOne::scopeReset() 97{ 98 TCCR1B &= TIMERNOCLK; // Stop the timer by setting clock select to 0 (no clock) 99 100 Serial.print((char) 0xFF); // Mark end of sweep to console 101 102 index = 0; // Reset sweep data 103 writeIndex = 0; 104 105 EIFR |= intBit; // Reset trigger interrupt flag 106 EIMSK |= intBit; // Enable interrupts on trigger input 107 108 // Wait for trigger signal interrupt 109} 110 111/* Reset the frequency counter to start another measurement */ 112void ScopeOne::counterReset() 113{ 114 periods = 0; // Reset counted periods 115 count = 0UL; // Reset total timer counts 116 TCNT1 = 0; // Reset timer 117 EIFR != intBit; 118} 119 120/* Set the sample time for the selected time base. 121 * The selection is done with a single character 'a'-'t'. 122*/ 123void ScopeOne::setSampleTime(byte c) 124{ 125 unsigned int cnt; 126 127 ADCSRA &= ADCPSCLR; // Clear prescaler 128 // Set ADC prescaler 129 switch (c) 130 { 131 case 0: 132 case 1: 133 case 2: 134 case 3: 135 ADCSRA |= ADCPS016; 136 break; 137 case 4: 138 ADCSRA |= ADCPS032; 139 break; 140 case 5: 141 ADCSRA |= ADCPS064; 142 break; 143 default: 144 ADCSRA |= ADCPS128; 145 break; 146 } 147 148 // Set #samples 149 switch (c) 150 { 151 case 0: 152 // samples = 77; 153 // samples = 71; 154 // samples = 66; 155 // samples = 62; 156 samples = 50; 157 // samples = 48; 158 break; 159 case 1: 160 // samples = 154; 161 // samples = 142; 162 // samples = 133; 163 // samples = 125; 164 samples = 100; 165 // samples = 95; 166 break; 167 case 2: 168 // samples = 384; 169 // samples = 357; 170 // samples = 333; 171 // samples = 312; 172 samples = 250; 173 // samples = 238; 174 break; 175 case 3: 176 case 4: 177 samples = 400; 178 break; 179 default: 180 samples = 500; 181 break; 182 } 183 184 // Set timer prescaler 185 timerPrescaler = (c <= 9 ? TIMERPS0001 : (c <= 17 ? TIMERPS0256 : TIMERPS1024)); 186 187 // Set counter max value 188 switch (c) 189 { 190 case 0: 191 case 1: 192 case 2: 193 // cnt = 208; 194 // cnt = 224; 195 // cnt = 240; 196 // cnt = 256; 197 cnt = 320; 198 // cnt = 336; 199 break; 200 case 3: 201 case 4: 202 case 5: 203 case 6: 204 case 7: 205 cnt = 400 << (c - 3); 206 break; 207 case 8: 208 cnt = 16000; 209 break; 210 case 9: 211 cnt = 32000; 212 break; 213 case 10: 214 cnt = 250; 215 break; 216 case 11: 217 case 12: 218 case 13: 219 cnt = 625 << (c - 11); 220 break; 221 case 14: 222 case 15: 223 case 16: 224 cnt = 6250 << (c - 14); 225 break; 226 case 17: 227 case 19: 228 cnt = 62500; 229 break; 230 case 18: 231 cnt = 31250; 232 break; 233 } 234 OCR1A = cnt; 235 236 writeImmediate = (c >= 8); 237} 238 239/* Set trigger mode to falling or rising edge */ 240void ScopeOne::setTriggerMode(byte c) 241{ 242 if (c == 1) 243 { 244 EICRA &= ~(TRIGCLR << (TriggerPin == 2 ? 0 : 2)); // 1 is falling edge 245 } 246 else 247 { 248 EICRA |= TRIGRISE << (TriggerPin == 2 ? 0 : 2); // 0 is rising edge 249 } 250} 251 252/* Sweep mode (continuous or single) */ 253void ScopeOne::setSweepMode(byte c) 254{ 255 continuousSweep = (c == 0); // 'y' is continuous, 'z' is single 256} 257 258/* Set the channel 'A'-'F' */ 259void ScopeOne::setChannel(byte c) 260{ 261 if (c < maxChannels) 262 { 263 byte offset = firstChannel - A0; 264 currentChannel = c; 265 ADMUX &= B11110000; 266 ADMUX |= ((offset + currentChannel) & 0x7); // Switch the ADC multiplexer to the channel pin 267 } 268} 269 270/* Start oscilloscope mode */ 271void ScopeOne::setScope() 272{ 273 TCCR1B = 0; // ... Stop counter 274 scope = true; 275 276 TCCR1A = TIMERCTCA; // Use Timer1 in 'match OCR' mode for sampling 277 TCCR1B = TIMERCTCB; // No clock, so no interrupts yet 278 279 Serial.print((char) 0xFF); // Mark sending of channel number 280 Serial.print((char) 0xFF); 281 Serial.print((char) (getNrChannels() + '0')); // Send channel number to PC 282 283 TIMSK1 |= (1 << OCIE1A); // Enable timer1 compare interrupts 284 execute(currentBase); // Set the time base to the last used 285 scopeReset(); // Restart scope 286} 287 288/* Start frequency counter mode */ 289void ScopeOne::setCounter() 290{ 291 scope = false; 292 periodCount = 1; 293 TCCR1A = TIMERCNTA; // Use Timer1 in normal mode for counting 294 TCCR1B = 0; // Hold timer 295 TIMSK1 &= ~(1 << OCIE1A); // Disable timer1 compare interrupts 296 EIFR |= intBit; 297 EIMSK |= intBit; // Enable external interrupt 298 counterReset(); // Restart frequency counter 299} 300 301/* Set the number of periods to count for determining frequency */ 302void ScopeOne::setPeriods(byte c) 303{ 304 int p = c / 3; // Period count 1, 10, 100, 1000 or 10000 305 counterDiv = 5 - (c % 3); // Clock divider 64, 256 or 1024 for accuracy 306 periodCount = 1; 307 for (int per = 0; per < p; per++) 308 { 309 periodCount *= 10; 310 } 311} 312 313/* Handle command characters sent from the console */ 314char ScopeOne::execute(char c) 315{ 316 switch (c) 317 { 318 case Reset: 319 // Reset always happens at the end of execute 320 break; 321 case TrigRising: 322 case TrigFalling: 323 setTriggerMode(c - TrigRising); 324 break; 325 case ContSweep: 326 case SingleSweep: 327 setSweepMode(c - ContSweep); 328 break; 329 case CounterMode: 330 setCounter(); 331 break; 332 case ScopeMode: 333 setScope(); 334 break; 335 default: 336 if (c >= Channel1 && c <= ChannelMax) 337 { 338 setChannel(c - Channel1); 339 } 340 else if (c >= TimeBaseMin && c <= TimeBaseMax) 341 { 342 setSampleTime(c - TimeBaseMin); 343 // Store the time base as current 344 currentBase = c; 345 } 346 else if (c >= CounterBaseMin && c <= CounterBaseMax) 347 { 348 setPeriods(c - CounterBaseMin); 349 } 350 else 351 { 352 return c; // No scope command, pass on to user's program 353 } 354 } 355 if (scope) 356 { 357 scopeReset(); 358 } 359 else 360 { 361 counterReset(); 362 } 363 return Reset; // Indicates a scope command was processed 364} 365 366/* Send all available samples to the console */ 367void ScopeOne::writeData() 368{ 369 for (; writeIndex < index; writeIndex++) 370 { 371 Serial.print((char) sample[writeIndex]); 372 } 373} 374 375/* Writes the count value for the defined number of periods in 4 bytes, LSB first */ 376void ScopeOne::writeCount(unsigned long cnt) 377{ 378 unsigned long c = cnt; 379 380 for (int d = 0; d < 4; d++) 381 { 382 Serial.print((char) (c & 0xFF)); 383 c >>= 8; 384 } 385 Serial.print((char) (0xFF)); // Send all ones to mark end of transmission 386} 387 388/* Standard set-up */ 389void ScopeOne::setup() 390{ 391 byte pin = firstChannel; 392 393 Serial.begin(115200); // Fast serial connection 394 395 pinMode(TriggerPin, INPUT_PULLUP); // The trigger input 396 397 for (int i = 0; i < maxChannels; i++, pin++) // Channel inputs 398 { 399 pinMode(pin, INPUT); 400 } 401 402 TIMSK0 = 0; // Disable other timer interrupts 403 TIMSK2 = 0; 404 405 // External interrupt for trigger signal 406 EIMSK &= ~intBit; // Disable trigger interrupt first; 407 EIFR |= intBit; // Clear pending interrupts 408 EICRA = TRIGRISE << (TriggerPin == 2 ? 0 : 2); // Start with rising edge 409 410 initAdc(); // Set up the analog inputs and the ADC 411 412 // Set the default controls 413 execute(TrigRising); // Rising edge trigger 414 execute(ContSweep); // Continuous sweep 415 execute(Channel1); // Channel A0 416 execute(TimeBaseMin + 7); // Time base at 10ms/div 417 execute(CounterBaseMin); // Counter time base at 1x/1024 418 419 execute(InitialMode); // Start in selected initial mode 420} 421 422/* Command read */ 423char ScopeOne::getCommand() 424{ 425 if (Serial.available()) // If a command was sent from the console, ... 426 { 427 return theScope->execute(Serial.read()); // ...handle it here 428 } 429 return 0; 430} 431 432inline void ScopeOne::trigger() 433{ 434 if (scope) 435 { 436 EIMSK &= ~intBit; 437 EIFR |= intBit; 438 439// readAdc(); // Read first sample immediately 440 ADCSRA &= CLEARADIF; 441 ADCSRA |= ADCSTART; // Start conversion for the first sample 442 443 TCNT1 = 0; // Reset timer 444 TCCR1B |= timerPrescaler; // Start timer now 445 } 446 else 447 { 448 int c = TCNT1; 449 450 TCNT1 = 0; 451 TCCR1B = counterDiv; // Start counter 452 count += c; // Add current timer to total count 453 periods++; // Another period counted 454 if (periods > periodCount) // If all periods counted for a measurment... 455 { 456 TCCR1B = 0; // ... Stop counter 457 writeCount(count); // Report value to PC 458 counterReset(); // Reset counter for next measurement 459 } 460 } 461} 462 463inline void ScopeOne::getSample() 464{ 465 readAdc(); // Read next ADC sample and store it 466 if (index >= samples) 467 { 468 stopSweep(); // Got all samples for this sweep, so end it 469 } 470 if (writeImmediate) 471 { 472 writeData(); 473 } 474} 475 476// Trigger Interrupt Service Routine 477#if (TriggerPin == 2) 478ISR(INT0_vect) 479#else 480ISR(INT1_vect) 481#endif 482{ 483 ScopeOne::theScope->trigger(); 484} 485 486// Interrupt Service Routine for timer OCR compare match 487ISR(TIMER1_COMPA_vect) 488{ 489 ScopeOne::theScope->getSample(); 490} 491
keywords.txt
text
Keyword file for the ScopeOne library
1####################################### 2# Syntax Coloring Map For ScopeOne 3####################################### 4 5####################################### 6# 7 Class (KEYWORD1) 8####################################### 9 10ScopeOne KEYWORD1 ScopeOneLibrary 11 12####################################### 13# 14 Methods and Functions (KEYWORD2) 15####################################### 16 17# 18 method names in ScopeOne 19 20install KEYWORD2 21create KEYWORD2 22setup KEYWORD2 23isScopeMode KEYWORD2 24getChannel1 KEYWORD2 25getNrChannels KEYWORD2 26getCommand KEYWORD2 27 28####################################### 29# 30 Constants (LITERAL1) 31####################################### 32 33DfltOnTime LITERAL1 34MinusPattern LITERAL1 35
library.properties
properties
ScopeOne library property file
1name=ScopeOne 2version=1.0.0 3author=Qixaz 4maintainer=Qixaz (www.qixaz.com) 5sentence=Digital 6 sampling oscilloscope and frequency counter for diagnosing your circuits 7paragraph=Library 8 to use your Arduino One as a digital sampling scope and frequency counter. You can 9 create the scope with just a few commands in your project code and diagnose your 10 circuit. 11category=Data Processing 12url=http://www.qixaz.com 13architectures=* 14dot_a_linkage=true 15
ScopeOne.h
h
ScopeOne header file
1#ifndef _Scope_h_ 2#define _Scope_h_ 3 4/* 5 1-6 channel oscilloscope 6 7 8 Time base: 9 100, 200, 500 us 10 1, 2, 5 ms 11 10, 20, 50 ms 12 100, 13 200, 500 ms 14 1, 2, 5 s 15 10, 20, 50 s 16 17 Counter time bases: 18 19 1 period, clock divider 1024 20 1 period, clock divider 256 21 1 period, 22 clock divider 64 23 10 periods, clock divider 1024 24 ... 25 10000 periods, 26 clock divider 64 27*/ 28 29#include <Arduino.h> 30 31#define MaxChannels 6 32 33// 34 Commands to the Arduino board 35#define Reset 'X' 36#define ScopeMode 'Y' 37#define 38 CounterMode 'Z' 39#define Channel1 'A' 40#define ChannelMax 'F' 41#define TrigRising 42 'w' 43#define TrigFalling 'x' 44#define ContSweep 'y' 45#define SingleSweep 'z' 46#define 47 TimeBaseMin 'a' 48#define TimeBaseMax 't' 49#define CounterBaseMin 'G' 50#define 51 CounterBaseMax 'U' 52#define InitialMode 'Z' 53 54#define ScopeOneCommand 'X' 55 56// 57 The trigger default is pin 2, unless user has defined it otherwise 58#ifndef TriggerPin 59#define 60 TriggerPin 2 61#endif 62 63#define MaxSamples 500 64 65// Timer and interrupt 66 settings 67#define INTBIT B00000001 68#define TRIGCLR B00000001 69#define TRIGRISE 70 B00000011 71#define TIMERCTCA B00000000 72#define TIMERCTCB B10001000 73#define 74 TIMERCNTA B00000000 75#define TIMERCNTB B00000000 76#define TIMERNOCLK B11111000 77#define 78 TIMERPS0001 B00000001 79#define TIMERPS0256 B00000100 80#define TIMERPS1024 B00000101 81#define 82 ADCINIT B10000111 83#define ADCSELECT B01100000 84#define ADCSTART B01000000 85#define 86 ADCPSCLR B11111000 87#define ADCPS016 B00000100 88#define ADCPS032 B00000101 89#define 90 ADCPS064 B00000110 91#define ADCPS128 B00000111 92#define ADCREADY B00010000 93#define 94 CLEARADIF B10101111 95 96#define InitValue 123 97 98class ScopeOne 99{ 100 public: 101 static 102 ScopeOne* install(byte channel1 = A0, byte nrChannels = MaxChannels); 103 static 104 ScopeOne* create(byte channel1 = A0, byte nrChannels = MaxChannels); 105 void 106 setup(); 107 108 static char getCommand(); 109 110 bool isScopeMode(); 111 byte 112 getChannel1(); 113 byte getNrChannels(); 114 115 inline void trigger(); 116 inline 117 void getSample(); 118 119 static ScopeOne* theScope; 120 121 protected: 122 // 123 Constructor 124 ScopeOne(byte channel1, byte nrChannels); 125 126 // Command 127 handler 128 char execute(char c); 129 130 // Scope functions 131 void 132 setScope(); 133 void scopeReset(); 134 void setChannel(byte c); 135 void 136 setSampleTime(byte c); 137 void setTriggerMode(byte c); 138 void setSweepMode(byte 139 c); 140 void stopSweep(); 141 void writeData(); 142 143 // Counter 144 functions 145 void setCounter(); 146 void counterReset(); 147 void setPeriods(byte 148 c); 149 void writeCount(unsigned long cnt); 150 151 void init(); 152 void 153 initAdc(); 154 void readAdc(); 155 156 bool scope; 157 bool continuousSweep; 158 byte 159 maxChannels; 160 byte firstChannel; 161 byte currentChannel; 162 char currentBase; 163 164 // 165 Sample variables 166 int samples; 167 volatile byte sample[MaxSamples]; 168 volatile 169 int index; 170 volatile int writeIndex; 171 172 // Frequencey counter current 173 ode variables 174 int periodCount; 175 volatile int periods; 176 volatile 177 unsigned long count; 178 179 byte timerPrescaler; 180 byte intBit; 181 volatile 182 byte counterDiv; 183 bool writeImmediate; 184 185 static byte _isInitialized; 186}; 187 188#endif 189
ScopeLCD.ino
arduino
An example project that uses the ScopeOne library together with an LCD, an NE555 based wave generator and a blinking LED
1/* 2 * ScopeLCD demonstrates the use of the ScopeOne library used in 3 4 * combination with an LCD display 5*/ 6 7#include <ScopeOne.h> 8#include 9 <LiquidCrystal.h> 10 11#define PwmFreq65k 0x1 12#define PwmFreq7k 0x2 13#define 14 PwmFreq976 0x3 15#define PwmFreq244 0x4 16#define PwmFreq061 0x5 17 18// Associate 19 LCD interface pins 20const int rs = 3, rw = 4, en = 5, d4 = 8, d5 = 9, d6 = 10, 21 d7 = 11; 22// Associate display contrast and backlight control pins 23const int 24 contrast = 6, backlight = 7; 25// An LED to play with 26const int Led = 12; 27 28LiquidCrystal 29 lcd(rs, en, d4, d5, d6, d7); 30 31// The actual contrast value 32int co = 70; 33 34unsigned 35 long blinkTime = 500; 36unsigned long blink = 0UL; 37 38void setup() 39{ 40 41 pinMode(rw, OUTPUT); 42 digitalWrite(rw, LOW); // Not used by LCD library, set 43 to write 44 45 pinMode(contrast, OUTPUT); 46 pinMode(backlight, OUTPUT); 47 48 analogWrite(contrast, co); 49 digitalWrite(backlight, HIGH); 50 51 pinMode(Led, 52 OUTPUT); 53 54 // Set up the LCD: 16 characters, 2 rows 55 lcd.begin(16, 56 2); 57 58 // Install the scope 59 ScopeOne::install(); 60 61 TCCR0B = (TCCR0B 62 & 0xF8) | PwmFreq7k; 63// TCCR0B = (TCCR0B & 0xF8) | PwmFreq976; // 64 Set pin 5/6 PWM frequency 65 66 // Print the scope configuration to the LCD 67 68 lcd.setCursor(0, 0); 69 lcd.print(ScopeOne::theScope->isScopeMode() ? "Scope" 70 : "Counter"); 71 lcd.setCursor(0, 1); 72 lcd.print("Ch1=A"); 73 lcd.print(ScopeOne::theScope->getChannel1() 74 - A0); 75 lcd.print(", #ch="); 76 lcd.print(ScopeOne::theScope->getNrChannels()); 77} 78 79void 80 loop() 81{ 82 if (blink == 0) 83 { 84 digitalWrite(Led, HIGH + LOW - digitalRead(Led)); 85 86 blink = blinkTime; 87 } 88 blink--; 89 90 switch (ScopeOne::getCommand()) 91 92 { 93 case 0: 94 // No command received 95 break; 96 case '-': 97 // Decrease contrast 98 if (co > 0) 99 { 100 co--; 101 102 analogWrite(contrast, co); 103 } 104 break; 105 case '+': // 106 Increase contrast 107 if (co < 255) 108 { 109 co++; 110 analogWrite(contrast, 111 co); 112 } 113 break; 114 case '.': // Toggle backlight 115 digitalWrite(backlight, 116 HIGH + LOW - digitalRead(backlight)); 117 break; 118 case Reset: // 119 Scope command received, update info 120 // Print a message to the LCD. 121 122 lcd.setCursor(0, 0); 123 lcd.print(ScopeOne::theScope->isScopeMode() 124 ? "Scope " : "Counter"); 125 break; 126 default: 127 // Ignore 128 other key strokes 129 break; 130 } 131 132 // Show the contrast value 133 134 lcd.setCursor(13, 0); 135 lcd.print(co); 136 lcd.print(" "); 137} 138
Downloadable files
Oscilloscope.fzz
The breadboard and schematics of the LCD and NE555 circuit
Oscilloscope.fzz
Oscilloscope.fzz
The breadboard and schematics of the LCD and NE555 circuit
Oscilloscope.fzz
Comments
Only logged in users can leave comments