ハードウェア技術者のスキルアップ日誌

某家電メーカーの技術者がスキルアップのために勉強したことを記録するブログです

ラズパイでステッピングモーターを動かす

前回、ラズパイでDCモーターを動かしてみました。

masaeng.hatenablog.com

DCモーターでは回転角度を制御できないため、今回はステッピングモーターを制御することに挑戦しました。事前に他の方がどのように制御されているかを調べたところ、ライブラリを使用している例が多い印象でした。具体的な制御手順を理解するため、ライブラリを使わずにやってみたいと思います!

ステッピングモーターとは?

直流のパルス電圧を印加して駆動するモーターで、DCモーターとは異なり、正確に回転を制御できます。例えば90度回したい、30度回したいといったことが容易にできます。

内部構成は以下のようになっています。周囲のコイルに電流を流すことでコイルがS極となり、中央の磁石のN極を近づけます。電流を流すコイルをL1, L2, L3, L4, L1・・・と変えることで、中央の磁石が回転します。

駆動方法はいくつかの手法がありますが、安定的に駆動できると言われる二相励磁を用います。電流を流す区間を1ステップ分、隣のコイルと重複させます。

      

f:id:masashi_k:20200623225754p:plain f:id:masashi_k:20200623225820p:plain

        画像引用:https://dotstud.io/blog/stepper-motor-nefrybt-control/

回路設計

 今回準備した部品はこちらです。
  ステッピングモーター28BYJ-48:http://akizukidenshi.com/catalog/g/gP-13256/
  モータードライバー TB6612FNG:http://akizukidenshi.com/catalog/g/gK-11219/
TB6612は本来DCモーター用のドライバーですが、ステッピングモーターにも使用できました。(DCモーター用に買ったものを流用するため、動かし方を調べました)

モーターの配線図はこんな感じ。3番端子(赤)には電源5Vを入力します。二相励磁で動かすためには2-A, 5-B, 4-A', 1-B'に対して右図のようにパルス電圧を印加する必要があります。

f:id:masashi_k:20200623230828p:plain  f:id:masashi_k:20200623231019p:plain

画像引用:http://akizukidenshi.com/download/ds/akizuki/28byj-48.pdf


電圧を供給するのはモータードライバーTB6612です。ドライバーモジュールの回路図は以下左図のようになっており、AIN1,2, BIN1,2が入力、A01,A02,B01,B02が出力です。ドライバーの真理値表で注目すべきところは赤枠で囲んだところです。OUT1,2がH/LまたはL/Hとなっているところを二相励磁のパルスに当てはめます。

f:id:masashi_k:20200623231439p:plain f:id:masashi_k:20200623231611p:plain

       画像引用:http://akizukidenshi.com/download/ds/akizuki/AE-TB6612.pdf

 

モーター端子、即ちTB6612の出力端子を上図の二相励磁の方式で動かそうとするとTB6612の入力は右のようにH/Lを制御すればよいことになります。これら入力AIN1,2,BIN1,2をラズパイのGPIOポートに接続して制御します。
 f:id:masashi_k:20200623232231p:plain

最終的に回路図は以下のようになります。モーターには乾電池で作ったDC5Vを入力。モータードライバーのPWM端子はVCC(3.3V)と接続しています。

f:id:masashi_k:20200623232620p:plain

こんな感じで回路を組みました。

f:id:masashi_k:20200623234101j:plain

ソースコード

Pythonでコードを書いていきます。回転角と回転方向を引数として与えるようにします。

まずはライブラリのインポートとGPIOピンの指定です。上の回路図に合わせて変数にGPIO番号を入力します。

import argparse
import time
import RPi.GPIO as GPIO
AIN1 = 2 AIN2 = 3 BIN1 = 4 BIN2 = 14 STBY = 15

 

ここで、回転速度を指定します。
ステップ角とは回転角の最小単位、ステップとは二相励磁の図における、縦の点線の区間1つ分です。モーターの回転速度(1秒に何回転するか)から1ステップの待ち時間を計算しています。ステップ角と1回転のステップ数はモーターの仕様です。購入サイトで確認できます。

f = 0.1 # 回転速度[rps] 1秒に何回転するか
s_angle = 5.625 # ステップ角[deg]
step_360 = 2048 # 1回転のステップ数
unit_step = step_360 * s_angle / 360 # ステップ角当たりのステップ数
wait = (1/f)*(s_angle/360)/unit_step # 1ステップの待ち時間

 

main関数です。
引数として回転角度と回転方向(0:時計回り、1:反時計回り)を受け取ります。
GPIOの初期設定後、回転方向が時計回りならforward()を、反時計回りならbackward()を実行して、停止処理を行います。forward/backward関数の引数はステップ数のため、回転角度からステップ数に変換して渡します。モーターに電圧をかけたままだと熱くなるため、最後にモータードライバーをスタンバイモードに入れます。

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('angle', type=float, help='rotation angle') 
    parser.add_argument('--dir', default=0, choices=["0","1"], help='rotate direction, 0=CW, 1=CCW')
    args = parser.parse_args()

    #GPIO初期化
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(AIN1, GPIO.OUT)
    GPIO.setup(AIN2, GPIO.OUT)
    GPIO.setup(BIN1, GPIO.OUT)
    GPIO.setup(BIN2, GPIO.OUT)
    GPIO.setup(STBY, GPIO.OUT)

    #モータードライバースタンバイ解除
    GPIO.output(STBY,1)

    if args.dir == "0":
        forward(int(args.angle / s_angle * unit_step))
    else :
        backward(int(args.angle / s_angle * unit_step))

    #停止処理
    print("stop!!")
    GPIO.output(AIN1,0)
    GPIO.output(AIN2,0)
    GPIO.output(BIN1,0)
    GPIO.output(BIN2,0)
    GPIO.output(STBY,0)   

 

時計回りの回転の場合は、各ポートの状態が左から右に遷移するようにGPIOを制御します。二相励磁の場合は4ステップ周期で同じ状態となるため、step/4回分だけforループを回します。

f:id:masashi_k:20200623225820p:plain

def forward(step):
    print("move forward")
    for i in range(step/4): # stepが4周期のため4で割る
        GPIO.output(AIN1,1)
        GPIO.output(AIN2,0)
        GPIO.output(BIN1,0)
        GPIO.output(BIN2,1)
        time.sleep(wait)

        GPIO.output(AIN1,1)
        GPIO.output(AIN2,0)
        GPIO.output(BIN1,1)
        GPIO.output(BIN2,0)
        time.sleep(wait)

        GPIO.output(AIN1,0)
        GPIO.output(AIN2,1)
        GPIO.output(BIN1,1)
        GPIO.output(BIN2,0)
        time.sleep(wait)

        GPIO.output(AIN1,0)
        GPIO.output(AIN2,1)
        GPIO.output(BIN1,0)
        GPIO.output(BIN2,1)
        time.sleep(wait)

def backward(step): print("move backward") for i in range(step/4): # stepが4周期のため4で割る GPIO.output(AIN1,0) GPIO.output(AIN2,1) GPIO.output(BIN1,0) GPIO.output(BIN2,1) time.sleep(wait) GPIO.output(AIN1,0) GPIO.output(AIN2,1) GPIO.output(BIN1,1) GPIO.output(BIN2,0) time.sleep(wait) GPIO.output(AIN1,1) GPIO.output(AIN2,0) GPIO.output(BIN1,1) GPIO.output(BIN2,0) time.sleep(wait) GPIO.output(AIN1,1) GPIO.output(AIN2,0) GPIO.output(BIN1,0) GPIO.output(BIN2,1) time.sleep(wait)

動作確認

こちらのソースコードで実際にモーターが回っている様子です。角度と回転方向の指定を変えることで、自在にモーターの回転が制御できています。


ラズパイ/TB6612でステッピングモーターを動かしてみた

まとめ

ライブラリを使わずにラズパイのGPIO制御でステッピングモーターを動かすことができました。動作原理を元に制御を考え、その結果、うまく動くと理解が正しかったことが確認でき、非常に勉強になると感じました。これが自分で手を動かす強みですね。

次回はステッピングモーターを使って何か作ってみたいと思います。

 

 

Python - 引数を渡す方法

仕事でちょっとしたPythonのコードを書くときに、よく引数を渡す方法を調べているので個人的な備忘録のためにまとめておきます。
これをコピペすれば作りやすいはず!

import argparse

def main():
    # parserを作成
    parser = argparse.ArgumentParser()

    #add_argumentで必要な引数を追加していく
    parser.add_argument('arg1', help='引数の説明(なくてもよい)') #必須の引数 引数が足りないとエラーになる
    parser.add_argument('arg2', help='')

    #--をつけるとオプション なくてもよい引数
    parser.add_argument('--arg3', default=0, type=int, help='') #default:引数を指定しなかったときに使用する値
                                                                 #type:型を指定 この場合自動でintに変換する
    #省略形を作成
    parser.add_argument('--arg4', '-a') #-を一つにすると文字1つでオプションを表現 どちらを使ってもよい

    #引数を使用
    args = parser.parse_args()

    #以降、args.引数名で指定した値を使用できる
    print(args.arg1)
    print(args.arg2)
    print(args.arg3)
    print(args.arg4) #省略形のaでは参照できない

if __name__ == '__main__':
    main()

 

実行するとこんな感じ

$ python sample.py a b --arg3 c -a d

a
b
c
d

 

参考サイト

ほとんどこちらのサイトを参考にさせていただきました。ありがとうございます。

qiita.com

ラズパイでPWM制御

ラズパイを使ってハードウェアを制御するシリーズ、第5回目はPWM通信です。
今回はPWM制御でモーターの回転速度を制御してみました。

 

PWMとは?

PWMはPulse Width Modulationの略で、パルス波のデューティー比を変化させて変調する変調方式のことです。デューティー比とはパルス1周期のHigh期間とLow期間の比のことで、モーターの制御においてはデューティー比を変えることで回転速度を制御することができます。

詳細はこちらの記事を参照ください。
https://monoist.atmarkit.co.jp/mn/articles/0706/06/news132.html

 

モーター制御に使用したパーツ

モーターを動かすにはそれなりの電流が必要ですので、ラズパイで直接は駆動できません。このため、モーター駆動用のモータードライバキットと電源として乾電池を使うために電池ボックスを準備しました。
http://akizukidenshi.com/catalog/g/gK-11219/
http://akizukidenshi.com/catalog/g/gP-11523/

モーターはこちらのDCモーターを使用しました。
http://akizukidenshi.com/catalog/g/gP-09169/

実は、モーターの駆動電圧が1.5V-3.0Vということを知らずに単三x4の電池ボックスを購入してしまいました。パーツを購入するときは仕様をよく調べて購入しましょう(笑)

これらのパーツを使って以下のように回路図を組みました。

f:id:masashi_k:20200524230405p:plain

後で述べますが、ラズパイはどのGPIO端子からでもPWM信号を出力できるので、使用する上記の通りにする必要はなく、GPIO端子はどれでもOKです。
配線のしやすさやIIC, SPIなど他機能との兼ね合いでお選びください。

また、モーターが回転していることが分かるように、モーターに100均扇風機のファンを取り付けました。無理やり引っ張って外しました。

ラズパイでPWM信号を出力する方法

ラズパイでPWMを使用するにはI2C制御の時に使用したwiringpiというライブラリを使用します。
http://wiringpi.com/reference/software-pwm-library/

サンプルコードを以下に示します。

import wiringpi

pin=1 #GPIO番号を指定

#初期設定
wiringpi.wiringPiSetupGpio() #GPIO番号で設定
wiringpi.pinMode(pin, wiringpi.OUTPUT)  #GPIO出力設定
softPwmCreate(pin, initialValue, pwmRange)   #PWM設定  Duty比の初期値と最大値を指定

#PWM制御
softPwmWrite(pin, value)                     #Duty何%で動かすかを設定


使用している関数について補足します。

int softPwmCreate (int pin, int initialValue, int pwmRange) ;

どのGPIOピンをPWMとして制御するかを設定します。initialValueとpwmRangeはデューティーの初期値と最大値を意味し、0-100の値を設定します。

void softPwmWrite (int pin, int value) ;

指定したGPIOから出力するデューティー比(value)を設定します。

モーターの回転数を制御するプログラム

今回作成したラズパイを使ってモーターを駆動するpythonプログラムはこちらです。
扇風機の風の強さを弱→中→強と変えた後、停止するようにしています。

import wiringpi as pi
import time

# GPIOピンの定義
AIN1 = 26
AIN2 = 16
PWMA = 19
STBY = 20

# GPIOピンの設定
pi.wiringPiSetupGpio()
pi.pinMode( AIN1, pi.OUTPUT )
pi.pinMode( AIN2, pi.OUTPUT )
pi.pinMode( PWMA, pi.OUTPUT )
pi.pinMode( STBY, pi.OUTPUT )

# PWM端子に接続したGPIOをPWM出力できるようにする
pi.softPwmCreate( PWMA, 0, 100 )

# スタンバイ状態にする
print("StandBy")
pi.digitalWrite( STBY, 0 )
pi.digitalWrite( AIN1, 0 )
pi.digitalWrite( AIN2, 1 )
pi.softPwmWrite( PWMA, 0 )

#SW on
pi.digitalWrite( STBY, 1 )

#Level1
print("Level weak")
pi.softPwmWrite( PWMA, 50 )
time.sleep(3)

#Level2
print("Level middle")
pi.softPwmWrite( PWMA, 75 )
time.sleep(3)

#Level3
print("Level strong")
pi.softPwmWrite( PWMA, 100 )
time.sleep(3)

#終了処理
print("Stop!!")
pi.digitalWrite( STBY, 0 )
pi.digitalWrite( AIN2, 0 )
pi.softPwmWrite( PWMA, 0 )

実際に動かしたときの動画です。

まとめ

ラズパイからPWM信号を出力し、モーターの回転数を制御する回路とプログラムを作成しました。モーターはこれまでの仕事でもあまり扱ってこなかったので、モータードライバーの使い方などいろいろと勉強になりました。

今回はDCモーターを使いましたが、モーターの種類は他にもいろいろあるので、今後は別のモーターも使ってみたいと思っています。次はステッピングモーターを使ってカメラのパン制御をしてみたいですね。

最後まで読んでいただきありがとうございました。

ラズパイでSPI通信を行う

ラズパイを使ってハードウェアを制御するシリーズ、第四回目はSPI通信です。
前回と同じく、加速度センサーをラズパイに接続して動かしてみました。

 

SPIとは?

SPIは Serial Peripheral Interface の略でデバイス同士を接続するシリアルバスの1種です。I2Cと同じく、一つのマスターが同じバスで複数のデバイスと通信ができます。通信速度はI2Cに比べて比較的速いです。信号線はSCK, MISO, MOSI, CSの4本から成ります。

規格の詳細は割愛しますが、こちらに日本語の解説があります。
https://www.analog.com/jp/analog-dialogue/articles/introduction-to-spi-interface.html

 

ラズパイとデバイスの接続

ラズパイでSPIを使用するにはデバイスと以下のように接続します。
ラズパイのSPI端子は設定を変えることでGPIO8をCE0#に, GPIO9をMISOに, GPIO10をMOSIに, GPIO11をSCLKに割り当てることができます。

   f:id:masashi_k:20200506223949p:plain

ちなみに、ラズパイにはCE端子がもう一本用意されており、GPIO7がCE1#となります。

今回もアナログデバイセズの加速度センサーADXL345を使います。同一デバイスでI2CとSPIインターフェースの両方を使用できます。今回はSPIインターフェースで動かしてみます。

購入サイト
http://akizukidenshi.com/catalog/g/gM-06724/
データシート
http://akizukidenshi.com/download/ds/freescale/ADXL345_jp.pdf

センサーモジュールとの接続は以下の通りです。

f:id:masashi_k:20200506225233p:plain

 

SPIを使うための設定

Rasbianのデフォルト設定ではラズパイでSPIを使うことはできません。
GUIの設定メニューからSPIを有効にします。

設定ーRaspberry Piの設定ーインターフェース

f:id:masashi_k:20200503232411p:plain

f:id:masashi_k:20200506225701p:plain

SPIを有効にした後、再起動するとSPIが使用できるようになります。

 

バイスがI2C接続で認識されているかを確認するために以下のコマンドを実行します。spi_bcm2835が表示されればOKです。

$ lsmod | grep spi
# spi_bcm2835 16384 0

 

Pythonライブラリのインストール

ラズパイのpythonでSPI通信を行うにはpi-spidevライブラリを使用します。
pipコマンドでインストールします。

$ sudo apt-get install python-pip
$ sudo pip3 install spidev

 

SPIで通信を行うプログラム

上記でインストールしたspidevライブラリを使って値を読み書きするサンプルコードを以下に示します。

import spidev

read_addr = 0x00 
write_addr = 0x1E 
write_data = 0x08

#初期設定
spi = spidev.SpiDev()
spi.open(0,0)
spi.mode = 3  #このデバイスはSPI mode3で動作
spi.max_speed_hz = 1000000

#アドレス"read_addr "の値を読み出す
read_data = spi.xfer2([0x80 | read_addr,0x00]) 
print('read_data = 0x{:02x}'.format(read_data[1]))  # デバイスID 11100101 が読めるはず
# read_data = 0xe5

#アドレス"write_addr "に対してwrite_dataを書き込む
spi.xfer2([write_addr, write_data])

#正しく書き込めたことを確認
read_data = spi.xfer2([0x80 | write_addr,0x00])
print('write_data = 0x{:02x}'.format(read_data[1]))
# write_data = 0x08


いくつかソースコードについての補足です。
①SPI mode

SPIには4つのモードがあります。ポイントはデータをラッチするタイミングがSCLKの立ち上がりエッジか、立下りエッジがということと、SCLK未送信時の電圧レベルがHighかLowかという2点です。今回使用するADXL345のデータシートのP15(図36-37)を見ると、このデバイスはmode3であることが分かるのでmode3で制御します。

f:id:masashi_k:20200510223623p:plain

(引用:http://www.lapis-semi.com/lazurite-jp/contents/reference/spi.html

②spi.xfer2の仕様

spidevのサイトを見ると以下の記載があります。Chip selectを使った通信方法で引数には送信データのリストを渡します。

xfer2(list of values[, speed_hz, delay_usec, bits_per_word])
Performs an SPI transaction. Chip-select should be held active between blocks.

引数の詳細はADXL345のデータシートのP15(図36-37)を確認します。

まずReadの場合、最初のByteのbit7を1にしておく必要があります。bit6は複数Byte読み出し指定。複数Byteを連続で読み出す場合はここを1にする。それ以降5-0bitは読み出すアドレスを指定します。

2Byte目以降は任意の値でよいのですが、0x00としています。読み出すデータが1Byteであれば0x00を一つ、複数Byteを連続で読み出したい場合はその数だけ0x00を送信します。

読み出したデータは2Byte目以降に入ってくるのでリストの2個目以降を参照します。

f:id:masashi_k:20200510230949p:plain

次にWriteの場合ですが、Readとほぼ同じですが最初のByteのbit7が0となります。bit6はReadの時と同様に複数Byte書き込みビットです。以降の5-0bitは書き込むアドレスを指定します。

2Byte目以降は書き込むデータを転送します。複数Byteを連続して書き込む場合はその数だけデータを送ればOKです。

f:id:masashi_k:20200510231131p:plain

リアルタイムに3軸加速度を読み出すプログラム

SPI通信を使ってリアルアイムにx,y,z方向の加速度を読み出すプログラムを作って動かしてみました。

import spidev
import time

#初期設定
spi = spidev.SpiDev()
spi.open(0,0)
spi.mode = 3  #このデバイスはSPI mode3で動作
spi.max_speed_hz = 1000000

spi.xfer2([0x2D, 0x08]) #測定スタート

try:
    while True:
        #x,y,z方向の加速度を取得(2の補数表現)
        x_data_list = spi.xfer2([0xc0|0x32, 0x00, 0x00])
        y_data_list = spi.xfer2([0xc0|0x34, 0x00, 0x00])
        z_data_list = spi.xfer2([0xc0|0x36, 0x00, 0x00])
        x_data = x_data_list[1] | (x_data_list[2] << 8)
        y_data = y_data_list[1] | (y_data_list[2] << 8)
        z_data = z_data_list[1] | (z_data_list[2] << 8)
        #2の補数を10進に変換
        if(x_data & 0x8000):
            x_data = ((~x_data & 0xFFFF) + 1)*-1
        if(y_data & 0x8000):    
            y_data = ((~y_data & 0xFFFF) + 1)*-1
        if(z_data & 0x8000):
            z_data = ((~z_data & 0xFFFF) + 1)*-1
        #加速度に変換(Dレンジ ±2g)
        x_data = 2 * 9.8 * x_data / 0x7FFF
        y_data = 2 * 9.8 * y_data / 0x7FFF
        z_data = 2 * 9.8 * z_data / 0x7FFF
        print('x: {:4.2f}, y: {:4.2f}, z: {:4.2f} [m/s^2]'.format(x_data, y_data, z_data))
        time.sleep(0.1)
    
except KeyboardInterrupt:
    print('!FINISH!')

実行結果は以下の通りです。I2Cと同様に動かすことができました。

f:id:masashi_k:20200510233444p:plain

まとめ

ラズパイでSPI通信を行う方法をまとめてきました。今回使用したspidevはI2Cのモジュールwiringpiと比較して送受信データのフォーマットを意識する必要があるため少し面倒ですが、慣れれば問題なさそうです。

ここまで、UART, I2C, SPIと主要なデータ通信のインターフェースの使い方を勉強できたので、大抵のデバイスはラズパイに接続して制御できるようになったと思います。次回はPWM制御をやってみたいと思っています。

参考サイト

https://stackoverflow.com/questions/48321492/adxl345-device-id-and-offset-being-wrong-raspberry-pi
https://teratail.com/questions/182635

ラズパイでI2C通信を行う

ラズパイを使ってハードウェアを制御するシリーズ、第三回目はI2C通信です。
今回は実際にデバイスをラズパイに接続して動作確認を行ったので、その手法も合わせてまとめておきたいと思います。

I2Cとは?

I2Cは Inter-Integrated Circuit の略でアイ・スクエアード・シーと読みます。
SCL, SDAの二本の信号線からなるシリアルバスです。

規格の詳細は割愛しますが、こちらに日本語の規格仕様書がアップされています。http://ekousaku.web.fc2.com/doc/I2C.pdf

 

ラズパイとデバイスの接続

ラズパイでI2Cを使用するにはデバイスと以下のように接続します。
I2Cの端子はオープンコレクタなので、外付けのプルアップ抵抗が必要です。センサーモジュールをお使いの場合はそちらに実装されていることがあるので、回路図で確認してください。

ラズパイのI2C端子は設定を変えることでGPIO2をSDAに, GPIO3をSCLに割り当てることができます。

 f:id:masashi_k:20200503223555p:plain

 

今回はアナログデバイセズの加速度センサーADXL345を使って動作確認をしてみます。
(こちらのサイトからモジュールを購入しました)
http://akizukidenshi.com/catalog/g/gM-06724/
データシート
http://akizukidenshi.com/download/ds/freescale/ADXL345_jp.pdf

 

センサーモジュールとの接続は以下の回路図のようにしました。CS端子とVDDを接続するとI2Cモードで動作するようになります。

  f:id:masashi_k:20200503231353p:plain


実際の接続(まだ電源は入れていません)

f:id:masashi_k:20200503231802j:plain



I2Cを使うための設定

Rasbianのデフォルト設定ではラズパイでI2Cを使うことはできません。
GUIの設定メニューからI2Cを有効にします。

設定ーRaspberry Piの設定ーインターフェース

f:id:masashi_k:20200503232411p:plain

f:id:masashi_k:20200503232554p:plain

I2Cを有効にした後、再起動するとI2Cが使用できるようになります。

 

バイスがI2C接続で認識されているかを確認するために以下のコマンドを実行します。

$ i2cdetect -y 1


正しく認識されると以下のように表示されます。0x1dが接続されている加速度センサーのスレーブアドレスです。

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- 1d -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

Pythonライブラリのインストール

ラズパイのpythonでI2C通信を行うにはWiringPiライブラリを使用します。
pipコマンドでインストールします。

$ pip3 install wiringpi

 

I2Cで通信を行うプログラム

上記でインストールしたWiringPiライブラリを使って値を読み書きするサンプルコードを以下に示します。

import wiringpi

slv_addr = 0x1D   #デバイスのスレーブアドレス
read_addr = 0x00   #デバイス内でアクセスしたいアドレス
write_addr = 0x1E    #デバイス内でアクセスしたいアドレス
write_data = 0x08   #書き込むデータ値

#初期設定
wiringpi.wiringPiSetup()
i2c = wiringpi.I2C() #get I2C
Fd = i2c.setup(slv_addr) #setup I2C device

#アドレス"read_addr"の値を読み出す
read_data = i2c.readReg8(Fd, read_addr)
print('read_data = 0x{:02x}'.format(read_data))  # デバイスID 11100101 が読めるはず
#>>read_data = 0xe5

#アドレス"write_addr"に対してwrite_dataを書き込む
i2c.writeReg8(Fd, write_addr , write_data)

#正しく書き込めたことを確認
read_data = i2c.readReg8(Fd, write_addr)
print('write_data = 0x{:02x}'.format(read_data)) 
#>>write_data = 0x08

read, writeとも8bit用の関数を使用していますが、16bitの関数も準備されています。

  • int wiringPiI2CWriteReg8 (int fd, int reg, int data) ;
  • int wiringPiI2CWriteReg16 (int fd, int reg, int data) ;
  • int wiringPiI2CReadReg8 (int fd, int reg) ;
  • int wiringPiI2CReadReg16 (int fd, int reg) ;

wiringpiのサイト
http://wiringpi.com/reference/i2c-library/

 

リアルタイムに3軸加速度を読み出すプログラム

i2cのアクセス関数を用いてリアルアイムにx,y,z方向の加速度を読み出すプログラムを作りましたので、参考までに乗せておきたいと思います。

import wiringpi
import time

slv_addr = 0x1D

#初期設定
wiringpi.wiringPiSetup()
i2c = wiringpi.I2C() #get I2C
Fd = i2c.setup(slv_addr) #setup I2C device

i2c.writeReg8(Fd, 0x2D , 0x08) #測定スタート

try:
    while True:
#x,y,z方向の加速度を取得(2の補数表現) x_data = i2c.readReg16(Fd, 0x32) y_data = i2c.readReg16(Fd, 0x34) z_data = i2c.readReg16(Fd, 0x36) #2の補数を10進に変換 if(x_data & 0x8000): x_data = ((~x_data & 0xFFFF) + 1)*-1 if(y_data & 0x8000): y_data = ((~y_data & 0xFFFF) + 1)*-1 if(z_data & 0x8000): z_data = ((~z_data & 0xFFFF) + 1)*-1
#加速度に変換(Dレンジ ±2g) x_data = 2 * 9.8 * x_data / 0x7FFF y_data = 2 * 9.8 * y_data / 0x7FFF z_data = 2 * 9.8 * z_data / 0x7FFF print('x: {:2.2f}, y: {:2.2f}, z: {:2.2f} [m/s^2]'.format(x_data, y_data, z_data)) time.sleep(0.1) except KeyboardInterrupt: print('!FINISH!')

実行結果は以下の通りです。センサーを動かすと加速度が変化することが確認できました。

f:id:masashi_k:20200505011606p:plain

 

まとめ

ラズパイでデバイスとI2C通信を行う方法を記載しました。加速度への変換部分がこれでよいのか自信がないですが、I2Cの読み書きは問題ないはず。
せっかく加速度センサを購入したので、何か面白い使い道がないか考えてみたいと思います。

 

Keras/Tensorflow - h5/hdf5からckptへの変換方法

以前、ckptからpbへの変換方法をブログに書きましたが、今回はKerasのh5/hdf5形式からckptへ変換する方法を調べましたので、ここにメモしておきます。

masaeng.hatenablog.com

 

変換方法

私の環境でうまくいったサンプルコードです。

from keras import backend as K
from keras.models import load_model
import tensorflow as tf

K.clear_session()
K.set_learning_phase(0)

model = keras.models.load_model("xxx.h5")
sess = keras.backend.get_session()

saver = tf.train.Saver()
save_path = saver.save(sess, "xxxx.ckpt")

上のリンクのckptからpbへ変換する方法と組み合わせるとh5/hdf5形式からpb形式に変換することができます。

また、こちらの手法では直接h5/hdf5からpbに変換することができます。
1回で済むのでこちらの方が手軽かもしれません。

from keras import backend as K
from keras.models import load_model
import tensorflow as tf

def freeze_session(session, keep_var_names=None, output_names=None, clear_devices=True):

    from tensorflow.python.framework.graph_util import convert_variables_to_constants
    graph = session.graph
    with graph.as_default():
        freeze_var_names = list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or []))
        output_names = output_names or []
        output_names += [v.op.name for v in tf.global_variables()]
        # Graph -> GraphDef ProtoBuf
        input_graph_def = graph.as_graph_def()
        if clear_devices:
            for node in input_graph_def.node:
                node.device = ""
        frozen_graph = convert_variables_to_constants(session, input_graph_def,
                                                    output_names, freeze_var_names)
        return frozen_graph


def main():
    model = load_model('xxx.h5')
    frozen_graph = freeze_session(K.get_session(),
                            output_names=[out.op.name for out in model.outputs])

    # Save pb file
    tf.train.write_graph(frozen_graph, "SAVE_PATH", "xxxx.pb", as_text=False)

if __name__ == '__main__':
    main()

 

参考サイト

https://stackoverflow.com/questions/52650842/how-to-convert-hdf5-to-tensorflow-checkpoint

https://www.dlology.com/blog/how-to-convert-trained-keras-model-to-tensorflow-and-make-prediction/

5Gの規格を勉強して分かった、理解する上での前提知識と誤解していたこと

仕事で5Gを扱うことになり、いろいろな文献を読み漁って5Gの規格を調査しました。これだけ話題となっている技術で、すでにサービスが開始されようとしている段階で今更感があるのですが。。。

前置き:どのようにして調査したか

初めは英語の規格書にチャレンジするも、膨大すぎてどこが自分に関係があるところかすらよくわからない。

日本語のサイトを探してもニュース記事程度の簡単なものばかりで、技術的な詳細な解説は少ない。5Gはこれまでの移動通信システム規格と異なり、キャリヤや基地局、携帯端末メーカー以外の幅広い分野での活用が期待されているため、専門家でなくても理解できるように優しく書かれている記事が多い印象です。ですが、端末を開発するのは全く不十分。

docomoがジャーナルにて技術解説を公開してくれていますが、これまでの規格を知っていることが前提で書かれています。例えば、4Gからどう変わったかという差分の説明がメインです。今まで移動通信に携わっていない本当の初心者がこの説明だけを読んで理解するのはほぼ不可能だと思います。

www.nttdocomo.co.jp

今回、以下の文献をメインに4Gの規格に遡って勉強しました。

インプレス標準教科書シリーズ 5G教科書 ―LTE/ IoTから5Gまで―

インプレス標準教科書シリーズ 5G教科書 ―LTE/ IoTから5Gまで―

 

規格の調査を通して、5Gの規格を理解する上で、ある程度の前提知識が必要だと感じました。無線端末や基地局を扱う方にとっては当然の内容ですが、前述したようなニュース記事ではあまり触れられていないことをまとめておきたいと思います。

また、5Gについてよく言われていることで、個人的に誤解していたこともいくつかありました。これらについて正しくはどうなのかを書いていきたいと思います。

 

5Gの規格を理解するための前提知識

3GPP, ITU-R

5Gの規格を調べていると3GPPITU-Rという単語がよく出てきます。ともに規格団体のようで、それぞれで5Gの規格をリリースするスケジュールになっており、関係性がわかりませんでした。

図3 3GPPリリース15と2019年末のリリース16のロードマップ 引用:https://sgforum.impress.co.jp/article/4880

Wikipediaで調べると、3GPPは移動通信システムの標準化作業を行うプロジェクト、ITU-Rは電気通信分野における国際連合の専門機関である国際電気通信連合(ITU: International Telecommunication Union)の無線通信部門とのことです。

ITU-Rは各国の代表が集まった国際機関であるのに対し、3GPPは企業が集まった団体です。移動通信の国際規格はITU-Rで決定するのですが、ITU-Rは自ら規格を作成せず、世界から案を募集しています。3GPPはこの提案書を作成する目的で結成されています。

ですのでスケジュールとしては3GPPが仕様案をリリースし、それをITU-Rが確認して仕様策定という流れとなります。正式仕様はITU-Rがリリースするものとなりますが、技術者としては3GPPの動きを注目する必要がありますね。

3GPPで検討された規格はリリース○○という形でリリースされます。リリース15以降が5Gの規格に相当します。一方、ITU-Rで策定する5G相当の規格はIMT-2020という名称です。

 参考サイト
https://sgforum.impress.co.jp/article/4880
http://www.rf-world.jp/bn/RFW37/samples/p079-080.pdf

 

3GPPのリリースと移動通信規格の世代との関係

3GPPは1,2年おきにバージョンアップした機能をリリースしています。一方で、よく耳にする"3G", "4G"という呼び方は移動通信の機能が大きく変わるタイミングで切り替わっており、3GPPの複数のリリースを指しています。具体的には以下のようになります。

f:id:masashi_k:20200321000538p:plain


③リリース15から5Gの規格を規定

②でも少し触れましたが、3GPPのリリース15から5Gの規格が規定されています。このリリース15には従来のLTE/LTE-Advancedと互換性がある高度化LTEと、これまでとは全く異なる周波数帯を用いる新たな無線技術New Radio(NR)の大きく2つの規格が含まれています。NRは従来のLTE/LTE-Advancedとは互換性がありません。

通常、"5G"と言うときに指しているのはNew Radioの技術だと思いますが、正式には高度化LTEも含めてリリース15以降で規定されている移動通信システムを5Gと呼びます。


引用:https://www.edn.com/what-is-5g-nr/

 

移動体通信システムの構成

移動体通信システムは端末と基地局が電波で接続されており、さらに交換局を通してインターネットや別の端末に接続されるという構成から成っています。

「携帯電話 基地局 インターネット」の画像検索結果引用:https://time-space.kddi.com/au-kddi/20190514/2627

今となっては当然のことですが、当時はこの構成が頭に入ってなく、この構成を意識することで文献の内容がよく理解できるようになったことがあります。

例えば、スタンドアローン(SA)方式、ノンスタンドアローン(NSA)方式。
基地局、交換局(コアネットワーク)は4G, 5G(NR)で別のものが必要となるので、早期にサービスを提供するために4Gの設備を流用して運用する方式(NSA)が取られます。


引用:https://www.soumu.go.jp/main_content/000593247.pdf

 

5Gについて誤解していたこと

①導入スケジュール

 5Gの3つの特徴は「超高速・大容量」、「低遅延」、「同時多接続」と言われていますが、5Gが導入されてからすぐにこれらすべての特徴が利用できるわけではありません。ニュース記事ではよくこれらが同列で扱われているため、誤解を招いていると思っています。

5Gの検討スケジュールは以下のように2ステップとなっています。
 フェーズ1:”超高速・大容量”を中心に2020年に実現する5Gの基本仕様の策定
 フェーズ2:2020年以降の実現を念頭に、”低遅延”、”多接続”を含む5Gのフルスペックの策定

5Gのロードマップ。2018年6月に、5Gのフェーズ1の仕様策定が完了する見込みだ(クリックで拡大) 出典:IHS Markit Technology引用:IHS Markit Technology

 

上図ではリリース16は19年12月に策定完了となっていますが、実際は3ヶ月遅れて2020年3月に完了予定です。

20年3月にdocomoが5Gのサービスをスタートするそうですが、そのサービスは「超高速・大容量」ということですね。「低遅延」、「同時多接続」が利用できるようになるのは2022年以降と言われています。これは5Gの導入当初は、先ほど述べたノンスタンドアローン方式でサービスが提供されることに起因します。5Gのフルスペックを実現するには交換局(コアネットワーク)も5G用のものにする必要があります。

 

②ローカル5G

5Gに関するニュース記事ではローカル5Gというワードもよく目にします。当初このワードを見たときに、LoRaのように自営で基地局を設置し、専用のネットワークを構築できると思ってしまいました。

しかし、ローカル5GはLoRaと異なり、基地局を立てて自営網を構築するにも無線局免許を取得する必要があります。LoRaはアンライセンスバンドを使用するため、免許不要でしたが、5Gはそうではないため、自営網だとしても免許が必要ということですね。

また、ローカル5Gの導入メリットについても、誤解していた点がありました。LoRaの場合、自拠点内に端末を多数設置する場合、端末1台ごとにサービス利用料を払うよりは自前で通信網を作る方がランニングコストがなくなるため、通信コストを削減できるというメリットがあったと思います。

しかし、ローカル5Gに関しては別のところに導入する意義があります。

5Gのスケジュールのところに記載しましたが、5Gのフルサービスが提供されるのは2022年以降となる見込みです。また、キャリアが設備を設置するのは利用者が多い都市部が最初で、人口が少ない地方は導入がさらに後になる可能性が高いです。

一方、産業界において、低遅延など5Gの特徴を生かしたい工場や建設現場などは地方にあることが多く、キャリアによる5Gのサービス導入を待つと非常に時間がかかってしまします。サービスを利用したい企業自らが自拠点に通信網を早期に構築し、5Gの特徴を生かしてビジネスを展開できる方法がローカル5Gです。

 

まとめ

5Gの規格を勉強する中で、前提知識として知っておけば理解が速いと思ったこと、勉強前に誤解をしてしまっていたことをまとめてきました。

移動体無線通信の初心者が1から規格を勉強するのはなかなかハードルが高かったですが、同様のことをされる方がこの記事によって少しでもスムーズに規格を習得できればと思います。