STM32F4-Discovery 用FM音源プログラム -- TGSTM32F4 (4)
STM32F4 の SPI/I2S モジュールには、オーバーサンプリング DAC 用のマスター・クロック (MCLK) を発生させる機能があります。
しかし、そのオーバーサンプリング比は 256 倍 (256 fs) に固定で、また、回路の制限により、SPI/I2S モジュールへのクロック (I2SCLK) は、さらにその 4 倍以上が必要になります。
この I2SCLK は、外部からのクロック入力と、内蔵の I2S 専用の PLL クロックとの、どちらかを選ぶことができます。
MCLK を発生させる場合の SPI/I2S のクロック系統を図にしたものを下に示します。
CPU コアや、バス (AHB/APB) のクロックを発生させる PLL と同様の機能を持つ別の PLL が I2S 専用に用意されています。
基準周波数は両方の PLL に共通で、HSI (内部高速クロック, 16 MHz) あるいは HSE (外部高速クロック) を 1/PLLM に分周して作成しています。
この基準周波数は 1 MHz 〜 2 MHz の範囲に制限されています。
RTC ブロックでは、通常の 32.768 kHz クロックの他に、HSE クロックを分周して得られた 1 MHz クロックを使用することができます。
そのため、HSE を 1 MHz の整数倍の周波数に選んでおけば、RTC 用の 32.768 kHz クリスタルを実装しなくても RTC ブロックを機能を活かせることになります。
実際の STM32F4-Discovery ボードでは、HSE に 8 MHz のクリスタルを使用し、PLLM = 8 として 1 MHz の PLL 基準周波数を発生させています。
これを、コアクロック用 PLL では PLLN = 336 倍して VCO 出力周波数として 336 MHz を発振させ、PLLP= 2 分の 1 にして 168 MHz を CPU コアに供給しています。
さらに、VCO 周波数 336 MHz を PLLQ = 7 分の 1 にして 48 MHz を得て、(フルスピード以下の) USB 回路のクロックとして供給しています。
この辺の PLL の設定は、デフォルトの C スタートアップ・ルーチンの中で自動的に行われており、各部分が最高速で動作する設定になっているので、通常は気にする必要はありません。
I2S 専用の PLLI2S については、デフォルトのスタートアップ・ルーチンでは設定されませんから、処理をスタートアップ・ルーチン内に追加するか、ユーザ・プログラム中でケアする必要があります。
また、サンプリング周波数を可変にする場合には、SPI/I2S モジュール内の自由度はあまり大きくないので、PLLI2S の設定自体を変更して、I2SCLK を変化させる必要があります。
上の図では、I2S のサンプリング周波数 fs = 48 kHz を得るための設定の具体的な数値を書き込んであります。
各パラメータの値は自由に選べるわけであなく、下のような制限があります。
192 <= PLLI2SN <= 432 2 <= PLLI2SR <= 7 0 <= I2SODD <= 1 4 <= (2*I2SDIV + I2SODD) <= 511 I2SCLK <= 192 [MHz]
ここで、PLLI2SN の制限は、実際は PLL VCO の発振周波数制限で、192 MHz から 432 MHz の間に選ぶ必要があり、基準周波数 1 MHz の条件では、PLLI2SN に対して上の表のような制限になるということです。
このような制限のもと、目的のサンプリング周波数から、誤差が最小になる最適なパラメータ値を選び出すプログラムを作成しました。
プログラムは後で示しますが、いくつかのサンプリング周波数について求めた結果を下に示します。
Target_fs PLLN PLLR I2SDIV I2SODD Real_fs [Hz] err [ppm] --------- ------ ------ ------ ------ ------------ --------- 22050 429 4 9 1 22049.75329 -11.19 24000 424 3 11 1 24003.62313 150.96 32000 213 2 6 1 32001.20192 37.56 35000 224 5 2 1 35000.00000 0.00 36000 258 4 3 1 35993.30357 -186.01 40000 256 5 2 1 40000.00000 0.00 44100 271 6 2 0 44108.07227 183.04 45000 288 5 2 1 45000.00000 0.00 46000 212 3 3 0 46006.94401 150.96 48000 258 3 3 1 47991.07143 -186.01 49516 355 7 2 0 49525.66895 195.27 49716 280 2 5 1 49715.90909 -1.83 49749 382 5 3 0 49739.58333 -189.28 50000 192 3 2 1 50000.00000 0.00 60000 384 5 2 1 60000.00000 0.00 64000 344 3 3 1 63988.09487 -186.02 88200 271 3 2 0 88216.14551 183.06 96000 344 2 3 1 95982.14286 -186.01 100000 256 2 2 1 100000.00000 0.00
この表は、STM32F4xx のリファレンス・マニュアル「RM0090」のセクション 25.4.4、ページ 694、Table 100. と同様の形式となっていますが、最後の誤差を ppm 単位で表示している部分が異なっています。
35 kHz、40 kHz、45 kHz、50 kHz、60 kHz、100 kHz といったサンプリング周波数については誤差ゼロとなっていますが、ディジタル・オーディオとして標準的な 32 kHz、44.1 kHz、48 kHz といった周波数については、おおむね ±200 ppm 程度の誤差となっています。
民生用 SPDIF のクラス 2 の許容サンプリング周波数偏差は ±1000 ppm ですから、それに比べると誤差は小さく、SPDIF トランスミッタを外付けして、外部 SPDIF DAC に送信する場合でも、外部 DAC でロック可能な範囲と言えます。
上の表で 49 kHz 台の周波数が 3 種ありますが、これは「本物」の FM 音源チップで使われているサンプリング周波数です。
49716 Hz に対して誤差が -1.83 ppm となっていますが、これは、NTSC カラー・サブキャリア周波数の 3.5795454 MHz や 14.31818 MHz の原発振を使う「古典的」な FM 音源チップ (YMF262 など) のサンプリング周波数 49715.90909 Hz を、目的のサンプリング周波数を Hz 単位の整数で表現したために生じた誤差で、実際の周波数は誤差ゼロで実現できています。
49516 Hz は、OPL4 (YMF278B) などの、44.1 kHz 系の原発振 (33.8688 MHz など) を使う FM 音源の場合のサンプリング周波数 49515.789 Hz を Hz 単位に整数化したものです。
49749 Hz は、DS-1 (YMF740F) などの、48 kHz 系の原発振 (24.576 MHz など) を使う FM 音源の場合のサンプリング周波数 49748.988 Hz を Hz 単位に整数化したものです。
44.1 kHz 系、48 kHz 系、ともに誤差の絶対値は 200 ppm 程度で、誤差ゼロのカラー・サブキャリア系に比べて悪くなっています。
最後にプログラムを下に示します。 各パラメータを許容範囲内で変化させ、総当りで誤差を計算し、誤差が最小になる場合のパラメータを表示します。
I2SDIV、I2SODD の計算は、Std_Periph_Driver 中の「I2S_Init()」関数の中で使われている計算式を、そっくりそのまま抜き出して使っています。
Cygwin 上の gcc で書いてありますから、特に環境には依存しないはずです。
// // i2spll_conf.c : I2S PLL configuration // for STM32F4 // // 2012/02/20 pcm1723 // #include <stdio.h> #include <stdlib.h> #include <math.h> #define I2SPLLR_MIN (2) #define I2SPLLR_MAX (7) #define I2SPLLN_MIN (192) #define I2SPLLN_MAX (432) #define I2SDIV_MIN (2) #define I2SDIV_MAX (511) uint32_t pllm = 8; uint32_t HSE_VALUE = 8000000UL; int32_t fs_tab[] = { 22050, 24000, 32000, 35000, 36000, 40000, 44100, 45000, 46000, 48000, 49516, 49716, 49749, 50000, 60000, 64000, 88200, 96000, 100000 }; // int fs_tab[] int main(int argc, char* argv[]) { int pllr, plln; int r_opt, n_opt, d_opt, o_opt; int i, fs; uint32_t fvco, i2sclk, tmp; uint32_t i2sdiv, i2sodd; double err, err_min, fs_opt; int n = (sizeof(fs_tab) / sizeof(int32_t)); if (1 < argc) { // argument found if (argc > n) { // too many arguments argc = (n + 1); // limit to table entries } // if (argc > n) ... for (n = 1; n < argc; n++) { // override table content by the argument sscanf(argv[n], "%ld", &(fs_tab[n-1])); } // for n--; } // if (1 < argc) ... printf("Target_fs PLLN PLLR I2SDIV I2SODD Real_fs [Hz] error [ppm]\r\xa"); printf("--------- ------ ------ ------ ------ ------------ -----------\r\xa"); for (i = 0; i < n; i++) { fs = fs_tab[i]; err_min = 9999; for (plln = I2SPLLN_MIN; plln <= I2SPLLN_MAX; plln++) { for (pllr = I2SPLLR_MAX; pllr >= I2SPLLR_MIN; pllr--) { fvco = plln * (HSE_VALUE / pllm); // VCO output freq i2sclk = fvco / pllr; // i2sclk to SPI/I2S module if (192000000UL >= i2sclk) { // i2sclk limited to 192 MHz // following calculations quoted from I2S_Init() function /* Compute the Real divider depending on the MCLK output state, with a floating point */ /* MCLK output is enabled */ tmp = (uint16_t)(((((i2sclk / 256) * 10) / fs)) + 5); /* Remove the floating point */ tmp = tmp / 10; // calculate fs error err = (((double)i2sclk / 256.0 / tmp / fs) - 1.0); /* Check the parity of the divider */ i2sodd = (uint16_t)(tmp & (uint16_t)0x0001); /* Compute the i2sdiv prescaler */ i2sdiv = (uint16_t)((tmp - i2sodd) / 2); /* Get the Mask for the Odd bit (SPI_I2SPR[8]) register */ i2sodd = (uint16_t) (i2sodd << 8); // check if minimum error value found if (1 < i2sdiv) { // i2sdiv[7:0] == 0000000X forbidden if (fabs(err) < fabs(err_min)) { // minimum error found r_opt = pllr; n_opt = plln; d_opt = i2sdiv; o_opt = i2sodd; fs_opt = fs * (1.0 + err); err_min = err; } // if (err < err_min) ... } // if (1 < i2sdiv) } // if (192000000UL >= i2sclk) } // for (pllr } // for (plln printf("%8d %6d %6d %6d %6d %13.5f %7.2f\r\xa", fs, n_opt, r_opt, d_opt, o_opt>>8, fs_opt, 1e6*err_min); } // for (i = 0; return (0); } // int main()
引数なしで実行すると、初期値付き配列の「fs_tab」中に設定されているサンプリング周波数についての表を出力します。
サンプリング周波数を Hz 単位の整数として引数に与えると (複数可)、そのサンプリング周波数についてのパラメータを表示します。
fs_tab 配列を「上書き」する形で実現しているので、fs_tab[] 配列の要素数までの引数を受け入れます。