From 32dad098c247f7f98b14fcb9c6892dc8f302a8d1 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Tue, 2 Jul 2013 17:58:01 +0300 Subject: [PATCH] Add railway control firmware Only motorola protocol is supported to begin with, but further features will be added in the near future. --- arducontrol/Makefile | 3 + arducontrol/arducontrol.c | 289 ++++++++++++++++++++++++++++++++++++++ arducontrol/commands.h | 21 +++ arducontrol/motorola.c | 124 ++++++++++++++++ arducontrol/motorola.h | 11 ++ arducontrol/packet.c | 8 ++ arducontrol/packet.h | 23 +++ 7 files changed, 479 insertions(+) create mode 100644 arducontrol/Makefile create mode 100644 arducontrol/arducontrol.c create mode 100644 arducontrol/commands.h create mode 100644 arducontrol/motorola.c create mode 100644 arducontrol/motorola.h create mode 100644 arducontrol/packet.c create mode 100644 arducontrol/packet.h diff --git a/arducontrol/Makefile b/arducontrol/Makefile new file mode 100644 index 0000000..1ffdd1d --- /dev/null +++ b/arducontrol/Makefile @@ -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 index 0000000..7b0c636 --- /dev/null +++ b/arducontrol/arducontrol.c @@ -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 +#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=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 index 0000000..bbd8dc8 --- /dev/null +++ b/arducontrol/commands.h @@ -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 index 0000000..e78ebfc --- /dev/null +++ b/arducontrol/motorola.c @@ -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 index 0000000..8b0bccf --- /dev/null +++ b/arducontrol/motorola.h @@ -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 index 0000000..2b20fc2 --- /dev/null +++ b/arducontrol/packet.c @@ -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 index 0000000..fdd10ce --- /dev/null +++ b/arducontrol/packet.h @@ -0,0 +1,23 @@ +#ifndef PACKET_H_ +#define PACKET_H_ + +#include + +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 -- 2.45.2