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を通したような感じです。

2021年3月15日月曜日

WaveShaper Saw2Tri ノコギリ波→三角波変換モジュールの製作

ユニバーサル基板でWaveShaper Saw2Triを試作しました。SawVCOから出力されるノコギリ波を三角波に変換します。

回路図

SawVCOのノコギリ波出力と+5V出力は裏側で配線するようにしました。

三角波の波形のズレを調整するRV1はパネル取り付けタイプのPOTを使って、パネル上で操作できるようにしました。

パネルに取り付け

ERK01にマウント

出力波形の観測


Shape POTで三角波の波形を崩した場合も出力しました。また、TLF01 (トランジスタ・ラダーフィルタ)を通した波形も観測しました。

SawVCOへの入力はCV=2Vで出力周波数は220Hz(A2)です。

三角波

C1:Saw2Tri C2:Saw2Tri→TLF01

Shape: Min

C1:Saw2Tri C2:Saw2Tri→TLF01

Shape: Max

C1:Saw2Tri C2:Saw2Tri→TLF01

三角波はノコギリ波と比較してマイルドな音色ですが、波形のズレが大きくなると(特にMax側)ノコギリ波に近い聴こえ方になるようです。

2021年3月12日金曜日

EuroRack 電源コネクタ増設 ERK01 PSU EXT Ver.1.1

自作のEuroRack: ERK01の電源コネクタが足りなくなってきたので増設しました。ユニバーサル基板だと配線が苦行なので、PCBGOGOでプリント基板を製造してもらいました。

回路図
CV/Gate線は未だ使ったことはないのですが、規格なので通しています。

プリント基板

1辺が100mmを超えると製造費が高くなるので両端のコネクタはギリギリに配置しています。

実装


リボンケーブルで既存の基板と接続します。

ERK01に取付け 

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のみの出力にするのが現実的でしょうか。