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