/*!*************************************************************************** *! *! FILE NAME : train_c.c *! *! DESCRIPTION: User space test program for the train_mod synchronous *! serial driver for model railroad train control. *! *! To some parts based on the serial_test.c program by *! Acme Systems srl *! http://www.acmesystems.it/articles/00035/serial_test.c *! *! Version: 0.0, 2007-01-27: Initial version. *! *! Version: 0.1, 2007-02-04: Added control of GPIO g1 to turn power on/off. *! *! Version: 0.2, 2007-02-12: Added DCC option (only the 3-byte base packet). *! *! Version: 0.3, 2008-05-10: Corrected the STDIN behaviour at program exit. *! Also added some explaining comments in the *! interface to the send function. *! *! --------------------------------------------------------------------------- *! *! (c) Copyright 2005 Acme Systems srl http://www.acmesystems.it *! and *! (C) Copyright 2007-2008 Per Zander, SWEDEN http://home.swipnet.se/perz/ *! *! --------------------------------------------------------------------------- *! *! This program is free software; you can redistribute it and/or modify *! it under the terms of the GNU General Public License as published by *! the Free Software Foundation; either version 2 of the License, or *! (at your option) any later version. *! *! This program is distributed in the hope that it will be useful, *! but WITHOUT ANY WARRANTY; without even the implied warranty of *! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *! GNU General Public License for more details. *! *! To have a copy of the GNU General Public License write to the Free *! Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *! 02110-1301 USA. *! *!***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "train_mod.h" #define NEW_M 0 #define FORWARD 0 #define REVERSE 1 #define F1OFF 2 #define F1ON 3 #define F2OFF 4 #define F2ON 5 #define F3OFF 6 #define F3ON 7 #define F4OFF 8 #define F4ON 9 #define OLD_M 10 #define OLD_REV 11 #define POS_REV 12 #define NEG_REV 13 #define DCC 14 /*----- Prototypes. -----*/ void send(int addr, int func, int speed, int dir_fx, int prot); int convert_addr(int addr); /*----- Speed + direction values. -----*/ unsigned char pos[15] = {0x88, 0x8c, 0x8d, 0x98, 0x99, 0x9c, 0x9d, 0x48, 0x49, 0x4c, 0x4d, 0x58, 0x59, 0x5c, 0x5d}; unsigned char neg[15] = {0xa2, 0xa6, 0xa7, 0xb2, 0xb3, 0xb6, 0xb7, 0x62, 0x63, 0x66, 0x67, 0x72, 0x73, 0x76, 0x77}; /*----- Speed + function values. -----*/ unsigned char f1off[15] = {0x0a, 0x0e, 0x27, 0x1a, 0x1b, 0x1e, 0x1f, 0x4a, 0x4b, 0x4e, 0x4f, 0x5a, 0x5b, 0x5e, 0x5f}; unsigned char f1on[15] = {0x8a, 0x8e, 0x8f, 0x9a, 0x9b, 0x9e, 0x9f, 0xca, 0xcb, 0xce, 0xcd, 0xda, 0xdb, 0xde, 0xdf}; unsigned char f2off[15] = {0x20, 0x24, 0x25, 0x32, 0x31, 0x34, 0x35, 0x60, 0x61, 0x64, 0x65, 0x70, 0x71, 0x74, 0x75}; unsigned char f2on[15] = {0xa0, 0xa4, 0xa5, 0xb0, 0xb1, 0xb4, 0xb5, 0xe0, 0xe1, 0xe4, 0xe5, 0xd8, 0xf1, 0xf4, 0xf5}; unsigned char f3off[15] = {0x28, 0x2c, 0x2d, 0x38, 0x39, 0x36, 0x3d, 0x68, 0x69, 0x6c, 0x6d, 0x78, 0x79, 0x7c, 0x7d}; unsigned char f3on[15] = {0xa8, 0xac, 0xad, 0xb8, 0xb9, 0xbc, 0xbd, 0xe8, 0xe9, 0xec, 0xed, 0xf8, 0xf9, 0xdc, 0xfd}; unsigned char f4off[15] = {0x2a, 0x2e, 0x2f, 0x3a, 0x3b, 0x3e, 0x37, 0x6a, 0x6b, 0x6e, 0x6f, 0x7a, 0x7b, 0x7e, 0x7f}; unsigned char f4on[15] = {0xaa, 0xae, 0xaf, 0xba, 0xbb, 0xbe, 0xbf, 0xea, 0xeb, 0xee, 0xef, 0xfa, 0xfb, 0xfe, 0xdd}; /*----- Old Motorola format. -----*/ unsigned char old[15] = {0x00, 0x0c, 0x0f, 0x30, 0x33, 0x3c, 0x3f, 0xc0, 0xc3, 0xcc, 0xcf, 0xf0, 0xf3, 0xfc, 0xff}; /*----- Explicit reverse commands. -----*/ unsigned char oldrev = 0x03; unsigned char posrev = 0x89; unsigned char negrev = 0xa3; /*----- Standard in, gpio and tty. -----*/ struct termios stdin_saved_attributes; int tty_fd; int gpio_fd; //-------------------------------------------------------------------------- // Open device syncser1. //-------------------------------------------------------------------------- int tty_open_sser() { tty_fd = open("/dev/syncser1", O_RDWR | O_NOCTTY | O_NONBLOCK); if (tty_fd < 0) { return -1; } return tty_fd; } //-------------------------------------------------------------------------- // Functions for the gpio device. //-------------------------------------------------------------------------- //----- Open. -----* static int gpio_open() { if ((gpio_fd = open("/dev/gpiog", O_RDWR)) < 0) { printf("Open error on /dev/gpiog\n"); return -1; } return gpio_fd; } //----- Set/clear bits. -----* static void gpio_set (int nr, int val) { switch (val) { case 0: ioctl(gpio_fd, _IO(ETRAXGPIO_IOCTYPE, IO_CLRBITS), 1 << nr); break; case 1: ioctl(gpio_fd, _IO(ETRAXGPIO_IOCTYPE, IO_SETBITS), 1 << nr); break; default: printf("Error, bad I/O bit value %d.\n", val); break; } } //---- Read bits. -----* static inline int gpio_read (int nr) { return (ioctl(gpio_fd, _IO(ETRAXGPIO_IOCTYPE, IO_READBITS)) >> nr) & 1; } //-------------------------------------------------------------------------- // Some stdin stuff. //-------------------------------------------------------------------------- int stdin_init(void) { struct termios tattr; // Make sure stdin is a terminal if (!isatty (STDIN_FILENO)) { fprintf (stderr, "stdin is not a terminal\n"); return -1; } // Save the terminal attributes so we can restore them later. tcgetattr (STDIN_FILENO, &stdin_saved_attributes); // Set the terminal modes. tcgetattr (STDIN_FILENO, &tattr); tattr.c_lflag &= ~(ICANON | ECHO); /* Clear ICANON and ECHO. */ tattr.c_cc[VMIN] = 0; tattr.c_cc[VTIME] = 0; tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr); return 0; } void restore_and_exit () { tcsetattr ( STDIN_FILENO, TCSANOW, &stdin_saved_attributes); close(tty_fd); printf("Exit\n"); exit(0); } void termination_handler (int signum) { restore_and_exit(); } //-------------------------------------------------------------------------- // Address conversion function. //-------------------------------------------------------------------------- int convert_addr(int addr) { int addr_bits; /* A4, A3, A2, A1 ; A1 sent first */ //addr = 18; addr_bits = 0; if (addr >= 80) { return (addr_bits); /* Address 80 is coded as 0. */ } else if (addr >= 54) { addr -= 54; addr_bits = 0x40; } else if (addr >= 27) { addr -= 27; addr_bits = 0xc0; } else if (addr == 0) { return (0x55); /* Idle address. */ } if (addr >= 18) { addr -= 18; addr_bits |= 0x10; } else if (addr >= 9) { addr -= 9; addr_bits |= 0x30; } if (addr >= 6) { addr -= 6; addr_bits |=0x04; } else if (addr >= 3) { addr -= 3; addr_bits |= 0x0c; } if (addr >= 2) { addr_bits |= 0x01; } else if (addr >= 1) { addr_bits |= 0x03; } return (addr_bits); } //-------------------------------------------------------------------------- // Send function. //-------------------------------------------------------------------------- void send(int addr, // Locomotive address. int func, // Function 0 (typically lights), 0 = off, 1 = on. int speed, // Range 0 - 14 (stop and 14 speed steps) int dir_fx, // Direction or function command: // FORWARD : Drive with absolute direction forward. // REVERSE : Drive with absolute direction backward. // F1OFF : Drive with function 1 off. // F1ON : Drive with function 1 on. // F2OFF : Drive with function 2 off. // F2ON : Drive with function 2 on. // F3OFF : Drive with function 3 off. // F3ON : Drive with function 3 on. // F4OFF : Drive with function 4 off. // F4ON : Drive with function 4 on. // OLD_M : Drive with current direction (obsolete). // OLD_REV : Stop and change direction (almost // obsolete, but may be the only way to // change direction with some older // Märklin locos). // POS_REV : Stop and set direction forward. // NEG_REV : Stop and set direction backward. int prot) // Protocol: // DCC : NMRA DCC standard protocol // (only speed and no functions supported // by this program). // OLD_M : Old Märklin/Motorola protocol // (obsolete). // NEW_M : Märklin/Motorola protocol. Use this // with all Märklin locos until I have // Implemented mfx. { unsigned char mm_cmd[4]; unsigned char dfx_val; int nr, i; if (prot == DCC) { mm_cmd[0] = train_cmd_dcc; mm_cmd[1] = addr & 0x7f; mm_cmd[2] = speed ? speed + 1 : speed; switch (dir_fx) { case FORWARD: mm_cmd[2] |= 0x60; break; case REVERSE: mm_cmd[2] |= 0x40; break; default: printf("Error, DCC function not supported\n"); return; } i = 0; while ((nr = write(tty_fd, mm_cmd, 3)) != 3) { printf ("\nTransmitter busy %d\n", nr); i++; if (i > 5) { printf("Give up\n"); break; } } printf ("\nSend %x\n", (* ((unsigned int *) &mm_cmd[0])) & 0xffffff); return; } /*----- First byte, command. -----*/ mm_cmd[0] = train_cmd_mm; /*----- Second byte, address. -----*/ mm_cmd[1] = convert_addr(addr); /*----- Function. -----*/ switch (func) { case 0: mm_cmd[2] = 0; break; // Trinary 0. Function off. case 1: mm_cmd[2] = 3; break; // Trinary 1. Function on. case 2: mm_cmd[2] = 2; break; // ESU half speed with function off. case 3: mm_cmd[2] = 1; break; // ESU half speed with function on. default: mm_cmd[0] = 'a'; mm_cmd[2] = func & 3; break; // debug } /*----- Speed and direction/function. -----*/ switch (dir_fx) { case FORWARD: dfx_val = pos[speed]; break; case REVERSE: dfx_val = neg[speed]; break; case F1OFF: dfx_val = f1off[speed]; break; case F1ON: dfx_val = f1on[speed]; break; case F2OFF: dfx_val = f2off[speed]; break; case F2ON: dfx_val = f2on[speed]; break; case F3OFF: dfx_val = f3off[speed]; break; case F3ON: dfx_val = f3on[speed]; break; case F4OFF: dfx_val = f4off[speed]; break; case F4ON: dfx_val = f4on[speed]; break; case OLD_M: dfx_val = old[speed]; break; case OLD_REV: dfx_val = oldrev; break; case POS_REV: dfx_val = posrev; break; case NEG_REV: dfx_val = negrev; break; default: dfx_val = pos[0]; break; } mm_cmd[2] |= dfx_val << 2; mm_cmd[3] = dfx_val >> 6; /*----- Send the command. -----*/ i = 0; while ((nr = write(tty_fd, mm_cmd, 4)) != 4) { printf ("\nTransmitter busy %d\n", nr); i++; if (i > 5) { printf("Give up\n"); break; } } printf ("\nSend %x\n", * ((unsigned int *) &mm_cmd[0])); } //-------------------------------------------------------------------------- // Get character and echo. //-------------------------------------------------------------------------- unsigned char getche() { unsigned char ch; while (read (STDIN_FILENO, &ch, 1) <= 0); printf("%c", ch); fflush(stdout); return ch; } //-------------------------------------------------------------------------- // Scan in a decimal value. //-------------------------------------------------------------------------- int scan_dec() { int val; int inv; unsigned char ch; val = 0; inv = 0; do { ch = getche(); } while (isspace(ch)); if (ch == '-') { inv = 1; ch = getche(); } else if (ch == '+') { ch = getche(); } while (isdigit(ch)) { val = val * 10; val += ch - '0'; ch = getche(); } if (inv) { val = -val; } return val; } //-------------------------------------------------------------------------- // Stop/go functions. //-------------------------------------------------------------------------- static inline void stop() { gpio_set(1, 0); } static inline void go() { gpio_set(1, 1); } //-------------------------------------------------------------------------- // Main function. //-------------------------------------------------------------------------- int main() { char ch; int speed0; int speed1; int dir0; int dir1; int dir_fx0; int dir_fx1; int func0; int func1; int addr0; int addr1; int ver0; int ver1; int f0[4]; int f1[4]; int prot0; int prot1; int i; int tmp; speed0 = 0; speed1 = 0; dir0 = FORWARD; dir1 = FORWARD; ver0 = NEW_M; /* Assume new Motorola format as default. */ ver1 = NEW_M; func0 = 0; /* Head lights off as default */ func1 = 0; addr0 = 0x0; /* Idle address as default */ addr1 = 0x0; f0[0] = 0; f0[1] = 0; f0[2] = 0; f0[3] = 0; f1[0] = 0; f1[1] = 0; f1[2] = 0; f1[3] = 0; prot0 = NEW_M; prot1 = NEW_M; printf("Train_mod Test (press ctrl-c to exit)\n"); if (tty_open_sser() < 0) { fprintf (stderr, "tty open error %s\n", strerror(errno)); exit(EXIT_FAILURE); } if (gpio_open() < 0) { printf("gpio open error %s\n", strerror(errno)); exit(EXIT_FAILURE); } if (stdin_init() < 0) { printf("stdin init error %s\n", strerror(errno)); exit(EXIT_FAILURE); } if (signal (SIGINT, termination_handler) == SIG_IGN) { signal (SIGINT, SIG_IGN); } if (signal (SIGHUP, termination_handler) == SIG_IGN) { signal (SIGHUP, SIG_IGN); } if (signal (SIGTERM, termination_handler) == SIG_IGN) { signal (SIGTERM, SIG_IGN); } go(); while (1) { ch = getche(); switch (ch) { case 'a': /* Set address for loco 0. */ { tmp = addr0; printf("\nEnter address for loco 0: "); fflush(stdout); addr0 = scan_dec(); if (addr0 != tmp) /* Reset if new loco. */ { speed0 = 0; dir0 = FORWARD; func0 = 0; ver0 = NEW_M; prot0 = NEW_M; } break; } case 'A': /* Set address for loco 1. */ { tmp = addr1; printf("\nEnter address for loco 1: "); fflush(stdout); addr1 = scan_dec(); if (addr1 != tmp) /* Reset if new loco. */ { speed1 = 0; dir1 = FORWARD; func1 = 0; ver1 = NEW_M; prot0 = NEW_M; } break; } case 'c': { ch = getche(); switch (ch) { case '0': speed0 = 0; break; case '1': speed0 = 1; break; case '2': speed0 = 2; break; case '3': speed0 = 3; break; case '4': speed0 = 4; break; case '5': speed0 = 5; break; case '6': speed0 = 6; break; case '7': speed0 = 7; break; case '8': speed0 = 8; break; case '9': speed0 = 9; break; } send(addr0, func0, speed0, dir0, prot0); break; } case 'C': { ch = getche(); switch (ch) { case '0': speed1 = 0; break; case '1': speed1 = 1; break; case '2': speed1 = 2; break; case '3': speed1 = 3; break; case '4': speed1 = 4; break; case '5': speed1 = 5; break; case '6': speed1 = 6; break; case '7': speed1 = 7; break; case '8': speed1 = 8; break; case '9': speed1 = 9; break; } send(addr1, func1, speed1, dir1, prot1); break; } case 'q': /* Quit */ case 'Q': case ' ': /* Emergency stop */ { stop(); speed0 = 0; speed1 = 0; if (dir0 == OLD_M) { dir_fx0 = OLD_M; } else { dir_fx0 = F4ON; } if (dir1 == OLD_M) { dir_fx1 = OLD_M; } else { dir_fx1 = F4ON; } for (i = 0; i < 8; i++) { send(addr0, func0, speed0, dir_fx0, prot0); send(addr0, func0, speed0, dir_fx0, prot0); send(addr1, func1, speed1, dir_fx1, prot1); send(addr1, func1, speed1, dir_fx1, prot1); send(addr1, func1, speed1, dir_fx1, prot1); send(addr1, func1, speed1, dir_fx1, prot1); send(addr0, func0, speed0, dir_fx0, prot0); send(addr0, func0, speed0, dir_fx0, prot0); } if (ch != ' ') { restore_and_exit(); } break; } case 'j': /* Decrease speed */ { speed0--; if (speed0 < 0) { speed0 = 0; } send(addr0, func0, speed0, dir0, prot0); break; } case 'J': /* Decrease speed */ { speed1--; if (speed1 < 0) { speed1 = 0; } send(addr1, func1, speed1, dir1, prot1); break; } case 'k': /* Increase speed */ { speed0++; if (speed0 > 14) { speed0 = 14; } send(addr0, func0, speed0, dir0, prot0); break; } case 'K': /* Increase speed */ { speed1++; if (speed1 > 14) { speed1 = 14; } send(addr1, func1, speed1, dir1, prot1); break; } case 's': /* Stop */ { speed0 = 0; send(addr0, func0, speed0, dir0, prot0); break; } case 'S': /* Stop */ { speed1 = 0; send(addr1, func1, speed1, dir1, prot1); break; } case 'r': /* Reverse */ { speed0 = 0; if (dir0 == OLD_M) { for (i = 0; i < 8; i++) { send(addr0, func0, speed0, OLD_REV, prot0); } } if (dir0 == FORWARD) { dir0 = REVERSE; } else if (dir0 == REVERSE) { dir0 = FORWARD; } send(addr0, func0, speed0, dir0, prot0); break; } case 'R': /* Reverse */ { speed1 = 0; if (dir1 == OLD_M) { for (i = 0; i < 8; i++) { send(addr1, func1, speed1, OLD_REV, prot1); } } if (dir1 == FORWARD) { dir1 = REVERSE; } else if (dir1 == REVERSE) { dir1 = FORWARD; } send(addr1, func1, speed1, dir1, prot1); break; } case 'd': /* Function off */ { ch = getche(); switch (ch) { case '1': f0[0] = 0; dir_fx0 = F1OFF; break; case '2': f0[1] = 0; dir_fx0 = F2OFF; break; case '3': f0[2] = 0; dir_fx0 = F3OFF; break; case '4': f0[3] = 0; dir_fx0 = F4OFF; break; default: func0 = 0; dir_fx0 = dir0; break; } send(addr0, func0, speed0, dir_fx0, prot0); break; } case 'D': /* Function off */ { ch = getche(); switch (ch) { case '1': f1[0] = 0; dir_fx1 = F1OFF; break; case '2': f1[1] = 0; dir_fx1 = F2OFF; break; case '3': f1[2] = 0; dir_fx1 = F3OFF; break; case '4': f1[3] = 0; dir_fx1 = F4OFF; break; default: func1 = 0; dir_fx1 = dir1; break; } send(addr1, func1, speed1, dir_fx1, prot1); break; } case 'f': /* Function on */ { ch = getche(); switch (ch) { case '1': f0[0] = 1; dir_fx0 = F1ON; break; case '2': f0[1] = 1; dir_fx0 = F2ON; break; case '3': f0[2] = 1; dir_fx0 = F3ON; break; case '4': f0[3] = 1; dir_fx0 = F4ON; break; default: func0 = 1; dir_fx0 = dir0; break; } send(addr0, func0, speed0, dir_fx0, prot0); break; } case 'F': /* Function on */ { ch = getche(); switch (ch) { case '1': f1[0] = 1; dir_fx1 = F1ON; break; case '2': f1[1] = 1; dir_fx1 = F2ON; break; case '3': f1[2] = 1; dir_fx1 = F3ON; break; case '4': f1[3] = 1; dir_fx1 = F4ON; break; default: func1 = 1; dir_fx1 = dir1; break; } send(addr1, func1, speed1, dir_fx1, prot1); break; } case 'g': // Go. case 'G': { go(); break; } case 'o': { speed0 = 0; dir0 = OLD_M; prot0 = OLD_M; break; } case 'O': { speed1 = 0; dir1 = OLD_M; dir1 = OLD_M; break; } case 'n': { speed0 = 0; dir0 = FORWARD; prot0 = NEW_M; break; } case 'N': { speed1 = 0; dir1 = FORWARD; prot1 = NEW_M; break; } case 'u': { speed0 = 0; dir0 = FORWARD; prot0 = DCC; break; } case 'U': { speed1 = 0; dir1 = FORWARD; prot1 = DCC; break; } case 'h': /* Help. */ case 'H': case '?': { printf("\n"); printf("Locomotive control program for Märklin/Motorola format.\n"); printf("The program controls two locomotives at the same time.\n"); printf("Use lowercase for loco #0 and uppercase for loco #1.\n\n"); printf("Command: Description:\n\n"); printf(" a Set loco address.\n"); printf(" c Set a speed between 0 and 9.\n"); printf(" dx Clear function.\n"); printf(" d Clear F.\n"); printf(" fx Set function.\n"); printf(" f Set F.\n"); printf(" g Go (resume after emergency stop).\n"); printf(" h This help.\n"); printf(" j Decrease speed.\n"); printf(" k Increase speed.\n"); printf(" n Set new Motorola format (default).\n"); printf(" o Set old Motorola format.\n"); printf(" q Quit.\n"); printf(" r Reverse direction.\n"); printf(" s Stop.\n"); printf(" u Set DCC format.\n"); printf(" v View current settings.\n"); printf(" Emergency stop (both locomotives).\n\n"); break; } case 'v': { printf("\n"); printf("Status for locomotive #0:\n\n"); printf("Address = %d\n", addr0); printf("Speed = %d\n", speed0); if (dir0 == FORWARD) { printf("Direction = forward\n"); } else if (dir0 == REVERSE) { printf("Direction = reverse\n"); } else { printf("Old Motorola format used, direction unknown.\n"); } if (func0) { printf("Function = on\n"); } else { printf("Function = off\n"); } if (f0[0]) { printf("F1 = on\n"); } else { printf("F1 = off\n"); } if (f0[1]) { printf("F2 = on\n"); } else { printf("F2 = off\n"); } if (f0[2]) { printf("F3 = on\n"); } else { printf("F3 = off\n"); } if (f0[3]) { printf("F4 = on\n"); } else { printf("F4 = off\n"); } break; } case 'V': { printf("\n"); printf("Status for locomotive #1:\n\n"); printf("Address = %d\n", addr1); printf("Speed = %d\n", speed1); if (dir1 == FORWARD) { printf("Direction = forward\n"); } else if (dir1 == REVERSE) { printf("Direction = reverse\n"); } else { printf("Old Motorola format used, direction unknown.\n"); } if (func1) { printf("Function = on\n"); } else { printf("Function = off\n"); } if (f1[0]) { printf("F1 = on\n"); } else { printf("F1 = off\n"); } if (f1[1]) { printf("F2 = on\n"); } else { printf("F2 = off\n"); } if (f1[2]) { printf("F3 = on\n"); } else { printf("F3 = off\n"); } if (f1[3]) { printf("F4 = on\n"); } else { printf("F4 = off\n"); } break; } case 'p': { speed0 = 0; dir0 = FORWARD; send (addr0, func0, speed0, POS_REV, prot0); break; } case 'P': { speed1 = 0; dir1 = FORWARD; send (addr1, func1, speed1, POS_REV, prot1); break; } case 'm': { speed0 = 0; dir0 = REVERSE; send (addr0, func0, speed0, NEG_REV, prot0); break; } case 'M': { speed1 = 0; dir1 = REVERSE; send (addr1, func1, speed1, NEG_REV, prot1); break; } case '+': { func0 |= 2; send(addr0, func0, speed0,dir0, prot0); break; } case '-': { func0 &= 1; send(addr0, func0, speed0, dir0, prot0); break; } case 'x': { send(0, 7, 0, 0, prot0); } } } }