2019年9月8日日曜日

【にわかAVマニアの計測】Arduino Nanoでオーディオサンプリング周波数を測定できるか?

Rapsberry piにAK4495を接続しようとしています.

そのためには以前こちらに書いたとおり、BCLKからMCLKを生成する回路が必要です.

そのためにはMCLKがBCLKの何倍の周波数になってりゃ良いのかを知る必要があります.

XMOS DDCでLRCK,BCLK,MCLK周波数を実測したところ、下表の結果を得ました.MCLK倍率が2,4,8とケースバイケースであるというめんどくさい結果でした.BCLK→MCLKするには、オーディオサンプリング周波数であるところのLRCK周波数を測定し、MCLK倍率を切り替えてやらなければなりません.げろげろ
XMOS208
fs[kHz]bitLRCK[kHz]BCLK[MHz]MCLK[MHz]MCLK/BCLK
192.02419212.288424.57682
96.024966.1442124.57684
48.024483.072124.57688
44.12444.12.8224922.57998
192.01619212.288424.57682
176.416176.411.289922.57992
96.016966.144224.57684
88.21688.25.6449922.57994
48.016483.072124.57688
44.11644.12.8224922.57998

そんな事情で、Arduino Nanoを利用してLRCK周波数を測定したくなりました.要するにArduino Nanoで周波数カウンタを作りたい.外付け回路は一切なしでやりたい.Arduino Nano CPU であるATmega328Pが内蔵するタイマだけを使って周波数カウンタを作りたい.

目標仕様は、
・LRCK周波数: 44.1kHz,48kHz,96kHz,192kHz,384kHz ぐらいのバラエティ
・LRCKをArduino Nanoのどこかのpinに入力する
・PCとArduino NanoをUSB接続し、COM portにLRCK周波数が逐次表示される
・Arduino Nanoに外付け回路は無し

ATmega328Pのdatasheetは、こんな日本語版が存在するんだから便利でいいわ.

どうやるか?
datasheetによると、ATmega328Pにはタイマが3つ載っています.
 TC0  8bit
 TC1  16bit
 TC2  8bit
これらを使えばたぶんできるでしょう.

妥当そうな実施例としては、タイマを2ヶ使うのが良さそう.
・TC2で100ms毎に割り込みさせる
・TC1をLRCKでカウントさせる
・TC2割り込み毎に、TC1の進み数を採取する =TCNT1
・周波数=TCNT1 ÷ 100 [kHz]
・LRCK=384kHzだとしても、100mSに進むのは38.4k=38400 < 65535なのでオーバーフローしないで済む

「TC2で100ms毎に割り込みさせる」については、MsTimer2というシンプルで判りやすいlibraryがあり、それを使えばあっさり解決します.

TC1の制御については既製libraryを使わずにレジスタを直叩きします.

------
ほいだば実装の様子を.

TC1の外部clock入力ピンはどこか?  → D5でぇす
D5にTTLレベルのLRCKを入力します.

ソースコードはこちらに置きました.

↓動作状況の写真.有効数字の3桁目は怪しいもんです.割り込みの遅延とかのせいでやられちゃってるんでしょう.それでもサンプリング周波数の44.1kHzと48kHzを識別できるぐらいには使えそうなのでよしとします.


ソースコードを説明します.Arduino Nanoでしか動作確認してません.

TC2を動かすlibraryです.
#include <MsTimer2.h>

蛇足ですがLEDチカチカも実装しときます.
const int led_pin = LED_BUILTIN;
boolean led = HIGH;

これはTC1のカウンタ値を読んで格納する変数なのですが、これがglobal変数であることは必須です.auto変数にすると値が死んでしまいます.
unsigned int tcnt1;  // LRCK counter, must be gloval variable

TC2の100mSec割り込みルーチン
void isr_TC2()
{
  float fs; // sampling freq. in kHz
  
  digitalWrite(led_pin, led);   LEDチカチカを行う
  led = !led;

TC1の処理.TCNT1が16bitカウンタそのものです.読んで100分の一すればkHz換算できます.
  tcnt1 = TCNT1; // counter value
  fs = tcnt1 / 100; // in kHz
  TCNT1 = 0;  // clear TC1

COM portへfs測定値を出力
  Serial.print("Fs: ");
  Serial.print(fs);
  Serial.print(" kHz\n");
}

お馴染みの初期化ルーチン
void setup()
{
  Serial.begin(115200);  // start serial for output
  pinMode(led_pin, OUTPUT);

TC1の初期化は最低限この2つでよかよか.他はreset valueで放置.
  TCCR1A = 0; // must be done
  TCCR1B = 7;  // start TC1, count LRCK

TC2割り込みの設定.100ms割り込みと、isr_TC2()への紐付け
  MsTimer2::set(100, isr_TC2); // interrupt ms period
TC2動作開始
  MsTimer2::start();
}

void loop()
{
}


今日の取り組みはここまでといたします.

かしこ

0 件のコメント:

コメントを投稿