Nucleo F303K8 ADC で複数チャンネルを読む w/STM32CubeIDE

ADCをもう少しだけ深く使ってみます。

以前複数チャンネルADCで苦労した記憶がありますので，自分のまとめとして書いておきます。

# 2つのチャンネルを一つずつ読む

テストのため，PA3とPA4 PA5とPA1を接続し，DAの出力をADで読みます。

```
PIN Arduino STM32
 7    A5     PA6 DAC2_OUT1 UnBuffer
 8    A2     PA5 DAC1_OUT2 UnBuffer --+
 9    A3     PA4 DAC1_OUT1 --+        |
10    A2     PA3 ADC1_IN4  --+        |
11    A1     PA1 ADC1_IN2  -----------+
...
```

ADC1 IN2 と IN4 を有効にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/0e81ea16-d8b8-38c3-d47b-6901a0dcb0ef.png)

- Scan Conversion 複数チャンネルをADすることのようです。Enable
- Continuous は連続してADする場合。Disable
- なぜか Discontinuous の設定項目もある。とりあえず Disable
- DMA まずは Disable
- End Of Conversion Selection いつ EOCフラグを立てるかの設定のようですね。1チャンネルごとにしたいので `End of single conversion`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/fa686ff6-8063-df42-a346-49893cf3d48e.png)

- Regular と Injection 2つの ADC があって，とにかく普通のは Regular です。
- Number Of Conversion を 2 にすると，その下でやっと Channel 2 と 4 が出てきます。わかりにくいですが仕方ないですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/e747220c-9e5a-d9ff-6e44-ae2a5117a356.png)

```main.c
  /* USER CODE BEGIN 2 */

  HAL_DAC_Init(&hdac1);

  HAL_DAC_SetValue(&hdac1, DAC1_CHANNEL_1, DAC_ALIGN_12B_R, 0x800);
  HAL_DAC_Start(&hdac1, DAC1_CHANNEL_1);

  HAL_DAC_SetValue(&hdac1, DAC1_CHANNEL_2, DAC_ALIGN_12B_R, 0x80);
  HAL_DAC_Start(&hdac1, DAC1_CHANNEL_2);

  HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);

  uint16_t result[8]= {0};

  HAL_ADC_Start(&hadc1);

  while ( !(ADC1->ISR & (1<<2)) ) {} // wait until EOC is 1
  result[0]=HAL_ADC_GetValue(&hadc1); // ADC1 channel 4 = DAC1 channel 1

  while ( !(ADC1->ISR & (1<<2)) ) {} // wait until EOC is 1
  result[1]=HAL_ADC_GetValue(&hadc1); // ADC1 channel 2 = DAC1 channel 2

  HAL_ADC_Start(&hadc1);

  while ( !(ADC1->ISR & (1<<2)) ) {} // wait until EOC is 1
  result[2]=HAL_ADC_GetValue(&hadc1); // ADC1 channel 4 = DAC1 channel 1

  while ( !(ADC1->ISR & (1<<2)) ) {} // wait until EOC is 1
  result[3]=HAL_ADC_GetValue(&hadc1); // ADC1 channel 2 = DAC1 channel 2

  /* USER CODE END 2 */
```

実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/0f840c05-e091-b4c7-a70e-00074b8f8dc5.png)

# DMAを使う場合

EOC設定を`End of sequence of conversion` にします。すべてのチャンネルを変換し終わったら，という感じですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/241c1ef9-5e0c-8249-2766-9809a9041c3a.png)

DMA設定を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/9cb59d4a-1188-64e3-75c2-5a842062315e.png)

8要素の配列を用意しましたが，`HAL_ADC_PollForConversion` のあと，最初の2個に結果が入ります。

```main.c
  uint16_t result[8]= {0};

  HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&result, 2);

  HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
```

その後，再度 `HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&result, 2);` して結果を待てば，再び result[0] result[1] に結果が入りました。

`HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&result, 2);` では 2ch分読むように指定しているのですが，最後の引数を4にしたらどうなるでしょうか。結果，2ch分だけ，`result[0]`と`result[1]`が更新されて戻ってきていました。

`HAL_ADC_PollForConversion` しない場合はどうなるのでしょうか。深くは検証できませんでしたが，DMAが終わっていれば結果が正しく入っていました。

たとえばAD前に準備が必要で，変換のタイミングを自分で決めたい場合にこの方法は有効ですね。

# 連続で変換する

要するにほったらかしでDMA結果を参照すればその時までに変換転送された値が読めるというものです。

Continous と DMA Continuous を Enable にします。EOCは single conversion にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/d84eb3a7-4bb7-fb58-799c-65417bc7482d.png)

Sampling Time を大きくします。ここでは`61.5 Cycles`にしました。（理由はあとで）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/aba0f5d0-a8fb-cec9-2a36-4662ad280b8e.png)

DMA Mode を変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/dfea35d6-0cf9-ced5-fc15-25a5f83fc38e.png)

```main.c
  /* USER CODE BEGIN 2 */

  // DAC設定1 省略

  HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);

  uint16_t result[8] = {0};

  HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&result, 2);

  // break 1 DAC設定1 が読める

  // DAC 設定 2 省略

  HAL_Delay(10);

  // break 2 DAC設定2 が読める

  // DAC 設定 3 省略

  HAL_Delay(10);

  // break 3 DAC設定3 が読める

  /* USER CODE END 2 */
```

`result[0]` と `result[1]` を参照すればその時までにDMAされた変換値が入ります。

ADC の Sampling Time をあえて大きくしたのは，DMAが終了する前に次のADCが終了してしまい，フリーズのような状況に陥るのを防ぐためです。本当はきちんと計算しなくてはいけないのですが，システム 8MHz ADC は div 4 で設定した本件では 61.5 Cycles 必要だったということになります。

参考

- https://www.st.com/content/ccc/resource/training/technical/product_training/1e/0f/65/34/7e/b0/4e/ca/STM32L4_Analog_ADC.pdf/files/STM32L4_Analog_ADC.pdf/jcr:content/translations/en.STM32L4_Analog_ADC.pdf
- https://www.computex.co.jp/article/use_stm32f_3.htm