Beaglebone BlackのDMTimerでTimer出力を行う

前回DMTimerにおけるTIMER4~TIMER7がPWMに使えると書いた。
今回BBBで実際にDMTimerが使えるか確認してみる。
尚、前回TIMER4~TIMER7の基準クロックが25MHzであると書いたが24MHzが正解であるようだ。

TIMER4~TIMER7は少なくともKernel3.8ベースではドライバとしてサポートされていないので簡単には使うことができない。
何らかの方法でDMTIMERのレジスタを叩く必要がある。
真っ当な方法としてはドライバを書けば良いが、単にPWM出力やtimer出力を行いたいだけであるならばレジスタ設定だけであるのでdevmem2で直接叩くこととする。
尚、devmem2のインストール方法に関しては過去の記事を参照のこと。
devmem2とprudebugをインストール

今回はTimer出力を試みる。
余力があれば次回以降PWM出力に関しても試行を行いたいと思うが、現時点PWMを使う予定が無いので予定は未定である。

AM335Xのレジスタ詳細に関しては以下のドキュメント参照。
AM335x Sitara(TM) Processors Technical Reference Manual

AM335Xには、一つのDMTimerR 1msと七つのDMTimer、計八つの汎用タイマがある。
DMTimerにはTIMER0とTIMER2~TIMER7が含まれる。
DMTimer 1msとTIMER0はそれぞれ個別の構成を持つが、TIMER2~TIMER7はほぼ同一の構成を持つ。
この中でTIMER4~TIMER7はそれぞれ一つの外部入出力ポートTIMx_OUTを持ち、タイマ出力もしくは外部入力信号のクロック数のキャプチャを行うことができる。
また、タイマ出力においてPWM出力を行うこともできる。
BBBにおいてもピンヘッダにはTIM4_OUT~TIM7_OUTが出ているのでPWMモード機能を使用することができる。

AM335XにはeCAP moduleがあり同じくクロック数のキャプチャとPWM出力ができるので機能的に被っているように見える。
キャプチャモードでもPWMモードでもeCAP moduleの方が多機能で使い易いのはeCAPの方が設計が新しい為である模様。

先ずはdevice treeの設定。
今回は以下のように使う。

TIMER4: P8.7
TIMER5: P8.9
TIMER6: P8.10
TIMER7: P8.8

BBBのdevice treeにはtimer4~timer7が予約語にあるようだが実際にどのように使うかが判らなかった。
しかしDMTimerの設定に必要なレジスタはデフォルトで全て設定可能であったりする。
device treeのoverrayではpinmuxの設定だけを行い、DMTimerの設定は全てdevmem2で行う。
dtsの例を示す。

/dts-v1/;
/plugin/;
 
/ {
    compatible = "ti,beaglebone", "ti,beaglebone-black";
 
    /* identification */
    part-number = "bone-JUNK-TIMER";
    version = "00A0";
 
    exclusive-use =
        /* pin header uses */
        "P8.7",  /* timer4_P8_7:   [Bot R7 ] timer4_mux3 */
        "P8.8",  /* timer7_P8_8:   [Bot T7 ] timer7_mux3 */
        "P8.10", /* timer6_P8_10:  [Bot U6 ] timer6_mux3 */
        "P8.9",  /* timer5_P8_9:   [Bot T6 ] timer5_mux3 */
 
        /* the hardware IP uses */
        "timer4",
        "timer5",
        "timer6",
        "timer7";
 
    fragment@0 {
        target = <&am33xx_pinmux>;
        __overlay__ {
            timer_gpio: pinmux_timer_pins {
                pinctrl-single,pins = <
                    0x090 0x0a  /* [Bot R7 ] (IDIS|OFF|MODE2)=(0 01 010) timer4_mux3 P8_7 */
                    0x094 0x0a  /* [Bot T7 ] (IDIS|OFF|MODE2)=(0 01 010) timer7_mux3 P8_8 */
                    0x098 0x0a  /* [Bot U6 ] (IDIS|OFF|MODE2)=(0 01 010) timer6_mux3 P8_10 */
                    0x09C 0x0a  /* [Bot T6 ] (IDIS|OFF|MODE2)=(0 01 010) timer5_mux3 P8_9 */
                >;
            };
        };
    };
 
    fragment@1 {
        target = <&ocp>;
        __overlay__ {
            test_helper: helper {
                compatible = "bone-pinmux-helper";
                pinctrl-names = "default";
                pinctrl-0 = <&timer_gpio>;
                status = "okay";
            };
        };
    };
};

このdtsファイルをbone-JUNK-TIMER-00A0.dtsという名前で/lib/firmwareに保存して以下のようにdtcでコンパイルする。

debian@beaglebone:~$ sudo -s
root@beaglebone:/home/debian# cd /lib/firmware/
root@beaglebone:/lib/firmware# dtc -@ -O dtb -o bone-JUNK-TIMER-00A0.dtbo bone-JUNK-TIMER-00A0.dts
root@beaglebone:/lib/firmware#

できたbone-JUNK-TIMER-00A0.dtboをbone_capemgrに与える。

root@beaglebone:/home/debian# export SLOTS=/sys/devices/bone_capemgr.*/slots
root@beaglebone:/home/debian# echo bone-JUNK-TIMER > $SLOTS
root@beaglebone:/home/debian# cat $SLOTS
0: 54:PF---
1: 55:PF---
2: 56:PF---
3: 57:PF---
4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
5: ff:P-O-- Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI
6: ff:P-O-- Bone-Black-HDMIN,00A0,Texas Instrument,BB-BONELT-HDMIN
7: ff:P-O-L Override Board Name,00A0,Override Manuf,bone-JUNK-TIMER
root@beaglebone:/home/debian#

bone-JUNK-TIMERがLoadされていることを確認。
引き続きpinmuxの設定を確認する。

root@beaglebone:/home/debian# export PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins
root@beaglebone:/home/debian# cat $PINS | grep 890
pin 36 (44e10890) 0000000a pinctrl-single
root@beaglebone:/home/debian# cat $PINS | grep 894
pin 37 (44e10894) 0000000a pinctrl-single
root@beaglebone:/home/debian# cat $PINS | grep 898
pin 38 (44e10898) 0000000a pinctrl-single
root@beaglebone:/home/debian# cat $PINS | grep 89c
pin 39 (44e1089c) 0000000a pinctrl-single

意図したpinmux設定となっていることが確認できた。

DMTIMERを使えるようにするためのレジスタはCM_DPLL REGISTERSにあるCM_PER_TIMERx_CLKCTRL RegisterでMODULEMODEをENABLEにする必要がある。
実際に設定してみる。

root@beaglebone:/lib/firmware# devmem2 0x44E00088 w 0x2
/dev/mem opened.
Memory mapped at address 0xb6fb6000.
Value at address 0x44E00088 (0xb6fb6088): 0x30000
Written 0x2; readback 0x2
root@beaglebone:/lib/firmware# devmem2 0x44E000ec w 0x2
/dev/mem opened.
Memory mapped at address 0xb6f7e000.
Value at address 0x44E000EC (0xb6f7e0ec): 0x30000
Written 0x2; readback 0x2
root@beaglebone:/lib/firmware# devmem2 0x44E000f0 w 0x2
/dev/mem opened.
Memory mapped at address 0xb6f35000.
Value at address 0x44E000F0 (0xb6f350f0): 0x30000
Written 0x2; readback 0x2
root@beaglebone:/lib/firmware# devmem2 0x44E0007c w 0x2
/dev/mem opened.
Memory mapped at address 0xb6fa5000.
Value at address 0x44E0007C (0xb6fa507c): 0x30000
Written 0x2; readback 0x10002
root@beaglebone:/lib/firmware# devmem2 0x44E0007c
/dev/mem opened.
Memory mapped at address 0xb6fe9000.
Value at address 0x44E0007C (0xb6fe907c): 0x2

readbackで0x2になっていれば問題ない。
readbackで0x10002になる場合もあるが過渡状態で再度読み出すと0x2になっていることが確認できる。

TIMER4~TIMER7は32bit長のタイマTCRRを持ち、基準クロックにより設定値からオーバーフローするまでカウントアップされる。
TCRRの基準クロックはPRCM(Power, Reset, and Clock Management)によりCLK_M_OSC、CLK_32KHz、TCLKINの三つの中から選択可能。
CLK_M_OSCはBBB Rev.Cの場合24MHzとなる。
各DMTimerのPRCMはCM_DPLL REGISTERSのCLKSEL_TIMERx_CLKにあるCLKSELで選択する。
デフォルトで”CLK_M_OSC clock”となっているので特に操作する必要はない。
“CLK_32KHZ clock”もしくは”TCLKIN clock”を選択したい場合はここを変更する。
一応明示的に”CLK_M_OSC clock”を設定しておく。

root@beaglebone:/lib/firmware# devmem2 0x44E00510 w 0x1
/dev/mem opened.
Memory mapped at address 0xb6ff3000.
Value at address 0x44E00510 (0xb6ff3510): 0x1
Written 0x1; readback 0x1
root@beaglebone:/lib/firmware# devmem2 0x44E00518 w 0x1
/dev/mem opened.
Memory mapped at address 0xb6f2a000.
Value at address 0x44E00518 (0xb6f2a518): 0x1
Written 0x1; readback 0x1
root@beaglebone:/lib/firmware# devmem2 0x44E0051c w 0x1
/dev/mem opened.
Memory mapped at address 0xb6fcd000.
Value at address 0x44E0051C (0xb6fcd51c): 0x1
Written 0x1; readback 0x1
root@beaglebone:/lib/firmware# devmem2 0x44E00504 w 0x1
/dev/mem opened.
Memory mapped at address 0xb6ff7000.
Value at address 0x44E00504 (0xb6ff7504): 0x1
Written 0x1; readback 0x1
root@beaglebone:/lib/firmware#

前準備としてはここまでで、後はDMTimerのレジスタを操作することにより任意の動作を行う事ができる。
当然の事ながら先に例示したdtsはTIMER4~TIMER7全てを出力設定にしているので、Capture modeで使う場合はdtsを書き換える必要がある。

DMTimerには多くのレジスタがあるが、PWM出力を行うだけであれば操作するレジスタは以下。

TCLR(Timer Control Register)
TCRR(Timer Counter Register)
TLDR(Timer Load Register)
TTGR(Timer Trigger Register)
TMAR(Timer Match Register)

タイマ出力だけを行う場合は上記のうちTMARの操作は不要。

TCRRは32ビット長のタイマカウンタで初期値から0xffffffffまでカウントアップする。
TCRRへの初期値の設定は幾つかの方法がある。
一つめはTCRRに直接値を書き込む方法。
二つめはTLDRに値を設定してある状態で、TCLRのSTビットによりTCRRを停止させることによりTLDRの値をTCRRに自動コピーさせる方法。
三つめはTLDRに値を設定してある状態で、TTGRに任意の値を書き込むことによりTLDRの値をTCRRに自動コピーさせる方法。

いずれの方法でもTLDRに値を設定してある状態で、TCLR ARビットをセットしておき、TCRRをオーバーフローさせることでTLDRの値をTCRRに自動コピーされる。
これによりTCRRはTLDRに設定された値から0xffffffffまでをカウントするフリーランカウンタとなり、オーバーフローにより割り込みフラグIRQSTATUS RegisterのOVF_IT_FLAGを発生させることができる。

今回は簡便の為、TCRRを止めた状態でTCRRとTLDRに同じ初期値を書き込む方法とする。
従ってTTGRも今回使用しない。

タイマ出力として用いる場合の設定の一例を示す。

・(もし動作していれば)TCLRのSTをStopとし、TCRRを止める。
・TLDRに所望周波数の二倍になるように初期値を与える。
・TCRRにTLDRに設定した値と同じ初期値を与える。
・TCLRを以下の設定にし、TCRRを開始する。
 ・GPO_CFG: output
 ・PT: Toggle
 ・TRG: Trigger on overflow
 ・CE: Compare mode is disabled
 ・PRE: Disabel(もしくは所望周波数に合わせる)
 ・PTV: 未使用(もしくは所望周波数に合わせる)
 ・AR: Auto-reload timer
 ・ST: Start timer

TCRRの開始以降出力周波数を変えたければTCRRが起動しているままTLDRの値を書き換えてよい。

この設定ではTCRRがoverflowするたびにTIMx_OUTが反転するので、overflow周期はTIMx_OUTの出力波形周期の半波長となる。
従ってクロック周波数Fs=24MHzであるとき、出力波形周波数Ftは、
$$Ft=\frac{Fs/2}{0x100000000-TLDR}=\frac{12000000}{0x100000000-TLDR}$$
となる。
逆にTLDRの設定値は、所望出力周波数Fdより
$$TLDR=0x100000000-Round\left(\frac{Fs/2}{Fd}\right)=0x100000000-Round\left(\frac{12000000}{Fd}\right)$$
で求めることができる。
ここでRoundは整数へのまるめ関数。
ただしTLDRの範囲は$$0xfffffffe\geq TLDR\geq0x00000000$$である必要がある。

実際に設定してみる。
TIMER4に440.00Hz相当(A)を出力

devmem2 0x48044038 w 0x0
devmem2 0x48044040 w 0xFFFF9577
devmem2 0x4804403c w 0xFFFF9577
devmem2 0x48044038 w 0x00001403

TIMER5に554.36Hz相当(C#)を出力

devmem2 0x48046038 w 0x0
devmem2 0x48046040 w 0xFFFFAB71
devmem2 0x4804603c w 0xFFFFAB71
devmem2 0x48046038 w 0x00001403

TIMER6に659.25Hz相当(E)を出力

devmem2 0x48048038 w 0x0
devmem2 0x48048040 w 0xFFFFB8E5
devmem2 0x4804803c w 0xFFFFB8E5
devmem2 0x48048038 w 0x00001403

TIMER7に880.00Hz相当(Aのオクターブ)を出力

devmem2 0x4804a038 w 0x0
devmem2 0x4804a040 w 0xFFFFCABC
devmem2 0x4804a03c w 0xFFFFCABC
devmem2 0x4804a038 w 0x00001403

周波数カウンタで計測する限り概ね所望周波数が出ているようだ。
この方式でのタイマ出力はデューティ比50%の矩形波出力となる。
出力波形周波数は基準クロックの整数分の一であり、出力可能最高周波数は基準クロックの四分の一である。
いわゆるビープ音原としての用途に使うことができる。
他にも色々応用がきくので重宝するだろう。


タイトルとURLをコピーしました