ソフト寄りのアドベントカレンダーが続いていたので,久しぶりにハードの話もしましょう.
回路
デジタル回路上では,論理1を信号のHigh電圧(だいたいロジック電源と同じ電圧),論理0を信号のLow電圧(だいたいGND電圧)で表現しています.つまり,外部からEdisonに1を入力したい場合はEdisonのピンにHigh電圧を,0を入力したい場合はLow電圧を印加すれば良いわけです.そのため,以下のようなプルアップ回路というのがよく使われます.
このようにすることで,スイッチを離した状態では,ピンに10[kΩ]の抵抗を介してHigh電圧が印加され,これは論理的には1と判断されます.また,スイッチが押されたときは,ピンはGNDに直結するためLow電圧となり,これは論理的には0と判断されます.
このR3の抵抗は,とても重要です.もしこの抵抗がなければ,スイッチが押されていない状態では,ピンに印加される電圧が不定(どちらかわからない状態)になってしまいます.そこで,抵抗を介して弱くHigh電圧に”吊って”おきます.このことから,この回路は プルアップ回路 と呼ばれています.また,スイッチが押された場合にも,もしR3の抵抗がなければ,ロジックHigh電圧とGNDが直結して,ショートしてしまいます.
ちなみに,プルアップがあるならプルダウンもあるだろうと予測した読者は鋭いほうで,実際にプルダウンの回路もたくさんあります.プルダウンの回路のほうが,スイッチが押されたときに1で,離されたときに0となるので,直感的に理解しやすそうです.ただ,それでもプルアップが選ばれて来たのは,色々な歴史的な背景があります.興味がある人は調べてみてください.
さて,Edisonでスイッチ入力を扱う場合も,この回路を組めば良いのですが,実はEdisonには,ソフトウェア上から特定のピンを指定してCPUの内部でプルアップする機能が搭載されています.その機能を利用することで,以下のように回路がとても簡単になります.
この回路を見て回路が簡単になった!と思うのは実は間違いで,以下の図のように,CPUの内部では見えない抵抗が接続しています.
プログラム
前章で構成した回路では,スイッチが押されていないときにJ18-1ピンの状態を読むと1が読込まれて,スイッチが押されていたときは0が読込まれます.それを,以下のプログラムで読み出しましょう.
#include <stdio.h>
#include <syslog.h>
#include <unistd.h>
#include <mraa.h>
int main(int argc, char *argv[])
{
mraa_init();
fprintf(stdout, "Hello mraa.\nVersion: %s\n", mraa_get_version());
// LED
mraa_gpio_context out;
out = mraa_gpio_init(20);
if(out == NULL){
printf("Error: init out.\r\n");
return 1;
}
mraa_gpio_dir(out, MRAA_GPIO_OUT);
// Switch
mraa_gpio_context in;
in = mraa_gpio_init(14);
if(in == NULL){
printf("Error: init in.\r\n");
return 1;
}
mraa_gpio_dir(in, MRAA_GPIO_IN);
// プルアップ指定
mraa_gpio_mode(in, MRAA_GPIO_PULLUP);
int i;
for(i=0;i<20;i++){
if(mraa_gpio_read(in) == 1){
mraa_gpio_write(out, 1);
printf("Released\r\n");
}else{
mraa_gpio_write(out, 0);
printf("Pushed\r\n");
}
sleep(1);
}
mraa_gpio_close(in);
mraa_gpio_close(out);
mraa_deinit();
return 0;
}
$ gcc -lmraa -o io io.c
ioという実行ファイルができるので,管理者権限で実行してください.
入力に指定したピンをプルアップモードに指定しているところに注意してください.このプログラムは,1秒ごとにスイッチが押されているかどうかをmraa_gpio_read関数で読込んで,結果をターミナルとLEDに出力します.LEDの回路は,前回の記事を参照してください.
実験した動画はこちらになります.
さて,これで一応スイッチの入力処理はできたわけですが,このプログラムはあまりイケてないですね.
スイッチの入力処理をメイン関数内に含めているというところが,まずイケてない.あと,スイッチの読込みを1秒毎にしているので,1秒よりも短い周期の入力には対応できない.
こうした問題は,ピンに入力されている信号が変化した場合に処理を割り込む関数を利用することで解決できます.実際に書き直したプログラムが以下になります.
#include <stdio.h>
#include <syslog.h>
#include <unistd.h>
#include <mraa.h>
mraa_gpio_context out;
volatile int mutex = 0;
void interrupt_in(void *arg)
{
mraa_gpio_context dev = (mraa_gpio_context)arg;
if(mutex == 0){
mutex = 1;
if(mraa_gpio_read(dev) == 1){
mraa_gpio_write(out, 1);
printf("Released\r\n");
}else{
mraa_gpio_write(out, 0);
printf("Pressed\r\n");
}
mutex = 0;
}
}
int main(int argc, char *argv[])
{
mraa_init();
fprintf(stdout, "Hello mraa.\nVersion: %s\n", mraa_get_version());
// LED
out = mraa_gpio_init(20);
if(out == NULL){
printf("Error: init out.\r\n");
return 1;
}
mraa_gpio_dir(out, MRAA_GPIO_OUT);
// Switch
mraa_gpio_context in = mraa_gpio_init(14);
if(in == NULL){
printf("Error: init in.\r\n");
return 1;
}
mraa_gpio_dir(in, MRAA_GPIO_IN);
mraa_gpio_mode(in, MRAA_GPIO_PULLUP);
// 割込関数の登録
mraa_gpio_isr(in, MRAA_GPIO_EDGE_BOTH, interrupt_in, (void *)in);
int i;
for(i=0;i<10;i++){
sleep(2);
}
mraa_gpio_isr_exit(in);
mraa_gpio_close(in);
mraa_gpio_close(out);
mraa_deinit();
return 0;
}
割込み関数を登録する関数では,ピンの入力がLからHに変わった”立ち上がり”のときと,HからLに変わった”立ち下がり”のとき,その両方ともの3種類のイベントのどれで発火するかを選択できます.今回は,両方を選択しました.
実際に実験した動画はこちら.
うん.良くなりましたね!
まとめ
- スイッチ入力はmraa_gpio_read関数を使って読込む.
- スイッチに設定したピンは,プルアップ/プルダウン処理を適切に行わないといけない.
- Edisonにはソフトウェアプルアップ/プルダウンの機能がある.
- ピンの入力が変化したときに発火する割込み関数を登録できる.
- チャタリング処理はしなくて良かった(わかる人向け情報).