スピーカ
この章ではI/Oボードに搭載されているスピーカを使って音楽を演奏することを目標にする. Raspberry Pi には PWM (Pulse Width Modulator)という装置が搭載されており, 指定の周波数で繰り返しON(高電位)とOFF(低電位)が切り替わるような信号を出力できる. この信号をスピーカに接続することで,下図のように指定した周波数の音を鳴らすことができる.
PWM制御
デジタル回路の信号は,ある瞬間を見ると1 (ON) か0 (OFF) しかない. しかし,場合によっては0と1の中間を出力したいこともある. 例えばLEDを少し暗く点灯させたい場合である.LEDを少し暗く点灯させるには, 高速に0と1を切り替え,1の時間の割合で明さを調整する.このような制御方法を PWM (Pulse Width Modulation) 制御と言う.
PWM制御で出力する信号は次のパラメタで決まる.
- 1を出力する時間の割合い(デューティ比)
- どのような波形にするか(単純な0と1の周期的な繰り返しであればその周波数)
下図にデューティ比25%の信号の例を2つ示す. 図中の赤い線は時間方向に平均した出力値であり,デューティ比の値 0.25 に等しい.
デューティ比25%, 非周期的 | デューティ比25%, 周期的 |
---|---|
![]() |
![]() |
音を鳴らす
LEDの明さを制御する場合は,明るさはデューティ比によって決まり, 十分に高速に0と1が切り替われば波形は重要ではない. 一方,PWMをスピーカに接続するときは,出力の周波数で音程が決まるので,上図の右のような出力をする必要がある(cf. 後述のM/S転送モード).
このような周期の短かい波形を精確に作り出すのは,プログラムでGPIOを制御していては手間がかかる. しかも,ゲームの計算処理などの他の処理を並行に行っていれば,精確に波形を作り出すのは難しい. そこで,PWM (Pulse Width Modulator) というPWM制御を行う専用のハードウェアを使う. PWM制御を行うために0と1の高速な切り替えを自動的に行う装置がPWMである.
準備
ピン機能の選択
Raspberry Piの PWM は2チャンネルあり,2つの出力を独立に制御できる. 実験用のI/Oボードではチャンネル2の出力がスピーカに接続されている.
PWMのチャンネル2はGPIO #19と配線を共有しており, GPIO #19の用途にPWM1を指定することでPWMの出力を選択できる. GPIO #19の機能はデータシートの92ページと102ページで調べること.
なお,低い周波数なら,GPIO #19をoutputに設定し,PWM装置に頼らずにCPUでON/OFFを制御して音を鳴らすこともできる.
Tips LEDやスイッチも使えるようにGPFSELレジスタを設定しておくとよい. 特にLEDは制御が簡単なので,問題が起こったときにプログラムの状態を表示させて状況を調べるのに便利である.
クロックソースの設定
PWM は専用のクロック信号の供給を受けて,そのクロックを使って1を出力する時間や0を出力する時間を計る. Raspberry Piの起動直後は PWM にクロックが供給されていないので, 音を鳴らすためにはプログラムの最初で PWM に供給するクロックの設定をする必要がある. この設定はクロックマネージャという装置に対して行う.
この設定のための情報はなぜか公式ドキュメントには十分記載されていないが,ここでは下記のプログラム片をそのまま使うことにする. 下記のプログラムは,9.6MHzのクロックが供給されるようにクロックマネージャを設定する.ただし,
CM_BASE
は0x3f101000CM_PWMCTL
は0xa0CM_PWMDIV
は0xa4
である.また,このプログラムは r0 レジスタと r1 レジスタを使用する.
(クロックマネージャについてはデータシートの105ページ「6.3 General Purpose GPIO Clocks」に少し説明がある.CM_PWMCTL
,CM_PWMDIV
の説明はないが,これらのレジスタの値の意味は107ページからのCM_GPnCTL
,CM_GPnDIV
と同じと考えられる.)
@ Set PWM clock source
@ src = osc, divider = 2.0
ldr r0, =CM_BASE
ldr r1, =0x5a000021 @ src = osc, enable=false
str r1, [r0, #CM_PWMCTL]
1: @ wait for busy bit to be cleared
ldr r1, [r0, #CM_PWMCTL]
tst r1, #0x80
bne 1b
ldr r1, =(0x5a000000 | (2 << 12)) @ div = 2.0
str r1, [r0, #CM_PWMDIV]
ldr r1, =0x5a000211 @ src = osc, enable=true
str r1, [r0, #CM_PWMCTL]
PWMの動作モードの設定
PWM の制御方法はデータシートの9章(138ページ)に記載されている.
PWM を制御するためのレジスタのベースアドレス (PWM_BASE
) は0x3f20c000である.
Raspberry Piに搭載された PWM は高機能で,様々な用途に使えるよう様々な動作モードが用意されている. ここでは指定の周波数で音を鳴らすことに使うので,下で述べるM/S転送モードを使用する.
PWMの動作モードはCTLレジスタで設定する.チャンネル2に対する以下の2ビットのみ 1 とし,他のビットは 0 と設定すればよい.
- CTLレジスタのPWENnビットを1に設定することで,チャンネルnのPWMが動作状態になる.
- CTLレジスタのMSENnビットを1に設定することで,M/S転送モードで動作するようになる.
M/S転送モード
このモードでは,PWM装置は,指定された時間連続して1を出力した後,指定された時間(正確には1周期から1を出力する時間を除いた残り)連続して0を出力することを繰り返す. 1周期の長さ RNG (range) と1を出力する時間の長さ DAT (data) をそれぞれRNGnレジスタ,DATnレジスタ(nはPWMのチャネル番号)に書き込めば,下図のような波形が出力される.
出力波形の周波数(音の高さ)はRNGで決まる.DATとRNGの比がデューティ比となる(音色に多少影響があるが,音の高さには無関係).
RNG, DATに指定する値の単位は,前述の「クロックソースの設定」で設定されたクロック周期(960万分の1秒)である.例えばRNGに10000を指定すれば,出力波形の周期は960分の1秒になり,960Hzの音が出力される.
ラ〜♪
ラの音の周波数である440Hzでスピーカを鳴らしてみよう.前節の説明を参考に,PWMの出力波形の周波数が440HzになるようにRNGレジスタを設定すればよい. デューティ比は適当でよいが,2分の1にするのが簡単だろう.
音を止めたいときは,DATレジスタに0を書き込んでデューティ比を0にすればよい.
演習3.5-1
chap5 ディレクトリ中に ra.s
というファイルを作り,ラの音(440Hz)を鳴らし続けるプログラムを記述しなさい(適当な時間鳴らした後で音を止めるようにしてもよい).以下のひな形を基に,日本語で書いてある部分を埋めればよい.
ra.img
を作るMakefileとREADME.mdもpushすること.
.equ PWM_HZ, 9600 * 1000
.equ KEY_A4, PWM_HZ / 440 @ 440Hzの1周期分のクロック数
(その他必要な定数を定義する)
.section .init
.global _start
_start:
(GPIO #19 を含め,GPIOの用途を設定する)
(PWM のクロックソースを設定する)
(PWM の動作モードを設定する)
@ ラの音を鳴らす
ldr r0, =PWM_BASE
ldr r1, =KEY_A4
str r1, [r0, #PWM_RNG2]
lsr r1, r1, #1
str r1, [r0, #PWM_DAT2]
loop:
b loop
上のプログラムでは,RNGレジスタに設定した値を1ビット右シフトすることで1/2倍してDATレジスタに設定している. これによりデューティ比は1/2となる.
(なお,KEY_A4
は,88鍵ピアノにおける4オクターブ目のAの鍵 (key) という意味.
See: WikiPedia: Piano key frequencies)
演習3.5-2
次の音階と周波数の対応表を使って,440Hzのラの音から1オクターブ上のラの音まで1音ずつ順に鳴らすプログラムを作りなさい.
全ての音を鳴らし終わったら音を止めること.プログラムのファイル名は doremi.s
とし,doremi.img
を作るMakefileとREADME.mdもpushすること.
音階 | ラ | シ | ド | レ | ミ | ファ | ソ | ラ |
---|---|---|---|---|---|---|---|---|
周波数(Hz) | 440 | 494 | 523 | 587 | 659 | 698 | 784 | 880 |
演習3.5-3
演習3.4-3で作ったプログラム count100sec.s を jiho.s というファイル名でコピーし,
07,08,09,00秒丁度になるタイミングで「ポッ、ポッ、ポッ、ポーーー」という時報のような音を鳴らすプログラムを作りなさい.
演習3.4-3で00から99までを繰り返すようにした場合でも,この問題では00から09までを繰り返すように変更しなさい.
ただし,「ポッ」は440Hzで0.1秒間, 「ポーーー」は880Hzで0.75秒間鳴らすこと.
jiho.img
を作るMakefileとREADME.mdもpushすること.
演習3.5-4(演習3.5-4, 3.5-5のうち1問以上必須)
音楽を演奏するプログラムを作りなさい.演奏する曲は何でも構わない.プログラムのファイル名はmusic.s
とすること.music.img
を作るMakefileとREADME.mdもpushすること.
演習3.5-5(演習3.5-4, 3.5-5のうち1問以上必須)
PWM制御を行うことで,LEDがゆっくり明るくなったり暗くなったりすることを繰り返すプログラムを作りなさい(例えば2秒かけて明るさ最大になり2秒かけて完全に消灯するなど.周期は好きに決めてよい.消灯した後1秒程度消灯したままにした方が感じがよいかも知れない).プログラムのファイル名はfade.s
にすること.
fade.img
を作るMakefileとREADME.mdもpushすること.
LEDが接続されているGPIO #10はPWM装置に接続できないので,PWM制御をプログラムで行う必要がある. 例えば,5ミリ秒点灯・5ミリ秒消灯することを繰り返せば,高速なため点滅は目に見えず,最大の明るさの半分の明るさで点灯しているように見える.3ミリ秒点灯・7ミリ秒消灯するようにすれば,最大の明るさの0.3倍の明るさに見える.