0

I've came up with a poor man's implementation of software-based PWM for the ATTINY44 and I'm kinda stuck now. It works but it screws up the timing functions badly (delay, delayMicroseconds) no matter what timer I use for the ISR (timer 1 or 0). The delay in my example is more like 1 second than 100ms. Can somebody point out what I'm doing wrong? :)

uint8_t pwmled[11] = {0};
volatile uint8_t currentStep = 0;
uint8_t p[11] = {PORTA0, PORTA1, PORTA2, PORTA3, PORTA4, PORTA5, PORTA6, PORTA7, PORTB0, PORTB1, PORTB2};

void initTimer1() {
  cli();
  TCCR1B = (1 << WGM12) | (1 << CS10);
  OCR1A = 64;
  TIMSK1 = (1 << OCIE1A);
  sei();
}


ISR(TIM1_COMPA_vect) {
  if (currentStep < 100)
    currentStep += 1;
  else
    currentStep = 0;
  for (uint8_t x = 0; x <= 10; x++)
    (currentStep < pwmled[x]) ? (x < 8 ? PORTA |= (1 << p[x]) : PORTB |= (1 << p[x])) : (x < 8 ? PORTA &= ~(1 << p[x]) : PORTB &= ~(1 << p[x]));
}


void setup() {
  initTimer1();
  for (int i = 0; i <= 10; i++) {
    pinMode(i, OUTPUT);
    pwmled[i] = 1;
  }
}

void loop() {
  pwmled[random() % 11] = random() % 100;
  delay(100);
}
  • What speed is your ATTiny44 clocked at? Any way to confirm this? – Paul Dec 12 '16 at 8:22
  • I've solved the issue, look in the comments below :) The attiny is clocked @8MHz using the internal oscillator. – Andrei Dragan Dec 12 '16 at 9:19
1

What you are doing wrong is:

The problem is your interrupt handling routine lasts too long. Since inside ISR() interrupt flag is automatically cleared, timer0 interrupts are being missed and delay() depends upon timer0 interrupts.

To confirm the problem is ISR, remove the for loop and just toggle one pin instead. I bet the delay will then be as it should.

Try to put sei() at the beginning of your ISR to see what is going to happen with the timming of the delay - but that is not the right way.

Instead of using delay() (based on timer0) you can count the number of timer1 interrupts but ISR routine must not last longer than the time between two interrupts and your ISR spends quite a few T-states inside the for loop. A rule of a thumb is ISR has to be very short (fast) - unless you really know what you are doing. For example, in one of my devices I am using a very long ISR routine (in fact the firmware consists of only the ISR - there is only sleep_mode(); in main) for which I know it sometimes lasts longer than the time window between interrupts and I know some of the interrupts are occasionally being missed. However, these few missed interrupts are not critical to that particular application and I am sure the interrupts are being missed rarely.

You could change OCR1A value to increase the time between interrupts but then you would have lower PWM frequency.

  • Thanks a lot for the clarification and help! I've come to a solution that works well enough for my needs (driving a bunch of LEDs). I have no idea how one's supposed to reply with a block of code but there's the link, now the timing functions work as usual. Indeed, the first code was more shitty than this one, the ISR overlapped with itself causing either timing to go nuts or a substantial amount of flicker (depending on what timer I've set to trigger the interrupt). Again, thanks a lot :D – Andrei Dragan Dec 12 '16 at 8:41
  • I usually just set a flag in the interrupt routine (or use the interrupt flag itself) and handle it in my loop. If the interrupt has a substantial amount of code/looping/calculations. – Paul Dec 12 '16 at 9:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.