※このバージョンは最新版ではありません。

 

ピンアサインは秋月電子のArduino Pro mini互換基板(http://akizukidenshi.com/catalog/g/gK-10347/

に合わせて変更しております。プログラムのみ更新する場合はピンアサインを以前のコードからコピペして書き換えて下さい。

 

※2019/10/16 追記

V1.00よりコードを一部変更し、音データの肥大化によりメモリが不足する問題を解消しました。

今回の変更により、メモリ使用量を気にする事無く音データを作りこむことが可能となります。

 

ソースコード

// ***************************************************************************************
//  WhiteBear Controller Next Generation
// ***************************************************************************************
//  ・PWM制御による鉄道模型用コントローラー。
//  ・12段階のロータリースイッチを用い、ワンハンドルマスコンを模擬する。
//  ・マスコンはP5-B5 + EB の12段。
//  ・各種状況はキャラクタLCD(16文字x2段)で表示。
//  ・力行各段の最高速度を設定可能。設定はソースコード内であらかじめ設定。
//  ・ノッチによる加速度の違いもついでに再現。
//  ・PWM周波数の変調により、VVVFなど制御機器の音を再現。
//  ・変調音は最大12種類までプログラム可能。
//  ・加速時と減速時で異なる変調音を設定可能。
//  ・マスコンP5で電源投入すると、開発モード。
//  ・開発モード(isDev=True)の時、現在/目標スピード値と加速率/常点灯ダイヤルの値も表示。
// ***************************************************************************************
//更新履歴
//2019/07/30 V1.00 初期版。
//2019/10/11 V1.10 音データをフラッシュメモリ格納に変更し、RAM不足を解消。
// ***************************************************************************************
#include <avr/io.h> // ATmega328P用のヘッダファイル
#include <avr/pgmspace.h>
#include <LiquidCrystal.h> // キャラクタ液晶ディスプレイ制御用のヘッダファイル
// ***********************************************
// 定数・変数定義 ここから
// ***********************************************

const char *MOJI1 = " WCNG Controller";  // Welcomeメッセージ。LCD1段目。16桁にすること。
const char *MOJI2 = "Program Ver 1.10";  // Welcomeメッセージ。LCD2段目。16桁にすること。

const int P_MASCON     = 3; // Arduino接続ピン番号:(Analog) ロータリースイッチ = A3
const int P_LIGHT_DIAL = 4; // Arduino接続ピン番号:(Analog) 常点灯調節ダイアル = A4
const int P_ACCEL_DIAL = 5; // Arduino接続ピン番号:(Analog) 加速率調節ダイアル = A5

const int P_PWM2B      = 3; // Arduino接続ピン番号:(Digital) OCR2B出力 to Rail = D3
const int P_LCD_RS     = 4; // Arduino接続ピン番号:(Digital) LCD-REGISTER      = D4
const int P_LCD_EN     = 5; // Arduino接続ピン番号:(Digital) LCD-ENABLE        = D5
const int P_LCD_D4     = 6; // Arduino接続ピン番号:(Digital) LCD-D4            = D6
const int P_LCD_D5     = 7; // Arduino接続ピン番号:(Digital) LCD-D5            = D7
const int P_LCD_D6     = 8; // Arduino接続ピン番号:(Digital) LCD-D6            = D8
const int P_LCD_D7     = 9; // Arduino接続ピン番号:(Digital) LCD-D7            = D9
const int P_PWM1B     = 10; // Arduino接続ピン番号:(Digital) OCR1B出力 to Rail = D10
const int P_PWM2A     = 11; // Arduino接続ピン番号:(Digital) OCR2A出力 to Rail = MOSI

// キャラクタ液晶
LiquidCrystal lcd(P_LCD_RS, P_LCD_EN, P_LCD_D4, P_LCD_D5, P_LCD_D6, P_LCD_D7);

const int DECISION_TIME = 5;   // setup時に走行パターンを決める際の、確定までの時間。秒。
const int ACCEL_RATIO = 20;    // (加速率調整小型ボリュームからの入力値) × ACCEL_RATIO ⇒ 加速率
const int BRAKE_RATIO = 6;    // (加速率調整小型ボリュームからの入力値) × BRAKE_RATIO × マスコンブレーキ値(1~5) ⇒ 減速率
const int UNTI_CHAT_RATE = 40; // チャタリング防止ループの値。大きくし過ぎるとノッチ切替時の反応が悪くなる。

const int MC_EB  = 1; // マスコン位置 非常
const int MC_B5  = 2; // マスコン位置 制動5
const int MC_B4  = 3; // マスコン位置 制動4
const int MC_B3  = 4; // マスコン位置 制動3
const int MC_B2  = 5; // マスコン位置 制動2
const int MC_B1  = 6; // マスコン位置 制動1
const int MC_N   = 7; // マスコン位置 惰行
const int MC_P1  = 8; // マスコン位置 力行1
const int MC_P2  = 9; // マスコン位置 力行2
const int MC_P3 = 10; // マスコン位置 力行3
const int MC_P4 = 11; // マスコン位置 力行4
const int MC_P5 = 12; // マスコン位置 力行5

// マスコン位置名称を保持する配列
char* masconPosName[] = {"EB", "B5", "B4", "B3", "B2", "B1", "N ", "P1", "P2", "P3", "P4", "P5"}; 

const int MD_STOP  = 1; // モード 停止
const int MD_BRAKE = 2; // モード 減速
const int MD_NTRL  = 3; // モード 惰行
const int MD_ACCEL = 4; // モード 加速

//boolean isDev;      // 開発モードのときTrue。
int vvvfPtn;        // 走行音パターン。0~11となる。
int masconPos;      // マスコンの位置を保持。1~12となる。
int inputMascon;    // 読み取ったマスコン位置を一時的に保持
int compareMascon;  // マスコンの位置前回と比較するため一時的に保持
int lightVol;       // 常点灯ダイアルから拾った値を0~127に変換して保持する。変換処理はloop()内にて。
int accelVol;       // 加速率ダイアルから拾った値を1~8に変換して保持する。変換処理はloop()内にて。
int kasoku;         // 走行用出力する加減速率
int mode;           // 走行状態。1=停止, 2=減速, 3=惰行, 4=加速
long notch1;        // ノッチ1での最高速度
long notch2;        // ノッチ2での最高速度
long notch3;        // ノッチ3での最高速度
long notch4;        // ノッチ4での最高速度
long notch5;        // ノッチ5での最高速度
long stopSpd;       // 走行用出力をカットする速度

long AsttFrq;   // 開始周波数
long AendFrq;   // 終了周波数
long Afrq;      // 周波数
long AsttSpd;   // 開始スピード
long AendSpd;   // 終了スピード
long BsttFrq;   // 開始周波数
long BendFrq;   // 終了周波数
long Bfrq;      // 周波数
long BsttSpd;   // 開始スピード
long BendSpd;   // 終了スピード
long spd;      // 内部スピード 0~3,000,000(速度×10,000)
long orderSpd; // 指示スピード

int i; // ループカウンター
int k; // ループカウンター

int dispSpd = 0;      // LCD表示用スピード値
//long dispOrderSpd = 0; // LCD表示用スピード目標値

// 走行音名称
char* soundName[12] = {
  "   201          ",
  " 209,70-000,etc ",
  " E231-0,500,etc ",
  "   E231-1000    ",
  "   E233, etc    ",
  "KQ 2100 (SI-GTO)",
  " N1000 (SI-GTO) ",
  " MITSUBISHI-GTO ",
  "   Toyo-GTO     ",
  "   Toyo-IGBT    ",
  "Toei 5300(Akuma)",
  "TOSHIBA 3LevIGBT"
};

// 各パターンの各ノッチ位置での最高速度値を配列で保持。
// 左から順に 1ノッチ, 2ノッチ, 3ノッチ, 4ノッチ, 5ノッチ。
// 5×12パターン=60個の値を持つ。
int maxSpdData[] = {
  21, 41, 71, 91, 101,   //パターン 1 201 
  31, 51, 81, 101, 111,  //パターン 2 209,70-000,etc
  31, 51, 91, 111, 121,  //パターン 3 E231-0,500,etc
  31, 51, 91, 111, 121,  //パターン 4 E231-1000
  31, 51, 91, 111, 121,  //パターン 5 E233, etc
  41, 71, 111, 121, 131, //パターン 6 KQ 2100 (SI-GTO)
  41, 71, 111, 121, 131, //パターン 7 N1000 (SI-GTO)
  41, 71, 111, 121, 131, //パターン 8 MITSUBISHI-GTO
  41, 71, 111, 121, 131, //パターン 9 Toyo-GTO
  41, 71, 111, 121, 131, //パターン10 Toyo-IGBT
  31, 61, 101, 111, 111, //パターン11 Toei 5300(Akuma)
  31, 61, 91, 101, 111,  //パターン12 TOSHIBA 3LevIGBT
};

// 走行用の出力を止める速度値を配列で保持。ピタッと止めるための値。
// 環境に合わせて適宜調整して下さい。
// 12パターン分の値を持つ。
int stopSpdData[] = {19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19};

// 音データ。
// 設定可能周波数 123Hz~8000kHz
// DUTY比の分解能確保のため30kHzぐらいを上限。整数に限る。
// ~~~(例) パターン3の場合~~~
// 1行目 速度0~ 3の間:380Hzで音程変化なし
// 2行目 速度4~11の間:380Hzから980Hzまで変化する

//加速時用の音データ。
const int AsoundDataBase[] PROGMEM = {
  //パターン1 201
  580, 580, 66,  // 1 開始周波数 終了周波数 切替スピード
  550, 980, 101, // 2 開始周波数 終了周波数 切替スピード
  -1,            // 3 終了コード

  //パターン2 209,70-000,etc
  230, 230, 3,     // 1 開始周波数 終了周波数 切替スピード
  90, 90, 4,       // 2 開始周波数 終了周波数 切替スピード
  230, 950, 11,    // 3 開始周波数 終了周波数 切替スピード
  600, 1150, 21,   // 4 開始周波数 終了周波数 切替スピード
  750, 1150, 31,   // 5 開始周波数 終了周波数 切替スピード
  600, 950, 46,    // 6 開始周波数 終了周波数 切替スピード
  470, 1150, 91,   // 7 開始周波数 終了周波数 切替スピード
  1150, 1150, 111, // 8 開始周波数 終了周波数 切替スピード
  -1,              // 9 終了コード

  //パターン3 E231-0,500,etc
  380, 380, 3,     // 1 開始周波数 終了周波数 切替スピード
  380, 980, 11,    // 2 開始周波数 終了周波数 切替スピード
  560, 780, 26,    // 3 開始周波数 終了周波数 切替スピード
  480, 1150, 101,  // 4 開始周波数 終了周波数 切替スピード
  1150, 1150, 121, // 5 開始周波数 終了周波数 切替スピード
  -1,              // 6 終了コード

  //パターン4 E231-1000
  1050, 1050, 11,  // 1 開始周波数 終了周波数 切替スピード
  1050, 700, 27,   // 2 開始周波数 終了周波数 切替スピード
  700, 6000, 35,   // 3 開始周波数 終了周波数 切替スピード
  480, 1150, 101,  // 4 開始周波数 終了周波数 切替スピード
  1150, 1150, 121, // 5 開始周波数 終了周波数 切替スピード
  -1,              // 6 終了コード

  //パターン5 E233, etc
  750, 750, 30,    // 1 開始周波数 終了周波数 切替スピード
  350, 1150, 101,  // 2 開始周波数 終了周波数 切替スピード
  1150, 1150, 121, // 3 開始周波数 終了周波数 切替スピード
  -1,              // 4 終了コード

  //パターン6 KQ 2100 (SI-GTO)
  350, 350, 3,   // 1 開始周波数 終了周波数 切替スピード
  390, 390, 4,   // 2 開始周波数 終了周波数 切替スピード
  440, 440, 5,   // 3 開始周波数 終了周波数 切替スピード
  490, 490, 6,   // 4 開始周波数 終了周波数 切替スピード
  530, 530, 7,   // 5 開始周波数 終了周波数 切替スピード
  580, 580, 8,   // 6 開始周波数 終了周波数 切替スピード
  650, 650, 9,   // 7 開始周波数 終了周波数 切替スピード
  700, 700, 10,  // 8 開始周波数 終了周波数 切替スピード
  780, 780, 22,  // 9 開始周波数 終了周波数 切替スピード
  510, 980, 111, //10 開始周波数 終了周波数 切替スピード
  580, 580, 131, //11 開始周波数 終了周波数 切替スピード
  -1,            //12 終了コード

  //パターン7 N1000 (SI-GTO)
  350, 350, 3,   // 1 開始周波数 終了周波数 切替スピード
  390, 390, 4,   // 2 開始周波数 終了周波数 切替スピード
  440, 440, 5,   // 3 開始周波数 終了周波数 切替スピード
  490, 490, 6,   // 4 開始周波数 終了周波数 切替スピード
  530, 530, 7,   // 5 開始周波数 終了周波数 切替スピード
  580, 580, 8,   // 6 開始周波数 終了周波数 切替スピード
  650, 650, 9,   // 7 開始周波数 終了周波数 切替スピード
  700, 700, 10,  // 8 開始周波数 終了周波数 切替スピード
  780, 780, 16,  // 9 開始周波数 終了周波数 切替スピード
  950, 970, 18,  //10 開始周波数 終了周波数 切替スピード
  850, 970, 20,  //11 開始周波数 終了周波数 切替スピード
  800, 970, 22,  //12 開始周波数 終了周波数 切替スピード
  510, 980, 111, //13 開始周波数 終了周波数 切替スピード
  580, 580, 131, //14 開始周波数 終了周波数 切替スピード
  -1,            //15 終了コード

  //パターン8 MITSUBISHI GTO
  490, 710, 16,    // 1 開始周波数 終了周波数 切替スピード
  710, 710, 25,    // 2 開始周波数 終了周波数 切替スピード
  600, 780, 31,    // 3 開始周波数 終了周波数 切替スピード
  420, 1250, 110,  // 4 開始周波数 終了周波数 切替スピード
  1250, 1250, 131, // 5 開始周波数 終了周波数 切替スピード
  -1,              // 6 終了コード
  
  //パターン9 Toyo-GTO
  490, 780, 18,    // 1 開始周波数 終了周波数 切替スピード
  490, 780, 30,    // 2 開始周波数 終了周波数 切替スピード
  420, 1250, 110,  // 3 開始周波数 終了周波数 切替スピード
  1250, 1250, 131, // 4 開始周波数 終了周波数 切替スピード
  -1,              // 5 終了コード

  //パターン10 Toyo-IGBT
  1050, 1050, 25,  // 1 開始周波数 終了周波数 切替スピード
  630, 1300, 40,   // 2 開始周波数 終了周波数 切替スピード
  650, 1150, 111,  // 3 開始周波数 終了周波数 切替スピード
  1150, 1150, 131, // 4 開始周波数 終了周波数 切替スピード
  -1,              // 5 終了コード

  //パターン11 Toei 5300(Akuma)
  780, 780, 11,
  450, 450, 16,
  500, 780, 22,
  430, 580, 25,
  400, 1150, 105,
  1150, 1150, 111,
  -1,            // 6 終了コード

  //パターン12 TOSHIBA 3LevIGBT
  730, 730, 11,
  510, 730, 16,
  600, 1150, 101,
  1150, 1150, 111,
  -1,            // 7 終了コード

};

//RAM消費を抑える為の配列。
//[]内の数字を3n(n=上記パターンの中で最も行数が長いパターンの行数)以上に設定して下さい。
//あまり数字を大きくし過ぎるとメモリ不足になるため、むやみに大きくしないように。
//初期値は45。
int AsoundData[45];

// 走行音パターン12種soundDataの各パターンデータ開始位置インデックスを保持。
// 実際の値設定は、setup処理内にて終了コード(-1)を検出し動的に設定する。
int AdataNum[13] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

//減速時用の音データ。
const int BsoundDataBase[] PROGMEM = {
  //パターン1 201
  580, 580, 61,  // 1 開始周波数 終了周波数 切替スピード
  580, 650, 76,  // 2 開始周波数 終了周波数 切替スピード
  550, 980, 101, // 3 開始周波数 終了周波数 切替スピード
  -1,            // 4 終了コード

  //パターン2 209,70-000,etc
  120, 120, 3,     // 1 開始周波数 終了周波数 切替スピード
  120, 1180, 26,   // 2 開始周波数 終了周波数 切替スピード
  600, 900, 36,    // 3 開始周波数 終了周波数 切替スピード
  550, 1800, 96,   // 4 開始周波数 終了周波数 切替スピード
  1150, 1150, 111, // 5 開始周波数 終了周波数 切替スピード
  -1,              // 6 終了コード

  //パターン3 E231-0,500,etc
  350, 350, 6,     // 1 開始周波数 終了周波数 切替スピード
  350, 980, 31,    // 2 開始周波数 終了周波数 切替スピード
  580, 900, 86,    // 3 開始周波数 終了周波数 切替スピード
  530, 1150, 101,  // 4 開始周波数 終了周波数 切替スピード
  1150, 1150, 121, // 5 開始周波数 終了周波数 切替スピード
  -1,              // 6 終了コード

  //パターン4 E231-1000
  1050, 1050, 16,  // 1 開始周波数 終了周波数 切替スピード
  1050, 700, 41,   // 2 開始周波数 終了周波数 切替スピード
  700, 2900, 44,   // 3 開始周波数 終了周波数 切替スピード
  780, 880, 46,    // 4 開始周波数 終了周波数 切替スピード
  580, 900, 86,    // 5 開始周波数 終了周波数 切替スピード
  530, 1150, 101,  // 6 開始周波数 終了周波数 切替スピード
  1150, 1150, 121, // 7 開始周波数 終了周波数 切替スピード
  -1,              // 8 終了コード

  //パターン5 E233, etc
  750, 750, 31,    // 1 開始周波数 終了周波数 切替スピード
  300, 1000, 106,  // 2 開始周波数 終了周波数 切替スピード
  1000, 1000, 121, // 3 開始周波数 終了周波数 切替スピード
  -1,              // 4 終了コード

  //パターン6 KQ 2100 (SI-GTO)
  780, 780, 22,  // 1 開始周波数 終了周波数 切替スピード
  780, 1150, 35, // 2 開始周波数 終了周波数 切替スピード
  780, 970, 45,  // 3 開始周波数 終了周波数 切替スピード
  600, 980, 111, // 4 開始周波数 終了周波数 切替スピード
  580, 580, 131, // 5 開始周波数 終了周波数 切替スピード
  -1,            // 6 終了コード

  //パターン7 N1000 (SI-GTO)
  780, 780, 16,  // 1 開始周波数 終了周波数 切替スピード
  780, 970, 19,  // 2 開始周波数 終了周波数 切替スピード
  780, 820, 22,  // 3 開始周波数 終了周波数 切替スピード
  780, 1150, 35, // 4 開始周波数 終了周波数 切替スピード
  780, 970, 45,  // 5 開始周波数 終了周波数 切替スピード
  600, 980, 111, // 6 開始周波数 終了周波数 切替スピード
  580, 580, 131, // 7 開始周波数 終了周波数 切替スピード
  -1,            // 8 終了コード

  //パターン8 MITSUBISHI GTO
  580, 720, 16,    // 1 開始周波数 終了周波数 切替スピード
  720, 720, 26,    // 2 開始周波数 終了周波数 切替スピード
  460, 650, 36,    // 3 開始周波数 終了周波数 切替スピード
  450, 1250, 110,  // 4 開始周波数 終了周波数 切替スピード
  1250, 1250, 131, // 5 開始周波数 終了周波数 切替スピード
  -1,              // 6 終了コード
  
  //パターン9 Toyo-GTO
  580, 580, 7,     // 1 開始周波数 終了周波数 切替スピード
  580, 750, 20,    // 2 開始周波数 終了周波数 切替スピード
  380, 650, 31,    // 3 開始周波数 終了周波数 切替スピード
  450, 1250, 110,  // 4 開始周波数 終了周波数 切替スピード
  1250, 1250, 131, // 5 開始周波数 終了周波数 切替スピード
  -1,              // 6 終了コード

  //パターン10 Toyo-IGBT
  1050, 1050, 26, // 1 開始周波数 終了周波数 切替スピード
  600, 930, 41,   // 2 開始周波数 終了周波数 切替スピード
  600, 950, 95,   // 3 開始周波数 終了周波数 切替スピード
  950, 950, 111,  // 4 開始周波数 終了周波数 切替スピード
  580, 580, 131,  // 5 開始周波数 終了周波数 切替スピード
  -1,             // 6 終了コード

  //パターン11 Toei 5300(Akuma)
  780, 780, 18,
  440, 460, 22,
  460, 570, 36,
  500, 1150, 105,
  1150, 1150, 111,
  -1,            // 6 終了コード

  //パターン12 TOSHIBA 3LevIGBT
  730, 730, 11,
  510, 1150, 101,
  1150, 1150, 111,
  -1,            // 7 終了コード

};

//RAM消費を抑える為の配列。
//[]内の数字を3n(n=上記パターンの中で最も行数が長いパターンの行数)以上に設定して下さい。
//あまり数字を大きくし過ぎるとメモリ不足になるため、むやみに大きくしないように。
//初期値は45。
int BsoundData[45];

// 走行音パターン12種soundDataの各パターンデータ開始位置インデックスを保持。
// 実際の値設定は、setup処理内にて終了コード(-1)を検出し動的に設定する。
int BdataNum[13] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

// ***********************************************
// 定数・変数定義 ここまで
// ***********************************************


// **********************************************************************************************
// 関数定義 ここから
// **********************************************************************************************

// ***********************************************
// マスコン位置を検出する。
// ロータリースイッチからのボリューム値を拾い、
// 適度な値に変換する処理。
// 
// 引数: アナログピンから取得した値(Max:1023)
// 戻値: 1(=EB)~12(=P5) 
// ***********************************************
// ロータリースイッチが12段階切替なので、
// 入力を12段階に分ける。
// 3番ピンからのanalogRead値は・・・(実測値)
// ・ロータリースイッチの位置が 1の時:11kΩ ⇒   83
// ・ロータリースイッチの位置が 2の時:10kΩ ⇒  169
// ・ロータリースイッチの位置が 3の時: 9kΩ ⇒  254
// ・ロータリースイッチの位置が 4の時: 8kΩ ⇒  340
// ・ロータリースイッチの位置が 4の時: 7kΩ ⇒  425
// ・ロータリースイッチの位置が 4の時: 6kΩ ⇒  511
// ・ロータリースイッチの位置が 4の時: 5kΩ ⇒  596
// ・ロータリースイッチの位置が 4の時: 4kΩ ⇒  682
// ・ロータリースイッチの位置が 4の時: 3kΩ ⇒  767
// ・ロータリースイッチの位置が 4の時: 2kΩ ⇒  853
// ・ロータリースイッチの位置が11の時: 1kΩ ⇒  938
// ・ロータリースイッチの位置が12の時: 0kΩ ⇒ 1023
int getMasconVol(int vol) {
  int ans = 0;
  // マスコンロータリースイッチから読み取った値を、小数点も扱える形で
  // 0~1023に収まるようにする。12接点なので12等分。
  // 四捨五入相当の処理をする⇒値に+0.5した上で、小数点以下を切り捨てるためint型に変換。
  ans = (int)(((double) analogRead(P_MASCON)) / (1024 / 12) + 0.5);
  return ans;
}

// ***********************************************
// マスコン位置を表示。LCDの右下2桁へ。
// ついでにスピード値も表示。なんとなくKATO103系の
// 動力ユニットでスケールスピード(?)
// 動力ユニットによって同じ電圧でも実速度が異なる
// ので、あくまでも目安値。
// -----------------------------------------------
// 開発モード(isDev=True)の時は、目標スピード値と
// 加速率ダイヤル、常点灯ダイヤルの値も表示する。
// ***********************************************
void dispMasPos(int masPos) {
  lcd.setCursor(14, 1);
  lcd.print(masconPosName[masPos - 1]);

  // spdも表示
  dispSpd = spd / 10000;
  lcd.setCursor(4, 1);
  if (dispSpd / 100 > 0) {
    // 3digits.
  } else {
    if (dispSpd / 10 > 0) {
      // 2digits.
      lcd.print(" ");
    } else {
      // 1digit.
      lcd.print("  ");
    }
  } 
  lcd.print(dispSpd);

/*
  if (isDev) {
    //                         "0123456789012345"
    // ついでにorderSpdも表示。"SPD:nnn>nnn   XX"
    dispOrderSpd = orderSpd / 10000;
    lcd.setCursor(8, 1);
    if (dispOrderSpd / 100 > 0) {
      // 3digits.
    } else {
      if (dispOrderSpd /10 > 0) {
        // 2digits.
        lcd.print(" ");
      } else {
        // 1digit.
        lcd.print("  ");
      }
    } 
    lcd.print(dispOrderSpd);

    // ----------------------  "0123456789012LLL"  ※LLL=常点灯ダイヤル値
    // ついでにorderSpdも表示。"Spd:nnn>nnn A XX"  ※A=加速率ダイヤル値 XX=マスコン値
    lcd.setCursor(12, 0);
    lcd.print(" ");
    if (lightVol < 100 && lightVol >= 10) {
      lcd.print(" ");
    } else if (lightVol < 10) {
      lcd.print("  ");
    }
    lcd.print(lightVol);
    lcd.setCursor(12, 1);
    lcd.print(accelVol);
  }
*/

}

// ***********************************************
// setup関数。最初に1度だけ実行。
// ***********************************************
void setup() {
  // PWM PIN設定
  pinMode(P_PWM2A, OUTPUT); // 常点灯用PWM出力
  pinMode(P_PWM2B, OUTPUT); // 走行用PWM出力
  pinMode(P_PWM1B, OUTPUT); // 何用?

  // WELCOMEメッセージ表示
  lcd.begin(16, 2);  // 16桁、2行タイプと宣言。
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(MOJI1);
  delay(2000);
  lcd.setCursor(0, 1);
  lcd.print(MOJI2);
  delay(2000);

//  isDev = (MC_P5 == getMasconVol(analogRead(P_MASCON)));  // マスコンがP5の時だけ開発モード。

  lcd.clear();

  // 走行音パターン各開始位置取得処理。
  // soundData配列のうち、各パターンの最初の値となる配列インデックスをdataNumで保持する。
  // パターン1は常に先頭(=インデックス0)なので、dataNumにセットするのは省略。
  
  int idx = 0;
 
  for (i=1; i<13; i++) {
    while (pgm_read_word_near(&AsoundDataBase[idx]) != -1) {
      idx++;
    }
    idx++;
    AdataNum[i] = idx;
  }

  idx = 0;
  
  for (i=1; i<13; i++) {
    while (pgm_read_word_near(&BsoundDataBase[idx]) != -1) {
      idx++;
    }
    idx++;
    BdataNum[i] = idx;
  }

  // 走行音パターン取得
  lcd.home();  // 1文字目、1行目
  for (i=0; i<2; i++) {
    lcd.clear();
    delay(180);
    lcd.print("Select SoundPtn.");
    delay(480);
  }
  delay(240);
  lcd.clear();
  lcd.print("SoundPtn.");
  lcd.setCursor(0, 1);
  lcd.print(" Revolve MasCon.");
  //delay(2000);
  
  // ロータリースイッチを10秒動かさないと決定。
  // ただし、何も動かしてないときはずっと待ち続ける。
  unsigned long sttTime = 0;
  unsigned long progress = 0;
  boolean isMoved = false; // マスコンを動かしたらTrueにする
  masconPos = getMasconVol(analogRead(P_MASCON)); // 初期値

  while (!isMoved) {
    inputMascon = getMasconVol(analogRead(P_MASCON)); // 現在値
    // 読み取った最新マスコン位置が、以前に覚えていたマスコン位置と異なる場合
    // =ロータリースイッチを動かしたと判断。
    isMoved = (masconPos != inputMascon);
  }
  sttTime = millis();  // ロータリースイッチをいじったので、初期化。
  do {
    while (true) {
      inputMascon = getMasconVol(analogRead(P_MASCON)); // 現在値
      if (masconPos != inputMascon) {
        // 読み取った最新マスコン位置が、以前に覚えていたマスコン位置と
        // 異なる場合=ロータリースイッチを動かしたと判断。
        masconPos = inputMascon;// 新たなマスコン位置を覚えておく。
        sttTime = millis();  // ロータリースイッチをいじったので、初期化。
        lcd.setCursor(0, 1);  // 1文字目、2行目
        lcd.print(soundName[masconPos - 1]); // LCDに走行音名称を表示させる。
      } else {
        if ((millis() - sttTime) > DECISION_TIME * 1000) {
          // 選んでから所定時間が過ぎた。
          break; // ループを抜ける。
        } 
      }
      progress = millis() - sttTime;
      if (progress > 0) {
        lcd.setCursor(15, 0);
        lcd.print((String) (DECISION_TIME - (progress / 1000)));
      }
      delay(1);
    }
  } while (masconPos < MC_EB || masconPos > MC_P5); // 12種類から選択されたらループを抜け次の処理へ。

  // 走行音パターンほか選択したパターンに合わせて設定
  vvvfPtn = masconPos - 1;
  notch1  = (long) maxSpdData[vvvfPtn * 5    ] * 10000 - 1;
  notch2  = (long) maxSpdData[vvvfPtn * 5 + 1] * 10000 - 1;
  notch3  = (long) maxSpdData[vvvfPtn * 5 + 2] * 10000 - 1;
  notch4  = (long) maxSpdData[vvvfPtn * 5 + 3] * 10000 - 1;
  notch5  = (long) maxSpdData[vvvfPtn * 5 + 4] * 10000 - 1;
  stopSpd = (long) stopSpdData[vvvfPtn]        *  1000 - 1;

  k = 0;
  
  for (i = AdataNum[vvvfPtn]; i<AdataNum[vvvfPtn+1]; i++)
  {
  AsoundData[k] = pgm_read_word_near(&AsoundDataBase[i]);
  k++;
  }

  k = 0;
 
  for (i = BdataNum[vvvfPtn]; i<BdataNum[vvvfPtn+1]; i++)
  {
  BsoundData[k] = pgm_read_word_near(&BsoundDataBase[i]);
  k++;
  }
  
  // 決定したパターンを点滅表示
  for (i=0; i<3; i++) {
    lcd.clear();
    delay(180);
    lcd.print("Sound Pattern is");
    lcd.setCursor(0, 1);  // 1文字目、2行目
    lcd.print(soundName[vvvfPtn]);
    delay(480);
  }
  delay(240);
  // マスコン位置を惰行か制動にしてもらう注意喚起。急に走り出さないようにするため。
  // マスコン位置が1~6、つまり 非常 か 制動5~1 に設定しないと
  // 注意喚起され続け、次の処理へ進めない。
  masconPos = getMasconVol(analogRead(P_MASCON));
  while (masconPos < MC_EB || masconPos > MC_B1) {
    lcd.clear();
    lcd.print("Caution!");
    lcd.setCursor(0, 1);
    lcd.print("Apply Brake.");
    delay(240);
    masconPos = getMasconVol(analogRead(P_MASCON));
  }
  
  lcd.clear();
  lcd.print("Ready.");
  delay(720);
  lcd.clear();
  lcd.print(soundName[vvvfPtn]);
  lcd.setCursor(0, 1);
  lcd.print("Spd:");
  lcd.setCursor(7, 1);
//  if (isDev) lcd.write(0x7E); // 右向き矢印
}

// ***********************************************
// ***********************************************
// loop関数
// ***********************************************
// ***********************************************
void loop() {
  //動作速度確保のため、音データをメモリに格納。

  accelVol = analogRead(P_ACCEL_DIAL) / 128 + 1; // analogRead 0~1023 → 1~8
  lightVol = analogRead(P_LIGHT_DIAL) / 8;       // analogRead 0~1023 → 0~127

  inputMascon = getMasconVol(analogRead(P_MASCON));
  if (inputMascon >= MC_EB && inputMascon <= MC_P5) {
    // 選択し得る12段階のマスコン位置であると判断。
    if (compareMascon == inputMascon) {
      masconPos = inputMascon;
    } else if (compareMascon > inputMascon) {
      compareMascon--;
    } else if (compareMascon < inputMascon) {
      compareMascon++;
    }
  }

  switch (masconPos) {
  case 1:
    // 非常
    orderSpd = 0;
    spd = 0;
    mode = MD_STOP;
    break;
  case 2:
    // 制動5
    orderSpd = 0;
    kasoku = accelVol * BRAKE_RATIO * 5;
    mode = MD_BRAKE;
    break;
  case 3:
    // 制動4
    orderSpd = 0;
    kasoku = accelVol * BRAKE_RATIO * 4;
    mode = MD_BRAKE;
    break;
  case 4:
    // 制動3
    orderSpd = 0;
    kasoku = accelVol * BRAKE_RATIO * 3;
    mode = MD_BRAKE;
    break;
  case 5:
    // 制動2
    orderSpd = 0;
    kasoku = accelVol * BRAKE_RATIO * 2;
    mode = MD_BRAKE;
    break;
  case 6:
    // 制動1
    orderSpd = 0;
    kasoku = accelVol * BRAKE_RATIO * 1;
    mode = MD_BRAKE;
    break;
  case 7:
    // 惰行
    orderSpd = 0;
    kasoku = accelVol * BRAKE_RATIO * 2;
    mode = MD_NTRL;
    break;
  case 8:
    // 力行1
    orderSpd = notch1;
    kasoku = accelVol * ACCEL_RATIO * 0.5;
    mode = MD_ACCEL;
    break;
  case 9:
    // 力行2
    orderSpd = notch2;
    kasoku = accelVol * ACCEL_RATIO * 0.6;
    mode = MD_ACCEL;
    break;
  case 10:
    // 力行3
    orderSpd = notch3;
    kasoku = accelVol * ACCEL_RATIO * 0.7;
    mode = MD_ACCEL;
    break;
  case 11:
    // 力行4
    orderSpd = notch4;
    kasoku = accelVol * ACCEL_RATIO * 0.9;
    mode = MD_ACCEL;
    break;
  case 12:
    // 力行5
    orderSpd = notch5;
    kasoku = accelVol * ACCEL_RATIO;
    mode = MD_ACCEL;
    break;
  }

  for ( k = 0; k < UNTI_CHAT_RATE; k++ ) { // チャタリング防止ループ
    dispMasPos(masconPos);
    if (spd < orderSpd) {
      if (orderSpd - spd <= kasoku) {
        spd = orderSpd;
      } else {
        spd = spd + kasoku;
      }
    }

    if ( spd > orderSpd ) {
      if ( masconPos >= MC_N ) {
        // 惰行or力行
        if ( spd - orderSpd <= kasoku / BRAKE_RATIO ) {
          spd = orderSpd;
        } else {
          spd = spd - kasoku / BRAKE_RATIO;
        }
      } else {
        // 制動
        if ( spd - orderSpd <= kasoku ) {
          spd = orderSpd;
        } else {
          spd = spd - kasoku;
        }
      }
    }

    if ( spd < stopSpd && masconPos < MC_P1 ) {
      // 惰行or制動ノッチのとき、ピタ停止速度値よりも現行速度が下回ったらピタッと停止させる。
      spd = 0;
      mode = MD_STOP;
    }

    // 走行時の常点灯PWM DUTY比
    // 周波数は31.2kHz = CPU周波数(16MHz) / 2 / 分周256
    // OCR2Aはこの数値からTOP255がDUTY比
    OCR2A = 255 - lightVol;

    // 走行用出力。
    // SPD / 128 / 4 / 10 / DUTY比 ( 1.5 + speed / 390)
    OCR2B = (int) (spd / ( 7680 + spd / 390 ));

    // soundDataは開始周波数 終了周波数 切替スピードの3つの値で構成されているので+3ずつカウントアップ。
    // soundDataの定義数は無制限(-1を終了コードとしている)

    //int j; // ループカウンター
    
    if (mode == MD_ACCEL) {
    
      for (i = 0; ;i = i + 3) {
          AsttFrq = AsoundData[i];
          AendFrq = AsoundData[i + 1];
            if (i == 0) {
            AsttSpd = 0;
            AendSpd = AsoundData[i + 2];
            }
            else { 
            AsttSpd = AsoundData[i - 1];
            AendSpd = AsoundData[i + 2];
            }
  
        if (i == 0 && BsttFrq == -1) { // 走行音が設定されていない場合の処理
          OCR1A = 421; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq 19000 = 421
          OCR1B = 421 - 421 * (word)lightVol / 245 ; // 音を出すPWMのDUTY比 
          TCCR1B = B00010001;
          TCCR1A = B00110001;
          TCCR2B = B00000001;
          TCCR2A = B00100001;
          break;
        }

        if (mode == MD_BRAKE) { // 変調モード切替
        break; 
        }
        
        if (mode == MD_STOP) { // 変調モード切替
        break; 
        }

        if (mode == MD_NTRL) { // 変調モード切替
        break; 
        }
  
        if (AsttFrq == -1) { // 高速時 VVVF音停止
          Afrq = 31500;
          OCR1A = (word)(8000000 / Afrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
          OCR1B = (word)(8000000 / Afrq - 8000000 / Afrq / 99); // 音を出すPWMのDUTY比 100/99 = 1%
          TCCR1B = B00010001;
          TCCR1A = B00110001;
          TCCR2B = B00000001; // PWM 2A/2B 分周1
          TCCR2A = B11100001; // PWM 2A/2B 出力
          break;
        }
  
        // VVVF音
        if ((spd >= AsttSpd * 10000) && (spd < AendSpd * 10000)) {
          Afrq = ( AendFrq * 10 - AsttFrq * 10 ) / ( AendSpd - AsttSpd ) * ( spd - AsttSpd * 10000 ) / 100000 + AsttFrq;
          if ( Afrq < 123 )        Afrq = 123;
          else if ( Afrq > 31500 ) Afrq = 31500;
          if (mode == MD_NTRL) Afrq = 123; // 惰行のとき。
          OCR1A = (word)(8000000 / Afrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
          OCR1B = (word)(8000000 / Afrq - 8000000 / Afrq / 15); // 音を出すPWMのDUTY比 100/15 = 6.6%
          TCCR1B = B00010001;
          TCCR1A = B00110001;
          TCCR2B = B00000001; // PWM 2A/2B 分周1
          TCCR2A = B11100001; // PWM 2A/2B 出力
          break;
        }
      }
    }

    else if (mode == MD_BRAKE) {
      for (i = 0; ;i = i + 3) {
          BsttFrq = BsoundData[i];
          BendFrq = BsoundData[i + 1];
            if (i == 0) {
            BsttSpd = 0;
            BendSpd = BsoundData[i + 2];
            }
            else { 
            BsttSpd = BsoundData[i - 1];
            BendSpd = BsoundData[i + 2];
            }
  
        if (i == 0 && BsttFrq == -1) { // 走行音が設定されていない場合の処理
          OCR1A = 421; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq 19000 = 421
          OCR1B = 421 - 421 * (word)lightVol / 245 ; // 音を出すPWMのDUTY比 
          TCCR1B = B00010001;
          TCCR1A = B00110001;
          TCCR2B = B00000001;
          TCCR2A = B00100001;
          break;
        }

        if (mode == MD_ACCEL) { // 変調モード切替
        break; 
        }
        
        if (mode == MD_STOP) { // 変調モード切替
        break; 
        }
       
        if (mode == MD_NTRL) { // 変調モード切替
        break; 
        }
         
        if (BsttFrq == -1) { // 高速時 VVVF音停止
          Bfrq = 31500;
          OCR1A = (word)(8000000 / Bfrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
          OCR1B = (word)(8000000 / Bfrq - 8000000 / Bfrq / 99); // 音を出すPWMのDUTY比 100/99 = 1%
          TCCR1B = B00010001;
          TCCR1A = B00110001;
          TCCR2B = B00000001; // PWM 2A/2B 分周1
          TCCR2A = B11100001; // PWM 2A/2B 出力
          break;
        }
  
        // VVVF音
        if ((spd >= BsttSpd * 10000) && (spd < BendSpd * 10000)) {
          Bfrq = ( BendFrq * 10 - BsttFrq * 10 ) / ( BendSpd - BsttSpd ) * ( spd - BsttSpd * 10000 ) / 100000 + BsttFrq;
          if ( Bfrq < 123 )        Bfrq = 123;
          else if ( Bfrq > 31500 ) Bfrq = 31500;
          if (mode == MD_NTRL) Bfrq = 123; // 惰行のとき。
          OCR1A = (word)(8000000 / Bfrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
          OCR1B = (word)(8000000 / Bfrq - 8000000 / Bfrq / 15); // 音を出すPWMのDUTY比 100/15 = 6.6%
          TCCR1B = B00010001;
          TCCR1A = B00110001;
          TCCR2B = B00000001; // PWM 2A/2B 分周1
          TCCR2A = B11100001; // PWM 2A/2B 出力
          break;
        }
      }
    }

    else if (mode == MD_NTRL) { // 惰行 VVVF音停止
          OCR1A = 421; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq 19000 = 421
          OCR1B = 421 - 421 * (word)lightVol / 245 ; // 音を出すPWMのDUTY比 
          TCCR1B = B00010001;
          TCCR1A = B00110001;
          TCCR2B = B00000001;
          TCCR2A = B00100001; // PWM 2A停止(常点灯部分 代わりに1Bで常点灯出力) 2B出力
    }

    else { // 停止時 音停止
          OCR1A = 421; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq 19000 = 421
          OCR1B = 421 - 421 * (word)lightVol / 255 ; // 音を出すPWMのDUTY比 
          TCCR1B = B00010001;
          TCCR1A = B00110001;
          TCCR2B = B00000001;
          TCCR2A = B00000001; // PWM 2A(常点灯部分 代わりに1Bで常点灯出力 19kHzで出力したいから)と2B停止
          break;
    }
  }
}