前回で複数のポートに繋がっているセンサデータを取得できました。次はDMAの機能を使って、CPUの負荷を低減します。
参考にした先達のサイトです。

DMA
Wikiから抜粋します。
調べてみると、昔からある機能のようです。PiCo Classic3に搭載しているマイコンRX631にもDMACが組み込まれています(ほとんど覚えてない)。
STM32F446データシートのDMA controllerの説明を翻訳してみます。
「これらのデバイスは、それぞれ8つのストリームを持つ2つの汎用デュアルポートDMA(DMA1およびDMA2)を備えています。 これらは、メモリからメモリ、ペリフェラルからメモリ、およびメモリからペリフェラルへの転送を管理できます。 これらはAPB / AHBペリフェラル専用のFIFOを備え、バースト転送をサポートし、最大ペリフェラル帯域幅(AHB / APB)を提供するように設計されています。
2つのDMAコントローラーは循環バッファー管理をサポートしているため、コントローラーがバッファーの最後に到達したときに特定のコードは必要ありません。 2つのDMAコントローラーには、特別なコードを必要とせずに2つのメモリバッファーの使用と切り替えを自動化するダブルバッファー機能もあります。
各ストリームは専用のハードウェアDMA要求に接続されており、各ストリームでソフトウェアトリガーがサポートされています。 構成はソフトウェアによって行われ、送信元と宛先の間の転送サイズは独立しています。」
DMAは、主要な周辺機器で使用できます。
- SPI and I2S
 - I2C
 - USART
 - General-purpose, basic and advanced-control timers TIMx
 - DAC
 - SDIO
 - Camera interface (DCMI)
 - ADC
 - SAI1/SAI2
 - SPDIF Receiver (SPDIFRx)
 - QuadSPI
 
APBとは、Advanced Peripheral Busの略で、低速の上記のような周辺機器とのデータのやり取りをするためのバス。AHBとは、Advanced High-Performance Busの略で、高速伝送用のパイプライン型のバスのようです。ARM社のオンチップ・バス規格AMBA(Advanced Microcontroller Bus Architecture)で定められているとのこと。
block diagramを見てみましょう。GP-DMA1とGP-DMA2が、APB/AHB1とAPB/AHB2とやり取りするための専用の線があることがわかります(やりとりするのがデータなのか指令なのかわからず)。APB/AHBは、AHBとAPBをインターフェースするブリッジ回路のことのようです。
Bus Arbiter(調停回路)が、DMA内にあるのかARMコア内にあるのか、調べてもよくわからなかったのですが、要するに、DMAがいい感じにこっちからあっち、あっちからこっちというふうにデータを転送してくれるようです。

STM32CubeMX
DMAを使ったADCの設定をしてみます。Pinout viewやClock Configurationの設定は前回と同じです。
ADC ConfigurationのDMA Settingsタブを開きます。
Addボタンを押して、DMA RequestでADC1を選択。ModeをCircularにします。

次は、Parameter SettingsタブのADC_Settingsの項目を以下に変更します。先に上記のDMA Settingsをしておかないと、DMA Continuous RequestsでEnableが選択できません。
- Scan Conversion ModeをEnableにする
 - Continuous Conversion ModeをEnableにする
 - Discontinuous Conversion ModeをDisableにする
 - DMA Continuous RequestsをEnableにする
 - End Of Conversion Selectionに「EOC flag at the end of all conversions」にする
 

ADC_Regular_ConversionModeは、前回と同様です。

プログラム
ループの前にHAL_ADC_Start_DMA関数を記述するだけで、DMAをつかったADCが使えます。本当にメモリを読み込んで使っているだけなのでプログラムの記述が超簡単です。
  /* USER CODE BEGIN 2 */
  setbuf(stdout, NULL);
  setbuf(stdin, NULL );
  printf("Started!\n\r");
  uint16_t adc_Value[5];
  float a[5];
  HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_Value, 5);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  // ADC sequence
	  for(int i=0; i<5; i++){
	  	  a[i] = (float)adc_Value[i] * 3.3 / 4096;
	  }
	  // Voltage divider resistor Vbatt -> 20kΩ -> 6.8kΩ -> GND
	  a[0] = a[0] * (20+6.8) / 6.8;
	  printf("adc_volt0 =%.3f, adc_volt1 =%.3f, adc_volt2 =%.3f, adc_volt3 =%.3f, adc_volt4 =%.3f\n\r", a[0], a[1], a[2], a[3], a[4]);
	  HAL_Delay(500);
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
実行
前回と特に変わっていません。CPUの処理が軽くなった?

