RubyとC.パート4. Raphael.jsを使用した加速度計、ジャイロスコープ、距離計の仲間

iv_sの前の部分( 2 または 3 )で、CとRubyを一緒に使用するためのさまざまな手法について説明しました。 別の可能なバンドル-既存のシステムC関数の使用についてお話したいと思います。



私は描画ロボットをゆっくりと改善しています。 Rubyで書かれているため、ジャイロスコープを備えた加速度計を接続したときに、もちろんこの技術を使い続けたいと思いました。



結局のところ、RubyでI2Cバス関数にアクセスするのは非常に簡単です。これにより、Cで既に作成およびインストールされたライブラリを使用できます







作業のスキームは次のとおりです。

RaspberryPiはSinatraサーバーを実行します。Sinatraサーバーにアクセスすると、X軸とY軸に沿ったボードの回転に関するデータと、最も近い障害物までの距離(センチメートル)が提供されます。

Raphael3d.jsを使用して視覚化とデバッグを行うための簡単なスクリプトがクライアントに書き込まれました。Raphael3d.jsはデバイスを100ミリ秒ごとにポーリングし、物理的なボードの位置に応じて回路ボードを回転させます。



ハードウェア



加速度計/ジャイロスコープボードを接続します。 私の場合、これは3 ドルのMPU6050です。



レジスタの読み取り/書き込み、初期化など、このボードの機能にアクセスするには、 wiringPiをインストールする必要があります。 リーダーの1つが最新でない場合、wiringPiはピン(GPIO)およびRaspberryPiデバイスに簡単にアクセスできます。 したがって、以下で説明するメカニズム全体は、LEDの点滅からPWMの操作まで、どのタスクでも有効です。



次のステップは、コンパイルされたwiringPiライブラリを見つけて、rubyプロジェクトに接続することです。

require 'fiddle' wiringpi = Fiddle.dlopen('/home/pi/wiringPi/wiringPi/libwiringPi.so.2.25')
      
      





これで、開発者が設計した形式で、このライブラリからすべての関数を直接呼び出すことができます。

Fiddleは、libffi(Foreign Function Interface Library)の標準* nixメカニズムを使用する標準Rubyモジュールです。

ライブラリのすべての機能が必要なわけではないため、必要な機能を選択して、それらのみを登録します。



lightingPiI2C.hファイルで必要なものを選択してください

 extern int wiringPiI2CSetup (const int devId) ; extern int wiringPiI2CWriteReg8 (int fd, int reg, int data) ;
      
      







そして、プログラムで接続します。

 int = Fiddle::TYPE_INT @i2c_setup = Fiddle::Function.new(wiringpi['wiringPiI2CSetup'], [int], int) @i2c_write_reg8 = Fiddle::Function.new(wiringpi['wiringPiI2CWriteReg8'], [int, int, int], int)
      
      





パラメーターは、関数の名前、受け入れられたパラメーターの配列、および戻り値です。 ポインターが渡される場合、タイプに関係なく、それらはFiddle :: TYPE_VOIDPと同等になります



これは、接続された関数の呼び出し方法です。

 @fd = @i2c_setup.call 0x68 #    I2C.        i2cdetect. @i2c_write_reg8.call @fd, 0x6B, 0x00 #   ,   0x6B  0.    –     .
      
      





以上で、必要なすべての関数を宣言するコンストラクターでMPU6050クラスを作成し、小さなカルマンマジックを使用してボード回転に関するデータを返す測定関数を作成しました。

加速度計を操作するための完全なクラスコード
 require 'fiddle' class MPU6050 attr_reader :last_x, :last_y, :k def initialize(path_to_wiring_pi_so) wiringpi = Fiddle.dlopen(path_to_wiring_pi_so) int = Fiddle::TYPE_INT char_p = Fiddle::TYPE_VOIDP # int wiringPiI2CSetup (int devId) ; @i2c_setup = Fiddle::Function.new(wiringpi['wiringPiI2CSetup'], [int], int) # int wiringPiI2CSetupInterface (const char *device, int devId) ; @i2c_setup_interface = Fiddle::Function.new(wiringpi['wiringPiI2CSetupInterface'], [char_p, int], int) # int wiringPiI2CRead (int fd) ; @i2c_read = Fiddle::Function.new(wiringpi['wiringPiI2CRead'], [int], int) # int wiringPiI2CWrite (int fd, int data) ; @i2c_write = Fiddle::Function.new(wiringpi['wiringPiI2CWrite'], [int, int], int) # int wiringPiI2CWriteReg8 (int fd, int reg, int data) ; @i2c_write_reg8 = Fiddle::Function.new(wiringpi['wiringPiI2CWriteReg8'], [int, int, int], int) # int wiringPiI2CWriteReg16 (int fd, int reg, int data) ; @i2c_write_reg8 = Fiddle::Function.new(wiringpi['wiringPiI2CWriteReg16'], [int, int, int], int) # int wiringPiI2CReadReg8 (int fd, int reg) ; @i2c_read_reg8 = Fiddle::Function.new(wiringpi['wiringPiI2CReadReg8'], [int, int], int) # int wiringPiI2CReadReg16 (int fd, int reg) ; @i2c_read_reg16 = Fiddle::Function.new(wiringpi['wiringPiI2CReadReg16'], [int, int], int) @fd = @i2c_setup.call 0x68 @i2c_write_reg8.call @fd, 0x6B, 0x00 @last_x = 0 @last_y = 0 @k = 0.30 end def read_word_2c(fd, addr) val = @i2c_read_reg8.call(fd, addr) val = val << 8 val += @i2c_read_reg8.call(fd, addr+1) val -= 65536 if val >= 0x8000 val end def measure gyro_x = (read_word_2c(@fd, 0x43) / 131.0).round(1) gyro_y = (read_word_2c(@fd, 0x45) / 131.0).round(1) gyro_z = (read_word_2c(@fd, 0x47) / 131.0).round(1) acceleration_x = read_word_2c(@fd, 0x3b) / 16384.0 acceleration_y = read_word_2c(@fd, 0x3d) / 16384.0 acceleration_z = read_word_2c(@fd, 0x3f) / 16384.0 rotation_x = k * get_x_rotation(acceleration_x, acceleration_y, acceleration_z) + (1-k) * @last_x rotation_y = k * get_y_rotation(acceleration_x, acceleration_y, acceleration_z) + (1-k) * @last_y @last_x = rotation_x @last_y = rotation_y # {gyro_x: gyro_x, gyro_y: gyro_y, gyro_z: gyro_z, rotation_x: rotation_x, rotation_y: rotation_y} "#{rotation_x.round(1)} #{rotation_y.round(1)}" end private def to_degrees(radians) radians / Math::PI * 180 end def dist(a, b) Math.sqrt((a*a)+(b*b)) end def get_x_rotation(x, y, z) to_degrees Math.atan(x / dist(y, z)) end def get_y_rotation(x, y, z) to_degrees Math.atan(y / dist(x, z)) end end
      
      







このアプローチは、厳密な時間制限がない場合に完全に正当化されます。 つまり、ミリ秒になると。 しかし、マイクロ秒になると、プログラムでCコード挿入を使用する必要があります。 それ以外の場合は、時間がありません。



これはレンジファインダーで起こりました。その動作原理は、測定開始の信号を10マイクロ秒で送信し、逆パルスの長さを測定し、係数で除算してセンチメートル単位の距離を取得することです。

距離を測定するクラス
 require 'fiddle' require 'inline' class HCSRO4 IN = 0 OUT = 1 TRIG = 17 ECHO = 27 def initialize(path_to_wiring_pi_so) wiringpi = Fiddle.dlopen(path_to_wiring_pi_so) int = Fiddle::TYPE_INT void = Fiddle::TYPE_VOID # extern int wiringPiSetup (void) ; @setup = Fiddle::Function.new(wiringpi['wiringPiSetup'], [void], int) # extern int wiringPiSetupGpio (void) ; @setup_gpio = Fiddle::Function.new(wiringpi['wiringPiSetupGpio'], [void], int) # extern void pinMode (int pin, int mode) ; @pin_mode = Fiddle::Function.new(wiringpi['pinMode'], [int, int], void) @setup_gpio.call nil @pin_mode.call TRIG, OUT @pin_mode.call ECHO, IN end inline do |builder| #sudo cp WiringPi/wiringPi/*.h /usr/include/ builder.include '<wiringPi.h>' builder.c ' double measure(int trig, int echo){ //initial pulse digitalWrite(trig, HIGH); delayMicroseconds(20); digitalWrite(trig, LOW); //Wait for echo start while(digitalRead(echo) == LOW); //Wait for echo end long startTime = micros(); while(digitalRead(echo) == HIGH); long travelTime = micros() - startTime; double distance = travelTime / 58.0; return distance; } ' end end
      
      







最小サーバー:

 require 'sinatra' require_relative 'mpu6050' require_relative 'hcsro4' configure do set :mpu, MPU6050.new('/home/pi/wiringPi/wiringPi/libwiringPi.so.2.25') set :hc, HCSRO4.new('/home/pi/wiringPi/wiringPi/libwiringPi.so.2.25') end get '/' do response['Access-Control-Allow-Origin'] = '*' settings.mpu.measure.to_s + ' ' + settings.hc.measure(17, 27).to_s # ,     end
      
      





pythonで書かないために人々がしないこと...

問題を解決するための多くの代替オプションがありますが、私は自分自身にもっと興味があります。

理論的には、RubyでlightingPiを操作するために必要なライブラリがありますが、公開時点では、2番目のモデルのRaspberryPiの作業をサポートしていません。

libffiエンジン用の便利なRuby ラッパーもあり、DSLが明確で、すべての例外を処理できます。



可視化



Ajaxは100ミリ秒ごとにリクエストし、Raphaelを使用してマッピングします。 厳密に言えば、これはRaphael自体ではなく、3次元オブジェクトを操作するための拡張機能です。

  var scene, viewer; var rotationX = 0, rotationY = 0; var divX = document.getElementById('rotation_x'); var divY = document.getElementById('rotation_y'); function rotate(x, y, z){ scene.camera.rotateX(x).rotateZ(y).rotateY(z); viewer.update(); } function getAngles(){ var r = new XMLHttpRequest(); r.open('get','http://192.168.0.102:4567', true); r.send(); r.onreadystatechange = function(){ if (r.readyState != 4 || r.status != 200) return; var angles = r.responseText.split(' '); divX.textContent = angles[0]; divY.textContent = angles[1]; x_deg = Math.PI * (parseFloat(angles[0]) - rotationX)/ 180; y_deg = Math.PI * (parseFloat(angles[1]) - rotationY)/ 180; rotate(x_deg, y_deg, 0); rotationX = parseFloat(angles[0]); rotationY = parseFloat(angles[1]); } } window.onload = function() { var paper = Raphael('canvas', 1000, 800); var mat = new Raphael3d.Material('#363', '#030'); var cube = Raphael3d.Surface.Box(0, 0, 0, 5, 4, 0.15, paper, {}); scene = new Raphael3d.Scene(paper); scene.setMaterial(mat).addSurfaces(cube); scene.projection = Raphael3d.Matrix4x4.PerspectiveMatrixZ(900); viewer = paper.Viewer(45, 45, 998, 798, {opacity: 0}); viewer.setScene(scene).fit(); rotate(-1.5,0.2, 0); var timer = setInterval(getAngles, 100); document.getElementById('canvas').onclick = function(){ clearInterval(timer); } }
      
      





結論として、私は現代の機会に魅了されていると言えます。 I2CバスとJavascriptの使用は、技術の異なる極にあります。 ハードウェア開発、3Dグラフィックス、およびJavascriptのギャップは、プログラマーによってまったく行われなくても、私の反対のマネージャーであるとしても、それほど深いことではありません。 豊富なドキュメントを掛けた喫煙マニュアルは、それ自体が感じられます。



PS

私はミンスクハッカースペースのすべての腺を取りました、完全なプロジェクトコードはここで見つけることができます



All Articles