You are on page 1of 18

© Gooligum Electronics 2013 www.gooligum.com.

au

Introduction to PIC Programming


Mid-Range Architecture and Assembly Language

by David Meiklejohn, Gooligum Electronics

Lesson 19: Using EEPROM Data Memory

So far in this tutorial series we’ve seen that the mid-range PIC architecture has program memory, used to
store the program (the opcodes comprising the executable code we’ve programmed into the PIC), the special
function registers, which allow the program to control and interact with the PIC’s core functions and its
peripherals, and general purpose registers (or data memory) which your program can use to store the data it
is working on, in variables or arrays.
Unfortunately, that data memory loses its contents when the device loses power. What if we needed to retain
some data when the PIC is powered off? We saw in lesson 12 that fixed data, such as lookup tables, can be
held in program memory. But what about variable data? We might need to log some data for later retrieval.
Or perhaps our device needs to retain its state (not “forget” where it was up to) from one power cycle to the
next. Or maybe we’d like to store some device-specific configuration data, without having to alter the
program in each device.
This lesson introduces the EEPROM data memory, which allows us to store, modify and retrieve data which
is retained when the device is powered off.
In summary, this lesson covers:
 Introduction to the EEPROM data memory
 Using assembler directives to initialise EEPROM data
 Reading and writing EEPROM data

EEPROM Data Memory


An EEPROM is a type of memory which can be modified by a running program, but which will retain its
contents when powered off.
Unlike the flash memory used for program memory in modern PICs, individual bytes in an EEPROM can be
written to, without affecting any of the others1.
However, it’s not suitable as a replacement for data memory (the PIC’s general purpose registers) used for
short term working storage, because:
 writing to EEPROM is very slow – an EEPROM write operation on the PIC16F684 typically
requires 5 ms to complete, compared with less than 1 µs for data memory
 EEPROMs have a limited write endurance – each location can only be written a limited number of
times (typically 1 million on the PIC16F684) before it fails to retain new data

1
Program memory in PICs such as the 16F684 consists of NAND flash, which must be erased and written to a whole
block (some number of bytes) at a time.

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 1
© Gooligum Electronics 2013 www.gooligum.com.au

For these reasons, EEPROM data should only be updated sparingly, as necessary. A million writes sounds
like a lot, but if your application updated an EEPROM location every second, your device would typically
fail after 1 million seconds = 287 hours = 11.6 days. Your customers may not be happy with that…

The PIC16F684 includes a 256-byte EEPROM.


Technically speaking, it’s a peripheral device (like the other modules we’ve looked at, such as timers or
comparators), not part of the PIC’s core and not directly accessible.
Instead, the data in the EEPROM is accessed indirectly, using a pair of 8-bit registers: EEDAT and EEADR.

The EEDAT (EEPROM data) register acts as a window, through which a single byte of EEPROM data can
be read or written.
The EEADR (EEPROM address) register specifies which location within the EEPROM is to be accessed.
Since this is an 8-bit register, able to specify 256 addresses, it means that no more than 256 bytes of
EEPROM data can be accessed in the mid-range PIC architecture.

The EEPROM is controlled by the EECON1 register:

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0

EECON1 – – – – WRERR WREN WR RD

The RD bit is used to initiate a read operation.


After loading the address of the location we wish to read into EEADR, setting the RD bit will cause the
contents of that location to appear in the EEDAT register.
The RD bit clears itself when the read operation is complete; it can only be set – you cannot clear it yourself.

Similarly, the WR bit is used to initiate a write operation.


After loading the data we wish to write into EEDAT, and the address of the location we wish to update into
EEADR, a specific sequence of values (55h then AAh) must be written to the EECON2 register (used only
for this specific purpose within the write sequence), as shown in example 2, below. Setting the WR bit will
then initiate the write operation.
When the write operation is complete, the WR bit is cleared automatically, and the EE write complete
interrupt flag (EEIF) in the PIR1 register will be set.
To detect that the write is complete, we can poll the EEIF flag in the PIR1 register (having cleared it before
initiating the write operation), or, because the EEPROM write operation takes a long time (5 ms or so) to
complete and our program may have other things to do than poll a flag, we could enable EEPROM write
interrupts by setting the EEIE bit in the PIE1 register (and of course the PEIE and GIE bits in INTCON, as
we’ve done for other peripheral interrupts).
The write operation will only succeed if the WREN (write enable) bit is set. By default, on power-up, it is
cleared, to avoid accidental writes to the EEPROM.
Finally, the WRERR (write error) flag indicates whether the write operation completed successfully
(WRERR = 0), or if it did not complete because it was interrupted by a device reset (WRERR = 1). If you
check the WRERR flag after the write operation, and see that it had failed, you can repeat the write.

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 2
© Gooligum Electronics 2013 www.gooligum.com.au

Example 1: Reading EEPROM data


As mentioned earlier, EEPROMs are sometimes used to store configuration or other data which is
programmed into the device, and then later accessed by the running program.
For example, the Gooligum Programmable Christmas Star uses the EEPROM on a PIC12F683 to store the
codes representing the patterns to be displayed. Since these patterns are stored in EEPROM instead of
program memory, they can be updated independently of the program, meaning that the users do not need
access to the program code in order to upload their own patterns.
More commonly, you might want to store just a couple of values which configure some aspects of your
device that might change from installation to installation, such as default output values or input reference
levels. Or you might want to record the hardware revision, so that your program can check which version of
the hardware it is running in, and behave appropriately. There are many possibilities!
To illustrate this, we’ll use the 3-digit 7-segment display circuit from lesson 12:

To implement this circuit using the Gooligum training board, place shunts:
 across every position (all six of them) of jumper block JP4, connecting segments A-D, F and G to
pins RA0-1 and RC1-4
 in position 1 (‘RA/RB4’) of JP5, connecting segment E to pin RA4
 across pins 2 and 3 (‘RC5’) of JP6, connecting digit 1 to the transistor controlled by RC5
 in jumpers JP8, JP9 and JP10, connecting pins RC5, RA5 and RC0 to their respective transistors
All other shunts should be removed.

We’ll store a configuration value as a single byte, which we’ll display in hex on digits 1 and 2, and the
hardware revision as a letter between A and F, which we’ll display as a single hex digit on digit 3.
Of course a real application wouldn’t display these configuration values; it would act on them. But for this
example the idea is to show how to program data into the EEPROM and then read it – and to see that most
clearly, we should keep the program as simple as possible.

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 3
© Gooligum Electronics 2013 www.gooligum.com.au

Firstly, we need to specify the values to be programmed into the EEPROM.

The assembler (MPASM) provides a directive for this: ‘de’ (“declare EEPROM data byte”).
It is used in a very similar way to the dt (define table) directive introduced in lesson 12.
Its syntax is:
[label] de expr1[,expr2,…,exprN]

where each expression is an 8-bit value.


It can also be used with text strings, for example:
de "Hello",0

is equivalent to:
de 'H'
de 'e'
de 'l'
de 'l'
de 'o'
de 0

or
de 'H','e','l','l','o',0

In the mid-range PIC architecture, the EEPROM is located at address 2100h.


We can use the CODE directive to place the EEPROM data, specified by de directives, at this address, for
example:
;***** EEPROM DATA ******************************************************
EEPROM CODE 0x2100

; configuration value
cfg_val DE 0x23 ; (single-byte)

; hardware revision
hw_rev DE 0x0E ; (single hex digit, A-F)

The first de directive specifies that the value 23h (you can use any single-byte value you want, here) will be
programmed into the start of the EEPROM (address 0).
The second de directive specifies that the value Eh (this could be any value from 0 to F, but in keeping with
the intent of this example, it should be a value between A and F) will be loaded into the next EEPROM
location (address 1).
The labels on the ‘de’ directives are optional, but our code will be much easier to maintain if we can refer to
these locations by name instead of their numeric addresses.

To read a byte of data from the EEPROM, we first load its address into EEADR:
movlw LOW cfg_val ; load address of config value
banksel EEADR
movwf EEADR

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 4
© Gooligum Electronics 2013 www.gooligum.com.au

Note the use of the ‘LOW’ assembler directive to load only the lower eight bits of the address into EEADR;
given that the EEPROM can only hold 256 bytes and EEADR is an 8-bit register, only the lower eight bits of
the address are relevant.

We can now initiate the EEPROM read operation, by setting the RD bit:
bsf EECON1,RD ; initiate read

At this point, our byte of EEPROM data is available in the EEDAT register, and we can access it via an
instruction such as:
movf EEDAT,w

Once again, in a real application, you’d typically use a configuration value read from an EEPROM like this
to set some parameter to a default value, perhaps storing it in a variable for later access. But in this example,
we’ll simply display it (in hexadecimal) on digits 1 and 2, with the most significant nybble (“tens”) in digit 1.
To do this, we can adapt code from the hexadecimal output example in lesson 13.
We’ll store the digits to be displayed in single-byte variables: digit1, digit2 and digit3.
The content of these variables is then displayed in the background by an ISR driven by Timer0.
To extract the hex digits from the value in EEDAT and copy them to variables for the ISR to display, we can
adapt the corresponding routine from the hex output example in lesson 13:
movf EEDAT,w ; get ones digit of config value
andlw 0x0F ; from low nybble of EEPROM data
banksel digit2 ; and display it in digit 2
movwf digit2
banksel EEDAT
swapf EEDAT,w ; get "tens" digit of config value
andlw 0x0F ; from high nybble of EEPROM data
banksel digit1 ; and display it in digit 1
movwf digit1

Similarly, to read and display the hardware revision as a single hex digit on digit 3, we have:
; read and display hardware revision
movlw LOW hw_rev ; load address of hardware revision value
banksel EEADR
movwf EEADR
bsf EECON1,RD ; initiate read
movf EEDAT,w ; get hardware revision value from EEPROM
andlw 0x0F ; (mask it to ensure 0-F range)
banksel digit3 ; and display it in digit 3
movwf digit3

Note the use of ‘andlw 0x0F’ (a masking operation) to ensure that the value copied to digit3 is restricted
to the single hex digit range (0h – Fh). Without this precaution, if you specified a value greater than 0Fh for
‘hw_rev’ in the EEPROM, the code would attempt to lookup patterns beyond the end of the 7-segment
pattern tables, with potentially disastrous results.
In general, if you are going to use the EEPROM to store configuration data, it can be a good idea to include
some bounds-checking code in your application, to handle cases where the EEPROM has been programmed
with configuration values outside their acceptable range.

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 5
© Gooligum Electronics 2013 www.gooligum.com.au

Complete program
Most of the code will be familiar from the examples in lessons 12 and 13, but it’s worth seeing the full listing
to see where the EEPROM read routines and data declarations fit in:
;************************************************************************
; Description: Lesson 19, example 1 *
; *
; Demonstrates use of DE directive to declare EEPROM data *
; and EEPROM read operations *
; *
; Configuration data stored in EEPROM is displayed on 3-digit *
; 7-segment LED display: *
; "configuration value" in address 0 is displayed as *
; 2 x hex digits ("tens" in digits 1, "ones" in digit 2) *
; "hardware revision" (A-F) in address 1 is displayed as *
; 1 x hex digit (digit 3) *
; *
;************************************************************************
; *
; Pin assignments: *
; RA0-1,RA4, RC1-4 = 7-segment display bus (common cathode) *
; RC5 = digit 1 enable (active high) *
; RA5 = digit 2 enable *
; RC0 = digit 3 enable *
; *
;************************************************************************

list p=16F684
#include <p16F684.inc>

#include <stdmacros-mid.inc> ; Lookup table,off_var - lookup table entry


; at offset held
; in off_var

errorlevel -302 ; no "register not in bank 0" warnings


errorlevel -312 ; no "page or bank selection not needed" messages

radix dec

;***** CONFIGURATION
; ext reset, no code or data protect, no brownout detect,
; no watchdog, power-up timer, int clock with I/O,
; no failsafe clock monitor, two-speed start-up disabled
__CONFIG _MCLRE_ON & _CP_OFF & _CPD_OFF & _BOD_OFF & _WDT_OFF & _PWRTE_ON
& _INTOSCIO & _FCMEN_OFF & _IESO_OFF

; pin assignments
#define sDIG1 sPORTC,5 ; digit 1 enable (shadow)
#define sDIG2 sPORTA,5 ; digit 2 enable
#define sDIG3 sPORTC,0 ; digit 3 enable

;***** VARIABLE DEFINITIONS


CONTEXT UDATA_SHR ; variables used for context saving
cs_W res 1
cs_STATUS res 1

SHADOW UDATA_SHR ; shadow registers


sPORTA res 1 ; PORTA
sPORTC res 1 ; PORTC

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 6
© Gooligum Electronics 2013 www.gooligum.com.au

GENVAR UDATA ; general variables


mpx_cnt res 1 ; multiplex counter
digit1 res 1 ; display variables
digit2 res 1
digit3 res 1

;***** RESET VECTOR *****************************************************


RESET CODE 0x0000 ; processor reset vector
pagesel start
goto start

;***** MAIN PROGRAM *****************************************************


MAIN CODE

;***** Initialisation
start
; configure ports
banksel TRISA ; configure PORTA and PORTC as all outputs
clrf TRISA
clrf TRISC

; configure timer
movlw b'11000010' ; configure Timer0:
; --0----- timer mode (T0CS = 0)
; ----0--- prescaler assigned to Timer0 (PSA = 0)
; -----010 prescale = 8 (PS = 010)
banksel OPTION_REG ; -> increment TMR0 every 8 us
movwf OPTION_REG ; -> TMR0 overflows every 2.048 ms

; initialise variables
banksel mpx_cnt
clrf mpx_cnt ; display ones digit first

;*** read config data from EEPROM


;
; read and display configuration value
movlw LOW cfg_val ; load address of config value
banksel EEADR
movwf EEADR
bsf EECON1,RD ; initiate read
movf EEDAT,w ; get ones digit of config value
andlw 0x0F ; from low nybble of EEPROM data
banksel digit2 ; and display it in digit 2
movwf digit2
banksel EEDAT
swapf EEDAT,w ; get "tens" digit of config value
andlw 0x0F ; from high nybble of EEPROM data
banksel digit1 ; and display it in digit 1
movwf digit1
;
; read and display hardware revision
movlw LOW hw_rev ; load address of hardware revision value
banksel EEADR
movwf EEADR
bsf EECON1,RD ; initiate read
movf EEDAT,w ; get hardware revision value from EEPROM
andlw 0x0F ; (mask it to ensure 0-F range)
banksel digit3 ; and display it in digit 3
movwf digit3

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 7
© Gooligum Electronics 2013 www.gooligum.com.au

; enable interrupts
movlw 1<<GIE|1<<T0IE ; enable Timer0 and global interrupts
movwf INTCON

;***** Main loop


main_loop
; do nothing
goto main_loop

;***** INTERRUPT SERVICE ROUTINE ****************************************


ISR CODE 0x0004
; *** Save context
movwf cs_W ; save W
movf STATUS,w ; save STATUS
movwf cs_STATUS

; *** Service Timer0 interrupt


;
; TMR0 overflows every 2.048 ms
;
; Displays digits on 7-segment displays
;
; (only Timer0 interrupts are enabled)
;
bcf INTCON,T0IF ; clear interrupt flag

; Display current count on 3 x 7-segment displays


; mpx_cnt determines current digit to display
;
banksel mpx_cnt
incf mpx_cnt,f ; increment mpx_cnt for next digit
movf mpx_cnt,w ; and copy to W
; determine current mpx_cnt by successive subtraction
addlw -1
btfsc STATUS,Z ; if current mpx_cnt = 0
goto dsp_dig1 ; display digit 1
addlw -1
btfsc STATUS,Z ; if current mpx_cnt = 1
goto dsp_dig2 ; display digit 2
clrf mpx_cnt ; else mpx_cnt = 2, so reset to 0
goto dsp_dig3 ; and display digit 3

dsp_dig1
; display digit 1
Lookup tb7segA,digit1 ; lookup pattern for PORTA
movwf sPORTA ; then output it
Lookup tb7segC,digit1 ; repeat for PORTC
movwf sPORTC
bsf sDIG1 ; enable digit 1 display
goto dsp_end

dsp_dig2
; display digit 2
Lookup tb7segA,digit2 ; lookup pattern for PORTA
movwf sPORTA ; then output it
Lookup tb7segC,digit2 ; repeat for PORTC
movwf sPORTC
bsf sDIG2 ; enable digit 2 display
goto dsp_end

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 8
© Gooligum Electronics 2013 www.gooligum.com.au

dsp_dig3
; display digit 3
Lookup tb7segA,digit3 ; lookup pattern for PORTA
movwf sPORTA ; then output it
Lookup tb7segC,digit3 ; repeat for PORTC
movwf sPORTC
bsf sDIG3 ; enable digit 3 display
dsp_end

; copy shadow regs to ports


banksel PORTA
movf sPORTA,w
movwf PORTA
movf sPORTC,w
movwf PORTC

isr_end ; *** Restore context then return


movf cs_STATUS,w ; restore STATUS
movwf STATUS
swapf cs_W,f ; restore W
swapf cs_W,w
retfie

;***** LOOKUP TABLES ****************************************************


TABLES CODE

; pattern table for 7 segment display on port A


; RA4 = E, RA1:0 = FG
tb7segA movwf PCL
retlw b'010010' ; 0
retlw b'000000' ; 1
retlw b'010001' ; 2
retlw b'000001' ; 3
retlw b'000011' ; 4
retlw b'000011' ; 5
retlw b'010011' ; 6
retlw b'000000' ; 7
retlw b'010011' ; 8
retlw b'000011' ; 9
retlw b'010011' ; A
retlw b'010011' ; b
retlw b'010010' ; C
retlw b'010001' ; d
retlw b'010011' ; E
retlw b'010011' ; F

; pattern table for 7 segment display on port C


; RC4:1 = CDBA
tb7segC movwf PCL
retlw b'011110' ; 0
retlw b'010100' ; 1
retlw b'001110' ; 2
retlw b'011110' ; 3
retlw b'010100' ; 4
retlw b'011010' ; 5
retlw b'011010' ; 6
retlw b'010110' ; 7
retlw b'011110' ; 8
retlw b'011110' ; 9
retlw b'010110' ; A

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 9
© Gooligum Electronics 2013 www.gooligum.com.au

retlw b'011000' ; b
retlw b'001010' ; C
retlw b'011100' ; d
retlw b'001010' ; E
retlw b'000010' ; F

;***** EEPROM DATA ******************************************************


EEPROM CODE 0x2100

; configuration value
cfg_val DE 0x23 ; (single-byte)

; hardware revision
hw_rev DE 0x0E ; (single hex digit, A-F)

END

Example 2: Writing EEPROM data


One of the key features of an EEPROM is that it can be written to at run-time. You might do this
periodically to log data, or to record changed configuration settings – or any situation where you need to
record variable data which will be retained when the PIC is powered off.
To illustrate this, we’ll re-implement the 3-digit timer from lesson 12, using the same circuit as in the
previous example.
In lesson 12, the timer counted minutes and seconds, starting from 0:00 every time it was powered on.
We will modify it so that, if the power is removed, it does not forget the current count; when it is powered on
again, it will continue from the previous count.
To achieve this, we will store a copy of the current count in the EEPROM, updating it every second2.

This means that, whenever the program starts, we need to read the stored count from the EEPROM.
The count is stored as minutes in one single-byte variable and seconds in another, so first we retrieve the
stored minutes:
movlw LOW ee_mins ; load address of stored minutes
banksel EEADR
movwf EEADR
bsf EECON1,RD ; initiate read
movf EEDAT,w
banksel mins ; mins = stored minutes value
movwf mins

2
As mentioned earlier, the EEPROM’s limited write endurance (typically 1 million on the PIC16F684) means that this
is a poor strategy – if we update the same location every second, the EEPROM would typically fail by around 12 days
of continual use. One way to alleviate this problem is to spread the “damage”, by successively writing to different
locations in the EEPROM, so that no single location fails early from being written more times than the rest of the
EEPROM. A better solution, if we want to preserve data when the device is powered off, is to detect the imminent loss
of power and to only update the EEPROM at that point. However, to do so requires hardware support (e.g. power rail
monitoring and a capacitor large enough to hold the MCU up long enough to complete the EEPROM write), and
illustrating that would take away from the mechanics of writing to the EEPROM, which we are trying to demonstrate.

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 10
© Gooligum Electronics 2013 www.gooligum.com.au

and then the seconds:


movlw LOW ee_secs ; load address of stored seconds
banksel EEADR
movwf EEADR
bsf EECON1,RD ; initiate read
movf EEDAT,w
banksel secs ; secs = stored seconds value
movwf secs

in much the same way as in the first example, above.

We need to program initial values into the EEPROM, so that when the program first runs the count will start
at some specified value (let’s say 0:00).
Again, this is done in the same was as in the first example:
;***** EEPROM DATA ******************************************************
EEPROM CODE 0x2100

; stored count (BCD format)


ee_mins DE 0 ; initial minutes = 0
ee_secs DE 0 ; initial seconds = 0
; (start with count = 0:00)

So far, this is no different from the previous example. We specify initial values to be programmed into the
EEPROM, and read them when our program starts.
But in this example, we’re going to update the values stored in the EEPROM, whenever the count changes.
Recall that, by default, the EEPROM is protected from write operations. So, as part of our initialisation
code, we need to write-enable it, by setting the WREN bit in EECON1:
banksel EECON1 ; enable EEPROM writes
bsf EECON1,WREN

After incrementing the count, we need to write the updated seconds value to the EEPROM.
First, we copy the byte of data that we wish to write to EEDAT:
banksel secs ; copy seconds value to data register
movf secs,w
banksel EEDAT
movwf EEDAT

We also need to load the address of the EEPROM location we wish to update into EEADR:
movlw LOW ee_secs ; load address of seconds storage
movwf EEADR

Now that we have the data in EEDAT, and the address to write it to in EEADR, it would be nice if we could
simply set the WR bit to write that data to that address.
Unfortunately, EEPROM writes are not as simple as reads. First, we have to perform a very specific
sequence of writes to the EECON2 register – and that sequence must not be interrupted!
This means that, before we perform that write sequence, we must disable interrupts.

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 11
© Gooligum Electronics 2013 www.gooligum.com.au

In this example, only Timer0 interrupts are enabled; we can disable them by clearing the T0IE flag3:
bcf INTCON,T0IE ; disable Timer0 interrupt

When we are sure that interrupts are not running, we can go ahead and initiate the EEPROM write operation,
with this specific sequence:
movlw 0x55 ; unlock write
movwf EECON2
movlw 0xAA
movwf EECON2
bsf EECON1,WR ; initiate write

The write operation will now begin, and it is safe to re-enable interrupts:
bsf INTCON,T0IE ; re-enable Timer0 interrupt

The write operation takes some time (around 5 ms) to complete. We could carry on and perform other
processing, but the next thing we need to do is another EEPROM write operation, and we can’t commence a
second write until the first is finished.
The WR bit is cleared when the EEPROM write completes, so we can wait for the EEPROM write to finish
by polling WR:
w_secs btfsc EECON1,WR ; wait for write to finish
goto w_secs

Alternatively, we could poll the EEIF bit (in the PIR register), which is set whenever an EEPROM write
completes. But since it’s not cleared automatically, polling EEIF requires a ‘bcf PIR,EEIF’ operation
before the EEPROM write is initiated – polling the WR bit is easier4.

We can now store the new minutes value in EEPROM, in the same way:
; store minutes
banksel mins ; copy minutes value to data register
movf mins,w
banksel EEDAT
movwf EEDAT
movlw LOW ee_mins ; load address of minutes storage
movwf EEADR
bcf INTCON,T0IE ; disable Timer0 interrupt
movlw 0x55 ; unlock write
movwf EECON2
movlw 0xAA
movwf EECON2
bsf EECON1,WR ; initiate write
bsf INTCON,T0IE ; re-enable Timer0 interrupt
w_mins btfsc EECON1,WR ; wait for write to finish
goto w_mins

3
To disable all interrupts you can of course clear the global interrupt enable (GIE) bit, but attempting to do so with a
single ‘bcf INTCON,GIE’ instruction is not guaranteed to always work, as explained in detail in application note
AN576, available on www.microchip.com. Clearing individual interrupt enable bits, as done here, is always reliable.
4
The EEIF bit is really only relevant if you are using EEPROM write interrupts, which we won’t cover in this lesson
(they work the same way as the other interrupt sources we have described).

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 12
© Gooligum Electronics 2013 www.gooligum.com.au

Complete program
Most of the program code is taken from the BCD version of the 3-digit timer example in lesson 12, but it’s
worth seeing the full listing to see how the EEPROM read and write routines fit in:
;************************************************************************
; *
; Description: Lesson 19, example 2 *
; *
; Demonstrates EEPROM data write operations *
; *
; 3 digit 7-segment LED display: 1 digit minutes, 2 digit seconds *
; counts in seconds 0:00 to 9:59 then repeats. *
; *
; The initial count is read from EEPROM. *
; The count is written to EEPROM whenever it is updated (each second) *
; *
;************************************************************************
; *
; Pin assignments: *
; RA0-1,RA4, RC1-4 = 7-segment display bus (common cathode) *
; RC5 = minutes digit enable (active high) *
; RA5 = tens digit enable *
; RC0 = ones digit enable *
; *
;************************************************************************

list p=16F684
#include <p16F684.inc>

#include <stdmacros-mid.inc> ; DelayMS - delay in ms


; (calls delay10)
; Lookup table,off_var - lookup table entry
; at offset held
; in off_var

EXTERN delay10 ; W x 10 ms delay

errorlevel -302 ; no "register not in bank 0" warnings


errorlevel -312 ; no "page or bank selection not needed" messages

radix dec

;***** CONFIGURATION
; ext reset, no code or data protect, no brownout detect,
; no watchdog, power-up timer, int clock with I/O,
; no failsafe clock monitor, two-speed start-up disabled
__CONFIG _MCLRE_ON & _CP_OFF & _CPD_OFF & _BOD_OFF & _WDT_OFF & _PWRTE_ON
& _INTOSCIO & _FCMEN_OFF & _IESO_OFF

; pin assignments
#define sMINS sPORTC,5 ; minutes digit enable (shadow)
#define sTENS sPORTA,5 ; tens digit enable
#define sONES sPORTC,0 ; ones digit enable

;***** VARIABLE DEFINITIONS


CONTEXT UDATA_SHR ; variables used for context saving
cs_W res 1
cs_STATUS res 1

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 13
© Gooligum Electronics 2013 www.gooligum.com.au

SHADOW UDATA_SHR ; shadow registers


sPORTA res 1 ; PORTA
sPORTC res 1 ; PORTC

GENVAR UDATA ; general variables


mpx_cnt res 1 ; multiplex counter
mins res 1 ; current count: minutes
secs res 1 ; seconds (BCD)
digit res 1 ; digit to display (extracted from secs)

;***** RESET VECTOR *****************************************************


RESET CODE 0x0000 ; processor reset vector
pagesel start
goto start

;***** MAIN PROGRAM *****************************************************


MAIN CODE

;***** Initialisation
start
; configure ports
banksel TRISA ; configure PORTA and PORTC as all outputs
clrf TRISA
clrf TRISC

; configure timer
movlw b'11000010' ; configure Timer0:
; --0----- timer mode (T0CS = 0)
; ----0--- prescaler assigned to Timer0 (PSA = 0)
; -----010 prescale = 8 (PS = 010)
banksel OPTION_REG ; -> increment TMR0 every 8 us
movwf OPTION_REG ; -> TMR0 overflows every 2.048 ms

; initialise variables
banksel mpx_cnt
clrf mpx_cnt ; display ones digit first

; get stored count from EEPROM


movlw LOW ee_mins ; load address of stored minutes
banksel EEADR
movwf EEADR
bsf EECON1,RD ; initiate read
movf EEDAT,w
banksel mins ; mins = stored minutes value
movwf mins
movlw LOW ee_secs ; load address of stored seconds
banksel EEADR
movwf EEADR
bsf EECON1,RD ; initiate read
movf EEDAT,w
banksel secs ; secs = stored seconds value
movwf secs

; configure EEPROM
banksel EECON1 ; enable EEPROM writes
bsf EECON1,WREN

; enable interrupts
movlw 1<<GIE|1<<T0IE ; enable Timer0 and global interrupts
movwf INTCON

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 14
© Gooligum Electronics 2013 www.gooligum.com.au

;***** Main loop


main_loop
;*** Delay 1 sec
DelayMS 1000 ; delay 1s

;*** Increment counters


banksel secs
incf secs,f ; increment seconds
movf secs,w ; if ones overflow,
andlw 0x0F
xorlw .10
btfss STATUS,Z
goto inc_end
movlw .6 ; BCD adjust seconds
addwf secs,f
movlw 0x60
xorwf secs,w ; if seconds = 60,
btfss STATUS,Z
goto inc_end
clrf secs ; reset seconds to 0
incf mins,f ; and increment minutes
movlw .10
xorwf mins,w ; if minutes overflow,
btfsc STATUS,Z
clrf mins ; reset minutes to 0
inc_end

;*** Store updated count in EEPROM


;
; store seconds
banksel secs ; copy seconds value to data register
movf secs,w
banksel EEDAT
movwf EEDAT
movlw LOW ee_secs ; load address of seconds storage
movwf EEADR
bcf INTCON,T0IE ; disable Timer0 interrupt
movlw 0x55 ; unlock write
movwf EECON2
movlw 0xAA
movwf EECON2
bsf EECON1,WR ; initiate write
bsf INTCON,T0IE ; re-enable Timer0 interrupt
w_secs btfsc EECON1,WR ; wait for write to finish
goto w_secs
;
; store minutes
banksel mins ; copy minutes value to data register
movf mins,w
banksel EEDAT
movwf EEDAT
movlw LOW ee_mins ; load address of minutes storage
movwf EEADR
bcf INTCON,T0IE ; disable Timer0 interrupt
movlw 0x55 ; unlock write
movwf EECON2
movlw 0xAA
movwf EECON2
bsf EECON1,WR ; initiate write
bsf INTCON,T0IE ; re-enable Timer0 interrupt
w_mins btfsc EECON1,WR ; wait for write to finish

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 15
© Gooligum Electronics 2013 www.gooligum.com.au

goto w_mins

;*** Repeat forever


pagesel main_loop
goto main_loop

;***** INTERRUPT SERVICE ROUTINE ****************************************


ISR CODE 0x0004
;*** Save context
movwf cs_W ; save W
movf STATUS,w ; save STATUS
movwf cs_STATUS

;*** Service Timer0 interrupt


;
; TMR0 overflows every 2.048 ms
;
; Displays current count on 7-segment displays
;
; (only Timer0 interrupts are enabled)
;
bcf INTCON,T0IF ; clear interrupt flag

; Display current count on 3 x 7-segment displays


; mpx_cnt determines current digit to display
;
banksel mpx_cnt
incf mpx_cnt,f ; increment mpx_cnt for next digit
movf mpx_cnt,w ; and copy to W
; determine current mpx_cnt by successive subtraction
addlw -1
btfsc STATUS,Z ; if current mpx_cnt = 0
goto dsp_ones ; display ones digit
addlw -1
btfsc STATUS,Z ; if current mpx_cnt = 1
goto dsp_tens ; display tens digit
clrf mpx_cnt ; else mpx_cnt = 2, so reset to 0
goto dsp_mins ; and display minutes digit

dsp_ones
; display ones digit
movf secs,w ; get ones digit
andlw 0x0F ; from low nybble of seconds
movwf digit
Lookup tb7segA,digit ; lookup ones pattern for PORTA
movwf sPORTA ; then output it
Lookup tb7segC,digit ; repeat for PORTC
movwf sPORTC
bsf sONES ; enable ones display
goto dsp_end

dsp_tens
; display tens digit
swapf secs,w ; get tens digit
andlw 0x0F ; from high nybble of seconds
movwf digit
Lookup tb7segA,digit ; output tens digit
movwf sPORTA
Lookup tb7segC,digit
movwf sPORTC

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 16
© Gooligum Electronics 2013 www.gooligum.com.au

bsf sTENS ; enable tens display


goto dsp_end

dsp_mins
; display minutes digit
Lookup tb7segA,mins ; output minutes digit
movwf sPORTA
Lookup tb7segC,mins
movwf sPORTC
bsf sMINS ; enable minutes display
dsp_end

; copy shadow regs to ports


banksel PORTA
movf sPORTA,w
movwf PORTA
movf sPORTC,w
movwf PORTC

isr_end ;*** Restore context then return


movf cs_STATUS,w ; restore STATUS
movwf STATUS
swapf cs_W,f ; restore W
swapf cs_W,w
retfie

;***** LOOKUP TABLES ****************************************************


TABLES CODE

; pattern table for 7 segment display on port A


; RA4 = E, RA1:0 = FG
tb7segA movwf PCL
retlw b'010010' ; 0
retlw b'000000' ; 1
retlw b'010001' ; 2
retlw b'000001' ; 3
retlw b'000011' ; 4
retlw b'000011' ; 5
retlw b'010011' ; 6
retlw b'000000' ; 7
retlw b'010011' ; 8
retlw b'000011' ; 9

; pattern table for 7 segment display on port C


; RC4:1 = CDBA
tb7segC movwf PCL
retlw b'011110' ; 0
retlw b'010100' ; 1
retlw b'001110' ; 2
retlw b'011110' ; 3
retlw b'010100' ; 4
retlw b'011010' ; 5
retlw b'011010' ; 6
retlw b'010110' ; 7
retlw b'011110' ; 8
retlw b'011110' ; 9

;***** EEPROM DATA ******************************************************


EEPROM CODE 0x2100

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 17
© Gooligum Electronics 2013 www.gooligum.com.au

; stored count (BCD format)


ee_mins DE 0 ; initial minutes = 0
ee_secs DE 0 ; initial seconds = 0
; (start with count = 0:00)

END

Conclusion
As we’ve seen in this lesson, using the EEPROM is not complicated and a couple of examples are all we
need to demonstrate initialisation, read and write operations – although as we’ve seen care has to be taken to
use the correct sequence of instructions to successfully complete an EEPROM write.

We have now described every major feature of the PIC16F684, concluding our introduction to the “classic”
mid-range PIC architecture.
Although some other, more advanced mid-range devices, such as the 16F690 and 16F887, include additional
peripherals, notably USART and MSSP modules used for serial communications, including SPI and I2C
interfacing, the original mid-range architecture described in this series of lessons has become dated. The
newer “enhanced mid-range” PIC architecture offers increased functionality, at a lower cost.
A new series of lessons will explore the enhanced mid-range PIC architecture, revisiting the topics covered
in this “classic” mid-range series (showing how many operations have been simplified), before moving on to
more advanced peripherals, including serial communications.

Until then, enjoy programming mid-range PICs!

Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 18

You might also like