2012年7月13日金曜日

秋月の加速度センサモジュール

 今回は秋月の3軸加速度センサモジュールをArduino (Akiduino)につないで試してみました。接続といっても、ブレットボードに載せて、配線するだけです。


 回路図を書く程でもなかったので、配線イメージ図っていう感じで


 はじめはArduinoのスケッチで、起動時数回取得したAD値をアベレージして、オフセット値として、その後、毎回数回繰り返し取得したAD値の平均からオフセットを引いいて、数値をシリアルで画面に出して・・・ってやってみたんですが、数値がパラパラ変わるのは分かるんですが、早すぎて、何が起こってるんだかよくわからない・・・。
 そこで、オフセットとか平均はとりあえずおいておいて、取得したAD値をシリアルに垂れ流して、パソコン側でグラフ表示して、何が起こってるか見てみようと思いました。
で、簡略化したスケッチが

#include "Arduino.h"

// 端子の割当。
const int PIN_ACC_X = 0;
const int PIN_ACC_Y = 1;
const int PIN_ACC_Z = 2;

// 変数宣言。
int adX = 0, adY = 0, adZ = 0;
byte sendData[8];

void setup() {
 // シリアルポートの初期化。
 Serial.begin(9600);
}

void loop() {
 
 // AD値取得。
 adX = analogRead(PIN_ACC_X);
 adY = analogRead(PIN_ACC_Y);
 adZ = analogRead(PIN_ACC_Z);
 
 // 送信データ構成。
 sendData[0] = 0x02;
 sendData[1] = (byte)((adX & 0xff00) >> 8);
 sendData[2] = (byte)(adX & 0x00ff);
 sendData[3] = (byte)((adY & 0xff00) >> 8);
 sendData[4] = (byte)(adY & 0x00ff);
 sendData[5] = (byte)((adZ & 0xff00) >> 8);
 sendData[6] = (byte)(adZ & 0x00ff);
 sendData[7] = 0x03;
 
 // 送信。
 for (int i = 0; i < 8; i++) {
  Serial.write(sendData[i]);
 }
 
 delay(200);
}

 これで、シリアルに吐き出されたデータをPCに側で受信して、グラフ表示するプログラムを作って、表示させたら、こんな感じになりました。


 赤・青・緑がそれぞれの軸のAD値で、加速度センサのブレッドボードを加速度センサの3軸方向にそれぞれ平行に振ってみたものです。3つの黄色の枠でそれぞれの方向に反応しているのがわかります。赤だけオフセットが大きいですが、赤はZ軸というか重力方向の軸です。重力加速度の影響かな?すみません、加速度センサのことをあまり勉強してないのでわかりません。でも、ためしにブレッドボード(Z軸の方向)をひっくり返したらオフセットが下側に行ったので、おそらく重力によるものでしょう。
 ちなみにグラフ表示プログラムは Visual Studio 2010 のグラフ表示コントロールを使っています。
ソースはこんな感じ。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Management;
using System.Windows.Forms.DataVisualization.Charting;
using System.IO.Ports;


namespace SerialGraph
{
    class acceleration
    {
        private int valueX = 0;
        private int valueY = 0;
        private int valueZ = 0;

        public acceleration(int x, int y, int z)
        {
            this.valueX = x;
            this.valueY = y;
            this.valueZ = z;
        }

        public int getValue(int no)
        {
            if (no == 0)
            {
                return this.valueX;
            }
            else if (no == 1)
            {
                return this.valueY;
            }
            else
            {
                return this.valueZ;
            }
        }
    }

    public partial class frmMain : Form
    {
        // キーと値をセットで保存。「COM1」が「標準シリアルポート1」など。
        Dictionary<string, string> ComPortTable = new Dictionary<string, string>();

        // 取得データの履歴を保存。
        const int MAX_DATA_COUNT = 40;
        Queue<acceleration> dataHistory = new Queue<acceleration>();

        // グラフ構成用。
        Series[] series = new Series[3];


        public frmMain()
        {
            InitializeComponent();

            // シリアルポートの実装状況を取得。
            ManagementClass mcW32SerialPort = new ManagementClass("Win32_SerialPort");
            foreach (ManagementObject aSerialPort in mcW32SerialPort.GetInstances())
            {
                ComPortTable.Add(
                    (string)aSerialPort.GetPropertyValue("Caption"),
                    (string)aSerialPort.GetPropertyValue("DeviceID"));
            }
            string[] ports = SerialPort.GetPortNames();
            foreach (string port in ports)
            {
                if (!ComPortTable.ContainsValue(port)) {
                    ComPortTable.Add(port, port);
                }
            }
        }

        private void drawChart(Chart chart)
        {
            try
            {
                chart.Series.Clear();
                for (int i = 0; i < 3; i++)
                {
                    series[i].Points.Clear();
                    foreach(acceleration acc in dataHistory)
                    {
                        series[i].Points.Add(new DataPoint(0, acc.getValue(i)));
                    }
                }
                for (int i = 0; i < 3; i++ )
                {
                    chart.Series.Add(series[i]);
                }

            }
            catch (Exception ex)
            {
                string errMsg = "@showChart(): ";
                errMsg += ex.Message;
                Console.WriteLine(errMsg);
                MessageBox.Show(errMsg);
            }
            
        }


        private void frmMain_Load(object sender, EventArgs e)
        {
            int i;

            // 画面の構成。

            // シリアルポートのコンボボックス構成。
            cmbPortNo.Items.Clear();
            foreach (string aPortCaption in ComPortTable.Keys)
            {
                cmbPortNo.Items.Add(aPortCaption);
            }
            if (ComPortTable.Count > 0)
            {
                cmbPortNo.SelectedIndex = 0;
            }
            else
            {
                MessageBox.Show("シリアルポートが存在しません。");
            }

            // シリアルポート「接続」「切断」ボタン。
            btnConnect.Enabled = true;
            btnDisconnect.Enabled = false;

            // グラフ表示領域の構成。
            chart.ChartAreas[0].AxisX.MinorGrid.Enabled = true;
            chart.ChartAreas[0].AxisY.Minimum = 0;
            chart.ChartAreas[0].AxisY.Maximum = 1023;

            // グラフ要素の初期化。
            for (i = 0; i < 3; i++)
            {
                series[i] = new Series();
                series[i].ChartType = SeriesChartType.FastLine;
                series[i].IsVisibleInLegend = false;
            }
            
            // グラフ線色設定。
            series[0].Color = Color.Red;
            series[1].Color = Color.Green;
            series[2].Color = Color.Blue;

            // チャート表示用データのクリア。
            while (dataHistory.Count <= MAX_DATA_COUNT)
            {
                dataHistory.Enqueue(new acceleration(0, 0, 0));
            }

            // タイマーを1秒(1000ms)周期に初期化。
            timer.Interval = 1000;
            timer.Enabled = true;
        }

        // 接続ボタンのハンドラ。
        private void btnConnect_Click(object sender, EventArgs e)
        {
            try {
                string comPort = ComPortTable[cmbPortNo.SelectedItem.ToString()];
                if (comPort != null)
                {
                    // COMポート接続。
                    serialPort.PortName = comPort;
                    if (serialPort.IsOpen) {
                        MessageBox.Show("既に" + comPort +"はオープンされています。");
                    } else {
                        serialPort.BaudRate = 9600;
                        serialPort.Parity = System.IO.Ports.Parity.None;
                        serialPort.StopBits = System.IO.Ports.StopBits.One;
                        serialPort.DataBits = 8;
                        serialPort.Open();
                        btnConnect.Enabled = false;
                        btnDisconnect.Enabled = true;
                    }
                }
            } catch (Exception ex) {
                if (serialPort.IsOpen) serialPort.Close();
                string errMsg = "@btnConnect_Click(): ";
                errMsg += ex.Message;
                Console.WriteLine(errMsg);
                MessageBox.Show(errMsg);
                btnConnect.Enabled = true;
                btnDisconnect.Enabled = false;
            }
        }

        // 切断ボタンのハンドラ。
        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            try
            {
                if (serialPort.IsOpen)
                {
                    serialPort.Close();
                    btnConnect.Enabled = true;
                    btnDisconnect.Enabled = false;
                }
            } catch (Exception ex) {
                string errMsg = ex.Message;
                Console.WriteLine(errMsg);
                MessageBox.Show(errMsg);
            }
        }

        // シリアルポート受信ハンドラ。
        private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            byte[] rcvData = new byte[8];
            ushort adX, adY, adZ;
            //int readCount;

            try
            {
                //serialPort.Read(rcvData, 0, 1);
                rcvData[0] = (byte)serialPort.ReadByte();
                if (rcvData[0] == 0x02)
                {
                    //readCount = serialPort.Read(rcvData, 1, 7);
                    // ↑ 7byte取得に設定しても5~6byteのみ取得する。受信が間に合っていない?
                    // ↓ 1byteずつ同期受信に変更。
                    for (int i = 1; i < 8; i++)
                    {
                        rcvData[i] = (byte)serialPort.ReadByte();
                    }
                    if (rcvData[7] == 0x03)
                    {
                        // コマンド解析。
                        adX = (ushort)((((ushort)rcvData[1]) << 8) + (ushort)rcvData[2]);
                        adY = (ushort)((((ushort)rcvData[3]) << 8) + (ushort)rcvData[4]);
                        adZ = (ushort)((((ushort)rcvData[5]) << 8) + (ushort)rcvData[6]);

                        // キューに登録。
                        dataHistory.Enqueue(new acceleration((int)adX, (int)adY, (int)adZ));
                        
                    }
                }
            }
            catch (Exception ex)
            {
                string errMsg = ex.Message;
                Console.WriteLine(errMsg);
                MessageBox.Show(errMsg);
            }
        }

        // タイマのハンドラ。
        private void timer_Tick(object sender, EventArgs e)
        {
            // キューの最大値を超えたら、古いものから削除。
            while (dataHistory.Count > MAX_DATA_COUNT)
            {
                dataHistory.Dequeue();
            }

            // グラフの再描画。
            drawChart(chart);
        }

    }
}

相変わらず、使い道とか考えてないですが、まあ、こういうことをやること自体が楽しいっていうことで。