Arduinoで鉄道模型PWMコントローラーを自作 ~ソース公開~
2017年 04月 25日
その後転職活動などで慌ただしい日々を送り、2年の月日が経っていました。
その間に、上手くゆかずに困っていらっしゃる方からソースコードの開示の依頼をいただいていました。
(しかも2016年6月…半年以上も気づかずに申し訳ない限りです。。)
私のソースを秘密にしておくものでもありませんので、ここで皆さんへ公開したいと思います。
###############
2017/04/28 修正
ソースコード内、ヘッダファイルのinclude宣言が、HTMLタグの誤変換のために欠けてしまっていました。
正しく表示されるように修正しました。
###############
// ***************************************************************************************
// WhiteBear Controller R Powered by Arduino
// ***************************************************************************************
// ・PWM制御による鉄道模型用コントローラー。
// ・12段階のロータリースイッチを用い、ワンハンドルマスコンを模擬する。
// ・非常ブレーキ1段、常用ブレーキ5段、惰行1段、力行5段。
// ・各種状況はキャラクタLCD(16文字x2段)で表示。
// ・力行各段の最高速度を設定可能。設定はソースコード内であらかじめ設定。
// ・PWM周波数の変調により、VVVFなど制御機器の音を再現。
// ・力行速度や音の車輛データは最大12種類。
// ・マスコンEBで電源投入すると、起動アニメ表示。
// ・マスコンP5で電源投入すると、開発モード。
// ・開発モード(isDev=True)の時、現在/目標スピード値と加速率/常点灯ダイヤルの値も表示。
// ***************************************************************************************
// ~~~素材~~~~~~~~~~~~~~~~~~秋月電子・マルツURL~~~~~~~~~~~~入数~税込
// ・Arduino相当 Arduino UNO→ http://akizukidenshi.com/catalog/g/gM-07385/ 1 2940
// ・Atmel社 AVRマイコン ATMEGA328P-PU http://www.marutsu.co.jp/shohin_130671/ 1 453
// ・ICソケット 28P 300mil http://www.marutsu.co.jp/shohin_59240/ 1 54
// ・クリスタル(水晶発振子) 16MHz http://akizukidenshi.com/catalog/g/gP-08671/ 1 30
// ・三端子レギュレーター 5V1A http://akizukidenshi.com/catalog/g/gI-06312/ 1 40
// ・電解コンデンサー 100μF http://akizukidenshi.com/catalog/g/gP-03122/ 1 10
// ・積層セラミックコンデンサー 22pF×2 http://akizukidenshi.com/catalog/g/gP-03620/ 20 100
// ・積層セラミックコンデンサー 0.1μF×5 http://akizukidenshi.com/catalog/g/gP-00090/ 10 100
// ・一般整流用ダイオード 1N4001 50V1A http://akizukidenshi.com/catalog/g/gI-08332/ 1 10
// ↑代替 1N4007 1kV1A
// (Arduino相当ここまで)
//
// ・パワーMOS-FET 2SK2232 http://akizukidenshi.com/catalog/g/gI-02414/ 1 50
// ・トランジスタ 2SA1015Y×2 http://akizukidenshi.com/catalog/g/gI-02612/ 10 100
// ・電解コンデンサー 470μF http://akizukidenshi.com/catalog/g/gP-03129/ 1 20
// ・積層セラミックコンデンサー 0.1μF×3 上記参照
// ・セラミックコンデンサー 470pF http://akizukidenshi.com/catalog/g/gP-05243/ 10 150
// ・カーボン抵抗 1kΩ×15 http://akizukidenshi.com/catalog/g/gR-25102/ 100 100
// ・カーボン抵抗 470Ω×7 http://akizukidenshi.com/catalog/g/gR-25471/ 100 100
// ・カーボン抵抗 2.2kΩ×2 http://akizukidenshi.com/catalog/g/gR-25222/ 100 100
// ・カーボン抵抗 10kΩ http://akizukidenshi.com/catalog/g/gR-25103/ 100 100
// ・RotarySW(1回路12接点ストッパ付,NonShortType) 札幌 梅澤無線にて購入
// ・小型ボリューム 10kΩBカーブ×2 http://akizukidenshi.com/catalog/g/gP-00246/ 1 40
// ・トグルスイッチ1回路2接点 http://akizukidenshi.com/catalog/g/gP-03774/ 1 80
// ・トグルスイッチ2回路2接点中点OFFあり http://akizukidenshi.com/catalog/g/gP-07399/ 1 100
// ・LED 緑 赤 各1
// ・キャラクタLCD(16文字2行) SC1602 http://akizukidenshi.com/catalog/g/gP-02919/ 1 800
// ・整流用ショットキーダイオード 1S4 40V1A http://akizukidenshi.com/catalog/g/gI-00127/ 1 20
// ・汎用小信号高速スイッチング・ダイオード
// 1N4148×3 http://akizukidenshi.com/catalog/g/gI-00941/ 50 100
// ・ポリスイッチ 0.65A(1.3Aで遮断) http://akizukidenshi.com/catalog/g/gP-00777/ 1 30
// ・2.1mm標準DCジャック http://akizukidenshi.com/catalog/g/gC-06299/ 1 40
// ・ピンヘッダ(オス)2列 http://akizukidenshi.com/catalog/g/gC-00166/ 1 20
// ・ユニバーサル基板 http://akizukidenshi.com/catalog/g/gP-03229/ 1 60
// ・ACアダプター12V1.5A http://akizukidenshi.com/catalog/g/gM-02194/ 1 950
// ***********************************************
#include <avr/io.h> // ATmega328P用のヘッダファイル
#include <LiquidCrystal.h> // キャラクタ液晶ディスプレイ制御用のヘッダファイル
// ***********************************************
// 定数・変数定義 ここから
// ***********************************************
const char *MOJI1 = "Welcome aboard! "; // Welcomeメッセージ。LCD1段目。16桁にすること。
const char *MOJI2 = " WhiteBear R2.3 "; // Welcomeメッセージ。LCD2段目。16桁にすること。
// オリジナル絵文字:DD51
byte dd51[8] = {
B01110,
B01010,
B01010,
B11011,
B10001,
B10001,
B11111
};
// オリジナル絵文字:コキ
byte koki[8] = {
B11111,
B10001,
B10001,
B10001,
B10001,
B10001,
B11111
};
const int P_MASCON = 3; // Arduino接続ピン番号:(Analog) ロータリースイッチ = ATMEGA328 pin #26
const int P_LIGHT_DIAL = 4; // Arduino接続ピン番号:(Analog) 常点灯調節ダイアル = ATMEGA328 pin #27
const int P_ACCEL_DIAL = 5; // Arduino接続ピン番号:(Analog) 加速率調節ダイアル = ATMEGA328 pin #28
const int P_PWM2B = 3; // Arduino接続ピン番号:(Digital) OCR2B出力 to Rail = ATMEGA328 pin #5
const int P_LCD_RS = 5; // Arduino接続ピン番号:(Digital) LCD-REGISTER = ATMEGA328 pin #11
const int P_LCD_D4 = 6; // Arduino接続ピン番号:(Digital) LCD-D4 = ATMEGA328 pin #12
const int P_LCD_D5 = 7; // Arduino接続ピン番号:(Digital) LCD-D5 = ATMEGA328 pin #13
const int P_LCD_D6 = 8; // Arduino接続ピン番号:(Digital) LCD-D6 = ATMEGA328 pin #14
const int P_LCD_D7 = 9; // Arduino接続ピン番号:(Digital) LCD-D7 = ATMEGA328 pin #15
const int P_PWM1B = 10; // Arduino接続ピン番号:(Digital) OCR1B出力 to Rail = ATMEGA328 pin #16
const int P_PWM2A = 11; // Arduino接続ピン番号:(Digital) OCR2A出力 to Rail = ATMEGA328 pin #17
const int P_LCD_EN = 12; // Arduino接続ピン番号:(Digital) LCD-ENABLE = ATMEGA328 pin #18
// キャラクタ液晶
LiquidCrystal lcd(P_LCD_RS, P_LCD_EN, P_LCD_D4, P_LCD_D5, P_LCD_D6, P_LCD_D7);
const int DECISION_TIME = 3; // setup時に走行パターンを決める際の、確定までの時間。秒。
const int ACCEL_RATIO = 50; // (加速率調整小型ボリュームからの入力値) × ACCEL_RATIO ⇒ 加速率
const int BRAKE_RATIO = 20; // (加速率調整小型ボリュームからの入力値) × BRAKE_RATIO × マスコンブレーキ値(1~5) ⇒ 減速率
const int UNTI_CHAT_RATE = 40; // チャタリング防止ループの値。大きくし過ぎるとノッチ切替時の反応が悪くなる。
const int MC_EB = 1; // マスコン位置 非常
const int MC_B5 = 2; // マスコン位置 制動5
const int MC_B4 = 3; // マスコン位置 制動4
const int MC_B3 = 4; // マスコン位置 制動3
const int MC_B2 = 5; // マスコン位置 制動2
const int MC_B1 = 6; // マスコン位置 制動1
const int MC_N = 7; // マスコン位置 惰行
const int MC_P1 = 8; // マスコン位置 力行1
const int MC_P2 = 9; // マスコン位置 力行2
const int MC_P3 = 10; // マスコン位置 力行3
const int MC_P4 = 11; // マスコン位置 力行4
const int MC_P5 = 12; // マスコン位置 力行5
// マスコン位置名称を保持する配列
char* masconPosName[] = {"EB", "B5", "B4", "B3", "B2", "B1", "N ", "P1", "P2", "P3", "P4", "P5"};
const int MD_STOP = 0; // モード 停止
const int MD_BRAKE = 1; // モード 減速
const int MD_NTRL = 2; // モード 惰行
const int MD_ACCEL = 3; // モード 加速
boolean isDev; // 開発モードのときTrue。
int vvvfPtn; // 走行音パターン。0~11となる。
int masconPos; // マスコンの位置を保持。1~12となる。
int inputMascon; // 読み取ったマスコン位置を一時的に保持
int compareMascon; // マスコンの位置前回と比較するため一時的に保持
int lightVol; // 常点灯ダイアルから拾った値を0~127に変換して保持する。変換処理はloop()内にて。
int accelVol; // 加速率ダイアルから拾った値を1~8に変換して保持する。変換処理はloop()内にて。
int kasoku; // 走行用出力する加減速率
int mode; // 走行状態。0=停止, 1=減速, 2=惰行, 3=加速
long notch1; // ノッチ1での最高速度
long notch2; // ノッチ2での最高速度
long notch3; // ノッチ3での最高速度
long notch4; // ノッチ4での最高速度
long notch5; // ノッチ5での最高速度
long stopSpd; // 走行用出力をカットする速度
long sttFrq; // 開始周波数
long endFrq; // 終了周波数
long frq; // 周波数
long sttSpd; // 開始スピード
long endSpd; // 終了スピード
long spd; // 内部スピード 0~3,000,000(速度×10,000)
long orderSpd; // 指示スピード
int i; // ループカウンター
int j; // ループカウンター
int f; // ループカウンター
long dispSpd = 0; // LCD表示用スピード値
long dispOrderSpd = 0; // LCD表示用スピード目標値
// 走行音名称
char* soundName[12] = {
"207,209 (GTO) ",
"223-2000 (IGBT) ",
"E501 (SIEMENS) ",
"Rheostatic Ctrl.",
"EF210,EF510 ",
"EH200,EH500 ",
"NoSound (max130)",
"NoSound (max250)",
"Diesel ",
"TEC) GTO ",
"TEC) IGBT ",
"TEC) SIEMENS "
};
// 各パターンの各ノッチ位置での最高速度値を配列で保持。
// 左から順に 1ノッチ, 2ノッチ, 3ノッチ, 4ノッチ, 5ノッチ。
// 5×12パターン=60個の値を持つ。
int maxSpdData[] = {
21, 41, 61, 81, 121, //パターン1 207,209 (GTO)
21, 41, 61, 81, 131, //パターン2 233-2000 (IGBT)
21, 41, 61, 81, 131, //パターン3 E501 (SIEMENS)
21, 41, 61, 81, 121, //パターン4 抵抗制御
21, 51, 91, 141, 201, //パターン5 EF210,EF510
21, 51, 91, 141, 201, //パターン6 EH200,EH500
21, 41, 61, 81, 131, //パターン7 NoSound (max130)
31, 71, 121, 181, 251, //パターン8 NoSound (max250)
11, 21, 41, 61, 111, //パターン9 ディーゼル
41, 81, 121, 201, 301, //パターン10 高速車 (GTO)
41, 81, 121, 201, 301, //パターン11 高速車 (IGBT)
41, 81, 121, 201, 301 //パターン12 高速車 (SIEMENS)
};
// 走行用の出力を止める速度値を配列で保持。ピタッと止めるための値。
// 新幹線はギヤ比が高く、値を大きくするとガクンと止まることから小さめの値29としている。
// 12パターン分の値を持つ。
int stopSpdData[] = {49, 49, 49, 49, 49, 49, 49, 49, 20, 29, 29, 29};
// 音データ。
// 設定可能周波数 123Hz~8000kHz
// DUTY比の分解能確保のため30kHzぐらいを上限。整数に限る。
// 音を出す場合必ず2段階以上入力すること。減速時1段階目音を止めるため。
// ~~~(例) パターン2の場合~~~
// 1行目 速度0~ 7の間:1,800Hzで音程変化なし
// 2行目 速度8~18の間:500Hzから1,800Hzまで変化する
int soundData[] = {
//パターン1 207,209(GTO)
800, 800, 11, // 1 開始周波数 終了周波数 切替スピード
380, 900, 31, // 2 開始周波数 終了周波数 切替スピード
500, 750, 41, // 3 開始周波数 終了周波数 切替スピード
500, 800, 81, // 4 開始周波数 終了周波数 切替スピード
800, 800, 101, // 5 開始周波数 終了周波数 切替スピード
-1, // 6 終了コード
//パターン2 223-2000(IGBT)
1800, 1800, 7, // 1 開始周波数 終了周波数 切替スピード
500, 1800, 18, // 2 開始周波数 終了周波数 切替スピード
500, 750, 31, // 3 開始周波数 終了周波数 切替スピード
600, 800, 41, // 4 開始周波数 終了周波数 切替スピード
550, 600, 51, // 5 開始周波数 終了周波数 切替スピード
250, 700, 121, // 6 開始周波数 終了周波数 切替スピード
-1, // 7 終了コード
//パターン3 E501(SIEMENS)
300, 300, 10, // 1 開始周波数 終了周波数 切替スピード
350, 350, 11, // 2 開始周波数 終了周波数 切替スピード
392, 392, 12, // 3 開始周波数 終了周波数 切替スピード
440, 440, 13, // 4 開始周波数 終了周波数 切替スピード
466, 466, 14, // 5 開始周波数 終了周波数 切替スピード
523, 523, 15, // 6 開始周波数 終了周波数 切替スピード
587, 587, 16, // 7 開始周波数 終了周波数 切替スピード
622, 622, 17, // 8 開始周波数 終了周波数 切替スピード
698, 698, 18, // 9 開始周波数 終了周波数 切替スピード
783, 783, 19, //10 開始周波数 終了周波数 切替スピード
900, 900, 35, //11 開始周波数 終了周波数 切替スピード
900, 1000, 37, //12 開始周波数 終了周波数 切替スピード
900, 1000, 41, //13 開始周波数 終了周波数 切替スピード
300, 500, 101, //14 開始周波数 終了周波数 切替スピード
-1, //15 終了コード
//パターン4 抵抗制御系
150, 200, 11, // 1 開始周波数 終了周波数 切替スピード
200, 1000, 101, // 2 開始周波数 終了周波数 切替スピード
-1, // 3 終了コード
//パターン5 EF210,EF510
784, 784, 11, // 1 開始周波数 終了周波数 切替スピード
784, 784, 21, // 2 開始周波数 終了周波数 切替スピード
784, 1480, 56, // 3 開始周波数 終了周波数 切替スピード
1480, 1480, 101, // 4 開始周波数 終了周波数 切替スピード
-1, // 5 終了コード
//パターン6 EH200,EH500
1000, 1000, 11, // 1 開始周波数 終了周波数 切替スピード
1000, 1000, 101, // 2 開始周波数 終了周波数 切替スピード
-1, // 3 終了コード
//パターン7 NoSound (max130)
-1, // 1 終了コード
//パターン8 NoSound (max250)
-1, // 1 終了コード
//パターン9 Diesel
130, 140, 5, // 1 開始周波数 終了周波数 切替スピード
140, 200, 51, // 2 開始周波数 終了周波数 切替スピード
150, 190, 101, // 3 開始周波数 終了周波数 切替スピード
-1, // 4 終了コード
//パターン10 新幹線性能版 GTO
800, 800, 11, // 1 開始周波数 終了周波数 切替スピード
380, 900, 31, // 2 開始周波数 終了周波数 切替スピード
500, 750, 41, // 3 開始周波数 終了周波数 切替スピード
500, 800, 81, // 4 開始周波数 終了周波数 切替スピード
800, 800, 101, // 5 開始周波数 終了周波数 切替スピード
-1, // 6 終了コード
//パターン11 新幹線性能版 IGBT
1800, 1800, 7, // 1 開始周波数 終了周波数 切替スピード
500, 1800, 18, // 2 開始周波数 終了周波数 切替スピード
500, 750, 31, // 3 開始周波数 終了周波数 切替スピード
600, 800, 41, // 4 開始周波数 終了周波数 切替スピード
550, 600, 51, // 5 開始周波数 終了周波数 切替スピード
250, 700, 121, // 6 開始周波数 終了周波数 切替スピード
-1, // 7 終了コード
//パターン12 新幹線性能版 SIEMENS
300, 300, 10, // 1 開始周波数 終了周波数 切替スピード
350, 350, 11, // 2 開始周波数 終了周波数 切替スピード
392, 392, 12, // 3 開始周波数 終了周波数 切替スピード
440, 440, 13, // 4 開始周波数 終了周波数 切替スピード
466, 466, 14, // 5 開始周波数 終了周波数 切替スピード
523, 523, 15, // 6 開始周波数 終了周波数 切替スピード
587, 587, 16, // 7 開始周波数 終了周波数 切替スピード
622, 622, 17, // 8 開始周波数 終了周波数 切替スピード
698, 698, 18, // 9 開始周波数 終了周波数 切替スピード
783, 783, 19, //10 開始周波数 終了周波数 切替スピード
900, 900, 35, //11 開始周波数 終了周波数 切替スピード
900, 1000, 37, //12 開始周波数 終了周波数 切替スピード
900, 1000, 41, //13 開始周波数 終了周波数 切替スピード
300, 500, 101, //14 開始周波数 終了周波数 切替スピード
-1 //15 終了コード
};
// 走行音パターン12種soundDataの各パターンデータ開始位置インデックスを保持。
// 実際の値設定は、setup処理内にて終了コード(-1)を検出し動的に設定する。
int dataNum[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// ***********************************************
// 定数・変数定義 ここまで
// ***********************************************
// **********************************************************************************************
// 関数定義 ここから
// **********************************************************************************************
// ***********************************************
// マスコン位置を検出する。
// ロータリースイッチからのボリューム値を拾い、
// 適度な値に変換する処理。
//
// 引数: アナログピンから取得した値(Max:1023)
// 戻値: 1(=EB)~12(=P5)
// ***********************************************
// ロータリースイッチが12段階切替なので、
// 入力を12段階に分ける。
// 3番ピンからのanalogRead値は・・・(実測値)
// ・ロータリースイッチの位置が 1の時:11kΩ ⇒ 83
// ・ロータリースイッチの位置が 2の時:10kΩ ⇒ 169
// ・ロータリースイッチの位置が 3の時: 9kΩ ⇒ 254
// ・ロータリースイッチの位置が 4の時: 8kΩ ⇒ 340
// ・ロータリースイッチの位置が 4の時: 7kΩ ⇒ 425
// ・ロータリースイッチの位置が 4の時: 6kΩ ⇒ 511
// ・ロータリースイッチの位置が 4の時: 5kΩ ⇒ 596
// ・ロータリースイッチの位置が 4の時: 4kΩ ⇒ 682
// ・ロータリースイッチの位置が 4の時: 3kΩ ⇒ 767
// ・ロータリースイッチの位置が 4の時: 2kΩ ⇒ 853
// ・ロータリースイッチの位置が11の時: 1kΩ ⇒ 938
// ・ロータリースイッチの位置が12の時: 0kΩ ⇒ 1023
int getMasconVol(int vol) {
int ans = 0;
// マスコンロータリースイッチから読み取った値を、小数点も扱える形で
// 0~1023に収まるようにする。12接点なので12等分。
// 四捨五入相当の処理をする⇒値に+0.5した上で、小数点以下を切り捨てるためint型に変換。
ans = (int)(((double) analogRead(P_MASCON)) / (1024 / 12) + 0.5);
return ans;
}
// ***********************************************
// マスコン位置を表示。LCDの右下2桁へ。
// ついでにスピード値も表示。なんとなくKATO103系の
// 動力ユニットでスケールスピード(?)
// 動力ユニットによって同じ電圧でも実速度が異なる
// ので、あくまでも目安値。
// -----------------------------------------------
// 開発モード(isDev=True)の時は、目標スピード値と
// 加速率ダイヤル、常点灯ダイヤルの値も表示する。
// ***********************************************
void dispMasPos(int masPos) {
lcd.setCursor(14, 1);
lcd.print(masconPosName[masPos - 1]);
// spdも表示
dispSpd = spd / 10000;
lcd.setCursor(4, 1);
if (dispSpd / 100 > 0) {
// 3digits.
} else {
if (dispSpd / 10 > 0) {
// 2digits.
lcd.print(" ");
} else {
// 1digit.
lcd.print(" ");
}
}
lcd.print(dispSpd);
if (isDev) {
// "0123456789012345"
// ついでにorderSpdも表示。"SPD:nnn>nnn XX"
dispOrderSpd = orderSpd / 10000;
lcd.setCursor(8, 1);
if (dispOrderSpd / 100 > 0) {
// 3digits.
} else {
if (dispOrderSpd /10 > 0) {
// 2digits.
lcd.print(" ");
} else {
// 1digit.
lcd.print(" ");
}
}
lcd.print(dispOrderSpd);
// ---------------------- "0123456789012LLL" ※LLL=常点灯ダイヤル値
// ついでにorderSpdも表示。"Spd:nnn>nnn A XX" ※A=加速率ダイヤル値 XX=マスコン値
lcd.setCursor(12, 0);
lcd.print(" ");
if (lightVol < 100 && lightVol >= 10) {
lcd.print(" ");
} else if (lightVol < 10) {
lcd.print(" ");
}
lcd.print(lightVol);
lcd.setCursor(12, 1);
lcd.print(accelVol);
}
}
// ***********************************************
// setup関数。最初に1度だけ実行。
// ***********************************************
void setup() {
// PWM PIN設定
pinMode(P_PWM2A, OUTPUT); // 常点灯用PWM出力
pinMode(P_PWM2B, OUTPUT); // 走行用PWM出力
pinMode(P_PWM1B, OUTPUT); // 何用?
// WELCOMEメッセージ表示
lcd.createChar(0, dd51);
lcd.createChar(1, koki);
lcd.begin(16, 2); // 16桁、2行タイプと宣言。
lcd.clear();
lcd.print(MOJI1);
lcd.setCursor(0, 1);
lcd.print(MOJI2);
lcd.write((byte)0); // DD51
for (i=0; i<7; i++) lcd.write((byte)1); // コキ
lcd.write((byte)0); // DD51
delay(800);
isDev = (MC_P5 == getMasconVol(analogRead(P_MASCON))); // マスコンがP5の時だけ開発モード。
// マスコンがEBの時だけ石北臨貨アニメーション表示。
if (MC_EB == getMasconVol(analogRead(P_MASCON))) {
for (i=0; i<24; i++) {
lcd.scrollDisplayLeft();
switch (i) {
case 0:
delay(900);
break;
case 1:
delay(750);
break;
case 2:
delay(550);
break;
case 3:
delay(400);
break;
default:
delay(250);
break;
}
}
}
lcd.clear();
// 走行音パターン各開始位置取得処理。
// soundData配列のうち、各パターンの最初の値となる配列インデックスをdataNumで保持する。
// パターン1は常に先頭(=インデックス0)なので、dataNumにセットするのは省略。
int idx = 0;
for (i=1; i<12; i++) {
while (soundData[idx] != -1) {
idx++;
}
idx++;
dataNum[i] = idx;
}
// 走行音パターン取得
lcd.home(); // 1文字目、1行目
for (i=0; i<2; i++) {
lcd.clear();
delay(180);
lcd.print("Select SoundPtn.");
delay(480);
}
delay(240);
lcd.clear();
lcd.print("SoundPtn.");
lcd.setCursor(0, 1);
lcd.print(" Revolve MasCon.");
//delay(2000);
// ロータリースイッチを10秒動かさないと決定。
// ただし、何も動かしてないときはずっと待ち続ける。
unsigned long sttTime = 0;
unsigned long progress = 0;
boolean isMoved = false; // マスコンを動かしたらTrueにする
masconPos = getMasconVol(analogRead(P_MASCON)); // 初期値
while (!isMoved) {
inputMascon = getMasconVol(analogRead(P_MASCON)); // 現在値
// 読み取った最新マスコン位置が、以前に覚えていたマスコン位置と異なる場合
// =ロータリースイッチを動かしたと判断。
isMoved = (masconPos != inputMascon);
}
sttTime = millis(); // ロータリースイッチをいじったので、初期化。
do {
while (true) {
inputMascon = getMasconVol(analogRead(P_MASCON)); // 現在値
if (masconPos != inputMascon) {
// 読み取った最新マスコン位置が、以前に覚えていたマスコン位置と
// 異なる場合=ロータリースイッチを動かしたと判断。
masconPos = inputMascon;// 新たなマスコン位置を覚えておく。
sttTime = millis(); // ロータリースイッチをいじったので、初期化。
lcd.setCursor(0, 1); // 1文字目、2行目
lcd.print(soundName[masconPos - 1]); // LCDに走行音名称を表示させる。
} else {
if ((millis() - sttTime) > DECISION_TIME * 1000) {
// 選んでから所定時間が過ぎた。
break; // ループを抜ける。
}
}
progress = millis() - sttTime;
if (progress > 0) {
lcd.setCursor(15, 0);
lcd.print((String) (DECISION_TIME - (progress / 1000)));
}
delay(1);
}
} while (masconPos < MC_EB || masconPos > MC_P5); // 12種類から選択されたらループを抜け次の処理へ。
// 走行音パターンほか選択したパターンに合わせて設定
vvvfPtn = masconPos - 1;
notch1 = (long) maxSpdData[vvvfPtn * 5 ] * 10000 - 1;
notch2 = (long) maxSpdData[vvvfPtn * 5 + 1] * 10000 - 1;
notch3 = (long) maxSpdData[vvvfPtn * 5 + 2] * 10000 - 1;
notch4 = (long) maxSpdData[vvvfPtn * 5 + 3] * 10000 - 1;
notch5 = (long) maxSpdData[vvvfPtn * 5 + 4] * 10000 - 1;
stopSpd = (long) stopSpdData[vvvfPtn] * 1000 - 1;
// 決定したパターンを点滅表示
for (i=0; i<3; i++) {
lcd.clear();
delay(180);
lcd.print("Sound Pattern is");
lcd.setCursor(0, 1); // 1文字目、2行目
lcd.print(soundName[vvvfPtn]);
delay(480);
}
delay(240);
// マスコン位置を惰行か制動にしてもらう注意喚起。急に走り出さないようにするため。
// マスコン位置が1~6、つまり 非常 か 制動5~1 に設定しないと
// 注意喚起され続け、次の処理へ進めない。
masconPos = getMasconVol(analogRead(P_MASCON));
while (masconPos < MC_EB || masconPos > MC_B1) {
lcd.clear();
lcd.print("Caution!");
lcd.setCursor(0, 1);
lcd.print("Apply Brake.");
delay(240);
masconPos = getMasconVol(analogRead(P_MASCON));
}
lcd.clear();
lcd.print("Ready.");
delay(720);
lcd.clear();
lcd.print(soundName[vvvfPtn]);
lcd.setCursor(0, 1);
lcd.print("Spd:");
lcd.setCursor(7, 1);
if (isDev) lcd.write(0x7E); // 右向き矢印
}
// ***********************************************
// ***********************************************
// loop関数
// ***********************************************
// ***********************************************
void loop() {
accelVol = analogRead(P_ACCEL_DIAL) / 128 + 1; // analogRead 0~1023 → 1~8
lightVol = analogRead(P_LIGHT_DIAL) / 8; // analogRead 0~1023 → 0~127
inputMascon = getMasconVol(analogRead(P_MASCON));
if (inputMascon >= MC_EB && inputMascon <= MC_P5) {
// 選択し得る12段階のマスコン位置であると判断。
if (compareMascon == inputMascon) {
masconPos = inputMascon;
} else if (compareMascon > inputMascon) {
compareMascon--;
} else if (compareMascon < inputMascon) {
compareMascon++;
}
}
switch (masconPos) {
case 1:
// 非常
orderSpd = 0;
spd = 0;
mode = MD_STOP;
break;
case 2:
// 制動5
orderSpd = 0;
kasoku = accelVol * BRAKE_RATIO * 5;
mode = MD_BRAKE;
break;
case 3:
// 制動4
orderSpd = 0;
kasoku = accelVol * BRAKE_RATIO * 4;
mode = MD_BRAKE;
break;
case 4:
// 制動3
orderSpd = 0;
kasoku = accelVol * BRAKE_RATIO * 3;
mode = MD_BRAKE;
break;
case 5:
// 制動2
orderSpd = 0;
kasoku = accelVol * BRAKE_RATIO * 2;
mode = MD_BRAKE;
break;
case 6:
// 制動1
orderSpd = 0;
kasoku = accelVol * BRAKE_RATIO * 1;
mode = MD_BRAKE;
break;
case 7:
// 惰行
orderSpd = 0;
kasoku = accelVol * BRAKE_RATIO * 2;
mode = MD_NTRL;
break;
case 8:
// 力行1
orderSpd = notch1;
kasoku = accelVol * ACCEL_RATIO;
mode = MD_ACCEL;
break;
case 9:
// 力行2
orderSpd = notch2;
kasoku = accelVol * ACCEL_RATIO;
mode = MD_ACCEL;
break;
case 10:
// 力行3
orderSpd = notch3;
kasoku = accelVol * ACCEL_RATIO;
mode = MD_ACCEL;
break;
case 11:
// 力行4
orderSpd = notch4;
kasoku = accelVol * ACCEL_RATIO;
mode = MD_ACCEL;
break;
case 12:
// 力行5
orderSpd = notch5;
kasoku = accelVol * ACCEL_RATIO;
mode = MD_ACCEL;
break;
}
for ( j = 0; j < UNTI_CHAT_RATE; j++ ) { // チャタリング防止ループ
dispMasPos(masconPos);
if (spd < orderSpd) {
if (orderSpd - spd <= kasoku) {
spd = orderSpd;
} else {
spd = spd + kasoku;
}
}
if ( spd > orderSpd ) {
if ( masconPos >= MC_N ) {
// 惰行or力行
if ( spd - orderSpd <= kasoku / BRAKE_RATIO ) {
spd = orderSpd;
} else {
spd = spd - kasoku / BRAKE_RATIO;
}
} else {
// 制動
if ( spd - orderSpd <= kasoku ) {
spd = orderSpd;
} else {
spd = spd - kasoku;
}
}
}
if ( spd < stopSpd && masconPos < MC_P1 ) {
// 惰行or制動ノッチのとき、ピタ停止速度値よりも現行速度が下回ったらピタッと停止させる。
spd = 0;
mode = MD_STOP;
}
// 走行時の常点灯PWM DUTY比
// 周波数は31.2kHz = CPU周波数(16MHz) / 2 / 分周256
// OCR2Aはこの数値からTOP255がDUTY比
OCR2A = 255 - lightVol;
// 走行用出力。
// SPD / 128 / 4 / 10 / DUTY比 ( 1.5 + speed / 390)
OCR2B = (int) (spd / ( 7680 + spd / 390 ));
// soundDataは開始周波数 終了周波数 切替スピードの3つの値で構成されているので+3ずつカウントアップ。
// soundDataの定義数は無制限(-1を終了コードとしている)
for (i = 0; ;i = i + 3) {
sttFrq = soundData[dataNum[vvvfPtn] + i];
endFrq = soundData[dataNum[vvvfPtn] + i + 1];
if (i == 0) sttSpd = 0;
else sttSpd = soundData[dataNum[vvvfPtn] + i - 1];
endSpd = soundData[dataNum[vvvfPtn] + i + 2];
if (i == 0 && sttFrq == -1) { // 走行音が設定されていない場合の処理
OCR1A = 421; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq 19000 = 421
OCR1B = 421 - 421 * (word)lightVol / 245 ; // 音を出すPWMのDUTY比
TCCR1B = B00010001;
TCCR1A = B00110001;
TCCR2B = B00000001;
TCCR2A = B00100001;
break;
}
if (mode == MD_STOP) { // 停止時 音停止
OCR1A = 421; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq 19000 = 421
OCR1B = 421 - 421 * (word)lightVol / 255 ; // 音を出すPWMのDUTY比
TCCR1B = B00010001;
TCCR1A = B00110001;
TCCR2B = B00000001;
TCCR2A = B00000001; // PWM 2A(常点灯部分 代わりに1Bで常点灯出力 19kHzで出力したいから)と2B停止
break;
}
if (i == 0 && spd <= endSpd * 10000 && mode == MD_BRAKE) { // 減速のとき1段階目の音停止
OCR1A = 421; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq 19000 = 421
OCR1B = 421 - 421 * (word)lightVol / 255 ; // 音を出すPWMのDUTY比
TCCR1B = B00010001;
TCCR1A = B00110001;
TCCR2B = B00000001;
TCCR2A = B00100001; // PWM 2A停止(常点灯部分 代わりに1Bで常点灯出力) 2B出力
break;
}
// if (mode == MD_NTRL) { // 惰行 VVVF音停止
// OCR1A = 421; // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq 19000 = 421
// OCR1B = 421 - 421 * (word)lightVol / 245 ; // 音を出すPWMのDUTY比
// TCCR1B = B00010001;
// TCCR1A = B00110001;
// TCCR2B = B00000001;
// TCCR2A = B00100001; // PWM 2A停止(常点灯部分 代わりに1Bで常点灯出力) 2B出力
// break;
// }
if (sttFrq == -1) { // 高速時 VVVF音停止
frq = 31500;
OCR1A = (word)(8000000 / frq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
OCR1B = (word)(8000000 / frq - 8000000 / frq / 99); // 音を出すPWMのDUTY比 100/99 = 1%
TCCR1B = B00010001;
TCCR1A = B00110001;
TCCR2B = B00000001; // PWM 2A/2B 分周1
TCCR2A = B11100001; // PWM 2A/2B 出力
break;
}
// VVVF音
if ((spd >= sttSpd * 10000) && (spd < endSpd * 10000)) {
frq = ( endFrq * 10 - sttFrq * 10 ) / ( endSpd - sttSpd ) * ( spd - sttSpd * 10000 ) / 100000 + sttFrq;
if ( frq < 123 ) frq = 123;
else if ( frq > 31500 ) frq = 31500;
if (mode == MD_NTRL) frq = 123; // 惰行のとき。
OCR1A = (word)(8000000 / frq); // TOP値 = CPUの動作周波数 / 2 / 分周 / 希望周波数 = 16,000,000 / 2 / 1 / frq
OCR1B = (word)(8000000 / frq - 8000000 / frq / 15); // 音を出すPWMのDUTY比 100/15 = 6.6%
TCCR1B = B00010001;
TCCR1A = B00110001;
TCCR2B = B00000001; // PWM 2A/2B 分周1
TCCR2A = B11100001; // PWM 2A/2B 出力
break;
}
}
}
}
ハマカゼ80さんのブログを拝見し自分も作ってみたいと思い、製作しているのですが、
マイコン制御にあまり経験がなく、LCDとマイコンの接続に苦戦しています。
LCDはマルツのTC1602を使っているのですが、
マイコンとLCDの詳しい接続を教えていただけないでしょうか?
こんにちは。
私のソースコードの
LiquidCrystal lcd(P_LCD_RS, P_LCD_EN, P_LCD_D4, P_LCD_D5, P_LCD_D6, P_LCD_D7);
にて定義してある引数の6本を繋げば良いはずです。
RSならArduinoの5番またはマイコンの11番です。(他の5本もソースコード中のコメントを参考にしてください)
問題が解決されることを祈ってます。
先日アドバイスいただいたKです。
教えていただいたLCDとの接続は解決したのですが、電源を入れてもLCDモジュールに表示が一切でない状態でして、回路の確認及び部品の確認はしたけれども、ミスを発見できませんでした。
RS・VRからの信号もシリアルモニタのプログラムで信号が出力されていることを確認又、製作した基板ではなくArduino本体に部品をとりつけても同状況でした。
プログラムをコンパイルした際にエラーメッセージ(warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings])が表示されましたが、動作に問題はないもののようでした。
ほかに調べる場所・方法が思いつかず、苦戦しています。何かアドバイスをいただけないでしょうか??
こんにちは。
一旦この鉄道模型コントローラから離れて、ネットにたくさん公開されているTC1602(または秋月のSC1602)の動作確認プログラムをArduinoに書き込んで動作確認させてみたら如何でしょうか。
それで動作確認できなければ初期不良を疑うことになるかと思います。
私の記事をご覧くださりありがとうございます。
ご確認ポイントとして・・・
(1) ご使用のロータリースイッチはノンショーティングタイプでしょうか?
(2) 12段階の各位置で、ロータリースイッチの抵抗値は0kΩから11kΩまで1kΩずつの変化でしたでしょうか?
(3) 12段階の各位置でanalogReadした値は、0から1023まで約85.33(=1024/12)ずつの変化でしたでしょうか?
パッと思いつく点としては以上3点です。
(2)や(3)が上記通りではない場合、ご推測箇所のソースコード修正が必要になりそうですね。
問題が解明されることを祈っています。
そこまで調査研究されていましたか。私もExcelで表作り検討したのを思い出しました。
analogRead値を液晶に表示させるなどして変化を見てみるのは如何でしょうか?
そこまで出来ているなら、B1だけ使用できないとか悔しいですよね。
また進捗をお聞かせいただければと思います。