mimikakimemo

自分用メモ。

Rust に付け焼き刃で入門する 2

Rust に付け焼き刃で入門する 1 の続き。

3. 一般的なプログラミングの概念

3.1. 変数と可変性

変数と可変性 - The Rust Programming Language 日本語版

不変のメリットについての説明は、特に Rust に限ったことではない。

不変な変数とは別に、定数もある。型注釈は必須。

const MAX_POINTS: u32 = 100_000;

シャドーイングについては、前回見たとおり。

3.2 データ型

データ型 - The Rust Programming Language 日本語版

Rust は静的型付き言語。型推論してくれるので、型注釈は必要なところでだけで良い。

整数型や論理値型など、プリミティブな型はスカラー型と呼ぶ。整数にはサイズがある。ローレベルな感じ。迷ったら Rust のデフォルトである i32 型を使えば良いとのこと(最速なので)。浮動小数点型の方は f64。いわゆる double。演算子は普通な感じ。

bool 型は truefalse

char 型は Unicode スカラー値の一つ。'a' のようにシングルクォートで囲む。文字列 "a" とは別。

タプルの要素の型は一致しなくてよい。destructring も可能。要素には、x.0 のようにドットでアクセス。

配列 array は同じ型の要素からなり、固定長。他の言語でいうような可変長のリストは、ベクタ vector を使う。配列の要素には x[0] のようにしてアクセス。範囲外へのアクセスは実行時エラー panic になる。

3.3 関数

関数 - The Rust Programming Language 日本語版

ソースコード中でanother_functionmain関数の後に定義していることに注目してください; 勿論、main関数の前に定義することもできます。コンパイラは、関数がどこで定義されているかは気にしません。 どこかで定義されていることのみ気にします。

なるほど。JavaScript だと巻き上げ hoisting が起きるけど、Rust はどうなんだろう。

fn main() {
    println!("Hello, world!");
    another_function();
}

let num = 42;

fn another_function() {
    println!("{}", num);
}

これだとerror: expected item, found keyword `let` というエラーになった。そもそもここには let を置けないので、let と関数定義の順序を気にする必要はない様子。また、

fn main() {
    println!("Hello, world!");
    another_function();

    let num = 42;

    fn another_function() {
        println!("{}", num);
    }
}

can't capture dynamic environment in a fn item というエラーになった。関数定義の順序を気にしないといけないようなコードは、そもそも文法的に許されていないようだ。

関数シグニチャにおいて、各仮引数の型を宣言しなければなりません。

はい。

文とは、なんらかの動作をして値を返さない命令です。 式は結果値に評価されます。

変数宣言や関数定義は文。プロック {} は式。式にセミコロンをつけると文になる。

戻り値に名前を付けはしませんが、 矢印(->)の後に型を書いて確かに宣言します。Rustでは、関数の戻り値は、関数本体ブロックの最後の式の値と同義です。 returnキーワードで関数から早期リターンし、値を指定することもできますが、多くの関数は最後の式を暗黙的に返します。

Rust では、ブロックの最後の式の値を戻り値とみなす。式なのでセミコロンはつけない(つけると文になってしまう)。慣れるまではうっかりセミコロンを消したり、つけたりしてしまいそうだ。ただ、戻り値の型が食い違っていたらコンパイルエラーで気づけるので、そこまで神経質にならなくても良さそう。

戻り値なしの場合は空のタプル -> () で型を表現できるが、この場合に限って、戻り値の型注釈を省略できるっぽい。

3.4. コメント

// でコメントを書ける。

3.5. フロー制御

フロー制御 - The Rust Programming Language 日本語版

if は式なので、値を返す。条件は bool 型である必要がある。アームの {} は省略不可。else, else if も使えるが、各アームが返す型は同じでなければならない。ユニオン型にはなってくれないようだ。

各アームが返す型は同じでなければならないので、値を返す式として使う場合は else が必ず必要。

let a = if cond { 100 } else { 0 }; // OK
let a = if cond { 100 }; // NG

無限ループは loop。条件付きループは while。コレクション(イテレータ?)に対するループは for ... inbreakcontinue もある。このあたりは普通な感じ。

ループは式なんだろうか? break から値は返せるんだろうか? …特にこのページには記述がなかった。

Rust に付け焼き刃で入門する 1

数十分〜数時間で手っ取り早く掴む系

ざくっと見てみたところ、所有権・借用や、ライフタイムの扱いがややこしそうな印象。コンパイルエラーのメッセージが親切なのはとても嬉しい。Hello World を書こうとすると、いきなりマクロ println! が出てきて「!」となる。

The Rust Programming Language

公式のこのドキュメントがまずはおすすめらしいので、以降はこれに沿って勉強してみる。TRPL あるいは the book と呼ばれているとのこと。


1. 事始め

事始め - The Rust Programming Language 日本語版

1.1. インストール

普段は Homebrew を使うことが多いが、今回はとりあえず公式にしたがってインストールする。

$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

デフォルトのままで動いた。

$ rustup --version
rustup 1.23.1 (3df2264a9 2020-11-30)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.50.0 (cb75ad5db 2021-02-10)`

$ rustc --version
rustc 1.50.0 (cb75ad5db 2021-02-10)

$ cargo --version
cargo 1.50.0 (f04e7fab7 2021-02-04)

公式のパッケージマネージャー cargo が一緒にインストールされるのも、楽でうれしい。

1.2. Hello, World!

hello_world/main.rshello world を書く。VS Code にはデフォルトで Rust のシンタックスハイライトが入っているようだ。

rustcコンパイルしたら、実行ファイルのバイナリができた。

$ rustc main.rs
$ file main
main: Mach-O 64-bit executable x86_64
$ ./main
Hello, world!

1.3 Hello, Cargo!

  • $ cargo new プロジェクトの作成。Git リポジトリの作成までやってくれる
  • $ cargo check 検証
  • $ cargo build [--release] ビルド
  • $ cargo run (ビルド+)実行

cargo があるので、rustcコンパイルする機会はあまりなさそう?

2. 数当てゲームをプログラムする

数当てゲームをプログラムする - The Rust Programming Language 日本語版

VS Code で写経。そのままだとフォーマットや補完ができなかったので、拡張機能を入れた。

let で変数の作成。immutable の方が無標になっているのはありがたい。

::new 行にある :: という記法は、newString 型の関連関数であることを表しています。

関連関数というのは聞き慣れない言葉。英語だと associated function。スタティックメソッドと同じようなものらしい。

io::stdin().read_line(&mut guess)

read_line に、String 型の変数 guess の参照を渡している。C 言語のポインタっぽい。可変であることが &mut で明示的に示されている。

Result 型に関しては、列挙子は OkErr です。

HaskellEither など、関数型でよくあるアレ。エラー処理が書きやすくなるのでありがたい。エラー処理が漏れていると、コンパイル時に検出できるのもうれしい。

0.3.14という数字は、実際には^0.3.14の省略記法で、これは、「バージョン0.3.14と互換性のある公開APIを持つ任意のバージョン」を意味します。

Cargo の依存の指定。実際に、手元の環境では 0.3.23 がインストールされた様子。ちなみに、クレート crate というのは木箱のことらしい。

次に、別の use 行を追加しています: use rand::Rng ですね。Rng トレイトは乱数生成器が実装するメソッドを定義していて、 このトレイトがスコープにないと、メソッドを使用できないのです。

これはわかりにくい気がする。…が、試しに use rand::Rngコメントアウトしてビルドしてみたら、

help: the following trait is implemented but not in scope; perhaps add a `use` for it:
    |
4   | use crate::rand::Rng;

という親切なメッセージが出てきた。これだったら大丈夫そうだ。

match式は、複数のアーム(腕)からできています。

パターンマッチ。

Rustでは、新しい値でguessの値を覆い隠す(shadow)ことが許されているのです。 この機能は、値を別の型に変換したいシチュエーションでよく使われます。

最初はこのシャドーイングの使いみちがわからなかったけど、そういうことか。immutable な変数が基本なので、混乱も起きなさそう。

loop, continue, break については、特に変わったところはなし。

プログラミング言語Rust 公式ガイド

プログラミング言語Rust 公式ガイド

PlatformIO IDE を使って ESP-WROOM-02 の開発をする

昨日作った ESP-WROOM-02 の実験用ボードを使って、温湿度センサー DHT11 からデータを取得したい。以前 NodeMCU で DHT11 を使ってみた記事はこちら

これまでは、Visual Studio Code extension for Arduino を使って VS Code 上で開発していた。しかし、

  • 依存ライブラリの管理が Arduino IDE まかせ
  • 機密情報を環境変数として外出しするのが面倒そう
  • ビルドのたびに Arduino IDE のスプラッシュスクリーンが表示されて邪魔

というような不満があった。そこで、今回は PlatformIO を使ってみる。

platformio.org

PlatformIO のデスクトップ IDE は、VS Code拡張機能として動く1

VSCode — PlatformIO 5.1.1a1 documentation

インストールとプロジェクト作成

ということで、VS Code 上で拡張機能「PlatformIO IDE」と検索し、インストールした。しばらく時間がかかるが、完了すると「Finish! Please restart VSCode.」というメッセージが出てくるので、VS Code を再起動する。

f:id:mimikakimemo:20210207151234p:plain

右下の 🏠 アイコンをクリックすると PlatformIO のホーム画面が開く。[New Project] をクリック。

f:id:mimikakimemo:20210207153640p:plain

プロジェクト作成のウィザートが開くので、適当な名前をつける。Board は ESP-WROOM-02 (Espressif) を選択。下図のようなファイル構成のプロジェクトが作成された。

f:id:mimikakimemo:20210207154701p:plain

ビルド・書き込み

src/main.cpp に簡単なプログラムを書く。初期状態で補完も効いていて使いやすい。

f:id:mimikakimemo:20210207171320p:plain

画面下部の ✔️ アイコンをクリックするとビルドが走る。ショートカットキーは Ctrl+Opt+B。

f:id:mimikakimemo:20210207160723p:plain

また、➡️ アイコンをクリックすると(ビルド+)書き込み(upload)が行われる。ショートカットは Ctrl+Opt+U。

f:id:mimikakimemo:20210207161849p:plain

しかし、自分の場合は上のように OSError: [Errno 16] Resource busy: '/dev/cu.usbserial-2' というエラーが出てうまく行かなかった。別のポートが選択されている様子。自分の場合は、前の記事でシリアル通信を試したように、ESP-WROOM-02 には /dev/tty.usbserial-DM02NA6M でアクセスできる。そこで、プロジェクトの設定ファイル platformio.ini を編集して upload_port を明示的に指定した。

--- a/platformio.ini
+++ b/platformio.ini
@@ -12,3 +12,4 @@
 platform = espressif8266
 board = esp_wroom_02
 framework = arduino
+upload_port = /dev/tty.usbserial-DM02NA6M

再度書き込みを試したら成功。

f:id:mimikakimemo:20210207163311p:plain

ちなみに、前回作成した実験用ボードでは、

  1. ファームウェア書き込みボタン(下のタクトスイッチ)を押しっぱなしにする
  2. そのまま、リセットボタン(上のタクトスイッチ)を押す
  3. リセットボタンを離す
  4. ファームウェア書き込みボタンを離す
  5. 書き込みを行う

というボタン操作をする必要があるので注意。

シリアルモニター

こちらも、monitor_portmonitor_speed をあらかじめ指定しておく。

--- a/platformio.ini
+++ b/platformio.ini
@@ -13,3 +13,5 @@ platform = espressif8266
 board = esp_wroom_02
 framework = arduino
 upload_port = /dev/tty.usbserial-DM02NA6M
+monitor_port = /dev/tty.usbserial-DM02NA6M
+monitor_speed = 115200

画面下部の 🔌 アイコンをクリックするとシリアルモニターが開く。ショートカットキーは Ctrl+Opt+S

f:id:mimikakimemo:20210207170054p:plain

自分の場合、一度ブレッドボード上のリセットボタンを押して再起動しないと、出力が表示されなかった。

ライブラリを依存関係に追加して使う

次に、温湿度センサー DHT11 を使うためのライブラリを追加する。ちなみに、以前 VS CodeArduino 拡張機能で開発していたときは、Arduino IDE のライブラリマネージャーからインストールする必要があった。

PlatformIO では、Libraries のページから検索できる。dht11 を検索窓に入力すると、DHT sensor library が出てくるので、クリック。

f:id:mimikakimemo:20210207172046p:plain

ライブラリの詳細画面が表示されるので、そこから [Add to Project] をクリック。

f:id:mimikakimemo:20210207172410p:plain

インストール先のプロジェクトを選択して [Add] で追加する。

f:id:mimikakimemo:20210207172543p:plain

platformio.inilib_deps に依存が追加された。

--- a/platformio.ini
+++ b/platformio.ini
@@ -15,3 +15,4 @@ framework = arduino
 upload_port = /dev/tty.usbserial-DM02NA6M
 monitor_port = /dev/tty.usbserial-DM02NA6M
 monitor_speed = 115200
+lib_deps = adafruit/DHT sensor library@^1.4.1

バージョンも指定されているので、他のプログラミング言語の依存管理みたいに、再現性のあるビルドができる。

しかし、このライブラリを使ったコード(後掲)をビルドしようと思ったら、Adafruit_Sensor.h: No such file or directory というエラーが出た。

f:id:mimikakimemo:20210207173953p:plain

依存の依存 Adafruit Unified Sensor も自分でインストールする必要があるらしい。先ほどと同じく、GUI から検索して追加した結果。

--- a/platformio.ini
+++ b/platformio.ini
@@ -15,4 +15,6 @@ framework = arduino
 upload_port = /dev/tty.usbserial-DM02NA6M
 monitor_port = /dev/tty.usbserial-DM02NA6M
 monitor_speed = 115200
-lib_deps = adafruit/DHT sensor library@^1.4.1
+lib_deps =
+       adafruit/DHT sensor library@^1.4.1
+       adafruit/Adafruit Unified Sensor@^1.1.4

これでビルドができるようになった。

完成

DHT11 は以下のモジュールを使った。モジュール上に抵抗も実装されている。

この DHT11 モジュールの出力を、ESP-WROOM-02 の IO4 ピンに接続した。

f:id:mimikakimemo:20210207181111j:plain

プログラムは、先日 NodeMCU で使ったものDHTPIN 以外は同様。

#include <Arduino.h>
#include "DHT.h"

#define DHTPIN 4      // IO4 ピンからセンサーの値を得る
#define DHTTYPE DHT11 // DHT 11 を使う

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);
  dht.begin();
}

void loop() {
  float t = dht.readTemperature();
  float h = dht.readHumidity();
  Serial.println("Temperature: " + String(t, 1) + "°C\tHumidity: " + String(h, 0) + "%");

  delay(2000);
}

取得した値がシリアルモニターに無事表示されている。

f:id:mimikakimemo:20210207182348p:plain


  1. 一応、他のエディタにもある程度対応はしている様子。

ESP-WROOM-02 DIP化キットで実験用ボードを作る

この前の記事で触れた『超特急Web接続!ESPマイコン・プログラム全集』という本を読んだ。もともとやりたかった、電池駆動の方法も載っている。原理というよりも、実際の利用例が回路図やサンプルコードとともにたくさん掲載されている。自分のような素人でも、とりあえず真似をすればすぐに作れそうな感じ。サポートページと GitHub リポジトリは以下の通り。

第1章では、ESP-WROOM-02 書き込み用・実験用のボードをブレッドボード上に実装する。初心者なので、まずはこれに従ってそのまま作ることにした。

ちなみに、今まで違いがよく分かっていなかったが、

ということらしい。なるほど。

はんだ付け

ブレッドボードを使うので基本的にははんだ不要だが、ESP-WROOM-02 DIP化キットと、USB シリアル変換モジュール AE-FT234X のピンヘッダははんだ付けをする必要がある。はんだ付けに必要な道具を持っていなかったので、ネット上の情報を参考にしつつ、適当に一式買い揃えた。今後どこまで使うかまだ分からないので、手頃なものを。

セラミック式の方が温度の立ち上がりが良くて使いやすいとのことだったので、ハッコーDASH FX650-81 にした。温度調節可能なものの方がおすすめらしいが、そこまでいくと数千円高くなってしまう。こて先は C2 型が使いやすいとのこと。それに加えて、安定感のあるほどほどのこて台を。

鉛入りのはんだと、不要なはんだを取り除くための吸い取り線。

このあたりの動画を参考にしてピンヘッダをはんだ付けした。

www.youtube.com

ブレッドボードに実装

部品は秋月電子で注文。全部で2,000円弱で、それに送料が500円。ちなみに、ESP-WROOM-02 DIP化キット自体は650円。サポートページにレギュレータの代替品についての参考資料という PDF があったので、それを参考にしてレギュレータを TA48033Sに置き換えた。一緒に使う電解コンデンサ・セラミックコンデンサも買ったのだが、秋月電子の TA48033S にはそれらが同梱されていたので、別で買う必要はなかったようだ。

f:id:mimikakimemo:20210206190551j:plain

14行目右から (-) に伸びている緑の線は不要な気がするが、何か意味があるんだろうか?

タクトスイッチは Osoyoo のキットに入っていたものを使い回せばいいかと思っていたが、いざ組んでみると一回り大きかった。なので、本で紹介されている実験用ボードの配線とは若干ずれている。

レギュレータと USB シリアル変換モジュールを差し込めば、こんな感じ。

f:id:mimikakimemo:20210206232858j:plain

ちなみに、最初は試したときは次のシリアル通信ができなくて焦った。テスターで電圧を調べたら、レギュレータの向きが逆だったことが発覚。正しい向き(刻印面が左を向く。ピンは上から入力、GND、出力の順)に差し直したら正常に動くようになった。

Mac からシリアル通信をする

USB シリアル変換モジュール AE-FT234X を、USB ケーブルで MacBook に接続。すると、USB シリアル変換モジュールの青い LED が点灯した。かなりまぶしい。Mac の System Information を開いてみると、FT230X Basic UART としてちゃんと認識されている。自分の環境では、ドライバのインストールは不要だった。

f:id:mimikakimemo:20210206191741p:plain

$ ls -l /dev/*usbserial*
crw-rw-rw- 1 root wheel 18, 5  2  6 19:09 /dev/cu.usbserial-DM02NA6M
crw-rw-rw- 1 root wheel 18, 4  2  6 19:09 /dev/tty.usbserial-DM02NA6M

Mac からシリアル通信をしたい場合は、screen コマンドを使えばいいらしい。

$ screen /dev/tty.usbserial-DM02NA6M 115200

最初は何も表示されないが、リセット(上のタクトスイッチ)を1秒程度押して離すと、再起動されて ready と出る。購入直後の ESP8266 では、AT コマンドというものが使えるらしい。試しに AT と入力してみると、OK という応答が返ってくる。

f:id:mimikakimemo:20210206235724p:plain

AT の後の改行は、Enter を押してから Ctrl+J も入力する必要があるので注意。ESP8266 は CR+LF を期待しているようだ。

AT+GMR コマンドを使うと、バージョン情報が表示される。

AT+GMR
AT version:1.6.2.0(Apr 13 2018 11:10:59)
SDK version:2.2.1(6ab97e9)
compile time:Jun  7 2018 19:34:26
Bin version(Wroom 02):1.6.2
OK

ただしこの AT コマンド、Arduino IDE でプログラムを書き込むと使えなくなる。ということで、深入りはしないことにする。

Osoyoo のキットに入っている I2C 1602 LCD モジュール を使う

先月買った Osoyoo のキットに 2 行の液晶ディスプレイ、I2C 1602 LCD モジュールが入っていたので、使ってみる。1602 は 16文字×2行 という意味らしい。Osoyoo 公式のドキュメント(あまり親切ではないが)は以下の通り。

LCD モジュールからは4本の端子 VCC, GND, SCL, SDA が出ている。SCL, SDA は I2C というインターフェイス規格で定義されているらしい。SCL がクロック、SDA がデータで、この2本の信号線があれば、まとめて複数のデバイスと通信できるのだとか。『超特急Web接続! ESPマイコン・プログラム全集』の Appendix 4 に解説があった。NodeMCU の場合、

LCD NodeMCU 備考
VCC VIN 3V3 だと電圧不足だった
GND GND
SCL D1 ESP8266 の IO5 に繋がっている
SDA D2 ESP8266 の IO4 に繋がっている

のように接続すると動かすことができる。

ライブラリは、Osoyoo のサイトから LiquidCrystal_I2C.zip がダウンロードできる。こういったライブラリのインストールのお作法がまだ良くわかっていないが、とりあえず LiquidCrystal_I2C.cppLiquidCrystal_I2C.h をそのままスケッチの隣につっこんだらコンパイルできた。

この前試した温湿度センサー DHT11 の値を LCD に表示してみた。

f:id:mimikakimemo:20210124223812j:plain

#include "LiquidCrystal_I2C.h"
#include "DHT.h"

#define DHTPIN D6     // D6 ピンからセンサーの値を得る
#define DHTTYPE DHT11 // DHT 11 を使う

LiquidCrystal_I2C lcd(0x27, 20, 4);
DHT dht(DHTPIN, DHTTYPE);

void setup()
{
  Serial.begin(115200);

  lcd.init();
  lcd.backlight();

  dht.begin();
}

void loop()
{
  float t = dht.readTemperature();
  float h = dht.readHumidity();
  Serial.println("Temperature: " + String(t, 1) + "°C\tHumidity: " + String(h, 0) + "%");

  // 1行目に温度を表示
  lcd.setCursor(0, 0);
  lcd.print("Temp:     " + String(t, 1) + char(0xdf) + "C");
  // 2行目に湿度を表示
  lcd.setCursor(0, 1);
  lcd.print("Humidity:    " + String(h, 0) + "%");

  delay(2000);
}

Arduino の文字列の扱いについては、まだいまいちよく把握していない…。この LCD は HD44780 互換というものらしく、半角カナや ASCII に無い記号も表示できた。温度記号 ℃ は無かったので、上のコードでは半角カナの半濁点 ゚(0xdf)を使って表示している。

参考サイト