/*! \file servo.c \brief Interrupt-driven RC Servo function library. */ //***************************************************************************** // // File Name : 'servo.c' // Title : Interrupt-driven RC Servo function library // Author : Pascal Stang - Copyright (C) 2002 // Created : 7/31/2002 // Revised : 8/02/2002 // Version : 1.0 // Target MCU : Atmel AVR Series // Editor Tabs : 4 // // This code is distributed under the GNU Public License // which can be found at http://www.gnu.org/licenses/gpl.txt // //***************************************************************************** #ifndef WIN32 #include #endif #include "global.h" #include "servo.h" // Program ROM constants // Global variables // servo channel registers u16 ServoPosTics; u16 ServoPeriodTics; u08 ServoChannel; ServoChannelType ServoChannels[SERVO_NUM_CHANNELS]; // functions //! initializes software PWM system void servoInit(void) { u08 channel; // disble the timer1 output compare A interrupt cbi(TIMSK, OCIE1A); // set the prescaler for timer1 timer1SetPrescaler(TIMER_CLK_DIV256); // attach the software PWM service routine to timer1 output compare A timerAttach(TIMER1OUTCOMPAREA_INT, servoService); // enable and clear channels for(channel=0; channel>8)); // write high byte outb(OCR1AL, (OCValue & 0x00FF)); // write low byte // enable the timer1 output compare A interrupt sbi(TIMSK, OCIE1A); } //! turns off software PWM system void servoOff(void) { // disable the timer1 output compare A interrupt cbi(TIMSK, OCIE1A); // detach the service routine timerDetach(TIMER1OUTCOMPAREA_INT); } //! set port and I/O pin for channel void servoSetChannelIO(u08 channel, u08 port, u08 pin) { ServoChannels[channel].port = port; ServoChannels[channel].pin = (1<<(pin&0x07)); } //! set servo position on channel void servoSetPosition(u08 channel, u08 position) { // input should be between 0 and SERVO_POSITION_MAX u16 pos_scaled; // calculate scaled position pos_scaled = ((u16)position*(SERVO_MAX-SERVO_MIN)/SERVO_POSITION_MAX)+SERVO_MIN; // set position servoSetPositionRaw(channel, pos_scaled); } //! get servo position on channel u08 servoGetPosition(u08 channel) { return (u08)( ((servoGetPositionRaw(channel)-SERVO_MIN)*SERVO_POSITION_MAX)/(SERVO_MAX-SERVO_MIN) ); } //! set servo position on channel (raw unscaled format) void servoSetPositionRaw(u08 channel, u16 position) { // bind to limits position = MAX(position, SERVO_MIN); position = MIN(position, SERVO_MAX); // set position ServoChannels[channel].duty = position; } //! get servo position on channel (raw unscaled format) u16 servoGetPositionRaw(u08 channel) { return ServoChannels[channel].duty; } void servoService(void) { u16 nextTics; if(ServoChannel < SERVO_NUM_CHANNELS) { // turn off current channel outb(_SFR_IO8(ServoChannels[ServoChannel].port), inb(_SFR_IO8(ServoChannels[ServoChannel].port)) & ~(ServoChannels[ServoChannel].pin)); } // next channel ServoChannel++; if(ServoChannel != SERVO_NUM_CHANNELS) { // loop to channel 0 if needed if(ServoChannel > SERVO_NUM_CHANNELS) ServoChannel = 0; // turn on new channel outb(_SFR_IO8(ServoChannels[ServoChannel].port), inb(_SFR_IO8(ServoChannels[ServoChannel].port)) | (ServoChannels[ServoChannel].pin)); // schedule turn off time nextTics = ServoChannels[ServoChannel].duty; } else //(Channel == SERVO_NUM_CHANNELS) { // ***we could save time by precalculating this // schedule end-of-period nextTics = ServoPeriodTics-ServoPosTics; } // schedule next interrupt u16 OCValue; // read in current value of output compare register OCR1A OCValue = inb(OCR1AL); // read low byte of OCR1A OCValue += inb(OCR1AH)<<8; // read high byte of OCR1A // increment OCR1A value by nextTics OCValue += nextTics; // OCR1A+=nextTics; // set future output compare time to this new value outb(OCR1AH, (OCValue>>8)); // write high byte outb(OCR1AL, (OCValue & 0x00FF)); // write low byte // set our new tic position ServoPosTics += nextTics; if(ServoPosTics >= ServoPeriodTics) ServoPosTics = 0; }