]> git.tdb.fi Git - model-railway-devices.git/blobdiff - arducontrol/arducontrol.c
Add railway control firmware
[model-railway-devices.git] / arducontrol / arducontrol.c
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)