ICP to PWM

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi im currently creating a program to take the typical servo command output & convert it to a higher frequency to drive a brushed motor. I'm beginning to think i have run out of timer registers on the Atmega168. Is it possible to do this? It might be a matter of timing.

I have included the code for the ICP but i don't know the rest

#include 
#include 
#define  F_CPU 8000000


volatile uint16_t tPulse; // Must be defined volatile or memory is lost

void init(void)
{
	TIMSK1 |=(1<<ICIE1)|(1<<OCIE1B); //enable overflow and input capture interrupts
	TCCR1B |=(1<<ICNC1)|(1<<ICES1)|(1<<CS11); //Noise canceller, rising edge, with 8 prescaler
	TIFR1 = (1 << ICF1);            //clear interrupt-flag

}

ISR(TIMER1_CAPT_vect)
{
    static uint16_t tStart; //Kept in memory, not forgotten until re-written
    uint16_t t = ICR1;

    if (TCCR1B & (1<<ICES1)) 
		{
        // falling edge next
        TCCR1B &= ~(1<<ICES1);
        tStart = t;
		} 
			else {
				  // rising edge next
        		  TCCR1B |= (1<<ICES1);
        		  tPulse = t - tStart;
				  TCNT1 =0; //Reset Timer otherwise it will overflow causing a false reading. Must have this.
				 }   
}

int main(void)
{
    DDRB = 0b00000010;
    init();
    sei();

    for(;;) {

			if (tPulse >= 1500)//Should be 1.5ms 1500 at 8Mhz
				{
				 PORTB |= (1<<PB1);		
				}
					 else {
						   PORTB &=~ (1<<PB1);
						  }
			}		
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
TCNT1 =0; //Reset Timer otherwise it will overflow causing a false reading. Must have this. 

Not quite right. Work it out on paper as using unsigned integers (as you are) a wrap around still works out correctly. By removing this line, the timer ticks over happily so you can use an output compare to generate a higher frequency output.

You also have an atomicity problem with the reading of the shared variable tPulse since it is 16 bits and two reads are required - if the capture interupt comes between these two reads, then the value read from tPulse may be invalid. Have a read of my tutorial "the traps of using interrupts" in the tutorial section - might be relegated to the second page.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Not quite right. Work it out on paper as using unsigned integers (as you are) a wrap around still works out correctly. By removing this line, the timer ticks over happily so you can use an output compare to generate a higher frequency output.

Well that might be the case if some other code was involved (e.g the higher frequency output part) but when the overflow of TCNT1 occurred at 65ms (approx) it caused the output pin to go low. As far as working it out on paper i have no idea how to do that.

The atomicity problem (is that what you call it i was aware of & will ready your info but i thought that with the speed of the micro compared to the ICP frequency it wouldn't be an issue.
The above code was used to test ICP only.

I didn't know there was Tutorials section sorry.

Anybody have an idea how to create a higher frequency output pulse from the lower input one with the same duty?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You've enabled output compare interrupts with no matching isr. Your code will crash but loading tcnt avoids this. Therefore, write a isr for the output compare! This code will read tPulse and set the future compare value and output pin state to generate the pulse you need at the required rate. Read up on the output compare and it should become clear.

Note you've enabled output compare B interrupts not overflow as your code mentions. If you remove the enable for the output compare and the TCNT1=0 then your code should work as expected.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok i did this but after a few sets of pulses to check the high low behavior (signal no signal) it stays locked high no matter what input condition. Is the the atomicity problem?

I had a look for your traps of reading interrupts but couldn't find it.

#include 
#include 
#define  F_CPU 8000000


volatile uint16_t tPulse; // Must be defined volatile or memory is lost

void init(void)
{
	TIMSK1 |=(1<<ICIE1); //enable input capture interrupts
	TCCR1B |=(1<<ICNC1)|(1<<ICES1)|(1<<CS11); //Noise canceller, rising edge, with 8 prescaler


}

ISR(TIMER1_CAPT_vect)
{
    static uint16_t tStart; //Kept in memory, not forgotten until re-written
    uint16_t t = ICR1;

    if (TCCR1B & (1<<ICES1)) 
		{
        // falling edge next
        TCCR1B &= ~(1<<ICES1);
        tStart = t;
		} 
			else {
				  // rising edge next
        		  TCCR1B |= (1<<ICES1);
        		  tPulse = t - tStart;
				 }   
}

int main(void)
{
    DDRB = 0b00000010;
    init();
    sei();

    for(;;) {

			if (tPulse >= 1500)//Should be 1.5ms 1500 at 8Mhz
				{
				 PORTB |= (1<<PB1);		
				}
					 else {
						   PORTB &=~ (1<<PB1);
						  }
			}		
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm confused about the big picture here. Input is an RC servo pulse (1-2ms pulse every 20ms). You want the output to be pwm speed control drive to a mosfet gate driver (0-100%). Your description made it sound like you were going to 'speed up' the servo pulse, which of course wont work for speed control because it only has a 10% duty cycle? I think you could calc the input duty by reading the time at the rising and falling edge of the servo pulse, and convert that to 0-255 to use for the OCR speed control.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

warren1 wrote:
Anybody have an idea how to create a higher frequency output pulse from the lower input one with the same duty?
Sure:
You need to capture at both rising and falling edges.
On a rising capture, record Trise for later.
On a falling capture, note Tcapt.
duty=Trise-Tcapt.
period=Tcapt-Tfall
Arrange for toggles at
Tcapture+offset
Tcapt+offset+duty/2
Tcapt+offset+period/2 and
Tcapt+offset+period/2+duty-duty/2
Record Tfall=Tcapt for later.

offset should be just big enough to ensure that all events are in the future.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I'm confused about the big picture here. Input is an RC servo pulse (1-2ms pulse every 20ms). You want the output to be pwm speed control drive to a mosfet gate driver (0-100%). Your description made it sound like you were going to 'speed up' the servo pulse, which of course wont work for speed control because it only has a 10% duty cycle? I think you could calc the input duty by reading the time at the rising and falling edge of the servo pulse, and convert that to 0-255 to use for the OCR speed control.

Yes that's right. There is to be a bridge between the above code & this code below. e.g The tPulse figure will be changed & put into the OCR1A of this code to create a 0-100% duty cycle.

#include 
#define F_CPU 8000000

int main(void)
{
	TCCR1A |= (1<<WGM11); //Fast PWM. Top ICR1. Update of OCR at BOTTOM. Tov1 Flag set at TOP
	TCCR1B |= (1<<WGM12)|(1<<WGM13)|(1<<CS11); //Prescaler of 8;
	TCCR1A |= (1<<COM1A1)|(1<<COM1B1); //Clear OC1A/OC1B on Compare Match
	     
	OCR1A =128; //Sets lenght of pulse
	ICR1 =256; //Sets frequency 3906.25Hz
	DDRB =_BV(PB1); //Set pin as output
	//A PWM squarewave will appear on PB1 128us high 128us low.
		for(;;)
		{}

}

But there may also be a problem with this due to the input capture is using ICR1 also. If there is a way to join the two code pieces that would be great or i might be able to feed ICP output into timer counter 0 to create a PWM.

What do you guys think?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

even Google found it!
I searched for kartman traps interrupts and what did I get?
https://www.avrfreaks.net/index.p...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok found it thanks, i didn't think to try Google.

Still if i apply the cli to sei below it still doesn't work (output pin will stay high at some point in time).

int main(void)
{
    DDRB = 0b00000010;
    init();
    sei();

    for(;;) {

        uint16_t t;
        // Get current pulse time
        cli();
        t = tPulse;
        sei();

         if (t >= 1500)//Should be 1.5ms 1500 at 8Mhz
            {
             PORTB |= (1<<PB1);      
            }
                else {
                     PORTB &=~ (1<<PB1);
                    }
         }      
} 

I have found that adding the TCNT1 =0; to the ISR fixes the problem but i don't know why?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Looks like im going to have to shit can this one its too difficult.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh come on. You have the output part. The input part doesnt have to use input capture. Just arm a pin change interrupt for 'either edge'. In the handler, clear a count on the rising edge. Inc the count in a separate 10usec int handler. On the falling edge, you have a number from 0 to 200 of those 10usec tics in the pulse. Stuff that number into the OCR.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok after 3weeks of my life i came up with this. It works but unfortunately the input range isn't from 1000 to 2000. Its 1146.8us to 1782.6us (On time). These upper & lower figures also limit the output & i need a slightly wider output range than this. Does anybody have an idea how to increase the output with the above figures. I dont know what you mean bobgardner.

#include 
#include 
#define  F_CPU 4000000


volatile uint16_t tPulse; // Must be defined volatile or memory is lost

void initICP(void)
{
   TIMSK1 |=(1<<ICIE1);//|(1<<OCIE1B); //enable overflow and input capture interrupts
   TCCR1B |=(1<<ICNC1)|(1<<ICES1)|(1<<CS11); //Noise canceller, rising edge, with 8 prescaler
   TIFR1 = (1 << ICF1);            //clear interrupt-flag

}

void initPWM(void)
{
	TCCR2A |= (1<<WGM20)|(1<<WGM21); //Fast PWM. Update of OCR at BOTTOM. Tov1 Flag set at TOP
	TCCR2B |= (1<<CS21); //Prescaler of 8;
	TCCR2A |= (1<<COM2A1); //Clear OCR2A on Compare Match
}

ISR(TIMER1_CAPT_vect)
{
    static uint16_t tStart; //Kept in memory, not forgotten until re-written
    uint16_t t = ICR1;

    if (TCCR1B & (1<<ICES1))
      {
        // falling edge next
        TCCR1B &= ~(1<<ICES1);
        tStart = t;
      }
         else {
              // rising edge next
                TCCR1B |= (1<<ICES1);
                tPulse = t - tStart;
				//TCNT1 =0; //Reset Timer otherwise it will overflow causing a false reading. Must have this.
             }   
}

int main(void)
{
    DDRB =_BV(PB3); //Set pin as output
    initICP();
	initPWM();
    sei();

    for(;;) {

        uint16_t t;
		uint8_t pwm;
        // Get current pulse time
        cli();
        t = tPulse;
        sei();
		pwm = (t - 1000)/4;

		OCR2A = pwm; //Sets lenght of pulse

         }      
} 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Surely this issue is with this calc:
pwm = (t - 1000)/4;
Divide by 3 but test the input value to see it doesn't exceed 767 (256 *3 -1) and isn't less than 0. Something like:

if (t < 1000)
  {
  t = 1000;
  }
 t = t - 1000;
 if (t > 767)
  {
  t = 767;
  }
 OCR2A = t/3;
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
pwm = (t - 1000)/4;

      OCR2A = pwm; //Sets lenght of pulse

As Said by Kartman you can re-write these lines.

if(t<1000)
{
t = 1000;
}
else if(t>2000)
{
t = 2000;
}
t = t-1000;

t = (t/4);

OCR2A = (uint8_t)(t);

I guess this shouldn't give u anymore bug.

-Krishna Balan S

-------------------------------------------------------------------------

"Heroes are ordinary people with extraordinary commitment"

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This prog reads pulse width, converts it to pwm. Runs on my mega32 with 16MHz xtal. Uses 38400 RS232. Hope it helps.

Attachment(s): 

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Update: Ill have a look at the above suggestions you responded before i could.

Well if im understanding correctly what you are talking about. Now the range will quickly reach max of 256 at OCR2A = t/3. The stick will be at 2/3approx & the output will be at max?

The only other way i can see how to do it is to float it & use a bogus duty such as. The code below isn't finished but i don't like floats.

int main(void)
{
    DDRB =_BV(PB3); //Set pin as output
    initICP();
	initPWM();
    sei();

    for(;;) {

        uint16_t t;
		uint8_t pwm;
		float i;
		float a;
		//uint16_t a;
        // Get current pulse time
        cli();
        t = tPulse;
  
	a = t - 1077; //Remote only went down to that number (1077us)not 1000 as in a perfect case. Also creates a 0 to 636 in this case.
	i = a/784; //784 sets the duty cycle ratio
	pwm = i * 256;
	OCR2A = pwm; //Sets lenght of pulse
	sei();
            }      
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You said the input was 1146 to 1782us unless I understood you wrong.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes thats right. 1146 to 1782us. It will go to 1077us with the stick at minimum but its unlikely the motor would spin in the 1077 to 1146 range. I was going to do.

if (x <= 1146)
    {turn off output (dont know how)}

If you are wondering where i got 636 from its (with rounding) 1783 - 1147 = 636.

Ideal range is

Min Max
Input
1077 to 1782us
Output
0 to 230us

230us at the output would be .9 duty cycle which is enough.

Sorry to confuse you guys i didn't get maths or English in my day.