2014年6月25日水曜日

STM32F1で7セグLED



内部リソース

7セグの表示に使っているのはTIM2とDMAです
DMAの転送タイミングをTIM2で作成し、RAMにある配列から点灯パターンを送り出しています
こうすることによりCPUは完全にノータッチでダイナミック点灯を実現できます
そのためCPUをスリープにしても点灯を継続できます
7セグLEDを使っている場合CPUの消費電力程度削減してもあまり効果はありませんが、CPUがノータッチで良いということは他の処理に負担をかけないということでもあります

今回はタイムソースにTIM2を使いましたが、TIM2のDMAはSPI1_RX等と競合するので、STM32F103VE等であればTIM5を使うなど、工夫したほうがいいと思います

今回のサンプルはとりあえず7セグを表示するのが目的だったので、表示文字の変更以外はWFIでスリープに入れて、USART1割り込みで起床するようにしています

スリープから抜けたら、USART行受信関数を呼び、もし改行文字まで受信していればその文字列を表示関数に渡した後にスリープする という感じです
表示関数の中では文字列をwhileで処理し、その中ではswitchで表示RAMに当該文字の点灯パターンを代入する という処理をしています


7セグLEDは結構簡単に扱える表示デバイスですが、表示桁数に比例して扱いづらくなっていきます
昔1個モノの機材を作った時には8ケタの表示モジュールを作りましたが、それだけでかなり時間がかかりました(メインの基板はあまり複雑じゃないのですぐに作れた気がします)

最近は7セグLED表示モジュール等があり、SPIやUSARTで制御できるようになっていますが、欲しい色がなかったりだとか、8ケタの表示のためにUSARTモジュールが1個必要だったりとか、あまり使いやすいとは言えないような気がします


汎用ポートがゴッソリと余っている場合は7セグLEDは表示デバイスとしては有用だと思います
ただし一般的な16x2のキャラ液晶は最小構成で6本のポートがあれば使えるので、どうしても という場合でない限りはキャラ液晶のほうが使いやすいのも事実です

2014年6月18日水曜日

TIMソースのタイミングでDMA割り込み

タイマで生成したタイミングでDMA転送

今回はTIM3の更新でDMAトリガです

まずTIM3の初期化

    TIM_InitStructure.TIM_Period = 1500 - 1;
    TIM_InitStructure.TIM_Prescaler = 0;
    TIM_InitStructure.TIM_ClockDivision = 0;
    TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_InitStructure);
    TIM_Cmd(TIM3, ENABLE);

今回は72MHzで48kHzを作りたいのでカウンタは1500 分周は無し
これで72000000/1500/1=48000Hzが作れる

なお、タイマ割り込みは使わず、ただの分周器として使いたいだけなのでNVICの初期化は不要

次にDMAの初期化

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(DAC_BASE + 0x00000008 + 0x00000004);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(&DAC_Buff[0]);
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = 480;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    DMA_Init(DMA1_Channel3, &DMA_InitStructure);
        
    DMA_Cmd(DMA1_Channel3, ENABLE);
    TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE);

DMAの転送元はuint16_t配列のDAC_Buffを 転送先はDMA1の12bit左詰めレジスタを指定
(DACはDAC1->ODRみたいに指定できないのが面倒)

基本的に普通の初期化方法と同じ
今回はぐるぐるとバッファの中身を出力させたいのでCircularモード
そしてM2MはDisableに

DMAの転送トリガはDMA_M2M or ペリフェラルトリガなので、M2MがEnaの場合はどんどん出力されていく

そしてDMA_Cmdの後にTIM_DMACmdでタイマとタイミングを指定する

タイマとトリガとDMAチャネルの組み合わせは決まっているので注意
組み合わせはRM0008のDMAの説明に書いてある


例えばSPI1はDMA1_ch2がRX DMA1_ch3がTXになっているが、TIM2更新もDMA1_ch2に割り当てられている
予めうまく組み合わせを考えておかないと、修正で面倒なことになる


TIMでタイミングを作ってDMA転送は結構簡単に使える感じ
ただ今回はDACとかいろいろの組み合わせで正常動作していないように見えてしまったためにややこしいことになった。。

注意点として、DMAとTIMを組み合わせてもあまり高速な転送はできない せいぜい6MHzくらい

DACはTIMでいいけど、ADCは注意が必要
ADCを使う場合はADCのトリガをTIMにして、DMAのトリガをADCにする
ちょっとフクザツだけどパズルみたいで面白い
ちゃんと作れれば

2014年6月13日金曜日

Processingの起動時間

millis()という関数でint型の起動時間がミリ秒で帰るようです
これを使えば起動時間を最大20日程度まで計測できます

似た関数に second() minute() hour() day() month() year() がありますが、これはシステム時間を返す関数なので注意してください


ProcessingではframeCountという変数があり、これは1フレーム毎にインクリメントされていきます
またframeRateという変数もあり、起動時間 = frameCount / frameRateと計算できるのでは? と思うのですが そう簡単なものでもありません

というのは、frameRateを表示してみればわかると思いますが、このframeRate変数は定数ではありません

frameRate()関数で設定できるのはあくまで目標値であり、実際のフレームレートはPCの処理速度や負荷に応じて変動しています
frameRate変数で獲得できるフレームレートは実際に計測したフレームレートなので、frameCount / frameRateの計算をすると 負荷が大きい時には実際よりも長く起動していると計算されてしまいます


そこで、起動時間を計算する方法ですが
まず前提として「1.0 / frameRate = 前フレームから経過時間」という条件を作ります
実際のframeRateの計算方法を知らないのでこの条件が当てはまるかはわかりません

しかし、こうすることで非常に簡単に計算できるようになります
前フレームからの経過時間が分かれば、フレームごとに時間を積算すればいいだけです
ということで
float time = 0;
void draw() {
    time += 1.0 / frameRate;
}
とすれば稼働時間がわかります

浮動小数点演算の精度がきになる場合は
int second = 0; float millisecond = 0;
void draw() {
    if ((millisecond += 1.0 / frameRate) >= 1.0) { millisecond -= 1.0; second++; }
}
とでもしておけばmillisecondの最大値はせいぜい1.1以下におさまるはずなので、演算時の切り捨ては無視できると思います
(小数点以下の演算誤差は相変わらず存在します)


とりあえずこれで時間が扱えるようになりました
なんかの処理に使えるかも

言い訳をすると、day hour millis minute month second yearと並んでいるので、millisはシステム時間のミリ秒を返す関数かと思っていました
システム時間のミリ秒が欲しい場合はどうすればいいんだろう?

2014年6月12日木曜日

タクトレっぽいハンドガンの練習

タクトレっぽいハンドガンの練習

必要なもの
1)ターゲットプレート
2)ターゲットプレートを設置する棒みたいなの
3)銃本体(エアコキがオススメ)
4)マガジン2本(リロードするため)
5)使いやすいホルスター
他にBB弾が必要です


まず直径5インチくらいの金属プレートをてきとーな高さに設置します
今回は12.5cmのアルミプレートを流用しました
金属じゃなくてもいいですが、金属だとわかりやすいので便利です

次に7mほど離れた場所にマーキングします

あとは撃ちます

と言っても7mのラインからではありません
更に後ろに下がって15-20mあたりがスタート位置です


銃は弾が装填されていない状態でマガジンを装填します

撃つ手順は
1)スタート位置から歩き始める
2)ドロウ
3)装填
4)照準・ファイア
5)マグリリース・ロード
6)装填
7)照準・ファイア
となります

2回の射撃とリロードの間、常に歩いています
また、2回めのファイアは7mラインより後ろで行う必要があります
リロード中に7mラインを超えてターゲットに接近した場合は射撃できません


複数人で遊ぶ場合は
・1発命中毎に得点が発生(1発5点 とか)
・マガジンを回収した場合は加点(3点 とか)
・時間毎に減点(1秒1点マイナス とか)
のようなルールを作れば面白いと思います

焦ると7mギリギリでも命中させるのが大変です(歩いてるし)
またマガジンは捨てたほうがリロードは早く終わりますが、マガジンを回収したほうがプロっぽいです
ゆっくり歩けばその分命中させやすいですが、動きまわってるほうが楽しいです

難易度を調整する場合は7mラインを移動することで可能です
遠くにすれば難しくなり、近くにすれば簡単になります
ただあんまり近くにすると狙わなくても当たるのでつまらないです


2014年6月4日水曜日

行政無線を聞いてみる

SDRで上富良野町の行政無線を聞いてみました

上富良野町の行政無線は68.805MHzです
ナローFM 帯域幅5kHzです

・今回使用したアンテナが137MHzのQFHアンテナだった
・送信場所からみて受信アンテナが建物の裏に有った
などの理由によりSNRは悪いですが、辛うじて聞き取ることができます


以前、上富良野町にも光回線を敷設しろ というNTTとの交渉で
行政無線を光回線で提供する という要求を出したそうです
返答としては「一行政のためのサービスをインターネットでやるなんて無理」だったらしいですが


現状のシステムでは専用の受信機のほか、実売2万円程度の受信機でも聞くことができます
上富良野町の場合は噴火等の災害への対策が主な目的のため、光回線等を使う"専用の受信機が必要なシステム"よりも冗長性が高いと言えます


警察や自衛隊の無線がデジタル化され暗号化されるのは仕方ない流れとも言えますが、防災無線は可能な限りローテクな方法で送信されるべきだと思います

なお、上富良野町の行政無線は電話 0167-45-4567 でも聞くことができます
ケータイ等が使用可能な平時の場合は使える方法です