Files
Last update 2 years 9 months
by
eddyluursema
SerialCommand.cpp/********************************************************************** SerialCommand.cpp COPYRIGHT (c) 2013-2016 Gregg E. Berman Part of DCC++ BASE STATION for the Arduino **********************************************************************/ // DCC++ BASE STATION COMMUNICATES VIA THE SERIAL PORT USING SINGLE-CHARACTER TEXT COMMANDS // WITH OPTIONAL PARAMTERS, AND BRACKETED BY < AND > SYMBOLS. SPACES BETWEEN PARAMETERS // ARE REQUIRED. SPACES ANYWHERE ELSE ARE IGNORED. A SPACE BETWEEN THE SINGLE-CHARACTER // COMMAND AND THE FIRST PARAMETER IS ALSO NOT REQUIRED. // See SerialCommand::parse() below for defined text commands. #include "SerialCommand.h" #include "DCCpp_Uno.h" #include "Accessories.h" #include "Sensor.h" #include "Outputs.h" #include "EEStore.h" #include "Comm.h" extern int __heap_start, *__brkval; /////////////////////////////////////////////////////////////////////////////// char SerialCommand::commandString[MAX_COMMAND_LENGTH+1]; volatile RegisterList *SerialCommand::mRegs; volatile RegisterList *SerialCommand::pRegs; CurrentMonitor *SerialCommand::mMonitor; /////////////////////////////////////////////////////////////////////////////// void SerialCommand::init(volatile RegisterList *_mRegs, volatile RegisterList *_pRegs, CurrentMonitor *_mMonitor){ mRegs=_mRegs; pRegs=_pRegs; mMonitor=_mMonitor; sprintf(commandString,""); } // SerialCommand:SerialCommand /////////////////////////////////////////////////////////////////////////////// void SerialCommand::process(){ char c; #if COMM_TYPE == 0 while(INTERFACE.available()>0){ // while there is data on the serial line c=INTERFACE.read(); if(c=='<') // start of new command sprintf(commandString,""); else if(c=='>') // end of new command parse(commandString); else if(strlen(commandString)<MAX_COMMAND_LENGTH) // if comandString still has space, append character just read from serial line sprintf(commandString,"%s%c",commandString,c); // otherwise, character is ignored (but continue to look for '<' or '>') } // while #elif COMM_TYPE == 1 EthernetClient client=INTERFACE.available(); if(client){ while(client.connected() && client.available()){ // while there is data on the network c=client.read(); if(c=='<') // start of new command sprintf(commandString,""); else if(c=='>') // end of new command parse(commandString); else if(strlen(commandString)<MAX_COMMAND_LENGTH) // if comandString still has space, append character just read from network sprintf(commandString,"%s%c",commandString,c); // otherwise, character is ignored (but continue to look for '<' or '>') } // while } #endif } // SerialCommand:process /////////////////////////////////////////////////////////////////////////////// void SerialCommand::parse(char *com){ switch(com[0]){ /***** SET ENGINE THROTTLES USING 128-STEP SPEED CONTROL ****/ case 't': // <t REGISTER CAB SPEED DIRECTION> /* * sets the throttle for a given register/cab combination * * REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS (inclusive), to store the DCC packet used to control this throttle setting * CAB: the short (1-127) or long (128-10293) address of the engine decoder * SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0) * DIRECTION: 1=forward, 0=reverse. Setting direction when speed=0 or speed=-1 only effects directionality of cab lighting for a stopped train * * returns: <T REGISTER SPEED DIRECTION> * */ mRegs->setThrottle(com+1); break; /***** OPERATE ENGINE DECODER FUNCTIONS F0-F28 ****/ case 'f': // <f CAB BYTE1 [BYTE2]> /* * turns on and off engine decoder functions F0-F28 (F0 is sometimes called FL) * NOTE: setting requests transmitted directly to mobile engine decoder --- current state of engine functions is not stored by this program * * CAB: the short (1-127) or long (128-10293) address of the engine decoder * * To set functions F0-F4 on (=1) or off (=0): * * BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16 * BYTE2: omitted * * To set functions F5-F8 on (=1) or off (=0): * * BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8 * BYTE2: omitted * * To set functions F9-F12 on (=1) or off (=0): * * BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8 * BYTE2: omitted * * To set functions F13-F20 on (=1) or off (=0): * * BYTE1: 222 * BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 + F19*64 + F20*128 * * To set functions F21-F28 on (=1) of off (=0): * * BYTE1: 223 * BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 + F27*64 + F28*128 * * returns: NONE * */ mRegs->setFunction(com+1); break; /***** OPERATE STATIONARY ACCESSORY DECODERS ****/ case 'a': // <a ADDRESS SUBADDRESS ACTIVATE> /* * turns an accessory (stationary) decoder on or off * * ADDRESS: the primary address of the decoder (0-511) * SUBADDRESS: the subaddress of the decoder (0-3) * ACTIVATE: 1=on (set), 0=off (clear) * * Note that many decoders and controllers combine the ADDRESS and SUBADDRESS into a single number, N, * from 1 through a max of 2044, where * * N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0 * * OR * * ADDRESS = INT((N - 1) / 4) + 1 * SUBADDRESS = (N - 1) % 4 * * returns: NONE */ mRegs->setAccessory(com+1); break; /***** CREATE/EDIT/REMOVE/SHOW & OPERATE A TURN-OUT ****/ case 'T': // <T ID THROW> /* * <T ID THROW>: sets turnout ID to either the "thrown" or "unthrown" position * * ID: the numeric ID (0-32767) of the turnout to control * THROW: 0 (unthrown) or 1 (thrown) * * returns: <H ID THROW> or <X> if turnout ID does not exist * * *** SEE ACCESSORIES.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "T" COMMAND * USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS */ Turnout::parse(com+1); break; /***** CREATE/EDIT/REMOVE/SHOW & OPERATE AN OUTPUT PIN ****/ case 'Z': // <Z ID ACTIVATE> /* * <Z ID ACTIVATE>: sets output ID to either the "active" or "inactive" state * * ID: the numeric ID (0-32767) of the output to control * ACTIVATE: 0 (active) or 1 (inactive) * * returns: <Y ID ACTIVATE> or <X> if output ID does not exist * * *** SEE OUTPUTS.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "O" COMMAND * USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS */ Output::parse(com+1); break; /***** CREATE/EDIT/REMOVE/SHOW A SENSOR ****/ case 'S': /* * *** SEE SENSOR.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "S" COMMAND * USED TO CREATE/EDIT/REMOVE/SHOW SENSOR DEFINITIONS */ Sensor::parse(com+1); break; /***** SHOW STATUS OF ALL SENSORS ****/ case 'Q': // <Q> /* * returns: the status of each sensor ID in the form <Q ID> (active) or <q ID> (not active) */ Sensor::status(); break; /***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ case 'w': // <w CAB CV VALUE> /* * writes, without any verification, a Configuration Variable to the decoder of an engine on the main operations track * * CAB: the short (1-127) or long (128-10293) address of the engine decoder * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) * VALUE: the value to be written to the Configuration Variable memory location (0-255) * * returns: NONE */ mRegs->writeCVByteMain(com+1); break; /***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/ case 'b': // <b CAB CV BIT VALUE> /* * writes, without any verification, a single bit within a Configuration Variable to the decoder of an engine on the main operations track * * CAB: the short (1-127) or long (128-10293) address of the engine decoder * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) * BIT: the bit number of the Configurarion Variable regsiter to write (0-7) * VALUE: the value of the bit to be written (0-1) * * returns: NONE */ mRegs->writeCVBitMain(com+1); break; /***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON PROGRAMMING TRACK ****/ case 'W': // <W CV VALUE CALLBACKNUM CALLBACKSUB> /* * writes, and then verifies, a Configuration Variable to the decoder of an engine on the programming track * * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) * VALUE: the value to be written to the Configuration Variable memory location (0-255) * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function * * returns: <r CALLBACKNUM|CALLBACKSUB|CV Value) * where VALUE is a number from 0-255 as read from the requested CV, or -1 if verificaiton read fails */ pRegs->writeCVByte(com+1); break; /***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON PROGRAMMING TRACK ****/ case 'B': // <B CV BIT VALUE CALLBACKNUM CALLBACKSUB> /* * writes, and then verifies, a single bit within a Configuration Variable to the decoder of an engine on the programming track * * CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024) * BIT: the bit number of the Configurarion Variable memory location to write (0-7) * VALUE: the value of the bit to be written (0-1) * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function * * returns: <r CALLBACKNUM|CALLBACKSUB|CV BIT VALUE) * where VALUE is a number from 0-1 as read from the requested CV bit, or -1 if verificaiton read fails */ pRegs->writeCVBit(com+1); break; /***** READ CONFIGURATION VARIABLE BYTE FROM ENGINE DECODER ON PROGRAMMING TRACK ****/ case 'R': // <R CV CALLBACKNUM CALLBACKSUB> /* * reads a Configuration Variable from the decoder of an engine on the programming track * * CV: the number of the Configuration Variable memory location in the decoder to read from (1-1024) * CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function * CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function * * returns: <r CALLBACKNUM|CALLBACKSUB|CV VALUE) * where VALUE is a number from 0-255 as read from the requested CV, or -1 if read could not be verified */ pRegs->readCV(com+1); break; /***** TURN ON POWER FROM MOTOR SHIELD TO TRACKS ****/ case '1': // <1> /* * enables power from the motor shield to the main operations and programming tracks * * returns: <p1> */ digitalWrite(SIGNAL_ENABLE_PIN_PROG,HIGH); digitalWrite(SIGNAL_ENABLE_PIN_MAIN,HIGH); INTERFACE.print("<p1>"); break; /***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/ case '0': // <0> /* * disables power from the motor shield to the main operations and programming tracks * * returns: <p0> */ digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); INTERFACE.print("<p0>"); break; /***** READ MAIN OPERATIONS TRACK CURRENT ****/ case 'c': // <c> /* * reads current being drawn on main operations track * * returns: <a CURRENT> * where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme */ INTERFACE.print("<a"); INTERFACE.print(int(mMonitor->current)); INTERFACE.print(">"); break; /***** READ STATUS OF DCC++ BASE STATION ****/ case 's': // <s> /* * returns status messages containing track power status, throttle status, turn-out status, and a version number * NOTE: this is very useful as a first command for an interface to send to this sketch in order to verify connectivity and update any GUI to reflect actual throttle and turn-out settings * * returns: series of status messages that can be read by an interface to determine status of DCC++ Base Station and important settings */ if(digitalRead(SIGNAL_ENABLE_PIN_PROG)==LOW) // could check either PROG or MAIN INTERFACE.print("<p0>"); else INTERFACE.print("<p1>"); for(int i=1;i<=MAX_MAIN_REGISTERS;i++){ if(mRegs->speedTable[i]==0) continue; INTERFACE.print("<T"); INTERFACE.print(i); INTERFACE.print(" "); if(mRegs->speedTable[i]>0){ INTERFACE.print(mRegs->speedTable[i]); INTERFACE.print(" 1>"); } else{ INTERFACE.print(-mRegs->speedTable[i]); INTERFACE.print(" 0>"); } } INTERFACE.print("<iDCC++ BASE STATION FOR ARDUINO "); INTERFACE.print(ARDUINO_TYPE); INTERFACE.print(" / "); INTERFACE.print(MOTOR_SHIELD_NAME); INTERFACE.print(": V-"); INTERFACE.print(VERSION); INTERFACE.print(" / "); INTERFACE.print(__DATE__); INTERFACE.print(" "); INTERFACE.print(__TIME__); INTERFACE.print(">"); INTERFACE.print("<N"); INTERFACE.print(COMM_TYPE); INTERFACE.print(": "); #if COMM_TYPE == 0 INTERFACE.print("SERIAL>"); #elif COMM_TYPE == 1 INTERFACE.print(Ethernet.localIP()); INTERFACE.print(">"); #endif Turnout::show(); Output::show(); break; /***** STORE SETTINGS IN EEPROM ****/ case 'E': // <E> /* * stores settings for turnouts and sensors EEPROM * * returns: <e nTurnouts nSensors> */ EEStore::store(); INTERFACE.print("<e "); INTERFACE.print(EEStore::eeStore->data.nTurnouts); INTERFACE.print(" "); INTERFACE.print(EEStore::eeStore->data.nSensors); INTERFACE.print(" "); INTERFACE.print(EEStore::eeStore->data.nOutputs); INTERFACE.print(">"); break; /***** CLEAR SETTINGS IN EEPROM ****/ case 'e': // <e> /* * clears settings for Turnouts in EEPROM * * returns: <O> */ EEStore::clear(); INTERFACE.print("<O>"); break; /***** PRINT CARRIAGE RETURN IN SERIAL MONITOR WINDOW ****/ case ' ': // < > /* * simply prints a carriage return - useful when interacting with Ardiuno through serial monitor window * * returns: a carriage return */ INTERFACE.println(""); break; /// /// THE FOLLOWING COMMANDS ARE NOT NEEDED FOR NORMAL OPERATIONS AND ARE ONLY USED FOR TESTING AND DEBUGGING PURPOSES /// PLEASE SEE SPECIFIC WARNINGS IN EACH COMMAND BELOW /// /***** ENTER DIAGNOSTIC MODE ****/ case 'D': // <D> /* * changes the clock speed of the chip and the pre-scaler for the timers so that you can visually see the DCC signals flickering with an LED * SERIAL COMMUNICAITON WILL BE INTERUPTED ONCE THIS COMMAND IS ISSUED - MUST RESET BOARD OR RE-OPEN SERIAL WINDOW TO RE-ESTABLISH COMMS */ Serial.println("\nEntering Diagnostic Mode..."); delay(1000); bitClear(TCCR1B,CS12); // set Timer 1 prescale=8 - SLOWS NORMAL SPEED BY FACTOR OF 8 bitSet(TCCR1B,CS11); bitClear(TCCR1B,CS10); #ifdef ARDUINO_AVR_UNO // Configuration for UNO bitSet(TCCR0B,CS02); // set Timer 0 prescale=256 - SLOWS NORMAL SPEED BY A FACTOR OF 4 bitClear(TCCR0B,CS01); bitClear(TCCR0B,CS00); #else // Configuration for MEGA bitClear(TCCR3B,CS32); // set Timer 3 prescale=8 - SLOWS NORMAL SPEED BY A FACTOR OF 8 bitSet(TCCR3B,CS31); bitClear(TCCR3B,CS30); #endif CLKPR=0x80; // THIS SLOWS DOWN SYSYEM CLOCK BY FACTOR OF 256 CLKPR=0x08; // BOARD MUST BE RESET TO RESUME NORMAL OPERATIONS break; /***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ case 'M': // <M REGISTER BYTE1 BYTE2 [BYTE3] [BYTE4] [BYTE5]> /* * writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the main operations track * FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER * * REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet * BYTE1: first hexidecimal byte in the packet * BYTE2: second hexidecimal byte in the packet * BYTE3: optional third hexidecimal byte in the packet * BYTE4: optional fourth hexidecimal byte in the packet * BYTE5: optional fifth hexidecimal byte in the packet * * returns: NONE */ mRegs->writeTextPacket(com+1); break; /***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/ case 'P': // <P REGISTER BYTE1 BYTE2 [BYTE3] [BYTE4] [BYTE5]> /* * writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the programming track * FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER * * REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet * BYTE1: first hexidecimal byte in the packet * BYTE2: second hexidecimal byte in the packet * BYTE3: optional third hexidecimal byte in the packet * BYTE4: optional fourth hexidecimal byte in the packet * BYTE5: optional fifth hexidecimal byte in the packet * * returns: NONE */ pRegs->writeTextPacket(com+1); break; /***** ATTEMPTS TO DETERMINE HOW MUCH FREE SRAM IS AVAILABLE IN ARDUINO ****/ case 'F': // <F> /* * measure amount of free SRAM memory left on the Arduino based on trick found on the internet. * Useful when setting dynamic array sizes, considering the Uno only has 2048 bytes of dynamic SRAM. * Unfortunately not very reliable --- would be great to find a better method * * returns: <f MEM> * where MEM is the number of free bytes remaining in the Arduino's SRAM */ int v; INTERFACE.print("<f"); INTERFACE.print((int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval)); INTERFACE.print(">"); break; /***** LISTS BIT CONTENTS OF ALL INTERNAL DCC PACKET REGISTERS ****/ case 'L': // <L> /* * lists the packet contents of the main operations track registers and the programming track registers * FOR DIAGNOSTIC AND TESTING USE ONLY */ INTERFACE.println(""); for(Register *p=mRegs->reg;p<=mRegs->maxLoadedReg;p++){ INTERFACE.print("M"); INTERFACE.print((int)(p-mRegs->reg)); INTERFACE.print(":\t"); INTERFACE.print((int)p); INTERFACE.print("\t"); INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); for(int i=0;i<10;i++){ INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t"); } INTERFACE.println(""); } for(Register *p=pRegs->reg;p<=pRegs->maxLoadedReg;p++){ INTERFACE.print("P"); INTERFACE.print((int)(p-pRegs->reg)); INTERFACE.print(":\t"); INTERFACE.print((int)p); INTERFACE.print("\t"); INTERFACE.print((int)p->activePacket); INTERFACE.print("\t"); INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t"); for(int i=0;i<10;i++){ INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t"); } INTERFACE.println(""); } INTERFACE.println(""); break; } // switch }; // SerialCommand::parse ///////////////////////////////////////////////////////////////////////////////