Interrupts on ATTiny13 (INT0)

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

HI,

 

I am trying to get some more understanding of the ATTiny family and playing with the ATTiny13 (see previous post).

 

I got the great help getting my LED blinking using TIMER0 and CTC

 

Right now I try to have the mcu respond to an INT0 interrupt. I have my code embedded below...

I have added a push button to INT0 (PB1), which pulls the pin to ground when pushed. But somehow the interrupt isn't firing.

(The intention is to send an IR command from another ATTiny, but to understand the logic behind it, this project only blinks the LED at 1 Hz)

 

Schematic:

 

Code:

/*
 * 1HzCTCTest.c
 *
 * Created: 2-9-2014 20:17:35
 *  Author: Xander
 */ 


/* We want to have the led blink once every second,
 * which means we have to set the internal oscillator
 * to 4.8Mhz, with th /8 devider enables
 *
 * Also we have to use the 1024 Counter Prescaler set
 *
 */

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

#ifndef F_CPU
#define F_CPU 600000UL
#endif

#include <util/delay.h>

#define LED PB0
#define RED PB4

volatile uint8_t j = 0;

/*
 * Internal functions
 */
void setupClkPrescale(void){
	// Enable to set clock prescale setup
	CLKPR |= (1 << CLKPCE);
	
	// Set the clock prescale to 2, effective clock speed should
	// be 300kHz
	CLKPR |= (1 << CLKPS0);
}

void initMCU() {
	for (uint8_t i = 0; i < 12; i++ ) {
		PORTB ^= (1 << LED);
		_delay_ms(25);
	}

	// We want to enable out INT0 interrupt handler(s)
	GIMSK |= (1 << INT0);

	// A low pin should trigger the interrupt
	MCUCR &= ~(1 << ISC01);
	MCUCR |= (1 << ISC00);

	sei();
}

void setupTIM0(void) {
	// We want to create a 1Hz blinking LED signal, which means
	// (If I am correct) that we have to setup the clock
	// at twice the speed, so we can properly blink the IR LED
	// For the test I am running the tiny at 4.8MHz / 8 = 600kHz
	// The calculations show, that I need to fire an interrupt
	// at the rate of 2Hz (2*1Hz)
	// We are using a timer prescaler of 1024, so we have to
	// fire the interrupt every 145 cycles
	
	// Set the CTC mode
	TCCR0A |= (1 << WGM01);
	
	// Set the prescaler to 1024 && enable the clock
	// Just setting all bits to 0 doesn't enable the clock
	TCCR0B |= (1 << CS00);
	TCCR0B &= ~(1 << CS01);
	TCCR0B |= (1 << CS02); 
	
	// Initialize our compare value
	OCR0A = 145;
	
	// Enable the compare interrupt
	TIMSK0 |= (1 << OCIE0A);
	
	// Initialize the counter (TCNT0)
	TCNT0 = 0;

}

/*
 * The interrupt handlers
 */
ISR (INT0_vect) {
	for ( uint8_t i = 0; i < 12 ; i++) {
		PORTB ^= (1 << LED);
		PORTB ^= (1 << RED);
	}
}

ISR (TIM0_COMPA_vect) {
	if ( j < 12) {
		// Flip/toggle the port/LED
		PORTB ^= (1 << LED);
		j++;
	} else {
		// Just do nothing (for now)
		j = 0;
	}
}

ISR (BADISR_vect) {
	// We shouldn't get here (??)
	for (uint8_t u = 0; u < 25; u++){
		PORTB ^= (1 << RED);
	}
}
/*
 * Main program loop
 */
int main(void)
{
	// Set the output pin (LED == PB0)
	DDRB |= (1 << LED);
	DDRB |= (1 << RED);

	// Initialize the MCU
	initMCU();

	// Setup the internal clock prescaler
	setupClkPrescale();
	
	// Setup the timer(s)
	setupTIM0();
	
    while(1)
    {
    }
}

 

This topic has a solution.

It's hard to return to basics when you always had everything available

Last Edited: Tue. Sep 9, 2014 - 07:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How have you determined that interrupt isn't firing?

 

Looks like you think the prescaler in CLKPR is separate from the CKDIV8 fuse.

 

It isn't.

 

The two are related.

 

Programming the CKDIV8 fuse simply cause the AVR to come out of reset with CLKPS[3:0] = 0b0011 for a prescaler of 8. You're changing it to a prescaler of 2, so the AVR will be running at 2.4 MHz, not 300 kHz like you think.

 

If you want to run at 300 kHz, set the prescaler to 16 with CLKPS[3:0] = 0b0100

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

In at least 2 places where you're trying to blink LEDs, you're blinking them WAY too fast.  Only a scope would show those blinks.

 

Also, you don't seem to have a pullup on INT0 (assuming that PB1 is INT0).

 

Also, with a button you're almost certain to get multiple interrupts very quickly.  You might get 10 or 20 in a few milliseconds.  Better to just turn the LED on in the ISR rather than to toggle it, to confirm you got the interrupt.  Then read about the better way to read and debounce buttons, using timer interrupts.

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

joeymorin wrote:

How have you determined that interrupt isn't firing?

 

LED1 and LED2 should blink 12 times ,see ISR (INT0_vect). I did forget to add a little _delay_ms() between them both, but after adding these, I still see nothing happening

 

joeymorin wrote:

Looks like you think the prescaler in CLKPR is separate from the CKDIV8 fuse.   It isn't.   The two are related.   Programming the CKDIV8 fuse simply cause the AVR to come out of reset with CLKPS[3:0] = 0b0011 for a prescaler of 8. You're changing it to a prescaler of 2, so the AVR will be running at 2.4 MHz, not 300 kHz like you think.   If you want to run at 300 kHz, set the prescaler to 16 with CLKPS[3:0] = 0b0100

 

Hmmm, good point. Think I made an error making these assumptions (you know what they say about that...) Somehow LED1 seems to blink at 1 Hz, weird...

 

Thanks for the info

It's hard to return to basics when you always had everything available

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

Hmmm... looks like the ATtiny13 is different. I made an assumption that CKDIV8/CLKPR are related in the same way that they are with newer device. That seems not to be the case. My bad :(

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

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

kk6gm wrote:

In at least 2 places where you're trying to blink LEDs, you're blinking them WAY too fast.  Only a scope would show those blinks.

I just added a little _delay_ms() to these, didn't think about that before blush

 

kk6gm wrote:

Also, you don't seem to have a pullup on INT0 (assuming that PB1 is INT0).

 

I did test with and without a 10KΩ pull-up. But just added the pull-up again.

 

kk6gm wrote:

Also, with a button you're almost certain to get multiple interrupts very quickly.  You might get 10 or 20 in a few milliseconds.  Better to just turn the LED on in the ISR rather than to toggle it, to confirm you got the interrupt.  Then read about the better way to read and debounce buttons, using timer interrupts.

Thank you for the info, I did read about debouncing, but thought (again: assumptions blush) I didn't have to use this for the test.

When just enabling LED2, it works and I see the interrupt is fired

It's hard to return to basics when you always had everything available

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

Thank you for the info, got the project doing what I intended to do

 

 

It's hard to return to basics when you always had everything available

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

"Thank you for the info, got the project doing what I intended to do"

 

So, what changes did you do to make it work?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

I added a little debounce hack in the code (needs some better implementation, but it works) and I added a _delay_ms() after the LED toggle

 

ISR (INT0_vect) {
    for ( uint8_t i = 0; i < 12 ; i++) {
        PORTB ^= (1 << RED);
        _delay_ms(50);
    }

    // Dirty hack, needs a cleaner solution
    // but works for now (just see something)
    _delay_us(100);
}

 

Once again, another lesson learned

It's hard to return to basics when you always had everything available

Last Edited: Tue. Sep 9, 2014 - 09:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As written, the _delay_us(100) is worthless since you have already delayed over 600ms with the blinking. But you still have a problem since the ISR will still be run twice. During the delay the interrupt flag will be set again, so as soon as you leave the ISR it will be run a second time. But using an external interrupt to detect a button press is a bad idea anyways. However since your final goal is to use the interrupt for something that does not bounce, I wouldn't worry about debouncing for the moment.

Regards,
Steve A.

The Board helps those that help themselves.

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

xjmaas wrote:

I added a little debounce hack in the code (needs some better implementation, but it works) and I added a _delay_ms() after the LED toggle

 

ISR (INT0_vect) {
    for ( uint8_t i = 0; i < 12 ; i++) {
        PORTB ^= (1 << RED);
        _delay_ms(50);
    }

    // Dirty hack, needs a cleaner solution
    // but works for now (just see something)
    _delay_us(100);
}

 

Once again, another lesson learned

It would be a very good educational exercise to figure out how to program the same functionality without any _delay_ms()/us().  Figuring out how to do something very slowly (e.g. blinking an LED a few times a second) without wasting millions of clock cycles in wait loops is a critical lesson for any embedded programmer.

 

I would state the exercise this way:

 

1) detect a button push as an external interrupt

2) set a flag F in the ISR

3) outside of the ISR, when F is set blink an LED N times with M milliseconds between blinks

4) while blinking, ignore any further button pushes

5) when blinking is finished, re-enable button push detection

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

Koschchi wrote:

As written, the _delay_us(100) is worthless since you have already delayed over 600ms with the blinking. But you still have a problem since the ISR will still be run twice. During the delay the interrupt flag will be set again, so as soon as you leave the ISR it will be run a second time. But using an external interrupt to detect a button press is a bad idea anyways. However since your final goal is to use the interrupt for something that does not bounce, I wouldn't worry about debouncing for the moment.

 

Thank you for the info, I will remove the _delay_us(100).

 

kk6gm wrote:

It would be a very good educational exercise to figure out how to program the same functionality without any _delay_ms()/us().  Figuring out how to do something very slowly (e.g. blinking an LED a few times a second) without wasting millions of clock cycles in wait loops is a critical lesson for any embedded programmer.

 

This what I intend to do. I 'just' added the delays so I can see what happens and the LED blink to debug my INT0 problem. The button will be removed in my final project and the INT0 should be given by another ATTiny.

 

kk6gm wrote:

I would state the exercise this way:

 

1) detect a button push as an external interrupt

2) set a flag F in the ISR

3) outside of the ISR, when F is set blink an LED N times with M milliseconds between blinks

4) while blinking, ignore any further button pushes

5) when blinking is finished, re-enable button push detection

 

Thank you for the 'excerise'. I will see into it and try to implement it.

 

It's hard to return to basics when you always had everything available

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

kk6gm wrote:

I would state the exercise this way:

 

1) detect a button push as an external interrupt

2) set a flag F in the ISR

3) outside of the ISR, when F is set blink an LED N times with M milliseconds between blinks

4) while blinking, ignore any further button pushes

5) when blinking is finished, re-enable button push detection

 

You mean something like (Do not mind the comments, some might have been left behind during some clean-up):

/*
 * CTCTest.c
 *
 * Created: 2-9-2014 20:17:35
 *  Author: Xander
 */ 


/* We want to have the led blink once every second,
 * which means we have to set the internal oscillator
 * to 4.8Mhz, with th /8 devider enables
 *
 * Also we have to use the 1024 Counter Prescaler set
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdbool.h>
#include "main.h"

#ifndef F_CPU
#define F_CPU 600000UL
#endif

/*
 * Global/private variables
 */
volatile bool INT0FIRED;

/*
 * Internal functions
 */
void setupClkPrescale(void){
	// Enable to set clock prescale setup
	CLKPR |= (1 << CLKPCE);
	
	// Set the clock prescale to 2, effective clock speed should
	// be 300kHz
	CLKPR |= (1 << CLKPS0);
}

void initMCU(void) {
	// Enable our CTC interrupts
	setupCTC();

	// make sure the INT0FIRED variable is unset initially
	INT0FIRED = false;

	// Enable global interrupts
	sei();

	blinkStatus = 25;
	blinkCommand = 25;

	enableInterruptA(true);
	enableInterruptB(true);

	// We want to enable out INT0 interrupt handler(s)
	GIMSK |= (1 << INT0);

	// A low pin should trigger the interrupt
	MCUCR &= ~(1 << ISC01);
	MCUCR |= (1 << ISC00);
}

void setupCTC(void) {
	// Set the CTC mode
	TCCR0A |= (1 << WGM01);

	// Set the prescaler to 1024
	TCCR0B |= (1 << CS00);
	TCCR0B &= ~(1 << CS01);
	TCCR0B |= (1 << CS02);

	// Set the OCR0A register so we get a 1Hz blink rate for our
	// IR-to-be LED
	OCR0A = commandCount;

	// Set theOCR0B register so we get a ~4Hz blink rate for our
	// STATUS LED
	OCR0B = statusCount;

}

void enableInterruptA(bool enabled) {
	if ( enabled ) {
		// Enable the Compare Interrupt Register A
		TIMSK0 |= (1 << OCIE0A);
	} else {
		// Disable the Compare Interrupt Register A
		TIMSK0 &= ~(1 << OCIE0A);
	}

	// Reset the TimerCounter0
	TCNT0 = 0;
}

void enableInterruptB(bool enabled) {
	if ( enabled) {
		// Enable the Compare Interrupt Register B
		TIMSK0 |= (1 << OCIE0B);
	} else {
		// Disable the Compare Interrupt Register B
		TIMSK0 &= ~(1 << OCIE0B);
	}

	// Reset the TimerCounter0
	TCNT0 = 0;
}

void enableINT0(bool enabled) {
	if ( enabled ) {
		GIMSK |= (1 << INT0);
	} else {
		GIMSK &= ~(1 << INT0);
	}
}

/*
 * The interrupt handlers
 */
ISR (INT0_vect) {
	// Disable INT0 interrupt
	GIMSK &= ~(1 << INT0);

	// Set the global variable INT0FIRED
	INT0FIRED = true;
}

ISR (TIM0_COMPA_vect) {
	// As soon as we get here, we might have to disable the INT0 interrupt
	enableINT0(false);

	if ( blinkCommand > 0 ) {
		// Flip/toggle the port/LED
		PORTB ^= (1 << COMMAND_LED);
		blinkCommand--;
	} else {
		blinkCommand = 0;
		enableInterruptA(false);

		// Turn off the COMMAND_LED
		PORTB &= ~(1 << COMMAND_LED);

		enableINT0(true);
	}

}

ISR (TIM0_COMPB_vect) {
	// As soon as we get here, we might have to disable the INT0 interrupt
	enableINT0(false);

	if ( blinkStatus > 0 ) {
		// Flip/toggle the status LED
		PORTB ^= (1 << STATUS_LED);
		blinkStatus--;
	} else {
		blinkStatus = 0;
		enableInterruptB(false);

		// Turn off the STTAUS_LED
		PORTB &= ~(1 << STATUS_LED);
		// Enable INT0 interrupts
		enableINT0(true);
	}
}


ISR (BADISR_vect) {
	enableINT0(false);
	// We shouldn't get here (??)
	if ( blinkStatus > 0 ) {
		PORTB ^= (1 << STATUS_LED);
		PORTB ^= (1 << COMMAND_LED);
		blinkStatus--;
	} else {
		blinkStatus = 0;
		enableInterruptB(false);
		enableInterruptA(false);
		enableINT0(true);
	}
}

/*
 * Main program loop
 */
int main(void)
{
	// Set the output pin (COMMAND_LED == PB0)
	DDRB |= (1 << COMMAND_LED);
	DDRB |= (1 << STATUS_LED);

	// Setup the internal clock prescaler
	setupClkPrescale();
	
	// Initialize the MCU
	initMCU();
	
    while(1)
    {
		if ( INT0FIRED ) {

			// Let the 'command' LED blink 6 times
			blinkCommand = 12;
			enableInterruptA(true);

			// Clear the INT0FIRED flag
			INT0FIRED = false;
			// Re-enable INT0
			enableINT0(true);
		}
    }
}

 

It's hard to return to basics when you always had everything available

Last Edited: Sat. Sep 13, 2014 - 09:00 PM