outcompare_multiservo.c - Demonstrates pulse width modulation using four digital outputs and the OC1 module to create four PWM outputs for hobby servos.ΒΆ
Demonstrates pulse width modulation using four digital outputs and the OC1 module to create four PWM outputs for hobby servos. A table is used to control the pulse widths of the four servos.
#include "pic24_all.h"
#include <stdio.h>
#define PWM_PERIOD 20000 //in microseconds
void configTimer2(void) {
T2CON = T2_OFF | T2_IDLE_CON | T2_GATE_OFF
| T2_32BIT_MODE_OFF
| T2_SOURCE_INT
| T2_PS_1_256 ; //1 tick = 1.6 us at FCY=40 MHz
PR2 = usToU16Ticks(PWM_PERIOD, getTimerPrescale(T2CONbits)) - 1;
TMR2 = 0; //clear timer2 value
}
//just pick four digital outputs
#define NUM_SERVOS 4
#define SERVO0 _LATB2
#define SERVO1 _LATB3
#define SERVO2 _LATB13
#define SERVO3 _LATB14
#define MIN_PW 600 //minimum pulse width, in us
#define MAX_PW 2400 //minimum pulse width, in us
#define SLOT_WIDTH 2800 //slot width, in us
volatile uint16_t au16_servoPWidths[NUM_SERVOS];
volatile uint8_t u8_currentServo =0;
volatile uint8_t u8_servoEdge = 1; //1 = RISING, 0 = FALLING
volatile uint16_t u16_slotWidthTicks = 0;
void initServos(void) {
uint8_t u8_i;
uint16_t u16_initPW;
u8_currentServo = 0;
CONFIG_RB2_AS_DIG_OUTPUT();
CONFIG_RB3_AS_DIG_OUTPUT();
CONFIG_RB13_AS_DIG_OUTPUT();
CONFIG_RB14_AS_DIG_OUTPUT();
u16_initPW = usToU16Ticks(MIN_PW + (MAX_PW-MIN_PW)/2, getTimerPrescale(T2CONbits));
//config all servos for half maximum pulse width
for (u8_i=0; u8_i<NUM_SERVOS; u8_i++) au16_servoPWidths[u8_i]=u16_initPW;
SERVO0 = 0; //all servo outputs low initially
SERVO1 = 0;
SERVO2 = 0;
SERVO3 = 0; //outputs initially low
u16_slotWidthTicks = usToU16Ticks(SLOT_WIDTH, getTimerPrescale(T2CONbits));
}
void setServoOutput (uint8_t u8_servo, uint8_t u8_val) {
switch (u8_servo) {
case 0:
SERVO0 = u8_val;
break;
case 1:
SERVO1 = u8_val;
break;
case 2:
SERVO2 = u8_val;
break;
case 3:
SERVO3 = u8_val;
break;
default:
break;
}
}
void _ISR _OC1Interrupt(void) {
_OC1IF = 0;
//change the servo's value
setServoOutput(u8_currentServo, u8_servoEdge);
//schedule next interrupt
if (u8_servoEdge == 1) { //rising edge
//next interrupt occurs after pulse width has elapsed
OC1R = OC1R + au16_servoPWidths[u8_currentServo];
u8_servoEdge = 0; //change to falling edge
} else { //falling edge
//next interrupt occurs at beginning of next slot
if (u8_currentServo != NUM_SERVOS -1)
OC1R = u16_slotWidthTicks*(u8_currentServo+1);
else //last servo!
OC1R = 0;
u8_servoEdge = 1; //change to rising edge
u8_currentServo++;
if (u8_currentServo == NUM_SERVOS) u8_currentServo = 0;
}
}
void configOutputCapture1(void) {
T2CONbits.TON = 0; //disable Timer when configuring Output compare
OC1R = 0; //initialize to 0
//turn on the compare toggle mode using Timer2
#ifdef OC1CON1
OC1CON1 = OC_TIMER2_SRC | //Timer2 source
OC_TOGGLE_PULSE; //single compare toggle, just care about compare event
OC1CON2 = OC_SYNCSEL_TIMER2; //synchronize to timer2
#else
OC1CON = OC_TIMER2_SRC | //Timer2 source
OC_TOGGLE_PULSE; //single compare toggle, just care about compare event
#endif
_OC1IF = 0;
_OC1IP = 1;
_OC1IE = 1; //enable the OC1 interrupt
}
char sz_buf[32];
void getServoValue(void) {
int16_t u16_servo;
int16_t u16_pw;
printf("Choose servo (1,2,3,4): ");
inStringEcho(sz_buf,31);
sscanf(sz_buf,"%d",(int *) &u16_servo);
if ((u16_servo > 4) || (u16_servo < 1)) {
printf("Invalid servo..\n");
return;
}
printf("Enter pulse width (min 600, max 2400): ");
inStringEcho(sz_buf,31);
sscanf(sz_buf,"%d",(int *) &u16_pw);
if ((u16_pw > 2400) || (u16_pw < 600)) {
printf("Invalid pulse width..\n");
return;
}
//set the pulse width
_OC1IE = 0; //disable the interrupt while changing
au16_servoPWidths[u16_servo-1]=usToU16Ticks(u16_pw, getTimerPrescale(T2CONbits));
_OC1IE = 1;
}
int main(void) {
configBasic(HELLO_MSG);
configTimer2();
initServos();
configOutputCapture1();
T2CONbits.TON = 1; //turn on Timer2 to start PWM
while (1) {
getServoValue(); //prints menu, gets new servo value from console.
}
}