ラズパイで3軸加速度センサー (AE-ADXL367) を使う方法

# 目次
- [販売リンク、データシート](#販売リンク、データシート)
- [開発環境](#開発環境)
- [ラズパイへの接続方法](#ラズパイへの接続方法)
- [コードを動かすための前準備](#コードを動かすための前準備)
- [コード](#コード)
    - [起こりやすいエラーとその対処](#起こりやすいエラーとその対処)
    - [コードの簡単な解説](#コードの簡単な解説)
- [応用](#応用)

# 販売リンク、データシート
秋月電子で買いました、以下リンクです

[3軸加速度センサーモジュール AE-ADXL367](https://akizukidenshi.com/catalog/g/g129428/)

[ADXL367 データシート (日本語)](https://www.analog.com/media/jp/technical-documentation/data-sheets/adxl367_jp.pdf)
# 開発環境
Raspberry Pi 4 Model B
OS : 2024-10-22-raspios-bullseye-armhf
python 3.10
# ラズパイへの接続方法
Raspberry PiはI2C通信用のピンを備えています
I2Cとは、マイコン間で主に使用される通信方式の一つです
ADXL367はI2C通信で情報をやり取りできるので、Raspberry PiとADXL367をI2C通信できるように接続します


<img width="330mm" alt="ADXL367.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550181/931e5ade-9242-4d42-9bfc-e5a8d6b8654c.jpeg">


# I2Cの有効化
Raspberry PiのI2Cはデフォルトでは無効になっているので、設定を変更して有効にします
以下のコマンドを使用してラズパイの設定を変更します

`raspi-config`

<img width="400" alt="Screenshot from 2018-04-29 11-56-31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550181/9dd83330-0a6f-42c8-9d9e-57f65dbd44b7.png">

Interfacing Options -> I2C -> Yes を選択してI2Cを有効化します
設定できたら、左右矢印でFinishを選択して、エンターキーを押して、raspi-configを終わります

# コードを動かすための前準備
PythonからI2Cをコントロールするためのライブラリ「smbus」のインストール
`$ sudo apt install python-smbus`
もろもろの計算用にnumpyのインストール
`$ sudo apt install python-numpy`

# コード
ADXL367を動かして加速度のデータを取得するためのコードは以下の通りです

```python:adxl367.py
# -*- coding: utf-8 -*-
# 使用センサー: ADXL367
import math
import time

import numpy as np
import smbus


class LowGAcc3:
    def __init__(self, calibrated=False, offset_list = [[0 for _ in range(10)] for _ in range(3)]):
        self.calibrated = calibrated
        self.offset_list = offset_list
        self.acc_x = 0
        self.acc_y = 0
        self.acc_z = 0
        self.acc_norm = 0

        # self.ADDR = 0x1d
        self.ADDR = 0x53
        self.i2c = smbus.SMBus(1)

        # センサーの諸設定
        self.i2c.write_byte_data(self.ADDR, 0x2C, 0x03) # output data rate 100 Hz
        self.i2c.write_byte_data(self.ADDR, 0x2D, 0x02)

        time.sleep(0.5)

    def apply_offset_list(offset_list, x, y, z):
        xyz_array = np.array([x, y, z])
        for i, offset in enumerate(offset_list):
            offset_array = np.array(offset)
            xyz_array[i] -= np.mean(offset_array)

        return tuple(xyz_array)
    
    def get_calibration_data(self):
        calibration_iter = 10
        for _ in range(calibration_iter):
            x, y, z = self.get_acc_raw()
            self.offset_list[0].append(x)
            self.offset_list[1].append(y)
            self.offset_list[2].append(z-1.0)
        
        print(self.offset_list)

        self.calibrated = True
    
    def get_acc_raw(self):
        # 単位はGです
        try:
            #データ読み込み
            xh = self.i2c.read_byte_data(self.ADDR, 0x0E)
            xl = self.i2c.read_byte_data(self.ADDR, 0x0F)
            yh = self.i2c.read_byte_data(self.ADDR, 0x10)
            yl = self.i2c.read_byte_data(self.ADDR, 0x11)
            zh = self.i2c.read_byte_data(self.ADDR, 0x12)
            zl = self.i2c.read_byte_data(self.ADDR, 0x13)
        
            #データ変換
            x = (xh << 6) | (xl >> 2)
            y = (yh << 6) | (yl >> 2)
            z = (zh << 6) | (zl >> 2)
        
            #極性判断
            if x >= 8192:
                x = x - 16384
        
            if y >= 8192:
                y = y - 16384
        
            if z >= 8192:
                z = z - 16384
        
            #物理量（加速度[g]）に変換
            x = x * 2 / 8191 # 2g設定
            y = y * 2 / 8191
            z = z * 2 / 8191

            self.acc_x = x
            self.acc_y = y
            self.acc_z = z
            self.acc_norm = math.sqrt(x**2 + y**2 + z**2)
            
        except IOError as e:
            print("I/O error({0}): {1}".format(e.errno, e.strerror))
        
        return x, y, z, math.sqrt(x**2+y**2+z**2)

    def get_acc_calibrated(self):
        if not self.calibrated:
            print("set calibration data!")
            return
        
        x, y, z = self.get_acc_raw()
        x, y, z = self.apply_offset_list(self.offset_list, x, y, z)

        self.acc_x = x
        self.acc_y = y
        self.acc_z = z
        self.acc_norm = math.sqrt(x**2 + y**2 + z**2)

        return x, y, z, math.sqrt(x**2 + y**2 + z**2)

def main():
    low_g_acc = LowGAcc3()
    
    while True:
        out_x, out_y, out_z, norm = low_g_acc.get_acc_raw()
        print(f"{out_x}, {out_y}, {out_z}, {norm}")
        time.sleep(0.1)

def test_calib():
    low_g_acc = LowGAcc3()
    low_g_acc.get_calibration_data()

    out_x, out_y, out_z, norm = low_g_acc.get_acc_calibrated()
    print(f"{out_x}, {out_y}, {out_z}, {norm}")
    print("offset_list:")
    print(low_g_acc.offset_list)


if __name__ == "__main__":
    main()
```

## 起こりやすいエラーとその対処
このコードを実行したときにこんな I/O error が表示されることがあります
```bash
$ python adxl367.py
Traceback (most recent call last):
  File "/home/pi/adxl367.py", line 124, in <module>
    main()
  File "/home/pond/adxl367.py", line 106, in main
    low_g_acc = LowGAcc3()
                ^^^^^^^^^^
  File "/home/pi/adxl367.py", line 24, in __init__
    self.i2c.write_byte_data(self.ADDR, 0x2C, 0x03) # output data rate 100 Hz
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
OSError: [Errno 5] Input/output error
```
この場合はソースコードの次の部分
```py
        # self.ADDR = 0x1d
        self.ADDR = 0x53
```
を
```py
        self.ADDR = 0x1d
        # self.ADDR = 0x53
```
とI2Cアドレスを変更することで解決する場合があります
こうなる理由としては、この加速度センサー ADXL367 は2つのI2Cアドレスを使用することが出来、ASEL pinをGNDに接続することで 0x1d のアドレスを使用するように出来るようになっているのですが、このピンをGNDに接続していないときでも 0x1d のアドレスを使用する場合があるためです（なぜこのようなことが起こるのかは不明）

## コードの簡単な解説
- classを使ってオブジェクト指向風に書きました
- \_\_init\_\_ メソッドで初期化設定を行い、データの範囲を ±2 g 、出力レートを 100 Hz に設定しました (データシート 44 ページ目)
- get_acc_raw メソッドでデータの取得を行い、x, y, z, norm の順で加速度のデータを返します (データシート 34, 35, 36 ページ目)
- その他メソッドはキャリブレーションに使用する目的で書きました

# 応用
　加速度センサーは衝撃検知や自由落下検知、速度測定に使用できます
　自由落下検知は単に加速度の値が 0 になったことを検知すれば実現できます。センサーの機能としても実装されているので興味があればデータシートを読んでみるのも面白いと思います
　また、加速度の値を積分すれば物体の速度を測ることも出来ます。数値積分の方法については台形積分やシンプソンの公式などがあるのでこれも調べてみると面白いと思います