拡張コネクタに SPI デバイスを接続して制御

次は SPI(Serial Peripheral Interface)を使用して外部デバイスとの通信を試してみます.BeagleBone Black の拡張コネクタに引き出されている信号線のうち,デフォルトの設定でユーザが使用可能な SPI となっているものはありませんので,Device Tree Overlay という仕組みを用いて,他の機能用に割当てられている端子の設定を SPI 機能に変更して使用します.

ターゲットは3軸加速度センサ LIS3DH

LIS3DH今回のターゲットは,秋月電子で入手した 3軸加速度センサモジュールで,ST マイクロ製加速度センサ LIS3DH が搭載されています.以下のような特徴があります.
・使いやすい 14 ピン DIP モジュール
・インターフェースに I2C または SPI を選択可能
・x / y / z 3軸の加速度が12ビットデータで読み取り可能
・A/D コンバータ入力(3CH)を搭載

秋月の説明書(PDF)はこちら:http://akizukidenshi.com/download/ds/st/LIS3DH.pdf
STマイクロのデータシートはこちら:http://www.st.com/st-web-ui/static/active/en/resource/technical/document/datasheet/CD00274221.pdf

H/W 接続

LIS3DH では,インターフェースとして SPI と I2C が選択できますが,モジュールはデフォルトで SPI 設定となっており,特に手を加えることなく SPI インターフェースが使用できます.インターフェースを I2C に変更する場合は,モジュール基板上のジャンパーパターンにハンダ付けが必要となります.
BeagleBone Black の拡張端子とモジュールの SPI 各端子は以下の対応で接続します.

BBB 端子LIS3DH 端子
P9 17 (spi0_cs0)pin 6 (CS)
P9 18 (spi0_d1)pin 4 (SDI)
P9 21 (spi0_d0)pin 5 (SDO)
P9 22 (spi0_sclk)pin 3 (SPC)

端子設定

LIS3DH との接続に使用した拡張コネクタ P9 の 端子 17 /18 / 21/ 22 は,起動時デフォルトではそれぞれ I2C, GPIO に割当てられているため,このままでは SPI 端子として使用できません.BeagleBone Blackでは,このような端子機能に対して,Device Tree Overlay という仕組みを用いて BeagleBone Black 起動後に端子の機能割り当てを変更することができます.おおまかな流れとしては,端子の設定などを記載した DTS ファイルを作成,Device tree コンパイラを用いてコンパイルしてバイナリ化,  /lib/firmware にコピーして設定を実行,となります.

まずは,下記のような内容のファイル(BB-SPI0-01-00A0.dts)を作成します.

/dts-v1/;
/plugin/;
/ {
        compatible = "ti,beaglebone", "ti,beaglebone-black";
    /* identification */
    part-number = "spi0pinmux";
    fragment@0 {
        target = <&am33xx_pinmux>;
        __overlay__ {
            spi0_pins_s0: spi0_pins_s0 {
                pinctrl-single,pins = <
                  0x150 0x30  /* spi0_sclk, INPUT_PULLUP | MODE0 */
                  0x154 0x30  /* spi0_d0, INPUT_PULLUP | MODE0 */
                  0x158 0x10  /* spi0_d1, OUTPUT_PULLUP | MODE0 */
                  0x15c 0x10  /* spi0_cs0, OUTPUT_PULLUP | MODE0 */
                >;
            };
        };
    };
    fragment@1 {
        target = <&spi0>;
        __overlay__ {
             #address-cells = <1>;
             #size-cells = <0>;
             status = "okay";
             pinctrl-names = "default";
             pinctrl-0 = <&spi0_pins_s0>;
             spidev@0 {
                 spi-max-frequency = <24000000>;
                 reg = <0>;
                 compatible = "linux,spidev";
            };
        };
    };
};

デバイスツリーコンパイラを使用して上記ファイルをコンパイルします.

dtc -O dtb -o BB-SPI0-01-00A0.dtbo -b 0 -@ BB-SPI0-01-00A0.dts

できたバイナリファイル(BB-SPI0-01-00A0.dtbo)を /lib/firmware にコピーし,下記を実行します.

echo BB-SPI0-01 > /sys/devices/bone_capemgr.9/slots

これで, 拡張コネクタ P9 の 端子 17 /18 / 21/ 22 は SPI 通信用の端子として使用できますので,あとは他の I/O 制御と同様にソフトから制御することができます.

ソフト

制御用のドライバソフトは,I2C と同様に Linux で用意されている汎用ドライバ(spi-dev)の仕組みを利用します.SPI バス用のデバイスファイル /dev/spidev1.0 を開き, ioctl で LIS3DH 内のレジスタを読み・書きすることで制御します.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/i2c-dev.h>
#include <linux/spi/spidev.h>
enum { SPI_RX, SPI_TX, SPI_TXRX };
/****************************************************************
* Constants
****************************************************************/
#define SYSFS_GPIO_DIR "/sys/class/gpio"
#define POLL_TIMEOUT (3 * 1000) /* 3 seconds */
#define MAX_BUF 64
/****************************************************************
* spi_open
****************************************************************/
int spi_open(void)
{
int ret;
int handle;
handle = open("/dev/spidev1.0", O_RDWR);
if(handle < 0) return -1;
unsigned char lsb = 0;
unsigned int speed = 10000;
unsigned char mode = SPI_MODE_3;
unsigned char r_mode;
ret = ioctl(handle, SPI_IOC_WR_LSB_FIRST, &lsb);
if(ret < 0) return -2;
ret = ioctl(handle, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if(ret < 0) return -3;
ret = ioctl(handle, SPI_IOC_WR_MODE, &mode);
if(ret < 0) return -4;
ret = ioctl(handle, SPI_IOC_RD_MODE, &r_mode);
if(ret < 0) return -5;
//printf("MODE_r : %d\n", r_mode);
return handle;
}
/****************************************************************
* spi_read_write
****************************************************************/
int spi_read_write(int fd, struct spi_ioc_transfer *p_xfer, int rw)
{
int ret;
unsigned char lsb;
unsigned int speed;
unsigned char mode = SPI_MODE_3;
unsigned char r_mode = SPI_MODE_3;
ret = ioctl(fd, SPI_IOC_RD_LSB_FIRST, &lsb);
if(ret < 0) return -1;
//printf("LSB : %d\n", lsb);
speed = 500000;
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
if(ret < 0) return -1;
//printf("SPEED : %d\n", speed);
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
if(ret < 0) return -1;
ret = ioctl(fd, SPI_IOC_RD_MODE, &r_mode);
if(ret < 0) return -1;
//printf("MODE : %d\n", r_mode);
if(rw == SPI_TX) {
ret = ioctl(fd, SPI_IOC_MESSAGE(1), p_xfer);
}
else if(rw == SPI_TXRX) {
ret = ioctl(fd, SPI_IOC_MESSAGE(2), p_xfer);
}
return ret;
}
int lis3dh_read_whoami(int fd, unsigned int *id)
{
int i;
int ret;
unsigned char    buf_t[2];
unsigned char    buf_r[2];
struct spi_ioc_transfer  xfer[2];
memset(xfer, 0, sizeof xfer);
memset(buf_t, 0, sizeof buf_t);
memset(buf_r, 0, sizeof buf_r);
buf_t[0] = 0x8f;
buf_t[1] = 0x00;
xfer[0].tx_buf = (unsigned long)buf_t;
xfer[0].rx_buf = (unsigned long)buf_r;
xfer[0].len = 2;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
*id = buf_r[1];
return 0;
}
int lis3dh_enable_axises(int fd)
{
int i;
int ret;
unsigned char    buf_t[2];
unsigned char    buf_r[2];
struct spi_ioc_transfer  xfer[2];
memset(xfer, 0, sizeof xfer);
memset(buf_t, 0, sizeof buf_t);
memset(buf_r, 0, sizeof buf_r);
buf_t[0] = 0x20;
buf_t[1] = 0x77;
xfer[0].tx_buf = (unsigned long)buf_t;
xfer[0].rx_buf = (unsigned long)buf_r;
xfer[0].len = 2;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
buf_t[0] = 0x23;
buf_t[1] = 0x08;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
return 0;
}
int lis3dh_read_axises(int fd, unsigned int *x, unsigned int *y, unsigned int *z)
{
int i;
int ret;
unsigned char    buf_t[4];
unsigned char    buf_r[4];
struct spi_ioc_transfer  xfer[4];
unsigned int temp_x;
unsigned int temp_y;
unsigned int temp_z;
memset(xfer, 0, sizeof xfer);
memset(buf_t, 0, sizeof buf_t);
memset(buf_r, 0, sizeof buf_r);
xfer[0].tx_buf = (unsigned long)buf_t;
xfer[0].rx_buf = (unsigned long)buf_r;
xfer[0].len = 2;
//--------------- status -------------------//
buf_t[0] = 0xA7;
buf_t[1] = 0x00;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
printf("%d buf_r : %02x \n", ret, buf_r[1]);
//--------------- axis-x -------------------//
buf_t[0] = 0xA8;
buf_t[1] = 0x00;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
temp_x = buf_r[1];
buf_t[0] = 0xA9;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
temp_x |= (buf_r[1] << 8);
//--------------- axis-y -------------------//
buf_t[0] = 0xAA;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
temp_y = buf_r[1];
buf_t[0] = 0xAB;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
temp_y |= (buf_r[1] << 8);
//--------------- axis-z -------------------//
buf_t[0] = 0xAC;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
temp_z = buf_r[1];
buf_t[0] = 0xAD;
ret = spi_read_write(fd, xfer, SPI_TX);
if(ret < 0) {
printf("SPI_IOC_MESSAGE\n");
return ret;
}
temp_z |= (buf_r[1] << 8);
*x = temp_x;
*y = temp_y;
*z = temp_z;
return 0;
}
/****************************************************************
* Main
****************************************************************/
int main(int argc, char **argv, char **envp)
{
unsigned int id;
int ret;
struct pollfd fdset[2];
int nfds = 2;
int gpio_fd, timeout, rc;
int spi_fd;
unsigned int x, y, z;
spi_fd = spi_open();
int count;
ret = lis3dh_read_whoami(spi_fd, &id);
printf("ID code : 0x%02x\n", id & 0xFF);
ret = lis3dh_enable_axises(spi_fd);
for(count = 0; count < 100; count++) {
usleep(500000);
ret = lis3dh_read_axises(spi_fd, &x, &y, &z);
printf("x : 0x%04x,  y : 0x%04x,  z : 0x%04x\n", x, y, z);
}
close(spi_fd);
printf("spi close\n");
return 0;
}

 

参考文献

参考 URL

BeagleBone Black 技術資料 : http://elinux.org/Beagleboard:BeagleBoneBlack#LATEST_PRODUCTION_FILES_.28C.29

BeagleBone Black Enable SPIDEVhttp://elinux.org/BeagleBone_Black_Enable_SPIDEV

How to Access Chips Over the SPI on BeagleBone Black : http://www.linux.com/learn/tutorials/746860-how-to-access-chips-over-the-spi-on-beaglebone-black

Device Tree Overlays : https://learn.adafruit.com/introduction-to-the-beaglebone-black-device-tree/device-tree-overlays

Device Tree Usage : http://www.devicetree.org/Device_Tree_Usage