]> git.tdb.fi Git - model-railway-devices.git/commitdiff
Add railway control firmware
authorMikko Rasa <tdb@tdb.fi>
Tue, 2 Jul 2013 14:58:01 +0000 (17:58 +0300)
committerMikko Rasa <tdb@tdb.fi>
Tue, 2 Jul 2013 14:58:01 +0000 (17:58 +0300)
Only motorola protocol is supported to begin with, but further features
will be added in the near future.

arducontrol/Makefile [new file with mode: 0644]
arducontrol/arducontrol.c [new file with mode: 0644]
arducontrol/commands.h [new file with mode: 0644]
arducontrol/motorola.c [new file with mode: 0644]
arducontrol/motorola.h [new file with mode: 0644]
arducontrol/packet.c [new file with mode: 0644]
arducontrol/packet.h [new file with mode: 0644]

diff --git a/arducontrol/Makefile b/arducontrol/Makefile
new file mode 100644 (file)
index 0000000..1ffdd1d
--- /dev/null
@@ -0,0 +1,3 @@
+include ../common/build.mk
+
+arducontrol.elf: motorola.o packet.o serial.o timer.o
diff --git a/arducontrol/arducontrol.c b/arducontrol/arducontrol.c
new file mode 100644 (file)
index 0000000..7b0c636
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+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)
diff --git a/arducontrol/commands.h b/arducontrol/commands.h
new file mode 100644 (file)
index 0000000..bbd8dc8
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef COMMANDS_H_
+#define COMMANDS_H_
+
+enum Command
+{
+       POWER_ON = 0x01,
+       POWER_OFF = 0x02,
+       MOTOROLA_SPEED = 0x11,
+       MOTOROLA_REVERSE = 0x12,
+       MOTOROLA_SPEED_DIRECTION = 0x13,
+       MOTOROLA_SPEED_FUNCTION = 0x14,
+       MOTOROLA_SOLENOID = 0x15,
+       COMMAND_OK = 0x80,
+       RECEIVE_OVERRUN = 0x81,
+       FRAMING_ERROR = 0x82,
+       INVALID_COMMAND = 0x83,
+       LENGTH_ERROR = 0x84,
+       INVALID_VALUE = 0x85
+};
+
+#endif
diff --git a/arducontrol/motorola.c b/arducontrol/motorola.c
new file mode 100644 (file)
index 0000000..e78ebfc
--- /dev/null
@@ -0,0 +1,124 @@
+#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;
+}
diff --git a/arducontrol/motorola.h b/arducontrol/motorola.h
new file mode 100644 (file)
index 0000000..8b0bccf
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef MOTOROLA_H_
+#define MOTOROLA_H_
+
+void motorola_locomotive_speed_packet(uint8_t, uint8_t, uint8_t);
+void motorola_locomotive_reverse_packet(uint8_t, uint8_t);
+void motorola_locomotive_speed_direction_packet(uint8_t, uint8_t, uint8_t, uint8_t);
+void motorola_locomotive_speed_function_packet(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
+void motorola_solenoid_packet(uint8_t, uint8_t, uint8_t);
+void motorola_set_repeat_count(uint8_t);
+
+#endif
diff --git a/arducontrol/packet.c b/arducontrol/packet.c
new file mode 100644 (file)
index 0000000..2b20fc2
--- /dev/null
@@ -0,0 +1,8 @@
+#include "packet.h"
+
+void clear_packet()
+{
+       packet.ready = 0;
+       packet.sending = 0;
+       packet.done = 0;
+}
diff --git a/arducontrol/packet.h b/arducontrol/packet.h
new file mode 100644 (file)
index 0000000..fdd10ce
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef PACKET_H_
+#define PACKET_H_
+
+#include <stdint.h>
+
+typedef struct
+{
+       uint8_t bit_duration:5;
+       uint8_t ready:1;
+       uint8_t sending:1;
+       volatile uint8_t done:1;
+       uint8_t length;
+       uint8_t data[32];
+       uint8_t repeat_count;
+       uint8_t repeat_delay;
+       uint8_t final_delay;
+} Packet;
+
+extern Packet packet;
+
+void clear_packet();
+
+#endif