Wio LTE JP Version(STM32F405RG)で HardwareTimer のタイマー割り込みを行う

LTE Cat.1 モデム搭載のマイコンボード "[Wio LTE JP Version](https://soracom.jp/products/module/wio_lte/)" は [STM32F405RG](https://www.st.com/content/st_com/ja/products/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus/stm32-high-performance-mcus/stm32f4-series/stm32f405-415/stm32f405rg.html) を搭載しています。

この MCU はハードウェアタイマーを計14個持っており、タイマー割り込み元として利用可能です。ここではハードウェアタイマーによる割り込みを利用する方法を記しておきます。

**5/9 更新**: `TIM_TypeDef` 等を指定しなくてもできることが分かったので、その方法に更新しました。詳しくはあとがきで。

**2022/1/8 更新**: [SeeedJP/Arduino_Core_STM32](https://github.com/SeeedJP/Arduino_Core_STM32) 1.8.0 で、コールバック関数が `set::bind()` に対応しました。コールバック引数の書き方が変更となっています。

## 論よりコード

1 秒毎に `timer1_cb()` が実行される最小コードです。

``` c++
#include <WioLTEforArduino.h>
WioLTE Wio;

HardwareTimer Timer1(TIM1);

void timer1_cb() {
  SerialUSB.println("=> timer1_cb!");
}

void setup() {
  SerialUSB.begin(115200);
  delay(500);
  SerialUSB.println("START ----");
  Wio.Init();

  Timer1.pause();
  Timer1.setOverflow(1000 * 1000, MICROSEC_FORMAT); // = per 1000 ms
  Timer1.refresh();
  Timer1.attachInterrupt(timer1_cb);
  
  Timer1.resume(); // START
}

void loop() {
}
```

このように動作します。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/3702c3f1-092e-b9fd-a987-27b09efe1ae8.gif)

### 解説

`setOverflow()` で割り込み間隔を設定、 `attachInterrupt()` でコールバック関数を設定、 `resume()` でタイマー開始です。
その他のメソッドは [HardwareTimer.h](https://github.com/SeeedJP/Arduino_Core_STM32/blob/main/cores/arduino/HardwareTimer.h#L100) を見てください。

## 調査したこと

### `setOverflow()` の第2引数で割り込み間隔の指定方法を変更

例では `MICROSEC_FORMAT` を使いましたが `HERTZ_FORMAT` も利用可能です。名前の通り Hz で指定します。 (定義は [ここ](https://github.com/stm32duino/Arduino_Core_STM32/blob/master/cores/arduino/HardwareTimer.h#L63))
以下は同じことです。お好きな指定方法をお使いください。

```c++
  Timer1.setOverflow(100 * 1000, MICROSEC_FORMAT); // = per 100 ms
  Timer1.setOverflow(10, HERTZ_FORMAT); // = per 100 ms
```

※ `TICK_FORMAT` は結局プリスケーラ―(Prescaler)やクロックから算出するソースとなります。使いたければどうぞ。

### 複数のハードウェアタイマーを同時に利用

インスタンスを複数作り、それぞれのインスタンスで `setOverflow()` や `attachInterrupt()` を指定していきます。

```c++
#include <WioLTEforArduino.h>
WioLTE Wio;

HardwareTimer Timer1(TIM1);

void timer1_cb() {
  SerialUSB.println("=> timer1_cb!");
}

HardwareTimer Timer6(TIM6);

void timer6_cb() {
  SerialUSB.println("===> timer6_cb!");
}

void setup() {
  SerialUSB.begin(115200);
  delay(500);
  SerialUSB.println("START ----");
  Wio.Init();
  delay(500);
  
  Timer1.pause();
  Timer1.setOverflow(1000 * 1000, MICROSEC_FORMAT); // = per 1 sec
  Timer1.refresh();
  Timer1.attachInterrupt(timer1_cb);
  Timer1.resume();
  
  Timer6.pause();
  Timer6.setOverflow(1200 * 1000, MICROSEC_FORMAT); // = per 1.2 sec
  Timer6.refresh();
  Timer6.attachInterrupt(timer6_cb);
  Timer6.resume();
}

void loop() {
}
```

#### 計 14 個のタイマーが利用できる

`HardwareTimer` で指定している `TIM1` や `TIM6` といった定数は `TIM1` から `TIM14` の計 14 個が利用できます。すなわち、同時に14個のハードウェアタイマーを利用できるという事です。

定数は以下のように利用できる機能が異なるようです。

| 定数                             | 機能名                           |
|----------------------------------|---------------------------------|
| `TIM1`, `TIM8`                   | Advanced-control timers         |
| `TIM3`, `TIM4`, `TIM9` ~ `TIM14` | General-purpose timers          |
| `TIM2`, `TIM5`                   | General-purpose timers (32-bit) |
| `TIM6`, `TIM7`                   | Basic timers                    |

それぞれ試してみましたが、「～秒に1回割り込む」程度の利用であればすべて同様に利用できました。
機能として何があるのかは [STM32F4xx の仕様書](https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f427-437-and-stm32f429-439-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf#page=17) を参照ください。

### コールバック関数の引数について

[SeeedJP/Arduino_Core_STM32]() 1.8.0 にて、コールバック関数の引数指定方法が変わりました。

```c++:旧
void timer1_cb(HardwareTimer *HT) {}
```

```c++:1.8.0以降
void timer1_cb() {}
```

本家 [Arduino_Core_STM32 の HardwareTimer.h](https://github.com/stm32duino/Arduino_Core_STM32/blob/master/cores/arduino/HardwareTimer.h#L134) の [2020年3月の修正](https://github.com/stm32duino/Arduino_Core_STM32/commit/2983175adc4162efcc88d7ed22cf310d21edb002) が取り込まれたためです。 ([当該コミット](https://github.com/SeeedJP/Arduino_Core_STM32/commit/2983175adc4162efcc88d7ed22cf310d21edb002#diff-92fadde44a62db1d865755492bfafe922d6200f5006456bf738ef33692276c79R944))

これにて `std::bind()` で[コールバック関数に引数を指定](https://github.com/stm32duino/STM32Examples/blob/main/examples/Peripherals/HardwareTimer/Timebase_callback_with_parameter/Timebase_callback_with_parameter.ino)ができるようになっています。

### 例) resume と pause を二つのタイマーでやり取りする

これまでの内容を踏まえて、二つのタイマーを使ったサンプルです。
二つのタイマーを用意し、片方が5回カウントしたら自分自身を止めつつもう片方のタイマーを動かすのを繰り返します。

```c++
#include <WioLTEforArduino.h>
WioLTE Wio;

HardwareTimer Timer1(TIM1);
volatile int t1;

HardwareTimer Timer3(TIM3);
volatile int t3;

void timer1_cb() {
  t1++;
  SerialUSB.print("timer1_cb: "); SerialUSB.println(t1);
  if (t1 > 4) {
    SerialUSB.println("timer1 stopping and timer3 will start");
    Timer1.pause();
    t3 = 0;
    Timer3.resume();
  }
}

void timer3_cb() {
  t3++;
  SerialUSB.print("timer3_cb: "); SerialUSB.println(t3);
  if (t3 > 4) {
    SerialUSB.println("timer3 stopping and timer1 will start");
    Timer3.pause();
    t1 = 0;
    Timer1.resume();
  }
}

void setup() {
  SerialUSB.begin(115200);
  delay(500);
  SerialUSB.println("START ----");
  Wio.Init();
  delay(500);

  Timer1.pause();
  Timer1.setOverflow(500 * 1000, MICROSEC_FORMAT); // = per 500 ms
  Timer1.refresh();
  Timer1.attachInterrupt(timer1_cb);

  Timer3.pause();
  Timer3.setOverflow(1000 * 1000, MICROSEC_FORMAT); // = per 1 sec
  Timer3.refresh();
  Timer3.attachInterrupt(timer3_cb);

  Timer1.resume();
}

void loop() {
}
```
動作の様子
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/7dc10ced-487b-66f1-6582-a7a9656c6dc3.gif)

## あとがき

結構いろいろ調べました。

### 5/9 更新

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">これも参考にどうぞ。<a href="https://t.co/0nvM930hfs">https://t.co/0nvM930hfs</a></p>&mdash; matsujirushi (@matsujirushi12) <a href="https://twitter.com/matsujirushi12/status/1259110843326722049?ref_src=twsrc%5Etfw">May 9, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ということで[ブログに更新情報](https://lab.seeed.co.jp/entry/2019/11/15/120000)が載っていました orz

EoT