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.
ASM program: External interrupts won't work in ATMega8
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
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.
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.
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)
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.
seems like external interrupt doesn't work.
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.
What is "outi" ??
EDIT: sorry, found it - not looking hard enough!
yes dec change the zero flag but not the carry flag.
but it change flags so it has to store the org value.
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
What is "outi" ??
It is macro to out constants, it's defined in the beginning of the code section.
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?
Try adding:
outi GIFR, 0xFF ; clear GIFR
right before
outi GICR, (1 << INT0) ; enable INT0 [of 'count_done:')
Try adding:
Unfortunately no effect.
Sorry about inc/dec and flags - its been 10+ years since i wrote AVR asm!
You want to save/restore SREG in your isr.
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?
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.
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?
Try adding
cbr flag_reg, BPF
in button_release_handle:
I believe this is the missing line
cbr flag_reg, BPF
Oh, I've really missed this and never cleared BPF flag. However, it still doesn't work.
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.
However, it still doesn't work.
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
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.
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.
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.
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?
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 ).
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).
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.