@FourZeroFive
Este código puede controlar cuatro servos de tipo pasatiempo modulado por impulsos como el SG90. Es una implementación complicada, por lo que es posible que no puedas ver cómo funciona.
Este es un ejemplo, no un tutorial sobre cómo usar el PICM PW16F690 para controlar los servos RC hobby.
/*
* Date: 2018-SEPTEMBER-12
* File: main.c
* Target: PIC16F690
* MPLAB: 8.92
* Compiler: XC8 v1.45
* Application: Use PWM to drive two RC servos
*
* PIC16F690
* +----------:_:----------+
* PWR -> 1 : VDD VSS : 20 <- GND
* <> 2 : RA5 PGD/AN0/RA0 : 19 <> PGD
* <> 3 : RA4/AN3 PGC/AN1/RA1 : 18 <> PGC
* VPP -> 4 : RA3/VPP AN2/RA2 : 17 <>
* SERVO1 <- 5 : RC5/P1A AN4/RC0 : 16 <>
* SERVO2 <- 6 : RC4/P1B AN5/RC1 : 15 <>
* SERVO3 <- 7 : RC3/P1C P1D/RC2 : 14 -> SERVO4
* <> 8 : RC6 RB4 : 13 <>
* <> 9 : RC7 RXD/RB5 : 12 <> RXD
* TXD <> 10 : RB7/TXD RB6 : 11 <>
* +-----------------------:
* DIP-20
*
* Use ECCP in PWM mode with pulse steering to one of four outputs
*/
#pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = OFF, MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF, IESO = OFF, FCMEN = OFF
#include <xc.h>
/* Helper macros */
#define clear_bit( reg, bitNumb ) ((reg) &= ~(1 << (bitNumb)))
#define set_bit( reg, bitNumb ) ((reg) |= (1 << (bitNumb)))
#define test_bit( reg, bitNumb ) ((reg) & (1 << (bitNumb)))
#define clear_wdt() CLRWDT()
#define nop() NOP()
/* HITECH PICC-Lite specific notes:
*
* To get the constants to be computed correctly by
* the compiler we specify all constants to be
* UNSIGNED LONG. To do this we appended "ul" to the
* constants used in these expressions.
*
* HITECH PICC has a "feature" that does not honor
* an explicit type cast of these kinds of constant
* expressions.
*/
/*
* These defines are used to configure this application for your needs.
*
* TMR2_PRESCALE
* PWM_MAX
* FOSC
* NUMBER_OF_SERVOS
*
* The default values are tuned for a 4MHz crystal and four servos.
*
* After servo has completed their pulse we need to take one
* PWM period to update the servo multiplexer.
*
* 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 trade-off 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 1
#define PWM_MAX (250ul)
#define FOSC 4000000ul
#define NUMBER_OF_SERVOS 4
#define TOSC_ns (1000000000ul / FOSC)
#define PWM_PERIOD_us ((TOSC_ns * 4ul * TMR2_PRESCALE * PWM_MAX) / 1000ul)
/* The update rate for our servos is 22.222 milliseconds. (45Hz)
* So this sets the number of PWM ticks in 22,222 microseconds
* SERVO_UPDATE_IN_PWM_PERIODS must be <= 65535 to fit into an unsigned int.
*/
#define SERVO_UPDATE_IN_PWM_PERIODS ((22222ul + (PWM_PERIOD_us / 2)) / 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.800 milliseconds.
* The servo rotates to the "right" limit for a pulse of 2.200 milliseconds.
*/
#define POSITION_MIN ((800ul * (PWM_MAX * 4ul)) / PWM_PERIOD_us)
#define POSITION_MAX ((2200ul * (PWM_MAX * 4ul)) / PWM_PERIOD_us)
/* Timer 2 interrupt handler
*
* This handler updates PWM one and PWM two
* for a typical pulse output to control a
* common analog model radio control servo
*
* This example code uses a T2 prescaler of 1:1
* and a PWM_MAX of 250. This means that half of
* the available clock cycles are used just for
* Timer 2 interrupt service.
*
* As the value for PWM_MAX gets closer to the
* worst case time a larger portion of real time
* is used just to deal with the Timer 2 interrupt.
*
* As the value for PWM_MAX gets closer to the
* best case time the ISR will fail and all of
* real time is spent thrashing in the ISR.
*
*/
unsigned int servo_bank[NUMBER_OF_SERVOS];
unsigned int update_tick_count;
bit servo_updated;
bit servo_bank_output_complete;
bit servo_select_next;
unsigned char servo_select;
static unsigned char T2_handler(void)
{
static unsigned int temp_period_one;
if (TMR2IE) /* test if T2 interrupt is enabled */
{
if (TMR2IF) /* test if T2 interrupt is asserted */
{
TMR2IF = 0; /* reset T2 assertion */
if(servo_select_next)
{
servo_select_next = 0;
if (++servo_select < NUMBER_OF_SERVOS)
{
PSTRCON = (PSTRCON & 0xF0) | ((PSTRCON << 1) & 0x0F);
temp_period_one = servo_bank[servo_select];
}
}
if (servo_bank_output_complete)
{
{
servo_bank_output_complete = 0;
servo_select_next = 1;
}
}
if (update_tick_count == 0)
{
update_tick_count = SERVO_UPDATE_IN_PWM_PERIODS;
servo_select = 0;
PSTRCON = 0b00010001; /* PWM pulse steering P1A is first servo */
temp_period_one = servo_bank[0];
servo_updated = 1;
}
--update_tick_count;
DC1B1 = 0;
DC1B0 = 0;
if (temp_period_one > (PWM_MAX * 4))
{
CCPR1L = PWM_MAX;
temp_period_one -= (PWM_MAX * 4);
}
else
{
if (temp_period_one != 0)
{
servo_bank_output_complete = 1;
if (test_bit(temp_period_one,0)) DC1B0 = 1;
if (test_bit(temp_period_one,1)) DC1B1 = 1;
temp_period_one >>= 2;
CCPR1L = (unsigned char)(temp_period_one);
temp_period_one = 0;
}
else
{
CCPR1L = 0;
}
}
return(1); /* interrupt serviced return value */
}
}
return(0); /* interrupt not serviced return value */
}
/* initialize the PWM and interrupt on T2 overflow */
void T2_init(void)
{
PR2 = PWM_MAX-1; /* set PWM period */
TMR2IE = 0; /* turn off TMR2 interrupt */
TRISC &= (0b11000011); /* make RC2,RC3,RC4,RC5 outputs for PWM */
PORTC &= (0b11000011); /* make all PWM outputs low */
#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;
CCP1CON = 0b00001100; /* PWM mode */
CCPR1L = 0;
PSTRCON = 0b00010000; /* PWM pulse steering no output */
update_tick_count = 0;
servo_updated = 0;
servo_select_next = 0;
for (servo_select=0; servo_select < NUMBER_OF_SERVOS; ++servo_select)
{
servo_bank[servo_select] = POSITION_MAX;
}
TMR2ON = 1; /* turn on TMR2 */
TMR2IE = 1; /* turn on TMR2 interrupt */
}
/*
* Interrupt vector starts here
*/
static void interrupt isr(void)
{
if (T2_handler()) return;
}
/* This function is part of the
* test and verification code.
*
* It uses global variables for
* input and output.
*
* This is not a good style for C programs but
* does build smaller, faster code for a PIC.
*
*/
unsigned int servo_period;
bit dir;
unsigned int delta;
static void update_position(void)
{
if (dir)
{ /* count up */
if (servo_period >= (unsigned int)(POSITION_MAX))
{
servo_period -= delta;
dir = 0;
}
else
{
servo_period += delta;
}
}
else
{ /* count down */
if (servo_period <= (unsigned int)(POSITION_MIN))
{
servo_period += delta;
dir = 1;
}
else
{
servo_period -= delta;
}
}
}
void main (void)
{
unsigned int servo_one;
unsigned int servo_two;
static bit servo_dir_one;
static bit servo_dir_two;
OSCCON = 0b01100000; /* Select 4MHz internal oscillator */
/* disable all interrupt sources */
INTCON = 0x00;
ADCON0 = 0x00; /* turn off ADC module */
ANSEL = 0; /* set GPIOs for digital I/O */
ANSELH = 0;
PIE1 = 0x00;
PIE2 = 0x00;
OPTION_REG = 0b11011110; /* 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 */
T2_init();
/* turn on the interrupt system */
PEIE = 1;
GIE = 1;
/* This is a simple servo movement test
* that runs two servos up and down at
* different rates.
*
* Servo one is the first servo in the update sequence.
* Servo two is the second servo in the update sequence.
*
* The cycle rates are:
* for servo one is about 1.66 seconds.
* for servo two is about 4.28 seconds.
*/
#define DELTA_ONE ((POSITION_MAX - POSITION_MIN) / 83ul)
#define DELTA_TWO ((POSITION_MAX - POSITION_MIN) / 214ul)
servo_one = POSITION_MAX;
servo_two = POSITION_MIN;
servo_dir_one = 0; /* set servo one to count down to start */
servo_dir_two = 1; /* set servo two to count up to start */
while(1)
{
if (servo_updated)
{
clear_wdt();
/* start critical section. */
/* do not allow PWM interrupts */
/* while updating servo position array. */
TMR2IE = 0;
servo_bank[0] = servo_one;
servo_bank[1] = servo_two;
TMR2IE = 1;
/* end critical section. */
servo_updated = 0;
servo_period = servo_one;
dir = servo_dir_one;
delta = DELTA_ONE;
update_position();
servo_one = servo_period;
servo_dir_one = dir;
servo_period = servo_two;
dir = servo_dir_two;
delta = DELTA_TWO;
update_position();
servo_two = servo_period;
servo_dir_two = dir;
}
}
}