Professional Documents
Culture Documents
au
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
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 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.
Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 2
© Gooligum Electronics 2013 www.gooligum.com.au
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
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]
is equivalent to:
de 'H'
de 'e'
de 'l'
de 'l'
de 'o'
de 0
or
de 'H','e','l','l','o',0
; 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>
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
Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 6
© Gooligum Electronics 2013 www.gooligum.com.au
;***** 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
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
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
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
; configuration value
cfg_val DE 0x23 ; (single-byte)
; hardware revision
hw_rev DE 0x0E ; (single hex digit, A-F)
END
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
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
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>
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
Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 13
© Gooligum Electronics 2013 www.gooligum.com.au
;***** 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
; 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
Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 15
© Gooligum Electronics 2013 www.gooligum.com.au
goto w_mins
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
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
Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 17
© Gooligum Electronics 2013 www.gooligum.com.au
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.
Mid-range PIC Assembler, Lesson 19: Using EEPROM Data Memory Page 18