STM32でADCをやってみる2(DMAを使ったレギュラ変換)

前回は単一チャンネルの変換を行ったが今回はDMAを使ったものをやって行こうと思う。

とりあえずADCおさらい

ADCは変換終了フラグ(EOC)を見てDRレジスタを見に行くことで変換データを得ることができる。

f:id:gsmcustomeffects:20170409035725p:plain

上記の図のようにシーケンスの終了でもフラグが立つ(EOS)
二つのフラグを見てソフトウエア的に行うこともできるがタイミングがずれるとDRのデータが上書きされてしまう。

それを防ぐ仕組みにオーバーランというものある。
これはデータを保護できるがそのあとの変換値は破棄されてしまうので注意が必要だ

f:id:gsmcustomeffects:20170409040231p:plain

そのため複数チャンネルを変換する場合EOCフラグをトリガとしてDMAでレジスタ→メモリを実現したほうがソフト的な介在がなくなるので楽に処理できる。

マニュアル曰く(RM0316 345ページ)

変換されたチャネルの値は特定のデータレジスタに格納されるので、複数のチャネルの変換には
DMA の使用が便利です。これによって、ADCx_DR レジスタにすでに格納されているデータの損失を防ぐことができます。
DMA モードが有効なとき(ADCx_CFGR レジスタの DMAEN ビットがシングル ADC モードで 1 にセットされている場合、またはデュアル ADC モードで MDMAが0b00 以外に設定されている場合)、
各チャネルの変換後、DMA リクエストが生成されます。これにより、変換データを ADCx_DR レジスタからソフトウェアで選択した場所へ転送することができます。
これにもかかわらず、DMA が DMA 転送リクエストを時間内に処理できなかったためにオーバーランが発生した場合(OVR=1)、ADC は DMA リクエストの生成を停止し、新しい変換に対応するデータ
は DMA によって転送されません。
これは、RAM に転送されるすべてのデータを有効とみなすことができることを意味します。

要するにCH変換ごとにDMAリクエストが生成されるということ。
オーバーランが発生するとDMAがとまる。
OVRMODを適切に処理するか頻繁に止まるようならDMAを時間内に処理できるように変換自体の速度を下げておくなど工夫が必要だ。

DMAのモードには

  • DMA ワンショットモード(DMACFG=0)
  • DMA サーキュラモード(DMACFG=1)

の2つがあり

ワンショットは一回きりの転送になります

f:id:gsmcustomeffects:20170409042453p:plain

一方サーキュラは連続的にADCのCH変換が終わるたびにDMAリクエストが生成するので連続的なストリームが構成できる。

f:id:gsmcustomeffects:20170409042740p:plain

さっそくやっていく

CubeMXでADCを設定していく。

f:id:gsmcustomeffects:20170409043833p:plain

ADCの詳細設定

f:id:gsmcustomeffects:20170409043955p:plain

DMAの設定

f:id:gsmcustomeffects:20170409044035p:plain

ここでアドレスのインクリメントにチェックを入れておくといい感じに配列に格納できる。

ハードウエア

前回と同じでこれにPA_5、PA_4を追加して2CHを読んでいる点だ

f:id:gsmcustomeffects:20170408045403j:plain

コード

参考までに動いたコードを示しておく
ADC終了時に呼ばれるコールバック内にprintfを入れてUART出力している。


動作例

Teratermではこんな感じに2CH読めている。

f:id:gsmcustomeffects:20170409050119p:plain

Expressionではこんな感じ

f:id:gsmcustomeffects:20170409050225p:plain

まとめ

3日間ぐらいかけてADCやってみたんだけど何だかんだDMAを使ったのが一番簡単だねぇ
DMAといったら結構難しいイメージあったけどCubeMXのおかげでほとんど設定してくれるのでかなり楽ですね。

3日間一緒に検討してくれたリアルテック先生には感謝です。

http://realteck-blog.netlify.com/2017/04/08/f031%E3%81%AEadc%E9%80%A3%E7%B6%9A%E5%A4%89%E6%8F%9B%E3%81%8C/realteck-blog.netlify.com

2017/5/18追記

お恥ずかしいお話なのですがオカダといろいろやってて複数のADCを使うとMainにブレーク立てても帰ってこないと2日間ぐらい悩みました。

f:id:gsmcustomeffects:20170518003058p:plain

んで上記の図のようにこいつのADCには低速チャネルもあるんです。
12ビット精度で変換する場合最低でも14サイクルかかります
f:id:gsmcustomeffects:20170518003238p:plain

高速の場合Cubeで19.5サイクルにすればよいのですが低速の部分も19.5にしてました・・・・・ほんと雑魚wwww

なので皆さんはこんなバカなことで時間とかしちゃだめですよ!

直すとちゃんと動くf:id:gsmcustomeffects:20170518003433p:plain

追記 2019/3/12

自分の場合上記のコードで動いたのですがshyachiさんより指摘があったので載せときます

 /* USER CODE BEGIN 2 */
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
    {
      Error_Handler();
    }

  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

の2つの関数はMX_ADC2_Initにてコールされているので呼ばなくて良さそうです。
特にHAL_ADC_ConfigChannelの方は呼ぶと2ch変換できなくなるケースも有るとか

動かない人はこの辺もお試しください