2014年3月24日月曜日

飛行機

最近のTwitter用画像投稿サイトって軒並みリサイズされるんですね…
ということで最後の砦のブログ




大韓航空のB-777っぽいです

PENTAX K-5に55-300を付けてどちらも換算450mm
上の画像はトリミング&拡大です



ついでにもう一枚
陸自のUH-1Jです

2014年3月21日金曜日

ターゲットプレート

切板.com(kiriita.com)でステンレス板を切ってもらいました


ステンレス430で、直径は12.5cm 中心と上の方に5mmの穴が開いています
見て分かる通り、上の穴は紐を通したりするものです
中心の穴は5mmの砲弾型LEDを入れようと思ったのですが、公差の関係で入りませんでした
とりあえず5.5mmに広げて接着剤で固定しようかと思います
ただ板は2mmで、LEDは8mmくらいあるので、普通に固定したらかなり飛び出ます
この辺りはもうちょっと色々考える必要があります

注文時にステンレス430を指定して、この種類は磁性があるので、磁石がくっつくはずなのですが、届いた板はつきませんでした
磁石を貼り付ける予定はあまりないのでいいのですが、ちょっと謎なのでショップに問い合わせているところです
※2014/03/22|ショップから連絡がありました ステンレスとアルミニウムを取り違えて加工していたようです アルミとステンレスって全然重さが違うはずなのだけど 向こうも金属切断が本業なのに、持ってみてわからないかなぁ 検品とかしてないんだろうなぁ 切断だけならする必要ないもんなぁ

そして、センサ等についてですが これはある程度設計は終わって次は基板の発注 というところまで来ています

感じとして
・プレート1枚にセンサモジュール1個
・プレートにはHITセンサとLEDが付く
・メインのコントローラまではケーブルで接続(ケーブルはLANケーブルを流用する予定)
・1個のコントローラで最低5 できれば8枚までのプレートを接続できるようにしたい
・BB弾がHITしたタイミングの表示(PCへ接続して)
・スタートボタンを押してからランダム(4-5秒くらい)時間後のブザー
・HITしたらプレートのLEDがフラッシュ
・複数のコントローラを同期(2人で10枚以上のターゲットを撃つ時間を計測 みたいな)
のような感じを想定しています

PCとの接続はFT232かな
しばらくは汎用ソフトでそっけない表示 ある程度余裕ができれば専用のソフトウェアを作ってログの保存とかスタートの操作とかも作るかもしれないけど


あんまり国内でシューティングタイマーを見かけないので、売ったらある程度は売れそうな気もするけど、値段にもよるので コストダウンをどうするかなー という感じ

もしも販売する場合は
1)ファーストセット(コントローラとターゲットモジュール1個+ターゲットプレート)
2)ターゲットモジュール(プレート無し)
のラインナップになります
値段は未定ですが、1は1万円から2万5千円の間 2は3千円くらい を予定しています
1についてはまだ設計が終わってないのでかなり幅広いです

何個かは北海道スポーツシューティングクラブで使うので、試作分はそこに出すとして、一定人数欲しいって人が入れば上記の感じで量産するかも

2014年3月12日水曜日

xprintfに引数で最小フィールド幅を指定する機能を追加する

stdio.hのprintf関数には最小フィールド幅というものがあります
例えば
printf("%05d", 123);
というコードの場合は"00123"という風に、指定した文字数(今回は5)に足りない場合に空白や0で埋めるという機能です

この機能はもちろんxprintfにもあり、同じように使うことができます

しかし、printfの地味な機能として、この最小フィールド幅を引数で指定することができます
printf("%0*d", 5, 123);
という感じです
最初のサンプルではフォーマットの中に5と指定していた部分をアスタリスクに置き換え、数字を引数として渡しています

この機能をxprintfで使えるようにしてみました

変更方法は
if (!w && c == '*') { w = va_arg(arp, unsigned int); c = *fmt++; }
をvfprintf関数の"/* Minimum width */"というコメントが書いてあるforの次に置くだけです

この機能は数字で最小フィールド幅を指定されていない場合 かつ フォーマットに'*'が書かれていた場合に、引数から最小フィールド幅を取り出す という動作です

あまり使う場面は多くありませんが、たまーに使いたい時があり、結構便利な機能となります

2014年3月11日火曜日

MacOSで画面を拡大

昔のMac(10.6の頃?)はOS標準で画面を拡大する機能があったのですが、最近のOS(10.8)ではその機能がOFFになっています
ということで有効にする方法

システム環境設定のアクセシビリティ内にある"ズーム機能"にある
"スクロールジェスチャと装飾キーを使ってズーム"をチェックします
またその直下にあるコンボボックスで装飾キーをControl/Option/Commandの3種類から選ぶことができます

例えばCommandを設定した場合、Cmdキーを押しながらトラックパッド奥のほうにスクロールすると拡大することができます

また、ソフトウェアによっては"イメージをスムージング"のチェックを外したほうが読みやすくなる可能性があります
この機能はあまりスムーズというほどスムーズにしてくれないので、ちょっと微妙なところです
Terminalのようなフォントを使っている場合は特にはっきりと体感できますが、スムージングがない方がはるかに読みやすいです

この機能を使えばデバッグコンソールを拡大表示したりできるので、少し離れて作業 とかには便利です

2014年3月10日月曜日

ノンブロッキングでコンソール入力

コンソール入力は通常ブロッキングで行われます
これはscanf等で読み込んだ場合、少なくともEnterを押すまでは処理が停止することを意味します
通常のコンソールプログラミングではコレで問題ありませんが、バックグラウンドで何か処理をしたい場合は困る場合があります

ということでノンブロッキングで読み込む方法
を参考にしました

 11 int CanIRecv(int fd) {
 12     fd_set fdset;
 13     struct timeval timeout;
 14     FD_ZERO(&fdset);
 15     FD_SET(fd, &fdset);
 16     timeout.tv_sec = 0;
 17     timeout.tv_usec = 0;
 18     return(select(fd + 1, &fdset, NULL, NULL, &timeout));
 19 }
 20 
 21 int Recv(int fd, char *buff, int buffSize) {
 22     int i = CanIRecv(fd);
 23     if (i) {
 24         read(fd, buff, buffSize);
 25         buff[strlen(buff) - 1] = '\0';
 26     }
 27     return(i);
 28 }
 29 
 30 int main() {
 31     int cnt = 0;
 32 
 33     while (1) {
 34         char buff[200];
 35         if (Recv(0, buff, sizeof(buff))) {
 36             printf("%d:[%s]\n", cnt, buff);
 37             if (!strcmp(buff, "quit")) { break; }
 38         }
 39         cnt++;
 40     }
 41 
 42     return(0);
 43 }
という感じのコードでテスト
なお<stdio.h>と<sys/time.h>のインクルードが必要

このコードだと文字列の受信はノンブロッキングで行われるため、何も入力しなくてもcntがどんどんインクリメントされていくはずです

結果は
hello
1508070:[hello]
test
2561543:[test]
quit
3515094:[quit]
という感じになり、正常に動作していることが伺えます

ちなみにCanIRecvなどに渡すのはファイルディスクリプタで、POSIXではstdinに0が stdoutに1が stderrに2が割り当てられ、openなどで開くとその後ろが割り当てられます


ということで、マイコンだとよく使ってるノンブロッキングな行入力の使い方でした


MacOSってGCCコンソールでも簡単にネットワークプログラミングができるので、ちょっと遊んでいます
もうちょっと色々できるようになったら XBeeWiFiとか使えば面白いかなーと思っています

2014年3月8日土曜日

エアガンのターゲットを作る

おそらく2回目

急にやる気になった理由ですがFbのHSSCで「電子ターゲット使いたいよねー 作ってよ~」と言われたので、放置していたものを進めよう ということになりました

さて、エアガンだと「アンリミテッド」と呼ばれる競技があります
これはハンドガンや長物等、クラスによって使う銃が分かれていますが、共通しているのは5枚のターゲットを撃つ早さを競うということです
で、そのターゲットはもちろん電子ターゲットが使われていて、開始から終了までの時間を計測しています

昔は1万円くらいでターゲットが市販されていましたが、生産終了で今ではどこのショップも在庫切れになっています

で、そのターゲットを解体した人のブログを見てみたら、何の事はない圧電素子でした

ということで前回の方向性を進めるということになりました



今回は検出能力を向上させるために1.0mmのステンレスプレートに耐衝撃アロンアルフアで圧電素子を貼り付けたものを使用しました
その波形です
縦が20V/div 横が25mSec/div です
プレートから70cmの距離に銃口先端が来るような位置でBOYsSPOMODで撃ちました
Vppで40V以上です 凄い

この画像は上記と同じ条件で3発連射した時の波形です
波形から見るとおよそ10発/秒で、これくらいのサイクルであれば十分に1発ずつの検出ができそうです
ただ検出回路はこんなに分解能が高くない予定なので、30発/秒になると厳しいかも

ちなみにこのステンレスプレート、10cm上から自由落下させた0.12gBB弾でも検出します(Vpp1.5V)
凄まじい検出能力です

ということで、検出装置の目処が立ったので次は検出回路に移りたいのですが、残念ながら手持ちのオペアンプが4558DDしか無いので当分できなさそうです

5Vくらいの単一電源動作なレールツーレールのオペアンプを買ったら続きをやろうと思います
Vpp1Vから40Vまでの範囲を検出できる回路ってどんな風に組めばいいんだろうか…

気が向いたら、PSoCで遊んで見るかも

2014年3月7日金曜日

Linuxのシリアルポート

Raspberry PiでFT232を使えれば便利だと思ったので、試しています

とりあえず
      1 #include <sys/types.h>
      2 #include <sys/stat.h>
      3 #include <fcntl.h>
      4 #include <termios.h>
      5 #include <stdio.h>
      6 #include <stdlib.h>
      7 #include <string.h>
      8 #include <unistd.h>
      9
     10 #define BAUDRATE (115200)
     11 #define MODEMDEVICE "/dev/ttyUSB0"
     12
     13 int main(void) {
     14     int fd;
     15     struct termios oldtio, newtio;
     16     char buf[255];
     17
     18     sprintf(buf, "stty -F %s speed %d\n", MODEMDEVICE, BAUDRATE);
     19     system(buf);
     20
     21     fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY);
     22     if (fd < 0) { perror(MODEMDEVICE); return(-1); }
     23
     24     tcgetattr(fd, &oldtio);
     25
     26     bzero(&newtio, sizeof(newtio));
     27     newtio.c_cflag = CRTSCTS | CS8 | CLOCAL | CREAD;
     28     newtio.c_iflag = IGNPAR;
     29     newtio.c_oflag = 0;
     30     newtio.c_lflag = 0;
     31     newtio.c_cc[VTIME] = 0;
     32     newtio.c_cc[VMIN]  = 0;
     33
     34     tcflush(fd, TCIOFLUSH);
     35     tcsetattr(fd, TCSANOW, &newtio);
     36
     37
     38     strcpy(buf, "hello world " __DATE__ " " __TIME__ " " __FILE__ "\n");
     39     write(fd, buf, strlen(buf));
     40
     41     int size = 0;
     42     int ret;
     43     memset(buf, 0, sizeof(buf));
     44
     45     while (size < 10) {
     46         ret = read(fd, buf + size, 10 - size);
     47         if (ret < 0) { break; }
     48         size += ret;
     49     }
     50
     51     printf("%d:%s\n", size, buf);
     52
     53     tcflush(fd, TCIOFLUSH);
     54     tcsetattr(fd, TCSANOW, &oldtio);
     55     close(fd);
     56
     57     return(0);
     58 }
という感じのコードになっています

tcsetattrでビットレートの設定ができなかったため、system関数でstty関数を叩いて変更しています

そしてwriteで出力後、readで10文字を入力して終了
という感じの動作となるはずです

しかし実際には
1)起動
2)入力待ち
3)10文字入力する
4)入力された文字列を表示
5)送信先に文字列が表示される
6)終了
という感じで、文字列の送信がうまく出来ていません

動作としてはバッファリングされている感じですが、そもそもopenはじめwriteなどの低レベル処理ではバッファリングはされていないはずで、結局どこが原因かわかっていません
とりあえずwriteした直後にmemsetでバッファを0埋めしているので、少なくともwrite関数から出た時点でどこかのバッファなりに転送しているはずです
なので問題が有るとすればwrite関数の中か、writeよりも低レベルな場所ということになります

「送信したいのに送信されてない」という問題さえなんとかなれば、いろいろ使い道が出てくるのですが。。

27行目、c_cflagに設定しているCTRSCTSを消したら希望通りの動作をするようになりました
やっぱ説明はちゃんとしたのを読む必要がありますね!!!

ということで、ファイルディスクリプタを引数にしたUARTアクセス用のライブラリを作れば結構便利に扱えそうです

例えば秋月のXBeeアダプタはFT232を使っているので、簡単にRasPiからXBee経由で他のマイコンと通信ができるようになります


まとめ

情報はちゃんと調べよう

Raspberry Piにジョイスティックを

最近はSTM32F1ネタばっかりでしたが
ちょっと違うネタも扱おうと思います

調べ物をしていて「どうやらLinuxでジョイスティックを使うのは簡単のようだ」ということで
じゃぁやってみよう という自然な流れでLinuxからジョイスティックを読んでみました

仮想のUbuntuを起動するのは面倒というこれまた自然な流れでRasPi上で試しています

ソースコードは
http://d.hatena.ne.jp/aki-yam/20130729/1375097014
を参考にしました

使用したジョイスティックは箱○の有線コントローラです

RasPiのUSBポートに突き刺したらドライバとかも必要なく簡単に認識してくれました

上記ブログのコードを動かしてみたところ
Joystick: Microsoft X-Box 360 pad
axis    : 8
buttons : 11
として認識され、操作も出力されています

8軸11スイッチのコントローラで
軸0:左スティック左右(左が- 右が+)
軸1:左スティック上下(上が- 下が+)
軸2:左トリガ(開放状態が- 引いた状態で+)
軸3:右スティック左右
軸4:右スティック上下
軸5:右トリガ
軸6:Pad左右(左が- 右が+)
軸7:Pad上下(上が- 下が+)

ボタン0:A
ボタン1:B
ボタン2:X
ボタン3:Y
ボタン4:LB
ボタン5:RB
ボタン6:Back
ボタン7:Start
ボタン9:Xboxロゴ
ボタン10:左スティック
ボタン11:右スティック

という感じの配置です
ジョイスティックの値は符号付き16bitで、Padは0か-32768か32767の3種類しかありません

Padは上下が-であれば上 +であれば下
左右が-であれば左 +であれば右
で、斜めは両方のANDで検出できます(上下が-で左右が+なら右上 など)

また、トリガについてですが これは左右で1軸ずつ用意されているようです
以前にC#のDirectX.DirectInputで遊んだ時は左右のトリガで1軸でした(左を引くと- 右を引くと+ 両方引くと0 みたいな)
この実装の場合 例えば飛行機のゲームを作る場合、トリガをラダーに割り当て、両方のトリガを引くとブレーキ みたいな動作ができません
しかし左右のトリガで2軸だとこのような実装ができるので便利です


ということで、RasPiで箱○コントローラを読むことができるようになったので
足が生えて歩きまわるようなロボットの制御とかに使えそうです
RasPiにXBeeを接続すればロボットを遠隔で制御できるようになります
また操作対象にもRasPiを搭載し、無線LANの11n等でリンクすれば、対象のカメラで写した画像をこちら側に表示したりもできそうです(もっとも、そこまで行くと両方共ノートPCとかを使ったほうが便利ですが。。。)

2014年3月6日木曜日

STM32F1でUSARTの受信割り込み

STM32F103でUSART1の受信割り込み
今回は単純に受信バッファを実装するだけです
過去のエントリのコードはかなり細切れなので、最後にまとめてコードを貼り付けておきます


まず初期化ですが、前回の送信割り込みの時にNVICを設定したので特に大きく変化する部分はありません
ただし受信割り込みの有効化はしておきます
    USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

割り込みハンドラの前に
char USART1rxBuff[500];
volatile uint16_t USART1rxSet = 0, USART1rxGet = 0;
という計3個の変数を追加しておきます

そして割り込みハンドラに
    if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);

        USART1rxBuff[USART1rxSet] = USART_ReceiveData(USART1);
        if (++USART1rxSet == sizeof(USART1rxBuff))
        { USART1txSet = 0; }

        if (USART1rxSet == USART1rxGet) {
            if (++USART1rxGet == sizeof(USART1rxBuff))
            { USART1rxGet = 0; }
        }

        return;
    }
という処理を追加します

最後にgetch関数を
int USART1_getch(void) {
    char ch;

    if (USART1rxSet == USART1rxGet)
    { return(-1); }

    ch = USART1rxBuff[USART1rxGet];

    if (++USART1rxGet == sizeof(USART1rxBuff))
    { USART1rxGet = 0; }

    return(ch);
}
のように書き換えます
これで受信バッファの出来上がりです


続いて1行受信の関数です
const char *USART1_getline(void) {
    static char USART1rxLine[100];
    static uint16_t USART1liSet = 0;

    int ch = USART1_getch();
    if (ch == -1) { return(0); }

    if (USART1liSet && (ch == '\n' || ch == '\r')) {
        USART1rxLine[USART1liSet] = '\0';
        USART1liSet = 0;
        return(USART1rxLine);
    }

    USART1rxLine[USART1liSet++] = ch;
    if (USART1liSet == sizeof(USART1rxLine) - 1) {
        USART1rxLine[USART1liSet] = '\0';
        USART1liSet = 0;
        return(USART1rxLine);
    }

    return(0);
}
この関数の使い方は、適切なタイミングでこの関数を呼び、戻り値を受け取ります
戻り値のポインタが0(NULL)の場合は1行分が入力されていません
ポインタが0でない場合はそれが1行分の文字列へのポインタとなります
1行が読み取られた後に再度関数を呼ぶと文字列バッファは新しい文字を読み込むため、データが破壊されます
関数を呼び出すサイクル以上に長い間文字列が必要な場合はstrcpy等を使って他のバッファに移動しておく必要があります

なお、この関数を使用する場合は他の箇所でUSART1_getch();を呼んではいけません
もしも呼んだ場合はその時点で行バッファに読み込まれていない最初の文字が欠損することになります
またこの関数では1行バッファ(-1)分の文字数以上が1行に入力された場合は、その時点でバッファに入っている文字列を1行として返し、それ以降は新しい行として読み取られます

それと、デバッグ環境で使用したコンソールがEnterを押すと'\r'だけを送信する という環境だったため、'\n'の環境や"\r\n"の環境での動作確認ができていません
おそらく動作すると思いますが、その点は注意してください


最後に、コードを最初から最後までまとめて貼り付けておきます

STM32F1でUSARTの送信割り込み

STM32F103でUSART1の送信割り込みを使います
ベースは以前のエントリの「割り込みを使用しないUSART」です

まず変更点1カ所目 USART初期化部分です
    USART_ClearITPendingBit(USART1, USART_IT_TXE);

    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
このようにNVICの初期化を行います
今回はUSART_IT_TXE割り込みを使いますが、この時点ではTXEの有効化はしません

次に割り込みハンドラ
char USART1txBuff[500];
volatile uint16_t USART1txSet = 0;
volatile uint16_t USART1txGet = 0;
void USART1_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_TXE)) {
        USART_ClearITPendingBit(USART1, USART_IT_TXE);

        USART_SendData(USART1, USART1txBuff[USART1txGet]);
        USART1txGet++;
        if (USART1txGet == sizeof(USART1txBuff))
        {   USART1txGet  = 0; }

        if (USART1txGet == USART1txSet)
        { USART_ITConfig(USART1, USART_IT_TXE, DISABLE); }

    }
}
このようになります
文字列を保存するバッファと、カウンタが2本です
バッファは今回は500バイトにしましたが、これは任意に設定できます
送出する文字列の量とマイコンのスペックに応じて設定してください

次にputch関数ですが、バッファ書き出すため、このようになります
void USART1_putch(char ch) {
    USART1txBuff[USART1txSet] = ch;
    if (++USART1txSet == sizeof(USART1txBuff))
    { USART1txSet  = 0; }

    if (USART1txSet == USART1txGet) {
        if (++USART1txGet == sizeof(USART1txBuff))
        { USART1txGet  = 0; }
    }

    USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
}
送信バッファに文字を設定し、Setカウンタをインクリメント
Setカウンタがバッファ外に出た場合は0に戻す
次にGetカウンタと比較し、Getカウンタと同じ場所になった(送信が追いついていない)場合はGetカウンタをインクリメント
Setカウンタと同様にバッファ外に出た場合は0に戻す
最後にUSART_ITConfigでTXEを有効に
という動作です
見て分かる通り、バッファが埋まった場合は開くのを待つのではなく、データを上書きします 大量に連続して文字列を送信する場合には注意が必要です

putsやgetchに変更はありませんが、1つ関数を追加しておきます
void USART1txDelay(void)
{ while (USART1txSet != USART1txGet); }
この関数はバッファの中身を送信し終わるまでループで待機する というものです


さて、STM32F1のUSARTは前々回に実装し、前回はタイマ割り込みを使ったRTCで送信にかかる時間を計測しました
その時は200文字を送出するのに17mSecが必要でした
今回も同じように計測してみます
start:0.004
end  :0.004
del  :0.021
diff1:0.000
diff2:0.017
startがxputsの前 endがxputsの後 そしてdelがUSART1txDelay();の後の時間です
diff1がstart-endで diff2がstart-delです
このようにバッファを使うとxputs関数では最低限の処理時間しか必要としていないことがわかります
そして実際に送出するには17mSecかかっていることがわかります

USARTの送信割り込みは受信割込みほど重要ではありませんが、使い方を知っておくといざ処理時間が少し足りない といった場合に便利だったりする可能性があります
特にXBeeでデータを飛ばす場合など、フロー制御でウエイトが入る場合に重要になります

次はUSARTの受信割り込みを実装したいところですが、実のところ送信割り込みよりはるかに複雑な動作になってしまうので、やりたくないというのがほんとうのところです
しかしまぁやらないわけにも行かないので、近いうちにUSARTの受信割り込み(+行読み取り)も書こうと思います

2014年3月5日水曜日

STM32F1でタイマ割り込み

今回はタイマ割り込みについて
STM32F103VEではタイマは合計で6本あり、TIM1,TIM2,TIM3,TIM4,TIM5,TIM8という構成です
TIM6とTIM7を飛んでいる理由についてですが
TIM1とTIM8は「高性能タイマ」とされており、他のタイマとは区別されています
対してそれ以外のタイマ(TIMx)は「汎用タイマ」とされています
そしてSTM32F103VEは2x高性能タイマ 4x汎用タイマ という構成なので6と7を飛ばしているわけです

今回は一定期間での割り込みしか使わないので汎用タイマ(TIM2)を使用します

まずはいつもどおりクロックを供給します
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,   ENABLE);
PWM機能等は使用しないため、タイマモジュールにクロックを供給するだけです

次にタイマの初期化です
    TIM_InitStructure.TIM_Period        = 720 - 1;
    TIM_InitStructure.TIM_Prescaler     = 100 - 1;
    TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_InitStructure.TIM_CounterMode   = TIM_CounterMode_Down;

    TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
タイマの割り込み周期は
周波数(Hz) = CPUの動作周波数(Hz)/TIM_Prescaler/TIM_Period
となりますす
今回は72MHzのマイコンなので、72000000/100/720となり、1000Hzで割り込みが発生します
またカウンターモードダウンの場合はカウンタにPeriodを設定してからデクリメントし、0から変化した瞬間に割り込みが発生します(0になった瞬間ではありません)
そのため、0もカウントすることを考慮に入れ1を減じた値を設定します


USARTの場合はすぐにモジュールを有効にしましたが、今回は割り込み動作があるのでNVIC(ネスト型ベクタ割り込みコントローラ)の設定を行います
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority  = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);
Channelに割り込みを発生する条件を指定します
今回はTIM2の割り込みを設定するのでTIM2_IRQnを指定しました

PreemptionPriorityは割り込みの優先度で、0が最高で15が最低です
またSubPriorityは同じ優先度(PreemptionPriority)の割り込みが同時に発生した場合にどちらを優先するかを設定します
これについてはまた別の機会に説明しようと思います


最後に割り込みの許可とモジュールの有効化を行います
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    TIM_Cmd(TIM2, ENABLE);
念の為にClearITPendingBitでTIM_IT_UpdateをクリアしてからITConfigで割り込みを設定します
その後でTIM_Cmdでタイマモジュールの動作を許可します
これでタイマモジュールの初期化が完了します

TIM_CmdでENABLEを指定しない限り動作は始まらないので、一旦タイマモジュールを初期化して、この時はTIM_Cmdを呼ばずに、他のモジュールやセンサなどをすべて初期化し終わってからTIM_Cmdで割り込みを許可し、その割り込みでセンサを読み込む といったことも可能です


次に割り込みハンドラです
タイマ2の場合は
void TIM2_IRQHandler(void)
という関数を作ることにより、割り込みが発生したらこの関数を呼ぶことができます
また
    if (TIM_GetITStatus(TIM2, TIM_IT_Update))
という条件を設定することにより、タイマ更新割り込みのみを処理しています
(今回はタイマ更新しか有効にしていないのでこの判定は必要ありませんが)

そして
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
で割り込みをクリアしてハンドラを抜けます

なお、多少時間がかかる処理を割り込みハンドラで行う場合は、処理の最後にClearITPendingBitを呼ぶと便利です
これはタイマ割り込みでハンドラ内にいるにもかかわらず次の割り込みでさらにハンドラが呼ばれてハングアップするのを防ぐ意味があります
もっとも、次の割り込みまでに終了できない処理をハンドラ内で行うべきではないのですが…


応用

今回のサンプルを作るにあたって
typedef struct {
    uint32_t Second;
    uint16_t Milli;
} Seconds;
という構造体を作りました
また
volatile Seconds sec;
というグローバル変数を作成しています

そして、割り込みハンドラ内で
        sec.Milli++;
        if (sec.Milli >= 1000) {
            sec.Milli -= 1000;
            sec.Second++;
        }
という処理を行っています

タイマ割り込みの周期は1000Hzで、これは1000回をカウントすると1秒になる ということを意味しています
ということで1000分の1秒は1ミリ秒なので、1回の割り込みでミリ秒をインクリメントし、ミリ秒が1000を超えるとミリ秒をリセットして秒をインクリメント という処理です

そして
        xprintf("now:%d.%03d\n", sec.Second, sec.Milli);
とすれば現在の(割り込みを使ったカウンタの動作が始まってからの)時間を表示することができます

また、ちょっと長いですが
int GetDiff(const Seconds *sec1, const Seconds *sec2, Seconds *sec) {
    sec->Second = sec->Milli = 0;

    if (sec1->Second == sec2->Second) {
        if (sec1->Milli == sec2->Milli) {
            return(0);
        } else if (sec1->Milli > sec2->Milli) {
            sec->Milli = sec1->Milli - sec2->Milli;
            return(1);
        } else {
            sec->Milli = sec2->Milli - sec1->Milli;
            return(-1);
        }
    } else if (sec1->Second > sec2->Second) {
        sec->Second = sec1->Second - sec2->Second;
        if (sec1->Milli > sec2->Milli) {
            sec->Milli = sec1->Milli - sec2->Milli;
        } else {
            sec->Milli = sec2->Milli - sec1->Milli;
            sec->Second--;
            sec->Milli = 1000 - sec->Milli;
        }
        return(1);
    } else {
        sec->Second = sec2->Second - sec1->Second;
        if (sec1->Milli > sec2->Milli) {
            sec->Milli = sec1->Milli - sec2->Milli;
            sec->Second--;
            sec->Milli = 1000 - sec->Milli;
        } else {
            sec->Milli = sec2->Milli - sec1->Milli;
        }
        return(-1);
    }
}
という関数を作っておけば
第一引数と第二引数でポインタとして渡したSeconds構造体の時間差を第三引数でポインタとして渡したSeconds構造体に設定し、戻り値でどちらが大きいかを返すという処理になります
第一引数>第二引数の場合は1が 第一引数<第二引数の場合は-1が戻り値として帰ります

これを利用し
        Seconds start = sec;
        /* 時間のかかる処理 */
        Seconds end = sec;

        Seconds diff;
        GetDiff(&start, &end, &diff);

        xprintf("start:%d.%03d\n", start.Second, start.Milli);
        xprintf("end  :%d.%03d\n", end.Second, end.Milli);
        xprintf("diff :%d.%03d\n", diff.Second, diff.Milli);
とすれば処理の実行時間を計測することができます

例えば
        Seconds start = sec;
        xputs(
            "1234567890" // 10
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890" // 50
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890" // 100
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890" // 150
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890" // 200
        );
        Seconds end = sec;
        xputc('\n');
とすればUSARTで200文字を出力する時間を計測することができます
結果は
start:0.004
end  :0.021
diff :0.017
となり、17ミリ秒必要だったということがわかります
これは115200bpsで10bit(1バイト+スタートビット+ストップビット)を200個送るための時間(17.36ミリ秒)と正確に一致しています

前回のUSARTで「次回は送信割り込みを使おうと思ったけどタイマ割り込みにします」と書いた理由がこれです
まずはプログラムループで送信するのにどれくらい時間がかかるかを把握しておかないと、割り込みで処理した場合に「たぶん早くなった」としか言えなくなるためです


ということで次回はUSARTの送信割り込みについて書こうと思います

STM32F1でUSART

以前にもSTM32F1でUSARTを使う方法を書きましたが

今回はUSART1で説明します
また、割り込みを使った送信/受信は話がややこしいのでまた今度ということで


まずクロックの供給
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,  ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
USART1モジュールにクロックを供給します
また、TXとRXがあるGPIOA9とGPIOA10を使うためにGPIOAにクロックを供給します

GPIOの初期化
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIOは入力と出力で2回に分けて初期化する必要があります
受信ポートはFLOATINGでもいいですが、Openの場合に困るのでPullUpにしておきました

USART1の初期化
    USART_StructInit(&USART_InitStructure);
    USART_InitStructure.USART_BaudRate = 115200;
    USART_Init(USART1, &USART_InitStructure);

    USART_Cmd(USART1, ENABLE);
まず構造体をStructInit関数で初期化します
この関数内では9600bps データ8bit ストップビット1 パリティなし ハードウェアフロー制御なし 送信と受信の両方が有効 という設定になります
今回は115.2kbpsで使いたいので、その設定を行っています
9600bpsでいいなら何も変更しなくてもいいです

そしてUSART_Init関数で初期化してから、USART_Cmd関数でモジュールをアクティブにします

この時点でUSARTモジュールを使えるようになっています

次に送信と受信ですが
送信の場合は送信バッファが埋まっていないかを確認してからデータをセットします
受信の場合はデータが有るかどうかを確認してから読み込みます
関数例を以下に示します

void USART1_putch(char ch) {
    while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE));
    USART_SendData(USART1, ch);
}

void USART1_puts(const char *str)
{ while (*str) { USART1_putch(*str++); } }

int USART1_getch(void) {
    if (!USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
    { return(-1); }

    return(USART_ReceiveData(USART1));
}
まずputchですが、これは文字出力関数です
次にputsですが、これはputchのラッパー関数になっていて、文字列を与えると1文字ずつ送信してくれます

最後にgetchですが、この関数は少し特殊な実装で、データを受信していない場合はループで待機ではなく、-1を返しています
これによりソフトウェアのLチカ等を停止せずにデータの受信を検出することができます


上記関数群の使い方ですが
文字列出力は
    USART1_puts("\r\nHello World!! " __DATE__ " " __TIME__ "\r\n");
のように使うことができます
__DATE__はそのファイルがコンパイルされた日付を
__TIME__はそのファイルがコンパイルされた時間を文字列として持っています
また文字列は連結することが可能なので、このように記述すると"Hello World!! Mar  5 2014 03:10:33"のように1つの文字列として出力することが可能です

次に入力ですが
int i;という変数を用意しておき
        i = USART1_getch();
        if (i > -1) { USART1_putch(i); }
とすると入力されたキーのエコーバックができます
ただしこれだけでは行入力や、入力された文字列の解析はできません

1行の読み取りや、文字列の解析を行いたい場合は入力バッファが必要になります
これはかなり面倒なので、今回は説明しません


printfのようなフォーマット出力を行いたい場合はxprintf関数が便利です
xprintf関数を使う前に
    xdev_out(USART1_putch);
を呼べばUSART1を標準出力として使用できます

使う場合は
    xprintf("\nHello World!! %s %s : %s %d\n",
        __DATE__, __TIME__, __FILE__, __LINE__);
のように書けば上記のコンパイル日時とともにファイル名と行番号が表示されます


/* *** */
次のSTM32F1関連のエントリはUSARTの送信割り込みにしようかと思いましたが、先にタイマ割り込みをやっちゃおうと思います
タイマ割り込みがあったほうがUSARTの送信割り込みの有り難みがわかりやすいので

2014年3月4日火曜日

FizzBuzz

前回のエントリで文字列を表示するために使った方法の応用編です

      1 #include <stdio.h>
      2
      3 int main(void) {
      4     int end = 36, i;
      5
      6     for (i = 1; i <= end; i++)
      7     printf("%d\n\0  Fizz\n\0Buzz\n\0FizzBuzz\n" + ((!(i % 3) + !(i % 5) * 2)) * 6, i);
      8
      9     return(0);
     10 }

そこそこトリッキーなコードですが、いちおうFizzBuzzとして正常に動作します

C言語の文字列がわかる人ならおそらく説明は必要ないでしょう
また若干ポインタの理解も要求されます

ただ全体的に見てそんなに難しい使い方ではないでしょう

年月日から曜日を計算

西暦と月日から曜日を計算する方法です

      1 #include <stdio.h>
      2
      3 int main(void) {
      4     const char str[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
      5     int y, m, d, Y, C, h;
      6
      7     y = 2014;
      8     m = 3;
      9     d = 4;
     10
     11     if (m == 1) { m = 13; y--; }
     12     if (m == 2) { m = 14; y--; }
     13
     14     C = y / 100;
     15     Y = y % 100;
     16
     17     h = (d + ((26 * (m + 1)) / 10) + Y + (Y / 4) + 5 * C + (C / 4)) % 7;
     18
     19     printf("%d\n", h);
     20     printf("%s\n", str + (((h + 6) % 7) * 4));
     21
     22     return(0);
     23 }

計算をしているのは7行目から17行目までです

strの文字列は曜日を表示するためのもので、ifやswitchを書くのが面倒なときに使う方法です

まずymdに年月日を代入します

そして1月と2月の場合は年をデクリメントしてから12を加算します
今回は1月と2月の判断をifで1個1個処理したので、加算ではなく代入で行いました

次に西暦を上2ケタと下2ケタに分割します
あとは計算式に代入するだけです

実はこの計算式 ページによって式が違っていたりします
結果が同じならどれを使ってもいいのですけども

最後にprintfで表示します
出力される値が土曜日を0として日曜が1 金曜が6 という感じなので少し扱いづらいので、hに6を加算してから7のあまりを取る という方法で日曜を0に移動しています
この曜日を移動するコードは17行目に追加するとスマートに実装できます


このコードを音声認識で起動してGoogle Glassに表示するアプリをかけばケンジ君ごっこができます
ちなみにこれはモジュラ演算ではなく ツェラーの公式という名前です
モジュラはおそらくModularのことで、これは合同式を意味します
これは17行目最後に出てくる%記号の演算処理のことで、あまりを計算しています


上の画像はWikipediaから拾ってきた式ですが
2つ目の式と3つ目の式のmodがModularの意味です
例えばyが2014だった場合 Y = y mod 100 つまりyを100で割ったあまりで、14が入ることになります

この計算式を何に使うのかは正直微妙なところですが、組み込みシステムなどで1秒に1回インクリメントするカウンタを前回のエントリで日付に直してからこの式で曜日を計算して液晶に表示  とかできるかもしれません(ちょっと苦しい)

秒から時分秒への変換 及び年月日時分秒への変換

秒から時分秒や年月日時分秒への変換について
後者はいわゆるUNIX時間を実際の年月日に変換する方法について

時分秒への変換は非常に楽です
void Sec2Time(uint32_t Second, Time_t *time) {
    time->Hour = Second / 3600; Second %= 3600;
    time->Min  = Second / 60;
    time->Sec  = Second % 60;
}
関数名とブロックを除けば3行になります
非常に簡単な処理なので、説明の必要も無いでしょう


次に年月日への変換ですが、これは簡単に実装することができません
数学的にちゃんと考えれば可能かもしれませんが 僕は思いつきませんでした。。(考えるのをやめたとも言う)

なぜ計算で簡単にできないかと言うと、年をまたぐ時間処理はうるう年を考慮に入れる必要があるからです
うるう年ではない場合の1年は31536000秒ですが、うるう年の場合は31622400秒となり、その頻度も「4年に1度」など簡単な処理では対応できないからです

Cでうるう年を判定するためのコードを示します(Wikipediaより)
#define IS_LEAP(year) ((!((year) % 4) && ((year) % 100)) || !((year) % 400))
うるう年の場合は真 うるう年でない場合は偽として展開されます

また
#define TIMEZONE_OFFSET (32400)
という定義も追加しておきます
これはタイムゾーンの時間で、経度が東の場合は正の値 西の場合は負の値になります
今回は日本で使うことを想定し9*60*60の値を設定しました
これを定義ではなくグローバル変数で扱えば動作中に任意のタイムゾーンを設定することもできます
タイムゾーンの設定を0に もしくは処理そのものを削除した場合はUTCとして動作します


関数は
void Sec2DateTime(uint32_t Second, DateTime_t *datetime)
のような感じになっています
第一引数に秒数を与え、第二引数に変換後の値を入れるための構造体のポインタを設定します
この構造体はYear,Mon,Day,Hour,Min,Secの6個のメンバがあります
すべての型は符号なし8bitです
ただし関数の引数を32bitではなく、64bit等で実装する場合はYearを16bitや32bitとして実装する必要があります


さて、実際のコードになるわけですが、まずはローカル変数を1つ準備しておきます
uint8_t IsDec = 0;
これはうるう年の処理に使用する変数です 1bitで十分ですが、最小単位が8bitなのでこの型を使用しました

最初の処理として
    datetime->Year = 0;
    Second += TIMEZONE_OFFSET;
を実行します
これは年の初期化と、タイムゾーンの設定です
年以外の月日時分秒はその都度値を代入しているため、初期化する必要はありません
またタイムゾーンの処理はこの箇所でのみ行っています

次に年の計算ですが
これは上記の通り面倒なのでwhileのループで行っています
    while (1) {
        if (!IS_LEAP(datetime->Year + 1970)) {
            if (Second < 31536000) { break; }
            Second -= 31536000;
        } else {
            if (Second < 31622400) { break; }
            Second -= 31622400;
        }  
        datetime->Year++;
    }
まずうるう年か否かを判定し、その結果にそって秒を判定します
その年1年分にみたない場合はbreakで処理を抜け、十分に時間があるなら1年分の時間を引いてから年をインクリメントします
年は代入ではなくインクリメントで行うため、最初に初期化する必要があるわけです

そして
    uint16_t Days = Second / 86400;
    Second %= 86400;
のコードで秒を日に変換します
秒は1日にみたない時間にします

その後で
    if (Days > (31 + 28) && IS_LEAP(datetime->Year + 1970)) { Days--; IsDec = 1; }
の処理を通します
これによりうるう年でかつ2月29日以降の場合は1日減算されます

次に月の変換です
         if (Days >= 334) { datetime->Mon = 12; Days -= 334; }
    else if (Days >= 304) { datetime->Mon = 11; Days -= 304; }
    else if (Days >= 273) { datetime->Mon = 10; Days -= 273; }
    else if (Days >= 243) { datetime->Mon = 9;  Days -= 243; }
    else if (Days >= 212) { datetime->Mon = 8;  Days -= 212; }
    else if (Days >= 181) { datetime->Mon = 7;  Days -= 181; }
    else if (Days >= 151) { datetime->Mon = 6;  Days -= 151; }
    else if (Days >= 120) { datetime->Mon = 5;  Days -= 120; }
    else if (Days >=  90) { datetime->Mon = 4;  Days -=  90; }
    else if (Days >=  59) { datetime->Mon = 3;  Days -=  59; }
    else if (Days >=  31) { datetime->Mon = 2;  Days -=  31; } 
    else                  { datetime->Mon = 1;               }
月はこのようにif分岐で行います
1月1日から11月30日までの日数以上の時間があるなら12月として
1月1日から10月31日までの日数以上の時間があるなら11月として のように判定します
先ほどうるう年の場合は1日減算したのはこの判定をするためです
そして日は適切に減算しておきます

その後
    datetime->Day  = Days + IsDec + 1;
で日を代入します
うるう年の場合はIsDecが1 それ以外は0なので うるう年でかつ2月29日以降の場合は1が加算されます
また日は0からではなく1からなので、常に1を加算します

残りは最初の関数と同じことをするだけなので簡単です
    datetime->Hour = Second / 3600; Second %= 3600;
    datetime->Min  = Second / 60;
    datetime->Sec  = Second % 60;

これですべての計算は終わりです
なお実際に使用する場合には年に1970を加算して表示してやります
構造体のYearを16bitにする場合はYearの初期化を0ではなく1970で行い、IS_LEAPで1970を加算せずに行うのもいいでしょう

他にも、このコードはまだ最適化の余地が残っているので、そのあたりをいじってみるのもいいと思います

ついでに書くと、この関数は入力が符号なしということからも分かる通り、1970年1月1日より前の時間については計算していません


なお、このコードでいくつかの時間を変換し、正常に値が出力されることを確認していますが、確実に動作するとは限らないことをご了承ください


最後に、細切れではない関数を書いておきます
void Sec2DateTime(uint32_t Second, DateTime_t *datetime) {
    uint8_t IsDec = 0;

    datetime->Year = 0;
    Second += TIMEZONE_OFFSET;

    while (1) {
        if (!IS_LEAP(datetime->Year + 1970)) {
            if (Second < 31536000) { break; }
            Second -= 31536000;
        } else {
            if (Second < 31622400) { break; }
            Second -= 31622400;
        }   
        datetime->Year++;
    }   

    uint16_t Days = Second / 86400;
    Second %= 86400;

    if (Days > (31 + 28) && IS_LEAP(datetime->Year + 1970)) { Days--; IsDec = 1; }

         if (Days >= 334) { datetime->Mon = 12; Days -= 334; }
    else if (Days >= 304) { datetime->Mon = 11; Days -= 304; }
    else if (Days >= 273) { datetime->Mon = 10; Days -= 273; }
    else if (Days >= 243) { datetime->Mon = 9;  Days -= 243; }
    else if (Days >= 212) { datetime->Mon = 8;  Days -= 212; }
    else if (Days >= 181) { datetime->Mon = 7;  Days -= 181; }
    else if (Days >= 151) { datetime->Mon = 6;  Days -= 151; }
    else if (Days >= 120) { datetime->Mon = 5;  Days -= 120; }
    else if (Days >=  90) { datetime->Mon = 4;  Days -=  90; }
    else if (Days >=  59) { datetime->Mon = 3;  Days -=  59; }
    else if (Days >=  31) { datetime->Mon = 2;  Days -=  31; } 
    else                  { datetime->Mon = 1;               }   

    datetime->Day  = Days + IsDec + 1;
    datetime->Hour = Second / 3600; Second %= 3600;
    datetime->Min  = Second / 60;
    datetime->Sec  = Second % 60;
}



2014年3月1日土曜日

CAN経由のサーボコントローラ


CAN経由でサーボを制御できるコントローラを作ってみました

STBeeMiniの下にDIPのMCP2551が乗っています
サーボ用の電源は外部から供給し、マイコンの電源はCANに使われる配線のもう1ペアで供給します
そのためケーブルはツイストペアが2ペア入っているケーブルで、ものすごく硬くて太いです
このケーブルは100m売りで、あと99mくらいあります ジャマです…

ビットレートは多分750kbpsくらいです
STM32F1のペリフェラルはあまり自由にビットレートを設定できないのが不便ですね

データフォーマットとして
拡張CAN(アドレス29bit)
1バイト目がコマンド(サーボ制御は2)
2バイト目が対象のサーボ(現状1-4の範囲)
3バイト目がパルス幅(マイクロ秒)のMSB
4バイト目がパルス幅(マイクロ秒)のLSB
という感じになっています

CANはこういう「何かに命令を出す」というデータが苦手で、全体に命令を出す もしくは全体にお知らせする というのが適しています
今回はコントローラ1個1個に違うメッセージアドレスを設定するという方法で対応していますが、あまりいい方法では無いですね


サーボ制御はTIM2のPWMを使っています
これはGPIOA0-3に出力ポートがあるので使いやすいです


何に使うかとかは特に考えていなくて、信頼性の要求される用途 例えばモデルロケットの動翼とかに使えそうですが そもそもCANコントローラ経由で制御するとなると全長3mクラスとかにならない限りメリットが無いので、あまり現実的な用途ではないですね
家の中で電気のスイッチを押したりするのが関の山かなぁ と思います