| QUOTE ("SteveN") |
I have post below the applicable parts of my code that deals with the LCD display. I will attempt to explain why and how I do certain things certain ways. I cannot explain everything though because, to be honest, I have forgotten why certain things have to be done a certain way (or at least why I thought they had to be done that way). Such things as why do I have routines to determine whether the digit is odd or even, or why I swap nibbles in a byte. I used application notes AVR064 and AVR065 and the STK502 User Manual extensively when I was learning how to deal with the Butterfly LCD. You can find the application notes on Atmels web site but the STK502 User Manual is no longer there (I think). I do believe someone pointed out that the STK502 User Manual could be found on this site. I found it on an Atmel CD that came with one of my "toys" I bought from Atmel. Anyway, the code is cut and pasted from an application I did so it may seem chopped up. I hope I get everything. |
| CODE |
; ***************************************************************************** ; ***************************************************************************** ; ; REGISTER DEFINITIONS - START ; .def TC_cntL = r0 ; Total Cycles count, low byte .def TC_cntH = r1 ; Total Cycles count, high byte ; ; Note: The LCD display used on the Butterfly has 7 alphanumeric digits ; available (+ special symbols), BUT, the mega169 does not have enough ; I/O available to drive all those segments. Thus, the Butterfly only ; uses 6 of the digits. Reading from left to right on the display, the ; first digit is not used...that is why I start with digit2 below. ; .def digit2 = r2 ; = 100,000's digit on Butterfly display .def digit3 = r3 ; = 10,000's digit on Butterfly display .def digit4 = r4 ; = 1,000's digit on Butterfly display .def digit5 = r5 ; = 100's digit on Butterfly display .def digit6 = r6 ; = 10's digit on Butterfly display .def digit7 = r7 ; = 1's digit on Butterfly display .def FC_cntL = r8 ; Failed Cycles count, low byte .def FC_cntH = r9 ; Failed Cycles count, high byte .def Delay_amt1 = r10 .def Delay_amt2 = r11 .def Byte_cnt = r12 .def Bit_pntr = r13 .def Rotate_cnt = r14 .def PC_INT1_cnt = r15 .def temp1L = r16 .def temp1H = r17 .def temp2L = r18 .def temp2H = r19 ; .def T0OF_cnt = r20 ; Timer0 overflow count .def T2OF_cnt = r21 ; Timer2 overflow count ; .def RangeE = r22 ; Range Exceeded ; ; ----------------------------------------------------------------------------- .def Flags = r23 ; --- bit definitions for Flags register --- ; ; bit0 = One second passed yet (OS) 1=Yes 0=No ; bit1 = Pass/Fail status (PF) 1=Fail 0=Pass ; bit2 = Even/Odd digit (EOD) 1=Odd digit, 0=Even digit ; bit3 = First time PE4 ISR (FTPE4) 1=>than first 0=first time through ; bit4 = Out of range (OOR) 1=out of range 0=within range ; bit5 = TBA ; bit6 = TBA ; bit7 = TBA ; ----------------------------------------------------------------------------- ; .def wtempL = r24 ; register pair to be used with adiw, sbiw .def wtempH = r25 ; (for example to find when a 16 bit value ; equals zero) ; ; ; REGISTER DEFINITIONS - END ; ; ***************************************************************************** ; ***************************************************************************** Code: ; ***************************************************************************** ; ***************************************************************************** ; ; EQUATES - START ; .equ OS_bn = 0 ; One second flag bit number .equ OS_msk = (1<<0) ; One second flag bit mask (0b00000001) ; .equ PF_bn = 1 ; Pass/Fail flag bit number .equ PF_msk = (1<<1) ; Pass/Fail flag bit mask (0b00000010) ; .equ EOD_bn = 2 ; Even/Odd Digit flag bit number .equ EOD_msk = (1<<2) ; Even/Odd Digit flag bit mask (0b00000100) ; .equ FTPE4_bn = 3 ; First time PE4 ISR status bit number .equ FTPE4_msk = (1<<3) ; First time PE4 ISR status bit mask ; .equ OOR_bn = 4 ; Timer0 value Out Of Range flag bit number .equ OOR_msk = (1<<4) ; Timer0 value Out Of Range flag bit mask ; ;.equ TBA = 5 ; ;.equ TBA = (1<<5) ; ; ;.equ TBA = 6 ; ;.equ TBA = (1<<6) ; ; .equ PC_INT1_bn = 7 ; .equ PC_INT1_msk = (1<<7) ; ; ; ; EQUATES - END ; ; ***************************************************************************** ; ***************************************************************************** ; ; ***************************************************************************** ; ***************************************************************************** ; ; Internal SRAM assignments - Start ; .dseg ; .org 0x0100 ; start of internal SRAM and an even address LCDDRx_ptr: .byte 2 ; temp save of LCDDRx pointer (low byte=YL) LCD_ptr: .byte 2 ; temp save of LCD data pointer (low byte=ZL) X_save: .byte 2 ; save XH:XL pointer register value here Cycle: .byte 90 ; 90 x 8 = 720 possible Cycles - each bit of a ; byte represents a completed Cycle. A "0" in ; a bit represents a "Passed" Cycle, a "1" in ; a bit represents a "Failed" Cycle ; ; Note: A cycle consists of (approximately) a 1 minute On and a 1 minute Off ; period. Therefore it is possible to measure approximately 30 cycles ; per hour. Over a 12 hour period the total number of cycles is ; approximately 12 * 30 = 360. The above 720 possible cycles would ; provide room for a 24 hour burn-in period. ; ; Internal SRAM assignments - End ; ; ***************************************************************************** ; ***************************************************************************** Code: ; ----------------------------------------------------------------------------- ; ; LCD Initialization - Start ; ; Use external 8MHz clock oscillator ; Setup for 1/3 Bias (LCD2B = 0) ; Setup for 1/4 duty and enable all COM (LCDMUX1,LCDMUX0 = 0b11) pins ; Enable all segment (LCDPM2,LCDPM1,LCDPM0 = 0b111) pins ; ldi temp1L, (0<<LCDCS) | (0<<LCD2B) | (3<<LCDMUX0) | (7<<LCDPM0) sts LCDCRB, temp1L ; ; Dividing LCD clock (8.00MHz) by 4096 (LCDPS2,LCDPS1,LCDPS0 = 0b111) ; and dividing output from prescaler by 8 (LCDCD2,LCDCD1,LCDCD0 = 0b111) ; gives a frame rate of 30.5 Hz ; ldi temp1L, (7<<LCDPS0) | (7<<LCDCD0) sts LCDFRR, temp1L ; ; Set LCD Contrast Control output voltage to 3.0 V ; ldi temp1L, (1<<LCDCC3) sts LCDCCR, temp1L ; ; Enable LCD ; ldi temp1L, (1<<LCDEN) sts LCDCRA, temp1L ; setup to display "WAIT04" on the LCD display ; "04" is the version of this code ldi temp1L,0x21 ; W mov digit2,temp1L ldi temp1L,0x0B ; A mov digit3,temp1L ldi temp1L,0x13 ; I mov digit4,temp1L ldi temp1L,0x1E ; T mov digit5,temp1L ldi temp1L,0x00 ; 0 mov digit6,temp1L ldi temp1L,0x04 ; 4 mov digit7,temp1L call LCD_Display ; ; LCD Initialization - End ; Code: ; ----------------------------------------------------------------------------- ; ----------------------------------------------------------------------------- ; ; LCD_Display SUBROUTINE - START ; ; The purpose of this software is to take BCD coded values stored in r2-r7 and ; display them on the Butterfly LCD display. The BCD coded values are stored in ; registers R2 - R7. The first value stored is labeled ; digit2 and represents the 100,000's digit of a number (my end application ; for this routine will not actually use this digit...it is included here for ; possible future use). ; ; There are 6 registers reserved for these BCD values because there are 6 ; digits on the Butterfly display (actually there are 7 digits available on the ; Butterfly display but because of limitations of the mega169 only 6 are ; available for use). Please see the "REGISTER DEFINITIONS" section of this ; code for further naming conventions for these SRAM locations. ; ; The digits on the Butterfly LCD display are numbered 2 - 7 going from left to ; right (the unused digit is the far left #1 digit). The code is, obviously, ; written to place the 100,000's digit in the far left LCD digit, the 10'000's ; digit in the next LCD digit to the right and so forth until the 1's digit is ; written to the far right LCD digit. ; ; It is up to the routine calling this routine to place the BCD values into the ; (correct) registers before calling this routine. ; ; I have utilized pointers (X, Y, and Z) so that I can use looping code instead ; of straight line code. Please see the "Pointer Initialization" section of this ; routine to see how I have assigned the pointers. ; ; I have utilized a look-up table located in Flash program memory as a means of ; converting the BCD values (stored in SRAM) into values that the mega169 LCD ; controller can use to activate the appropriate segments. ; ; ***************************************************************************** ; ***************************************************************************** ; ; Statistics ; ; Execution time = 626 cycles (78.25uS @ 8MHz clock) ; Registers used = 5 ; SRAM used = 9 ; ; ; ***************************************************************************** ; ***************************************************************************** ; ; ; save registers used in this routine LCD_Display: push XL push XH push YL push YH push ZL push ZH push wtempL push wtempH push temp2L push temp2H push temp1L push temp1H ldi temp1L,0x00 ; clear LCDDRx registers of previous data ldi YL,LCDDR0 ; Y points to LCDDRx registers clr YH Clear_LCDDRx: st Y+,temp1L cpi YL,LCDDR18+1 brne Clear_LCDDRx ; suppress leading zeroes if present and/or required in number to be displayed ; in 100's, 10's and 1's digits (r5, r6 and r7) ldi XL,0x05 ; X points to 100's (r5) digit clr XH ; X will never be greater than 255 ; therefore XH will always be zero Leading_zero: ld temp1L,X ; temp1L = contents @ X (r5, r6 or r7) tst temp1L ; is it = 0 ? brne Pointer_init ; if not, go re-initialize pointer to r2 ; (r2 = leftmost digit of LCD) ldi temp1L,0x0A ; otherwise save 0x0A offset (0x0A points to ; value saved in the lookup table for a blank ; digit) st X,temp1L ; into location pointed to by X adiw XH:XL,0x01 ; point to next digit in SRAM cpi XL,0x08 ; compare X with address of r7 + 1 breq Pointer_init ; if 2 values compare r7 has been checked rjmp Leading_zero ; if not, go check r6 or r7 ; X will never be greater than 255 ; therefore XH will always be zero ; end of leading zero suppression checking ; setting up the pointer registers Pointer_init: ldi XL,0x02 ; X points to r2 (left most digit) clr XH ldi YL,LCDDR0 ; Y points to LCDDRx registers clr YH ldi ZL,low(2*LCD_data) ; Z points to LCD data lookup table ldi ZH,high(2*LCD_data) Save_seeds: sts LCDDRx_ptr,YL ; save Y pointer sts LCD_ptr,ZL ; save Z pointer sts LCD_ptr+1,ZH sbrc XL,0 ; if pointer = even digit - clear that flag bit rjmp Set_Odd ; otherwise go set that flag bit cbr Flags,EOD_msk ; clear Odd/Even Flags bit to indicate Even dig. rjmp Load_LCDDRx1 Set_Odd: sbr Flags,EOD_msk ; set Odd/Even Flags bit to indicate Odd digit ; Load_LCDDRx1: ldi wtempL,0x02 ; setup counter ld temp1L,X+ ; load digit value (X=BCD digit pointer) lsl temp1L ; multiply digit value by 2 add ZL,temp1L ; add offset to Z (Z=Lookup table pointer) clr temp1L adc ZH,temp1L Load_LCDDRx2: ; X points to low byte first time thru lpm temp1L,Z+ ; X points to high byte second time thru mov temp1H,temp1L ; copy temp1L into temp1H sbrc Flags,EOD_bn ; if Odd/Even bit = even (0) then go do Even rjmp Odd ; otherwise do Odd Even: swap temp1H ; swap nibbles for temp1H for even digits andi temp1H,0x0F ; clear high nibble andi temp1L,0x0F ; clear high nibble rjmp Continue_load Odd: swap temp1L ; swap nibbles for temp1L for odd digits andi temp1L,0xF0 ; clear low nibble andi temp1H,0xF0 ; clear low nibble Continue_load: ld temp2L,Y ; save whatever was in nibble that is not being or temp1L,temp2L ; written this time st Y,temp1L ; LCDDRx = low nibble of byte of LCD data code adiw YH:YL,0x05 ; go to next LCDDRx for this digit ld temp2L,Y ; save whatever was in nibble that is not being or temp1H,temp2L ; written this time st Y,temp1H ; LCDDRx = hi nibble of byte of LCD data code dec wtempL ; have we written all four registers ? breq Reload_seeds ; if so go reload seeds and get next digit adiw YH:YL,0x05 ; if not, go to next LCDDRx for this digit rjmp Load_LCDDRx2 Reload_seeds: cpi XL,0x08 ; have we displayed all locations? breq LCD_Display_ret ; if we have return to Main loop routine ; otherwise continue here lds YL,LCDDRx_ptr ; reload Y pointer seed (Y=LCDDRx pointer) sbrc Flags,EOD_bn ; if Odd/Even bit = Even (0) then don't inc YL inc YL ; Odd/Even bit = Odd, point to next higher ; LCDDRx lds ZL,LCD_ptr ; reload Z pointer seed lds ZH,LCD_ptr+1 rjmp Save_seeds ; display next digit LCD_Display_ret: cbr Flags,EOD_msk ; clear Even/Odd Flags bit pop temp1H pop temp1L pop temp2H pop temp2L pop wtempH pop wtempL pop ZH pop ZL pop YH pop YL pop XH pop XL ret ; ; ; ; LCD_Display SUBROUTINE - END ; ; ----------------------------------------------------------------------------- Code: ; ***************************************************************************** ; ***************************************************************************** ; ; ; LOOK-UP TABLES/FLASH CONSTANTS - START ; LCD_data: .dw 0x5559 ; (0x00) zero .dw 0x0118 ; (0x01) one .dw 0x1e11 ; (0x02) two .dw 0x1b11 ; (0x03) three .dw 0x0b50 ; (0x04) four .dw 0x1b41 ; (0x05) five .dw 0x1f41 ; (0x06) six .dw 0x0111 ; (0x07) seven .dw 0x1f51 ; (0x08) eight .dw 0x1b51 ; (0x09) nine .dw 0x0000 ; (0x0A) blank display, for leading zero suppression .dw 0x0f51 ; (0x0B)'A' .dw 0x3991 ; (0x0C)'B' .dw 0x1441 ; (0x0D)'C' .dw 0x3191 ; (0x0E)'D' .dw 0x1e41 ; (0x0F)'E' .dw 0x0e41 ; (0x10)'F' .dw 0x1d41 ; (0x11)'G' .dw 0x0f50 ; (0x12)'H' .dw 0x2080 ; (0x13)'I' .dw 0x1510 ; (0x14)'J' .dw 0x8648 ; (0x15)'K' .dw 0x1440 ; (0x16)'L' .dw 0x0578 ; (0x17)'M' .dw 0x8570 ; (0x18)'N' .dw 0x1551 ; (0x19)'O' .dw 0x0e51 ; (0x1A)'P' .dw 0x9551 ; (0x1B)'Q' .dw 0x8e51 ; (0x1C)'R' .dw 0x9021 ; (0x1D)'S' .dw 0x2081 ; (0x1E)'T' .dw 0x1550 ; (0x1F)'U' .dw 0x4448 ; (0x20)'V' .dw 0xc550 ; (0x21)'W' .dw 0xc028 ; (0x22)'X' .dw 0x2028 ; (0x23)'Y' .dw 0x5009 ; (0x24)'Z' ; |
| CODE |
; =============================================================== ; ; File........: lcd_driverCO.asm ; ; Author(s)...: Christian Gaida ; ; Target(s)...: ATmega169 (Butterfly) ; ; Compiler....: AVRASM ; ; Description.: LCD-Routine for the AVR-Butterfly ; codesize optimized ; ; Revision....: 2.0 ; ; YYYYMMDD - VER. - COMMENT - SIGN. ; ; 20050521 - 0.1 - created - CG ; 20051105 - 0.2 - - CG ; 20060729 - 1.0 - add some commentars - CG ; 20060730 - 1.1 - codesize optimized version - CG ; =============================================================== ; ========================================================= ; to optimize speed put all subroutines together to avoid ; subroutine calls and returns ; but i usually prefer one routine for one thing ; ========================================================= ; ========================================================= ; Function name : lcdInit ; Returns : - ; Parameters : - ; Purpose : Initialize LCD_displayData buffer. ; Set up the LCD (timing, contrast, etc.) ; ========================================================= lcdInit: ldi r16, (1<<LCDCS) | (3<<LCDMUX0)| (7<<LCDPM0) sts LCDCRB, r16 ; Set LCD prescaler to give a framerate of 32,0 Hz ldi r16, (0<<LCDPS0) | (7<<LCDCD0) sts LCDFRR, r16 ; Set segment drive time and output voltage ldi r16, (1<<LCDDC1) | (1<<LCDCC3) | (1<<LCDCC2) | (1<<LCDCC1) sts LCDCCR, r16 ; Enable LCD, default waveform and interrupt enabled ldi r16, (1<<LCDEN) | (1<<LCDAB) | (1<<LCDIE) sts LCDCRA, r16 ret ; ========================================================= ; Function name : lcd_out ; Returns : - ; Parameters : - ; Purpose : Call the subroutines getAddr and write_char ; ========================================================= lcd_out: ; to optimize speed: rcall getAddr ; don't use rcall write_char ; this subroutine ret ; ========================================================= ; Function name : getAddr ; Returns : Adress of the char (stored in the ; Z-Register) ; Parameters : char (r21), r16 ; Purpose : Set Pointer to the char ; ========================================================= getAddr: ldi ZL, LOW(data*2) ;ZL -> r30 ldi ZH, HIGH(data*2) ;ZH -> r31 ;Adresse der Daten in Z-Register subi char, 0x30 ;('0'=0x30) ldi r16, 4 mul r16, char add ZL, r0 ; brcc skip ;Überlauf in ZL(r30)? (Carry-Flag in SREG gesetzt) inc ZH ;wenn ja erhoehe ZH(r31) um 1 skip: ret ; ========================================================= ; Function name : write_char ; Returns : - ; Parameters : X, Z, r16, r17 ; Purpose : write char on the display ; ========================================================= write_char: ;Erste Position im Display ldi nibble, 0b11110000 rcall setDigit loopy: lpm r16, Z+ ;lade nibble aus Z-Register und auto-increment Z ld r17, X ;Inhalt von LCDDRx aus SRAM laden and r17, nibble ;und unteres (od. oberes) nibble auf 0 setze sbrc digit, 0 ;Stelle 1,3 oder 5, swap r16 ;dann vertausche die nibbles or r16, r17 ;Beide nibble zusammenführen st X, r16 ;store to sram LCDDRx -> Ausgabe adiw XL, 5 ; XL +5 -> LCDDR5, LCDDR10, etc. cpi XH, 1 ; last segment? brne loopy ; no? then go on ret ; ========================================================= ; Function name : setDigit ; Returns : XH, XL ; Parameters : digit(r22) ; Purpose : set digit (0-5) on the display ; (0=first digit from left) ; ========================================================= setDigit: ldi XL, LOW(LCDDR0) ldi XH, HIGH(LCDDR0) cpi digit, 2 brlo digit0 cpi digit, 4 brlo digit2 adiw XL, 2 ldi r18, 4 cpse digit, r18 swap nibble ret digit2: adiw XL, 1 ldi r18, 2 cpse digit, r18 swap nibble ret digit0: ldi r18, 0 cpse digit, r18 swap nibble ret data: .db 0x9, 0x5, 0x5, 0x5, 0x8, 0x1, 0x1, 0x0 ;Char '0', '1' .db 0x1, 0x1, 0xE, 0x1, 0x1, 0x1, 0xB, 0x1 ; '2', '3' .db 0x0, 0x5, 0xB, 0x0, 0x1, 0x4, 0xB, 0x1 ; '4', '5' .db 0x1, 0x4, 0xF, 0x1, 0x1, 0x1, 0x1, 0x0 ; '6', '7' .db 0x1, 0x5, 0xF, 0x1, 0x1, 0x5, 0xB, 0x1 ; '8', '9' .db 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ; dummy for .db 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ; 0x3a .db 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ; to .db 0x0, 0x0, 0x0, 0x0 ; 0x4 .db 0x1, 0x5, 0xF, 0x0, 0x1, 0x9, 0x9, 0x3 ;Char 'A', 'B' .db 0x1, 0x4, 0x4, 0x1, 0x1, 0x9, 0x1, 0x3 ; 'C', 'D' .db 0x1, 0x4, 0xe, 0x1, 0x1, 0x4, 0xe, 0x0 ; 'E', 'F' .db 0x1, 0x4, 0xd, 0x1, 0x0, 0x5, 0xf, 0x0 ; 'G', 'H' .db 0x0, 0x8, 0x0, 0x2, 0x0, 0x1, 0x5, 0x1 ; 'I', 'J' .db 0x8, 0x4, 0x6, 0x8, 0x0, 0x4, 0x4, 0x1 ; 'K', 'L' .db 0x8, 0x7, 0x5, 0x0, 0x0, 0x7, 0x5, 0x8 ; 'M', 'N' .db 0x1, 0x5, 0x5, 0x1, 0x1, 0x5, 0xe, 0x0 ; 'O', 'P' .db 0x1, 0x5, 0x5, 0x9, 0x1, 0x5, 0xe, 0x8 ; 'Q', 'R' .db 0x1, 0x2, 0x0, 0x9, 0x1, 0x8, 0x0, 0x2 ; 'S', 'T' .db 0x0, 0x5, 0x5, 0x1, 0x8, 0x4, 0x4, 0x4 ; 'U', 'V' .db 0x0, 0x5, 0x5, 0xc, 0x8, 0x2, 0x0, 0xc ; 'W', 'X' .db 0x8, 0x2, 0x0, 0x2, 0x9, 0x0, 0x0, 0x5 ; 'Y', 'Z' |
| CODE |
.include "m169def.inc" .def char = r21 ; the character to be displayed .def digit = r22 ; digit 0-5 (from left to right) .def nibble = r23 .org 0x00 .cseg reset: ldi r16, LOW(RAMEND) out spl, r16 ldi r16, HIGH(RAMEND) out sph, r16 main: rcall lcdInit ldi char, 'H' ldi digit, 0 rcall lcd_out ldi char, 'E' inc digit rcall lcd_out ldi char, 'L' inc digit rcall lcd_out ldi char, 'L' inc digit rcall lcd_out ldi char, 'O' inc digit rcall lcd_out ldi char, 'U' inc digit rcall lcd_out infinite_loop: rjmp infinite_loop .include "lcd_driverCO.asm" |