; *** ; HD44780 LCD Driver for ATmega2560. ; (DFRobot LCD Keypad Shield v1.1, Arduino Mega2560) ; ; Title: LCD.asm ; Description: LCD Configuration and Subroutines ; ; LCD pin and port assignment, as well as setting things like LCD size in ; columns and rows, and the collection of HD44780 display modes and settings, ; can be done either here in the header #defines, or in the header of the ; program including the driver. In order for the program to assemble, the ; following symbols must be defined, as well as their listed children: ; ; LCD_SIZE ; LCD_ROW (n = 1, 2, 4) ; LCD_COLUMN (n = 8, 12, 16, 20, 24, 40) ; LCD_DISPLAY_SET ; LCD_SHOW_CURSOR (n = 0, 1) ; LCD_BLINK_CURSOR (n = 0, 1) ; LCD_DISPLAY_ON (n = 0, 1) ; LCD_INTERFACE_SET ; LCD_INTERFACE_WIDTH (n = 0, 1) ; LCD_LINE_COUNT (n = 0, 1) ; LCD_FONT_SIZE (n = 0, 1) ; LCD_CURSOR SET ; LCD_CURSOR_DIR (n = 0, 1) ; LCD_DISPLAY_SHIFT (n = 0, 1) ; LCD_SHIFT_SET ; LCD_MOVE_SHIFT (n = 0, 1) ; LCD_SHIFT_DIR (n = 0, 1) ; LCD_INTERFACE_PORTS ; LCD_PORT_D4 All LCD ports must be set using .equ ; LCD_PORT_D5 rather than #define, and must be the ; LCD_PORT_D6 memory address of an I/O port. ; LCD_PORT_D7 This uses values from your Atmel partdef ; LCD_PORT_RS include, and so I/O space offset does not ; LCD_PORT_ENA need to be applied. Use of .equ is for ; compatibility with Atmel's partdefs. Use ; the defaults provided here as an example. ; LCD_PIN_D4 ; LCD_PIN_D5 ; LCD_PIN_D6 Pins are bit numbers within a port, ; LCD_PIN_D7 (0 <= n <= 7) ; LCD_PIN_RS ; LCD_PIN_ENA ; ; ; If no partdef file has been included yet, include the partdef for ; the ATmega2560. .ifndef SIGNATURE_000 .include "m2560def.inc" .endif ; Define the LCD size in rows x columns. Constants are conditionally ; determined based on this, and should be compatible with any HD44780 ; controlled display. #ifndef LCD_SIZE #define LCD_SIZE #message "LCD_SIZE not set, using default values." #define LCD_ROW 2 #define LCD_COLUMN 16 #endif #ifndef LCD_DISPLAY_SET #define LCD_DISPLAY_SET #message "LCD_DISPLAY_SET not set, using default values." #define LCD_SHOW_CURSOR 0 ; Show cursor = 1, Hide cursor = 0 #define LCD_BLINK_CURSOR 0 ; Blink on = 1, off = 0 #define LCD_DISPLAY_ON 1 ; Entire Display On = 1, Off = 0 #endif #ifndef LCD_CURSOR_SET #define LCD_CURSOR_SET #message "LCD_CURSOR_SET not set, using default values." #define LCD_CURSOR_DIR 1 ; Increment = 1, Decrement = 0 #define LCD_DISPLAY_SHIFT 0 ; Display shift on = 1, off = 0 #endif #ifndef LCD_INTERFACE_SET #define LCD_INTERFACE_SET #message "LCD_INTERFACE_SET not set, using default values." #define LCD_INTERFACE_WIDTH 0 ; 4-bit = 0, 8-bit = 1 #define LCD_LINE_COUNT 1 ; 1 line = 0, 2 lines = 1 #define LCD_FONT_SIZE 0 ; 5x8 dot = 0, 5x10 dot = 1 #endif #ifndef LCD_SHIFT_SET #define LCD_SHIFT_SET #message "LCD_SHIFT_SET not set, using default values." #define LCD_MOVE_SHIFT 0 ; Move cursor = 0, Shift display = 1 #define LCD_SHIFT_DIR 0 ; Shift left = 0, Shift right = 1 #endif ; *** ; LCD Pin Definitions. ; Changing these should affect lcd_init, lcd_nbl, lcd_byte, and lcd_putchar ; These default assignments are for the Arduino Mega2560 with DFRobot LCD ; Keypad Shield. Symbols used must be defined in the Atmel AVR partdef file ; used, or you're going to have a bad time. .ifndef LCD_INTERFACE_PORTS .set LCD_INTERFACE_PORTS = 1 .message "LCD_INTERFACE_PORTS not set, using default values." .equ LCD_PORT_D4 = PORTG .equ LCD_PORT_D5 = PORTE .equ LCD_PORT_D6 = PORTH .equ LCD_PORT_D7 = PORTH .equ PIN_D4 = 5 .equ PIN_D5 = 3 .equ PIN_D6 = 3 .equ PIN_D7 = 4 .equ LCD_PORT_ENA = PORTH .equ LCD_PORT_RS = PORTH .equ PIN_ENA = 6 .equ PIN_RS = 5 .endif ; *** ; End of LCD Pin Definitions ; *** ; Include LCD Driver Constants and Conditionals. .include "LCDdefs.inc" ; *** ; *** ; Code Segment. .cseg ; ** ; Default Program Initialization/Setup ; (Not included if LCD_LIBONLY is defined.) #ifndef LCD_LIBONLY call lcd_init ; call lcd_init to Initialize the LCD ldi TEMP, high(str) ; Push the data memory address push TEMP ; of str to the stack ldi TEMP, low(str) push TEMP ldi TEMP, high(init<<1) ; Push the address of init, shifted for push TEMP ; program memory access, to the stack ldi TEMP, low(init<<1) push TEMP call str_init ; Call str_init to initialize data memory address pop TEMP ; str with the contents of program memory segment pop TEMP ; init pop TEMP pop TEMP ldi TEMP, high(str) push TEMP ldi TEMP, low(str) push TEMP call lcd_puts ; Call lcd_puts to output the initialized string ; to the LCD. For demonstration purposes only. ; May be commented without impacting LCD functionality. pop TEMP pop TEMP #endif ; ** ; End of Default Program Initialization/Setup ; ** ; Default Main Program Loop ; (Not included if LCD_LIBONLY is defined.) #ifndef LCD_LIBONLY mainloop: jmp mainloop #endif ; ** ; End of Main Program Loop #ifndef LCD_LIBONLY subroutinedefinitions: jmp subroutinedefinitions ; Just in case. #else jmp lcd_after #endif ; *** *** ; LCD Controller Subroutine Definitions ; ; * LCD Subroutines * ; lcd_nbl - Take byte from stack. Send high nibble to LCD. Return byte. ; lcd_byte - Take byte from stack. Push to lcd_nbl. Swap nibbles of byte, push to stack. ; call lcd_nbl ; lcd_cmd - Take byte from stack. Set RS pin to 0 (command). Push byte to LCD ; through lcd_byte. ; lcd_putchar - Take byte from stack. Set RS to 1 (write). Push byte to lcd_byte. ; Increment cursor_xy. ; lcd_puts - Take two-byte address of string from stack. Set X pointer to address. ; Push (X) to stack. Call lcd_putchar. ; lcd_gotoxy - Take byte from stack. Byte takes form YYYYXXXX. Update cursor_xy to byte. ; High nibble is row value, low nibble is column. Use LCD definitions to calculate ; memory address for location on display. Push address to stack. Call lcd_cmd. ; Update cursor_xy to byte. ; lcd_clr - Push LCD_CMD_CLR to the stack, call lcd_cmd. Clears display, returns cursor to 0,0 ; ; ; * Delay Subroutines * ; dly_us - Busy-wait delay loop for ~(DREG) microseconds. (0 <= (DREG) <= 255) ; dly_ms - Busy-wait delay loop for ~(DREG) milliseconds (0 <= (DREG) <= 15) ; ; Both delay subroutines currently assume a CPU frequency of 16 MHz. ; ; ; * String Subroutines * ; str_init - Take two-byte pointer for string constant location in program memory, take ; two-byte pointer for target location in data memory. Copy each byte from ; program memory to data memory until a null character is found. ; * * ; ** ; lcd_nbl : Send high nibble of CREG to LCD. Pulses clock. ; ; Registers: CREG - Data byte of which to send high nibble to LCD. ; TEMP - Temporary working register ; DREG - Passed to dly_us. ; Memory: None. ; Stack: None. ; Returns: Nothing. ; lcd_nbl: push TEMP push CREG push DREG lds TEMP, PINS_D4 cbr TEMP, (1<= 15ms to power up. (Conservatively.) call dly_ms ldi DREG, 0x5 call dly_ms ldi CREG, LCD_CMD_INI ; send the first half of 0x30 (8-bit mode) three times call lcd_nbl ldi DREG, 0x5 ; wait 5ms before sending the second set command call dly_ms ldi CREG, LCD_CMD_INI call lcd_nbl ldi R21, 0x7 ; wait 15ms (max for dly_ms) 7 times is ~100ms dly_init: ldi DREG, 0xF ; wait 100ms before sending the last one call dly_ms dec R21 ; dec temp counter (not used in dly_ms) brne dly_init ; if 0, send the nibble again ldi CREG, LCD_CMD_INI call lcd_nbl ldi DREG, LCD_DAT ; wait LCD_DATus before sending more commands call dly_us ldi CREG, LCD_CMD_FNC ; load 4-bit mode command into CREG call lcd_nbl ldi DREG, LCD_DAT call dly_us ldi TEMP, LCD_CMD_FUNCTION_SET ; 4-bit, 2-line, 5x8 dot push TEMP call lcd_cmd pop TEMP ldi TEMP, LCD_CMD_DSP ; Display Off, Cursor Off, Blink Off push TEMP call lcd_cmd pop TEMP ldi TEMP, LCD_CMD_CLR ; Display Clear push TEMP call lcd_cmd pop TEMP ldi DREG, LCD_CMD_HOM call dly_ms ldi TEMP, LCD_CMD_ENTRY_MODE ; Increment cursor, no Display Shift push TEMP call lcd_cmd pop TEMP ldi TEMP, LCD_CMD_DISPLAY_MODE ; Display On, Cursor On, Blink On push TEMP call lcd_cmd pop TEMP clr TEMP sts cursor_row, TEMP ; Update cursor position to (0,0) sts cursor_col, TEMP pop DREG pop CREG pop TEMP ret ; ** ; ** ; dly_us : Busy-Wait loop for about DREG microseconds. ; (0 < DREG <= 255) ; Regrettably assumes a CPU speed of 16 MHz. ; This should be abstracted to use the symbol ; FCPU to calculate a 1us loop for the CPU ; speed of the given processor. ; ; Registers: DREG - Input. Used as counter. MODIFIED. ; TEMP - Counter. MODIFIED. ; Memory: Nope. ; Stack: Nah. ; Returns: Nothing. dly_us: push TEMP push DREG dlyus_dreg: ldi TEMP, 0x05 dlyus_in: dec TEMP brne dlyus_in dec DREG brne dlyus_dreg pop DREG pop TEMP ret ; ** ; End of dly_us ; ** ; dly_ms: Busy-wait loop for about DREG milliseconds. ; Hackily adapted from the delay_ms function ; in the AVR C libraries. Regrettably assumes a ; CPU speed of 16 MHz. ; ; Registers : DREG - Input. Number of ms to wait. MODIFIED. ; YH:YL - 16-bit counter. MODIFIED. ; TEMP - Temporary value. MODIFIED. ; Memory: None. ; Stack: None. ; Returns: Nothing. dly_ms: push TEMP push TEMP2 push DREG push YH push YL ; 1ms = FCPU / 1000 instructions ; This loop is 4 instructions per iteration. ; ldi TEMP, 0xFD mul DREG, TEMP mov TEMP, R1 swap TEMP andi TEMP, 0xF0 mov YH, TEMP mov TEMP, R0 swap TEMP mov TEMP2, TEMP andi TEMP, 0xF0 andi TEMP2, 0x0F mov YL, TEMP or YH, TEMP2 dlyms: sbiw YH:YL, 1 brne dlyms pop YL pop YH pop DREG pop TEMP2 pop TEMP ret ; ** ; End dlyms ; ** ; str_init: Takes a pointer to an initialized constant in program memory, ; and a pointer to a location in data memory. Iterates over the ; segment of program memory and loads each byte into the corresponding ; byte of data memory until a null character is found. You should probably ; make sure of two things: ; 1) That the string in program memory is explicitly null terminated, ; otherwise you can have fun with data memory full of instructions. ; 2) That you've reserved enough memory in data space to fit the string ; you initialized in program memory, otherwise you're going to have a ; super-great time trying to figure out why your string keeps getting ; mangled. ; This subroutine does automatically toss a null character on the end of the ; string being initialized. str_init: .set PARAM_OFFSET = 7 push TEMP push ZH push ZL push XH push XL push YH push YL in YH, SPH in YL, SPL ldd ZL, Y+1+(SP_OFFSET+7) ldd ZH, Y+1+(SP_OFFSET+7)+1 ldd XL, Y+1+(SP_OFFSET+7)+2 ldd XH, Y+1+(SP_OFFSET+7)+3 initloop: lpm TEMP, Z+ cpi TEMP, 0x00 st X+, TEMP brne initloop pop YL pop YH pop XL pop XH pop ZL pop ZH pop TEMP ret ; ** ; End of str_init ; *** *** ; End of Subroutine Definitions ; *** ; Program Memory Allocation ; Initialization value for default string. (Not included if LCD_LIBONLY defined.) #ifndef LCD_LIBONLY init: .db "Hello, World!", '\0' #endif ; *** ; End of Program Memory Allocation #ifdef LCD_LIBONLY lcd_after: #endif ; *** ; Data Memory Allocation .dseg ; Data memory reserved for default string. (Not allocated if LCD_LIBONLY defined.) #ifndef LCD_LIBONLY str: .byte lcd_length ; #endif ; Data memory allocated for current LCD cursor position. cursor_row: .byte 1 cursor_col: .byte 1 ; *** ; End of Data Memory Allocation