Path:
Home ==>
AVR-EN ==>
Micro beginner ==> 7. LED with key interrupt
Diese Seite in Deutsch (extern):
Lecture 7: A LED blinks with a key interrupt
With this lecture we learn how external level changes on a pin can be used to generate
an interrupt. We use the interrupt INT0 to detect such level changes without having to poll
pins in lengthy loops.
7.0 Overview
- Introduction to key operation and INT0 programming
- Tasks to be performed
- Hardware, components, mounting
- Program
7.1.1 Keys on input ports
If a port pins output driver is switched of, by clearing its DDR/port bit, the logical
state of the pin can be read in and, depending from this state, decisions can be made
such as conditional branching etc.
This is such a simple wiring to follow the external switch:
To light a LED on port pin PB0 as long as the switch on PB1 is high (connected to the
positive operating voltage), the following program would be necessary:
sbi DDRB,PB0 ; enable LED output driver PB0
Loop:
sbis PINB,PINB1 ; jump over next instruction if input pin is high
sbi PORTB,PORTB0 ; switch LED off
sbic PINB,PINB1 ; jump over next instruction if input pin is low
cbi PORTB,PORTB0 ; switch LED on
rjmp Loop
Now, what happens if the switch is defect or the switch is not attached to the pin?
The input is open and, due to the extremely high input resistance on any changes in
its proximity, e.g. electrical fields from the 50/60 cs/s electricity supply net,
static voltages of fingers in close distance or level changes on neighboring pins.
The result is an uncontrolled flickering of the LED.
To end the flickering the input resistance of the port pin has to be reduced and
a definitive level has to be applied. This is done with a resistor of e.g. 47 k.
Now the open input has a defined high level, if the resistor is attached to the
operating voltage. If the switch is closed the additional current is not very large
(0.1 mA), even with a mouse piano attached (eight switches in a row). Note that
the default level with such a pull-up resistor is high if no switch or key are
attached.
Because pull-up resistors on open input pins are necessary very often, those are
built-in in AVRs. They are switched on by clearing the data direction bit and setting
the output bit. E.g. like this:
cbi DDRB,DDB1 ; direction pin PB1 = input
sbi PORTB,PORTB1 ; switch on pull-up resistor
Why pull-up and not pull-down? This is due to historic reasons: the input of TTL
devices were by default high, so why not have CMOS inputs on the same default level.
The same applies if a key or push-button is attached: it is open by default.
7.1.2 Keys and switches are bouncing
This is an example of a switch. If the switch changes polarity, this comes not into
effect immediately. On a µs or ms level the switch is bouncing before reaching
a steady state. In the example case 14 level changes occur within approximately
2 ms.
Such a signal swarm can cause immense difficulties if as an AVR reacts very much faster
than in ms time. We have to consider bouncing in such cases. Bouncing plays a role in the
next lecture.
7.1.3 The INT0 interrupt
The INT0 interrupt detects levels and level changes on the INT0 input, which is pin 6
in an ATtiny13. The interrupt branches to the INT0 vector, if the respective interrupt
is enabled. The bits ISC01 and ISC00 allow to select which states or changes trigger
this interrupt:
ISC01 | ISC00 | Interrupt triggered |
0 | 0 | Low level triggers INT0 interrupt |
0 | 1 | Any change of input level triggers INT0 interrupt |
1 | 0 | Falling level triggers INT0 interrupt |
1 | 1 | Rising level triggers INT0 interrupt |
The default, ISC01 = ISC00 = 0, is rather fatal because a low level on the INT0 pin triggers
never ending interrupts, with leaving no time for any other activities. As the INT0 interrupt
has the highest priority, no other interrupt comes through as long the low level on the
INT0 input pin continues.
The ISC bits are located in the same port, MCUCR, as the SE bits for selecting sleep mode
and sleep enable.
The INT0 enable bit is located in the General Interrupt MaSK register GIMSK:
A typical instruction sequence to enable INT0 is as follows:
ldi R16,(1<<ISC01)|(1<<ISC00)|(1<<SE) ; rising level triggers INT0, sleep mode idle
out MCUCR,R16 ; to the universal control port
ldi R16,1<<INT0 ; set INT0 enable bit
out GIMSK,R16 ; to the General Interrupt Mask
sei ; enable interrupt processing
In the vector table INT0 is the first interrupt following the reset vector, with the
highest priority of all interrupts. In the ATtiny13:
.CSEG ; Assemble to program flash (Code Segment)
.ORG 0 ; Addresse to zero (Reset- and Interrupt vectors start at zero)
rjmp Start ; Reset vector, jump to init routine
rjmp Int0_Isr ; INT0 int, active
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
;
; Interrupt service routine
;
Int0_Isr: ; INT0 ISR
in R15,Sreg ; save SREG
[...] ; further actions
out SREG,R15 ; restore SREG
reti ; return from int, set I flag
;
; Program init at reset
;
Start:
; [Here the program starts]
That is it and we can enter practical usage.
This here is the complex task:
A key signal starts the sequence of the duoled, that consists of two short and a third
longer signal with the displayed timing. The program should be insensible against key
bouncing.
7.3.1 Hardware
The duo LED is attached to port pins PB2 (anode red) and PB0 (anode green) via a current
limiting resistor. The port bit combinations and the resulting colors are listed in the
table.
The key is attached to the INT0 input on PB1, the second key pin is on low (minus).
7.3.2 Components
The Duo-LED
The duo-LED consists of two LEDs, a red and a green one, connected to two pins. Note
that there are other types available with three pins. The longer connecting wire is
the anode of the red LED and the cathode of the green LED.
Key
This is a key. With this type two of the pins are internally connected, pushing the
key connects the four pins.
7.4.1 Processes and flows
To perform the task a clear flow is necessary. In this case this flow is obviously
like this:
This is a counting scheme. The counter counts down from six to zero, at the different
values the on and off periods of the LED and its colors can be derived for the six phases.
The count step duration is 0.25 seconds, which has to be derived from a timer overflow
or CTC/compare match A interrupt. The counter starts with six and a red LED. Also, in phases
4 and 2 the LED has to be switched to red, in phases 5 and 3 to green.
If the counter reaches zero, the cycle ends and can be restarted by the key. To ensure that
the process is only restarted if the cycle has ended we need a flag that signals an active
cycle. We use the T flag for that purpose. This also ensures that bouncing has no effect.
The T flag is bit 6 of the status register, it can be used freely as it is not affected by
any other instruction. To set this flag the instruction SET can be used,
to clear it CLT is available. The instructions BRTC
and BRTS can be used to branch if the T bit is clear or set. The
instruction BST Register, Bit can be used to copy a bit in the register
to the T flag, BLD Register, Bit to copy the T flag to the bit in the
register.
7.4.2 Flow diagrams
The INT0 ISR is simple: it simply sets the T flag. To achieve that the usual saving and
restoring of the status register here would be adverse: the restore would override the
changed T flag! Fortunately no automatic save/restore mechanism has been implemented,
so we can skip this here.
This is the CTC interrupt. To achieve cycles of 0.25 s duration we select a prescaler
of 8, a compare match A divider of 150 for the CTC and an 8 bit software counter with
250 counting stages. This yields exactly 0.25 seconds (8 * 150 * 250 / 1200000 =
0.25). The ISR of the CTC decreases the cycle counter. If the counter reaches zero, a
flag (bTo = Time-out) is set (to be handled outside the ISR) and the counter is restarted.
The main flow diagram shows what has to done outside the two ISRs, after init and after
waking up from the sleep phase. The first task is to detect if the counter is at zero.
If yes then the T flag is checked. If set, a new cycle starts and the counter is set to
six. If the cycle counter is not zero, the bTo flag is checked (which is set by the CTC
ISR when a time-out occurs). If this is set, the next stage is handled. In any case the
flow returns to the sleep instruction.
The start sequence does the following with the timer:
- the CTC compare match A value of 150 is written,
- the CTC mode is written to control register A,
- with a prescaler of 8 in control register B the timer is started, and
- the compare match A interrupt is enabled.
The LED is switched to red and the cycle counter is started with 6.
7.4.3 The program
This is the program, the source code is here:
;
; ***************************************
; * Key with INT0 interrupt *
; * (C)2017 by www.avr-asm-tutorial.net *
; ***************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; --------- Registers -----------------
; free R0 .. R14
.def rSreg = R15 ; save and restore status register
.def rmp = R16 ; multi purpose register
.def rimp = R17 ; multi purpose inside interrupts
.def rFlag = R18 ; Flag register
.equ bTo = 0 ; Timeout flag timer
.def rCntDwn = R19 ; Cycle count down register
.def rCtcCnt = R20 ; CTC count down
; free R21 .. R31
;
; --------- Ports ---------------------
.equ pOut = PORTB ; Output port
.equ pDir = DDRB ; Direction port
.equ pIn = PINB ; Input port
.equ bARO = PORTB2 ; Anode LED red output pin
.equ bCRO = PORTB0 ; Cathode LED red output pin
.equ bPuO = PORTB1 ; Pull-Up key output pin
.equ bARD = DDB2 ; Anode LED red direction pin
.equ bCRD = DDB0 ; Cathode LED red direction
;
; --------- Timing --------------------
; Clock = 1200000 cs/s
; Prescaler = 8
; CTC divider = 150
; Counter = 250
; ----------------------------
; Signaldauer = 0,250 Sekunden
;
; --------- Konstanten ----------------
.equ cCtcCmp = 149 ; CTC divider - 1
.equ cCtcInt = 250 ; CTC Int counter
;
; --------- Reset- and Int vectors ----
.CSEG ; Assemble to program flash (Code Segment)
.ORG 0 ; Address to zero (Reset- and int vectors start at zero)
rjmp Start ; Reset vector, jump to init
rjmp Int0_Isr ; INT0-Int, active
reti ; PCINT-Int, inactive
reti ; TIM0_OVF, inactive
reti ; EE_RDY-Int, inactive
reti ; ANA_COMP-Int, inactive
rjmp Tc0CmpA ; TIM0_COMPA-Int, active
reti ; TIM0_COMPB-Int, inactive
reti ; WDT-Int, inactive
reti ; ADC-Int, inactive
;
; Interrupt Service Routines
;
Int0_Isr: ; INT0-ISR
set ; set T flag
reti ; return, set I flag
;
Tc0CmpA: ; TC0 Compare A ISR
in rSreg,SREG ; save SREG
dec rCtcCnt ; decrease CTC counter
brne Tc0CmpA1 ; not yet zero
sbr rFlag,1<<bTo ; set time out flag
ldi rCtcCnt,cCtcInt ; restart counter
Tc0CmpA1:
out SREG,rSreg ; restore SREG
reti ; return, set I flag
;
; Program start at reset
;
Start:
; Stack Init
ldi rmp,LOW(RAMEND)
out SPL,rmp;
; LED outputs init
ldi rmp,(1<<bARD)|(1<<bCRD) ; outputs
out pDir,rmp ; to direction port
; Pull-Up resistor on
sbi pOut,bPuO ; Pull-up on
; Start conditions counter
clr rCntDwn ; Countdown counter off
clt ; Busy flag off
; Sleep enable, external Int0
ldi rmp,(1<<SE)|(1<<ISC01) ; Sleep, INT0 falling edge
out MCUCR,rmp ; to universal control port
; INT0 enable
ldi rmp,1<<INT0 ; INT0 interrupts on
out GIMSK,rmp ; to General Interrupt Mask
; Enable Interrupts
sei ; set interrupt flag
Loop: ; main program loop
sleep ; go to sleep
nop ; after wakeup
tst rCntDwn ; Countdown count = zero?
brne Loop1 ; no, do not start
brtc Loop1 ; T flag=0, do not start
rcall StartSeq ; start sequence
Loop1:
sbrc rFlag,bTo ; Springe, wenn CTC-Flagge aus
rcall Countdown ; abwaerts bearbeiten
rjmp Loop
;
; Start sequence
;
StartSeq:
ldi rCntDwn,6 ; start value first cycle
ldi rmp,(1<<bPuO)|(1<<bARO) ; red LED and pullup
out pOut,rmp ; to output port
ldi rCtcCnt,cCtcInt ; Restart CTC Int counter
cbr rFlag,1<<bTo ; clear time out flag
ldi rmp,cCtcCmp ; CTC compare match value
out OCR0A,rmp ; to compare port A
ldi rmp,1<<WGM01 ; TC0 as CTC with compare A
out TCCR0A,rmp ; to control port A
ldi rmp,1<<CS01 ; Prescaler = 8
out TCCR0B,rmp ; to control port B
ldi rmp,1<<OCIE0A ; Compare-A-Interrupt
out TIMSK0,rmp ; to TC0 Int Mask
ret ; Ready, return
;
; 250 ms over, next phase
;
Countdown:
cbr rFlag,1<<bTo ; clear bTo flag
dec rCntDwn ; next phase down
breq CountDownOff ; reached zero, end of cycle
cpi rCntDwn,5 ; cycle = 5?
breq CountDownGreen ; yes, LED to green
cpi rCntDwn,3 ; cycle = 3?
breq CountDownGreen ; yes, LED to green
; Switch LED to red
ldi rmp,(1<<bPuO)|(1<<bARO) ; Red and Pullup
out pOut,rmp ; to output port
ret
CountDownGreen: ; LED to green
ldi rmp,(1<<bPuO)|(1<<bCRO) ; Green and Pullup
out pOut,rmp ; to output port
ret ; return
CountDownOff: ; End of cycle, switch all off
clr rmp ; timer off
out TCCR0B,rmp ; to control register B
out TIMSK0,rmp ; timer int off
ldi rmp,1<<bPuO ; all clear but Pullup
out pOut,rmp ; LED off
clt ; T flag off
ret ; return
;
; End of source code
;
Two instructions are new:
- CLR Register: clears register and sets the Z flag,
- TST Register: tests if register is clear (equivalent to OR
Register,Register.
7.4.4 Simulating the processes
To simulate all these mechanisms we feed the source code to
avr_sim
and step through the sequences.
That is the status of port B after init:
- The two pin outputs PB0 and PB2 are defined as outputs,
their direction bits are set and their driver is switched
on. As they both are low, the Duo-LED between PB0 and PB2
is off.
- On PB1, where the key is attached to, the direction bit
is zero and the PORT bit is one, which switches the internal
pullup resistor on. If we would read the PIN1 bit, it would
be high.
- On PB1 the INT0 input pin is located. The software has
enabled the respective interrupt enable bit on falling
edges on this input, which will happen if the key will be
closed.
Now the controller goes to sleep mode idle and port B waits for
signals on PB1.
Either by clicking onto the PINB bit 1 with the active pull-up
or by clicking onto the INT0 we initiate an INT0 interrupt request.
If no other interrupts are executed, the INT0 interrupt will be
processed either after four clock cycles or with the next
instruction. This would be the case even if another interrupt
would be pending because INT0 is the interrupt with the highest
priority (highest position in the interrupt vector list.
The controller woke up from sleep mode idle, stored the current
execution address to the stack and jumped to the INT0 vector
address. From there he jumps to the interrupt service routine.
The only task in the INT0 interrupt service routine is to set
the T flag in SREG - to start a new sequence.
After waking up, the controller realizes that the T flag has
been set and branches with an RCALL to the StartSeq: section.
By doing that, the stackpointer is decreased by two to store
the calling address on the stack.
The StartSeq: has set the downcounter in R19 to 6 to start a
downcounting sequence. The CTC counter in R20 has been initiated
to 250 to execute 250 timer CTC cycles. Note that the T flag in
SREG is still set: it will only be cleared after the whole
sequence of six phases has been absolved. Further INT0 interrupts
therefore do nothing.
The StartSeq: has brought the timer TC0 to count in CTC mode.
The TOP value of the counter is 149, so the counter restarts
after 150 pulses. The controller clock is divided by 8 in the
prescaler, so that the whole CTC cycle lasts 8 * 150 / 1,200,000
= 1.00 ms.
After TC0 reached 150 (and restarted on compare match A) he
initiates a compare match interrupt and the controller jumps
to the compare match A interrupt vector.
As calculated the first compare match interrupt happens after
1.0025 ms have elapsed. The "overtime" of
0.0025 ms is due to the fact that the compare match had
to store the execution address to the stack, to clear the I flag
in SREG and to jump to the vector address. If you need more
accuracy in your application consider these time delays. In
our application that makes no sense as the delay is always the
same and as the human eye is not that fast and accurate.
250 CTC cycles have been absolved here. The bTO flag will be
set now to signal the timeout. Execution time since timer
start is rather accurate.
Now that the bTO flag is set, it is time to change the color of
the duo LED according to the phase diagram from red to green.
The portbits PB0 and PB2 change here.
This is repeated five times, with one color change omitted.
After 1.5 seconds the whole cycle is over, the T flag is
cleared and a restart can be initiated by the key.
©2017 by http://www.avr-asm-tutorial.net