;
; 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