ATtiny461a - having trouble with timer 1 in CTC mode

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

Hi. I have ATtiny461a and wanted to generate a square waveform, but using timer1 in CTC mode. Unfortunately, it only seems to work when I set OCR1A=255. The lower value I set OCR1A to, the shorter lower part of the signal gets, but the part where the signal is high seems unaffected. Here's my code

 

#include <Arduino.h>

volatile bool state = false;

ISR(TIMER1_COMPA_vect) {
 state = !state;
 if(state) PORTB |= (1 << PORTB0); //sets PB0 to high
 else PORTB &= ~(1 << PORTB0); //sets PB0 to low
}
//------------------------------
void setup() {
  pinMode(PIN_B0,OUTPUT);
    TCCR1A = 0;
    TCCR1B = 0;
    TCNT1  = 0;
    TCCR1A |= (1 << COM1A1);   // CTC mode
    TCCR1B |= (1 << CS10) | (1 << CS11) | (1 << CS12);    // 1:64 prescaler
    OCR1A = 51;
    TIMSK |= (1 << OCIE1A);  // enable timer compare interrupt
    //-----------------------------
}

void loop() {
}

Here's what it looks like on the oscilloscope when I set OCR1A to 255

 

OCR1A=255

And here's what it looks like if I set it to 51 for example:

OCR1A=51

Am I doing something wrong in the code? I can't find my mistake.

Also, I know Timer1 has builtin square-wave generation - but I want to use CTC mode, just for educational purposes :) 

This topic has a solution.
Last Edited: Sun. May 15, 2022 - 07:35 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The code looks ok to me at first glance, but since you want to just toggle the port pin, IIRC you can do that with a simple write to the PIN register, try that and see if it works.

ie: change this:

volatile bool state = false;

ISR(TIMER1_COMPA_vect) {
 state = !state;
 if(state) PORTB |= (1 << PORTB0); //sets PB0 to high
 else PORTB &= ~(1 << PORTB0); //sets PB0 to low
}

to:

//volatile bool state = false;

ISR(TIMER1_COMPA_vect) {
// state = !state;
// if(state) PORTB |= (1 << PORTB0); //sets PB0 to high
//else PORTB &= ~(1 << PORTB0); //sets PB0 to low
PINB = 1; //writing to PIN toggles bit
}

 

 

FF = PI > S.E.T

 

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

I think you need to set a TOP count in OCR1C to set the period.

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

It indeed works, thanks for the tip!

However, main problem still persists.

 

AFAIK, OCR1C is not used in CTC mode.

 

I noticed something interesting - increasing OCR1A value seems to lengthen low pulse duration - at 128 and 255 I get perfect square waveform.

 

Examples : OCR1A set to 254 :

OCR1A set to 1:

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

The t461a has one of the most complex timers in the AVR family, on this model the timer is 8 or 10 bit operation, has dead time generation as well as PWM modes.

from the data sheet:

For non-PWM modes the COM1x1:0 bits control whether the output should be set, cleared, or
toggled at a Compare Match.

 

I don't see where you have setup the COM1x bits for CTC mode, if fact it took me a while to find it in the DS.  Since this is Arduino code, and it may set up the timers before the application starts, any time you want to manually configure a timer, you need to set all bits in the registers, using assignment "=" and not or-ing "|=" when assigning config bits so all bits are set as you expect them to be.  You can not rely on them being at default power up values.

Also, don't think that all T1's are the same on all AVRs, you need to consult the DS and fully understand it, configs for other arduino's (ie M328) can not be used, the timer registers are different.

 

good luck

 

Jim

 

 

 

FF = PI > S.E.T

 

Last Edited: Wed. May 11, 2022 - 06:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ki0bk wrote:

I don't see where you have setup the COM1x bits for CTC mode

 

It's right here

TCCR1A |= (1 << COM1A1);   // CTC mode

ki0bk wrote:

Since this is Arduino code, and it may set up the timers before the application starts, any time you want to manually configure a timer, you need to set all bits in the registers, using assignment "=" and not or-ing "|=" when assigning config bits so all bits are set as you expect them to be.  You can not rely on them being at default power up values.

I actually clean all the required registers here

    TCCR1A = 0;
    TCCR1B = 0;
    TCNT1  = 0;

I realized I haven't cleared TIMSK register, but after doing it before running any code nothing changed anyway.

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

It's right here
TCCR1A |= (1 << COM1A1);   // CTC mode

 

DO NOT initialize anything this way.  Who told you to do so???   How are you sure what all the other bits are?  So you immediately set yourself up for all kinds of confusion scenarios.

For initializations use = ...don't you want to be able to look at that code and tell EXACTLY how the register is set...guessing is for the racetrack.   

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Wed. May 11, 2022 - 07:15 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrcandies wrote:

 How are you sure what all the other bits are? 

 

A few lines earlier I write 

TCCR1A = 0;

So the whole register should contain zeros, right?

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

blazej222 wrote:

It's right here

TCCR1A |= (1 << COM1A1);   // CTC mode

Where did you get that idea?  Read the datasheet.   Timer0 has a CTC mode.   But Timer1 appears to wait for OVF e.g. 256 ticks.

 

Yes.  It is fine to use |= if you started with an = 0

But it would be much simpler to just use = in the first place.  e.g.

TCCR1A = (2 << COM1A0);  // Clear on Match (COMnx=2)

 

If you want CTC mode,  choose Timer0

 

The Tiny261 / 461 / 861 family Timers are very different to most other Tiny or Mega.

 

David.

 

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

david.prentice wrote:

Where did you get that idea?  Read the datasheet.   Timer0 has a CTC mode.   But Timer1 appears to wait for OVF e.g. 256 ticks.

If you want CTC mode,  choose Timer0

 

It says Timer1 has CTC mode, which is controlled via COM1A1 i COM1A0. It is in manual - section 12.12.1 TCCR1A – Timer/Counter1 Control Register.

 

 

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


My (elderly) datasheet says

It is possible that later datasheets are different.

 

I possess a real-life Tiny861.   But I will probably just test your code in the AS7.0 Simulator.

 

My apologies in advance !!

 

David.

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

A few lines earlier I write 
TCCR1A = 0;
So the whole register should contain zeros, right?

Well, that's a relief, but why are you doing that? Just set the register with what you want---why are you writing to it multiple times?  

You can see that this has already caused some confusion.  If you can look at one line of code and see what is happening, compared to needing to look around at several and combine their effects in your head, it is an easy choice.     

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

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

My apologies for real.

 

Just tried it in the Simulator.   CTC mode works fine.  You set the CTC frequency with OCR1C register.

 

If you want 50% duty cycle you can toggle the OC1A pin on match.   Without any interrupts.

If you want a frequency set by OCR1C and a variable duty cycle you can use a PWM mode.   Without any interrupts.

Using the hardware COMnx modes you get jitter-free operation.  In fact it is cycle-perfect !!

 

Or you can do whatever you want via interrupts.   With a small amount of jitter.

 

David.

 

/*
 * CTC1_t461.c
 *
 * Created: 11-May-22 21:22:07
 * Author : David Prentice
 */

#define F_CPU 8000000

#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER1_COMPA_vect)
{
    //PINB = (1 << PB1);
}

int main(void)
{
    DDRB |= (1 << PB1);      //OC1A pin
    TCNT1  = 0;
    TCCR1A = (1 << COM1A0);   // toggle on COMPA
    OCR1C = F_CPU / 64 / 1000 - 1; // 1ms period
    OCR1A = 51;
    TCCR1B = (7 << CS10);    // 1:64 prescaler
    TIMSK |= (1 << OCIE1A);  // enable timer compare interrupt
    sei();
    while (1)
    {
        asm("nop");
    }
}

 

Last Edited: Wed. May 11, 2022 - 08:57 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks David! Unfortunately, the code you provided does not work for me, and I believe it isn't suitable for my case :(

You see, what I'm trying to achieve is to have Timer1 trigger an interrupt x (lets assume 1200, whole point is I want to be able to change it easily) times per second - the whole square waveform generation is actually only a test for me to check if interrupts are generated correctly(they unfortunately aren't). My apologies - I should have made it clear from the very beginning.

 

I remember using similiar approach to the one presented in my code on 328p, and it worked flawlessly. Yes, I'm aware ATtiny works differently to 328p, but I made my best effort to write a similiar code (after consulting the datasheet), which according to my knowledge, should work.

 

I think I need to ask another question.

 

Just to make sure we are on the same page, I've added appropriate comments in code which point to my sources on those information. I am using this datasheet.

Could you please look at the following piece of code:

 

#include <Arduino.h>

ISR(TIMER1_COMPA_vect) { //OCR1A is 51 so this function should be executed 1200 times per second (every 0.832ms)
PINB = 1; // toggle pin B0 state
}
//------------------------------
void setup() {
    pinMode(PIN_B0,OUTPUT); //I'm gonna be testing pin B0
    TCCR1A = (1 << COM1A1);   // Clear on compare match mode - according to table 12-8, page 111 datasheet
    TCCR1B = (1 << CS10) | (1 << CS11) | (1 << CS12);    // 1:64 prescaler - according to table 12-17, page 115 datasheet
    OCR1A = 51; // should trigger 1201 times per second - according to avrCalc (photo included)
    TIMSK = (1 << OCIE1A);  // enable timer compare interrupt - according to 12.12.13, page 122 datasheet
    TCNT1 = 0; //set it to 0 to start the timer - according to 12.12.7, page 129 datasheet
}

void loop() {
}

and tell me why this code does not trigger the interrupt 1200 times per second? It is very important for me to learn why my approach is bad and what's wrong - even more than just copying ready code to make it work.

Here's how I convert 51 OCR1A value to 1200Hz - I use simple program called AVRcalc. Here's a photo from it:

If any further clarification to my code is needed, I'll be happy to provide it.

Thanks for all the help so far :)

Last Edited: Wed. May 11, 2022 - 10:15 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

balisong42 wrote:

I think you need to set a TOP count in OCR1C to set the period.

 

CTC mode of TC0 uses OCR0A for TOP.

 

CTC-like mode of TC1 uses OCR1C for TOP.

 

What are you expectations from setting OCR1A?

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

balisong42 wrote:

balisong42 wrote:

I think you need to set a TOP count in OCR1C to set the period.

 

CTC mode of TC0 uses OCR0A for TOP.

 

CTC-like mode of TC1 uses OCR1C for TOP.

 

What are you expectations from setting OCR1A?

 

Thank you! Turns out there's a big difference between OCR1A and OCR1C -

In datasheet next to OCR1C it says " The Timer/Counter Output Compare Register C contains data to be continuously compared with Timer/Counter1, and a compare match will clear TCNT1."

Next to OCR1A it says " The Timer/Counter Output Compare Register A contains data to be continuously compared with Timer/Counter1. Actions on compare matches are specified in TCCR1A "

 

So basically what I believe was happening, the interrupt was being triggered but TCNT1 was not being cleared - it kept running until it overflowed and triggered the interrupt once again on overflow (?)

 

I did a quick test, and my code works if you use OCR1A and then manually set TCNT1 to 0 when interrupt happens.

 

What mislead me was this part of OCR1A's description : "Actions on compare matches are specified in TCCR1A". I thought if TCCR1A is set to CTC, it will clear TCNT1 when interrupt occurs, which is not the case. Apparently, the only register which causes TCNT1 to be automatically cleared is OCR1C.

 

Out of curiosity, may I ask why you call this mode of TC1 "CTC-like" ?

 

Thanks for help once again!

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

The Timer/Counter1 section of the datasheet contains the letter sequence CTC exactly zero times. That mode is only mentioned in the Timer/Counter0 section. TC1 has 5 mode: Normal Mode and 4 PWM modes. You are using Normal Mode with an active Compare Output.

 

If you are just after a periodic interrupt, I would use overflow instead of compare. To me that intuitively indicates a periodic event that doesn't change. When I see a compare interrupt I expect something to happen at a specific interval relative to overflow.

 

 

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

david.prentice wrote:

    TCCR1A = (1 << COM1A0);   // toggle on COMPA
    OCR1C = F_CPU / 64 / 1000 - 1; // 1ms period
    OCR1A = 51;

 

If you want 1200 interrupt frequency

    OCR1C = F_CPU / 64 / 1200 - 1; // 833us period
 

Using COM mode #1 will toggle the OC1A pin in hardware.   (PB1)

Since OCR1C is 103 there will be a COMPA with OCR1A = 51.

If OCR1A was greater than 103 there would never be a match.

 

What do you really want?

A 1200Hz square wave or a 600Hz square wave.

Or a variable duty-cycle PWM with period 833us.

 

PWM can operate without any interrupts.   But you must use hardware pins e.g. OC1A,  /OC1A)

If you want,  you can use both COMPA and OVF interrupt vectors to control random GPIO pins.

 

Life is much easier with the Simulator.   It tells you the exact cycles and time to the nanosecond.

But obviously a scope or Logic Analyser can give you the real signals.

Note that the tiny461 has an 8MHz RC and a 128kHz RC.   And a CLKDIV8 fuse.   And a PLL clock.

 

David.

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

I was bored.   

 

This should give you a 1200Hz PWM signal with 50% duty cycle.

Edit duty variable for a different shape.   Edit the 1200 for a different frequency.  

Note that you could use div32 for slightly better resolution.  e.g. OCR1A = 0..207 instead of 0..103

You don't need any interrupts.   But they are handy for checking the cycle counts and each pin changing in the Simulator.

I strongly recommend trying this in the Simulator.

 

#define F_CPU 8000000

#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER1_COMPA_vect)
{
    //PINB = (1 << PB0);  //toggle pin
}

ISR(TIMER1_OVF_vect)
{
    //PINB = (1 << PB1);  //toggle pin
}

int duty = 50;               //50% duty-cycle

int main(void)
{
    DDRB |= (1 << PB1)|(1 << PB0);      // OC1A, /OC1A pin
    TCNT1  = 0;              // default at Reset
    TCCR1A = (1 << COM1A0)|(1 << PWM1A);   // Clear on match, Fast PWM, both OCR1A, /OCR1A
    OCR1C = F_CPU / 64 / 1200 - 1; // 1ms interrupt
    OCR1A = (((OCR1C + 1) * duty) / 100) - 1;
    //OCR1A = 51;              // 50% duty-cycle
    TCCR1B = (7 << CS10);    // 1:64 prescaler
    TIMSK |= (1 << OCIE1A);  // enable timer compare interrupt
    TIMSK |= (1 << TOIE1);   // enable timer overflow interrupt
    sei();                   // only need interrupts for Simulator
    while (1)
    {
        asm("nop");
    }
}

 

David.

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

david.prentice wrote:

 

What do you really want?

A 1200Hz square wave or a 600Hz square wave.

Or a variable duty-cycle PWM with period 833us.

 

PWM can operate without any interrupts.   But you must use hardware pins e.g. OC1A,  /OC1A)

If you want,  you can use both COMPA and OVF interrupt vectors to control random GPIO pins.

 

I actually needed COMPA interrupt vectors to work - the whole point was to understand how COMPA interrupt works and how to setup timers for it to work properly :) I didn't really need PWM - I wanted to have a working timer that would trigger X times per second (code was used to realize serial transmission)

But thank you for the code you provided, it helped me to understand PWM mode of the timer :)

 

Thanks for quick help everyone, my question has been answered - I was simply using incorrect (for my purpose) register :)