; Dick Cappels' project pages projects@cappels.org 
; Note: Typo in MoreToCopy routine that prevents assembly in later versions of the 
; assembler which was discovered by Allun Bennett was corrected, but not tested by me.
; You should be able to copy and paste this text into an assembler file.
; No liability for use of this code is assumed. See full disclaimer on web site at 
; www.projects.cappels.org.
; Please email comments or corrections to projectsxcappels.org where x is the @ sign. 
; (the above is to mask the address from email address gathering robots).
; Begin assembly souce.
; Character receive and display for Truly MTC-C162DPLY-2N 2 line X 16 char LCD
; and two button which send an ASCII "R" and an ASCII carriage return.
; Two include files are needed."2x16lcd.inc" is available on projects.cappels.org, and
; "2313def.inc" is available from Atmel as part of AVR Studio.
; Version LDCbutons040904C Has a larger UART receive buffer and two button inputs to provid
; control signals vai the UART. If the two buttons are not needed, don't connect them, and thsi
; will be a receive-only application.
; Requires AT90S2313 or equivalent with 4 MHz clock.
; Below are the connections to the Truly Display
; Pin   Function (connection)
; 1     GND
; 2     VCC (+5V)
; 3     Contrast (0to +5V from wiper of 10k pot)
; 4     RS (Register Select 1=data 0=command)
; 5     R/W (tie to ground for write-only)
; 6     OE (Enable - data clocked on neg transition)
; 7-10  D0 - D3 lower 4 data bits (not used - ground these)
; 11-14 D4-D7   upper 4 data bits (connects to AVR90S2313 PB4 through PB7 respectively).
; The AT90S2313 routines to control the display, based on the Hitachi HD44780, and the initialization 
; code in particular, are based on code originally published by Richard Hosking. 
; The display is driven in the 4 bit mode. The Truly display uses a Samsung KS0070B controller
; that appears to differ from the Hitachi HD44780 controller in that the Samsung requires an
; additional 4 bit write operation during "Function set" when in 4 bit mode. This was accomplished 
; by writing the "Function set" command twice to the controller twice instead of once.
; I suspect that this would work without modification with the Hitachi
; controller as the second write would be a redundant command for the Hitachi controller. 
; Note that 
; Below re the connections for the AT90S2313
; Pin           Function (Connection)
; VCC           +5V (decoupled)
; GND           ground
; XTAL1         Clock (see data sheet)
; XTAL2         Clcok (see data sheet)
; TXD           Serial out (RS-232 inverting buffer to remote device - NOT USED)
; RXD           Serial in (RS-232 inverting receive buffer)
; PD2          "R" buton. When this pin is momentarily groudned, an ASCII "R" is sent via UART.
; PD3          'Return" button. When this pin is momentarily grounded, an ASCII CR is sent.
; PD4-6        Unassigned (not connected)
; PD6
; PB0,PB1       Unassigned (not connected)
; PB2           R/S pin on Truly LCD (Truly module pin 4)
; PB3           OE pin on Truly LCD (Truly module pin 6)
; PB4-PB7       D4-D7 on Truly LCD (Truly module pins 11-14 respectively)
; Note that unused I/O pins are pulled up with the weak pull-up (direction bits 
; set to inputs, data bits written with a logic "1").
; Behavior of the receive and display code:
; Upon the application of power, communications protocol and code date and revision letter are
; displayed. "9600" Refers to 9600 baud, "1" refers to 1 stop bit being required, and 
; "N" refers to no parity bits are expected. The next 7 characters uniquely identify the
; firmware revision level.
; Incoming characters are display on the LCD module on the lower of the two lines on the 
; display, referred to here as line two. The upper line is referred to as line 1. the first
; 16 characters are displayed and any additional characters are not. Control characters, 
; defined as those represented by ASCII values below $1F are not displayed. 
; Linefeed characters cause the display to wait for the first non-control
; character following the linefeed before copying line 2 to line 1 (scrolling the display),
; clearing line 2 and positioning the cursor at the start of line 2.
; The display responds to carriage return characters ($0D)by placing the cursor to the 
; fist (leftmost) position of line two.
; The display cursor is on.
; A note about how this code works:
; The display is two lines of 16 characters. 
; The maximum data rate for allowable operation is 9600 baud with 1 stop bit and no parity.
; All display write operations are "open loop" timing. That is to say that delay loops are
; used to assure that display write operations don't proceed faster than the display can handle.
; Therefore, if the controller's clock rate is changed, the timing routines will have to be 
; modified accordingly. Mr. Hosking has thoughtfully indicated the delay time expected for each
; routine.
; Upon coming out of reset, the display is initialized and the data format and firmware revision
; are displayed.
; An 8 character circular buffer is used to capture incoming characters and a 16 line buffer 
; is is used to store a copy of the line two (lower line) of the display so it can be copied
; to line one during scrolling. The circular buffer is necessary because scrolling of the display
; takes 1.8 character times at 9600 baud, and without the buffer, a character would be lost each 
; time display content is scrolled.
; Incoming characters cause and interrupt and each character is stored in an 8 character 
; circular buffer. 
; The main loop of the program, named "forever" continuously checks to see if new characters 
; have been written to the buffer and processes new characters. Displayable characters, 
; defined as those with ASCII codes above $1F, are written to display line 2 and to a 16 
; character buffer in RAM. Linefeed and carriage return are the only control characters that
; recognized. Carriage returns cause the line buffer to be erased and its pointer set to the start
; of the buffer. Linefeeds cause a flag to be set, which will cause display scrolling when the first
; displayable character after the linefeed is read from the circular buffer
; Display scrolling, in response to the first character read from the circular buffer after a
; linefeed calls the routine "linefeed". Linefeed scrolls the display upward one line, leaving
; line two clear with the cursor positioned at the start of the line. In scrolling the display
; linefeed copies the line buffer, which contains a copy of the contents of line two of the display
; to line one, resets the line buffer by clearing it and setting the pointer to the start, then it
; clears line two and sets the cursor to the start of line two. 
.include "2313def.inc"
.def    charcount       =r1     ;Number of characters displayed on line being written
.def    temp            =r16    ;Temporary register.(May not be R17).
.def    temp2           =r18    ;Temporary register.
.def    outchar         =r19;   ;Char to send by UART.
.def    inchar          =r20    ;Char received by UART
.def    flagreg         =r21    ;Flags
.def    charbuf         =r22    ;Char read from circular buffer
.def    gpcount         =r23    ;Genral purpose counter (was YH in earlier version)
;       YL                      ;UART circular buffer write ponter.
;       ZL                      ;UART circultar buffer read pointer
;       XL                      ;16 char line buffer pointer
                                ;Assign I/O pins.
.equ    OE              =8      ;Bit 3 port B display enable (also directly addressed).
.equ    RS              =4      ;Bit 2 in port B display register select (also directly addressed).
.equ    Returnbut       =3      ;Button used to send return ($0D) via UART.
.equ    Rbut            =2      ;Button used to send ASCII "R" ($52) vai UART.
                                ;Allocate buffers
.equ    lbufsiz         =16
.equ    circbufsiz      =64
.equ    cbufbot         =$60    ;Bottom of circular UART receive buffer.
.equ    cbuftop         =cbufbot + circbufsiz   ;Top of circular UART receive buffer.
.equ    lbufbot         =cbuftop + 1            ;Bottom of display line buffer.
.equ    lbuftop         =lbufbot + lbufsiz      ;Top of display line buffer.
                ;Baudrate Calculation
.equ    clock           = 4000000               ;clock frequency
.equ    baudrate        = 9600                  ;choose a baud rate
.equ    baudconstant    = (clock/(16*baudrate))-1
;Flagreg bit assignments
;       bit     0               Pending linefeed if high.
;       bit     1       
;       bit     3
;       bit     4
;       bit     5
;       bit     6
;       bit     7
;Memory uage:
;Ring buffer from $60 through $67
;Line buffer from $70 to $7F
.org    $00
        rjmp    start           ; Reset 
        rjmp    start
        rjmp    start
        rjmp    start
        rjmp    start
        rjmp    start
        rjmp    start
        rjmp    UartRecInt      ;UART interrupt 
.include "2x16lcd.inc"
.db     "LDCbutons040904C "
.db     00,00
        ldi     temp,RAMEND      ;Init Stack Pointer
        out     SPL,temp
        ldi     temp,0b00000011 ;Weak pullups on inputs
        out     PORTB,temp
        ldi     temp,0b11111100    
        out     DDRB,temp       ;PORTB = all outputs except bits 0,1 
        ldi     temp,0b11111111 ;Weak pullups on inputs
        out     PORTD,temp
        ldi     temp,0b00000000 ;PORTD - all inputs
        ldi     flagreg,$00     ;Set all flags to zero.
        ldi     temp,$00
        mov     charcount,temp
        rcall   ClearLineBuffer ;Initialize line buffer.
        rcall   InitDisplay     ;Initialize LCD display module,
        ldi     temp,baudconstant       
        out     ubrr,temp       ;load baudrate
        sbi     ucr,txen        ;Enable the UART transmitter
        sbi     ucr,rxen        ;Enable the receiver..
        rcall   sendhello       ;write line 1 power-up information
        rcall   Hometwo         ;Position cursor for input on line two. 
        clr     XH              ;Clear XH, YH, ZY for processors with 16 bit RAM addressing
        clr     YH
        clr     ZH
        ldi     YL,cbufbot      ;Set Y and Z circ buff pointers to bottom.
        ldi     ZL,cbufbot
        sbi     UCR,7           ;Emable UART Interrupt.
        sei                     ;Global interrupt flag set (enabled).
forever:        ;Waiting for new data from circular buffer, check buttons
        rcall   checkbut        ;Check to see if any buttons are down.
                                ;Get waiting char from circular buffer if there is one.
        cp      YL,ZL           ;Is circular buffer read pointer alredy pointing to latest entry?
        breq    Buffempty       ;If so, there is no new data in the buffer.
        ld      charbuf,Z+      ;If pointers are not equal, then read next char in buffer.
        cpi     ZL,cbuftop + 1  ;Advance circular buffer read pointer to next value.
        brne    NoZeroZL        ;If end of buffer, wrap around to start of buffer.
        ldi     ZL,cbufbot
                ;Handle the new character as either a cotnrol char or a displaybale char.
                ;If bufchar is a control char,test for CR and LF
        cpi     charbuf,$1F     ;If not a control char branch to displayable char routine..
        brpl    ItsDisplayable
        cpi     charbuf,$0D     ;If this is a carriage return character, 
        brne    noCR            ;Set cursor to start of bottom line.
        ldi     XL,lbufbot      ;Its a CR so set ponters back to start of line.
        ldi     temp,$00
        mov     charcount,temp
        rcall   hometwo         ;Put cursor back in first column of line two.   
        cpi     charbuf,$0A     ;If its a linfeed char then
        brne    NotALineFeed    ;set linfeed pending flag.
        ori     flagreg,0b00000001      
        rjmp    Buffdone
ItsDisplayable: ;If not a control char then do line feed if pending then
                        ;write to display and to line buffer.
        sbrc    flagreg,0       ;If linefeed i spending, then do it     
        rcall   linefeed
        cpi     XL,lbuftop+1    ;Don't store if buffer at limit.
        breq    Xfull
        st      X+,charbuf
        mov     temp,charbuf
        rcall   SendData        
        rjmp    forever 
SendHello:      ;Send HelloString       
        rcall   Homeone
        ldi     ZH,high(2*HelloString)  ; Load high part of byte address into ZH
        ldi     ZL,low(2*HelloString)   ; Load low part of byte address into ZL
        lpm                             ; Load byte from program memory into r0
        tst     r0                      ; Check if we've reached the end of the message
        breq    finishsendstering       ; If so, return
        mov     temp,r0
        rcall   SendData
        adiw    ZL,1                    ; Increment Z registers
        rjmp    moretosend
linefeed:       ;Handle a linefeed char
                ;Clear line 1 (top line), copy line two to line one, clear
                ;line two, the position the cursor in first column of line two.
        push    gpcount
        rcall   hometwo         ;Put cursor at start of line 2 so it can be cleared.
        ldi     gpcount,$10             ;Number of chars in line.
        ldi     temp,$20        ;Fill line with spaces (erase).
        rcall   SendData
        dec     gpcount
        brne    clearmore
        rcall   homeone         ;Copy line buffer to line 1, reset XL to bottom.
        ldi     XL,lbufbot
        ldi     temp,$00
        mov     charcount,temp
        ld      temp,X+
        rcall   SendData
        cpi     XL,lbuftop + 1
        brne    MoreToCopy
        rcall   ClearLineBuffer
        andi    flagreg,0b11111100     ;Clear linefeed pending and enable flagsb.
        rcall   hometwo
        pop     gpcount
        ret                     ;Done
ClearLineBuffer:        ;Fill line buffer with spaces, set XL to bottom.
        ldi     XL,lbufbot
        ldi     temp,$00
        mov     charcount,temp
        ldi     temp,$20
        st      X+,temp
        cpi     XL,lbuftop + 1
        brne    MoreToSpace
        ldi     XL,lbufbot
        sbis    usr,rxc         ;Wait for a char.
        rjmp    recchar
        in      inchar,udr      ;Read the char.
        sbis    usr,udre        ;wait until the register is cleared
        rjmp    emitchar   
        out     udr,outchar     ;send the byte
        ret                     ;go back
UartRecInt:     ;Uart interrupt service -Write received char into a circular buffer
        push    temp
        in      temp,sreg
        push    temp
        rcall   recchar                 ;Get the char from the UART.
        st      Y+,inchar
        cpi     YL,cbuftop + 1
        brne    NoZeroYL
        ldi     YL,cbufbot
        pop     temp
        out     sreg,temp
        pop     temp
checkbut:                               ;Check for button down; if so, send by UART
        sbis    PIND,Returnbut
        rjmp    sendreturn
        sbis    PIND,Rbut
        rjmp    sendr
        ldi     outchar,$0D
        rcall   EimtAndWait
        sbis    PIND,Returnbut
        rjmp    ISRT
        rcall   wait20ms
        ldi     outchar,'R'
        rcall   EimtAndWait
        sbis    PIND,Rbut
        rjmp    ISR
        rcall   wait20ms
        rcall   emitchar
        rcall   wait20ms
wait20ms:                               ;20 millisecond debounce timer
        clr     temp
dlx0:   clr     outchar                         
dlx1:   dec     outchar
        brne    dlx1
        dec     temp
        brne    dlx0
;Richard Cappels