MEL PICBASIC Forum - PWM : Servo PWM encode/decode


  • PWM : Servo PWM encode/decode

    This project uses DT Interrupts to measure pulses being sent from your RC receiver, and also to create new pulses to be sent to your servo's. This code uses assembly to ensure fast measurement and creation of pulse width in the background, so you can add code that would be usefull to you.
    Receiver channels 1, 3 and 5 are input all on to the CCP capture pin. Although this is only 3 pins, RC channels 2 and 4 can be determined by measureing the time between 1 and 3, and 3 and 5, giving you a 5 channels decoded. To keep these receiver lines from grounding each other out, diodes were added.




    Four ouputs are left for servos. The code only uses about 22% of the space in the PIC12F683, so there is a bit more space to make some cool devices. Using this slow 8mhz chip, it can perform about 80,000 insctructions each RC frame. This should be plenty for some lower and higher math on the servo variables.


    PWM Code


    by scalerobotics

    And for a PIC18F2431 series:

    The 18F2431 makes it slightly easier to perform servo PWM because it's PCPWM module can go down to 19 htz while maintaining fast OSC rates, unlike other PIC chips HPWM. Here is an example of using the PCPWM to output 3 servo signals. This specific example senses the pulses (up to 5 channels) by using the CCP1 capture pin. To get 5 channels into one pin for PW capture, channels 1, 3 and 5 are input using small signal diodes. Then, since this drops the signal down to under 3 volts, I used two 2n3904 transistors. One to boost up the voltage, and the other one to invert the signal back to normal. (It was all I had lying around). Somehow, you will need to boost your signal after the diodes for the CCP1 capture to sense the incoming pulses.

    Note: Because the PCPWM was used, this will not work on any chip outside of the 18F2431 family.

    Code:
    ;This only works on a 18F2431 family device with Power Control PWM (PCPWM)
    ;By Scale Robotics Inc. 
    ; The input signal should look something like this:- 
    ;
    ;        |<------------------ 1 frame (~20mS) -------------->|
    ;        |<1~2mS>                                 
    ;         ______        ______        ______                  ______
    ; _______|      |______|      |______|      |________________|      |____
    ;   gap    ch1    ch2    ch3    ch4    ch5      sync  gap      ch1    etc
    define OSC 40
    asm
        __CONFIG    _CONFIG1H, _OSC_HSPLL_1H
        __CONFIG    _CONFIG2H, _WDTEN_OFF_2H & _WDPS_512_2H
        __CONFIG    _CONFIG4L, _LVP_OFF_4L
    endasm
    clear 
    ADCON0 = %00000000
    ADCON1 = %00000000
    portb=0
    trisb = %11000000
    trisc = %00000110
    trisa = %00000000
    DTCON = %00000101   'dead time for complimentary ouputs
    PTCON0 = %00001101  '1:1 postscale, Fosc/4 1:64 prescale, Sincle Shot mode
    PTPERL = 255        ' 
    PTPERH = 251
    PWMCON0 =%01010000  'PWM[5:0] ouputs enabled
    PWMCON1 = 1         'updates enabled, overrides sync w/timebase
    PTCON1 = %10000000  'PWM timebase is on, counts up
    FLTCONFIG = %00000010 'disable fault A, cycle by cycle mode
    CCP1CON = %00000101    'Capture mode; every rising edge
    
    duty1 var word        'width of outgoing pulse1
    duty2 var word  'duty values 625 to 1250 = 1 to 2 ms pulse (Center at 625)
    duty3 var word
    risetime1 var word    'Rise Time for incoming pulse1 
    risetime2 var word
    risetime3 var word
    falltime1 var word     'falltime for incoming pulse1
    falltime2 var word
    falltime3 var word
    pulsewidth1 var word    'pulse width for incoming pulse1
    pulsewidth2 var word
    pulsewidth3 var word
    pulsewidth4 var word     
    pulsewidth5 var word
    pulseNumber var byte
    pulseNumber = 1         'tells which pulse we are reading
    CCP1CON.0  = 1
    timerone var word 
    timerone = 60315 ;
    
    INCLUDE "DT_INTS-18.bas"     ; Base Interrupt System 
    ASM
     
    INT_LIST  macro    ; IntSource,        Label,  Type, ResetFlag?
            INT_Handler   TMR5_INT,   _PulseOut,   ASM,  yes
            INT_Handler   CCP1_INT,   _PulseMeasure,   ASM,  yes
            INT_Handler   TMR0_INT,   _TimeOut, ASM, yes
        endm
        INT_CREATE               ; Creates the interrupt processor
    ENDASM
     
    T1CON = %00110001            ;Timer1 used by CCP1 capture
    T5CON = $01                  ;used as pulseout timer
    T0CON = %11000111              ; Prescaler = 8, TMR1ON
    
    @   INT_ENABLE  TMR5_INT     ; Enable Timer 1 Interrupts            
    @   INT_ENABLE  CCP1_INT      ; Enable Capture Compare for pulse width measurement
    @   INT_ENABLE  TMR0_INT    ; deadtime (sync gap) indicator for lull between pulses
    Main:
            pause 10
            'do something to these values if you want to filter, center, etc
            'Below we are just passing values through
            duty1 = pulsewidth1 >>1  'send channel 1 out PCPWM on PortB.1
            duty2 = pulsewidth3 >>1  'send channel 3 out PCPWM on PortB.3
            duty3 = pulsewidth5 >>1  'send channel 5 out PCPWM on PortB.4
    GOTO Main
     
    '---[TMR5_INT - interrupt handler]------------------------------------------ 
    PulseOut: 'set up pulse width values for pulseout and reset single shot bit
        TMR5L = timerone.byte0
        TMR5H = timerone.byte1
        PDC0L = duty1.lowbyte
        PDC0H = duty1.highbyte
        PDC1L = duty2.lowbyte
        PDC1H = duty2.highbyte
        PDC2L = duty3.lowbyte
        PDC2H = duty3.highbyte
        PTCON1.7=1  'resets single shot PTEN bit
    @    INT_RETURN
        
    '---[CCP1_INT - interrupt handler]------------------------------------------
    PulseMeasure:
    if CCP1CON.0  = 1 then ; Check CCP1M0 for rising edge watch
        select case pulseNumber
            case 1
                TMR0L = 0    'reset timeout timer0
                TMR0H = 0
                risetime1.lowbyte = CCPR1L
                risetime1.highbyte = CCPR1H
            case 2
                TMR0L = 0    'reset timeout timer0
                TMR0H = 0
                risetime2.lowbyte = CCPR1L
                risetime2.highbyte = CCPR1H    
            case 3
                TMR0L = 0    'reset timeout timer0
                TMR0H = 0
                risetime3.lowbyte = CCPR1L
                risetime3.highbyte = CCPR1H
        End select 
        @ BCF     CCP1CON, CCP1M0 ; Now capture the trailing edge     
    Else     'check for falling edge time
        @ BSF     CCP1CON, CCP1M0 ; Re-set for trailing edge capture
        select case pulsenumber
            case 1
                falltime1.lowbyte = CCPR1L
                falltime1.highbyte = CCPR1H
                pulsewidth1 = falltime1 - risetime1
            case 2
                falltime2.lowbyte = CCPR1L
                falltime2.highbyte = CCPR1H
                pulsewidth2 = risetime2 - falltime1
                pulsewidth3 = falltime2 - risetime2
            case 3
                falltime3.lowbyte = CCPR1L
                falltime3.highbyte = CCPR1H
                pulsewidth4 = risetime3 - falltime2
                pulsewidth5 = falltime3 - risetime3
        end select 
        pulsenumber = pulsenumber + 1 'get ready for next channel
            
    endif
    @    INT_RETURN
    '---[TMR0_INT - interrupt handler]------------------------------------------
    TimeOut:
        pulsenumber = 1 'if pause between pulse in exceeds about 7 ms, get ready
        'to receive pulse 1 (senses dead time between pulse bursts)
    @   INT_RETURN