/*************** ARDROT project - rotator controller based on Arduino *************** (c) OK1DX 2012-3 */ /*********** FUNCTION LIST ***************** setup - arduino setup procedure. Performed once at beginning loop - arduino loop procedure. Performed cyclically (started immy again when finished) PCINT2 - interrupt routine, performed when there is any change on one o 3 signals from encoder. Incremental encoder counting, referencing getPosnEncoder - access to ecoder value from main program loop (necessary for consistent reading) calculateSetpoint - to calculate relative position setpoint from azimuth command ramp - function performed from main loop, used to control positioning (direction, speed, relays,...). called in fixed cyclic rate getAnalogSetpoint - reads potentiometr (position setpoint), command for auto movement from panel SetSerialPort - configure serial port speed RxCharCom - processing of one character received over serial port ProcessComCmd - processing of whole command received over serial port Ascii2Byte, Nibble2Ascii - convertion between number and ASCII characters CheckEEPROM - verify magic byte and when not matching, EEPROM is reloaded from flash (default values) SaveEncoder - actual encoder value is saved to EEPROM upon power failure LoadEncoder - initialize incremental encoder to last saved value from EEPROM EepromReadInt - read one integer (2 bytes) from EEPROM LoadEepromParams - configuration parameters are read from EEPROM to RAM (during power up) */ /* v teto fazi funguje - LCD displej - inkrementalni enkoder (citani a referencovani) - kalkulace uhlu natoceni - rizeni otaceni, ramp generator rychlosti - ovladani vystupnich rele - zadavani auto pozicovani potenciometrem - ovladani tlacitky CW a CCW - komunikace COM - protokol GS232 - signaly z koncaku - timeout na automaticke pozicovani - cteni DIP switche (konfig mode) - kulturni zobrazeni na displeji - okamzite zastaveni pri vypadku napajeni - ulozeni enkoderu do EEPROM, recovery - cteni a zapis EEPROM (config mode) - komunikace ethernet (telnet) implementovano neotestovano - USB datovy kanal - spolecny RX buffer neni hotove - otestovani komunikace s jinymi programy / kompatibilita - web server */ #define AROT_ETHERNET #define AROT_USB #include #include #include #ifdef AROT_ETHERNET #include #endif // constants const int maxSpdCW = 32767; const int maxSpdCCW = -32767; #define PRGMODE_OPER 0 // normal operation, local and remote control possible #define PRGMODE_CONFIG 1 // diagnostic operation, movement only by panel buttons without software limits, EEPROM read/write. #define PRGMODE_2 2 // for future use #define PRGMODE_3 3 #define RXBUFSIZE 10 // size of RX data buffer #define MAGIC 0x37 // magic byte (when not matching, EEPROM is reloaded) // paramters to be remanent int encCounterIdx; // position of incremental encoder when index signal comes int offsetCCW; // CCW position (always non negative number 0 to 3600) int rotationRange; // permitted rotation (CW - CCW) int tenths of degree unsigned int rampStep; // ramp generator step in miliseconds unsigned int toutLimit; // timeout for any movement limit, in ramp generator cycles int deadband; // how far to start slowdown before posn is reached (temporary solution!) int rampUp; // acceleration ramp int rampDwn; // desceleration ramp byte Xspeed; // speed selector (not used - for compatibility / future version) // process values byte prgMode; // program mode (dip switches scan at startup) 0=normal, 1=diagno, 2=config byte motor; //0 = stop, 1 = CW, 2 = CCW used to detect change int cmdSP; // absolute position setpoint-command (azimuth, north based) in 0.1 deg unit int posnSP; // relative setpoint for automatic positioning, in 0.1 deg, starting from CCW posn unsigned long msec2; // msec timer, used for ramp generator int motorSpeed; // motor speed between -32767 to 32767. 0 = motor stopped int timeout; // timeout for movement, actual counter char cmdSource; // a letter indicating source of last command - Potentiometer, Com port, Ethernet, Usb char dataSource; // a letter indicating source of last received data - ?,C,E,U / used to forward responce bool cmdRcvd; // set when command received, reset when movement becomes active bool automoveCW; // auto (azimuth command) move CW bool automoveCCW; // auto (azimuth command) move CCW bool cmdButtonOld; // edge detector bool cmdCCW; // commmand go CCW from external communication. Terminated by STOP command. bool cmdCW; // commmand go CW from external communication. Terminated by STOP command. bool butCWok; // allows move by CW button (can be disabled because of timeout or HW LS) bool butCCWok; // allows move by CW button (can be disabled because of timeout or HW LS) bool pwrOK; // monitor power supply bool encSaved; // saving of encoder value after power down has been performed bool LcdRefreshStatus; // display status data (there is a change). Reset when done. volatile int encCounter; volatile byte encPortOld; volatile bool refDone; // COM port communication variables byte ComRxBuffer[RXBUFSIZE]; // to save received data byte ComBufPtr; // position where to save next char bool ComCmdRx; // command received - ready to be processed bool ComErr; // error in reception, further processing blocked; waiting for next to clear error #ifdef AROT_ETHERNET // Ethernet port communication variables byte EthRxBuffer[RXBUFSIZE]; // to save received data byte EthBufPtr; // position where to save next char bool EthCmdRx; // command received - ready to be processed bool EthErr; // error in reception, further processing blocked; waiting for next to clear error #endif #ifdef AROT_USB // USB port communication variables byte UsbRxBuffer[RXBUFSIZE]; // to save received data byte UsbBufPtr; // position where to save next char bool UsbCmdRx; // command received - ready to be processed bool UsbErr; // error in reception, further processing blocked; waiting for next to clear error #endif byte RxBuffer[RXBUFSIZE]; // received data copied from individual buffers before being processed byte BufPtr; // position after last character in buffer // initialize the library with the numbers of the interface pins LiquidCrystal lcd(44, 45, 49, 48, 47, 46); #ifdef AROT_ETHERNET EthernetServer server1(EepromReadInt(34)); // command port number #endif // ************************************************************************************** // ARDUINO SETUP FUNCTION void setup() { // set up the LCD's number of columns and rows: lcd.begin(16, 2); lcd.print("ARDROT 1.0"); delay(1000); // give some time for power supply to get full power pinMode(43,INPUT); // DIP switch reading pinMode(42,INPUT); // set 2 pins as Inputs with Pullup active digitalWrite(43,HIGH); digitalWrite(42,HIGH); prgMode = PRGMODE_OPER; if (digitalRead(43) == LOW) prgMode += 1; if (digitalRead(42) == LOW) prgMode += 2; if (prgMode != PRGMODE_OPER) // no special display in operational mode { lcd.setCursor(0,2); if (prgMode == PRGMODE_CONFIG) lcd.print("CONFIG"); else lcd.print("??????"); delay(1000); } if (CheckEEPROM()) { lcd.setCursor(0,2); lcd.print("EEPROM"); delay(1000); } lcd.setCursor(0,0); lcd.print("Az: "); lcd.setCursor(0,1); lcd.print("Cmd: "); SetSerialPort(); #ifdef AROT_ETHERNET InitEthernetIface(); #endif #ifdef AROT_USB UsbBufPtr = 0; UsbCmdRx = false; UsbErr = false; Serial.begin(9600); #endif // parameters initialized from EEPROM /* rotationRange = 4000; // 400 degreess encCounterIdx = 1000; // middle of the range (2000 pulses per revolution) offsetCCW = 0; rampStep = 50; //ramp calculated for 50 msec step deadband = 50; // 5 degrees rampUp = 4096; // 4 cycles rampDwn = 2048; // 16 cycles toutLimit = 2400; // 2 minutes timeout */ LoadEepromParams(); encCounter = 0; // just for test; should be stored in remanent memory upon every power off. pwrOK = true; cmdButtonOld = true; DDRC=0x70; // 4,5,6 is output, other inputs PORTC= 0x87; // pullup on 1,2,3,7 PORTK=0x7f; // pullup for 0-6, 7 is ADC DDRK=0x00; // all inputs PCICR=4; // active PCINT group 2 PCMSK2=7; // active PCINT16,17,18 sei(); msec2 = millis(); cmdRcvd = false; automoveCW = false; automoveCCW = false; cmdCCW = false; cmdCW = false; cmdSP =0; dataSource = '?'; cmdSource = '?'; // undefined butCWok = true; butCCWok = true; timeout = 0; LcdRefreshStatus = true; encSaved = false; refDone = false; encCounter = LoadEncoder(); // value from last power down } #ifdef AROT_ETHERNET // Initiates Ethernet interface addresses void InitEthernetIface() { byte mac[6]; // MAC address of the interface byte ip[4]; //the IP address for the shield: byte gateway[4]; // the router's gateway address: byte subnet[4]; // the subnet: int i; int ptr = 24; // read data from EEPROM for(i=0;i<6;i++) mac[i] = EEPROM.read(ptr++); for(i=0;i<4;i++) ip[i] = EEPROM.read(ptr++); ptr = 38; for(i=0;i<4;i++) gateway[i] = EEPROM.read(ptr++); for(i=0;i<4;i++) subnet[i] = EEPROM.read(ptr++); Ethernet.begin(mac, ip, gateway, subnet); // initiate interface EthBufPtr = 0; // initialize buffer, flags EthCmdRx = false; EthErr = false; server1.begin(); // start the game } #endif // *********************************************************************************** // Incremental Encoder interrupt routine SIGNAL(PCINT2_vect) { byte encPort = PINK & 0x7; // only lower 3 bits byte change = encPort ^ encPortOld; // bit set to 1 -> change if (change & 1) // phase A change { if (((encPort&1) && (encPort&2)) || (!(encPort&1) && !(encPort&2))) encCounter++; else encCounter--; } if (change & 2) // phase B change { if (((encPort&1) && (encPort&2)) || (!(encPort&1) && !(encPort&2))) encCounter--; else encCounter++; } if ((!(encPort&4)) && (encPortOld&4) && (motorSpeed == maxSpdCW)) // falling edge, moving CW full speed { encCounter = encCounterIdx; // do referencing refDone = true; } encPortOld = encPort; // remember port bits for next call } // ************************************************************************************** // set serial port speed (EEPROM param) and initialize the port void SetSerialPort() { byte spd = EEPROM.read(21); // 0=9600, 1=4800, 2=2400, 3=1200, 4=600, 5=300 Bd long comspd = 9600; if (spd < 6) { while (spd > 0) { comspd = comspd >> 1; spd--; } } ComBufPtr = 0; ComCmdRx = false; ComErr = false; Serial1.begin(comspd); } // ************************************************************************************** // return actual relative encoder position, unit 0.1 deg, CCW position = 0 int getPosnEncoder() { // disable interrupt to guarantee consistent reading cli(); // todo some calculation to convert counter value to angle // 500 pulses per revolution, 4 quadrat detect -> 2000 increments per revolution // posn = counter * 360 * 10 / 2000 = counter * 1.8 = counter * 9/5 int ctr = (encCounter * 9)/5; sei(); return ctr; } // *************************************************************************************************************** // calculates setpoint for automatic movement // criteria: minimum angle distance (when 2 setpoints possible), closest position (when out of range of rotation) // input angle (0.1 deg unit) between CCW limit and requested position // output angle setpoint (0.1 deg unit), between 0 to rotationRange int calculateSetpoint(int req) { while(req < 0) req += 3600; while(req >= 3600) req -= 3600; // now the req position is normalized in range 0 to 3599 int req2 = req + 3600; // optional 2nd position in range 3600 to 7199 if (req2 <= rotationRange) // when both possibilies should be considered { int posnActual = getPosnEncoder(); if (abs(req - posnActual) <= abs(req2 - posnActual)) return req; // req is closer else return req2; // req2 is closer } if (req <= rotationRange) return req; // only one choice // this can happen only when rotation range is less than 360 degs if ((3600 - req) < (rotationRange - req)) return 0; // the closest position is CCW return rotationRange; // the closest position is CW } // ********************************************************************************************* // performed every rampStep milliseconds // speed ramp generator, limit switch handling, output relay control, movement timeout, autostop,.... void ramp() { int posnActual = getPosnEncoder(); if (cmdRcvd) // processing of command, only upon receive { posnSP = calculateSetpoint(cmdSP - offsetCCW); // cmdSP has to be set in advance LcdRefreshStatus = true; cmdCW = false; // cancel other possible movement command cmdCCW = false; if (posnActual > posnSP) { // request to move in direction CCW automoveCW = false; automoveCCW = prgMode == PRGMODE_OPER; // allowed in operation mode only } else { // request to move in direction CW automoveCW = prgMode == PRGMODE_OPER; automoveCCW = false; } cmdRcvd = false; // reset request, it is processed only once } bool butCW = digitalRead(37) == LOW; bool butCCW = digitalRead(36) == LOW; if (!butCW) butCWok = true; // reset timeout - by button release if (!butCCW) butCCWok = true; // reset timeout - by button release // terminate automatic movement when we are close to destination angle or if ant of CW or CCW buttons is pressed if ((automoveCW && ((posnSP - posnActual) < deadband)) || (automoveCCW && ((posnActual - posnSP) < deadband)) || butCW || butCCW) { automoveCW = false; // we need to stop! automoveCCW = false; } // terminate CCW command because of softlimit switch if (posnActual < 0) { cmdCCW = false; automoveCCW = false; if (prgMode == PRGMODE_OPER) // in diagno allowed to block soft limit switch butCCWok = false; } // terminate CW command because of softlimit switch if (posnActual > rotationRange) { cmdCW = false; automoveCW = false; if (prgMode == PRGMODE_OPER) // in diagno allowed to block soft limit switch butCWok = false; } pwrOK = digitalRead(34) == HIGH; if ((digitalRead(A11) == LOW) || (!pwrOK)) // active hardware limit switch CW or power fail { cmdCW = false; // reset move command automoveCW = false; // reset automove command butCWok = false; // disable button signal } if ((digitalRead(A12) == LOW) || (!pwrOK)) // active hardware limit switch CCW or power fail { cmdCCW = false; // reset move command automoveCCW = false; // reset automove command butCCWok = false; // disable button signal } bool goCW = automoveCW || cmdCW || (butCW && butCWok) ; // summary to go CW bool goCCW = automoveCCW || cmdCCW || (butCCW && butCCWok); // summary to go CCW if (goCW && (motorSpeed >= 0)) // already moving or starting CW { if (motorSpeed < maxSpdCW) { // remp Up long spd = ((long)motorSpeed) + rampUp; if (spd > maxSpdCW) motorSpeed = maxSpdCW; else motorSpeed = (int) spd; } } if (goCCW && (motorSpeed <= 0)) // already moving or starting CCW { if (motorSpeed > maxSpdCCW) { // remp Up long spd = motorSpeed - rampUp; if (spd < maxSpdCCW) motorSpeed = maxSpdCCW; else motorSpeed = (int) spd; } } // both buttons pressed together - operator mistake, stop the rotor! if ((!goCW || (butCW && butCCW)) && (motorSpeed >0)) // motor is moving CW but there is no command to move CW or both buttons pressed = do slowdown ramp { motorSpeed -= rampDwn; if (motorSpeed < 0) motorSpeed = 0; // final stop } if ((!goCCW || (butCW && butCCW)) && (motorSpeed <0)) // motor is moving CW but there is no command to move CW or both buttons pressed = do slowdown ramp { motorSpeed += rampDwn; if (motorSpeed > 0) motorSpeed = 0; // final stop } // timeout for movement. Will prevent further movement when rotor is unable to reach position in given time interval if (motorSpeed == 0) { timeout = 0; } else { timeout++; if (timeout > toutLimit) { timeout == toutLimit; // prevent overflow automoveCW = false; automoveCCW = false; cmdCW = false; cmdCCW = false; butCWok = false; // disable CW and CCW panel buttons butCCWok = false; } } // OUTPUT RELAY CONTROL byte motorActual; // breake control. Brake opened when nonzero speed if (motorSpeed == 0) { digitalWrite(31,LOW); motorActual = 0; } else digitalWrite(31,HIGH); if (motorSpeed == maxSpdCW) // moving max speed (after speed up ramp)CW { digitalWrite(32, HIGH); digitalWrite(33, LOW); motorActual = 1; } else if (motorSpeed == maxSpdCCW) // moving max speed (after speed up ramp) CCW { digitalWrite(32, LOW); digitalWrite(33, HIGH); motorActual = 2; } else { digitalWrite(32, LOW); // no max speed in both directions digitalWrite(33, LOW); } if (motorActual != motor) // if there is change in rotation, force display status refresh { motor = motorActual; LcdRefreshStatus = true; } } // ***************************************************************************************** // read setpoint command from potentiometer void getAnalogSetpoint() { int adc = analogRead(15); // degrees = value * 3600 / 1024 = value * 225 / 64 long setpoint = (((long)adc) * 226) >> 6; cmdSP = (int) setpoint; // save new setpoint cmdRcvd = true; // new flag - accept command only in normal mode cmdSource = 'P'; // received from potentiometer LcdRefreshStatus = true; } //***************************************************************************************** // one character received from COM port. Saves to buffer. If command complete, returns true bool RxCharCom(int c) { if (c == 0x0A) return false; // ignored if (c == 0x0D) // { if (ComErr) { ComBufPtr = 0; ComErr = false; // clear the buffer and error flag return false; } if (ComBufPtr > 0) { ComCmdRx = true; // set flag; further reception blocked return true; // command ready to be processed } return false; // empty command, ignored } if (ComBufPtr >=10) { ComErr = true; // command too long / error. Since this moment we wait for to clear the error ComBufPtr = 0; return false; } ComRxBuffer[ComBufPtr] = (byte)c; ComBufPtr++; return false; } #ifdef AROT_ETHERNET //***************************************************************************************** // one character received from Ethernet port. Saves to buffer. If command complete, returns true bool RxCharEth(int c) { if (c == 0x0A) return false; // ignored if (c == 0x0D) // { if (EthErr) { EthBufPtr = 0; EthErr = false; // clear the buffer and error flag return false; } if (EthBufPtr > 0) { EthCmdRx = true; // set flag; further reception blocked return true; // command ready to be processed } return false; // empty command, ignored } if (EthBufPtr >=10) { EthErr = true; // command too long / error. Since this moment we wait for to clear the error EthBufPtr = 0; return false; } EthRxBuffer[EthBufPtr] = (byte)c; EthBufPtr++; return false; } #endif #ifdef AROT_USB //***************************************************************************************** // one character received from USB port. Saves to buffer. If command complete, returns true bool RxCharUsb(int c) { if (c == 0x0A) return false; // ignored if (c == 0x0D) // { if (UsbErr) { UsbBufPtr = 0; UsbErr = false; // clear the buffer and error flag return false; } if (UsbBufPtr > 0) { UsbCmdRx = true; // set flag; further reception blocked return true; // command ready to be processed } return false; // empty command, ignored } if (UsbBufPtr >=10) { UsbErr = true; // command too long / error. Since this moment we wait for to clear the error UsbBufPtr = 0; return false; } UsbRxBuffer[UsbBufPtr] = (byte)c; UsbBufPtr++; return false; } #endif // ********************************************************************************* // convert ascii character to number 0-15 byte Ascii2Byte(char c) { if ((c >= '0') && (c <='9')) return (c - '0'); if ((c >= 'A') && (c <='F')) return (c - 'A' + 10); if ((c >= 'a') && (c <='f')) return (c - 'a' + 10); return 0; // error } // ********************************************************************************** // convert 2 ASCII chars to byte 0-255. c1 High, c2 low byte Ascii2Byte(char c1, char c2) { return (Ascii2Byte(c1) << 4) + Ascii2Byte(c2); } // ********************************************************************************** // 4 lower bits in byte to ASCII character (upper nible is ignored) char Nibble2Ascii(byte x) { x &= 0x0F; if (x < 10) return (x + '0'); return (x + 'A' - 10); } // ********************************************************************************** // sends a character into channel from where we received the data void SendData(char c) { switch(dataSource) { case 'C': Serial1.write(c); break; case 'E': server1.write(c); break; case 'U': Serial.write(c); break; } } // ********************************************************************************** // sends a string into channel from where we received the data void SendDataX(const char* aaa) { switch(dataSource) { case 'C': Serial1.write(aaa); break; case 'E': server1.write(aaa); break; case 'U': Serial.write(aaa); break; } } // ********************************************************************************** // called when command is completely in common buffer void ProcessCmd() { bool sendLF = false; byte cmd = RxBuffer[0]; if ((cmd >= 0x61) && (cmd <= 0x7a)) cmd -= 0x20; // from lower case to upper case switch (cmd) { case 'R': // start CW turning if (BufPtr == 1) { cmdCW = prgMode == PRGMODE_OPER; cmdCCW = false; automoveCW = false; automoveCCW = false; cmdSource = dataSource; // from where command received } break; case 'L': // start CCW turning if (BufPtr == 1) { cmdCCW = prgMode == PRGMODE_OPER; cmdCW = false; automoveCW = false; automoveCCW = false; cmdSource = dataSource; // from where command received } break; case 'A': // stop azimuth movement case 'S': // stop any movement cmdCW = false; cmdCCW = false; automoveCW = false; automoveCCW = false; break; case 'X': if (BufPtr == 2) { Xspeed = RxBuffer[1] - '0'; } break; case 'H': if (BufPtr == 1) { SendDataX("ARDROT 1.0\n"); } break; case 'C': // request to send actual azimuth int azi; byte c1, c2, c3; azi = (getPosnEncoder() + offsetCCW + 5)/10; // rounding to whole degrees while(azi<0) azi += 360; while (azi >= 360) azi -= 360; c3 = azi % 10 + '0'; azi = azi/10; c2 = azi % 10 + '0'; c1 = azi/10 + '0'; if (BufPtr <= 2) // send "AZ=234" for Az == 234 degs { SendData('A'); SendData('Z'); SendData('='); SendData(c1); SendData(c2); SendData(c3); } if ((BufPtr == 2) && (RxBuffer[1] == '2')) // command "C2", for compatibility { SendDataX(" EL=000"); // elevation } sendLF = true; break; case 'M': case 'W': if ((BufPtr == 4) || (BufPtr == 8)) // command to turn in auto mode "M123" or "W123 023" { int az = 100 * (RxBuffer[1] - '0') + 10 * (RxBuffer[2] - '0') + RxBuffer[3] - '0'; if ((az >= 0) && (az <= 450)) { if (az >=360) az -= 360; cmdSP = az * 10; // setpoint, unit 0.1 deg cmdSource = dataSource; // command received from channel cmdRcvd = true; // the flag for automode processing } LcdRefreshStatus = true; } break; case 'Q': // write EEPROM, format "Q12@01A3" -> write 0x12 to address 0x01A3 max EEPROM addr 0x0FFF (4kBytes) if ((BufPtr == 8) && (RxBuffer[3] == '@') && (RxBuffer[4] == '0') && (prgMode == PRGMODE_CONFIG)) { byte value = Ascii2Byte(RxBuffer[1], RxBuffer[2]); int addr = (Ascii2Byte(RxBuffer[5]) << 8) + Ascii2Byte(RxBuffer[6], RxBuffer[7]); EEPROM.write(addr, value); SendDataX("OK"); sendLF = true; } break; case 'B': // read EEPROM, format "B01A3" -> read from address 0x01A3 if ((BufPtr == 5) && (RxBuffer[1] == '0') && (prgMode == PRGMODE_CONFIG)) { int addr = (Ascii2Byte(RxBuffer[2]) << 8) + Ascii2Byte(RxBuffer[3], RxBuffer[4]); byte value = EEPROM.read(addr); SendData(Nibble2Ascii(value >> 4)); SendData(Nibble2Ascii(value)); sendLF = true; } break; } SendData('\r'); // if (sendLF) SendData('\n'); // BufPtr = 0; } /* OBSOLETE // ********************************************************************************** // called when command is completely in buffer void ProcessComCmd() { bool sendLF = false; byte cmd = ComRxBuffer[0]; if ((cmd >= 0x61) && (cmd <= 0x7a)) cmd -= 0x20; // from lower case to upper case switch (cmd) { case 'R': // start CW turning if (ComBufPtr == 1) { cmdCW = prgMode == PRGMODE_OPER; cmdCCW = false; automoveCW = false; automoveCCW = false; cmdSource = 'C'; // command received at COM port } break; case 'L': // start CCW turning if (ComBufPtr == 1) { cmdCCW = prgMode == PRGMODE_OPER; cmdCW = false; automoveCW = false; automoveCCW = false; cmdSource = 'C'; // command received at COM port } break; case 'A': // stop azimuth movement case 'S': // stop any movement cmdCW = false; cmdCCW = false; automoveCW = false; automoveCCW = false; break; case 'X': if (ComBufPtr == 2) { Xspeed = ComRxBuffer[1] - '0'; } break; case 'H': if (ComBufPtr == 1) { Serial1.write("ARDROT 1.0\n"); } break; case 'C': // request to send actual azimuth int azi; byte c1, c2, c3; azi = (getPosnEncoder() + offsetCCW + 5)/10; // rounding to whole degrees while(azi<0) azi += 360; while (azi >= 360) azi -= 360; c3 = azi % 10 + '0'; azi = azi/10; c2 = azi % 10 + '0'; c1 = azi/10 + '0'; if (ComBufPtr <= 2) // send "AZ=234" for Az == 234 degs { Serial1.write('A'); Serial1.write('Z'); Serial1.write('='); Serial1.write(c1); Serial1.write(c2); Serial1.write(c3); } if ((ComBufPtr == 2) && (ComRxBuffer[1] == '2')) // command "C2", for compatibility { Serial1.write(" EL=000"); // elevation } sendLF = true; break; case 'M': case 'W': if ((ComBufPtr == 4) || (ComBufPtr == 8)) // command to turn in auto mode "M123" or "W123 023" { int az = 100 * (ComRxBuffer[1] - '0') + 10 * (ComRxBuffer[2] - '0') + ComRxBuffer[3] - '0'; if ((az >= 0) && (az <= 450)) { if (az >=360) az -= 360; cmdSP = az * 10; // setpoint, unit 0.1 deg cmdSource = 'C'; // command received at COM port cmdRcvd = true; // the flag for automode processing } LcdRefreshStatus = true; } break; case 'Q': // write EEPROM, format "Q12@01A3" -> write 0x12 to address 0x01A3 max EEPROM addr 0x0FFF (4kBytes) if ((ComBufPtr == 8) && (ComRxBuffer[3] == '@') && (ComRxBuffer[4] == '0') && (prgMode == PRGMODE_CONFIG)) { byte value = Ascii2Byte(ComRxBuffer[1], ComRxBuffer[2]); int addr = (Ascii2Byte(ComRxBuffer[5]) << 8) + Ascii2Byte(ComRxBuffer[6], ComRxBuffer[7]); EEPROM.write(addr, value); Serial1.write("OK"); sendLF = true; } break; case 'B': // read EEPROM, format "B01A3" -> read from address 0x01A3 if ((ComBufPtr == 5) && (ComRxBuffer[1] == '0') && (prgMode == PRGMODE_CONFIG)) { int addr = (Ascii2Byte(ComRxBuffer[2]) << 8) + Ascii2Byte(ComRxBuffer[3], ComRxBuffer[4]); byte value = EEPROM.read(addr); Serial1.write(Nibble2Ascii(value >> 4)); Serial1.write(Nibble2Ascii(value)); sendLF = true; } break; } Serial1.write('\r'); // if (sendLF) Serial1.write('\n'); // ComBufPtr = 0; ComCmdRx = false; // reception can resume and new command can be buffered } #ifdef AROT_ETHERNET // ********************************************************************************** // called when command is completely in buffer void ProcessEthCmd() { bool sendLF = false; byte cmd = EthRxBuffer[0]; if ((cmd >= 0x61) && (cmd <= 0x7a)) cmd -= 0x20; // from lower case to upper case switch (cmd) { case 'R': // start CW turning if (EthBufPtr == 1) { cmdCW = prgMode == PRGMODE_OPER; cmdCCW = false; automoveCW = false; automoveCCW = false; cmdSource = 'E'; // command received at Ethernet port } break; case 'L': // start CCW turning if (EthBufPtr == 1) { cmdCCW = prgMode == PRGMODE_OPER; cmdCW = false; automoveCW = false; automoveCCW = false; cmdSource = 'E'; // command received at Ethernet port } break; case 'A': // stop azimuth movement case 'S': // stop any movement cmdCW = false; cmdCCW = false; automoveCW = false; automoveCCW = false; break; case 'X': if (EthBufPtr == 2) { Xspeed = EthRxBuffer[1] - '0'; } break; case 'H': if (EthBufPtr == 1) { server1.write("ARDROT 1.0\n"); } break; case 'C': // request to send actual azimuth int azi; byte c1, c2, c3; azi = (getPosnEncoder() + offsetCCW + 5)/10; // rounding to whole degrees while(azi<0) azi += 360; while (azi >= 360) azi -= 360; c3 = azi % 10 + '0'; azi = azi/10; c2 = azi % 10 + '0'; c1 = azi/10 + '0'; if (EthBufPtr <= 2) // send "AZ=234" for Az == 234 degs { server1.write('A'); server1.write('Z'); server1.write('='); server1.write(c1); server1.write(c2); server1.write(c3); } if ((EthBufPtr == 2) && (EthRxBuffer[1] == '2')) // command "C2", for compatibility { server1.write(" EL=000"); // elevation } sendLF = true; break; case 'M': case 'W': if ((EthBufPtr == 4) || (EthBufPtr == 8)) // command to turn in auto mode "M123" or "W123 023" { int az = 100 * (EthRxBuffer[1] - '0') + 10 * (EthRxBuffer[2] - '0') + EthRxBuffer[3] - '0'; if ((az >= 0) && (az <= 450)) { if (az >=360) az -= 360; cmdSP = az * 10; // setpoint, unit 0.1 deg cmdSource = 'E'; // command received at Ethernet port cmdRcvd = true; // the flag for automode processing } LcdRefreshStatus = true; } break; } server1.write('\r'); // if (sendLF) server1.write('\n'); // EthBufPtr = 0; EthCmdRx = false; // reception can resume and new command can be buffered } #endif */ // ***************************************************************************************** // function checks whether the EEPROM is initialized (to factory defaults) // if not, it performs initialization (in this case returns TRUE) bool CheckEEPROM() { const byte defs[] = {0xA0,0xF,0xE8,3,8,7,0x32,0,0x32,0,0,0x10,0,8,0x60,9,0,0xDE,0xAD,0xBE,0xEF,0xFE,0xED,0xC0,0xA8,0xA,0xB1,0x62,4,0x50,0,0xC0,0xA8,0x0A,1,0xFF,0xFF,0xFF,0}; if (EEPROM.read(0) == MAGIC) return false; // EEPROM initialized. Empty EEPROM -> 0xFF else { EEPROM.write(0, MAGIC); int i = 0; for(int adr = 7; adr <46; adr++) EEPROM.write(adr, defs[i++]); } return true; } // **************************************************************************************** // called upon power failure when motor is stopped // the value will be recovered from EEPROM during next start void SaveEncoder() { cli(); word posn = (word)encCounter; sei(); byte posnLow = posn & 0xFF; byte posnHigh = posn >> 8; int ptr = 1; EEPROM.write(ptr++, posnLow); EEPROM.write(ptr++, posnHigh); EEPROM.write(ptr++, posnLow); EEPROM.write(ptr++, posnHigh); EEPROM.write(ptr++, posnLow); EEPROM.write(ptr++, posnHigh); // the same value saved 3 times, for sure } // ********************************************************************************* // read one 16 bit int from EEPROM int EepromReadInt(int addr) { int retval = (int)EEPROM.read(addr++); return retval + (((int)(EEPROM.read(addr))) << 8); } // *************************************************************************************** // load setup global parameters from EEPROM to RAM // the communication parameters are read when communication is initialized void LoadEepromParams() { rotationRange = EepromReadInt(7); encCounterIdx = EepromReadInt(9); offsetCCW = EepromReadInt(11); deadband = EepromReadInt(13); rampStep = EepromReadInt(15); rampUp = EepromReadInt(17); rampDwn = EepromReadInt(19); toutLimit = EepromReadInt(21); } // *************************************************************************************** // called on startup to restore last encoder value // if one of 3 values is different, take that identical // the best chance to be correct is for e1 (it is written first, see SaveEncoder) int LoadEncoder() { int e1 = EepromReadInt(1); int e2 = EepromReadInt(3); int e3 = EepromReadInt(5); if ((e1 == e2) || (e1 == e3)) return e1; if (e2 == e3) return e2; return e1; // last choice } // ******************* MAIN PROGRAM LOOP ******************************* void loop() { // COM port handling if (Serial1.available() > 0) { if (RxCharCom(Serial1.read())) { dataSource = 'C'; // received from serial port for (int i=0; i= 0) { if (RxCharEth(zn)) { dataSource = 'E'; // received from ethernet port for (int i=0; i 0) { if (RxCharUsb(Serial.read())) { dataSource = 'U'; // received from USB port for (int i=0; i=3600) angle -= 3600; lcd.setCursor(5,0); lcd.print(angle/10); // print whole degreess lcd.print('.'); char tenths = (char)(angle%10) + '0'; lcd.print(tenths); lcd.print(' '); lcd.print(' '); // spaces added because the print(int) can be 0 to 3 chars long } } // end of standart display else // out of normal operation (DIAG or PROGRAM mode) { if (LcdRefreshStatus) { lcd.setCursor(5,1); lcd.print(cmdSP); lcd.print(" "); lcd.setCursor(10,1); lcd.print(posnSP); lcd.print(" "); lcd.setCursor(15,1); lcd.print((char)cmdSource); LcdRefreshStatus = false; } else { // display current position (only in cycle when no need to display status) lcd.setCursor(5,0); lcd.print(getPosnEncoder()); // print whole degreess lcd.print(" "); lcd.setCursor(11,0); if (refDone) lcd.print("REF"); // spaces added because the print(int) can be 0 to 3 chars long else lcd.print("XXX"); } } // azimuth command by potentiometer if (digitalRead(35) == LOW) // low means Command button is pressed { if (!cmdButtonOld) // detect falling edge (buttons just pressed) { getAnalogSetpoint(); // performed only once, accept analog command cmdButtonOld = true; } } else { cmdButtonOld = false; } unsigned long tmptime = millis(); if ((tmptime - msec2) < 10000) // take care of overflow!! { ramp(); msec2 += rampStep; if ((tmptime - msec2) < 10000) // increment rampStep is not sufficient!!! msec2 = tmptime + rampStep; // initiate msec2; this should never happen, it indicates too long main loop/error! just protection!!!! } if ((!pwrOK) && (!encSaved) && (motorSpeed == 0)) { SaveEncoder(); encSaved = true; } if (pwrOK) encSaved = false; // to recover from short power drop, to be ready for next failure }