STM32のタイマーをCubeMXで

# はじめに
STM32のタイマーには色々設定が有って複雑です。
今回、ADコンバータの変換トリガーとしてタイマーを使う方法を調べてみました。

# 使用機材
## 基板
製品名：STM32F103 Nucleo-64
マイコン：STM32F103RBT6 (Cortex-M3 72MHz)
ペリフェラル：ADC(2), TIM(4), I2C(2), SPI(2), USART(3), USB(1), CAN(1)
出力端子：ST morpho, Arduino Uno
デバッガ：ST-LINK/V2-1

[秋月電子](http://akizukidenshi.com/catalog/g/gM-07724/)
1,500円でST-LINKデバッガもついてくるのでコスパは良いです。

[STM32F103RBマイコンマニュアル](https://www.st.com/content/st_com/ja/products/microcontrollers/stm32-32-bit-arm-cortex-mcus/stm32-mainstream-mcus/stm32f1-series/stm32f103/stm32f103rb.html#sw-tools-scroll)


## コードジェネレータ
製品名：[STM32CubeMX](https://www.st.com/ja/development-tools/stm32cubemx.html)
Version 5.0.0

GUIで各種ペリフェラルの設定が行えて、コードも自動生成してくれます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/a0de766b-ea8c-b507-b628-b78ca3715c11.png)

設定可能な組み合わせを教えてくれる(設定不可が分かる)のでとても簡単です。

なお、ライブラリは、CMSISでは無くHAL Driverというものになります。
CMSISより抽象度を高めたものですが、コードやRAM使用量が増加するので注意が必要です。
ただ、CMSISで自力で書くという場合でも、設定方法を調べるのに便利です。

## 統合開発環境
[Atollic TrueSTUDIO for STM32](https://www.st.com/ja/development-tools/truestudio.html)
Version: 9.2.0

元はAtollic社による製品だったのですが、STマイクロが買収して無償化されたものです。
OpenSTM32 Community の System Workbench for STM32 も有りますが、
こちらは元製品版のProfessional相当ということも有りますのでお勧めです。
ちなみに、eclipseベースです。

## Flash Writer
[STM32CubeProg](https://www.st.com/ja/development-tools/stm32cubeprog.html)
もし、統合開発環境からST-Linkで基板に接続出来ない(デバッグ出来ない)という場合は、このツールでドライバーを入れてみると認識するようになるかもしれません。

# 基本設定
## RCC設定
ST-LINKのクロックを使用するので、HSEに"BYPASS Clock Source"を設定します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/678f5488-1166-61ab-83c1-803dd71c9f38.png)
自前で水晶発振器を取り付ける場合は、"Crystal/Ceramic Resonator"を設定します。

## Clock Configuration
クロックの設定を行います。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/4afd7ab5-d7c7-a462-c9cc-db6dcd16dca9.png)
外部入力クロックHSE 8Mhzが入力になるように選択し(HSIが内蔵クロック)、PLLMulで周波数を上げたPLLCLKをシステムクロック(SYSCLK)とします。
システムクロックがプログラムコードの実行速度に影響するのでなるだけ上げておきます。
そこから、ペリフェラルに供給するクロックを設定していきますが、各ペリフェラルで必要な設定との兼ね合いで決めていきます。

# タイマー更新トリガーでADC変換
## ADC設定
使用するADCを選択し、使用するピン(IN0)を指定します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/b857f6ea-8b14-af8c-fda3-f318c5e46d89.png)
PA0がADC1で使用状態になります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/9dd94622-e5e9-58c4-c068-26b2359fde96.png)

タイマーをトリガにしたいので、
"ADC_Regular_ConversionMode"の "External Trigger Conversion Source"で入力となるタイマーイベントを選択します。
ここでは、"Timer 3 Trigger Out event"(更新イベント)を選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/c407243a-eefc-e03f-2847-e3d34ff401b2.png)

複数のPINを変換する場合は、"Number Of Conversion"でその数を入力して、RankでそれぞれのChannelを指定するようです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/e79273a9-6827-deab-7f3c-edddaab57014.png)

## タイマー設定
ADCでTimer 3を指定したので、TIM3を設定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/cf3d43b1-9625-797b-a9ce-1ce2594969aa.png)
ここではシンプルに、指定時間間隔でタイマーイベントが発生するだけのものにしています。
Clock Source の "Internal Clock"は、Clock Configurationの APB1 Timer clocks (72MHz)が入力になります。

ここでは、1秒間隔のタイマーにしてみました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/f515224a-8d9c-ed2d-0f22-18bcf0713fcb.png)
Prescalerで分周したクロック周期が Counter Period の回数だけカウントしたらトリガーが発生します。

Prescalerは、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/15860d91-f1a3-29da-f3ed-a5d9505f70bd.png)

f(CK_PSC) = 72MHz
PSC = Prescaler = 36000 - 1
なので、カウンタ クロック周波数は 72MHz / (36000-1 + 1) = 2000Hz となります。
1秒タイマーにするには2000カウントすればいいのですが、カウンターは0から始まって指定カウントに到達してからイベントが発生するので、
Counter Periodには2000-1を設定します。

ADCに更新イベントを送りたいので、"Trigger Event Selection"に "Update Event"を指定します。

## 起動処理
main()関数でペリフェラルの初期化関数が呼ばれた後で、ADCとTIMをスタートします。

```c
/* USER CODE BEGIN 2 */
HAL_ADC_Start_IT(&hadc1);
HAL_TIM_Base_Start_IT(&htim3);
/* USER CODE END 2 */
```

関数名末尾に'\_IT'とついているのは割り込みによる非同期版という意味です。
同期的にタイマカウンタをループで監視したい場合は、HAL_TIM_Base_Start() を呼びます。

# 更新タイマーの挙動
ADCのトリガに入れた場合は問題無いのですが、経過時間後に"割り込み"を発生させる設定にした場合、
何故かタイマーをスタートした直後にも更新イベントの割り込みが呼ばれます。
~~(仕様っぽい感じも有りますが..)~~

比較イベントであれば問題無いようなので以下で対応出来ます。

(2019/01/03 追記)
タイマーをスタートさせる前に以下で更新割り込みフラグをクリアする必要が有りました。

```c
__HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
```

## 比較イベント設定
どれかChannelに、"Output Compare No Output"を設定します。(他のペリフェラルの入力にしないのでNo Outputにしています)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/d8937353-cac9-3e8a-488d-7acf0b33c304.png)

Configurationに、"Output Compare No Output Channel 1"が現れるので、Pulseにカウンタ(2000-1)を設定します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/d3a04eb7-7aeb-e23c-cdcd-bb5321dbcd7c.png)

## 割り込み有効
割り込み関数が呼ばれるようにするにはタイマ設定からNVIC Settingsタブを選択して、
TIM3 global interruptをEnabledにします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/120192/0f09f37f-4457-008b-441f-befa295822a4.png)

## 割り込み関数
stm32f1xx_hal_tim.hに、割り込み発生時に呼ばれるコールバック関数のプロトタイプ宣言が有ります。

```c
/* Callback in non blocking modes (Interrupt and DMA) *************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim);
```

更新イベントは、HAL_TIM_PeriodElapsedCallbackですが、
比較イベントは、HAL_TIM_OC_DelayElapsedCallbackです。

USER CODEが書ける場所に以下を追加します。

```c
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
  if((htim->Instance == TIM3) && (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)) {
    // タイムアウト処理
  }
}
```

## 起動処理
こちらもやはり、main()関数でペリフェラルの初期化関数が呼ばれた後で以下を呼び出します。

```c
/* USER CODE BEGIN 2 */
HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1);
/* USER CODE END 2 */
```