冬コミに出す薄い本にしようかと思ったのですが、1冊にするほどの内容でもない気がしたので、ここで供養します。
この度、所属している鉄道研究会で学園祭に出すため、本物のディーゼル機関車(DE15/DE10)の部品を使用したフルサウンドNゲージ鉄道模型コントローラを製作した。本記事では鉄道に関する知識がない方にも分かるように配慮しつつ、その技術解説を行う。
システムを構成するコンポーネント
- PWMコントローラとソフトウェア : コンピュータから指令されたレベルの電気を出力し、レールに接続する
- マスコンユニットとソフトウェア : 自動車のアクセルに相当する「マスコン」から現在のレベルを読み出し、コンピュータにシリアル伝送で間欠的に送信する
- ブレーキ統合ユニットとソフトウェア : ブレーキハンドルの角度と、その他のボタン操作やペダル操作をコンピュータに間欠送信し、同時に速度をコンピュータから取得して、速度計に表示する
- スピーカーとウーファー振動モジュール: コンピュータのアナログオーディオ端子から出力してスピーカーから音を出しつつ、椅子の裏に固定されたウーファーモジュールで運転士にリアルな振動を与える
- DE10クラス : マスコンレベルとブレーキレベルから、DE10形液体式ディーゼル機関車の0.1秒ごとの加速度を計算することで、現在の車両の速度を求めるソフトウェア
- Sounderクラス : そのタイミングに応じた音を呼び出し、Pygame(SDLのPythonラッパー)でALSA Audioに渡すソフトウェア
- NControllerクラス : 以上のコンポーネントを呼ぶ、中心となるソフトウェア
ソフトウェア群は著者のGitHub (https://github.com/mipsparc/NController) で公開されている。
PWMコントローラ
2018年の4月ごろ、PICマイコンに入門することを主目的として、コンピュータからレベルをシリアル伝送(UART)で指示すると、PWM信号が出力されるファームウェアと基板を製作した。
PIC開発の入門に当たっては「C言語による PICプログラミング大全(後閑 哲也)」を参考とした。この書籍にはPIC16F1シリーズで現代的なCプログラミングをする方法が丁寧に書かれており、C言語をしっかりと触れたことがあれば楽に組み込みプログラミングができるようになっている。もっとも「大全」というには情報量が少々足りないような気もするが。
新世代PICは安価で十分な機能が備えられており、MCCやHarmonyを使えば、今からでも選択するのは正解だと思うが、なぜオワコン扱いされるのか不思議である。
今回は16bitPWMを複数備えており、扱いやすいPIC16F1579をコントローラに採用した。1つ140円なので、たくさん買っておくと大抵のことはこれで済んで便利。
PIC16F1579 8bit PICアーキテクチャ、最大動作周波数:32MHz、駆動電圧:1.8V-5.5V、プログラムメモリ:14KB、SRAM:1KB、10bitADC:12ch、16bitPWM:4ch、UART
コンピュータからのレベル指示は、0x21(ASCIIコード通常文字で一番小さい「!」)を0として、1文字で行われる。0x0から0x20までの任意のコードを送信すると、方向が転換する。これは、特別なソフトウェアなしにターミナルエミュレータのみで制御することを第一目標としたため。1回の受信だけで出力してしまうとノイズ耐性に問題があったので、2回連続同値受信で実行されるようにし、送信側では3回連続で送信する仕組みにした。
コンピュータとPICとの通信は、UARTで行われる。これは3.3Vまたは5Vによるシリアル通信で、RS232Cをレベルシフトしたものだ。USB-UART変換ケーブルというのがAmazonで200円程度で売っているので、極めて扱いやすい。ただし、3.3Vと5Vが混在しているのが罠で、これを同じにしないと文字化けを起こす。
PWM変調周波数は50kHzとかなり高く設定した。その結果、超低速での走行もできるようになったが、レールにスパークが原因と思われる汚れが多く付着するようになり、速度が低下する問題が発生した。いろいろと定数が変わってしまうので今回はこのままで運用したが、機械的反応速度は全く追いついていないと思われるので、メリットがなく、推奨はされない。
今回、出力レベルが上がるときに0.02秒間最大出力を出す仕組みを入れてある。これは超低速駆動するための仕掛けで、停止状態から動き出すための勢いをつけるのに有用である。
コントローラ装置はDCDCコンバータ・PICの乗っている制御基板と、トランジスタ・リレーの乗ったスイッチング基板の2つで構成されている。本当はHブリッジにより方向転換もフルデジタルにしたかったのだが、万が一プログラムにミスがあった時に短絡してしまうので、安全性を優先してリレーでの切り替えとした。FETでなくトランジスタを採用したのは、単純に制御電圧と動力電圧の橋渡しをする回路をつくるのが面倒だったからだ。消費電力を気にしなければ、高速トランジスタを使用することで回路をシンプルにするのは1つの策だろう。しかし、結果的に条件によっては素子の温度が高くなってしまったのは、反省である。製作に当たってはブレッドボードに試作して、プログラムを書き、回路図に起こし、実体配線図を書き、実装するという手順を踏んだ。
試作時、リレーが駆動する方向転換時にコントローラがリセットしてしまう不具合があった。これはリレーのL成分により電圧が不安定になるのが原因だったため、パスコン(積層セラミックコンデンサ)を電源ラインへ挿入することで解決した。ノイズは常に意識して設計しないといけないことを学んだ。
シリアルとPythonの受け渡しにはPySerialを使用する。このとき、まれに壊れたデータが送られてくるのを前提にしてtimeoutとwrite_timeoutを設定して、不正なレスポンスは読み飛ばすプログラムを書く必要がある。実際、直前の動作テストで、数十分に一回異常データが送られてきたときに落ちるバグが発覚して原因究明に焦ることになった。
マスコン
そもそも運転台を作ることになったきっかけは、ヤフオク巡りでDE15形ディーゼル機関車のマスコンを見つけたことであった。25000円程度で落札することができた。届いてみるとだいぶ大きく、一人では持ち上げるのも困難なほどの重量があった。後になって、実はディーゼル機関車そのもののマスコンではなく、除雪モジュールであるラッセルヘッドのマスコンであることに気がついた。そのため目標としていたDE10形ディーゼル機関車のものとは左右は逆になってしまったが、気にしないことで解決した。実は、マスコンが左にある機関車は、小移動を繰り返す入換(駅構内で客車や貨車を繋ぎ変えること)機に限られ少数派である。
ノッチ(出力レベル)位置を検出する仕組みはシンプルだ。レバー操作に従って下にある切り欠きのある銅板が貼られたドラムが回転し、固定された接点が銅板に触れて、接点への加圧・非加圧で2値となる。接点は6個あり、0から14までが区別できる。
これをまたPIC16F1579で読み出し、0.1秒ごとにPICへシリアル伝送をする基板を製作し、Pythonで受信するプログラムを書いた。
マスコンは単体では自立しない不安定な形状をしているので、机の天板をピッタリのサイズにくり抜いたものが必要となる。電動工具のジグソーとドリルで木工をすることで、単体で自立する机状のマスコンユニットが完成した。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// PIC16F1579 Configuration Bit Settings | |
// CONFIG1 | |
#pragma config FOSC = INTOSC // Oscillator Selection Bits (INTOSC oscillator; I/O function on CLKIN pin) | |
#pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled) | |
#pragma config PWRTE = ON // Power-up Timer Enable (PWRT enabled) | |
#pragma config MCLRE = OFF // MCLR Pin Function Select (MCLR/VPP pin function is digital input) | |
#pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled) | |
#pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled) | |
#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin) | |
// CONFIG2 | |
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off) | |
#pragma config PPS1WAY = ON // PPSLOCK bit One-Way Set Enable bit (PPSLOCKED Bit Can Be Cleared & Set Once) | |
#pragma config PLLEN = OFF // PLL Disabled | |
#pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset) | |
#pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.) | |
#pragma config LPBOREN = OFF // Low Power Brown-out Reset enable bit (LPBOR is disabled) | |
#define _XTAL_FREQ 16000000 | |
#include <xc.h> | |
#include <stdio.h> | |
void putch(char write_char) | |
{ | |
while(!TXSTAbits.TRMT); | |
TXREG = write_char; | |
} | |
void main(void) | |
{ | |
OSCCON = 0b01111011; // 16MHz | |
ANSELC = 0b00000000; // All digital | |
TRISC = 0b11111111; // PORTC all input | |
PORTC = 0b00000000; // PORTC init | |
ANSELB = 0b00000000; | |
TRISB = 0b00010000; // RB4 input | |
PORTB = 0b00000000; | |
TRISA = 0b00000000; | |
RA0PPS = 0b00001001; // RA0 UART TX | |
TXSTA = 0b00100000; // 8bit UART | |
RCSTA = 0b10010000; | |
SPBRG = 25; // 16MHz 9600bps | |
INTCONbits.GIE = 1; // accept interrupt | |
PIE1bits.TXIE = 1; // accept TX interrupt | |
unsigned char mascon = 0; | |
unsigned char txbyte; | |
while (1) { | |
if (PORTCbits.RC3 == 1 && (PORTC & 0b00010111)) { // 18????ON | |
mascon = (PORTCbits.RC4 << 3) + (PORTC & 0b00000111) – 1; | |
} else { | |
mascon = 0; | |
} | |
txbyte = (PORTC & 0b11100000) + (PORTBbits.RB4 << 4) + mascon; | |
putch("%c", txbyte); | |
while(!TXSTAbits.TRMT); | |
__delay_ms(100); | |
} | |
return; | |
} |
ブレーキ統合ユニット
右手が用意できたら、次は左手のブレーキが欲しくなる。
残念ながら、ブレーキはレバーのみしか本物を入手できなかった。そのため、アルミケースにポテンショメータを固定して、そこにブレーキハンドルを取り付けることで実現した。
ブレーキハンドルの軸穴にネジを通して、ナットでネジと固定、ネジをカップリングに取り付けて、反対側にポテンショメータの軸を固定することで締結をした。しかし、ここが構造上の弱点となってしまい、本番中に何度か緩まることがあったのが残念だった。改善方法としては、ネジロック剤の塗布、旋盤でカップリング接続部のネジ山を削るなどが考えられる。
ブレーキハンドルの実物はだいぶ回転トルクが高い(回すのに力を要す)が、ポテンショメータと直結しているので、回転は相当に軽くなってしまう。メーカーは小ロットでも特注で回転トルクの高いものを作れると言ってくれたが、納期が全く間に合わない。苦肉の策で高張力バネで紙やすりを押し付けることである程度の向上はできたが、ギヤなどで重くするのが正解と思われる。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "mcc_generated_files/mcc.h" | |
void main(void) | |
{ | |
// initialize the device | |
SYSTEM_Initialize(); | |
// Enable the Global Interrupts | |
INTERRUPT_GlobalInterruptEnable(); | |
// Enable the Peripheral Interrupts | |
INTERRUPT_PeripheralInterruptEnable(); | |
int dinputs = 0; | |
int level = 0; | |
while (1) | |
{ | |
if (EUSART_is_rx_ready()) { | |
level = EUSART_Read(); | |
PWM1_DutyCycleSet(level * 15); | |
PWM1_LoadBufferSet(); | |
} | |
adc_result_t result = ADC1_GetConversion(Brake_ADC); | |
dinputs = IO_RA0_GetValue() << 7 | |
| IO_RA1_GetValue() << 6 | |
| IO_RA2_GetValue() << 5 | |
| IO_RC0_GetValue() << 4 | |
| IO_RC1_GetValue() << 3 | |
| IO_RC2_GetValue() << 2 | |
| IO_RB4_GetValue() << 1 | |
| IO_RB6_GetValue(); | |
printf("%lu,%u,\r\n", (long)result, dinputs); | |
__delay_ms(100); | |
} | |
} |
スピーカーと振動ユニットとSounderクラス
フルサウンドNゲージ鉄道模型コントローラということで、操作に応じた音がアナログ出力される。アナログ出力を直接分岐して、運転士座席の裏に固定されたウーファーが振動しつつ、音がスピーカ+ウーファーから出力される。
音は、主に小坂レールパークで録音したDD13液体式ディーゼル機関車のものとなっている。
コンピュータからの出力はSounderクラスを使用する。PygameでALSAから出しており、これを呼び出すことで音が出る。
DE10クラス
知恵袋に記載されていたDE10の加速度を参考にして、ノッチとブレーキハンドル角度から0.1秒ごとに速度を計算する。もっともこれは単機のときのものなので、適当な係数を掛けた。
将来的には、元空気溜めの空気量を管理すると、コンプレッサの作動も制御できるかもしれない。
NControllerクラス
シリアル通信との送受信は非同期に実施したいため、threadingによりマルチプロセスを起動することで管理する。共有メモリによりシリアル送受信プロセスと通信する。