STM32 + LL でADCでDMAを使用した複数チャンネル変換を動かしてみる

2020年6月11日

STM32マイコンのペリフェラル関連記事を一覧にまとめました。

前回はADCのシングル変換を行いました。

複数のデータを取りたいときや、ロボコンロボットの情報取得用センサーなどアナログ入力の複数のセンサを使用したときには、複数チャンネルのAD変換が必要になると思います。今回は、DMA1を使用して一回の複数チャンネルの変換をやっていきます。連続で変換するためには何度も同じ処理をする必要がありますが、変換をスタートするタイミングは自分で指定が可能です。

今回使用するマイコンはSTM32F450RGTです。

 




リファレンスマニュアルを読む

ADCの機能を確認する

まずは、ADCの機能から見ていきます。

RM0090 リファレンスマニュアルより引用

STM32F4シリーズはADC一つにつき一つのデータレジスタしかないため複数チャンネルの変換を行う場合は、1チャンネル変換を複数回それぞれのピンごとに設定しながら行うか、DMAを使用するといった方法が挙げられます。スキャンモードを使用した場合は、DMAのビットがセットされている場合は変換が終わった後のDRレジスタの値を自動でDMAに転送してくれるそうです。

RM0090 リファレンスマニュアルより引用

データの損失を防ぐためにどうするか書いてありました。DMAの利用をしましょうということです。設定方法はここにあるものを基本にプログラムを書いていこうと考えています。今回のプログラムではオーバーランについての割り込み処理の設定、処理を行う予定はないです。オーバーランの設定は各自で必要に応じて書いていただければと思います。

 

DMAの機能を確認する

DMAのADC1で使用できるストリームを確認をしていきます。

RM0090 リファレンスマニュアルより引用

ADC1で使用できるのはDMA2のストリーム0のチャンネル0またはストリーム5のチャンネル0であるということがわかりました。

RM0090 リファレンスマニュアルより引用

DMAの転送を開始したあとに、転送が完了すると割り込みを生成してくれるそうです。割り込み処理は半分のときと最後で行われるそうですが、今回のプログラムではすべての変換が終わった後の割り込みを使用していきたいと考えています。

 

この後のプログラムで何をしているかの説明はするので、レジスタの確認は各自でお願いします。

 

STM32CubeMXの設定

ADC、ADCのDMAの設定は次の通り行いました。

複数のレギュラ変換を行うためSCANモードをEnableにしました。また10bitでの入力に設定しています。他の設定項目を見ていきましょう。

* 2020/4/11 追記 : 画像ではDMA Continuous Requests がDisabledになっていますが、Enableにしてください。

上記設定では、レギュラ変換の数を4つにしてそれぞれのランクにそれぞれのチャンネルを設定しています。

続いてDMAの設定を行っていきます。ConfigrationのDMA SettingsでDMAの設定を行います。

ストリームはDMA2の0に、優先度はLowにしています。

今回のADCの動作確認にはUSARTでprintfの表示をしたいと考えているためUSAETを有効化にしています。LLライブラリでUSARTでprintfを表示する方法は以下の記事を確認していただければと思います。

ここまでの設定が終わったらProject ManagerのAdvanced Settingsで使用するライブラリをHALからLLに設定、プロジェクト名を決めるといった処理を行った後コードの生成をしてもらいましょう。

 

ソースコードを実装する

生成されたソースコードを確認する

STM32CubeMXが生成されたコードを確認していきましょう。adc.cのMX_ADC1_Init関数を見ていくと、CubeMXで設定したADCの設定が反映されていることと、DMAの初期設定がされていることがわかります。DMAの初期設定の仕方についてはリファレンスマニュアルに書いてあるので各自で確認していただければと思います。続いて、dma.cのMX_DMA_Init関数をみるとNVICの優先度の設定とDMAの割り込み設定がされていることがわかりました。
ここまでの内容を踏まえて次の処理を行う関数を実装していきたいと考えています。
  • ADCを有効化する
  • ADCの変換をDMAに転送する設定をして始める
  • コールバック時に行う処理をする
  • DMA2_Stream0_IRQHandler関数内でADCの変換がすべて終わった後のDMAのコールバック処理を実装する

 

実装する

ソースコードは以下の通りになりました。

#define ADC_CONVERT_DATA_BUFFR_SIZE		((uint32_t)4)

uint16_t adcConvertData[ADC_CONVERT_DATA_BUFFR_SIZE];

/**
 * @brief ADCの有効化、ADC変換終了時のDMAの終了割り込みを有効化
 */
void ADC1_Start(void)
{
	LL_DMA_EnableIT_TC(DMA2, LL_DMA_STREAM_0);
	LL_ADC_Enable(ADC1);
}

/**
 * @brief ADCのDMA変換を開始する
 */
void ADC1_Start_DMA2(void)
{
	ADC1_DMA2_ConvertStart();
}

/**
 * @brief ADCのDMA変換を止める
 */
void ADC1_Stop_DMA2(void)
{
	
}

/**
 * @brief ADCのDMA変換の処理を行う
 * @detail 処理の内容は次の通り
 *   DMAのストリームを設定する
 *   DMAのアドレスの設定を行う
 *  データのアドレスの長さを設定する
 *  ストリームの有効化を行う
 *  AD変換の処理を始める 
 */
void ADC1_DMA2_ConvertStart(void)
{
	LL_DMA_DisableStream(DMA2,LL_DMA_STREAM_0);

	LL_DMA_ConfigAddresses(DMA2, LL_DMA_STREAM_0,
	                      LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA),
	                      (uint32_t)&adcConvertData, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);


	LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_0, ADC_CONVERT_DATA_BUFFR_SIZE);

	LL_DMA_EnableStream(DMA2,LL_DMA_STREAM_0);

	LL_ADC_REG_StartConversionSWStart(ADC1);
}

/**
 * @brief コールバック関数の中で呼ぶ
 *    EOCフラグをクリアする
 *   割り込み処理を行う
 */
void ADC1_DMA2_TransferComplete_Callback(void)
{  
	LL_ADC_ClearFlag_EOCS(ADC2);
    /* 割り込み処理を書く */
}

stm32f4xx_it.c内

/**
  * @brief This function handles DMA2 stream0 global interrupt.
  */
void DMA2_Stream0_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream0_IRQn 0 */
  if( LL_DMA_IsActiveFlag_TC0(DMA2) == 1){
	  LL_DMA_ClearFlag_TC0(DMA2);
	  ADC1_DMA2_TransferComplete_Callback();
  }
  /* USER CODE END DMA2_Stream0_IRQn 0 */
  
  /* USER CODE BEGIN DMA2_Stream0_IRQn 1 */

  /* USER CODE END DMA2_Stream0_IRQn 1 */
}

変換処理完了後のフラグを確認してフラグが立っていた場合は割り込み動作を行う。また、DMAの割り込み変換終了フラグはソフトウェアでクリアする必要がある。したがって、ソフトウェアでクリアしている。

 

使い方の例

int main()
{
    // 初期化処理は省略
    ADC1_Start();

    ADC1_Start_DMA2();

  while(1){
   }
}

変換終了後に、printfで数値を表示して再度変換をスタートすれば連続変換が可能になります。

関数の改造例

/**
 * @brief コールバック関数の中で呼ぶ
 *    EOCフラグをクリアする
 *   割り込み処理を行う
 */
void ADC1_DMA2_TransferComplete_Callback(void)
{  
	/* 割り込み処理を書く */
	LL_ADC_ClearFlag_EOCS(ADC2);
	printf("%d,%d,%d,%drn",
       adcConvertData[0],adcConvertData[1],
       adcConvertData[2],adcConvertData[3]);
        ADC1_Start_DMA2();
}

1msおきといった一定周期ごとに変換をスタートした場合は、TIM割り込みなどで実行処理を呼ぶなりすればできると思います。

 

おわりに

ADC+DMAの設定は色々と行うことがあります。ADC、DMAのレジスタのビットの説明を読みながら設定を見ていくとソースコードの内容がわかりやすいと思います。

次回は、TIMの割り込み処理の実装のやり方を書いていきたいと思います。

 

参考文献

RM0090 リファレンスマニュアル

UM1725 User Manual

  1. Direct Memory Accessの略