読者です 読者をやめる 読者になる 読者になる

Nゲージ鉄道模型のATS・ATC案③-LEDモジュール

Nゲージ閉塞システム

この記事は過去に作ったNゲージ鉄道模型ATCシステムについて記述しています.
システム全体については下の過去記事を参照してください.

sosoru.hatenablog.jp


f:id:sosoru_m:20151004222219g:plain

LEDモジュール

LEDの制御もユーザーの端末で出来るようにしていました.ATCの動作に直接の関係はありません.当時は駅ホームの照明に使っていました.(本当は信号に使うべきかもしれない)

ハードウェアについて

f:id:sosoru_m:20151018122725p:plain

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

f:id:sosoru_m:20151018143134p:plain

ソフトウェアについて

マイコンは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の割り込み等を考えると,もう少し遅くなっているでしょう.

おわり

あとで写真を撮って貼っておきたい
本当はポイントモジュールが先だったけど,設計がちょっと疑問なので実測してから載せる予定