2015年9月29日火曜日

GPIOを操作するマクロを作る

久しぶりにSTM32F1で遊んでいます。
雛形を作りなおしているので、そのついでにGPIOを操作するマクロを作ってみました。
基本的にSTM32F1用。

マクロ

#ifndef __hardware_H
#define __hardware_H

#include "includes.h"

#define     HW(name, x, type)       name##_##x##_##type
#define     HW_OUT(name, x, val)    GPIO_WriteBit(HW(name, x, PORT), HW(name, x, PIN), (val) ? HW(name, x, ACTIVE) : !HW(name, x, ACTIVE))
#define     HW_IN(name, x)          (GPIO_ReadInputDataBit(HW(name, x, PORT), HW(name, x, PIN)) == HW(name, x, ACTIVE))
#define     HW_OT(name, x)          (GPIO_ReadOutputDataBit(HW(name, x, PORT), HW(name, x, PIN)) == HW(name, x, ACTIVE))
#define     HW_TGL(name, x)         HW_OUT(name, x, !HW_OT(name, x));

#define     LED_1_RCC       RCC_APB2Periph_GPIOD
#define     LED_1_PORT      GPIOD
#define     LED_1_PIN       GPIO_Pin_4
#define     LED_1_ACTIVE    0

#define     SW_1_RCC        RCC_APB2Periph_GPIOA
#define     SW_1_PORT       GPIOA
#define     SW_1_PIN        GPIO_Pin_0
#define     SW_1_ACTIVE     1

#endif /* __hardware_H */
LED1はアクティブLOW接続でResetで点灯、Setで消灯。
SW1はアクティブHIGH接続で押された時にSet。

使用例

void LED_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(HW(LED, 1, RCC), ENABLE);

    GPIO_InitStructure.GPIO_Pin = HW(LED, 1, PIN);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(HW(LED, 1, PORT), &GPIO_InitStructure);
}

void SW_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(HW(SW, 1, RCC), ENABLE);

    GPIO_InitStructure.GPIO_Pin = HW(SW, 1, PIN);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(HW(SW, 1, PORT), &GPIO_InitStructure);
}

スイッチを読む場合
if (HW_IN(SW, 1)) { ... } else { ... }

LEDを点灯させる場合
HW_OUT(LED, 1, 1);

LEDを消灯させる場合
HW_OUT(LED, 1, 0);

LEDを切り替える場合
HW_TGL(LED, 1);

もちろんスイッチやLED以外にも使用可能です。
ほとんど便利じゃないし、forで回したりできないので、ベタ書きとほとんど変わらないですが。
あとUnity(テストハーネス)使ってる人はわかると思いますが、マクロでごにょごにょやってるとエラった時にメッセージが非常に難解です。
今回はHWを接頭辞として使っていますが、LED(1, PORT)やSW(1, PIN)のほうが直感的な気がします。

2015年9月23日水曜日

C#で音声合成出力

C#で音声合成出力を試してみた。OSはWin10、環境はVS2015WinDesktopのC#を使った。
参照にSystem.Speechを加える必要がある。

using System;
using System.Speech.Synthesis;
using System.Windows.Forms;

namespace Speech
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            using (SpeechSynthesizer syn = new SpeechSynthesizer())
            {
                syn.SelectVoice("Microsoft Zira Desktop");
                syn.Speak(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));
            }
        }
    }
}

今回はSelectVoiceでZira(en-US)を指定した。デフォルトはHanako(ja-JP)だが、日本語だとイントネーションが不思議な感じになる。やはり英語のほうが自然。
それとSpeakで音声を出力している間はスレッドをブロックしてしまうので、実際に使うなら何らかの工夫が必要だと思う。
読み上げる速度はOSの設定から「音声認識のプロパティ」ダイアログ内「音声合成」タブにある「音声の速度」で設定できる。最速だと何を言ってるかわからないし、最遅でも何を言ってるかわからない。あんまり速度の調整はやらないほうが良さそう。

2015年9月19日土曜日

XNAでマイク入力

XNAでマイク入力を試してみた。
XNAは開発が終了してしまったが、まだDLは可能(なはず)。XNAをインストールするときにはVisual Studio C# 2010が必要になるみたいなのでそれを先に入れる。一旦インストールしてしまえば、ライブラリはVisual Studio 2015のC#からも使用可能。XNAは音声(WAVE)IOやXbox360コントローラなど、C#では珍しくハードウェアアクセスが豊富なので入れておくと便利。

さて、マイク入力だが、今回はスレッドを1本作って無限ループとした。ただ作ってから気がついたけどタイマとかで定期的に読めば問題なさそう。今回試した環境では16bit1chのデータが読めた。リファレンスによると8bit or 16bit / 1ch or 2chということなので、データ数からビット数やチャンネル数を予測するのは不可能っぽい。実際に使うには何らかの対策が必要そう。
それとVS2010がインストールされていたのがデスクトップだったので、そっちで開発をしているが、ノートにEXEを投げると正常に動作してくれないという問題がある。ノート側でビルドすれば動きそうな気もするけど、まだVS2010が入ってないので確認ができない(そもそもVS2015も入れてないが)。

音声出力も動作確認はしたが、また次の機会に。

/* 例:Microphone Mic = Microphone.Default; */
/* Microphone.Allですべてのデバイスを獲得できるのでそこから選んでも良い */

Mic.Start();

while (IsLoop)
{
    byte[] buffer = new byte[Mic.GetSampleSizeInBytes(TimeSpan.FromSeconds(0.1))];
    int bytesRead = 0;

    DateTime ReadTime = DateTime.Now;

    while (bytesRead < buffer.Length)
    { bytesRead += Mic.GetData(buffer, bytesRead, buffer.Length - bytesRead); Thread.Sleep(10); }

    short[,] ROW = new short[buffer.Length / 2, 1];

    for (int i = 0; i < buffer.Length; i += 2)
    { ROW[i / 2, 0] = BitConverter.ToInt16(buffer, i); }

    this.ROW = ROW;
}

Mic.Stop();

軽く動作の説明をすると、最初の方の0.1を渡すメソッドでバッファ数を決めている。これは0.1秒分のバッファサイズとなる。
次にMic.GetDataでデータを読む。これは引数にオフセットを渡せるので、バッファ全体を読み込むまでループする。データを纏めて読む必要が無い場合は1回に読める分だけ処理してもいい。
またGetDataのループの中ではThread.Sleep(10)で10ミリ秒のスリープを入れている。これはこのwhite(true)でCPUリソースを浪費しないようにするためだ。
そしてshort(符号付き16bit)の2次元配列を作る。今回は1ch16bitで決め打ちしているのでバッファの半分の長さで1ch分となっている。
あとはforで回してBitConverterでshortに戻している。これはビットシフトとかでもいいと思うが、BitConverterのほうが確実(エンディアンが一致している限りは)。
最後にthis.ROWにROWを代入している。コレとは別のループでthis.ROWを監視しておき、データが入ったら処理を行い、処理が終わったらnullを入れてデータがないことを示す という感じでマイク入力のWAVEデータを扱えばいい。

配列をBitmapにGraphicsのDrawLineでグラフにすれば生の波形を表示することができる。PCにステレオミキサが入ってればそれを録音することによりPCの音が表示されるので、iTunesとかてきとーな音源を見てみると面白い。

2015年9月14日月曜日

C#のQueueに配列を突っ込む

C#のQueue(System.Collections.Generic.Queue<T>)は可変長のFIFOとして使えるので、バッファとして使いやすい(コストは考えないことにする)。しかしQueueへ追加するにはEnqueueメソッドを使う必要があり、これは1要素ずつしか追加することができない。
ということで拡張メソッドで拡張してみた。

using System;
using System.Collections.Generic;

namespace QueueTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Queue<int> Q;

            Q = new Queue<int>();
            Q.Enqueue(1);
            Q.Enqueue(2);
            Q.Enqueue(3);
            Q.Enqueue(4);
            Q.Enqueue(5);
            while (Q.Count > 0)
            { Console.WriteLine(Q.Dequeue()); }

            Q = new Queue<int>();
            Q.Enqueue(new int[] { 1, 2, 3, 4, 5 });
            while (Q.Count > 0)
            { Console.WriteLine(Q.Dequeue()); }
        }
    }

    public static class MyExtensions
    {
        public static void Enqueue<T>(this Queue<T> Queue, T[] Datas)
        {
            for (int i = 0; i < Datas.Length; i++)
            { Queue.Enqueue(Datas[i]); }
        }
    }
}

動作としては与えられた要素数をforで追加するだけなので、わざわざ書かなくとも適時forで回せばいい。しかし拡張メソッドで作っておけば通常のEnqueueとおなじ感覚で使用することができる。もしもオーバーロードするのが嫌というならEnqueueRange<T>(this Queue<T>, T[] Datas) とでもしておけばいいだろう。

2015年9月13日日曜日

C#の拡張メソッド

C#には拡張メソッドという仕組みがあるらしいので、使ってみる。

using System;
using System.Text;

namespace MyExSample
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] Array = Console.ReadLine().Split(' ').ArrayToInts();
            Console.WriteLine(Array.ArrayToString());
        }
    }

    static public class MyExtensions
    {
        public static string ArrayToString(this int[] Array)
        {
            if (Array.Length == 0)
            { return (""); }

            StringBuilder sb = new StringBuilder();

            sb.Append(Array[0]);

            for (int i = 1; i < Array.Length; i++)
            { sb.Append(" "); sb.Append(Array[i]); }

            return (sb.ToString());
        }

        public static int[] ArrayToInts(this string[] Texts)
        {
            int N = Texts.Length;
            int[] A = new int[N];
            for (int i = 0; i < N; i++)
            { A[i] = int.Parse(Texts[i]); }
            return (A);
        }
    }
}
/*------*/
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MyExSample.Tests
{
    [TestClass()]
    public class MyExtensionsTests
    {
        [TestMethod()]
        public void ArrayToIntsTest()
        {
            int[] Array;

            Array = new string[] { }.ArrayToInts();
            Assert.AreEqual(0, Array.Length);

            Array = new string[] { "1", "2", "3" }.ArrayToInts();
            Assert.AreEqual(3, Array.Length);
            Assert.AreEqual(1, Array[0]);
            Assert.AreEqual(2, Array[1]);
            Assert.AreEqual(3, Array[2]);
        }

        [TestMethod()]
        public void ArrayToStringTest()
        {
            Assert.AreEqual("", new int[] { }.ArrayToString());
            Assert.AreEqual("1 2 3", new int[] { 1, 2, 3 }.ArrayToString());
        }
    }
}

文字列の配列を整数の配列に変換するやつと、整数の配列を文字列に変換するやつ。エラー処理とかは一切していないので実際に使う場合はてきとーにごにょごにょと。

ところで、MsTestで配列をテストするのにいい方法は無いものか。forで回してエラーメッセージにIndexを入れておくとかかなぁ… それこそ拡張メソッドで実現するべきっぽいですよね。まぁMsTestが使いづらかろうともデフォルトで使える以上は使い続けますが。

2015年9月3日木曜日

OpenCvSharpでWebカメラを表示する

Webカメラというか、USBビデオクラス(UVC)を使用するUSBカメラを表示するためのサンプル。USBカメラを表示したかったが録画する機能とかもいらないしサクッと作ってみた。

OpenCvSharpで読み込んでC#のフォームに表示してもいいが、今回は手っ取り早くOCvで表示まで行った。

ソースコードはオブジェクト指向とは思えないような感じになってる。がまぁ動作してるので。。。

文字列入力を読み込みたかったので、スレッドでQueue<string>にConsole.ReadLineを読み込んでいる。一般的には「CvのWindowで何かキーを押せば終了」だと思うが、このコードはConsoleにquitを入力することで終了する。Console.ReadLineは同期読み込みだから、途中で読み込みを止めたいと思っても止められない(はず)。ということで最後にユーザーがEnterを入力してやる必要がある。Inのストリームを引っ張ってきて自前で処理すればいいかもしれないが、今回は面倒だったのでやってない。

解像度は「適切な(縦横比の)解像度で縦横を連続して設定すると適用される」みたいな動作になってるのでハードコードしてやった。

リアルタイムで映像を表示するだけの機能しかないが、画角の確認だとかフォーカスの確認くらいなら可能。



2015年9月2日水曜日

BCDを計算するメソッド

たまーに使われてる二進化十進表現(BCD)をC#で扱うためのメソッド。

二進化十進表現とは10進数の何桁かの数字を16進数で見やすくするためのエンコードの事。二進化というけど、2進数での表現ではあまり人間にはメリットがない。
そもそもBCDが便利ということはほとんど無く、実際に使うのは7セグLEDデコーダチップのインターフェースだったりとかで使われているだけ。他にはシリアルポートに特定のデータを流したくない場合にBCDでエンコードするとかもやるみたい。例えばスタートバイトをとストップバイトを0xF0以降に配置しておき、数値データはBCDで流した場合、16進表示のシリアルモニタで見るときに人間がわかりやすいし、BCDエンコードしてしまえば0x00-0x99の範囲しか扱わないのでストップバイトと競合することもない ということらしい。

本体
public class BCD
{
    static public UInt64 To(UInt64 HEX)
    {
        UInt64 val = 0;

        val |= (HEX % 10) << 4 * 0; HEX /= 10;
        val |= (HEX % 10) << 4 * 1; HEX /= 10;
        val |= (HEX % 10) << 4 * 2; HEX /= 10;
        val |= (HEX % 10) << 4 * 3; HEX /= 10;
        val |= (HEX % 10) << 4 * 4; HEX /= 10;
        val |= (HEX % 10) << 4 * 5; HEX /= 10;
        val |= (HEX % 10) << 4 * 6; HEX /= 10;
        val |= (HEX % 10) << 4 * 7; HEX /= 10;
        val |= (HEX % 10) << 4 * 8; HEX /= 10;
        val |= (HEX % 10) << 4 * 9; HEX /= 10;
        val |= (HEX % 10) << 4 * 10; HEX /= 10;
        val |= (HEX % 10) << 4 * 11; HEX /= 10;
        val |= (HEX % 10) << 4 * 12; HEX /= 10;
        val |= (HEX % 10) << 4 * 13; HEX /= 10;
        val |= (HEX % 10) << 4 * 14; HEX /= 10;
        val |= (HEX % 10) << 4 * 15;

        return (val);
    }

    static public UInt64 From(UInt64 BCD)
    {
        UInt64 val = 0;

        val += (BCD & 0xF) * 1; BCD >>= 4;
        val += (BCD & 0xF) * 10; BCD >>= 4;
        val += (BCD & 0xF) * 100; BCD >>= 4;
        val += (BCD & 0xF) * 1000; BCD >>= 4;
        val += (BCD & 0xF) * 10000; BCD >>= 4;
        val += (BCD & 0xF) * 100000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000; BCD >>= 4;
        val += (BCD & 0xF) * 10000000; BCD >>= 4;
        val += (BCD & 0xF) * 100000000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000000; BCD >>= 4;
        val += (BCD & 0xF) * 10000000000; BCD >>= 4;
        val += (BCD & 0xF) * 100000000000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000000000; BCD >>= 4;
        val += (BCD & 0xF) * 10000000000000; BCD >>= 4;
        val += (BCD & 0xF) * 100000000000000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000000000000;

        return (val);
    }
}

単体テスト
[TestClass()]
public class BCDTests
{
    [TestMethod()]
    public void ToTest()
    {
        Assert.AreEqual(0x000000000000ul, BCD.To(0));
        Assert.AreEqual(0x000000000001ul, BCD.To(1));
        Assert.AreEqual(0x000000000009ul, BCD.To(9));
        Assert.AreEqual(0x000000000010ul, BCD.To(10));
        Assert.AreEqual(0x123456789012ul, BCD.To(123456789012));
        Assert.AreEqual(0x999999999999ul, BCD.To(999999999999));
    }

    [TestMethod()]
    public void FromTest()
    {
        Assert.AreEqual(0ul, BCD.From(0x0000000000000000));
        Assert.AreEqual(1ul, BCD.From(0x0000000000000001));
        Assert.AreEqual(9ul, BCD.From(0x0000000000000009));
        Assert.AreEqual(10ul, BCD.From(0x0000000000000010));
        Assert.AreEqual(123456789012ul, BCD.From(0x123456789012));
        Assert.AreEqual(999999999999ul, BCD.From(0x999999999999));
    }
}

Visual Studioの単体テストの本来の使い方をやっと(偶然)発見したのだけど、結構便利。あとどれくらい時間がかかったのかがわかるのでちょっとプレッシャーがかかる。Fromのテストでは1msec未満だが、Toのテストでは4msec以上かかっている。パフォーマンスの差が激しいのが不思議。無理やり解釈すれば、Toはコンパイラ最適化によってビット演算のみで計算され、Fromはビット演算以外に加算と乗算が発生しているため とか?