--- /dev/null
+# $Id$
+
+MCU = atmega328p
+CLOCK = 16000000
+CFLAGS = -Wall -Os -ffunction-sections -fdata-sections -mmcu=$(MCU) -DF_CPU=$(CLOCK)
+LDFLAGS = -Os -Wl,--gc-sections -mmcu=$(MCU)
+
+help:
+ @echo "Targets:"
+ @echo " %.hex: Build firmware from %.c"
+ @echo " upload-%: Upload firmware to AVR"
+
+%.hex: %.elf
+ avr-objcopy -O ihex $< $@
+
+%.elf: %.o
+ avr-gcc $(LDFLAGS) -o $@ $<
+
+%.o: %.c
+ avr-gcc -c $(CFLAGS) -o $@ $<
+
+upload-%: %.hex
+ avrdude -p$(MCU) -cstk500v1 -P/dev/ttyUSB0 -b57600 -D -Uflash:w:$<:i
--- /dev/null
+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010 Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+
+#define BIT(n) (1<<(n))
+#define NOP() __asm__("nop");
+
+uint8_t digits[11] =
+{
+ 0x3F,
+ 0x06,
+ 0x5B,
+ 0x4F,
+ 0x66,
+ 0x6D,
+ 0x7D,
+ 0x07,
+ 0x7F,
+ 0x6F,
+ 0x40
+};
+
+uint16_t read_input(void);
+uint16_t read_input_filtered(void);
+void write_serial(uint8_t);
+void write_7seg(uint8_t);
+
+uint8_t speed = 0;
+uint16_t funcs = 0;
+volatile uint8_t ticks = 0;
+volatile uint8_t rx_buf[3];
+volatile uint8_t rx_fill = 0;
+
+ISR(TIMER1_COMPA_vect)
+{
+ if(ticks<255)
+ ++ticks;
+}
+
+ISR(USART_RX_vect)
+{
+ uint8_t c = UDR0;
+ if(rx_fill==0)
+ {
+ if(c=='A' || c=='S')
+ rx_buf[rx_fill++] = c;
+ }
+ else
+ {
+ rx_buf[rx_fill++] = c;
+ if(rx_buf[0]=='A' && rx_fill==3)
+ {
+ uint8_t n = (rx_buf[1]-'0')*10+(rx_buf[2]-'0');
+ write_7seg(n/10);
+ write_7seg(n%10);
+ rx_fill = 0;
+ }
+ else if(rx_buf[0]=='S' && rx_fill==3)
+ {
+ speed = (rx_buf[1]-'0')*10+(rx_buf[2]-'0');
+ rx_fill = 0;
+ }
+ }
+}
+
+int main()
+{
+ uint16_t state = 0;
+
+ DDRD = 0x02; // 00000010
+ PORTD = 0xFC; // 11111100
+ DDRB = 0x3F; // 00111111
+ PORTB = 0x0F; // 00001111
+
+ write_7seg(10);
+ write_7seg(10);
+
+ // 9600 baud, 8N1
+ UBRR0H = 0;
+ UBRR0L = 103;
+ UCSR0C = BIT(UCSZ00) | BIT(UCSZ01);
+ UCSR0B = BIT(RXEN0) | BIT(TXEN0) | BIT(RXCIE0);
+
+ TCCR1A = 0;
+ TCCR1B = BIT(WGM12) | BIT(CS12);
+ OCR1AH = 24;
+ OCR1AL = 106;
+ TIMSK1 = BIT(OCIE1A);
+
+ sei();
+
+ while(1)
+ {
+ uint16_t toggled;
+ uint8_t i;
+ uint16_t input;
+
+ input = read_input_filtered();
+ input ^= 0xFFFC;
+
+ toggled = state^input;
+ state = input;
+
+ if((toggled&3)==2 && (state&1))
+ {
+ uint8_t action = 0;
+ if(state&2)
+ {
+ if(speed<14)
+ {
+ ++speed;
+ action = 1;
+ }
+ }
+ else
+ {
+ if(speed>0)
+ {
+ --speed;
+ action = 1;
+ }
+ else if(ticks>10)
+ action = 2;
+ }
+
+ if(action==1)
+ {
+ write_serial('S');
+ write_serial('0'+speed/10);
+ write_serial('0'+speed%10);
+ }
+ else if(action==2)
+ write_serial('R');
+
+ ticks = 0;
+ }
+
+ for(i=0; i<14; ++i)
+ {
+ uint16_t bit = 4<<i;
+ if(toggled&~state&bit)
+ {
+ if(i==0)
+ write_serial('N');
+ else if(i==1)
+ write_serial('P');
+ else
+ {
+ uint8_t f = i-2;
+ uint16_t fbit = 1<<f;
+ funcs ^= fbit;
+ write_serial((funcs&fbit) ? 'H' : 'L');
+ write_serial('0'+f);
+ }
+ }
+ }
+ }
+}
+
+uint16_t read_input(void)
+{
+ uint8_t row;
+ uint16_t input = 0;
+
+ for(row=0; row<4; ++row)
+ {
+ uint8_t pins;
+
+ PORTB = (PORTB|0x0F)&~(1<<row);
+ NOP();
+ NOP();
+ NOP();
+ NOP();
+ NOP();
+ NOP();
+ pins = PIND>>2;
+ input |= pins&3;
+ input |= (pins&0x3C)<<(row*4);
+ }
+
+ return input;
+}
+
+uint16_t read_input_filtered(void)
+{
+ uint16_t valid = 0xFFFF;
+ uint16_t prev;
+ uint16_t input;
+ uint8_t i;
+
+ prev = read_input();
+ for(i=0; i<20; ++i)
+ {
+ input = read_input();
+ valid &= ~(input^prev);
+ prev = input;
+ }
+
+ return input&valid;
+}
+
+void write_serial(uint8_t c)
+{
+ while(!(UCSR0A&(1<<UDRE0))) ;
+ UDR0 = c;
+}
+
+void write_7seg(uint8_t n)
+{
+ uint8_t segs = ~digits[n];
+ uint8_t i;
+ for(i=0; i<8; ++i)
+ {
+ PORTB &= ~0x20;
+ if(segs&0x80)
+ PORTB |= 0x10;
+ else
+ PORTB &= ~0x10;
+ PORTB |= 0x20;
+ segs <<= 1;
+ }
+}