MORON'S GUIDE TO THE BUTTERFLY JOYSTICK & LCD v1.2 by RetroDan@GMail.com TABLE OF CONTENTS: INTRODUCTION THE BUTTERFLY JOYSTICK THE LCD SCREEN THE JOYSTICK TESTER PROGRAM THE MAIN LOOP THE MESSAGES DEFINED TRANSFERING DATA TO OUR BUFFER THE DISPLAY ROUTINE THE LCD DATA REGISTERS BIT MANIPULATION GYMNASTICS INTIALIZING THE LCD MODULE THE LOOKUP TABLE A SOUND EFFECT AND A PAUSE FINAL JOYSTICK TESTER PROGRAM LISTING AN LCD SCROLLING PROGRAM LCD SCROLLING PROGRAM LISTING INTRODUCTION The joystick is a neat little switch that can be used for input on the Butterfly Demo Boards. The Liquid Crystal Display (LCD) is used as output, to display up to six characters. We are going to use these two devices to create a joystick tester in assembly language that will display which position of the joystick is active at any time. Then we make a few changes and re-use most of the same code to create a program that will scroll long messages across the small LCD screen. THE BUTTERFLY JOYSTICK The Joystick is a combination of five switches in one; one for each of four directions and a centre switch which is activated by pressing down in the middle position. A quick look at the schematics for the Butterfly board shows that the joystick is connected to input pins of both Port B and Port E. Note that the left & right switches are connected to Port E. MIDDLE SWITCH ____-____ PinB,4 UP SWITCH ____-____ PinB,6 DOWN SWITCH ____-____ PinB,7 LEFT SWITCH ____-____ PinE,2 RIGHT SWITCH ____-____ PinE,3 The joystick switches are pulled up by the pull-up resisters and are read as ones when not in use and are shorted to ground and read as zero when pressed. When untouched and open, the input pins float up to the three volts supplied to the Butterfly through an internal resistor tied to Vcc: +3VDC +3VDC | | Z Z Z Z OPEN Z CLOSED Z SWITCH: _ | SWITCH: | .--- ---+---- PINx .------------+---- PINx | | V V GROUND GROUND Since the joystick is an input device we use the PINx command to read them and not the PORTn form. To catch all the possibilities we might have code that resembles the following: SBIS PINB,4 ;JOYSTICK PRESS RJMP JOYMID SBIS PINB,6 ;JOYSTICK UP RJMP JOYUP SBIS PINB,7 ;JOYSTICK DOWN RJMP JOYDOWN SBIS PINE,2 ;JOYSTICK LEFT RJMP JOYLEFT SBIS PINE,3 ;JOYSTICK RIGHT RJMP JOYRIGHT Note that while three inputs are to Port B pins, the Left and Right switches are connected to Port E pins. The Skip if Bit is Set command (SBIS) test the indicated pin and if it is still set (indicating not pressed) it will skip the RJMP command that follows it. When the associated pin line is pressed (reads zero) the program jumps to the correct routine. THE LCD SCREEN The LCD is created by long crystals mounted behind polarized glass. In their normal state they are aligned with the polarized glass and appear transparent so the grey back of the LCD can be seen. When a voltage is applied, the crystals bend enough that light cannot be transmitted through the polarized glass, the associated segment then appears black to the viewer. RELAXED VOLTAGE APPLIED POLARIZED GLASS: - - - - - - - - - - - - - - - - - - - - LIQUID CRYSTALS: | | | | | | | | ==> / / / / / / / / / / BACKGROUND: ----------------- --------------------- The LCD characters are made from fourteen segments (a to n). To create a character we need to activate the segments that make up the character. For example To create the letter I we might activate segments j and n; and if you look for the letter I in the table below, you see that there is a one in the position for n and j, and the letter C uses segments d,e,f,a: ; mpnd legc jfhb k a <------> LCD SEGMENTS .DW 0b_0011_1001_1001_0001 ;B -----a----- .DW 0b_0001_0100_0100_0001 ;C | \ | / | .DW 0b_0011_0001_1001_0001 ;D f h j k b .DW 0b_0001_1110_0100_0001 ;E | \ | / | .DW 0b_0000_1110_0100_0001 ;F --g-- --l-- .DW 0b_0001_1101_0100_0001 ;G | / | \ | .DW 0b_0000_1111_0101_0000 ;H e p n m c .DW 0b_0010_0000_1000_0000 ;I | / | \ | .DW 0b_0001_0101_0001_0000 ;J -----d----- The above is part of a look-up table we use to convert values and ASCII characters to LCD Segments. Later you can modify it to create your own character set. The LCD segments are memory mapped to twenty memory locations LCDDR0 to LCDDR19 as shown below in a small subroutine that clears all the segments. The Y-Pointer is set to the first memory location LCDDR0, a zero is written to that location and the pointer is increased by one, and then next is cleared until we reach LCDDR19, at which point we stop: ;---------------------------; ; CLEAR ALL SEGMENTS ON LCD ; ;---------------------------; LCD_CLR: LDI YL,LOW(LCDDR0) CLR YH CLRLUPE: ST Y+,ZERO CPI YL,LCDDR18+1 BRNE CLRLUPE RET The LCD Module is quite complex, but as long as we initialize and configure it correctly, all we need to do is convert numbers and ASCII characters to LCD segments, write them to the appropriate memory locations and they will display on the LCD. The LCD Module takes care of things such as duty cycle, frame rates, etc. THE JOYSTICK TESTER PROGRAM First we tell the assembler to include the definitions for the ATmega169 MCU on the Butterfly: .INCLUDE "M169DEF.INC" ;BUTTERFLY DEFS We then define the registers that we will be using. Note that the six registers from R2 to R7 are used as a character buffer for our LCD routine. To display up to six character we load them into these six registers and call our LCD routine, which will do the conversion from numerical or ASCII characters to LCD Segments. CHR6BUF is a pointer to this buffer: .SET CHR6BUF = 2 ;6 CHAR BUFFER IS [R2,R3,R4,R5,R6,R7] .DEF ZERO = R8 .DEF T1 = R11 .DEF T2 = R12 .DEF A = R16 ;R16:R31 CAN BE LOADED IMMEDIATE (LDI) .DEF AH = R17 .DEF B = R18 .DEF C = R19 .DEF D = R20 .DEF I = R21 .DEF J = R22 .DEF K = R23 .DEF N = R24 We start our program at the bottom-of-memory. Set a register called ZERO to zero. Then we set-up a stack at the top-of-memory: .ORG $0000 RJMP ON_RESET ON_RESET: CLR ZERO LDI A,HIGH(RAMEND) ;SETUP THE STACK POINTER OUT SPH,A ;AT TOP OF MEMORY AND LDI A,LOW(RAMEND) ;GROW DOWNWARDS OUT SPL,A We set Port A and Port E for input. Then we initialize the LCD and make sure it is cleared: SER A ;INIT PORTS B&E FOR INPUT OUT PORTB,A OUT PORTE,A RCALL LCD_INIT ;INITIALIZE LCD RCALL LCD_CLR ;CLEAR LCD SEGMENTS THE MAIN LOOP The main part of the program polls the joystick switches. If one is pressed it becomes a zero and the appropriate routine is called: MAIN: LOOP: SBIS PINB,4 ;JOYSTICK PRESS RJMP JOYMID SBIS PINB,6 ;JOYSTICK UP RJMP JOYUP SBIS PINB,7 ;JOYSTICK DOWN RJMP JOYDOWN SBIS PINE,2 ;JOYSTICK LEFT RJMP JOYLEFT SBIS PINE,3 ;JOYSTICK RIGHT RJMP JOYRIGHT If no joystick switches are depressed, then the Z-Pointer is set to the message “PRESS” and then jumps to a routine that will display that message on the LCD: NOJOY: LDI ZL,LOW(MESWAIT*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESWAIT*2) RJMP SHOWMESS If a joystick switch then the program jumps to one of the following labels, which sets the Z-Pointer to an appropriate message. JOYMID: LDI ZL,LOW(MESMID*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESMID*2) RJMP BPMESS JOYUP: LDI ZL,LOW(MESUP*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESUP*2) RJMP BPMESS JOYDOWN:LDI ZL,LOW(MESDOWN*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESDOWN*2) RJMP BPMESS JOYLEFT:LDI ZL,LOW(MESLEFT*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESLEFT*2) RJMP BPMESS JOYRIGHT: LDI ZL,LOW(MESRIGHT*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESRIGHT*2) This part of the main routine displays the characters pointed to by the Z-Pointer then calls a delay routine before it loops-back to start over. If a switch is closed then it enters this part of the code from the BPMESS label which also calls a routine to emit a sound from the built-in speaker. BPMESS: RCALL WHIT SHOWMESS: RCALL SHOWBUF ;SHOW MESSAGE RCALL DELAY ;WAIT DONE: RJMP LOOP THE MESSAGES DEFINED Here we define the six messages for the LCD Display: MESWAIT: .DB "PRESS " MESMID: .DB "CENTRE" MESUP: .DB " UP " MESDOWN: .DB " DOWN " MESLEFT: .DB " LEFT " MESRIGHT: .DB "RIGHT " TRANSFERING DATA TO OUR BUFFER The SHOWBUF routine copies the characters pointed to by the Z-Pointer into the six character buffer in registers R2 to R7, then calls the routine DISPN that will display them on the LCD Screen: SHOWBUF:LPM A,Z+ MOV R7,A LPM A,Z+ MOV R6,A LPM A,Z+ MOV R5,A LPM A,Z+ MOV R4,A LPM A,Z+ MOV R3,A LPM A,Z+ MOV R2,A PUSH A RCALL DISPN POP A RET THE DISPLAY ROUTINE The DISPN routine does the conversion from an ASCII character (or a number value) stored in the six character buffer at R2-R7 to LCD segments then stuffs the results into the appropriate LCD Display Registers LCDDR0-LCDDR19. First it points the X-Pointer to the six character registers R2-R7: DISPN: LDI XL,LOW(CHR6BUF) ;POINTS BUFFER-6 LDI XH,HIGH(CHR6BUF) Our character buffer and LCD Display is six characters long, so we set a counter to six. The LCD registers stuff two characters into one byte, so we are going to need a bit mask $F0 to strip away one four bit nybble for us later. LCD_DSP: LDI N,6 ;SIX CHARS LDI B,$F0 ;BITMASK Next we read in a character from our buffer and check if it is a space, and if so we set it to a blank space (no segments activated): DSPNXT: LD A,X+ ;FETCH THE CHAR TO DISP CPI A,' ' ;SPACE? BRNE NOSPC ;SPACE XLATION LDI A,SPACE-LCD_TABLE We check if it is a small letter of ASCII and if so convert it to upper-case: NOSPC: CPI A,'a' ;CHARACTER XLATION BRLO NOSMLET ;SMALL LETTERS? SUBI A,$20 ;FOLD#1 a=>A If it is an upper-case ASCII letter we subtract $37 to make the letter “A” the tenth character in our look-up table (A=10 in Hex). This will make the rest of the upper-case letters line up properly for our translation table: NOSMLET: CPI A,'A' ;CAP LETTERS BRLO NOBGLET ; SUBI A,$37 ;FOLD#2 A=>10 ASCII numbers have $30 subtracted from them so that the character zero is made to equal zero. This aligns the numbers in our look-up table from 0-9. NOBGLET: CPI A,'0' ;ASCII NUMBERS BRLO NOANUM ; SUBI A,$30 ;FOLD#3 "0"=>0 Once we have the ASCII converted to an entry in our look-up table, we multiply it by two and use it as an off-set into our segment look-up table. We multiply it by two because each entry in the table is is a “word” wide, made of two bytes: NOANUM: LSL A ;POINT Z INTO TABLE LDI ZL,LOW(LCD_TABLE*2) LDI ZH,HIGH(LCD_TABLE*2) ADD ZL,A ;OFFSET INTO ADC ZH,ZERO ;CHARACTER TABLE THE LCD DATA REGISTERS The next part is tricky because the LCD Module expects two characters to be stuffed into one byte, but also the segments for these two characters are spread over four different registers which are stored five bytes apart: For example if the two characters are “C” and “I”: ; mpnd legc jfhb k a <------> LCD SEGMENTS .DW 0b_0011_1001_1001_0001 ;B -----a----- .DW 0b_0001_0100_0100_0001 ;C | \ | / | .DW 0b_0011_0001_1001_0001 ;D f h j k b .DW 0b_0001_1110_0100_0001 ;E | \ | / | .DW 0b_0000_1110_0100_0001 ;F --g-- --l-- .DW 0b_0001_1101_0100_0001 ;G | / | \ | .DW 0b_0000_1111_0101_0000 ;H e p n m c .DW 0b_0010_0000_1000_0000 ;I | / | \ | .DW 0b_0001_0101_0001_0000 ;J -----d----- high-nybble low-nybble LCDDRx: k - - a k - - a LCDDRx+5: j f h b j f h b LCDDRx+10: l e g c l e g c LCDDRx+15: m p n d m p n d We see that “C” requires segments d,e,f,a activated and “I” needs n & j so our LCD Data Registers would look like this: LCD “C” “I” LCDDR0: 0 0 0 1 0 0 0 0 LCDDR4: 0 1 0 0 1 0 0 0 LCDDR9: 0 1 0 0 0 0 0 0 LCDDR14: 0 0 0 1 0 0 1 0 BIT MANIPULATION GYMNASTICS Now that we have converted our ASCII character into LCD segments, the next part does bit manipulation gymnastics because the LCD Module expects our two characters to be stuffed into one byte, but also the segments for these two characters are spread over four different registers which are stored five bytes apart. LDI YL,LOW(LCDDR1)-1 ;(=251)POINTS TO CLR YH ;LCD SEGMENTS MOV A,N ;USE COUNTER DEC A LSR A ;AS OFFSET TO ADD YL,A ;SEGMENTS SET LDI I,4 DISPLUP: CPI YL,LOW(LCDDR8) ;PAST CHECK POINT? BRLO NOZINC ;PAST 2ND READ? BRTC NOZINC ;SHOULD WE INCZ? ADIW ZH:ZL,1 ;INCZ AFTER 2ND READ CLT ;STOP FURTHER INCZ NOZINC: LPM A,Z ;LOAD SEGMENT DATA SBRS I,0 ;USE BIT0 SWAP A ;SWAP ON EVEN SEGS SBRC N,0 ;USE BIT0 SWAP A ;SWAP ON EVEN DIGITS POTRIP: AND A,B ;MASK NEEDED INFO COM B ;INVERT MASK LD C,Y ;READ-IN SEGMENT AND C,B ;CLEAR A SPOT OR A,C ;SHOVE-IN NEW ST Y,A ;WRITE-BACK COM B ;RE-INVERT MASK ADIW YH:YL,5 ;NEXT SEG DEC I BRNE DISPLUP ;DONE 4 SEGS? SKPNUM: COM B ;INVERT BIT-MASK DEC N ;DONE 6 DIGITS? NOINC: BRNE DSPNXT RET INTIALIZING THE LCD MODULE Before we can use the LCD Module we must set-up and initialize it. First we set the clock to external by setting the LCD Clock Select (LCDCS) to one, we select a duty cycle of Ό by setting the LCDMUX1 & LCDMUX0 to one. We tell the Module to use 4 x 25 pins for output by setting the three bits LCDPM2:0 to one in the LCD Control Register “B” (LCDCRB): LCDCRB: [LCDCS,LCDB2,LCDMUX1,LCDMUX0,_,LCDPM2,LCDPM1,LCDPM0] LCD_INIT: LDI A,0b1011_0111 ;SET CLOCK, DUTY CYCLE AND # of PINS STS LCDCRB, A ;ENABLE ALL SEGEMENTS To set the update/frame rate to 32Hz we set the clock divider to 8 by setting the by setting the LCDCD2:0 to one in the LCD Frame Rate Register (LCDFRR). Anything slower than 26Hz and the screen will flicker: LCDFRR: [_,LCDPS2,LCDPS1,LCDPS0,_,LCDCD2,LCDCD1,LCDCD0] LDI A,0b0000_0111 ;SET FRAME RATE TO 32Hz STS LCDFRR, A For high contrast we select a voltage of 3.3 Volts by setting the LCDCC3:1 to one in the LCD Contrast Control Register (LCDCCR). To save power you could use a lower setting but the characters will be less black: LDI A,0b0000_1110 ;(1< LCD SEGMENTS .DW 0b_0001_0101_0101_0001 ;ZERO .DW 0b_0010_0000_1000_0000 ;1 .DW 0b_0001_1110_0001_0001 ;2 .DW 0b_0001_1011_0001_0001 ;3 .DW 0b_0000_1011_0101_0000 ;4 .DW 0b_0001_1011_0100_0001 ;5 .DW 0b_0001_1111_0100_0001 ;6 .DW 0b_0000_0001_0101_0001 ;7 .DW 0b_0001_1111_0101_0001 ;8 .DW 0b_0001_1011_0101_0001 ;9 .DW 0b_0000_1111_0101_0001 ;A .DW 0b_0011_1001_1001_0001 ;B -----a----- .DW 0b_0001_0100_0100_0001 ;C | \ | / | .DW 0b_0011_0001_1001_0001 ;D f h j k b .DW 0b_0001_1110_0100_0001 ;E | \ | / | .DW 0b_0000_1110_0100_0001 ;F --g-- --l-- .DW 0b_0001_1101_0100_0001 ;G | / | \ | .DW 0b_0000_1111_0101_0000 ;H e p n m c .DW 0b_0010_0000_1000_0000 ;I | / | \ | .DW 0b_0001_0101_0001_0000 ;J -----d----- .DW 0b_1000_0110_0100_1000 ;K .DW 0b_0001_0100_0100_0000 ;L .DW 0b_0000_0101_0111_1000 ;M .DW 0b_1000_0101_0111_0000 ;N .DW 0b_0001_0101_0101_0001 ;0 .DW 0b_0000_1110_0101_0001 ;P .DW 0b_1001_0101_0101_0001 ;Q .DW 0b_1000_1110_0101_0001 ;R .DW 0b_0001_1011_0100_0001 ;S .DW 0b_0010_0000_1000_0001 ;T .DW 0b_0001_0101_0101_0000 ;U .DW 0b_1000_0001_0011_0000 ;V .DW 0b_1100_0101_0101_0000 ;W .DW 0b_1100_0000_0010_1000 ;X .DW 0b_0010_0000_0010_1000 ;Y .DW 0b_0101_0000_0000_1001 ;Z .DW 0b_0001_0100_0100_0001 ;[ .DW 0b_1000_0000_0010_0000 ;\ .DW 0b_0001_0001_0001_0001 ;] .DW 0b_0000_0000_0110_0000 ;^ .DW 0b_0001_0000_0000_0000 ;_ .DW 0b_0000_0000_0000_1000 ;' .DW 0b_1110_1010_1010_1000 ;* .DW 0b_0010_1010_1000_0000 ;+ SPACE: .DW 0B_0000_0000_0000_0000 ;(SPACE) .DW 0b_0000_1010_0000_0000 ;- .DW 0b_0100_0000_0000_0000 ;. .DW 0b_0100_0000_0000_1000 ;/ .DW 0b_1000_0000_0000_1000 ;< .DW 0b_0001_1010_0000_0000 ;= .DW 0b_0100_0000_0010_0000 ;> A SOUND EFFECT AND A PAUSE The WHIT routine simply makes a small sound effect on the speaker. It repeatedly toggles the speaker pin and calls a pause routine between the toggles, the result is a sound on the speaker. As it does this the counter R0 is decremented so the inter-toggle pause gets smaller and smaller, so the frequency goes up. The result is a sound effect like “WHIT”: WHIT: CLR R0 SBI DDRB,5 ;SET PORTB-BIT5 FOR OUTPUT WHLUPE: SBI PINB,5 ;SET PORTB-BIT5 RCALL WPAUSE ;WAIT DEC R0 BRNE WHLUPE ;LOOP AROUND RET WPAUSE: PUSH R0 ;PAUSE TWEEN PULSES WPLUPE: DEC R0 ;IE DETERMINS FREQ BRNE WPLUPE POP R0 RET The pause routine just goes in loops wasting time: PAUSE: DELAY: PUSH A LDI A,8 DLUPE: DEC R0 BRNE DLUPE DEC R1 BRNE DLUPE DEC A BRNE DLUPE POP A RET FINAL JOYSTICK TESTER PROGRAM LISTING We put all the pieces together an you get this program ready to run for the Butterfly: ;---------------------------------------------; ; JOYSTICK_TESTER ; ; ; ; AUTHOR: DANIEL J, DOREY (RETRODAN@GMAIL.COM ; ; 19-OCT-09: CREATED LAST UPDATE:04-SEP-10 ; ;---------------------------------------------; .NOLIST .INCLUDE "M169DEF.INC" ;BUTTERFLY DEFS .LIST ;---------------------------------: ; RENAME/DEFINE WORKING REGISTERS ; ;---------------------------------; .SET CHR6BUF = 2 ;6 CHAR BUFFER IS [R2,R3,R4,R5,R6,R7] .DEF ZERO = R8 .DEF T1 = R11 .DEF T2 = R12 .DEF A = R16 ;R16:R31 CAN BE LOADED IMMEDIATE (LDI) .DEF AH = R17 .DEF B = R18 .DEF C = R19 .DEF D = R20 .DEF I = R21 .DEF J = R22 .DEF K = R23 .DEF N = R24 .ORG $0000 RJMP ON_RESET ;-----------------; ; INITIALIZATIONS ; ;-----------------; ON_RESET: CLR ZERO LDI A,HIGH(RAMEND) ;SETUP THE STACK POINTER OUT SPH,A ;AT TOP OF MEMORY AND LDI A,LOW(RAMEND) ;GROW DOWNWARDS OUT SPL,A SER A ;INIT PORTS B&E FOR INPUT OUT PORTB,A OUT PORTE,A RCALL LCD_INIT ;INITIALIZE LCD RCALL LCD_CLR ;CLEAR LCD SEGMENTS ;-----------; ; MAIN LOOP ; ;-----------; MAIN: LOOP: SBIS PINB,4 ;JOYSTICK PRESS RJMP JOYMID SBIS PINB,6 ;JOYSTICK UP RJMP JOYUP SBIS PINB,7 ;JOYSTICK DOWN RJMP JOYDOWN SBIS PINE,2 ;JOYSTICK LEFT RJMP JOYLEFT SBIS PINE,3 ;JOYSTICK RIGHT RJMP JOYRIGHT NOJOY: LDI ZL,LOW(MESWAIT*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESWAIT*2) RJMP SHOWMESS JOYMID: LDI ZL,LOW(MESMID*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESMID*2) RJMP BPMESS JOYUP: LDI ZL,LOW(MESUP*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESUP*2) RJMP BPMESS JOYDOWN:LDI ZL,LOW(MESDOWN*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESDOWN*2) RJMP BPMESS JOYLEFT:LDI ZL,LOW(MESLEFT*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESLEFT*2) RJMP BPMESS JOYRIGHT: LDI ZL,LOW(MESRIGHT*2) ;SET A POINTER TO MESSAGE LDI ZH,HIGH(MESRIGHT*2) BPMESS: RCALL WHIT SHOWMESS: RCALL SHOWBUF ;SHOW MESSAGE RCALL DELAY ;WAIT DONE: RJMP LOOP MESWAIT: .DB "PRESS " MESMID: .DB "CENTRE" MESUP: .DB " UP " MESDOWN: .DB " DOWN " MESLEFT: .DB " LEFT " MESRIGHT: .DB "RIGHT " ;=====================[ SUBROUTINES ]========================= ;-----------------------------------------------; ; NO RESTORE WHIT ROUTINE, USES THE R0 REGISTER ; ;-----------------------------------------------; WHIT: CLR R0 SBI DDRB,5 ;SET PORTB-BIT5 FOR OUTPUT WHLUPE: SBI PINB,5 ;SET PORTB-BIT5 RCALL WPAUSE ;WAIT DEC R0 BRNE WHLUPE ;LOOP AROUND RET WPAUSE: PUSH R0 ;PAUSE TWEEN PULSES WPLUPE: DEC R0 ;IE DETERMINS FREQ BRNE WPLUPE POP R0 RET ;-------------------------------; ; COPIES TEXT TO DISPLAY BUFFER ; ; MUST LOAD (Z) FIRST ; ;-------------------------------; SHOWBUF:LPM A,Z+ MOV R7,A LPM A,Z+ MOV R6,A LPM A,Z+ MOV R5,A LPM A,Z+ MOV R4,A LPM A,Z+ MOV R3,A LPM A,Z+ MOV R2,A PUSH A RCALL DISPN POP A RET ;-----------------------------------------------; ; DISPN - DISPLAY THE NUMBER IN R7:R2 REGISTERS ; ; NOTE CHR6BUF MUST BE POINTING 6 CHAR BUFFER ; ; APR/06 VERSION II WITH ASCII XLATION ; ;-----------------------------------------------; DISPN: LDI XL,LOW(CHR6BUF) ;POINTS BUFFER-6 LDI XH,HIGH(CHR6BUF) ;-------------------------; ; ENTER HERE IF XH:XL SET ; ;-------------------------; LCD_DSP: LDI N,6 ;SIX CHARS LDI B,$F0 ;BITMASK DSPNXT: LD A,X+ ;FETCH THE CHAR TO DISP CPI A,' ' ;SPACE? BRNE NOSPC ;SPACE XLATION LDI A,SPACE-LCD_TABLE NOSPC: CPI A,'a' ;CHARACTER XLATION BRLO NOSMLET ;SMALL LETTERS? SUBI A,$20 ;FOLD#1 a=>A NOSMLET: CPI A,'A'-1 ;CAP LETTERS BRLO NOBGLET ; SUBI A,$37 ;FOLD#2 A=>10 NOBGLET: CPI A,'0'-1 ;ASCII NUMBERS BRLO NOANUM ; SUBI A,$30 ;FOLD#3 "0"=>0 NOANUM: LSL A ;POINT Z INTO TABLE LDI ZL,LOW(LCD_TABLE*2) LDI ZH,HIGH(LCD_TABLE*2) ADD ZL,A ;OFFSET INTO ADC ZH,ZERO ;CHARACTER TABLE LDI YL,LOW(LCDDR1)-1 ;(=251)POINTS TO CLR YH ;LCD SEGMENTS MOV A,N ;USE COUNTER DEC A LSR A ;AS OFFSET TO ADD YL,A ;SEGMENTS SET LDI I,4 DISPLUP: CPI YL,LOW(LCDDR8) ;PAST CHECK POINT? BRLO NOZINC ;PAST 2ND READ? BRTC NOZINC ;SHOULD WE INCZ? ADIW ZH:ZL,1 ;INCZ AFTER 2ND READ CLT ;STOP FURTHER INCZ NOZINC: LPM A,Z ;LOAD SEGMENT DATA SBRS I,0 ;USE BIT0 SWAP A ;SWAP ON EVEN SEGS SBRC N,0 ;USE BIT0 SWAP A ;SWAP ON EVEN DIGITS POTRIP: AND A,B ;MASK NEEDED INFO COM B ;INVERT MASK LD C,Y ;READ-IN SEGMENT AND C,B ;CLEAR A SPOT OR A,C ;SHOVE-IN NEW ST Y,A ;WRITE-BACK COM B ;RE-INVERT MASK ADIW YH:YL,5 ;NEXT SEG DEC I BRNE DISPLUP ;DONE 4 SEGS? SKPNUM: COM B ;INVERT BIT-MASK DEC N ;DONE 6 DIGITS? NOINC: BRNE DSPNXT RET ;---------------------------; ; CLEAR ALL SEGMENTS ON LCD ; ;---------------------------; LCD_CLR: LDI YL,LOW(LCDDR0) CLR YH CLRLUPE: ST Y+,ZERO CPI YL,LCDDR18+1 BRNE CLRLUPE RET ;-------------------------------; ; INITIALIZE LCD DISP REGISTERS ; ;-------------------------------; LCD_INIT: PUSH A LDI A,0b1011_0111 ;SET CLOCK, DUTY CYCLE, # PINS STS LCDCRB, A ;ENABLE ALL SEGEMENTS LDI A,0b0000_0111 ;SET FRAME RATE TO 32Hz STS LCDFRR, A LDI A,0b0000_1110 ;SET CONTRAST VOLTAGE TO 3.3 VOLTS STS LCDCCR, A LDI A,0b1100_0000 ;ENABLE LCD WITH POWER SAVE WAVEFORM STS LCDCRA, A POP A RET PAUSE: DELAY: PUSH A LDI A,8 DLUPE: DEC R0 BRNE DLUPE DEC R1 BRNE DLUPE DEC A BRNE DLUPE POP A RET ;-----------------------------------------------------------; ; RETRO DAN'S IMPROVED LCD CHARACTER TABLE V1.2 ; ; ;-----------------------------------------------------------; LCD_TABLE: ; mpnd legc jfhb k a <------> LCD SEGMENTS .DW 0b_0001_0101_0101_0001 ;ZERO .DW 0b_0010_0000_1000_0000 ;1 .DW 0b_0001_1110_0001_0001 ;2 .DW 0b_0001_1011_0001_0001 ;3 .DW 0b_0000_1011_0101_0000 ;4 .DW 0b_0001_1011_0100_0001 ;5 .DW 0b_0001_1111_0100_0001 ;6 .DW 0b_0000_0001_0101_0001 ;7 .DW 0b_0001_1111_0101_0001 ;8 .DW 0b_0001_1011_0101_0001 ;9 .DW 0b_0000_1111_0101_0001 ;A .DW 0b_0011_1001_1001_0001 ;B -----a----- .DW 0b_0001_0100_0100_0001 ;C | \ | / | .DW 0b_0011_0001_1001_0001 ;D f h j k b .DW 0b_0001_1110_0100_0001 ;E | \ | / | .DW 0b_0000_1110_0100_0001 ;F --g-- --l-- .DW 0b_0001_1101_0100_0001 ;G | / | \ | .DW 0b_0000_1111_0101_0000 ;H e p n m c .DW 0b_0010_0000_1000_0000 ;I | / | \ | .DW 0b_0001_0101_0001_0000 ;J -----d----- .DW 0b_1000_0110_0100_1000 ;K .DW 0b_0001_0100_0100_0000 ;L .DW 0b_0000_0101_0111_1000 ;M .DW 0b_1000_0101_0111_0000 ;N .DW 0b_0001_0101_0101_0001 ;0 .DW 0b_0000_1110_0101_0001 ;P .DW 0b_1001_0101_0101_0001 ;Q .DW 0b_1000_1110_0101_0001 ;R .DW 0b_0001_1011_0100_0001 ;S .DW 0b_0010_0000_1000_0001 ;T .DW 0b_0001_0101_0101_0000 ;U .DW 0b_1000_0001_0011_0000 ;V .DW 0b_1100_0101_0101_0000 ;W .DW 0b_1100_0000_0010_1000 ;X .DW 0b_0010_0000_0010_1000 ;Y .DW 0b_0101_0000_0000_1001 ;Z .DW 0b_0001_0100_0100_0001 ;[ .DW 0b_1000_0000_0010_0000 ;\ .DW 0b_0001_0001_0001_0001 ;] .DW 0b_0000_0000_0110_0000 ;^ .DW 0b_0001_0000_0000_0000 ;_ .DW 0b_0000_0000_0000_1000 ;' .DW 0b_1110_1010_1010_1000 ;* .DW 0b_0010_1010_1000_0000 ;+ SPACE: .DW 0B_0000_0000_0000_0000 ;(SPACE) .DW 0b_0000_1010_0000_0000 ;- .DW 0b_0100_0000_0000_0000 ;. .DW 0b_0100_0000_0000_1000 ;/ .DW 0b_1000_0000_0000_1000 ;< .DW 0b_0001_1010_0000_0000 ;= .DW 0b_0100_0000_0010_0000 ;> AN LCD SCROLLING PROGRAM In the last program we used to the LCD to tell which switch of the Butterfly joystick was depressed. The messages were six or less characters long. To display a longer message on the LCD we scroll it across the screen from right to left. First we setup a speed constant that is used in the pause/delay routine which is called inside our scrolling routine. .SET SPEED = 6 ;USED TO SET SCROLL SPEED PAUSE: DELAY: PUSH A LDI A,SPEED DLUPE: DEC R0 BRNE DLUPE DEC R1 BRNE DLUPE DEC A BRNE DLUPE POP A RET The main loop of the program simply points to our message, then calls a scroll routine in an endless loop: MAIN: LOOP: LDI YL,LOW(MESSAGE*2) ;SET A POINTER TO MESSAGE LDI YH,HIGH(MESSAGE*2) RCALL SCROLL ;SCROLL MESSAGE DONE: RJMP LOOP Our message is much longer than six characters and ends with a period “.” and it is inside quotes. We use blank spaces so the message scrolls onto and completely off the screen each time. MESSAGE: .DB " HELLO TO THE WORLD FROM INSIDE THE AVR169 BUTTERFLY USING ASSEMBLY LANGUAGE ." The scroll routine copies our pointer for the message to the Z-Pointer for the SHOWBUF routine and after it is displayed on the LCD the Y-pointer is incremented and we do this over and over until we hit the period: SCROLL: MOVW Z,Y ;MOVE FROM 1ST POINTER TO 2ND PUSH YL ;SAVE 1ST POINTER PUSH YH RCALL SHOWBUF ;DISPLAY WHAT Z POINTS AT RCALL DELAY ;WAIT POP YH ;RESTORE 1ST POINTER POP YL ADIW YH:YL,1 ;INCREMENT POINTER CPI A,'.' ;STOP AT PERIOD '.' BRNE SCROLL RET THE LCD SCROLLING PROGRAM LISTING ;------------------------------------------; ; HELLO WORLD #3 (SCROLLING) ; ; ============================= ; ; ; ; DANIEL J, DOREY AKA RETRODAN @GMAIL.COM ; ; 05-OCT-09: CREATED LAST UPDATE:04-SEP-10 ; ; ; ; SCROLLS LONG MESSAGES ACROSS LCD SCREEN ; ; MESSAGE TERMINATED WITH A PERIOD (.) ; ;------------------------------------------; .INCLUDE "M169DEF.INC" ;BUTTERFLY DEFS ;---------------------------------: ; RENAME/DEFINE WORKING REGISTERS ; ;---------------------------------; .SET SPEED = 6 ;USED TO SET SCROLL SPEED SEE PAUSE ROUTINE .SET CHR6BUF = 2 ;6 CHAR BUFFER IS [R2,R3,R4,R5,R6,R7] .DEF ZERO = R8 .DEF T1 = R11 .DEF T2 = R12 .DEF A = R16 ;R16:R31 CAN BE LOADED IMMEDIATE (LDI) .DEF AH = R17 .DEF B = R18 .DEF C = R19 .DEF D = R20 .DEF I = R21 .DEF J = R22 .DEF K = R23 .DEF N = R24 .ORG $0000 RJMP RESET ;-----------------; ; INITIALIZATIONS ; ;-----------------; RESET: CLR ZERO LDI A,HIGH(RAMEND) ;SETUP THE STACK POINTER OUT SPH,A ;AT TOP OF MEMORY AND LDI A,LOW(RAMEND) ;GROW DOWNWARDS OUT SPL,A RCALL LCD_INIT ;INITIALIZE LCD RCALL LCD_CLR ;CLEAR LCD SEGMENTS ;-----------; ; MAIN LOOP ; ;-----------; MAIN: LOOP: LDI YL,LOW(MESSAGE*2) ;SET A POINTER TO MESSAGE LDI YH,HIGH(MESSAGE*2) RCALL SCROLL ;SCROLL MESSAGE DONE: RJMP LOOP MESSAGE: .DB " HELLO TO THE WORLD FROM INSIDE THE AVR169 BUTTERFLY USING ASSEMBLY LANGUAGE ." ;---------------------; ; SCROLL MESSAGE LOOP ; ;---------------------; SCROLL: MOVW Z,Y ;MOVE FROM 1ST POINTER TO 2ND PUSH YL ;SAVE 1ST POINTER PUSH YH RCALL SHOWBUF ;DISPLAY WHAT Z POINTS AT RCALL DELAY ;WAIT POP YH ;RESTORE 1ST POINTER POP YL ADIW YH:YL,1 ;INCREMENT POINTER CPI A,'.' ;STOP AT PERIOD '.' BRNE SCROLL RET ;-------------------------------; ; COPIES TEXT TO DISPLAY BUFFER ; ; MUST LOAD (Z) FIRST ; ;-------------------------------; SHOWBUF:LPM A,Z+ MOV R7,A LPM A,Z+ MOV R6,A LPM A,Z+ MOV R5,A LPM A,Z+ MOV R4,A LPM A,Z+ MOV R3,A LPM A,Z+ MOV R2,A PUSH A RCALL DISPN POP A RET ;-----------------------------------------------; ; DISPN - DISPLAY THE NUMBER IN R7:R2 REGISTERS ; ; NOTE CHR6BUF MUST BE POINTING 6 CHAR BUFFER ; ;-----------------------------------------------; DISPN: LDI XL,LOW(CHR6BUF) ;POINTS BUFFER-6 LDI XH,HIGH(CHR6BUF) ;-------------------------; ; ENTER HERE IF XH:XL SET ; ;-------------------------; LCD_DSP: LDI N,6 ;SIX CHARS LDI B,$F0 ;BITMASK DSPNXT: LD A,X+ ;FETCH THE CHAR TO DISP CPI A,' ' ;SPACE? BRNE NOSPC ;SPACE XLATION LDI A,SPACE-LCD_TABLE NOSPC: CPI A,'a' ;CHARACTER XLATION BRLO NOSMLET ;SMALL LETTERS? SUBI A,$20 ;FOLD#1 a=>A NOSMLET: CPI A,'A' ;CAP LETTERS BRLO NOBGLET ; SUBI A,$37 ;FOLD#2 A=>10 NOBGLET: CPI A,'0' ;ASCII NUMBERS BRLO NOANUM ; SUBI A,$30 ;FOLD#3 "0"=>0 NOANUM: LSL A ;POINT Z INTO TABLE LDI ZL,LOW(LCD_TABLE*2) LDI ZH,HIGH(LCD_TABLE*2) ADD ZL,A ;OFFSET INTO ADC ZH,ZERO ;CHARACTER TABLE LDI YL,LOW(LCDDR1)-1 ;(=251)POINTS TO CLR YH ;LCD SEGMENTS MOV A,N ;USE COUNTER DEC A LSR A ;AS OFFSET TO ADD YL,A ;SEGMENTS SET LDI I,4 DISPLUP: CPI YL,LOW(LCDDR8) ;PAST CHECK POINT? BRLO NOZINC ;PAST 2ND READ? BRTC NOZINC ;SHOULD WE INCZ? ADIW ZH:ZL,1 ;INCZ AFTER 2ND READ CLT ;STOP FURTHER INCZ NOZINC: LPM A,Z ;LOAD SEGMENT DATA SBRS I,0 ;USE BIT0 SWAP A ;SWAP ON EVEN SEGS SBRC N,0 ;USE BIT0 SWAP A ;SWAP ON EVEN DIGITS POTRIP: AND A,B ;MASK NEEDED INFO COM B ;INVERT MASK LD C,Y ;READ-IN SEGMENT AND C,B ;CLEAR A SPOT OR A,C ;SHOVE-IN NEW ST Y,A ;WRITE-BACK COM B ;RE-INVERT MASK ADIW YH:YL,5 ;NEXT SEG DEC I BRNE DISPLUP ;DONE 4 SEGS? SKPNUM: COM B ;INVERT BIT-MASK DEC N ;DONE 6 DIGITS? NOINC: BRNE DSPNXT RET ;---------------------------; ; CLEAR ALL SEGMENTS ON LCD ; ;---------------------------; LCD_CLR: LDI YL,LOW(LCDDR0) CLR YH CLRLUPE: ST Y+,ZERO CPI YL,LCDDR18+1 BRNE CLRLUPE RET ;-------------------------------; ; INITIALIZE LCD DISP REGISTERS ; ;-------------------------------; LCD_INIT: PUSH A LDI A,0b1011_0111 ;SET CLOCK, DUTY CYCLE AND # PINS STS LCDCRB, A ;ENABLE ALL SEGEMENTS LDI A,0b0000_0111 ;SET FRAME RATE TO 32Hz STS LCDFRR, A ;SET PRESCALER TO 32KHz LDI A,0b0000_1110 ;SET THE CONTRAST TO 3.3 VOLTS STS LCDCCR, A ;SET THE CONTRAST LDI A,0b1100_0000 ;ENABLE LCD POWER-SAVE WAVE FROM STS LCDCRA, A ;ENABLE THE LCD POP A RET ;---------------------; ; PAUSE/DELAY ROUTINE ; ;---------------------; PAUSE: DELAY: PUSH A LDI A,SPEED DLUPE: DEC R0 BRNE DLUPE DEC R1 BRNE DLUPE DEC A BRNE DLUPE POP A RET ;-----------------------------------------------------------; ; RETRO DAN'S IMPROVED LCD CHARACTER TABLE V1.2 ; ; ; ALTERATIONS FROM ORIGINAL CHAR SET FOUND IN APP NOTES ; ; 1. CONVERTED TO BINARY FROM HEX FOR LEGIBITIY ; ; 2. REPLACED SOME CHARS ; ; 3. PLACED ALPHABET RIGHT AFTER NUMBERS EASES TABLE ; ; LOOKUPS WHEN USING HEX: 10 OVERFLOWS INTO A, 11=>B ETC ; ; 4. ZERO MOVED TO FIRST ENTRY TO EASE TABLE LOOKUPS ; ;-----------------------------------------------------------; LCD_TABLE: ; --mpndlegcjfhbk--a <------> LCD SEGMENTS .DW 0b0001010101010001 ;ZERO .DW 0b0010000010000000 ;1 .DW 0b0001111000010001 ;2 .DW 0b0001101100010001 ;3 .DW 0b0000101101010000 ;4 .DW 0b0001101101000001 ;5 .DW 0b0001111101000001 ;6 .DW 0b0000000101010001 ;7 .DW 0b0001111101010001 ;8 .DW 0b0001101101010001 ;9 .DW 0b0000111101010001 ;A .DW 0b0011100110010001 ;B -----a----- .DW 0b0001010001000001 ;C | \ | / | .DW 0b0011000110010001 ;D f h j k b .DW 0b0001111001000001 ;E | \ | / | .DW 0b0000111001000001 ;F --g-- --l-- .DW 0b0001110101000001 ;G | / | \ | .DW 0b0000111101010000 ;H e p n m c .DW 0b0010000010000000 ;I | / | \ | .DW 0b0001010100010000 ;J -----d----- .DW 0b1000011001001000 ;K .DW 0b0001010001000000 ;L .DW 0b0000010101111000 ;M .DW 0b1000010101110000 ;N .DW 0b0001010101010001 ;0 .DW 0b0000111001010001 ;P .DW 0b1001010101010001 ;Q .DW 0b1000111001010001 ;R .DW 0b0001101101000001 ;S .DW 0b0010000010000001 ;T .DW 0b0001010101010000 ;U .DW 0b1000000100110000 ;V .DW 0b1100010101010000 ;W .DW 0b1100000000101000 ;X .DW 0b0010000000101000 ;Y .DW 0b0101000000001001 ;Z .DW 0b0001010001000001 ;[ .DW 0b1000000000100000 ;\ .DW 0b0001000100010001 ;] .DW 0b0000000001100000 ;^ .DW 0b0001000000000000 ;_ .DW 0b0000000000001000 ;' .DW 0b1110101010101000 ;* .DW 0b0010101010000000 ;+ SPACE:.DW 0 ;(SPACE) ; .DW 0b0000101000000000 ;- .DW 0b0100000000000000 ;. .DW 0b0100000000001000 ;/ .DW 0b1000000000001000 ;< .DW 0b0001101000000000 ;= .DW 0b0100000000100000 ;>