はじめに

“Raspberry Pi”に専用Debianをインストールした直後では、I2C&SPIデバイスドライバが共に有効になっていないのだ。
PCで普段Linuxを使っているような方なら、Webカメラサーバなど色々なものを作ることができるだう。このような方はデバイスの扱い方は雑学として知ってても、実際に使ったことがある人はすくないだろう。これが理由かも知れない。一歩踏み越えて電子工作の分野に入るには壁があるということだ。逆にAVRなどで電子工作に慣れた方にはLinuxという壁がある。”Raspberry Pi”や”Beagle Bone Block”は微妙な立ち位置にあるのだ。

秋月のRTC-8564をRaspberry Piで動かしたので動作報告を書くつもりだったのであるが、Shuji009さんにLinuxでデバイスを弄る方法を説明するとお約束したのだ。
そこでAVRなどで電子工作に慣れた方の壁へのとっかかりとして、C言語からのアプローチを少し触れてみる。

C言語でデバイスを弄る

Linux(UNIX)では、接続されてる周辺機器は/dev 以下のディレクトリにある特別なファイルをオープンして行う。通常ファイルならopen関数でオープンしreadやwrite関数で読み書きで完了であるが、特別なファイルはioctl関数を用いて周辺機器制御することになる。

とここまで書いたのであるが改めて”Raspberry Pi & SPI”でググると、神戸Andriod会でお知り合いになった石井さんのブログ(日エレ)の“Raspberry PiでSPIシリアルで有機ELにフレームバッファを表示する実験してみた“にて、Raspberry Piのフレームバッファ(これも特別なファイルになってる)からSPI上のLCDに書き出すプログラムを紹介されてる。コレを読んだらShuji009さんなら理解できるだろう。

“Raspberry Pi”とShuji009さんがチャレンジされている”Beagle Bone Black”を比べると、教育用とか称されてる分”Raspberry Pi”の方が圧倒的に情報量が多そうだ。どちらもLinux、扱い方は一緒なので、”Raspberry Pi”から入って”Beagle Bone Black”に行くほうが効率的なような気がしますけど、どうでしょうか?Shuji009さん。

ということで、詳しいことは石井さんのブログにお任せして。
秋月のRTC-8564を動かしたので、これを例に書いてみる。

これがソースである。Arduinoのライブラリイからの移植になる。

/*
 *
 *
 */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>

#include <wiringPi.h>
#include <wiringPiI2C.h>

#define _BV(bit)    (1 << (bit))

// RTC-8564 Registers
#define CONTROL1    0x00
#define CONTROL2    0x01
#define SECONDS     0x02
#define MINUTES     0x03
#define HOURS       0x04
#define DAYS        0x05
#define WEEKDAYS    0x06
#define MONTHS      0x07
#define YEARS       0x08
#define MINUTES_ALARM   0x09
#define HOURS_ALARM 0x0A
#define DAYS_ALARM  0x0B
#define WEEKDAYS_ALARM  0x0C
#define CLKOUT      0x0D
#define TIMER_CONTROL   0x0E
#define TIMER       0x0F

#define RTC8564_SLAVE_ADRS 0x51

typedef struct
{
    uint8_t sec;        // 0 to 59
    uint8_t min;        // 0 to 59
    uint8_t hour;       // 0 to 23
    uint8_t day;        // 1 to 31
    uint8_t month;      // 1 to 12
    uint16_t year;      // 2000 to
    uint8_t wday;       // 0 to 6
} RTC_TIME;

typedef struct
{
    uint8_t min;        // 0 to 59
    uint8_t hour;       // 0 to 23
    uint8_t day;        // 1 to 31
    uint8_t wday;       // 0 to 6
} ALARM_TIME;


static int dev;


static int getWeekday( int nYear, int nMonth, int nDay )
{
    int nWeekday, nTmp;

    if (nMonth == 1 || nMonth == 2)
    {
        nYear--;
        nMonth += 12;
    }

    nTmp = nYear/100;
    nWeekday = (nYear + (nYear >> 2) - nTmp + (nTmp >> 2) + (13 * nMonth + 8)/5 + nDay) % 7;

    return nWeekday;
}


/**
 *
 */
uint8_t dec2bcd( uint8_t d )
{
    return ((d/10 * 16) + (d % 10));
}

uint8_t bcd2dec( uint8_t b )
{
    return ((b/16 * 10) + (b % 16));
}

static int setRegBit8(int reg_address, uint8_t set_bit)
{
    int ret;
    uint8_t data;

    if ((ret = wiringPiI2CReadReg8 (dev, reg_address))!=-1)
    {
        data = ret;
        data |= set_bit;

        return wiringPiI2CWriteReg8(dev, reg_address, data);
    }

    return -1;
}

static int clearRegBit8(int reg_address, uint8_t clear_bit)
{
    int ret;
    uint8_t data;

    if ((ret = wiringPiI2CReadReg8 (dev, reg_address))!=-1)
    {
        data = ret;
        data &= ~clear_bit;

        return wiringPiI2CWriteReg8(dev, reg_address, data);
    }

    return -1;
}

static int masksetRegBit8(int reg_address, uint8_t mask, uint8_t set_bit)
{
    int ret;
    uint8_t data;

    if ((ret = wiringPiI2CReadReg8 (dev, reg_address))!=-1)
    {
        data = ret;
        data &= ~mask;
        data |= set_bit;
        return wiringPiI2CWriteReg8(dev, reg_address, data);
    }

    return -1;
}

static int isRunning(void)
{
    int ret;
    uint8_t data;

    if ((ret = wiringPiI2CReadReg8 (dev, SECONDS))!=-1)
    {
        data = ret;

        return !(data & _BV(7));
    }

    return 0;
}


static int init_dev(void)
{
    if ((dev = wiringPiI2CSetup(RTC8564_SLAVE_ADRS)) == -1)
    {
        fprintf (stderr, "RTC-8564: Unable to initialise I2C: %s\n", strerror (errno)) ;
        return 1 ;
    }

    return 0;
}

static int power_on(void)
{
    wiringPiI2CWriteReg8(dev, CONTROL1, 0x20) ; // 00 Control 1, STOP=1
    wiringPiI2CWriteReg8(dev, CONTROL2, 0x00) ;
    wiringPiI2CWriteReg8(dev, SECONDS, 0x00) ;
    wiringPiI2CWriteReg8(dev, MINUTES, 0x00) ;
    wiringPiI2CWriteReg8(dev, HOURS, 0x00) ;
    wiringPiI2CWriteReg8(dev, DAYS, 0x01) ;
    wiringPiI2CWriteReg8(dev, WEEKDAYS, 0x01) ;
    wiringPiI2CWriteReg8(dev, MONTHS, 0x01) ;
    wiringPiI2CWriteReg8(dev, YEARS, 0x01) ;
    wiringPiI2CWriteReg8(dev, MINUTES_ALARM, 0x80) ;
    wiringPiI2CWriteReg8(dev, HOURS_ALARM, 0x80) ;
    wiringPiI2CWriteReg8(dev, DAYS_ALARM, 0x80) ;
    wiringPiI2CWriteReg8(dev, WEEKDAYS_ALARM, 0x80) ;
    wiringPiI2CWriteReg8(dev, CLKOUT, 0x00) ;
    wiringPiI2CWriteReg8(dev, TIMER_CONTROL, 0x00) ;
    wiringPiI2CWriteReg8(dev, TIMER, 0x00) ;
    wiringPiI2CWriteReg8(dev, CONTROL1, 0x00) ; // 00 Control 1, STOP=0(START)

    return 0;
}

static void now(RTC_TIME *time)
{
    uint8_t tmp;

    time->sec = bcd2dec(wiringPiI2CReadReg8 (dev, SECONDS) & 0x7F);
    time->min = bcd2dec(wiringPiI2CReadReg8 (dev, MINUTES) & 0x7F);
    time->hour = bcd2dec(wiringPiI2CReadReg8 (dev, HOURS) & 0x3F);
    time->day = bcd2dec(wiringPiI2CReadReg8 (dev, DAYS) & 0x3F);
    time->wday = bcd2dec(wiringPiI2CReadReg8 (dev, WEEKDAYS) & 0x07);

    tmp = wiringPiI2CReadReg8 (dev, MONTHS);
    time->month = bcd2dec(tmp & 0x1F);
    time->year = bcd2dec(wiringPiI2CReadReg8 (dev, YEARS));

    if (tmp & 0x80)
    {
        time->year += 2100;
    }
    else
    {
        time->year += 2000;
    }
}

static void adjust(RTC_TIME *time)
{
    // RTC8564 Stop
    masksetRegBit8( CONTROL1, _BV(7) | _BV(5) | _BV(3), _BV(5) );

    wiringPiI2CWriteReg8 (dev, SECONDS, dec2bcd(time->sec));
    wiringPiI2CWriteReg8 (dev, MINUTES, dec2bcd(time->min));
    wiringPiI2CWriteReg8 (dev, HOURS, dec2bcd(time->hour));
    wiringPiI2CWriteReg8 (dev, DAYS, dec2bcd(time->day));
    wiringPiI2CWriteReg8 (dev, WEEKDAYS,getWeekday( time->year, time->month, time->day ));

    if (time->year >= 2100)
    {
        wiringPiI2CWriteReg8 (dev, MONTHS, dec2bcd(time->month) | 0x80);
        wiringPiI2CWriteReg8 (dev, YEARS, dec2bcd(time->year - 2100));
    }
    else
    {
        wiringPiI2CWriteReg8 (dev, MONTHS, dec2bcd(time->month));
        wiringPiI2CWriteReg8 (dev, YEARS, dec2bcd(time->year - 2000));
    }

    clearRegBit8 ( CONTROL1, _BV(7) | _BV(5) | _BV(3) ) ;
}

int main(int argc, char* argv[])
{
    struct tm *t ;
    time_t now_data ;
    RTC_TIME now_time;
    char *wday[] = {"日","月","火","水","木","金","土"};

    if(init_dev()==1)
        return 1;

    if (!isRunning())
    {
        power_on();

        printf("SET Time\n");

        time (&now_data);
        t = localtime (&now_data) ;

        now_time.sec = t->tm_sec;
        now_time.min = t->tm_min;
        now_time.hour = t->tm_hour;
        now_time.day = t->tm_mday;
        now_time.month = t->tm_mon+1;
        now_time.year = t->tm_year + 1900;

        adjust(&now_time);
    }
    while(1)
    {
        now(&now_time);
        printf("%d年%d月%d日(%s) %d時%d分%d秒\n",now_time.year,now_time.month,now_time.day,wday[now_time.wday],now_time.hour,now_time.min,now_time.sec);
        delay(1000);
    }

    return 0;
}

インストールしたwinirngPi ライブラリィに含まれてる関数を利用している。

実行はこうなります。

bant@raspberrypi ~ $ cd workspace/wiringPi/examples/
bant@raspberrypi ~/workspace/wiringPi/examples $ sudo ./i2c_RTC8564
[sudo] password for bant:
SET Time
2013年7月31日(水) 10時11分15秒
2013年7月31日(水) 10時11分16秒
2013年7月31日(水) 10時11分17秒
2013年7月31日(水) 10時11分18秒
2013年7月31日(水) 10時11分19秒
2013年7月31日(水) 10時11分20秒
2013年7月31日(水) 10時11分21秒

当該の関数は、以下のようにopen&ioctl関数を利用している。

/*
 * wiringPiI2C.c:
 *	Simplified I2C access routines
 *	Copyright (c) 2013 Gordon Henderson
 ***********************************************************************
 * This file is part of wiringPi:
 *	https://projects.drogon.net/raspberry-pi/wiringpi/
 *
 *    wiringPi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    wiringPi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with wiringPi.
 *    If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************
 */

/*
 * Notes:
 *	The Linux I2C code is actually the same (almost) as the SMBus code.
 *	SMBus is System Management Bus - and in essentially I2C with some
 *	additional functionality added, and stricter controls on the electrical
 *	specifications, etc. however I2C does work well with it and the
 *	protocols work over both.
 *
 *	I'm directly including the SMBus functions here as some Linux distros
 *	lack the correct header files, and also some header files are GPLv2
 *	rather than the LGPL that wiringPi is released under - presumably because
 *	originally no-one expected I2C/SMBus to be used outside the kernel -
 *	however enter the Raspberry Pi with people now taking directly to I2C
 *	devices without going via the kernel...
 *
 *	This may ultimately reduce the flexibility of this code, but it won't be
 *	hard to maintain it and keep it current, should things change.
 *
 *	Information here gained from: kernel/Documentation/i2c/dev-interface
 *	as well as other online resources.
 *********************************************************************************
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#include "wiringPi.h"
#include "wiringPiI2C.h"

// I2C definitions

#define I2C_SLAVE	0x0703
#define I2C_SMBUS	0x0720	/* SMBus-level access */

#define I2C_SMBUS_READ	1
#define I2C_SMBUS_WRITE	0

// SMBus transaction types

#define I2C_SMBUS_QUICK		    0
#define I2C_SMBUS_BYTE		    1
#define I2C_SMBUS_BYTE_DATA	    2 
#define I2C_SMBUS_WORD_DATA	    3
#define I2C_SMBUS_PROC_CALL	    4
#define I2C_SMBUS_BLOCK_DATA	    5
#define I2C_SMBUS_I2C_BLOCK_BROKEN  6
#define I2C_SMBUS_BLOCK_PROC_CALL   7		/* SMBus 2.0 */
#define I2C_SMBUS_I2C_BLOCK_DATA    8

// SMBus messages

#define I2C_SMBUS_BLOCK_MAX	32	/* As specified in SMBus standard */	
#define I2C_SMBUS_I2C_BLOCK_MAX	32	/* Not specified but we use same structure */

// Structures used in the ioctl() calls

union i2c_smbus_data
{
  uint8_t  byte ;
  uint16_t word ;
  uint8_t  block [I2C_SMBUS_BLOCK_MAX + 2] ;	// block [0] is used for length + one more for PEC
} ;

struct i2c_smbus_ioctl_data
{
  char read_write ;
  uint8_t command ;
  int size ;
  union i2c_smbus_data *data ;
} ;

static inline int i2c_smbus_access (int fd, char rw, uint8_t command, int size, union i2c_smbus_data *data)
{
  struct i2c_smbus_ioctl_data args ;

  args.read_write = rw ;
  args.command    = command ;
  args.size       = size ;
  args.data       = data ;
  return ioctl (fd, I2C_SMBUS, &args) ;
}


/*
 * wiringPiI2CRead:
 *	Simple device read
 *********************************************************************************
 */

int wiringPiI2CRead (int fd)
{
  union i2c_smbus_data data ;

  if (i2c_smbus_access (fd, I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &data))
    return -1 ;
  else
    return data.byte & 0xFF ;
}


/*
 * wiringPiI2CReadReg8: wiringPiI2CReadReg16:
 *	Read an 8 or 16-bit value from a regsiter on the device
 *********************************************************************************
 */

int wiringPiI2CReadReg8 (int fd, int reg)
{
  union i2c_smbus_data data;

  if (i2c_smbus_access (fd, I2C_SMBUS_READ, reg, I2C_SMBUS_BYTE_DATA, &data))
    return -1 ;
  else
    return data.byte & 0xFF ;
}

int wiringPiI2CReadReg16 (int fd, int reg)
{
  union i2c_smbus_data data;

  if (i2c_smbus_access (fd, I2C_SMBUS_READ, reg, I2C_SMBUS_WORD_DATA, &data))
    return -1 ;
  else
    return data.word & 0xFFFF ;
}


/*
 * wiringPiI2CWrite:
 *	Simple device write
 *********************************************************************************
 */

int wiringPiI2CWrite (int fd, int data)
{
  return i2c_smbus_access (fd, I2C_SMBUS_WRITE, data, I2C_SMBUS_BYTE, NULL) ;
}


/*
 * wiringPiI2CWriteReg8: wiringPiI2CWriteReg16:
 *	Write an 8 or 16-bit value to the given register
 *********************************************************************************
 */

int wiringPiI2CWriteReg8 (int fd, int reg, int value)
{
  union i2c_smbus_data data ;

  data.byte = value ;
  return i2c_smbus_access (fd, I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE_DATA, &data) ;
}

int wiringPiI2CWriteReg16 (int fd, int reg, int value)
{
  union i2c_smbus_data data ;

  data.word = value ;
  return i2c_smbus_access (fd, I2C_SMBUS_WRITE, reg, I2C_SMBUS_WORD_DATA, &data) ;
}


/*
 * wiringPiI2CSetupInterface:
 *	Undocumented access to set the interface explicitly - might be used
 *	for the Pi's 2nd I2C interface...
 *********************************************************************************
 */

int wiringPiI2CSetupInterface (const char *device, int devId)
{
  int fd ;

  if ((fd = open (device, O_RDWR)) < 0)
    return wiringPiFailure (WPI_ALMOST, "Unable to open I2C device: %s\n", strerror (errno)) ;

  if (ioctl (fd, I2C_SLAVE, devId) < 0)
    return wiringPiFailure (WPI_ALMOST, "Unable to select I2C device: %s\n", strerror (errno)) ;

  return fd ;
}


/*
 * wiringPiI2CSetup:
 *	Open the I2C device, and regsiter the target device
 *********************************************************************************
 */

int wiringPiI2CSetup (const int devId)
{
  int rev ;
  const char *device ;

  rev = piBoardRev () ;

  if (rev == 1)
    device = "/dev/i2c-0" ;
  else
    device = "/dev/i2c-1" ;

  return wiringPiI2CSetupInterface (device, devId) ;
}

ちなみに

上位のARMならMPUにRTC内蔵されているのでいいのだが”Raspberry Pi”にはRTCがないのだ。代わりにNTPサーバを用いてネットから正確な時間を貰ってる。時間を基準に動作するソフトも多く、ネットに繋がっていないと”Raspberry Pi”の時刻は無茶苦茶で動かないということにもなる。今回作ったRTC-8564のソフトを少し弄れば時刻修正コマンドとして活用できるのだがよくよく調べてみると、さすがに歴史あるLinuxだ。RTC-8564のデバイスドライバが用意されてる様子で、デバイスドライバを組み込めたらハードウェア・クロックとして振る舞うことができるような気がする。そのためにはカーネルの再構築が必要かもしれないので二の足を踏んでる。 😳