21 May 2018

Raspberry Pi Zero Wでwiringpi-perlをインストール

最新のWiringPi C言語ライブラリを用いてBCM2835を認識させる

Raspberry Pi Zero Wでwiringpi-perl (https://github.com/WiringPi/WiringPi-Perl)をインストールすると、実行時に次のようなエラーが出る

Unable to determine hardware version. I see: Hardware	: BCM2835

これは wiringpi-perl の サブモジュール、WiringPi 内の wiringPi.cがBCM2835に対応していないことが原因だ。

最新のgit ライブラリ WiringPi (https://github.com/WiringPi/WiringPi)では最新のプロセッサにも対応しているが、wiringpi-perlがサブモジュールを登録した時点のものがpullされてきているためだ。

まずは、wiringpi-perl の build.sh を見てみる

build.sh
#!/bin/bash
echo "Updating submodule..."
git submodule update --init
 
echo "Generating bindings..."
swig2.0 -perl wiringpi.i
 
CORE=`perl -MConfig -e 'print $Config{archlib}'`/CORE
WIRINGPI=WiringPi/wiringPi
 
echo "Building against: $CORE"
 
gcc -fpic -c -Dbool=char -I$CORE wiringpi_wrap.c \
$WIRINGPI/wiringSerial.c \
$WIRINGPI/wiringShift.c \
$WIRINGPI/wiringPi.c \
$WIRINGPI/softPwm.c \
$WIRINGPI/softTone.c \
-D_GNU_SOURCE
 
gcc -shared wiringPi.o \
softPwm.o \
softTone.o \
wiringSerial.o \
wiringShift.o \
wiringpi_wrap.o \
-o wiringpi.so

この git submodule update --init でサブモジュールがGitからダウンロードされてくる。

このダウンロードされてきたモジュールのファイルを、最新にするためには

git pull origin master

というコマンドを、サブモジュールのディレクトリ内で実行すればよい。

まず、問題のwiringPi.cの過去バージョンと、最新バージョンの違いを見てみよう。

赤で着色した部分が、新しいCPUのBCM2835に対応できるかどうかの違い。

wiringPi.c (ver 2.32)
int piBoardRev (void)
{
  FILE *cpuFd ;
  char line [120] ;
  char *c ;
  static int  boardRev = -1 ;
 
  if (boardRev != -1)	// No point checking twice
    return boardRev ;
 
  if ((cpuFd = fopen ("/proc/cpuinfo", "r")) == NULL)
    piBoardRevOops ("Unable to open /proc/cpuinfo") ;
 
// Start by looking for the Architecture to make sure we're really running
//	on a Pi. I'm getting fed-up with people whinging at me because
//	they can't get it to work on weirdFruitPi boards...
 
  while (fgets (line, 120, cpuFd) != NULL)
    if (strncmp (line, "Hardware", 8) == 0)
      break ;
 
  if (strncmp (line, "Hardware", 8) != 0)
    piBoardRevOops ("No hardware line") ;
 
  if (wiringPiDebug)
    printf ("piboardRev: Hardware: %s\n", line) ;
 
// See if it's BCM2708 or BCM2709
 
  if (strstr (line, "BCM2709") != NULL)	// Pi v2 - no point doing anything more at this point
  {
    piModel2 = TRUE ;
    fclose (cpuFd) ;
    return boardRev = 2 ;
  }
  else if (strstr (line, "BCM2708") == NULL)
  {
    fprintf (stderr, "Unable to determine hardware version. I see: %s,\n", line) ;
    fprintf (stderr, " - expecting BCM2708 or BCM2709.\n") ;
    fprintf (stderr, "If this is a genuine Raspberry Pi then please report this\n") ;
    fprintf (stderr, "to projects@drogon.net. If this is not a Raspberry Pi then you\n") ;
    fprintf (stderr, "are on your own as wiringPi is designed to support the\n") ;
    fprintf (stderr, "Raspberry Pi ONLY.\n") ;
    exit (EXIT_FAILURE) ;
  }
wiringPi.c (ver 2.44)
int piGpioLayout (void)
{
  FILE *cpuFd ;
  char line [120] ;
  char *c ;
  static int  gpioLayout = -1 ;
 
  if (gpioLayout != -1)	// No point checking twice
    return gpioLayout ;
 
  if ((cpuFd = fopen ("/proc/cpuinfo", "r")) == NULL)
    piGpioLayoutOops ("Unable to open /proc/cpuinfo") ;
 
// Start by looking for the Architecture to make sure we're really running
//	on a Pi. I'm getting fed-up with people whinging at me because
//	they can't get it to work on weirdFruitPi boards...
 
  while (fgets (line, 120, cpuFd) != NULL)
    if (strncmp (line, "Hardware", 8) == 0)
      break ;
 
  if (strncmp (line, "Hardware", 8) != 0)
    piGpioLayoutOops ("No \"Hardware\" line") ;
 
  if (wiringPiDebug)
    printf ("piGpioLayout: Hardware: %s\n", line) ;
 
// See if it's BCM2708 or BCM2709 or the new BCM2835.
 
// OK. As of Kernel 4.8,  we have BCM2835 only, regardless of model.
//	However I still want to check because it will trap the cheapskates and rip-
//	off merchants who want to use wiringPi on non-Raspberry Pi platforms - which
//	I do not support so don't email me your bleating whinges about anything
//	other than a genuine Raspberry Pi.
 
  if (! (strstr (line, "BCM2708") || strstr (line, "BCM2709") || strstr (line, "BCM2835")))
  {
    fprintf (stderr, "Unable to determine hardware version. I see: %s,\n", line) ;
    fprintf (stderr, " - expecting BCM2708, BCM2709 or BCM2835.\n") ;
    fprintf (stderr, "If this is a genuine Raspberry Pi then please report this\n") ;
    fprintf (stderr, "to projects@drogon.net. If this is not a Raspberry Pi then you\n") ;
    fprintf (stderr, "are on your own as wiringPi is designed to support the\n") ;
    fprintf (stderr, "Raspberry Pi ONLY.\n") ;
    exit (EXIT_FAILURE) ;
  }

最終的に、wiringpi-perl の build.sh を次のように修正する

build.sh
#!/bin/bash
echo "Updating submodule..."
git submodule update --init
cd WiringPi
git pull origin master
cd ..
 
echo "Generating bindings..."
swig2.0 -perl wiringpi.i
 〜 以下省略 〜

利用する関数を記したwiringpi.iをアップデート

wiringpi.iに列挙されている関数群は、以前の(ver 2.32版の)ものだ。これを今回アップデートして利用する最新版の関数群に修正する。

wiringPi.c (かつてのver 2.32対応)
%module wiringpi
 
%apply unsigned char { uint8_t };
 
extern int  wiringPiSetup     (void) ;
extern int  wiringPiSetupSys  (void) ;
extern int  wiringPiSetupGpio (void) ;
 
extern void wiringPiGpioMode  (int mode) ;
extern void pullUpDnControl   (int pin, int pud) ;
extern void pinMode           (int pin, int mode) ;
extern void digitalWrite      (int pin, int value) ;
extern void pwmWrite          (int pin, int value) ;
extern int  digitalRead       (int pin) ;
extern void shiftOut          (uint8_t dPin, uint8_t cPin, uint8_t order, uint8_t val);
extern uint8_t shiftIn        (uint8_t dPin, uint8_t cPin, uint8_t order);
 
extern void         delay             (unsigned int howLong) ;
extern void         delayMicroseconds (unsigned int howLong) ;
extern unsigned int millis            (void) ;
 
extern int   serialOpen      (char *device, int baud) ;
extern void  serialClose     (int fd) ;
extern void  serialPutchar   (int fd, uint8_t c) ;
extern void  serialPuts      (int fd, char *s) ;
extern int   serialDataAvail (int fd) ;
extern int   serialGetchar   (int fd) ;
extern void  serialPrintf    (int fd, char *message, ...) ;
 
extern void pwmSetMode      (int mode) ;
extern void pwmSetRange     (unsigned int range) ;
extern void pwmSetClock     (int divisor) ;
 
%{
#include "WiringPi/wiringPi/wiringPi.h"
#include "WiringPi/wiringPi/wiringShift.h"
#include "WiringPi/wiringPi/wiringSerial.h"
%}
wiringPi.c (最新のver 2.44に対応)
%module wiringpi
 
%apply unsigned char { uint8_t };
 
/*****
 * wiringPi.h
*****/
 
extern void wiringPiVersion	(int *major, int *minor) ;
extern int  wiringPiSetup       (void) ;
extern int  wiringPiSetupSys    (void) ;
extern int  wiringPiSetupGpio   (void) ;
extern int  wiringPiSetupPhys   (void) ;
 
extern          void pinModeAlt          (int pin, int mode) ;
extern          void pinMode             (int pin, int mode) ;
extern          void pullUpDnControl     (int pin, int pud) ;
extern          int  digitalRead         (int pin) ;
extern          void digitalWrite        (int pin, int value) ;
extern unsigned int  digitalRead8        (int pin) ;
extern          void digitalWrite8       (int pin, int value) ;
extern          void pwmWrite            (int pin, int value) ;
extern          int  analogRead          (int pin) ;
extern          void analogWrite         (int pin, int value) ;
 
extern          int  piGpioLayout        (void) ;
extern          void piBoardId           (int *model, int *rev, int *mem, int *maker, int *overVolted) ;
extern          int  wpiPinToGpio        (int wpiPin) ;
extern          int  physPinToGpio       (int physPin) ;
extern          void setPadDrive         (int group, int value) ;
extern          int  getAlt              (int pin) ;
extern          void pwmToneWrite        (int pin, int freq) ;
extern          void pwmSetMode          (int mode) ;
extern          void pwmSetRange         (unsigned int range) ;
extern          void pwmSetClock         (int divisor) ;
extern          void gpioClockSet        (int pin, int freq) ;
extern unsigned int  digitalReadByte     (void) ;
extern unsigned int  digitalReadByte2    (void) ;
extern          void digitalWriteByte    (int value) ;
extern          void digitalWriteByte2   (int value) ;
 
extern void         delay             (unsigned int howLong) ;
extern void         delayMicroseconds (unsigned int howLong) ;
extern unsigned int millis            (void) ;
extern unsigned int micros            (void) ;
 
/*****
 * wiringSerial.h
*****/
 
extern int   serialOpen      (const char *device, const int baud) ;
extern void  serialClose     (const int fd) ;
extern void  serialFlush     (const int fd) ;
extern void  serialPutchar   (const int fd, const unsigned char c) ;
extern void  serialPuts      (const int fd, const char *s) ;
extern void  serialPrintf    (const int fd, const char *message, ...) ;
extern int   serialDataAvail (const int fd) ;
extern int   serialGetchar   (const int fd) ;
 
%{
#include "WiringPi/wiringPi/wiringPi.h"
#include "WiringPi/wiringPi/wiringShift.h"
#include "WiringPi/wiringPi/wiringSerial.h"
%}

このファイルをperlのモジュールファイル(.pm)に変換するswigのコマンドは

swig2.0 -perl wiringpi.i

build.sh を実行する前に、wiringpi.pmファイルは念の為 削除しておくほうが良い。

コンパイルを行い、モジュールファイルをインストール

コンパイルを行う

./build.sh

wiringpi-perl のコンパイルが終われば、次の2つのファイルをPerlモジュールのディレクトリにコピーする。

・wiringpi.pm
・wiringpi.so

コピー先は perl -e 'print @INC' で表示されるディレクトリだが、その中のどれなのかというのは、適当なcpanモジュールをインストールするときの画面出力を見ていれば分かる。

今回は、 /usr/local/share/perl/5.22.1 に上述の2個のファイルをコピー (インストール)した。

※ コンパイラはcソースコードの全ての関数をsoに格納しているはずだが、ある特定の関数が含まれているかどうかは、シンボル一覧をダンプしてチェックすれば良い

readelf  -s wiringpi.so

RPi::Const のインストール

WiringPiの各関数で用いる定数を定義しているcpanモジュールをインストールしておくと便利

sudo cpan RPi::Const

サンプル・プログラム

pwmでLEDの明るさを変化させるサンプル

#!/usr/bin/perl
 
use strict;
use warnings;
use wiringpi;
use RPi::Const;
use Time::HiRes 'usleep';
 
# IMPORTANT ! : Rpi BCM_GPIO = 18  is equivalent to WiringPi GPIO = 1
use constant GPIO_PIN => 1;
 
if( wiringpi::wiringPiSetup()==-1 ){
    die 1;
}
 
wiringpi::pinMode(GPIO_PIN, RPi::Const::PWM_OUT);
wiringpi::pwmSetMode(RPi::Const::PWM_MODE_BAL);
wiringpi::pwmSetRange(100);
wiringpi::pwmSetClock(218);
 
for(my $j=0; $j<5; $j++){
    for(my $i=0; $i<100; $i++){
        wiringpi::pwmWrite(GPIO_PIN,$i);
        usleep(30*1000);      # 30ミリ秒
    }
    wiringpi::pwmWrite(GPIO_PIN,0);
    usleep(500*1000);     # 500ミリ秒
}
 
wiringpi::pinMode(GPIO_PIN, RPi::Const::OUTPUT);
wiringpi::digitalWrite(GPIO_PIN, RPi::Const::LOW);