Maker Pro
Maker Pro

Programming PIC to sontrol 6x SERVOs

The loop as you have it must be executed continually. If you go more than 20 msec without updating the servo will be erratic. If you need to so something else during each loop, the simplest was is, instead of the 8 msec delay at the end, go off and do what you need as use a timer to start the next servo update. Set up a timer for 20 msec and during your other processing, check frequently for it's expiration.

If you are going to use interrupts, I would interrupt every 2 msec and then handle 1 servo in each of the first 6 and do nothing for the next eight. You would turn on the servo you are handling at the interrupt, then schedule a timer interrupt for when to turn it off.

This would leave nearly all the processing time for other tasks without having to check for timing.

Bob
Hi B,
Considering your preferred method of 2ms LOOPs. This may be beyond my skills.

Thanks.

C.
 
Hi B,
Considering your preferred method of 2ms LOOPs. This may be beyond my skills.

Thanks.

C.
That's why I gave you the simplest method first. :)

There is actually no 2 msec loop. The interrupts occur at 2 msec intervals and the handler would only execute a few instruction and then return. But interrupt driven programming is a lot more difficult than normal sequential programming. The advantage is that you can have other things going on and they do not have to be aware of the existence of the servos.

Bob
 
That's why I gave you the simplest method first. :)

There is actually no 2 msec loop. The interrupts occur at 2 msec intervals and the handler would only execute a few instruction and then return. But interrupt driven programming is a lot more difficult than normal sequential programming. The advantage is that you can have other things going on and they do not have to be aware of the existence of the servos.

Bob
Hi B,
Ok, I'll try the simplest method, and see about the second method later.
Thanks,
C.
 
That's why I gave you the simplest method first. :)

There is actually no 2 msec loop. The interrupts occur at 2 msec intervals and the handler would only execute a few instruction and then return. But interrupt driven programming is a lot more difficult than normal sequential programming. The advantage is that you can have other things going on and they do not have to be aware of the existence of the servos.

Bob
Hi B,
Looking a bit deeper into what you are saying.

Is this correct: The interrupt occurs every 2ms intervals (How long does it last?) The first interval SETS SERVO1, and increments to SERVO2, goes back to the MAIN LOOP for the remainder of the 20ms, then at the next interval SETS SERVO2 and increments to SERVO3 and so on.

Perhaps this is a bit difficult to write and read?
C
 
Hi B,
Looking a bit deeper into what you are saying.

Is this correct: The interrupt occurs every 2ms intervals (How long does it last?)
This is correct. How long it lasts is until you return.
The first interval SETS SERVO1, and increments to SERVO2, goes back to the MAIN LOOP for the remainder of the 20ms, then at the next interval SETS SERVO2 and increments to SERVO3 and so on.
Not quite. It turns on the pulse for servo 1, then sets a different timer to interrupt at the end of the pulse for timer1, and returns from the interrupt (which returns you to wherever you were when the interrupt occurred.

When the timer interrupt occurs, you turn off the pulse for servo 1 and increment a variable to tell you servo 2 is next. In order to make sure this timer interrupts before the next servo interrupt, In thinking this through, you want to make the servo interrupt longer than 2 msec. 2.5 msec would do nicely since it divides evenly into 20.
Perhaps this is a bit difficult to write and read?
C

So there are 2 different timer interrupts you use. One that occurs at a regular interval, which I have now changed to 2.5 msec. And the other which is set up based on the pulse width you want for each servo.

Alternately, you could simply delay in the periodic interrupt handler for the length of the pulse without using a second interrupt, making the code simpler. But this would waste a lot of time when the controller could be doing other things.

Third alternative, if you can't spare another timer, you could use the same timer and do it like this.
Set up the interrupt to occur immediately. Set a state variable to indicate it is in turn on state, and another to say that you are working on the first servo. When the interrupt occurs, turn on the servo it is handling and schedule the next interrupt for the turn off time. Change the state variable to say that you are in the turn off state now. When the turn off interrupt occurs, turn off the active servo, set the state variable to turn on, schedule the next interrupt for 2 msec - the on time you just completed, set the state to turn on, and increment the servo number. When you get to the last servo you can set the next interrupt for the end of the 20 msec interval.

If this all sounds very complicated, that is because it is :)

Bob
 
[QUOTE="BobK, post: 1769244, member: 11295"
Not quite. It turns on the pulse for servo 1, then sets a different timer to interrupt at the end of the pulse for timer1, and returns from the interrupt (which returns you to wherever you were when the interrupt occurred.

When the timer interrupt occurs, you turn off the pulse for servo 1 and increment a variable to tell you servo 2 is next. In order to make sure this timer interrupts before the next servo interrupt, In thinking this through, you want to make the servo interrupt longer than 2 msec. 2.5 msec would do nicely since it divides evenly into 20.


So there are 2 different timer interrupts you use. One that occurs at a regular interval, which I have now changed to 2.5 msec. And the other which is set up based on the pulse width you want for each servo.

If this all sounds very complicated, that is because it is :)

Bob[/QUOTE]

Hi B,
Before I concentrate on your 'best' option.

Here is something I tried as a simple test. By changing each time, the SERVOs move as expected. Previously, I had set a TIMER in the MAIN LOOP to gosub to the SERVO LOOP, then return to the MAIN PROGRAM.
The timings are inputted via HSERIN, and separated. I was going to subtract 'say' SERVO1 time from 2000 to give the OFF time for SERVO1

Is there any use for this?

C.

servoloop:
RB0 = 1 'SERVO1
WaitUs 1999
RB0 = 0
WaitUs 1

RB1 = 1 'SERVO2
waitus 500
RB1 = 0
WaitUs 1500

RB2 = 1
WaitUs 1200
RB2 = 0
WaitUs 800

RB3 = 1
WaitUs 800
RB3 = 0
WaitUs 1200

RB4 = 1
WaitUs 1000
RB4 = 0
WaitUs 1000

RB5 = 1
WaitUs 1000
RB5 = 0
WaitUs 1000

WaitUs 8000

'FULLMOVEMENT WaitUs 2600
Goto servoloop
 
If you have a timer for 20msec in the main loop, which is presumably doing something else, get rid uf the wait for 8000usec that is the yime you have for doing other things. As you have it now, it would not leave any time in the main loop, it would return to the servo update immediately sice that code is taking the entire 20msec.

Bob
 
If you have a timer for 20msec in the main loop, which is presumably doing something else, get rid uf the wait for 8000usec that is the yime you have for doing other things. As you have it now, it would not leave any time in the main loop, it would return to the servo update immediately sice that code is taking the entire 20msec.

Bob
Hi B,
Am I correct that I can use the SERVOLOOP above, but make the ON/OFF times add up to 2500us and remove the 8000us?

Also is this correct: The 20ms timer in the MAINLOOP triggers a 'gosub' to the SERVOLOOP, which sets 6x SERVOS sequentially and once done, allows the MAINLOOP to continue till the next trigger?

NOTE: 6x 2500us leaves less time than 6x 2000us.
C
 
You are conflating parts of two different solutions we have been discussing. The 2.5 msec interval was in the context of handling the servos in a timer interupt handler. You are not using that technique, so my musings on that do not apply.

A gosub does not allow the main loop to run concurrently. That is why you must get rid of the 8000 usec delay at the end of the subroutine. That 8000 usec is the only time you have to run any other code. And, if that code must respond to anything in less than 12000 usec, it cannot do so.

I am not familiar with the BASIC you are using, so I don’t know if using interrupts is even possible. Are you experienced with using interrupts?

It might help if you could tell me what the main loop is doing.

Bob
 
Hi B,
I'm not sure what a handler is, but I think I understand what you have been explaining.

When I say GOSUB I mean the jump that the TIMER makes. I have previously used TMRs, but I wouldn't say I am experienced.

This PCB is a receiver which is receiving signals from a transmitter, as in radio control. main LOOP receiver a GPS signal, as well as the DATA from the transmitter, and reads different modules such as compass etc. I'm making smaller manageable programs, which will eventually be all added together to one large program (hopefully).

Here is an example of a program, with a TIMER.
C
 

Attachments

  • New Text Document.txt
    2 KB · Views: 69
Okay. The handler is this piece of code:

Code:
On High Interrupt
Save System

INTCON.TMR0IF = 0
Toggle wled  'RC2
TMR0H = %00000111
TMR0L = %00000000

Resume  'RESETS GIEH GIEL

The way this works, is when the timer has counted the number of times set by these two lines:

TMR0H = %00000111
TMR0L = %00000000

It then interrupts the main program loop, and the handler is executed instead. When the handler does the "Resume" it goes back where it was in the main program. Basically, magic.

You could use a timer handler like this to implement the third method I gave you before. Here is a high level description of how it would operate. Note that this is actually improved from what I suggested before.

1. The main program sets up the timer and causes it to interrupt immediately (by putting 0 in TMR0H and 1 in TMR0L like the program you posted. It also initializes a variable called SERVO to 1 and a variable called TOTAL_TIME to zero.

2. When the handler is called the first time, SERVO will be 1 so it turns on servo 1 and sets up the timer to interrupt next when the on time for servo 1 is completed. It also adds this time to TOTAL_TIME, and increments SERVO, which will become 2.

3. When the handler is called the second time, SERVO will be 2 so it turns off servo 1, turns on servo 2 and sets up the timer to interrupt next when the on time for servo 1 is completed. It also adds this time to TOTAL_TIME, and increments SERVO, which will become 3.

do the same for servo SERVO = 3, 4, 5 and 6.

In the step that turns off servo 6, you set the timer to occur after 20 msec - TOTAL_TIME, set SERVO to 1 and set TOTAL_TIME to zero, which repeats the entire sequence after a total time of 20 msec.

Hope this helps.
 
Great, have a go at it. If you can’t get it going you can post the code and I will have a look at it.​

Bob
 
Hi B,
I have your instructions in #33, but I had a lot processing rattling around my mind, so I purged it and got a program that simulates, here:
C
 

Attachments

  • SIM.jpg
    SIM.jpg
    513.4 KB · Views: 33
  • 18LF4431 SERVO TEST 210618 0800 W.txt
    7 KB · Views: 56
Hi B,
Is it important to have the TIMER control everything?

I have been setting the SERVO ON time from an incoming serial connection, then subtracting the ON time from 2500us to give the OFF time. e,g, Trigger each SERVO with the TIMER, SERVO1 ON/OFF for a total of 2500us, then SERVO2 and so on till SERVO6, which adds up to 15000us, leaving 5000us (MAIN LOOP), always.

Do I need to calculate each time or can I simply use fixed times as here?

EDIT: Thinking about it, I may be blurring the lines between TIMERS an WAITS, and what I suggest above is not sensible. Correct me if I'm wrong!

C.
 
Last edited:
Hi B,
I've made a start on the #33 instructions.

Is this on the correct path?

C.
 

Attachments

  • 18LF4431 SERVO TEST 220618 1200 Bob.txt
    8.1 KB · Views: 39
I am afraid not.

I don't know the basic language you are using, so I can't really write the actual code for you, but here are some comments.

You get off to a good start by checking the servo number with if statements, but you need to do a lot more in each if.

You have a delay in the handler. This means you really don't understand how it is supposed to work, so let me try to explain it.

A timer works by setting a value in TMR0H and TMR0L. The timer then starts counting up at some rate determined by the configuration settings. When it overflows to 0, an interrupt is generated. So the idea here is to set a value in the timer that will cause the next interrupt to occur after the desired on time for the servo it is handling this time around. We set a value of 65535 intiallialy so that the first interrupt occurs right away. You appear to have done this correctly.

The idea here is that each time we get a timer interrupt we are going to start the pulse for one servo. We then set the timer to interrupt at the end of that pulse, a variable amount of time based on where we want the servo positioned. When we get the next interrupt, we turn off the pulse for the previous servo and start the pulse for the next servo. So the first and last interrupts will be special. The first one will have not servo to turn off and the last one will have no servo to turn on. It then takes 7 pulses to handle the six servos. You did get that we need to use a servo number to know what to do each time. You used a variable x to do this, then copied x to a variable servo. That is unnecessary, just use the variable servo.

I am going to assume that you deal with time in usec, and that the timer counts once per microsecond. If you cannot arrange for that rate, then you will have to translate the usec times into timer ticks. For example, if the timer actually counts every 2 usecs you would divide the time by 2 to get timer ticks.

So, in pseudo code, the handler should look something like this:

Code:
if servo = 1
    turn on pulse for servo 1
    time = time for servo 1
    count = 65535 - time + 1
    TRM0H = 8 high bits of count
    TMR0L = 8 low bits of count
    totalime = totaltime + time
    servo = 2

else if servo = 2
    torn off pulse for servo 1
    turn on pulse for servo 2
    time = time for servo 2
    count = 65535 - time + 1
    TRM0H = 8 high bits of count
    TMR0L = 8 low bits of count
    totaltime = totaltime + time
    servo = 3

(repeat for 3,4,5,6 )

else if servo = 7
    turn off pulse for servo 6
    time = 20000 - totaltime
    count = 65535 - time + 1
    TRM0H = 8 high bits of count
    TMR0L = 8 low bits of count
    servo = 1
end if

Note that use must write TMR0H and TMR0L in that order, because the hardware handles them specially.

I don't know if your basic has something like the C switch statement, which allows you to do one test and go to multiple branches, but if it does, that would be better than a series of if statements like I have in the pseudocode.

Bob
 
Top