Timers, PWM, and ATTiny84

I’m working on a project where I am wanting to use 4 PWM outputs, but I also use millis. Currently in my code, I configure the timers thusly:

TCCR1A = (TCCR1A & 0b11110000) | 0b0001;
  TCCR1B = (TCCR1B & 0b11110000) | 0b0001;
  TCCR0A = (TCCR0A & 0b11110000) | 0b0001;
  TCCR0B = (TCCR0B & 0b11110000) | 0b0001;

Inside my loop, just for the purposes of testing, I’m blinking an LED just to monitor that millis is working. That portion looks like this:

if (currLEDState == LOW) {
    if (currentMillis - prevMillis >= currLEDOffInterval) {
      currLEDState = HIGH;
      prevMillis += currLEDOffInterval;
      digitalWrite(ledOut, HIGH);
    }
  }

  if (currLEDState == HIGH) {
    if (currentMillis - prevMillis >= currLEDOnInterval) {
      currLEDState = LOW;
      prevMillis += currLEDOnInterval;
      digitalWrite(ledOut, LOW);
    }
  }

All that is doing is taking my pre-set delay time, and making sure the LED is on for half of it and off for half of it.

So I have some questions, as most of what I can find on the forum is regarding ATTiny85. While there are many similarities, there are also some differences. My setup is:

Board:ATTiny24/44/84
Processor: ATTiny84
Clock: Internal 8 MHz
LowFuse: 0xE2 (disabled CLKDIV8)

Here are the questions/issues that I have:

  1. Delay interval is off by a factor of 32. I have to slow everything down by 32 (or, conversely, multiply my desired delay value by 32).

I wonder if this has to do with which timer is being used, as one timer is 8 bit and the other is 16 bit. However, I would expect that if this is the case, that there would be an 8 bit (factor of 256) difference.

  1. Should I be able to use millis() and delay() while having 4 PWM channels configured?

I am under the impression that one timer is needed to do millis(), delay(), etc. Am I not actually configuring my PWM output channels correctly?

Any help here would be great. This is my first topic, so let me know if any additional info is needed.

I took a quick look at the Attiny84 datasheet and it only has two timers (an 8 bit and a 16 bit). millis() is probably using the 16 bit timers since it offers more flexibility compared to using the 8 bit one.

You can use the 4 PWM output but you would have to give up millis() and roll out your own timer using interrupts.

Here's how I think you would implement it

Make sure your data direction registers are set for all your PWM pins

First disable millis() by shutting down both timers (since you are not sure which one is being used)

TCCR0B = 0x00;
TCCR1B = 0x00;

Then do your normal fast PWM initialization for both timers

On one of the timers (preferably timer1, enable overflow interrupts)

Then go ahead and start both timers

I don't have the Arduino IDE but I imagine its going to look like this

volatile uint8_t flag = 0;

ISR(TIM1_OVF_vect)
{
	flag++;
}

setup {
	// setup output pins
	// setup tim0 PWM
	// setup tim1 PWM
}

loop {

	if(flag > 99)
	{
		flag = 0;
		
		// this code gets executed every X seconds
	}	
}

The key here is knowing the period of PWM1. Lets say you have a 50 Hz PWM, meaning timer1 overflows every 20 ms
The flag variable increases by 1 every 20 ms

Inside you main loop, you test for the value of flag. In my example, once flag reaches 100 (or after 2 seconds), the statement becomes true. You then reset the flag to start the counter from zero and wait till it reaches 100 again (which is after 2 seconds).

This would allow you to use all PWM outputs and still has some sort of a delay implemented (by varying the compare value).

Hope this helps.

Are you using this core to support the tiny84?

This will give you millis() and four pwm outputs via analogWrite(). If this doesn't meet your needs perhaps you should explain what you are trying to achieve.

You can find documentation for the core at ATTinyCore/avr/extras/. It is also available at the github site.

You should post all of your code, not bits and pieces.

Millis uses Timer0 (like basically all AVR parts - I don't think I have ever seen an example of this!). This also drives two PWM channels.

As long as you do not manipulate the timer0 registers directly (for example, to change the frequency), millis() will work.

If you do need to manipulate the timer0 registers to change the frequency - particularly if you want to increase it significantly (which it appears that you do), you should disable millis entirely. At prescale of 1 instead of 64, with millis enabled, the CPU is spending something like 40% (very rough estimate - I have not timed the classic AVRs. That data is extrapolated from the tinyAVR 0/1-series supported by megaTinyCore) of it's time in the millis ISR!

With millis disabled, delay() and delayMicroseconds() still work (slightly less accurately - delay gets changed from the normal implementation, which allows other interrupts to fire and calls yield(), to just calling delayMicroseconds(1000) (which turns off interrupts and counts clock cycles) for every millisecond).

If you're looking for something that size, but with 4 PWM channels you can play with without trashing millis, there are two options:

(and, naturally, I have breakout boards for those, assembled or bare, in my Tindie store :stuck_out_tongue: )

As an aside, if you're planning to use that PWM to switch a MOSFET, you probably need a gate driver on it - the relatively weak drivers on an microcontroller I/O pin are not able to switch a FET on/off as quickly as one might want (see "gate charge", "gate capacitance"), so switching losses (from the time the FET is half-on durign the transitions, and hence conducting, but at higher resistance) increase, leading to your fet getting far hotter than you would expect. More of a problem with big FETs, as they have higher gate capacitance (so slower switching) and are presumably carrying a heavier load, hence a half-on FET will generate more heat. I do happen to sell MOSFET boards with beefy MOSFETs and gate drivers if you really need high current PWM at high frequencies - gate drivers are designed to switch with low input current at logic levels while supplying a brief pulse of high current into the gate to slam it on or off quickly.

I only know of one AVR for which cores have been in circulation that didn't use timer0 for millis. Some old '85 cores used timer1 instead. The "logic" if you can call it that, was that timer1 on the '85 is weird (it's a wacky "high speed" timer than can be clocked at 64MHz from the on-chip PLL, among other things), and they assumed nobody would know how to program it, since it's unique to the '85.... but nobody knows the 8-bit timer0 off-hand either, because on every other AVR with arduino support, it's used for millis! And the high-speed timer1 is like, one of the headline features of the '85....

Thank you all for the great info! I have been doing a bunch of digging and experimenting as well. I found a couple of issues with what I was doing.

  1. I wasn't even configuring the TCCR's correctly. I was re-using some code from an ATTiny85 that didn't really work with what I needed. I spent some time on the data sheet, as well as on this page: How to set the PWM-frequency for the Attiny84

  2. With timer0 being used for millis(), I found DrAzzy's posts about ATTiny841 and have ordered a couple of those to play with. This looks like it should basically be exactly what I'm looking for. I had never even heard of it before yesterday. Having the two 16 bit timers with their resolution will actually help solve some issues I was trying to work around with my code.

  3. The factor to make delay() work was due to manipulating Timer0, as you guys have mentioned. I commented out the Timer0 configuration writes and it now works as expected. Unfortunately, this means ATTiny84 won't work for this application, but as already noted, the ATTiny841 looks amazing. I've got some SOIC-to-DIP adapters en route, as well as an SOIC14 socket for programming purposes.

Again, I want to thank you all for helping me with this. Just goes to show that digging deeper into the datasheet is useful. The funny thing is that I work at a semiconductor company and have helped with datasheet review before. I guess I was just trying to cut corners. Thanks all!