ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

raspberry pi + python3でSHARPの赤外線測距モジュールGP2Y0A21YK0Fを使う(SPI接続A/D変換器MCP3002使用)

何年も放置していたラズパイマガジン2015年秋号用の部品セットの動作確認中です。
今回はSPIバス接続のA/DコンバーターMCP3002と、赤外線測距モジュールGP2Y0A21YK0Fを組み合わせて見ました。

ラズパイマガジン2015年秋号のサンプルソースがpython2向けかつ、実装が古く上手く動かないので、python3で組み直しています。

SPI接続A/Dコンバーター MCP3002

MCP3002はSPIバスに接続して使います。
データシートは秋月電子のサイト他で入手可能です。
https://akizukidenshi.com/download/ds/microchip/mcp3002.pdf

ピンは下記の様な8ピン。

ピンの用途とraspberry piとの接続は以下の様になります。

ピン番号ピン用途接続先備考
1

CS

Chip SelectラズパイのCE0またはCE1今回のサンプルではCE0(24番ピン)
2CH0アナログ入力チャンネル0測定したいアナログソース
3CH1アナログ入力チャンネル1測定したいアナログソース
4GNDグランドラズパイのGND(6番ピン,9番ピン等)
5Dinシリアルデータ入力ラズパイのMOSI(19番ピン)
6Doutシリアルデータ出力ラズパイのMISO(21番ピン)
7CLKシリアルクロックラズパイのSCLK(23番ピン)
8Vdd/Vref電源/参照電圧ラズパイの3.3V(1番または17番)
アナログ入力が2チャンネルあり、同時に2種類の信号をデジタル化出来ます。 A/D変換は10ビット(0〜1023)で、Vdd/Vrefに与えた電圧=1023となる様にサンプリングされます。 Vdd/Vrefは+2.7から5.5Vの範囲で選択可能。 またVdd/Vrefの値により、シリアルの最大クロック周波数が変わります。Vdd/Vref =2.7Vの場合は1.2MHzで最大サンプリングレートは75ksps、5Vの場合は3.2MHzで200kspsになっています。 とりあえず低めのクロックで十分なのでraspberry piの3.3V電源ピンから電源は供給します。 実際のチップはこんな感じで、ブレッドボードに挿して結線します。 このチップからA/D変換結果を読み出すコードは以下のサイトのサンプルソースを参考に、Classライブラリ化+チャンネル選択を可能にしました。 qiita.com ctrl_mcp3002.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import spidev
import time

class ctrl_mcp3002:
    # コンストラクタ
    #   spidevのインスタンスを渡す
    def __init__(self,spi):
        self.spi = spi
        self.spi.max_speed_hz = 100000 # MAX clock 1MHz
        self.spi.mode = 0b00 # Mode 00
    
    # AD変換結果読み出し
    #   引数: ch - チャンネル番号 0もしくは1
    #   戻り値: 読み出し結果 0〜1023
    def read_ch(self,ch):
        cmd = 0x40 + ((2 + ch) << 4) + 8
#        print("cmd = " + hex(cmd))
        resp = self.spi.xfer2([cmd,0x00])
        value = (resp[0] * 256 + resp[1]) & 0x3ff
        return value

# テストコード
if __name__ == '__main__':

    spi = spidev.SpiDev()
    spi.open(0,0)

    test_cnt = 10 # 読み出しテスト回数

    mcp3002 = ctrl_mcp3002(spi)

    # Channel 0と1を1秒間間隔で10回読み出し
    for i in range(test_cnt):
        # 1秒待ち
        time.sleep(1)
        ch0_value = mcp3002.read_ch(0)
        ch1_value = mcp3002.read_ch(1)
        print("=== Test Count %d" % i)
        print("  channel0 = %d" % ch0_value)
        print("  channel1 = %d" % ch1_value)

    exit(0)
   

使い方はテストコード部分の通りで、SpiDevをimportしてインスタンスを作成、そのインスタンスをctrl_mcp3002クラスのコンストラクターに渡します。
あとはctrl_mcp3002クラスのインスタンスのread_ch()メソッドにチャンネル番号を渡すと、0〜1023の読み出し値を返却します。

Vdd/Vrefを考慮した電圧値変換は実装していないので、電圧値が欲しい場合はVdd/Vrefに与えた電圧をベースに算出してください。

赤外線測距モジュールGP2Y0A21YK0F

SHARP製の赤外線測距離モジュールです。

モジュールの外観は以下の通りで、向かって右側の黒いラインが電源、真ん中のオレンジがGND(紛らわしい配色ですね)、左側の白が距離情報を出力するラインです。

測定結果はデジタルではなく、アナログの電圧値から読み取る必要があるため、この白いラインをMCP3002のアナログ入力チャンネルに接続して測定します。
電源電圧は4.5〜5.5V指定のため、raspberry piの5V電源(2番または4番ピン)から給電します。

測定の有効距離は10cm〜80cmですが、距離が近いほど出力電圧が高く10cmで2.3Vくらい。80cmで0.4Vくらいですが距離と電圧の関係がリニアではないため、電圧から単純計算では距離が求められません。
データシートには以下の様なグラフが載っています。また出力電圧はMINとMAXで±0.15Vくらいの幅があるため、それほど高精度な測定はできなそうです。このMIN/MAXが個体差でどちらかに振れているなら、キャリブレーション測定の仕組みを組み込む必要がありそうですが、今回のサンプルではそこまでやっていません。

MCP3002用に作成したクラスライブラリと組み合わせて下記の様なコードを書きました。
データシートの距離-電圧グラフから目視でラフに読み取った対応テーブルをコーディングしています。(定規すら使っていないので、かなりいい加減ですがアナログ信号の精度やキャリブレーションを実施していないので、ここの精度をがんばってもあまり意味は無いかなと..)

ctrl_gp2y0a.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import spidev
import time

from ctrl_mcp3002 import ctrl_mcp3002


class ctrl_gp2y0a25:
    # MCP3002のVREF: 3.3V - 回路構成に合わせて修正のこと
    vref_mcp3002 = 3.3
    # 距離-電圧対応テーブル
    #   数値はかなりラフなので、きちんとデータシート見て調整のこと
    dist_table = ((7,3.0),
                  (10,2.25),
                  (15,1.7),
                  (20,1.3),
                  (25,1.1),
                  (30,0.9),
                  (35,0.8),
                  (40,0.75),
                  (45,0.7),
                  (50,0.6),
                  (55,0.55),
                  (60,0.5),
                  (70,0,45),
                  (80,0.4))
    # コンストラクタ
    #   spidevのインスタンスを渡すとMCP3002に接続するチャンネル番号を渡す
    #    spi: SpiDevのインスタンス
    #    ch : MCP3002のチャンネル番号
    def __init__(self,spi,ch):
        self.spi = spi
        self.channel = ch
        self.mcp3002 = ctrl_mcp3002(spi)
        self.tbl_len = len(ctrl_gp2y0a25.dist_table)
    # 測定: 距離をcm単位で返却
    def read_dist(self):
        voltage = self.mcp3002.read_ch(self.channel) / 1023 * ctrl_gp2y0a25.vref_mcp3002
        hit = -1
        for i in range(0,self.tbl_len - 1):
            if voltage <= ctrl_gp2y0a25.dist_table[i][1] and voltage >= ctrl_gp2y0a25.dist_table[i + 1][1]:
                hit = i
                break
        
        if hit == -1:
            dist = -1
        else:
            w = ctrl_gp2y0a25.dist_table[hit + 1][0] - ctrl_gp2y0a25.dist_table[hit][0]
            h0 = ctrl_gp2y0a25.dist_table[hit][1] - ctrl_gp2y0a25.dist_table[hit + 1][1]
            h1 = ctrl_gp2y0a25.dist_table[hit][1] - voltage
            dist = ctrl_gp2y0a25.dist_table[hit][0] + h1 / h0 * w
        return dist

# テストコード
#  MCP3002のチャンネル番号を1で設定しているので、ch0に繋ぐ場合はchを書き換えのこと
if __name__ == '__main__':

    ch = 1 # AD変換器 MCP3002の接続先チャンネル番号
    spi = spidev.SpiDev()
    spi.open(0,0)

    test_cnt = 10 # 読み出しテスト回数
    gp2y0a25 = ctrl_gp2y0a25(spi,ch)

    for i in range(test_cnt):
        # 1秒待ち
        time.sleep(1)
        dist = gp2y0a25.read_dist()
        print("=== Test Count %d" % i)
        print("  distance = %2.2f cm" % dist)


    exit(0)


動作例: モジュールに手をかざしながらテスト

$ python3 ctrl_gp2y0a.py
=== Test Count 0
  distance = 10.04 cm
=== Test Count 1
  distance = 10.60 cm
=== Test Count 2
  distance = 14.77 cm
=== Test Count 3
  distance = 15.52 cm
=== Test Count 4
  distance = 21.53 cm
=== Test Count 5
  distance = 20.32 cm
=== Test Count 6
  distance = 21.94 cm
=== Test Count 7
  distance = 26.21 cm
=== Test Count 8
  distance = 26.29 cm
=== Test Count 9
  distance = 28.71 cm

今回のサンプルソースgithubに上げてあります。
github.com