// // Name: servo.c // // // Revision History: // // M. Gray 15 Jan 2002 V1.00 Initial release. // // M. Gray 02 Dec 2002 V1.01 Incremental release for web. // // // COPYRIGHT (c) 2002 Michael Gray, KD7LMO // // 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. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // #include "16f876.h" #fuses XT,NOWDT,NOPROTECT,NOBROWNOUT,NOLVP // These compiler directives set the clock and baud rate information. #use delay(clock=3686400) #use rs232(baud=38400, xmit=PIN_C6, rcv=PIN_C7) #use fast_io(B) #use fast_io(C) #byte PORTB=6 // We define types that are used for all variables. These are // declared because compilers have different sizes for int and long. typedef signed int int8; typedef int uint8; typedef signed long int16; typedef long uint16; // PROGRAMMING NOTE: Since we don't have embedded C++ for this processor, // we try to use C in a manner that emulates classes. Each function // and variable is prefaced by the name of the class. The classes // have a function that acts as a constructor and sometimes a distructor. // Even though variables are declared as globals, they are only accessed // through the appropriate class function. The order of the classes and // variables is not important in the application because we only access // the class through a variable. // Public methods for each class. void commandConstructor(); uint8 commandRead(); void servoCheckState(); void servoConstructor(); void servoTimerInterrupt(); void servoSetCommand(uint8 *buffer); /** * Class to handle the timer for servo control. */ // Number of seconds between each servo PWM output. Since we have 4 channels, // the update rate is 20mS or 50Hz for all the channels. // value = 5mS * (clockHz / 4) => 0.005 * (3686400/4) = 4608 #define SERVO_PRI 4608 // Minimum pulse width. value = 950uS * (clockHz / 4) => 0.000950 * (3686400/4) = 875 #define SERVO_MIN_PW 875 // Default servo position. value = 1.5mS * (clockHz / 4) => 0.001500 * (3686400/4) = 1382 #define SERVO_DEFAULT_POSITION 1382 // Upper and lower limits for a select servo PW. #define SERVO_MIN_SELECT_PW 875 #define SERVO_MAX_SELECT_PW 1382 // State machine constants. #define SERVO_RISE_STATE 0 #define SERVO_FALL_STATE 1 // Defines the bitmap used for the servo output pins. #define SERVO_OUT1 0x08 #define SERVO_OUT2 0x10 #define SERVO_OUT3 0x40 #define SERVO_OUT4 0x20 // Defines pin number for each servo switch. #define SERVO_SW1 PIN_B7 #define SERVO_SW2 PIN_B2 #define SERVO_SW3 PIN_C4 #define SERVO_SW4 PIN_C5 // Define the pin number for the heart beat LED. #define SERVO_LED PIN_C0 // Define the pin number for the R/C input selector. #define SERVO_SELECT PIN_C2 // Define the pin numbers for the aux outputs. #define SERVO_AUX1 PIN_C2 #define SERVO_AUX2 PIN_C3 // Define the status LED on/off time constants. #define SERVO_LED_ONTIME_NORM 20 #define SERVO_LED_OFFTIME_NORM 180 #define SERVO_LED_ONTIME_FAULT 30 #define SERVO_LED_OFFTIME_FAULT 30 // The number of 5mS periods without a command packet before a fault is declared. #define SERVO_COMM_TIMEOUT_COUNT 16 // Servo LED state machine constants. #define SERVO_LED_NORM_HIGH 0 #define SERVO_LED_NORM_LOW 1 #define SERVO_LED_FAULT_HIGH 2 #define SERVO_LED_FAULT_LOW 3 // State machine. uint8 servoState; // Index 0:3 of the servo we are currently processing. uint8 servoIndex; // Track the last command so we only change control switches if the state changes. uint8 servoLastCommand; // Current Port B state. uint8 servoPortB; // An array of bit masks used to turn PWM states on and off. uint8 servoMask[4]; // Port B tristate buffer. uint8 servoTriState; // Stores the time of the next interrupt. uint16 servoTimerCompare; // Current servo position. uint16 servoPosition[4]; // Commanded servo position that is updated on the PWM output. uint16 newServoPosition[4]; // Servo LED state, normal or fault condition. uint8 servoLEDState; // Count down timer used for the heart beat LED. uint8 servoLEDTick; // Timer used to determine if the comm link has failed. uint8 servoCommTimeOut; // Last state of select channel override. uint8 servoLastState; // Time of select channel low to high transition. uint16 servoLastRiseTime; // Flag used to indicate the R/C radio is allowing remote servo operation. uint8 servoSelectFlag; void servoCheckState() { int16 pulseWidth; // We'll just exit if the input state hasn't changed. if (servoLastState == input(SERVO_SELECT)) return; // Record the time of the low to high transition. if (servoLastState == 0) { servoLastState = 1; servoLastRiseTime = CCP_1; } else { servoLastState = 0; pulseWidth = CCP_1 - servoLastRiseTime; if (pulseWidth > SERVO_MIN_SELECT_PW && pulseWidth < SERVO_MAX_SELECT_PW) servoSelectFlag = 0x80; else servoSelectFlag = 0x00; } } void servoConstructor() { uint8 i; // Set our initial conditions. servoState = SERVO_RISE_STATE; servoIndex = 0; servoLastCommand = 0x0f; servoLastState = 0; servoLastRiseTime = 0; servoSelectFlag = 0; servoLEDTick = SERVO_LED_ONTIME_NORM; servoCommTimeOut = SERVO_COMM_TIMEOUT_COUNT; servoLEDState = SERVO_LED_NORM_HIGH; servoPortB = 0x84; servoTimerCompare = 4 * SERVO_PRI; servoTriState = SERVO_OUT1 | SERVO_OUT2 | SERVO_OUT3 | SERVO_OUT4 | 0x03; // Default servo position (mid-range) in uS. for (i = 0; i < 4; ++i) { servoPosition[i] = SERVO_DEFAULT_POSITION; newServoPosition[i] = SERVO_DEFAULT_POSITION; } // These bits are AND/ORed with the output register to determine what output is // enabled or disabled. servoMask[0] = SERVO_OUT1; servoMask[1] = SERVO_OUT2; servoMask[2] = SERVO_OUT3; servoMask[3] = SERVO_OUT4; // Enable the interrupts and clock the timer register. setup_ccp1( CCP_COMPARE_INT ); setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 ); // We will wait 20mS before the servo interrupts start. CCP_1 = servoTimerCompare; set_timer1(0); // Configure the output ports for the servo switch, control, and heart beat LED. set_tris_b (servoTriState); set_tris_c (0x82); // Set the servo switch to pass through mode, turn off the heart beat LED. output_high (SERVO_SW1); output_high (SERVO_SW2); output_high (SERVO_SW3); output_high (SERVO_SW4); output_high (SERVO_LED); output_low (SERVO_AUX1); output_low (SERVO_AUX2); } /** * This function is called when the timer 1 compare register matches. */ #INT_CCP1 void servoTimerInterrupt() { uint8 i; // The first thing we do is update the servo output pins. PORTB = servoPortB; // We toggle the PWM output on or off and setup for the next interrupt. // This method works because the PRI is 20mS and our PW is 1 to 2 mS. // If had a very low or high duty cycle, we wouldn't have time to toggle states. switch (servoState) { case SERVO_RISE_STATE: // Send the last servo command as a sync pulse to the main controller. if (servoIndex == 0) puts (servoLastCommand | servoSelectFlag); // In the next interrupt we'll turn off the current servo. servoPortB &= ~servoMask[servoIndex]; servoTimerCompare += servoPosition[servoIndex]; servoState = SERVO_FALL_STATE; // If the comm times out, then set default servo positions. if (servoCommTimeOut != 0) if (--servoCommTimeOut == 0) for (i = 0; i < 4; ++i) servoPosition[i] = SERVO_DEFAULT_POSITION; // We'll turn off the LED if (servoLEDTick == 0) { switch (servoLEDState) { case SERVO_LED_NORM_HIGH: output_low (SERVO_LED); if (servoCommTimeOut != 0) { servoLEDState = SERVO_LED_NORM_LOW; servoLEDTick = SERVO_LED_OFFTIME_NORM; } else { servoLEDState = SERVO_LED_FAULT_LOW; servoLEDTick = SERVO_LED_OFFTIME_FAULT; } break; case SERVO_LED_NORM_LOW: output_high (SERVO_LED); if (servoCommTimeOut != 0) { servoLEDState = SERVO_LED_NORM_HIGH; servoLEDTick = SERVO_LED_ONTIME_NORM; } else { servoLEDState = SERVO_LED_FAULT_HIGH; servoLEDTick = SERVO_LED_ONTIME_FAULT; } break; case SERVO_LED_FAULT_HIGH: output_low (SERVO_LED); if (servoCommTimeOut == 0) { servoLEDState = SERVO_LED_FAULT_LOW; servoLEDTick = SERVO_LED_OFFTIME_FAULT; } else { servoLEDState = SERVO_LED_NORM_LOW; servoLEDTick = SERVO_LED_OFFTIME_NORM; } break; case SERVO_LED_FAULT_LOW: output_high (SERVO_LED); if (servoCommTimeOut == 0) { servoLEDState = SERVO_LED_FAULT_HIGH; servoLEDTick = SERVO_LED_ONTIME_FAULT; } else { servoLEDState = SERVO_LED_NORM_HIGH; servoLEDTick = SERVO_LED_ONTIME_NORM; } break; } // END switch } else --servoLEDTick; break; case SERVO_FALL_STATE: // The number of uS until we turn on the next servo. servoTimerCompare += (SERVO_PRI - servoPosition[servoIndex]); // Update the servo posotion. servoPosition[servoIndex] = newServoPosition[servoIndex]; // Select the next servo in the list. if (++servoIndex == 4) servoIndex = 0; // In the next interrupt, we'll turn on the next servo. servoPortB |= servoMask[servoIndex]; servoState = SERVO_RISE_STATE; break; } // END switch // Update the timer compare register for the next interrupt. CCP_1 = servoTimerCompare; } /** * Process the command stream. Byte 0 contains the control switch * status. Bytes 1-4 contain the control position for servos * 1 through 4. */ void servoSetCommand(uint8 *buffer) { uint8 i; // We only need to update the servo enable/disable if they change. if (buffer[0] != servoLastCommand) { servoLastCommand = buffer[0]; // Bits 0:3 select servo status 1 through 4. if ((servoLastCommand & 0x01) == 0x01) { servoPortB |= 0x80; servoTriState |= SERVO_OUT1; } else { servoPortB &= ~0x80; servoTriState &= ~SERVO_OUT1; } if ((servoLastCommand & 0x02) == 0x02) { servoPortB |= 0x04; servoTriState |= SERVO_OUT2; } else { servoPortB &= ~0x04; servoTriState &= ~SERVO_OUT2; } if ((servoLastCommand & 0x04) == 0x04) { output_high (SERVO_SW3); servoTriState |= SERVO_OUT3; } else { output_low (SERVO_SW3); servoTriState &= ~SERVO_OUT3; } if ((servoLastCommand & 0x08) == 0x08) { output_high (SERVO_SW4); servoTriState |= SERVO_OUT4; } else { output_low (SERVO_SW4); servoTriState &= ~SERVO_OUT4; } // Bits 4:5 select the aux outputs. if ((servoLastCommand & 0x10) == 0x10) output_high (SERVO_AUX1); else output_low (SERVO_AUX1); if ((servoLastCommand & 0x20) == 0x20) output_high (SERVO_AUX2); else output_low (SERVO_AUX2); // Update the tri-state buffer. set_tris_b (servoTriState); } // We'll save the new servo positions for application later. for (i = 0; i < 4; ++i) newServoPosition[i] = SERVO_MIN_PW + (((uint16) buffer[i + 1]) << 2); servoCommTimeOut = SERVO_COMM_TIMEOUT_COUNT; } /** * Class of commands to read and process the serial port commands. */ // Value that indicates the start of a new command stream. #define COMMAND_HEADER 0xfe // The number of bytes in a command stream. #define COMMAND_LENGTH 5 // State machine constants. #define COMMAND_WAIT_HEADER 0 #define COMMAND_DATA 1 #define COMMAND_CHECKSUM 2 // Private variables. uint8 commandState, commandChecksum, commandIndex, commandBuffer[5]; /** * Initialize the state machine. */ void commandConstructor() { commandState = COMMAND_WAIT_HEADER; } /** * Read the serial port and process the command stream. The stream includes the * header <b>COMMAND_HEADER 0xfe</b>, 5 command bytes, and a single byte checksum. */ void commandRead() { uint8 value; // Read the serial port and return if it is empty. if (!kbhit()) return; value = getc(); switch (commandState) { // When we find the header, reset the buffer pointer and checksum. case COMMAND_WAIT_HEADER: if (value == COMMAND_HEADER) { commandState = COMMAND_DATA; commandChecksum = 0x00; commandIndex = 0; } // END if break; // Accumulate the data bytes. case COMMAND_DATA: commandBuffer[commandIndex++] = value; commandChecksum ^= value; if (commandIndex == COMMAND_LENGTH) commandState = COMMAND_CHECKSUM; break; // Verify the checksum and if it is good, update the servo information. case COMMAND_CHECKSUM: if (value == commandChecksum) servoSetCommand (commandBuffer); commandState = COMMAND_WAIT_HEADER; break; } // END switch } void main() { // Initialize all our classes. servoConstructor(); commandConstructor(); // Setup our interrupts. enable_interrupts(INT_CCP1); enable_interrupts(GLOBAL); while (1) { commandRead(); servoCheckState(); } // END while }