<更新内容>

(V3.20)

・惰行時・各力行ノッチ最高速度以上時の減速率計算方法を変更しました。

 

一部の変調音は選択に以下のソフト(PPO for PC)の使用が必須となります。

PPO for PCは以下のリンクをクリックするとダウンロードできます。

同梱のReadmeを熟読の上、自己責任でご利用ください。

<通常版>

PPO for PC (V1.40)

特に理由が無い場合は通常版をご利用下さい。

なお本プログラムの使用には、Microsoft .NET Framework 4.8 再頒布可能パッケージをインストールする必要があります。

 

<スタンドアロン版>

PPO for PC (V1.40) standalone(x64)

PPO for PC (V1.40) standalone(x86)

.NETって何?というものぐさな方のためにスタンドアロン版も用意しました。(Google Driveに飛びます)

通常版に比べ容量が非常に大きくなっておりますのでご注意ください。

64bit版Windowsをご利用の場合はx64版、32bit版Windowsをご利用の場合はx86版をご利用下さい。

なおスタンドアロン版につきましては、一応動作確認はしておりますが動作の保証はできかねますので基本的には通常版をご利用頂けますようお願い申し上げます。

また通常版、スタンドアロン版の機能は共通です。

 

<PPO for PCのモニタリング機能について>

現在速度とノッチを文字で表示するだけです。グラスコックピットみたいな表示を作りたい方は頑張って下さい。

私はそこまで頑張れませんでした。

 

 

加速度について

(以下における[km/h]はスケールスピードとします)

0km/h時の加速度及び減速度(V1.20以前の加速度)を1として、速度が上がるごとに加速度及び減速度が減衰するよう、プログラムを修正しました。

この修正により、模型の運転感覚がより実車に近づいています。

 

<加速度について>

音データごとに定加速度領域が設定可能になりました。定加速度の終わる速度をコードに入力して設定します。

定加速度領域を超えると、速度が上がるにつれて

f(x) = x^y (xはスケールスピード)

の曲線に沿って加速度が減衰していきます。

yは音データごとに設定可能です。

そのため、最高速度に対して定加速度領域が低すぎると加速できなくなりますので注意してください。

※あくまで実車の「ような」挙動です。このパワーパックでは簡単の為、定出力領域と特性領域をまとめて適当な曲線に置き換えています。

 

<減速度について>

速度が下がるにつれて、

g(x) = 1 ー ((x*y) / (P5の最高速度))^(1/2) (xはスケールスピード)

の曲線に沿って減速度が増加していきます。つまり適当。

こちらもyは音データごとに設定可能です。

スケールスピードにおいて、加速度を3.5km/h/sに合わせたとき120km/hから約560m~570mで止まるよう調整するとこのくらいになりました。

 

式だと非常にわかりにくいので、グラフを用意しました。なおこのグラフは新1000形(シーメンスGTO)の設定値です。

※「当パワーパックの」設定値です!実車の性能ではありません!

 

ソースコード

/*
***************************************************************************************
WhiteBear Controller Next Generation
***************************************************************************************
・PWM制御による鉄道模型用コントローラー。
・12段階のロータリースイッチを用い、ワンハンドルマスコンを模擬する。
・マスコンはP5-B5 + EB の12段。
・各種状況はキャラクタLCD(16文字x2段)で表示。
・力行各段の最高速度を設定可能。設定はソースコード内であらかじめ設定。
・ノッチによる加速度の違いをついでに再現。
・速度に応じた加速度の変化もだいたい再現。
・PWM周波数の変調により、VVVFなど制御機器の音を再現。
・変調音は最大255種類までプログラム可能。(メモリ容量によってはプログラム可能な最大数が少なくなります)
・加速時と減速時で異なる変調音を設定可能。
 ・外部制御モードを搭載。ノッチ反応速度と走行音を外部から随時変更可能。
***************************************************************************************
更新履歴
2019/07/30 V1.00 初期版。
2019/10/11 V1.10 音データをフラッシュメモリ格納に変更し、RAM不足を解消。
2020/01/30 V1.20 USBによる外部制御モードの追加
2020/02/21 V2.00 速度制御方式の変更(加速度変動機能)及びバグ修正
2020/04/25 V2.10 指定した周波数で出力されないことがあるバグを修正
2020/05/10 V2.20 マスコンのチャタリング対策を強化
2020/06/17 V2.21 マスコンのチャタリング対策を更に強化
2020/09/22 V2.30 加速度調節の細分化(8段階→16段階)、RAM使用量の削減
2020/11/17 V3.00 PWM周波数および出力設定の変更、加速度調節の細分化(16段階→32段階)、
常点灯の感度調整機能追加、EBの動作変更、スケールスピード調整機能追加、
最高速度400km/hに変更(モーターの性能により400km/hに届かない場合もあります)
2021/07/03 V3.10 速度及びノッチ情報について外部に送信するように変更。外部からモニタリングが
できるようになりました。
加速曲線と減速度を音データ毎に決められるようになりました。
表示速度の変化率に対するDuty比の変化率を調整できるようにしました。
2022/07/15 V3.20 惰行時・各力行ノッチ最高速度以上時の減速率計算方法を変更しました。
***************************************************************************************
*/
#include <avr/io.h> // ATmega328P用のヘッダファイル
#include <avr/pgmspace.h>
#include <LiquidCrystal.h> // キャラクタ液晶ディスプレイ制御用のヘッダファイル
// ***********************************************
// ユーザー設定領域
// 環境に合わせて各値を適宜調整してください。
// ***********************************************
#define SOUNDNUM 16 //走行音データの数。13番目以降のデータを選択するには外部通信端末またはPC用ソフトウェアとの接続が必要です。PC用ソフトウェアはHP上に公開中。
#define STOPSPD 9 //ピタッと止めるための値。環境に合わせて調整可能
#define SDATANUM 45 //3n(n=最も行数が多い音データの行数)以上の値に設定して下さい。あまり数字を大きくし過ぎるとメモリ不足になるため、むやみに大きくしないように。
#define ANTI_CHAT_RATE_NOUSB 10 //外部通信なしの際のチャタリング防止ループの値
const int DECISION_TIME = 3; // setup時に走行パターンを決める際の、確定までの時間。秒。
const int ACCEL_RATIO = 5; // (加速率調整小型ボリュームからの入力値) × ACCEL_RATIO ⇒ 加速率
const int BRAKE_RATIO = 2; // (加速率調整小型ボリュームからの入力値) × BRAKE_RATIO × マスコンブレーキ値(1~5) ⇒ 減速率
const int MAS_CHAT = 80; //ロータリスイッチが中間で止まった時の異常な指令を回避する。EBに投入した時のアナログ値を少し下回る値を設定。
const float lmax = 2; //常点灯ボリュームの最大値。lmax=1→出力MAX10%まで、1.5→出力MAX15%まで、のようにボリュームの範囲を調整可能。選択可能な範囲を狭くするほど微調整が可能となる。
const float sscale = 3; //表示速度に対するDuty比の倍率。環境に合わせ実測して調整。
const float adrate = 0.5; //加速度及び減速度の変化に対するDuty比の変化率。環境に合わせ実測して調整。
const float coast = 0.07; //惰行時最高速度以上時の減速率。
const float pcoast = 0.01; //各力行ノッチ最高速度以上時の減速率。
/*
sscaleとadrateの関係について:
sscaleは表示速度に対するduty比を全域にわたって調整します。グラフをそのまま上下に動かすイメージです。
主に列車の起動速度調整に使用します。
一方、adrateは表示速度の変化に対するDuty比の変化率を調整します。
加速時を例とすると、表示速度の上昇速度に対するDuty比の上昇速度を調整します。
加速度ボリュームは表示速度の変化率を調整するのに対し、adrateは表示速度に対する実速度の変化率を調整する項目であるといえます。
*/
boolean viewduty = false; //(開発用)走行中、走行音名の代わりにDUTY比を表示します。
// ***********************************************
// 走行音データ領域
// データの追加や変更を行う場合は慎重に行ってください。
// ***********************************************
// 走行音名称。16桁で指定すること
char* soundName[SOUNDNUM] = {
" 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",
"MITSUBISHI Chop.",
"N1000 (SUS-IGBT)",
"N1000 (SI-IGBT) ",
" Toyo-IGBT(AE) "
};
// 各パターンの各ノッチ位置での最高速度値を配列で保持。
// 左から順に 1ノッチ, 2ノッチ, 3ノッチ, 4ノッチ, 5ノッチ。
const int maxSpdDataBase[] PROGMEM = {
31, 46, 101, 111, 111, //パターン 1 201
41, 56, 101, 111, 111, //パターン 2 209,70-000,etc
41, 56, 111, 121, 121, //パターン 3 E231-0,500,etc
41, 56, 111, 121, 121, //パターン 4 E231-1000
41, 56, 111, 121, 121, //パターン 5 E233, etc
51, 66, 121, 131, 131, //パターン 6 KQ 2100 (SI-GTO)
51, 66, 121, 131, 131, //パターン 7 N1000 (SI-GTO)
51, 66, 121, 131, 131, //パターン 8 MITSUBISHI-GTO
51, 66, 121, 131, 131, //パターン 9 Toyo-GTO
51, 66, 121, 131, 131, //パターン10 Toyo-IGBT
41, 56, 101, 111, 111, //パターン11 Toei 5300(Akuma)
41, 56, 101, 111, 111, //パターン12 TOSHIBA 3LevIGBT
41, 56, 101, 111, 111, //パターン13 MITSUBISHI Chop.
51, 66, 121, 131, 131, //パターン14 N1000 (SUS-IGBT)
51, 66, 121, 131, 131, //パターン15 N1000 (SI-IGBT)
51, 66, 151, 161, 161 //パターン16 Toyo-IGBT(AE)
};
//定加速度領域値。
//指定した速度まで一定の加速度を保ちます。
int keepac[SOUNDNUM] = {37, 39, 39, 39, 39, 55, 55, 55, 55, 59, 50, 50, 45, 59, 55, 85};
//加速曲線。
//数値を大きくすると加速性能が良くなります。お好みで調整して下さい。
const float Aracurve[SOUNDNUM] = {1.2, 1.3, 1.4, 1.4, 1.5, 1.55, 1.55, 1.5, 1.5, 1.4, 1.4, 1.45, 1.45, 1.5, 1.5, 1.4};
//減速度。
//数値を大きくすると減速性能が良くなります。お好みで調整して下さい。
const float Arbcurve[SOUNDNUM] = {0.13, 0.15, 0.15, 0.15, 0.16, 0.14, 0.14, 0.14, 0.15, 0.17, 0.14, 0.14, 0.14, 0.14, 0.14, 0.17};
// 音データ。
// 設定可能周波数 150Hz~100kHz(値は整数で指定)
// ~~~(例) パターン3の場合~~~
// 1行目 速度0~2の間:380Hzで音程変化なし
// 2行目 速度3~11の間:380Hzから980Hzまで変化する
//加速時用の音データ。
const int AsoundDataBase[] PROGMEM = {
//パターン1 201
590, 590, 66, // 1 開始周波数 終了周波数 切替スピード
550, 980, 101, // 2 開始周波数 終了周波数 切替スピード
-1, // 3 終了コード
//パターン2 209,70-000,etc
230, 230, 2, // 1 開始周波数 終了周波数 切替スピード
90, 90, 3, // 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, 2, // 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
760, 760, 31, // 1 開始周波数 終了周波数 切替スピード
350, 1150, 101, // 2 開始周波数 終了周波数 切替スピード
1150, 1150, 121, // 3 開始周波数 終了周波数 切替スピード
-1, // 4 終了コード
//パターン6 KQ 2100 (SI-GTO)
350, 350, 2, // 1 開始周波数 終了周波数 切替スピード
390, 390, 3, // 2 開始周波数 終了周波数 切替スピード
440, 440, 4, // 3 開始周波数 終了周波数 切替スピード
490, 490, 5, // 4 開始周波数 終了周波数 切替スピード
530, 530, 6, // 5 開始周波数 終了周波数 切替スピード
580, 580, 7, // 6 開始周波数 終了周波数 切替スピード
650, 650, 8, // 7 開始周波数 終了周波数 切替スピード
700, 700, 9, // 8 開始周波数 終了周波数 切替スピード
780, 780, 23, // 9 開始周波数 終了周波数 切替スピード
510, 980, 111, //10 開始周波数 終了周波数 切替スピード
580, 580, 131, //11 開始周波数 終了周波数 切替スピード
-1, //12 終了コード
//パターン7 N1000 (SI-GTO)
350, 350, 2, // 1 開始周波数 終了周波数 切替スピード
390, 390, 3, // 2 開始周波数 終了周波数 切替スピード
440, 440, 4, // 3 開始周波数 終了周波数 切替スピード
490, 490, 5, // 4 開始周波数 終了周波数 切替スピード
530, 530, 6, // 5 開始周波数 終了周波数 切替スピード
580, 580, 7, // 6 開始周波数 終了周波数 切替スピード
650, 650, 8, // 7 開始周波数 終了周波数 切替スピード
700, 700, 9, // 8 開始周波数 終了周波数 切替スピード
780, 780, 16, // 9 開始周波数 終了周波数 切替スピード
950, 970, 18, //10 開始周波数 終了周波数 切替スピード
850, 970, 21, //11 開始周波数 終了周波数 切替スピード
800, 970, 23, //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, 19, // 1 開始周波数 終了周波数 切替スピード
490, 780, 31, // 2 開始周波数 終了周波数 切替スピード
420, 1250, 110, // 3 開始周波数 終了周波数 切替スピード
1250, 1250, 131, // 4 開始周波数 終了周波数 切替スピード
-1, // 5 終了コード
//パターン10 Toyo-IGBT
1050, 1050, 26, // 1 開始周波数 終了周波数 切替スピード
630, 1300, 41, // 2 開始周波数 終了周波数 切替スピード
650, 1150, 111, // 3 開始周波数 終了周波数 切替スピード
1150, 1150, 131, // 4 開始周波数 終了周波数 切替スピード
-1, // 5 終了コード
//パターン11 Toei 5300(Akuma)
780, 780, 11, // 1 開始周波数 終了周波数 切替スピード
450, 450, 16, // 2 開始周波数 終了周波数 切替スピード
500, 780, 22, // 3 開始周波数 終了周波数 切替スピード
430, 580, 25, // 4 開始周波数 終了周波数 切替スピード
400, 1150, 105, // 5 開始周波数 終了周波数 切替スピード
1150, 1150, 121, // 6 開始周波数 終了周波数 切替スピード
-1, // 7 終了コード
//パターン12 TOSHIBA 3LevIGBT
730, 730, 11, // 1 開始周波数 終了周波数 切替スピード
510, 730, 16, // 2 開始周波数 終了周波数 切替スピード
600, 1150, 101, // 3 開始周波数 終了周波数 切替スピード
1150, 1150, 111, // 4 開始周波数 終了周波数 切替スピード
-1, // 5 終了コード
//パターン13 MITSUBISHI Chop.
590, 590, 4, // 1 開始周波数 終了周波数 切替スピード
900, 900, 36, // 2 開始周波数 終了周波数 切替スピード
590, 590, 41, // 3 開始周波数 終了周波数 切替スピード
390, 390, 46, // 4 開始周波数 終了周波数 切替スピード
390, 1150, 101, // 5 開始周波数 終了周波数 切替スピード
1150, 1150, 111, // 6 開始周波数 終了周波数 切替スピード
-1, // 7 終了コード
//パターン14 N1000 (SUS-IGBT)
710, 710, 31, // 1 開始周波数 終了周波数 切替スピード
350, 1150, 101, // 2 開始周波数 終了周波数 切替スピード
1150, 1150, 131, // 3 開始周波数 終了周波数 切替スピード
-1, // 4 終了コード
//パターン15 N1000 (SI-IGBT)
1180, 1180, 16, // 1 開始周波数 終了周波数 切替スピード
1180, 1960, 33, // 2 開始周波数 終了周波数 切替スピード
460, 1150, 101, // 3 開始周波数 終了周波数 切替スピード
1150, 1150, 131, // 4 開始周波数 終了周波数 切替スピード
-1, // 5 終了コード
//パターン16 Toyo-IGBT(AE)
1000, 1000, 26, // 1 開始周波数 終了周波数 切替スピード
580, 1300, 41, // 2 開始周波数 終了周波数 切替スピード
650, 1150, 141, // 3 開始周波数 終了周波数 切替スピード
1150, 1150, 161, // 4 開始周波数 終了周波数 切替スピード
-1 // 5 終了コード
};
//減速時用の音データ。
const int BsoundDataBase[] PROGMEM = {
//パターン1 201
10000, 10000, 6, // 1 開始周波数 終了周波数 切替スピード
590, 590, 61, // 2 開始周波数 終了周波数 切替スピード
590, 650, 76, // 3 開始周波数 終了周波数 切替スピード
550, 980, 101, // 4 開始周波数 終了周波数 切替スピード
-1, // 5 終了コード
//パターン2 209,70-000,etc
150, 150, 6, // 1 開始周波数 終了周波数 切替スピード
150, 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
760, 760, 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, // 1 開始周波数 終了周波数 切替スピード
440, 460, 22, // 2 開始周波数 終了周波数 切替スピード
460, 570, 36, // 3 開始周波数 終了周波数 切替スピード
500, 1150, 105, // 4 開始周波数 終了周波数 切替スピード
1150, 1150, 121, // 5 開始周波数 終了周波数 切替スピード
-1, // 6 終了コード
//パターン12 TOSHIBA 3LevIGBT
730, 730, 11, // 1 開始周波数 終了周波数 切替スピード
510, 1150, 101, // 2 開始周波数 終了周波数 切替スピード
1150, 1150, 111, // 3 開始周波数 終了周波数 切替スピード
-1, // 4 終了コード
//パターン13 MITSUBISHI Chop.
900, 900, 36, // 1 開始周波数 終了周波数 切替スピード
590, 590, 41, // 2 開始周波数 終了周波数 切替スピード
390, 390, 46, // 3 開始周波数 終了周波数 切替スピード
390, 1150, 101, // 4 開始周波数 終了周波数 切替スピード
1150, 1150, 111, // 5 開始周波数 終了周波数 切替スピード
-1, // 6 終了コード
//パターン14 N1000 (SUS-IGBT)
710, 710, 31, // 1 開始周波数 終了周波数 切替スピード
350, 1150, 101, // 2 開始周波数 終了周波数 切替スピード
1150, 1150, 131, // 3 開始周波数 終了周波数 切替スピード
-1, // 4 終了コード
//パターン15 N1000 (SI-IGBT)
1180, 1180, 16, // 1 開始周波数 終了周波数 切替スピード
1180, 2050, 31, // 2 開始周波数 終了周波数 切替スピード
780, 1150, 36, // 3 開始周波数 終了周波数 切替スピード
560, 980, 111, // 4 開始周波数 終了周波数 切替スピード
580, 580, 131, // 5 開始周波数 終了周波数 切替スピード
-1, // 6 終了コード
//パターン16 Toyo-IGBT(AE)
1000, 1000, 26, // 1 開始周波数 終了周波数 切替スピード
600, 880, 41, // 2 開始周波数 終了周波数 切替スピード
600, 950, 95, // 3 開始周波数 終了周波数 切替スピード
950, 950, 106, // 4 開始周波数 終了周波数 切替スピード
580, 580, 161, // 5 開始周波数 終了周波数 切替スピード
-1 // 6 終了コード
};
// ***********************************************
// ここから先はみだりに変更しないで下さい。
// 誤動作や故障の原因となります。
// ***********************************************
int ANTI_CHAT_RATE; // チャタリング防止ループの値。ノッチの反応速度調整に使用。USB接続では動的設定可能。
//外部通信用
int Result; //データ受信状況
int ReceiveData[] = {255, 255, 0}; //外部制御モード時に受け取った値
/*
送受信パケットは以下の様に定義する。
・送受信はHEX(16進)で行う。
・送信パケット=(ヘッダ)(モード1)(マスコン位置)(速度1)(速度2)の5バイトまたは(ヘッダ)(モード2)(受信結果)の3バイト。
 ヘッダ=255、モード1=254、モード2=253、マスコン位置=1(EB)~12(P5)。
 速度1=0~200、速度2=0~200。速度=速度1+速度2とする。
 受信結果=0(正常受信),=1(走行中またはブレーキノッチ未投入),=2(受信失敗)。
・受信パケット=(ヘッダ)(チャタリング防止ループ値)(走行音データ番号) の3バイト。ヘッダ=0
・チャタリング防止ループ値、走行音データ番号の値は1~255。
・よって走行音データは(理論上は)255種類まで搭載可能だが、増やせば当然メモリ食うので現実には限界がある。
・走行音は用意したデータ数より大きい番号を指定しないように送信側で制御を行うこと。
*/
const char *MOJI1 = " WCNG POWERPACK "; // Welcomeメッセージ。LCD1段目。16桁にすること。
const char *MOJI2 = "Program Ver 3.20"; // Welcomeメッセージ。LCD2段目。16桁にすること。
//使用ピン指定
const int P_MASCON = A3; // Arduino接続ピン番号:(Analog) ロータリースイッチ = A3
const int P_LIGHT_DIAL = A4; // Arduino接続ピン番号:(Analog) 常点灯調節ダイアル = A4
const int P_ACCEL_DIAL = A5; // 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) OCR1B出力 to Rail = D11
//マスコン用
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 isUSB; // 外部制御モードのときTrue。
int vvvfPtn; // 走行音パターン。
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; // 内部スピード 0~3,000,000(速度×10,000)
long orderSpd; // 指示スピード
float duty; //duty比
float acurve; //加速曲線
float bcurve; //減速度
int i; // ループカウンター
int k; // ループカウンター
int dispSpd = 0; // LCD表示用スピード値
int maxSpdData[5];
int AsoundData[SDATANUM];
// 走行音パターンの各パターンデータ開始位置インデックスを保持。
// 実際の値設定は、setup処理内にて終了コード(-1)を検出し動的に設定する。
int AdataNum[SOUNDNUM + 1];
int BsoundData[SDATANUM];
// 走行音パターンの各パターンデータ開始位置インデックスを保持。
// 実際の値設定は、setup処理内にて終了コード(-1)を検出し動的に設定する。
int BdataNum[SOUNDNUM + 1];
// キャラクタ液晶
LiquidCrystal lcd(P_LCD_RS, P_LCD_EN, P_LCD_D4, P_LCD_D5, P_LCD_D6, P_LCD_D7);
// **********************************************************************************************
// 関数定義 ここから
// **********************************************************************************************
// ***********************************************
// マスコン位置を検出する。
// ロータリースイッチからのボリューム値を拾い、
// 適度な値に変換する処理。
//
// 引数: アナログピンから取得した値(Max:1023)
// 戻値: 1(=EB)~12(=P5)
// ***********************************************
// ロータリースイッチが12段階切替なので、入力を12段階に分ける。
int getMasconVol(int vol) {
int ans = 0;
// マスコンロータリースイッチから読み取った値を、小数点も扱える形で
// 0~1023に収まるようにする。12接点なので12等分。
// 四捨五入相当の処理をする⇒値に+0.5した上で、小数点以下を切り捨てるためint型に変換。
if(analogRead(P_MASCON)>MAS_CHAT){
ans = (int)(((double) analogRead(P_MASCON)) / (1024 / 12) + 0.5);
if (ans == MC_EB) {
for ( k = 0; k < ANTI_CHAT_RATE; k++ ) { // チャタリング防止ループ
ans = (int)(((double) analogRead(P_MASCON)) / (1024 / 12) + 0.5);
}
}
return ans;
}
}
// マスコン位置を表示。LCDの右下2桁へ。
void dispMasPos(int masPos) {
lcd.setCursor(14, 1);
lcd.print(masconPosName[masPos - 1]);
// spdも表示
dispSpd = spd / 10000;
lcd.setCursor(0, 1);
if (dispSpd / 100 > 0) {
// 3digits.
} else {
if (dispSpd / 10 > 0) {
// 2digits.
lcd.print(" ");
} else {
// 1digit.
lcd.print(" ");
}
}
lcd.print(dispSpd);
}
//外部制御用
void SetSound(byte CHAT, byte VVVF){
// 走行音パターンほか選択したパターンに合わせて設定
ANTI_CHAT_RATE = (int)CHAT;
vvvfPtn = (int)VVVF - 1;
acurve = Aracurve[(int)VVVF];
bcurve = Arbcurve[(int)VVVF];
k = 0;
for (i = vvvfPtn * 5; i < vvvfPtn * 5 + 5; i++)
{
maxSpdData[k] = pgm_read_word_near(&maxSpdDataBase[i]);
k++;
}
notch1 = (long) maxSpdData[0] * 10000 - 1;
notch2 = (long) maxSpdData[1] * 10000 - 1;
notch3 = (long) maxSpdData[2] * 10000 - 1;
notch4 = (long) maxSpdData[3] * 10000 - 1;
notch5 = (long) maxSpdData[4] * 10000 - 1;
stopSpd = (long) STOPSPD * 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(F("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(F("Pls Apply BRAKE."));
delay(240);
masconPos = getMasconVol(analogRead(P_MASCON));
}
lcd.clear();
lcd.print(F("Ready."));
delay(720);
lcd.clear();
lcd.print(soundName[vvvfPtn]);
lcd.setCursor(3, 1);
lcd.print(F("km/h"));
lcd.setCursor(7, 1);
}
//シリアル受信用
int Receive(){
while (Serial.available() > 0) { // 受信したデータが存在する
if(Serial.available() >= 3) { //パケットを正常に受信している
ReceiveData[0] = Serial.read();
if (ReceiveData[0] == 0x00) { //ヘッダが先頭である
ReceiveData[1] = Serial.read(); //チャタリング防止ループ値
ReceiveData[2] = Serial.read(); //走行音データ番号
//キャッシュ解放処理
while (Serial.available() > 0) {
Serial.read();
}
return 0; //正常受信
} else {
//キャッシュ解放処理
while (Serial.available() > 0) {
Serial.read();
}
return 1; //受信エラー
}
}
}
return -1; //未受信
}
//加速度調整。現在速度が0km/hのとき1倍、各ノッチの最高速度以上のとき0倍になるよう調整。
void kaccel(int notch) {
float rate, rate2;
switch(notch){
case 1:
if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
rate = 1;
}
else if (notch1 - spd > 0) { //各ノッチの最高速度未満
rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
rate = pow(rate2, acurve);
}
else { //各ノッチの最高速度以上
rate = 0;
}
kasoku = accelVol * ACCEL_RATIO * 0.3 * rate;
break;
case 2:
if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
rate = 1;
}
else if (notch2 - spd > 0) { //各ノッチの最高速度未満
rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
rate = pow(rate2, acurve);
}
else { //各ノッチの最高速度以上
rate = 0;
}
kasoku = accelVol * ACCEL_RATIO * 0.5 * rate;
break;
case 3:
if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
rate = 1;
}
else if (notch3 - spd > 0) { //各ノッチの最高速度以下
rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
rate = pow(rate2, acurve);
}
else { //各ノッチの最高速度未満
rate = 0;
}
kasoku = accelVol * ACCEL_RATIO * 0.8 * rate;
break;
case 4:
if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
rate = 1;
}
else if (notch4 - spd > 0) { //各ノッチの最高速度未満rate = ((float)keepac[vvvfPtn]*10000 / (float)spd) * 2.0;
rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
rate = pow(rate2, acurve);
}
else { //各ノッチの最高速度以上
rate = 0;
}
kasoku = accelVol * ACCEL_RATIO * 0.9 * rate;
break;
case 5:
if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
rate = 1;
}
else if (notch5 - spd > 0) { //各ノッチの最高速度未満
rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
rate = pow(rate2, acurve);
}
else { //各ノッチの最高速度以上
rate = 0;
}
kasoku = accelVol * ACCEL_RATIO * rate;
break;
}
}
//減速度。現在速度が0km/hのとき1倍、P5の最高速度のとき約0.3倍になるよう調整。
void kbrake(int notch) {
float rate;
if (spd == 0) { //0km/h
rate = 1;
}
else{
rate = 1 - sqrt((float)((spd * bcurve) / notch5));
}
kasoku = accelVol * BRAKE_RATIO * notch * rate;
}
//常点灯および走行制御
void pwm(){
//常点灯用と走行用
TCCR2A = B10100001;
TCCR2B = B00000001;
OCR2B = (unsigned int)(255 * ((float)(lightVol * lmax) / 10230.0));
OCR2A = (unsigned int)(255 * duty);
}
//走行音制御
void spwm(int stype){
//走行音用
TCCR1A = B00100001;
TCCR1B = B00010001;
if(stype == 0){
OCR1A = (unsigned int)(8000000 / Afrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
OCR1B = (unsigned int)(8000000 / Afrq / 100);
}
else if(stype == 1){
OCR1A = (unsigned int)(8000000 / Bfrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
OCR1B = (unsigned int)(8000000 / Bfrq / 100);
}
else{
OCR1A = (unsigned int)400; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / 20000 = 400
OCR1B = (unsigned int)(1);
}
}
// ***********************************************
// setup関数。最初に1度だけ実行。
// ***********************************************
void setup() {
// PWM PIN設定
pinMode(P_PWM2A, OUTPUT); // 常点灯用PWM出力
pinMode(P_PWM1B, OUTPUT); // 走行音(VVVF音)用PWM出力
pinMode(P_PWM2B, OUTPUT); // 走行用PWM出力
// 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);
lcd.clear();
// 走行音パターン各開始位置取得処理。
// soundData配列のうち、各パターンの最初の値となる配列インデックスをdataNumで保持する。
AdataNum[0] = 0;
BdataNum[0] = 0;
k = 0;
for (i=1; i<=SOUNDNUM; i++) {
while (pgm_read_word_near(&AsoundDataBase[k]) != -1) {
k++;
}
k++;
AdataNum[i] = k;
}
k = 0;
for (i=1; i<=SOUNDNUM; i++) {
while (pgm_read_word_near(&BsoundDataBase[k]) != -1) {
k++;
}
k++;
BdataNum[i] = k;
}
k = 0;
for (i = 0; i<SDATANUM; i++)
{
AsoundData[k] = 0;
k++;
}
k = 0;
for (i = 0; i<SDATANUM; i++)
{
BsoundData[k] = 0;
k++;
}
lcd.home(); // 1文字目、1行目
//外部制御モードの設定(USB接続)
lcd.clear();
delay(180);
lcd.setCursor(0, 0);
lcd.print(F(" If using USB, "));
lcd.setCursor(0, 1);
lcd.print(F("Please Set to EB"));
delay(3000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("Use USB Connect?"));
delay(480);
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行目
isUSB = (MC_EB == getMasconVol(analogRead(P_MASCON)));
if (isUSB == true){
lcd.print(F("Yes")); // Y or N
} else {
lcd.print(F("No ")); // Y or N
}
} else {
if ((millis() - sttTime) > DECISION_TIME * 1000) {
// 選んでから所定時間が過ぎた。
break; // ループを抜ける。
}
}
progress = millis() - sttTime;
if (progress > 0) {
lcd.setCursor(15, 1);
lcd.print((String) (DECISION_TIME - (progress / 1000)));
}
delay(1);
}
} while (masconPos < MC_EB || masconPos > MC_P5); // 12種類から選択されたらループを抜け次の処理へ。
//外部制御モード
if (isUSB == true){
lcd.clear();
delay(180);
lcd.setCursor(0, 0);
lcd.print(F("Use USB Connect."));
delay(2000);
lcd.setCursor(0, 1);
lcd.print(F(" Connecting... "));
Serial.begin(9600);
do{
Result = Receive();
switch (Result) {
case -1: //未受信
break;
case 0: //正常受信
if (spd == 0) { //列車が停止している
Serial.write(255);
Serial.write(253);
Serial.write(0);
//SetSound(ReceiveData[1], ReceiveData[2]);
} else { //列車走行中
Serial.write(255);
Serial.write(253);
Serial.write(1);
}
break;
case 1: //受信エラー
Serial.write(255);
Serial.write(253);
Serial.write(2);
break;
}
} while (Result != 0);
SetSound(ReceiveData[1], ReceiveData[2]);
}
//独立モード
else {
ANTI_CHAT_RATE = ANTI_CHAT_RATE_NOUSB; // チャタリング防止ループの値。ノッチの反応速度調整に使用。
lcd.clear();
delay(180);
lcd.setCursor(0, 0);
lcd.print(F(" Not Use USB. "));
delay(2000);
// 走行音パターン取得
for (i=0; i<2; i++) {
lcd.clear();
delay(180);
lcd.print(F("Select SoundPtn."));
delay(480);
}
delay(240);
lcd.clear();
lcd.print(F("SoundPtn."));
lcd.setCursor(0, 1);
lcd.print(F(" Revolve MasCon."));
//delay(2000);
// ロータリースイッチを5秒動かさないと決定。
// ただし、何も動かしてないときはずっと待ち続ける。
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;
acurve = Aracurve[vvvfPtn];
bcurve = Arbcurve[vvvfPtn];
k = 0;
for (i = vvvfPtn * 5; i < vvvfPtn * 5 + 5; i++)
{
maxSpdData[k] = pgm_read_word_near(&maxSpdDataBase[i]);
k++;
}
notch1 = (long) maxSpdData[0] * 10000 - 1;
notch2 = (long) maxSpdData[1] * 10000 - 1;
notch3 = (long) maxSpdData[2] * 10000 - 1;
notch4 = (long) maxSpdData[3] * 10000 - 1;
notch5 = (long) maxSpdData[4] * 10000 - 1;
stopSpd = STOPSPD * 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(F("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(F("Pls Apply BRAKE."));
delay(240);
masconPos = getMasconVol(analogRead(P_MASCON));
}
lcd.clear();
lcd.print(F("Ready."));
delay(720);
lcd.clear();
lcd.print(soundName[vvvfPtn]);
lcd.setCursor(3, 1);
lcd.print(F("km/h"));
lcd.setCursor(7, 1);
}
}
// ***********************************************
// ***********************************************
// loop関数
// ***********************************************
// ***********************************************
void loop() {
//動作速度確保のため、音データをメモリに格納。
accelVol = analogRead(P_ACCEL_DIAL) / 32 + 1; // analogRead 0~1023 → 1~32
lightVol = analogRead(P_LIGHT_DIAL); // analogRead 0~1023
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 MC_EB:
orderSpd = 0;
kbrake(7);
mode = MD_NTRL;
break;
case MC_B5:
orderSpd = 0;
kbrake(5);
mode = MD_BRAKE;
break;
case MC_B4:
orderSpd = 0;
kbrake(4);
mode = MD_BRAKE;
break;
case MC_B3:
orderSpd = 0;
kbrake(3);
mode = MD_BRAKE;
break;
case MC_B2:
orderSpd = 0;
kbrake(2);
mode = MD_BRAKE;
break;
case MC_B1:
// 制動1
orderSpd = 0;
kbrake(1);
mode = MD_BRAKE;
break;
case MC_N:
orderSpd = 0;
mode = MD_NTRL;
break;
case MC_P1:
orderSpd = notch1;
kaccel(1);
mode = MD_ACCEL;
break;
case MC_P2:
orderSpd = notch2;
kaccel(2);
mode = MD_ACCEL;
break;
case MC_P3:
orderSpd = notch3;
kaccel(3);
mode = MD_ACCEL;
break;
case MC_P4:
orderSpd = notch4;
kaccel(4);
mode = MD_ACCEL;
break;
case MC_P5:
orderSpd = notch5;
kaccel(5);
mode = MD_ACCEL;
break;
}
for ( k = 0; k < ANTI_CHAT_RATE; k++ ) { // チャタリング防止ループ
dispMasPos(masconPos);
if (spd < orderSpd) {
if (orderSpd - spd <= kasoku) {
spd = orderSpd;
} else {
spd = spd + kasoku;
}
}
else if ( spd > 0 ) {
if ( masconPos >= MC_P1 ) {
// 力行
if ( spd - orderSpd <= 0 ) {
spd = orderSpd;
} else {
spd = spd * (1.0 - (pcoast / 10000.0));
}
}
else if ( masconPos == MC_N ) {
// 惰行
spd = spd * (1.0 - (coast / 10000.0));
}
else {
// 制動
if ( spd - kasoku < 0 ) {
spd = 0;
} else {
spd = spd - kasoku;
if ( spd < stopSpd ) {
// 惰行or制動ノッチのとき、ピタ停止速度値よりも現行速度が下回ったらピタッと停止させる。
spd = 0;
mode = MD_STOP;
}
}
}
}
duty = ((float)spd * adrate / 4000000.0)* sscale;
if (duty > 1.0){
duty = 1.0;
}
if(viewduty == true){
lcd.setCursor(0, 0);
lcd.print("DUTY= ");
lcd.setCursor(5, 0);
lcd.print(duty);
}
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 && AsttFrq == -1) { // 走行音が設定されていない場合の処理
pwm();
spwm(2);
break;
}
if (mode == MD_BRAKE) { // 変調モード切替
break;
}
if (mode == MD_NTRL) { // 変調モード切替
break;
}
if (AsttFrq == -1) { // 高速時 VVVF音停止
pwm();
spwm(2);
break;
}
// VVVF音
if ((spd >= AsttSpd * 10000) && (spd < AendSpd * 10000)) {
Afrq = ( AendFrq * 10 - AsttFrq * 10 ) / ( AendSpd - AsttSpd ) * ( spd - AsttSpd * 10000 ) / 100000 + AsttFrq;
if ( Afrq < 150 ) Afrq = 150;
else if ( Afrq > 100000 ) Afrq = 100000;
pwm();
spwm(0);
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) { // 走行音が設定されていない場合の処理
pwm();
spwm(2);
break;
}
if (mode == MD_ACCEL) { // 変調モード切替
break;
}
if (mode == MD_NTRL) { // 変調モード切替
break;
}
if (BsttFrq == -1) { // 高速時 VVVF音停止
pwm();
spwm(2);
break;
}
// VVVF音
if ((spd >= BsttSpd * 10000) && (spd < BendSpd * 10000)) {
Bfrq = ( BendFrq * 10 - BsttFrq * 10 ) / ( BendSpd - BsttSpd ) * ( spd - BsttSpd * 10000 ) / 100000 + BsttFrq;
if ( Bfrq < 150 ) Bfrq = 150;
else if ( Bfrq > 100000 ) Bfrq = 100000;
pwm();
spwm(1);
break;
}
}
}
else if(mode == MD_NTRL) { // 惰行・非常
pwm();
spwm(2);
}
else { // 停止時 音停止
pwm();
//走行音用
TCCR1A = B00000001;
TCCR1B = B00010001;
}
}
if (isUSB == true) {
Result = Receive();
switch (Result) {
case -1: //未受信
Serial.write(255);
Serial.write(254);
Serial.write(masconPos);
if(dispSpd > 200){
Serial.write(200);
Serial.write(dispSpd - 200);
}else{
Serial.write(dispSpd);
Serial.write(0);
}
break;
case 0: //正常受信
if (spd == 0) { //列車が停止している
Serial.write(255);
Serial.write(253);
Serial.write(0);
SetSound(ReceiveData[1], ReceiveData[2]);
} else { //列車走行中
Serial.write(255);
Serial.write(253);
Serial.write(1);
}
break;
case 1: //受信エラー
Serial.write(255);
Serial.write(253);
Serial.write(2);
break;
}
}
}
/* *************************************************************************************** WhiteBear Controller Next Generation *************************************************************************************** ・PWM制御による鉄道模型用コントローラー。 ・12段階のロータリースイッチを用い、ワンハンドルマスコンを模擬する。 ・マスコンはP5-B5 + EB の12段。 ・各種状況はキャラクタLCD(16文字x2段)で表示。 ・力行各段の最高速度を設定可能。設定はソースコード内であらかじめ設定。 ・ノッチによる加速度の違いをついでに再現。 ・速度に応じた加速度の変化もだいたい再現。 ・PWM周波数の変調により、VVVFなど制御機器の音を再現。 ・変調音は最大255種類までプログラム可能。(メモリ容量によってはプログラム可能な最大数が少なくなります) ・加速時と減速時で異なる変調音を設定可能。  ・外部制御モードを搭載。ノッチ反応速度と走行音を外部から随時変更可能。 *************************************************************************************** 更新履歴 2019/07/30 V1.00 初期版。 2019/10/11 V1.10 音データをフラッシュメモリ格納に変更し、RAM不足を解消。 2020/01/30 V1.20 USBによる外部制御モードの追加 2020/02/21 V2.00 速度制御方式の変更(加速度変動機能)及びバグ修正 2020/04/25 V2.10 指定した周波数で出力されないことがあるバグを修正 2020/05/10 V2.20 マスコンのチャタリング対策を強化 2020/06/17 V2.21 マスコンのチャタリング対策を更に強化 2020/09/22 V2.30 加速度調節の細分化(8段階→16段階)、RAM使用量の削減 2020/11/17 V3.00 PWM周波数および出力設定の変更、加速度調節の細分化(16段階→32段階)、 常点灯の感度調整機能追加、EBの動作変更、スケールスピード調整機能追加、 最高速度400km/hに変更(モーターの性能により400km/hに届かない場合もあります) 2021/07/03 V3.10 速度及びノッチ情報について外部に送信するように変更。外部からモニタリングが できるようになりました。 加速曲線と減速度を音データ毎に決められるようになりました。 表示速度の変化率に対するDuty比の変化率を調整できるようにしました。 2022/07/15 V3.20 惰行時・各力行ノッチ最高速度以上時の減速率計算方法を変更しました。 *************************************************************************************** */ #include <avr/io.h> // ATmega328P用のヘッダファイル #include <avr/pgmspace.h> #include <LiquidCrystal.h> // キャラクタ液晶ディスプレイ制御用のヘッダファイル // *********************************************** // ユーザー設定領域 // 環境に合わせて各値を適宜調整してください。 // *********************************************** #define SOUNDNUM 16 //走行音データの数。13番目以降のデータを選択するには外部通信端末またはPC用ソフトウェアとの接続が必要です。PC用ソフトウェアはHP上に公開中。 #define STOPSPD 9 //ピタッと止めるための値。環境に合わせて調整可能 #define SDATANUM 45 //3n(n=最も行数が多い音データの行数)以上の値に設定して下さい。あまり数字を大きくし過ぎるとメモリ不足になるため、むやみに大きくしないように。 #define ANTI_CHAT_RATE_NOUSB 10 //外部通信なしの際のチャタリング防止ループの値 const int DECISION_TIME = 3; // setup時に走行パターンを決める際の、確定までの時間。秒。 const int ACCEL_RATIO = 5; // (加速率調整小型ボリュームからの入力値) × ACCEL_RATIO ⇒ 加速率 const int BRAKE_RATIO = 2; // (加速率調整小型ボリュームからの入力値) × BRAKE_RATIO × マスコンブレーキ値(1~5) ⇒ 減速率 const int MAS_CHAT = 80; //ロータリスイッチが中間で止まった時の異常な指令を回避する。EBに投入した時のアナログ値を少し下回る値を設定。 const float lmax = 2; //常点灯ボリュームの最大値。lmax=1→出力MAX10%まで、1.5→出力MAX15%まで、のようにボリュームの範囲を調整可能。選択可能な範囲を狭くするほど微調整が可能となる。 const float sscale = 3; //表示速度に対するDuty比の倍率。環境に合わせ実測して調整。 const float adrate = 0.5; //加速度及び減速度の変化に対するDuty比の変化率。環境に合わせ実測して調整。 const float coast = 0.07; //惰行時最高速度以上時の減速率。 const float pcoast = 0.01; //各力行ノッチ最高速度以上時の減速率。 /* sscaleとadrateの関係について: sscaleは表示速度に対するduty比を全域にわたって調整します。グラフをそのまま上下に動かすイメージです。 主に列車の起動速度調整に使用します。 一方、adrateは表示速度の変化に対するDuty比の変化率を調整します。 加速時を例とすると、表示速度の上昇速度に対するDuty比の上昇速度を調整します。 加速度ボリュームは表示速度の変化率を調整するのに対し、adrateは表示速度に対する実速度の変化率を調整する項目であるといえます。 */ boolean viewduty = false; //(開発用)走行中、走行音名の代わりにDUTY比を表示します。 // *********************************************** // 走行音データ領域 // データの追加や変更を行う場合は慎重に行ってください。 // *********************************************** // 走行音名称。16桁で指定すること char* soundName[SOUNDNUM] = { " 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", "MITSUBISHI Chop.", "N1000 (SUS-IGBT)", "N1000 (SI-IGBT) ", " Toyo-IGBT(AE) " }; // 各パターンの各ノッチ位置での最高速度値を配列で保持。 // 左から順に 1ノッチ, 2ノッチ, 3ノッチ, 4ノッチ, 5ノッチ。 const int maxSpdDataBase[] PROGMEM = { 31, 46, 101, 111, 111, //パターン 1 201 41, 56, 101, 111, 111, //パターン 2 209,70-000,etc 41, 56, 111, 121, 121, //パターン 3 E231-0,500,etc 41, 56, 111, 121, 121, //パターン 4 E231-1000 41, 56, 111, 121, 121, //パターン 5 E233, etc 51, 66, 121, 131, 131, //パターン 6 KQ 2100 (SI-GTO) 51, 66, 121, 131, 131, //パターン 7 N1000 (SI-GTO) 51, 66, 121, 131, 131, //パターン 8 MITSUBISHI-GTO 51, 66, 121, 131, 131, //パターン 9 Toyo-GTO 51, 66, 121, 131, 131, //パターン10 Toyo-IGBT 41, 56, 101, 111, 111, //パターン11 Toei 5300(Akuma) 41, 56, 101, 111, 111, //パターン12 TOSHIBA 3LevIGBT 41, 56, 101, 111, 111, //パターン13 MITSUBISHI Chop. 51, 66, 121, 131, 131, //パターン14 N1000 (SUS-IGBT) 51, 66, 121, 131, 131, //パターン15 N1000 (SI-IGBT) 51, 66, 151, 161, 161 //パターン16 Toyo-IGBT(AE) }; //定加速度領域値。 //指定した速度まで一定の加速度を保ちます。 int keepac[SOUNDNUM] = {37, 39, 39, 39, 39, 55, 55, 55, 55, 59, 50, 50, 45, 59, 55, 85}; //加速曲線。 //数値を大きくすると加速性能が良くなります。お好みで調整して下さい。 const float Aracurve[SOUNDNUM] = {1.2, 1.3, 1.4, 1.4, 1.5, 1.55, 1.55, 1.5, 1.5, 1.4, 1.4, 1.45, 1.45, 1.5, 1.5, 1.4}; //減速度。 //数値を大きくすると減速性能が良くなります。お好みで調整して下さい。 const float Arbcurve[SOUNDNUM] = {0.13, 0.15, 0.15, 0.15, 0.16, 0.14, 0.14, 0.14, 0.15, 0.17, 0.14, 0.14, 0.14, 0.14, 0.14, 0.17}; // 音データ。 // 設定可能周波数 150Hz~100kHz(値は整数で指定) // ~~~(例) パターン3の場合~~~ // 1行目 速度0~2の間:380Hzで音程変化なし // 2行目 速度3~11の間:380Hzから980Hzまで変化する //加速時用の音データ。 const int AsoundDataBase[] PROGMEM = { //パターン1 201 590, 590, 66, // 1 開始周波数 終了周波数 切替スピード 550, 980, 101, // 2 開始周波数 終了周波数 切替スピード -1, // 3 終了コード //パターン2 209,70-000,etc 230, 230, 2, // 1 開始周波数 終了周波数 切替スピード 90, 90, 3, // 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, 2, // 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 760, 760, 31, // 1 開始周波数 終了周波数 切替スピード 350, 1150, 101, // 2 開始周波数 終了周波数 切替スピード 1150, 1150, 121, // 3 開始周波数 終了周波数 切替スピード -1, // 4 終了コード //パターン6 KQ 2100 (SI-GTO) 350, 350, 2, // 1 開始周波数 終了周波数 切替スピード 390, 390, 3, // 2 開始周波数 終了周波数 切替スピード 440, 440, 4, // 3 開始周波数 終了周波数 切替スピード 490, 490, 5, // 4 開始周波数 終了周波数 切替スピード 530, 530, 6, // 5 開始周波数 終了周波数 切替スピード 580, 580, 7, // 6 開始周波数 終了周波数 切替スピード 650, 650, 8, // 7 開始周波数 終了周波数 切替スピード 700, 700, 9, // 8 開始周波数 終了周波数 切替スピード 780, 780, 23, // 9 開始周波数 終了周波数 切替スピード 510, 980, 111, //10 開始周波数 終了周波数 切替スピード 580, 580, 131, //11 開始周波数 終了周波数 切替スピード -1, //12 終了コード //パターン7 N1000 (SI-GTO) 350, 350, 2, // 1 開始周波数 終了周波数 切替スピード 390, 390, 3, // 2 開始周波数 終了周波数 切替スピード 440, 440, 4, // 3 開始周波数 終了周波数 切替スピード 490, 490, 5, // 4 開始周波数 終了周波数 切替スピード 530, 530, 6, // 5 開始周波数 終了周波数 切替スピード 580, 580, 7, // 6 開始周波数 終了周波数 切替スピード 650, 650, 8, // 7 開始周波数 終了周波数 切替スピード 700, 700, 9, // 8 開始周波数 終了周波数 切替スピード 780, 780, 16, // 9 開始周波数 終了周波数 切替スピード 950, 970, 18, //10 開始周波数 終了周波数 切替スピード 850, 970, 21, //11 開始周波数 終了周波数 切替スピード 800, 970, 23, //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, 19, // 1 開始周波数 終了周波数 切替スピード 490, 780, 31, // 2 開始周波数 終了周波数 切替スピード 420, 1250, 110, // 3 開始周波数 終了周波数 切替スピード 1250, 1250, 131, // 4 開始周波数 終了周波数 切替スピード -1, // 5 終了コード //パターン10 Toyo-IGBT 1050, 1050, 26, // 1 開始周波数 終了周波数 切替スピード 630, 1300, 41, // 2 開始周波数 終了周波数 切替スピード 650, 1150, 111, // 3 開始周波数 終了周波数 切替スピード 1150, 1150, 131, // 4 開始周波数 終了周波数 切替スピード -1, // 5 終了コード //パターン11 Toei 5300(Akuma) 780, 780, 11, // 1 開始周波数 終了周波数 切替スピード 450, 450, 16, // 2 開始周波数 終了周波数 切替スピード 500, 780, 22, // 3 開始周波数 終了周波数 切替スピード 430, 580, 25, // 4 開始周波数 終了周波数 切替スピード 400, 1150, 105, // 5 開始周波数 終了周波数 切替スピード 1150, 1150, 121, // 6 開始周波数 終了周波数 切替スピード -1, // 7 終了コード //パターン12 TOSHIBA 3LevIGBT 730, 730, 11, // 1 開始周波数 終了周波数 切替スピード 510, 730, 16, // 2 開始周波数 終了周波数 切替スピード 600, 1150, 101, // 3 開始周波数 終了周波数 切替スピード 1150, 1150, 111, // 4 開始周波数 終了周波数 切替スピード -1, // 5 終了コード //パターン13 MITSUBISHI Chop. 590, 590, 4, // 1 開始周波数 終了周波数 切替スピード 900, 900, 36, // 2 開始周波数 終了周波数 切替スピード 590, 590, 41, // 3 開始周波数 終了周波数 切替スピード 390, 390, 46, // 4 開始周波数 終了周波数 切替スピード 390, 1150, 101, // 5 開始周波数 終了周波数 切替スピード 1150, 1150, 111, // 6 開始周波数 終了周波数 切替スピード -1, // 7 終了コード //パターン14 N1000 (SUS-IGBT) 710, 710, 31, // 1 開始周波数 終了周波数 切替スピード 350, 1150, 101, // 2 開始周波数 終了周波数 切替スピード 1150, 1150, 131, // 3 開始周波数 終了周波数 切替スピード -1, // 4 終了コード //パターン15 N1000 (SI-IGBT) 1180, 1180, 16, // 1 開始周波数 終了周波数 切替スピード 1180, 1960, 33, // 2 開始周波数 終了周波数 切替スピード 460, 1150, 101, // 3 開始周波数 終了周波数 切替スピード 1150, 1150, 131, // 4 開始周波数 終了周波数 切替スピード -1, // 5 終了コード //パターン16 Toyo-IGBT(AE) 1000, 1000, 26, // 1 開始周波数 終了周波数 切替スピード 580, 1300, 41, // 2 開始周波数 終了周波数 切替スピード 650, 1150, 141, // 3 開始周波数 終了周波数 切替スピード 1150, 1150, 161, // 4 開始周波数 終了周波数 切替スピード -1 // 5 終了コード }; //減速時用の音データ。 const int BsoundDataBase[] PROGMEM = { //パターン1 201 10000, 10000, 6, // 1 開始周波数 終了周波数 切替スピード 590, 590, 61, // 2 開始周波数 終了周波数 切替スピード 590, 650, 76, // 3 開始周波数 終了周波数 切替スピード 550, 980, 101, // 4 開始周波数 終了周波数 切替スピード -1, // 5 終了コード //パターン2 209,70-000,etc 150, 150, 6, // 1 開始周波数 終了周波数 切替スピード 150, 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 760, 760, 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, // 1 開始周波数 終了周波数 切替スピード 440, 460, 22, // 2 開始周波数 終了周波数 切替スピード 460, 570, 36, // 3 開始周波数 終了周波数 切替スピード 500, 1150, 105, // 4 開始周波数 終了周波数 切替スピード 1150, 1150, 121, // 5 開始周波数 終了周波数 切替スピード -1, // 6 終了コード //パターン12 TOSHIBA 3LevIGBT 730, 730, 11, // 1 開始周波数 終了周波数 切替スピード 510, 1150, 101, // 2 開始周波数 終了周波数 切替スピード 1150, 1150, 111, // 3 開始周波数 終了周波数 切替スピード -1, // 4 終了コード //パターン13 MITSUBISHI Chop. 900, 900, 36, // 1 開始周波数 終了周波数 切替スピード 590, 590, 41, // 2 開始周波数 終了周波数 切替スピード 390, 390, 46, // 3 開始周波数 終了周波数 切替スピード 390, 1150, 101, // 4 開始周波数 終了周波数 切替スピード 1150, 1150, 111, // 5 開始周波数 終了周波数 切替スピード -1, // 6 終了コード //パターン14 N1000 (SUS-IGBT) 710, 710, 31, // 1 開始周波数 終了周波数 切替スピード 350, 1150, 101, // 2 開始周波数 終了周波数 切替スピード 1150, 1150, 131, // 3 開始周波数 終了周波数 切替スピード -1, // 4 終了コード //パターン15 N1000 (SI-IGBT) 1180, 1180, 16, // 1 開始周波数 終了周波数 切替スピード 1180, 2050, 31, // 2 開始周波数 終了周波数 切替スピード 780, 1150, 36, // 3 開始周波数 終了周波数 切替スピード 560, 980, 111, // 4 開始周波数 終了周波数 切替スピード 580, 580, 131, // 5 開始周波数 終了周波数 切替スピード -1, // 6 終了コード //パターン16 Toyo-IGBT(AE) 1000, 1000, 26, // 1 開始周波数 終了周波数 切替スピード 600, 880, 41, // 2 開始周波数 終了周波数 切替スピード 600, 950, 95, // 3 開始周波数 終了周波数 切替スピード 950, 950, 106, // 4 開始周波数 終了周波数 切替スピード 580, 580, 161, // 5 開始周波数 終了周波数 切替スピード -1 // 6 終了コード }; // *********************************************** // ここから先はみだりに変更しないで下さい。 // 誤動作や故障の原因となります。 // *********************************************** int ANTI_CHAT_RATE; // チャタリング防止ループの値。ノッチの反応速度調整に使用。USB接続では動的設定可能。 //外部通信用 int Result; //データ受信状況 int ReceiveData[] = {255, 255, 0}; //外部制御モード時に受け取った値 /* 送受信パケットは以下の様に定義する。 ・送受信はHEX(16進)で行う。 ・送信パケット=(ヘッダ)(モード1)(マスコン位置)(速度1)(速度2)の5バイトまたは(ヘッダ)(モード2)(受信結果)の3バイト。  ヘッダ=255、モード1=254、モード2=253、マスコン位置=1(EB)~12(P5)。  速度1=0~200、速度2=0~200。速度=速度1+速度2とする。  受信結果=0(正常受信),=1(走行中またはブレーキノッチ未投入),=2(受信失敗)。 ・受信パケット=(ヘッダ)(チャタリング防止ループ値)(走行音データ番号) の3バイト。ヘッダ=0 ・チャタリング防止ループ値、走行音データ番号の値は1~255。 ・よって走行音データは(理論上は)255種類まで搭載可能だが、増やせば当然メモリ食うので現実には限界がある。 ・走行音は用意したデータ数より大きい番号を指定しないように送信側で制御を行うこと。 */ const char *MOJI1 = " WCNG POWERPACK "; // Welcomeメッセージ。LCD1段目。16桁にすること。 const char *MOJI2 = "Program Ver 3.20"; // Welcomeメッセージ。LCD2段目。16桁にすること。 //使用ピン指定 const int P_MASCON = A3; // Arduino接続ピン番号:(Analog) ロータリースイッチ = A3 const int P_LIGHT_DIAL = A4; // Arduino接続ピン番号:(Analog) 常点灯調節ダイアル = A4 const int P_ACCEL_DIAL = A5; // 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) OCR1B出力 to Rail = D11 //マスコン用 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 isUSB; // 外部制御モードのときTrue。 int vvvfPtn; // 走行音パターン。 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; // 内部スピード 0~3,000,000(速度×10,000) long orderSpd; // 指示スピード float duty; //duty比 float acurve; //加速曲線 float bcurve; //減速度 int i; // ループカウンター int k; // ループカウンター int dispSpd = 0; // LCD表示用スピード値 int maxSpdData[5]; int AsoundData[SDATANUM]; // 走行音パターンの各パターンデータ開始位置インデックスを保持。 // 実際の値設定は、setup処理内にて終了コード(-1)を検出し動的に設定する。 int AdataNum[SOUNDNUM + 1]; int BsoundData[SDATANUM]; // 走行音パターンの各パターンデータ開始位置インデックスを保持。 // 実際の値設定は、setup処理内にて終了コード(-1)を検出し動的に設定する。 int BdataNum[SOUNDNUM + 1]; // キャラクタ液晶 LiquidCrystal lcd(P_LCD_RS, P_LCD_EN, P_LCD_D4, P_LCD_D5, P_LCD_D6, P_LCD_D7); // ********************************************************************************************** // 関数定義 ここから // ********************************************************************************************** // *********************************************** // マスコン位置を検出する。 // ロータリースイッチからのボリューム値を拾い、 // 適度な値に変換する処理。 // // 引数: アナログピンから取得した値(Max:1023) // 戻値: 1(=EB)~12(=P5) // *********************************************** // ロータリースイッチが12段階切替なので、入力を12段階に分ける。 int getMasconVol(int vol) { int ans = 0; // マスコンロータリースイッチから読み取った値を、小数点も扱える形で // 0~1023に収まるようにする。12接点なので12等分。 // 四捨五入相当の処理をする⇒値に+0.5した上で、小数点以下を切り捨てるためint型に変換。 if(analogRead(P_MASCON)>MAS_CHAT){ ans = (int)(((double) analogRead(P_MASCON)) / (1024 / 12) + 0.5); if (ans == MC_EB) { for ( k = 0; k < ANTI_CHAT_RATE; k++ ) { // チャタリング防止ループ ans = (int)(((double) analogRead(P_MASCON)) / (1024 / 12) + 0.5); } } return ans; } } // マスコン位置を表示。LCDの右下2桁へ。 void dispMasPos(int masPos) { lcd.setCursor(14, 1); lcd.print(masconPosName[masPos - 1]); // spdも表示 dispSpd = spd / 10000; lcd.setCursor(0, 1); if (dispSpd / 100 > 0) { // 3digits. } else { if (dispSpd / 10 > 0) { // 2digits. lcd.print(" "); } else { // 1digit. lcd.print(" "); } } lcd.print(dispSpd); } //外部制御用 void SetSound(byte CHAT, byte VVVF){ // 走行音パターンほか選択したパターンに合わせて設定 ANTI_CHAT_RATE = (int)CHAT; vvvfPtn = (int)VVVF - 1; acurve = Aracurve[(int)VVVF]; bcurve = Arbcurve[(int)VVVF]; k = 0; for (i = vvvfPtn * 5; i < vvvfPtn * 5 + 5; i++) { maxSpdData[k] = pgm_read_word_near(&maxSpdDataBase[i]); k++; } notch1 = (long) maxSpdData[0] * 10000 - 1; notch2 = (long) maxSpdData[1] * 10000 - 1; notch3 = (long) maxSpdData[2] * 10000 - 1; notch4 = (long) maxSpdData[3] * 10000 - 1; notch5 = (long) maxSpdData[4] * 10000 - 1; stopSpd = (long) STOPSPD * 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(F("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(F("Pls Apply BRAKE.")); delay(240); masconPos = getMasconVol(analogRead(P_MASCON)); } lcd.clear(); lcd.print(F("Ready.")); delay(720); lcd.clear(); lcd.print(soundName[vvvfPtn]); lcd.setCursor(3, 1); lcd.print(F("km/h")); lcd.setCursor(7, 1); } //シリアル受信用 int Receive(){ while (Serial.available() > 0) { // 受信したデータが存在する if(Serial.available() >= 3) { //パケットを正常に受信している ReceiveData[0] = Serial.read(); if (ReceiveData[0] == 0x00) { //ヘッダが先頭である ReceiveData[1] = Serial.read(); //チャタリング防止ループ値 ReceiveData[2] = Serial.read(); //走行音データ番号 //キャッシュ解放処理 while (Serial.available() > 0) { Serial.read(); } return 0; //正常受信 } else { //キャッシュ解放処理 while (Serial.available() > 0) { Serial.read(); } return 1; //受信エラー } } } return -1; //未受信 } //加速度調整。現在速度が0km/hのとき1倍、各ノッチの最高速度以上のとき0倍になるよう調整。 void kaccel(int notch) { float rate, rate2; switch(notch){ case 1: if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下 rate = 1; } else if (notch1 - spd > 0) { //各ノッチの最高速度未満 rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd; rate = pow(rate2, acurve); } else { //各ノッチの最高速度以上 rate = 0; } kasoku = accelVol * ACCEL_RATIO * 0.3 * rate; break; case 2: if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下 rate = 1; } else if (notch2 - spd > 0) { //各ノッチの最高速度未満 rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd; rate = pow(rate2, acurve); } else { //各ノッチの最高速度以上 rate = 0; } kasoku = accelVol * ACCEL_RATIO * 0.5 * rate; break; case 3: if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下 rate = 1; } else if (notch3 - spd > 0) { //各ノッチの最高速度以下 rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd; rate = pow(rate2, acurve); } else { //各ノッチの最高速度未満 rate = 0; } kasoku = accelVol * ACCEL_RATIO * 0.8 * rate; break; case 4: if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下 rate = 1; } else if (notch4 - spd > 0) { //各ノッチの最高速度未満rate = ((float)keepac[vvvfPtn]*10000 / (float)spd) * 2.0; rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd; rate = pow(rate2, acurve); } else { //各ノッチの最高速度以上 rate = 0; } kasoku = accelVol * ACCEL_RATIO * 0.9 * rate; break; case 5: if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下 rate = 1; } else if (notch5 - spd > 0) { //各ノッチの最高速度未満 rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd; rate = pow(rate2, acurve); } else { //各ノッチの最高速度以上 rate = 0; } kasoku = accelVol * ACCEL_RATIO * rate; break; } } //減速度。現在速度が0km/hのとき1倍、P5の最高速度のとき約0.3倍になるよう調整。 void kbrake(int notch) { float rate; if (spd == 0) { //0km/h rate = 1; } else{ rate = 1 - sqrt((float)((spd * bcurve) / notch5)); } kasoku = accelVol * BRAKE_RATIO * notch * rate; } //常点灯および走行制御 void pwm(){ //常点灯用と走行用 TCCR2A = B10100001; TCCR2B = B00000001; OCR2B = (unsigned int)(255 * ((float)(lightVol * lmax) / 10230.0)); OCR2A = (unsigned int)(255 * duty); } //走行音制御 void spwm(int stype){ //走行音用 TCCR1A = B00100001; TCCR1B = B00010001; if(stype == 0){ OCR1A = (unsigned int)(8000000 / Afrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq OCR1B = (unsigned int)(8000000 / Afrq / 100); } else if(stype == 1){ OCR1A = (unsigned int)(8000000 / Bfrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq OCR1B = (unsigned int)(8000000 / Bfrq / 100); } else{ OCR1A = (unsigned int)400; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / 20000 = 400 OCR1B = (unsigned int)(1); } } // *********************************************** // setup関数。最初に1度だけ実行。 // *********************************************** void setup() { // PWM PIN設定 pinMode(P_PWM2A, OUTPUT); // 常点灯用PWM出力 pinMode(P_PWM1B, OUTPUT); // 走行音(VVVF音)用PWM出力 pinMode(P_PWM2B, OUTPUT); // 走行用PWM出力 // 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); lcd.clear(); // 走行音パターン各開始位置取得処理。 // soundData配列のうち、各パターンの最初の値となる配列インデックスをdataNumで保持する。 AdataNum[0] = 0; BdataNum[0] = 0; k = 0; for (i=1; i<=SOUNDNUM; i++) { while (pgm_read_word_near(&AsoundDataBase[k]) != -1) { k++; } k++; AdataNum[i] = k; } k = 0; for (i=1; i<=SOUNDNUM; i++) { while (pgm_read_word_near(&BsoundDataBase[k]) != -1) { k++; } k++; BdataNum[i] = k; } k = 0; for (i = 0; i<SDATANUM; i++) { AsoundData[k] = 0; k++; } k = 0; for (i = 0; i<SDATANUM; i++) { BsoundData[k] = 0; k++; } lcd.home(); // 1文字目、1行目 //外部制御モードの設定(USB接続) lcd.clear(); delay(180); lcd.setCursor(0, 0); lcd.print(F(" If using USB, ")); lcd.setCursor(0, 1); lcd.print(F("Please Set to EB")); delay(3000); lcd.clear(); lcd.setCursor(0, 0); lcd.print(F("Use USB Connect?")); delay(480); 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行目 isUSB = (MC_EB == getMasconVol(analogRead(P_MASCON))); if (isUSB == true){ lcd.print(F("Yes")); // Y or N } else { lcd.print(F("No ")); // Y or N } } else { if ((millis() - sttTime) > DECISION_TIME * 1000) { // 選んでから所定時間が過ぎた。 break; // ループを抜ける。 } } progress = millis() - sttTime; if (progress > 0) { lcd.setCursor(15, 1); lcd.print((String) (DECISION_TIME - (progress / 1000))); } delay(1); } } while (masconPos < MC_EB || masconPos > MC_P5); // 12種類から選択されたらループを抜け次の処理へ。 //外部制御モード if (isUSB == true){ lcd.clear(); delay(180); lcd.setCursor(0, 0); lcd.print(F("Use USB Connect.")); delay(2000); lcd.setCursor(0, 1); lcd.print(F(" Connecting... ")); Serial.begin(9600); do{ Result = Receive(); switch (Result) { case -1: //未受信 break; case 0: //正常受信 if (spd == 0) { //列車が停止している Serial.write(255); Serial.write(253); Serial.write(0); //SetSound(ReceiveData[1], ReceiveData[2]); } else { //列車走行中 Serial.write(255); Serial.write(253); Serial.write(1); } break; case 1: //受信エラー Serial.write(255); Serial.write(253); Serial.write(2); break; } } while (Result != 0); SetSound(ReceiveData[1], ReceiveData[2]); } //独立モード else { ANTI_CHAT_RATE = ANTI_CHAT_RATE_NOUSB; // チャタリング防止ループの値。ノッチの反応速度調整に使用。 lcd.clear(); delay(180); lcd.setCursor(0, 0); lcd.print(F(" Not Use USB. ")); delay(2000); // 走行音パターン取得 for (i=0; i<2; i++) { lcd.clear(); delay(180); lcd.print(F("Select SoundPtn.")); delay(480); } delay(240); lcd.clear(); lcd.print(F("SoundPtn.")); lcd.setCursor(0, 1); lcd.print(F(" Revolve MasCon.")); //delay(2000); // ロータリースイッチを5秒動かさないと決定。 // ただし、何も動かしてないときはずっと待ち続ける。 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; acurve = Aracurve[vvvfPtn]; bcurve = Arbcurve[vvvfPtn]; k = 0; for (i = vvvfPtn * 5; i < vvvfPtn * 5 + 5; i++) { maxSpdData[k] = pgm_read_word_near(&maxSpdDataBase[i]); k++; } notch1 = (long) maxSpdData[0] * 10000 - 1; notch2 = (long) maxSpdData[1] * 10000 - 1; notch3 = (long) maxSpdData[2] * 10000 - 1; notch4 = (long) maxSpdData[3] * 10000 - 1; notch5 = (long) maxSpdData[4] * 10000 - 1; stopSpd = STOPSPD * 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(F("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(F("Pls Apply BRAKE.")); delay(240); masconPos = getMasconVol(analogRead(P_MASCON)); } lcd.clear(); lcd.print(F("Ready.")); delay(720); lcd.clear(); lcd.print(soundName[vvvfPtn]); lcd.setCursor(3, 1); lcd.print(F("km/h")); lcd.setCursor(7, 1); } } // *********************************************** // *********************************************** // loop関数 // *********************************************** // *********************************************** void loop() { //動作速度確保のため、音データをメモリに格納。 accelVol = analogRead(P_ACCEL_DIAL) / 32 + 1; // analogRead 0~1023 → 1~32 lightVol = analogRead(P_LIGHT_DIAL); // analogRead 0~1023 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 MC_EB: orderSpd = 0; kbrake(7); mode = MD_NTRL; break; case MC_B5: orderSpd = 0; kbrake(5); mode = MD_BRAKE; break; case MC_B4: orderSpd = 0; kbrake(4); mode = MD_BRAKE; break; case MC_B3: orderSpd = 0; kbrake(3); mode = MD_BRAKE; break; case MC_B2: orderSpd = 0; kbrake(2); mode = MD_BRAKE; break; case MC_B1: // 制動1 orderSpd = 0; kbrake(1); mode = MD_BRAKE; break; case MC_N: orderSpd = 0; mode = MD_NTRL; break; case MC_P1: orderSpd = notch1; kaccel(1); mode = MD_ACCEL; break; case MC_P2: orderSpd = notch2; kaccel(2); mode = MD_ACCEL; break; case MC_P3: orderSpd = notch3; kaccel(3); mode = MD_ACCEL; break; case MC_P4: orderSpd = notch4; kaccel(4); mode = MD_ACCEL; break; case MC_P5: orderSpd = notch5; kaccel(5); mode = MD_ACCEL; break; } for ( k = 0; k < ANTI_CHAT_RATE; k++ ) { // チャタリング防止ループ dispMasPos(masconPos); if (spd < orderSpd) { if (orderSpd - spd <= kasoku) { spd = orderSpd; } else { spd = spd + kasoku; } } else if ( spd > 0 ) { if ( masconPos >= MC_P1 ) { // 力行 if ( spd - orderSpd <= 0 ) { spd = orderSpd; } else { spd = spd * (1.0 - (pcoast / 10000.0)); } } else if ( masconPos == MC_N ) { // 惰行 spd = spd * (1.0 - (coast / 10000.0)); } else { // 制動 if ( spd - kasoku < 0 ) { spd = 0; } else { spd = spd - kasoku; if ( spd < stopSpd ) { // 惰行or制動ノッチのとき、ピタ停止速度値よりも現行速度が下回ったらピタッと停止させる。 spd = 0; mode = MD_STOP; } } } } duty = ((float)spd * adrate / 4000000.0)* sscale; if (duty > 1.0){ duty = 1.0; } if(viewduty == true){ lcd.setCursor(0, 0); lcd.print("DUTY= "); lcd.setCursor(5, 0); lcd.print(duty); } 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 && AsttFrq == -1) { // 走行音が設定されていない場合の処理 pwm(); spwm(2); break; } if (mode == MD_BRAKE) { // 変調モード切替 break; } if (mode == MD_NTRL) { // 変調モード切替 break; } if (AsttFrq == -1) { // 高速時 VVVF音停止 pwm(); spwm(2); break; } // VVVF音 if ((spd >= AsttSpd * 10000) && (spd < AendSpd * 10000)) { Afrq = ( AendFrq * 10 - AsttFrq * 10 ) / ( AendSpd - AsttSpd ) * ( spd - AsttSpd * 10000 ) / 100000 + AsttFrq; if ( Afrq < 150 ) Afrq = 150; else if ( Afrq > 100000 ) Afrq = 100000; pwm(); spwm(0); 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) { // 走行音が設定されていない場合の処理 pwm(); spwm(2); break; } if (mode == MD_ACCEL) { // 変調モード切替 break; } if (mode == MD_NTRL) { // 変調モード切替 break; } if (BsttFrq == -1) { // 高速時 VVVF音停止 pwm(); spwm(2); break; } // VVVF音 if ((spd >= BsttSpd * 10000) && (spd < BendSpd * 10000)) { Bfrq = ( BendFrq * 10 - BsttFrq * 10 ) / ( BendSpd - BsttSpd ) * ( spd - BsttSpd * 10000 ) / 100000 + BsttFrq; if ( Bfrq < 150 ) Bfrq = 150; else if ( Bfrq > 100000 ) Bfrq = 100000; pwm(); spwm(1); break; } } } else if(mode == MD_NTRL) { // 惰行・非常 pwm(); spwm(2); } else { // 停止時 音停止 pwm(); //走行音用 TCCR1A = B00000001; TCCR1B = B00010001; } } if (isUSB == true) { Result = Receive(); switch (Result) { case -1: //未受信 Serial.write(255); Serial.write(254); Serial.write(masconPos); if(dispSpd > 200){ Serial.write(200); Serial.write(dispSpd - 200); }else{ Serial.write(dispSpd); Serial.write(0); } break; case 0: //正常受信 if (spd == 0) { //列車が停止している Serial.write(255); Serial.write(253); Serial.write(0); SetSound(ReceiveData[1], ReceiveData[2]); } else { //列車走行中 Serial.write(255); Serial.write(253); Serial.write(1); } break; case 1: //受信エラー Serial.write(255); Serial.write(253); Serial.write(2); break; } } }
/*
 ***************************************************************************************
  WhiteBear Controller Next Generation
 ***************************************************************************************
  ・PWM制御による鉄道模型用コントローラー。
  ・12段階のロータリースイッチを用い、ワンハンドルマスコンを模擬する。
  ・マスコンはP5-B5 + EB の12段。
  ・各種状況はキャラクタLCD(16文字x2段)で表示。
  ・力行各段の最高速度を設定可能。設定はソースコード内であらかじめ設定。
  ・ノッチによる加速度の違いをついでに再現。
  ・速度に応じた加速度の変化もだいたい再現。
  ・PWM周波数の変調により、VVVFなど制御機器の音を再現。
  ・変調音は最大255種類までプログラム可能。(メモリ容量によってはプログラム可能な最大数が少なくなります)
  ・加速時と減速時で異なる変調音を設定可能。
 ・外部制御モードを搭載。ノッチ反応速度と走行音を外部から随時変更可能。
 ***************************************************************************************
更新履歴
2019/07/30 V1.00 初期版。
2019/10/11 V1.10 音データをフラッシュメモリ格納に変更し、RAM不足を解消。
2020/01/30 V1.20 USBによる外部制御モードの追加
2020/02/21 V2.00 速度制御方式の変更(加速度変動機能)及びバグ修正
2020/04/25 V2.10 指定した周波数で出力されないことがあるバグを修正
2020/05/10 V2.20 マスコンのチャタリング対策を強化
2020/06/17 V2.21 マスコンのチャタリング対策を更に強化
2020/09/22 V2.30 加速度調節の細分化(8段階→16段階)、RAM使用量の削減
2020/11/17 V3.00 PWM周波数および出力設定の変更、加速度調節の細分化(16段階→32段階)、
                 常点灯の感度調整機能追加、EBの動作変更、スケールスピード調整機能追加、
                 最高速度400km/hに変更(モーターの性能により400km/hに届かない場合もあります)
2021/07/03 V3.10 速度及びノッチ情報について外部に送信するように変更。外部からモニタリングが
                 できるようになりました。
                 加速曲線と減速度を音データ毎に決められるようになりました。
                 表示速度の変化率に対するDuty比の変化率を調整できるようにしました。
2022/07/15 V3.20 惰行時・各力行ノッチ最高速度以上時の減速率計算方法を変更しました。
 ***************************************************************************************
 */

#include <avr/io.h> // ATmega328P用のヘッダファイル
#include <avr/pgmspace.h>
#include <LiquidCrystal.h> // キャラクタ液晶ディスプレイ制御用のヘッダファイル



// ***********************************************
// ユーザー設定領域
// 環境に合わせて各値を適宜調整してください。
// ***********************************************

#define SOUNDNUM 16 //走行音データの数。13番目以降のデータを選択するには外部通信端末またはPC用ソフトウェアとの接続が必要です。PC用ソフトウェアはHP上に公開中。
#define STOPSPD 9 //ピタッと止めるための値。環境に合わせて調整可能
#define SDATANUM 45 //3n(n=最も行数が多い音データの行数)以上の値に設定して下さい。あまり数字を大きくし過ぎるとメモリ不足になるため、むやみに大きくしないように。
#define ANTI_CHAT_RATE_NOUSB 10 //外部通信なしの際のチャタリング防止ループの値
const int DECISION_TIME = 3;   // setup時に走行パターンを決める際の、確定までの時間。秒。
const int ACCEL_RATIO = 5;    // (加速率調整小型ボリュームからの入力値) × ACCEL_RATIO ⇒ 加速率
const int BRAKE_RATIO = 2;    // (加速率調整小型ボリュームからの入力値) × BRAKE_RATIO × マスコンブレーキ値(1~5) ⇒ 減速率
const int MAS_CHAT = 80; //ロータリスイッチが中間で止まった時の異常な指令を回避する。EBに投入した時のアナログ値を少し下回る値を設定。
const float lmax = 2; //常点灯ボリュームの最大値。lmax=1→出力MAX10%まで、1.5→出力MAX15%まで、のようにボリュームの範囲を調整可能。選択可能な範囲を狭くするほど微調整が可能となる。
const float sscale = 3; //表示速度に対するDuty比の倍率。環境に合わせ実測して調整。
const float adrate = 0.5; //加速度及び減速度の変化に対するDuty比の変化率。環境に合わせ実測して調整。
const float coast = 0.07; //惰行時最高速度以上時の減速率。
const float pcoast = 0.01; //各力行ノッチ最高速度以上時の減速率。
/*
sscaleとadrateの関係について:
sscaleは表示速度に対するduty比を全域にわたって調整します。グラフをそのまま上下に動かすイメージです。
主に列車の起動速度調整に使用します。
一方、adrateは表示速度の変化に対するDuty比の変化率を調整します。
加速時を例とすると、表示速度の上昇速度に対するDuty比の上昇速度を調整します。
加速度ボリュームは表示速度の変化率を調整するのに対し、adrateは表示速度に対する実速度の変化率を調整する項目であるといえます。
*/
 
boolean viewduty = false;  //(開発用)走行中、走行音名の代わりにDUTY比を表示します。

// ***********************************************
// 走行音データ領域
// データの追加や変更を行う場合は慎重に行ってください。
// ***********************************************

// 走行音名称。16桁で指定すること
char* soundName[SOUNDNUM] = {
  "   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",
  "MITSUBISHI Chop.",
  "N1000 (SUS-IGBT)",
  "N1000 (SI-IGBT) ",
  " Toyo-IGBT(AE)  "
};



// 各パターンの各ノッチ位置での最高速度値を配列で保持。
// 左から順に 1ノッチ, 2ノッチ, 3ノッチ, 4ノッチ, 5ノッチ。
const int maxSpdDataBase[] PROGMEM = {
  31, 46, 101, 111, 111, //パターン 1 201 
  41, 56, 101, 111, 111, //パターン 2 209,70-000,etc
  41, 56, 111, 121, 121, //パターン 3 E231-0,500,etc
  41, 56, 111, 121, 121, //パターン 4 E231-1000
  41, 56, 111, 121, 121, //パターン 5 E233, etc
  51, 66, 121, 131, 131, //パターン 6 KQ 2100 (SI-GTO)
  51, 66, 121, 131, 131, //パターン 7 N1000 (SI-GTO)
  51, 66, 121, 131, 131, //パターン 8 MITSUBISHI-GTO
  51, 66, 121, 131, 131, //パターン 9 Toyo-GTO
  51, 66, 121, 131, 131, //パターン10 Toyo-IGBT
  41, 56, 101, 111, 111, //パターン11 Toei 5300(Akuma)
  41, 56, 101, 111, 111, //パターン12 TOSHIBA 3LevIGBT
  41, 56, 101, 111, 111, //パターン13 MITSUBISHI Chop.
  51, 66, 121, 131, 131, //パターン14 N1000 (SUS-IGBT)
  51, 66, 121, 131, 131, //パターン15 N1000 (SI-IGBT)
  51, 66, 151, 161, 161  //パターン16 Toyo-IGBT(AE)
};



//定加速度領域値。
//指定した速度まで一定の加速度を保ちます。
int keepac[SOUNDNUM] = {37, 39, 39, 39, 39, 55, 55, 55, 55, 59, 50, 50, 45, 59, 55, 85};

//加速曲線。
//数値を大きくすると加速性能が良くなります。お好みで調整して下さい。
const float Aracurve[SOUNDNUM] = {1.2, 1.3, 1.4, 1.4, 1.5, 1.55, 1.55, 1.5, 1.5, 1.4, 1.4, 1.45, 1.45, 1.5, 1.5, 1.4};

//減速度。
//数値を大きくすると減速性能が良くなります。お好みで調整して下さい。
const float Arbcurve[SOUNDNUM] = {0.13, 0.15, 0.15, 0.15, 0.16, 0.14, 0.14, 0.14, 0.15, 0.17, 0.14, 0.14, 0.14, 0.14, 0.14, 0.17};

// 音データ。
// 設定可能周波数 150Hz~100kHz(値は整数で指定)
// ~~~(例) パターン3の場合~~~
// 1行目 速度0~2の間:380Hzで音程変化なし
// 2行目 速度3~11の間:380Hzから980Hzまで変化する

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

  //パターン2 209,70-000,etc
  230, 230, 2,     // 1 開始周波数 終了周波数 切替スピード
  90, 90, 3,       // 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, 2,     // 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
  760, 760, 31,    // 1 開始周波数 終了周波数 切替スピード
  350, 1150, 101,  // 2 開始周波数 終了周波数 切替スピード
  1150, 1150, 121, // 3 開始周波数 終了周波数 切替スピード
  -1,              // 4 終了コード

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

  //パターン7 N1000 (SI-GTO)
  350, 350, 2,   // 1 開始周波数 終了周波数 切替スピード
  390, 390, 3,   // 2 開始周波数 終了周波数 切替スピード
  440, 440, 4,   // 3 開始周波数 終了周波数 切替スピード
  490, 490, 5,   // 4 開始周波数 終了周波数 切替スピード
  530, 530, 6,   // 5 開始周波数 終了周波数 切替スピード
  580, 580, 7,   // 6 開始周波数 終了周波数 切替スピード
  650, 650, 8,   // 7 開始周波数 終了周波数 切替スピード
  700, 700, 9,   // 8 開始周波数 終了周波数 切替スピード
  780, 780, 16,  // 9 開始周波数 終了周波数 切替スピード
  950, 970, 18,  //10 開始周波数 終了周波数 切替スピード
  850, 970, 21,  //11 開始周波数 終了周波数 切替スピード
  800, 970, 23,  //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, 19,    // 1 開始周波数 終了周波数 切替スピード
  490, 780, 31,    // 2 開始周波数 終了周波数 切替スピード
  420, 1250, 110,  // 3 開始周波数 終了周波数 切替スピード
  1250, 1250, 131, // 4 開始周波数 終了周波数 切替スピード
  -1,              // 5 終了コード

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

  //パターン11 Toei 5300(Akuma)
  780, 780, 11,    // 1 開始周波数 終了周波数 切替スピード
  450, 450, 16,    // 2 開始周波数 終了周波数 切替スピード
  500, 780, 22,    // 3 開始周波数 終了周波数 切替スピード
  430, 580, 25,    // 4 開始周波数 終了周波数 切替スピード
  400, 1150, 105,  // 5 開始周波数 終了周波数 切替スピード
  1150, 1150, 121, // 6 開始周波数 終了周波数 切替スピード
  -1,              // 7 終了コード

  //パターン12 TOSHIBA 3LevIGBT
  730, 730, 11,    // 1 開始周波数 終了周波数 切替スピード
  510, 730, 16,    // 2 開始周波数 終了周波数 切替スピード
  600, 1150, 101,  // 3 開始周波数 終了周波数 切替スピード
  1150, 1150, 111, // 4 開始周波数 終了周波数 切替スピード
  -1,              // 5 終了コード

  //パターン13 MITSUBISHI Chop.
  590, 590, 4,     // 1 開始周波数 終了周波数 切替スピード
  900, 900, 36,    // 2 開始周波数 終了周波数 切替スピード
  590, 590, 41,    // 3 開始周波数 終了周波数 切替スピード
  390, 390, 46,    // 4 開始周波数 終了周波数 切替スピード
  390, 1150, 101,  // 5 開始周波数 終了周波数 切替スピード
  1150, 1150, 111, // 6 開始周波数 終了周波数 切替スピード
  -1,              // 7 終了コード

  //パターン14 N1000 (SUS-IGBT)
  710, 710, 31,    // 1 開始周波数 終了周波数 切替スピード
  350, 1150, 101,  // 2 開始周波数 終了周波数 切替スピード
  1150, 1150, 131, // 3 開始周波数 終了周波数 切替スピード
  -1,              // 4 終了コード

  //パターン15 N1000 (SI-IGBT)
  1180, 1180, 16,  // 1 開始周波数 終了周波数 切替スピード
  1180, 1960, 33,  // 2 開始周波数 終了周波数 切替スピード
  460, 1150, 101,  // 3 開始周波数 終了周波数 切替スピード
  1150, 1150, 131, // 4 開始周波数 終了周波数 切替スピード
  -1,              // 5 終了コード

  //パターン16 Toyo-IGBT(AE)
  1000, 1000, 26,  // 1 開始周波数 終了周波数 切替スピード
  580, 1300, 41,   // 2 開始周波数 終了周波数 切替スピード
  650, 1150, 141,  // 3 開始周波数 終了周波数 切替スピード
  1150, 1150, 161, // 4 開始周波数 終了周波数 切替スピード
  -1               // 5 終了コード

};

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

  //パターン2 209,70-000,etc
  150, 150, 6,     // 1 開始周波数 終了周波数 切替スピード
  150, 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
  760, 760, 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,    // 1 開始周波数 終了周波数 切替スピード
  440, 460, 22,    // 2 開始周波数 終了周波数 切替スピード
  460, 570, 36,    // 3 開始周波数 終了周波数 切替スピード
  500, 1150, 105,  // 4 開始周波数 終了周波数 切替スピード
  1150, 1150, 121, // 5 開始周波数 終了周波数 切替スピード
  -1,              // 6 終了コード

  //パターン12 TOSHIBA 3LevIGBT
  730, 730, 11,    // 1 開始周波数 終了周波数 切替スピード
  510, 1150, 101,  // 2 開始周波数 終了周波数 切替スピード
  1150, 1150, 111, // 3 開始周波数 終了周波数 切替スピード
  -1,              // 4 終了コード

  //パターン13 MITSUBISHI Chop.
  900, 900, 36,    // 1 開始周波数 終了周波数 切替スピード
  590, 590, 41,    // 2 開始周波数 終了周波数 切替スピード
  390, 390, 46,    // 3 開始周波数 終了周波数 切替スピード
  390, 1150, 101,  // 4 開始周波数 終了周波数 切替スピード
  1150, 1150, 111, // 5 開始周波数 終了周波数 切替スピード
  -1,              // 6 終了コード

  //パターン14 N1000 (SUS-IGBT)
  710, 710, 31,    // 1 開始周波数 終了周波数 切替スピード
  350, 1150, 101,  // 2 開始周波数 終了周波数 切替スピード
  1150, 1150, 131, // 3 開始周波数 終了周波数 切替スピード
  -1,              // 4 終了コード

  //パターン15 N1000 (SI-IGBT)
  1180, 1180, 16, // 1 開始周波数 終了周波数 切替スピード
  1180, 2050, 31, // 2 開始周波数 終了周波数 切替スピード
  780, 1150, 36,  // 3 開始周波数 終了周波数 切替スピード
  560, 980, 111,  // 4 開始周波数 終了周波数 切替スピード
  580, 580, 131,  // 5 開始周波数 終了周波数 切替スピード
  -1,             // 6 終了コード

  //パターン16 Toyo-IGBT(AE)
  1000, 1000, 26, // 1 開始周波数 終了周波数 切替スピード
  600, 880, 41,   // 2 開始周波数 終了周波数 切替スピード
  600, 950, 95,   // 3 開始周波数 終了周波数 切替スピード
  950, 950, 106,  // 4 開始周波数 終了周波数 切替スピード
  580, 580, 161,  // 5 開始周波数 終了周波数 切替スピード
  -1              // 6 終了コード

};










// ***********************************************
// ここから先はみだりに変更しないで下さい。
// 誤動作や故障の原因となります。
// ***********************************************

int ANTI_CHAT_RATE; // チャタリング防止ループの値。ノッチの反応速度調整に使用。USB接続では動的設定可能。

//外部通信用
int Result; //データ受信状況
int ReceiveData[] = {255, 255, 0}; //外部制御モード時に受け取った値
/*
送受信パケットは以下の様に定義する。
・送受信はHEX(16進)で行う。
・送信パケット=(ヘッダ)(モード1)(マスコン位置)(速度1)(速度2)の5バイトまたは(ヘッダ)(モード2)(受信結果)の3バイト。
 ヘッダ=255、モード1=254、モード2=253、マスコン位置=1(EB)~12(P5)。
 速度1=0~200、速度2=0~200。速度=速度1+速度2とする。
 受信結果=0(正常受信),=1(走行中またはブレーキノッチ未投入),=2(受信失敗)。
・受信パケット=(ヘッダ)(チャタリング防止ループ値)(走行音データ番号) の3バイト。ヘッダ=0
・チャタリング防止ループ値、走行音データ番号の値は1~255。
・よって走行音データは(理論上は)255種類まで搭載可能だが、増やせば当然メモリ食うので現実には限界がある。
・走行音は用意したデータ数より大きい番号を指定しないように送信側で制御を行うこと。
*/

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

//使用ピン指定
const int P_MASCON     = A3; // Arduino接続ピン番号:(Analog) ロータリースイッチ = A3
const int P_LIGHT_DIAL = A4; // Arduino接続ピン番号:(Analog) 常点灯調節ダイアル = A4
const int P_ACCEL_DIAL = A5; // 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) OCR1B出力 to Rail = D11

//マスコン用
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 isUSB;      // 外部制御モードのときTrue。
int vvvfPtn;        // 走行音パターン。
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;      // 内部スピード 0~3,000,000(速度×10,000)
long orderSpd; // 指示スピード
float duty; //duty比
float acurve; //加速曲線
float bcurve;  //減速度
int i; // ループカウンター
int k; // ループカウンター
int dispSpd = 0;      // LCD表示用スピード値
int maxSpdData[5];
int AsoundData[SDATANUM];

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

int BsoundData[SDATANUM];

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

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










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



// ***********************************************
// マスコン位置を検出する。
// ロータリースイッチからのボリューム値を拾い、
// 適度な値に変換する処理。
// 
// 引数: アナログピンから取得した値(Max:1023)
// 戻値: 1(=EB)~12(=P5) 
// ***********************************************

// ロータリースイッチが12段階切替なので、入力を12段階に分ける。
int getMasconVol(int vol) {
  int ans = 0;
  // マスコンロータリースイッチから読み取った値を、小数点も扱える形で
  // 0~1023に収まるようにする。12接点なので12等分。
  // 四捨五入相当の処理をする⇒値に+0.5した上で、小数点以下を切り捨てるためint型に変換。
  if(analogRead(P_MASCON)>MAS_CHAT){
  ans = (int)(((double) analogRead(P_MASCON)) / (1024 / 12) + 0.5);
  if (ans == MC_EB) {
    for ( k = 0; k < ANTI_CHAT_RATE; k++ ) { // チャタリング防止ループ
      ans = (int)(((double) analogRead(P_MASCON)) / (1024 / 12) + 0.5);
    }
  }
  return ans;
  }
}



// マスコン位置を表示。LCDの右下2桁へ。
void dispMasPos(int masPos) {
  lcd.setCursor(14, 1);
  lcd.print(masconPosName[masPos - 1]);

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

}



//外部制御用
void SetSound(byte CHAT, byte VVVF){
  // 走行音パターンほか選択したパターンに合わせて設定
  ANTI_CHAT_RATE = (int)CHAT;
  vvvfPtn = (int)VVVF  - 1;

  acurve = Aracurve[(int)VVVF];
  bcurve = Arbcurve[(int)VVVF];

  k = 0;

  for (i = vvvfPtn * 5; i < vvvfPtn * 5 + 5; i++)
  {
  maxSpdData[k] = pgm_read_word_near(&maxSpdDataBase[i]);
  k++;
  }
    
  notch1  = (long) maxSpdData[0] * 10000 - 1;
  notch2  = (long) maxSpdData[1] * 10000 - 1;
  notch3  = (long) maxSpdData[2] * 10000 - 1;
  notch4  = (long) maxSpdData[3] * 10000 - 1;
  notch5  = (long) maxSpdData[4] * 10000 - 1;
  stopSpd = (long) STOPSPD * 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(F("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(F("Pls Apply BRAKE."));
    delay(240);
    masconPos = getMasconVol(analogRead(P_MASCON));
  }
  
  lcd.clear();
  lcd.print(F("Ready."));
  delay(720);
  lcd.clear();
  lcd.print(soundName[vvvfPtn]);
  lcd.setCursor(3, 1);
  lcd.print(F("km/h"));
  lcd.setCursor(7, 1);
}



//シリアル受信用
int Receive(){

  while (Serial.available() > 0) { // 受信したデータが存在する
    if(Serial.available() >= 3) { //パケットを正常に受信している
     ReceiveData[0] = Serial.read();
      if (ReceiveData[0] == 0x00) { //ヘッダが先頭である
       ReceiveData[1] = Serial.read(); //チャタリング防止ループ値
       ReceiveData[2] = Serial.read(); //走行音データ番号
       //キャッシュ解放処理
       while (Serial.available() > 0) {
       Serial.read();
       } 
       return 0; //正常受信
      } else {
       //キャッシュ解放処理
       while (Serial.available() > 0) {
       Serial.read();
       }
      return 1; //受信エラー
      }
    }
  }
  return -1; //未受信
}



//加速度調整。現在速度が0km/hのとき1倍、各ノッチの最高速度以上のとき0倍になるよう調整。
void kaccel(int notch) {
  float rate, rate2;
  switch(notch){
    case 1:
      if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
        rate = 1;
      }
      else if (notch1 - spd > 0) {  //各ノッチの最高速度未満
        rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
        rate = pow(rate2, acurve);
      }
      else {  //各ノッチの最高速度以上
       rate = 0; 
      }
      kasoku = accelVol * ACCEL_RATIO * 0.3 * rate;
      break;

    case 2:
      if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
        rate = 1;
      }
      else if (notch2 - spd > 0) {  //各ノッチの最高速度未満
        rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
        rate = pow(rate2, acurve);
      }
      else {  //各ノッチの最高速度以上
       rate = 0; 
      }
      kasoku = accelVol * ACCEL_RATIO * 0.5 * rate;
      break;

    case 3:
      if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
        rate = 1;
      }
      else if (notch3 - spd > 0) {  //各ノッチの最高速度以下
        rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
        rate = pow(rate2, acurve);
      }
      else {  //各ノッチの最高速度未満
       rate = 0; 
      }
      kasoku = accelVol * ACCEL_RATIO * 0.8 * rate;
      break;

    case 4:
      if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
        rate = 1;
      }
      else if (notch4 - spd > 0) {  //各ノッチの最高速度未満rate = ((float)keepac[vvvfPtn]*10000 / (float)spd) * 2.0;
        rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
        rate = pow(rate2, acurve);
      }
      else {  //各ノッチの最高速度以上
       rate = 0; 
      }
      kasoku = accelVol * ACCEL_RATIO * 0.9 * rate;
      break;

    case 5:
      if (spd == 0 || spd <= (long)keepac[vvvfPtn]*10000) { //現在速度が0km/hまたは定加速度領域以下
        rate = 1;
      }
      else if (notch5 - spd > 0) {  //各ノッチの最高速度未満
        rate2 = (float)keepac[vvvfPtn]*10000 / (float)spd;
        rate = pow(rate2, acurve);
      }
      else {  //各ノッチの最高速度以上
       rate = 0; 
      }
      kasoku = accelVol * ACCEL_RATIO * rate;
      break;
  }
}



//減速度。現在速度が0km/hのとき1倍、P5の最高速度のとき約0.3倍になるよう調整。
void kbrake(int notch) {
  float rate;
  if (spd == 0) { //0km/h
    rate = 1;
  }
  else{
    rate = 1 - sqrt((float)((spd * bcurve) / notch5));
  }
  kasoku = accelVol * BRAKE_RATIO * notch * rate;
}



//常点灯および走行制御
void pwm(){
  
  //常点灯用と走行用
  TCCR2A = B10100001;
  TCCR2B = B00000001;
  OCR2B = (unsigned int)(255 * ((float)(lightVol * lmax) / 10230.0));
  OCR2A = (unsigned int)(255 * duty);

}



//走行音制御
void spwm(int stype){
  
  //走行音用
  TCCR1A = B00100001;
  TCCR1B = B00010001;
  
  if(stype == 0){
    OCR1A = (unsigned int)(8000000 / Afrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
    OCR1B = (unsigned int)(8000000 / Afrq / 100);
  }
  else if(stype == 1){
    OCR1A = (unsigned int)(8000000 / Bfrq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
    OCR1B = (unsigned int)(8000000 / Bfrq / 100);
  }
  else{
    OCR1A = (unsigned int)400; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / 20000 = 400
    OCR1B = (unsigned int)(1);
  }
}







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

  // 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);
  lcd.clear();

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

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

  k = 0;
  
  for (i = 0; i<SDATANUM; i++)
  {
  AsoundData[k] = 0;
  k++;
  }

  k = 0;
 
  for (i = 0; i<SDATANUM; i++)
  {
  BsoundData[k] = 0;
  k++;
  }
  
  lcd.home();  // 1文字目、1行目
  
  //外部制御モードの設定(USB接続)
  lcd.clear();
  delay(180);
  lcd.setCursor(0, 0);
  lcd.print(F(" If using USB,  "));
  lcd.setCursor(0, 1);
  lcd.print(F("Please Set to EB"));
  delay(3000);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(F("Use USB Connect?"));
  delay(480);
  
  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行目
        isUSB = (MC_EB == getMasconVol(analogRead(P_MASCON)));
        if (isUSB == true){
          lcd.print(F("Yes")); // Y or N
        } else {
          lcd.print(F("No ")); // Y or N
        }
      } else {
        if ((millis() - sttTime) > DECISION_TIME * 1000) {
          // 選んでから所定時間が過ぎた。
          break; // ループを抜ける。
        } 
      }
      progress = millis() - sttTime;
      if (progress > 0) {
        lcd.setCursor(15, 1);
        lcd.print((String) (DECISION_TIME - (progress / 1000)));
      }
      delay(1);
    }
  } while (masconPos < MC_EB || masconPos > MC_P5); // 12種類から選択されたらループを抜け次の処理へ。

  //外部制御モード
  if (isUSB == true){
   lcd.clear();
   delay(180);
   lcd.setCursor(0, 0);
   lcd.print(F("Use USB Connect."));
   delay(2000);
   lcd.setCursor(0, 1);
   lcd.print(F(" Connecting...  "));
   Serial.begin(9600);
   
   do{
    Result = Receive();
     switch (Result) {
      case -1: //未受信
       break;
      case 0: //正常受信
        if (spd == 0) { //列車が停止している
          Serial.write(255);
          Serial.write(253);
          Serial.write(0);
          //SetSound(ReceiveData[1], ReceiveData[2]);
        } else {  //列車走行中
          Serial.write(255);
          Serial.write(253);
          Serial.write(1);
        }
        break;
      case 1: //受信エラー
        Serial.write(255);
        Serial.write(253);
        Serial.write(2);
        break;
     }
   } while (Result != 0);

   SetSound(ReceiveData[1], ReceiveData[2]);
   
  }
    
  //独立モード
  else {
   ANTI_CHAT_RATE = ANTI_CHAT_RATE_NOUSB; // チャタリング防止ループの値。ノッチの反応速度調整に使用。
   lcd.clear();
   delay(180);
   lcd.setCursor(0, 0);
   lcd.print(F("  Not Use USB.  "));
   delay(2000);
   
    // 走行音パターン取得
    for (i=0; i<2; i++) {
      lcd.clear();
      delay(180);
      lcd.print(F("Select SoundPtn."));
      delay(480);
    }
    delay(240);
    lcd.clear();
    lcd.print(F("SoundPtn."));
    lcd.setCursor(0, 1);
    lcd.print(F(" Revolve MasCon."));
    //delay(2000);
    
    // ロータリースイッチを5秒動かさないと決定。
    // ただし、何も動かしてないときはずっと待ち続ける。
    
    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;

    acurve = Aracurve[vvvfPtn];
    bcurve = Arbcurve[vvvfPtn];
    
    k = 0;

    for (i = vvvfPtn * 5; i < vvvfPtn * 5 + 5; i++)
    {
    maxSpdData[k] = pgm_read_word_near(&maxSpdDataBase[i]);
    k++;
    }
  
    notch1  = (long) maxSpdData[0] * 10000 - 1;
    notch2  = (long) maxSpdData[1] * 10000 - 1;
    notch3  = (long) maxSpdData[2] * 10000 - 1;
    notch4  = (long) maxSpdData[3] * 10000 - 1;
    notch5  = (long) maxSpdData[4] * 10000 - 1;
    stopSpd = STOPSPD * 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(F("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(F("Pls Apply BRAKE."));
      delay(240);
      masconPos = getMasconVol(analogRead(P_MASCON));
    }
    
    lcd.clear();
    lcd.print(F("Ready."));
    delay(720);
    lcd.clear();
    lcd.print(soundName[vvvfPtn]);
    lcd.setCursor(3, 1);
    lcd.print(F("km/h"));
    lcd.setCursor(7, 1);

  }
}










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

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

  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 MC_EB:
      orderSpd = 0;
      kbrake(7);
      mode = MD_NTRL;
      break;
    case MC_B5:
      orderSpd = 0;
      kbrake(5);
      mode = MD_BRAKE;
      break;
    case MC_B4:
      orderSpd = 0;
      kbrake(4);
      mode = MD_BRAKE;
      break;
    case MC_B3:
      orderSpd = 0;
      kbrake(3);
      mode = MD_BRAKE;
      break;
    case MC_B2:
      orderSpd = 0;
      kbrake(2);
      mode = MD_BRAKE;
      break;
    case MC_B1:
      // 制動1
      orderSpd = 0;
      kbrake(1);
      mode = MD_BRAKE;
      break;
    case MC_N:
      orderSpd = 0;
      mode = MD_NTRL;
      break;
    case MC_P1:
      orderSpd = notch1;
      kaccel(1);
      mode = MD_ACCEL;
      break;
    case MC_P2:
      orderSpd = notch2;
      kaccel(2);
      mode = MD_ACCEL;
      break;
    case MC_P3:
      orderSpd = notch3;
      kaccel(3);
      mode = MD_ACCEL;
      break;
    case MC_P4:
      orderSpd = notch4;
      kaccel(4);
      mode = MD_ACCEL;
      break;
    case MC_P5:
      orderSpd = notch5;
      kaccel(5);
      mode = MD_ACCEL;
      break;
  }

  for ( k = 0; k < ANTI_CHAT_RATE; k++ ) { // チャタリング防止ループ
    dispMasPos(masconPos);
    if (spd < orderSpd) {
      if (orderSpd - spd <= kasoku) {
        spd = orderSpd;
      } else {
        spd = spd + kasoku;
      }
    }
    else if ( spd > 0 ) {
      if ( masconPos >= MC_P1 ) {
        // 力行
        if ( spd - orderSpd <= 0 ) {
          spd = orderSpd;
        } else {
          spd = spd * (1.0 - (pcoast / 10000.0));
        }
      }
      else if ( masconPos == MC_N ) {
        // 惰行
        spd = spd * (1.0 - (coast / 10000.0));
      }
      else {
        // 制動
        if ( spd - kasoku < 0 ) {
          spd = 0;
        } else {
          spd = spd - kasoku;

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

    duty = ((float)spd * adrate / 4000000.0)* sscale;
    if (duty > 1.0){
      duty = 1.0;
    }

    if(viewduty == true){
      lcd.setCursor(0, 0);
      lcd.print("DUTY=           ");
      lcd.setCursor(5, 0);
      lcd.print(duty);
    }
        
    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 && AsttFrq == -1) { // 走行音が設定されていない場合の処理

          pwm();
          spwm(2);

          break;
        }

        if (mode == MD_BRAKE) { // 変調モード切替
        break; 
        }
        
        if (mode == MD_NTRL) { // 変調モード切替
        break; 
        }
  
        if (AsttFrq == -1) { // 高速時 VVVF音停止
          
          pwm();
          spwm(2);

          break;
        }
  
        // VVVF音
        if ((spd >= AsttSpd * 10000) && (spd < AendSpd * 10000)) {
          Afrq = ( AendFrq * 10 - AsttFrq * 10 ) / ( AendSpd - AsttSpd ) * ( spd - AsttSpd * 10000 ) / 100000 + AsttFrq;
          if ( Afrq < 150 )        Afrq = 150;
          else if ( Afrq > 100000 ) Afrq = 100000;

          pwm();
          spwm(0);
          
          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) { // 走行音が設定されていない場合の処理
          
          pwm();
          spwm(2);
          
          break;
        }

        if (mode == MD_ACCEL) { // 変調モード切替
        break; 
        }
     
        if (mode == MD_NTRL) { // 変調モード切替
        break; 
        }
         
        if (BsttFrq == -1) { // 高速時 VVVF音停止
          
          pwm();
          spwm(2);
          
          break;
        }
  
        // VVVF音
        if ((spd >= BsttSpd * 10000) && (spd < BendSpd * 10000)) {
          Bfrq = ( BendFrq * 10 - BsttFrq * 10 ) / ( BendSpd - BsttSpd ) * ( spd - BsttSpd * 10000 ) / 100000 + BsttFrq;
          if ( Bfrq < 150 )        Bfrq = 150;
          else if ( Bfrq > 100000 ) Bfrq = 100000;
          
          pwm();
          spwm(1);
          
          break;
        }
      }
    }

    else if(mode == MD_NTRL) { // 惰行・非常
      
      pwm();
      spwm(2);
      
    }
      
    else { // 停止時 音停止

      pwm();
      
      //走行音用
      TCCR1A = B00000001;
      TCCR1B = B00010001;
          
    }
  }

  if (isUSB == true) {
    
   Result = Receive();
   switch (Result) {
    case -1: //未受信
      Serial.write(255);
      Serial.write(254);
      Serial.write(masconPos);
      if(dispSpd > 200){
        Serial.write(200);
        Serial.write(dispSpd - 200);
      }else{
        Serial.write(dispSpd);
        Serial.write(0);
      }
      break;
      
    case 0: //正常受信
      if (spd == 0) { //列車が停止している
        Serial.write(255);
        Serial.write(253);
        Serial.write(0);
        SetSound(ReceiveData[1], ReceiveData[2]);
      } else {  //列車走行中
        Serial.write(255);
        Serial.write(253);
        Serial.write(1);
      }
      break;
    case 1: //受信エラー
      Serial.write(255);
      Serial.write(253);
      Serial.write(2);
      break;
   }
  }
  
}