Dealing with interrupts and scrolling lists in assembly

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

I'm having a bit of trouble making my LCD program run with interrupts. I have to make it so that the screen will scroll up and down (vertical, not horizontal, scrolling) when I press one of two buttons (one for up, one for down). When I load the program it does not do anything, as far as I know it doesn't even reset the lcd. The only new things are the addition of the interrupts code - the rest of the program works (go here for the program code without interrupts: https://www.avrfreaks.net/index.p...)

Thanks in advance!

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

Whoops, here's my program code:

;-----------------------------------
; LCD version 15
; Using Port A
; Scrolling Strings with buttons
;-----------------------------------

.INCLUDE "M2560DEF.INC"

;Interrupts setup

.org INT0addr						;Up
	jmp up_button_interrupt
	
.org INT1addr						;Down
	jmp down_button_interrupt


.def data_reg = r24
.def delay_reg1 = r26
.def delay_reg2 = r27
.def mult_reg = r17
.def counter_reg = r28

.equ control = portb
.equ data = porta
.equ dataDR = ddra
.equ controlDR = ddrb

.equ rw = 7
.equ rs = 6
.equ enable = 5


;LCD codes
.equ initial_pulse = 0b00110000
.equ four_bit_mode_com = 0b00100000
.equ ndisp_charfont_com = 0b00101000
.equ disp_on_curs_off_com = 0x0C
.equ disp_off_com = 0b0000_1000
.equ disp_clear_com = 0b0000_0001
.equ entry_mode_com = 0b00000110
.equ disp_on_blink_com = 0b00001100
.equ curs_pos2_com = 0b1000_0001
.equ disp_H_com = 0b0100_1010
.equ curs_pos1_com = 0x80
.equ curs_pos1_2line = 0b1100_0000

	rjmp main

main:

	ldi r16, LOW(RAMEND)	; stack setup
	out SPL, r16
	ldi r16, HIGH(RAMEND)
	out SPH, r16


	ser r16
	out dataDR,r16
	out controlDR,r16
	clr r16
	out data,r16
	out control,r16

	ldi counter_reg,1		;Initialize Counter to one

	rcall init


	; Turn display on, blinking, cursor
	ldi data_reg,disp_on_blink_com
	rcall sendcom

	ldi zh, HIGH(string*2)		;Print Hello LCD
	ldi zl, LOW(string*2)
	rcall print_string

	rcall wait1sec
	rcall wait1sec

	rcall clearLCD
	
	ldi r16,0b0000_1010			;Enable Interrupts on falling edge on EICRA 0 and 1 (pins 21 and 20) (PD0 and PD1)
	sts EICRA,r16

	ldi r16,(1<<INT0)+(1<<INT1) ; int masks 0 and 1 set
	out EIMSK,r16

	sei							;Enable Global Interrupts


	rjmp loop
	
	;End MAIN

loop:

	rjmp loop


button_press:
	
	cpi counter_reg,1
	breq one

	cpi counter_reg,2
	breq two

	cpi counter_reg,3
	breq three

	cpi counter_reg,4
	breq four

one:
	rcall clearLCD

	ldi zh, HIGH(string1*2)
	ldi zl, LOW(string1*2)
	rcall print_string

	ldi data_reg,curs_pos1_2line	;Cursor on second line
	rcall sendcom

	ldi zh, HIGH(string2*2+8)
	ldi zl, LOW(string2*2+8)
	rcall print_string

	ret


two:
	rcall clearLCD

	ldi zh, HIGH(string2*2)
	ldi zl, LOW(string2*2)
	rcall print_string

	ldi data_reg,curs_pos1_2line	;Cursor on second line
	rcall sendcom

	ldi zh, HIGH(string3*2)
	ldi zl, LOW(string3*2)
	rcall print_string

	ret
	
three:	
	rcall clearLCD
	
	ldi zh, HIGH(string3*2)
	ldi zl, LOW(string3*2)
	rcall print_string

	ldi data_reg,curs_pos1_2line	;Cursor on second line
	rcall sendcom

	ldi zh, HIGH(string4*2)
	ldi zl, LOW(string4*2)
	rcall print_string

	ret

four:	
	rcall clearLCD

	ldi zh, HIGH(string4*2)
	ldi zl, LOW(string4*2)
	rcall print_string

	ldi data_reg,curs_pos1_2line	;Cursor on second line
	rcall sendcom

	ldi zh, HIGH(string1*2)
	ldi zl, LOW(string1*2)
	rcall print_string

	ret
	



string:
	.db "Hello LCD",0x00

string1:
	.db "Hi, my name is",0x00

string2:
	.db "Slim Shady",0x00

string3:
	.db "Hi kids do you",0x00

string4:
	.db "like violence?",0x00



;------------------------------------------------------
; Print String
; MUST set z with the string you want to print FIRST
;------------------------------------------------------

print_string:
	
	;ldi zh, HIGH(string*2)
	;ldi zl, LOW(string*2)
print_loop:
	; Print a character
	lpm
	tst r0
	breq string_finished
	mov data_reg, r0
	push zh
	push zl
	rcall senddat

	pop zl
	pop zh

	adiw zl,1

	rjmp print_loop

string_finished:
	ret
	



init:
	rcall wait125

	cbi control,rs			;Register select: command
	cbi control,rw			;RW select, write

	rcall first_pulse
	rcall first_pulse
	rcall first_pulse


	rcall wait46
	ldi r16,four_bit_mode_com			;Start in 4-bit mode
	out data,r16
	sbi control,enable			;Set Enable
	nop
	cbi control,enable			;Clear Enable
	rcall wait46	

	ldi data_reg,ndisp_charfont_com	;Set number of display lines and character font
	rcall sendcom

	ldi data_reg,disp_on_blink_com	;Display on cursor blinking
	rcall sendcom

	ldi data_reg,disp_clear_com	;Display clear
	rcall sendcom

	ldi data_reg,entry_mode_com	;Entry mode set
	rcall sendcom
	
	;ldi data_reg,curs_pos1_com
	;rcall sendcom


	ret


first_pulse:
	rcall wait125
	ldi r16,initial_pulse
	out data,r16
	cbi control,enable			;Clear Enable
	nop
	sbi control,enable			;Set Enable
	nop
	cbi control,enable			;Clear Enable
	ret



clearLCD:					;Routine to clear the LCD screen
	ldi data_reg,disp_clear_com	;Display clear
	rcall sendcom
	ret



setcursor:
	ldi r31,curs_pos1_com
	add r16,r31				;Add the spaces
	
	mov data_reg,r16
	rcall sendcom
	ret
	

sendcom:				;Send Command - because we are using 4-bit mode, must send twice
	
	cbi control,rw			;RW select, write
	cbi control,rs			;Register select: command
	ser r16
	out dataDR,r16

	out data,data_reg		;Send High
	cbi control,enable	;Clear Enable
	nop
	sbi control,enable	;Set Enable
	nop
	cbi control,enable	;Clear Enable
	rcall delay5msec
	
	swap data_reg			;Swap Nibbles

	out data,data_reg		;Send Low
	cbi control,enable	;Clear Enable
	nop
	sbi control,enable	;Set Enable
	nop
	cbi control,enable	;Clear Enable
	rcall delay5msec

	ret



senddat:				;Send Data - because we are using 4-bit mode, must send twice

	cbi control,rw		;RW select, write
	sbi control,rs		;Register select: data
	ser r16
	out dataDR,r16

	out data,data_reg		;Send High
	cbi control,enable	;Clear Enable
	nop
	sbi control,enable	;Set Enable
	nop
	cbi control,enable	;Clear Enable
	rcall delay5msec

	swap data_reg			;Swap Nibbles

	out data,data_reg		;Send Low
	cbi control,enable	;Clear Enable
	nop
	sbi control,enable	;Set Enable
	nop
	cbi control,enable	;Clear Enable
	rcall delay5msec

	ret


	
;For now this code is not being used
LCD_busy:

	cbi ddra,7			;Set BF as input

check:
	sbic pina,7
	rjmp check

	sbi ddra,7			;Set back as output
	ret



wait1sec:
	ldi mult_reg,64			;Determines pause length, in this case 1s

outer_loop2:
	
	ldi delay_reg1,low(3037)
	ldi delay_reg2,high(3037)

delay_loop2:
	
	adiw delay_reg1,1
	brne delay_loop2

	dec mult_reg
	brne outer_loop2
	ret


wait125:
	ldi mult_reg,8			;Determines pause length, in this case 125ms

outer_loop:
	
	ldi delay_reg1,low(3037)
	ldi delay_reg2,high(3037)

delay_loop:
	
	adiw delay_reg1,1
	brne delay_loop

	dec mult_reg
	brne outer_loop
	ret



wait46:
	ldi mult_reg,3			;Determines pause length, in this case 46ms

outer_loop1:
	
	ldi delay_reg1,low(3037)
	ldi delay_reg2,high(3037)

delay_loop1:
	
	adiw delay_reg1,1
	brne delay_loop1

	dec mult_reg
	brne outer_loop1
	ret


delay100usec:		; 255 * 6 =~ 1600 cycles
	ldi delay_reg1, 255	
    loop100:
    	nop
    	nop
    	nop
	dec delay_reg1
	brne loop100
	
	ret


delay5msec:		; 80 * 4 * 255 =~ 80000 cycles

	ldi delay_reg1, 80
    outerloop2:
	ldi delay_reg2, 255
    innerloop2:
	dec delay_reg2
	nop
	brne innerloop2
    
	dec delay_reg1
	brne outerloop2
	
	ret


up_button_interrupt:
	rjmp up
	reti


down_button_interrupt:
	rjmp down
	reti

up:
	clr r16
	cpse counter_reg,r16
	rcall res1
	dec counter_reg
	rcall button_press

down:
	ldi r16,4
	cpse counter_reg,r16
	rcall res2
	inc counter_reg
	rcall button_press

res1:
	ldi counter_reg,5
	ret

res2:
	ldi counter_reg,0
	ret
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is the first line of code in your program:

.org INT0addr                  ;Up
   jmp up_button_interrupt

Guess what happens first? You jump to up_button_interrupt, hit the reti and go off into wonderland since your stack isn't initialized. The first line of code needs to be a jump to your reset routine, in your case a jump to main.

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

Also you don't show the routine button_press, so I don't know if you return from it, but you're going to have other problems. Take your UP: routine for example, you do a call to button_press and assuming you return from that call, the next thing you do is run into Down:. I'm assuming that's not what you want to do.

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

Quote:
Guess what happens first? You jump to up_button_interrupt, ...

Do you understand what he's saying here? You've used .org directives to place the jumps to the handlers at the correct addresses, but then you follow them with your jump to main (left over from your previous version). Unfortunately you don't have a .org back to 0x00 prior to that jump, so it immediately follows the second interrupt vector. It needs to be at 0x00, and the way most people do it is to have it be the first instruction.

While it seems like you could just put a .org 0x00 in front of the jump to main, that won't solve the problem for the following reason. That jump will get in the right place, but main and its code will then follow until it tries to overlay those two interrupt vectors that you .orged earlier. So you'd need

      .org  interrupt_vector_name
      jmp   interrupt vector
      .org  other_vector_name
      jmp   to_that_vector
      .org  0x00     // gets us back to 0x0000
      jmp   main
      .org  some_address_after the vectors
main: your_code

It's easier to have:

      jmp    main  // defaults to 0x00
      .org   vector1
      jmp    vector1_handler
      .org   vector2
      jmp    vector2_handler
      .org   beyond_vectors
main: your_code

What I prefer, as does the King of the Assembly World Captain Giovannious, is to put in all of the vectors explicitly, and send all of the inactive ones to a single reti instruction. Cut and paste and you're done, with no ambiguity.

But I digress. I was going to suggest pulling out all the LCD stuff and stick in a couple of LEDs (breadboard? dev board that's easy to rewire?), one for scroll up and the other for scroll down. Get the buttons and interrupts all working simulating the LCD, and when you're happy with that, put the LCD code back in place.

Chuck Baird

"I wish I were dumber so I could be more certain about my opinions. It looks fun." -- Scott Adams

http://www.cbaird.org

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

Thanks guys! Here is my working program:

;-----------------------------------
; LCD version 15
; Using Port A
; Scrolling Strings with buttons
;-----------------------------------

.INCLUDE "M2560DEF.INC"


.def data_reg = r24
.def delay_reg1 = r26
.def delay_reg2 = r27
.def mult_reg = r17
.def counter_reg = r28

.equ control = portb
.equ data = porta
.equ dataDR = ddra
.equ controlDR = ddrb

.equ rw = 7
.equ rs = 6
.equ enable = 5


;LCD codes
.equ initial_pulse = 0b00110000
.equ four_bit_mode_com = 0b00100000
.equ ndisp_charfont_com = 0b00101000
.equ disp_on_curs_off_com = 0x0C
.equ disp_off_com = 0b0000_1000
.equ disp_clear_com = 0b0000_0001
.equ entry_mode_com = 0b00000110
.equ disp_on_blink_com = 0b00001100
.equ curs_pos2_com = 0b1000_0001
.equ disp_H_com = 0b0100_1010
.equ curs_pos1_com = 0x80
.equ curs_pos1_2line = 0b1100_0000


	rjmp main

;Interrupts setup

.org INT0addr						;Up
	jmp up_button_interrupt
	
.org INT1addr						;Down
	jmp down_button_interrupt




main:

	ldi r16, LOW(RAMEND)	; stack setup
	out SPL, r16
	ldi r16, HIGH(RAMEND)
	out SPH, r16


	ser r16
	out dataDR,r16
	out controlDR,r16
	clr r16
	out data,r16
	out control,r16

	rcall init


	; Turn display on, blinking, cursor
	ldi data_reg,disp_on_blink_com
	rcall sendcom

	ldi zh, HIGH(string*2)		;Print Hello LCD
	ldi zl, LOW(string*2)
	rcall print_string

	rcall wait1sec
	rcall wait1sec

	rcall clearLCD
	
	ldi r16,0b0000_1111			;Enable Interrupts on rising edge on EICRA 0 and 1 (pins 21 and 20) (PD0 and PD1)
	sts EICRA,r16

	ldi r16,(1<<INT0)+(1<<INT1) ; int masks 0 and 1 set
	out EIMSK,r16

	sei							;Enable Global Interrupts
	
	ldi counter_reg,1		;Initialize Counter to one

	rcall button_press

	rjmp loop
	
	;End MAIN

loop:

	rjmp loop

check_counter_up:
	clr r16
	cpse counter_reg,r16	;Checks for counter = 0
	ret	
	ldi counter_reg,4
	ret


check_counter_down:
	ldi r16,5
	cpse counter_reg,r16	;Checks for counter = 5
	ret
	ldi counter_reg,1
	ret


button_press:

	rcall check_counter_up
	rcall check_counter_down

	cpi counter_reg,1
	breq one

	cpi counter_reg,2
	breq two

	cpi counter_reg,3
	breq three

	cpi counter_reg,4
	breq four

one:
	rcall clearLCD

	ldi zh, HIGH(string1*2)
	ldi zl, LOW(string1*2)
	rcall print_string

	ldi data_reg,curs_pos1_2line	;Cursor on second line
	rcall sendcom

	ldi zh, HIGH(string2*2)
	ldi zl, LOW(string2*2)
	rcall print_string

	ret


two:
	rcall clearLCD

	ldi zh, HIGH(string2*2)
	ldi zl, LOW(string2*2)
	rcall print_string

	ldi data_reg,curs_pos1_2line	;Cursor on second line
	rcall sendcom

	ldi zh, HIGH(string3*2)
	ldi zl, LOW(string3*2)
	rcall print_string

	ret
	
three:	
	rcall clearLCD
	
	ldi zh, HIGH(string3*2)
	ldi zl, LOW(string3*2)
	rcall print_string

	ldi data_reg,curs_pos1_2line	;Cursor on second line
	rcall sendcom

	ldi zh, HIGH(string4*2)
	ldi zl, LOW(string4*2)
	rcall print_string

	ret

four:	
	rcall clearLCD

	ldi zh, HIGH(string4*2)
	ldi zl, LOW(string4*2)
	rcall print_string

	ldi data_reg,curs_pos1_2line	;Cursor on second line
	rcall sendcom

	ldi zh, HIGH(string1*2)
	ldi zl, LOW(string1*2)
	rcall print_string

	ret
	



string:
	.db "Hello LCD",0x00

string1:
	.db "Hi, my name is",0x00

string2:
	.db "Slim Shady",0x00

string3:
	.db "Hi kids do you",0x00

string4:
	.db "like violence?",0x00



;------------------------------------------------------
; Print String
; MUST set z with the string you want to print FIRST
;------------------------------------------------------

print_string:
	
	;ldi zh, HIGH(string*2)
	;ldi zl, LOW(string*2)
print_loop:
	; Print a character
	lpm
	tst r0
	breq string_finished
	mov data_reg, r0
	push zh
	push zl
	rcall senddat

	pop zl
	pop zh

	adiw zl,1

	rjmp print_loop

string_finished:
	ret
	



init:
	rcall wait125

	cbi control,rs			;Register select: command
	cbi control,rw			;RW select, write

	rcall first_pulse
	rcall first_pulse
	rcall first_pulse


	rcall wait46
	ldi r16,four_bit_mode_com			;Start in 4-bit mode
	out data,r16
	sbi control,enable			;Set Enable
	nop
	cbi control,enable			;Clear Enable
	rcall wait46	

	ldi data_reg,ndisp_charfont_com	;Set number of display lines and character font
	rcall sendcom

	ldi data_reg,disp_on_blink_com	;Display on cursor blinking
	rcall sendcom

	ldi data_reg,disp_clear_com	;Display clear
	rcall sendcom

	ldi data_reg,entry_mode_com	;Entry mode set
	rcall sendcom
	
	;ldi data_reg,curs_pos1_com
	;rcall sendcom


	ret


first_pulse:
	rcall wait125
	ldi r16,initial_pulse
	out data,r16
	cbi control,enable			;Clear Enable
	nop
	sbi control,enable			;Set Enable
	nop
	cbi control,enable			;Clear Enable
	ret



clearLCD:					;Routine to clear the LCD screen
	ldi data_reg,disp_clear_com	;Display clear
	rcall sendcom
	ret



setcursor:
	ldi r31,curs_pos1_com
	add r16,r31				;Add the spaces
	
	mov data_reg,r16
	rcall sendcom
	ret
	

sendcom:				;Send Command - because we are using 4-bit mode, must send twice
	
	cbi control,rw			;RW select, write
	cbi control,rs			;Register select: command
	ser r16
	out dataDR,r16

	out data,data_reg		;Send High
	cbi control,enable	;Clear Enable
	nop
	sbi control,enable	;Set Enable
	nop
	cbi control,enable	;Clear Enable
	rcall delay5msec
	
	swap data_reg			;Swap Nibbles

	out data,data_reg		;Send Low
	cbi control,enable	;Clear Enable
	nop
	sbi control,enable	;Set Enable
	nop
	cbi control,enable	;Clear Enable
	rcall delay5msec

	ret



senddat:				;Send Data - because we are using 4-bit mode, must send twice

	cbi control,rw		;RW select, write
	sbi control,rs		;Register select: data
	ser r16
	out dataDR,r16

	out data,data_reg		;Send High
	cbi control,enable	;Clear Enable
	nop
	sbi control,enable	;Set Enable
	nop
	cbi control,enable	;Clear Enable
	rcall delay5msec

	swap data_reg			;Swap Nibbles

	out data,data_reg		;Send Low
	cbi control,enable	;Clear Enable
	nop
	sbi control,enable	;Set Enable
	nop
	cbi control,enable	;Clear Enable
	rcall delay5msec

	ret


	
;For now this code is not being used
LCD_busy:

	cbi ddra,7			;Set BF as input

check:
	sbic pina,7
	rjmp check

	sbi ddra,7			;Set back as output
	ret



wait1sec:
	ldi mult_reg,64			;Determines pause length, in this case 1s

outer_loop2:
	
	ldi delay_reg1,low(3037)
	ldi delay_reg2,high(3037)

delay_loop2:
	
	adiw delay_reg1,1
	brne delay_loop2

	dec mult_reg
	brne outer_loop2
	ret


debounce:				;Basically a pause that waits long enough until the switch has stopped bouncing.
	ldi r17,16			;In this case, 250ms

outer_loop_d:
	
	ldi r24,low(3037)
	ldi r25,high(3037)

delay_loop_d:
	
	adiw r24,1			;ADIW and BRNE both take 2 cycles to execute. 4x(2^16 - 3037) ~= 250,000 cycles
	brne delay_loop_d

	dec r17				;250,000 x whatever the value of r17 is gives you total number of cycles to wait
	brne outer_loop_d
	ret




wait125:
	ldi mult_reg,8			;Determines pause length, in this case 125ms

outer_loop:
	
	ldi delay_reg1,low(3037)
	ldi delay_reg2,high(3037)

delay_loop:
	
	adiw delay_reg1,1
	brne delay_loop

	dec mult_reg
	brne outer_loop
	ret



wait46:
	ldi mult_reg,3			;Determines pause length, in this case 46ms

outer_loop1:
	
	ldi delay_reg1,low(3037)
	ldi delay_reg2,high(3037)

delay_loop1:
	
	adiw delay_reg1,1
	brne delay_loop1

	dec mult_reg
	brne outer_loop1
	ret


delay100usec:		; 255 * 6 =~ 1600 cycles
	ldi delay_reg1, 255	
    loop100:
    	nop
    	nop
    	nop
	dec delay_reg1
	brne loop100
	
	ret


delay5msec:		; 80 * 4 * 255 =~ 80000 cycles

	ldi delay_reg1, 80
    outerloop2:
	ldi delay_reg2, 255
    innerloop2:
	dec delay_reg2
	nop
	brne innerloop2
    
	dec delay_reg1
	brne outerloop2
	
	ret


up_button_interrupt:
	rcall debounce
	inc counter_reg
	rcall button_press
	;ldi r16,(1<<INT0)+(1<<INT1) ; int masks 0 and 1 set
	;out EIMSK,r16
	reti


down_button_interrupt:
	rcall debounce
	dec counter_reg
	rcall button_press
	;ldi r16,(1<<INT0)+(1<<INT1) ; int masks 0 and 1 set
	;out EIMSK,r16
	reti

I have a couple of questions: one, it works pretty good, but sometimes it skips two lines instead of one. I have already debounced the switches at the very beginning of the interrupts with a 250ms delay - do I need a bigger delay or is there an error in my code that could be causing this?

Two, it seems even to me that the program I came up with, with the whole "jump here if one, or here if two...etc" is kind of long and inefficient. I'm just learning AVR assembler now (I've been going at it for about two weeks); what can I do to make it be more efficient? Is there a way I could use some kind of pointer and increment and decrement it to go up and down the list?

Thanks in advance!

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

You really want to stop at this point as your program has a number of serious defects. First up, don't use pushbuttons on interrupts - has been discussed a zillion times before. Have a read of my tutorial 'the trap when using interrupts'. Your program is getting larger but yet you have dedicated registers for certain variables. You'll soon run into a brick wall metaphorically as you'll run out of registers. Learn to use the ram to store your variables. Then learn about storing and restoring the processor state when you enter/exit a isr. Why do you not use C? Then see how the C compiler handles things at an assembler level.

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

Quote:
You really want to stop at this point....
He's got too much enthusiasm to take the time to stop and clean up his code. Once he gets something 'working' he just plunges ahead to the next idea. He has ignored many of the suggestions made to him in his previous thread https://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=103663&start=0.

Don

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

You're not handling your interrupts correctly. At a minimum you need to save the status register as soon as you enter the ISR (dedicated register or push on the stack), then restore the status register just before the reti. You will also learn that you need to keep your ISR's short and fast, use the ISR to set flags that your main routine can work with. This allows other interrupts to occur since you don't spend all your time in the ISR's.