Why does INT0_handler fire twice?

Go To Last Post
19 posts / 0 new
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

People!  Can anybody explain this?
I have a real-world problem that happens in the AT85, but does not happen in the AS-7 simulator.


I'm sorry there is a bit of reading to do, but if I don't tell the whole story, you'll say there isn't enough detail.
Please don't tell me how to do it differently.  I know there are a thousand ways to accomplish any task on a computer, but I'm trying to understand why it does not work the way I did it. 

INT0_handler repeats ion the chip, but I have been unable to make it happen in the AS-7 simulator.


With a good deal of help from this forum, I have completed my project - a triac-driven soft-start for a 230V motor.  It works, but the motor acceleration is twice as fast as intended.  It is supposed to speed up the motor over a 2-second period, but it comes up to speed in 1 second.


The program is in three parts :

main: sets up some variables and I/O control registers - interrupts etc, then goes into a wait loop, jumping to "again:"  From that point, everything is interrupt driven.  Everything happens in two, simple interrupts.

  rcall delay_25mS ; Give it time to power up
  ;Initialize the stack pointer
  ldi R16, low(RAMEND)
  out SPL, R16
  ldi R16, high(RAMEND)
  out SPH, R16

  ; MCU setup
  ldi R16,0x03
  out DDRB,R16   ; PB0, PB1 OUT.  All other pins IN. PB0 is OC0A
  ldi R16,0x02   ; "0" sets 'normal' mode. "2" sets CTC mode.  "3" sets fast PWM.
  out TCCR0A,R16 ; Pin 5 = PB0.  Set to 0xc0 to make pin5 = OC0A
  ldi R16,0x04   ; <<==== Set to 02 (Clk/8) for test;  Set to 04 for Clk/256
  out GTCCR,R16  ; Synch mode in CTC mode protects timer prescaler from being reset by hardware
  ldi R30,low(2*delay_table)  ; Set up Z-reg
  ldi R31,high(2*delay_table) 
  clr R0         ; R0 = 0 (reference value)
  clr R1
  inc R1         ; R1 = 1 (reference value)
  ldi R16,200    ; there are 200 values in the delay_table. 
  mov R9,R16     ; R9 counts the decrements of the Z-reg 200 -> 0
  ldi R16,0x03   
  out MCUCR,R16  ; Enable INT0 interrupt on rising edge.  Pull-ups enabled.
  ldi R16,0x40
  out GIMSK,R16  ; enable external INT0
  ldi R16,0x10   ; OCIE0A
  out TIMSK,R16  ; Enable the Compare interrupt
  ldi R16,255    ; We want the first INT0 before the first OC0A
  out OCR0A,R16
  out PORTB,R0   ; set all bits low
  sei            ; Enable interrupts
   rjmp again

  clr R17
  clr R16        ; find value experimentally
  loop1:         ; inner loop
  dec R16
  brne loop1     ; inner loop
  dec R17
  brne loop2


INT0_handler: Triggered once every 10mS by mains zero-crossing.
              Sets TCNT0 to zero and starts it running
              Loads OCR0A with a value from a lookup table (the motor acceleration is not linear - irrelevant to this discussion)
              There is a delay to allow ringing on the INT0 signal to finish (it rings for about 20uS on the positive mains crossing - see Note 1)
              Returns to the wait loop in main.

     Interesting : The third line is commented out, where I tried to disable the INT0 interrupt to prevent it from firing again.  This did not work - it still fired twice.

                          maybe it was ineffective because in the ISR, it is already disabled.

INT0_handler:   ; INT0 vector points here
; Start the timer/counter from a zero count, and set the compare counter to a figure from the lookup table
; Stay in the INT0 routine for 39uS to keep interrupts disabled and prevent re-triggering on ringing of INT0 signal.  
; The earliest that the TCNT0 routine can come is after 78uS (count of 2), so we have plenty of time.

  in R15,SREG    ; save the status register
  sbi PORTB,1   ; diagnostic pulse on pin 6
  ; out GIMSK,R0   ; disable external INT0. It will be reenabled in TCNT0_compare_a
  out TCNT0,R0   ; set the timer to zero (R0 = 0)
  ldi R20,0x04
  out TCCR0B,R20 ; start I/O clock at CLK/256 
  ldi R20,10     ; running delay (overwritten from lookup table during acceleration phase)
  cp R9,R0       ; Has R9 counted down to zero - starting phase ended?
  breq running
  dec R9         ; No it hasn't. Count it down one
  lpm R20,Z+     ; load from look-up table if in acceleration phase
  out OCR0A,R20  ; Value from table if in acceleration, otherwise preset for running phase
  ; wait till TCNT0 = 1 @ 39uS
    in R20,TCNT0 ; Ringing delay - wait for IO clock = 1 (39 uS)
	cp R20,R1
	brlo waiting ; falls through when TCNT0 >= 1
  cbi PORTB,1   ; end the diagnostic pulse on pin 6
  out SREG,R15  ; restore SREG

TCNT0_compare_a: Triggered by TCNT0 having counted up to the value in OCR0A, which was loaded by INT0_handler from the lookup table.
              Stops the I/O clock to prevent further compares before INT0 fires again
              and sends a pulse through PORTB0 to the triac.

; Pulse the triac
  in R15,SREG  ;
  out TCCR0B,R0 ; stop the IO clock to prevent more compares before INT0.
                ; It will be restarted in INT0.
  ; ldi R16,0x40   ; this diagnostic measure did not prevent INT0 from re-firing
  ; out GIMSK,R16  ; enable external INT0 - attempted diagnostic - but INT0 still fired twice
  sbi PORTB,0    ; Pulse the triac on pin 5
  clr R5         ; should already be zero
  wait:          ; 
  dec R5		 ; This gives a delay of 117uS
  brne wait		 ; 
  cbi PORTB,0    ; end the triac pulse on pin 5
  out SREG,R15   ; restore SREG - nothing uses the SREG outside of the interrupts

Intended program flow : Initiation ... INT0_handler ... TCNT0_compare_a ... INT0_handler ... TCNT0_compare_a ... INT0_handler ... TCNT0_compare_a ...
This means that every time INT0 fires, it should be followed by a compare.


The problem : INT0 triggers, but immediately on completion, it fires again.  I cannot make this happen in the AS-7 simulator.


Here's how I found out.  
When I noticed that motor acceleration was faster than desired, I modified the code slightly by adding an overflow ISR, and added a counter to each ISR
           R11 in INT)_handler
           R12 in the overflow ISR
           R13 in TCNT0_compare_a
    All three counters were explicitly zeroed in main.
    I also added a routine to display the counters on a scope (RS232-style) through the available PORTB1.
    In normal operation, INT0_handler decrements R9 on each visit, from 200 down to zero, so ...
    Finally, a mechanism was added to indicate when R9 has counted down to zero.  This stopped further interrupts (CLI) and triggered the scope display of regs R11,R12,R13.
    I discovered that INT0_handler fires 200 times as expected
    The overflow interrupt was never triggered
    TCNT0_compare is triggered 100 times
    This means that the compare ISR is triggered only once for every two times that the INT0 ISR is triggered.  Odd!
    My next move was to raise a signal on PORTB1 at the beginning of INT0_handler and drop it again before the routine exits. 

    This signal is the top trace on the scope display below.

The chip is mounted on a socket so I can put it into the programmer.  its environment in-circuit is ...

PB5 is not connected to anything, but can be probed with an oscilloscope. 


Please note : I'm not asking how to accomplish my task.  I could do it by ignoring every instance of INT0_handler in which R9 is an even number.
What I want to know is why it doesn't work as it is.  My guess is that I'm doing something that is prohibited by the data sheet, but if so, I haven't been able to find it.


Note 1 : The IO clock ticks every 39uS, so before exiting, INT0_handler waits for the first tick (39uS after invocation of the ISR) to keep interrupts disabled until the ringing has died down.  This is so that continued ringing will not re-trigger INT0_handler.  The delay in INT0_handler has been extended up to 15 counts of the IO clock, so that ringing is definitely no longer a problem, but INT0_handler still re-fires immediately on completion.

In the picture below, the delay at the end of INT0_handler has been extended to 15 IO clock ticks.  As can be seen, the ringing of the INT0 signal has ended a long time before the second execution of INT0_handler.  The bottom trace is the (ringing) INT0 signal.