Nゲージ鉄道模型のATS・ATC案③-LEDモジュール
この記事は過去に作ったNゲージ鉄道模型のATCシステムについて記述しています.
システム全体については下の過去記事を参照してください.
LEDモジュール
LEDの制御もユーザーの端末で出来るようにしていました.ATCの動作に直接の関係はありません.当時は駅ホームの照明に使っていました.(本当は信号に使うべきかもしれない)
ハードウェアについて
Dフリップフロップ(D-FF)とバッファを組み合わせた(恐らく)ポピュラーな回路構成です.D-FFはクロック入力CLKが立ち上がった時,入力Dの状態が出力Qに保存され,次にCLKに変化あるまで,出力Qは状態を保持し続けます.幾つかのD-FFを組み合わせ,それぞれの入力を共有しておき,各D-FFことにCLKを制御することで,マイコンが扱えるIOピンが増えます.このモジュールでは8入出力D-FFを4つ使っています.
DフリップフロップICの出力ピンに流せる電流が心許ないので,バッファを入れて大電流に備えています.
ちらつかない程度な低周波の128段階PWM波形を生成し,一つのモジュール当たり,32個のLEDを駆動することが出来ます.実際には出力を30個に切っています.これは,出力コネクタにパラレルATAケーブルを流用している都合です.
マイコン | ATmega1284P |
D-FF | TC74VHC574F |
バッファ | TD62783APG |
ソフトウェアについて
マイコンは20MHzで駆動しており,通信基板とSPIで会話しつつ,ソフトPWMをこなします.下にコードを貼ります.(いま見ると命名がひどいなぁ)
#include "avr_base.hpp" #include <avr/io.h> #include <avr/delay.h> #include <avr/interrupt.h> #include <Timer.h> #include <string.h> #include <tus.h> #include <led_packet.h> using namespace AVRCpp::Timer; #define PORT_DATA PORTA #define PORT_CLK PORTD #define PORT_ENABLE PORTC #define PORT_ENABLE_NUM 0 #define LED_COUNT 30 LedState g_States[LED_COUNT]; DeviceID g_myDevID; uint8_t led_count_remains[LED_COUNT]; uint8_t led_count_init[LED_COUNT]; #define APPLY_TO_LED(clk,ind) \ if(led_count_remains[(clk+1)*8-ind-1] >0){ \ sbi(led_signal_buffer, ind); \ led_count_remains[(clk+1)*8-ind-1]--; \ }else{ \ cbi(led_signal_buffer, ind);} ISR(TIMER1_COMPA_vect) { tus_spi_process_packets(); } void device_init() { DDRA = 0xFF; DDRD = 0xFF; DDRC = 0xFF; cbi(PORT_ENABLE, PORT_ENABLE_NUM); // enable D-flipflops memset(led_count_init, 0, sizeof(led_count_init)); memset(led_count_remains, 0x00, sizeof(led_count_remains)); } void init_led_status() { uint8_t cache = SREG; cli(); memcpy(led_count_remains, led_count_init, sizeof(led_count_init)); SREG = cache; } void apply_led_status() { uint8_t led_signal_buffer; APPLY_TO_LED(0,0); APPLY_TO_LED(0,1); APPLY_TO_LED(0,2); APPLY_TO_LED(0,3); APPLY_TO_LED(0,4); APPLY_TO_LED(0,5); APPLY_TO_LED(0,6); APPLY_TO_LED(0,7); PORT_DATA = led_signal_buffer; PORT_CLK = 1<<0; APPLY_TO_LED(1,0); APPLY_TO_LED(1,1); APPLY_TO_LED(1,2); APPLY_TO_LED(1,3); APPLY_TO_LED(1,4); APPLY_TO_LED(1,5); APPLY_TO_LED(1,6); APPLY_TO_LED(1,7); PORT_DATA = led_signal_buffer; PORT_CLK = 1<<1; APPLY_TO_LED(2,0); APPLY_TO_LED(2,1); APPLY_TO_LED(2,2); APPLY_TO_LED(2,3); APPLY_TO_LED(2,4); APPLY_TO_LED(2,5); APPLY_TO_LED(2,6); APPLY_TO_LED(2,7); PORT_DATA = led_signal_buffer; PORT_CLK = 1<<2; //APPLY_TO_LED(3,0); //APPLY_TO_LED(3,1); APPLY_TO_LED(3,2); APPLY_TO_LED(3,3); APPLY_TO_LED(3,4); APPLY_TO_LED(3,5); APPLY_TO_LED(3,6); APPLY_TO_LED(3,7); PORT_DATA = led_signal_buffer; PORT_CLK = 1<<3; PORT_CLK = 0; } bool ProcessLedPacket(LedState *pstate) { if(pstate->Base.ModuleType != MODULETYPE_LED && pstate->Base.InternalAddr > LED_COUNT && pstate->Base.InternalAddr == 0) return false; led_count_init[pstate->Base.InternalAddr-1] = pstate->DutyValue; return true; } void spi_received(args_received *e) { g_myDevID.raw = e->pdstId->raw; ProcessLedPacket((LedState*)e->ppack); } int main(void) { uint8_t i; MCUCR = 0b01100000; //todo: turn off bods MCUSR = 0; // Do not omit to clear this resistor, otherwise suffer a terrible reseting cause. tus_spi_init(); tus_spi_set_handler(spi_received); device_init(); TimerCounter1::SetUp(NoPrescaleB, Normal16, NormalPortOperationA, NormalPortOperationB, Off, Fall); TimerCounter1::CompareMatchAInterrupt::Enable(); while(1) { init_led_status(); for(i=0; i<128; ++i) { apply_led_status(); _delay_us(100); } } }
通信基板との会話
別ファイルにSPI割り込みの関数が定義されていて,データの送受信を行っています.
タイマ割り込みでtus_spi_process_packets()が呼び出されており,定期的に受信したデータの処理を行っています.この関数は,事前にtus_spi_handler()で指定された関数を間接的に呼び出し,具体的な処理を委ねています.このモジュールではspi_received()が設定されています.spi_received()は受信したデータを実際に反映していて,LEDのDuty比設定を更新しています.
ソフトPWM
led_count_initに設定するDuty比を書いておいて,周期の最初,led_count_remainsにled_count_initをコピーします.
ループ毎にled_count_remainsをデクリメントし,0になるまでオンにしておきます.この処理に相当するのがapply_led_status()です.
これが最も時間的な制約を受けそうですが,コンパイル時に-O3で最適化を書けた場合で,最悪でも大体200クロックで抜けるようです.これは,クロック周波数が20MHzだとすると,10usec程度で処理か終わる見込みです.
直後の100usecのdelayを考えると,1周期は約14msecぐらいで回り,PWM周波数は71Hz程度と予測できます.SPIの割り込み等を考えると,もう少し遅くなっているでしょう.
おわり
あとで写真を撮って貼っておきたい
本当はポイントモジュールが先だったけど,設計がちょっと疑問なので実測してから載せる予定