CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

quick (non-precise) way to get PWM duty cycle

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
weg22



Joined: 08 Jul 2005
Posts: 91

View user's profile Send private message

quick (non-precise) way to get PWM duty cycle
PostPosted: Tue Aug 09, 2005 3:46 pm     Reply with quote

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
newguy



Joined: 24 Jun 2004
Posts: 1908

View user's profile Send private message

PostPosted: Tue Aug 09, 2005 4:00 pm     Reply with quote

If the pin is high, it's a 100% duty cycle. If it's low, it's 0%.
weg22



Joined: 08 Jul 2005
Posts: 91

View user's profile Send private message

PostPosted: Tue Aug 09, 2005 4:07 pm     Reply with quote

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...
newguy



Joined: 24 Jun 2004
Posts: 1908

View user's profile Send private message

PostPosted: Tue Aug 09, 2005 5:15 pm     Reply with quote

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.
SherpaDoug



Joined: 07 Sep 2003
Posts: 1640
Location: Cape Cod Mass USA

View user's profile Send private message

PostPosted: Tue Aug 09, 2005 5:55 pm     Reply with quote

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.
Mark



Joined: 07 Sep 2003
Posts: 2838
Location: Atlanta, GA

View user's profile Send private message Send e-mail

PostPosted: Tue Aug 09, 2005 6:07 pm     Reply with quote

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);
}

Ttelmah
Guest







PostPosted: Wed Aug 10, 2005 3:22 am     Reply with quote

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
sseidman



Joined: 14 Mar 2005
Posts: 159

View user's profile Send private message

PostPosted: Wed Aug 10, 2005 6:58 am     Reply with quote

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.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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