--- /dev/null
+/*
+Firmware for model railway control interface
+
+ATMega pinout:
+D0 - serial RX
+D1 - serial TX
+D2 - Polarity control
+D3 - Power control
+
+Connections for Pololu high-powered motor driver:
+D2 <-> DIR
+D3 <-> PWM
+
+This is not a complete control device. Rather, it is a low-level bus driver,
+intended to be controlled with a computer program. In particular, no state is
+kept about locomotives or solenoids. It is up to the controlling program to
+maintain a refresh cycle, turn off solenoids after a sufficient delay, etc.
+
+Commands are received as a packetized stream over the serial interface. Each
+packet begins with a header byte, which is bitwise not of the packet's length,
+not including the header byte itself. The longest allowed packet is 15 bytes.
+Thus, the high four bits of the header byte are always ones. The first data
+byte specifies the command, and the rest of the packet is command arguments.
+
+After a command has been processed, a similarly packetized response is sent.
+
+Track output is asynchronous up to a single packet. If an output command is
+received while a previous packet is still being sent to the track, execution
+blocks until the operation completes. The command returns as soon as sending
+its data to the track can start.
+*/
+#include <avr/io.h>
+#include "commands.h"
+#include "motorola.h"
+#include "packet.h"
+#include "serial.h"
+#include "timer.h"
+
+Packet packet;
+uint8_t out_bit;
+uint8_t out_time;
+uint8_t out_data;
+uint8_t delay_time;
+
+volatile uint8_t recv_buf[32];
+uint8_t recv_head = 0;
+uint8_t recv_tail = 0;
+volatile uint8_t recv_fill = 0;
+volatile uint8_t recv_overrun = 0;
+uint8_t cmd_buf[15];
+uint8_t cmd_length;
+
+void process_commands();
+uint8_t process_command();
+
+int main()
+{
+ DDRD = 0x0E;
+ PORTD = 0;
+
+ serial_init(9600);
+ timer_start_hz(0, 80000, 1);
+
+ sei();
+
+ while(1)
+ {
+ if(recv_overrun)
+ {
+ serial_write(0xFE);
+ serial_write(RECEIVE_OVERRUN);
+ recv_overrun = 0;
+ }
+ if(recv_fill>0)
+ process_commands();
+ }
+
+ return 0;
+}
+
+static inline void receive(uint8_t c)
+{
+ if(recv_fill>=sizeof(recv_buf))
+ {
+ recv_overrun = 1;
+ return;
+ }
+
+ recv_buf[recv_head++] = c;
+ if(recv_head>=sizeof(recv_buf))
+ recv_head = 0;
+ ++recv_fill;
+}
+
+SERIAL_SET_CALLBACK(receive)
+
+void process_commands()
+{
+ while(recv_fill>0)
+ {
+ uint8_t consumed;
+ uint8_t c = recv_buf[recv_tail];
+
+ cmd_length = 0;
+
+ if(c>=0xF0)
+ {
+ cmd_length = ~c;
+ if(recv_fill<=cmd_length)
+ break;
+
+ uint8_t i, j;
+ for(i=0, j=recv_tail+1; i<cmd_length; ++i, ++j)
+ {
+ if(j>=sizeof(recv_buf))
+ j = 0;
+ cmd_buf[i] = recv_buf[j];
+ }
+
+ consumed = 1+cmd_length;
+ }
+ else
+ {
+ serial_write(0xFE);
+ serial_write(FRAMING_ERROR);
+ consumed = 1;
+ }
+
+ recv_tail += consumed;
+ if(recv_tail>=sizeof(recv_buf))
+ recv_tail -= sizeof(recv_buf);
+ recv_fill -= consumed;
+
+ if(cmd_length>0)
+ {
+ uint8_t result = process_command();
+ serial_write(0xFE);
+ serial_write(result);
+ }
+ }
+}
+
+uint8_t process_command()
+{
+ if(cmd_buf[0]==POWER_ON || cmd_buf[0]==POWER_OFF)
+ {
+ if(cmd_length!=1)
+ return LENGTH_ERROR;
+
+ if(cmd_buf[0]==POWER_ON)
+ PORTD |= 0x08;
+ else
+ PORTD &= ~0x08;
+ }
+ else if(cmd_buf[0]==MOTOROLA_SPEED || cmd_buf[0]==MOTOROLA_SPEED_DIRECTION || cmd_buf[0]==MOTOROLA_SPEED_FUNCTION)
+ {
+ if(cmd_length!=4)
+ return LENGTH_ERROR;
+
+ uint8_t addr = cmd_buf[1];
+ if(addr>80)
+ return INVALID_VALUE;
+
+ if(cmd_buf[2]&0x0E)
+ return INVALID_VALUE;
+ uint8_t aux = cmd_buf[2]&0x01;
+
+ uint8_t func = (cmd_buf[2]&0xF0)>>4;
+ if(cmd_buf[0]==MOTOROLA_SPEED_FUNCTION)
+ {
+ if(func<1 || func>4)
+ return INVALID_VALUE;
+ }
+ else if(cmd_buf[2]&0xFE)
+ return INVALID_VALUE;
+ uint8_t state = cmd_buf[2]&0x02;
+
+ uint8_t speed = cmd_buf[3]&0x7F;
+ if(speed>14)
+ return INVALID_VALUE;
+
+ uint8_t dir = !(cmd_buf[3]&0x80);
+
+ while(packet.ready && !packet.done) ;
+
+ if(cmd_buf[0]==MOTOROLA_SPEED)
+ motorola_locomotive_speed_packet(addr, aux, speed);
+ else if(cmd_buf[0]==MOTOROLA_SPEED_DIRECTION)
+ motorola_locomotive_speed_direction_packet(addr, aux, speed, dir);
+ else if(cmd_buf[0]==MOTOROLA_SPEED_FUNCTION)
+ motorola_locomotive_speed_function_packet(addr, aux, speed, func, state);
+ }
+ else if(cmd_buf[0]==MOTOROLA_REVERSE)
+ {
+ if(cmd_length!=3)
+ return LENGTH_ERROR;
+
+ uint8_t addr = cmd_buf[1];
+ if(addr>80)
+ return INVALID_VALUE;
+
+ if(cmd_buf[2]&0xFE)
+ return INVALID_VALUE;
+ uint8_t aux = cmd_buf[2]&0x01;
+
+ while(packet.ready && !packet.done) ;
+
+ motorola_locomotive_reverse_packet(addr, aux);
+ }
+ else if(cmd_buf[0]==MOTOROLA_SOLENOID)
+ {
+ if(cmd_length!=3)
+ return LENGTH_ERROR;
+
+ uint8_t addr = cmd_buf[1];
+ if(addr>80)
+ return INVALID_VALUE;
+
+ if(cmd_buf[2]&0x8E)
+ return INVALID_VALUE;
+ uint8_t output = (cmd_buf[2]&0x70)>>4;
+ uint8_t state = cmd_buf[2]&1;
+
+ while(packet.ready && !packet.done) ;
+
+ motorola_solenoid_packet(addr, output, state);
+ }
+ else
+ return INVALID_COMMAND;
+
+ return COMMAND_OK;
+}
+
+static inline void tick()
+{
+ if(delay_time && --delay_time)
+ return;
+
+ if(out_time && !--out_time)
+ {
+ ++out_bit;
+ if(out_bit>=packet.length)
+ {
+ PORTD &= ~0x04;
+ if(packet.repeat_count>1)
+ {
+ if(packet.repeat_count<0xFF)
+ --packet.repeat_count;
+ delay_time = packet.repeat_delay;
+ packet.sending = 0;
+ }
+ else
+ {
+ delay_time = packet.final_delay;
+ packet.done = 1;
+ }
+ }
+ else
+ {
+ if((out_bit&7)==0)
+ out_data = packet.data[out_bit>>3];
+ else
+ out_data >>= 1;
+
+ if(out_data&1)
+ PORTD |= 0x04;
+ else
+ PORTD &= ~0x04;
+
+ out_time = packet.bit_duration;
+ }
+
+ return;
+ }
+
+ if(packet.ready && !packet.sending)
+ {
+ packet.sending = 1;
+ out_bit = 0;
+ out_time = packet.bit_duration;
+ out_data = packet.data[0];
+ if(out_data&1)
+ PORTD |= 0x04;
+ else
+ PORTD &= ~0x04;
+ }
+}
+
+TIMER_SET_CALLBACK(0, tick)
--- /dev/null
+#include "packet.h"
+
+static uint8_t motorola_speed_to_value(uint8_t speed)
+{
+ if(speed>14)
+ return 15;
+ else if(speed)
+ return speed+1;
+ else
+ return 0;
+}
+
+static void motorola_common_packet(uint8_t addr, uint8_t aux, uint8_t value)
+{
+ uint8_t i;
+
+ clear_packet();
+
+ packet.bit_duration = 2;
+ packet.length = 18*8;
+
+ for(i=0; i<4; ++i)
+ {
+ uint8_t d = addr%3;
+ addr /= 3;
+
+ packet.data[i*2] = (d==0 ? 0x01 : 0x7F);
+ packet.data[i*2+1] = (d==1 ? 0x7F : 0x01);
+ }
+
+ packet.data[8] = (aux ? 0x7F : 0x01);
+ packet.data[9] = packet.data[8];
+
+ for(i=0; i<4; ++i)
+ {
+ packet.data[10+i*2] = ((value&1) ? 0x7F : 0x01);
+ value >>= 1;
+ }
+
+ packet.repeat_count = 2;
+ // Duration of three trits
+ packet.repeat_delay = 96;
+ packet.final_delay = 224;
+}
+
+static void motorola_old_packet(uint8_t addr, uint8_t aux, uint8_t value)
+{
+ uint8_t i;
+
+ motorola_common_packet(addr, aux, value);
+
+ for(i=0; i<4; ++i)
+ packet.data[11+i*2] = packet.data[10+i*2];
+}
+
+void motorola_locomotive_speed_packet(uint8_t addr, uint8_t aux, uint8_t speed)
+{
+ motorola_old_packet(addr, aux, motorola_speed_to_value(speed));
+
+ packet.ready = 1;
+}
+
+void motorola_locomotive_reverse_packet(uint8_t addr, uint8_t aux)
+{
+ motorola_old_packet(addr, aux, 1);
+
+ packet.ready = 1;
+}
+
+void motorola_locomotive_speed_direction_packet(uint8_t addr, uint8_t aux, uint8_t speed, uint8_t dir)
+{
+ motorola_common_packet(addr, aux, motorola_speed_to_value(speed));
+
+ packet.data[11] = (dir ? 0x01 : 0x7F);
+ packet.data[13] = (dir ? 0x7F : 0x01);
+ packet.data[15] = (dir ? 0x01 : 0x7F);
+ packet.data[17] = 0x80-packet.data[16];
+
+ packet.ready = 1;
+}
+
+void motorola_locomotive_speed_function_packet(uint8_t addr, uint8_t aux, uint8_t speed, uint8_t func, uint8_t state)
+{
+ uint8_t i;
+ uint8_t value;
+
+ value = motorola_speed_to_value(speed);
+ motorola_common_packet(addr, aux, value);
+
+ /*
+ 001 -> 011
+ 010 -> 100
+ 011 -> 110
+ 100 -> 111
+ */
+ func += 2;
+ if(func>=5)
+ ++func;
+ if(state)
+ func |= 8;
+ if(func==value)
+ func = ((value&8) ? 2 : 5) | (func&8);
+
+ for(i=0; i<4; ++i)
+ {
+ packet.data[11+i*2] = ((func&1) ? 0x7F : 0x01);
+ func >>= 1;
+ }
+
+ packet.ready = 1;
+}
+
+void motorola_solenoid_packet(uint8_t addr, uint8_t output, uint8_t state)
+{
+ uint8_t value = output;
+ if(state)
+ value |= 8;
+
+ motorola_old_packet(addr, 0, value);
+ packet.repeat_delay >>= 1;
+ packet.bit_duration = 1;
+
+ packet.ready = 1;
+}