製作モジュール一覧

2021年3月24日水曜日

STM32CubeIDE: I2S DACのPCM5102Aを32bitで使う

STM32CubeIDE: Version 1.5.1
Target board: Nucleo-F446RE

32bit/48kHz動作


まず、DMAを利用して32bit/48kHzでサイン波を出力させてみます。16bit/48kHzのプログラムを32bitに変更します。

参考

配線


16bitの場合と同様です。


MXの設定


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
        Data and Frame Format: 32Bits Data 32Bits Frame
        Selected Audio Frequency: 48KHz
    DMA Settings
      SPI2_TX
        DMA Request Settings
          Mode: Circular
          Peripheral: Data Width: Half Word
          Memory   : Data Width: Half Word

 Data and Frame Formatを32Bits Data 32Bits Frameにしています。

main.cにコードを追加


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

<中略>

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

<中略>

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

<中略>

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

<中略>

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

<中略>

/* USER CODE BEGIN 4 */
static int32_t swap16(int32_t x)
{
	return ((uint32_t)x << 16) | ((uint32_t)x >> 16);
}

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);
	int32_t v = fv * 0x7fffffff;
	tx_buffer[0] = swap16(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;
	int32_t v = fv * 0x7fffffff;
	tx_buffer[1] = swap16(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 */

出力波形を観測



16bitの場合とノコギリ波で比較するとほとんど変わらないようです。

スペクトラム


Lch(サイン波) 500kHzレンジ

Lch(サイン波) 100kHzレンジ

Rch(ノコギリ波) 50kHzレンジ

Rch(ノコギリ波) 500kHzレンジ

スペクトラムも大差ありません。縦の精度はオーバーサンプリングとデジタルフィルタで十分補間されているということでしょうか。

32bit/192kHz動作


サンプリング周波数を上げてみます。STM32F446Eでは32bit/192kHzが最高のレートです。I2Sのクロック周波数Fclkは
Fclk = 2 * 32bit * 192kHz = 12.288MHz
となります。

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
       Data and Frame Format: 32Bits Data 32Bits Frame
       Selected Audio Frequency: 192KHz
    DMA Settings
      SPI2_TX
        DMA Request Settings
          Mode: Circular
          Peripheral: Data Width: Half Word
          Memory   : Data Width: Half Word

Selected Audio Frequencyを192KHzに変更しています。

MXの設定 - Clock Configuration


STM32F446REのデフォルトのクロック周波数(84MHz)で、I2Sのサンプリング周波数を192kHzで動作させると処理が間に合わないので、STM32F446REを180MHz動作させます。

PLL Source Mux: HSE
HCLK(MHz): 180


コンパイラで速度優先最適化を指定


最適化しないと処理が間に合わないので、速度優先で最適化を指定します。階層が深いので迷いますw


Project > Properties > C/C++ Build > Settings
Tool Settings > MCU GCC Compiler > Optimization
Optimization level: Optimization for speed (-Ofast)

main.cにコードを追加


32bit/48kHzのコードのサンプリング周波数の定義を変更します。

float sampling_rate = 48000.0f;
                    ↓
float sampling_rate = 195312.0f;

MXでSelected Audio Frequencyに192kHzを指定すると、Real Audio Frequencyが195.312kHzとなるので、これを使って出力波形の周波数を補正します。

出力波形を観測



32bit/48kHzの場合と比べるとノコギリ波のエッジの部分の振動が細かくなっています。過渡特性はかなり向上します。

スペクトラム


Lch(サイン波) 500kHzレンジ

Lch(サイン波) 100kHzレンジ

サイン波のスペクトラムを32bit/48kHzの場合と比較するとノイズフロアが15dBぐらい下がりますが、ところどころピークが現れています。いやですねw

Rch(ノコギリ波) 50kHzレンジ

Rch(ノコギリ波) 500kHzレンジ

※ノコギリ波のスペクトラムは変動が激しいのでLinear RMS Averageにして平均値を表示しています。

ノコギリ波のスペクトラムを32bit/48kHzの場合と比較するとナイキスト周波数の96kHz付近までノコギリ波らしい高調波が見られます。平均値をとっているので単純には比較できませんが、高調波の間のピークが残っています。ノコギリ波の綺麗さではやはりアナログ発振器に分がありそうです。

こういう歪んだ(?)ノコギリ波のほうが味がある場合もあるので、音源としてはいろいろなビット数/サンプリング周波数で出力できるようにすると面白いかもしれません。

PCM5102Aのハードウェア設定


32bit/192kHzの場合もFLTとDEMPの設定による出力の変化を見てみました。

FLT: H

上側の振動がなくなり下側の振動が増えてますね。

DEMP: H

32bit/192kHzではDEMPによる変化はないようです。PCM5102Aのデータシートには以下の記述があります。

De-emphasis control for 44.1-kHz sampling rate(1): Off (Low) / On (High)

(1) Failsafe LVCMOS Schmitt trigger input

 

2021年3月21日日曜日

STM32CubeIDE: I2Sを32bit長で使う

STM32のI2Sで32bitデータを扱う場合は、32bit変数の上位16bitと下位16bitを入れ替える必要があります。

参考
「可燃ごみ箱」さん 「STM32 I2S DMA利用時の32ビット対応
「平坂久門ただいま失業中」さん 「STM32でDCCを作る方向で (8) I2Sの具合はどうかな?

uint32_t型の変数をそのまま使った場合のI2S信号をAnalog Discovery 2のLogic機能で見てみました。

STM32CubeIDE: Version 1.5.1
Target board: Nucleo-F446RE

MXの設定


Multimedia
  I2S2
    Mode
      Mode: Half-Duplex Master
  Configuration
    Parameter Settings
      Generic Parameters
        Data and Frame Format: 32Bits Data 32Bits Frame
        Selected Audio Frequency: 48KHz

Data and Frame Formatで32bitデータを指定します。

main.cに追加


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  uint32_t data[2] = { 0xffeeddcc, 0x77665544 };
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	if (HAL_I2S_Transmit(&hi2s2, (uint16_t *)data, 2, 100) != HAL_OK) {
		Error_Handler();
	}
  }
  /* USER CODE END 3 */

簡単にメインループ内でPollingしました。送信するデータはわかりやすいようにバイトごとに区切って数字を変えています。

HAL_I2S_Transmit()の3番めの引数は、STM32F4のHALのドキュメント「UM1725 User Manual Description of STM32F4 HAL and low-layer drivers」に
HAL_StatusTypeDef HAL_I2S_Transmit (I2S_HandleTypeDef * hi2s, uint16_t * pData, uint16_t Size, uint32_t Timeout)

Size: number of data sample to be sent
とありややこしいですが、32bitデータの場合、32bitデータとしての個数を指定すれば良いようです。

「32 Bits Data on 32 Bits Frame」で「32bitデータが2個」なのでSize=2となります。

pDataがuint16_t型へのポインタなので何となく「16bitデータがが4個」としてSize=4としたくなりますw

I2S信号を観測



LchがDDCC:FFEE、Rchが5544:7766と16bit単位で逆順になっています。

Analog Discovery 2のLogicのI2Sの設定

Analog Discovery 2の「-I2S」のカラムをダブルクリックするとI2Sの解釈方法を設定できます。FormatをHexadecimalとして16進数表示させています。

16bit毎の入れ替え処理を追加

Byte(8bit)単位の入れ替えは、ARMのアセンブラにREVという命令があり、GCCでも__builtin_bswapXX()という関数が使えますが、16bitごとの入れ替えを一発でというものは無いようなので、プログラムで行うことにします。

/* USER CODE BEGIN PFP */
static int32_t swap16(int32_t x);
/* USER CODE END PFP */

<中略>

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  int32_t data[2] = { 123456, -234567 };
  data[0] = swap16(data[0]);
  data[1] = swap16(data[1]);
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	if (HAL_I2S_Transmit(&hi2s2, (uint16_t *)data, 2, 100) != HAL_OK) {
		Error_Handler();
	}
  }
  /* USER CODE END 3 */

<中略>

/* USER CODE BEGIN 4 */
static int32_t swap16(int32_t x)
{
	return (uint32_t)x << 16 | (uint32_t)x >> 16;
}
/* USER CODE END 4 */

I2Sは符号付き整数の場合が多いのでint32_t型のデータに変更しています。

ビットシフトにより16bit単位の入れ替えを行っていますが、シフトする場合は論理シフトを使うようにuint32_tにキャストします。int32_tのままだと算術シフトになり負数の場合空いた上位ビットが1で埋まってしまいます。

I2S信号を観測



期待通りの信号が出力されています。

Analog Discovery 2のLogicのI2Sの設定

FormatをTwo's complementとして2の補数で表示させています。
 

2021年3月19日金曜日

STM32CubeIDE: I2S DACのPCM5102Aを16bitで使う

PCM5102Aは表面実装パッケージですが、秋月からDIP化キットが販売されていてブレッドボードやユニバーサル基板でも実験しやすいDACです。

外付け回路が必要な電流出力ではなく電圧出力で、内蔵電源によりGNDを中心としたAC波形が得られます。また内蔵PLLによりマスタークロックなしの3線(BCK/DIN/LRCK)で使用可能です。

電源電圧が3.3Vで、絶対定格が-0.3V~3.9Vな点は十分注意する必要があります。私は実験中に間違えて5Vを掛けて壊してしまいました。ほんの数秒でアウトです。

秋月ではIC単体ではなくDIP化基板のみの販売です。近年TIがIC単体の販売を絞っているためでしょうか。どういう意図があるんでしょうね?


PCM5102Aは32bit/384kHzまで対応してますが16bitでも使えます。今回は16bit/48kHzで動作させてみます。

STM32CubeIDE: Version 1.5.1
Target board: Nucleo-F446RE

回路図

データシートに載っているTypical Applicationの回路から、出力にかかっているCR LPFを削除しています。SCKは使わずBCKを利用するためGNDに落としています。

配線図



実験のようす

3.3V電源はNucleoからも供給可能ですがPCM5102Aは結構電源にシビアなので、別の電源からとっています。今回は3.3Vのリニアレギュレータを使った電池電源を使用しています。

STM32のプログラムは「STM32CubeIDE: I2S DACのTDA1543を使う - サイン波を出力」のものと同じです。

MXの設定


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: 48KHz
    DMA Settings
      SPI2_TX
        DMA Request Settings
          Mode: Circular
          Peripheral: Data Width: Half Word
          Memory   : Data Width: Half Word

main.cにコードを追加


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

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

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

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

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

/* 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 */

出力波形を観測


C1:Lch C2:Rch

NOSのTDA1503の場合と比べるとオーバーサンプリングとデジタルフィルタが効いているため、同じ16bit/48kHzのデータでもサイン波にガタガタがほとんど見られません。

ノコギリ波もガタガタは無くなっていますが、上下のエッジに振動が見られます。内蔵のデジタル補間フィルタの特性ですね。

サイン波ではあまりわかりませんが、ノコギリ波だと過渡特性がよくわかります。

スペクトラム


Lch(サイン波) 500kHzレンジ

Lch(サイン波) 100kHzレンジ

48kHz動作ですが、TDA1543を192kHzで動作させた場合と比較して歪が少なくなっています。

Rch(ノコギリ波) 50kHzレンジ

Rch(ノコギリ波) 500kHzレンジ

ノコギリ波では25kHz付近で一旦高調波がカットされ、その後放物線状のカーブが現れ350kHz~400kHzでは帯状のピークが出ています。面白いですね。

PCM5102Aのハードウェア設定


PCM5102AのFLTピンとDEMPピンはH/Lさせることにより特性が変わります。

FLT: H

FLTピンをHレベルにすると補間フィルタがLow Latencyになります。ノコギリ波の波形がNormalとは異なります。

PCM5102Aのデータシートのインパルス応答を見るとNormalがFIR、Low LatencyがIIRっぽいです。IIRのほうがアナログフィルタに近い特性で次数が少なくて済むようですがどうなんでしょうね。

DEMP: H

DEMPピンをHレベルにするとDe-emphasisがONになります。この機能はよく知らないのですがレコードのRIAAのように録音時に高音を強め、再生時に高音を弱めるのでしょうか。ノコギリ波の出力波形を見るとLPFを通したような感じです。