PIC24 Support Libraries
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
outputcompare_multiservo.c
Go to the documentation of this file.
1 /*
2  * "Copyright (c) 2008 Robert B. Reese, Bryan A. Jones, J. W. Bruce ("AUTHORS")"
3  * All rights reserved.
4  * (R. Reese, reese_AT_ece.msstate.edu, Mississippi State University)
5  * (B. A. Jones, bjones_AT_ece.msstate.edu, Mississippi State University)
6  * (J. W. Bruce, jwbruce_AT_ece.msstate.edu, Mississippi State University)
7  *
8  * Permission to use, copy, modify, and distribute this software and its
9  * documentation for any purpose, without fee, and without written agreement is
10  * hereby granted, provided that the above copyright notice, the following
11  * two paragraphs and the authors appear in all copies of this software.
12  *
13  * IN NO EVENT SHALL THE "AUTHORS" BE LIABLE TO ANY PARTY FOR
14  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
15  * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE "AUTHORS"
16  * HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17  *
18  * THE "AUTHORS" SPECIFICALLY DISCLAIMS ANY WARRANTIES,
19  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
21  * ON AN "AS IS" BASIS, AND THE "AUTHORS" HAS NO OBLIGATION TO
22  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
23  *
24  * Please maintain this header in its entirety when copying/modifying
25  * these files.
26  *
27  *
28  */
29 #include "pic24_all.h"
30 #include <stdio.h>
31 
32 /** \file
33 Demonstrates pulse width modulation using four digital outputs
34 and the OC1 module to create four PWM outputs for hobby servos.
35 A table is used to control the pulse widths of the four servos.
36 For more accuracy, use an external crystal and define the following
37 CLOCK_CONFIG=PRIPLL_8MHzCrystal_40MHzFCY in the MPLAB project.
38 Remove this macro if you wish to use the internal oscillator.
39 
40 The PIC24E/dsPIC33E code is differs from other families.
41 */
42 
43 #define PWM_PERIOD 20000 //in microseconds
44 
45 void configTimer2(void) {
46  T2CON = T2_OFF | T2_IDLE_CON | T2_GATE_OFF
47  | T2_32BIT_MODE_OFF
48  | T2_SOURCE_INT
49  | T2_PS_1_256 ; //1 tick = 1.6 us at FCY=40 MHz
50  PR2 = usToU16Ticks(PWM_PERIOD, getTimerPrescale(T2CONbits)) - 1;
51  TMR2 = 0; //clear timer2 value
52 }
53 
54 //just pick four digital outputs
55 #define NUM_SERVOS 4
56 #define SERVO0 _LATB2
57 #define SERVO1 _LATB3
58 #define SERVO2 _LATB13
59 #define SERVO3 _LATB14
60 
61 #define MIN_PW 600 //minimum pulse width, in us
62 #define MAX_PW 2400 //minimum pulse width, in us
63 #define SLOT_WIDTH 2800 //slot width, in us
64 
65 volatile uint16_t au16_servoPWidths[NUM_SERVOS];
66 volatile uint8_t u8_currentServo =0;
67 volatile uint8_t u8_servoEdge = 1; //1 = RISING, 0 = FALLING
68 volatile uint16_t u16_slotWidthTicks = 0;
69 
70 void initServos(void) {
71  uint8_t u8_i;
72  uint16_t u16_initPW;
73 
74  u8_currentServo = 0;
75  CONFIG_RB2_AS_DIG_OUTPUT();
76  CONFIG_RB3_AS_DIG_OUTPUT();
77  CONFIG_RB13_AS_DIG_OUTPUT();
78  CONFIG_RB14_AS_DIG_OUTPUT();
79  u16_initPW = usToU16Ticks(MIN_PW + (MAX_PW-MIN_PW)/2, getTimerPrescale(T2CONbits));
80 
81  //config all servos for half maximum pulse width
82  for (u8_i=0; u8_i<NUM_SERVOS; u8_i++) au16_servoPWidths[u8_i]=u16_initPW;
83  SERVO0 = 0; //all servo outputs low initially
84  SERVO1 = 0;
85  SERVO2 = 0;
86  SERVO3 = 0; //outputs initially low
87  u16_slotWidthTicks = usToU16Ticks(SLOT_WIDTH, getTimerPrescale(T2CONbits));
88 }
89 
90 
91 
92 void setServoOutput (uint8_t u8_servo, uint8_t u8_val) {
93  switch (u8_servo) {
94  case 0:
95  SERVO0 = u8_val;
96  break;
97  case 1:
98  SERVO1 = u8_val;
99  break;
100  case 2:
101  SERVO2 = u8_val;
102  break;
103  case 3:
104  SERVO3 = u8_val;
105  break;
106  default:
107  break;
108  }
109 }
110 #if (defined(__dsPIC33E__) || defined(__PIC24E__))
111 //use Timer2 interrupt to kick off the first servo output,
112 //the OC1 interrupt handles the rest.
113 void _ISRFAST _T2Interrupt (void) {
114  _T2IF = 0; //clear the timer interrupt bit
115  u8_currentServo = 0;
116  setServoOutput(u8_currentServo, 1);
117  OC1R = au16_servoPWidths[u8_currentServo];
118  OC1RS = au16_servoPWidths[u8_currentServo];
119  _OC1IF = 0;
120  _OC1IP = 1;
121  _OC1IE = 1; //enable the OC1 interrupt
122 //turn on the compare toggle mode using Timer2
123  OC1CON1 = OC_TIMER2_SRC | //Timer2 source
124  OC_TOGGLE_PULSE; //use toggle mode, just care about compare event
125  OC1CON2 = 0x001F; //reset internal timer when OCxRS match occurs
126 }
127 
128 void _ISR _OC1Interrupt(void) {
129  _OC1IF = 0;
130 //change the servo's value
131  setServoOutput(u8_currentServo, 0); //turn off current servo
132  u8_currentServo++; //increment to next servo
133  if (u8_currentServo != NUM_SERVOS) {
134  setServoOutput(u8_currentServo, 1); //turn on this servo
135  OC1R = au16_servoPWidths[u8_currentServo]; //set the pulse width
136  OC1RS = au16_servoPWidths[u8_currentServo];
137  } else {
138  //last servo, disable OC1
139  OC1CON1 = 0x0; //done disable the OC1 module
140  _OC1IE = 0; //disable the OC1 interrupt
141  }
142 }
143 
144 //this does nothing since configuration done in Timer2 interrupt.
145 void configOutputCapture1(void) {}
146 
147 
148 
149 #else
150 void _ISR _OC1Interrupt(void) {
151  _OC1IF = 0;
152 //change the servo's value
153  setServoOutput(u8_currentServo, u8_servoEdge);
154 //schedule next interrupt
155  if (u8_servoEdge == 1) { //rising edge
156  //next interrupt occurs after pulse width has elapsed
157  OC1R = OC1R + au16_servoPWidths[u8_currentServo];
158  u8_servoEdge = 0; //change to falling edge
159  } else { //falling edge
160  //next interrupt occurs at beginning of next slot
161  if (u8_currentServo != NUM_SERVOS -1)
162  OC1R = u16_slotWidthTicks*(u8_currentServo+1);
163  else //last servo!
164  OC1R = 0;
165  u8_servoEdge = 1; //change to rising edge
166  u8_currentServo++;
167  if (u8_currentServo == NUM_SERVOS) u8_currentServo = 0;
168  }
169 }
170 
171 
172 void configOutputCapture1(void) {
173  T2CONbits.TON = 0; //disable Timer when configuring Output compare
174  OC1R = 0; //initialize to 0
175 //turn on the compare toggle mode using Timer2
176  OC1CON = OC_TIMER2_SRC | //Timer2 source
177  OC_TOGGLE_PULSE; //use toggle mode, just care about compare event
178  _OC1IF = 0;
179  _OC1IP = 1;
180  _OC1IE = 1; //enable the OC1 interrupt
181 }
182 #endif
183 
184 
185 char sz_buf[32];
186 
187 void getServoValue(void) {
188  int16_t u16_servo;
189  int16_t u16_pw;
190  printf("Choose servo (1,2,3,4): ");
191  inStringEcho(sz_buf,31);
192  sscanf(sz_buf,"%d",(int *) &u16_servo);
193  if ((u16_servo > 4) || (u16_servo < 1)) {
194  printf("Invalid servo..\n");
195  return;
196  }
197  printf("Enter pulse width (min 600, max 2400): ");
198  inStringEcho(sz_buf,31);
199  sscanf(sz_buf,"%d",(int *) &u16_pw);
200  if ((u16_pw > 2400) || (u16_pw < 600)) {
201  printf("Invalid pulse width..\n");
202  return;
203  }
204 #if (defined(__dsPIC33E__) || defined(__PIC24E__))
205 //set the pulse width
206  _T2IE = 0; //enable the timer interrupt
207  _OC1IE = 0; //disable the interrupt while changing
208  au16_servoPWidths[u16_servo-1]=usToU16Ticks(u16_pw, getTimerPrescale(T2CONbits));
209  _OC1IE = 1;
210  _T2IE = 0; //enable the timer interrupt
211 #else
212  //set the pulse width
213  _OC1IE = 0; //disable the interrupt while changing
214  au16_servoPWidths[u16_servo-1]=usToU16Ticks(u16_pw, getTimerPrescale(T2CONbits));
215  _OC1IE = 1;
216 #endif
217 }
218 
219 int main(void) {
220  configBasic(HELLO_MSG);
221  configTimer2();
222  initServos();
223  configOutputCapture1();
224 #if (defined(__dsPIC33E__) || defined(__PIC24E__))
225  _T2IP = 1; //choose a priority
226  _T2IE = 1; //enable the timer interrupt
227 #endif
228  T2CONbits.TON = 1; //turn on Timer2 to start PWM
229 
230  while (1) {
231  getServoValue(); //prints menu, gets new servo value from console.
232  }
233 }