STM32F103VE(STBee)でAMトランスミッタを作ってみました。
市販品はFMトランスミッタですが、FMラジオの周波数帯をマイコンで扱うのは辛い、FM変調を作るのは更に辛い、等の理由によりAMとしました。もっとも、変調はAMですが周波数帯は中波ではなく長波なので、市販のAMラジオでは聞けないと思います。今回はIC-R6を使って動作確認をしました。
画面右下にあるのが受信に使用したIC-R6で、音声出力がカメラの音声入力に入っています。画面中央部にあるプローブが刺さったボードがSTBeeで、これがマイク入力とRF出力を行っています。マイクは基盤右上の黒い丸の部分です。基盤左下から右上に向かっている赤い線が空中線代わりのジャンパワイヤです。送信出力が非常に低いため、この距離でもスケルチがぎりぎり開く程度で、撮影の時はIC-R6のアンテナとトランスミッタのアンテナを接触させています(被覆で絶縁されてますが)。
(こういうブロック図を簡単に作る方法って何かないかな)
今回は100kHz(0.100MHz)のAM波を送信するため、正位相・逆位相を出力するために2本のDACを使用しました。これはTIM3のタイミングでDMA転送し、DMAのHTとTCで割り込みを発生させ、その割り込みでマイクのバッファに入っているアナログデータを生成します。
図中ではTIM3から割り込みが発生してる風ですが、実際にはDMAから発生させています。
音質もラジオで衰退してる割には近距離であれば問題無いですし、電波で遊びたいという欲求はかなり解消できたかな、と思っています。100kHzではアンテナの利得は無いも同然で、送受信共に感度が低いので長距離通信はおろか、1mも離したらスケルチが閉じっぱなしになってしまいますが。
現行法に対応した形で簡単に無線で遊ぶのはコレが限界かな、とも思います(コレも厳密にはどうかわかりませんが)。
2015年10月6日火曜日
2015年9月29日火曜日
GPIOを操作するマクロを作る
久しぶりにSTM32F1で遊んでいます。
雛形を作りなおしているので、そのついでにGPIOを操作するマクロを作ってみました。
基本的にSTM32F1用。
SW1はアクティブHIGH接続で押された時にSet。
スイッチを読む場合
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)のほうが直感的な気がします。
雛形を作りなおしているので、そのついでに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を加える必要がある。
今回はSelectVoiceでZira(en-US)を指定した。デフォルトはHanako(ja-JP)だが、日本語だとイントネーションが不思議な感じになる。やはり英語のほうが自然。
それとSpeakで音声を出力している間はスレッドをブロックしてしまうので、実際に使うなら何らかの工夫が必要だと思う。
読み上げる速度はOSの設定から「音声認識のプロパティ」ダイアログ内「音声合成」タブにある「音声の速度」で設定できる。最速だと何を言ってるかわからないし、最遅でも何を言ってるかわからない。あんまり速度の調整はやらないほうが良さそう。
参照に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も入れてないが)。
音声出力も動作確認はしたが、また次の機会に。
軽く動作の説明をすると、最初の方の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とかてきとーな音源を見てみると面白い。
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要素ずつしか追加することができない。
ということで拡張メソッドで拡張してみた。
動作としては与えられた要素数をforで追加するだけなので、わざわざ書かなくとも適時forで回せばいい。しかし拡張メソッドで作っておけば通常のEnqueueとおなじ感覚で使用することができる。もしもオーバーロードするのが嫌というならEnqueueRange<T>(this Queue<T>, T[] Datas) とでもしておけばいいだろう。
ということで拡張メソッドで拡張してみた。
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#には拡張メソッドという仕組みがあるらしいので、使ってみる。
文字列の配列を整数の配列に変換するやつと、整数の配列を文字列に変換するやつ。エラー処理とかは一切していないので実際に使う場合はてきとーにごにょごにょと。
ところで、MsTestで配列をテストするのにいい方法は無いものか。forで回してエラーメッセージにIndexを入れておくとかかなぁ… それこそ拡張メソッドで実現するべきっぽいですよね。まぁMsTestが使いづらかろうともデフォルトで使える以上は使い続けますが。
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のストリームを引っ張ってきて自前で処理すればいいかもしれないが、今回は面倒だったのでやってない。
解像度は「適切な(縦横比の)解像度で縦横を連続して設定すると適用される」みたいな動作になってるのでハードコードしてやった。
リアルタイムで映像を表示するだけの機能しかないが、画角の確認だとかフォーカスの確認くらいなら可能。
OpenCvSharpで読み込んでC#のフォームに表示してもいいが、今回は手っ取り早くOCvで表示まで行った。
ソースコードはオブジェクト指向とは思えないような感じになってる。がまぁ動作してるので。。。
文字列入力を読み込みたかったので、スレッドでQueue<string>にConsole.ReadLineを読み込んでいる。一般的には「CvのWindowで何かキーを押せば終了」だと思うが、このコードはConsoleにquitを入力することで終了する。Console.ReadLineは同期読み込みだから、途中で読み込みを止めたいと思っても止められない(はず)。ということで最後にユーザーがEnterを入力してやる必要がある。Inのストリームを引っ張ってきて自前で処理すればいいかもしれないが、今回は面倒だったのでやってない。
解像度は「適切な(縦横比の)解像度で縦横を連続して設定すると適用される」みたいな動作になってるのでハードコードしてやった。
リアルタイムで映像を表示するだけの機能しかないが、画角の確認だとかフォーカスの確認くらいなら可能。
ラベル:
C#,
OpenCV,
OpenCvSharp
登録:
投稿 (Atom)
