![](templates/subSilver/images/CCSLogo.jpg) |
![CCS C Software and Maintenance Offers](templates/subSilver/images/forumAd6.jpg) |
View previous topic :: View next topic |
Author |
Message |
weg22
Joined: 08 Jul 2005 Posts: 91
|
quick (non-precise) way to get PWM duty cycle |
Posted: Tue Aug 09, 2005 3:46 pm |
|
|
I should of just asked this question first instead of bothering with interrupts :-) I am using a F87 and would like to determine the duty cycle of a PWM signal coming in on one of the pins. I don't, however, need anything precise. The PWM coming in has either a 0% duty cycle or 100% duty cycle (nothing in between). I just need to determine if it's 100%. Any help is much apreciated.
Thanks,
weg |
|
![](templates/subSilver/images/spacer.gif) |
newguy
Joined: 24 Jun 2004 Posts: 1903
|
|
Posted: Tue Aug 09, 2005 4:00 pm |
|
|
If the pin is high, it's a 100% duty cycle. If it's low, it's 0%. |
|
![](templates/subSilver/images/spacer.gif) |
weg22
Joined: 08 Jul 2005 Posts: 91
|
|
Posted: Tue Aug 09, 2005 4:07 pm |
|
|
Actually, I should have mentioned that this is concerning an R/C servo. So, when I said 0% duty cycle, I meant the pulse has a length of 1 ms and a period of about 20 ms
..and when I said 100% duty cycle, I meant the pulse has a length of 2ms and a period of 20 ms. Sorry for any confusion... |
|
![](templates/subSilver/images/spacer.gif) |
newguy
Joined: 24 Jun 2004 Posts: 1903
|
|
Posted: Tue Aug 09, 2005 5:15 pm |
|
|
Whoops, my mistake.
It just occured to me that some code I put in the Code Library quite a while back may be adapted to do what you wish.
Look for "IR Remote Code Grabber s/w" (or something like that) posted by me. I used an external interrupt to measure the high time & period from an IR remote demodulator. It wouldn't be hard to tweak the code to look for a 0 or 100% duty cycle r/c waveform. |
|
![](templates/subSilver/images/spacer.gif) |
SherpaDoug
Joined: 07 Sep 2003 Posts: 1640 Location: Cape Cod Mass USA
|
|
Posted: Tue Aug 09, 2005 5:55 pm |
|
|
You really want to tell 5% duty cycle from 10%, a bit more demanding than 0% and 100%.
You can't do anything till a pulse arrives so either poll or use an interrupt to tell when the puilse has started. Then wait 1.5ms and check it again. If it has gone away it was short, if not it is long. _________________ The search for better is endless. Instead simply find very good and get the job done. |
|
![](templates/subSilver/images/spacer.gif) |
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Tue Aug 09, 2005 6:07 pm |
|
|
Here is some code snippets that I had wrote a very long time ago that was used to read R/C servo signals and drive two motors. You can see how I read the pulse widths. As I recall, it seemed to work quite well and I actually built a prototype "BattleBot". I soon lost interest after seeing how much money I was sinking into this thing.
Code: |
#CASE
#include "16F876.h"
/* used to setup device specifications for programming the chips
HS - oscillator type
WDT- watchdog timer
NOPROTECT - do not protect code
PUT - power up timer on
BROWNOUT - brownout detect on */
#fuses HS, WDT, PROTECT, PUT, BROWNOUT, NOLVP
/* Initialize all RAM locations to 0 */
#ZERO_RAM
#define CLOCK_FREQUENCY 20000000
/* the following are compiler options for delay loops, interrupt priorities */
/* io methods */
#USE DELAY(CLOCK = CLOCK_FREQUENCY)
#USE STANDARD_IO(A)
#USE STANDARD_IO(B)
#USE STANDARD_IO(C)
/****************************************************************************
* Card IO *
****************************************************************************/
/*
The IO on this card is as follows:
RA0 - A_IN1 (input)
RA1 - A_IN2 (input)
RA2 - LED1 (output)
RA3 - A_IN3 (input)
RA4 - LED2 (output)
RA5 - LED3 (output)
TRISA - 0000 1011 - 0Bh
PortB serves as the direction control & channel inputs
RB0 - Reverse 2 (output)
RB1 - Forward 2 (output)
RB2 - Reverse 1 (output)
RB3 - Forward 1 (output)
RB4 - Channel 4 (input)
RB5 - Channel 3 (input)
RB6 - Channel 2 (input)
RB7 - Channel 1 (input)
TRISB - 1111 0000 - F0h
RC0 - Option A (output)
RC1 - PWM 2 (output)
RC2 - PWM 1 (output)
RC3 - Option B (output)
RC4 - UNUSED (input)
RC5 - UNUSED (input)
RC6 - UNUSED (input)
RC7 - UNUSED (input)
TRISC - 1111 0000 - F0h
TRIS masks are:
0 = OUTPUT
1 = INPUT
*/
#define PORT_A_TRIS_MASK 0x0B
#define PORT_B_TRIS_MASK 0xF0
#define PORT_C_TRIS_MASK 0xF0
/******************************* MACROS **************************************/
#define ENABLE_PORTB_INTERRUPT() bit_set(INTCON, RBIE)
#define ENABLE_TIMER2_INTERRUPT() bit_set(PIE1, TMR2IE)
#define ENABLE_GLOBAL_INTERRUPT() bit_set(INTCON, GIE)
#define DISABLE_GLOBAL_INTERRUPT() bit_clear(INTCON, GIE)
#define ENABLE_PERIPHERAL_INTERRUPT() bit_set(INTCON, PEIE)
BIT PowerUp_Flag; /* Remote just powered up */
BIT System_Reset_Flag; /* Request a reset */
BIT Check_ADC_Inputs_Flag;
BIT Check_PWM_Flag = FALSE;
UINT8 Direction1=0;
UINT8 Direction2=0;
#define BRAKE 0
#define FORWARD 1
#define REVERSE 2
#define COAST 3
/*************************** Declare Global Variables ************************/
#define CHAN1_BIT 0
#define CHAN2_BIT 1
#define CHAN3_BIT 2
#define CHAN4_BIT 3
#define BRAKEZONE 25 /* 24us / 1.6us = 15 */
/* This is actually 1/2 of the deadband */
#define DEADBAND BRAKEZONE + 15
//#define NEUTRAL_CHAN_VAL 937 /* 1500us / 1.6us = 937.5 */
//#define MIN_CHAN_VAL 625 /* 1000us / 1.6us = 625 */
//#define MAX_CHAN_VAL 1250 /* 2000us / 1.6us = 1250 */
#define NEUTRAL_CHAN_VAL 937 /* 1500us / 1.6us = 937.5 */
#define MIN_CHAN_VAL NEUTRAL_CHAN_VAL-(255)
#define MAX_CHAN_VAL NEUTRAL_CHAN_VAL+(255)
/* Maximum # of channels */
#define MAX_CHAN 4
/* Channel refresh time in ms */
#define REFRESH_TIME 25
UINT16 Chan[MAX_CHAN]; /* Channel's current value */
UINT16 New_Chan[MAX_CHAN]; /* Channel's new value */
UINT16 Chan_Timer[MAX_CHAN]; /* Channel's new timer value */
UINT8 Chan_Timeout[MAX_CHAN];
UINT8 Chan_Changed; /* which channels have changed */
UINT8 EEPROM_Timeout;
INT16 New_PWM[MAX_CHAN];
INT16 PWM[MAX_CHAN];
/****************************************************************************
* NAME: Timer2_ISR
* DESCRIPTION: Timer2 is set to go off every 0.5ms.
* PARAMETERS: none
* RETURN: none
* ALGORITHM: none
* NOTES: none
*****************************************************************************/
#INT_TIMER2
void Timer2_ISR(void)
{
static BIT state = FALSE;
static UINT8 PWM_count = 0;
/* Counters */
static UINT8 check_pwm = 0;
state = !state;
/* Only handle every other int to give us a 1ms tick. */
if (!state)
return;
/* Channel Refresh time */
if (Chan_Timeout[0])
{
--Chan_Timeout[0];
/* The channel did not refresh in time */
if (!Chan_Timeout[0])
{
New_PWM[0] = 0;
New_PWM[1] = 0;
Direction1 = BRAKE<<2;
Direction2 = BRAKE;
}
}
/* Channel Refresh time */
if (Chan_Timeout[1])
{
--Chan_Timeout[1];
/* The channel did not refresh in time */
if (!Chan_Timeout[1])
{
New_PWM[0] = 0;
New_PWM[1] = 0;
Direction1 = BRAKE<<2;
Direction2 = BRAKE;
}
}
if (check_pwm)
{
--check_pwm;
}
else
{
Check_PWM_Flag = TRUE;
check_pwm = 2;
}
}
/****************************************************************************
* NAME: PortB_Change
* DESCRIPTION: Signals when a change on RB4-7 occurs
* PARAMETERS: none
* RETURN: none
* ALGORITHM: none
* NOTES: none
*****************************************************************************/
#INT_RB
void PortB_Change(void)
{
static UINT8 last_stable = 0;
static UINT16 chan[MAX_CHAN];
UINT8 current;
UINT8 trans_hi;
UINT8 trans_lo;
UINT16 timer1;
/* Get the current timer value */
timer1 = get_timer1();
/* Get the current PORTB state. We are only interested in the 4 MSB's */
current = *PORTB;
current >>= 4;
/* Figure out the transitions */
trans_lo = ((last_stable ^ current) & last_stable);
trans_hi = ((last_stable ^ current) & current);
/* Set our known last state for future comparison */
last_stable = current;
/* Determine which channels just went high and mark their time */
/* The timeout is the maximum time allowed for a refresh. */
if (bit_test(trans_hi, CHAN1_BIT))
{
chan[0] = timer1;
Chan_Timeout[0] = REFRESH_TIME;
}
if (bit_test(trans_hi, CHAN2_BIT))
{
chan[1] = timer1;
Chan_Timeout[1] = REFRESH_TIME;
}
if (bit_test(trans_hi, CHAN3_BIT))
{
chan[2] = timer1;
Chan_Timeout[2] = REFRESH_TIME;
}
if (bit_test(trans_hi, CHAN4_BIT))
{
chan[3] = timer1;
Chan_Timeout[3] = REFRESH_TIME;
}
/* See if any channels just went low and compute their on time */
if (bit_test(trans_lo, CHAN1_BIT))
{
if (timer1 > chan[0])
Chan_Timer[0] = timer1 - chan[0];
}
if (bit_test(trans_lo, CHAN2_BIT))
{
if (timer1 > chan[1])
Chan_Timer[1] = timer1 - chan[1];
}
if (bit_test(trans_lo, CHAN3_BIT))
{
if (timer1 > chan[2])
Chan_Timer[2] = timer1 - chan[2];
}
if (bit_test(trans_lo, CHAN4_BIT))
{
if (timer1 > chan[3])
Chan_Timer[3] = timer1 - chan[3];
}
/* Signal which channels have changed */
Chan_Changed |= trans_lo;
/* We are not counting so reset our timer */
if (current == 0)
{
set_timer1(0);
}
}
void main(void)
{
UINT16 timer_value;
INT16 level;
UINT8 bit_mask;
UINT8 chan_num = 0;
UINT8 direction=0;
System_Reset_Flag = TRUE;
port_b_pullups(TRUE);
while (TRUE)
{
/* Do we need to reset */
if (System_Reset_Flag)
{
DISABLE_GLOBAL_INTERRUPT();
/* initialize variables first, since initializing pic
will allow interrupts to begin occurring */
Initialize_Variables();
Init_PIC();
}
restart_wdt();
/* Is it time to adjust the PWM's */
if (Check_PWM_Flag)
{
Check_PWM_Flag = FALSE;
Check_PWM_Signal();
}
/* See if it is time to reevaluate the analog inputs */
if (Check_ADC_Inputs_Flag)
{
Check_ADC_Inputs_Flag = FALSE;
ADC_Timeout = 10;
}
if (Chan_Changed)
{
/* Setup the data for the current channel */
switch (chan_num)
{
case 0:
bit_mask = 0x01;
timer_value = Chan_Timer[0];
break;
case 1:
bit_mask = 0x02;
timer_value = Chan_Timer[1];
break;
case 2:
bit_mask = 0x04;
timer_value = Chan_Timer[2];
break;
case 3:
bit_mask = 0x08;
timer_value = Chan_Timer[3];
break;
default:
}
/* Test to see if the channel has changed */
if (Chan_Changed & bit_mask)
{
/* Clear the flag bit */
Chan_Changed &= ~bit_mask;
/* Forward */
if (timer_value > NEUTRAL_CHAN_VAL)
{
/* Invalid Timer Value */
if (timer_value > MAX_CHAN_VAL)
level = MAX_CHAN_VAL - NEUTRAL_CHAN_VAL;
else
level = timer_value - NEUTRAL_CHAN_VAL;
direction = FORWARD;
}
/* Reverse */
else if (timer_value < NEUTRAL_CHAN_VAL)
{
/* Invalid Timer Value */
if (timer_value < MIN_CHAN_VAL)
level = NEUTRAL_CHAN_VAL - MIN_CHAN_VAL;
else
level = NEUTRAL_CHAN_VAL - timer_value;
level = -level;
direction = REVERSE;
}
/* Neutral */
else
{
level = 0;
/* Direction will be set below unless the deadband is zero */
}
/* Apply the brake */
if (labs(level) <= BRAKEZONE)
direction = BRAKE;
switch (chan_num)
{
case 0:
direction <<= 2;
/* This will prevent relay chatter */
if ((labs(level) <= BRAKEZONE) || (labs(level) >= DEADBAND))
Direction1 = direction;
/* Turn off the PWM */
if (labs(level) <= DEADBAND)
level = 0;
New_PWM[0] = level;
break;
case 1:
/* This will prevent relay chatter */
if ((labs(level) <= BRAKEZONE) || (labs(level) >= DEADBAND))
Direction2 = direction;
/* Turn off the PWM */
if (labs(level) <= DEADBAND)
level = 0;
New_PWM[1] = level;
break;
case 2:
break;
case 3:
break;
default:
}
}
++chan_num;
if (chan_num >= MAX_CHAN)
chan_num = 0;
}
} /* end of forever loop */
}
/****************************************************************************
* NAME: Init_PIC
* DESCRIPTION: Initializes the PIC, its timers, WDT, etc.
* PARAMETERS: none
* RETURN: none
* ALGORITHM: none
* NOTES: none
*****************************************************************************/
#separate
void Init_PIC(void)
{
/* Setup PORT A */
set_tris_a(PORT_A_TRIS_MASK);
/* Setup PORT B */
set_tris_b(PORT_B_TRIS_MASK);
/* Setup PORT C */
set_tris_c(PORT_C_TRIS_MASK);
/* Set timer1 to include every 1.6uS */
setup_timer_1(T1_DISABLED);
set_timer1(0);
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
/* setup timer2 - 0.5ms counter. This will also setup a 20KHz PWM frequency. */
setup_timer_2(T2_DISABLED,0,1);
setup_timer_2(T2_DIV_BY_1, 0xFA, 10);
setup_ccp1(CCP_PWM);
set_pwm1_duty(0);
setup_ccp2(CCP_PWM);
set_pwm2_duty(0);
/* setup watchdog to timeout @ 18ms and Timer0 to increment every 1.6us */
setup_counters(RTCC_INTERNAL,RTCC_DIV_8);
/* Enable interrupts */
ENABLE_TIMER2_INTERRUPT();
ENABLE_PERIPHERAL_INTERRUPT();
ENABLE_GLOBAL_INTERRUPT();
ENABLE_PORTB_INTERRUPT();
}
/****************************************************************************
* NAME: Initialize_Variables
* DESCRIPTION: Initializes the relay f/w's variables.
* PARAMETERS: none
* RETURN: none
* ALGORITHM: none
* NOTES: none
*****************************************************************************/
#separate
void Initialize_Variables(void)
{
/* If the POR bit is clear then a power on reset occurred. We must set the
POR bit as well as the BOR (brown-out reset). */
if (!bit_test(*PCON, POR))
{
bit_set(PCON, POR);
bit_set(PCON, BOR);
PowerUp_Flag = TRUE;
}
else
PowerUp_Flag = FALSE;
System_Reset_Flag = FALSE;
Chan_Timeout[0] = REFRESH_TIME;
Chan_Timeout[1] = REFRESH_TIME;
Chan_Timeout[2] = REFRESH_TIME;
Chan_Timeout[3] = REFRESH_TIME;
}
void Check_PWM_Signal(void)
{
UINT8 portb;
UINT16 level;
/* Only change direction at 0 speed */
if (PWM[0] ==0)
{
portb = PORTB & 0b11110011;
portb |= Direction1;
PORTB = portb;
}
if (New_PWM[0] > PWM[0])
{
++PWM[0];
level = labs(PWM[0]);
level <<= 2;
set_pwm1_duty(level);
}
else if (New_PWM[0] < PWM[0])
{
--PWM[0];
level = labs(PWM[0]);
level <<= 2;
set_pwm1_duty(level);
}
/* Only change direction at 0 speed */
if (PWM[1]==0)
{
portb = PORTB & 0b11111100;
portb |= Direction2;
PORTB = portb;
}
if (New_PWM[1] > PWM[1])
{
++PWM[1];
level = labs(PWM[1]);
level <<= 2;
set_pwm2_duty(level);
}
else if (New_PWM[1] < PWM[1])
{
--PWM[1];
level = labs(PWM[1]);
level <<= 2;
set_pwm2_duty(level);
}
}
long labs(signed long l)
{
long r;
r = (l < 0) ? -l : l;
return(r);
}
|
|
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah Guest
|
|
Posted: Wed Aug 10, 2005 3:22 am |
|
|
weg22 wrote: | Actually, I should have mentioned that this is concerning an R/C servo. So, when I said 0% duty cycle, I meant the pulse has a length of 1 ms and a period of about 20 ms
..and when I said 100% duty cycle, I meant the pulse has a length of 2ms and a period of 20 ms. Sorry for any confusion... |
For servo control, you do not need the duty cycle at all. Servos only 'care' about the pulse length. In fact you will find that different companies use significantly different pulse repetition rates, it is only the pulse widths that are standardised. 20mSec repeat rate is common, but by no means 'guaranteed'...
A little bit depends on what other things the chip is doing at the time. However for example), if you set timer1, as a counter, and program it to increment at perhaps 1uSec intervals. Then just look for the rising edge of the incoming pulse, and then 'set_timer1(0L)'. Then wait for the falling edge, and read timer1. You will have a value that will range from 1000 to 2000 for a 1mSec to 2mSec pulse.
so something like:
Code: |
//assuming 'input_pin' is defined to be the pin with the pulse on it
while (input_pin==0) ;
set_timer1(0);
while (input_pin);
time=get_timer1();
if (time<1000L) time=0L;
else time-=1000;
|
Within the accuracies of the various clocks (and with a limit that it will never go below '0'), 'time', will then be a count from 0 to 1000, according to the pulse width.
You could do this inside a 'interrupt on change' interrupt handler, to allow other work to be going on while the pulse is timed. However this then means having to offset slightly for the interrupt latency.
You can also do the timing in hardware, if the chip has a CCP module. With this, you program the CCP to capture the timer value on the falling edge, and on the rising edge, reset the counter (either by polling the input, or using an interrupt), then on the next falling edge, the CCP module will grab the required count.
Best Wishes |
|
![](templates/subSilver/images/spacer.gif) |
sseidman
Joined: 14 Mar 2005 Posts: 159
|
|
Posted: Wed Aug 10, 2005 6:58 am |
|
|
weg22 wrote: | Actually, I should have mentioned that this is concerning an R/C servo. So, when I said 0% duty cycle, I meant the pulse has a length of 1 ms and a period of about 20 ms
..and when I said 100% duty cycle, I meant the pulse has a length of 2ms and a period of 20 ms. Sorry for any confusion... |
As an aside, I had a rough time getting PWM frequencies slow enough to control an RC servo. I use timer interrupts, one timer to deal with the pulse rate, and a second timer to deal with the pulse width. |
|
![](templates/subSilver/images/spacer.gif) |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|