追記

日々をアレコレ

-広告-


2017年01月05日 この日を編集

シリアルコントロールモータードライバを使ってみた

乾電池6本という比較的大きめの電圧でモーターをmbedのBLE経由で駆動させたいということで、シリアル通信でモーター駆動できるモータードライバを探していたら、 SparkFunのシリアルコントロールモータードライバ を発見。これまでよく使っていた、DRV8830を使ったI2Cモータードライバモジュール と比べると性能、機能として以下の部分が優れてる感じ。

  • 動作電圧が11V(最大定格は12V)まで。
  • モーター接続が2チャンネルあって、1チャンネルあたり瞬間最大1.5A(定常1.2A)まで対応の上、ブリッジすることで1台なら2倍の電流に対応可。
  • 通信方法はTTLなUART(9600/57600/115200)、I2C、SPIが選択可。
  • I2Cのアドレスは10パターン選択可。
  • 1台をマスターとして上位のCPUと通信し、最大16台をスレーブとして接続することで最大34台まで同時に制御可能。
  • 入力電源から3.3Vを出力できるので、マイコンの電源も場合によっては供給可能。

実際に使ってみると、少し面倒な感じになってたのでメモ。詳細はSerial Controlled Motor Driver Hookup Guide - learn.sparkfun.com を参考のこと。

  1. Hardware Overview にある通り、通信の設定は基板裏のジャンパで設定する。デフォルトは9600bpsのUARTになっている。商品ページにある「2Cアドレスには、デフォルトでUARTのbaudsが利用可能」という一文がよくわからないなーと思ったら、アドレス設定というよりは、通信方法の設定のデフォルトがUARTで9600bpsになってるということらしい。
  2. I2Cで使う場合、プルアップ抵抗を自分で実装、あるいは、裏面のジャンパをUser向けのジャンパを閉じる必要がある。
  3. I2C(多分、SPIでも)で使う場合、初期化処理が時間かかる場合がある。利用する流れは次の通り。

    1. IDを読み込んで、正しい値(0xA9)が返ってくるのを待つ。
    2. コマンド受付可になるまで待つ。
    3. ブリッジや回転方向などの設定をする。
    4. モーターの各チャンネルを有効にする。

    どうも、2のところで時間がかかる場合があるっぽい。その辺、は仕方ないのかなーと思ってとりあえず深くは調べてない。

面倒なところがあるとはいっても、一度操作になれると結構便利に使えそうな感じではあるので、ネタによっては使っていきたいものではある。


2016年12月06日 この日を編集

GR-CITRUSで.NET MicroFramework

GR-CITRUS を.NET MicroFramework対応させる手順メモ。基本的にはNETMF for RXのドキュメント(PDF) のBuild stepsに書いてある手順に従うのだけれど、何点か違うことも併せて書いておく。

  1. GCCのインストール

    GR-CITRUSのmrubyソースをmakeする方法 - Qiita にある GNU Toolsサイトへの登録 から rx631用のrx-elf-gccのセットアップ までを参考にGNURXv1403-ELFをインストールする。以下、インストール先は C:\cross\KPIT\GNURXv14.03-ELF とする。

  2. Visual Studioのインストール

    Visual Studio 2012 Expressが推奨されているけど、僕はVisual Studio 2013 Community Editionでファームのビルドをした。Visual Studio 2015でもできるのかもしれないけど、インストールした内容によってはできないかもい。何が必要なのかは未確認。

  3. .NET MicroFramework SDKのインストール

    .NET Micro Framework - Download: SDK v4.3 (QFE2-RTM) から FULL ZIP FIle of all SDK Filesをダウンロードして、SDKと利用するVisual Studioに合わせた拡張機能をインストール。僕は Visual Studio 2015の拡張機能を使った。今回、作るファームは .NET MicroFramework v4.3なので、.NET Micro Framework 4.4 リリース – デバイスとITの架け橋 を参考に、拡張機能の更新をしないように注意が必要。

  4. .NET MicroFramework Porting Kitのインストール

    .NET Micro Framework - Download: .NET MF 4.3 RTM (QFE1) から Porting Kit 4.3 (RTM QFE1) をダウンロードしてインストールする。インストール先は、C:\MicroFrameworkPK_v4_3とする。

  5. NETMF for RXのソースをダウンロード

    NETMF for RX - Source Code から、ファームのソースをクローンするなり、ZIPでダウンロードして展開して、C:\MicroFrameworkPK_v4_3以下にコピーする。

  6. I2Cを使うためのソースファイルの書き換えをする。

    GR-SAKURAではI2Cを使うポートがADの4番、5番を使っているのけど、GR-CITRUSには出ていないのでそのままでは使えないので、C:\MicroFrameworkPK_v4_3\DeviceCode\Targets\Native\RX631\DeviceCode\RX631_I2CSW\RX631_I2CSW.cpp の26,27行目を次のように書き換える。

    #define SCLPIN RX63N_GPIO::P42
    #define SDAPIN RX63N_GPIO::P43
    

    こうすると、GR-CITRUSの16,17番ピンがSCL、SDAになる。

  7. C:\MicroFrameworkPK_v4_3\setenv_base.cmdを書き換える(VS2013/VS2015を使う場合)

    標準のsetenv_base.cmdはVS2012以前を使う設定になっているので、VS2013/VS2015を使う場合は、82~83行目を書き換える。VS2013の場合は、

      IF NOT "%VS120COMNTOOLS%" == "" (
          CALL "%VS120COMNTOOLS%vsvars32.bat"
    

    VS2015の場合は、

      IF NOT "%VS140COMNTOOLS%" == "" (
          CALL "%VS140COMNTOOLS%vsvars32.bat"
    
  8. ファームをビルドする。

    1. コマンドプロンプトを開く。
    2. C:\MicroFrameworkPK_v4_3\に移動する。
    3. setenv_base.cmd GCC 4.8-GNURX_v14.03 C:\cross\KPIT\GNURXv14.03-ELF\rx-elf\rx\elf RX を実行する。
    4. set PATH=%PATH%;C:\cross\KPIT\GNURXv14.03-ELF\rx-elf\rx-elf\bin を実行する。
    5. msbuild.exe build.dirproj を実行して、ポーティングキットツールを作成する。

      エラーが幾つか発生してもそのまま進んでOK。僕の環境では、VS2015を使う設定でビルドすると300個以上のエラーが発生して、それでは続くファームのビルドも失敗するので注意。

    6. C:\MicroFrameworkPK_v4_3\Solutions\GR_SAKURAに移動する。

    7. msbuild.exe dotNetMF.proj /t:build /p:flavor=release;memory=flash を実行して、ファームのビルドをする。
  9. ファームをGR-CITRUSに書き込む。

    ファームのビルドに成功すると、C:\MicroFrameworkPK_v4_3\BuildOutput\RX63N\GCC4.8\le\FLASH\release\GR_SAKURA\binにtinyclrnbl.mot ができているので、それをGR-CITRUSに書き込む。詳しい方法は、GR-CITRUSにmotファイルを書き込む方法 - Qiita を参考に。

  10. ドライバのインストール

    GR SAKURAで.NET Micro Frameworkを使おう – デバイスとITの架け橋 を参考にUSBドライバをインストールする。

これで、GR-CITRUSで.NET MicroFrameworkを使うことができる。


2016年11月27日 この日を編集

Androidのレイアウトには気をつけろ

lipoyang師匠 から、Android版GP Propo をAndroid6.0位上の端末で動かすとデータの送信はできるけど受信がされないんだけどそっちの端末はどう?っていう連絡がきた。試してみると、Android4.4なXperiaZでは通信できてるっぽいのに、Android6.1なMoto G4plusでは送信はできるけどデータの受信ができてない。それについては、使ってるライブラリの問題かもというのが共通の認識で、最小限の機能を切り出して作り直したほうが速いかもってことで3日で作ってプルリクを投げた。

ただ、師匠の方では確認されてなかったけど、一部のViewが表示されなくなるという現象がMoto G4plusでだけ発生した。具体的には4列あるGridLayoutの2列目以降が表示されていない。いろいろと試しながら調べた結果、直接的な原因としては、独自で定義したViewのサイズが固定されていることだったみたい。元のソースの上では、以下のようなレイアウト定義になっていた。

  • GridLayoutはマージンとか除くと画面横向きにいっぱいの幅をもっている。
  • 4列すべてのウェイトが1に設定されている。
  • 2列目以降には独自で定義されたViewを持っていて、そのViewが持ってる子要素の幅は160dpとなっている。

これらを総合するとGridLayoutの表示にはマージンとかを考慮すると640dpともう少し必要となることになる。調べてみると、5.0インチフルHDでも5.5インチフルHDでもDP解像度は360x640が一般的なことが多く、5.0インチフルHDなXperiaZで発生していないのに、5.5インチフルHDなMoto G4plusで発生する原因としてはOSの違いだけになってくる。で、一つ思い浮かんだのは、画面下に表示される「バック/ホーム/マルチタスク」ボタンの存在。それらの扱いがOSのバージョンや端末の間で違うせいで、アプリ内で利用できるサイズに影響しているのかも、ということ。

で、解決するために以下のように修正。

  • 1列目は機能名の表示なので、ウェイトをなくして最小限のサイズに。ただし、2列目とのことを考えて少しマージンを追加。
  • 独自定義のViewに含まれる子要素の幅をすべて固定にはせずに、固定すべきものだけ設定して残りはlayout_weightを1にして、幅をmatch_parentに設定する。
  • GridLayoutの中の独自定義のViewについては、layout_gravityをfillに設定することで、独自定義のViewに近いサイズになるように。

もしかすると、画面サイズが極端に小さい端末があると、問題が発生するかもしれないけれど、元々BLEを対象としたアプリなので最近の端末しかインストールできないから多分問題ないはず。


2016年11月07日 この日を編集

RN4020で.NET GadgeteerをBLEセントラルに

画像の説明

BLEを使っておもちゃを無線操縦するにあたって、操作用のUIをスマートフォンにするんじゃなくて、マイコン使ってできないものかとぼんやりと考えていた。mbedやGenuino101みたいにペリフェラルになるお手軽マイコンは多いけどセントラルになるものってなかなかなくて、候補として、 bCoreNKD も考えてはみたものの、マイコンの開発環境整えたり、書き込み機器を揃えたりとちょっと面倒だなーとか思って先送りにしてた。で、思い出したのが MicrochipのRN4020 というモジュール。乱暴にいうてしまうと、UARTを使ってBLE機能のないマイコンにBLEで通信できるようにするためのモジュールで、設定によってはセントラルデバイスとして使うことも可能。UARTのコマンドフォーマットがデータをASCIIコードにエンコードして末尾にCRLFをつけて送るという結構クセのあるもので、ペリフェラルとして使う場合は正直言ってmbedやGenuino101、あるいは、konashiやkoshianのが使いやすいと思うくらい。ただ、今回使ってみて思ったのは、C#というか、.NET Gadgeteerというか、.NET MicroFrameworkとの相性はいい。

まず、ASCIIコードにエンコードすればいいので、数値は基本的に byte.ToString("X2") を使えばよい。エンディアンさえ気をつければ short.ToString("X4") でも int.ToString("X8") でも問題ない。また、逆に受信したデータもエンディアンにさえ気をつけてやれば、 Convert.ToInt32(string, 16) とか Convert.ToInt16(string, 16) みたいに変換してやればいい。また、Gadgeteerのシリアル通信用のクラス Gadgeteer.SocketInterfaces.Serial が便利で、ETX的なものを設定する NewLine プロパティがあるのでそれをCRLFにしておけば、 Serial.WriteLine メソッドで末尾を意識することなくコマンドの送信ができるし、 Serial.LineReceived イベントを登録しておけばデータが末尾まで来ているかどうか意識せずに返答内容を確認できる。あと、コマンドの送信は基本的に、「(コマンド文字列)」の後に引数があればカンマ区切りで送るので、送信するメソッドを次のようにするといい。

void WriteCommand(string command, params string[] args)
{
    _serial.Write(command);
    if (args != null)
    {
        foreach(var v in args)
        {
            _serial.Write("," + v);
        }
    }
    _serial.WriteLine("");
}

で、.NET Gadgeteerで使うにあたって、今回はBreakoutボードを使って以下のように結線。

Gadgeteer Pin RN4020 Pin
1 (3.3V) 23 (VDD)
3 (GPIO) 7 (WAKE_SW)
4 (Tx) 6 (UWRT_RX)
5 (Rx) 5 (UART_TX)
10 (GND) 1/24(GND)

で、RN4020同士で相互通信するためのMLDPは使わないので、RN4020の8番ピンはGNDに接続。これをUARTが使えるGadgeteerの「U」のマークが使えるポートに繋げばハードウェア側の準備は完了。


2016年09月14日 この日を編集

iOS9以降で電話帳から連絡先の項目を取得する

去年苦労したiOSで電話帳から連絡先を取得する 件、Xamarinのライブラリも更新されて、ABPeoplePickerNavigationController Class - Xamarin 辺りを参考に動くようにできたーとか思っていたら、Addressbook/AddressbookUIのAPIがiOS9から削除されるということに。iOS9からは Contacts/ContactsUIを使う必要があるらしい。iOS9でもAddressbookを使ったアプリはとりあえず動くようなんだけど、特定の条件で落ちることがあることがわかった。落ちる条件としては、「複数個の電話番号/メールアドレスを持っている人」から「後で追加した電話番号/メールアドレス」を選択して、その値を取得するというもの。処理としては、 ABPeoplePickerNavigationController.PerformAction2 イベントで次のような処理をしたとき。

void OnPerformAction2(object sender, ABPeoplePickerPerformAction2EventArgs e)
{
    // 電話番号を一つ選択
    using (var phoneNumbers = e.Person.GetPhones())
    {
        if (phones?.Count > 0)
        {
            nint idx = e.Identifier.HasValue ? phones.GetIndexForIdentifier(e.Identifier.Value) : 0;
            // idx = -1 となる場合があって、下の配列の値の取得で問題となる。
            var selectedItem = phones[idx].Value;
        }
    }
}

具体的に値を見てみると、phones の各要素が持つ Identifier の値は要素の数に応じたものになっているのに大して、ABPeoplePickerPerformAction2EventArgs.Identifier.Value の値が要素数を超える場合があったりする。少なくともiOS9では。なので、Choose a Contact - Xamarin を参考にContacts/ContactsUI APIを使った次のようなコードがいいのかも。

public class ContactPhoneItemPicker
{
    public class ContactPickerDelegate : CNContactPickerDelegate
    {
        // キャンセル時イベント
        public event EventHandler Canceled;

        // 連絡先の「人」を選択時イベント
        public event EventHandler<CNContact> SelectedContact;

        // 連絡先から「電話番号」を選択時イベント
        public event EventHandler<CNContactProperty> SelectedContactProperty;

        public ContactPickerDelegate() { }
        public ContactPickerDelegate(IntPtr handle) : base(handle) { }

        public override void ContactPickerDidCancel(CNContactPickerViewController picker)
        {
            Canceled?.Invoke(picker, EventArgs.Empty);
        }

        public override void DidSelectContact(CNContactPickerViewController picker, CNContact contact)
        {
            SelectedContact?.Invoke(picker, contact);
        }

        public override void DidSelectContactProperty(CNContactPickerViewController picker, CNContactProperty contactProperty)
        {
            SelectedContactProperty?.Invoke(picker, contactProperty);
        }
    }

    public event EventHandler<string> SelectedItem;

    public void SelectPhoneNumber(UIViewController parentViewController)
    {
        var picker = new CNContactPickerViewController();

        var pickerDelegate = new ContactPickerDelegate();
        picker.Delegate = pickerDelegate;

        pickerDelegate.Canceled += (s, e) -> SelectedItem?.Invoke(this, null);

        pickerDelegate.SelectedContact += (s, e) =>
        {
            if (e.PhoneNumbers.Count() == 1)
            {
                (s as CNContactPickerViewController).DismissViewController(true, null);
                SelectedItem?.Invoke(this, e.PhoneNumbers.FirstOrDefault().Value.ToString());
            }
        };

        pickerDelegate.SelectedContactProperty += (s, e) =>
        {
            if (e.Key == "phoneNumbers")
            {
                (s as CNContactPickerViewController).DismissViewController(true, null);
                SelectedItem?.Invoke(this, (e.Value as CNPhoneNumber).StringValue);
            }
        };

        picker.DisplayedPropertyKeys = new NSString[] { Contacts.CNContactKey.PhoneNumbers};
        picker.PredicateForEnablingContact = NSPredicate.FromFormat("phoneNumbers.@count > 0");
        picker.PredicateForSelectionOfContact = NSPredicate.FromFormat("phoneNumbers.@count == 1");

        parentViewController.PresentViewController(picker, true, null);
    }
}
Tags: xamarin ios

-広告-

追記