X-Plane コクピットの自作に挑戦!
Teensyでは「DogLcd」という特別のLCDが使われています。一般的には沢山のピンを使ってTeensyと繋ぐLCDが使われていますが、I2CというタイプのLCDを使うと少ない接続(3本の信号線)で無駄なく使えるようになります、限られたピンなのでこれ以外の選択肢はないでしょう。
秋月電子通商のI2C接続キャラクターLCDモジュール 16✕2行白色バックライト付 (ACM1602NI)です。I2Cタイプだったら、他の安いのでも大丈夫だと思います。
ACM1602NI-FLW-FBW-M01 800円
Tennsyでは5(SCL)、6(SDA)番ピンが使用可能になっています。このピン以外は使用できません。
このピンを使うと2線式でi2Cディバイスと通信することができ、Arduinoの普通のスクリプトを理解し、表示できるようになっています。
以下のサイトから、2つのACM1602NIを使う為のドライバとなるライブラリをインストールします。
ここに下2つのライブラリがありますのでダウンロードします。(下2つも同じリンクです)
● LcdCore.h:これはLCDのコア部分になるライブラリです。
「LcdCore_20120528.zip」をダウンロード
● LCD_ACM1602NI.h:(秋月I2C液晶用)このライブラリをLcdCoreと一緒に使うことにより、元のLiquidCrystal(液晶ディスプレイを4本または8本のデータラインとして繋ぐとき)ライブラリと同様に動作します。
「LCD_ACM1602NI_20120528.zip」をダウンロード

ダウンロードすると、この2つのフォルダ毎 Arduino.app/Contents/Resources/Java/libraries フォルダの中にそのまま入れれば完了です。
あるいはArduinoのスケッチメニューから「ファイルを追加」からでも入れることができるのかな?やったことありません。
本当にありがとうございます。これで何の苦労もなくACM1602NIがつかえるようになります。
下の回路図ではLCDの3番と6番で構成されている可変抵抗はLCDの輝度を調整します。丁度いい状態になったとき文字が表示されるようになります。

ACM1602NIにお馴染み「hello, world!」を表示してみましょう。
これも、スケッチを公開しているこのサイトから頂きました。コードにはピンの位置は書かれていませんが、Teensy2.0では5、6ピンを使うと問題ありません。
HelloWorld.ino
LCDを繋ぐピンの位置は、Teensy2.0では、5(SCL)、6(SDA)ピンだけです。他と重なる場合は、他を移動するしかありません。
※Teensy++2.0は、0(SCL)、1(SDA)ピンだけ。
#include <Wire.h> // Arduino IDE のI2Cライブラリ
#include <LcdCore.h> // LCDコアライブラリ
#include <LCD_ACM1602NI.h> // 秋月I2C液晶用のI/Oライブラリ
LCD_ACM1602NI lcd(0xa0); // 0xa0はI2Cアドレス(Tennsy2.0では5(SCL),6(SDA)番ピンが使用可能)
void setup() {
lcd.begin(16, 2); //16文字、2行の液晶を使う場合
lcd.print("hello, world!");
}
void loop() {
lcd.setCursor(0, 1); //(列, 行)一番左が0、一番右が19(液晶によって違う)。行も同じ。このディスプレイでは0〜2までが使える。
lcd.print(millis()/1000); //Arduinoをリセットしてからの秒数の取得です
}
X-Planeで使用する場合の例
セスナ172SPの例としてあげています。Com1とNav1をロータリーエンコーダで上げ下げします。
それをX-Planeから受信して、1行目にNav1、2行目にCom1をLCDに表示します。
実際のdatarefでは下のように、小数点が入っていません。

従ってこのように小数点がない状態で表示されます。

※中の数字はダミー
小数点を入れるには、136025という数値の3桁目に小数点を入れたいので1000で割ります。結果は136.025ですね。しかし、まだ問題があります。
浮動小数点数の場合は、小数点以下第2位までLCDに出力するのがデフォルトの動作らしいのです。従って、そのまま出力すると「136.02」としが表示されないのです。
しかし、浮動小数点数を出力する場合は、第2パラメータの数値によって有効桁数を指定できます。
lcd.print(val, 3); と最後に桁数を指示してやると3桁まで表示してくれます。何もいれないと2桁までということになります。
下が小数点まで表示できるスケッチです。数値のアップダウンはエンコーダの1クリック4カウントのままです。
X-Plane_LCD_View.ino
//I2C液晶用とロータリーエンコーダ
#include <Wire.h> // Arduino IDE のI2Cライブラリ
#include <LcdCore.h> // LCDコアライブラリ
#include <LCD_ACM1602NI.h> // 秋月I2C液晶用のI/Oライブラリ
#include <Encoder.h>
LCD_ACM1602NI lcd(0xa0); // 0xa0はI2Cアドレス teensy2.0は5と6ピンと繋ぐ
Encoder nav = Encoder(14, 15); // Rotary Encoder on pin 5 and 6
Encoder com = Encoder(16, 17);
FlightSimInteger NavFrequencyHz;
FlightSimInteger ComFrequencyHz;
// variables
long encoderNav_prev=0; // エンコーダの回転位置の変化を検出します
long encoderCom_prev=0;
elapsedMillis inactivityTimeout;// 非活動タイムアウト
// setup runs once, when Teensy boots.
//
void setup() {
// 全てのハードウェアの初期化
pinMode(14, INPUT_PULLUP);
pinMode(15, INPUT_PULLUP);
pinMode(16, INPUT_PULLUP);
pinMode(17, INPUT_PULLUP);
lcd.begin(16, 2); //液晶ディスプレイの初期化、LCDを16文字で2行表示、setup()の中で実行すること
//このlcdは最初に読み込むライブラリの中でオブジェクト化されたもので必ずこの「lcd」でないとダメ
NavFrequencyHz = XPlaneRef("sim/cockpit2/radios/actuators/nav1_frequency_hz");
NavFrequencyHz.onChange(update_Navlcd); // X-Planeが変化するとLCDも更新する
ComFrequencyHz = XPlaneRef("sim/cockpit2/radios/actuators/com1_frequency_hz_833");
ComFrequencyHz.onChange(update_Comlcd);
}
void loop() {
FlightSim.update();
// ロータリーエンコーダを読む、それが変化したらNavFrequencyHzに書き込む
long encNav = nav.read();
if (encNav != encoderNav_prev) {
NavFrequencyHz = NavFrequencyHz + (encNav - encoderNav_prev);
encoderNav_prev = encNav;
update_Navlcd(NavFrequencyHz);
inactivityTimeout = 0; // 無活動タイムアウトをリセット
}
long encCom = com.read();
if (encCom != encoderCom_prev) {
ComFrequencyHz = ComFrequencyHz + (encCom - encoderCom_prev);
encoderCom_prev = encCom;
update_Comlcd(ComFrequencyHz);
inactivityTimeout = 0; // 無活動タイムアウトをリセット
}
}
float tmp; // 周波数dataref整数に小数点を置くための宣言
void update_Navlcd(long val){
lcd.setCursor(0, 0); //先頭から表示、1行目に表示
lcd.print("Nav1:"); //先頭の文字
tmp = val / 100.0; //小数点以下2桁はNav1の数値を100.0で割る
lcd.print(tmp);
}
void update_Comlcd(long val){
lcd.setCursor(0, 1); //先頭から表示、2行目に表示
lcd.print("Com1:");
tmp = val / 1000.000; //小数点以下3桁はCom1の数値を1000.0で割る
lcd.print(tmp,3); //小数点以下3桁まで表示するために、第2パラメータに3と入れる
}
上のスケッチは2つのエンコーダでそれぞれ別々に周波数をコントロールしていますが、大きく周波数を変える時はやはり大変です。
X-Plane上でノブを回すように、小数点以上と以下をそれぞれ別々にやったほうが早く処理できますよね。
datarefをよく見ると、今までやったような全てのデータを一度に扱うdatarefもあるが、小数点より上と、下に分割したdatarefもあります。
Nav1が
sim/cockpit2/radios/actuators/nav1_frequency_Mhz
sim/cockpit2/radios/actuators/nav1_frequency_khz
Com1は
sim/cockpit2/radios/actuators/com1_frequency_Mhz
sim/cockpit2/radios/actuators/com1_frequency_khz
Mhzを左のエンコーダ、Khzを右のエンコーダでコントロールするようにします。Nav1とCom1の切替はプッシュボタンで行ないます。
下のスケッチ、まだ完全でありません。範囲の設定がまだです。
ModeChenge_LCD_View.ino
//I2C液晶用とロータリーエンコーダ
#include <Wire.h> // Arduino IDE のI2Cライブラリ
#include <LcdCore.h> // LCDコアライブラリ
#include <LCD_ACM1602NI.h> // 秋月I2C液晶用のI/Oライブラリ
#include <Encoder.h>
#include <Bounce.h>
LCD_ACM1602NI lcd(0xa0); // 0xa0はI2Cアドレス teensy2.0は5と6ピンと繋ぐ
Encoder MhzEncPin = Encoder(14, 15); // Rotary Encoder on pin 5 and 6
Encoder KhzEncPin = Encoder(16, 17);
Bounce modeSwitchPin = Bounce (10, 5);
FlightSimInteger Nav1FrequencyMhz;
FlightSimInteger Nav1FrequencyKhz;
FlightSimInteger Com1FrequencyMhz;
FlightSimInteger Com1FrequencyKhz;
// variables
long encoder_Mhz_prev=0; // エンコーダの回転位置の変化を検出します
long encoder_Khz_prev=0;
elapsedMillis inactivityTimeout;// 非活動タイムアウト
// モードの設定
enum Modes {
Mode_Nav1, // Nav1モード
Mode_Com1, // Com1モード
Mode_Count // これで自動的に2つのモードを持つことになる
};
int mode = 0;
// setup runs once, when Teensy boots.
//
void setup() {
// 全てのハードウェアの初期化
pinMode(14, INPUT_PULLUP);
pinMode(15, INPUT_PULLUP);
pinMode(16, INPUT_PULLUP);
pinMode(17, INPUT_PULLUP);
pinMode(10, INPUT_PULLUP);
lcd.begin(16, 2); //液晶ディスプレイの初期化、LCDを16文字で2行表示、setup()の中で実行すること
//このlcdは最初に読み込むライブラリの中でオブジェクト化されたもので必ずこの「lcd」でないとダメ
Nav1FrequencyMhz = XPlaneRef("sim/cockpit2/radios/actuators/nav1_frequency_Mhz");
Nav1FrequencyMhz.onChange(update_Nav1_Mhz_lcd); // X-Planeが変化するとLCDも更新する
Nav1FrequencyKhz = XPlaneRef("sim/cockpit2/radios/actuators/nav1_frequency_khz");
Nav1FrequencyKhz.onChange(update_Nav1_Khz_lcd);
Com1FrequencyMhz = XPlaneRef("sim/cockpit2/radios/actuators/com1_frequency_Mhz");
Com1FrequencyMhz.onChange(update_Com1_Mhz_lcd);
Com1FrequencyKhz = XPlaneRef("sim/cockpit2/radios/actuators/com1_frequency_khz");
Com1FrequencyKhz.onChange(update_Com1_Khz_lcd);
}
void loop() {
FlightSim.update();
modeSwitchPin.update();
// change mode when switch pressed
if(modeSwitchPin.fallingEdge()) {
++mode;
if(mode >= Mode_Count)
mode = 0;
}
// ロータリーエンコーダを読む、それが変化したらNavFrequencyHzに書き込む
if (mode == Mode_Nav1) {
long MhzEnc = MhzEncPin.read();
if (MhzEnc != encoder_Mhz_prev) {
Nav1FrequencyMhz = Nav1FrequencyMhz + (MhzEnc - encoder_Mhz_prev);
encoder_Mhz_prev = MhzEnc;
update_Nav1_Mhz_lcd(Nav1FrequencyMhz);
inactivityTimeout = 0; // 無活動タイムアウトをリセット
}
long KhzEnc = KhzEncPin.read();
if (KhzEnc != encoder_Khz_prev) {
Nav1FrequencyKhz = Nav1FrequencyKhz + (KhzEnc - encoder_Khz_prev);
encoder_Khz_prev = KhzEnc;
update_Nav1_Khz_lcd(Nav1FrequencyKhz);
inactivityTimeout = 0; // 無活動タイムアウトをリセット
}
}
if (mode == Mode_Com1) {
long MhzEnc = MhzEncPin.read();
if (MhzEnc != encoder_Mhz_prev) {
Com1FrequencyMhz = Com1FrequencyMhz + (MhzEnc - encoder_Mhz_prev);
encoder_Mhz_prev = MhzEnc;
update_Com1_Mhz_lcd(Com1FrequencyMhz);
inactivityTimeout = 0; // 無活動タイムアウトをリセット
}
long KhzEnc = KhzEncPin.read();
if (KhzEnc != encoder_Khz_prev) {
Com1FrequencyKhz = Com1FrequencyKhz + (KhzEnc - encoder_Khz_prev);
encoder_Khz_prev = KhzEnc;
update_Com1_Khz_lcd(Com1FrequencyKhz);
inactivityTimeout = 0; // 無活動タイムアウトをリセット
}
}
}
float tmp; // 周波数dataref整数に小数点を置くための宣言
void update_Nav1_Mhz_lcd(long val){
lcd.setCursor(0, 0); //先頭から表示、1行目に表示
lcd.print("Nav1:"); //先頭の文字
lcd.print(val);
}
void update_Nav1_Khz_lcd(long val){
lcd.setCursor(8, 0); //先頭から表示、1行目に表示
lcd.print(".");
lcd.print(val);
}
void update_Com1_Mhz_lcd(long val){
lcd.setCursor(0, 1); //先頭から表示、2行目に表示
lcd.print("Com1:"); //先頭の文字
lcd.print(val);
}
void update_Com1_Khz_lcd(long val){
lcd.setCursor(8, 1); //先頭から表示、2行目に表示
lcd.print(".");
lcd.print(val);
}
小数点以下を扱う場合はチョット変更が必要になる。
8行目の FlightSimFloat(これにより小数点以下を扱うことが出来る)と、LCDの表示には最後の行のlcd.print(NavFrequencyHz,6);のように第2パラメーターとして表示する桁数を入れる必要がある。ここでは6桁を指定している。
以下はX-Planeのフラップの状態(dataref)を単純に読み込んでいるのだが、小数点以下も読み込めるのでほとんどのデータを表示できる。
Simple_LCD_View.ino
//小数点まで表示する I2C液晶用LCD
#include <Wire.h> // Arduino IDE のI2Cライブラリ
#include <LcdCore.h> // LCDコアライブラリ
#include <LCD_ACM1602NI.h> // 秋月I2C液晶用のI/Oライブラリ
LCD_ACM1602NI lcd(0xa0); // 0xa0はI2Cアドレス teensy2.0は5と6ピンと繋ぐ
FlightSimFloat NavFrequencyHz;
long encoderNav_prev=0; // エンコーダの回転位置の変化を検出します
elapsedMillis inactivityTimeout;// 非活動タイムアウト
void setup() {
lcd.begin(16, 2); //液晶ディスプレイの初期化、LCDを16文字で2行表示、setup()の中で実行すること
//このlcdは最初に読み込むライブラリの中でオブジェクト化されたもので必ずこの「lcd」でないとダメ
NavFrequencyHz = XPlaneRef("sim/flightmodel2/controls/flap1_deploy_ratio");
}
float tmp; // 周波数dataref整数に小数点を置くための宣言
void loop() {
FlightSim.update();
lcd.setCursor(0, 0);
lcd.print(NavFrequencyHz,6);
}
アップダウンの別の変化の仕方はここで解説しています。
後は、余談ですがteensyのUSB FlightSimの例の中にある「FrameRateDisplay」はLCDモジュールにフレームレートを表示するスケッチですが、i2CのLCDでも以下の設定で表示できます。
ちなみに、X-Planeでフレームレートを表示するには、設定>データ入力/出力 で freme rate にチェックを入れると表示されるので比較してみるとわかります。

FrameRateDisplay_i2c.ino
//小数点まで表示する I2C液晶用LCD
#include <Wire.h> // Arduino IDE のI2Cライブラリ
#include <LcdCore.h> // LCDコアライブラリ
#include <LCD_ACM1602NI.h> // 秋月I2C液晶用のI/Oライブラリ
LCD_ACM1602NI lcd(0xa0); // 0xa0はI2Cアドレス teensy2.0は5と6ピンと繋ぐ
FlightSimElapsedFrames frameCount; // increases each simulation frame 各シミュレーションフレームを1つずつ増加させる
elapsedMillis milliSeconds; // 毎秒1000で増加
void setup() {
lcd.begin(16, 2); //液晶ディスプレイの初期化、LCDを16文字で2行表示、setup()の中で実行すること
//このlcdは最初に読み込むライブラリの中でオブジェクト化されたもので必ずこの「lcd」でないとダメ
pinMode(LED_BUILTIN, OUTPUT); //teensyの11ピンに出力
}
void loop() {
FlightSim.update();
// X-Planeが実行されている場合、Teensy11ピンのLEDが表示され、プラグインが有効になります。
if (FlightSim.isEnabled()) {
digitalWrite(LED_BUILTIN, HIGH);
} else {
digitalWrite(LED_BUILTIN, LOW);
}
// すべて10フレーム、フレームレートの表示を更新します
if (frameCount >= 10) {
// read the elapsed frames and elapsed time
// 経過フレームと経過時間を読み取ります
int frames = frameCount;
int ms = milliSeconds;
// ゼロの両方をリセット(すぐに読んだ後)
milliSeconds = 0;
frameCount = 0;
// 計算し、秒あたりのフレーム数を示します
float fps = (float)frames / (float)ms * 1000.0;
lcd.setCursor(0, 0);
lcd.print(fps);
lcd.print(" fsp ");
}
}