Interfacing an I2C LCD Display to PIC Example Code


;
; For more details, refer to post:
;
;    Interfacing an I2C LCD Display to PIC
;

;————————————————————————————————–
; Project:  I2C_LCD_Demo — Main PIC software
; Date:     5/9/17
; Author:   Mike Schoonover
; Revision: See Revision History notes below.
;
; Overview:
;
; This program displays data on an LCD display and demonstrates various functionality.
;
;————————————————————————————————–
; Notes on PCLATH
;
; The program counter (PC) is 13 bits. The lower 8 bits can be read and written as register PCL.
; The upper bits cannot be directly read or written.
;
; When the PCL register is written, PCLATH<4:0> is copied at the same time to the upper 5 bits of
; PC.
;
; When a goto is executed, 11 bits embedded into the goto instruction are loaded into the PC<10:0>
; while bits PCLATH<4:3> are copied to bits PC<12:11>
;
; Changing PCLATH does NOT instantly change the PC register. The PCLATH will be used the next time
; a goto is executed (or similar opcode) or the PCL register is written to. Thus, to jump farther
; than the 11 bits (2047 bytes) in the goto opcode will allow, the PCLATH register is adjusted
; first and then the goto executed.
;
;————————————————————————————————–
;
; Revision History:
;
; 1.0   Code copied from NoahBot1. Code note related to LCD removed.
;
;————————————————————————————————–
; Hardware Control Description
;
; Function by Pin
;
; Port A        Pin/Options/Selected Option/Description  (only the most common options are listed)
;
; RA0   I/*,IOC,USB-D+                  ~ In ~
; RA1   I/*,IOC,USB-D-                  ~ In ~
; RA2   not implemented in PIC16f1459   ~
; RA3   I/*,IOC,T1G,MSSP-SS,Vpp,MCLR    ~ In ~
; RA4   I/O,IOC,T1G,CLKOUT,CLKR, AN3    ~ In ~
; RA5   I/O,IOC,T1CKI,CLKIN             ~ Out ~
; RA6   not implemented in PIC16f1459
; RA7   not implemented in PIC16f1459
;
; Port B        Pin/Options/Selected Option/Description  (only the most common options are listed)
;
; RB0   not implemented in PIC16f1459
; RB1   not implemented in PIC16f1459
; RB2   not implemented in PIC16f1459
; RB3   not implemented in PIC16f1459
; RB4   I/O,IOC,MSSP-SDA/SDI,AN10       ~ I ~ I2CSDA, I2C bus data line
; RB5   I/O,IOC,EUSART-RX/DX,AN11       ~ I ~ EUSART-RX, serial port data in
; RB6   I/O,IOC,MSSP-SCL/SCK            ~ I ~ I2CSCL, I2C bus clock line
; RB7   I/O,IOC,EUSART-TX/CK            ~ O ~ EUSART-TX, serial port data out
;
; Port C        Pin/Options/Selected Option/Description  (only the most common options are listed)
;
; RC0   I/O,AN4,C1/2IN+,ICSPDAT,Vref    ~ Out ~ ICSPDAT ~ in circuit programming data
; RC1   I/O,AN5,C1/2IN1-,ICSPCLK,INT    ~ In  ~ ICSPCLK ~ in circuit programming clock
; RC2   I/O,AN6,C1/2IN2-,DACOUT1        ~ In  ~
; RC3   I/O,AN7,C1/2IN3-,DACOUT2,CLKR   ~ Out ~
; RC4   I/O,C1/2OUT                     ~ Out ~
; RC5   I/O,T0CKI,PWM1                  ~ In  ~
; RC6   I/O,AN8,PWM2,MSSP-SS            ~ Out ~
; RC7   I/O,AN9,MSSP-SDO                ~ In  ~
;
;end of Hardware Control Description
;————————————————————————————————–
;
; User Inputs
;
;
;————————————————————————————————–

;————————————————————————————————–
; Defines
;

; COMMENT OUT “#define DEBUG_MODE” line before using code in system.
; Defining DEBUG_MODE will insert code which simplifies simulation by skipping code which waits on
; stimulus and performing various other actions which make the simulation run properly.
; Search for “DEBUG_MODE” to find all examples of such code.

;#define DEBUG_MODE 1     ; set DEBUG_MODE testing “on” ;//debug mks — comment this out later

; end of Defines
;————————————————————————————————–

;————————————————————————————————–
; LCD Display Defines
;

;LCD Screen Position Codes

LINE1_COL1  EQU     0x00
LINE2_COL1  EQU     0x40

; commands

LCD_CLEARDISPLAY    EQU 0x01
LCD_RETURNHOME      EQU 0x02
LCD_ENTRYMODESET    EQU 0x04
LCD_DISPLAYCONTROL  EQU 0x08
LCD_CURSORSHIFT     EQU 0x10
LCD_FUNCTIONSET     EQU 0x20
LCD_SETCGRAMADDR    EQU 0x40
LCD_SETDDRAMADDR    EQU 0x80

; flags for display entry mode

LCD_DEC                 EQU 0x00
LCD_INC                 EQU 0x02
LCD_SHIFT               EQU 0x01
LCD_NO_SHIFT            EQU 0x00

; flags for display on/off control

LCD_DISPLAYON   EQU 0x04
LCD_DISPLAYOFF  EQU 0x00
LCD_CURSORON    EQU 0x02
LCD_CURSOROFF   EQU 0x00
LCD_BLINKON     EQU 0x01
LCD_BLINKOFF    EQU 0x00

; flags for display/cursor shift

LCD_DISPLAYMOVE EQU 0x08
LCD_CURSORMOVE  EQU 0x00
LCD_MOVERIGHT   EQU 0x04
LCD_MOVELEFT    EQU 0x00

; flags for function set

LCD_8BITMODE    EQU 0x10
LCD_4BITMODE    EQU 0x00
LCD_2LINE       EQU 0x08
LCD_1LINE       EQU 0x00
LCD_5x10DOTS    EQU 0x04
LCD_5x8DOTS     EQU 0x00

; control flags

LCD_BACKLIGHT   EQU .3
LCD_EN          EQU .2                    ; high pulse to write or read data from LCD
LCD_RW          EQU .1                    ; 1 = read 0 = write
LCD_RS          EQU .0                    ; 1 = data register 0 = instruction register

; LCD Display Commands

CLEAR_SCREEN_CMD    EQU        0x01

; LCD Display On/Off Command bits

;  bit 3: specifies that this is a display on/off command if 1
;  bit 2: 0 = display off, 1 = display on
;  bit 1: 0 = cursor off, 1 = cursor on
;  bit 0: 0 = character blink off, 1 = blink on

DISPLAY_ONOFF_CMD_FLAG    EQU        0x08
DISPLAY_ON_FLAG            EQU        0x04
CURSOR_ON_FLAG            EQU        0x02
BLINK_ON_FLAG            EQU        0x01

; I2C bus ID bytes for writing and reading to LCD
; bits 7-1 = device address of 0x3f
; upper nibble = 0111 (bits 7-4)
; chip A2-A0 inputs = 111 (bits 3-1)
; R/W bit set to 0 (bit 0) for writing
; R/W bit set to 1 (bit 0) for reading

LCD_ADDR        EQU     b’0111111′

LCD_WRITE_ID    EQU     (LCD_ADDR << 1) | 0
LCD_READ_ID     EQU     (LCD_ADDR << 1) | 1

; end of LCD Display Defines
;————————————————————————————————–

;————————————————————————————————–
; Configurations, etc. for the Assembler Tools and the PIC

LIST p = PIC16F1459    ;select the processor

errorlevel  -306 ; Suppresses Message[306] Crossing page boundary — ensure page bits are set.

errorLevel  -302 ; Suppresses Message[302] Register in operand not in bank 0.

errorLevel    -202 ; Suppresses Message[205] Argument out of range. Least significant bits used.
;    (this is displayed when a RAM address above bank 1 is used — it is
;     expected that the lower bits will be used as the lower address bits)

#INCLUDE <p16f1459.inc>         ; Microchip Device Header File

;#include <xc.h>

; Specify Device Configuration Bits

; CONFIG1
; __config 0xF9E4
__CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
; CONFIG2
; __config 0xFFFF
__CONFIG _CONFIG2, _WRT_ALL & _CPUDIV_NOCLKDIV & _USBLSCLK_48MHz & _PLLMULT_4x & _PLLEN_DISABLED & _STVREN_ON & _BORV_LO & _LPBOR_OFF & _LVP_OFF

; _FOSC_INTOSC -> internal oscillator, I/O function on CLKIN pin
; _WDTE_OFF -> watch dog timer disabled
; _PWRTE_OFF -> Power Up Timer disabled
; _MCLRE_OFF -> MCLR/VPP pin is digital input
; _CP_OFF -> Flash Program Memory Code Protection off
; _BOREN_OFF -> Power Brown-out Reset off
; _CLKOUTEN_OFF -> CLKOUT function off, I/O or oscillator function on CLKOUT pin
; _IESO_OFF -> Internal/External Oscillator Switchover off
;   (not used for this application since there is no external clock)
; _FCMEN_OFF -> Fail-Safe Clock Monitor off
;   (not used for this application since there is no external clock)
; _WRT_ALL -> Flash Memory Self-Write Protection on — no writing to flash
;
; _CPUDIV_NOCLKDIV -> CPU clock not divided
; _USBLSCLK_48MHz -> only used for USB operation
; _PLLMULT_4x -> sets PLL (if enabled) multiplier — 4x allows software override
; _PLLEN_DISABLED -> the clock frequency multiplier is not used
;
; _STVREN_ON -> Stack Overflow/Underflow Reset on
; _BORV_LO -> Brown-out Reset Voltage Selection — low trip point
; _LPBOR_OFF -> Low-Power Brown-out Reset Off
; _LVP_OFF -> Low Voltage Programming off
;
; end of configurations
;————————————————————————————————–

;————————————————————————————————–
; Hardware Definitions
;
; NOTE: All ports for outputs are defined as the latches for the port. Writing to the latches
; avoids problems with the read-modify-write system of the PIC.
;
; For inputs, the port must be read.
;

; Port A

; Port B

I2CSDA_LINE     EQU     RB4
I2CSCL_LINE     EQU     RB6

; Port C

ICSPDAT        EQU    RC0
ICSPCLK        EQU    RC1

; end of Hardware Definitions
;————————————————————————————————–

;————————————————————————————————–
; Software Definitions

; bits in flags variable

UNUSED_1_0  EQU     0x0
UNUSED_1_1  EQU     0x1
UNUSED_1_2  EQU     0x2
UNUSED_1_3  EQU     0x3
UNUSED_1_4  EQU     0x4
UNUSED_1_5  EQU     0x5
UNUSED_1_6  EQU     0x6
UNUSED_1_7    EQU     0x7

; bits in flags2 variable

UNUSED_2_0          EQU 0
UNUSED_2_1          EQU 1
UNUSED_2_2          EQU 2
UNUSED_2_3          EQU 3
LCD_REG_SEL         EQU 4
LCD_BACKLIGHT_SEL   EQU 5
WARNING_ERROR       EQU 6
UNUSED_2_7          EQU 7

; bits in flags3 variable

UNUSED_3_0      EQU     0

; bits in statusFlags variable

SERIAL_COM_ERROR    EQU 0
I2C_COM_ERROR       EQU 1

; Serial Port Packet Commands

NO_ACTION_CMD               EQU .0
ACK_CMD                     EQU .1
SET_OUTPUTS_CMD             EQU .2
SWITCH_STATES_CMD           EQU .3
LCD_DATA_CMD                EQU .4
LCD_INSTRUCTION_CMD         EQU .5
LCD_BLOCK_CMD               EQU .6

; end of Software Definitions
;————————————————————————————————–

;————————————————————————————————–
; Variables in RAM
;
; Note that you cannot use a lot of the data definition directives for RAM space (such as DB)
; unless you are compiling object files and using a linker command file.  The cblock directive is
; commonly used to reserve RAM space or Code space when producing “absolute” code, as is done here.
;

; Assign variables in RAM – Bank 0
; Bank 0 has 80 bytes of free space

cblock 0x20                ; starting address

flags                   ; bit 0: 0 =
; bit 1:

flags2                  ; bit 0:
; bit 1:
; bit 2:
; bit 3:
; bit 4: 0 = LCD instruction register, 1 = LCD data register
; bit 5: 0 = LCD backlight off, 1 = on
; bit 6: 0 = Warning message, 1 = Error message
; bit 7: 0 =

flags3                  ; bit 0:
; bit 1:
; bit 2:
; bit 3:
; bit 4:
; bit 5:
; bit 6:
; bit 7:

statusFlags             ; bit 0: 1 =
; bit 1: 1 =

secDelayCnt
msDelayCnt
bigDelayCnt
smallDelayCnt

cursorPos               ; contains the location of the cursor
; NOTE: LCD addressing is screwy – the lines are not in sequential order:
; line 1 column 1 = 0x80      (actually address 0x00)
; line 2 column 1 = 0xc0    (actually address 0x40)
; line 3 column 1 = 0x94    (actually address 0x14)
; line 4 column 1 = 0xd4    (actually address 0x54)
;
; To address the second column in each line, use 81, C1, 95, d5, etc.
;
; The two different columns of values listed above are due to the fact that the address
; is in bits 6:0 and control bit 7 must be set to signal that the byte is an address
; byte.  Thus, 0x00 byte with the control bit set is 0x80.  The 0x80 value is what is
; actually sent to the LCD to set address 0x00.
;
;  Line 3 is actually the continuation in memory at the end of line 1
;    (0x94 – 0x80 = 0x14 which is 20 decimal — the character width of the display)
;  Line 4 is a similar extension of line 2.
;
; Note that the user manual offered by Optrex shows the line addresses
; for 20 character wide displays at the bottom of page 20.

scratch0                ; these can be used by any function
scratch1
scratch2
scratch3
scratch4
scratch5
scratch6
scratch7
scratch8
scratch9
scratch10

I2CScratch0                ; these are used by I2C functions
I2CScratch1
I2CScratch2
I2CScratch3
I2CScratch4
I2CScratch5

; next variables ONLY written to by interrupt code

intScratch0                ; scratch pad variable for exclusive use by interrupt code

; end of variables ONLY written to by interrupt code

slaveI2CErrorCnt        ; number of com errors from Slave PICs via I2C bus

endc

;—————–

; Assign variables in RAM – Bank 1
; Bank 1 has 80 bytes of free space

cblock 0xa0                ; starting address

endc

;—————–

; Assign variables in RAM – Bank 2
; Bank 2 has 80 bytes of free space

cblock 0x120                ; starting address

endc

;—————–

; Define variables in the memory which is mirrored in all RAM banks.
;
; On older PICs, this section was used to store context registers during an interrupt as the
; current bank was unknown upon entering the interrupt. Now, the section can be used for any
; purpose as the more powerful PICs automatically save the context on interrupt.
;
;    Bank 0        Bank 1        Bank 2        Bank3
;    70h-7fh        f0h-ffh        170h-17fh    1f0h-1ffh
;

cblock    0x70

BANKSEL_TEMP
FSR0H_TEMP
FSR0L_TEMP
FSR1H_TEMP
FSR1L_TEMP

endc

; end of Variables in RAM
;————————————————————————————————–

;————————————————————————————————–
; Power On and Reset Vectors
;

org 0x00                ; Start of Program Memory

goto start              ; jump to main code section
nop                        ; Pad out so interrupt
nop                        ; service routine gets
nop                        ; put at address 0x0004.

; interrupt vector at 0x0004

; NOTE: You must set PCLATH before jumping to the interrupt routine – if PCLATH is wrong the
; jump will fail.

movlp   high handleInterrupt
goto    handleInterrupt    ; points to interrupt service routine

; end of Reset Vectors
;————————————————————————————————–

;————————————————————————————————–
; start
;

start:

movlp   high setup      ; preset variables and configure hardware
call    setup
movlp   high start

goto    demoLCD

haltLoop:
goto    haltLoop

; end of start
;————————————————————————————————–

;————————————————————————————————–
; demoLCD
;
; Demonstrates LCD functionality.
;

demoLCD:

movlw   high string0                ; “NoahBot 1.0”
movwf   FSR1H
movlw   low string0
movwf   FSR1L
call    printStringI2CUnbuffered

call    gotoLCDLine2Col1

movlw   high string1                ; “Noah = tater tot”
movwf   FSR1H
movlw   low string1
movwf   FSR1L
call    printStringI2CUnbuffered

movlw   .4
call    delayWSeconds

movlw   high string2                ; “Destructo Mode”
movwf   FSR1H
movlw   low string2
movwf   FSR1L
movlw   .10                         ; flash warning x number of times
call    displayWarningOnLCD

movlw   high string3                ; “I’m f*ing lost”
movwf   FSR1H
movlw   low string3
movwf   FSR1L
movlw   .255                        ; flash warning x number of times
call    displayErrorOnLCD

demoLCDLoop:
goto    demoLCDLoop

return

; end of demoLCD
;————————————————————————————————–

;————————————————————————————————–
; clearLCD
;
; Clears the LCD display and moves the cursor to line 1 column 1.
;

clearLCD:

movlw   LCD_CLEARDISPLAY
call    sendControlCodeToLCD

movlw   .3                      ; delay at least 1.53 ms after Clear Display command
call    delayWms

return

; end of clearLCD
;————————————————————————————————–

;————————————————————————————————–
; gotoLCDLine1Col1
;
; Moves the cursor to line 1 column 1 on the LCD.
;

gotoLCDLine1Col1:

movlw   LCD_SETDDRAMADDR | 0x00     ; move to row 1, col 1
goto    sendControlCodeToLCD

; end of gotoLCDLine1Col1
;————————————————————————————————–

;————————————————————————————————–
; gotoLCDLine2Col1
;
; Moves the cursor to line 2 column 1 on the LCD.
;

gotoLCDLine2Col1:

movlw   LCD_SETDDRAMADDR | 0x40     ; move to row 2, col 1
goto    sendControlCodeToLCD

; end of gotoLCDLine2Col1
;————————————————————————————————–

;————————————————————————————————–
; displayWarningOnLCD
;
; Flashes “* Warning *” on line 1 while displaying the string pointed by FSR1 on line 2 of the LCD.
;
; On entry:
;
; WREG contains number of times to flash
; FSR1 points to string to be displayed
;

displayWarningOnLCD:

banksel flags2
bcf     flags2,WARNING_ERROR        ; clear flag to indicate a warning message
goto    dWOLCD1

displayErrorOnLCD:

banksel flags2
bsf     flags2,WARNING_ERROR        ; set flag to indicate an error message

dWOLCD1:

banksel I2CScratch5                 ; store the flash count
movwf   I2CScratch5

call    clearLCD

call    gotoLCDLine2Col1            ; display the specified string on line 2
call    printStringI2CUnbuffered

dWOLCDLoop:

call    gotoLCDLine1Col1

banksel flags2
btfsc   flags2,WARNING_ERROR
goto    dWOLCD2

movlw   high warningStr             ; display “* Warning *” on line 1
movwf   FSR1H
movlw   low warningStr
movwf   FSR1L
goto    dWOLCD3

dWOLCD2:

movlw   high errorStr               ; display “* Error *” on line 1
movwf   FSR1H
movlw   low errorStr
movwf   FSR1L

dWOLCD3:

call    printStringI2CUnbuffered

movlw   .255                        ; delay 255 ms
call    delayWms

call    gotoLCDLine1Col1            ; erase line 1
movlw   high clearStr
movwf   FSR1H
movlw   low clearStr
movwf   FSR1L
call    printStringI2CUnbuffered

movlw   .255                        ; delay 255 ms
call    delayWms

banksel I2CScratch5
decfsz  I2CScratch5,F
goto    dWOLCDLoop

return

; end of displayWarningOnLCD
;————————————————————————————————–

;————————————————————————————————–
; longCallSendNybblesToLCDViaI2C
;
; Sets the PCLATH register to allow a call to the function which is in a different memory page. Sets
; the register back to the local page after the call returns.
;

longCallSendNybblesToLCDViaI2C:

movlp   high sendNybblesToLCDViaI2C
call    sendNybblesToLCDViaI2C
movlp   longCallSendNybblesToLCDViaI2C

return

; end of longCallSendNybblesToLCDViaI2C
;————————————————————————————————–

;————————————————————————————————–
; sendControlCodeToLCD
;
; Sends a control code in WREG to the LCD via the I2C bus.
;
; On entry:
;
; WREG = control code to be sent.
;
; On exit:
;
; The flags2.LCD_REG_SEL bit is cleared which selects the LCD’s instruction register.
;

sendControlCodeToLCD:

banksel flags2
bcf     flags2, LCD_REG_SEL         ; select the LCD’s instruction register

movlp   high sendNybblesToLCDViaI2C
call    sendNybblesToLCDViaI2C
movlp   sendControlCodeToLCD

return

; end of sendControlCodeToLCD
;————————————————————————————————–

;————————————————————————————————–
; pushFSR1
;
; Saves FSR1 to temporary variables.
;

pushFSR1:

movf    FSR1H,W
movwf   FSR1H_TEMP
movf    FSR1L,W
movwf   FSR1L_TEMP

return

; pushFSR1
;————————————————————————————————–

;————————————————————————————————–
; popFSR1
;
; Loads FSR1 from temporary variables.
;

popFSR1:

movf    FSR1H_TEMP,W
movwf   FSR1H
movf    FSR1L_TEMP,W
movwf   FSR1L

return

; popFSR1
;————————————————————————————————–

;————————————————————————————————–
; bigDelay
;
; On entry at bigDelay, W holds LSB of delay value, MSB will be 0.
; On entry at bigDelayA, scratch1:W holds delay value.
;
; Notes on code for decrementing to 0:
;  subtract 1 from LSB by adding 0xff (two’s comp for -1)
;  C bit will be set until LSB goes from 0 to 255; 0 is only value added to 0xff that won’t carry
;  When C bit not set, subtract 1 from MSB until it reaches 0
;
;

bigDelay:
clrf    scratch1
bigDelayA:
movwf   scratch0        ; store W

ifdef DEBUG_MODE        ; if debugging, don’t delay
return
endif

loopBD1:

; call inner delay for each count of outer delay

movlw   0x1
movwf   scratch3
movlw   0x6a            ; scratch3:W = delay value
call    smallDelayA

movlw   0xff            ; decrement LSByte by adding -1
addwf   scratch0,F
btfss   STATUS,C        ; did LSByte roll under (0->255)?
decf    scratch1,F        ; decrement MSByte after LSByte roll under
movf    scratch0,W        ; check MSB:LSB for zero
iorwf    scratch1,W
btfsc    STATUS,Z
return

goto    loopBD1         ; loop until outer counter is zero

; end of bigDelay
;————————————————————————————————–

;————————————————————————————————–
; smallDelay
;
; Delays while periodically clearing WDT to prevent watch dog timer trigger.
;
; On entry at smallDelay, W holds LSB of delay value, MSB will be 0.
; On entry at smallDelayA, scratch3:W holds delay value.
;

smallDelay:
clrf    scratch3
smallDelayA:
movwf   scratch2        ; store W

ifdef DEBUG_MODE        ; if debugging, don’t delay
return
endif

loopSD1:

clrwdt                  ; keep watch dog timer from triggering

movlw   0xff            ; decrement LSByte by adding -1
addwf   scratch2,F
btfss   STATUS,C        ; did LSByte roll under (0->255)?
decf    scratch3,F        ; decrement MSByte after LSByte roll under
movf    scratch2,W        ; check MSB:LSB for zero
iorwf    scratch3,W
btfsc    STATUS,Z
return

goto    loopSD1         ; loop until outer counter is zero

; end of smallDelay
;————————————————————————————————–

;————————————————————————————————–
; delayWms
;
; Creates a delay of x milliseconds where x is specified in the W register.
;
; On Entry:
;
; W contains number of milliseconds to delay.
;
; Values to achieve 1 millisecond for various Fosc:
;
; for 4Mhz  Fosc -> bigDelayCnt = .2, smallDelayCnt = .166
; for 16Mhz Fosc -> bigDelayCnt = .6, smallDelayCnt = .222
;
; Note: these values do not take into account interrupts processing which will increase the delay.
;

delayWms:

banksel msDelayCnt

ifdef DEBUG_MODE            ; if debugging, don’t delay
return
endif

movwf   msDelayCnt          ; number of milliseconds

msD1Loop1:

movlw    .6                  ; smallDelayCnt * bigDelayCnt give delay of 1 millisecond
movwf    bigDelayCnt

msD1Loop2:

movlw    .222
movwf    smallDelayCnt

msD1Loop3:

decfsz    smallDelayCnt,F
goto    msD1Loop3

decfsz    bigDelayCnt,F
goto    msD1Loop2

decfsz    msDelayCnt,F
goto    msD1Loop1

return

; end of delayWms
;————————————————————————————————–

;————————————————————————————————–
; delayWSeconds
;
; Creates a delay of x seconds where x is specified in the W register. Uses delayWms repeatedly
; to create proper delay.
;
; On Entry:
;
; W contains number of seconds to delay.
;
;

delayWSeconds:

banksel secDelayCnt

movwf   secDelayCnt          ; number of seconds

sDLoop1:

movlw   .250
call    delayWms

movlw   .250
call    delayWms

movlw   .250
call    delayWms

movlw   .250
call    delayWms

decfsz  secDelayCnt,F
goto    sDLoop1

return

; end of delayWSeconds
;————————————————————————————————–

;————————————————————————————————–
; printStringI2CUnbuffered
;
; Prints the string pointed to by FSR1 to the I2C port.
;
; On entry:
;
; FSR1 points to the desired string
;

printStringI2CUnbuffered:

banksel flags2
bsf     flags2, LCD_REG_SEL         ; select the LCD’s data register

loopPSI:

moviw   FSR1++

btfss   STATUS,Z
goto    pSI1

return

pSI1:

call    longCallSendNybblesToLCDViaI2C

goto    loopPSI          ; loop until length of string reached

; end of printStringI2CUnbuffered
;————————————————————————————————–

;————————————————————————————————–
; generateI2CStart
;
; Generates a start condition on the I2C bus.
;

generateI2CStart:

banksel SSP1CON2
bsf     SSP1CON2,SEN

return

; end of generateI2CStart
;————————————————————————————————–

;————————————————————————————————–
; generateI2CRestart
;
; Generates a restart condition on the I2C bus.
;

generateI2CRestart:

banksel SSP1CON2
bsf     SSP1CON2,RSEN

return

; end of generateI2CRestart
;————————————————————————————————–

;————————————————————————————————–
; generateI2CStop
;
; Generates a stop condition on the I2C bus.
;

generateI2CStop:

banksel SSP1CON2
bsf     SSP1CON2,PEN

return

; end of generateI2CStop
;————————————————————————————————–

;————————————————————————————————–
; clearSSP1IF
;
; Sets the SSP1IF bit in register PIR1 to 0.
;

clearSSP1IF:

banksel PIR1
bcf     PIR1, SSP1IF

return

; end of clearSSP1IF
;————————————————————————————————–

;————————————————————————————————–
; waitForSSP1IFHigh
;
; Waits in a loop for SSP1IF bit in register PIR1 to go high.
;

waitForSSP1IFHigh:

ifdef DEBUG_MODE  ; if debugging, don’t wait for interrupt to be set high as the MSSP is not
return            ; simulated by the IDE
endif

banksel PIR1

wfsh1:
btfss   PIR1, SSP1IF
goto    wfsh1

return

; end of waitForSSP1IFHigh
;————————————————————————————————–

;————————————————————————————————–
; waitForSSP1IFHighThenClearIt
;
; Waits in a loop for SSP1IF bit in register PIR1 to go high and then clears that bit.
;
; The bit must be cleared at some point after it is set before performing most actions with the I2C
; but. In most cases, it can be cleared immediately for which this function is useful.
;
; If the bit is not cleared before an operation, then checking the bit immediately after the
; operation will make it appear that the operation completed immediately and the code will not
; wait until the MSSP module sets the bit after actual completion.
;

waitForSSP1IFHighThenClearIt:

call    waitForSSP1IFHigh

call    clearSSP1IF

return

; end of waitForSSP1IFHighThenClearIt
;————————————————————————————————–

;————————————————————————————————–
; sendI2CByte
;
; Waits until the SSP1IF bit in register PIR1 goes high and then transmits the byte in the W
; register on the I2C bus.
;

sendI2CByte:

; wait for SSP1IF to go high

call    waitForSSP1IFHigh

; put byte in transmit buffer

banksel SSP1BUF
movwf   SSP1BUF

; clear interrupt flag

call    clearSSP1IF

return

; end of sendI2CByte
;————————————————————————————————–

;————————————————————————————————–
; clearSSP1OV
;
; Clears the MSSP overflow bit to allow new bytes to be read.
;
; The bit is set if a byte was received before the previous byte was read from the buffer.
;

clearSSP1OV:

banksel SSP1CON1
bcf     SSP1CON1,SSPOV

return

; end of clearSSP1OV
;————————————————————————————————–

;————————————————————————————————–
; clearWCOL
;
; Clears the MSSP write collision bit to allow new bytes to be written
;
; The bit is set if a byte was placed in SSPBUF at an improper time.
;

clearWCOL:

banksel SSP1CON1
bcf     SSP1CON1, WCOL

return

; end of clearWCOL
;————————————————————————————————–

;————————————————————————————————–
; reallyBigDelay
;
; Delays for a second or two.
;

reallyBigDelay:

ifdef DEBUG_MODE       ; if debugging, don’t delay
return
endif

banksel scratch6

movlw   .50
movwf   scratch6

rbd1:

movlw   .255
movwf   scratch7

rbd2:

movlw   .255
movwf   scratch8

rbd3:

decfsz  scratch8,F
goto    rbd3

decfsz  scratch7,F
goto    rbd2

decfsz  scratch6,F
goto    rbd1

return

; end of reallyBigDelay
;————————————————————————————————–

org 0x800    ;start code on page 2 of 2047 byte boundary

;————————————————————————————————–
; setup
;
; Presets variables and configures hardware.
;
; NOTE: The system does not use the internal comparators even though it appears there are analog
; inputs to some of the comparator pins.  Those inputs are the output of external op-amps
; acting as comparators – their outputs are read as digital inputs on RA3 and RA4
;

setup:

clrf    INTCON          ; disable all interrupts

call    setupClock      ; set system clock source and frequency

call    setupPortA      ; prepare Port A for I/O

call    setupPortB      ; prepare Port B for I/O

call    setupPortC      ; prepare Port C  for I/O

call    initializeOutputs

call    setupI2CMaster7BitMode ; prepare the I2C serial bus for use

call    initLCD

;start of hardware configuration

banksel OPTION_REG
movlw   0x58
movwf   OPTION_REG      ; Option Register = 0x58   0101 1000 b
; bit 7 = 0 : weak pull-ups are enabled by individual port latch values
; bit 6 = 1 : interrupt on rising edge
; bit 5 = 0 : TOCS ~ Timer 0 run by internal instruction cycle clock (CLKOUT ~ Fosc/4)
; bit 4 = 1 : TOSE ~ Timer 0 increment on high-to-low transition on RA4/T0CKI/CMP2 pin (not used here)
; bit 3 = 1 : PSA ~ Prescaler disabled; Timer0 will be 1:1 with Fosc/4
; bit 2 = 0 : Bits 2:0 control prescaler:
; bit 1 = 0 :    000 = 1:2 scaling for Timer0 (if enabled)
; bit 0 = 0 :

;end of hardware configuration

banksel flags

movlp   high reallyBigDelay     ; ready PCLATH for the calls below
call    reallyBigDelay
movlp   high setup              ; set PCLATH back to what it was

clrf   flags3

; enable the interrupts

bsf        INTCON,PEIE        ; enable peripheral interrupts (Timer0 and serial port are peripherals)
bsf     INTCON,T0IE     ; enable TMR0 interrupts
bsf     INTCON,GIE      ; enable all interrupts

return

; end of setup
;————————————————————————————————–

;————————————————————————————————–
; setupClock
;
; Sets up the system clock source and frequency.
;
; Assumes clock related configuration bits are set as follows:
;
;   _FOSC_INTOSC,  _CPUDIV_NOCLKDIV, _PLLMULT_4x, _PLLEN_DISABLED
;
; Assumes all programmable clock related options are at Reset default values.
;
; NOTE: Adjust I2C baud rate generator value when Fosc is changed.
;

setupClock:

; choose internal clock frequency of 16 Mhz

banksel OSCCON

bsf     OSCCON, IRCF0
bsf     OSCCON, IRCF1
bsf     OSCCON, IRCF2
bsf     OSCCON, IRCF3

return

; end of setupClock
;————————————————————————————————–

;————————————————————————————————–
; setupPortA
;
; Sets up Port A for I/O operation.
;
; NOTE: Writing to PORTA is same as writing to LATA for PIC16f1459. The code example from the
; data manual writes to both — probably to be compatible with other PIC chips.
;
; NOTE: RA0, RA1 and RA3 can only be inputs on the PIC16f1459 device.
;       RA2, RA6, RA7 are not implemented.
;

setupPortA:

banksel WPUA
movlw   b’00000000′                 ; disable weak pull-ups
movwf   WPUA

banksel PORTA
clrf    PORTA                       ; init port value

banksel LATA                        ; init port data latch
clrf    LATA

banksel ANSELA
clrf    ANSELA                      ; setup port for all digital I/O

; set I/O directions

banksel TRISA
movlw   b’11111111′                 ; first set all to inputs
movwf   TRISA

; set direction for each pin used

return

; end of setupPortA
;————————————————————————————————–

;————————————————————————————————–
; setupPortB
;
; Sets up Port B for I/O operation.
;
; NOTE: Writing to PORTB is same as writing to LATB for PIC16f1459. The code example from the
; data manual writes to both — probably to be compatible with other PIC chips.
;
; NOTE: RB0, RB1, RB2, RB3 are not implemented on the PIC16f1459 device.
;

setupPortB:

banksel WPUB
movlw   b’00000000′                 ; disable weak pull-ups
movwf   WPUB

banksel PORTB
clrf    PORTB                       ; init port value

banksel LATB                        ; init port data latch
clrf    LATB

banksel ANSELB
clrf    ANSELB                      ; setup port for all digital I/O

; set I/O directions

banksel TRISB
movlw   b’11111111′                 ; first set all to inputs
movwf   TRISB

bsf        TRISB, RB4                  ; set as input for use as I2C bus
bsf        TRISB, RB6                  ; set as input for use as I2C bus

return

; end of setupPortB
;————————————————————————————————–

;————————————————————————————————–
; setupPortC
;
; Sets up Port C for I/O operation.
;
; NOTE: Writing to PORTC is same as writing to LATC for PIC16f1459. The code example from the
; data manual writes to both — probably to be compatible with other PIC chips.
;

setupPortC:

; Port C does not have a weak pull-up register

banksel PORTC
clrf    PORTC                       ; init port value

banksel LATC                        ; init port data latch
clrf    LATC

banksel ANSELC
clrf    ANSELC                      ; setup port for all digital I/O

; set I/O directions

banksel TRISC
movlw   b’11111111′                 ; first set all to inputs
movwf   TRISC

return

; end of setupPortC
;————————————————————————————————–

;————————————————————————————————–
; initializeOutputs
;
; Initializes all outputs to known values.
;
; Write to port latches to avoid problems with read-modify-write modifying port bits due to
; line noise.
;

initializeOutputs:

return

; end of initializeOutpus
;————————————————————————————————–

;————————————————————————————————–
; setupI2CMaster7BitMode
;
; Sets the MASTER SYNCHRONOUS SERIAL PORT (MSSP) MODULE to the I2C Master mode using the 7 bit
; address mode.
;
; NOTE: RB4 and RB6 must have been configured elswhere as inputs for this mode.
;

setupI2CMaster7BitMode:

movlw   0x27            ; set baud rate at 100kHz for oscillator frequency of 16 Mhz
banksel SSP1ADD
movwf   SSP1ADD

banksel SSP1CON1
bcf    SSP1CON1,SSP1M0        ; SSPM = b1000 ~ I2C Master mode, clock = FOSC / (4 * (SSPADD+1))(4)
bcf    SSP1CON1,SSP1M1
bcf    SSP1CON1,SSP1M2
bsf    SSP1CON1,SSP1M3

bsf    SSP1CON1,SSPEN        ;enables the MSSP module

return

; end setupI2CMaster7BitMode
;————————————————————————————————–

;————————————————————————————————–
; initLCD
;
; Initialize the LCD display.
;
; See Dmcman_full-user manual.pdf from www.optrex.com for details.
;

initLCD:

banksel flags2
bcf     flags2, LCD_REG_SEL         ; select the LCD’s instruction register

bsf     flags2, LCD_BACKLIGHT_SEL   ; turn on the backlight

movlw   0x00                    ; init the I2C expander chip
call    sendNybbleToLCDViaI2C   ; RW = 0; BL = 0; EN = 0; RS = 0

movlw   .200                    ; delay at least 15 ms after Vcc = 4.5V
call    longCallDelayWms        ; delay plenty to allow power to stabilize

movlw    0x03                    ; 1st send of Function Set Command: (8-Bit interface)
call    sendNybbleToLCDViaI2C   ; (BF cannot be checked before this command.)

movlw   .6                      ; delay at least 4.1 mS
call    longCallDelayWms

movlw    0x03                    ; 2nd send of Function Set Command: (8-Bit interface)
call    sendNybbleToLCDViaI2C   ; (BF cannot be checked before this command.)

movlw   .6                      ; delay at least 100uS
call    longCallDelayWms

movlw    0x03                    ; 3rd send of Function Set Command: (8-Bit interface)
call    sendNybbleToLCDViaI2C   ; (BF can be checked after this command)

; it’s too complicated to check the LCD busy flag via the I2C expander bus, so care must be
; taken not to send data too quickly — as the I2C bus is relatively slow, this shouldn’t be
; a problem

movlw   0x02                    ; set to 4-bit interface
call    sendNybbleToLCDViaI2C

; from here on, two nybbles are sent for each transmission to send a complete byte

movlw   (LCD_FUNCTIONSET | LCD_2LINE)  ; 2 line display, 5×8 font
call    sendNybblesToLCDViaI2C

movlw    (LCD_DISPLAYCONTROL | LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF);
call    sendNybblesToLCDViaI2C

movlw   LCD_CLEARDISPLAY        ; debug mks — make this a function
call    sendNybblesToLCDViaI2C

movlw   .3                      ; delay at least 1.53 ms after Clear Display command
call    longCallDelayWms

; set the entry mode — cursor increments position (moves right) after each character
; display does not shift

movlw   (LCD_ENTRYMODESET | LCD_INC | LCD_NO_SHIFT);
call    sendNybblesToLCDViaI2C

movlw   LCD_RETURNHOME          ; set cursor at home position    debug mks — make this a function
call    sendNybblesToLCDViaI2C

movlw   .3                      ; delay at least 1.53 ms after Return Home command
call    longCallDelayWms

return

; end of initLCD
;————————————————————————————————–

;————————————————————————————————–
; sendNybblesToLCDViaI2C
;
; Sends a single byte to the LCD via the I2C bus one nybble at a time, upper nybble first.
;
; On entry:
;
; W should contain the nybbles to be written.
; Flags2.LCD_REG_SEL should be set 0 to access the instruction register or 1 for the data register
;
; On exit:
;
; The proper address~R/W code will be stored in scratch0.
; Variable scratch1 will be set to 2 to indicate a single byte.
; The nybbles to be written will be stored in scratch2:scratch3
; Indirect register FSR0 will point to scratch2.
;

sendNybblesToLCDViaI2C:

banksel  I2CScratch0

movwf    I2CScratch2             ; save for swapping and later retrieval of lower nybble
swapf    I2CScratch2,W           ; swap upper to lower

call     sendNybbleToLCDViaI2C   ; send upper nybble (now in lower, upper will be ignored)

banksel  I2CScratch2
movf     I2CScratch2,W              ; retrieve to send lower nybble

goto     sendNybbleToLCDViaI2C   ; send lower nybble (upper will be ignored)

; end of sendNybblesToLCDViaI2C
;————————————————————————————————–

;————————————————————————————————–
; sendNybbleToLCDViaI2C
;
; Sends a single nybble to the LCD via the I2C bus. The value is sent once to latch it into the
; outputs of the I2C expander chip and allow for setup time before strobing, then sent again with
; the LCD Enable line high, then again with the Enable line low to strobe the value into the LCD.
; The nybble being sent is in the upper half of WREG, the Enable line is controlled by a bit in the
; lower nybble.
;
; On entry:
;
; WREG lower nybble should contain value to be written. The upper nybble of WREG will be ignored.
; Flags2.LCD_REG_SEL should be set 0 to access the instruction register or 1 for the data register
;
; On exit:
;
; The proper address~R/W code will be stored in scratch0.
; Variable scratch1 will be set to 1 to indicate a single byte.
; The byte to be written will be stored in scratch2.
; Indirect register FSR0 will point to scratch2.
;

sendNybbleToLCDViaI2C:

banksel  I2CScratch0

andlw    0x0f                ; upper nybble to zeroes (these end up being the control lines)

movwf    I2CScratch4         ; store the value to be transmitted

swapf    I2CScratch4,F       ; swap so value is in upper nybble and control lines are in lower

btfsc    flags2,LCD_REG_SEL
bsf      I2CScratch4,LCD_RS

btfsc    flags2,LCD_BACKLIGHT_SEL
bsf      I2CScratch4,LCD_BACKLIGHT

movlw    high I2CScratch4    ; set pointer to location of nybble to be sent
movwf    FSR0H
movlw    low I2CScratch4
movwf    FSR0L

movlw    LCD_WRITE_ID        ; LCD’s I2C address with R/W bit set low
movwf    I2CScratch0

movlw    .1
movwf    I2CScratch1         ; set number of bytes to be transmitted
call     sendBytesViaI2C     ; send value to preset the data lines

bsf      I2CScratch4, LCD_EN    ; set the Enable line high in the value

movlw    .1                  ; send value again to set the Enable line high
movwf    I2CScratch1
addfsr   FSR0,-.1
call     sendBytesViaI2C

bcf      I2CScratch4, LCD_EN    ; set the Enable line low in the value

movlw    .1                  ; send again to set the Enable line low (strobes nybble into LCD)
movwf    I2CScratch1
addfsr   FSR0,-.1
call     sendBytesViaI2C

return

; end of sendNybbleToLCDViaI2C
;————————————————————————————————–

;————————————————————————————————–
; sendBytesToLCDViaI2C
;
; Sends bytes to the LCD via the I2C bus.
;
; The number of bytes to be written should be in scratch1.
; Indirect register FSR0 should point to first byte in RAM to be written.
;
; The proper address~R/W code will be stored in scratch0.
;

sendBytesToLCDViaI2C:

banksel  I2CScratch0
movlw    LCD_WRITE_ID        ; LCD’s I2C address with R/W bit set low
movwf    I2CScratch0

goto     sendBytesViaI2C

; end of sendBytesToLCDViaI2C
;————————————————————————————————–

;————————————————————————————————–
; sendBytesViaI2C
;
; Sends byte to the LED PIC via the I2C bus.
;
; On entry:
;
; The I2C address of the destination device with R/W bit (bit 0) set low should be in scratch0
; The number of bytes to be written should be in scratch1.
; Indirect register FSR0 should point to first byte in RAM to be written.
;

sendBytesViaI2C:

movlp   high clearSSP1IF        ; ready PCLATH for the calls below

call    clearSSP1IF             ; make sure flag is cleared before starting

call    generateI2CStart

banksel I2CScratch0
movf    I2CScratch0,W           ; I2C device address and R/W bit
call    sendI2CByte             ; send byte in W register on I2C bus after SSP1IF goes high

loopSBLP1:

movlp   high sendI2CByte        ; ready PCLATH for the call to sendI2CByte
moviw   FSR0++                  ; load next byte to be sent
call    sendI2CByte

movlp   high loopSBLP1          ; set PCLATH for the goto
banksel I2CScratch1
decfsz    I2CScratch1,F           ; count down number of bytes transferred
goto    loopSBLP1               ; not zero yet – transfer more bytes

movlp   high waitForSSP1IFHighThenClearIt   ; ready PCLATH for the calls below
call    waitForSSP1IFHighThenClearIt        ; wait for high flag upon transmission completion
call    generateI2CStop
call    waitForSSP1IFHighThenClearIt    ; wait for high flag upon stop condition finished
movlp   high sendBytesViaI2C            ; set PCLATH back to what it was on entry

return

; end of sendBytesViaI2C
;————————————————————————————————–

;————————————————————————————————–
; longCallDelayWms
;
; Sets the PCLATH register to allow a call to delayWms which is in a different memory page. Sets
; the register back to the local page after the call returns.
;

longCallDelayWms:

movlp   high delayWms
call    delayWms
movlp   longCallDelayWms

return

; end of longCallDelayWms
;————————————————————————————————–

;————————————————————————————————–
; handleInterrupt
;
; All interrupts call this function.  The interrupt flags must be polled to determine which
; interrupts actually need servicing.
;
; Note that after each interrupt type is handled, the interrupt handler returns without checking
; for other types.  If another type has been set, then it will immediately force a new call
; to the interrupt handler so that it will be handled.
;
; NOTE NOTE NOTE
; It is important to use no (or very few) subroutine calls.  The stack is only 16 deep and
; it is very bad for the interrupt routine to use it.
;

handleInterrupt:

; INTCON is a core register, no need to banksel
btfsc     INTCON, T0IF             ; Timer0 overflow interrupt?
call     handleTimer0Int         ; call so the serial port interrupts will get checked
;  if not, the timer interrupt can block them totally

banksel PIR1
btfsc   PIR1, RCIF              ; serial port receive interrupt
retfie                          ; does nothing in this program

banksel PIE1                    ; only handle UART xmt interrupt if enabled
btfss   PIE1, TXIE              ;  the TXIF flag is always set whenever the buffer is empty
retfie                          ;  and should be ignored unless the interrupt is enabled

banksel PIR1
btfsc   PIR1, TXIF              ; serial port transmit interrupt
retfie                          ; does nothing in this program

; Not used at this time to make interrupt handler as small as possible.
;    btfsc     INTCON, RBIF              ; NO, Change on PORTB interrupt?
;    goto     portB_interrupt           ; YES, Do PortB Change thing

INT_ERROR_LP1:                        ; NO, do error recovery
;GOTO INT_ERROR_LP1              ; This is the trap if you enter the ISR
; but there were no expected interrupts

endISR:

retfie                      ; Return and enable interrupts

; end of handleInterrupt
;————————————————————————————————–

;————————————————————————————————–
; handleTimer0Int
;
; This function is called when the Timer0 register overflows.
;
; TMR0 is never reloaded — thus it wraps around and does a full count for each interrupt.
;
; NOTE NOTE NOTE
; It is important to use no (or very few) subroutine calls.  The stack is only 16 deep and
; it is very bad for the interrupt routine to use it.
;

handleTimer0Int:

bcf     INTCON,T0IF     ; clear the Timer0 overflow interrupt flag

return

; end of handleTimer0Int
;————————————————————————————————–

;————————————————————————————————–
; Constants in Program Memory
;

; end of Constants in Program Memory
;————————————————————————————————–

;————————————————————————————————–
; Strings in Program Memory
;

warningStr  dw  ‘ ‘,’ ‘,’*’,’ ‘, ‘W’,’a’,’r’,’n’,’i’,’n’,’g’,’ ‘,’*’,0x00
errorStr    dw  ‘ ‘,’ ‘,’ ‘,’*’,’ ‘, ‘E’,’r’,’r’,’o’,’r’,’ ‘,’*’,0x00
clearStr    dw  ‘ ‘,’ ‘, ‘ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,’ ‘,0x00

string0        dw    ‘N’,’o’,’a’,’h’,’B’,’o’,’t’,’ ‘,’1′,’.’,’1′,0x00
string1        dw    ‘N’,’o’,’a’,’h’,’ ‘,’=’,’ ‘,’t’,’a’,’t’,’e’,’r’,’ ‘,’t’,’o’,’t’,0x00
string2        dw    ‘D’,’e’,’s’,’t’,’r’,’u’,’c’,’t’,’o’,’ ‘,’M’,’o’,’d’,’e’,0x00
string3        dw    ‘I’,’\”,’m’,’ ‘,’f’,’*’,’i’,’n’,’g’,’ ‘,’l’,’o’,’s’,’t’,’!’,0x00
string4        dw    ‘O’,0x00
string5        dw    ‘1’,0x00
string6        dw    ‘1’,0x00
string7        dw    ‘2’,0x00
string8        dw    ‘3’,0x00
string9        dw    ‘ ‘,0x00
string10    dw    ‘0’,0x00
string11    dw    ‘J’,0x00
string12    dw    ‘Z’,0x00
string13    dw    ‘O’,0x00
string14    dw    ‘T’,0x00
string15    dw    ‘U’,0x00
string16    dw    ‘D’,0x00
string17    dw    ‘N’,0x00
string18    dw    ‘W’,0x00
string19    dw    ‘C’,0x00
string20    dw    ‘5’,0x00
string21    dw    ‘4’,0x00
string22    dw    ‘N’,0x00
string23    dw    ‘R’,0x00
string24    dw    ‘6’,0x00
string25    dw    ‘7’,0x00
string26    dw    ‘N’,0x00
string27    dw    ‘1’,0x00
string28    dw    ‘O’,0x00
string29    dw    ‘O’,0x00

; end of Strings in Program Memory
;————————————————————————————————–

END

 

,

Leave a Reply

Your email address will not be published. Required fields are marked *