Arduino Nano Everyのタイマー割り込み機能について

##概要
Arduino Nano Everyのタイマー割り込み機能に関する覚書き。
原理とかどうでもいいからタイマー機能を使いたい！という方には下記リンク先のライブラリが便利。
https://github.com/Kees-van-der-Oord/Arduino-Nano-Every-Timer-Controller-B
(いつかこれも翻訳して整理したい)

Arduino Nano EveryにはType A(TCA)とType B(TCB)の2種類がある。
今回はTCBのch2を使用する。

ATmega4809をArduino Nano Everyとして使用する際、各タイマーは下記の機能に使われている。
TCAには比較チャンネルが3つあるので複数のPWM信号を出力できる（らしい。比較チャンネルを使ったことがなので理解が追い付いていない・・・）。
D3とD6は独立したタイマーを使用しているのでPWM周波数を変更しても他の機能に影響しない。

| タイマー | 標準ライブラリ | PWM | その他の機能 |
|:-:|:-:|:-:|:-:|
| TCA |  | D2,D5,D9,D10 | TCB0～TCB3のクロック |
| TCB0 |   | D6 |   |
| TCB1 |   | D3 |   |
| TCB2 |   |   |   |
| TCB3 | delay() |   |   |

※調査中のため抜け漏れの可能性あり。

なぜTCB2が使われていないのかな？と思って調べていたが、TCB2の出力ピンは2ピンあるが1つはPCとのシリアル通信のTXD、もう1つは未配線でどこにも繋がっていなかった。なので今回のような割込み処理に使うために残してあるチャンネルなのかと勝手に考えている。


##実行環境
MCU：Arduino Nano Every (ATmega4809)
エディタ：Arduino IDE & VSCode



##参考サイト
https://qiita.com/ShunHattori/items/1085de3d283b68683f9b
https://avr.jp/



##参考資料
ATmega4809のデータシート
https://www.mouser.jp/datasheet/2/268/ATmega4808_09_DataSheet_DS40002173C-2237623.pdf
※avr.jpで日本語に翻訳されたものがダウンロード可。



##サンプルコード
D2を100ms毎にHIGH/LOW切り替えするコード。
オシロがなければD13に設定して内蔵LEDで確認するのもOK。

```c++
int a = 1;

void setup() {
  pinMode(2, OUTPUT);

  TCB2.CCMP = 25000;  // TOP値の設定
  TCB2.CTRLB = (TCB2_CTRLB & 0b10101000) + 0b00000000;  //タイマーのGPIO出力ON、クロックソースを設定
  TCB2.CTRLA = (TCB2_CTRLA & 0b11111000) + 0b00000101;  //カウント周期を設定してカウントスタート
  TCB2.INTCTRL = 1;  //割り込み許可
}

void loop() {

}

ISR(TCB2_INT_vect) {
  TCB2.INTFLAGS = 1;  //割り込みフラグのクリア
  if (a == 1) {
    digitalWrite(2, HIGH);
  } else {
    digitalWrite(2, LOW);
  }
  a = 1 - a;
}
```

動作確認結果
![タイマー割込み_動作確認.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/10d05ea8-aa37-07af-e316-332468cbb183.jpeg)

##初期設定


###初期化の手順
<details><summary>データシートのInitializationをGoogle翻訳</summary><div>

```
初期化
デフォルトでは、TCBは定期割り込みモードです。 使用を開始するには、次の手順に従ってください。
1. TOP値をCompare / Capture（TCBn.CCMP）レジスタに書き込みます。
2.オプション：コントロールB（TCBn.CTRLB）レジスタのCompare / Capture Output Enable（CCMPEN）ビットを「1」に書き込みます。 これにより、対応するピンで波形出力が使用可能になり、対応するPORT出力レジスタの値が上書きされます。
3.コントロールA（TCBn.CTRLA）レジスタのENABLEビットに「1」を書き込んでカウンタを有効にします。 カウンタは、コントロールA（TCBn.CTRLA）レジスタのクロック選択（CLKSEL）ビットフィールドのプリスケーラ設定に従って、クロックティックのカウントを開始します。
4.カウンタ値は、カウント（TCBn.CNT）レジスタから読み取ることができます。 ペリフェラルは、CNT値がTOPに達すると、CAPT割り込みとイベントを生成します。
4.1。 Compare / Captureレジスタが現在のCountレジスタよりも低い値に変更された場合、ペリフェラルはMAXにカウントされ、ラップアラウンドします。
```

</div></details>

<details><summary>TCBのレジスタ一覧</summary><div>
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/ee012a9f-26b3-7ccc-6ac1-d0bf4d1e8b0b.png)
</div><details>

今回の場合は、
1.　TCB2.CCMPにTOP値を設定する。(16bitなので最大値は65535)
2.　TCB2.CTRLAのCLKSELビットを設定してカウント周期を決める。（デフォルトではCLKTCAに設定されている）
3.　TCB2.CTRLBのCNTMODEビットに0を書き込んで定期割り込みモードに設定する。（デフォルトではPWM出力モードに設定されている）
4.　TCB2.CTRLAのENABLEビットに1を書き込んでタイマーを有効化・カウントを開始する。
5.　TCB2.INTCTRLのCAPTビットに1を書き込んで割込み処理を有効にする。
6.　ISR(TCB2_INT_vect)に割込み発生時の処理を書く。処理の中でTCB2.INTFLAGSのCAPTビットに1を書き込んで次の割込みが入るようにしておく。

※TOP値　→カウンタがTOP値になったら割り込み発生
※割り込み周期 = カウント周期 x TOP値（サンプルコード:　4us x 25000カウント = 100ms）
※TCB2.INTFLAGSをいつ処理するかは用途に応じて変える。個人的にはタイマーの目的を考えて最初に書くことが多い。

|  | メリット | デメリット |
|:-:|:-:|:-:|
| 処理の最初に実行 | 割込み処理が必ず<br>設定した周期で実行される | 割込み処理が終わっていなくても<br>時間が来たら次の処理が実行される<br>→バグ発生 |
| 処理の最後に実行 | 割込み処理が最後まで<br>確実に実行される | 次の割込みの時間になっても処理中なら<br>次の割込み処理は無視される<br>→割込み周期が不安定になる |


###CLKSELの設定値
| 設定値 | 設定名 | カウント周期 | 設定可能な割り込み頻度 |
|:-:|:-:|:-:|:-:|
| 00 | CLKDIV1 | 62.5ns | 62.5ns ~ 約4.1ms |
| 01 | CLKDIV2 | 125ns | 125ns ~ 約8.2ms |
| 10 | CLKTCA | 4us | 4us ~ 262.14ms |

※CLK_PERはデフォルト設定（62.5ns・16MHz)と仮定してカウント周期を計算。
※TCA0はデフォルト設定のDIV64(4us・250kHz)と仮定してカウント周期を計算。

CLKDIV1に設定して16回割り込みに入るとちょうど1usなので、これを利用すると使いやすいライブラリが作れそう。

<details><summary>PWM出力に設定してクロックの確認</summary><div>
PWMの設定については下記。
正確な周波数を測定するため、CCMPH = 127, CCMPL = 255 で出力してn=255の平均値と考える。

![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/39b14402-d7f7-1716-7f8d-a29f656c8656.png)

下写真のようにPC0の配線を引き出してオシロに接続
![DSC_0147.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/67e0a6f0-ee2c-5102-a781-64f250e5f441.jpeg)


CLKDIV1
PWM周期 = 16Mhz / 255カウント ≒ 62.745kHz
![タイマー割込み_動作確認2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/3fbe7d99-970f-580a-c75b-1ed6877e8806.jpeg)

CLKDIV2
PWM周期 = (16Mhz / 2) / 255カウント ≒ 31.373kHz
![タイマー割込み_動作確認.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/a294332b-681f-1388-0944-3af046bc7cc6.jpeg)

CLKTCA
PWM周期 = (16Mhz / 64) / 255カウント ≒ 980.392Hz
![タイマー割込み_動作確認.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/00288112-f485-ae25-1ed4-28c0affea00f.jpeg)
</div></details>



##CNTMODEの設定値
###設定一覧
| 設定値 | 設定名 | 機能の日本語訳 |
|:-:|:-:|:-:|
| 000 | INT | 定期割り込みモード |
| 001 | TIMEOUT | タイムアウトチェックモード |
| 010 | CAPT | インプットキャプチャ |
| 011 | FRQ | インプットキャプチャ・周波数測定モード |
| 100 | PW | インプットキャプチャ・パルス幅測定モード |
| 101 | FRQPW | インプットキャプチャ・周波数/パルス幅測定モード |
| 110 | SINGLE | シングルショットモード |
| 111 | PWM8 | 8bit PWMモード |



###定期割り込みモード
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/990b2446-37d1-cbde-4026-0b3cc9608bfb.png)
カウント=TOP値になった時に割り込み処理を実行するモード。
一定周期で何か処理したいときに使う。

###タイムアウトチェックモード
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/ac8cd99a-e754-b64b-f059-d471a16173db.png)
対応しているピンに別のICのデジタル信号を接続し、立上りを検出してカウントを開始、カウント=TOP値になった時に割り込み処理を実行するモード。
一定時間応答がなかった時に何か処理をしたいときに使う。

###インプットキャプチャ
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/fecc1977-876c-c95e-354e-cbdca8e77325.png)
対応しているピンに別のICのデジタル信号を接続し、立下りを検出したときに割り込み処理を実行するモード。
外部割込みと異なる点は、割込みが発生したときのCNT値がCCMPに格納される点。
カウントは割込み処理中も常に回っており、外部入力によるカウントリセットはしない。
カウントをリセットしたいときはCNTレジスタに0を書き込む。

###周波数測定モード
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/1d8e504b-374e-4aee-5b23-83e0c6ae51c8.png)
対応しているピンに別のICのデジタル信号を接続し、立上りを検出したときに割り込み処理を実行するモード。
インプットキャプチャと異なる点は、立上りで割込みが発生することと、割込みが発生するとCNTを0にリセットするところ。
CNTを0にする前にCNT値をCCMPに格納するので、立上りから次の立上りまでの経過時間が測定できる。

###パルス幅測定モード
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/2cd17f86-acf7-f70c-975d-03bde7ec4512.png)
対応しているピンに別のICのデジタル信号を接続し、立下りを検出したときに割り込み処理を実行するモード。
インプットキャプチャと異なる点は、立上りを検出したときにCNTを0にリセットするところ。
割り込み発生時にCNT値をCCMPに格納するので、立上りから立下りまで(信号がHIGHになっている時間)の経過時間が測定できる。

###周波数/パルス幅測定モード
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/30db0b2c-c77f-2d6d-0829-e55f71a0a446.png)
対応しているピンに別のICのデジタル信号を接続し、立上りを検出したときに割り込み処理を実行するモード。
インプットキャプチャと異なる点は、立上りで割込みが発生すること、割込みが発生するとカウントストップすること、カウントの自動リセットがあること。
カウントの自動リセットはCCMP値を他の変数等に代入した後の立上りで実行される。（CCMPを読むまでの立上りは無視される）
割り込み処理でCNT値とCCMP値を読むことでHIGH時間と周期が同時に測定できる。（DUTY測定に便利）

###シングルショットモード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/96b57f20-05d8-abc3-b9a3-e8c5b0ad35ee.png)
使ったことがないので説明は省略します・・・
何かしらのイベントが発生してから一定時間、対応したピンからHIGHを出力するためのモードと思われる。たぶん。

###8bit PWMモード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321914/184db388-4f4d-e651-091a-7caeec8f49e9.png)
対応したピンからPWM信号を出力するモード。このモードのみ、CCMPの上位8bitをCCMPH、CCMPの下位8bitをCCMPLと呼ぶ。
CCMPHはPWMのDUTYを、CCMPLはTOP値(=PWM周波数)を設定するために使う。
TOP値が8bitになるためカウントの最大値が255となり、設定できる周波数の選択幅が狭くなるので注意。
またCNT = TOP値となった時に割り込みが発生するので、同じチャンネルでPWM出力と割込み処理を同時に実行も可能。(割込み処理を重い内容にするとPWMに影響が出るかも)


##割り込み処理
AVRでは`ISR(割込みベクタ)` という関数を定義して割り込み処理を記述する。
ATmega4809でのTCB2の割込みベクタは`TCB2_INT_vect` の名前で定義されている。
実際にどのように書くかはサンプルコード参照。
他の割込み処理の名前を調べるときは、vscodeの機能を使って`TCB2_INT_vect` の定義にジャンプし、その周辺を探るとラク。