ハードウェア機能をカスタマイズする

本章では、Armadillo-400 シリーズのハードウェア機能をカスタマイズする方法について説明します。

2.1. シリアルインターフェース

2.1.1. コンソールとして別のシリアルインターフェースを使用する

Armadillo-400シリーズは、標準状態でシリアルインターフェース1(CON3)をコンソールとして使用します。コンソールには、起動ログやカーネルメッセージなどが出力されるため、標準の設定ではシリアルインターフェース1に外部機器を接続して使用するといったことはできません。ここでは、コンソールとして別のシリアルインターフェースを使用する方法について説明します。例として、コンソールをシリアルインターフェース2(CON9_3、CON9_5)に変更します。

第1部「起動の仕組み」でも説明したように、コンソールに文字を表示するプログラムには、ブートローダー、Linuxカーネル、ユーザーランドアプリケーションプログラムの3種類があります。

まず、ブートローダーの起動ログとカーネルメッセージを出力する先を変更します。カーネルメッセージの出力先は、カーネルパラメータのconsoleオプションで指定できます。カーネルパラメータは、ブートローダーのsetenvコマンドで設定します。ブートローダーは、consoleオプションが指定されている場合、それと同じシリアルインターフェースに起動ログを出力します。

カーネルパラメータを設定するには、Armadilloを保守モードで起動して、図2.1「コンソールをシリアルインターフェース2に変更する」のように、使用するシリアルデバイスのデバイスファイル名を入力します。シリアルインターフェースとデバイスファイルの対応は、「Armadillo-400シリーズ ソフトウェアマニュアル」の「UART」の章を参照してください。

hermit> setenv console=ttymxc2,115200

図2.1 コンソールをシリアルインターフェース2に変更する


現在のカーネルパラメータは、setenvコマンドを引数なしで実行することで確認できます。また、カーネルパラメータの指定を解除し、標準状態にもどすには、clearenvコマンドを使用します。

hermit> setenv
1: console=ttymxc2,115200

図2.2 カーネルパラメータの確認


console=ttymxc2,115200を指定した状態で起動すると、起動ログやカーネルメッセージなどはシリアルインターフェース2に出力されるようになります。但し、ログインプロンプトはまだシリアルインターフェース1に出力されます。

atmark-dist v1.45.0 (AtmarkTechno/Armadillo-420)
Linux 3.14.36-at4 [armv5tejl arch]

armadillo420-0 login:

図2.3 ログインプロンプトの表示


ログインプロンプトをシリアルインターフェース2に表示するには、/etc/inittab/etc/securettyを修正する必要があります。標準の、Atmark Distで作成したユーザーランドの場合、/etc/inittab図2.4「標準のinittab」のようになっています。3行目のttymxc1をttymxc2に変更すると、ログインプロンプトをシリアルインターフェース2に表示するようになります。また、/etc/securetty図2.5「標準のsecuretty」のようになっています。ttymxc2を追加すると、シリアルインターフェース2に表示されたログインプロンプトから、rootユーザーでログインできるようになります。

::sysinit:/etc/init.d/rc

::respawn:/sbin/getty -L 115200 ttymxc1 vt102

::shutdown:/etc/init.d/reboot
::ctrlaltdel:/sbin/reboot

図2.4 標準のinittab


ttymxc1

図2.5 標準のsecuretty


inittabsecurettyを変更するには、ユーザーランドを再構築する必要があります。第1部「Atmark Distを使ったルートファイルシステムの作成」などを参照し、使用するプロダクト用に基本的な設定をして、一度ビルドしたAtmark Distを用意してください。そして、atmark-dist/vendors/AtmarkTechno/プロダク ト名/etc/inittabatmark-dist/vendors/AtmarkTechno/プロダク ト名/etc/securetty図2.6「ログインプロンプトをシリアルインターフェース2にしたinittab」図2.7「シリアルインターフェース2からのrootログインを許可したsecuretty」に示すように修正してユーザーランドをビルドし、作成されたルートファイルシステムイメージ(romfs.img.gz)をArmadilloのフラッシュメモリのユーザーランド領域に書き込んでください。Armadilloを再起動すると、ログインプロンプトもシリアルインターフェース2に表示されるようになります。

::sysinit:/etc/init.d/rc

::respawn:/sbin/getty -L 115200 ttymxc2 vt102

::shutdown:/etc/init.d/reboot
::ctrlaltdel:/sbin/reboot

図2.6 ログインプロンプトをシリアルインターフェース2にしたinittab


ttymxc1
ttymxc2

図2.7 シリアルインターフェース2からのrootログインを許可したsecuretty


ユーザーランドをDebian GNU/Linuxで構築している場合は、/etc/inittabのログインプロンプトに関連する部分は図2.8「Armadillo-400シリーズ用Debian GNU/Linuxのinittab(抜粋)」のようになっています。ログインプロンプトをシリアルインターフェース2に出力するには、ttymxc1をttymxc2に変更します。また、securettyは、図2.9「Armadillo-400シリーズ用Debian GNU/Linuxのsecuretty(抜粋)」のようになっているので、ttymxc2を追加します。

# Example how to put a getty on a serial line (for a terminal)
#
#T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
#T1:23:respawn:/sbin/getty -L ttyS1 9600 vt100

# Example how to put a getty on a modem line.
#
#T3:23:respawn:/sbin/mgetty -x0 -s 57600 ttyS3

T1:23:respawn:/sbin/getty -L ttymxc1 115200 vt100

図2.8 Armadillo-400シリーズ用Debian GNU/Linuxのinittab(抜粋)


# MXC serial ports
ttymxc0
ttymxc1

図2.9 Armadillo-400シリーズ用Debian GNU/Linuxのsecuretty(抜粋)


2.1.2. コンソールへの出力を止める

実際の製品においては、コンソールが使える事自体が問題となる場合もあるでしょう。コンソールへの出力を止めるのも、「コンソールとして別のシリアルインターフェースを使用する」と同様の手順で行うことができます。

コンソールへの出力を止めるには、カーネルパラメータのconsolenoneを指定します。 

hermit> setenv console=none

図2.10 コンソールへの出力を止める


また、/etc/inittabgettyに関する行は、削除するかコメントアウトします。/etc/securettyに関する設定は、ログインプロンプトを表示しなければ関係ないので、そのままで構いません[5]

::sysinit:/etc/init.d/rc

#::respawn:/sbin/getty -L 115200 ttymxc2 vt102

::shutdown:/etc/init.d/reboot
::ctrlaltdel:/sbin/reboot

図2.11 ログインプロンプトを表示しない(標準のinittab)


# Example how to put a getty on a serial line (for a terminal)
#
#T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100
#T1:23:respawn:/sbin/getty -L ttyS1 9600 vt100

# Example how to put a getty on a modem line.
#
#T3:23:respawn:/sbin/mgetty -x0 -s 57600 ttyS3

#T1:23:respawn:/sbin/getty -L ttymxc1 115200 vt100

図2.12 ログインプロンプトを表示しない(Armadillo-400シリーズ用Debian GNU/Linuxのinittab)


2.2. I2C接続A/Dコンバーター

Armadillo-400 シリーズでは、CON11とCON14にI2Cバスが出ており、外部のデバイスと接続することができます。Armadillo-400 シリーズLCD拡張ボードやArmadillo-400 シリーズRTCオプションモジュール、Armadillo-400シリーズWLANモジュールでは、I2Cバスにリアルタイムクロックを接続しています。ここでは、I2CバスにA/Dコンバーターを接続する方法を紹介します。

使用するソフトウェア、デバイスは以下のとおりです。

  1. Linuxカーネル: linux-3.14-at4 以降
  2. A/Dコンバーター: PCF8591(NXP社製)

2.2.1. I2C概要

I2C(Inter Integrated Circuit)は、IC間のデータ転送に使われる2線式の通信方式です。正式にはI2Cと記述し、I-squared-C(アイ・スクエアド・シー)と読みます。[6]

I2Cではシリアルデータライン(SDA)とシリアルクロック(SCL)の2本の信号線のみを使用して通信をおこないます。この2本の信号線に複数のデバイスを接続し、バスを構成することができます。I2Cバスに接続するデバイスの出力段はオープンドレイン(またはオープンコレクタ)とし、信号線はプルアップします。そのため、全てのデバイスがHighを出力しているときだけ信号線はHighとなり、どれかひとつのデバイスがLowを出力すると信号線はLowとなります[7]

I2Cバスに接続されたデバイスは、その役割によってマスタとスレーブに分かれます。マスタとスレーブは、一つのI2Cバスにそれぞれ複数接続することができます。通信は必ずマスタが開始し、バスに接続されたスレーブとデータのやりとりを行います。スレーブはそれぞれ固有のアドレスを持っており、マスタはアドレスを指定することで通信をおこなうスレーブを特定します。Armadillo-400シリーズは、I2Cマスタとなることができます。

I2Cでは、1クロックにつき1bitのデータの転送を行います。そのため、データ転送速度はクロックの速度によって決まります。I2Cにはいくつかのモードがあり、モードごとに転送速度の上限が決まっています。標準モードでは0から100kbit/sec、ファーストモードでは400kbit/secまで、ハイスピードモードでは3.4Mbit/secまでとなっています。Armadillo-400シリーズは、ファーストモードまで対応しています。

データの転送はクロックに同期して行われます。クロックは転送を開始するマスタが生成します。SCLがHighの時にSDAをHighからLowに変化させることで転送が開始されます。これをスタートコンディションと呼びます。また、SCLがHighの時にSDAをLowからHighに変化させることでデータの転送を終了します。これをストップコンディションといいます。スタートコンディションとストップコンディションの発行は、必ずマスターによって行われます。

I2Cでは、1回の転送で複数のバイトを送受信することができます。各バイトの長さは必ず8bitになります。データは最上位ビット(MSB)から順に送信されます。SCLがHighの時のSDAのレベルによって、論理が0(SDA=Low)か1(SDA=High)かが決定します。SCLがHighの間、SDAのレベルは一定でなければなりません。SDAのレベルを変更できるのは、SCLがLowの時だけです。

各バイト(8bit)の転送ごとに、アクノリッジ(ACK)が必要になります。受信側は、正常に通信がおこなえている場合、アクノリッジ信号として、SDAをLowにします。アクノリッジ信号としてSDAをHighにすることで、送信側にデータの終了を知らせることができます。

I2Cプロトコル

図2.13 I2Cプロトコル


2.2.2. サンプル回路

今回使用するA/DコンバーターPCF8591は、以下の特長を持ちます。

  1. 単一電源(2.5Vから6V)動作
  2. I2C接続
  3. 3つのハードウェアピンでアドレス指定可能
  4. オンチップ サンプルアンドホールド回路
  5. 8bit分解能逐次比較型A/D入力×4
  6. 8bit分解能D/A×1

Armadillo-400シリーズと、A/Dコンバーターとの接続を図2.14「I2C接続A/Dコンバーター回路図」に示します。Armadillo-400シリーズのCON14から出ているI2C2にPCF8591を接続します。アドレスを指定するA0、A1、A2ピンは全てプルダウンしておきます。AIN0からAIN3ピンがアナログ入力ピンです。アナログ入力にかかる電圧を、20kΩの可変抵抗で変えられるようにしています。リファレンス電圧VREFに電源電圧と同じ3.3Vを入力しているため、0Vから3.3Vの範囲のアナログ入力を8bit(256段階)のデジタル値に変換します。

I2C接続A/Dコンバーター回路図

図2.14 I2C接続A/Dコンバーター回路図


2.2.3. PCF8591通信プロトコル

PCF8591は内部にレジスタを持っており、レジスタの値を変更することで、デバイスの機能を変更することができます。レジスタへの書き込みは、図2.15「PCF8591通信フォーマット(コントロールバイト書き込み)」に示すフォーマットにしたがっておこないます。

PCF8591通信フォーマット(コントロールバイト書き込み)

図2.15 PCF8591通信フォーマット(コントロールバイト書き込み)


まず、マスタがスタートコンディションを発行し、アドレスバイトを送信します。スレーブからのACKが返ってきたら、続いてコントロールバイトを送信します。再びスレーブからのACKが返ってきたら、ストップコンディションを発行して、レジスタへの書き込みを完了します。

アドレスバイトのフォーマットを図2.16「PCF8591通信フォーマット(アドレスバイト)」に示します。上位7bitでスレーブのアドレスを指定します。A2、A1、A0は、それぞれPCF8591のA2、A1、A0ピンのレベルに対応した値とします。今回の例では全てプルダウンしたので、A2、A1、A0は全て0を指定します。書き込み転送の場合は、R/W*を0とします。

PCF8591通信フォーマット(アドレスバイト)

図2.16 PCF8591通信フォーマット(アドレスバイト)


コントロールバイトのフォーマットを図2.17「PCF8591通信フォーマット(コントロールバイト)」に示します。AOEはアナログ出力有効の場合、1を指定します。AISEL1とAISEL0で、どのようにアナログ入力ピンを使用するか指定します。シングルエンド(方線接地)入力×4とする場合は0b00、ディファレンシャル(差動)入力×3とする場合は0b01、シングルエンド入力×2+ディファレンシャル入力×1とする場合は0b10、ディファレンシャル入力×2とする場合は0b11となります。AINCはオートインクリメント有効の時、1を指定します。CH1とCH0にはアナログ入力に使用するチャンネルを指定します。今回の例では、AOE、AISEL1、AISEL0、AINCを全て0とします。

PCF8591通信フォーマット(コントロールバイト)

図2.17 PCF8591通信フォーマット(コントロールバイト)


PCF8591からデータを読み出すと、CHで指定したチャンネルの値を得ることができます。データの読み出しは、図2.18「PCF8591通信フォーマット(データバイト読み出し)」に示すフォーマットでおこないます。

PCF8591通信フォーマット(データバイト読み出し)

図2.18 PCF8591通信フォーマット(データバイト読み出し)


まず、マスタがスタートコンディションを発行し、アドレスバイトを送信します。スレーブはアドレスバイトへのACKを返し、続いてデータバイトを送信します。マスタはデータバイトを受信したあと、続けてデータが欲しい場合はACKを返します。データバイトの受信を終了したい場合はNACKを返し、ストップコンディションを発行します。

アドレスバイトのフォーマットは、R/W*が1になる以外、コントロールバイト書き込みの場合と同じです。

データバイトのフォーマットを図2.19「PCF8591通信フォーマット(データバイト)」に示します。PCF8591では、アクノリッジ信号のトレイリングエッジで、指定されたチャンネルの入力電圧がサンプルされ、データバイトの送信中にA/D変換がおこなわれます。そのため、データバイトで転送される値は、一つ前のデータバイト送信中に変換された値となります。なお、パワーオンリセット後の最初のデータバイトで転送される値は、0x80となります。

PCF8591通信フォーマット(データバイト)

図2.19 PCF8591通信フォーマット(データバイト)


2.2.4. i2cdevドライバー

一般的に、I2C接続のデバイスをLinuxシステムで使用する場合、I2Cスレーブデバイス用のデバイスドライバーを作成して、デバイスの制御をおこないます。今回は、PCF8591専用のデバイスドライバーを作成するのではなく、汎用のi2cdevドライバーを使用することにします。i2cdevドライバーを使用すると、デバイスファイルインターフェースを経由して、ユーザーランドで動作するアプリケーションプログラムからデバイスの制御をおこなうことができます。Armadillo-400シリーズでは、標準のカーネルでi2cdevドライバーが有効になっているため、特に何も設定しなくとも使用可能です。

i2cdevドライバーでは、/dev/i2c-N(Nは0から始まる数値文字)デバイスファイルに対して、open/close/read/write/ioctlシステムコールを発行することで、I2Cデバイスの制御をおこないます。Armadillo-400シリーズで使用できるデバイスファイルを表2.1「I2Cバスとデバイスファイルの対応」に示します。

表2.1 I2Cバスとデバイスファイルの対応

I2Cバス コネクタ デバイスファイル

I2C1

なし[a]

/dev/i2c-0

I2C2

CON14

/dev/i2c-1

I2C3

CON11

/dev/i2c-2

[a] ボード内蔵バスとして使用。


アプリケーションプログラムでI2Cデバイスの制御をおこなうには、まず、デバイスファイルをオープンします。

int fd;

fd = open("/dev/i2c-0", O_RDWR);

図2.20 I2Cデバイスファイルのオープン


デバイスをオープンした後、通信を行うスレーブデバイスを特定するため、スレーブデバイスのアドレスを設定します。これには、ioctlシステムコールを使用します。I2C_SLAVEなどのi2cdevを使用する際に必要となる定義は、<linux/i2c-dev.h>で定義されています。

#include <linux/i2c-dev.h>

int addr = 0x40; /* The I2C address */

ioctl(fd, I2C_SLAVE, addr);

図2.21 I2Cスレーブデバイスのアドレス指定


I2Cスレーブデバイスとのデータ転送をおこなうには、単純にread/writeシステムコールを実行するだけです。スタート/ストップコンディションの発行、アドレスバイトの生成と送信、アクノリッジの処理などは、全てドライバーがおこなってくれるので、アプリケーションプログラム側ではそれらを意識する必要はありません。

i2cdevドライバーに関する詳しい情報は、linux-3.14-at[version]/Documentation/i2c/dev-interfaceを参照してください。

2.2.5. サンプルプログラム

PCF8591と通信をおこない、A/D変換結果を表示するサンプルプログラムを紹介します。プログラムは図2.22「PCF8591を使用したA/D変換プログラム」に示すように、オプションとしてデバイスファイル名とA/D変換をおこなうチャンネルを指定することにします。

adc_pcf8591 <-d|--device FILENAME> [-c|--channel CHANNEL]

図2.22 PCF8591を使用したA/D変換プログラム


main関数を図2.23「adc_pcf8591.c」に示します。pcf8591_で始まる名前の関数で、実際の制御をおこないます。main関数では、以下の処理をおこなっています。

  1. pcf8591_open()で、デバイスファイル名とアドレスを指定してデバイスをオープンする
  2. pcf8591_read()で、チャンネルを指定してデジタル値を読み出す
  3. デジタル値を電圧に変換して表示
  4. pcf8591_close()で、デバイスをクローズする
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>

#include "pcf8591.h"

#define MAIN_C
#include "exitfail.h"

#define BASENAME(p)   ((strrchr((p), '/') ? : ((p) - 1)) + 1)

#define REF_VOLTAGE     3.3     /* 基準電圧[V] */
#define I2C_ADDR        0x48    /* PCF8591のI2Cアドレス */
#define DEFAULT_CH      0       /* オプションで指定されなかった場合に使用するチャンネル */

static void usage(char *prog)
{
        printf("Usage: %s <-d|--device FILENAME> [-c|--channel CHANNEL]\n", BASENAME(prog));
}

static void parse_arg(int argc, char *argv[], char **device, int *ch)
{
        int c;
        char *endptr;

        *device = NULL;

        for(;;) {
                int option_index = 0;
                static struct option long_options[] = {
                        /* name,        has_arg,           flag, val*/
                        {"device",      required_argument, NULL, 'd'},
                        {"channel",     required_argument, NULL, 'c'},
                        {0,             0,                 0,    0},
                };

                c = getopt_long(argc, argv, "d:c:",
                                long_options, &option_index);
                if (c == -1)
                        break;

                switch (c) {
                case 'd':
                        *device = optarg;
                        break;
                case 'c':
                        errno = 0;
                        *ch = strtol(optarg, &endptr, 0);
                        if (errno != 0 || optarg == endptr)
                                goto err;

                        if (*ch < PCF8591_CH_MIN || PCF8591_CH_MAX < *ch)
                                goto err;
                        break;
                default:
                        goto err;
                }
        }

        if (*device == NULL)
                goto err;

        return;

err:
        usage(argv[0]);
        exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
        struct pcf8591 *adc;
        char *device;
        int channel = DEFAULT_CH;
        double voltage;
        uint8_t digital_code;
        int ret;

        exitfail_init();

        parse_arg(argc, argv, &device, &channel);

        adc = pcf8591_open(device, I2C_ADDR);
        if (adc == NULL)
                exitfail_errno("pcf8591_open");

        ret = pcf8591_read(adc, channel, &digital_code);
        if (ret != 0)
                exitfail_errno("pcf8591_read");

        voltage = digital_code * REF_VOLTAGE /
                  ((1 << PCF8591_RESOLUTION_BITS) - 1);

        printf("%1.3fV\n", voltage);

        ret = pcf8591_close(adc);
        if (ret != 0)
                exitfail_errno("pcf8591_close");

        return EXIT_SUCCESS;
}

図2.23 adc_pcf8591.c


実際の処理は図2.25「pcf8591.c」に記述してあります。図2.24「pcf8591.h」には、図2.25「pcf8591.c」で記述されている関数のプロトタイプ宣言が記述されています。

#ifndef PCF8591_H
#define PCF8591_H

#include <stdint.h>

#define PCF8591_RESOLUTION_BITS 8   /* PCF8591の分解能(bit) */

#define PCF8591_CH_MIN 0 /* PCF8591で指定できるアナログ入力チャンネルの最小値 */
#define PCF8591_CH_MAX 3 /* PCF8591で指定できるアナログ入力チャンネルの最大値 */

struct pcf8591;

struct pcf8591 *pcf8591_open(const char *dev_path, int addr);
int pcf8591_read(struct pcf8591 *adc, int ch, uint8_t *digit);
int pcf8591_close(struct pcf8591 *adc);

#endif /* PCF8591_H */

図2.24 pcf8591.h


#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <linux/i2c-dev.h>

#include "pcf8591.h"

#define PCF8591_ADDR_MIN 0x48 /* PCF8591のアドレスの最小値 */
#define PCF8591_ADDR_MAX 0x4F /* PCF8591のアドレスの最大値 */

#define PCF8591_RETRY_MAX 3 /* read()とwrite()のリトライ回数 */

/* PCF8591のCONTROL BYTEの設定 */
#define PCF8591_ANALOG_OUTPUT_ENABLE          (1 << 6)
#define PCF8591_ANALOG_OUTPUT_DISABLE         (0 << 6)

#define PCF8591_SINGLE_ENDED                  (0 << 4)
#define PCF8591_THREE_DIFFERENTIAL            (1 << 4)
#define PCF8591_SINGLE_ENDED_AND_DIFFERENTIAL (2 << 4)
#define PCF8591_TWO_DIFFERENTIAL              (3 << 4)

#define PCF8591_AUTO_INCREMENT_ENABLE         (1 << 2)
#define PCF8591_AUTO_INCREMENT_DISABLE        (0 << 2)

#define PCF8591_CH_SHIFT                      (0)


struct pcf8591 {
        int fd;
};

static ssize_t write_uninterruptible(int fd, const void *buf, size_t count,
                                     int retry)
{
        ssize_t ret;
        int i;

        for (i = 0; i < retry; i++) {
                ret = write(fd, buf, count);
                if (ret < 0 && errno != EINTR)
                        return -1;

                if ((size_t)ret == count)
                        return ret;
        }

        errno = ETIMEDOUT;
        return -1;
}

static ssize_t read_uninterruptible(int fd, void *buf, size_t count, int retry)
{
        size_t read_length = 0;
        ssize_t ret;
        int i;

        for (i = 0; i < retry; i++) {
                ret = read(fd, buf + read_length, count - read_length);
                if (ret < 0) {
                        if (errno == EINTR)
                                continue;
                        else
                                return -1;
                }

                read_length += ret;
                if (read_length == count)
                        return read_length;
        }

        errno = ETIMEDOUT;
        return -1;
}

/**
 * 指定されたデバイスファイルをオープンする
 *
 * @param dev_path  PCF8591が接続されたi2cdevのデバイスファイルへのパス。
 * @param addr      PCF8591のアドレス。
 *
 * @return 成功するとstruct pcf8591へのポインタを返す。
 *         失敗するとNULLを返す。その際、適切なerrnoを設定する。
 */
struct pcf8591 *pcf8591_open(const char *dev_path, const int addr)
{
        struct pcf8591 *adc;
        int error;
        int ret;

        if (dev_path == NULL ||
            addr < PCF8591_ADDR_MIN || PCF8591_ADDR_MAX < addr) {
                errno = EINVAL;
                return NULL;
        }

        adc = calloc(1, sizeof(struct pcf8591));
        if (adc == NULL)
                return NULL;

        adc->fd = open(dev_path, O_RDWR);
        if (adc->fd < 0) {
                error = errno;
                goto err1;
        }

        ret = ioctl(adc->fd, I2C_SLAVE, addr);
        if (ret < 0) {
                error = errno;
                goto err2;
        }

        return adc;

err2:
        close(adc->fd);
err1:
        free(adc);
        errno = error;
        return NULL;
}

/**
 * 指定されたチャンネルの値をA/Dコンバータにセットし、
 * PCF8591からA/D変換結果を読み込む。
 * 読み込んだA/D変換結果をdigitに格納する。
 *
 * @param adc    オープン済みのPCF8591デバイスへのポインタ。
 * @param ch     サンプリングするチャンネル。
 * @param digit  A/D変換された値が格納される。
 *
 * @return 成功すると0を返し、digitにA/D変換された値を格納する。
 *         失敗すると-1を返す。その際、適切なerrnoを設定する。
 */
int pcf8591_read(struct pcf8591 *adc, const int ch, uint8_t *digit)
{
        uint8_t buf[2];
        int ret;

        if (adc == NULL || digit == NULL ||
            ch < PCF8591_CH_MIN || PCF8591_CH_MAX < ch) {
                errno = EINVAL;
                return -1;
        }

        buf[0] = PCF8591_ANALOG_OUTPUT_DISABLE |
                 PCF8591_SINGLE_ENDED |
                 PCF8591_AUTO_INCREMENT_DISABLE |
                 (ch << PCF8591_CH_SHIFT);

        ret = write_uninterruptible(adc->fd, buf, 1, PCF8591_RETRY_MAX);
        if (ret < 0)
                return -1;

        /* 現在のアナログ入力を変換した値を取得するために、2バイト読み込む */
        ret = read_uninterruptible(adc->fd, buf, 2, PCF8591_RETRY_MAX);
        if (ret < 0)
                return -1;

        *digit = buf[1];

        return 0;
}

/**
 * 指定されたPCF8591デバイスをクローズする
 *
 * @param adc オープン済みのPCF8591デバイスへのポインタ。
 *
 * @return 成功すると0を返す。
 *         失敗すると-1を返す。その際、適切なerrnoを設定する。
 */
int pcf8591_close(struct pcf8591 *adc)
{
        int fd;

        if (adc == NULL) {
                errno = EINVAL;
                return -1;
        }

        fd = adc->fd;
        free(adc);

        return close(fd);
}

図2.25 pcf8591.c


サンプルプログラムをビルドするmakefileを図2.26「adc_pcf8591をビルドするmakefile」に示します。

CROSS   := arm-linux-gnueabi

ifneq ($(CROSS),)
CROSS_PREFIX    := $(CROSS)-
endif

CC      = $(CROSS_PREFIX)gcc
CFLAGS  = -Wall -Wextra -O2 -I../common

TARGET  = adc_pcf8591

all: $(TARGET)

adc_pcf8591: adc_pcf8591.o pcf8591.o
        $(CC) $(CFLAGS) -o $@ $^

clean:
        $(RM) *~ *.o $(TARGET)

%.o: %.c
        $(CC) $(CFLAGS) -c -o $@ $<

図2.26 adc_pcf8591をビルドするmakefile


adc_pcf8591.cpcf8591.hpcf8591.cMakefileを同じディレクトリに置き、一つ上のcommonディレクトリに第2部でも使用したexitfail.hを置いておきます。makeコマンドを実行すると、adc_pcf8591がビルドされます。

[ATDE ~/i2c-adc]$ make
arm-linux-gnueabi-gcc -Wall -Wextra -O2 -I../common -c -o adc_pcf8591.o adc_pcf8591.c
arm-linux-gnueabi-gcc -Wall -Wextra -O2 -I../common -c -o pcf8591.o pcf8591.c
arm-linux-gnueabi-gcc -Wall -Wextra -O2 -I../common -o adc_pcf8591 adc_pcf8591.o pcf8591.o

図2.27 adc_pcf8591のビルド


生成されたadc_pcf8591をArmadilloにコピーして、実行権限をつけてください。adc_pcf8591を実行すると、図2.28「adc_pcf8591コマンドの実行」に示すような結果が得られます。

[armadillo ~]# chmod +x adc_pcf8591
[armadillo ~]# ./adc_pcf8591 --device /dev/i2c-1 --channel 0
3.235V

図2.28 adc_pcf8591コマンドの実行


2.3. SPI接続A/Dコンバーター

Armadillo-400 シリーズでは、CON9をSPIバスとして使用することができます。ここでは、SPIバスにA/Dコンバータを接続する方法を紹介します。

使用するソフトウェア、デバイスは以下のとおりです。

  1. Linuxカーネル: linux-3.14-at4 以降
  2. A/Dコンバーター: MCP3204(Microchip社製)

2.3.1. SPI概要

SPI(Serial Peripheral Interface)は、IC間のデータ転送に使われる4線式の通信方式です。SPIでは、シリアルクロック(SCLK)、マスタアウトプット/スレーブインプット(MOSI)、マスタインプット/スレーブアウトプット(MISO)、スレーブセレクト(SS)の4本の信号線を使用して通信をおこないます。これらの信号線に、複数のデバイスを接続してバスを構成することができます。

SPIバスに接続されたデバイスは、その役割によってマスタとスレーブに分かれます。SPIでは、1つのバスに1つのマスタと複数のスレーブを接続できます。通信は必ずマスタが開始し、バスに接続されたスレーブとデータのやりとりを行います。マスタは、通信をおこなうスレーブをSS信号によって特定します。そのため、通常、スレーブ1つにつき1本のSS信号を接続します。Armadillo-400シリーズは、SPIマスタとなることができます。

SPIでは、1クロックにつき1bitのデータ転送をおこないます。そのため、データ転送速度はクロックの速度によって決まります。Armadillo-400シリーズは、約16MHzまで対応できます。

データの転送は、マスタがSS信号をアサートすることで開始されます。SCLK信号に同期して、マスターからスレーブへのデータをMOSIに出力し、スレーブからマスタへのデータをMISOから入力します。データの入出力をおこなう線が分かれているため、全二重の通信をおこなうことができます。一度の転送で送受信できるビット数はデバイスごとに異なり、SPIプロトコルとしての制限はありません。

SPIでは、クロックのポラリティとフェーズを指定できます。それぞれの設定をCPOLとCPHAで表します。

  1. CPOL=0: クロックを出力していないときSCLKをLowに保ちます。

    1. CPHA=0: クロックの立ち上がりでデータをラッチします。
    2. CPHA=1: クロックの立ち下がりでデータをラッチします。
  2. CPOL=1: クロックを出力していないときSCLKをHighに保ちます。

    1. CPHA=0: クロックの立ち下がりでデータをラッチします。
    2. CPHA=1: クロックの立ち上がりでデータをラッチします。

CPOLとCPHAの組み合わせを、SPIモードで表現する場合もあります。

表2.2 SPIモード

モード CPOL CPHA

0

0

0

1

0

1

2

1

0

3

1

1


SPIプロトコル

図2.29 SPIプロトコル


2.3.2. サンプル回路

今回使用するA/DコンバーターMCP3204は、以下の特長を持ちます。

  1. 単一電源(2.7Vから5.5V)動作
  2. SPI接続
  3. サンプリング速度 最大100ksps(Vdd=5V時)、50ksps(Vdd=2.7V時)
  4. オンチップ サンプルアンドホールド
  5. 12bit分解能逐次比較型A/D入力×4

Armadillo-400シリーズと、A/Dコンバーターとの接続を図2.30「SPI接続A/Dコンバーター回路図」に示します。Armadillo-400シリーズのCON9から出ているCSPI3にMCP3204を接続します。SS信号には、CSPI3のSS0を使用します。AIN0からAIN3ピンがアナログ入力ピンです。アナログ入力にかかる電圧を、20kΩの可変抵抗で変えられるようにしています。リファレンス電圧VREFに電源電圧と同じ3.3Vを入力しているため、0Vから3.3Vの範囲のアナログ入力を12bit(4096段階)のデジタル値に変換します。

SPI接続A/Dコンバーター回路図

図2.30 SPI接続A/Dコンバーター回路図


2.3.3. MCP3204通信プロトコル

MCP3204は、SPIモード0(CPOL=0、CPHA=0)またはSPIモード3(CPOL=1、CPHA=1)で通信をおこないます。MCP3204の通信フォーマットを図2.31「MCP3204通信フォーマット」に示します。

MCP3204通信フォーマット

図2.31 MCP3204通信フォーマット


MOSIから1を出力することで、転送の開始をMCP3204に指示します。SGL/DIFF*、D2、D1、D0の組み合わせにより、A/D変換をおこなうチャンネルを指定します。D0以降、MOSIから出力されるデータは意味を持ちません。

表2.3 MCP3204チャンネル指定

SGL/DIFF* D2[a] D1 D0 入力構成 チャンネル

1

d.c.

0

0

シングルエンド

CH0

1

d.c.

0

1

シングルエンド

CH1

1

d.c.

1

0

シングルエンド

CH2

1

d.c.

1

1

シングルエンド

CH3

0

d.c.

0

0

ディファレンシャル

CH0=IN+、CH1=IN-

0

d.c.

0

1

ディファレンシャル

CH0=IN-、CH1=IN+

0

d.c.

1

0

ディファレンシャル

CH2=IN+、CH3=IN-

0

d.c.

1

1

ディファレンシャル

CH2=IN-、CH3=IN+

[a] MCP3204ではD2は意味を持ちません。


MCP3204は、11番目のクロックの上昇部でアナログ入力のサンプリングを開始し、次のクロックの下降部で完了します。A/D変換結果は、B11からB0に出力されます。

2.3.4. spidevドライバー

一般的に、SPI接続のデバイスをLinuxシステムで使用する場合、SPIスレーブデバイス用のデバイスドライバーを作成して、デバイスの制御をおこないます。今回は、MCP3204専用のデバイスドライバーを作成するのではなく、汎用のspidevドライバーを使用することにします。spidevドライバーを使用すると、デバイスファイルインターフェースを経由して、ユーザーランドで動作するアプリケーションプログラムからデバイスの制御をおこなうことができます。Armadillo-400シリーズでは、標準のカーネルではspidevドライバーが有効になっていないため、spidevを使用するにはカーネルの設定を変更する必要があります。

spidevドライバーでは、/dev/spidevN.M (NM は0から始まる数値文字)デバイスファイルに対して、open/close/read/write/ioctlシステムコールを発行することで、SPIデバイスの制御をおこないます。Armadillo-400シリーズで使用できるデバイスファイルを表2.4「SPIバスとデバイスファイルの対応」に示します。

表2.4 SPIバスとデバイスファイルの対応

SPIバス コネクタ デバイスファイル

CSPI1

CON9

/dev/spidev0.M (Mは0または1)

CSPI3

CON9

/dev/spidev2.M (Mは0、1、2、3のいずれか)


アプリケーションプログラムでSPIデバイスの制御をおこなうには、まず、デバイスファイルをオープンします。

int fd;

fd = open("/dev/spidev0.0", O_RDWR);

図2.32 SPIデバイスファイルのオープン


ioctlシステムコールにより、SPIの設定を変更することができます。

  1. SPI_IOC_RD_MODE, SPI_IOC_WR_MODE: 読み出しまたは書き込み時に使用するSPIモードを設定します。
  2. SPI_IOC_RD_LSB_FIRST, SPI_IOC_WR_LSB_FIRST: 読み出しまたは書き込み時にLSBから転送するか、MSBから転送するか設定します。
  3. SPI_IOC_RD_BITS_PER_WORD, SPI_IOC_WR_BITS_PER_WORD: 1回の読み出しまたは書き込みで転送するビット数を設定します。
  4. SPI_IOC_RD_MAX_SPEED_HZ, SPI_IOC_WR_MAX_SPEED_HZ: 読み出しまたは書き込みの最大転送速度を設定します。

read/writeシステムコールを使用すると、半二重通信をおこなうことができます。全二重通信をおこなうには、ioctlシステムコールのSPI_IOC_MESSAGE(N)メッセージを使用します。SPI_IOC_MESSAGE(N)の使用方法は、サンプルプログラムで解説します。

spidevドライバーに関する詳しい情報は、linux-3.14-at[version]/Documentation/spi/spidev を参照してください。

2.3.5. カーネルコンフィギュレーション

Armadillo-400シリーズの標準のカーネルでは、SPIドライバーは有効になっていません。そのため、SPIマスタドライバーとspidevドライバーが有効になったカーネルを作成する必要があります。

まず、カーネルのソースコードアーカイブを取得します。ここでは、Armadilloサイトからダウンロードしてくることにします。

[ATDE ~]$ wget http://armadillo.atmark-techno.com/files/downloads/armadillo-4x0/source/kernel/linux-3.14-at[version].tar.gz
[ATDE ~]$ tar xzvf linux-3.14-at[version].tar.gz

図2.33 Linuxカーネルの取得と展開


次にArmadillo-400シリーズの標準コンフィギュレーションを適用します。

[ATDE ~]$ cd linux-3.14-at[version]/
[ATDE ~/linux-3.14-at[version]]$ make ARCH=arm armadillo4x0_defconfig

図2.34 LinuxカーネルにArmadillo-400シリーズ標準コンフィギュレーションを適用する


続いて、menuconfigを使用して、図2.36「SPIドライバーを有効にする」及び図2.37「SPIに使用するピンを指定する」に示すようにカーネルコンフィギュレーションを変更します。

[ATDE ~/linux-3.14-at[version]]$ make ARCH=arm menuconfig

図2.35 menuconfigを使用してカーネルコンフィギュレーションを変更する


Linux Kernel Configuration
  Device Drivers  --->
    [*] SPI support  --->                        チェックを入れる
      {*}   Utilities for Bitbanging SPI masters M から * に変更する
      <*>   Freescale i.MX SPI controllers       M から * に変更する
      <*>   User mode SPI device driver support  チェックを入れる

図2.36 SPIドライバーを有効にする


Linux Kernel Configuration
  System Type  --->
    Freescale i.MX support  --->
      Armadillo-400 Board options  --->
        [ ] Enable UART5 at CON9           チェックを外す
        [*] Enable SPI3 at CON9            チェックを入れる
        [*]   Enable SPI3_SS0 at CON9_16   標準で選択されているのでそのまま
        [ ]   Enable SPI3_SS1 at CON9_18   チェックを外す
        [ ]   Enable SPI3_SS2 at CON9_15   チェックを外す
        [ ]   Enable SPI3_SS3 at CON9_17   チェックを外す

図2.37 SPIに使用するピンを指定する


また、カーネルのソースコードにも一部修正が必要になります。SPIスレーブデバイスドライバーを使用するには、spi_board_infoを登録する必要があります。spi_board_infoの登録は、linux-3.14-at[version]/arch/arm/mach-imx/armadillo4x0_extif.cでおこなっています。armadillo4x0_spi2_board_infoを、図2.38「CSPI3、SS0をspidevで使用する修正」に示すように修正してください。

static struct spi_board_info armadillo4x0_spi2_board_info[] __initdata = {
        {
                .modalias = "spidev",
                .max_speed_hz = 1000000,
                .bus_num = 2,
                .chip_select = 0,
        },
};

図2.38 CSPI3、SS0をspidevで使用する修正


コンフィギュレーションの変更と、ソースの修正をおこなったら、カーネルをビルドします。

[ATDE ~/linux-3.14-at[version]]$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- && gzip -c arch/arm/boot/Image > linux.bin.gz

図2.39 Linuxカーネルをビルドする


正常にビルドが完了すると、linux-3.14-at[version]/linux.bin.gzにカーネルイメージが作成されます。linux.bin.gzをArmadilloのフラッシュメモリのカーネル領域に書き込んでください。

2.3.6. サンプルプログラム

MCP3204と通信をおこない、A/D変換結果を表示するサンプルプログラムを紹介します。プログラムは図2.40「MCP3204を使用したA/D変換プログラム」に示すように、オプションとしてデバイスファイル名とA/D変換をおこなうチャンネルを指定することにします。

adc_mcp3204 <-d|--device FILENAME> [-c|--channel CHANNEL]

図2.40 MCP3204を使用したA/D変換プログラム


main関数を図2.41「adc_mcp3204.c」に示します。mcp3204_というプレフィックスがついた関数で、実際の制御をおこないます。main関数では、以下の処理をおこなっています。

  1. mcp3204_open()で、デバイスファイル名を指定してデバイスをオープンする
  2. mcp3204_read()で、チャンネルを指定してデジタル値を読み出す
  3. デジタル値を電圧に変換して表示
  4. mcp3204_close()で、デバイスをクローズする
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>

#include "mcp3204.h"

#define MAIN_C
#include "exitfail.h"

#define BASENAME(p)   ((strrchr((p), '/') ? : ((p) - 1)) + 1)

#define REF_VOLTAGE     3.3     /* 基準電圧[V] */
#define DEFAULT_CH      0       /* オプションで指定されなかった場合に使用するチャンネル */

static void usage(const char *prog)
{
        printf("usage: %s <-d|--device FILENAME> [-c|--channel CHANNEL]\n", BASENAME(prog));
}

static void parse_arg(int argc, char *argv[], const char **device, int *ch)
{
        int c;
        char *endptr;

        *device = NULL;

        for(;;) {
                int option_index = 0;
                static struct option long_options[] = {
                        /* name,        has_arg,           flag, val*/
                        {"device",      required_argument, NULL, 'd'},
                        {"channel",     required_argument, NULL, 'c'},
                        {0,             0,                 0,    0},
                };

                c = getopt_long(argc, argv, "d:c:",
                                long_options, &option_index);
                if (c == -1)
                        break;

                switch (c) {
                case 'd':
                        *device = optarg;
                        break;
                case 'c':
                        errno = 0;
                        *ch = strtol(optarg, &endptr, 0);
                        if (errno != 0 || optarg == endptr)
                                goto err;

                        if (*ch < MCP3204_CH_MIN || MCP3204_CH_MAX < *ch)
                                goto err;
                        break;
                default:
                        goto err;
                }
        }

        if (*device == NULL)
                goto err;

        return;

err:
        usage(argv[0]);
        exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
        struct mcp3204 *adc;
        const char *device;
        int channel = DEFAULT_CH;
        double voltage;
        uint16_t digital_code;
        int ret;

        exitfail_init();

        parse_arg(argc, argv, &device, &channel);

        adc = mcp3204_open(device);
        if (adc == NULL)
                exitfail_errno("mcp3204_open");

        ret = mcp3204_read(adc, channel, &digital_code);
        if (ret != 0)
                exitfail_errno("mcp3204_read");

        voltage = digital_code * REF_VOLTAGE /
                ((1 << MCP3204_RESOLUTION_BITS) - 1);

        printf("%1.3fV\n", voltage);

        ret = mcp3204_close(adc);
        if (ret != 0)
                exitfail_errno("mcp3204_close");

        return EXIT_SUCCESS;
}

図2.41 adc_mcp3204.c


実際の処理は図2.43「mcp3204.c」に記述してあります。図2.42「mcp3204.h」には、図2.43「mcp3204.c」で記述されている関数のプロトタイプ宣言が記述されています。

#ifndef MCP3204_H
#define MCP3204_H

#include <stdint.h>

#define MCP3204_RESOLUTION_BITS 12 /* MCP3204の分解能(bit) */

#define MCP3204_CH_MIN 0 /* MCP3204で指定できるアナログ入力チャンネルの最小値 */
#define MCP3204_CH_MAX 3 /* MCP3204で指定できるアナログ入力チャンネルの最大値 */

struct mcp3204;

struct mcp3204 *mcp3204_open(const char *dev_path);
int mcp3204_read(struct mcp3204 *adc, int ch, uint16_t *digit);
int mcp3204_close(struct mcp3204 *adc);

#endif /* MCP3204_H */

図2.42 mcp3204.h


#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

#include <linux/types.h>
#include <linux/spi/spidev.h>

#include "mcp3204.h"

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

#define MCP3204_START_BIT    (1 << 2)
#define MCP3204_SINGLE_ENDED (1 << 1)
#define MCP3204_DIFFERENTIAL (0 << 1)
#define MCP3204_CH_SHIFT     (6)

#define MCP3204_SPI_MODE        (SPI_CPOL | SPI_CPHA)
#define MCP3204_SPI_SPEED_HZ    1000000
#define MCP3204_SPI_DELAY_USECS 0
#define MCP3204_SPI_BITS        8

struct mcp3204 {
        int fd;
};

/**
 * 指定されたデバイスファイルをオープンする
 *
 * @param dev_path  MCP3204が接続されたspidevデバイスファイルへのパス。
 *
 * @return 成功するとstruct mcp3204へのポインタを返す。
 *         失敗するとNULLを返す。その際、適切なerrnoを設定する。
 */
struct mcp3204 *mcp3204_open(const char *dev_path)
{
        struct mcp3204 *adc;
        uint8_t mode = MCP3204_SPI_MODE;
        int error;
        int ret;

        if (dev_path == NULL) {
                errno = EINVAL;
                return NULL;
        }

        adc = calloc(1, sizeof(struct mcp3204));
        if (adc == NULL)
                return NULL;

        adc->fd = open(dev_path, O_RDWR);
        if (adc->fd < 0) {
                error = errno;
                goto err1;
        }

        /* SPIモードを設定する */
        ret = ioctl(adc->fd, SPI_IOC_WR_MODE, &mode);
        if (ret < 0) {
                error = errno;
                goto err2;
        }

        return adc;

err2:
        close(adc->fd);
err1:
        free(adc);
        errno = errno;
        return NULL;
}

/**
 * 指定されたチャンネルのA/D変換結果を読み込む。
 * 読み込んだA/D変換結果をdigitに格納する。
 *
 * @param adc     オープン済みのMCP3204デバイスへのポインタ。
 * @param ch      サンプリングするチャンネル。
 * @param digit   A/D変換結果のデジタル値が格納される。
 *
 * @return 成功すると0を返し、voltageに電圧を格納する。
 *         失敗すると-1を返す。その際、適切な errno を設定する。
 */
int mcp3204_read(struct mcp3204 *adc, int ch, uint16_t *digit)
{
        uint8_t tx[3] = {0, };
        uint8_t rx[3] = {0, };
        struct spi_ioc_transfer tr;
        int ret;

        if (adc == NULL || digit == NULL ||
            ch < MCP3204_CH_MIN || MCP3204_CH_MAX < ch) {
                errno = EINVAL;
                return -1;
        }

        /* 送信バッファにスタートビット、SGL/DIFF*、D2、D1、D0をセットする */
        tx[0]            = MCP3204_START_BIT | MCP3204_SINGLE_ENDED;
        tx[1]            = ch << MCP3204_CH_SHIFT;

        /* 転送設定をセットする */
        tr.tx_buf        = (unsigned long)tx;
        tr.rx_buf        = (unsigned long)rx;
        tr.len           = ARRAY_SIZE(tx);
        tr.delay_usecs   = MCP3204_SPI_DELAY_USECS;
        tr.speed_hz      = MCP3204_SPI_SPEED_HZ;
        tr.bits_per_word = MCP3204_SPI_BITS;
        tr.cs_change     = 0;

        /* 全二重通信をおこなう */
        ret = ioctl(adc->fd, SPI_IOC_MESSAGE(1), &tr);
        if (ret < 1)
                return -1;

        /* 受信バッファからA/D変換結果を取り出す */
        *digit = (rx[1] & 0x0f) << 8;
        *digit |= rx[2];

        return 0;
}

/**
 * 指定されたMCP3204デバイスをクローズする
 *
 * @param adc オープン済みのMCP3204デバイスへのポインタ。
 *
 * @return 成功すると0を返す。
 *         失敗すると-1を返す。その際、適切なerrnoを設定する。
 */
int mcp3204_close(struct mcp3204 *adc)
{
        int fd;

        if (adc == NULL) {
                errno = EINVAL;
                return -1;
        }

        fd = adc->fd;
        free(adc);

        return close(fd);
}

図2.43 mcp3204.c


サンプルプログラムをビルドするmakefileを図2.44「adc_mcp3204をビルドするmakefile」に示します。

CROSS   := arm-linux-gnueabi

ifneq ($(CROSS),)
CROSS_PREFIX    := $(CROSS)-
endif

CC      = $(CROSS_PREFIX)gcc
CFLAGS  = -Wall -Wextra -O2  -I../common
LFLAGS  =

TARGET  = adc_mcp3204

all: $(TARGET)

adc_mcp3204: adc_mcp3204.o mcp3204.o
        $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

clean:
        $(RM) *~ *.o $(TARGET)

%.o: %.c
        $(CC) $(CFLAGS) -c -o $@ $<

図2.44 adc_mcp3204をビルドするmakefile


adc_mcp3204.cmcp3204.hmcp3204.cMakefileを同じディレクトリに置き、一つ上のcommonディレクトリに第2部でも使用したexitfail.hを置いておきます。makeコマンドを実行すると、adc_mcp3204がビルドされます。

[ATDE ~/spi-adc]$ make
arm-linux-gnueabi-gcc -Wall -Wextra -O2 -I../common -c -o adc_mcp3204.o adc_mcp3204.c
arm-linux-gnueabi-gcc -Wall -Wextra -O2 -I../common -c -o mcp3204.o mcp3204.c
arm-linux-gnueabi-gcc -Wall -Wextra -O2 -I../common -o adc_mcp3204 adc_mcp3204.o mcp3204.o

図2.45 adc_mcp3204のビルド


生成されたadc_mcp3204をArmadilloにコピーして、実行権限をつけてください。adc_mcp3204を実行すると、図2.46「adc_mcp3204コマンドの実行」に示すような結果が得られます。

[armadillo ~]# chmod +x adc_mcp3204
[armadillo ~]# ./adc_mcp3204 --device /dev/spidev2.0 --channel 0
3.235V

図2.46 adc_mcp3204コマンドの実行


2.4. 1-Wire接続温度センサ

Armadillo-400シリーズでは、CON9 2ピンとCON9 26ピンを1-Wireバスとして使用することができます。ここでは、1-Wireバスに温度センサICを接続する方法を紹介します。

使用するソフトウェア、デバイスは以下のとおりです。

  1. Linuxカーネル: linux-3.14-at4 以降
  2. 温度センサ: DS18B20(MAXIM社製)

2.4.1. 1-Wire概要

1-Wireは、IC間のデータ転送に使われる1線式の通信方式です。最低限、1本の信号線と接地線の2本だけでバスを構成することができます。このとき、電力は信号線から得ます。なお、電源線を別途用意し、3線で構成することもできます。1-Wireバスに接続されたデバイスの出力段はオープンドレインとし、信号線はプルアップします。

1-Wireバスに接続されたデバイスは、その役割によってマスタとスレーブに分かれます。1-Wireでは、1つのバスに1つのマスタと複数のスレーブを接続できます。通信は必ずマスタが開始し、バスに接続されたスレーブとデータのやりとりを行います。スレーブデバイスは、チップごとに固有な64bitのROM IDを持っており、マスタはROM IDを指定することで通信をおこなうスレーブを特定します。Armadillo-400シリーズは、1-Wireマスタとなることができます。

1-Wireでは、クロック信号がないため、タイムスロットに基づいてデータの転送をおこないます。マスタからスレーブに値を書き込む場合、マスタからローパルスを出力します。パルスの立ち下がりエッジでスレーブ内の単安定マルチバイブレーターが開始し、立ち下がりエッジから約30μsecの時点でサンプリングをおこないます。そのため、マスタは1を書き込む場合は1から15μsecの短いローパルスを出力し、0を書き込む場合は60μsecの長いローパルスを出力します。

スレーブからの値を読み出す場合、まず、マスタがローパルス1から15μsecの短いローパルスを出力します。スレーブ側は、1を送信したい場合何もしません。0を送信したい場合、60μsecの間信号線をローに引っ張ります。マスタは、立ち下がりエッジから30μsecの時点でサンプリングをおこない、スレーブからの出力をサンプリングします。

なお、タイムスロットにはスタンダード(1タイムスロット60μsec)とオーバードライブ(1タイムスロット8μsec)の二つがあります。上記の説明はスタンダードの場合のタイミングです。

1-Wireプロトコル(ビット転送)

図2.47 1-Wireプロトコル(ビット転送)


マスターとスレーブ間でのデータ転送は3つのシーケンスでおこないます。3つのシーケンスは、リセットシーケンス、ROMコマンドシーケンス、ファンクションシーケンスの順番に実行されます。

リセットシーケンスでは、まず、マスタがリセットパルスを出力します。1-Wireバスにスレーブが接続されている場合、スレーブはプレゼンスパルスを出力します。

ROMコマンドシーケンスでは、マスタが8bitのROMコマンドを出力した後、64bitのROM IDを出力します。ROM IDの先頭8bitはデバイス種類を示すファミリーコードです。続く48bitがシリアルナンバーになっています。最後の8bitはCRCです。ROMコマンドには次のものがあります。

  1. SEARCH ROM(0xF0): バスに接続されているスレーブデバイスのROM IDを得ることができます。1回のSEARCH ROMコマンドで1つのデバイスのROM IDを特定することができます。
  2. READ ROM(0x33): バスに接続されているスレーブデバイスが一つだけの場合、SEARCH ROMコマンドの代わりにREAD ROMコマンドを使用して、ROM IDを得ることができます。
  3. MATCH ROM(0x55): MATCH ROMコマンドでマスタが出力したROM IDに一致したスレーブデバイスが、続くファンクションコマンドに応答します。それ以外のデバイスは、次のリセットシーケンスを待ちます。
  4. SKIP ROM(0xcc): SKIP ROMコマンドに続いて送信されたファンクションコマンドは、バスに接続されているスレーブデバイス全てに同時に適用されます。

ファンクションシーケンスは、スレーブデバイスごとに異なります。基本的には、マスタが8bitのフォワードコマンド出力した後、データの読み出しまたは書き込みをおこないます。

1-Wireプロトコル

図2.48 1-Wireプロトコル


2.4.2. DS18B20

今回使用する温度センサDS18B20は、以下の特長を持ちます。

  1. 単一電源(3.0Vから5.5V)動作
  2. 電源は信号線から供給可能
  3. 外部部品不要
  4. 温度計測範囲-55℃から+125℃
  5. 分解能9bitから12bit
  6. 変換時間750msec(最大12bit時)

DS18B20のファンクションコマンドには以下のものがあります。

  1. CONVERT T(0x44): このコマンドにより、温度変換がおこなわれます。変換結果は、DS18B20の内蔵2バイトレジスタに格納されます。
  2. WRITE SCRATCHPAD(0x48): DS18B20の内蔵メモリに書き込みをおこないます。書き込むデータは3バイト長で、TH、TL、Configuration Registerの順番に送信します。
  3. READ SCRATCHPAD(0xbe): DS18B20の内蔵メモリを読み出します。読み出すデータのバイト数は最大9バイトです。途中で、マスタからリセットパルスを送信することで、データの読み出しを中断できます。

DS18B20内蔵レジスタは次のようになっています。

表2.5 DS18B20内蔵レジスタ

バイト内容

0

Temperature Register LSB

1

Temperature Register MSB

2

TH or User Byte 1

3

TL or User Byte 2

4

Configuration Register

5

Reserved (0xff)

6

Reserved

7

Reserved (0x10)

8

CRC


DS18B20の温度センサ分解能は、Configuration Registerの5bit目と6ビット目で決まります。それ以外のConfiguration Registerのビットは内部的に使用され、上書きすることはできません。

表2.6 DS18B20温度センサ分解能

BIT 6BIT 5分解能

0

0

9 bit

0

1

10 bit

1

0

11 bit

1

1

12 bit[a]

[a] パワーオンリセット時の設定


Temperature Registerのフォーマットは次のようになっています。温度は摂氏で格納されています。12ビット分解能の場合は、BIT10からBIT0全てのビットが有効です。11ビット分解能の場合、BIT0が不定となります。10ビット、9ビット分解能の場合も同様です。BIT15からBIT11は、温度が正の場合0、負の場合1となります。

DS18B20 Temperature Registerフォーマット

図2.49 DS18B20 Temperature Registerフォーマット


2.4.3. サンプル回路

Armadillo-400シリーズと温度センサとの接続を、図2.50「1-Wire接続温度センサ回路図」に示します。信号線は、CON9 2ピンに接続します。また、今回はVDDに+3.3Vを接続し電源は外部から供給します。

1-Wire接続温度センサ回路図

図2.50 1-Wire接続温度センサ回路図


2.4.4. 温度センサドライバー

一般的に、1-Wire接続のデバイスをLinuxシステムで使用する場合、1-Wireスレーブデバイス用のデバイスドライバーを作成して、デバイスの制御をおこないます。今回は、1-Wireスレーブデバイスドライバーの「Thermal family implementation」を使用します。

「Thermal family implementation」を使用すると、1-Wireバスに接続された温度センサデバイスを自動で検出し、sysfs経由で温度データの読み出しを可能にします。

[armadillo ~]# cd /sys/devices/w1_bus_master1/
[armadillo /sys/devices/w1_bus_master1]# ls
28-0000022e2355/           w1_master_name
driver@                    w1_master_pointer
power/                     w1_master_search
subsystem@                 w1_master_slave_count
uevent                     w1_master_slaves
w1_master_attempts         w1_master_timeout
w1_master_max_slave_count
[armadillo /sys/devices/w1_bus_master1]# cat 28-0000022e2355/w1_slave
c4 01 4b 46 7f ff 0c 10 3b : crc=3b YES
c4 01 4b 46 7f ff 0c 10 3b t=28250
c3 01 4b 46 7f ff 0d 10 2f : crc=2f YES
c3 01 4b 46 7f ff 0d 10 2f t=28187

図2.51 1-Wire接続温度センサドライバーの使用例


/sys/devices/w1_bus_master1/が、1-Wireに関連するsysfsディレクトリです。「Thermal family implementation」が有効になっていて、スレーブデバイスが検出されると、28-0000022e2355のようにデバイスのROM ID に対応したディレクトリが作成されます。その中のw1_slaveを読み出すと、ドライバーはCONVERT Tコマンドを実行したあとREAD SCRATCHPADコマンドを実行し、DS18B20の内蔵レジスタを表示します。「crc=xx YES」でCRCが一致したことを表します。また、「t=28250」は温度(摂氏)を1000倍した値を示します。1度の読み出しで、2回分の変換結果を表示します。

2.4.5. カーネルコンフィギュレーション

Armadillo-400シリーズの標準のカーネルでは、1-Wireドライバーは有効になっていません。そのため、1-Wireマスタドライバーと「Thermal familyimplementation」ドライバーが有効になったカーネルを作成する必要があります。

まず、カーネルのソースコードアーカイブを取得します。ここでは、Armadilloサイトからダウンロードしてくることにします。

[ATDE ~]$ wget http://armadillo.atmark-techno.com/files/downloads/armadillo-4x0/source/kernel/linux-3.14-at[version].tar.gz
[ATDE ~]$ tar xzvf linux-3.14-at[version].tar.gz

図2.52 Linuxカーネルの取得と展開


次にArmadillo-400シリーズの標準コンフィギュレーションを適用します。

[ATDE ~]$ cd linux-3.14-at[version]/
[ATDE ~/linux-3.14-at[version]]$ make ARCH=arm armadillo4x0_defconfig

図2.53 LinuxカーネルにArmadillo-400シリーズ標準コンフィギュレーションを適用する


続いて、menuconfigを使用して、図2.55「1-Wireドライバーを有効にする」に示すようにカーネルコンフィギュレーションを変更します。

[ATDE ~/linux-3.14-at[version]]$ make ARCH=arm menuconfig

図2.54 menuconfigを使用してカーネルコンフィギュレーションを変更する


Linux Kernel Configuration
  System Type  --->
    Freescale i.MX support  --->
      Armadillo-400 Board options  --->
        [*] Enable one wire at CON9_2  ←チェックを入れる

  Device Drivers  --->
    <*> Dallas's 1-wire support  --->  ←チェックを入れる
      1-wire Bus Masters  --->
        <*> Freescale MXC 1-wire busmaster  ←チェックを入れる
      1-wire Slaves  --->
        <*> Thermal family implementation  ←チェックを入れる

図2.55 1-Wireドライバーを有効にする


コンフィギュレーションの変更をおこなったら、カーネルをビルドします。

[ATDE ~/linux-3.14-at[version]]$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- && gzip -c arch/arm/boot/Image > linux.bin.gz

図2.56 Linuxカーネルをビルドする


正常にビルドが完了すると、linux-3.14-at[version]/linux.bin.gzにカーネルイメージが作成されます。linux.bin.gzをArmadilloのフラッシュメモリのカーネル領域に書き込んでください。

図2.50「1-Wire接続温度センサ回路図」に示すようにArmadilloとDS18B20を接続してからArmadilloを起動し、図2.51「1-Wire接続温度センサドライバーの使用例」に示す手順で動作確認をおこなってください。

2.5. CAN

Armadillo-400 シリーズでは、CON14をCANバスとして使用することができます。ここでは、Armadillo同士をCANで接続する方法を紹介します。

使用するソフトウェア、デバイスは以下のとおりです。

  1. Linuxカーネル: linux-3.14-at4 以降
  2. ユーザーランド: Atmark Dist v20151026 以降
  3. ネットワークおよびトラッフィック制御ツール: iproute2 (Atmark Distに含まれるもの)
  4. CAN通信プログラム: can-utils (Atmark Distに含まれるもの)
  5. CANトランシーバー: AMIS-42673(ON Semiconductor社製)

2.5.1. CAN概要

CAN(Controller Area Network)は、機器間のデータ転送に使われる、2線差動電圧式の通信方式です。差動電圧式を採用しているため耐ノイズ性に優れる点や、エラー検出方法と検出後の動作が明確化されているといった特長から、比較的信頼性の求められるネットワークに用いられます。

CANでは、CAN+とCAN-の2本の信号線間の電圧差を変化させることで通信をおこないます。この2本の信号線に複数のノード(機器)を接続し、バスを構成します。CANの物理的な仕様に関連する規格には、通信速度が125kbpsまでの低速CAN(ISO1159-2)、通信速度125kbpsから1Mbpsの高速CAN(ISO11898-2)など様々なものがあります。一般的にCANのノードは、物理層の処理をおこなうCANトランシーバとその後のデータ処理をおこなうCANコントローラから構成されます。今回の例では、CANコントローラはi.MX25内蔵のFlexCANコントローラを使用し、CANトランシーバにはISO 11898-2に対応したAMIS-42673を使用します。

CANプロトコルでは、CAN+とCAN-間の電圧差を、RS-232C通信のようにあらかじめ決められた通信速度(ビットレート)に従って変化させることで、データの転送をおこないます。転送される各ビットは、ドミナントかリセッシブのいずれかの状態を取ります。高速CANでは、CAN+とCAN-の電圧差がある場合ドミナント、無い場合リセッシブとなります。通常、ドミナントを論理0、リセッシブを論理1として扱います。CANはマルチマスタ構成のため、複数のデバイスが同時に通信をおこない、バス上でデータの衝突がおこる場合があります。この場合、どれか一つのノードがドミナントを出力していた場合、バスの状態はドミナントとなります(ドミナントがリセッシブに対して優先される)。CANでは、この特性を利用して調停をおこないます。

データの転送は、フレームという単位でおこないます。フレームには、表2.7「CANプロトコルフレーム」に示す4つの種類があります。

表2.7 CANプロトコルフレーム

名称 機能

データフレーム

データを送信する。

リモートフレーム

データフレームを要求する。

オーバーロードフレーム

前回のフレーム処理が完了していないことを通知する。

エラーフレーム

エラーが発生したことを通知する。


データフレームとリモートフレームを合わせて、メッセージフレームといいます。CANでは、ノードごとのアドレスというものはなく、その代わりにそれぞれのメッセージが固有なID(識別子、Identifier)を持っています。受信ノードは、IDによって、自分が処理すべきメッセージかどうか判断します。メッセージに含まれるIDの長さによって、メッセージフレームには標準フォーマット(11bit長)と拡張フォーマット(29bit長)の2種類の形式があります。

データフレームの形式を図2.57「CANプロトコル(データフレーム)」に示します。上の線はリセッシブを、下の線はドミナントを意味します。データフレームは、データを送信するノードがバスをドミナントにすることから始まります。これをスタート・オブ・フレーム(SOF)と呼びます。SOFに続き、アービトレーションフィールド(ARBI)、コントロールフィールド(CONT)、データフィールド(DATA)、CRCフィールド(CRC)が順に送信されます。続いて、受信ノードはACKフィールド(ACK)を送信します。最後に、7ビット分バスをリセッシブに保ちエンド・オブ・フレーム(EOF)とします。

CANプロトコル(データフレーム)

図2.57 CANプロトコル(データフレーム)


アービトレーションフィールドは、標準フォーマットか拡張フォーマットかによって異なります。標準フォーマットの場合、11bitのIDを送信したあと、リモート・トランスミッション・リクエスト・ビット(RTR)にドミナントを送信します。拡張フォーマットの場合、11bitのベースID(BASE ID)を送信したあと、代替リモート・リクエスト・ビット(SRR)、アイデンティファイヤ・エクステンション・ビット(IDE)として、2bit分バスをリセッシブに保ちます。続いて、18bitの拡張ID(Ext ID)を送信したあと、RTRにドミナントを送信します。

[注記]CANプロトコルのバリエーション

CANプロトコルには、バージョン2.0Aと2.0Bの2つがあります。プロトコルバージョン2.0Aでは、標準フォーマットしか扱うことができません。もし拡張フォーマットのフレームを受信した場合、エラーフレームを送信します。プロトコルバージョン2.0Bパッシブでは、拡張フォーマットの送信はできませんが、拡張フォーマットを受信しても、無視します。プロトコルバージョン2.0Bアクティブでは、拡張フォーマットの送受信が可能です。

Armadillo-400シリーズは、プロトコルバージョン2.0Bアクティブに対応しています。

コントロールフィールドは、最初の2ビットが予約ビットとなっており、常にドミナントとします。続く4bitのデータ長コード(DLC)に送信するデータのバイト数を送信します。そのため、データフィールドは0から8バイト(64bit)長となります。

CRCフィールドのCRCシーケンス(CRC sequence)には、SOFからデータフィールドまでのCRC(Cyclic Redundancy Check)を送信します。CRCフィールドの区切りを示すCRCデリミタとして、1bit分リセッシブとします。

受信ノードは、受信したメッセージのCRCが一致した場合、ACKスロット(ACK slot)でドミナントを送信します。ACKスロットでバスがドミナントとなることで、送信ノードは少なくとも一つの受信ノードがデータフィールドを正常に受信できたことを確認できます。ACKスロットに続いて、ACKフィールドの区切りを示すACKデリミタ(ACK delimiter)として、1bit分リセッシブとします。

リモートフレームは、データフレームの要求に使用されます。リモートフレームを受信したノードは、リモートフレームで指定されたIDと同じIDのメッセージを返信します。リモートフレームの形式を、図2.58「CANプロトコル(リモートフレーム)」に示します。RTRをリセッシブにして、データフィールドが無い以外、データフレームと同じです。DLCは、リモートフレームへの返信として帰ってくるデータフレームのデータ長と同一にします。

CANプロトコル(リモートフレーム)

図2.58 CANプロトコル(リモートフレーム)


メッセージフレームのアービトレーションフィールドという名前は、このフィールド送信中にバスの調停をおこなうことに由来します。同時に複数のノードがメッセージの送信を開始した場合、バスの衝突が発生します。送信ノードは、各ビットでバスの状態を確認し、もし自身がリセッシブを送信したにも関わらず、バスがドミナントとなっていた場合、以後の送信を中止します。そのため、より小さなIDが優先して送信されます。また、RTRにより、リモートフレームよりもデータフレームが優先されます。

オーバーロードフレームとエラーフレームは、一般にCANコントローラによって自動で処理されます。そのため、ここでは説明を割愛します。

[注記]同期とビット・スタッフィング・ルール

CANでは、ビットレートに従ってデータの送受信をおこなうため、ノードごとのクロックに誤差がある場合、タイミングが少しずつずれていきます。これを補正するため、バスがリセッシブからドミナントへ変化するとき、タイミングの同期をおこないます。

しかし、リセッシブやドミナントだけが続いた場合、この同期が行われないことになります。そこで、ビット・スタッフィング・ルールが適用されます。これは、同じ状態が5bit連続した場合、反対の状態のビット(スタッフビット)を一つ送信するルールです。このルールにより、一定期間内に必ず同期が行われることを保証しています。

なお、ビット・スタッフィング・ルールの処理はCANコントローラで自動で行われるため、ユーザー側は通常それを意識することはありません。

2.5.2. サンプル回路

Armadillo-400シリーズとCANトランシーバーとを接続する回路図を、図2.59「CAN接続回路図」に示します。ArmadilloのCON14から出ているCAN2を使用します。CANトランシーバーには、AMIS-42673(ON Semiconductor社製)を使用します。LM2731YMF(National Semiconductor社製)は、3.3Vから5Vを生成するスイッチングコンバーターです。CON9 2ピン(GPIO3_17)で出力のON/OFFを切り替えることができます。

CAN接続回路図

図2.59 CAN接続回路図


Armadillo-400シリーズ同士を接続する場合は、次のように接続してください。3つ以上のArmadilloを接続しても構いません。

CANバスを介したArmadillo同士の接続

図2.60 CANバスを介したArmadillo同士の接続


2.5.3. CANドライバー

Armadillo-400シリーズのCAN機能は、SocketCANフレームワークを使用して実装されています。SocketCANでは、通常のネットワークデバイスと同様に、socketインターフェースを用いてデータの送受信を行います。

CAN通信をおこなうプログラムは、TCP/IPなどを用いたネットワークプログラムと同様に記述できます。図2.61「CANソケットのオープン」に、CAN通信用のソケットをオープンし、can0インターフェースに関連付けるコード例を示します。プロトコルファミリーには、PF_CANを指定します。プロトコルには、ローソケットプロトコル(CAN_RAW)または、ブロードキャストマネージャ(BCM)を指定します。bindシステムコールでCANインターフェースとソケットを関連付けます。

int s;
struct sockaddr_can addr;
struct ifreq ifr;

s = socket(PF_CAN, SOCK_RAW, CAN_RAW);

strcpy(ifr.ifr_name, "can0" );
ioctl(s, SIOCGIFINDEX, &ifr);

addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;

bind(s, (struct sockaddr *)&addr, sizeof(addr));

図2.61 CANソケットのオープン


以降の処理は、通常のネットワークプログラムと同様です。CANメッセージの送受信には、read/writeシステムコールや、send/sendto/sendmsgシステムコール、recv/recvfrom/recvmsgシステムコールを使用できます。

SocketCANに関する詳しい情報は、linux-3.14-at[version]/Documentation/networking/can.txtを参照してださい。

SocketCANフレームワークでは、sysfsインターフェースを用いて、CAN通信に関わる設定をおこないます。CAN2を使用する場合、/sys/devices/platform/FlexCAN.1/以下のファイルを使用します。使用可能なsysfsファイルの一覧は、「Armadillo-400シリーズ ソフトウェアマニュアル」の「CAN」を参照してください。

通常のSocketCANフレームワークには無い、Armadillo-400シリーズ独自の拡張として、リモートフレームのサポートを追加しています。set_resframeファイルに、ID#DATAという形式で値を書き込むと、対応するIDのリモートフレームワークを受信した場合、自動でデータフレームを返信します。

2.5.4. カーネルコンフィギュレーション

Armadillo-400シリーズの標準のカーネルでは、CANドライバーは有効になっていません。そのため、CANドライバーが有効になったカーネルを作成する必要があります。

まず、カーネルのソースコードアーカイブを取得します。ここでは、Armadilloサイトからダウンロードしてくることにします。

[ATDE ~]$ wget http://armadillo.atmark-techno.com/files/downloads/armadillo-4x0/source/kernel/linux-3.14-at[version].tar.gz
[ATDE ~]$ tar xzvf linux-3.14-at[version].tar.gz

図2.62 Linuxカーネルの取得と展開


次にArmadillo-400シリーズの標準コンフィギュレーションを適用します。

[ATDE ~]$ cd linux-3.14-at[version]/
[ATDE ~/linux-3.14-at[version]]$ make ARCH=arm armadillo4x0_defconfig

図2.63 LinuxカーネルにArmadillo-400シリーズ標準コンフィギュレーションを適用する


続いて、menuconfigを使用して、図2.65「CANドライバーを有効にする」及び図2.66「CANに使用するピンを指定する」に示すようにカーネルコンフィギュレーションを変更します。

[ATDE ~/linux-3.14-at[version]]$ make ARCH=arm menuconfig

図2.64 menuconfigを使用してカーネルコンフィギュレーションを変更する


Linux Kernel Configuration
  Networking  --->
    <*>   CAN bus subsystem support  --->  ← チェックを入れる
      <*>   Raw CAN Protocol (raw access with CAN-ID filtering)  ← チェックが入っているのでそのまま
      <*>   Broadcast Manager CAN Protocol (with content filtering)  ← チェックが入っているのでそのまま
      CAN Device Drivers  --->
        <*>   Support for Freescale FLEXCAN based chips  ← チェックを入れる

図2.65 CANドライバーを有効にする


Linux Kernel Configuration
  System Type  --->
    Freescale MXC Implementations  --->
      Armadillo-400 Board options  --->
        [ ] Enable I2C2 at CON14  ← チェックを外す
        [*] Enable CAN2 at CON14  ← チェックを入れる

図2.66 CANに使用するピンを指定する


コンフィギュレーションの変更と、ソースの修正をおこなったら、カーネルをビルドします。

[ATDE ~/linux-3.14-at[version]]$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- && gzip -c arch/arm/boot/Image > linux.bin.gz

図2.67 Linuxカーネルをビルドする


正常にビルドが完了すると、linux-3.14-at[version]/linux.bin.gzにカーネルイメージが作成されます。linux.bin.gzをArmadilloのフラッシュメモリのカーネル領域に書き込んでください。

2.5.5. ipコマンドの準備

CANの通信速度設定は ipコマンドを使用します。ipコマンドは Atmark Distのユーザーランドコンフィギュレーションで iproute2を選択する[8]事で組み込む事が可能です。

第1部の「Atmark Distを使ったルートファイルシステムの作成」などを参照し、使用するプロダクト用に基本的な設定をして一度ビルドしたAtmark Distを用意してください。Atmark Distのユーザーランドコンフィギュレーションで以下の項目にチェックを入れます。

Userland Configuration
  Network Applications  --->
    [*] iproute2   ← チェックを入れる
    [*]   ip       ← チェックを入れる

図2.68 iproute2を選択する


2.5.6. CAN通信プログラムの準備

Atmark Distには、CAN通信プログラムのサンプルとしてcan-utilsが含まれています。can-utilsには、一つのメッセージを送信するcansend、複数のメッセージを連続して送信するcangen、受信したメッセージを表示するcandumpがあります。今回は、これらを使用してCANの動作確認をおこなうことにします。

can-utilsを使用可能にするには、Atmark Distのユーザーランドコンフィギュレーションで以下の項目にチェックを入れます。

Userland Configuration
  Network Applications  --->
    [*] can-utils  ← チェックを入れる
    [*]   cansend  ← チェックを入れる
    [*]   candump  ← チェックを入れる
    [*]   cangen   ← チェックを入れる

図2.69 can-utilsを選択する


これらを選択した状態でユーザーランドをビルドし、作成されたルートファイルシステムイメージ(romfs.img.gz)をArmadilloのフラッシュメモリのユーザーランド領域に書き込んでください。

2.5.7. 使用例

実際に、CANバスを通じてArmadillo同士で通信をおこなう手順を説明します。

まず、通信速度を設定します。通信速度は送受信をおこなうノード全てで一致している必要があるので、それぞれのArmadilloでおこなってください。

通信速度はipコマンドで設定します。以下の例では通信速度を125kbpsに設定しています。

[armadillo ~]# ip link set can0 type can bitrate 125000 loopback off

図2.70 ipコマンドによるCAN通信速度の設定例


次に、CANインターフェースを有効にします。これも、それぞれのArmadilloで実行します。

[armadillo ~]# ifconfig can0 up

図2.71 CANインターフェースの有効化


CANメッセージを受信するArmadilloで、candumpを実行しておきます。

[armadillo ~]# candump can0

図2.72 CANメッセージの受信準備


別のArmadilloでcansendを実行すると、一つのメッセージを送信できます。図2.73「CANメッセージの送信」の例では、ID=0x5a5、データ=0x01234567を送信しています。

[armadillo ~]# cansend can0 5a5#01234567

図2.73 CANメッセージの送信


candumpコマンドを実行している受信側のArmadilloでは、メッセージを受信すると図2.74「CANメッセージの受信結果」に示しすような表示が得られます。

[armadillo ~]# candump can0
  can0  5a5  [4] 01 23 45 67

図2.74 CANメッセージの受信結果


また、cangenを実行すると、連続したメッセージを送信できます。オプションにCANインターフェース名だけを指定した場合、cangenはアドレス、データ共にランダムな値を送信します。

[armadillo ~]# cangen can0

図2.75 連続したCANメッセージの送信


candumpを実行している受信側のArmadilloでは、図2.76「連続したCANメッセージの受信」に示すような受信結果が得られます。

[armadillo ~]# candump can0
  can0  567  [6] 69 98 3C 64 73 48
  can0  451  [8] 4A 94 E8 2A EC 58 55 62
  can0  729  [8] BA 58 1B 3D AB D7 7E 50
  can0  1F2  [8] E3 A9 E2 79 46 E1 45 75
  can0   7C  [2] 54 08
  :
  :
  :

図2.76 連続したCANメッセージの受信




[5] もちろん、セキュリティ面を考慮すれば、securettyにはログインを許可するインターフェースのみを記述すべきです。

[6] I2Cと書かれることから、アイ・ツー・シーと発音される場合もあります。

[7] このような接続を、ワイヤード・ANDといいます。

[8] ユーザーランドにはBusyboxのipコマンドが標準で組み込まれています。しかし、BusyBoxのipコマンドはCANの通信速度をサポートしていません。従って、iproute2を組み込む必要があります。