SNSへはこちら

STM32の書き込みプログラムを自作する

STM32 についてはアセンブリしたりバイナリ手打ちしたりバイナリ手打ちしたりと、ままキ○ガイじみたアクティビティを行ってきました。あ、そういえば Nim やら Rust やらでプログラムを書くなんてやりましたねぇ...

ということで、ついにせっかく手書きしたバイナリの書き込みで、既存の書き込みプログラムを使うなんて何事だと思うようになってしまいました。せっかくアセンブラも使わず自分で苦労して書いたバイナリを、最後の最後に他所で作ったプログラムに投げるのか!といった感じです。
最近モチベが上がってきたのでその書き込みプログラムを自作することとしました

完成プログラム

以下に GitHub のリポジトリを示します。是非クローンして使ってみてください。今の所、書き込み・シリアルモニタ(受信のみ、送信は不可)・フラッシュ読み出し の対応です。言語は最近良く書く C++ を使いました。

Hand-made USART programmer for STM32. Contribute to shima-529/stm32prog development by creating an account on GitHub.

使ったライブラリ等

今回はまたしても Mac での開発です。使ったライブラリは termios.h で、POSIX に準拠した OS (Windows以外) なら使えると思います。Windows の方も、Cygwin や MSYS 等を入れれば良いのではないでしょうか。よくわかりませんが。なお実際のファイルオープン等々は UNIX のシステムコールを使っています。

恐らくこのライブラリを使って一番いい感じに動くのは Linux だと思います。Mac の場合は /dev/tty.* では動かず、/dev/cu.* では動作を確認しています。ですので間違えずに cu のデバイスファイルを使ってください。

では何を参考にして書き込みプログラムを作ったかといいますと、それはもちろん STM32 の USART Bootloader のアプリケーションノートです。ここに全てが書いてあります。なお見落としがちな条件をここで書いておきましょう。

  • スタートビット 1bit、ストップビット 1bit
  • 偶数パリティあり
  • 最大 115200 bps まで

これさえ守ればソフト作成は簡単...じゃないですねすみません。以下で termios を使う上でのコツ等々を述べさせていただきます。

シリアルの初期化

結局は UNIX のシステムコールでファイルと同等のオープン・クローズを行うことになります。それでは如何にしてそのファイルオープン・クローズについて仕様を決定していくかということになりますね。すなわち baud rate やパリティ等の設定になります。簡単には以下となります。

open(portPath, O_RDWR | O_NOCTTY | O_NDELAY); // portPath(char *)に指定されたポートを開く
// O_NDELAYはファイル読み込みの際の遅延を無視するためのフラグ

if( fd < 0 ) { // fd == -1 はファイルオープン失敗
    throw "Failed to open port";
    return false;
}
fcntl(fd, F_SETFL, 0); // open時のフラグを反映。おまじない
tcgetattr(fd, &oldtio); // store the current tty attr
tcgetattr(fd, &newtio); // store the current tty attr
cfmakeraw(&newtio); // rawに設定。制御文字関連の処理を無効に
cfsetispeed(&newtio, baud); // 入力をbaudに指定されたbaud rateに
cfsetospeed(&newtio, baud); // 出力をbaudに指定されたbaud rateに

とこんな感じです。tcgetattr 以下は単純に struct termios 構造体をいじっているだけなんですけど、こうやって指定するといい感じに動くんですね。

待ち時間の指定

このままだとデータをマイコン側から受信しないまま値を取得(したふり)になってしまいます。ですので、待ち時間及び最低待ち文字数を指定してあげましょう。

newtio.c_cc[VTIME] = 5; // wait for 0.5sec
newtio.c_cc[VMIN] = 1; // wait for 1 char

これは(おそらく)1文字待つという処理が優先されます。...あれ? VTIME の処理いらない?

まとめてreadの仕方

地味にハマりポイントです。プログラム領域からのデータ読み出しの機能をつける時にちょっとハマったのですが、一気に read してはいけないというのが教訓になります。

えー、というのは、一度に所望のバイト数リードできるとは限らず、バッファのたまり具合で状況が変わるのです。ですのでリードしてそれが所望のバイト数に行っていなかったらまたリードをかけるという処理が必要になります。僕のプログラムでは以下のように while を使いました。

int SerialPort::readStr(uint8_t *dest, uint32_t size) {
    while( size > 0 ) {
        int ret = read(fd, dest, size);
        if( ret < 0 ) {
            return -1;
        }
        size -= ret;
        dest += ret;
    }
    return 1;
}

参考

なんだかんだ言って stm32flash のリポジトリ は大いに参考にさせてもらいました。特に上の read とかですね。