Linear actuator controller via an R/C Servo system

Go To Last Post
5 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I just finished polishing off this code for a project I've been working on for a while now (on and off).

It's application is to use a motor controller like this: http://www.dimensionengineering.... to power a linear actuator /w feedback resistor like this: http://www.firgelliauto.com/prod...

This is basically a piece of glue logic for a pretty specific application, but something vital if you don't have it.

/////////////////////////////////////////////
////  Position to Rate Converter ////////////
////  By: Greg Clements          ////////////
////  For Weber State University ////////////
////  For the H.A.T.V.           ////////////
/////////////////////////////////////////////
//  Physical circuit:
//  Current micro controller used: ATtiny13a
//  Connect the signal from the R/C reciever to PWMSIGNAL (PCINT4/PB4) ( pin number 3 )
//  Connect the output signal to motor controller to PWMOut (PB3) ( pin number 2 )
//  Connect the feedback position pot to (ADC1) ( pin number 7)
//  Connect ground and VCC (pin number 4 and 8 respectively)
//  Other pins may be used for debugging (see below)
//  PLEASE REFER TO THE DATASHEET WHEN CHANGING ANY OF THESE PARAMATERS!!!

#include 
#include 

//pulse length constants
//to calculate values, use [seconds * (9.6M/256)] (or clock speed over timer prescaler).
//note, the timer must overflow between pulses to properly update the compare registers, if a different clock source is used be sure this condition is met

#define PulseMax 81  // The longest valid pulse input (~2ms).  Any longer pulse will be shortened to this.
#define PulseMid 60  //
#define PulseMin 40  // The Shortest valid pulse input (~1ms).  Any shorter will be set to this.
#define PulseDiff (PulseMax - PulseMin) //  The difference between PulseMax and PulseMin
#define PulseFact 6  //scaling factor, should be aprox 255 / pulsediff rounded down
#define NullFact 12  //ammount of space (range of 0 to 255) to ignore changes

#define PulseoutMax 81  //Longest output pulse (~2ms)
#define PulseoutMid 60	//Zero movement (or angle) pulse (~1.5ms)
#define PulseoutMin 40	//Shortest output pulse (~1ms)

//pin definitions
#define PWMOut PB3
#define PWMOutPort PORTB
#define PWMSignal PB4
#define PWMPort PINB
//not defined here:
//Location Input for ADC
// --Use ADMUX register in initlize()
//Pin Change Interrupt for PWM input
// --Use PCMSK register in initlize()

///////////////////////////////////////
/////////Global Varriables/////////////
///////////////////////////////////////

volatile unsigned char PWMRaw = 0;  //raw input from timer

volatile unsigned char PWL = 0;  //pulse input scaled to compare to location (0 to 255)

volatile unsigned char Location = 0;  //location of servo's position pot

volatile unsigned char PulseoutA = PulseoutMid;  //value to set timer compare register to, sets length of output pulse.

volatile unsigned char DataFlag = 0;  //semephore flags for shared data

#define fPulsein 0x01		//semephore flags for shared data
#define fPulseoutA 0x02
#define fPulseoutB 0x04
#define fLocation 0x08



///////////////////////////////////////
/////INTERRUPT HANDLERS////////////////
///////////////////////////////////////

ISR(PCINT0_vect)
{

	if (PWMPort & (1<<PWMSignal)) 
	{  //rising edge
		TCNT0 = 0; 				//reset timer count
		PWMOutPort |= (1<<PWMOut);  	//set ouput signal to high
								//PWMOut must be the same pin as OCR0A
								//PWMOut will be se low by ISR(TIM0_COMPA_vect)
		TIFR0 = (1<<OCF0A);  //clear COMP A interrupt to prevent glitches
	}
	else if (!(DataFlag & fPulsein))  //if only execute if old data has been used
	{//falling edge
		PWMRaw = TCNT0;		//store pulse length
		DataFlag |= fPulsein;  //set pulse length flag
	}
		

}

ISR(TIM0_OVF_vect)  //triggered when timer overflows from 255 to 0, this is needed to update OCR0A
{
	OCR0A = PulseoutA;  //Set next pulse width
	TIFR0 = (1<<OCF0A);  //clear COMP A interrupt to prevent glitches
}	


ISR(TIM0_COMPA_vect)  //trigger when timer = PulseoutA
{
	PWMOutPort &= ~(1<<PWMOut);  //set output pulse low
}


///////////////////////////////////////////////////TASK CODE/////////////////////////////////////////////////////////////



void initlize()
{
//setup Clock
//+ATOMIC!!  NO INTERRUPTS ALLOWED! DO NOT SEPERATE THE FOLLOWING COMMANDS!
	
	cli();  //Disable Interrupts
	//Enable Clock Change
	CLKPR = (1<<CLKPCE);

	//Set Clock Precaler to 1  (This must be less than 4 clock cycles after seting CLKPCE)
	CLKPR = (0<<CLKPS3) | (0<<CLKPS2) | (0<<CLKPS1) | (0<<CLKPS0) | (0<<CLKPCE);

//-ATOMIC  END OF ATOMIC SECTION

//setup Data Direction Register for port B (DDRB)

	//Set DDB4 OUT| DDB3 OUT  | DDB2 IN   | DDB1 IN   | DDB0 OUT
	DDRB = (0<<DDB4) | (1<<DDB3) | (0<<DDB2) | (1<<DDB1) | (1<<DDB0);
	
//setup ADC

	//set ADC input to ADC1, set Right shift, set Refrence to VCC
	ADMUX = (0 << REFS0) | (1<<ADLAR) | (0<<MUX1) | (1<<MUX0);   /////////////////////////ADLAD is cleard (right shifting)  *********************
	
	//Set free running mode
	ADCSRB = (0<<ADTS2) | (0<<ADTS1) | (0<<ADTS0);
	
	//disable data pin for ADC use (ADC1)
	DIDR0 = (1<<ADC1D) | (0<<ADC0D) | (0<<ADC2D) | (0<<ADC3D);
	
	//enable and configure ADC.  Set Prescaler to 2, Clear AD Interupt Enable, Set Auto Trigger
	//Start Converting
	ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<< ADPS1) | (0<<ADPS0) | (0<<ADIE) | (1<<ADATE) |(1<<ADSC);

//setup Timer

	//Setup timer control register (TCCR0A)
	//Set Normal port operation (Compare Match [clear on OC0A], No Waveform Generation Mode)
	TCCR0A = (0<<COM0A1) | (0<<COM0A0) | (0<<COM0B1) | (0<<COM0B0) | (0<<WGM01) | (0<<WGM00);

	//Setup Timer Control Register (TCCR0B)
	//Set Normal port Operation (No Waveform Generation, No Force Compare), Set Clock Source and scaler to /256
	TCCR0B = (0<<WGM02) | (0<<FOC0A) | (0<<FOC0B) | (1<<CS02) | (0<<CS01) | (0<<CS00);
		
	//Setup Timer Interrupt mask register
	//Timer Overvlow Enabled
	TIMSK0 = (0<<OCIE0B) | (1<<OCIE0A) | (1<<TOIE0);

	//Set Output Compare Register to middle untill tasks change it.
	OCR0A = PulseoutMid;

//Setup External interrupt

	//Not used
	//MCUCR &= ~(1<<ISC01) | (1<<ISC00);

	//Set pin change mask to PCINT4 only
	PCMSK = (0<<PCINT5) | (1<<PCINT4) | (0<<PCINT3) | (0<<PCINT2) | (0<<PCINT1) | (0<<PCINT0);
	
	//enable external interrupts, disable pin change
	GIMSK = (0<<INT0) | (1<<PCIE);
	

	sei();  //Enable Interrupts
}


void GetPulse()
{
	if (DataFlag & fPulsein)  //only execute if new data is available
	{
		unsigned char temp1 = 0;	

		temp1 = PWMRaw;
		DataFlag &= ~fPulsein;  //set data flag to allow interrupt to modify PWMRaw

		if (temp1 < PulseMin) temp1 = PulseMin;  //limit maximum and minimum to valid paramaters
		if (temp1 > PulseMax) temp1 = PulseMax;

		temp1 -= PulseMin;  //subtract 1ms (now ~0ms < temp1 < ~1ms)

		PWL = temp1 * PulseFact;  //scale temp1 to be from 0 to 256
	}

}


void GetLocation()
{
	if (ADCSRA & (1<<ADIF)) // If ACD has completed. . .
	{
		
	//Location = ((ADCL & 0xFC) >> 2) + (ADCH << 6);	//store the decoded value
	Location = ADCH;
	}
}


void SetOutput()
{
	if (PWL > (Location + NullFact)) PulseoutA = PulseoutMax;  //PWL is much greater than Location

	else if (PWL < (Location - NullFact)) PulseoutA = PulseoutMin;  //PWL is much smaller than Location

	else PulseoutA = PulseoutMid;  //PWL is not far enough away to start moving
}


int main ()
{
	initlize();  //setup everything

while (1)   //executive loop
{
	GetPulse();
	GetLocation();
	SetOutput();
}
}

//  The following function is not currently called in the program, and is for debugging purposes only
#define shift_sync PB0  //use this signal to trigger an o-scope
#define shift_bit PB1  //this signal is the data.
#define shift_port PORTB  
void shiftout(unsigned char outbyte)
{
	shift_port |= (1<<shift_sync);
	for (unsigned char a = 0; a < 8; a++)
	{
		if (outbyte & (1<<a)) shift_port |= (1<<shift_bit);
		else shift_port &= ~(1<<shift_bit);
	}
}

I'm pretty sure all of the comments are accurate, but I have gone through MANY revisions, so double check if you decided to change something.
Also, I used WINAVR as the compiler, so you may need to change the ISR decelerations for your application.

"A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools."
-- Douglas Adams

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
volatile unsigned char DataFlag = 0;  //semephore flags for shared data 

#define fPulsein 0x01      //semephore flags for shared data 
#define fPulseoutA 0x02 
#define fPulseoutB 0x04 
#define fLocation 0x08 

This setup can cause atomicity problems although in the code shown only one bit is actually used.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you want to fix the spelling in the comments, change semaphore and variable

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Heh, avr studio does not have a spell checker.

I never do use those other flags, only the one was needed, but I thought I might use the for something.

I had originally written this in ASM, but it never ran well, so I did a rewrite in C and it seems to work very well now.

"A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools."
-- Douglas Adams

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

One odd glitch I found seemed to happen in ISR(PCINT0_vect) when I set the counter to zero. I noticed on my scope that the output pulse occasionally was extremely short (probably only a few clocks), this indicated that the output compare ISR(TIM0_COMPA_vect) was executing early.

The only cause that I could think of is that during the moment that tcnt0 is being set to zero, it happens to equal OCR0A and sets the interrupt flag. So I added the line that clears that flag before exiting the ISR and enabling global interrupts again, thus eliminating the glitches.

Anyone know of a better way to handle this problem? I know of no other way to reset TCNTx than simply loading the desired value in it.

"A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools."
-- Douglas Adams