ASM program: External interrupts won't work in ATMega8

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

Hello, I'm writing a program in assembly language for M8 that counts the number of pushes on button on INT0 pin and outputs it in binary format to PORTC. However, seems like external interrupt doesn't work. I checked this on real hardware, in Proteus and in Atmel Studio simulator (using stimuli).
So it looks like this: I bit in SREG is set, MCUCR and GICR are also configured, and the MCU itself catches the IRQ (GIFR is also set), but somehow the handler is not being called.
Code: https://gist.github.com/blgkv/22d20f259cb582f98b4d44e8b96ef8c6
Proteus schematic: https://drive.google.com/file/d/...
Any ideas what's wrong with my code? Thank you in advance.

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

My projects (for products I sell) use the MCU ATmega8. I write my codes in assembly always. Almost all my boards use an external interrupt; INT0 and/or INT1.

I wonder what you have missed in your code.

For instance, I never had the chance to let the MCU of a new project work in the first try, even to make it boot properly.

Since I can't get any emulator (including Proteus), a new code took me from 50 (if I am lucky) to 500 (if not) tests before releasing it as acceptable for production (since there will be always the need to update it after sale).

 

You may like presenting the code that you have and is related to INT0; so we can both analyse it.

 

Kerim

 

Edit:

I usually don't follow a link (as of your code). But I will see if I can make an exception here smiley

 

Last Edited: Fri. Jun 21, 2019 - 01:00 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

KerimF wrote:

You may like presenting the code that you have and is related to INT0; so we can both analyse it.


The whole code is on GitHub Gist, please check the link in the first post of this thread.

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

does inc and dec set flags??

 

mechanical switches and interrupts don't mix real well. You'd be better off just sampling the input via your timer isr and debouncing it there rather than your jiggery-pokery with external interrupts.

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

First it works you must have an error!

 

Are you sure that you don't have the bootloader enabled or something like that.

 

add:

Some chips the ISR clr the active interrupt bit, other times you have to do it yourself. (if that is the problem you will actually get one correct ISR)

Last Edited: Fri. Jun 21, 2019 - 01:02 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And as pointed out in #4 inc and dec changes flags so it can't be de done this way.

 

If you are new to the AVR, I will suggest that you first get it to work in C (and I would simulate it in studio 7's simulator).

And after that perhaps do it in ASM if needed. 

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

cyanog3n wrote:
seems like external interrupt doesn't work.
Tell us more about what "doesn't work" means and how you know this to be the case. What I see is that you enable the ext_int, when it occurs you stop ex_int's happening and start a timer that counts down 6 overflows then it turns the ext_int back on again. So that's presumably your "debounce". First time through BPF is not set so it toggles and the count is 6 overflows, next time it is set so it toggles again then the count is 15 overflows. and the "final output" of the whole thing is the count on PORTC so how are you observing activity on C?

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

To be more explicit:

 

TIM0_OVF:
  dec ovf_counter
 

breq count_done

 

 

 

the breq is testing the zero flag - but the dec instruction doesn't touch the flags. You might use subi instead of dec as this will set the flags. If you touch flags in your ISR, you need to save/restore SREG otherwise funky stuff will happen and lead to great confusion.

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

What is "outi" ??

 

EDIT: sorry, found it - not looking hard enough! blush

Last Edited: Fri. Jun 21, 2019 - 01:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

yes dec change the zero flag but not the carry flag. 

 

but it change flags so it has to store the org value.

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

Kartman wrote:
but the dec instruction doesn't touch the flags

If I understand this correctly, http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf at page 84 says that Z is set if the result is $00

clawson wrote:
What is "outi" ??

It is macro to out constants, it's defined in the beginning of the code section.

clawson wrote:

Tell us more about what "doesn't work" means and how you know this to be the case


I expect the following behaviour: when the button is pushed, there's a LOW signal on INT0. INT0 is configured to trigger on falling edge, so the EXT_INT0 ISR is called. If it's the first time when the interruption is called (the button is pressed, but not released), then wait 0.2s for debouncing and set INT0 to trigger on rising edge (rising edge happens if the button is released). When the button is released, the EXT_INT0 is called again, does payload code (increments the counter and outputs it to PORTC), sets INT0 to falling edge again (waits for button to be pressed again) and waits 0.5s for debouncing. What I get is no reaction on button push and no LEDs turned on.
 

UPD: what about push and pop SREG value before and after dec and inc instructions? Would it help?

Last Edited: Fri. Jun 21, 2019 - 01:33 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Try adding:

        outi GIFR, 0xFF ; clear GIFR
right before

          outi GICR, (1 << INT0) ; enable INT0 [of 'count_done:')

 

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

KerimF wrote:
Try adding:

Unfortunately no effect.

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

Sorry about inc/dec and flags - its been 10+ years since i wrote AVR asm!
You want to save/restore SREG in your isr.

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

Kartman wrote:
You want to save/restore SREG in your isr.

Could you please make it more clear to me? Should i save SREG at the beginning of the ISR and restore it in the end before the reti?

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

And I guess your push-button on Proteus is ideal (no glitches).

You are right to be confused. In my free time I will test it on a real board.

About push and pop SREG, the main loop is empty in your code so it is not affected by ISR.

 

Added:

I usually add

IN sregTemp, SREG (at the ISR beginning)

and

OUT SREG, sregTemp (before RETI)

 

where sregTemp is a general low register reserved for this.

Last Edited: Fri. Jun 21, 2019 - 01:57 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As it happens, in this case, corruption of SREG in the ISR(s) may not matter as the code they'll be interrupting is just the "rjmp loop" in the main code. I doubt it cares if SREG mysteriously changes ;-)

 

it is prudent to get into the habit of preserving things that change in ISRs though as I guess loop: will have more stuff added later perhaps?

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

Try adding

     cbr flag_reg, BPF
in button_release_handle:

 

I believe this is the missing line smiley

Last Edited: Fri. Jun 21, 2019 - 02:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

KerimF wrote:
 cbr flag_reg, BPF

Oh, I've really missed this and never cleared BPF flag. However, it still doesn't work.

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

In such situation, I test the code step by step:

Here, I would test if EXT_INT0: is called properly in the first place by turning on an LED1 for push and turning on an LED2 for release. 

 

Added:

The LED2 is not necessary here because PORTC, as your test shows, is not loaded with the counter value that starts as 1.

 

 

Last Edited: Fri. Jun 21, 2019 - 03:04 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cyanog3n wrote:

However, it still doesn't work.

Can you please tell us what "doesn't work" actually means? You may also want to re-post your code to show which of the suggestions above you have taken on board.

 

If I've understood things so far I guess we're expecting to see something like:

.include "../inc/m8def.inc"

    .def temp0 = r15
    .def temp = r16
    .def ovf_counter = r17
    .def counter = r18
    .def flag_reg = r19
    .equ INT_PIN = 2
    .equ BPF = 0 ; button pushed flag, 0th bit of flag_reg

; FLASH
    .CSEG
; ====== INTERRUPT VECTOR TABLE ATMEGA8 ======
    rjmp RESET            ; Reset Handler
    rjmp EXT_INT0         ; IRQ0 Handler
    reti ;rjmp EXT_INT1   ; IRQ1 Handler
    reti ;rjmp TIM2_COMP  ; Timer2 Compare Handler
    reti ;rjmp TIM2_OVF   ; Timer2 Overflow Handler
    reti ;rjmp TIM1_CAPT  ; Timer1 Capture Handler
    reti ;rjmp TIM1_COMPA ; Timer1 CompareA Handler
    reti ;rjmp TIM1_COMPB ; Timer1 CompareB Handler
    reti ;rjmp TIM1_OVF   ; Timer1 Overflow Handler
    rjmp TIM0_OVF         ; Timer0 Overflow Handler
    reti ;rjmp SPI_STC    ; SPI Transfer Complete Handler
    reti ;rjmp USART_RXC  ; USART RX Complete Handler
    reti ;rjmp USART_UDRE ; UDR Empty Handler
    reti ;rjmp USART_TXC  ; USART TX Complete Handler
    reti ;rjmp ADC_CC     ; ADC Conversion Complete Handler
    reti ;rjmp EE_RDY     ; EEPROM Ready Handler
    reti ;rjmp ANA_COMP   ; Analog Comparator Handler
    reti ;rjmp TWSI       ; Two-Wire Serial Interface Handler
    reti ;rjmp SPM_RDY    ; Store Program Memory Ready Handler

; ====== CODE ======
    .macro outi
        ldi temp, @1
        out @0, temp
    .endm

RESET:
    clr counter
    clr flag_reg
    outi SPL, low(RAMEND)
    outi SPH, high(RAMEND)
    outi DDRC, 0xff ; set portc to output
    outi DDRD, 0 ; set portd to input
    outi PORTC, 0
    outi PORTD, (1 << INT_PIN) ; attach pullup resistor to INT0 pin
    outi MCUCR, (1 << ISC01) ; INT0 is pulled up, so set to trigger on falling edge 
    outi GICR, (1 << INT0) ; enable INT0 IRQ
    outi GIFR, 0xFF ; clear GIFR
    sei

loop: ; empty loop, just waiting for interrupts
    nop
    rjmp loop

EXT_INT0:
    ; INT0 is configured on rising/falling edge, so first of all we determine what time is this interrupt handled
    ; check this using BPF bit in our flag_reg
    sbrs flag_reg, BPF
    rjmp button_push_handle
    rjmp button_release_handle
    button_push_handle:
        ; first of all, mark that next time we will handle release
        sbr flag_reg, BPF
        ; to avoid jitter, disable INT0 IRQs for 0.2s using 1:1024 prescaler
        ; to do this, we set up the timer
        ; ovf_counter = (Tdelay * Fcpu) / (Nprescaler * 256) for 8-bit timer
        ; (0,2×8000000) / (1024×256) = 6
        ldi ovf_counter, 6
        outi MCUCR, ( (1 << ISC01) | (1 << ISC00) ) ; set INT0 to rising edge trigger
        rjmp set_timer

    button_release_handle:
        cbr flag_reg, BPF
        ; to avoid jitter, disable INT0 IRQs for 0.2s using 1:1024 prescaler
        ; to do this, we set up the timer
        ; ovf_counter = (Tdelay * Fcpu) / (Nprescaler * 256) for 8-bit timer
        ; (0,5×8000000) / (1024×256) = 15
        ldi ovf_counter, 15
        outi MCUCR, (1 << ISC01) ; set INT0 to falling edge trigger
        ; payload code ===
        inc counter
        out PORTC, counter
        ; =====

set_timer:
    outi GICR, 0 ; disable INT0 IRQs
    outi TCCR0, 0b00000101 ; enable TIM0 with 1:1024 prescaler
    reti

TIM0_OVF:
    push r0
    in r0, SREG
    push r0
    SUBI ovf_counter, 1
    brne ret_OVF

    outi TCCR0, 0 ; stop timer
    outi GICR, (1 << INT0) ; enable INT0

ret_OVF:        
    pop r0
    out SREG, r0
    pop r0
    reti

 

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

What about the fuses setting?

Since I don't have Proteus, how could Proteus be informed about the setting of fuses?

 

In my case, I include/append the fuses automatically at the end of the hex file after being generated by AS6 (thanks to the "post-build event command line"). [for this task, I wrote a small C program soon after installing/running AS6; by using the only compiler I was able to get; BORLANDC 3.1].

 

Note: Before re-enabling an interrupt, one may need to consider if it is necessary to clear its flag first (to skip possible glitches for example) or not.

 

Last Edited: Fri. Jun 21, 2019 - 08:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is a very simple task.

 

So why complicate it by using the timer interrupt?

(that is if you don't need the timer for anything else or

your instructor is telling you you must use the timer)

 

An extremely simple delay for debouncing will work here and the key is it will work.

 

I use 50mS when the switch was pressed, then another 50mS to wait until the switch is released.

 

Same deal, I set a Flag for the program to decide whether or not to continue based on whether the switch was pressed.

 

I wrote a small program to control my treadmill (10 years ago and still working great). 

When the switch is pushed, it signals the small board I created to control the speed.

 

( I didn't want to use the program in the treadmill because I think having a

heart attack while doing intervals would be a downer, so this is the reason why I

choose to control the intervals in the motor speed)

 

Here is the  interrupt code:

;-----------------------------------------------
EXT_INT0:
	in	S,sreg
	rcall	Delay50mS

	sbic	PIND, Push_SW	;make sure it's still low after 50mS
	rjmp	Ext_Int0_Exit	;if not, noise on the line - exit

;again 50mS for debouncing
	rcall	Delay50mS

ExtIntWait:
	sbis	PIND, Push_SW	;wait for button release
	rjmp	ExtIntWait		;

	sbr	Flags,(1<<StopFlag) ;button pressed, stop program after current session is done

Ext_Int0_Exit:
	out	sreg,S
	reti
;--------------------------------------------
Delay50mS:
; 50ms at 8 MHz for debouncing
;button must be pushed for at least 50mS
;
	ldi  r24, 3
	ldi  r25, 8
	ldi  r26, 120
Del50mS:
	dec  r26
	brne Del50mS
	dec  r25
	brne Del50mS
	dec  r24
	brne Del50mS
	ret
;--------------------------------------------

 

The main thing is this code is working. I know there are probably more efficient ways of doing this (and I am sure some will criticize my code) but it's not necessary here because all you want to do is increment a byte, then send it to PORTC.

Last Edited: Sat. Jun 22, 2019 - 01:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Whilst it may ‘work’, it still is a poor method of debouncing. We want to make embedded devices more reliable, not repeat the sins of the past.

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

Kartman wrote:

Whilst it may ‘work’, it still is a poor method of debouncing. We want to make embedded devices more reliable, not repeat the sins of the past.

 

Well using the timer (in this case) does not work so it is not reliable.

 

Why is my method a poor method of debouncing?

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

I don't think there is a best code to detect reliably a key push-release. There is instead an optimum working solution for each application (including homework smiley ).

 

On my side, almost all my projects have push-buttons (for users and/or for in-house setting/adjusting). So I had to find out the optimum code of the 'key_press' subroutine for every application. Naturally, these subroutines are different from each other (they are so for other reasons too; for example a single key may be needed for multi-tasks). But, since their code is of a relatively very slow process, I don't see the need to use for it an ISR (external interrupt or timer). After all, all timers and external interrupts (in my ATmega8) are usually busy, if not very busy.

 

Finally, let us remember that even if a code is completely free of bugs, the MCU won't work as expected if its fuses are not set properly too (mainly if it is tested on its real board).

 

 

 

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

Google ganssle debounce

When you read the port pins, it is like taking a snapshot of an event with a shutterspeed of nanoseconds. Taking two snapshots is only marginally better. Taking 10 or more gives a much better idea of what the port pin is actually doing. What works on your desk won’t necessarily work in the real world - things like ESD and EMF conspire to make your life difficult. Add in interrupts that can get triggered by a nanosecond wide pulse, then unreliability creeps in. The old adage ‘garbage in, garbage out’ applies. Filter all your inputs.

If the code was written to make the timer not run and the timer is not running, then that makes it very reliable. It might be a quality issue if the requirement was for the timer to run, but it doesn’t as that would make it non-conformant.