Search code examples
cmicrocontrollerinterruptavrattiny

Weird behaviour when enabling interrupts for CTC mode onTimer0 on ATtiny85


I'm using an ATtiny85 for a simple IR project built on C (an IR remote for a DSLR camera). I generated a 38.4 kHz square signal using Timer 0 in CTC mode and set the output to toggle PB0 every time the timer reaches it's max value OCRA. I wanted to use the interrupts generated by the timer 0 compare unit to count the number of cycles that it has generated, since the counter reaches it's max two times to generate a full period of the 38.4 kHz square wave, counting two interrupts should indicate a full cycle. The problem: I noticed that every time I called the sei() function to enable global interrupts, the generated square wave would deform and the ISR associated would never be called. Using an oscilloscope I checked the generated wave on PB0:

  • Not calling sei() function during setup: The generated square wave looks as expected. working-example

  • Calling sei() during setup: The generated square wave has some longer pulses. enter image description here

How did I test this?

I implemented the following test-code that configures Timer 0 as previously described and toggles PB1 every time an interrupt associated with the compare unit of timer 0 is detected:

  • main.h
#ifndef MAIN_H
#define MAIN_H

#define __AVR_ATtiny85__
#define F_CPU 8000000UL

#endif
  • main.c
#include "main.h"
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void)
{
    cli();
    DDRB |= _BV(PB0) | _BV(PB1);
    PORTB &= ~_BV(PB1);
    // Set general register to sync mode for configuration
    GTCCR |= _BV(TSM) | _BV(PSR0);
    // Default registers
    TCCR0A = 0; 
    TCCR0B = 0;
    TCNT0 = 0;
    TCCR0A |= _BV(COM0A0) | _BV(WGM01); // Set toggle on match and CTC mode
    TCCR0B |= _BV(CS00); // internal clock no-prescaling
    OCR0A |= 12; // For app 38,8kHz
    TIMSK |= _BV(OCIE0A);
    // Enable timer
    GTCCR &= ~_BV(TSM) & ~_BV(PSR0);
    // Enable global interrupts
    sei();  
    while(1);

}
// Demo interrupt service routine
ISR(TIMER0_COMPA_vect){
    PORTB ^= _BV(PB1);
}

To compile the code I used avr-gcc as follows:

avr-gcc main.c -o main.elf -Wall -Wextra

No warnings where raised from the compiler. Then I used avr-objcopy to get the hex file:

avr-objcopy -O ihex -j .text -j .data main.elf main.hex

Finally I programmed the IC using avrdude:

avrdude -p t85 -c usbtiny -B 125kHz -U flash:w:main.hex

The fuses are:

avrdude: safemode: Fuses OK (E:FF, H:DF, L:62)

What have I tried?

- Setting the SREG register manually:

Replacing the sei() line for:

SREG |= 1 << 7;

sreg_text Does something, but still doesn't work. The generated square wave looks better, but is not as expected and the ISR is not doing anything to the PB1.

- Replacing the ISR operation: I though maybe the port operation was taking to much time, but replacing it with _NOP(); had no effect on the weird behaviour on the generated square wave.

-Tried with other IC: I had another ATtiny85 laying around and behaves the same. Maybe I have two defective ICs?

I don't know if I configured something wrong or when I enable the interrupts using sei() the IC is getting bombarded with interruptions and not working properly?

Thanks in advance for any tip or reply!


Solution

  • #define __AVR_ATtiny85__

    Don't fiddle with internal macros of the tool chain. Instead...

    avr-gcc main.c -o main.elf -Wall -Wextra

    ...compile and link with -mmcu=attiny85. If that option is missing, the link stage won't link against the startup code crtattiny85.o, and setting some macro won't fix that.

    Notice that without startup code, there won't be a vector table either. This means when an IRQ triggers and the hardware jumps to respective IRQ vector, the code will end up somewhere else in the program but not in the ISR code.

    Moreover, without -mmcu= the compiler will use the wrong AVR architecture: In avr-gcc, ATtiny85 is avr25 whereas the default (without -mmcu=) is avr2. See avr-gcc machine dependent options.


    Some improvements not related to the problem:

    • OCR0A |= 12; is odd, just OCR0A = 12;. Same for some other SFRs like configuring Timer 0 using TCCR0A/B: in almost all cases, you wans to set all bits there, not just or-ing some bits.

    • Some AVRs like ATtiny85 support toggling I/O pins by writing a one to PINx register: PINB = 1 << PB1 to toggle PortB1 instead of PORTB ^= _BV(PB1).

    • avrdude understands ELF format, so you need not build iHEX, you can flash main.elf directly, cf. documentation of option -U.

    • It might be better to define F_CPU on the command line by means of -DF_CPU=8000000. That way it's ensured that each module has the macro handy.