« Pandaの到着 | トップページ

FEZ Panda - 仮想COMポート&SDカード

今年最後となりましたが、今回はUSBClientの機能の1つCDCクラスを利用してみようと思います。

Pandasd

CDCと言えば仮想COMポート、netduinoの時と同様にシリアル接続をUSB経由で、余計な機器無しで行なってみます。

更にSDカードを接続して、FAT32を扱っていきます。

●仮想COMポート

NETMFにはUSBClientという名前空間があり、これを利用することによってオリジナルのUSBデバイスを作ることが可能となります。

また、MassStorage、CDC(Communication Device Class)、Mouse、Keyboardのデバイスが既に登録されているので、例えばKeyboardのデバイスを作成してキーコードを送るプログラムを作った場合、FEZ PandaとPCを接続したとたん勝手にタイピングされる悪戯などが可能かも?

netduinoの時はPCとシリアル接続する時に、外部のモジュール(AE-UM232R)を使用して間接的な接続で実現してましたが、FEZ PandaではUSBClientのUSBC_CDCを使用することで実現出来ます。

まず、USBC_CDC Classから「GHI_NETMF_Interface_with_CDC.zip」をダウンロードして適当な場所に解凍しておきます。FEZ PandaをCDCクライアントで接続した場合にCDCドライバをここからインストールして下さい(後述参照)。

 

プログラムについては、NETMFProjectsから「FezTerm」を使用させて頂きました。

これはCDCを用いたデバッガープログラムのサンプルで、簡易ターミナルとFEZ用のプログラムの2部で構成されています。

FEZ用はCdcPlusDebuggerという名前のプロジェクトでメインのプログラム、ターミナル用のホストとなるFEZTermというプログラムから成っており、コンポーネントとしてボタンやスピーカー、XBee(無線通信)が接続されていれば、それらをPCのFEZTermからコントロール出来るようになっています。

それらコンポーネントを持ってないので(作ればいいだけなんだけど)、MicroSDカードを接続して、FATファイルシステムにアクセスし、その結果をFEZTermに表示させることをやってみようと思います。

 

●MicroSDカード

MicroSDカードのモジュールには、サンハヤトのCK-40というコネクタ変換基板を利用しました。

Sd_ck40_2

 

FEZ PandaのSDカード用のアクセス端子は10本あるのですが、CK-40は8本です。余った2本の用途がわかりませんが、カード挿入判定用のスイッチも残りのどちらかになるのでしょうか…

取り敢えずNETMFProjectsのSD Card Connection Guideを拝見しましたが、不明な2本については未接続です。

 

Pandasd_sch

回路図はこの様になりますが、MODEとIO46(PowerEnable)について、どなたか分かる方いらっしゃいますでしょうか??

FEZ Pandaは3.3V動作でSDカードへの信号もそのまま接続出来ます。

Pandasd_p

 

●プログラム

というわけでCdcPlusDebuggerを改造してSDカードへのアクセスを組み込んだプログラムです。

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.USBClient;
using System.Collections;
using System.Text;
using Fez.IO;
using System.Reflection;
using System.IO.Ports;

using System.IO;
using Microsoft.SPOT.IO;
using GHIElectronics.NETMF.IO;

namespace CdcPlusDebugger
{
    public class Program
    {
        static FezTermHost term;
        static readonly byte newLine = 255;     // 区切り用デリミタ
        static AutoResetEvent are = new AutoResetEvent(false);
        static FEZ_Components.Piezo speaker = new FEZ_Components.Piezo(FEZ_Pin.PWM.Di5);
        static FEZ_Components.Button button;

        static ArrayList mItems = null;     // アイテム(ファイル)リスト
        static int mItemIndex;              // 選択アイテム
        static string mCurPath = null;      // カレント・パス
        static string mRootDir = null;      // ルート・ディレクトリ

        public static void Main()
        {
            PersistentStorage sdPS = new PersistentStorage("SD");
            sdPS.MountFileSystem();
            if (GetRootFolder())
                mCurPath = mRootDir;
            mItems = new ArrayList();

            button = new FEZ_Components.Button(FEZ_Pin.Interrupt.Di4);
            button.ButtonPressEvent += new FEZ_Components.Button.ButtonPressEventHandler(button_ButtonPressEvent);

            Debug.Print("Term new.");
            term = new FezTermHost(newLine);
            term.MessageReceived += new MessageFunc(term_MessageReceived);
            term.Stopped += new Action(term_Stopped);

            Debug.Print("Term start.");
            term.Start();

            // Stopped event will set are.
            are.WaitOne();
            Debug.Print("Main exited.");

            sdPS.UnmountFileSystem();
        }

        // イベント:ボタン押下
        static void button_ButtonPressEvent(FEZ_Pin.Interrupt pin, FEZ_Components.Button.ButtonState state)
        {
             speaker.Play(5000, 300);
        }

        // イベント:ターミナル停止
        static void term_Stopped()
        {
            are.Set();
        }

        // イベント:メッセージ受信
        static void term_MessageReceived(string value)
        {
            if (value == null) return;
            string result = HandleCommands(value);

            if (result == "bye")
            {
                term.WriteMessage("bye");
                term.Stop();
                return;
            }
            else
            {
                term.WriteMessage(result);
            }
        }

        // コマンド処理
        static string HandleCommands(string cmd)
        {
            string lcmd = cmd.ToLower();
            switch (lcmd)
            {
                case "exit":
                    return "bye";
                case "v":
                case "ver":
                    return Assembly.GetExecutingAssembly().FullName;
                case "t":
                case "time":
                    return DateTime.Now.ToString();
                case "?":
                case "h":
                case "help":
                    return GetHelp();
                case "i":
                case "info":
                    return GetSystemInfo();
                case "beephigh":
                    speaker.Play(8000, 500);
                    return "OK";
                case "beeplow":
                case "beep":
                    speaker.Play(3000, 500);
                    return "OK";
                case "btnleft":
                    speaker.Play(4000, 500);
                    return "OK";
                case "btnright":
                    speaker.Play(6000, 500);
                    return "OK";
                case "ping":
                    return "reply from FezTermHost.";
                case "dir":
                case "ls":
                    GetItems(false);
                    return "OK";
                case "xbee":
                    XBeeSend();
                    return "OK";
                default:
                    if (lcmd[0] >= '0' && lcmd[0] <= '9')
                    {
                        int num = Int32.Parse(lcmd);
                        if (num == 0)
                        {
                            if (mRootDir != "\\SD")
                            {
                                mRootDir = StripSlash2(mRootDir);
                                if (mRootDir != "")
                                    GetItems(false);
                            }
                            else
                            {
                                GetItems(false);
                            }
                            return "OK";
                        }
                        else if (num > 0 && num < mItems.Count)
                        {
                            mItemIndex = num;
                            GetItems(true);
                            return "OK";
                        }
                    }
                    return "'" + cmd + "' is not recognized as in internal or external command.";
            }
        }

        // システム情報取得
        static string GetSystemInfo()
        {
            string si = "Is Big Endian: " + Microsoft.SPOT.Hardware.SystemInfo.IsBigEndian;
            si += "\r\nOEM String: " + Microsoft.SPOT.Hardware.SystemInfo.OEMString;
            si += "\r\nMF Version: " + Microsoft.SPOT.Hardware.SystemInfo.Version;
            si += "\r\nBattery voltage: " + Microsoft.SPOT.Hardware.Battery.ReadVoltage();
            return si;
        }

        // ヘルプ取得
        static string GetHelp()
        {
            string h =
@"
?,h,help - Help
i,info   - System info
v,ver    - Version
t,time   - Get time
exit     - Quit session. Host returns bye.
ls,dir   - Directory listing
0        - Directory up
1..n     - Open directory or file
ping     - Ping host
beep     - beep
";
            return h;
        }

        // XBee通信
        static SerialPort xbee;
        static void XBeeSend()
        {
            if (xbee == null)
            {
                xbee = new SerialPort("COM3", 115200, Parity.None, 8, StopBits.One);
                xbee.Open();
            }
            // create "Hello!" string as bytes
            byte[] helloBytes = Encoding.UTF8.GetBytes("Hello!");
            xbee.Write(helloBytes, 0, helloBytes.Length);
        }

        // ディレクトリ取得
        static string GetDirSD()
        {
            string list = "";
            list = "Getting files and folders...";
            term.WriteMessage(list);

            if (VolumeInfo.GetVolumes()[0].IsFormatted)
            {
                string rootDirectory = VolumeInfo.GetVolumes()[0].RootDirectory;
                string[] files = Directory.GetFiles(rootDirectory);
                string[] folders = Directory.GetDirectories(rootDirectory);

                list = "Files available on " + rootDirectory + ":";
                term.WriteMessage(list);
                for (int i = 0; i < files.Length; i++)
                {
                    list = " " + files[i];
                    term.WriteMessage(list);
                }
                term.WriteMessage("");

                list = "Folders available on " + rootDirectory + ":";
                term.WriteMessage(list);
                for (int i = 0; i < folders.Length; i++)
                {
                    list = " " + folders[i];
                    term.WriteMessage(list);
                }
            }
            else
            {
                list = "Storage is not formatted. Format on PC with FAT32/FAT16 first.";
                term.WriteMessage(list);
            }

            return list;
        }

        // ルートディレクトリ取得
        static private bool GetRootFolder()
        {
            VolumeInfo[] Volumes = VolumeInfo.GetVolumes();
            if (Volumes.Length > 0)
            {
                mRootDir = Volumes[0].RootDirectory;
                return true;
            }

            return false;
        }

        // ファイルリスト取得
        static private void GetItems(bool show)
        {
            if (show)
            {
                if (StripSlash1(mItems[mItemIndex] as string) != "")
                {
                    GetContents();
                }
                else
                {
                    mRootDir = mItems[mItemIndex] as string;
                    GetItems(false);
                }
            }
            else
            {
                mItems.Clear();
                mItems.Add("..");

                string[] list = Directory.GetDirectories(mRootDir);
                for (int i = 0; i < list.Length; i++)
                {
                    mItems.Add(list[i] + "\\");
                }

                list = Directory.GetFiles(mRootDir);
                for (int i = 0; i < list.Length; i++)
                {
                    mItems.Add(list[i]);
                }

                term.WriteMessage("Root: " + mRootDir);
                for (int i = 0; i < mItems.Count; i++)
                {
                    string str = i.ToString() + ": " + mItems[i] as string;
                    if (mItems[i] as string != "..")
                    {
                        FileInfo fi = new FileInfo(mItems[i] as string);
                        if (fi.Attributes != FileAttributes.Directory)
                        {
                            DateTime dt = fi.LastWriteTime;
                            string dstr = dt.Year.ToString() + "/" + dt.Month.ToString() + "/" + dt.Day.ToString();
                            term.WriteMessage(str + " ... " + dstr + "  " + fi.Length.ToString());
                        }
                        else
                        {
                            term.WriteMessage(str);
                        }
                    }
                    else
                    {
                        term.WriteMessage(str);
                    }
                }
            }

            // Set to first item
            mItemIndex = 0;
        }

        // ファイル内容取得
        static private void GetContents()
        {
            mCurPath = mItems[mItemIndex] as string;
            FileStream fs = File.OpenRead(mCurPath);
            long len = fs.Length;
            byte[] data = new byte[len];

            int cnt = fs.Read(data, 0, (int)len);
            fs.Close();

            term.WriteMessage("The size of data we read is: " + cnt.ToString() + " bytes");
            term.WriteMessage("Data from file: " + fs.Name);
            term.WriteMessage(new string(System.Text.Encoding.UTF8.GetChars(data)));
        }

        // 最後の\\までを取り出す1
        static private string StripSlash1(string str)
        {
            return (str.Substring(str.LastIndexOf('\\') + 1));
        }

        // 最後の\\までを取り出す2
        static private string StripSlash2(string str)
        {
            // 最後の\\を削除する
            if (str[str.Length - 1] == '\\')
                str = str.TrimEnd('\\');
            return (str.Substring(0, str.LastIndexOf('\\')));
        }
    }
}

Main()関数にあるMountFileSystemUnMountFileSystemは使用する時に実行すると応答しなくなること多いので、開始時に実行した方が良いみたいです。

要となる部分はterm_MessageReceived()関数で、ここでメッセージを受信し、その結果を送信します。

HandleCommands()関数で受信メッセージの各処理を行い、term_MessageReceived()関数へ結果を返します。

後半のGetDirSD()関数以降はSDカードへのアクセス関係となり、ディレクトリ取得、ファイル内容の取得などを行います。書き込みは行ってません。

 

●FEZTermによる通信

デバッグを開始して下図のような出力メッセージが出てきたら、デバッグを停止します。

この状態ではUSBポートが占有されたままなので、停止しないと仮想COMポートの接続が出来ません。

Fezterm2

初回時はドライバを要求してきますので、前述で解凍したディレクトリを指定します。

Fezterm0

 

認識出来たらFEZTermを起動し、認識したCOMポートが出てきているか確認します。

稀に認識しない時がありますので、デバッグ停止後に一度FEZ Panda本体のリセットを押しておくと確実です。

Fezterm1

「Connect」を押して接続し、下欄の入力エリアにコマンドを入力するとFEZ Pandaからの受信内容が表示されます。

Fezterm3

SDカード関連は dir か ls コマンドでディレクトリ・リストを取得し、リストの番号を入力するとアクセスします。

ディレクトリの場合は階下へ移動し、ファイルの場合は内容を表示します。

また、0 で上のディレクトリへ戻ります(ルートの場合は同じリストが表示されます)。

Fezterm4

リストは、番号・パス名・日付・サイズ、が表示され、ディレクトリの場合はパス名の最後に \ が付加されています。

ファイル内容では、ファイルサイズ・パス名・内容、が各行に表示されます。

下記はSDカードのディレクトリ・リストで、上図と同じになっていることがわかります。

 
SD
    |----\Sub01
    |        |----Test001.txt
    |        |----Test002.txt
    |----\Sub02
    |        |----Test001.txt
    |        |----Test002.txt
    |        |----Test003.txt
    |        |----Test004.txt
    |----\Sub03
    |        |----\Sub03a
    |        |        |----Test001.txt
    |        |        |----Test003.txt
    |        |----\Sub03b
    |        |        |----Test002.txt
    |        |        |----Test003.txt
    |        |        |----Test005.txt
    |        |----Test001.txt
    |        |----Test003.txt
    |        |----Test005.txt
    |----Test001.txt
    |----Test002.txt
    |----Test003.txt
    |----Test004.txt
    |----Test005.txt

 

終了時は「exit」コマンドを送信して下さい。

そうしないとずっと仮想COMポートとして認識してしまいますが、そのままで良ければexitしなくても構いません。

デバッグモードとして使う場合は、必ずexitして終了させておきます。

途中トラブルでメッセージが返ってこなくなった場合は、接続解除(disconnect)をしてFEZ Pandaをリセットしてから再接続します。

 

というわけで、今年一年皆様お疲れ様でした。また来年も宜しくお願い致しますm(_ _)m

|

« Pandaの到着 | トップページ

電子工作」カテゴリの記事

コメント

日本語の情報が少ないので、大変参考になります。good

投稿: msx68000 | 2011-10-05 16:59

msx68000さん、はじめまして!

確かに情報少なくて苦労しますよね。
使われるケースは少ないかもしれませんが、お役に立てて幸いですm(_ _)m

投稿: | 2011-10-11 11:01

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/204580/50451081

この記事へのトラックバック一覧です: FEZ Panda - 仮想COMポート&SDカード:

« Pandaの到着 | トップページ