2024年9月17日火曜日

STM32 ADC→DMA→MEMにたくさん積んでから割り込みしてよ

STM32のADCとDMAについてです.

net検索しても以下のissueは見かけないんだけどなんでだろ?

まずはフツーのAD変換と割り込み周期の関係
↓STMのADCには1ch変換するsingle modeと、多ch変換するscan modeがあります.
この例ではサンプリング周波数1kHzですから、出力data数に1個か3個かの違いはあれど、変換完了割り込みは1kHz周期です.HAL_ADC_ConvCpltCallback()が変換完了割り込みです.
ただし、scan ch毎に割り込ませることも可能で、その場合の割り込み周期は3kHzになる.でもあまり高速な割り込みは嫌いです.

次に、DMAと組み合わせて動かしたいとき
STM32をちゃちゃっと設定して自走させるとこれになっちゃうことが多いです.でもこれだったら①と同じ逐次割り込みですからDMAする意味が薄いですよね?

DMAを使うとしたら、こんな風に100 sample分をメモリに積んでから割り込みさせる、という運用をしたい.結果として割り込み周期が10Hzに遅くなるので演算処理をバースト的にやる余地が生まれる.

というわけで、、、
主題:DMA②とDMA③を分ける設定はどうすりゃいいのか?

うぇ~ん、もう眠いので明日続きを書きます

ーーーー
翌朝

健康診断のためM先生のところへ来ています。

実はSTM32F401を使っていた時は、DMA②になってしまって「冴えないDMA」と思いつつも、fs=1kHzだったので放置しました。

しかし今STM32H723のADCをfs=32kHzで動かそうとしているので、DMA②だと忙しくてもう嫌になりました。
演算で32k→4kに間引きます。位相回転を避けるため。

検診終わった、お腹すいた〜
 ↓
サイゼリヤ。中目黒民はサイゼが好きな様で常に満席。ホールの人の忙しさは中目黒最強なのではと、この店に来るといつも思ふ。
ドリア食ってたら飲み会の誘いが来て、久しぶりの人が来るというので行きたかったが、奥さんがパートに出る日なので欠席になりました。残念です。

お釣りを貰うと新札がちらほら混じるこの頃ですが、新札未対応のマシンがそこら中にあるので新札だと「チッ」と思います。

ーーーー
帰宅して一息ついたのでSTM32 ADC/DMAの件に戻ります.

↓DMA③を実現できた様子です.
条件は、、、
 ・TIM15で32kHzのADC trigger
 ・ADCはTIM15 triggerでharware自走
 ・DMAで1024wordのメモリに積む
 ・1024word積んだら割り込み
 ・ゆえに割り込み周期は32Hz
 ・オシロ表示はHAL_ADC_ConvCpltCallback()でport toggleしたもの

DMA③にするための設定を以下に記します.STM32CubeMXの画面をスクショすると重くなるのでInit codeをコピペするにとどめます.不要行は削除します.CPUはSTM32H723ですので、他の品番だと微妙に異なります.
設定はこの5つから成ります.
 1)ADC init
 2)DMA init
 3)TIM15 init
 4)main()
 5)HAL_ADC_ConvCpltCallback()

1)ADC init
static void MX_ADC1_Init(void){
 ↓IN5のみの1ch入力なのでscanはdisableです
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
 ↓ADC自身がグルグル自走するmodeにしない 重要
  hadc1.Init.ContinuousConvMode = DISABLE;
 ↓IN5のみなので1回変換
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DiscontinuousConvMode = DISABLE; ←これ知らない
 ↓TIM15でトリガされる
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T15_TRGO;
 ↓データをDMAに送り、DMAの送り先はリングバッファ
  hadc1.Init.ConversionDataManagement =                ADC_CONVERSIONDATA_DMA_CIRCULAR;

 ↓IN5の設定
  sConfig.Channel = ADC_CHANNEL_5;
 ↓ADC速度最速だが遅くしてもいいんじゃね?
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
 ↓IN5はシングルエンド(差動ではない)
  sConfig.SingleDiff = ADC_SINGLE_ENDED;

DMA②かDMA③かの設定の違いは、ContinuousConvMode です.注意しましょう!

2)DMA init
STM32CubeMXの画面です.お好みでどうぞ.DMA1 stream1がADC1の担当です.

3)TIM15 init
static void MX_TIM15_Init(void){
 ↓STM32H723を最高clockで動かすと、TIM15 clock=275MHzになります.275MHz/8593=32kHzになります
  htim15.Init.Prescaler = 0;
  htim15.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim15.Init.Period = 8592;
  htim15.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim15.Init.RepetitionCounter = 0;
 ↓自走してグルグル回る
  htim15.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
 ↓トリガ=updateにするとカウンタフルでトリガが出る=32kHz
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;

4)main()
int main(void){
 uint32_t ADCfifo[1030]; DMAの積み先確保
 HAL_TIM_Base_Start_IT(&htim15);  TIM15スタート
 ↓ADC/DMAスタート  1024回まとめてDMAしてね
 HAL_ADC_Start_DMA(&hadc1, &ADCfifo[0], 1024);
 while (1)  {
 }

5)HAL_ADC_ConvCpltCallback()
DMAに1024個積み終わったら呼ばれる割り込み関数です.
HALライブラリでweak宣言されているので、自分のsource codeに同名で記述するとそれが採用されます.
いろいろな信号処理を記述すればいいでしょう.


以上です.

かしこ

4 件のコメント:

  1. >DMA③
    https://blog.haru3.me/posts/20220913-stm32-timer-based-adc/
    STM32で一定周期のADC変換を行いたい
    が、これじゃないでしょうか?
    ※確かに、よく出てくるのは、DMA②(ch数=サンプル数)ばかりですね。
    HAL_ADC_Start_DMA(&hadc,(uint32_t *)adcData, count)
    で、count≠ch数 でも、OKのようです。(count > ch数 のときは、同じチャンネルで繰り返しサンプルする模様)あと、「タイマーを併用する」のが、ミソみたいです。
    (タイマーを使わなくても出来るが、バス使用率?が上がって、CPU処理が遅くなる模様。参考文献のところに書いてある)

    返信削除
    返信
    1. これじゃぁ
      Continuous Conversion=Disabled
      これがポイント!

      削除
    2. ↑の参考文献の、
      DMAで連続変換するときの鍵、によると
      ・continuous conversion enableにしてDMA continuous request enableにしておくとDMA割り込みが入りまくることに注意.ADCが終わるたびにDMA走る.
      にしておくと、「勝手に次のADC&DMAが走る」みたいですね
      (勝手に「リングバッファ処理」と、同じことしてくれるので使い方によっては、こっちの方がいいみたいですが。)
      ※CubeMX って、ホントによく出来てるな

      削除
    3. CubeMXがあるからSTM32を使っています.無料でサンクス

      細かい設定は型番によって微妙に違っていて、たとえばH723には、
       DMA continuous request enable
      が無かったりするんです
      面食らいます

      削除