2013年8月19日月曜日

夏休みの工作 ~pythonにおけるセンサ出力値のビット演算~

大した話ではないけど半日詰まったのでメモ。


I2Cなんかから1バイト×2(Hbit、Lbit)のような形でデータを受け取って数値に変換するときの注意点。

例:
Hbyte : 11101110
Lbyte : 00011111

→2つあわせて 1110111000011111 を作ってから数値に変換する。Hの方を左に8bitシフトしたり256を掛けたりしてから足す(あるいはHとLのorをとる)

特にこのあたりでは問題は無いが、2の補数を取って負の値を出したいときpythonでは正常に処理できない場合がある。

例:
H : 10000000
L : 11001100

あわせて 1000000011001100(符号付10進数で-32564) となる。この値をそのままpythonで扱うとpythonの中では32972として扱われる(符号なし扱いのため)。何bitの符号付整数か(どのビットが1だと負扱いなのか)がpythonに認識できないので当然こうなるといえばこうなる。しかしpythonに例えばこのビット列は16ビット符号付整数として扱ってね、とやる方法もちょっとわからなかったので普通にxorを使って2の補数を算出する処理を実装する。これで何ビット整数として扱わせるかもこちらで指定できる。

(プログラムのイメージ)

if H & 0b1000000=0b10000000:   ←Hバイトの頭が1(負数)だったら。ここの数値の桁数でビットを指定している。

   val= (H <<8) | L ←HとLを合体 1000000011001100
   val_inv=val ^ 0b1111111111111111 ←「^」はxor(pythonでは累乗は**なので注意)16bitの全部1の値とxorをとってすべてのビットを反転する。
  val_inv = val_inv+1    ←1足して2の補数にする。この時点で正しい値の絶対値になっている
 out =val_inv*(-1) ←マイナスをつける

以上。これで上の例だと outが-32564になる。
面倒だけどこれをやらないとpythonでは符号付出力のI2Cセンサがまともに使えないので地味に重要。

2013年8月10日土曜日

夏休みの工作 ~python-arduino連携でI2Cを辛うじて使う(今日の分)~

センサーとかモータードライバ(まだ碌なのがないが)を少ないPIN数で使うためにI2Cが必要だったのでありがたい外部ライブラリを勝手に拡張してみた。

今回の参考資料:Tristan Hearn氏が作ってくださった以下のライブラリ。どうやらNASAの方でお勤めの方っぽい((((゜Д゜;))))。ちなみにライブラリはMITライセンス。
 ↓入手場所
https://github.com/thearn/Python-Arduino-Command-API

↓MITライセンスに基づく告知(ここから)
Copyright (c) 2012-2013 Tristan A. Hearn <tristanhearn@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

↑(ここまで)

今回は上記ライブラリのソースを勝手に弄ってしまうお話です。MITライセンス万歳。

↓python側のプログラム。これを自分のプログラムの上のほうに差し込んでおきます。要上記ライブラリとそれを動かすのに必要なもの

from Arduino import Arduino
class Arduino_C(Arduino):
    def __init__(self,baud=9600,port=None,timeout=2):
        Arduino.__init__(self, baud, port, timeout)

    def i2c_write_1(self,device,data):
        print "i2cwrite"
        cmd_str=''.join(["@i2cw1%",str(device),"%",str(data),"$!"])
        try:
            self.sr.write(cmd_str)
            self.sr.flush()
        except:
            pass
        rd = self.sr.readline().replace("\r\n","")
        print "i2c return",hex(int(rd))
    def i2c_write_2(self,device,data1,data2):
        print "i2cwrite"
        cmd_str=''.join(["@i2cw2%",str(device),"%",str(data1),"%",str(data2),"$!"])
        try:
            self.sr.write(cmd_str)
            self.sr.flush()
        except:
            pass
        rd = self.sr.readline().replace("\r\n","")
        print "i2c return",hex(int(rd))

    def i2c_read_1(self,device,location):
        cmd_str=''.join(["@i2cr1%",str(device),"%",str(location),"$!"])
        try:
            self.sr.write(cmd_str)
            self.sr.flush()
        except:
            pass
        rd = self.sr.readline().replace("\r\n","")

        output=int(rd)
        return output

    def i2c_read_2(self,device,location):
        cmd_str=''.join(["@i2cr2%",str(device),"%",str(location),"$!"])
        try:
            self.sr.write(cmd_str)
            self.sr.flush()
        except:
            pass
        rd1 = self.sr.readline().replace("\r\n","")
        rd2 = self.sr.readline().replace("\r\n","")
        output=[int(rd1),int(rd2)]
        return output

↑筆者の頭脳とやる気の関係で1バイトまたは2バイトの書き込み、読み込みの4つの関数を追加しただけ、せめてバイト数ぐらいは可変にすべきだが・・・しょうがないよね、夏風邪引いてるしね。

引数のdeviceとかdata、locationとかは16進数(pythonでも0x00表記)でやると頭で変換しなくていいから楽。

↓以下、arduino側のソース(prototype.ino)に追加した部分のその1。serial_parser関数より上のどこかに差し込む。

void i2c_write_1b(String data){
    String sdata[2];
    split(sdata,2,data,'%');
    int id = Str2int(sdata[0]);
    int wdata = Str2int(sdata[1]);
    Wire.beginTransmission(id);
    Wire.write(wdata);
    Wire.endTransmission();
    Serial.println(id);
}

void i2c_write_2b(String data){
    String sdata[3];
    split(sdata,3,data,'%');
    int id = Str2int(sdata[0]);
    int wdata1 = Str2int(sdata[1]);
    int wdata2 = Str2int(sdata[2]);
    Wire.beginTransmission(id);
    Wire.write(wdata1);
    Wire.write(wdata2);
    Wire.endTransmission();
    Serial.println(id);
}

void i2c_read_1b(String data){
    String sdata[2];
    split(sdata,2,data,'%');
    int id = Str2int(sdata[0]);
    int location = Str2int(sdata[1]);
    Wire.beginTransmission(id);
    Wire.write(location);
    Wire.endTransmission(false);
    Wire.requestFrom(int(id),1,false);
    int out1 = Wire.read();              
    Wire.endTransmission(true);
    Serial.println(out1);
   
}

void i2c_read_2b(String data){
    String sdata[2];
    split(sdata,2,data,'%');
    int id = Str2int(sdata[0]);
    int location = Str2int(sdata[1]);
    Wire.beginTransmission(id);
    Wire.write(location);
    Wire.endTransmission(false);
    Wire.requestFrom(int(id),2,false);
    int out1 = Wire.read();              
    int out2 = Wire.read();
    Wire.endTransmission(true);
    Serial.println(out1);
    Serial.println(out2);
}

↑ここまで

↓その2。serial_parserのcmd==(コマンド)のelse ifが続いているあたりに自然に差し込む。

else if (cmd =="i2cw1"){
   i2c_write_1b(data);
  }
    else if (cmd =="i2cw2"){
   i2c_write_2b(data);
  }
  else if (cmd =="i2cr1"){
   i2c_read_1b(data);
  }
  else if (cmd =="i2cr2"){
   i2c_read_2b(data);
  }

↑ここまで

明らかに命令の種類が足りてないような気がするが、ストリナとかで売ってるようなジャイロとかならこれである程度読み書きできるはず。読み込んだデータバイトの処理(2つのバイトを接続したりとか単位換算とか)なんかはpython側で。

いつかHearn氏が正規(?)のI2C実装をしてくれることを祈りつつ今回は終了。

P.S : prototype.ino のsetup() 内に Wire.begin(); を入れないと何も機能しないので注意(10分ほどはまった)