2021年3月1日月曜日

STM32CubeIDE: I2Sをサンプリング周波数192kHzで使う

STM32F446REのI2SのFS(サンプリング周波数)は最大192kHzなので、FS=192kHzで動作させてみます。TDA1543も192kHzまで対応しているのでDACはTDA1543を使用します。ただしbit長は16bitです。

配線等ハードウェア構成は「STM32CubeIDE: I2S DACのTDA1543を使う - OPアンプでI-V変換」のものを使います。

STM32CubeIDE: Version 1.5.1
Target board: Nucleo-F446RE

MXの設定 (Clock Configuration)



ClockソースにHSEを指定します。Nucleo64の場合、ST-Linkパート上の8MHzの水晶振動子を使うことになります。デフォルトのHSIはRC発振なのでクロックの精度が上がります。

HCLKはデフォルトでは84MHzになっていますが、MAXの180MHzを入力すると、PLLやプリスケーラなどを自動的に設定してくれると思います。

MXの設定 (Pinout & Configuration)


System Core
  GPIO
    PC5: 
      GPIO mode: Output Push Pull
      User Label: CK_PERIOD
    PC6:
      GPIO mode: Output Push Pull
      User Label: CK_CPLT
    PC8:
      GPIO mode: Output Push Pull
      User Label: CK_HALF_CPLT
Multimedia
  I2S2
    Mode
      Mode: Half-Duplex Master
  Configuration
    Parameter Settings
      Generic Parameters
        Selected Audio Frequency: 192KHz
    DMA Settings
      SPI2_TX
        DMA Request Settings
          Mode: Circular
          Peripheral: Data Width: Half Word
          Memory   : Data Width: Half Word

処理のタイミングを計測するため、GPIOを何本か追加しています。


Selected Audio Frequencyで192KHzを選択すると、Real Audio Frequencyが195.312KHz、Error between Selected and Realが1.72%になります。正確に192KHzにするためにはClock源を192KHzの2^N倍にする必要があります。例えば12.288MHzの水晶振動子を外部クロックとして使うなど。

※設定時に何らかのエラーが出た場合はClock ConfigurationをResolve Clock Issueしたり、.iocファイルを一旦閉じて開き直すと改善する場合があります。

main.cにコードを追加


/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <math.h>
/* USER CODE END Includes */

浮動小数点数演算を行うため<math.h>をインクルードします。

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define PI_F	(3.141592f)
/* USER CODE END PD */

πの値を単精度で定義します。

/* USER CODE BEGIN PV */
float sampling_rate = 195312.0f;
float frequency = 1000.0f;
float phi = 0.0f;
float delta;
uint16_t tx_buffer[2] = { 0, 0 };
/* USER CODE END PV */

サンプリングレートを195.312kHzと定義しています。オーディオデータを再生するには正確に192kHzにしなければなりませんが、プログラムで波形を生成するので実際のサンプリング周波数で計算することにします。

出力波形の周波数は1kHzとしています。

phiは位相角で、サンプリング周期ごとにdeltaだけ増分します。

tx_bufferはDMA転送に使うバッファです

  /* USER CODE BEGIN 1 */
  delta = (2.0f * PI_F * frequency) / sampling_rate;
  /* USER CODE END 1 */

増分deltaを計算します。

  /* USER CODE BEGIN 2 */
  HAL_I2S_Transmit_DMA(&hi2s2, tx_buffer, 2);
  /* USER CODE END 2 */

DMA経由でI2Sを開始します。Ciruclarモードにしているので呼び出しは1回だけで勝手にメモリ→ペリフェラル間の転送が繰り返されます。

/* USER CODE BEGIN 4 */
void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
	HAL_GPIO_WritePin(GPIOC, CK_HALF_CPLT_Pin, GPIO_PIN_SET);

	// Generate Sine wave
	float fv = sinf(phi);
	int16_t v = fv * 0x7fff;
	tx_buffer[0] = (uint16_t)v;

	HAL_GPIO_WritePin(GPIOC, CK_HALF_CPLT_Pin, GPIO_PIN_RESET);
}

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
{
	HAL_GPIO_WritePin(GPIOC, CK_CPLT_Pin, GPIO_PIN_SET);

	// Generate Sawtooth wave
	float fv = phi / PI_F;
	int16_t v = fv * 0x7fff;
	tx_buffer[1] = (uint16_t)v;

	// Advance in phase
	phi += delta;
	if (phi > PI_F) {
		phi -= 2.0f * PI_F;
		HAL_GPIO_TogglePin(GPIOC, CK_PERIOD_Pin);
	}

	HAL_GPIO_WritePin(GPIOC, CK_CPLT_Pin, GPIO_PIN_RESET);
}
/* USER CODE END 4 */

割り込みハンドラで、Lchはサイン波、Rchはノコギリ波を生成しています。

Build Setting


最適化を速度優先にします。


Project > Properties
C/C++ Build > Settings > Tool Settings > MCU GCC Compiler > Optimization

Optimization leverl: Optimize for size (-Os)

実行結果


Analog Discovery 2で出力波形とI2S信号を観測しました。

出力波形

C1:Lch C2:Rch

I2S信号


Lch(サイン波) スペクトラム(500kHzレンジ)

Lch(サイン波) スペクトラム(100kHzレンジ)

FS=48KHzの場合と比べて可聴帯域内のエイリアスは少なくなっています。

最適化フラグを-Ofastとして速度優先にしましたが、デフォルトの最適化なし-O0だと割り込み中の処理が間に合わず出力波形が乱れました。

出力波形

C1:Lch C2:Rch

I2S信号

Cpltが呼び出されず、HalfCpltが二回連続していてかなり怪しいことになっています。

FPUが搭載されているので高速に浮動小数点数演算ができますが、それほど余裕があるわけでもないようです。凝った信号処理をしようと思うとDSPを使う必要があるかも知れません。

今回は16bit長ですが、24bitや32bitにするには、浮動小数点数を整数に変換する処理が16bitが32bitかの違いなので、それほど負荷はないと思います。

STM32F4では、24(32)bit/96kHzや24(32)bit/48kHzあたりを使うか、1chのみの出力にするのが現実的でしょうか。

 

0 件のコメント:

コメントを投稿