Jump to content
JF1980

Trying to control servos with PIC

Recommended Posts

I'm trying to learn how to drive servos using a PIC 16F877A. My project is trying to drive up to 8 servos on PORTB using two CCP modules; I have the servo signal wire connected to the PIC output pins with the servos getting 5v from a standard 4.8v RX pack. I have connected the -ve battery terminal to my proto board so that the servos share a common ground.

My code is written in MikroC:

/* 

 * Project name: 

	 MoveServo1  

 * Description: 

	 Move Servo with TMR0/1 Interrupts. 

 * Test configuration: 

	 MCU:			 PIC16F877A 

	 Dev.Board:	   EasyPic4 

	 Oscillator:	  HS, 08.000 MHz 

	 Ext. Modules:	- 

	 SW:			  mikroC v7.0 

 * NOTES: 


Drive up to 8 servos on PortB using CCP1, CCP2 and TMR1 


Configure TMR1 for 1us ticks 

CCP1 is set up for special event on match with TMR1 

CCP1 is loaded with servo cycle length = 20000us / number of servos; 2500us for 8 servos.  It triggers an interrupt and resets TMR1. 

CCP1 triggers every servo cycle resetting TMR1, moves program to next servo via single left bitshift, turns turns that servo on. 


CCP2 is set up for a normal event which does not reset the timer. 

CCP2 value is loaded from the servo position array when the servo is switched on via CCP1 interrupt service. 

CCP2 triggers when the servo-on cycle has been completed. 

CCP2 interrupt service switches off servo output (all of PORTB) and clears the CCPIR2.0 interrupt flag.   


*/ 



// Define variables 


#define ServoOnIntEnable PIE1.F2 

#define ServoOnFlag PIR1.F2 

#define ServoOffIntEnable PIE2.F0 

#define ServoOffFlag PIR2.F0 

//#define ServoPeriod CCPR1L 

//#define ServoDuty CCPR2L 


unsigned int volatile ServoPeriod absolute 0x0015;		 // CCPR1L 

unsigned int volatile ServoDuty absolute 0x001B;		   // CCPR2L 

unsigned int Servo[8];									 // servo position array 

unsigned int xx;										  // var for test loop 

unsigned short Servo_Output;							   // Shadow register for Output 

unsigned short sn;										// Servo number, to ref Servo array 


// Define functions 


void init(void); 


// Main 


void main(void) { 


	init(); 


	while (1) { 

	   for (xx = 350; xx <= 2400; xx++){	 // cycle servos 

		   Servo[0] = 500; 

		   Servo[1] = xx; 

		   Servo[2] = xx; 

		   Servo[3] = xx; 

		   Servo[4] = xx; 

		   Servo[5] = xx; 

		   Servo[6] = xx; 

		   Servo[7] = 2400; 

		   Delay_us(5500); 

	   } 

	} 

  } 



// Setup CPU 


void init(void) { 


	PORTB = 0;					// servo port is portB 

	TRISB = 0;					// set portB as output 

	T1CON = 0b00010000;			 // prescaler = 2 = 1us ticks; timer1 off 

	TMR1L = 0;					 // Timer1L starts at 0 

	TMR1H = 0;					 // Timer1H starts at 0 

	CCP1CON = 0b00001011;		   // CCP1 triggers special compare event on Timer1 match 

	CCP2CON = 0b00001010;		   // CCP2 normal compare event on Timer1 match 

	ServoPeriod = 2500;	   // special event interrupt every 2500us; jumps to ServoON ISR 

	Servo_Output = 0b00000001;		 // servo outputs start at PORTB.F0, then shift and rotate 

	sn = 0;						// start with servo(0) 

	INTCON = 0b11000000;			// enable GIE and PEIE 

	ServoOnFlag = 0 ; 

	ServoOffFlag = 0; 

	ServoOnIntEnable = 1; 

	ServoOffIntEnable = 1; 

	T1CON.F0 = 1;				   // start Timer1 

	} 



// Interrupt 


void interrupt(void) { 


	if (ServoOnFlag == 1) {			  // CCP1 Special Compare Event Interrupt 

		PortB = Servo_Output;			 // Turn On Next Servo 

		ServoDuty = Servo[sn];		  // Active Servo Value loaded in CCPR2 

		Servo_Output = Servo_Output << 1;   // Prepare Servo_Output for next cycle with bitshift 

		sn++;  

		sn = sn & 7; 

		ServoOnFlag = 0; 

		} 

	else {						// CCP2 Normal Compare Event Interrupt 

		PORTB = 0;				// Turn Off Active Servo 

		ServoOffFlag = 0; 

		} 

}

When I power the circuit on the servos jump which I would put down to static (the same as they do when connecting RX power with the transmitter off), then nothing. They can be moved by hand so are not holding a static position, it's as if they aren't seeing the PWM signal.

If I run through the code with a software debugger then everything looks like it works fine. I have to simulate interrupts by changing PIR1 to 4, following it through the interrupt routine, then setting PIR2 to 1 and repeating the process. I can watch all of the variables change and the program seems to be working as it should.

PORTC has a bank of LED's so I've mirrored various registers to see if the PIC is actually doing anything. If I mirror CCPR2 or xx then I can see it is. Mirroring PORTB doesn't do anything but I'm guessing that the output is on for too short a time for the LEDs to emit any light.

Any ideas of what could be wrong? It's very frustrating!

Share this post


Link to post
Share on other sites

Hmm...

Analyzing all the code would take more time than what I have at hand right now, but I'll try obvious things first.

Are you sure your interrupt routine is correctly defined as such, and is actually being executed?

I've personally used mikroC for dsPIC, not for PICs so that will be a bit of guess work. The dsPICs have one interrupt vector for each interrupt source, and for a function to be actually used as handler for a certain interrupt source it must be located at the address corresponding to the vector for that particular source. Example:

// System timer interrupt, 100ms

void T5_int(void) org 0x40

{

(...)

}

where 0x40 is the interrupt vector for the Timer5 interrupt, i.e. when that interrupt is triggered the dsPIC will automatically start executing code located at address 0x40 in ROM. That address is different for each interrupt source, so for each source you want to use you will put a separate function at the corresponding address.

PICs have a single interrupt vector, which means that when any interrupt is triggered it will jump to the same address, which is 0x04.

So logically if mikroE have some consistency between their compilers, I think you would have to:

- Locate your ISR at address 0x04 ( void interrupt(void) org 0x04 {} )

- If your handling of interrupts from CCP1 and CCP2 needs to be different, as a first thing in the ISR identify what the source is, and execute the code appropriate for each of them.

But you should check the mikroC doc / view one of the samples that uses interrupts to see if my guess was right ;)

Do you have access to an oscilloscope? It can be a good help when working with signals to check you really have what you expect.

Other than this, you should be able to debug on the target with the mikroE board, that is have the actual program running on the PIC, and you doing steps, watching vars etc from the IDE. Basically, you should be able to set a breakpoint inside your ISR, run the program, and wait until it halts on the breakpint - if it never does, it means that code is never being executed.

Other good tools are the LCDs and LEDs on those boards, I like using them to display various info about program execution. When in doubt, it's easy to throw things like "Lcd_Write_Text("In ISR"); delay_ms(1000);" or equivalent at various places in your program and looking at the display to check that all the code paths you want the program to run through are actually executed correctly.

Oh another thing from my quick look, you should be able to do it easily using only a single CCP, or even just a timer.

Edited by Kilrah

Share this post


Link to post
Share on other sites

Quick peeks at other folk's code is always a challenge for me. But it looks to me like servo_output is not reloaded at each new PPM frame. From what I can tell it is only good for one round of servo pulses, then zip.

I'm not a MikroC user so I don't know how it handles it, but in general, be aware that timing functions like delay_us() will be affected by interrupts. In your case it will probably not be noticed, but it sometimes matters in some applications. The issue is that some compilers disable the interrupts during the timing function (which can create its own set of problems), and other compilers don't (which adds additional overhead time to the timer's delay causing it to run "slower"). Just be prepared for such things.

If you don't have an o-scope, then use an LED that you flash within the interrupt routines (you'll have to slow down the flash rate using some additional code). This will allow you to see if interrupts are being serviced and give heartbeat indication to basic MCU operation. The LED can be used in a wide variety of debugging trickery as your debugging advances into other routines.

Share this post


Link to post
Share on other sites

Thanks for the fast reply. I did think I can do this with one CCP, once this is working I'll modify it to use one CCP and run on a 628A or something similar.

You're right in saying that once an interrupt is triggered, a conditional statement selects the desired course of action depending on the trigger.

I've checked it with the debugger and it seems to work fine in theory, I can check on the various registers and see the expected numbers. Good call on using the LCD, it won't be much more bother than using the LED's for diagnostics and will give me more specific information. I did place commands to turn an LED on at various stages so can confirm that it's running through both sets of interrupt routine. No oscilloscope I'm afraid. My multi-meter has a Hz setting with 2000 or 20000 range options. Don't think that'll be much use.

Share this post


Link to post
Share on other sites

As mentioned, it seems to me that the servo_output var is not reloaded at each new PPM frame. This means the bit rotation performed in your code will just be moving zeros around after the first PPM frame is done. Your debugging would need to step through several interrupts before you see this issue.

Share this post


Link to post
Share on other sites

You're right Mr RC, after one rotation Servo_Output is 0b00000000 because I forgot to reset it. Threw a temporary 'if' statement in there and now the code works. :D

The original code I used as a reference was written in Basic and targeted at the 18F chips. It uses an ASM bitshift instruction which rotates the bits in the register. I couldn't find a similar instruction for the 16F877A and forgot to reset the register after a complete cycle.

Thanks for looking over it, this has been driving me crazy for the last 24 hours!

Share this post


Link to post
Share on other sites

Glad to hear you got it working. Those sneaky little ones and zeros really know how to drive us nuts.

Share this post


Link to post
Share on other sites

Very much so!

Now I've got the basics covered I'm going to become a little more ambitious. I would like to build a servo control board so that the timers/CCP in the main MCU will be free for other things. I have some 16F690 samples which would seem to be ideal with one CCP (will modify the code to use one CCP module) and USART/SPI/I2C capabilities. I was wondering what the preferred protocol would be? There seem to be a lot of sensors using SPI so that would seem like the logical choice as I could share all but the CS line, however I'm not sure that it's fast enough? I haven't seen any commercial servo control boards using SPI or I2C, only USART.

I suppose it wouldn't be too much bother to use a jumper and two extra pins for protocol selection; just don't want to waste my time if SPI and I2C are unsuitable. It would be nice to leave USART free for GPS etc if I can. I did search the forum for SPI and I2C with no results, maybe due to a minimum letter count restriction?

Share this post


Link to post
Share on other sites

It all depends on what else you intend to add to your system... You'll see that some of the external units/sensors you'll want are I2C, others are SPI... each is used just as much as the other. UART only allows point-to-point, so usually you won't use that if you have choice and/or expect to hook more peripherals.

What I'd do myself is put headers on the servo board for all 3 interfaces, and write a bit of code so that it can receive the same commands over any of the 3, so the board would be "universal" :)

Share this post


Link to post
Share on other sites

I've gotten quite some way towards building a 16F690 based servo control board. I'm using 1 CCP module to drive the servos and am currently only working with USART. I have kept the SPI/I2C pins free and will implement them once everything else works as it should.

I've based my theory on the mikroBasic example here (second example from top uses 1CCP, the first uses 2CCP's). My data is sent over the serial link as ASCI characters as my terminal emulator will only allow me to send ASCI. I did post my questions over there but the forum is pretty dead and it can take weeks for people to come up with a suggestion.

There are two issues at the moment:

1. Servos off-centre with 1CCP driver

I've written example programs for 1 and 2 CCP operation. I've physically built and tested the 2 CCP example using the 16F877A on my development board and it seems to work fine.

I would like to build a small servo driver board so have gone on to build a 1 CCP driver for the 16F690. I have written the code and set up a circuit design in Proteus VSM for testing as it saves a lot of messing around and allows for rapid debugging / development.

It would appear to work ok, however, I've noticed that when loaded with a duty cycle of 1500us the servos are all off-centre, by increasing amounts ( -1.00', -1.30', -1.70', -2.10', -2.50', -2.90', -3.30', -3.70').

I can send a serial command to servos 0 and 7, setting them both to 2000us duty and they are still off-set (+49.0', +46.30'). Setting the duty cycle of each back to 1500us returns the servos to their off-centre positions. The virtual servos are configured for a range of 600 - 2400us = -+90'. I have set up a virtual copy of my 2 CCP servo driver and the virtual servos show dead centre (1500us = 0.00') so it doesn't seem to be an issue with Proteus.

It looks like the timing is out, maybe because of calculations being made in the ISR. Due to the servo outputs being spread over multiple ports I had to write a switch statement setting the appropriate servo outputs:

	// servo output on code here 

	activeservo++; 

	activeservo = activeservo & 7;  //move to next servo 


	switch (activeservo) { 


			case 0 : { 


				 S1 = 1; 

				 S2 = 0; 

				 S3 = 0; 

				 S4 = 0; 

				 S5 = 0; 

				 S6 = 0; 

				 S7 = 0; 

				 S8 = 0; 


			} 

			break; 


			case 1 : { 


				 S1 = 0; 

				 S2 = 1; 

				 S3 = 0; 

				 S4 = 0; 

				 S5 = 0; 

				 S6 = 0; 

				 S7 = 0; 

				 S8 = 0; 


			} 

			break; 


			etc etc etc
I fear this may be causing too much of a delay in the ISR but can't really see another way of doing it. The strange thing is that the Proteus oscilloscope shows all of the servo control lines with the correct 1500us on cycle. 2: Controlling servo speed I've set up serial control on my 1CCP F690 project. It works fine. I first send the servo number, then a four digit position value (to move servo 1 across its range: 10600, 11500, 12400). I now want to also have control of the speed at which the servo moves. I understand how servo speed was controlled in the 2CCP example, with a delay being added in the loop to slow down the rate at which the duty cycle is incremented. Getting the servo to move at a constant speed seems a bit more complicated for real life applications as the latest position requested could be either very close to the current position or very far away, so the delays need to be adjusted depending on the difference between the current position and requested position. Also implementing this delay via a loop may impede the movement of other servos. I'm not quite sure how it will be achieved. Ideally I would set a servo speed of 0-255 independently from requesting it's position, serial input being something like:
S(servoID)(servo speed 000-255) 

e.g. S1255 


P(servoID)(servo position 0600-2400) 

e.g. P11500

I would really appreciate suggestions on a sane and practical way to control the servo speed :huh:

Share this post


Link to post
Share on other sites

It would be interesting to see the whole ISR. As the delay increases with each servo, it's clearly the time it takes for the processor to get to the code handling that particular servo. As you're using a C compiler, who knows how much overhead it's generating, and how much time is uselessly spent before reagching the correct code bit.

What frequency is your PIC running at? The thing is, at typical PIC speeds (0.2-2us), it doesn't take many instructions before a noticeable error creeps in.

Personally I would code the ISR in assembly, where I have full control of what happens, and where I can count the time spent for each instruction. The key is to ensure that the code paths from entry in the ISR to the time each servo output is set have the exact same lengths. Of course you'll need some "switch" structure that will skew this, but what you can do is insert extra instructions doing nothing (nop) in the branches for the first servos in order to equalize the lengths. Then, due to the time spent in there all your "1500us" values will be actually a bit longer, you only need to subtract this from your base neutral value. But an oscilloscope really comes handy in those kinds of apps.

Regarding servo speed control: add an extra layer between the input from the UART and the servo output, that will take the current position, and calculate the next one based on the speed that is set for that servo. An example (not necessarily a good one): Speed is 1-255. At each frame, take current position (lets say 1500us), set position (1800us), and speed (100). Instead of directly setting the servo output to 1800, set it to 1500+100=1600. Next frame, current position will be 1600, set position still 1800, speed still 100. Set output to 1600+100=1700. Next iteration will finally get to 1800, so it will have taken 3 frames to reach new position. The smaller speed is, the more frames it takes to get to the set position and the more the servo is slowed down.

Your turn to improve on that in making a more sensible calculation of the speed :)

Share this post


Link to post
Share on other sites
It would be interesting to see the whole ISR. As the delay increases with each servo, it's clearly the time it takes for the processor to get to the code handling that particular servo. As you're using a C compiler, who knows how much overhead it's generating, and how much time is uselessly spent before reagching the correct code bit.

What frequency is your PIC running at? The thing is, at typical PIC speeds (0.2-2us), it doesn't take many instructions before a noticeable error creeps in.

Agreed, the fact that the servo offset increases with each servo also leads me to believe that something is delaying with increasing effect on each servo update cycle. I can only imagine this is the switch statement. Here is the full ISR code block. The PIC is running on the internal ocs running at 4Mhz. It's capable of 8Mhz but I figured I don't really need 8 Mhz and the PIC would use possibly less power if running at half the frequency.

void interrupt(void) {


	servostatus = !(servostatus);


	if (servostatus == 1) {			  // CCP1 Special Compare Event Interrupt


	// servo output on code here

	activeservo++;

	activeservo = activeservo & 7;  //move to next servo


	switch (activeservo) {


			case 0 : {


				 S1 = 1;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;


			}

			break;


			case 1 : {


				 S1 = 0;

				 S2 = 1;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;


			}

			break;


			case 2 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 1;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;


			}

			break;


			case 3 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 1;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;


			}

			break;


			case 4 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 1;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;


			}

			break;


			case 5 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 1;

				 S7 = 0;

				 S8 = 0;


			}

			break;


			case 6 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 1;

				 S8 = 0;


			}

			break;


			case 7 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 1;


			}

			break;

	}


	servo[activeservo] = ComShadowServo[activeservo];  // copy servo position from Comms array

	CCPR = servo[activeservo];						 // load CCPR compare value

	offtime = 2500 - servo[activeservo];			   // calculate off-time


	}

	else {


		// Turn Off All Servo Outputs

		 S1 = 0;

		 S2 = 0;

		 S3 = 0;

		 S4 = 0;

		 S5 = 0;

		 S6 = 0;

		 S7 = 0;

		 S8 = 0;


		CCPR = offtime;

		}


		PIR1.CCP1IF = 0;

}

Personally I would code the ISR in assembly, where I have full control of what happens, and where I can count the time spent for each instruction. The key is to ensure that the code paths from entry in the ISR to the time each servo output is set have the exact same lengths. Of course you'll need some "switch" structure that will skew this, but what you can do is insert extra instructions doing nothing (nop) in the branches for the first servos in order to equalize the lengths. Then, due to the time spent in there all your "1500us" values will be actually a bit longer, you only need to subtract this from your base neutral value. But an oscilloscope really comes handy in those kinds of apps.

I'm not sure I want to write the ISR in assembly but something is obviously effecting the timing. Actually -1' offset on the servo can be equated to 10uS, so I can centre the servos by setting them to 1510,1513,1517,1521,1525,1529,1533,1537. I could allow for this offset in code, but, that would be rather messy. I'm going to logically run through the code and see if there's a different way of manipulating the CCPR so that the switch statement doesn't effect it. Maybe there is a better way of dealing with the servo outputs being spread across multiple ports? With all servos on the same port it is easy to use a left bitshift to rotate the active output.

Here you can see the servos centred by manually setting the offset timing:

ServoBoardSim3.png

Regarding servo speed control: add an extra layer between the input from the UART and the servo output, that will take the current position, and calculate the next one based on the speed that is set for that servo. An example (not necessarily a good one): Speed is 1-255. At each frame, take current position (lets say 1500us), set position (1800us), and speed (100). Instead of directly setting the servo output to 1800, set it to 1500+100=1600. Next frame, current position will be 1600, set position still 1800, speed still 100. Set output to 1600+100=1700. Next iteration will finally get to 1800, so it will have taken 3 frames to reach new position. The smaller speed is, the more frames it takes to get to the set position and the more the servo is slowed down.

Your turn to improve on that in making a more sensible calculation of the speed :)

I'm wondering whether servo speed control will be worth the hassle, there's a lot of calculation for the PIC to do. For flight control I think maximum speed would always be required.

Share this post


Link to post
Share on other sites
The PIC is running on the internal ocs running at 4Mhz.

OK. That means that each instruction is 1-2us. As you can see, it only takes 5-10 instructions to create a 1° skew, and even the simplest switch structure will use that quickly especially as a switch means lots of jumps, and those are the ones that take 2us.

I could allow for this offset in code, but, that would be rather messy.

As I see in your list, it's always 3-4us skew for each servo. Instead of defining a different "zero" for each servo, you could just subtract 4 from it each time you run the ISR, and it would mostly be just fine. Reload initial value when you've ended a 8-servo loop.

I'm not sure I want to write the ISR in assembly but something is obviously effecting the timing. [...] Maybe there is a better way of dealing with the servo outputs being spread across multiple ports? With all servos on the same port it is easy to use a left bitshift to rotate the active output.

That's the key point. Yes if you can, use an entire 8-bit port for the servos. Use a bitshift, or the RLF/RRF assembly instructions. That way you can get rid of the whole switch statement. The first thing you do in the ISR is shift the port. Then your code path will be the same length for all servos. Set the CCP just after that.

Once this is done, you have all the time in the world to do some little calculations for servo speed, this won't be a problem.

Share this post


Link to post
Share on other sites

It's just not possible to use a whole port for servo output on a chip this size whilst still using the com ports. I know this is a dirty fix but it does the job:

	if (servostatus == 1) {			  // CCP1 Special Compare Event Interrupt


	// servo output on code here

	activeservo++;

	activeservo = activeservo & 7;  //move to next servo


	switch (activeservo) {


			case 0 : {


				 S1 = 1;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;

				servo[activeservo] = ComShadowServo[activeservo] + 10;  // copy servo position from Comms array

				//CCPR = servo[activeservo];						 // load CCPR compare value

				//offtime = 2500 - servo[activeservo];			   // calculate off-time


			}

			break;


			case 1 : {


				 S1 = 0;

				 S2 = 1;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;

				servo[activeservo] = ComShadowServo[activeservo] +13;  // copy servo position from Comms array

				//CCPR = servo[activeservo];						 // load CCPR compare value

				//offtime = 2500 - servo[activeservo];			   // calculate off-time


			}

			break;


			case 2 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 1;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;

				servo[activeservo] = ComShadowServo[activeservo] + 17;  // copy servo position from Comms array

				//CCPR = servo[activeservo];						 // load CCPR compare value

				//offtime = 2500 - servo[activeservo];			   // calculate off-time


			}

			break;


			case 3 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 1;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;

				servo[activeservo] = ComShadowServo[activeservo] + 21;  // copy servo position from Comms array

				//CCPR = servo[activeservo];						 // load CCPR compare value

				//offtime = 2500 - servo[activeservo];			   // calculate off-time


			}

			break;


			case 4 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 1;

				 S6 = 0;

				 S7 = 0;

				 S8 = 0;

				servo[activeservo] = ComShadowServo[activeservo] + 25;  // copy servo position from Comms array

				//CCPR = servo[activeservo];						 // load CCPR compare value

				//offtime = 2500 - servo[activeservo];			   // calculate off-time


			}

			break;


			case 5 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 1;

				 S7 = 0;

				 S8 = 0;

				servo[activeservo] = ComShadowServo[activeservo] + 29;  // copy servo position from Comms array

				//CCPR = servo[activeservo];						 // load CCPR compare value

				//offtime = 2500 - servo[activeservo];			   // calculate off-time


			}

			break;


			case 6 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 1;

				 S8 = 0;

				servo[activeservo] = ComShadowServo[activeservo] + 33;  // copy servo position from Comms array

				//CCPR = servo[activeservo];						 // load CCPR compare value

				//offtime = 2500 - servo[activeservo];			   // calculate off-time


			}

			break;


			case 7 : {


				 S1 = 0;

				 S2 = 0;

				 S3 = 0;

				 S4 = 0;

				 S5 = 0;

				 S6 = 0;

				 S7 = 0;

				 S8 = 1;

				servo[activeservo] = ComShadowServo[activeservo] + 37;  // copy servo position from Comms array

				//CCPR = servo[activeservo];						 // load CCPR compare value

				//offtime = 2500 - servo[activeservo];			   // calculate off-time


			}

			break;

	}


	//servo[activeservo] = ComShadowServo[activeservo];  // copy servo position from Comms array

	CCPR = servo[activeservo];						 // load CCPR compare value

	offtime = 2500 - servo[activeservo];			   // calculate off-time


	}

I'm going to take a look at SPI and I2C after which I'll have a think about controlling servo speed.

With regards to the control protocol; it does strike me that in MCU/MCU communications it would be better to use binary rather than ASCII, however any operation with programs like HyperTerminal etc is out of the window as it they only send ASCII codes. Using ASCII as I currently am requires 5 bytes be sent rather than 3 bytes in binary (ServoNum, ServoPos1, ServoPos2, ServoPos3, ServoPos4 vs ServoNum, PositionH, PositionL). Don't think that will be too much of a problem.

With regards to I2C, so far as I understand each I2C device should have a unique address, this is how multiple devices can share the same BUS. How is this dealt with in a production run where multiple units are being built? Does each PIC chip already have a unique address pre-programmed? How would it be known? Does a unique HEX need to be compiled for each device before programming?

Share this post


Link to post
Share on other sites
With regards to I2C, so far as I understand each I2C device should have a unique address, this is how multiple devices can share the same BUS. How is this dealt with in a production run where multiple units are being built? Does each PIC chip already have a unique address pre-programmed? How would it be known?

The address must be unique on the same bus. Usually, a part of which the manufacturer thinks you wouldn't use more than one unit on a bus will have a fixed address, in the other case like I2C EEPROM memory chips the manufacturer would give a way to change the address, for example with address pins. Same goes with boards using MCUs, if you figure nobody would use 2 of your boards on the same bus then choose whatever address you want, otherwise add a DIP switch or whatever way to change the address, maybe through another port...

Due to this here can be cases where you're out of luck and 2 devices from different manufacturers you want to use have the same fixed address, but thats pretty rare.

Share this post


Link to post
Share on other sites

Still having problems with this, it's fine if I use a servo time of 0.6 - 2ms but if I go above 2ms then the next servo (say servo 1 if I am moving servo 0) off-sets by a small amount.

Think I'm going to be best off ditching the 16F690 and looking for something similar with two CCP modules. The 28 pin 16F726 looks like a good match.

Edited by JF1980

Share this post


Link to post
Share on other sites

It seems neither MikroC nor Proteus support the 16F726 so I'm going to look at porting my current code to the 16F822 instead.

Edited by JF1980

Share this post


Link to post
Share on other sites
It seems neither MikroC nor Proteus support the 16F726 so I'm going to look at porting my current code to the 16F822 instead.

Hi,

Did you just ask for a MkC PRO Password ??? It supports the '726 .... ( Free if you own a MkC License - otherwise 2k limited ). The PRO release also could spare some lines at compile time.

But, it's you to decide ...

Alain

Share this post


Link to post
Share on other sites
Hi,

Did you just ask for a MkC PRO Password ??? It supports the '726 .... ( Free if you own a MkC License - otherwise 2k limited ). The PRO release also could spare some lines at compile time.

But, it's you to decide ...

Alain

Not quite sure where I asked for a password?!? Apparently MikroC Pro supports the 16F726; the free version is still good for up to 2K of program code. I'll just go with the 16F882, it has all the same features and is cheaper.

Share this post


Link to post
Share on other sites

Hello !

I was trying to do the same thing as you did last year, converrting the basic code into c code for 8 servos.

I'm a novice at PIC and i have some doubts :

1. why are ccpr1 and ccpr2 declared absolute addresses

2. what is the use of delay 5500us in main func

3. how did you replace the asm instruction ?

4. can the code be modified to control 4 dc motors as well on another port ?

please reply

thanks in advance

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×