Mac Catalina で 32bit GCC コンパイル
Mac Catalina で docker を使用して 32bit GCC クロススコンパイルをするための環境構築手順
背景
書籍「30日でできる!OS自作入門」を Mac(OS は Catalina) で構築する際に、 C 言語の 32bit GCC のコンパイル環境構築の時点でつまずいた。
まずつまずきの一つ目はもともと HomeBrew でインストールできた i386 向けの GCC コンパイラ i386-elf-gcc
が 2020/09/14 現在インストールできないということ。
参考にさせていただいた Catalina 向けのOS自作入門の記事ではこのコンパイラをインストールする手順となっていることが多いが、自分がトライした時点では
このバージョンの GCC は HomeBrew ではなくなっており、代わりに x86_64-elf-gcc
になっていた。
そして、この GCC で 32bit コンパイル用のオプションを指定してコンパイルを試みるも Bad CUP みいたなエラーが出てうまくいかなかった。
次に 32bit 用 GCC を自前でビルドしようとトライしましたが、これもビルドするためのライブラリなどがうまく入手できず諦めました。
色々調べてみたんですが、どうも Mac が 32bit 向けのツールやライブラリを非推奨にする流れっぽくそのあたりの影響もあるのかなと思います。
解決方法
あまり無茶なやり方をせずいい方法がないかなと考えました。
その結果 docker 上に ubuntu コンテンナを作成し、 そこに 32bit 向け GCC ビルド環境を構築しコンパイルすることで解決しました。
やりかた
- Mac に docker をインストールする
- この記事では説明を省きます
mac docker
などで検索すればたくさん情報が見つかります
- DockerHub から ubuntu のリポジトリを取得する
- 今回はバージョン 18.04 を取得する
docker pull ubunntu:18.04
docker run -it -d --name ubuntu ubuntu:18.04
# 作成した ubuntu の bash を起動。カレントディレクトリをホームディレクトリ(/root)にする docker exe -it -w /root ubuntu /bin/bash # GCC をインストールする apt update && upgrade apt install gcc
- コンパイルに必要なリンカスクリプトなどを ubuntu に渡す
- 「30日でできる!OS自作入門」用の実行形式を作成するためのリンカスクリプトを ubuntu に渡す
- リンカスクリプトは以下の「30日でできるOS自作入門メモ」にある「OS用リンカスクリプト」を参考にさせていただきました(ありがとうございます)
- 30日でできるOS自作入門メモ
# リンカスクリプト(os.ld) をコンテナ内の /root に渡す docker cp os.ld ubuntu:/root
- 環境構築した ubuntu コンテナから新たにイメージファイルを作成する
- ここまででクロスコンパイルするための環境が整ったため一旦このコンテナからイメージファイルを作成する
- 再度同じ環境が欲しくなった時はこのイメージからコンテナを作成する
# ubuntu コンテナから ubuntu_for_xcomp というイメージファイルを作成する docker commit ubuntu ubuntu_for_xcomp
# ubuntu の /root にコンパイル対象(hoge.c)を渡す docker cp hoge.c ubuntu:/root # コンパイル対象を 32bit 向けにコンパイル。リンカスクリプトは os.ld を指定する docker exec -w /root ubuntu gcc -march=i486 -m32 -nostdlib -fno-pic -T os.ld -o output hoge.c # コンパイル結果である実行形式(output)を取得する docker cp ubuntu:/root/output output
- 完成
- 以上の手順でOS自作入門用の実行形式ができました
- 後は適宜コンテナを停止・削除してください
# コンテナを停止 docker stop ubuntu # コンテナを削除 docker rm ubuntu
Go で Go のコード整形(Beautifire)ツールを作る
Go で Go のコード整形(Beautifire)ツールを作る
前回の記事では私が作成した Go の識別子ケース変換ツール goconvcaseを取り上げました. その際に使用したパッケージや手法などはそれ以外の所謂 Beautifire や Formatter のような Go 向けの コード整形ツールを作成する際に使用できるイディオムかと思いました. 本記事では Go で Go のコード整形ツールを作成した際の手順をまとめます.
整形ツール作成の流れ
コードを整形するツールの処理フローは以下の通りです.
- ソースをパースしデータ(AST)化する
- パースしたデータを更新する
- 更新したデータをソースに戻す
実際に作る
作成手順の詳細を goconvcase のような識別子を変換するツールの作成を元に説明します.
仕様は以下とします.
- ソース中のスネークケースの識別子をキャメルケースに変換する
- 識別子とは変数名や関数名など
初めに完成したソースお見せし, 各手順を後に説明します.
package main import ( "bytes" "go/ast" "go/format" "go/parser" "go/token" "io/ioutil" "log" ) func main() { src, err := ioutil.ReadFile("src.go") if err != nil { log.Fatal(err) } // src.go をパースして node(AST) を得る fset := token.NewFileSet() node, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { log.Fatal(err) } // node(AST) を走査しスネークケースの識別子をキャメルケースに変換する ast.Inspect(node, func(n ast.Node) bool { switch n.(type) { case *ast.Ident: ident := n.(*ast.Ident) if isSnakeCase(ident.Name) { ident.Name = convertSnakeToCamel(ident.Name) } } return true }) // buf に更新したソース書いて標準出力 var buf bytes.Buffer err = format.Node(&buf, fset, node) if err != nil { log.Fatal(err) } fmt.Println(buf) }
ソースをパースする
ソースを更新するためにはソースをデータ化した方が都合かいいためデータ化します. ソースをパースしてデータ化したものを AST(Abstract Syntax Tree) といい, 日本語では抽象構文木と言います.
AST はプログラムを構成する様々な要素から成るツリー状のデータです. 簡単に説明すれば AST は文をいくつか持ち文は式や文から成り, 式は識別子やリテラルや演算式・・・ という感じでツリーを形成します.
Go ソースをパースし AST を得るには go/parser
パッケージが便利です.
以下のように parser.ParseFile()
にパースしたいソースのテキストを渡して node(AST) を取得します.
package main import ( "bytes" "go/ast" "go/format" "go/parser" "go/token" "io/ioutil" "log" ) func main() { src, err := ioutil.ReadFile("src.go") if err != nil { log.Fatal(err) } // src.go をパースして node(AST) を取得する fset := token.NewFileSet() node, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { return "", err } }
AST を更新する
取得した AST を更新します. やりたいこととしては AST を走査しスネークケースの識別子があればキャメルケースの識別子に変換することです.
AST を走査するための API は go/ast
パッケージにあります.
今回は ast.Inspec()
を使用して走査&更新を行います.
// node(AST) を走査しスネークケースの識別子をキャメルケースに変換する ast.Inspect(node, func(n ast.Node) bool { switch n.(type) { case *ast.Ident: ident := n.(*ast.Ident) if isSnakeCase(ident.Name) { ident.Name = convertSnakeToCamel(ident.Name) } } return true })
スネークケースか判定する関数 isSnakeCase()
と スネークケースからキャメルケースに変換する関数 convertSnakeToCamel()
の実装の詳細は省きます.
ast.Inspect()
の詳細ですが, 第一引数は処理したい node をとり, その node のトップから深さ優先で走査します.
第二引数で走査中の各ノードに対して行いたい処理が書かれた関数を引数に取ります.
関数が true を返す限り node の次の子へと nil になるまで走査を続けます.
AST はプログラムを表現する様々な型の要素からなりますが, すべて interface ast.Node
を実装しているため,
全ての要素を走査することができ, 全ての要素に対して引数の関数を適用できます.
上記のコードでは node が 識別子(*ast.Ident
)の場合でスネークケースであればキャメルケースに変換しています.
これで, AST のトップからスネークケースの識別子は全てキャメルケースに変換されました.
ソースを出力する
AST の更新が完了したらソースに戻します. これも go/format
パッケージを使えば一発です.
// buf に更新したソース書いて標準出力 var buf bytes.Buffer err = format.Node(&buf, fset, node) if err != nil { log.Fatal(err) } fmt.Println(buf)
format.Node()
に io.Writer
(&buf) と パースの際に作成した FileSet
(fset) と更新した node
を渡せば
AST をソースにしたものを io.Writer
に書き込みます.
おわり
以上が Go のソース内にあるスネークケースをキャメルケースに変換するツールのイメージです. 色々端折っているので実際のケース変換ツールは goconvcase をご覧ください.
Go でスネークケースやっちまった人のための識別子変換ツール
Go でスネークケースやっちまった人のための識別子変換ツール
何を作った?
Go ソース内のスネークケースの識別子をキャメルケースに変換するツールを作りました.
GitHub : https://github.com/kita127/goconvcase
なぜ作った?
Go ではキャメルケースが推奨です.(http://go.shibu.jp/effective_go.html) しかし、定数などはさすがにいいだろうと思い以下のようなソースを書くと...
package sample // SNAKE_CASE identifires const ( HOGE_CONST = iota FUGA_CONST PIYO_CONST )
lint が許しません(^^)
$golint .\sample.go sample.go:5:2: don't use ALL_CAPS in Go names; use CamelCase sample.go:6:2: don't use ALL_CAPS in Go names; use CamelCase sample.go:7:2: don't use ALL_CAPS in Go names; use CamelCase
なんとなく Go ではキャメルだと知っていても定数までダメとは思わずついつい書いてしまう人もいるのではないでしょうか.
私は書きました.
こまめに lint をかければ気づくのでしょうがズボラなのでコーディングも後半になってから lint をかけて, そのころには大量のスネークで作られた定数が...という人もいるのではないでしょうか.
私はそうでした.
もしかすると正規表現力が高ければ一括で置換できるかもしれませんが, 正規表現に精通していない人もいるでしょう.
私にはできませんでした.
そんな人のために goconvcase を作りました.
インストール方法
利用対象の方は基本的に Go をお使いと思いますので go get
でインストールお願いします.
go get github.com/kita127/goconvcase/cmd/goconvcase
使用方法
先ほどの例に出てきた以下のファイル(sample.go)に対して,
package sample // SNAKE_CASE identifires const ( HOGE_CONST = iota FUGA_CONST PIYO_CONST )
以下のコマンド実行でスネークケースの識別子がキャメルケースに変換されたソースが標準出力されます.
$goconvcase --from us --to uc .\sample.go package sample // SNAKE_CASE identifires const ( HogeConst = iota FugaConst PiyoConst )
解説すると --from
で変換対象となる識別子のケースを指定します. us
は Upper Snake Case の略で大文字のスネークケースです.
そして --to
で変換後のケースを指定します. uc
は Upper Camel Case の略で大文字のキャメルケースです.
最後に変換対象のファイルを指定します.
変換対象は識別子だけです.
コメントの // SNAKE_CASE identifires
はスネークケースのままです.
ファイルを上書きする場合は, gofmt
や goimports
と同じように -w
を指定します.
$goconvcase -w --from us --to uc .\sample.go
指定可能な全てのケースを知りたい場合は --list
を指定してください.
$goconvcase --list us : UPPER_SNAKE_CASE like this. uc : UpperCamelCase like this. ls : lower_snake_case like this. lc : lowerCamelCase like this.
現在以上の4種類のケースが相互に変換可能です. (キャメルからスネークに変換する必要性はないと思いますが...)
終わり
基本的には「命名ルールをしっかり把握する」「lint をこまめにかける」が大事かと思いますが, もし私と同じようにやらかしてしまった方いればよければ使ってみてください.
mbed + LCD(AQM0802A) でオリジナルキャラクタ(自作文字)の表示
mbed + LCD(AQM0802A) でオリジナルキャラクタ(自作文字)の表示
mbed + AQM0802A ピッチ変換モジュール(完成品) で自作文字(オリジナルキャラクタ)の表示のやり方メモ.
- 秋月電子通商
- I2C接続小型LCDモジュール(8×2行)ピッチ変換モジュール(完成品)
- http://akizukidenshi.com/catalog/g/gM-09109/
通常のキャラクタ表示はライブラリ検索 AQM0802A
で調べれば先人達が作成した素敵なライブラリが公開されていますが,
オリジナルのキャラクタを作成して表示しようとするとそのための便利ライブラリはなく, データシートを読んで自前実装が必要でした.
結構ちゃんとデータシートを読まなければならず, まあ, 当然英語でして...うぐぅ, 頑張って読んで何とかオリジナルのキャラクタの
表示までできたので自分用のメモとして本ブログをしたためます.
また、SC1602 シリーズの LCD が大体同じような感じでオリジナルキャラクタを表示できるっぽいので AQM0802A に限らず 役立つノウハウな気がします.
参考
参考にしたサイトおよびデータシートです.
- AQM0802A のデータシート
- たぶん秋月さんが ST7032i の重要な箇所だけピックアップしてまとめたと思われるもの
- http://akizukidenshi.com/download/ds/xiamen/AQM0802.pdf
- コントロールIC ST7032i のデータシート
- ディスプレイを制御している IC
- マイコンは I2C により ST7032i にデータやコマンドを送信し, それを受け ST7032i がディスプレイを制御する
- http://akizukidenshi.com/download/ds/sitronix/st7032.pdf
環境
I2C について
mbed からは I2C による通信で AQM0802A を制御します. 正確には AQM0802A に搭載されている ST7032i という IC が I2C で受信したデータやコマンドを元にディスプレイを制御します.
mbed から AQM0802A にデータやコマンドを送信する際のフォーマットにはルールがあり大まかに以下です.
- 初めに Slave アドレスを 0x7C を送信する
- Slave アドレスは固定
- サイズは 1byte
- 続いて control byte を送信する
- 次に送信する data byte がコマンドの指令かデータの書き込みかを決める
- コマンドには Clear Display や Return Home など色々
- control byte には Co, RS のビットがあるが詳細は後述
- Co, RS 以外のビットは 0 固定
- サイズは 1byte
- 次に送信する data byte がコマンドの指令かデータの書き込みかを決める
- 続いて data byte を送信する
- コマンドの場合は実行するコマンドを決める
- データの場合はデータを送信する
- サイズは 1byte
パラメータの説明
以上のフォーマットに従って AQM0802A を I2C で制御するわけなのですが, いろんなパラメータが登場し それらが何なのかある程度把握してないと操作の仕方がそもそもわからないです. とりあえずオリジナルのキャラクタを表示するのに必要な最低限を解説します.
Co
連続でデータを送信する際に最終 byte を決めるためのビット. control byte の 7bit 目に割り当てられている. control byte と data byte を1セットとし数セット連続で送信可能ですが, その場合受け手が最終セットの判断がつかないため, 最終セットの Co のみ 0 を設定することにより受け手に最後の1セットを伝える.
control byte と data byte を1セットしか送信しない場合はそれがすなわち最終セットとなるため Co=0 となります.
RS
data byte を IR(Instruction Register) に書き込むか DR(Data Register) に書き込むか決めるビット. control byte の 6bit 目に割り当てられている. 要するに「ディスプレイを消去しろ」や「カーソルを先頭に戻せ」など何らかのコマンドを送信したい場合は RS=L とし データを書き込む場合は RS=H とします. コマンドを送信したいかデータを書きたいかを決めるビットです.
Address Counter(AC)
後述の DDRAM/CGRAM/ICON RAM のアドレスを記憶する. DDRAM/CGRAM/ICON RAM を書き込んだ後、AC は自動的に 1 加算される.
Display Data RAM(DDRAM)
表示するデータを 8bitキャラコードとして保持する RAM. DDRAMアドレスカウンターは AC に16進数として設定される.
Character Generator RAM(CGRAM)
オリジナルキャラクタ作成時のキャラクタパターンを指定するための RAM. この RAM にパターンを書き込むことによりオリジナルキャラクタを作成する.
起動時の設定
起動時に所定のおまじないをする必要がある. 内容は AQM0802A のデータシート「初期設定例」に記載の通り 決まったコマンドを決まった順序で送信する. コマンドとコマンドの間は一定時間の wait を設ける必要がある.
初期設定についてはデータシートに記載の通りなのと, 先人達のソースを見ればわかるため本記事では取り上げません.
通常のキャラクタ表示
データシートの「CHARACTER PATTERNS」に載っている, 既に定義済み文字の表示手順説明です. 通常の文字であればすでに公開されているライブラリが使いやすいインタフェースを提供してくれているので自前実装する必要はないんですが, AQM0802A への I2C を使った基本的なコマンド送信方法の説明も兼ねて書こうかと思います.
実際に2行目左から4番目の位置に 'K' を表示する手順で説明します. 以下のイメージです.
□□□□□□□□
□□□K□□□□
Set DDRAM address で書き込み位置を指定
「Set DDRAM address」が「コマンド」にあたります. 各コマンドの送信方法は AQM0802A データシート記載の「DISPLAY INSTRUCTION TABLE」 一覧に載っています. Set DDRAM address で指定するアドレスとは AQM0802A ディスプレイの 8x2 のどの位置に文字を表示するかになります. 表示位置と DDRAM アドレスの対応は AQM0802A データシートの「液晶表示 DDRAMアドレス」から判断します.
表示位置は2行目の4番目なので「液晶表示 DDRAMアドレス」一覧から 0x43 が該当します.
Write data to RAM で書き込む文字を指定
書き込み位置が決まったら次に「Write data to RAM」により表示する文字のコードを送信します. データ送信の場合は control byte の RS に 1 を設定します. 文字コードは「CHARACTER PATTERNS」一覧に記載の値ですが, 通常の ASCII でも表示可能です.
サンプルコード
以上を踏まえると mbed ソースは以下のような感じです. Slave アドレス以降は control byte と data byte の送信を交互に繰り返す感じですね.
int main() { // I2C の初期化 i2c = new I2C(p26, p25); i2c->frequency(400000); // I2C bus speed // 初期化コマンドシーケンス // 初期化シーケンスについては本記事では取り上げない init_seq(); char buf[4]; // Slave address const unsigned char slave_addr = 0x7C; // control byte // Co=1, RS=0 // RS=0 はコマンドの指定となる buf[0] = 0x80; // data byte // Set DDRAM address 0x80(0b1XXXXXXX) // DDRAM Address 0x43 buf[1] = (char)(0x80 | 0x43); // control byte // Co=0(最終データ), RS=1 // RS=1 はデータの送信 buf[2] = 0x40; // data byte // Write Data to RAM buf[3] = 'K'; // I2C 送信 i2c->write(slave_addr, buf, sizeof(buf)); while(1) { } }
オリジナルのキャラクタ表示
本題のオリジナルキャラクタを作成して表示する手順です. 画像のオリジナルキャラクタ(にこにこハート)を表示します.
表示手順は大まかに以下です.
データシート, 「CHARACTER PATTERNS」の「CGRAM」と書かれた箇所がオリジナルのキャラクタ登録用に 割り当てられたコードです. このいずれかのコードにこれから作成するオリジナルのキャラクタを紐付けます.
どのコードに割り当てるかは ST7032i データシートの 「Table4 Relationship between CGRAM Addresses 〜」 を 見て判断します.
CGRAM address の 5bit - 3bit がどのコード(DDRAM data)に登録するかを決定します. 2bit - 0bit が CGRAM に書き込むドットパターンの行を決めます. つまり コード 0x00 にオリジナルキャラクタを設定する場合は CGRAM address 0x00 - 0x07 に ドットパターンを登録します. CGRAM address の指定コマンドは「DISPLAY INSTRUCTION TABLE」の「Set CGRAM」が該当します. 一度 CGRAM address を決めれば AC は自動的にインクリメントされるため以降は CGRAM address の指定を省けます.
CGRAM へのドットパターンの書き込みが完了したら、そのコードに作成したキャラクタが登録されますので, 通常のキャラクタ表示と同じ要領でそのコードを表示すればよいわけです.
そして実際にコードに落とし込んだのが以下.
char pattern1[8] = {0b00000110, 0b00001001, 0b00010010, 0b00010010, 0b00001000, 0b00000101, 0b00000010, 0b00000001 }; char pattern2[8] = {0b00001100, 0b00010010, 0b00001001, 0b00001001, 0b00000010, 0b00010100, 0b00001000, 0b00010000 }; int main() { // I2C の初期化 i2c = new I2C(p26, p25); i2c->frequency(400000); // I2C bus speed // 初期化コマンドシーケンス init_seq(); char buf[128]; // Slave address const unsigned char slave_addr = 0x7C; int i = 0; // control byte // Co=1, RS=0 buf[i] = 0x80; i++; // data byte // Set CGRAM address // 初回に設定以降、AC は自動的にインクリメントされるため // 続きはドットパターンのデータ設定のみ buf[i] = (0x40 | 0x00); i++; // CGRAM にドットパターンのデータを設定する int j; for(j = 0; j < 8; i += 2, j++) { // control byte // Co=1, RS=1 buf[i] = 0xC0; // data byte // ドットパターンを書き込み buf[i+1] = pattern1[j]; } for(j = 0; j < 8; i += 2, j++) { // control byte // Co=1, RS=1 buf[i] = 0xC0; // data byte // ドットパターンを書き込み buf[i+1] = pattern2[j]; } // control byte // Co=1, RS=0 buf[i] = 0x80; i++; // data byte // Set DDRAM address // 2行目4番目に表示する buf[i] = (0x80 | 0x43); i++; // contorol byte // Co=1, RS=1 buf[i] = 0xC0; i++; // data byte // Write data to RAM // 作成した pattern1 の Character Code buf[i] = 0x00; i++; // contorol byte // Co=0, RS=1 buf[i] = 0x40; i++; // data byte // Write data to RAM // 作成した pattern2 の Character Code buf[i] = 0x01; int size = i + 1; // I2C 送信 i2c->write(slave_addr, buf, size); while(1) { } }
おわり
オリジナルのドット文字を表示できるようになったら無性にゲームボーイ的な何かを作りたくなりますね. そのうち安価なパーツだけを駆使してオレオレゲームボーイをつくるのもありですね.
そして全く触れなかった ICON RAM についてもそのうち掘り下げたい...
Go と mbed で USB Serial 通信をする
Go と mbed で USB Serial 通信をする
前回の記事で mbed(LPC11U35) と TeraTerm を使用して USB Serial 通信を実現できました. やったー
次は適当なテキストの内容をまるっと mbed に USB Serial 送信したいなという気持ちになったんですが, 軽く調べた感じ TeraTerm からではやりかたがわからない, あるいはできない模様.
また, 単純なテキストの送受信をしたいだけに対して TeraTerm は少し大仰な気もするので...
テキストをまるっと送信するのと簡単な対話をするだけの軽量な CLI ツールが欲しい, ということでつくろー, となりました.
仕様
- 標準入力から受け取ったテキストを対象のポートに USB Serial 送信する
- 対象と対話的に USB Serial 通信するモードを備える
- 改行区切りで入力を受け取り対象に送信する
- 対象からのデータを受信して標準出力する
環境
- Windows10
- mbed EA LPC11U35
プログラムを作成
そしてできたのが以下. Go 製です.
一応、release ページに Windows 用のバイナリを置いてあるのでそちらをダウンロード&解凍すれば使用できるはずです.
Go 言語用の USB Serial 通信のパッケージはいくつかありますが今回は以下をチョイス.
もうコード量それほどないのですべて載せます.
package main import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "time" "github.com/jacobsa/go-serial/serial" "gopkg.in/alecthomas/kingpin.v2" ) var ( portFlag = kingpin.Flag("port", "port name (--port=COM3)").Required().String() baudRate = kingpin.Flag("baud-rate", "baud rate (--baud-rate=9600)").Default("9600").Int() readTime = kingpin.Flag("read-time", "read cycle time(ms)").Default("100").Int() interactive = kingpin.Flag("interactive", "interactive mode").Short('i').Bool() ) func main() { kingpin.Parse() // Set up options. options := serial.OpenOptions{ PortName: *portFlag, BaudRate: uint(*baudRate), DataBits: 8, StopBits: 1, MinimumReadSize: 4, } // Open the port. port, err := serial.Open(options) if err != nil { log.Fatalf("serial.Open: %v", err) } // Make sure to close it later. defer port.Close() if *interactive { // interactive mode interactiveMode(port) } else { // one-shot mode text, err := ioutil.ReadAll(os.Stdin) if err != nil { log.Fatal(err) } _, err = port.Write([]byte(text)) if err != nil { log.Fatalf("port.Write: %v", err) } } } func interactiveMode(port io.ReadWriteCloser) { fmt.Fprintln(os.Stdout, "This is Katuobushi interactive mode.") fmt.Fprintln(os.Stdout, "Please enter the sending texts...") go func() { t1 := time.NewTicker(time.Duration(*readTime) * time.Millisecond) defer t1.Stop() for { select { case <-t1.C: //Read buf := make([]byte, 128) n, err := port.Read(buf) if n != 0 { if err != nil { if err != io.EOF { fmt.Fprintln(os.Stdout, "Error reading from serial port: ", err) } } else { buf = buf[:n] //fmt.Println("n =", n) fmt.Fprintf(os.Stdout, "%s", buf) } } } } }() // Write go func() { f := bufio.NewScanner(os.Stdin) for f.Scan() { text := f.Text() text = text + "\n" _, err := port.Write([]byte(text)) if err != nil { log.Fatalf("port.Write: %v", err) } } }() for { // loop } }
ターミナルで実行してそのまま USB Serial 通信が可能です. 使い方の説明の前に mbed 側にプログラムを書き込みましょう.
mbed のプログラム
前回ブログで取り上げたのと同様 USB Serial で受信したデータに "recv: " の文字列を頭に付与して送り返すプログラムです. こちらを mbed に書き込みます.
#include "mbed.h" #include "USBSerial.h" DigitalOut myled(LED1); USBSerial serial; int main() { uint8_t buf[128]; while(1) { myled = 1; // LED is ON wait(0.5); // 200 ms myled = 0; // LED is OFF serial.scanf("%s", buf); serial.printf("recv: %s\n\r", buf); wait(0.5); // 1 sec } }
COM ポートを調べる
mbed を USB で PC と接続します.
使用している COM ポートとボーレートを調べるためコマンドプロンプトで MODE
コマンドを実行して確認します.
今回使用している仮想ポートは COM3、ボーレートは 9600 みたいです.
$ MODE
デバイス状態 COM3: ------------ ボー レート: 9600 パリティ: None データ ビット: 8 ストップ ビット: 1 タイムアウト: ON XON/XOFF: OFF CTS ハンドシェイク: OFF DSR ハンドシェイク: OFF DSR の検知: OFF DTR サーキット: ON RTS サーキット: OFF
通信してみる
全ての準備が整ったのでGo製の簡易 USB Serial 通信ツール Katuobushi と mbed で通信してみます.
対話モード
mbed と PC を USB で接続し, mbed をリセットします. Katuobushi.exe を実行します. まずは TeraTerm のように対話的に通信してみましょう. 以下のオプションを付与して実行します.
$katuobushi.exe --port=COM3 -i
仮想ポートは先ほど調べた際に COM3 だったためそれをコマンドラインオプションで指定.
ボーレートはデフォルトで 9600 なので指定なしで OK です.
そして -i
オプションを指定します.これは対話モードで実行するためのオプションで --interactive
のショートフラグです.
helloEmbed
と入力して recv: helloEmbed
が返ってきました.どうやらうまくいったみたいです.
$go run main.go --port=COM3 -i This is Katuobushi interactive mode. Please enter the sending texts... helloEmbed recv: helloEmbed
ワンショットモード
それでは次に適当なファイルの内容をまるっと送信してみましょう.
適当な入力用のファイルを作成.
$echo hoge > input.txt $echo fuga >> input.txt $echo piyo >> input.text $type input.txt hoge fuga piyo
標準入力から katuobushi.exe に入力して実行します.
$type input.txt | katuobushi.exe --port=COM3
・・・すぐに実行終了しました.
--interactive
オプションを使用しない場合はワンショットモードになり
標準入力の内容を丸っと送信したら実行を終了します.
これだと返信があったのかわかりませんが, 一応、mbed 側で内臓 LED を 3 回点滅しているので
まあ, 受信できているようです.気になる場合は外付けの LCD などで受信したデータを出力させてみても良いかもです.
まとめ
といった感じで mbed と USB Serial 通信を実現したついでに軽量な USB Serial 通信ツールを作成しました. 題材に取り上げている mbed 自体は安価なものなんですが, PC と USB 通信できるようになっただけで色々広がりますね.
mbed(LPC11U35) で USB シリアル通信
mbed(LPC11U35) で USB シリアル通信
mbed LPC11U35 で USB シリアル通信をしたのでメモ
USB CDC(Communications Device Class)と呼ばれる
USB上でデバイス間のデータのやりとりを行うための通信規格を利用して実現している
環境
- Windows10
- mbed EA LPC11U35
- TeraTerm
準備
- TeraTerm のインストール
- Chocolatey を使用している場合は以下でインストール
choco install teraterm
- それ以外は以下のサイトからインストーラをダウンロードしインストールする
- Chocolatey を使用している場合は以下でインストール
- mbed ワークスペースから USBDevice ライブラリをインポートする
- コーディングし mbed に書き込む
- コードは以下を参照
- mbed をリセット
- OS が USB ポートを認識することを確認する
- TeraTerm を起動
- 新しい接続は シリアル を選択
- COMx ポートとかになっているはず
- TeraTerm から文字列を送信できるようにする
- 「設定」タブ
- 端末
- 「ローカルエコー」にチェック
- 「OK」
- TeraTerm から文字列を入力し、mbed からエコーバックされれば成功
コード
端末から文字列を受け取ってエコーバックするプログラム
#include "mbed.h" #include "USBSerial.h" DigitalOut myled(LED1); USBSerial serial; int main() { uint8_t buf[128]; while(1) { myled = 1; // LED is ON wait(0.5); // 200 ms myled = 0; // LED is OFF serial.scanf("%s", buf); serial.printf("recv: %s\n\r", buf); wait(0.5); // 1 sec } }
stdedit : 標準入力をエディタで編集し結果を標準出力するコマンド
stdedit : 標準入力をエディタで編集し結果を標準出力するコマンド
何を作った?
stdedit
というコマンドを作りました.
機能はシンプルで標準入力を好きなエディタで編集して結果を標準出力に出力するだけのコマンドです.
https://github.com/kita127/stdedit
インストール方法
Go 言語をインストールする必要があります.
そのうちバイナリも配布したいと考えていますが今のところは Go でビルドしてもらう必要があります.
Go があれば以下のコマンドでバイナリのインストールも完了します.
go get github.com/kita127/stdedit
使い方
単純です.
bash の場合以下を実行で hoge
を vim で開き編集後, 上書き保存すると結果がクリップボードにコピーされます.
vim 以外のエディタを使用する場合は環境変数 $STDEDIT
に使用するエディタのパスを設定します.
echo hoge | stdedit | pbcopy
応用例
例えば私は「選択した範囲をエディタで開き編集後, クリップボードにコピーされるツール」などを stdedit
を活用して制作しました.
そのように単品で機能するコマンドというよりは他の機能を実現するための道具として使用することを想定しています.