BMX055のデータをUSBでパソコンに転送してみた。
角速度の実験を追加 2023/06/28
2023/06/14
PICでBMX055から加速度と角速度が取り込めたので、そのデータをパソコンにUSBで転送出来るか試した。
2019年の8月にPICとパソコンをUSBで繋ぐ実験をしていたので、そのときと同様の手順で今の環境にプログラムを構築する。
うまくUSBが繋がると、次はI2Cのプログラムを追加する。
通信のプログラムが二つ同居することになるので、ここで色々なトラブルが発生するかとハラハラしていたのだが、何も起こらない。
何も起こらずに順調だと、私のようなポンコツ・プログラマーは、かえって不安になるのである。
「俺は何かとんでもない勘違いをしていて、とんでもないヘマをしているのか?、もしくは、肝心な事が全く出来ていないンじゃないか?」と、疑心暗鬼になるのである。
「もしかして、今読み込んでいると思っているデータは、実は全然見当違いの意味の無いものなんじゃないか?」などとか、成功を素直に喜べないという、ポンコツ・プログラマーの「悲しい嵯峨」とでも言うべきものかもしれない。
PIC側ののプログラムでは、BMX055のデータをPC側にバイト単位で設定し、65バイトで流すので、わざわざ整数の形式にせず、BMX055から取得したデータをUSBのバッファーにそのままセットする。
//
//
void BMX055_GET_ACC(unsigned char acc_data[]) {
// unsigned char data[7];
LATAbits.LA1 = 1;
I2C_Master_Start();
I2C_Master_Write(BMX055_ACC_ADDR); // ??????
I2C_Master_Write(BMX055_ACC_DATA); // Select data register
I2C_Master_Stop();
I2C_Master_Start();
I2C_Master_Write(BMX055_ACC_ADDR+1); // ???????
for (int i = 0; i < 6; i++) {
acc_data[i] = I2C_Master_Read(0); // Request 1 byte of data
// Read 7 bytes of data
// xAccl lsb,msb, yAccl lsb,msb, zAccl lsb,msb temp
}
acc_data[6] = I2C_Master_Read(1); // Request 1 byte of data
I2C_Master_Stop();
//data[0] = 0xFF;
//data[1] = 0xFF;
//acc_data[6] = 0xFF;
// acc_data[0] = (data[0] >> 4) + (data[1] << 4);
// acc_data[1] = (data[2] >> 4) + (data[3] << 4);
// acc_data[2] = (data[4] >> 4) + (data[5] << 4);
// acc_data[3] = data[6];
// if(acc_data[0] > 2047) acc_data[0] = acc_data[0] - 4096;
// if(acc_data[1] > 2047) acc_data[1] = acc_data[1] - 4096;
// if(acc_data[2] > 2047) acc_data[2] = acc_data[2] - 4096;
// if(acc_data[3] > 127) acc_data[3] = acc_data[3] - 256;
// acc_data[3] = acc_data[3] + 46; // 23*2 0.5???
LATAbits.LA1 = 0;
}
//
// ??????
void BMX055_GET_GYRO(unsigned char gyro_data[]) {
// unsigned char data[6];
LATAbits.LA1 = 1;
I2C_Master_Start();
I2C_Master_Write(BMX055_GYRO_ADDR); // ???????
I2C_Master_Write(BMX055_GYRO_DATA); // Select data register
I2C_Master_Stop();
I2C_Master_Start();
I2C_Master_Write(BMX055_GYRO_ADDR+1); // ??????????
for (int i = 0; i < 5; i++) {
gyro_data[i] = I2C_Master_Read(0); // Request 1 byte of data
// Read 6 bytes of data
// xAccl lsb,msb, yAccl lsb,msb, zAccl lsb,msb temp
}
gyro_data[5] = I2C_Master_Read(1); // Request 1 byte of data
I2C_Master_Stop();
//data[0] = 0xFF;
//data[1] = 0xFF;
// gyro_data[0] = data[0] + (data[1] << 8);
// gyro_data[1] = data[2] + (data[3] << 8);
// gyro_data[2] = data[4] + (data[5] << 8);
// if(gyro_data[0] > 32767) gyro_data[0] = gyro_data[0] - 65536;
// if(gyro_data[1] > 32767) gyro_data[1] = gyro_data[1] - 65536;
// if(gyro_data[2] > 32767) gyro_data[2] = gyro_data[2] - 65536;
LATAbits.LA1 = 0;
}
void APP_DeviceCustomHIDTasks()
{
/* If the USB device isn't configured yet, we can't really do anything
* else since we don't have a host to talk to. So jump back to the
* top of the while loop. */
if( USBGetDeviceState() < CONFIGURED_STATE )
{
return;
}
/* If we are currently suspended, then we need to see if we need to
* issue a remote wakeup. In either case, we shouldn't process any
* keyboard commands since we aren't currently communicating to the host
* thus just continue back to the start of the while loop. */
if( USBIsDeviceSuspended()== true )
{
return;
}
//Check if we have received an OUT data packet from the host
if(HIDRxHandleBusy(USBOutHandle) == false)
{
//We just received a packet of data from the USB host.
//Check the first uint8_t of the packet to see what command the host
//application software wants us to fulfill.
switch(ReceivedDataBuffer[0]) //Look at the data the host sent, to see what kind of application specific command it sent.
{
case COMMAND_TOGGLE_LED: //Toggle LEDs command
LED_Toggle(LED_USB_DEVICE_HID_CUSTOM);
break;
case COMMAND_GET_BUTTON_STATUS: //Get push button state
//Check to make sure the endpoint/buffer is free before we modify the contents
BMX055_GET_ACC(acc_data);
//__delay_ms(10) ; // ms
BMX055_GET_GYRO(gyro_data);
if(!HIDTxHandleBusy(USBInHandle))
{
ToSendDataBuffer[0] = 0x81; //Echo back to the host PC the command we are fulfilling in the first uint8_t. In this case, the Get Pushbutton State command.
if(BUTTON_IsPressed(BUTTON_USB_DEVICE_HID_CUSTOM) == false) //pushbutton not pressed, pull up resistor on circuit board is pulling the PORT pin high
{
ToSendDataBuffer[1] = 0x01;
}
else //sw3 must be == 0, pushbutton is pressed and overpowering the pull up resistor
{
ToSendDataBuffer[1] = 0x00;
}
for (ix = 0; ix < 6;ix++) {
ToSendDataBuffer[ix+2] = acc_data[ix];
ToSendDataBuffer[ix+9] = gyro_data[ix];
}
ToSendDataBuffer[8] = acc_data[6];
//Prepare the USB module to send the data packet to the host
USBInHandle = HIDTxPacket(CUSTOM_DEVICE_HID_EP, (uint8_t*)&ToSendDataBuffer[0],64);
}
break;
}
//Re-arm the OUT endpoint, so we can receive the next OUT data packet
//that the host may try to send us.
USBOutHandle = HIDRxPacket(CUSTOM_DEVICE_HID_EP, (uint8_t*)&ReceivedDataBuffer[0], 64);
}
}
ということで、パソコンに加速度のデータと角速度のデータが取り込めるようになったので、ちょっとした実験をするアプリを作ることにした。
アプリの機能としては、以下のような手順で動くものである。
1,パソコンで一定の間隔でタイマーイベントを発生させる。
2,イベントが発生したら、パソコンはUSB通信でPICにデータを要求する。
3,要求を受け取ったPICは、BMX055からI2C通信を使って加速度と角速度のデータを入手する。
4,PICは、入手したデータをUSB通信を使ってパソコンに送り返す。
5,パソコンは、送り返された加速度と角速度のデータを実時間と共に保管し、再描画イベントを発生させる。
6,パソコンは、再描画イベントが発生したら、保管されたデータを画面に書き出し、タイマーイベントを待つ。
あと、保管されたデータをテキスト形式で書き出して、エクセルなどで解析すれば良いのである。
パソコン側のプログラミングの前に、だいたいの時間を計っておく。
BMX055に対して、加速度と角速度を読み出すと、両方合わせて2.4ミリ秒かかっている。
パソコンからの問い合わせの間隔は、一応100ミリ秒を想定していたので、特に問題はなさそうである。
パソコンでの表示は、分かりやすいように線グラフで表示し、併せて数値も表示する。
データ表示の画面。
データの取得は100ミリ秒毎で、右から左に流れる。
上の3本の線が加速度で、下の3本の線が角速度である。
加速度は、増加すると上に行き、計測をスタートすると、Z軸は1000近い値なので、かなり上に表示されている。
角速度は、画面上では+/-が逆である。
角速度はかなり敏感で、手で少し動かしただけで、だいぶ値が変動する。
PC側のプログラムは、とくに変わった処理は無く、PIC側から送られたデータを整数にして、表示域内に収まるようにするだけである。
また、データはそれなりの個数を保持しておいて、テキストで出力するようにしておく。
これをエクセルなどの表計算ソフトで取り込んで、色々解析してみようという計画である。
void CBMX055APP01View::OnTimer(UINT_PTR nIDEvent)
{
// TODO: ここにメッセージ ハンドラー コードを追加するか、既定の処理を呼び出します。
CView::OnTimer(nIDEvent);
DWORD BytesWritten = 0;
DWORD BytesRead = 0;
unsigned char OutputPacketBuffer[65]; //Allocate a memory buffer equal to our endpoint size + 1
unsigned char InputPacketBuffer[65]; //Allocate a memory buffer equal to our endpoint size + 1
CDC* pDC = GetDC();
CBMX055APP01Doc* pDoc = GetDocument();
int temp1;
int temp2;
int ix;
int iy;
CString str01;
// SYSTEMTIME systime_s; // 処理の開始時刻
// SYSTEMTIME systime_e; // 処理の終了時刻
if (WriteHandle == INVALID_HANDLE_VALUE) return;
GetLocalTime(&systime_s); // 開始時刻を取得
InputPacketBuffer[0] = 0; //The first byte is the "Report ID" and does not get transmitted over the USB bus. Always set = 0.
OutputPacketBuffer[0] = 0; //The first byte is the "Report ID" and does not get transmitted over the USB bus. Always set = 0.
OutputPacketBuffer[1] = 0x81;
WriteFile(WriteHandle, &OutputPacketBuffer, 65, &BytesWritten, 0);
ReadFile(ReadHandle, &InputPacketBuffer, 65, &BytesRead, 0);
GetLocalTime(&systime_e); // 終了時刻を取得
acc_data[0] = (InputPacketBuffer[3] >> 4) + (InputPacketBuffer[4] << 4);
acc_data[1] = (InputPacketBuffer[5] >> 4) + (InputPacketBuffer[6] << 4);
acc_data[2] = (InputPacketBuffer[7] >> 4) + (InputPacketBuffer[8] << 4);
if (acc_data[0] > 2047) acc_data[0] = acc_data[0] - 4096;
if (acc_data[1] > 2047) acc_data[1] = acc_data[1] - 4096;
if (acc_data[2] > 2047) acc_data[2] = acc_data[2] - 4096;
gyro_data[0] = InputPacketBuffer[10] + (InputPacketBuffer[11] << 8);
gyro_data[1] = InputPacketBuffer[12] + (InputPacketBuffer[13] << 8);
gyro_data[2] = InputPacketBuffer[14] + (InputPacketBuffer[15] << 8);
if (gyro_data[0] > 32767) gyro_data[0] = gyro_data[0] - 65536;
if (gyro_data[1] > 32767) gyro_data[1] = gyro_data[1] - 65536;
if (gyro_data[2] > 32767) gyro_data[2] = gyro_data[2] - 65536;
temp_data = InputPacketBuffer[9];
if (temp_data > 127) temp_data = temp_data - 256;
cds_data = InputPacketBuffer[2];
for (ix = 0; ix < 499; ix++) {
for (iy = 0; iy < 3; iy++) {
pDoc->Doc_acc_data[iy][ix] = pDoc->Doc_acc_data[iy][ix+1];
pDoc->Doc_gyro_data[iy][ix] = pDoc->Doc_gyro_data[iy][ix+1];
}
pDoc->Doc_temp_data[ix] = pDoc->Doc_temp_data[ix + 1];
pDoc->Doc_cds_data[ix] = pDoc->Doc_cds_data[ix + 1];
pDoc->systime_d[ix] = pDoc->systime_d[ix + 1];
}
for (ix = 0; ix < 3; ix++) {
pDoc->Doc_acc_data[ix][499] = acc_data[ix] - pDoc->calib_acc_data[ix];
pDoc->Doc_gyro_data[ix][499] = gyro_data[ix]; // gyro は敏感
}
// pDoc->Doc_acc_data[2][499] = pDoc->Doc_acc_data[2][499] - 1000; // Z軸は特別
pDoc->Doc_temp_data[499] = temp_data;
pDoc->Doc_cds_data[499] = cds_data;
pDoc->systime_d[499] = systime_e;
InvalidateRect(NULL, TRUE);
}
加速度が計れるということで、基礎的な実験をやってみることにした。
高校生のときに物理の力学の実験でやった、重力と加速度の実験である。
道具箱の中にあった戸車で、滑車と、三輪の台車を作る。
これを、平らな台の上で錘で引っ張り、どのような加速をするか検証しようという計画である。
ところが、どうも私はこういう工作がヘタクソで、なんだか見ていてイヤになってしまうようなオンボロが出来てしまうのである。
台車の上にブレッドボードを乗せ、錘を乗せる屋根を付けたものである。
USBのケーブルが付いているので、そうとうな誤差が出るのだろうが、この台車の上に錘を乗せて差を取ることで誤差を減らそうという魂胆である。
空の台車の重さは295グラムである。
錘は鉄製のクランプで、133グラムである。
台車の動く距離は、机の上で約50センチメートルである。
理論上はどんな計算になるのであろうか?
錘は133グラムがgの重力加速度を受けており、その力が滑車で295グラムの台車を引っ張っている。
F=mαという式だと、133g×980cm/s**2=295g×αで、α=(133*980)/295=441.8cm/s**2となるはずである。
等加速を受けている物の距離は、S=1/2αt**2なので、ある距離(今回は50cm)を動くのは、t=sqrt(2*s/α)ということで、0.476秒となる。
この計算で合っているンじゃろうか?
ということで、早速机の上で台車を引っ張って実験してみることにする。
ただ、実際の台車の車輪は、私の工作なので、正確な並行が取れているとは思えず、また、USBのケーブルを引きずっており、これらは全てが抵抗として働き、加速度を減らす要因になっているのである。
台車の可動する時間が0.476秒程度だとすると、おそらく感覚としては一瞬なのである。
もうちょっと計測の時間間隔を短くした方が良いのかもしれない。
ということで、タイマーのイベントが発生してから、画面上にデータを書き出す時間を計測してみると、だいたい26〜36ミリ秒で動いているようなのである。
最大を計測したが、けっこうな時間を動かしても36ミリ秒を超えることがなかった。
そこで、タイマーイベントを50ミリ秒まで短縮してテストをしてみることにした。
計測したデータを出力し、エクセルに取り込んでみる。
同じことを数回繰り返し、その平均を出してみる。
加速度が増加し始めてから、減速し始める(手で止めているので、けっこう差が出る)までの平均は約0.62秒、平均の加速度の値は、Y軸が282という値になった。
しかし、実験をしていて感じるのが、この装置の欠陥というか、ダメなところが次々に浮かぶのである。
1,そもそも机というか台が、微妙に水平ではない。
2,台車が動くと、どうも右にずれる。つまり車輪が正面を向いていない。
3,色々試しても、USBのケーブルはかなりの抵抗を持っている。
4,台車に乗せたセンサーが、そもそも台車と水平に取り付けられていない。
5,平らに見えた台の板がデコボコである。
本当の研究をするには、これらの不確実な事柄を一つづつ全て潰していかないとならないのだろうが、私には一つとして排除するような根性が無い。
なんだか、やっていてだんだんと「こんなのをやって、何の意味があるンだろうか?」と、空しくなってくるぐらいなのである。
空しい気分はともかく、やれるところまでやろうと、錘を追加することにした。
錘は普通の鯖缶で、185グラムある。
この重量が追加されることによって、どのくらい加速度に影響があるかの検証である。
実行してみると、なんとなく加速が悪くなって、ゴールにに到達する時間もちょっと長くなったような気がするのである。
加速度が増加し始めてから、減速し始めるまでの平均は約0.64秒、平均の加速度の値は、Y軸が187という値になった。
鯖缶をもう一つ追加して、371.5グラムを乗せる。
さすがにこの重量になると、明らかに加速度は下がり、かなり速度も低下している感じになる。
ただ、それだけに台車の不安定さとか、横にずれる感じが大きくなり、工作物のポンコツぶりが益々顕著になってくるのである。
加速度が増加し始めてから、減速し始めるまでの平均は約0.87秒、平均の加速度の値は、Y軸が138という値になった。
ということで、台車だけの加速度、台車+鯖缶1個、台車+鯖缶2個の場合の加速度のグラフを描くと、このように反比例のものになる。
実測データが青の棒の3個しかないので、平均を取って反比例のグラフを追加したのがオレンジ色の棒グラフになる。
加速度の絶対値はどうも信用がおけないのであるが、とある力による加速は、重量に逆比例するということはちゃんと計測出来るようなのである。
加速度の実験の次は角速度の実験ということで、素人が作れる一番簡単なものということで、振り子の実験をすることにした。
ただ、その前に若干考慮しておかないとならないものがあるような気がするのである。
それは、データのノイズなのである。
加速度のデータはあまり気にならないのだが、角速度のデータには、かなりのノイズが乗っているようなのである。
これは、静かに机の上に置いてある状態のデータで、常に3軸共データが触れ続けている。
電気的ノイズなのか?と、パスコンを入れてみたり、センサーとCPUの距離を離してみたりしたが、変化は無い。
データの取得の問題なのか?と、加速度の取得を止めてみたり、取得の時間間隔を変えたが、それも変化が無い。
若干の変化が感じられたのが向きで、どうやら重力加速度の影響があるようなのである。
ただ、3軸のどれかにデータの偏りが移るだけで、全体の変化が変わるわけではない。
どうやら、こういうノイズが乗ったデータのようなのである。
500回の測定のデータでは、以下のようになる。
平均値:X=-40.928,Y=-32.548,Z=1.426
平均偏差:X=64.3123,Y=68.3236,Z=72.2763
標準偏差:X=78.4146,Y=85.0387,Z=90.7549
データを80くらいで割って使うようになりそうな感じなのである。
ということで、どうも実データで、実際にどのくらいの変化が読み取れるのか?が重要な感じである。
読み取りたいデータがノイズの中に埋もれてしまうのか?
一番簡単に理論値が出せるであろう、振り子を使った実験を行うことにした。
しかし、やってみると振り子の実験も意外とめんどくさいのである。
BMX055とPICの乗ったブレッドボードがあるので、板切れを工作して尖った釘を支点とするものを作ってみたのだが、やはり問題となるのがUSBケーブルで、振り子が回転したり、揺れる方向が定まらなかったり、どうにもならないのである。
しかたがないので、1cm角のアルミのL字アングルを切って、点ではなく、二点の支点を持つ、一方向のみへ振れる振り子を作った。
この二つのアングルの間に重心がくれば、よいはずなのである。
振り子の方のL字アングル。
以前、全然違う用途のために作った色々なガラクタを寄せ集めて作っているので、相変わらずオンボロな物が出来上がる。
振り子の下端には、錘のクランプを取り付けて、重心を調整する。
完成した振り子。
一番上の支点に近い場所に、センサーとPICを乗せたブレッドボードを置き、諸悪の根源のUSBケーブルはなるべく邪魔にならないように余裕を持たせる。
振り子の支点から下端までは約77cmある。
tan(1度)は0.01745なので、振り子の先端を1.34cm移動させると約1度の角度となる計算である。
この振り子の重心が何処なのかは不明なのだが、なるべく重い錘を使い、可能なかぎり重心を下げる。
振り子の周期から重心の位置は逆算出来ると思うのであるが、考えてみれば今回の実験では重心の位置は関係が無い。
CDSは、光が入ると抵抗が下がるセンサーで、今回使用したのは直径が5mmほどの一番小さく安いものである。
通常はこの機器を抵抗でプルアップして、A/D変換して使用するのが正統派なのだろうが、抵抗値を計ると、暗い場合は数100k〜数メガオームのオーダーで、明るい場合は数Kオームに変化するということなのである。
そこで、10Kオーム程度の抵抗で5Vをプルアップして電圧を計ると、明るい場合は0.1V弱程度、暗い場合は4.7V程度と、うまくすれば、ICの入力でハイとローで読み込めそうなのである。
ということで実験すると、3Vの乾電池で光らせたLEDの光で、うまくスイッチする。
ということで、CDSの電極にポリウレタン線をはんだ付けして延長したものを作る。
そして、それを黒く塗った紙で筒を作り、筒の中にはめ込む。
その筒を振り子の先端に取り付けておく。
光らせたLEDを静止したときの振り子の最下端の場所に置いておく。
動いた振り子が最下端を横切る瞬間に光スイッチが入るという仕掛けである。
それと、振れ幅の目安に、紙で1cm毎の目盛りを張り付けておく。
約1度程度の傾きで振り子を動作させると、それなりにサインカーブを描いて、ノイズよりは大きなデータが見て取れる。
センサーが置いてあるのは、完全に振り子の支点というわけではないので、加速度のデータも若干は動いているのだが、主な信号は角速度ということで、なかなか良い雰囲気である。
これが、左のデータを表計算ソフトで取り込んだものである。
このデータから見ると、この振り子の周期は平均で1.73秒、周波数は0.578Hzとなる。
この表で大事なのは、上下の一番値が大きい箇所(上と下)が、振り子の下端を通過しているときだということなのである。
最下端を通過しているときが最大の値になるということは、ちゃんと角速度が出ているということになると考えられるのである。
振り子の角度は目分量の1度で、最大の値が約1000である。
ということで、理論値はどうなるのであろうか?
イメージとしては、半径が1度の円筒が周期1.73秒で回転しており、その円筒に描かれた黒丸を横から見ているという感じである。
円周は円の公式で2×π×rで、今回はrが1度となる。
2π度/1.73秒ということで、角速度の最大は3.632度/秒となる。
黒丸を横から見ているので、振り子の角速度は、回転する円筒の角速度×COS(振り子の角度)となり、0度のときが最大である。
センサーの設定で、1ビットあたり0.003815度/秒となっているので、3.632/0.003815とすると、952となり、値はだいたい合っている感じである。
約2度程度の傾きで振り子を動作させると、サインカーブは1度のときより値が大きくなる。
加速度のデータは当然ながら大きくなるのだが、角速度の値も大きくなる。
これが、左のデータを表計算ソフトで取り込んだものである。
このデータから見ると、振り子の周期は平均で1.73秒変わらず、周波数は0.578Hz変わらずとなる。
センサーから出力される角速度の値は明らかに大きくなっており、1度のときのピークが1000だったのが、2度のときのピークがちょうど二倍の2000くらいになっている。
円筒の直径が1度から2度と倍になっているので、当然ながら角速度も倍になるということで、計測としては良い感じである。
ということで、ダメ押しで約3度の場合を実測してみる。
約3度程度の傾きで振り子を動作させると、サインカーブは2度のときより値が大きくなる。
加速度のデータは当然ながら大きくなるのだが、それほどでもなく、角速度の値は2度のときより大きくなる。
これが、左のデータを表計算ソフトで取り込んだものである。
このデータから見ると、振り子の周期は平均で1.735秒で若干増加傾向、周波数は0.576Hzと若干減少傾向となる。
しかし、センサーから出力される角速度の値は明らかに大きくなっており、1度のときのピークが1000だったのが、2度のときのピークがちょうど二倍の2000くらいになっている。
それが今回は3000弱ということで、ほぼ開始の角度に比例しているのである。
今回の振り子の実験の動画は以下のようなものである。
angular velocity from BMX055
https://youtu.be/Qkc46DUtgVI
HOME