![見出し画像](https://assets.st-note.com/production/uploads/images/157185011/rectangle_large_type_2_50fe5087332beb4b1d1762161a6c4c30.png?width=1200)
libgpiod(v1)でラズパイ5のGPIOを制御:高レベルAPIの利用
C/C++でラズパイ5のGPIOを制御する方法として下記の記事を書きました。
どの方法を使うかは決め手にかけるものの、現状ではlibgpiodのバージョン1を使うのが妥当なのかと思い、もう少し使ってみました。
サンプルとしては前回と同じく、GPIO5(29番ピン)とGPIO6(31番ピン)をスイッチ入力、GPIO13(33番ピン)をLED出力とし、GPIO5につないだスイッチを押すとLEDの点滅が始まり、GPIO6につないだスイッチを押すと終了するというものとします。
前回の記事のlibgpiod(v1)を使ったソースでは、libgpiodのコアな関数を使う例を紹介しました。libgpiodではそれ以外に高レベルなAPIも準備されています。この高レベルAPIでは、よく使う動作をコアな関数を組み合わせてまとめて1つの関数としています。
今回はこの高レベルAPIの関数の使い方について説明しようかと思います。
単純な入力と出力
下記は、前回の記事の③のgpiod_test_v1.cppを高レベル関数を使うように書き換えたものです。
#include <stdio.h>
#include <unistd.h>
#include <gpiod.h>
#define PROGNAME "gpiod_test_v1_ctxless"
int main(int argc, char *argv[])
{
int sw1 = 5; // GPIO5: input
int sw2 = 6; // GPIO6: input
int led = 13; // GPIO13: output
system("pinctrl set 5 pu");
system("pinctrl set 6 pu");
system("pinctrl set 13 pn");
bool active_low = 1;
printf("push GPIO5 to start.\n");
while(gpiod_ctxless_get_value("gpiochip4", sw1, active_low, PROGNAME) == 0){
usleep(10000);
}
printf("push GPIO6 to finish.\n");
int freq = 500000;
int cnt = 0;
while(1){
gpiod_ctxless_set_value("gpiochip4", led, cnt%2, active_low, PROGNAME, NULL, NULL);
usleep(freq);
if(gpiod_ctxless_get_value("gpiochip4", sw2, active_low, PROGNAME) == 1){
break;
}
cnt++;
}
gpiod_ctxless_set_value("gpiochip4", led, 1, active_low, PROGNAME, NULL, NULL);
return 0;
}
上記をgpiod_test_v1_ctxless.cppという名のファイルに保存した場合、前回と同様、下記のようにすればコンパイルできると思います。
$ g++ -o gpiod_test_v1_ctxless gpiod_test_v1_ctxless.cpp `pkg-config --cflags --libs libgpiod
高レベル関数では、入力であればgpiod_ctxless_get_value()関数、出力ならばgpiod_ctxless_set_value()関数を呼び出すだけで使うことができるようになっています。
前回のコアな関数を使った場合には、gpiod_chipやgpiod_lineの変数(オブジェクト)を自分で準備する必要がありましたが、そういったものは意識せずに使うことができるようになっています。
引数のactive_lowは正論理(0)か負論理(1)かを指定するもので、今回のスイッチ入力はプルアップしているので負論理(1)としています。これを指定しておくことで、正論理でも負論理でもgpiod_ctxless_get_value()で取得される値が、入力があれば1、無ければ0になります。
active_low=1にした場合、実際の値と関数で得られる値が変わってしまうので、かえって混乱する場合もあるかもしれないので、使うかどうかは場合によるかもしれません。
複数ポートの入力と出力
複数のGPIOポートをまとめて入出力する場合の関数として、gpiod_ctxless_get_value_multiple()やgpiod_ctxless_set_value_multiple()関数が用意されています。
今回のソフトの例ではかえって冗長になってしまいますが、先のプログラムをこれらの関数を使うように書き換えた例です。
#include <stdio.h>
#include <unistd.h>
#include <gpiod.h>
#define PROGNAME "gpiod_test_v1_ctxless_multi"
int main(int argc, char *argv[])
{
system("pinctrl set 5 pu");
system("pinctrl set 6 pu");
system("pinctrl set 13 pn");
unsigned int sw[2] = {5, 6}; // 使用するGPIOのピン番号(入力用)
unsigned int sw_len = 2; // 使用するGPIOの数
unsigned int led[1] = {13}; // 使用するGPIOのピン番号(出力用)
unsigned int led_len = 1; // 使用するGPIOの数
bool active_low = 1;
int invals[2];
int outvals[1];
printf("push GPIO5 to start.\n");
while(1){
gpiod_ctxless_get_value_multiple("gpiochip4", sw, invals, sw_len, active_low, PROGNAME);
if(invals[0] == 1){
break;
}
usleep(10000);
}
printf("push GPIO6 to finish.\n");
int freq = 500000;
int cnt = 0;
while(1){
outvals[0] = cnt%2;
gpiod_ctxless_set_value_multiple("gpiochip4", led, outvals, led_len, active_low, PROGNAME, NULL, NULL);
usleep(freq);
gpiod_ctxless_get_value_multiple("gpiochip4", sw, invals, sw_len, active_low, PROGNAME);
if(invals[1] == 1){
break;
}
cnt++;
}
outvals[0] = 1;
gpiod_ctxless_set_value_multiple("gpiochip4", led, outvals, led_len, active_low, PROGNAME, NULL, NULL);
return 0;
}
使うGPIOの番号のリスト(sw, led)と、その数(sw_len, led_len)、getの場合は各ポートの取得値を格納する領域(invals)、setの場合は出力する値の配列(outvals)を指定すればまとめて入出力が行えます。
イベント(RISING/FALLING)によるコールバック関数の呼び出し
GPIOにイベントがあった場合に指定したコールバックが呼び出される関数として、gpiod_ctxless_event_monitor_multiple()関数が用意されています。
イベントは、GPIOの入力が0→1(RISING)や、1→0(FALLING)に変化したときのタイミングで発生します。
下記はその使用例です。GPIO5、6につないだスイッチを押すとコールバック関数が呼ばれます。コールバック関数の中では、イベントが生じたGPIOの番号とイベントの種別を表示しています。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <gpiod.h>
#define PROGRAM "gpiod_test_cb_v1"
// コールバック関数
// イベント発生またはタイムアウトで呼ばれる
// 戻り値に負の値を返すと、イベント待ちのメインループが終了する
int event_callback(int evtype, unsigned int offset, const struct timespec *ts, void *data)
{
int *cnt = (int *)data;
printf("cnt: %d, offset:%d", *cnt, offset);
if(evtype == GPIOD_CTXLESS_EVENT_CB_RISING_EDGE){
printf(", evtype:%d(rising)", evtype);
}
else if(evtype == GPIOD_CTXLESS_EVENT_CB_FALLING_EDGE){
printf(", evtype:%d(falling)", evtype);
}
else{
printf(", evtype:%d(timeout)", evtype);
}
printf("\n");
(*cnt)++;
if(*cnt > 100){
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
system("pinctrl set 5 pu");
system("pinctrl set 6 pu");
int event_type = GPIOD_CTXLESS_EVENT_BOTH_EDGES;
unsigned int offset[2] = {5, 6}; // 使用するGPIOのピン番号
unsigned int num_lines = 2; // 使用するGPIOの数
struct timespec ts = {1, 0}; // タイムアウトの時間{sec, nsec}
bool active_low = 1; // 正論理(0), 負論理(1)
static int cnt = 0;
// &tsの代わりにNULLにすると、イベント発生まで待つ
gpiod_ctxless_event_monitor_multiple(
"gpiochip4",
event_type,
offset,
num_lines,
active_low,
PROGRAM,
&ts,
NULL,
event_callback,
&cnt
);
return 0;
}
基本的にはgpiod_ctxless_get_value_multiple()と同じですが、引数としてタイムアウト時間(ts)とコールバック関数(event_callback)の引数が多くなった感じです。
タイムアウト(st)の代わりにNULLを指定した場合には、タイムアウトが発生せずにイベントが発生するまで待ち続けるようになります。
gpiod_ctxless_event_monitor_multiple()は、イベントが発生するたびにコールバック関数を呼び出します。コールバック関数には、イベントのタイプ(RISING/FALLING/タイムアウトなど)と、発生したGPIOの番号が引数として渡されます。
また、コールバック関数へ任意の引数が渡せるようにしてあります。この例では、呼び出し回数のカウンター(cnt)を渡しています。コールバック関数の中でcntを+1し、100回を超えたら、コールバック関数が-1を返すようにしてあります。gpiod_ctxless_get_value_multiple()はコールバック関数から負の値が戻されると、繰り返しイベント待ちのループを終了します。
コールバック関数を常に負の値をリターンするようにすれば、gpiod_ctxless_get_value_multiple()は、イベント待ちの繰り返しループは行わずに、ワンショット(1回のイベントだけを受け付ける)の動作とすることができます。
gpiod_ctxless_event_monitor_multiple()は複数のGPIOのイベントを監視しますが、1つのGPIOだけで良い場合には、gpiod_ctxless_event_monitor()関数も用意されています。この場合は配列を準備する必要がなくなりちょっとだけコードがすっきりすると思います。ただ、gpiod_ctxless_event_monitor()関数は、内部でgpiod_ctxless_event_monitor_multiple()関数を呼び出しているので、どちらを使っても動作的には同じだと思います。
まとめ
libgpiodの使用例はネットで検索してもあまり出てきません。各関数の使い方も説明がほとんど見当たりませんでした。
一番参考になるのは、高レベル関数のソースコードctxless.cではないかと思いました。高レベル関数の中でコアな関数をどのように呼び出しているかが分かると同時に、高レベル関数の引数の意味も理解できます。
libgpiod(v1)のソースコードは、下記のようにGitHubからクローンすれば落とせます。リビジョンの指定がないとv2がクローンされてしまいますので注意してください。
% git clone https://github.com/brgl/libgpiod.git -b v1.6.x