Another PWM post

Discussion in 'Programmer's Corner' started by MCrowe, May 29, 2011.

  1. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    Hi Could someone please send me all the code to drive two servos... kidding.

    But seriously, Im currently using a Pic16F877a and im trying to implement PWM. I Know I probably should have gone with a PIC with PWM built in but I've done all my previous work with a Motoroal 68HC11 which didn't have PWM built in but had enough interupts and timers to implement it without overly affecting my main code.

    I know I can obviously do it just by using delays, but I cant figure out how the timers work in Pics. If anyone could help it would be greatly appreciated. I really just want to know what the best solution is to be able to drive say between 2 and 4 hobby servos and still have a bunch of other cope running.

    P.S. Im using assembly language and a 4mhz crystal if that helps.

    Thank.
     
  2. BillO

    Well-Known Member

    Nov 24, 2008
    985
    136
    It would be trivial to write something in assembly if you find the internal timers too difficult to use. Here is non-standard pseudo-code...

    Code ( (Unknown Language)):
    1.  
    2.  
    3. Value X (is) byte, range - 1, 254
    4. Value I (is) byte, range - valid pin number
    5.  
    6. Loop
    7.     Get X;
    8.     Let Y=255-X;
    9.  
    10.     Set Pin(I) = hi
    11.     High
    12.         Decrement X;
    13.         Goto High if X > 0;
    14.  
    15.     Set Pin(I) = low;
    16.     Low
    17.         Decrement Y;
    18.         Goto Low if Y > 0;
    19.  
    20.     Goto Loop
    21.  
     
  3. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    Thanks for the reply, Been flat out with real work and its takin me a while to get back here.

    I understand I can do it that way. But that requires keeping an eye on any other code I have running in there at the same time. I dont think my initial post was very clear. I meant that I used to use Timer Interrupts. So I would set up on, to interrupt every 20ms? i think it was, which would always drive all servos line high. Then, when that interupt occured, it would reset itself, as well as setting the other interupts with the value that drove the line low. (Any where between .5 and 1.5ms). That way, I could change the values that were passed in for the servo position interupts any time within my program and they would be passed on to the servo as soon as the 20ms interupt occured. Does that make sense?

    It means that no matter how much code I have running on the microcontroller the timing for the servos is always correct. What I was wondering is, is there a way to do this in PIC's. From what I can gather, reading the manuals, they either dont have interupts or they work quite differently to the Motorola chips. I assume I need to use the Watch dog timer?

    Ok, Here is what I think I need to do:

    1: Set WDT to trigger every 20ms. Dont know how to do that because Ive never used it before but I guess I can figure it out.

    2: I think there are two timer interrupts? yes? so I set them to start when the WDT interrupt occurs. They are set with the value that drives the pins low again.

    Does this make sense? Can this be done? and more importantly, is it the best way? It will only give me control over two servos but if its the only way to do it then that is what I will go with. Thanks.
     
  4. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    Here's code for 8 channels on Port B of a 16F628A. It uses the CCP module in "special event" mode which resets Timer 1 to zero on each compare match interrupt. The servo channel outputs are sequential and each channel has a 20-msec period with a pulse width range of 400 to 2400 usecs (1 usec resolution).

    If you don't have a good grasp on PIC assembly language, this may not make a lot of sense to you (sorry).

    Good luck... Cheerful regards, Mike

    Code ( (Unknown Language)):
    1.  
    2. ;******************************************************************
    3. ;*                                                                *
    4. ;*  Filename: Crazy-8 Soft Servo.asm                              *
    5. ;*    Author: Mike McLaren, K8LH                                  *
    6. ;*      Date: 10-Nov-06  (revised 10-Nov-06)                      *
    7. ;*                                                                *
    8. ;*   16F628A 8-Channel (Port B) Servo Controller Driver Demo      *
    9. ;*                                                                *
    10. ;*   Uses 16F628A INTOSC (4-MHz)                                  *
    11. ;*                                                                *
    12. ;*     MPLab: 7.40    (tabs=8)                                    *
    13. ;*     MPAsm: 5.03                                                *
    14. ;*                                                                *
    15. ;******************************************************************
    16.  
    17.         #include        <p16f628A.inc>
    18.         errorlevel      -302
    19.         radix   dec
    20.  
    21.  
     
    Last edited: Jul 17, 2011
  5. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    Hey thank heap for that.

    I think I can figure that out. Im only new to this so I am programming in Assembly language. All the programming I've done has been in assembly, but it seems now days that most stuff on the net is in C.

    Any ways, Ill have a look through that code to see if I can figure it out. Meant to be working at the moment. It certainly looks to be what I was hopeing for. If i have any major problems Ill be back. :) Thanks again.
     
  6. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    OK So I think I have come to the resonable conclusion that there isn't a simple way to do PWM with the particular chip I have. It doesn't have enough timer interupts. So I've been using that chip to learn how to turn LED's on and off with a Switch. Ya. Now I have a ??? something else. With 40 pins, and I think 15 interupts. Should be easy now if I can just manage to get it working on a bread board without blowing something up. Too many pins to accidently connect wrong.

    Actually, one question if you dont mind MMcLaren. Why is it that you load 1500 into CCPR1Lo and CCPR1Hi? I dont quite understand how that part works. CCPR1Lo and Hi are the one register aren't they? but 16 bit?

    Wouldn't you just load the length of the pulse into a interupt compare.

    movlw low 1500 ; |B0
    movwf Servo+00 ; Servo 1 lo |B0

    movlw high 1500 ; |B0
    movwf Servo+01 ; Servo 1 hi |B0

    This hurts my brain lots. I need to do lots more study on this before I understand it properly.
     
  7. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    More likely you don't understand how to use the timer interrupts effectively for some particular task. It gets easier with experience. Hang in there.
    CCPR1H and CCPR1L are two 8-bit registers that make up the 16-bit "compare" register pair. A 1500 (usec) "compare" value will generate a 1.5-msec servo pulse which should put the servo in its centered position. More information in the CCP section of the datasheet.

    The code you're showing is used to initialize the eight servo[] array elements with values for 1500 usec pulse widths. The ISR loads the 16-bit CCPR1 "compare" register pair from the array.

    You're trying to understand how to write timer "compare" code and I posted a program that's just way too complex. I apologize. Maybe someone will jump in with a more appropriate example and explanation...
     
    Last edited: Jun 9, 2011
  8. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    Here's another (also fairly crazy) way to do it using a PIC16F84:
    Setting the pulses off spaced out rather than at the same time avoids timing issues when the pulse lengths are similar.
     
  9. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    Not WAY too complex. Just a little too complex.. Like I said at the start. My intention was to use Timer compare interupts. This was what I was doing with the 68HC11. (Motorola). But I was using 3 timer compare interupts for two servos. One interupt set the 20ms pulse (drives pin high), the other two are reset everytime the 20ms pulse is reset and just contain the value that drives the pulse low after a set time. Except I think it was much easier to use the timer compare interrupts in the motorla MC. All you do is load the current timer value, and add the amount to it you want to wait for an interupt. Make sense.

    The Pic has all these prescaler values and such which Im having trouble with. I understand the concept, its just putting it into practice. AND with the Pic I was using I only had one interupt to use. I think.

    Hey I see the problem here, The Pic I was ORIGINALLY using was the 16F84a, which is the one with only 1 interrupts.

    The new one I have is the 16f877a, with 15 or so interrupts. Sorry to be so confusing. Thanks for all the advice. Its all starting to make some semblance of sense. I have ordered an evaluation board so I wont have to have wires running everywhere on my bread board while im still trying to learn the code. Hopefully I should start making some progress when the board arrives.
     
  10. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    Oh and just to clarify, the part I couldn't understand was, why do you load 1500 into both side of the 16 bit register. As in 1500 into the low side AND 1500 into the high side. I though you would just load 1500 into the 16 bit compare register. Not load 1500 into two 8 bit registers that make up the 16 bit register.??

    Thanks for you help.
     
  11. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    The low() and high() operators are used to split the 1500 constant into the lower 8 bit portion and the upper 8 bit portion of the number so that you can stuff it into two 8 bit registers. You could just as easily use "movlw 1500/256" and "movlw "1500%256" to do the same thing (with decimal radix).

    Since you're in learning mode already, may I suggest that investing a little more time learning to use the MPLAB Simulator? It allows you to "single step" through your assembly language program and "watch" registers and variables. In the case of loading up our Servo[] array, you would be able to single step through the instructions and see the values change in the W register and the servo[] array.

    Cheerful regards, Mike
     
  12. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    Mmm, OK, sounds like a good idea. I haven't tried MPLAB Simulator yet, but it sound very useful. Would allow me to see what was going on instead of constantly trying it on my bread board then trying to figure out why things dont work.

    I have spent the weekend readying my 16F877a manual when ever I got a chance. Was away from the house so couldn't try any programs. But I think im getting the idea with the interupts and such now.

    Any way, thanks for all your help. Cheers.

    P.S. Although now ill be back later asking how to get the Simulator working properly. :)
     
  13. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    ok, if someone could possible chime in with some help again. I left the servo control for a bit and have been discovering other things about my pic. Ive got it displaying characters on the screen, responding to commands, reading AD channels, outputting PWM using PWM module, but no servo.

    I can figure out the code if someone can point me in the right direction.. Whats the process for controlling a servo, 50hz PWM. I know it cant be done just with the PWM module as I cant get it that slow. So whats the best way to go. I want to control only 2 servos, (may expand later) and I dont want to overly impact the rest of my program. So, I can use one timer in special event mode to continiously drive a pin high, as well as jumping to the interupt vector ((I assume thats what the special event is, it says it an ""internal hardware event is initiated"")). So, the interupt then needs to set a timer compare to drive the pins low after the variable servo time. But how??

    If im only controlling one servo, I can simply subtract the variable servo time from the period time. no problems, i think. But if im controlling two servos using the one timer, you would have to find out which high pulse was the shortest one, subtract that from the longer pulse, and store the remainder. so when the compare matched it would drive PIN1 low, then rest the timer to drive PIN2 low very soon after, THEN subtract the longest variable servo time from the period, and set the Period timer to drive both pin high again at the end fo the 20ms.

    And even trying to think of that to type it made my brain hurt. Im not even sure it possible in code.?? or is it? Is this how people do it? It seems you would end up with a lot of time spent running interrupt returns. Ahhh help please.
     
  14. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    Probably the easiest way is to send the servo pulses sequentially, so send one complete pulse, then the next, then pad out to 20ms. Because the maximum pulse length you would want to send is around 2.5ms and the servos only expect a pulse every 20ms, you can manage up to 8 servos with this method.
     
  15. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    yeah, thats what your program did isn't it. I had a look at that. Got it working for a bit then something went wrong and the servo stopped responding on one pin, then on all pins... Any who, thanks for the suggestion. Even though I had tried your program I forgot the method you used until I just read the above post. Cheers.
     
  16. Markd77

    Senior Member

    Sep 7, 2009
    2,803
    594
    MMcLaren's method does them sequentialy as well and is a better method than mine if you have the CCP module in your PIC.
    Mine suffers from more interrupts happening, lower resolution, easier to break :) , and also I don't make the period fixed at 20ms, it can get shorter or longer by a few ms which could make servos drift a bit.
     
  17. John P

    AAC Fanatic!

    Oct 14, 2008
    1,634
    224
    However, your method would work, but it involves some hairy programming, especially to deal with the situation where the pulses are nearly the same length.

    But as my Welsh math teacher used to say: "Well boy, you could do it that way, but the game's not worth the candle, is it?"
     
  18. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    Help :( I cant get my timer working properly. Im using McLarens program with a 16f877a chip, with a 4mhz Crystal but my period time is coming out as 2.5HZ (approx), instead of 50hz. Why, why. I cant get it to change. I have the prescale value set as 1:1. Loading 20000 into period register. (CCPR1H:CCPR1L).
     
  19. John P

    AAC Fanatic!

    Oct 14, 2008
    1,634
    224
    One thing that can cause very slow apparent operation is that the watchdog is resetting the processor repeatedly. Is that possible? If that's what's happening, the timer isn't running at all, or isn't doing what you think it is. Or maybe it is running, but after some fraction of a second, everything halts as the chip resets.
     
  20. MCrowe

    Thread Starter Member

    May 29, 2011
    69
    0
    Nope... Got the WDT turned off. But I have tried adjusting the period (timer value) and it doesn't appear to change anything. So my best guess would be that its causing an interrupt on overflow of the 16 bit timer. maybe?? I can't figure out whats wrong with it. I was trying to adapt MMcLarens program, but couldn't get the timing to work at all. I ended up making my own for two servos, using on CCP timer and it works!!! Took a while, lots of mucking around but finally got something working.
     
Loading...