Timer OC mystery - can you figure it out?

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

Hi Everyone,

 

I've got a timer and I am using it in PWM mode and setting OC1A and OC1B to be opposites of each other.  This is on a 1284P:

 

I know there is a way to force the states of the output compare using FOC1A and FOC1B, but that requires taking it out the PWM mode.  Again, I know this does work, I've tried it and it indeed does.  I want to understand why the below does NOT work:

  TCCR1A = _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1) | _BV(WGM11);
  TCCR1B = _BV(WGM13) | _BV(WGM12);

  ICR1=65535;
  OCR1A=32767;
  OCR1B=32767;
  
  TCNT1=OCR1A-1;
  TCCR1B |= _BV(CS10);
  TCCR1B &= ~_BV(CS10);

This will set one of the states, but not the other.

  TCNT1=ICR1-1;
  TCCR1B |= _BV(CS10);
  TCCR1B &= ~_BV(CS10);

This will set both, but I have a feeling that it only works because the one it doesn't set was already set right.

 

If I go through this cycle, which hits both states, it does work.

  TCNT1=OCR1A-1;
  TCCR1B |= _BV(CS10);
  TCCR1B &= ~_BV(CS10);

  TCNT1=ICR1-1;
  TCCR1B |= _BV(CS10);
  TCCR1B &= ~_BV(CS10);

  TCNT1=OCR1A-1;
  TCCR1B |= _BV(CS10);
  TCCR1B &= ~_BV(CS10);

But my question is, in the first example, should not both OCR1A and OCR1B get "hit" and set each state accordingly?  Why does there seem to be a prerequisite that the other state was triggered in the past first?

 

Thanks,

 

Alan

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
  OCR1A=32767;
  OCR1B=32767;
  
  TCNT1=OCR1A-1;

This sequence won't work as intended because in PWM modes the OC-registers are double buffered and updated on the next timer wrap. So the value read from OCR1A in the third line is not 32767 but the old value.

Stefan Ernst

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

As with most questions like this, it is best to post a small complete program we can load and see the problem.

Also state what toolchain and MPU(you did that) your using.

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274

 

 

 

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

AS7.  I think sternst has it figured out, which explains why it works when the timer wraps, but doesn't until it does.

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

Is there a better way to do what I'm trying to do?

 

I am trying to drive a piezo with 2 pins, out of phase with each other for more volume.

 

I am using a PWM mode with ICR1 defining the top (for frequency) so I can use OCR1A/OCR2A to toggle the pins at half of the ICR1.

 

Everything done with PB1 is for debug test output only (so I can see when events show up on the logic analyzer)

 

 

Right at 20ms is where I initialize the timer (using FOC1A/FOC2B to set the initial state in a CTC mode before entering the PWM mode.

 

Then I start it at 30ms and let it run for 8.5ms (to see how it would cut off that last uneven cycle).

 

Perhaps it doesn't matter and only the transitions do so i could turn it off and let it start up from the top line low and the bottom line high instead of the reverse...

 

Is there a better way to do this than I am doing it?

 

//piezo
#define FREQ 500
#define DURATION 8

int main(void)
{
  //disable watchdog
  wdt_reset();
  MCUSR=0;
  wdt_disable();

  //switch to clk/2
  CLKPR=_BV(CLKPCE);
  CLKPR=_BV(CLKPS0);

  //enable interrupts
  sei();

  //startup delay
  Delay_ms(50);

  //initialize debug
  DDRB|=_BV(1);
  Delay_ms(10);
  PORTB|=_BV(1);
  Delay_ms(10);
  PORTB&=~_BV(1);
  Delay_ms(10);

  //initialize piezo
  TCCR1A = _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1);
  TCCR1B = _BV(WGM13) | _BV(WGM12);
  TCCR1C = _BV(FOC1A) | _BV(FOC1B);
  PORTD|=_BV(PIEZOP);
  DDRD|=_BV(PIEZOP) | _BV(PIEZON);
  TCCR1A |= _BV(WGM11);

  PORTB|=_BV(1);
  PORTB&=~_BV(1);
  Delay_ms(10);

uint8_t z1;

for(z1=0;z1<3;z1++)
{
  //start sound
  ICR1 = F_CPU/FREQ/8;
  OCR1A= F_CPU/FREQ/2/8;
  OCR1B= F_CPU/FREQ/2/8;
  TCNT1=ICR1-1;
  TCCR1B |= _BV(CS11);

  PORTB|=_BV(1);
  PORTB&=~_BV(1);

  //delay
  Delay_ms(DURATION);
  Delay_us(500);

  //stop sound
  TCCR1B &= ~_BV(CS11);
  TCCR1A &= ~_BV(WGM11);
  TCCR1C = _BV(FOC1A) | _BV(FOC1B);
  TCCR1A |= _BV(WGM11);

  PORTB|=_BV(1);
  PORTB&=~_BV(1);

Delay_ms(20);
}

 

Last Edited: Tue. Apr 18, 2017 - 05:58 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

alank2 wrote:
I am trying to drive a piezo with 2 pins, out of phase with each other for more volume.
Why a PWM mode then at all?

I would use a non-PWM mode, half of the timer period, and "toggle on compare match" for the outputs.

Stefan Ernst

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

sternst wrote:
Why a PWM mode then at all?

But I'm confused -- why doesn't OP's first code, setting OC1A in non-inverting mode and OC1B in inverting mode, work fine without any fussing with TCNT1 or the clock bits?  Yeah, the first pass through there may be a bit of a runt depending on the initial states of the pins.  But who cares?  At e.g. 2000Hz it will only last for a millisecond.  And simply avoided, IIRC, by setting the pin states before the COM bits.

 

Now, what timer mode do we end up with in the OP?  Lessee -- yep, mode 14.  I agree.

 

What speed is this AVR running at?  At 16MHz, that code would give a 4ms period.  A 250Hz piezo?  Perhaps; I've never worked with one that slow I guess.  Off to DigiKey...a few models out of the thousands that are 400Hz or below.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

I don't know how any of this works at all. The documentation says:

 

Writing to the TCNT1 Register blocks (removes) the compare match on the following timer clock for all
compare units.

 So when you do this...

  TCNT1=OCR1A-1;            //will match on next clock
  TCCR1B |= _BV(CS10);      //pulse clock
  TCCR1B &= ~_BV(CS10);     //but match is disabled!

...nothing should happen. I think. Or maybe this confuses the timer in some undocumented way, since you started and stopped the timer. I would try

TCNT1=OCR1A-2;

and see what happens.

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

sternst wrote:

Why a PWM mode then at all?

I would use a non-PWM mode, half of the timer period, and "toggle on compare match" for the outputs.

 

It seems like I tried this and it was producing the same signal on both pins.  Perhaps I didn't set them up to be opposites of each other using FOC ahead of time?

 

Would you use ICR for the top or OCR1A?  Then just set OCR1B to the same value?

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

Sorry a newbie, what the '|' means in the code? like: 

  TCCR1A = _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1) | _BV(WGM11);

Thanks

 

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

laoadam wrote:

Sorry a newbie, what the '|' means in the code? like: 

  TCCR1A = _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1) | _BV(WGM11);

 

 

Bitwise inclusive OR

#1 This forum helps those that help themselves

#2 All grounds are not created equal

#3 How have you proved that your chip is running at xxMHz?

#4 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

Last Edited: Wed. Jan 16, 2019 - 04:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

| is the C operator for OR. Say you use:

0x40 | 0x08 | 0x01

then this combines the bits. If you look at those in binary rather than hex and stack them vertically you get:

01000000 (that is 0x40)
00001000 (that is 0x08)
00000001 (that is 0x01)

then using | (OR) means combining the 1 bits. So if you collect together all the 1 bits you see there you get:

01001001

So these three bit patterns ORd together are 0x49 and therefore if you use:

dest = 0x40 | 0x08 | 0x01;

you are really just writing 0x49 to the destination. Then there is _BV() that is just defined as:

#define _BV(n) (1 << n)

So in your line:

TCCR1A = _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1) | _BV(WGM11);

that's really just the same as:

TCCR1A = (1 << COM1A1) | (1 << COM1B0) | (1 << COM1B1) | (1 << WGM11);

then I don't know the exact values of these symbols but say they are:

#define COM1A1 7
#define COM1B0 5
#define COM1B1 4
#define WGM11 2

then it actually says:

TCCR1A = (1 << 7) | (1 << 5) | (1 << 4) | (1 << 2);

Now:

(1 << 7) is 0x80
(1 << 6) is 0x40
(1 << 5) is 0x20
(1 << 4) is 0x10
(1 << 3) is 0x08
(1 << 2) is 0x04
(1 << 1) is 0x02
(1 << 0) is 0x01

So another way to read this is:

TCCR1A = 0x80 | 
         0x20 | 
         0x10 | 
         0x04 ;

So doing the "binary stacking thing" again this is:

TCCR1A = 0b10000000 | 
         0b00100000 | 
         0b00010000 | 
         0b00000100 ;

So ultimately this writes 10110100 to the TCCR1A register.

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

sternst wrote:
Why a PWM mode then at all? I would use a non-PWM mode, half of the timer period, and "toggle on compare match" for the outputs.

That is how I drove the buzzers in our ultrasonic gas sensors with them wired across two port pins, worked well!

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274