Arduino Dueを使ったオシロスコープの試作(3)

このページをスマホなどでご覧になる場合は、画面を横長にする方が読みやすくなります。
目次へ  前のページへ (1) (2) (3) (4) (5) 次のページへ
2018年08月12日 公開。

5.Arduino Dueを使ったオシロスコープの試作機のソフトウェア

今回試作したオシロスコープのソフトウェアについて説明します。

5-1.MGLCDライブラリのインストール

今回作成したオシロスコープのスケッチは、MGLCDライブラリを使用しています。

MGLCDライブラリをまだインストールされていない方、あるいはVer.0.41以前のMGLCDライブラリをご使用の方は、MGLCDライブラリのページからMGLCDライブラリ Ver.0.42をダウンロードし、インストールしてください。詳しいインストールの方法が、MGLCDライブラリユーザーマニュアルのページに説明されています。

広告

5-2.オシロスコープのスケッチのダウンロード

今回開発したオシロスコープのスケッチを次のリンクからダウンロードできる様にしておきました。

Arduino Due用オシロスコープスケッチ Ver.010 DueScope010.zip (6kB)のダウンロード

ファイルをダウンロードすると、DueScope010.zipというファイル名のファイルがダウンロードされます。

このファイルはZIPで圧縮してありますので、それを展開すると、DueScopeという名前のフォルダができあがります。(図8参照)

図8、ダウンロードしたZIPファイルと、それを展開してできるDueScopeフォルダ
図8、ダウンロードしたZIPファイルと、それを展開してできるDueScopeフォルダ

このDueScopeフォルダは、必要に応じて場所を移動してください。

DueScopeフォルダを開くと、中にはDueScope.inoというスケッチのファイルが1つだけあります。このDueScope.inoArduino IDE 1.8.0以降で開くと、図9の様な画面になります。

図9、DueScope.inoを開いた様子
図9、DueScope.inoを開いた様子

前のページで紹介したプリアンプ基板、キーパッド基板、および128×64モノクログラフィックLCDシールドの3つを接続したArduino Dueにこのスケッチを書き込むと、オシロスコープが動作します。

5-3.スケッチの簡単な説明

ここでは、スケッチ内で行っている処理の内、低レベルの処理である波形の取り込みの部分について主に説明します。

このスケッチでは、処理を高速化するため、A/D変換analogRead関数を用いていません。

A/D変換器には、タイマ(TC0のチャネル2)が1秒間に16万回スタート信号を送ります。その結果、オシロスコープのCH1(A0)とCH2(A1)の信号は自動的に160kspsのサンプリング周波数でA/D変換されます。

タイマの初期設定を行うのが、リスト1のstartTimer関数で、A/D変換器の初期設定を行うのが、リスト2のAdcSetup関数です。

リスト1StartTimer関数COPY
void StartTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t frequency) {
  pmc_set_writeprotect(false);
  pmc_enable_periph_clk((uint32_t)irq);
  tc->TC_CHANNEL[channel].TC_IDR=0xFFFFFFFF; // disable interrupts
  TC_Configure(tc, channel, TC_CMR_WAVE|TC_CMR_WAVSEL_UP_RC|TC_CMR_TCCLKS_TIMER_CLOCK1|TC_CMR_ACPA_CLEAR|TC_CMR_ACPC_SET);
  uint32_t rc = VARIANT_MCK/2/frequency; //128 because we selected TIMER_CLOCK1 above
  TC_SetRA(tc,channel,rc/2); //50% high, 50% low
  TC_SetRC(tc,channel,rc);
  TC_Start(tc, channel);
} // StartTimer
リスト2AdcSetup関数COPY
void AdcSetup()
{
  adc_set_writeprotect(ADC,false);
  NVIC_EnableIRQ(ADC_IRQn);   // enable ADC interrupt vector
  ADC->ADC_IDR=0xFFFFFFFF;    // disable interrupts
  ADC->ADC_IER=0x80;          // enable AD7 End-Of-Conv interrupt (Arduino pin A0)
  ADC->ADC_CHDR=0xFFFF;       // disable all channels
  ADC->ADC_CHER=0xC0;         // enable A0 & A1
  ADC->ADC_CGR =0x15555555 ;  // All gains set to x1
  ADC->ADC_COR =0x00000000 ;  // All offsets off
  ADC->ADC_MR =(ADC->ADC_MR&0xFFFFFFF0)|(3<<1)|ADC_MR_TRGEN; // trig source TIOA from TC0 ch2
} // AdcSetup

startTimer関数とAdcSetup関数は、setup関数の中で、次の様に呼び出されています。

リスト3startup関数内のStartTimer関数とAdcSetup関数の呼び出しCOPY
  // initialize timer
  StartTimer(TC0,2,TC2_IRQn,160000); //TC0 channel 2, the IRQ for that channel and the desired frequency

  // initialize ADC
  AdcSetup();

これらの初期化により、160kspsのサンプリング周波数でCH1およびCH2の信号がA/D変換され、A/D変換後は、自動的に割り込みがかかる状態になります。

A/D変換終了後の割り込みハンドラがADC_Handlerという関数です。

リスト4、A/D変換に関する割り込みのハンドラであるADC_Handler関数COPY
void ADC_Handler(void)
{
  if (ADC->ADC_ISR & ADC_ISR_EOC7) {  // ensure there was an End-of-Conversion and we read the ISR reg
    static int16_t LastSample=-1;
    uint16_t sample0,sample1,TrigSample;
    static uint16_t StepCnt=0;
    static uint16_t SampledNum;

    sample0=ADC->ADC_CDR[7]-OscSetting.vofs[0]; // get conversion result (A0)
    sample1=ADC->ADC_CDR[6]-OscSetting.vofs[1]; // get conversion result (A1)
    TrigSample=OscSetting.TrigCh==0 ? sample0 : sample1;
    AutoCnt++;

    if(++StepCnt>=HRange[OscSetting.hr].SamplingStep) {
      StepCnt=0;
      buf[BufIndex][0]=sample0;
      buf[BufIndex][1]=sample1;
      if(++BufIndex>=BufSize) BufIndex=0;
      
      if(TState==StandBy) {
        if(   (   LastSample>=0
               && (   (OscSetting.TrigEdge==RISE && LastSample<OscSetting.thresh && TrigSample>=OscSetting.thresh)
                  || (OscSetting.TrigEdge==FALL && LastSample>OscSetting.thresh && TrigSample<=OscSetting.thresh)
                 )
              )
                  || (OscSetting.TrigMode==AUTO && AutoCnt>=AutoLimit)
          ) {
          TState=triggered;
          TrigIndex=BufIndex;
          SampledNum=0;
        } // if  
      } else if(TState==OffsetCancelling) {
        TState=triggered;
        TrigIndex=BufIndex;
        SampledNum=0;
      } // if

      if(TState==triggered) {
        if(++SampledNum>=OscSetting.PostSampleNum) {
          TState=sampled; 
        } // if
      } // if
      
      LastSample=TrigSample;
    } // if
  } // if
} // ADC_Handler

ADC_Handler関数では、基本的に、A/D変換した波形の情報を、bufという変数名の環状バッファに格納する処理を行っています。

変数bufは、次の様に宣言されています。

リスト5、変数bufの宣言COPY
const uint16_t BufSize=256;
volatile uint16_t buf[BufSize][2];

BufSize(256)は環状バッファのループの長さを表わしています。CH1とCH2の2チャネルの信号を取り込んでいるので、バッファの要素はuint16_t型(16ビット符号なし整数)2個分の配列になります。

ADC_Handler関数では、波形の取り込みの他に、A/D変換値のオフセット値の補正や、トリガ条件の判定や、取り込んだデータの個数を数えて取り込みが終わったかどうかの判定などをしています。

loop関数では、キー操作(AC結合/DC結合切り替え用のスイッチの操作を含む)に対応する部分と、LCDに波形表示をする部分の2種類の処理を、交互に呼び出す様になっています。

リスト6loop関数COPY
void loop() {
  boolean AcDcChanged=false;
  for(int ch=0; ch<ChNum; ch++) {
    AcDcSig[ch]=digitalRead(AcDcSigPin[ch]);
    if(AcDcSig[ch]!=LastAcDcSig[ch]) AcDcChanged=true;
    LastAcDcSig[ch]=AcDcSig[ch];
  } // for ch
  
  if(ProcessKey() || AcDcChanged) {
    WriteInfo(); 
    WriteGndAndTrig();
    WriteScope(NULL);
    TState=StandBy;
  } // if

  if(TState==sampled){
    uint8_t y[SampleNum][2];
    uint16_t SampleCopy[SampleNum][2];
    int numerator[2],denominator[2];
    int index;

    index=TrigIndex+BufSize-(SampleNum-OscSetting.PostSampleNum);
    if(index>=BufSize) index-=BufSize;
    for(int i=0; i<SampleNum; i++) {
      SampleCopy[i][0]=buf[index][0];
      SampleCopy[i][1]=buf[index][1];
      if(++index>=BufSize) index=0;  
    } // for i
    for(int i=0; i<2; i++) {
      numerator[i]=VRange[OscSetting.vr[i]].numerator;
      denominator[i]=VRange[OscSetting.vr[i]].denominator;
    } // for i
    for(int i=0; i<2; i++) {
      if(OscSetting.ChState[i]!=ChOff) {
        if(OscSetting.ChState[i]==ChGnd) {
          numerator[i]=0;
        } else if(OscSetting.ChState[i]==ChInvert) {
          numerator[i]=-numerator[i];
        } //if
      } // if
      for(int j=0; j<SampleNum; j++) {
        y[j][i]=GetY(SampleCopy[j][i],numerator[i],denominator[i],OscSetting.vpos[i]);
        if(++index>BufSize) index=0;
      } // for j
    } // for i
    WriteScope(y);
    TState=StandBy;
    AutoCnt=0;
  } // if
} // loop

if (TState==sampled)と書かれている部分以降がLCDに波形を表示する処理で、それより前の部分がキー操作に対応する処理です。

これら2つの処理は、本来は独立して処理されるべき事なので、マルチスレッドないしマルチタスクとして記述されるのが自然なのですが、Arduinoの処理系は基本的にシングルスレッド/シングルタスクとしてしか処理を記述できませんから、これら2種類の処理を交互に呼び出す形になっています。

注:Arduinoでマルチスレッドやマルチタスクを実現するためのライブラリが何種類かあるようです。ただ、それらはArduino IDEに付属するライブラリではないので、読者の混乱を避けるため、それらを使わなくても作れるスケッチの場合は、筆者はシングルスレッドの形で実装する様にしています。

次のページでは、このオシロスコープの使い方を説明します。

広告
目次へ  前のページへ (1) (2) (3) (4) (5) 次のページへ

このページで使われている用語の解説

関連ページ

関連製品

128×64モノクログラフィックLCDシールド 商品名 128×64モノクログラフィックLCDシールド
税抜き小売価格 3600円
販売店 スイッチサイエンス
サポートページ
Arduino 電子工作
このサイトの記事が本になりました。
書名:Arduino 電子工作
ISBN:978-4-7775-1941-5
工学社の書籍の内容の紹介ページ
本のカバーの写真か書名をクリックすると、Amazonの書籍購入ページに移動します。