STM32/DMAによるダブルバッファ(的なもの)を作る例

Simulate a DMA double buffering on STM32
 
組み込み機器において、メモリー領域の効率的な転送に欠かせないDMAですが、転送完了時の割り込みではメモリーのコピーや加工など、どうしてもCPUの介入が必要で、そこにわずかな隙が出てきます。

オーディオストリームなどでは2つのバッファを用意して適切なタイミングで参照先を入れ替えるダブルバッファと呼ばれる手法が一般的です。STM32F4などのハイパフォーマンスモデルではDMA1/DMA2でダブルバッファをサポートしていますが、逆に言えばエントリーモデルではハードウェア側でダブルバッファを実装することはできません。そこで、STM32で一般的に使われているダブルバッファを模した機能を実装するための手法を紹介します。

STM32のDMAには、指定したメモリーのアドレスが末端に達したら自動で先頭アドレスに戻り、エラーが発生しない限り続行しつづける、循環モード(Circular mode)と呼ばれる機能が用意されています。また、DMA転送の割り込みには「データの半分の転送が完了」でも発生させることができるので、通常の2倍バッファを確保しておき、割り込みごとに前半と後半のデータを読み書きすれば、ダブルバッファを再現でき、転送を停止させることなく処理をし続けられることになります。
STM32CubeMXでは、DMA設定の「DMA Request Setting」の「Mode」を「Circular」にすることで循環モードを指定します。
こちらは循環モードによるI2Sからのデータを半永久的に受信するプログラム例です。コールバック関数はライブラリー内ですでに定義されているので、関数ポインターの関連付けなどは不要です。
main.c
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint32_t buffer[64];	// 必要なバッファサイズの2倍確保

void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
	// 前半のメモリーがアクセス可能
	ConvertI2SData(hi2s, buffer + 0, 32);
}

void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s)
{
	// 後半のメモリーがアクセス可能
	ConvertI2SData(hi2s, buffer + 32, 32);
}
/* USER CODE END 0 */

int main(void)
{
  /* USER CODE BEGIN 2 */
  // 循環モードでは1回呼び出したら基本的に動き続ける
  HAL_I2S_Receive_DMA(&hi2s, buffer, 64);
  /* USER CODE END 2 */
}
2019/03/08