AD9833でファンクションジェネレータを作る5 スケッチ修正

published_with_changes 更新日: event_note 公開日:

labelAD9833 labelESP32 labelファンクションジェネレータ

 5 オリジナルのスケッチが通らない

開発ボードをESP32に変えることで、色々なトラブルが発生しました。
参考にしている2件のブログはATmega328やArduino Nanoです。一方、ネットにはESP32とAD9833の組み合わせの事例は少なく、試行錯誤で解決していきます。

なお、知識と経験が乏しいので、google先生に相談しながら手数で勝負します。色々手出しして、こうやったらうまくいったというレベルなので、他の方の参考にはならないと思います。

5.1   コンパイルが通らない(トラブル I)

スケッチの最後の部分
        // Interrupt service routine for the 'frequency' rotary encoder.
        ISR(PCINT2_vect) {

 で、”)”がないというエラーが出てコンパイルが止まりました。

この文は、ロータリエンコーダの割り込みトリガーが発生した場合に実行される関数で、ISR(Interrupt Service Routine)などと呼ばれている割り込み処理のハンドラーです。

ESP32で割り込みを扱っているスケッチ例をみると、ISRはvoid関数として呼び出されています。そこで、

ISR(PCINT2_vect) { --->  void  rotary_encoder(){

とすると、コンパイルが通るようになりました。想像なのですが、ESP32は全GPIOを割り込みに使えるので、より厳密にしているのかと思っています。

ESP32は割り込みに対してArduinoより厳格なルールがあります。この章の最後にそれをまとめておきます。

5.2 OLEDを使うと波形が出ない(トラブル II)

OLEDのSSD1283AとAD9833が同じSPI接続で両立できないようです。SPI通信によってAD9833への書き込みがうまくいきません。

次項を施策しましたが、何が効いたかは不明です。ESP32にはSPIが2系統あります。

a) OLEDのSSD1283Aは VSPI(ノーマルのSPI)で接続し、AD9833はHSPIで接続しました。
【補足】SSD1283Aはライブラリの都合でVSPIで使う必要がありました。

b) さらに、VSPI:SPI_MODE0, HSPI : SPI_MODE2にしました。どこかに、AD9833はSPI Mode2だと書いてあったように思います。


c)  AD9833へのデータの書き込みは、8bit 2回ですが、16bit 1回に変更しました。
    (a, bのあと、これを実施したら、うまく動くようになりました。)

修正前
void WriteRegister(int dat) {

// Display and AD9833 use different SPI MODES so it has to be set for the AD9833 here.
SPI.setDataMode(SPI_MODE2);
<省略>
SPI.transfer(highByte(dat)); // Each AD9833 register is 32 bits wide and each 16
SPI.transfer(lowByte(dat)); // bits has to be transferred as 2 x 8-bit bytes. 
<省略>

修正後

void WriteRegister(uint16_t dat) {

// Display and AD9833 use different SPI MODES so it has to be set for the AD9833 here.
// hspi.setDataMode(SPI_MODE2);
<省略>
hspi.transfer16(dat); // bits has to be transferred as 16 bit bytes. 2 x 8-bit bytes are not well.
<省略>

【補足】SSD1283A、AD9833ともVSPIにして、b),c)を適用する組み合わせをやってみました。このときは動きますが、AD9833の出力周波数が入力とずれるという問題がでました。推定原因はSSD1283A、AD9833に同じSPIを使うのでうまく通信できていないためか?と思っています。

5.3 周波数をスイープさせる方法

AD9833のデータシートを読まないで進めていますので、周波数スイープのやり方で行き詰まりました。ライブラリのAD9833-Library-ArduinoのサンプルスケッチAD9833_test_suite.inoには周波数を増加させるやり方が出ています。ただ、このライブラリはVSPIでしか動かず、それなりにスケッチを修正してみたのですが、HSPIにしたAD9833では動きませんでした。そのままの流用が無理なので、AD9833の使い方を学ぶことから始める必要があります。


参照してる迷走の果て・Tiny Objects AD9833 DDSモジュールを試す(13) 現状に示されているスケッチや他のブログで紹介されているスケッチでは、波形を出力させるのに次の2つのvoid関数を使って、AD9833のレジスタへ書き込みを行っています。

void AD9833setFrequency(long frequency, int Waveform) { // レジスタへ書き込む内容(波形定義)や順番を設定

void WriteRegister(int dat) { // レジスタへの書き込み処理


今更ながらですが、これには次のコメントが添えられていました。
      
// Display and AD9833 use different SPI MODES so it has to be set for the AD9833 here.

なんとなく波形の出し方がわかってきました。
AD9833_test_suite.inoのやり方に倣って、スイープ周波数を決めてやり、その値をAD9833setFrequency(long frequency, int Waveform)関数のfrequencyに放り込んでやれば、周波数スイープができるという恰好です。

周波数スイープのスケッチ

・周波数を刻む条件を設定
・開始周波数から、for文で周波数を増加させつつ
AD9833setFrequency(j,waveType); で波形形成
・500ms間隔で周波数をOLEDに表示
という流れでスケッチを作りました。
void AutoSweep() {

<省略>
float startHz = 1000, stopHz = 5000, incHz = 1, sweepTimeSec = 5.0; 
// Calculate the delay between each increment.
uint16_t numMsecPerStep = (sweepTimeSec * 1000.0 ) / ((uint16_t)((stopHz - startHz) / incHz) + 1); // 小数なら、小数点以下は切り捨てられる
if ( numMsecPerStep == 0 ) numMsecPerStep = 1; // 1以下の小数なら、小数点以下は切り捨てられ、numMsecPerStep = 0  ---> 1 ms
// Apply a signal to the output. If phaseReg is not supplied, then
// a phase of 0.0 is applied to the same register as freqReg
AD9833setFrequency(startHz-incHz, waveType); //意味が分からんが、D9833-Library-ArduinoのIncrementFrequencyTest関数に倣った
      for ( float j = startHz ; j <= stopHz; j += incHz ) {
        if (!sweepState){break;} /* stateがfalse(sweepさせたい割り込みが消滅)ならばfor文を抜ける。(Sweepを抜ける)これがなければ、stopHzまでfor を繰り返したのち、Sweepを抜ける */
          AD9833setFrequency(j,waveType);
          delay(numMsecPerStep); 
          if((millis() - dsTime) > 500) {  // 500ミリ秒以下は無視する 0.5sec毎に周波数を表示する
            dispfrequency(j);                    // Remember new frequency to avoid unwanted display
            dsTime = millis();
          }
      } 
}


5.4 波形に定期的にノイズが乗る(トラブル III

周波数スイープで波形を出力させると、波形(sin波)の始まりにレジスタ書き込み信号のような高周波の波形が載っていました。

力技での対応ですが、
    void AD9833setFrequency(long frequency, int Waveform) にある
    WriteRegister(0x2100);  1行を削除したら、出なくなりました。

Datasheetによると、0x2100はコントロール・レジスタというもので、レジスタへの書き込み許可とリセットをするものらしいです。

したがって、この文は周波数スイープの時だけ無効になるように書き換えておきました。

if (!sweepState){WriteRegister(0x2100);} // Write control register, both register are allowed to write and reset
// Sweep モードのときこれがあると、波形の始まりにレジスタ書き込み信号らしきものが載る


Analog DevicesのAD9833 - FAQには、周波数を切り換える方法について以下のQ&Aがあります。

これが本来の方法でしょうが、AD9833_test_suite.inoがこの方法を取っていないと思います。この先、波形がもしおかしくなった時にはこの方法をやってみようと思います。

DDSの周波数切り替え

Q:   周波数を変更しようと、16ビット/ワードで周波数レジスタ内容を書き換えると、出力が不連続になる。連続的に周波数を変更することはできないか。

A:   AD9833はデータレジスタの書き込みが16ビット毎となっておりますので、出力に選択している周波数ジスタの値を書き換えると、最初に16ビット書き込んでから次の16ビットデータが書き込まれるまでに意図しない周波数が出力されます。 AD9833は周波数レジスタが2組ありますので、周波数を変更する場合には、出力に選択していない方の周波数レジスタを更新しFSELで出力周波数レジスタを切り替えてください。

5.5 スイープ割り込みの解除(通常モードへの復帰)ができない(トラブル IV)

周波数スイープはfor文で周波数を加算しながら、AD9833setFrequency関数で波形を出力しています。このとき、割り込み解除のスイッチを押しても、すぐにはスイープ解除にならず、for文の最後まで待たねばなりませんでした。

Yahoo知恵袋にあった下記の囲みをヒントに、割り込みのフラグstateがfalseの時には、for ループをbreakすることで、すぐにスイープモードを解除できるように修正しました。

「割り込み」は、H/W的に割り込みルーチンが自動的に呼び出されます。 そして、割り込み処理が終了すると、前まで実行していたプロセスに戻ってきますので、forが無限ループだとすると、forから抜ける事はせず無限ループを続行する事になります。 スイッチを押すことによってforを途中で抜けたい場合は、「スイッチの状態I/O」を直接監視するか、または スイッチが押された割り込みで「スイッチONフラグ」を立て、forでこのフラグを監視する必要があると思います。


スイープの割り込みを定義するISR

sweepStateが割り込みのフラグ
// sweep 押下の割り込み できるだけ簡素にする
void IRAM_ATTR select() {
    sweepState = !sweepState; //stateの値が変わる
    digitalWrite(sweepPin, sweepState); //stateの真偽によりLEDをオンオフ
} 

 // freaquency sweep の割り込みが起きた時の処理
  if(sweepState ){ // 割り込みフラグがtrueなら、AutoSweep()関数を実行
    if((millis() - msTime) > 100) {  // 100ミリ秒以下の割り込みは無視する
      sweepStateOld = sweepState; // 割り込みフラグ
      AutoSweep();
      msTime = millis();
    }
  } else if (sweepStateOld == true) { // 割り込みフラグがtrueではなく、前のフラグがtrueなら、freaquency Stepモードへ復帰
   
    AD9833reset();
    AD9833setFrequency(temp, waveType);     // must have been turned so update AD9833 and display.
    mylcd.Fill_Screen(BLACK);
    waveOld = 1;
    updateDisplay(freq);
    sweepStateOld = false; // 割り込み 復帰フラグ
  } 

5.6 割り込みとシリアル通信は相性がよくないのか?(トラブル V)

Serial.begin(115200);を書くとロータリエンコーダの割り込みが効かなくなりました。なぜ?

とりあえず、Serial.begin(115200); Serial.print....なしでこのプロジェクト開発を進めることにします。

Arduino 日本語リファレンスのattachInterruptの項ほか、他にも下記の記述がありましたので、やはり相性が良くないようです。

・シリアル通信により受信したデータは、失われる可能性があります@レファレンスマニアル
・割り込み関数の中でSerial.printを書いていると、割り込みを使うと時々リセットがかかる。
Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).

後からESP32のGPIOの割り振りを整理していて気づいたのですが、シリアル通信UARTと割り込みが競合している割り振りになっているのが理由かもしれません。4章で載せた一覧でGPIO#1と#3がUART(HardwareSerial)となっています。スケッチでUARTを設定している訳ではないのですが、何らかの縛りがあるのでしょうか?


5.6 EPS32で割り込みを使うときの注意点

調べたことをまとめておきます。

(1)  attachInterrupt() :指定したGPIOピンに対する割り込みハンドラを定義する。
【書式】
void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode);
void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void * arg, int mode);

【引数】
pin         利用するピン番号。
handler 登録する関数。戻り値は持たない。引数は0個もしくは1個。引数がある場合は、2番目の書式を用いる。
mode いつ割り込みサービスルーチンを呼び出すかを指定する。以下の値が定義されている。
                RISING:ピンがLOWからHIGHになった時に割り込みサービスルーチンを呼び出す
                FALLING:ピンがHIGHからLOWになった時に割り込みサービスルーチンを呼び出す
                CHANGE:ピンの値が変わった時に割り込みサービスルーチンを呼び出す
                ONLOW:ピンがLOWのときに割り込みサービスルーチンを呼び出す
                ONHIGH:ピンがHIGHのときに割り込みサービスルーチンを呼び出す

 

(2) ISR関数に関するルール
  • ISR関数は引数を伴わないvoid型でなければならない
  • ISRの関数にはIRAM_ATTR属性を必ずつける(フラッシュではなく内部RAMメモリに配置する)
  • IRAM_ATTR属性をつけたISR関数は setup より前に書かないとコンパイルエラーになる
  • ISRの関数からアクセスする変数にはvolatile修飾子を必ずつける
  • 必要最低限の処理だけを行う
  • 高度な処理は排他制御を使って保存し、別タスクで実行する
  • 呼び出す関数群にFromISRがついている関数があったらそちらを使う

 

(3) シンプルな事例

void setup()

{
  pinMode(GPIO_pin, INPUT);
  attachInterrupt(GPIO_pin, Ext_INT1_ISR, RISING);
}
void IRAM_ATTR Ext_INT1_ISR()
{
  // Do Something ...
}

Powered by Blogger | Designed by QooQ

keyboard_double_arrow_down

keyboard_double_arrow_down