要約
Arduino Uno R4(以下R4) のレジスタを操作してPWM出力を行う方法を記載します。
下記はMinimaでもWifiでも同様ですが、出力ピンに対する内部ピン(pinout)が2種で異なることに注意して下さい。
実施することは3点です。
- ピンの選定と設定
- GPT(General Purpose Timer)の初期設定
- 周期とdutyの設定
ピンの選定と設定
GPTのビット数の選定
GPTは32bitが2つ、16bitが6つ用意されています。32bitが優位であるものの、16bitで十分な用途ではそちらを選択するべきです。
どちらを使うべきかはPWMのdutyの分解能で決まります。
もしdutyを0.1%=1/1000刻みで変えたい場合、カウンタは1周期の中で1000カウントアップする必要があります。16bitは65536なので十分に余裕があり、実用的には16bitを使えば十分です。
また、分解能はPWM周波数とトレードオフです。Arduino Uno R4は48MHzで動作します。PWMの1周期で必要なカウント数をNとすると、1周期はどんなに短くてもN/48[us]必要です。この逆数の48/N[MHz]が最大PWM周波数なので、Nが1000であればPWM周波数の上限は48kHzです。Nが16bit最大値の65536では732Hzまで遅くなるため、用途によりますが32bitタイマは不適のことが多いでしょう。
なお、delay()やdelayMicroseconds()はAGTという別のタイマを利用するため、ここで考慮する必要はありません。
内部ピンの選定
16bitタイマはGPT162からGPT167という名前がついています。ここではGPT162を使うと仮定します。この時「Renesas RA4M1グループユーザーズマニュアル ハードウェア編」(以下マニュアル)を見ると、対応する出力機能がGTIOC2AおよびGTIOC2Bであること、そのうちGTIOC2AはP103およびP113,P500に割り当てられていることがわかります。
このうちR4 MinimaではP113はD4ピンに割り当てられていますのでこれを利用します。P113はマニュアルでm=1, n=13としてPmnPFSレジスタでピン設定が可能です。
const int timerPinM = 1;
const int timerPinN = 13;
R_PFS->PORT[timerPinM].PIN[timerPinN].PmnPFS_b.PDR = 1;
R_PFS->PORT[timerPinM].PIN[timerPinN].PmnPFS_b.PSEL = 0b00011;
R_PFS->PORT[timerPinM].PIN[timerPinN].PmnPFS_b.PMR = 1;
ピンはPDRでデジタル出力として設定します。Arduinoらしくpinmodeなどを使っても大丈夫です。その場合はPSELレジスタの設定を忘れずに実施してください。
GPTの初期設定
まず初期設定のためのレジスタを設定します。この部分はPWMの周波数やdutyと関係がありません。
R_MSTP->MSTPCRD_b.MSTPD6 = 0;
R_GPT2->GTCR_b.CST = 0;
R_GPT2->GTIOR_b.OAE = 1;
R_GPT2->GTIOR_b.GTIOA = 0b11001;
R_GPT2->GTBER_b.CCRA = 0b01;
MSTPD6は16bitGPTモジュールの省電力機能を解除し、モジュールに通電させる指令です。CSTは初期設定のためのモジュール停止を、0AEはGTIOC2Aを有効にする命令です。OBEはGTIOC2Bを有効にします(以下同様です)。GTIOAの設定は、「初期出力HIGH」「周期の終わり(リセット)でHIGH」「コンペアマッチ時LOW」を表します。詳細は次の周期設定で記述します。CCRAはバッファリングの設定で、dutyに関係するGTCCRレジスタをバッファリングします。これによりリセットタイミングとdutyタイミングの関係を切り離せます。
周期とduty
レジスタ値の計算
仮にPWMの希望する分解能0.1%とします。先ほど述べたように分解能の逆数が最大カウント数なので、次に述べるgptrCountを1000に設定します。また、希望する出力%を10倍した値を次に述べるgtccrCountに設定します。50%出力をしたい場合、1000カウントのうち500カウントはHIGH出力、500カウントはLOW出力をするということです。PWM周波数は元クロックが48MHzの場合48kHz、元クロックを分周した場合それに応じて周波数が低くなります。
逆に所望の周波数を得たい場合もあります。X[kHz]のPWM出力を得たい場合、48000/X カウントで1周期になるような設定とすればいいです。10kHzの信号が得たければ、gtccrCountは4800です。0.732kHzより遅い信号(1Hzとか)が欲しければ分周するか32bitのレジスタを使いましょう。
レジスタへの書き込み
最後に周期およびdutyに関わる設定を行い、出力を開始します。
R_GPT2->GTCNT = 0;
R_GPT2->GTPR = gptrCount - 1; // gptrCountはカウンタの周期
R_GPT2->GTCCR[0] = gtccrCount;
R_GPT2->GTCCR[2] = gtccrCount;
R_GPT2->GTCR_b.CST = 1;
GTCNTはカウンタの値を保持するレジスタで、最初に0に初期化します。GTPRはカウンタの最大値であり、この値を超えるタイミングが「周期の終わり」で、そのタイミングでGTPRは1にリセットされます。そのため、想定する周期よりもカウンタのリセットは1クロック遅く、逆に設定する値は1を減じる必要があります。
GTCCR[0]はこの値と一致した時(コンペアマッチ)に出力を変更する値で、GTIOAの設定により出力がLOWに変わります。GTCCR[2]に設定した値はカウンタリセットのタイミングでGTCCR[0]にコピーされるため、初期設定では同じ値を入れておく必要があります。
最後にCSTを1にすると出力が開始されます。
最後に
R4は12bitのD/Aを持つため従来よりPWMの重要度は低くなりました。ライブラリを介さずにPWMを使うことはあまりないかもしれませんが、何かに役に立てば幸いです。