Arduino oscilloscope 22kHz bandwidth

  簡易 Arduino 示波器

    Arduino 的 ADC 是很好用的東西,可以用來測量電壓轉成數位資料,而示波器就是把隨著時間變化的電壓畫成圖形表示出來,利用 arduino 的 ADC 加上電腦的繪圖能力,作成了一個簡易示波器。

Arduino oscilloscope - flashlight waveform
做了一下功課,已經有人作了Arduino 示波器 (不靠其的的元件,只用 arduino)
窮人的示波器 - 使用 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="">
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="">
    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="">
    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="">
    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: