Home ==> Mikrobeginner ==> 6. LED with interrupt
ATtiny13

Lecture 6: A LED blinks with the timer interrupt


With this lecture we enter into the large and powerful world of interrupt programming and say good bye to simple linear program execution. As you will see, interrupts enable a whole world of complex operations while linear programming allows to solve only simple projects and requirements. With interrupts the controller will execute several tasks (seemingly) in parallel. If you are familiar with that you will never return to the linear method. But be aware that this requires lots of new techniques that are far outside the linear program execution world (who said that assembler programming will be easy learning as it offers so many new opportunties?).

6.0 Overview

  1. Introduction to interrupt programming
  2. Hardware, components, mounting
  3. Timer with Overflow interrupt
  4. Timer with CTC interrupt

6.1 Introduction to interrupt programming

6.1.1 Interrupts

The main advantage of interrupt programming is that several processes can run in parallel and that all arising conditions that require handling can be handled in due time and correct. This is the end of any delay loops with exact execution times.

Interrupts are automated breaks that occur whenever certain conditions come true. They interrupt normal program execution, perform so-called interrupt service routines and return back to exactly the location where normal execution was interrupted. For example, if the timer overflows (from 0xFF to 0x00), a certain bit in the timer's interrupt mask port is tested. If enabled, the normal program execution is suspended and the controller
  1. sets a certain bit in a port to signal that this interrupt occurred,
  2. saves the current execution address (PC counter) in a certain storage place,
  3. clears the Interrupt Enable bit I in its status register to prevent further interrupts from execution,
  4. jumps to a certain fixed location in the flash and continues execution there, and
  5. clears the set bit in the port to signal that this interrupt is actually processed (it could well be that an interrupt cannot be processed immediately, so the bit continues to signal that interrupt execution is still to be performed).
The further program execution is a matter of the self-designed interrupt service routine (ISR), e.g. what has to be done if the timer overflows. After having done what has to be done in case of a timer overflow
  1. the Interrupt-Enable bit I in the status register has to be set again,
  2. the saved program execution address is read from the storage, written back to the PC counter and program execution continues there (where execution was interrupted).
This execution flow has several serious consequences: I-Bit This is the location of the I bit in the status register. It is by default cleared. So to execute interrupts requires to set this bit. Don't forget this. This is done with the SEI instruction. If you need to (temporarily or permanently) disable interrupt execution use the CLI instruction to clear this bit.

This bit is automatically cleared at the start of the ISR, it has to be set at the end of the ISR (see below for a respective instruction).

Home Top Introduction Hardware Overflow-Int CTC-Int


6.1.2 Stack storage

To enable the execution of interrupts the controller needs a storage where the address of the PC can be temporarily stored after having been interrupted. For this the AVRs use a so-called stack in the static memory. The ATtiny13 has 64 bytes of SRAM on board, by far enough to store those two bytes. Those are not at fixed locations but are dynamically allocated on the stack. The stack is an SRAM area that starts at the last SRAM location (in def.inc defined as RAMEND) and grows downwards, to lower addresses. Its address is in port SPL (stack pointer Low). As the ATtiny13 has less than 256 bytes SRAM, the port SPH (stack pointer High) is not necessary in our case.

The throw of the PC's address at an interrupt is decreasing SPL by two bytes. After re-storing the PC to its original value before interrupting, SPL is having its previous value again.

The stack can further be used to call subroutines and, after execution, to return to the calling location. The respective instruction is RCALL: it pushes the PC (low and high byte) to the stack, jumps to the given address (resp. a relative displacement) and at the instruction RET back to the calling address.

The same is the case at an interrupt. At returning back the I flag in the status register has to be set to enable pending and further interrupts. The return from the ISR with the instruction RETI additionally sets the I flag.

6.1.3 Interrupt vectors

To which location in the flash storage does an interrupt jump? To the first locations in the flash storage called RESET and INTERRUPT vectors. In an ATtiny13 those are:
#AddressNameDescription
00000RESETJump address after reset, by applying the operating voltage, at brown-out detection and at a watchdog reset
10001INT0Level change on the INT0 input pin
20002PCINT0Level change on an input pin
30003TIM0_OVFTimer 0 overflow
40004EE_RDYFinished a write cycle to the EEPROM
50005ANA_COMPPolarity change on the analog comparer
60006TIM0_COMPATimer 0 Compare A
70007TIM0_COMPBTimer 0 Compare B
80008WDTWatchdog event
90009ADCAD conversion complete


In later parts of the course we will use all these vectors, excluding the watchdog.

As the vectors are consequtive and have only one word (in larger ATtiny and ATmega two words) a vector has to be either a return from the interrupt or a jump instruction. The first 10 instruction words in an ATtiny13 assembler program therefore look like this:

; Reset- and vector table
;
.CSEG ; Assemble to the flash storage (Code SEGment)
.ORG 0 ; Address to zero (Reset- and interrupt vektors start at zero)
	rjmp Start ; Reset Vektor, jump to init
	reti ; INT0-Int, inactive
	reti ; PCINT-Int, inactive
	reti ; TIM0_OVF, inactive
	reti ; EE_RDY-Int, inactive
	reti ; ANA_COMP-Int, inactive
	reti ; TIM0_COMPA-Int, inactive
	reti ; TIM0_COMPB-Int, inactive
	reti ; WDT-Int, inactive
	reti ; ADC-Int, inactive
;
; Program init at Reset
;
Start:
	; [Here the program starts]

The RETI instruction on all inactive vectors ensures that in case of an unplanned activation of an interrupt the return back is executed in a systematic manner.

More chaotic software designers are incautious and set the vector address with an .ORG directive. They forget that a flash memory location cannot be empty but still contains 0xFFFF, an NOP instruction. If they accidentially enable a timer 0 compare match interrupt funny things will happen. In the most probable case a different ISR is executed or the I flag is never set and further interrupts are simply blocked or the init routine restarts all indefintely. Good luck with debugging and identifying the error.

6.1.4 The timer overflow interrupt

Overflow-Int-Enable To use this interrupt the Overflow-Interrupt-Enable-Flag TOIE0 in the Timer Interrupt Mask Register TIMSK0 has to be set, e.g. with

	ldi R16,1<<TOIE0
	out TIMSK0 R16

Of course, And, of course, an ISR has to be written so that the jump in the vector table finds a location. Such a typical ISR could be:

ovflw_isr:
	in R15,SREG ; save status register in a register
	dec R17 ; count down a register
	brne ovflw_isr1 ; jump if not zero
	sbr R18,0b00000001 ; set bit 0 in register R18
	ldi R17,10 ; restart count down from 10
ovflw_isr1:
	out SREG,R15 ; restore status register
	reti ; return from interrupt and set I-flag

Typical is the saving of the status. Because the DEC and SBR instructions within the ISR change flags, it is absolutely crucial to do that.

Further, the ISR sets a flag in a register to signal that the timer reached overflow for ten times. This flag can be used outside the ISR to perform further operations, e.g. to write a chacter to an LCD or do other time-consuming operations. The ISR works fine as there is enough time: twenty timer overflows until the flag is overwritten should provide enough time to react.

6.1.5 The compare match interrupt

If the compare match interrupt bits OCIE0A and/or OCIE0B are set every match leads to a jump to the respective vector. If the timer is in CTC mode, the compare match A interrupt is initiated on every reset of the timer. The same applies to the PWM mode.

6.1.6 Interrupts and sleep modes

In the sleep mode "idle", that we used already, all interrupts wake up the controller. Execution continues with the ISR of the respective interrupt source, then continues with the instruction following the sleep instruction. In this phase it makes sense to check if any of the ISR routines has set flags that require extended reaction.

Home Top Introduction Hardware Overflow-Int CTC-Int


6.2 Hardware, components, mounting

Two LEDs For the interrupt experiments the same hardware as already used comes into play. The only change is that an additional red LED and a resistor is attached to OC0B (on pin 6). These components are already described in lecture 2.

6.3 Timer with overflow interrupt

6.3.1 Task description

The following task has to be performed:

6.3.2 Steps towards solution

It is clear from the task description that it requires a lot more than simple blinking mechanics. The parallel change in intensity with the second LED also requires some new methods. The solution is an interrupt-controlled counting method.

Such a program looks basically very different than a linear program. As most of the controller software is interrupt-driven a closer look into such structure is recommendable.

We use the timer overflow interrupt. The 1.2 Mcs/s controller clock divided by 256 pulses that are necessary to overflow the timer leads to 4,687.5 interrupts per second, if the prescaler would be at 1. If we divide this by 2 (for 0.5 s ON cycle and 0.5 s OFF cycle), we are at 2,344 ints per second (rounded up). As this is larger than 256 we need a 16 bit wide counter.

Because the masking of the fifth LED pulse requires some more complex processes we place this outside the interrupt service routine. This would not be necessary in any case because the next overflow int is 256 clock cycles later and we have only one single ISR to process, but we do that to learn the principle.

6.3.3 Program

This here is one of the possible solutions, here is the source code. The rigid listing of used registers, ports and port bits, the distinct naming of all constants and the complete interrupt vector list is not only for educational purposes but is a recommendable principle in assembler programming as it eases debugging and finding errors. This and the exctended comments in the source code allows to understand the code even weeks after.

;
; ***************************************
; * Timer with Overflow interrupt       *
; * (C)2017 by www.avr-asm-tutorial.net *
; ***************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ---------- Registers ------------------
; free: R0 .. R14
.def rSreg = R15 ; SREG interim storage
.def rmp = R16 ; multi purpose register
.def rimp = R17 ; multi purpose inside interrupts
.def rFlag = R18 ; Flag register
	.equ bPol = 0 ; Flag polarity change
.def rCnt= R19 ; Blink counter
.def rPwm = R20 ; PWM counter
; free: R20 .. R23
.def rCntL = R24 ; 16 bit counter, LSB
.def rCntH = R25 ; dto., MSB
;free: R26 .. R31
;
; ---------- Ports, port bits -----------
.equ pLedOut = PORTB ; LED output port
.equ bLedOut = PORTB0 ; LED ON/OFF output pin
.equ pLedDdr = DDRB ; LED direction port
.equ bLedDdr = DDB0 ; LED direction pin
.equ pLedIn  = PINB ; LED input port
.equ bLedIn  = PINB0
;
.equ bPwmOut = PORTB1 ; LED PWM output pin
.equ bPwmDdr = DDB1 ; Direction pin PWM
;
; ---------- Timing ---------------------
.equ cClock  = 1200000 ; Controller clock
.equ cPolChange = 2 ; Polarity changes per cycle Led On/Off
.equ cPresc  = 1 ; Prescaler timer
.equ cCount  = cClock / 256 / cPresc / cPolChange + 1
.equ cBlink  = 5 ; Blink counter
;
; ---------- Reset- and Interrupt-vectors -------
.CSEG ; Program code segment
.ORG 0 ; Reset- and vector address
	rjmp Start ; Reset vektor, jump to init
	reti ; INT0-Int, inactive
	reti ; PCINT-Int, inactive
	rjmp Tc0IsrO ; TIM0_OVF, active
	reti ; EE_RDY-Int, inactive
	reti ; ANA_COMP-Int, inactive
	reti ; TIM0_COMPA-Int, inactive
	reti ; TIM0_COMPB-Int, inactive
	reti ; WDT-Int, inactive
	reti ; ADC-Int, inactive
;
; ---------- Interrupt-Service-Routines ----------
Tc0IsrO: ; Timer 0 Overflow ISR
	in rSreg,SREG ; save status register
	sbiw rCntL,1 ; decrease counter by one
	brne Tc0IsrO1 ; jump if not zero
	sbr rFlag,1<<bPol ; set flag polarity change
	ldi rCntH,HIGH(cCount) ; restart counter
	ldi rCntL,LOW(cCount)
Tc0IsrO1:
	mov rimp,rCntL ; copy Low byte counter
	andi rimp,0b00011111 ; isolate the lower five bits
	brne Tc0IsrO2 ; jump if not zero
	dec rPwm ; decrease PWM value
	out OCR0B,rPwm ; to compare match port B
Tc0IsrO2:
	out SREG,rSreg ; restore status register
	reti ; End of ISR, set I bit
;
; ---------- Initiieren und Programmschleife -----
Start:
	; Init stack
	ldi rmp,LOW(RAMEND) ; stack pointer to end of SRAM
	out SPL,rmp ; set stack pointer
	; Activate LED output pins
	ldi rmp,(1<<bLedDdr)|(1<<bPwmDdr) ; LED output pins on, drivers on
	out pLedDdr,rmp ; to direction port
	; Init counter
	ldi rCnt,cBlink ; init Blink counter
	ldi rCntH,HIGH(cCount) ; 16 bit counter to start value
	ldi rCntL,LOW(cCount)
	; Init compare register for PWM
	ldi rmp,0xFF ; Compare match A to 255
	out OCR0A,rmp ; to compare match port A
	clr rPwm ; Start value for PWM
	out OCR0B,rPwm ; to compare match port B
	; Start Timer/Counter 0 as timer with overflow int
	ldi rmp,(1<<COM0B1)|(1<<WGM01)|(1<<WGM00) ; Fast PWM, COM0B
	out TCCR0A,rmp ; to timer control port A
	ldi rmp,1<<CS00 ; prescaler 1, start timer
	out TCCR0B,rmp ; to timer control port B
	ldi rmp,1<<TOIE0 ; overflow interrupt
	out TIMSK0,rmp ; to timer interrupt mask
	; Sleep mode
	ldi rmp,1<<SE ; sleep enable, mode idle
	out MCUCR,rmp ; to universal control port
	; Enable interrupts
	sei ; set I flag in status register
; Program loop
Loop:
	sleep ; send to sleep
	nop ; delay one cycle after wakeup
	sbrc rFlag,bPol ; jump over next instruction if flag not set
	rcall Polarity ; process flag polarity change
	rjmp Loop ; send to sleep again
;
; --------- Change polarity of the LED ----------
Polarity:
	cbr rFlag,1<<bPol ; clear flag
	sbis pLedOut,bLedOut ; jump over next instruction if LED is off
	rjmp Polarity3 ;  Led is on, switch LED off
	; Led is Off
	cpi rCnt,0 ; is blink counter zero?
	breq Polarity1 ; yes, restart counter
	; Led off, counter not zero
	dec rCnt ; decrease blink counter
	brne Polarity2 ; switch Led on
	ret ; no polarity change, return
Polarity1: ; Restart counter
	ldi rCnt,cBlink ; restart counter
	ret ; no polarity change, return
Polarity2: ; switch Led on
	cbi pLedOut,bLedOut ; Led on
	ret ; return, LED on
Polarity3: ; switch Led off
	sbi pLedOut,bLedOut ; Led off
	ret ; return, Led off
;
; End of source code
;

New are the following instructions:

6.3.4 The result

Led combination These are the LEDs in different modes: the one to the right with abrupt on and off cycles, the one to the left with softly changing intensity. Current requirements are minimized by the sleep mode: while the controller is majourly sleeping and is only woken up when needed.

6.3.5 Program structuring

The visible structure of the source code is:
  1. Register definitions, port definitions and constants at the beginning,
  2. Reset- and interrupt vectors,
  3. Interrupt service routines,
  4. Main program inits with hardware configuration and start values,
  5. Loop processing, and
  6. routines outside interrupt service routines.
This structure makes sense in all interrupt controlled programs, in order to keep the overview. This also helps with potential debugging activities if the program does not do what it should.

6.3.6 Advantages and disadvantages

The advantage of this solution is high flexibility: it can simply be modified to fit to other needs. The frequncy of blinking or changes of the LED port pins can simply be modified by changing a few constants on top.

The disadvantage is that blinking is not exact. This is because dividing by 256 does not lead to an integer value. This disadvantage is addressed by the following opportunity.

Home Top Introduction Hardware Overflow-Int CTC-Int


6.4 Timer with CTC interrupt

In order to achieve a more exact timing the following is considered.

6.4.1 CTC selection

Different divider factors can be achieved in the CTC mode of the timer. Other division factors than 256 are possible. To achieve more exact timings a combination of the clock frequency, the prescaler, the CTC compare match and the software counter has to be selected in a more intelligent way. The following table provides for all clock frequencies of the ATtiny13 and for 8 and 16 bit counters the potential CTC dividers that result in an integer value of 1 cs/s.

Colors:16 bit counter 8 bit counter CTC=256@16 bit counter CTC=256@8 bit counter

From that the following consequences result (CTC values larger than 100 only).
Clock (cs/s)Prescaler Integer CTC divider
9,600,0001256 (37500) 250 (38400)240 (40000) 200 (48000)192 (50000) 160 (60000)150 (64000)
8250 (4800)240 (5000) 200 (6000)192 (6250) 160 (7500)150 (8000) 128 (9375)
64250 (600)240 (625) 200 (750)150 (1000)
256250 (150)150 (250)
1024
4,800,0001256 (18750) 250 (19200)240 (20000) 200 (24000)192 (25000) 160 (30000)150 (32000) 128 (37500)
8250 (2400)240 (2500) 200 (3000)192 (3125) 160 (3750)150 (4000)
64250 (300)200 (375) 150 (500)
256250 (75)150 (125)
1024
2,400,0001256 (9375) 250 (9600)240 (10000) 200 (12000)192 (12500) 160 (15000)150 (16000) 128 (18750)
8250 (1200)250 (1200) 240 (1250)200 (1500) 160 (1875) 
64 250 (150)150 (250)  
256 
1024 
1,200,0001250 (4800) 240 (5000)200 (6000) 192 (6250)160 (7500) 150 (8000)128 (9375)
8250 (600)240 (625) 200 (750)150 (1000)
64250 (75)150 (125)
256
1024
128,0001256 (500) 250 (512)200 (640) 160 (800)128 (1000)
8250 (64)200 (80) 160 (100)128 (125)
64250 (8)200 (10)
256250 (2)
1024


For 1.2 Mcs/s a CTC divider of 250 (125 for 2 cs/s) and a prescaler value of 64 is appropriate and convenient. Because the second LED has to be controlled by a PWM the timer has to be operated in PWM mode.

6.4.2 Program

Now an 8 bit register is sufficient as CTC counter, here is the source code..

;
; ***************************************
; * Timer with COMP-A-Interrupt         *
; * (C)2017 by www.avr-asm-tutorial.net *
; ***************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ---------- Registers ------------------
; free: R0 .. R14
.def rSreg = R15 ; SREG interim storage
.def rmp = R16 ; multi purpose register
.def rimp = R17 ; multi purpose inside ISRs
.def rFlag = R18 ; Flag register
	.equ bPol = 0 ; Flag polarity change
.def rCnt= R19 ; Blink counter
.def rCtcInt = R20 ; CTC interrupt counter
.def rPwm = R21 ; PWM counter
; free: R22 .. R31
;
; ---------- Ports, Port bits -----------
.equ pLedOut = PORTB ; LED output port
.equ bLedOut = PORTB0 ; LED on/off pin
.equ pLedDdr = DDRB ; LED direction port
.equ bLedDdr = DDB0 ; LED direction pin
.equ pLedIn  = PINB ; LED input port
.equ bLedIn  = PINB0 ; LED input pin
;
.equ bPwmOut = PORTB1 ; second LED Pwm pin
.equ bPwmDdr = DDB1 ; second LED direction pin
;
; ---------- Timing ---------------------
.equ cClock  = 1200000 ; controller clock
.equ cPolChange = 2 ; frequency Led on/off
.equ cPresc  = 64 ; prescaler timer
.equ cCtcInt = 125 - 1 ; CTC int counter
.equ cCtcCnt = cClock / cPresc / cPolChange / (cCtcInt +1)
.equ cBlink  = 5 ; fifth pulse off
;
; ---------- Reset- and Interrupt-vectors -------
.CSEG ; Program code
.ORG 0 ; Reset- and vector table address
	rjmp Start ; Reset vector, jump to init
	reti ; INT0-Int, inactive
	reti ; PCINT-Int, inactive
	reti ; TIM0_OVF, inactive
	reti ; EE_RDY-Int, inactive
	reti ; ANA_COMP-Int, inactive
	rjmp Tc0IsrA ; TIM0_COMPA-Int, active
	reti ; TIM0_COMPB-Int, inactive
	reti ; WDT-Int, inactive
	reti ; ADC-Int, inactive
;
; ---------- Interrupt Service Routines ----------
Tc0IsrA: ; Timer 0 Compare A Interrupt
	in rSreg,SREG ; save status register
	dec rPwm ; decrease PWM counter
	brne Tc0IsrA1 ; jump if not zero
	ldi rPwm,cCtcInt ; restart PWM counter
Tc0IsrA1:
	out OCR0B,rPwm ; update PWM compare
	dec rCtcInt ; decrease ctc counter
	brne Tc0IsrA2 ; jump if not zero
	sbr rFlag,1<<bPol ; set flag polarity change
	ldi rCtcInt,cCtcCnt ; restart ctc counter
Tc0IsrA2:
	out SREG,rSreg ; restore status register
	reti ; End of ISR, set I bit
;
; ---------- Init and program loop -----
Start:
	; Init stack
	ldi rmp,LOW(RAMEND) ; stack pointer to SRAM end
	out SPL,rmp ; set stack pointer
	; Activate LED output pins
	ldi rmp,(1<<bPwmDdr)|(1<<bLedDdr) ; LED output pins, driver on
	out pLedDdr,rmp ; to direction port
	; Init counters
	ldi rCnt,cBlink ; init blink counter
	ldi rCtcInt,cCtcCnt ; init ctc counter
	ldi rPwm,cCtcInt ; init PWM start value
	; Compare match A value for CTC
	ldi rmp,cCtcInt ; CTC divider 125
	out OCR0A,rmp ; to compare match port A
	; Timer 0 as in PWM mode, start with overflow interrupt
	ldi rmp,(1<<COM0B1)|(1<<WGM01)|(1<<WGM00) ; PWM on port OC0B, Fast PWM
	out TCCR0A,rmp ; to timer control port A
	ldi rmp,(1<<WGM02)|(1<<CS01)|(1<<CS00) ; Fast PWM, prescaler 64, start
	out TCCR0B,rmp ; to timer control port B
	ldi rmp,1<<OCIE0A ; Compare A interrupt
	out TIMSK0,rmp ; to timer interrupt mask
	; Sleep mode
	ldi rmp,1<<SE ; sleep enable, mode idle
	out MCUCR,rmp ; to universal control register
	; Enable interrupts
	sei ; set interrupt flag in status register
; Program loop
Loop:
	sleep ; put to sleep
	nop ; delay after wakeup
	sbrc rFlag,bPol ; jump over next instruction if flag clear
	rcall Polarity ; process flag
	rjmp Loop ; go to sleep again
;
; --------- Polarity change of the LED ----------
Polarity:
	cbr rFlag,1<<bPol ; clear flag
	sbis pLedOut,bLedOut ; jump over next instruction if LED is off
	rjmp Polarity3 ;  Led is on, jump to LED = on
	; Led is off
	cpi rCnt,0 ; is the blink counter zero?
	breq Polarity1 ; yes, restart counter
	; Led off, counter not zero
	dec rCnt ; decrease blink counter
	brne Polarity2 ; switch Led on
	ret ; return, no polarity change
Polarity1: ; Restart counter
	ldi rCnt,cBlink ; restart counter
	ret ; return, no polarity change
Polarity2: ; switch Led on
	cbi pLedOut,bLedOut ; Led on
	ret
Polarity3: ; switch Led off
	sbi pLedOut,bLedOut ; Led off
	ret
;
; End of source code
;

Off course, the change in the LED timing is invisible, the difference to the previous solution is minimal. But we know for sure that this is exact because we programmed that.

Home Top Introduction Hardware Overflow-Int CTC-Int


©2017 by http://www.avr-asm-tutorial.net