beagleboard を触ろう - LED とボタン [組み込みソフト]
beagleboard 上の LED を点灯させてみたいと思います。
beagleboard には、ユーザーが制御できる LED が 3 つあります。
うち 2 つは GPIO によって制御でき、残り 1 つは電源モジュール TPS65950 によって制御できます。
TPS65950 は、OMAP3530 の外部にあるデバイスで、OMAP3530 とは I2C バスによって繋がっています。
TPS65950 制御の LED は、OMAP3530 からは、I2C モジュールを制御することによって点灯させます。
まずは GPIO 制御の LED の方から。
ボードを見ると、2 つの LED の近傍に USR0, USR1 と書いてあります。
USR0/USR1 LED は、GPIO 149 と GPIO 150 によって制御します。
USR0 LED は、OMAP3530 の外部ピン W8 に、USR1 LED は、同じく AA9 に接続されています。
W8 は、内部的には、UART1 CTS か、GPIO 150 か、HSUSB3 TLL CLK かのいずれかに割り当てることができます。
また、AA9 は、内部的には、UART1 RTS か、GPIO 149 か、どちらかに割り当てることができます。
この選択は、S/W で行います。
xloader による設定では、W8 も AA9 も、GPIO に割り当てられます。
971: MUX_VAL(CP(UART1_RTS), (IDIS | PTD | DIS | M4)) /*GPIO_149*/
972: MUX_VAL(CP(UART1_CTS), (IDIS | PTD | DIS | M4)) /*GPIO_150*/
GPIO の制御は簡単です。
1. 入力か、出力かを決める
2. 読み/書きする
だけです。
入力か出力かは、GPIO_OE レジスタによって決定します。
GPIO にディップスイッチなどが繋がっている場合は入力を、LED などが繋がっている場合は出力にします。
読み/書きは、GPIO_DATAIN / GPIO_DATAOUT を読み書きします。
書き込みの場合は、GPIO_SETDATAOUT, GPIO_CLEARDATAOUT の組を使う方法もあります。
GPIO_SETDATAOUT に 1 を書けば 1 が立ち、GPIO_CLEARDATAOUT に 1 を書けば、1 が落ちて 0 になります。
書き込んだ値は保持されるので、一度 1 にすれば LED が点灯し続けます。
消灯させるには、1 を落として 0 にします。
GPIO_OE, GPIO_DATAIN, GPIO_DATAOUT, GPIO_SETDATAOUT, GPIO_CLEARDATAOUT といったレジスタは、いずれも 32 ビットのレジスタ x 6 本で構成されています。
各ビットが、1 つの GPIO に対応します。
つまり、GPIO は、32 x 6 = 192 個存在します。
LED の点灯では、このうち 149 番と 150 番を使います。
TPS65950 制御の LED の方は、近傍に PMU STAT と印字されています。
PMU STAT LED を制御するには、TPS65950 での LED 制御方法と I2C の制御方法の両方を知る必要があります。
TPS65950 TRM の中に数ページほど、LED の制御方法が解説されています。
制御レジスタは、LEDEN という名前で、こんな↓です。
beagleboard に搭載されている PMU STAT LED は、TRM でいうところの LEDB です。
なので、LEDEN レジスタの LEDBON ビット(ビット 1)を立ててやればよいということになります。
(実際に動かしてみると、LEDBON ビットのみならず、LEDBPWM ビットも立てないと光らないです。なぜかなあ?)
LEDEN レジスタは、OMAP3530 からは直接アクセスできません。
I2C バス上にコマンドを流すことによって、間接的にアクセスします。
I2C は、下の図のように、「スレーブのアドレス」 + 「データ」の組でやり取りを行います。
データをマスターから送るか、スレーブから送るかは、スレーブアドレスの後の 1 ビット (R/W) が 0 か 1 かで決まります。
下の図では、上の方がマスターからスレーブにデータ送信する場合、下の方がマスターがスレーブからデータ受信する場合を表しています。
I2C のプロトコルに関する説明は、検索すると、よいものがたくさんあります。
"I2C 仕様" とかで検索してみてください。
TPS65950 は I2C バス上でスレーブになります。
スレーブアドレスは、0x48, 0x49, 0x4a, 0x4b の 4 つを持っており、スレーブアドレスごとに担当する機能が分けられているみたいです。
LED を制御する機能は、スレーブアドレス 0x4A なので、0x4A に続いてデータを送信します。
TPS65950 の LEDEN レジスタに LEDBON ビット(と LEDBPWM ビット)を立てるにはどうすればよいでしょうか。
TPS65950 TRM を見てみると、LEDEN の Physical address は 0x0000_00EE とあります。
0x0000_00EE に、LEDBON ビット + LEDBPWM ビット = 0x22 を書き込めばよいのです。
こんな感じで、アドレス、レジスタアドレス、書き込みデータ、という順に送信します。
TPS65950 はこのデータを受け取ると、アドレス 0xEE にデータ 0x22 を書き込む命令と解釈し、内部で書込みを実行します。
レジスタ値を読み出す場合は、スレーブアドレス、レジスタアドレスを送信した後、再度スレーブアドレスを送信すると、TPS65950 は、指定されたレジスタの値を返してきます。
I2C 上で送るデータをどのように解釈するかは、スレーブデバイス次第だと思いますが、シリアル EEPROM なんかだと、TPS65950 と全く同じようなデータフォーマットでやり取りを行います。
即ち、書き込みの場合は、スレーブアドレス、書き込みアドレス、書き込みデータという順に送信し、読み込みの場合は、スレーブアドレス、読み出しアドレス、スレーブアドレスという順に送信し、その後、読み出しデータを受信します。
TPS65950 制御の PMU STAT LED は、PWM によって輝度調整が可能です。
GPIO 制御の USR0/USR1 LED は、オン、オフの 2 通りしかありませんが、こちらは暗めに点けたりとか、だんだん明るくしたりとか、だんだん暗くしたりとかの制御ができます。
PWM をどのように制御するかは、TPS65950 TRM に載っています。
クロック周波数 32KHz の 128 クロック分が 1 サイクルになっています。
1 サイクルの中で、どのタイミングでオンして、どのタイミングでオフするかがプログラム可能になっています。
上の図でいうと、赤線部分がオンで、青線部分がオフです。
オンの時間が長ければ明るく、オフの時間が長ければ暗くなります。
例えば、クロック 0 でオンして、クロック 127 でオフするようにプログラムすると、128 クロック中、127 クロックの間オンということになるので、最大輝度の 127/128 の明るさで点灯することになります。
クロック 126 でオンして、クロック 127 でオフすると、1/128 の明るさで点灯します。
徐々に明るくしたり、暗くしたりするには、タイマーを使えばいいですね。
10 msec ごとに 1/128 ずつ明るくしていけば、1.28 秒で最小輝度から最大輝度まで変化します。
それでは、簡単なテストプログラムです。
GPIO にしろ、I2C にしろ、xloader がドライバ関数を用意してくれているので、LED を点けるのは簡単です。
231: static unsigned char ledon;
232: static unsigned char ledoff;
233:
234: static void led_timer_handler(int irq)
235: {
236: static int inc = -1;
237:
238: clear_timer_interrupt(irq);
239:
240: ledon += inc;
241:
242: if (ledon == 126)
243: inc = -1;
244: else if (ledon == 0)
245: inc = 1;
246:
247: i2c_write(0x4A, 0xF1, 1, &ledon, 1);
248: }
249:
250: static int led_test(void)
251: {
252: unsigned char cmd;
253:
254: printf("[%s]\n", __FUNCTION__);
255:
256: omap_set_gpio_direction(149, 0);
257: omap_set_gpio_direction(150, 0);
258:
259: serial_ch = 0;
260: intc_enable_irq(UART3_IRQ);
261:
262: cmd = 0x22;
263: ledon = 126;
264: ledoff = 127;
265: i2c_write(0x4A, 0xEE, 1, &cmd, 1);
266: i2c_write(0x4A, 0xF1, 1, &ledon, 1);
267: i2c_write(0x4A, 0xF2, 1, &ledoff, 1);
268:
269: start_timer(1, 10000, led_timer_handler);
270:
271: while (serial_ch != 'q') {
272: omap_set_gpio_dataout(149, 1);
273: omap_set_gpio_dataout(150, 0);
274:
275: wait_us_interrupt(2, 1260000);
276:
277: omap_set_gpio_dataout(149, 0);
278: omap_set_gpio_dataout(150, 1);
279:
280: wait_us_interrupt(2, 1260000);
281: }
282:
283: omap_set_gpio_dataout(149, 0);
284: omap_set_gpio_dataout(150, 0);
285:
286: ledon = 127;
287: ledoff = 0;
288: i2c_write(0x4A, 0xF1, 1, &ledon, 1);
289: i2c_write(0x4A, 0xF2, 1, &ledoff, 1);
290:
291: stop_timer(1);
292: intc_disable_irq(UART3_IRQ);
293:
294: return 0;
295: }
256, 257 行目で GPIO 149, 150 の入出力方向を「出力」に設定しています。
262 行目から 267 行目までは PMU STAT LED の初期設定です。
PWM を使って、輝度 1/128 の明るさで PMU STAT LED を点けています。
PMU STAT LED は、10 ms タイマーを使って、led_timer_handler() により、輝度を 1/128 ずつ変えています。
USR0/USR1 LED は、271 行目から 281 行目の処理により、126 ms 毎に点滅させています。
PMU STAT LED が輝度最小から最大(またはその逆)に変化するまで 126 ms かかるので、USR0, USR1, PMU STAT は同期して点灯します。
こんな感じです。
ダウンロードは🎥こちら
手前の交互に点滅しているのが USR0, USR1, 奥の徐々に明るさが変わっているのが PMU STAT です。
左側の常時点灯しているのは、電源が入ると自動的に光る PWR LED です。
PMU STAT の明暗の変化が、なんか変なような気もしますが・・・。
一番暗くなった後に、急に明るくなる感じ (^^;
・・・
出力タイプの GPIO を使ったので、ついでに入力タイプの方も見てみたいと思います。
beagleboard 上には USER ボタンが付いていますが、これが押されたかどうか、GPIO 7 により取得することができます。
入力方向の場合は、割り込みも使えます。
押された/離されたというイベントが発生した時に、割り込みを上げることができます。
USER ボタンは OMAP3530 の AE21 ピンに繋がっています。
AE21 ピンは、内部的には、sys_boot5 か、mmc2_dir_dat3 か、GPIO 7 のいずれかに割り当てることができますが、xloader による設定では、GPIO に割り当てられています。
制御方法は、割り込みを使う場合、
1. GPIO_OE レジスタによって、「入力」方向に設定する
2. 立ち上がり、立ち下がり、高値水平、低値水平(日本語変?)のうち、どのイベントで割り込みを上げるかを決定する
3. GPIO_IRQENABLE1 レジスタによって、割り込みを有効にする
4. 割り込みが上がってきたら、割り込みハンドラーによって GPIO_DATAIN レジスタを読む
といった感じです。
2 についてですが、ボタンが押された時、離された時を知りたい時は、立ち上がり、および立ち下がりで割り込みを上げるように設定します。
仮に、高値水平、低値水平の両方で割り込みを上げるようにすると、ボタンが押された / 離された瞬間以外は常に割り込みが上がる状態になってしまい、あまり使えません (^^;
高値水平、低値水平はどういうデバイスで使うのかな・・・。
ボタン、ディップスイッチでは使わなさそうですね。
入力は、GPIO_CTRL によって定まるクロック x 2 以上の間、サンプリングされます。
立ち上がり / 立ち下がりで割り込みを上げる設定にしている場合、この時間内に立ち上がって立ち下がって元の状態に戻ったとすると、立ち上がり / 立ち下がり割り込みは発生しません。
即ち、H/W によってチャタリングが除去されるわけですね。
実際にテストプログラムを動かしてみても、チャタリング除去が効いていることが分かります。
テストプログラムは、こんな感じです。
296: static void button_handler(int irq)
297: {
298: if (omap_get_gpio_irqstatus(7) == 0) {
299: serial_printf("other event 0x%x\n", omap_get_gpio_irqstatus(7));
300: return;
301: }
302:
303: if (omap_get_gpio_datain(7))
304: serial_printf("button pressed\n");
305: else
306: serial_printf("button released\n");
307:
308: omap_clear_gpio_irqstatus(7);
309: }
310:
311: static int button_test(void)
312: {
313: serial_ch = 0;
314: intc_enable_irq(UART3_IRQ);
315:
316: omap_set_gpio_direction(7, 1);
317: omap_set_gpio_risingdetect(7, 1);
318: omap_set_gpio_fallingdetect(7, 1);
319:
320: register_handler(button_handler, GPIO_IRQ(7));
321: omap_clear_gpio_irqstatus(7);
322: omap_enable_gpio_irq(7);
323: intc_enable_irq(GPIO_IRQ(7));
324:
325: while (serial_ch != 'q') {
326: ;
327: }
328:
329: intc_disable_irq(UART3_IRQ);
330:
331: intc_disable_irq(GPIO_IRQ(7));
332: omap_disable_gpio_irq(7);
333: omap_clear_gpio_irqstatus(7);
334:
335: return 0;
336: }
316 行目で、GPIO 7 の入出力方向を、「入力」に設定しています。
317 行目、318 行目で、立ち上がり、立ち下がりで割り込みが発生するように設定しています。
320 行目で、296 行目の button_handler を割り込みハンドラーとして登録しています。
マクロ GPIO_IRQ(7) は、29 に展開されます。
GPIO 0 - 31 は、IRQ 番号 29 が割り当てられていますので。
322 行目は、GPIO_IRQENABLE1 レジスタにより、GPIO 7 の割り込みを有効にしています。
323 行目は、割り込みコントローラのマスクレジスタにおいて、IRQ 29 をアンマスクしています。
296 行目の割り込みハンドラーは、ボタンが押された / 離された時に呼び出されます。
303 行目で GPIO 7 の値を読んで、1 ならば「ボタンが押された」、0 ならば「ボタンが離された」を表示します。
308 行目で、割り込み要因をクリアしています。
これがないと、延々と割り込みが上がり続け、割り込み永久ループになってしまいます。
このテストプログラムを動かして、ボタンを 1 回だけ押して離してみると、下図のようになります。
「押された」「離された」が 1 回ずつしか表示されていませんので、チャタリング除去が効いているということですね。
・・・
xloader が用意してくれている I2C ドライバ関数は、PIO モードで動作します。
I2C にも、UART 同様、割り込みモード、DMA モードがあるわけでして、そちらも見てみた方がいいかなあという気もしますが、扱い方は UART とほぼ同じなので、省略します。
コメント 0