Arduino 的 ADC 是很好用的東西,可以用來測量電壓轉成數位資料,而示波器就是把隨著時間變化的電壓畫成圖形表示出來,利用 arduino 的 ADC 加上電腦的繪圖能力,作成了一個簡易示波器。
|  | 
| Arduino oscilloscope - flashlight waveform | 
窮人的示波器 - 使用 arduino 透過序列埠傳到到電腦,然後用 Processing 程式作繪圖。
Arduino – Poor Man’s Oscilloscope
http://randomnerdtutorials.com/arduino-poor-mans-oscilloscope/
arduino 接上電腦後用 Processing IDE 執行他的程式,可以看到由右往左一直捲動的波形,我有一台示波器了 :)。用10k電阻加光敏電阻做了一個測試電路來觀察波形,用手電筒的光照射產生電壓變化,得到了以上的波形,手電筒有五檔變化:強光、中光、弱光、爆閃、SOS,可以看到中光和弱光是用PWM來調整亮度的。
|  | 
| Arduino oscilloscope photo resistor test circuit | 
可惜的是原來的程式算了一下每秒大約更新 400 個點,對一個示波器來說最重要的參數就是頻寬,除了看到變化,更希望能看到細節。看了一下程式作了一些改進,首先是 Serial 的速度從 9600 提高到了115200 bps,然後每個點原來是送3個byte,改成只送1個byte,犧牲了解析度換取速度,這樣從每秒 400 個點提高到了每秒約 8300 個點,原以為這樣就是極限了,但還有更好的方式,待續 ...
11760
試試調高Baud rate ,結果我的機器只能到230400,再高 Serial 就開始錯亂了,再看現在的程式,最花時間的是ADC ,Google 找到了這一篇講 ADC clock 是可以調整的,
Faster analogRead()
http://www.microsmart.co.za/technical/2014/03/01/advanced-arduino-adc/
最後調整 ADC clock 到 500 kHz, 每做一次約 36 us,配上 230400 的 baud,結果就是現在 arduino 每秒可送出 22240 個點。
|  | 
| Arduino oscilloscope fluorescent light | 
|  | 
| Arduino oscilloscope Auto Run Trig'd 22240 | 
接下來想要的功能是訊號的同步(Trigger)觸發功能,當訊號是周期性的波時,如果沒能掌握
波開始顯示的時機,週期性的波並不一定能重疊在一起,畫面上會看到一堆不同起始點的波跑來跑去,
|  | 
| unsynced waveform | 
上升邊緣觸發
|  | 
| Arduino oscilloscope auto stop trigger rising edge | 
|  | 
| Arduino oscilloscope single stop trigger falling edge | 
手電筒的弱光模式波形
|  | 
| Arduino oscilloscope flashlight pwm | 
拿來看 uart 9600 的波形
|  | 
| Arduino oscilloscope Serial 9600 bps singal | 
按鍵說明:
左鍵 設定觸發電壓(Set trigger voltage)
右鍵 切換 上升邊緣(正緣)觸發、下降邊緣(負緣)觸發 (Rising edge/Falling edge trigger)
z,x 縮小、放大 (Zoom out, Zoom in)
-,+ 忽略資料點 (Ignore data, adjust bandwidth)
r,Space 開始、停止 (Run, Stop)
a,s 切換 自動、單次觸發 (Auto, Single trigger)
Enter 所有設定回到預設值 (Default Setup)
程式:從 google drive下載
// arduino 上跑的程式
/*
* Arduino oscilloscope
* Read ADC value from pin A3 and send byte data to serial port.
*/
#define ANALOG_IN 3
// Define ADC prescaler
const unsigned char PS_32 = (1 << ADPS2) | (1 << ADPS0);
const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
void setup() {
Serial.begin(230400); // as quick as possible
pinMode(ANALOG_IN, INPUT);
// setup the ADC
ADCSRA &= ~PS_128;
ADCSRA |= PS_32;
}
void loop() {
Serial.write( (analogRead(ANALOG_IN) + 2) >> 2 );
}
/*********************************************************************/
// processing 上跑的程式,請下載 processing  IDE 來執行  https://processing.org/download/
// 因為 Serial port 的位置可能不一樣,可以參考一開始會印的 COM port 位址,
// 不一樣的時候請改這一行 port = new Serial(this, Serial.list()[1], 230400);
//
/* \O/ \O/ \O/ \O/ \O/ \O/ \O/ \O/ \O/ \O/ \O/ \O/ \O/ \O/ \O/ \O/ 
 * Arduino Oscilloscope
 * Vsualize signal of analog pin output from arduino.
 * 
 * (c) 2016 Kinmenalex Software
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  
 */
import processing.serial.*;
final String title = "Arduino Oscilloscope 0.80";
Serial port;  
int[] values;
float zoom;
int count=0;
final int trigSensitivity=1;
int trigSlope=0;
int trigLevel;
int trigVoltage; // 2500 mV
boolean trigd;
boolean bSingle;
boolean bRun;
final int bufSize=1<<13 8k="" div="" nbsp="">13>
  
final int bufMask=bufSize-1;
int bufIndex = 0;
int t0=0;
final int skipLevel[] = {1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000};
int skipIndex;
int skipCount;
void setup() 
{
  size(1280, 512);
  // List all the available serial ports
  for (int i=0; i
    println("["+ i +"] "+ Serial.list()[i]);
  }
  // Open the port that the arduino is connected to and use the same speed (230400 bps)
  port = new Serial(this, Serial.list()[1], 230400);
  values = new int[bufSize];
  defaultSet();
  surface.setTitle(title);
  frameRate(50);
}
void trigVoltageSet(int voltage) {
  trigLevel = voltage*256/5/1000; 
  trigVoltage = voltage;
}
void trigLevelSet(int level) {
  int voltage = level *5 *1000 / height;
  trigVoltageSet(voltage);
}
void defaultSet() {
  zoom = 1.0f;
  trigVoltageSet(2500);
  trigSlope=0;
  skipIndex=0;
  bRun=true;
  bSingle=false;
}
int getY(int val) {
  return (int)(height - val * height / 256 );
}
int getValues() {
  while ( port.available() > 0 ) {
    if (skipCount>0) {
      port.read();
      skipCount--;
      continue;
    }
    skipCount = skipLevel[skipIndex]-1;
    values[bufIndex] = (port.read());
    bufIndex = (bufIndex+1) & bufMask;
    count++;
  }
  return bufIndex;
}
void drawLines() {
  int displayWidth = (int) (width / zoom);
  int k;
  if ( trigd ) {
    k = t0 - (displayWidth/2);
  } else {
    k = bufIndex-1-displayWidth;
  }
  if (k<0 div="" nbsp="">0>
    k+=bufSize;
  }
  stroke(255);
  int x0 = 0;
  int y0 = getY(values[k]);
  for (int i=1; i
    k=(k+1)&bufMask;
    int x1 = (int) (i * (width-1) / (displayWidth-1));
    int y1 = getY(values[k]);
    line(x0, y0, x1, y1);
    x0 = x1;
    y0 = y1;
  }
}
int lastShow=0;
int lastCount;
int rate;
void drawGrid() {
  int y;
  int now = millis();
  int dt = now - lastShow;
  if ( dt >= 1000) {
    rate = (count-lastCount)*1000 /dt;
    lastCount = count;
    lastShow = now;
  }
  // Voltage line
  stroke(0, 128, 0);
  textSize(14);
  fill(255, 255, 0, 255);
  for (int i=1; i<5 div="" i="">5>
    y=height/5*i+1;
    line(0, y, width, y);
    text(nf(5-i)+"V", 1, y);
  }
  // Trigger voltage
  stroke(96, 0, 0);
  textSize(14);
  fill(255, 0, 0, 255);
  y=getY(trigLevel);
  line(0, y, width, y);
  text(nf(trigVoltage/1000)+"."+
    nf((trigVoltage%1000)/100)+nf((trigVoltage%100)/10)+"V " +
    (trigSlope == 0 ? "↗":"↘"), 22, y);
  // text y
  y = height-5;
  fill(255, 128, 0, 255);
  if ( bRun ) {
    if ( rate > 0 && rate <= 50000 ) {
      text(nf(rate) + " pps", 11, y);
    }
    if ( skipIndex > 0 ) {
      text(" Skip "+nf(skipLevel[skipIndex]-1), 186, y);
    }
  }
  String str;
  fill(0, 255, 0, 255);
  if ( bSingle ) {
    str = "Single";
  } else {
    str = " Auto ";
  } 
  text(str, 90, y); 
  float w = textWidth(str);
  float h = textAscent() + textDescent(); 
  if ( !bRun ) {
    str = " Stop ";
    fill(255, 0, 0, 255);
    rect( 97+w-1, height -h-1, textWidth(str), h-1);
    fill(0);
    text(str, 96+w, y);
  } else if ( trigd ) {
    str = " Trig'd ";
    fill(0, 255, 0, 255);
    //stroke(0);    
    rect( 96+w-1, height -h-1, textWidth(str), h-1);
    fill(0);
    text(str, 96+w, y);
  }
}
int b=0;
void draw()
{
  // fade background
  stroke(0, 100);
  if ( trigd ) {
    fill(0, 100);
  } else {
    fill(0, 200);
  }
  rect(0, 0, width, height);
  // set trigger level
  if (mousePressed && mouseButton == LEFT) {
    trigLevelSet(height-mouseY);
  }
  drawGrid();
  int startCount=count;
  if ( bRun ) {
    getValues();
  }
  trigd = findTrig(int(width / zoom));
  if (bSingle && trigd) {
    bRun = false;
  }
  if ( (b++ & 0x1ff) == 0 ) {
    print(count-startCount + " ");
  }
  drawLines();
}
void mousePressed() {
  if (mouseButton == LEFT) {
    trigLevelSet(height-mouseY);
  } else if (mouseButton == RIGHT) {
    trigSlope = trigSlope==0 ? 1 : 0;
  }
}
void keyReleased() {
  println(key);
  switch (key) {
  case 'x':
    zoom *= 2.0f;
    println(zoom);
    if ( (int) (width / zoom) <= 1 )
      zoom /= 2.0f;
    break;
  case 'z':
    zoom /= 2.0f;
    println(zoom);
    if ( bufSize * zoom < width)
      zoom *= 2.0f;
    break;
  case 'r':
  case ' ':
    bRun = !bRun;
    break; 
  case 'a':
  case 's':
    bSingle = !bSingle;
    break; 
  case '+':
    if ( skipIndex > 0) {
      skipIndex--;
    }
    break;    
  case '-':
    if ( skipIndex < (skipLevel.length-1)) {
      skipIndex++;
    }
    break;     
  case '\n':  //Enter
    defaultSet();
    break; 
  default :
    break;
  }
}
boolean findTrig(int displayWidth)
{
  int th1, th2, k;
  int len = bufSize-displayWidth;
  boolean trig=false;
  int sync=0;
  k = bufIndex-1-(displayWidth/2); // get last half screen position in scan buffer
  if (k<0 div="" nbsp="">0>
    k+=bufSize;
  }
  // calculate trigger thresholds
  th1 = trigLevel + trigSensitivity; 
  th2 = trigLevel - trigSensitivity; 
  // search for trigger
  if (trigSlope == 0) { //trigger slope is rising edge
    while (len>0) {
      len--;
      if ((sync == 0) && (values[k] > th1)) {
        sync = 1;  // above trigger threshold
      } else if ((sync == 1) && (values[k] < th2)) {
        trig=true; // below trigger threshold
        break;
      }
      k = k - 1;
      if ( k < 0 ) {     
        k+=bufSize;
      }
    }
  } else {  // trigger slope is falling edge
    while (len>0) {
      len--;
      if ((sync == 0) && (values[k] <= th2)) {
        sync = 1;  // below trigger threshold
      } else if ((sync == 1) && (values[k] >= th1)) {
        trig=true; // above trigger threshold
        break;
      }
      k = k - 1;
      if ( k < 0 ) {    
        k+=bufSize;
      }
    }
  }
  t0=k;
  return trig;
}
 
 

 
 
No comments:
Post a Comment