2018年08月12日 | 公開。 |
今回試作したオシロスコープのソフトウェアについて説明します。
今回作成したオシロスコープのスケッチは、MGLCDライブラリを使用しています。
MGLCDライブラリをまだインストールされていない方、あるいはVer.0.41以前のMGLCDライブラリをご使用の方は、MGLCDライブラリのページからMGLCDライブラリ Ver.0.42をダウンロードし、インストールしてください。詳しいインストールの方法が、MGLCDライブラリユーザーマニュアルのページに説明されています。
今回開発したオシロスコープのスケッチを次のリンクからダウンロードできる様にしておきました。
Arduino Due用オシロスコープスケッチ Ver.010 DueScope010.zip (6kB)のダウンロード
ファイルをダウンロードすると、DueScope010.zipというファイル名のファイルがダウンロードされます。
このファイルはZIPで圧縮してありますので、それを展開すると、DueScopeという名前のフォルダができあがります。(図8参照)
このDueScopeフォルダは、必要に応じて場所を移動してください。
DueScopeフォルダを開くと、中にはDueScope.inoというスケッチのファイルが1つだけあります。このDueScope.inoをArduino IDE 1.8.0以降で開くと、図9の様な画面になります。
前のページで紹介したプリアンプ基板、キーパッド基板、および128×64モノクログラフィックLCDシールドの3つを接続したArduino Dueにこのスケッチを書き込むと、オシロスコープが動作します。
ここでは、スケッチ内で行っている処理の内、低レベルの処理である波形の取り込みの部分について主に説明します。
このスケッチでは、処理を高速化するため、A/D変換にanalogRead関数を用いていません。
A/D変換器には、タイマ(TC0のチャネル2)が1秒間に16万回スタート信号を送ります。その結果、オシロスコープのCH1(A0)とCH2(A1)の信号は自動的に160kspsのサンプリング周波数でA/D変換されます。
タイマの初期設定を行うのが、リスト1のstartTimer関数で、A/D変換器の初期設定を行うのが、リスト2のAdcSetup関数です。
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
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関数の中で、次の様に呼び出されています。
// 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という関数です。
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は、次の様に宣言されています。
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種類の処理を、交互に呼び出す様になっています。
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に付属するライブラリではないので、読者の混乱を避けるため、それらを使わなくても作れるスケッチの場合は、筆者はシングルスレッドの形で実装する様にしています。
次のページでは、このオシロスコープの使い方を説明します。
商品名 | 128×64モノクログラフィックLCDシールド | |
税抜き小売価格 | 3600円 | |
販売店 | スイッチサイエンス | |
サポートページ | 128×64モノクログラフィックLCDシールドサポートページ |