Porting timer1 code from ATTiny84 to ATTiny85

(Please let me know if this thread should be placed in the “Programming Questions” sub-forum!)

I wish to port the following code functionality from Tiny84 to Tiny85:

// ATTINY84 running at 8MHz internal clock
// 
// Timer/counter1 : 50% duty-cycle
//                  16kHz to 48kHz adjustable frequency range
//                  no prescale (4MHz) operation for improved freqeuncy range resolution

// GPIO defines -------------------------------------------
#define SPEED       A3    // Timer frequency setting input (analog)
#define CLKOUT      6     // Timer output (TIMER1, OC1A)

// Timer frequency function -------------------------------
void timerFreq() {
  OCR1A = 252 - (analogRead(SPEED)/6); // Yields approx. 16kHz to 48kHz freq range
}

// SETUP --------------------------------------------------
void setup() {
  pinMode(CLKOUT, OUTPUT);  // Set pin as output

  TCCR1A = _BV(COM1A0) | _BV(WGM11) | _BV(WGM10); // COM : Toggle OC1A on Match
  TCCR1B = _BV(CS10) | _BV(WGM12) | _BV(WGM13);   // CS  : no prescale 
                                                  // WGM : FAST PWM, variable top (OCR1A) yields 50% duty-cycle
}

// LOOP ---------------------------------------------------
void loop() {
  timerFreq();
}

The code was developed with aid from the below article. I suspect the setup I’ve used is over-complicated as I just require a 50% duty-cycle clock output:
https://andreasrohner.at/posts/Electronics/How-to-set-the-PWM-frequency-for-the-Attiny84/

When porting to Tiny85 I wish to use timer1 OC1A because timer0 affects the millis() function that I intend to use for a separate, unrelated purpose. The Timer1 setup is different for Tiny84 vs Tiny85 and I’m unsure how to proceed. Tiny85 Timer1 setup is done with the TCCR1 and GTCCR Control Registers (12.3.1 and 12.3.2 in the datasheet):

TCCR1 [CTC1 . PWM1A . COM1A1 . COM1A0 . CS13 . CS12 . CS11 . CS10]

GTCCR [TSM . PWM1B . COM1B1 . COM1B0 . FOC1B . FOC1A . PSR1 . PSR0]

From my understanding I do not need to modify the GTCCR register as it relates to Timer1 OC1B. I wish to use OC1A. In the TCCR1 register I setup for no prescale, and “toggle OC1A upon compare match”. I also disable the OC1A-invert on PB0. This is important as I intend to use the leftover three GPIO pins for an unrelated purpose. I’m unclear about the function of the CTC1 register and how to calculate and adjust the output frequency. Both the OCR1A and OCR1C registers seem to relate to the frequency setting of OC1A output.

My Tiny85 code (so far):

// ATTINY85 running at 8MHz internal clock
// 
// Timer/counter1 : 50% duty-cycle
//                  16kHz to 48kHz adjustable frequency range
//                  no prescale (4MHz) operation for improved freqeuncy range resolution

// GPIO defines -------------------------------------------
#define SPEED       A3    // Timer frequency setting Pot (analog)
#define CLKOUT      1     // Timer output (TIMER1, OC1A)

// Timer frequency function -------------------------------
void timerFreq() {
  // Adjust Timer1 OC1A output frequency using analogRead(SPEED)
}

// SETUP --------------------------------------------------
void setup() {
  pinMode(CLKOUT, OUTPUT);  // Set pin as output

  TCCR1 = _BV(CS10) | _BV(COM1A0);  // CS : no prescale
                                    // COM : Toggle OC1A on Match, disable OC1A-invert
}

// LOOP ---------------------------------------------------
void loop() {
  timerFreq();
}

Couple of observations.

Timer 1 is 8-bit so the max count will be 255.

You need to set CS01 to turn the timer on (no pre-scale). See Table 12-5.

With PWM1A set, you need to look at Table 12-1 for the OCR1A functioning when setting COM1A1:COM1A0, there is no toggle available. OCR1C will now be the TOP and what happens with OCR1A when TOP is reached is determined by the COM1A1:COM1A0 pair.

Also with only 8 bits you have a very limited frequency range without using the pre-scaler as well. The pre-scaler has a lot more options than timer1 in the ATtiny84.

Hope that gives some answers.

EDIT: Just to add - You set OCR1C to the overall frequency and then set OCR1A to half that which will give you 50% duty cycle. If you want to disable the inverted OCR1A you need to set COM1A1:COM1A0 = 1:0 (or 1:1 to start with a low. See Table 12-1).

Willem.

Thank you Willem! I updated the code to reflect the suggested changes.

// GPIO defines -------------------------------------------
#define SPEED       A3    // Timer frequency setting Pot (analog)
#define CLKOUT      1     // Timer output (TIMER1, OC1A)

// SETUP --------------------------------------------------
void setup() {
  pinMode(CLKOUT, OUTPUT);  // Set pin as output
  TCCR1 = _BV(PWM1A) | _BV(COM1A0) | _BV(COM1A1) | _BV(CS10);  

  // CS : no prescale ( /1 )
  // COM : Set OC1A on compare match, disable OC1A-invert
  // PWM1A : Activate PWM mode
}

// LOOP ---------------------------------------------------
void loop() {
  OCR1C = 252 - (analogRead(SPEED)/6); // Yields approx. 16kHz to 48kHz freq range
  OCR1A = OCR1C / 2;  // 50% duty cycle
}

Why is it necessary to have the Timer in PWM mode?

Would the following formula be correct for calculating the output frequency?

f = [ CPUclockfrequency / Prescaler / (TOPlimit + 1) / 2 ]
TOPlimit = OCR1C

EDIT:
In the PWM mode:

  • OCR1C is the compare match register / TOPlimit for resetting the counter
  • OCR1A is the comparator for setting the duty cycle

If I understand correctly the timer starts counting at 0 which would mean that to gain a 50% duty cycle at for instance OCR1C = 7 I would need OCR1A = 3. In the same instance OCR1A = 1 would yield a 25% duty cycle?

But if for instance OCR1C = 4 there's no way to gain a 50% duty cycle (only 40 or 60 if OCR1A = 1 or 2)? Am I understanding this correctly?

Hope to test the code tomorrow. Will report my findings.

That is correct.

If you can lower the prescaler another notch (be sure to read the datasheet - the prescaler on the '84's timer1 lets you do only every other power of 2, while the '85's timer1 prescaler lets you do every power of two). You can then increase the TOP to get the same frequency, and now you'll be able to get a more precise duty cycle.

On the t85 (and t861, but almost nothing else) you can clock the timer off of the on-chip PLL, which will make it run at a base frequency of 64MHz instead of 8 (and then prescale it down to what you want) if you need a high enough frequency that you can't get the duty cycle you need with a prescaler of 1.

The timer1 on the t85 (and 861) are very different from timer1 on normal AVRs - basically every other AVR (with a few exceptions) has the same 16-bit timer1; but the t85/861 have an 8 (or 10 for 861)

As an aside, if you're using my ATTinyCore, from the tools menu you can choose the timer1 clock source as the PLL, instead of doing it yourself. And if you use the right pin, you can even use Tone() to get 50% duty cycle from timer1 at very high frequencies.

knutolai:
Why is it necessary to have the Timer in PWM mode?

Not really necessary. Another way to use timer_1 for your purpose would be to use normal mode.

Set TCCR1 to clear on reaching TOP (the OCR1C value) and then use the overflow interrupt to clock any output pin which is convenient.

OCR1C = required TOP value.          // Reaching TOP will toggle output pin
TCCR1 = (1<<CTC1) | (1<<CS10)        // Clear CTC1 on TOP  (ie OCR1C) - pre-scaler to 1
TIMSK |= (1<<TOIE1)                  // Enable Overflow vector for timer 1
// Make sure you OR the enable bit without changing any of the other bits (don't ask)
.....

ISR(TIMER1_OVF_vect)  {              // Interrupt vector for TIMER-1 OVR which set 50% period
  OutputPin = !OutoutPin             // Toggle any of the available output pins;           
}

This will always produce a 50% pulse at half the OCR1C value frequency. Another option to play with.

Willem.

Thanks! Would the interrupt vector in the last code by Willem43 be disruptive to other functions? Say would a function utilizing millis() or delay() be able to work simultaneously? Like for instance a code using delay() to blink an LED?

knutolai:
Thanks! Would the interrupt vector in the last code by Willem43 be disruptive to other functions? Say would a function utilizing millis() or delay() be able to work simultaneously? Like for instance a code using delay() to blink an LED?

Depends on which core you are using. My ATTinyCore does not use Timer1 for millis (it uses timer0, like most every other board uses). However, I have seen cores that use timer1 for millis on the t85, on the justification that “timer1 is too confusing, and timer0 is the same as timers on other boards so people will know how to use it” - which is a terrible justification because a) nobody has any experience with timer0, because nobody uses it on other boards because millis uses it, b) timer1 is also the “jewelry” of the t85, the peripheral that can do things that almost no other AVR can, and c) why are worrying about how hard it is for people who are writing timer registers directly?

knutolai:
Would the interrupt vector in the last code by Willem43 be disruptive to other functions?

I assume you are using DrAzzy's ATTinyCore, and if you don't, you should.

In that case the answer is no. Using interrupts with timers is standard fare with AVR MCUs. There is a reason they have them on every compare and overflow. The time taken for that interrupt is miniscule. Just try it.

Actually, since you mention it, using delay() is far more disruptive than any interrupt. Also, never use it in an interrupt.

Willem.

Ah I see. So when the interrupt is complete the code execution continues where it left off. I had an impression that the interrupt would break other executions and leave you at the top of the main loop() when it's done.

I'm currently using "attiny" by David A. Mellis, but I'll install DrAzzy's cores and try them out.

Would the formula for calculating the timer frequency stay the same in the non-pwm code?

f = [ CPUclockfrequency / Prescaler / (TOPlimit + 1) / 2 ]
TOPlimit = OCR1C

knutolai:
Ah I see. So when the interrupt is complete the code execution continues where it left off.

Yes, when an interrupt occurs the current MCU state is saved, the interrupt code executed and then finally the MCU status restored to continue where it left off.

Would the formula for calculating the timer frequency stay the same in the non-pwm code?

f = [ CPUclockfrequency / Prescaler / (TOPlimit + 1) / 2 ]
TOPlimit = OCR1C

My last example uses what is called CTC Mode - Clear Timer on Compare Match. If you want to learn more there are some good tutorials on the web, including some by Nick Gammon (present here on the site).

A short explanation: Since we use the overflow to toggle the output pin, the pin only changes once each time we match OCR1C. This implies we need to cycle the timer twice to get a full period.

So, Time = 2 x Timer_time and since Timer_time = Timer_clock x (OCR1C + 1) and Frequency = 1 / time we get the final frequency as

Frequency = Frequency_clock / (2 x (OCR1C + 1) ).

Frequency_clock = Frequency_system/Pre-scaler, so that brings us to your formula above. A long winded answer but at least you know how we get the formula.

Edit: Note that for the ATtiny85 we can use either the system clock or either a 32MHz or 64MHz clock as a timer_1 clock. These options are selectable in Tools when using DrAzzy’s ATTinyCore.

Willem.

Thank you for that explanation. I think the CTC mode option is best suited for my purpose as it always yields a 50% duty cycle. The timer output will be used to clock a separate micro. I've ordered a PCB to test the code. I'll post the complete code once I have it working.

knutolai:
I've ordered a PCB to test the code. I'll post the complete code once I have it working.

You could put an ATtiny85 together on a breadboard in a few minutes to test the code. In any case, have fun and I hope it all works for you.

Willem

I agree. I just need to test it together with a specific SMD package chip which is hard without the PCB :slight_smile: Haven’t gotten around to finish the soldering quite yet.

About the 8 bit frequency range. Could it be expanded to 10 bit with the below method or would that require too many instructions per interrupt?

#define pot A0; // potentiometer analog input
unsigned int counter = 0;

OCR1C = 0          // Reaching TOP will toggle output pin
TCCR1 = (1<<CTC1) | (1<<CS10)        // Clear CTC1 on TOP  (ie OCR1C) - pre-scaler to 1
TIMSK |= (1<<TOIE1)                  // Enable Overflow vector for timer 1


ISR(TIMER1_OVF_vect)  {              // Interrupt vector for TIMER-1 (Timer freq = system clock freq)
  counter += 1;                     // increment counter
  if (counter > analogRead(pot)) {  // compare counter with 10bit pot value
    counter = 0;                     // reset counter
    OutputPin = !OutputPin             // Toggle output pins;      
  }     
}

An inerrupt routine must execute as fast as possible. The analogRead is going to take a long time - too long for me.

If you want a 10bit timer count why do you not use the ATtiny84?

I always get a DIL package along with the SMD, when available. That allows me to experiment on the breadboard. I have also made some "breakout" PC boards to mount a SMD for use on a breadboard.

Willem.

Willem43:
An interrupt routine must execute as fast as possible. The analogRead is going to take a long time - too long for me.

If you want a 10bit timer count why do you not use the ATtiny84?

Ah I see. So the method would be viable if I placed the analogRead in the loop() instead of in the interrupt routine like in the code below? For my purpose I don’t need a very fast reading speed for the analogRead() so that wouldn’t really matter. I got the other codes posted in the thread working, but I ended up needing more I/O pins for my project so I’ll switch to the t84. I’ll look into the 10bit timer config.

Thank you so much for all the great help!

#define pot A0; // potentiometer analog input
unsigned int counter = 0;

OCR1C = 0          // Reaching TOP will toggle output pin
TCCR1 = (1<<CTC1) | (1<<CS10)        // Clear CTC1 on TOP  (ie OCR1C) - pre-scaler to 1
TIMSK |= (1<<TOIE1)                  // Enable Overflow vector for timer 1


ISR(TIMER1_OVF_vect)  {              // Interrupt vector for TIMER-1 (Timer freq = system clock freq)
  counter += 1;                     // increment counter
  if (counter > limit) {  // compare counter with 10bit pot value
    counter = 0;                     // reset counter
    OutputPin = !OutputPin             // Toggle output pins;      
  }     
}

void loop() {
  limit = analogRead(pot);
}