Ruby C拡張を使ったRaspberry pi gpio 制御

RubyでRaspberry piのgpio制御したかったので、pi_pier gem のインストールに挑戦したがうまくいかなかったので以下の情報を参考にRuby C拡張ライブラリを作ってみた。

C言語によるgpioの制御 

  こじ研(Raspberry Pi)

Ruby C拡張ライブラリ

  Blog Alpha Networking: RubyによるC拡張ライブラリの作成方法

 

動作環境

 Raspberry pi1 Model B 

   Raspbian GNU/Linux 8.0 (jessie)

   ruby 2.1.5p273 (2014-11-13) [arm-linux-gnueabihf]

 

1.C言語プログラム

rasp_gpio.c
// rasp_gpio: Raspberry Pi GPIO Ruby module

#include <ruby.h>

#ifndef __RASP_GPIO__
#define __RASP_GPIO__

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

// ピン機能(BCM2835)
#define GPIO_INPUT 0x0 // 入力
#define GPIO_OUTPUT 0x1 // 出力
#define GPIO_ALT0 0x4
#define GPIO_ALT1 0x5
#define GPIO_ALT2 0x6
#define GPIO_ALT3 0x7
#define GPIO_ALT4 0x3
#define GPIO_ALT5 0x2

 

// レジスタブロックの物理アドレス
#define PERI_BASE 0x20000000
#define GPIO_BASE (PERI_BASE + 0x200000)
#define BLOCK_SIZE 4096

#endif


// 関数の宣言
static VALUE gpio_alloc(VALUE klass);
static VALUE gpio_init(VALUE self);
static VALUE gpio_conf(VALUE self, VALUE i_pin, VALUE i_mode);
static VALUE gpio_set(VALUE self, VALUE i_pin);
static VALUE gpio_clear(VALUE self, VALUE i_pin);
static VALUE gpio_read(VALUE self, VALUE i_pin);


void Init_rasp_gpio(void)
{
 VALUE RaspGpio;

 // クラスを定義
 RaspGpio = rb_define_class( "RaspGpio", rb_cObject ); // class cGpio < rb_cObject

 //フィールドのクラスへの関連付け
 rb_define_alloc_func(RaspGpio, gpio_alloc);

 // メソッドクラスへの関連付け
 rb_define_private_method( RaspGpio, "initialize", gpio_init, 0);
 rb_define_method( RaspGpio, "conf", gpio_conf, 2);
 rb_define_method( RaspGpio, "set", gpio_set, 1);
 rb_define_method( RaspGpio, "clear", gpio_clear, 1);
 rb_define_method( RaspGpio, "read", gpio_read, 1);

}

//フィールドの定義
struct raspgpio {
 volatile unsigned int *Gpio; // GPIO 関連レジスタ (volatile=必ず実メモリにアクセス させる)
};


//フィールドをクラスへセット
static VALUE gpio_alloc(VALUE klass)
{
 struct raspgpio *ptr = ALLOC(struct raspgpio);

 // klass へフィールドをセットする
 return Data_Wrap_Struct(klass, 0, -1, ptr);
}


// GPIO 初期化
// initialize メソッドをクラスへセット
VALUE gpio_init(VALUE self)
{
 struct raspgpio *ptr;
 // self("struct RData *" である VALUE)から "struct RaspGpio *" を取り出す
 Data_Get_Struct( self, struct raspgpio, ptr );

 int fd;
 void *gpio_map;
 // /dev/mem(物理メモリデバイス)を開く(sudo が必要)
 fd = open("/dev/mem", O_RDWR | O_SYNC);
 if (fd == -1) { return T_FALSE; }

 // mmap で GPIO(物理メモリ)を gpio_map(仮想メモリ)に対応づける
 gpio_map = mmap(NULL, BLOCK_SIZE,
 PROT_READ | PROT_WRITE, MAP_SHARED,
 fd, GPIO_BASE );
 if (gpio_map == MAP_FAILED) { return T_FALSE; } // MAP_FAILED => (void *)-1)

 // mmap 後は不要な fd をクローズ
 close(fd);
 // gpio[index]: 整数 uint32 の配列としてレジスタへのアクセスを確立
 ptr -> Gpio = (unsigned int *) gpio_map;

 return Qnil;
}


// ピン機能の設定(ピンを使用する前に必ず設定)
// mode: GPIO_INPUT, _OUTPUT, _ALT0, _ALT1, _ALT2, _ALT3, _ALT4, _ALT5
VALUE gpio_conf(VALUE self,VALUE i_pin, VALUE i_mode)
{
 struct raspgpio *ptr;
 // self("struct RData *" である VALUE)から "struct raspgpio *" を取り出す
 Data_Get_Struct( self, struct raspgpio, ptr );

 int pin = NUM2INT(i_pin);
 int mode = NUM2INT(i_mode);

 // ピン番号範囲チェック
 if (pin < 0 || pin > 31) { return T_FALSE; }
 // レジスタ番号(index)と3ビットマスクを生成
 int index = pin / 10;
 unsigned int mask = ~(0x7 << *1;
 // GPFSEL0/1 の該当する FSEL (3bit) のみを書き換え
 ptr->Gpio[index] = (ptr->Gpio[index] & mask) | *2;

 return T_TRUE;
}

// 指定ピンの出力を1にする
VALUE gpio_set( VALUE self, VALUE i_pin)
{
 struct raspgpio *ptr;
 // self("struct RData *" である VALUE)から "struct raspgpio *" を取り出す
 Data_Get_Struct( self, struct raspgpio, ptr );

 int pin = NUM2INT(i_pin);

 // ピン番号範囲チェック
 if (pin < 0 || pin > 31) { return T_FALSE; }
  // ピンに1を出力(3.3V 出力)
  ptr->Gpio[7] = 0x1 << pin; // GPSET0

  return T_TRUE;
 }

// 指定ピンの出力を0にする
VALUE gpio_clear( VALUE self, VALUE i_pin)
{
 struct raspgpio *ptr;
 // self("struct RData *" である VALUE)から "struct raspgpio *" を取り出す
 Data_Get_Struct( self, struct raspgpio, ptr );

 int pin = NUM2INT(i_pin);

 // ピン番号範囲チェック
 if (pin < 0 || pin > 31) { return T_FALSE; }
 // ピンに0を出力(0V 出力)
 ptr->Gpio[10] = 0x1 << pin; // GPCLR0

 return T_TRUE;
}

// ピンへの入力状態を取得する
VALUE gpio_read (VALUE self, VALUE i_pin)
{
 VALUE ret;
 int st = 0;
 struct raspgpio *ptr;
 // self("struct RData *" である VALUE)から "struct raspgpio *" を取り出す
 Data_Get_Struct( self, struct raspgpio, ptr );

 int pin = NUM2INT(i_pin);

 // ピン番号範囲チェック
 if (pin < 0 || pin > 31) { return T_FALSE; } // FALSE

 // ピンへの入力状態を 1/0 で返す
 if *3 != 0) {
  st = 1;
 }
 ret = INT2FIX(st);
 return ret;
}

 

2.共有ライブラリの作成

 extconf.rb

require "mkmf"
create_makefile "rasp_gpio"

 

makefile を作成する

ruby extconf.rb

実行すると Makefile が作成されます。

make を実行すると rasp_gpio.so ができ上がります。

 

3.テスト

    GPIO 27ピンを出力に設定  LEDのカソードに接続

                出力値 0:LED ON  1:LED OFF

 GPIO 22 ピンを入力に設定  スイッチに接続  

                入力値  スイッチOFF:1  スイッチON:0 

 動作 

  GPIO 22 ピンから読み込んだ値を GPIO 27ピンに出力する   

  実行  sudo ruby test.rb  

       ※rasp_gpio.o は test.rb と同じディレクトリに置く。

 test.rb

require './rasp_gpio'
gpio = RaspGpio.new
gpio.conf(27, 0x01)     # GPIO 27 OUT
gpio.conf(22, 0x00)     # GPIO 22 IN
5.times do |n|
   st = gpio.read(22)
   puts "GPIO 22pin: %d"%st
   (st == 1)? gpio.clear(27): gpio.set(27)

   sleep(1)
end

 

f:id:ik8823:20160305221739p:plain

 

 

 

 

*1:pin % 10) * 3

*2:mode & 0x7) << ((pin % 10) * 3

*3:ptr->Gpio[13] & (0x1 << pin