ラズパイ2試食レポ[6]: シャットダウンボタン作ってみた [Raspberry Pi]
シャットダウンボタンを作られた先人も非常に多数おられます。いろいろ参考にしながら私なりにまとめました。
もともとラズパイに電源ボタンがない、サーバー的に使用したいのでラズパイ単体で電源を落としたい、などのニーズがあります。ここでは、以下の方針(仕様)で作っていきます。
- GPIOにプッシュスイッチをつけて、長押しでshutdownが走るようにする。
- 長押し期間中は、Power LEDを点滅させる。shutdownが走り出したら点灯に戻す。
- ポーリングではなく割り込みにする。
- 汎用的なCで実装する。
◆GPIO
GPIOを制御するにはいろいろな手段があるようです。もっとも簡便なのは、sysfsを使用したものです。GPIO4をHIGHにする例です。
pi@raspberrypi ~ $ echo 4 > /sys/class/gpio/export # gpio4を有効化
pi@raspberrypi ~ $ echo out > /sys/class/gpio/gpio4/direction # 出力に設定
pi@raspberrypi ~ $ echo 1 > /sys/class/gpio/gpio4/value # Highを出力
pythonでも当然できますが、ここでは省略します。
Cでは、wiringPiというライブラリを使うのがよいようです。apt-getできないので、以下のように導入します。
pi@raspberrypi ~ $ git clone git://git.drogon.net/wiringPi
pi@raspberrypi ~ $ cd wiringPi
pi@raspberrypi ~/wiringPi $ ./build
/usr/local/bin/gpioというコマンドラインから使えるコマンドも導入されます。
pi@raspberrypi ~ $ gpio -g mode 4 in # gpio4を入力モード
pi@raspberrypi ~ $ gpio -g mode 4 up # gpio4をプルアップ
pi@raspberrypi ~ $ gpio -g read 4 # gpio4を読む
1 # 1=HIGH
実際にCから使う例は後ほど。
◆LEDの制御
ラズパイにはPower LED(赤色)とACT LED(緑色)が搭載されていますが、これが制御可能とのこと。任意のGPIO端子の状態に連動させることもできるそうなので、プログラムの動作確認などにも使えそうです。
GPIOと同様、sysfsから制御できます。ACT LEDがled0、Power LEDがled1です。
pi@raspberrypi ~/c $ cat /sys/class/leds/led0/trigger
none [mmc0] timer oneshot heartbeat backlight gpio cpu0 cpu1 cpu2 cpu3 default-on input
pi@raspberrypi ~/c $ cat /sys/class/leds/led1/trigger
none mmc0 timer oneshot heartbeat backlight gpio cpu0 cpu1 cpu2 cpu3 default-on [input]
led0ではmmc0すなわちSDカードアクセスで点灯、led1では、inputすなわち電源ラインで点灯するモードになっています。これを切り替えることができます。heartbeatにすると、「チカチカ…チカチカ…」と点滅します。
pi@raspberrypi ~/c $ sudo su
pi@raspberrypi ~/c # echo heartbeat > /sys/class/leds/led1/trigger
◆シャットダウンプログラム
ピンヘッダの#7(GPIO4)と#9(GND)の間にプッシュスイッチを接続します。
GPIO4は入力モードにしてプルアップします。スイッチオフではHIGH、スイッチオンではLOWになります。
以下がCで書いたシャットダウンプログラムです。
//
// shutdown_sw
//
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <wiringPi.h>
#define GPIO4 4
#define WAIT_SEC 5
#define BUFFER_MAX 10
// set power led
int setPowerLed(char *mode) {
int fd;
char buffer[BUFFER_MAX];
ssize_t bytes_written;
fd = open("/sys/class/leds/led1/trigger", O_WRONLY);
if (fd == -1) {
fprintf(stderr, "Faild to open led1 trigger\n");
return(-1);
}
bytes_written = snprintf(buffer, BUFFER_MAX, "%s", mode);
write(fd, buffer, bytes_written);
close(fd);
return(0);
}
// callback function for interrupt
// 1st, PowerLED blinging for WAIT_SEC second,
// then, PowerLED on, goto shutdown.
void callback_go(void) {
int i;
int ret = 0;
usleep(30000); // for chattering
if (digitalRead(GPIO4) == LOW) {
// wait for WAIT_SEC
for (i = WAIT_SEC; i >= 0; i--) {
if (digitalRead(GPIO4) == LOW) {
setPowerLed("heartbeat");
sleep(1);
} else {
setPowerLed("input");
break;
}
}
if (i < 0) {
setPowerLed("input");
// exec shutdown
system("/sbin/shutdown -h now");
}
}
}
// main routin
int main(void) {
int ret = 0;
// initialize wiringPi
if ( wiringPiSetupGpio() == -1 ) return 1;
// set gpio4 pin to input mode & pullup
pinMode(GPIO4, INPUT);
pullUpDnControl(GPIO4, PUD_UP);
// set interrupt
wiringPiISR(GPIO4, INT_EDGE_FALLING, callback_go);
// wait for interrupt
for (;;) {
sleep(100000);
}
return 0;
}
mainルーチンは、GPIOの設定後、長いスリープを行う無限ループで割り込みを待ちます。単なるポーリングより圧倒的にCPUにやさしいです。
スイッチが押されてGPIO4が立ち下がると、割り込みが掛かり、callback_goが実行されます。チャタリングを避けるため30ms後に再度レベルを確認しています。
毎秒レベルを確認し、長押しの途中でボタンが離された場合にはシャットダウンせずに割り込み待ちに戻ります。
ボタンが押された状態ではPower LEDを点滅します。5秒間押されたままであれば shutdownを実行します。
このプログラムは常駐する必要があるので、/etc/rc.localの最後に追加します。
# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi
# Shutdown SW
if [ -f /usr/local/bin/shutdown_sw ]; then
su -c /usr/local/bin/shutdown_sw &
fi
exit 0
再起動して、ps -ef すると実行されていることが確認できます。
コメント 0