PIC 16F877 > Timers > 2 servos

Status
Not open for further replies.
Thank you for the explanation. Unfortunately, I don't think I can do much with only sixteen 64-usec steps across that 1000-us range.

The ESP32 is a very good and easy option with excellent PWM capabilities. The chip is typically described as controlling LEDs but PWM frequency of 50Hz and 12bit resolution is handled behind the scenes at clock speeds more than 140MHz.

You can program with the Expressif IDE or Arduino IDE.

Onboard Bluetooth and WiFi (run by separate proceßor core so no interrupt issues like ESP8266
 
LOL here a sample
Code:
#define COUNT_LOW 0
 #define COUNT_HIGH 8888

 #define TIMER_WIDTH 16

#include "esp32-hal-ledc.h"

void setup() {
   ledcSetup(1, 50, TIMER_WIDTH); // channel 1, 50 Hz, 16-bit width
   ledcAttachPin(2, 1);   // GPIO 22 assigned to channel 1
}

void loop() {
   for (int i=COUNT_LOW ; i < COUNT_HIGH ; i=i+100)
   {
      ledcWrite(1, i);       // sweep servo 1
      delay(50);
   }
}
 
And here the header file dosn't look like any hardware pwm is used. All done with a timer.
Code:
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef _ESP32_HAL_LEDC_H_
#define _ESP32_HAL_LEDC_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <stdbool.h>

typedef enum {
    NOTE_C, NOTE_Cs, NOTE_D, NOTE_Eb, NOTE_E, NOTE_F, NOTE_Fs, NOTE_G, NOTE_Gs, NOTE_A, NOTE_Bb, NOTE_B, NOTE_MAX
} note_t;

//channel 0-15 resolution 1-16bits freq limits depend on resolution
double      ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits);
void        ledcWrite(uint8_t channel, uint32_t duty);
double      ledcWriteTone(uint8_t channel, double freq);
double      ledcWriteNote(uint8_t channel, note_t note, uint8_t octave);
uint32_t    ledcRead(uint8_t channel);
double      ledcReadFreq(uint8_t channel);
void        ledcAttachPin(uint8_t pin, uint8_t channel);
void        ledcDetachPin(uint8_t pin);


#ifdef __cplusplus
}
#endif

#endif /* _ESP32_HAL_LEDC_H_ */
 
Yeah, that's why I said "libraries" - but at 240MHz, a little overhead can be tolerated.
 
What is wrong with you?
 
This was more of an academic exercise for me since it aligned with a similar project I was already working on. The Micro I am using is a PIC16F15323, but the section for the TMR1 and Compare circuit are almost identical to the 16F877 with a little massaging of the code below and digging into the datasheet a little, you should be able to get it to work on the 16F877 running at 8MHz.

This will control 8 servo's using only ONE timer and one of the compare modules in standard (1ms to 2ms) or extended (500us to 2500us) mode with 1us resolution

The idea is this .... setup TMR1 so that a fixed interrupt interval is 1/8th of the required 20ms (IOW 2.5ms) and reference this as the period interrupt. Each Period interrupt increments a servo Index variable but limit it so that it only counts from 0 to 7. Use the servo Index to dispatch and properly setup the compare value to trigger the next compare interrupt. At each servo index set the proper pin HIGH, when the compare interrupt triggers, then set the pin LOW. The following index will point to the next allocated time slot for the next servo.

Code:
#include "p16f15323.inc"
    
; Config BITS   
;#######################################################################################################################   
;#######################################################################################################################   
 __CONFIG _CONFIG1, _FEXTOSC_OFF & _RSTOSC_HFINT32 & _CLKOUTEN_OFF & _CSWEN_ON & _FCMEN_ON
 __CONFIG _CONFIG2, _MCLRE_OFF & _PWRTE_OFF & _LPBOREN_OFF & _BOREN_ON & _BORV_LO & _ZCD_OFF & _PPS1WAY_OFF & _STVREN_ON
 __CONFIG _CONFIG3, _WDTCPS_WDTCPS_31 & _WDTE_OFF & _WDTCWS_WDTCWS_7 & _WDTCCS_SC
 __CONFIG _CONFIG4, _BBSIZE_BB512 & _BBEN_OFF & _SAFEN_OFF & _WRTAPP_OFF & _WRTB_OFF & _WRTC_OFF & _WRTSAF_OFF & _LVP_ON
 __CONFIG _CONFIG5, _CP_ON

 
; Servo I/O Pin definitions   
;#######################################################################################################################   
;#######################################################################################################################
 #define    Servo_0    LATC,  RC0
 #define    Servo_1    LATC,  RC1
 #define    Servo_2    LATC,  RC2
 #define    Servo_3    LATC,  RC3
 #define    Servo_4    LATC,  RC4
 #define    Servo_5    LATC,  RC5
 #define    Servo_6    LATA,  RA0
 #define    Servo_7    LATA,  RA1

 
; Variable Definitions   
;#######################################################################################################################   
;####################################################################################################################### 
ServoIndex    equ    h'40'
ServoTempH    equ    h'41'
ServoTempL    equ    h'42'

; Interrupt and Program Vector   
;#######################################################################################################################   
;#######################################################################################################################   
RES_VECT  CODE    0x0000            ; processor reset vector
    GOTO    Initial_START           ; go to beginning of program
    
ISR       CODE    0x0004           ; interrupt vector location

;     <Search the device datasheet for 'context' and copy interrupt
;     context saving code here.  Older devices need context saving code,
;     but newer devices like the 16F#### don't need context saving code.>
;
; START Interrupt Program
;#######################################################################################################################   
;#######################################################################################################################       
    banksel PIR6
    btfss   PIR6,   0       
    goto    PeriodInterval
    
PulseInterval:
    banksel PIR6
    bcf        PIR6,   0
    
    banksel ServoIndex
    movf    ServoIndex,W
    banksel PCL
    addwf   PCL,F
    goto    Handle_SERVO_OFF_0
    goto    Handle_SERVO_OFF_1
    goto    Handle_SERVO_OFF_2
    goto    Handle_SERVO_OFF_3
    goto    Handle_SERVO_OFF_4
    goto    Handle_SERVO_OFF_5
    goto    Handle_SERVO_OFF_6
    goto    Handle_SERVO_OFF_7
    
Handle_SERVO_OFF_0:   
    banksel LATC
    bcf        Servo_0   
Handle_SERVO_OFF_1:
    banksel LATC
    bcf        Servo_1   
Handle_SERVO_OFF_2:
    banksel LATC
    bcf        Servo_2   
Handle_SERVO_OFF_3:
    banksel LATC
    bcf        Servo_3   
Handle_SERVO_OFF_4:
    banksel LATC
    bcf        Servo_4   
Handle_SERVO_OFF_5:
    banksel LATC
    bcf        Servo_5   
Handle_SERVO_OFF_6:
    banksel LATA
    bcf        Servo_6 
Handle_SERVO_OFF_7:
    banksel LATA
    bcf        Servo_7   
    
    goto    Interrupt_Done    ; Should never reach this line
    
PeriodInterval:   
    banksel PIR4
    btfss   PIR4,   0
    goto    Interrupt_Done
    
    bcf        PIR4,   0
    
    banksel T1CON    ; Dissable Timer 1
    bcf        T1CON,  0
    
    banksel TMR1H    ; Reset Timer 1 Period Value
    movlw   HIGH TMR1_Period_value
    movwf   TMR1H
    banksel TMR1L
    movlw   LOW TMR1_Period_value
    movwf   TMR1L 
    
    banksel T1CON    ; Enable Timer 1
    bsf        T1CON,  0   
    
;...............................................................................   
        
    banksel ServoIndex        ; Increment ServoIndex and keep range from 0 to 7
    incf    ServoIndex,W
    andlw   b'00000111'
    movwf   ServoIndex
    
    banksel ServoIndex
    movf    ServoIndex,W
    banksel PCL
    addwf   PCL,F
    goto    Handle_SERVO_ON_0
    goto    Handle_SERVO_ON_1
    goto    Handle_SERVO_ON_2
    goto    Handle_SERVO_ON_3
    goto    Handle_SERVO_ON_4
    goto    Handle_SERVO_ON_5
    goto    Handle_SERVO_ON_6
    goto    Handle_SERVO_ON_7   

Handle_SERVO_ON_0:
    banksel LATC            ; Set Servo 0 I/O HIGH
    bsf        Servo_0
    movlw   HIGH Servo0_value        ; Load Servo 0 Time ON Data
    banksel ServoTempH
    movwf   ServoTempH
    movlw   LOW Servo0_value
    banksel ServoTempL
    movwf   ServoTempL
    call    Process_Data
    goto    Interrupt_Done
    
Handle_SERVO_ON_1:
    banksel LATC            ; Set Servo 1 I/O HIGH
    bsf        Servo_1
    movlw   HIGH Servo1_value        ; Load Servo 1 Time ON Data
    banksel ServoTempH
    movwf   ServoTempH
    movlw   LOW Servo1_value
    banksel ServoTempL
    movwf   ServoTempL
    call    Process_Data   
    goto    Interrupt_Done 
    
Handle_SERVO_ON_2:
    banksel LATC            ; Set Servo 2 I/O HIGH
    bsf        Servo_2
    movlw   HIGH Servo2_value        ; Load Servo 2 Time ON Data
    banksel ServoTempH
    movwf   ServoTempH
    movlw   LOW Servo2_value
    banksel ServoTempL
    movwf   ServoTempL
    call    Process_Data   
    goto    Interrupt_Done     
    
Handle_SERVO_ON_3:
    banksel LATC            ; Set Servo 3 I/O HIGH
    bsf        Servo_3
    movlw   HIGH Servo3_value        ; Load Servo 3 Time ON Data
    banksel ServoTempH
    movwf   ServoTempH
    movlw   LOW Servo3_value
    banksel ServoTempL
    movwf   ServoTempL
    call    Process_Data   
    goto    Interrupt_Done   
    
Handle_SERVO_ON_4:
    banksel LATC            ; Set Servo 4 I/O HIGH
    bsf        Servo_4
    movlw   HIGH Servo4_value        ; Load Servo 4 Time ON Data
    banksel ServoTempH
    movwf   ServoTempH
    movlw   LOW Servo4_value
    banksel ServoTempL
    movwf   ServoTempL
    call    Process_Data   
    goto    Interrupt_Done
    
Handle_SERVO_ON_5:
    banksel LATC            ; Set Servo 5 I/O HIGH
    bsf        Servo_5
    movlw   HIGH Servo5_value        ; Load Servo 5 Time ON Data
    banksel ServoTempH
    movwf   ServoTempH
    movlw   LOW Servo5_value
    banksel ServoTempL
    movwf   ServoTempL
    call    Process_Data   
    goto    Interrupt_Done
    
Handle_SERVO_ON_6:
    banksel LATA            ; Set Servo 6 I/O HIGH
    bsf        Servo_6
    movlw   HIGH Servo6_value        ; Load Servo 6 Time ON Data
    banksel ServoTempH
    movwf   ServoTempH
    movlw   LOW Servo6_value
    banksel ServoTempL
    movwf   ServoTempL
    call    Process_Data   
    goto    Interrupt_Done
    
Handle_SERVO_ON_7:
    banksel LATA            ; Set Servo 7 I/O HIGH
    bsf        Servo_7
    movlw   HIGH Servo7_value        ; Load Servo 7 Time ON Data
    banksel ServoTempH
    movwf   ServoTempH
    movlw   LOW Servo7_value
    banksel ServoTempL
    movwf   ServoTempL
    call    Process_Data   
    goto    Interrupt_Done   
    
    ;banksel CCPR1H    ; Set Compare 1 Value
    ;movlw   h'00'
    ;movwf   CCPR1H
    ;banksel CCPR1L
    ;movlw   h'00'
    ;movwf   CCPR1L   

Interrupt_Done:   
    RETFIE
    
Process_Data:   
    banksel ServoTempL                ; Multiply Servo Time by 8
    rlf        ServoTempL,F
    banksel ServoTempH                ; Note: your mileage may vary here...
    rlf        ServoTempH,F            ;       I used a processor running at 32MHz.
    banksel ServoTempL                ;       That means that the instruction time was 125ns
    rlf        ServoTempL,F            ;       To get 1 micro second Resolution I multiplied
    banksel ServoTempH                ;       125ns by 8 which equals 1 micro second.
    rlf        ServoTempH,F            ;       So a processor running at 8MHz could still
    banksel ServoTempL                ;       achieve a 1 microsecond resolution by omitting
    rlf        ServoTempL,F            ;       this section.
    banksel ServoTempH
    rlf        ServoTempH,F
    banksel ServoTempL
    movf    ServoTempL,W
    andlw   b'11111000'
    movwf   ServoTempL   
    
    movlw   LOW TMR1_Period_value        ; Add TMR1_Period_value to ServoTemp
    banksel ServoTempL
    addwf   ServoTempL,F
    btfsc   STATUS,C
    banksel ServoTempH
    incf    ServoTempH,F
    movlw   HIGH TMR1_Period_value
    banksel ServoTempH
    addwf   ServoTempH,F   

    banksel ServoTempL                ; Set Compare Value
    movf    ServoTempL,W
    banksel CCPR1L
    movwf   CCPR1L
    banksel ServoTempH
    movf    ServoTempH,W
    banksel CCPR1H
    movwf   CCPR1H
    
    return
    
MAIN_PROG CODE                      ; let linker place main program
 
Initial_START:
; START Main Program Initialization - Define PORTs
;#######################################################################################################################   
;#######################################################################################################################   
    
    banksel LATA            ; Preset PORTA to all LOW
    clrf    LATA
    
    banksel TRISA
    movlw   b'11111100'
    movwf   TRISA            ; DEFINE ALL pins as INPUT
    
    banksel LATC            ; Preset PORTA to all LOW
    clrf    LATC
    
    banksel TRISC
    movlw   b'11000000'
    movwf   TRISC            ; DEFINE RC0 to RC5 as OUTPUT
    
; START Main Program Initialization - Define Analog vs Digital operation Mode
;#######################################################################################################################   
;#######################################################################################################################   
    
    banksel ANSELA
    movlw   b'00000000'
    ;          XX......    Unimplemented
    ;         ..XX....  RA5 RA4 anaolg or digital select
    ;         ....X...  Unimplemented
    ;         .....XXX  RA2 RA1 RA0 anaolg or digital select
    movwf   ANSELA
    
    banksel ANSELC
    movlw   b'00000000'
    ;          XX......    Unimplemented
    ;         ..XXXXXX  RC5 RC0 anaolg or digital select
    movwf   ANSELC

  
;                TIMER and Interrupt Setup   
;#######################################################################################################################   
;#######################################################################################################################   
    banksel INTCON
    movlw   b'11000000'    ;DEBUG DISABLE
    ;         X.......    Global Interrupt Enable
    ;         .X......  Peripheral Interrupt Enable
    ;         ..XXXXX.  Unimplemented
    ;         .......X  Interrupt Edge Select
    movwf   INTCON
    
    banksel T1CON
    movlw   b'00000011'
    ;          XX......    Unimplemented: Read as '0'
    ;          ..XX....  CKPS<1:0>: Timer1 Input Clock Prescale Select bits
    ;         ....X...  Unimplemented: Read as '0'
    ;         .....X..  SYNC: Timer1 Synchronization Control bit
    ;         ......X.  RD16: 16-bit Read/Write Mode Enable bit
    ;         .......X  ON: Timer1 On bit
    movwf   T1CON
    
    banksel T1CLK
    movlw   b'00000001'
    ;          XXXX....    Unimplemented: Read as '0'
    ;         ....XXXX  CS<3:0>: Timer1 Clock Select bits
    movwf   T1CLK   
    
    banksel PIE4
    movlw   b'00000001'
    ;          XXXXXX..    Unimplemented: Read as '0'
    ;         ......X.    TMR2 to PR2 Match Interrupt Enable
    ;         .......X  Timer1 Interrupt Enable
    movwf   PIE4   
    
 
;                Compare Setup   
;#######################################################################################################################   
;#######################################################################################################################
    
    banksel CCP1CON
    movlw   b'10000010'
    ;          X.......    EN: CCPx Module Enable bit
    ;         .X......  Unimplemented: Read as '0'
    ;         ..X.....  OUT: CCPx Output Data bit (read-only)
    ;         ...X....  FMT: CCPW (Pulse Width) Alignment bit
    ;         ....XXXX  MODE<3:0>: CCPx Mode Select bits(1)
    movwf   CCP1CON 
    
    banksel PIE6
    movlw   b'00000001'
    ;         XXXXXX..  Unimplemented: Read as '0'
    ;         ......X.  CCP2IE: CCP2 Interrupt Enable bit
    ;         .......X  CCP1IE: CCP1 Interrupt Enable bit
    movwf   PIE6

    
;   How to set Period interval   
;#######################################################################################################################   
;#######################################################################################################################   
    
;  TMR1_Period_value = 65535 - ( ( Period * Fosc/4 ) / PreScale )
;
;  Period ... Normal 20ms Period for ONE channel is 0.02 ... divide .02 by 8 for 8 Channels to obtain 0.0025 Period Value
;   
;  TMR1_Period_value = 65535 - ( ( 0.0025 * 32000000/4 ) / 1 )
;   
;  TMR1_Period_value = 65535 - ( ( 0.0025 *  8000000 ) / 1 )
;   
;  TMR1_Period_value = 65535 - ( 20000 / 1 )   
; 
;  TMR1_Period_value = 45535 
    TMR1_Period_value        equ        d'45535'   
        
;                                    Note: Due to overhead requirements, running at 32MHz, the shortest pulse is 133 us and the longest pulse is 2495 us       

    Servo0_value        equ        d'500'    ;micro seconds
    Servo1_value        equ        d'787'    ;micro seconds
    Servo2_value        equ        d'1071'    ;micro seconds
    Servo3_value        equ        d'1357'    ;micro seconds   
    Servo4_value        equ        d'1643'    ;micro seconds
    Servo5_value        equ        d'1929'    ;micro seconds
    Servo6_value        equ        d'2214'    ;micro seconds
    Servo7_value        equ        d'2495'    ;micro seconds        <--- Note cannot exceed Period Value PLUS some overhead   
    
Main_Program:
; START Main Program
;#######################################################################################################################   
;#######################################################################################################################   
    GOTO    Main_Program

    END
 
Hey, Beau. Nice effort. Can you handle an error report and some constructive criticism? If not, please say so and I will go away...

Cheerful regards, Mike
 
That's fine Mike I can take it .. I put this together rather quickly and tested it on a scope for reasonable accuracy
 
Ok, well... the error is in the Process_Data subroutine. Where you add the low values of Timer_Period_value and ServoTempL and check for overflow. Your bit test and skip instruction is skipping over a banksel opcode and the effect is that you're incrementing ServoTempH unconditionally. I'm also curious why you would use this Process_Data subroutine instead of just setting the TMR1 prescaler value to 8?

Everything else is probably method and style, which is highly subjective. For example, you can eliminate many of your 'banksel' statements by simply keeping track of which bank you're in. A quick analysis reveals some simple and effective optimizations. For example;
Code:
; START Interrupt Program
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    banksel PIR6            ; bank 14                         |14
    btfss   PIR6,CCP1IF     ; match irq? yes, skip, else      |14
    goto    PeriodInterval  ; branch (check TMR1IF)           |14
PulseInterval:
    bcf     PIR6,CCP1IF     ; clear the interrupt flag and    |14
    banksel LATC            ; bank 00                         |00
    clrf    LATC            ; turn servo output off           |00
    goto    Interrupt_done  ;                                 |00
At end-of-period (TMR1 overflow at 2.5-ms) you turn off TMR1, reset the period and setup the new on-time compare values, then turn TMR1 back on a full 13 cycles before you set the Servo output pin. That 1.625-uS delay may not be noticeable or problematic in this application, but, if it was, I would consider turning TMR1 back on immediately before or after the servo pin.

May I ask if you're using the ADDWF PCL,F table method in this example instead of the BRW table method because the OA is using a 16F877A target?

I don't mean to 'bash' your work or effort. It's a good example. Thank you.

Cheerful regards, Mike
 
"Your bit test and skip instruction is skipping over a banksel opcode and the effect is that you're incrementing ServoTempH unconditionally " - ahh good catch
Code:
    ;WAS
    movlw   LOW TMR1_Period_value        ; Add TMR1_Period_value to ServoTemp
    banksel ServoTempL
    addwf   ServoTempL,F
    btfsc   STATUS,C
    banksel ServoTempH
    incf    ServoTempH,F
    movlw   HIGH TMR1_Period_value
    banksel ServoTempH
    addwf   ServoTempH,F
    
    ;SHOULD BE
    movlw   LOW TMR1_Period_value        ; Add TMR1_Period_value to ServoTemp
    banksel ServoTempL
    addwf   ServoTempL,F
    banksel ServoTempH
    btfsc   STATUS,C
    incf    ServoTempH,F
    movlw   HIGH TMR1_Period_value
    banksel ServoTempH
    addwf   ServoTempH,F


"I'm also curious why you would use this Process_Data subroutine instead of just setting the TMR1 prescaler value to 8 " .... No particular reason other than I thought it would be easier for the OP to just remark the section that is multiplying by 8 rather than fiddle with the prescaler

"you can eliminate many of your 'banksel' statements by simply keeping track of which bank you're in. " - As I said I just put this together in an hour or so and typically do this optimization towards the end... The reason is that code can easily shuffle around throughout the development process. A missed bankselect can cause a bit of a headache if not encapsulated properly. Fortunately the one you pointed out is in the same bank, but the erroneous incrementing was definitely a problem.

"At end-of-period (TMR1 overflow at 2.5-ms) you turn off TMR1, reset the period and setup the new on-time compare values, then turn TMR1 back on a full 13 cycles before you set the Servo output pin. That 1.625-uS delay may not be noticeable or problematic in this application, but, if it was, I would consider turning TMR1 back on immediately before or after the servo pin. " - There is no buffering with the TMR1 register ... The datasheet suggested turning OFF the timer while making changes to the value before turning it back on. Since the OFF time is not as critical with controlling a Servo, I didn't bother compensating for the lost clock cycles.

"May I ask if you're using the ADDWF PCL,F table method in this example instead of the BRW table method because the OA is using a 16F877A target? " -
In the "PulseInterval" and the "Servo Indexing" I am using the ADDWF PCL,F table method .... the BRW instruction is not available in the 16F877A or the PIC16F15323 that I am using.

"I don't mean to 'bash' your work or effort. It's a good example. Thank you. " - No problem, I ran the Parallax forum for almost 20 years. There isn't much that bothers me.
 
You're welcome, Beau.

BTW, the BRW instruction is available on all the "enhanced mid-range" devices, including the 16F15323...

Cheerful regards, Mike
 
Mike - K8LH ,

We went to this rodeo over a decade ago:



Perhaps the youngsters here would benefit from from what a couple of old guys talked about.
 
Hi, Dan. Yeah, I often wonder if anyone takes time to look at that stuff? I used to spend hours and hours looking at code examples on PICLIST and elsewhere...

Take care. Cheerful regards, Mike, K8LH
 
The link I posted in post #5 is to an 11 year old thread.
If we are going to discuss how old we've gotten Mike - K8LH and I started talking about this in September 2007. Over 13 years ago.

Here's the demo code I did back then. Updated to use the "new" tool chain from Microchip:
C:
/*
* Author: DAN1138
* Date: 2020-JAN-3
* File: main.c
* Target: PIC16F877A
* OS: Win7 Pro, 64-bit
* IDE: MPLABX v5.25
* Compiler: XC8 1.45 (free)
*
* Application: Use PWM to drive one RC servo from analog voltage present on AN0
*
*   RA0 - Analog ADC input from potentiometer
*
*   RC2 - CCP1, pulse output for servos
*/

/* Set up the configuration bits */
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#include <xc.h>

/*
* 4MHz crystal
* FOSC set for 4MHz
*/
#define FOSC 4000000L       /* target device system clock freqency */
#define FCYC (FOSC/4L)      /* target device instruction clock freqency */

#define _XTAL_FREQ FOSC     /* required for HITECH PICC delay macros */

#define NUMBER_OF_SERVOS 1
#define SERVO1_INDEX 0

/*
* These defines are used to configure this application for your needs.
*
* FCYC
* TMR2_PRESCALE
* PWM_MAX
* NUMBER_OF_SERVOS
*
* The default values are tuned for a 8MHz instruction clock and eight servos.
*
* After each servo has completed its pulse we need to take one
* PWM period to update the servo multiplexor.
*
* You need to make sure that time period for updating all the servos
* pulse an extra PWM period for each servo is less than 20 milliseconds.
*
* Making PWM_MAX smaller will make the extra PWM period for each servo shorter.
* The tradeoff is that we get interrupted more often to service TMR2.
*
*/

/*
* PWM_MAX must be >= 1 and <= 255.
* small values (< 100) will cause the TMR2 interrupt
* to occur so often they are kind of useless.
*
*/
#define TMR2_PRESCALE 4L
#define PWM_MAX (250L)

#define TCYC_ns (1000000000L / FCYC)
#define PWM_PERIOD_us ((TCYC_ns * TMR2_PRESCALE * PWM_MAX) / 1000L)

/*
* The update rate for our servos is 20.000 milliseconds. (50Hz)
* So this sets the number of PWM ticks in 20,000 microseconds
* SERVO_UPDATE_IN_PWM_PERIODS must be <= 65535 to fit into an unsigned int.
*
*/
#define SERVO_UPDATE_IN_PWM_PERIODS ((20000L + (PWM_PERIOD_us / 2L)) / PWM_PERIOD_us)

/*
* Position min and max are 16-bit unsigned values
*
* Define the limits for a particular servo
* The servo rotates to the "left" limit for a pulse of 0.500 milliseconds.
* The servo rotates to the "right" limit for a pulse of 2.000 milliseconds.
*
*/
#define POSITION_MIN ((500L  * (PWM_MAX * 4)) / PWM_PERIOD_us)
#define POSITION_MAX ((2000L * (PWM_MAX * 4)) / PWM_PERIOD_us)


void init (void);

void update_position(unsigned int  *servo_period,unsigned char *dir, unsigned int  delta);


/*
* Timer 2 interrupt handler
*
* This handler updates PWM one for a typical
* pulse output to control a common analog
* model radio control servo.
*
*/

unsigned int servo_bank_one[NUMBER_OF_SERVOS];

unsigned int update_tick_count;

struct {
    unsigned char updated:1;
    unsigned char one_complete:1;
    unsigned char select_next:1;
    unsigned char select;
} servo;


unsigned char IsServoUpdateComplete(void)
{
    if (servo.updated)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

void ResetServoUpdateComplete(void)
{
    servo.updated = 0;
}

void SetServoPosition(unsigned char servo_number, unsigned int  servo_position)
{
    if (servo_number < NUMBER_OF_SERVOS)
    {
        /* Do not allow PWM interrupts while */
        /* updating servo position array. */
        /* Start critical section. */
        PIE1bits.TMR2IE = 0;
        servo_bank_one[servo_number] = servo_position;
        PIE1bits.TMR2IE = 1;
        /* End critical section. */
    }
}

void TMR2_handler(void)
{
    static unsigned int temp_period_one;

    if (PIE1bits.TMR2IE)   /* test if T2 interrupt is enabled */
    {
        if (PIR1bits.TMR2IF) /* test if T2 interrupt is asserted */
        {
            PIR1bits.TMR2IF = 0;  /* reset T2 assertion */

            if(servo.select_next)
            {
                servo.select_next = 0;
                if (++servo.select < NUMBER_OF_SERVOS)
                {
                    temp_period_one = servo_bank_one[servo.select];
                }
                else
                {
                    servo.select = 0;
                }
            }

            if (servo.one_complete)
            {
                servo.one_complete = 0;
                servo.select_next = 1;
            }

            if (update_tick_count == 0)
            {
                update_tick_count = SERVO_UPDATE_IN_PWM_PERIODS;
                temp_period_one = servo_bank_one[0];
                servo.updated = 1;
            }
            --update_tick_count;

            CCP1CONbits.CCP1Y = 0;
            CCP1CONbits.CCP1X = 0;
            if (temp_period_one > (PWM_MAX * 4))
            {
                CCPR1L = PWM_MAX;
                temp_period_one -= (PWM_MAX * 4);
            }
            else
            {

                if (temp_period_one != 0)
                {
                    servo.one_complete = 1;
                    if (temp_period_one & 0b00000001)
                    {
                        CCP1CONbits.CCP1Y = 1;
                    }
                    if (temp_period_one & 0b00000010)
                    {
                        CCP1CONbits.CCP1X = 1;
                    }
                    temp_period_one >>= 2;
                    CCPR1L  = (unsigned char)(temp_period_one);
                    temp_period_one = 0;
                }
                else
                {
                    CCPR1L  = 0;
                }
            }
        }
    }
}

/* initialize the PWM and interrupt on T2 overflow */
void TMR2_init(void)
{
    PIE1bits.TMR2IE = 0;    /* turn off TMR2 interrupt */
    TRISC &= (0b11111011);  /* make RC2 output for PWM */

#if (TMR2_PRESCALE == 1)
    T2CON = 0b00000000; /* postscaler 1:1 */
    /* TMR2 off */
    /* prescaler 1:1 */
#endif
#if (TMR2_PRESCALE == 4)
    T2CON = 0b00000001; /* postscaler 1:1 */
    /* TMR2 off */
    /* prescaler 1:4 */
#endif
#if (TMR2_PRESCALE == 16)
    T2CON = 0b00000010; /* postscaler 1:1 */
    /* TMR2 off */
    /* prescaler 1:16 */
#endif

    TMR2 = 0;
    PR2 = PWM_MAX-1;        /* set PWM period */

    CCP1CON = 0b00001100;   /* PWM mode */
    CCPR1L  = 0;

    update_tick_count = 0;
    servo.updated = 0;
    servo.select_next = 0;
    for (servo.select=0; servo.select < NUMBER_OF_SERVOS; ++servo.select)
    {
        servo_bank_one[servo.select] = POSITION_MAX;
    }
    servo.select=0;

    T2CONbits.TMR2ON = 1;   /* turn on TMR2 */
    PIE1bits.TMR2IE  = 1;   /* turn on TMR2 interrupt */
}


/*----------------------------------------------*/
/* interrupt handlers                           */
/*----------------------------------------------*/

void interrupt Interrupt_Handlers(void)
{
    TMR2_handler();
}

/*----------------------------------------------*/
/* Initialize this PIC                          */
/*----------------------------------------------*/
void init (void)
{

    /* disable all interrupt sources */
    INTCON = 0x00;
    PIE1 = 0x00;
    PIE2 = 0x00;

    ADCON0 = 0xC1;          /* turn on ADC module */
                            /* TAD = ADCRC */
                            /* Select AN0 as input */
    ADCON1 = 0x8E;          /* set port A AN0 for analog input all others for digital I/O */
    /* ADC results are right justified */

    OPTION_REG = 0b11011110;/* PORTB weak pull ups disabled */
                            /* Interrupt on rising edge of RB0/INT pin */
                            /* T0 internal clock source */
                            /* T0 clock edge high-to-low */
                            /* Prescaler assigned to WDT */
                            /* Prescale 1:64 for WDT */


    /* initialize my interrupt handlers */
    TMR2_init();

    /* turn on the interrupt system */
    PEIE = 1;
    GIE  = 1;
}

/*----------------------------------------------*/
/* Main application                             */
/*----------------------------------------------*/
#define SERVO_UPDATES_PER_SECOND 50
#define ADC_MAX_VALUE 1023L
#define SW2 PORTAbits.RA4
#define SW3 PORTAbits.RA5

void main (void)
{
    unsigned int servo_one;
    unsigned long PotPosition;
    unsigned long increment;
    unsigned char Pause;

    init();

    /*
     * This is a simple servo movement test that
     * sets the position servo based on the analog
     * voltage present on AN0.
     */
    /* initial servo position at mid range */
    servo_one = ((POSITION_MAX - POSITION_MIN) / 2) + POSITION_MIN;

    increment = 0;
    Pause = 0;
    while(1)
    {
        if (IsServoUpdateComplete())
        {
            SetServoPosition(SERVO1_INDEX,servo_one);
            ResetServoUpdateComplete();

            if(SW2 && SW3)
            {
                ADCON0bits.GO = 1;
                while(ADCON0bits.GO) continue;
                PotPosition = ADRESH;
                PotPosition = PotPosition << 8;
                PotPosition = PotPosition | ADRESL;
                servo_one = (unsigned int)( (((ADC_MAX_VALUE - PotPosition) * (POSITION_MAX - POSITION_MIN)) / ADC_MAX_VALUE) + POSITION_MIN );
            }
            else
            {
                if(!SW2 && SW3)
                {
                    if (Pause == 0)
                    {
                        servo_one = (unsigned int)( ((increment * (POSITION_MAX - POSITION_MIN)) / ADC_MAX_VALUE) + POSITION_MIN );
                        Pause = SERVO_UPDATES_PER_SECOND / 50;
                        increment++;
                        if (increment > ADC_MAX_VALUE)
                        {
                            increment = 0;
                        }
                    }
                    else
                    {
                        Pause--;
                    }
                }
                else
                {
                    if(SW2 && !SW3)
                    {
                        servo_one = ((POSITION_MAX - POSITION_MIN) / 2) + POSITION_MIN;
                    }
                }
            }
        }
    }
}
Yes I tested this and it still works with a PIC16F877A in a PICDEM2 PLUS demo board.

Mike's implementation is simpler, but that is his code to post or not as he desires.
 
Last edited:
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…