swerveのアイコン画像
swerve 2022年10月17日作成
セットアップや使用方法 セットアップや使用方法 閲覧数 3892
swerve 2022年10月17日作成 セットアップや使用方法 セットアップや使用方法 閲覧数 3892

オプティカルフローセンサ PMW3901 (UART版) を Arduino で読んでみる

オプティカルフローセンサ PMW3901 (UART版) を Arduino で読んでみる

はじめに

オプティカルフローセンサとは、画像中の物体の移動方向・量を検知するセンサで、PCのマウスの移動量計測や物体追跡、ドローンの位置推定などに使用されています。

通販などで簡単に入手できるオプティカルフローセンサは種類が少なく、大抵はPMW3901というセンサを積んでいるモジュールが売られています。
PMW3901を使ったモジュールの中にはSPI方式のものとUART式の2種類があり、
SPI方式のものはArduinoのライブラリがあるのですが、UART式はライブラリがなかった?のと使用例を紹介する記事などもなかったので、ここで使い方をまとめます。

UART版PMW3901はmarutsuで購入しました。
購入先URL:https://www.marutsu.co.jp/pc/i/33033296/
価格は2500円程度、納期は5~7日程度(2022年10月記事執筆時)でした。

仕様

主な仕様
電源 3.3~3.6V
UARTのボーレート 19200
FPS 66Hz
寸法 14 * 11 * 5 mm
重量 0.6g

データシートはここです。でもこのデータシート、センサを読むのに必要な情報はありますが、内部でどんな処理をしているかに関する情報が皆無なんですよね

センサにX,Yの指定があり、以下のようになっています。
キャプションを入力できます

UARTで送られてくるデータのフォーマットは以下のようになっています。
キャプションを入力できます

x,yの移動量のほかに表面の粗さ?も計測できるそうですが、これに関してはどう処理すれば良いのかよく分かりませんでした。

また、通販で写真を見てもどこになんのピン配置が生えているのかが謎だったのですが、実際に届いたセンサはこのようなものでした。
キャプションを入力できます
↑ 表/裏

Y,T,V,Gの4つパッドがありました。
TがTX、Vが3.3~3.6V、GがGNDです。
Yが何のピンかはデータシートに書いてなかったので謎ですが、特に使わなくても値は読めます。

ArduinoUnoで色々やってたら読めたのであまりきれいなコードではないですが、その時のコードを貼っておきます。
TをArduinoのRXに、V,Gをそれぞれ3.3VとGNDに繋げます。

arduino側コード

PMW3901(UART版)を読むコード

#include <SoftwareSerial.h> SoftwareSerial mySerial(2,3); int hd = 0; int16_t x_value16bit; int16_t y_value16bit; void setup() { Serial.begin(115200); mySerial.begin(19200); } void loop() { byte x_buffer[2]; byte y_buffer[2]; int data[9]; while(mySerial.available()){ byte inChar = (byte)mySerial.read();//シリアル読み込み if(inChar == 0xFE && hd == 0){//初期状態でヘッダを読み込んだとき読み取り開始 hd = 1; data[0] = int(inChar); } else if(hd == 1){ if(inChar != 0x04){//ヘッダの次にnumber of data bytesが来てなかったらデータを捨てる hd =0; for( int i=0;i<9;i++){ data[i] = 0; } } else{//正しかった読み取りを続ける hd = hd +1; data[1] = int(inChar); } } else if(hd == 0 && inChar != 0xFE){//初期状態で読み込んだものがヘッダでない場合初期化 hd = 0; for( int i=0;i<9;i++){ data[i] = 0; } } else{ hd = hd +1; data[hd-1] = int(inChar); } if(hd == 3){//xの下位、上位バイトを結合 x_buffer[0] = inChar; } else if( hd == 4 ){ x_buffer[1] = inChar; x_value16bit = x_buffer[1] << 8 | x_buffer[0]; } if(hd == 5){//yの下位、上位バイトを結合 y_buffer[0] = inChar; } else if( hd == 6 ){ y_buffer[1] = inChar; y_value16bit = y_buffer[1] << 8 | y_buffer[0]; } else{ } //一応ヘッダとフッタが正しいのかをチェックしてセンサから来たXY差分を表示 if(inChar == 0xAA && hd == 9){ Serial.print("x_diff :"); Serial.print(x_value16bit); Serial.print(", y_diff :"); Serial.println(y_value16bit); for( int i=0;i<9;i++){ data[i] = 0; } hd = 0; delay(10); } else if (hd >= 9){//読み込んだデータ数が多すぎたらそのデータは捨てる hd = 0; for( int i=0;i<9;i++){ data[i] = 0; } Serial.println(""); } else{ } } }

マルチコプターの位置推定に使う場合

このセンサは画像内の差分しか取らないので、そのままの値で水平位置を測定することはできません。
同じ距離を移動しても高度に応じて画像の差分量は変化するためです。従って水平位置を取りたいときは距離センサなどで高度を測定して、処理をする必要があります。
で、これらの高度と画像の差分、水平位置がどのような関係性を持っているのかについても、詳しい資料が特に見つかりませんでした。
カメラの視野角が一定ならば、画像内の移動距離 * 高度 * 定数 = 水平移動距離 になる気がしたので、高度を3種類でそれぞれ適当に3m移動させて上の式の定数をおおよそ求めました。
実際に使った計算式は、水平移動距離 = 画像内の移動距離 * 高度 /18 でした。
マルチコプタの水平位置保持をさせた所、ドリフトせずにその場に留まれる程度には精度が出ていました。

おわりに

UART版(2500円)の方がSPI版(4000円)よりも少し安くサイズも小さいなどメリットは多いです。
一方でSPI版だとFPSが120Hzなのに対し、UART版では66Hzなので少し更新周期が遅くはなってしまいますが、マルチコプタの水平位置推定に使うには十分な早さでした。
使ってみた感じでは、室内、屋外ともに精度よく計測できていそうなのですが、体育館のように周囲が明るすぎる/照明が多すぎる環境はどうやら苦手なようです。
お手軽にお安く2次元の移動距離計測ができるセンサは貴重なので、活用できると楽しそうです。

swerveのアイコン画像
東京の大学の学生です。3DCADと電子工作の勉強中です。
  • swerve さんが 2022/10/17 に 編集 をしました。 (メッセージ: 初版)
ログインしてコメントを投稿する