ATtiny202のPWM機能を試す

# はじめに
本記事ではATtiny202のPWM機能について、自分なりに使い方をまとめてみました。非常に安価なAVRマイコンとして秋月電子通商より1個70円から購入できるATtiny202ですが、意外と日本語の資料は少ない印象なので少しでも参考になりましたら幸いです。
![picture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/04f2b21f-d8f9-8cdb-9b69-821d7691b594.jpeg)
# 開発環境
ATtinyシリーズはArduino開発環境が使えます。
* Windows 11 Home 22H2
* Arduino IDE 1.8.19
* megaTinyCore 2.6.10

:::note info
以下のURLをボード設定に追加し、インストールすることで導入できます。
http://drazzy.com/package_drazzy.com_index.json
:::
# ATtiny202
ピン配置は以下の通りです。
![pinout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/3eacd47f-88e6-52f0-4bb0-d2435d17470d.png)
また、各ピンの機能は以下の通りです。
![port_function.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/dff99ee6-6bc3-81a7-cee1-1a2695afaeeb.png)
ATtiny202は2個のタイマを内蔵しています。TCA0とTCB0です。TCA0のチャンネルはWO0、WO1、WO2、WO3の4個、TCB0のチャンネルはWO0の1個です。対応するピンはそれぞれPA7、PA1、PA2、PA3の4本、そして、PA6の1本となります。

:::note info
データシートより抜粋しました。
https://akizukidenshi.com/goodsaffix/attiny202-402.pdf
:::

# 書き込み
ATtiny202は新しい世代のAVRマイコンとなるため、書き込み方式としてはUPDIが採用されています。従来から採用されているICSPとは異なり単線式なので配線が楽です。また、下図のように簡単な回路を組むだけで汎用的なUSBシリアル変換モジュールが書き込み装置として利用できます。専用品は不要なので便利です。
![schematic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/49028bec-bd66-5815-57ed-c4249ad7cc81.png)
クロック周波数は20MHzと設定しています。ただし、設定したらブートローダの書き込みも併せて実行しておく必要があります。ATtinyシリーズはブートローダが不要なので実際に書き込まれる訳ではないものの、ヒューズビットが書き換えられるため、設定した内容を反映できます。また、書き込み装置は`Serial UPDI`を選択します。書き込み速度は`SLOW`を選んでおくと失敗しにくいです。
![screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/fb9e2ec4-1c2a-8cad-0acb-b5457e46ae81.png)
プログラムは動いている最中でも書き込み可能です。書き込みに成功したらプログラムがそのまま動き始めます。
# 回路構成
PA6に緑色LED、PA2に赤色LEDを接続しています。書き込み回路も配線してあります。
![breadboard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/c40cc7c6-7056-aff8-8665-d3d16fb8050a.png)
# Arduino言語の場合
関数`analogWrite`を使い、PWM信号を出力します。なお、関数`pinMode`は不要です。内部的にはTCA0を利用しているようなのでPA6からは出力できません。今回はPA2から出力しました。また、ピン番号は予め定義されているピン名で指定しました。赤色LEDがじんわり点滅します。
```cpp:pwm_standard.ino
void setup() {
}

void loop() {
  static unsigned int duty = 255;
  
  analogWrite(PIN_PA2, duty--); //change the duty cycle
  if (!duty) {
    duty = 255;
  }
  delay(10);
}
```
# TCA0のレジスタを叩く場合
まずはType-Aのタイマを使い、PWM信号を出力します。

:::note info
公式の資料がダウンロードできます。
https://www.microchip.com/en-us/application-notes/tb3217
:::

クロック周波数$f_{MAIN}$が20MHzの場合、ペリフェラルに供給されるクロック周波数$f_{PER}$はデフォルトだと6分周されるため、約3.33MHzとなります。タイマに供給されるクロック周波数はさらに$N$分周されます。また、$PER$はカウンタが生成する波形の高さに当たりPWM分解能、つまりはPWM周期を表します。最小値は2ビットの4、最大値は16ビットの65536です。ただし、実際には0から数えるため、1だけ小さい値を指定します。まとめるとPWM周波数$f_{PWM}$は次式より求まります。今回は分周比を2、PWM分解能を8ビットに指定したので約6.51kHzと求まりました。
```math
f_{PWM} = \frac{f_{PER}}{N(PER + 1)}
```
今回はカウンタの波形が鋸波となるモードを選択し、PWM信号を出力しています。三角波を生成するモードよりも高速なPWM信号を出力できる点が特徴です。
![tca.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/82aacd69-dc8e-1b96-fb9d-a787365c3bf0.png)
ここで、$n$はチャンネル番号を表します。今回はWO2を有効化し、対応するPA2から出力しました。デューティ比$D_{WOn}$は次式より求まります。閾値$CMPn$を調整することでデューティ比が変わります。なお、複数のチャンネルを有効化しても各チャンネルで個別にデューティ比は設定できます。
```math
D_{WOn} = \frac{CMPn}{PER + 1}
```
先述のプログラムと同様に赤色LEDがじんわり点滅します。
```cpp:pwm_tca.ino
void setup() {
  pinMode(PIN_PA2, OUTPUT);
//  PORTMUX.CTRLC = PORTMUX_TCA00_DEFAULT_gc; //turn off the port multiplexer, assigning WO0 to PA3
//  PORTMUX.CTRLC = PORTMUX_TCA00_ALTERNATE_gc; //turn on the port multiplexer, assigning WO0 to PA7
  
  takeOverTCA0(); //disable and reset the timer
  TCA0.SINGLE.CTRLA = TCA_SINGLE_ENABLE_bm | TCA_SINGLE_CLKSEL_DIV2_gc; //start the timer, set the prescaler to 2
  TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP2EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc; //enable WO2, select the single slope PWM mode
  TCA0.SINGLE.PER = 255; //set the PWM resolution to 8 bits
  TCA0.SINGLE.CMP2 = 0; //set the duty cycle
}

void loop() {
  static unsigned int duty = 255;
  
  TCA0.SINGLE.CMP2 = duty--; //change the duty cycle
  if (!duty) {
    duty = 255;
  }
  delay(10);
}
```
補足ですが、WO0はデフォルトだとPA7に出力されるようです。PA3から出力したい場合は上記のコメントアウトを解除し、ピン配置を再設定する必要があります。ただし、同時には出力できません。関数`analogWrite`なら特に設定する手間もなく同時に出力できます。
# TCB0のレジスタを叩く場合
次にType-Bのタイマを使い、PWM信号を出力します。

:::note info
公式の資料がダウンロードできます。
https://www.microchip.com/en-us/application-notes/tb3214
:::

PWM周波数$f_{PWM}$は次式より求まります。先述の式と似ていますが、レジスタ名は異なります。$CCMP$の上位8ビットを$CCMPH$、下位8ビットを$CCMPL$と別々に指定することが可能です。$CCMPL$はPWM分解能を表します。今回は分周比を1、PWM分解能は最大8ビットなので255を指定し、約13.02kHzと求まりました。
```math
f_{PWM} = \frac{f_{PER}}{N(CCMPL + 1)}
```
PWM分解能を下げるとPWM周波数は上がります。また、カウンタの波形は鋸波です。
![tcb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/daf93437-315f-06d2-4cfe-fe515d7cfc56.png)
TCB0のチャンネルはWO0のみです。つまり、対応するPA6からしか出力できません。デューティ比$D_{WOn}$は次式より求まります。閾値$CCMPH$を調整することでデューティ比が変わります。
```math
D_{WOn} = \frac{CCMPH}{CCMPL + 1}
```
先述のプログラムとは異なり緑色LEDがじんわり点滅します。
```cpp:pwm_tcb.ino
void setup() {
  pinMode(PIN_PA6, OUTPUT);
  
  TCB0.CTRLA = TCB_ENABLE_bm | TCB_CLKSEL_CLKDIV1_gc; //start the timer, set the prescaler to 1
  TCB0.CTRLB = TCB_CCMPEN_bm | TCB_CNTMODE_PWM8_gc; //enable WO0, select the 8 bits PWM mode
//  TCB0.CCMP = 0x00FF;
  TCB0.CCMPL = 255; //set the PWM resolution to 8 bits
  TCB0.CCMPH = 0; //set the duty cycle
}

void loop() {
  static unsigned int duty = 255;
  
  TCB0.CCMPH = duty--; //change the duty cycle
  if (!duty) {
    duty = 255;
  }
  delay(10);
}
```
# おわりに
ATtiny202はプログラムメモリのサイズが2kBと控え目なので用途は限定されそうですが、Arduino開発環境が使えて機能もそこそこ豊富なので重宝しそうです。