PICmicro & dsPIC >> PICmicro & dsPIC

Pages: 1
ryanstewart
stranger


Reged: Apr 16 2009
Posts: 17
RC servo with potentiometer
      #121522 - Mon Jul 27 2009 04:49 AM

Hi guys

I am having an extremely difficult time (3 months struggle) with my project trying to smoothly control an rc servomotor with a potentiometer.

I am using the PIC16F690, there are many ways to do this but i have not got a clue where to start.

The servo either does not follow the position of the pot or jumps about.
Can someone please help?
This is what i have for the code (taken from a website and modified), please feel free to demolish it!! There must be a much simplar non blocking way to do this?

Code:
  
#include "pic.h"
#include "delay.h"
#include "delay.c"


static unsigned int nTimerVal @ 0x7C; // Value Used To Count Down
static unsigned char nTimerValLo @ 0x7C;
static unsigned char nTimerValHi @ 0x7D;
int nOnTime; // Time To Hold

#define INTERVAL16mS 0x1F40 // 16mS Overall Time
#define MINONTIME 0x03E8 // 1mS Interval
#define MIDONTIME 0x05DC // 1.5mS Interval
#define MAXONTIME 0x07D0 // 2ms Interval

__CONFIG(FCMDIS & IESODIS & BORDIS & UNPROTECT & MCLRDIS &
PWRTDIS & WDTDIS & INTIO);


#define POSITION_ADC 0x01
#define SPEED_ADC 0x05
void SwitchADC(unsigned char nADC)
{
int nCount; //Delay Counter

// Set Up The Required ADC
ADCON0 = nADC;

//Wait For a Few Microsceonds
nCount = 0;
while(nCount < 255)nCount++;

// Request A Read Of The ADC
ADCON0 |= 0x02;

// Loop Until The ADC Reports It Is Done
while((ADCON0 & 0x02)!=0){}
}

/*****************************************************************************/
/***** WaitTimer0 - Wait For Set TIme To Expire *****/
/*****************************************************************************/
void WaitTimer0(void)
{
// Remove The Overhead
// nTimerVal -= 150;

while (nTimerVal>10)nTimerVal= nTimerVal-9;
}

/*****************************************************************************/
/***** main.c - Main program entry point *****/
/*****************************************************************************/
void main(void)
{
OSCCON = 0b11111111;
unsigned char nMode; // Mode Indication
unsigned char nReadVal; // Read Value From Mode Buttons
unsigned char nPositionVal; // ADC Reading For Position
unsigned char nSpeedVal; // ADC Reading For The Speed

// Initialise Port A - All Lines To Input, Weak Pull-up Enabled on RA5:2
WPUA = 0x3C;
TRISA = 0x3F;

// Initialise Port C - All Lines To Output
TRISC = 0x00;

ADCON0 = 0b01000001; // Turn on the ADC
// Bit 7 - Left Justified Sample
// Bit 6 - Use VDD
// Bit 4:2 - Channel 0
// Bit 1 - Do not Start
// Bit 0 - Turn on ADC
ADCON1 = 0b01100000; // Selemct the Clock as Fosc/8

// Setup the ADC System - Stage 3 = ANSEL
// Set RA0/AN0 and RA1/AN1 to be Analogue Inputs.
ANSEL = 0x03;
ANSELH = 0x00;


// Run The Main Loop Continuously Now Setup Complete
for(;;)
{
// Read The Position Pot
SwitchADC(POSITION_ADC);
nPositionVal = ADRESH;

// Set The Servo Output High & Wait For The End Time
RC5 = 1;
nTimerVal = nOnTime;
WaitTimer0();

// Set The Servo Output Low & Wait For The End Time
RC5 = 0;
nTimerVal = INTERVAL16mS - nOnTime;
WaitTimer0();

nOnTime = (nPositionVal * 4) + MINONTIME;
if(nOnTime > MAXONTIME) nOnTime = MAXONTIME;

// And Set THe Value For The Timer
// nOnTime = nMode2Current;

}
}




Many thanks

Ryan


Post Extras: Print Post   Remind Me!   Notify Moderator  
Frank Everest

***

Reged: Oct 16 2003
Posts: 1294
Loc: Herts, UK
Re: RC servo with potentiometer [Re: ryanstewart]
      #121542 - Mon Jul 27 2009 05:50 AM

I seem to recall that RC servos need a PWM input. So why don't you use the ECCP module on the 16F690?

Post Extras: Print Post   Remind Me!   Notify Moderator  
swanny
enthusiast


Reged: Sep 13 2008
Posts: 381
Loc: New Zealand
Re: RC servo with potentiometer [Re: ryanstewart]
      #121592 - Mon Jul 27 2009 02:42 PM

My vote would be to use the PWM. Use the datasheet to get it setup for a period of about 25ms. Then vary the duty cycle to set the position.

Here are the rough numbers. You'll need to experiment.

1.0ms is all the way one way
1.5ms is the middle
2.0ms is all the way the other.
20ms is about how long you should wait between pulses.

Alternatively you can use TMR1 and it's compare feature (if the PWM is already used). You'll get an interrupt when each of the periods is complete (the low cycle ~20ms and the high ~1-2ms). This is the method I've used in the past. I've hacked out most of the code that I think you'll need and posted it here (shouldn't be far from compiling). You can control as many servos as you have output pins with this technique.

Code:

#include "htc.h";

#define true 1
#define false 0
#define byte unsigned char
#define bool unsigned char

volatile bool LowDuty;

void main()
{
// --- Timing for driving servo ---
// Use TMR1 for generating the pulse.
// PS 1:8, 125kHz, 8us. 1ms = 125, 1.5ms = 188, 2ms = 250, 20ms = 2500
TMR1L = 0;
TMR1H = 0;

// Set both to compare mode, generate software interrupt on match
CCP2CON = 0x0A;

CCP2IE = 1;


//low for 2500 (close enough to a period of 2500).
CCPR2L = 0xC4;
CCPR2H = 0x09;
LowDuty = true;

T1CKPS0 = 1;
T1CKPS1 = 1;
T1OSCEN = 0;
TMR1CS = 0;
TMR1ON = true;

TRISB = 0x00;
PEIE = 1;
GIE = 1;

while (true)
{}

}

void interrupt ISR()
{
if (CCP2IF)
{
// should do this automatically. that way timing would be more accurate.

// end of period
TMR1ON = false;
TMR1L = 0;
TMR1H = 0;

if (LowDuty) // (what it was doing)
{
CCPR2H = 0;

// Could change the pulse width here and/or drive other servos
CCPR2L = 188;
RB0 = 1;

LowDuty = false;
}
else
{
CCPR2L = 0xC4;
CCPR2H = 0x09;
RB0 = 0;
LowDuty = true;
}

CCP2IF = 0;
TMR1ON = true;
}
}



Edited by swanny (Mon Jul 27 2009 02:44 PM)


Post Extras: Print Post   Remind Me!   Notify Moderator  
petel
stranger


Reged: Nov 10 2008
Posts: 6
Re: RC servo with potentiometer [Re: ryanstewart]
      #121642 - Tue Jul 28 2009 01:55 AM

OK, a few things.
First of all you're initialising the ADC ADCONO register with a value of 0b01000001 then passing the constant POSITION_ADC into the ADC routine, which overwrites ADCON0 with the value 0x01
Next up, make sure your "Wait For a Few Microsceonds" loop actually waits for 12uSec or more.
This one's a bit subtle - check the code produced by the fragment
Code:

// Loop Until The ADC Reports It Is Done
while((ADCON0 & 0x02)!=0){}


as this may get optimised out by the compiler, depending on which the level of optimisation you have enabled.

Here's an example routine that reads the ADC of another micro (16c74A running at 4MHz). It's only an 8bit ADC and it reads the ADC 256 times, but it might give you some ideas
Code:

/* routine to read the ADC, use oversampling */
/* to improve the resolution of the ADC from 8 to 12 bits */

int read_adc(unsigned char chn) {
unsigned int i;
unsigned int res;

ADCON0 = 0x40 | ((chn & 0x7) << 3) | ADON; /* 8Tosc (Tad = 2uS) , set channel, turn ADC on */

/* Here we oversample the same channel 256 times to improve ADC resolution */
/* Note: resolution, not accuracy */
/* At the end, drop the 4 LSBs to return a 12 bit result */

for (res = 0, i = 0; i < 256; i++) {

ADIF = 0;
GODONE = 1; /* trigger conversion */

while(!ADIF) { /* wait for conversion to complete */
asm("nop"); /* to prevent the loop being optimised out */
}

res += ADRES; /* read 8-bit ADC result and accumulate */
/* wait 2Tad (4uSec) before next conversion */
/* this is accounted for in the for() loop time */
}
return(res >> 4); /* return 12-bit result (13.5mSec @ 4MHz) */
}


I'd strongly suggest you become familiar with the MPLAB simulator, if you aren't already, and step through the code while watching what happens.
Pete


Post Extras: Print Post   Remind Me!   Notify Moderator  
ryanstewart
stranger


Reged: Apr 16 2009
Posts: 17
Re: RC servo with potentiometer [Re: swanny]
      #121652 - Tue Jul 28 2009 01:58 AM

Hi Swanny

Thank you so much! This works!

I had to modify it a little for it to work on my PIC but it works! Thanks

The only issue with my PIC16F690 is i would like to multiplex the PWM for the 4 outputs so would i do this in:

Code:
  

void interrupt ISR()
{
if (CCP1IF)
{
// should do this automatically. that way timing would be more accurate.
// end of period
TMR1ON = false;
TMR1L = 0;
TMR1H = 0;
if (LowDuty) // (what it was doing)
{
CCPR1H = 0;
// Could change the pulse width here and/or drive other servos
STRB=1; ///////////////////////
RC4 = 1; ////
CCPR1L = m;//60 - 255 //// This section here for multiplexing
STRB=0; //// STRA,STRB,STRC,STRD
////
STRA=1; ///////////////////////
RC5 = 1;
CCPR1L = z;//60 - 255

LowDuty = false;
}
else
{
CCPR1L = 0xC4;
CCPR1H = 0x09;
STRB=0;
STRA=0;
RC4 = 0;
RC5 = 0;
LowDuty = true;
}
CCP1IF = 0;
TMR1ON = true;
}
}




Loop code:

Code:

while(true)
{
y = read_adc(0); // read channel 0
DelayMs(1); // delay
x = read_adc(2); // read channel 2

z = map(x,12200,28000,60,180); // default 28000
m = map(y,12200,28000,60,180); // simple map function of the ADC to suitable PWM value taken from www.aduino.cc

}



For some reason my ADC gives me rediculously high value?

Seeing as there is only 1 PWM output but can be pulse steered to either of these 4 pins.

Im not quite sure how to multiplex but does it consist of turning on the first pin set the PWM, turn off the pin and turn on the next pin set the PWM etc...

Many thanks again!


Post Extras: Print Post   Remind Me!   Notify Moderator  
swanny
enthusiast


Reged: Sep 13 2008
Posts: 381
Loc: New Zealand
Re: RC servo with potentiometer [Re: ryanstewart]
      #121672 - Tue Jul 28 2009 10:46 AM

Using my second technique you're not actually using the inbuilt pwm generator.

If you want to control more than one output I would so something like this. Note the code is not complete, it requires a lot of the setup code from before.

Code:

volatile byte servos[8];
volatile byte servoIndex;

void main()
{
//just some starting values
for (servoIndex = 0;servoIndex < 8; servoIndex++)
{
servos[servoIndex] = 188;
}
servoIndex = 0;

TRISB = 0x00; // assume all servo pins are port b.
PORTB = 0x00;

// Start first servo pulse.
CCPR1H = 0;
CCPR1L = servos[servoIndex];
PORTB = (0x01 << servoIndex);
}

void interrupt ISR()
{
if (CCP1IF)
{
PORTB = 0x00;
servoIndex++;
if (servoIndex > 8)
{
servoIndex = 0;
}

if (servoIndex < 8) // the 8 servos
{
CCPR1H = 0;
CCPR1L = servos[servoIndex];
PORTB = (0x01 << servoIndex); // turn on appropriate servo pulse
servoIndex++;
}
else if (servoIndex == 8) // the 9th, the long 0 between pulses
{
// could reduce this time accordingly since we have 7 other servos plus this time between signals
CCPR1L = 0xC4;
CCPR1H = 0x09;
}
}



EDIT: fix bug for roll over of servoIndex

Edited by swanny (Tue Jul 28 2009 10:49 AM)


Post Extras: Print Post   Remind Me!   Notify Moderator  
mikerjModerator
Guru
****

Reged: Oct 19 2003
Posts: 1482
Loc: Devon, UK
Re: RC servo with potentiometer [Re: ryanstewart]
      #121912 - Wed Jul 29 2009 07:22 PM

Quote:


The only issue with my PIC16F690 is i would like to multiplex the PWM for the 4 outputs so would i do this in:





This code may be useful for your application.


Post Extras: Print Post   Remind Me!   Notify Moderator  
John Payson

****

Reged: Oct 16 2003
Posts: 1339
Re: RC servo with potentiometer [Re: ryanstewart]
      #121952 - Thu Jul 30 2009 01:16 AM

If you wouldn't mind adding some external hardware, you could probably improve the precision and reliability of the code. There are many approaches that would work with the addition of one or two chips, depending upon your desired tradeoffs of I/O usage and CPU time.

For example, you could use a 74HC595 (shift register with latching outputs) along with the CCP module. Wire the CCP output to the RCLK input of the '595 (a rising edge will latch the outputs of the '595 from the shift register). You will set the CCP to trigger and interrupt and cause a high pulse on its output when a compare event occurs.

The interrupt logic should be something like the following; note that the frequency of PWM pulses will depend upon the total value in pwm_values[], so it may be desirable to have one or more 'dummy channels' whose width is set to pad things out to a constant value.
Code:

void CCP_interrupt(void)
{
static unsigned short last_ccp;
unsigned char which_pwm;

/* Clear CCP interrupt flags, etc. and force CCP output
low. Then... */

CCPR1L = last_ccp & 255;
CCPR1H = last_ccp >> 8;

DATA_OUT = 0;
if (which_pwm >= NUM_CHANNELS)
{
DATA_OUT = 1; /* Data feeding '595 */
which_pwm = 0;
}
last_ccp += pwm_values[which_pwm++];
CLOCK_OUT = 1; /* Clock last bit into '595 */
CLOCK_OUT = 0;
}


This approach will allow any number of servos to be controlled by the PIC using two I/O general-purpose I/O pins plus the CCP output. If more than 8 servos are required, simply cascade the 595's in the usual fashion.

Adding a resistor and capacitor would allow the I/O requirement to be reduced to one pin plus the CCP output (wire the SCLK directly to the CCP output, and wire the RCLK output to the CCP output through a 1us RC delay).

Note that provided that the interrupt handler services each PWM pulse before the next one is due to arrive, the pulse durations will be unaffected by interrupt latency. Be sure to disable interrupts while writing pwm_values[], and never write a pwm_values[] value which is would be shorter than the worst-case time to service an interrupt.


Post Extras: Print Post   Remind Me!   Notify Moderator  
ryanstewart
stranger


Reged: Apr 16 2009
Posts: 17
Re: RC servo with potentiometer [Re: mikerj]
      #122212 - Sat Aug 01 2009 02:51 AM

Many thanks for this code!! It works correctly thank you so much! Also thanks to everyone else for their efforts in getting me where i want to be with my project!

Thanks again!

Ryan


Post Extras: Print Post   Remind Me!   Notify Moderator  
Pages: 1



Extra information
0 registered and 38 anonymous users are browsing this forum.

Moderator:  mikerj, jtemples, jeff, Dan Henry, Andrew L 

Print Topic

Forum Permissions
      You cannot start new topics
      You cannot reply to topics
      HTML is enabled
      UBBCode is enabled

Rating:
Topic views: 6443

Rate this topic

Jump to

Contact Us | Privacy statement HI-TECH Software

Powered by UBB.threads™ 6.5.5