Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PIC16f877 EEPROM Write

Status
Not open for further replies.

Olshansk

New Member
I have already read several forsums regarding this subject but I still cannot get my code to work. I followed the steps outline in the PIC16f877 data sheet but still cannot write to EEPROM.

Here is my code:

EEPROM_Write macro ADDR, VALUE;
bcf STATUS, RP0
bsf STATUS, RP1
movf ADDR, W
movwf EEADR
movf VALUE, W
movwf EEDATA
bsf STATUS, RP0
bcf EECON1, EEPGD
bsf EECON1, WREN
bcf STATUS, RP1
bcf INTCON, GIE
bsf STATUS, RP1

movlw 0x55
movwf EECON2
movlw 0xAA
movwf EECON2
bsf EECON1, WR

bsf INTCON, GIE

bcf EECON1, WREN

bcf STATUS, RP1
bcf STATUS, RP0

endm

Since this is a macro, I call it in the following way:
movlw 0x00
movwf ADDR
movlw 0x00
movwf VALUE
EEPROM_Write ADDR, VALUE

And in the beginning of my program I do this to reserve space for ADDr and VALUE:

udata
Table_Counter res 1
Delay1 res 1
Delay2 res 1
Delay3 res 1
ADDR res 1
VALUE res 1


Does anyone see anything wrong with any of this? Any help is appreciated.
 
This should not be done as a macro. It should be done as a subroutine. Canning it as a macro will just insert the EEPROM write routine in line everytime you invoke the macro whereas canning it as a subroutine will save precious code space as the routine will only have to be in code memory once.

Second thing...you should set up buffer registers for the write address and data in the common RAM space (addresses 0x70-0x7F), then preload the address and data into these registers prior to calling the write subroutine. Setting them up in the common RAM space will make it so that you don't have to bank select when transferring the data into EEADR and EEDATA.

Third thing...after setting bit WR, you need to poll WR and wait for it to go clear before clearing WREN. Bit WR gets cleared by the hardware upon completion of the write. So you poll WR until it goes clear so you know when it's done writing, THEN you disable write mode by clearing bit WREN. The data sheet does not reflect this for whatever reason, but it does need to be done in order for EEPROM write to work.

All this being said, try this code -

Code:
DATA_EE_ADDR		EQU		0x70			;place at top of code
DATA_EE_DATA		EQU		0x71			;place at top of code

;EEPROM write subroutine (place with other subroutines)

EEPROM_Write		bcf		INTCON,GIE		;disable interrupts for EEPROM write
			banksel		EEADR			;bank 2
			movfw		DATA_EE_ADDR		;load write address to EEPROM address pointer
			movwf		EEADR
			movfw		DATA_EE_DATA		;load write data to EEPROM data buffer
			movwf		EEDATA
			clrf		EEADRH			;clear hi byte of EEPROM address pointer
			clrf		EEDATH			;clear hi byte of EEPROM data buffer
			banksel		EECON1			;bank 3
			bcf 		EECON1,EEPGD		;write to data space
			bsf 		EECON1,WREN 		;enable EEPROM write
			movlw 		0x55 			;unlock codes
			movwf 		EECON2 
			movlw		0xAA 
			movwf 		EECON2 
			bsf 		EECON1,WR		;enable EEPROM write
			btfsc		EECON1,WR 		;wait here until write completes
			goto		$-1
			bcf 		EECON1,WREN		;write complete, disable write mode 
			banksel		0			;bank 0
			bcf		PIR2,EEIF		;clear EEPROM write interrupt flag
			movlw		0			;are interrupts being used?
			xorwf		INTCON,W
			btfss		STATUS,Z
			bsf		INTCON,GIE		;yes,enable interrupts
			return

Then to use it, run this code -

Code:
			movlw		0x00			;load write address to EEPROM address pointer
			movwf		DATA_EE_ADDR
			movlw		0x00			;load write data to EEPROM data buffer
			movwf		DATA_EE_DATA
			call		EEPROM_Write		;write data to EEPROM

This will drastically minimize how much code space gets used as the EEPROM write routine only needs to reside in code space once rather than in every place you would need to invoke the macro.

Let us know how it works.
 
Last edited:
Thank you so much Jon!

All the things you said (and the comments in the code) made so much sense, and I also feel like I have a much better understanding of how the process of writing to memory actually works.

I also was not aware that there are registers which are shared between all 4 banks, nor did I know that you can use the directive banksel as opposed to setting or clearing RP1 and RP0 from STATUS all the time; real time saver!

Quick Question though: If I were to use the directive res to reserve memory for some of my variables, would it be saved in the first immediate bank with space? Will calling the instruction banksel Var_Name work if it is an alias I made myself?

I'll implement the EEPROM_Write subroutine and do some more rigorous, but so far its looking pretty good!

By the way, I am having another issue regarding reading and writing data from a 4x4 keypad to an LCD display and would really appreciate it if you could help me out with this as well since it seems that you are experienced with PIC programming.

I use the following subroutine to read in a character from the keypad:

Code:
Read_Key btfss		PORTB,1     ;Wait until data is available from the keypad
         goto		$-1 

         swapf		PORTB,W     ;Read PortB<7:4> into W<3:0>
         andlw		0x0F
         call       KPHexToChar ;Convert keypad value to LCD character (value is still held in W)
       
		 call       WR_DATA      ;Write the value in W to LCD
         btfsc		PORTB,1      ;Wait until key is released
         goto		$-1
		 return

This is the table I use to convert keypad info to LCD info:

Code:
KPHexToChar	
	addwf     PCL,F
    dt        "123A456B789C*0#D"

My code works fine for the most part, but it only prints the first four characters which appears on the keypad (123A); all of these are in the first row. If I press any key that is in the same column as those four (i.e. I press 7 which is in the same column as 1), it still prints the character which is in the first row of that column. Any ideas?
 
I also was not aware that there are registers which are shared between all 4 banks,

Yes...this is known as the common RAM space. I have a blog article I wrote about it. Here's a link to it -

https://www.electro-tech-online.com/blogs/jon-wilder/179-pic-16f-common-uses-common-ram.html


nor did I know that you can use the directive banksel as opposed to setting or clearing RP1 and RP0 from STATUS all the time; real time saver!

Banksel is a built in assembler macro in the MPASM assembler. It does come in handy quite a bit.

Quick Question though: If I were to use the directive res to reserve memory for some of my variables, would it be saved in the first immediate bank with space? Will calling the instruction banksel Var_Name work if it is an alias I made myself?

Banksel should work with any defined/labeled variable. Here's the nice thing about banksel. You don't have to use the exact variable name that you plan to address in that bank. You just have to use a variable name that is located in the bank that you're selecting.

By the way, I am having another issue regarding reading and writing data from a 4x4 keypad to an LCD display and would really appreciate it if you could help me out with this as well since it seems that you are experienced with PIC programming.

I use the following subroutine to read in a character from the keypad:

Code:
Read_Key btfss		PORTB,1     ;Wait until data is available from the keypad
         goto		$-1 

         swapf		PORTB,W     ;Read PortB<7:4> into W<3:0>
         andlw		0x0F
         call       KPHexToChar ;Convert keypad value to LCD character (value is still held in W)
       
		 call       WR_DATA      ;Write the value in W to LCD
         btfsc		PORTB,1      ;Wait until key is released
         goto		$-1
		 return

This is the table I use to convert keypad info to LCD info:

Code:
KPHexToChar	
	addwf     PCL,F
    dt        "123A456B789C*0#D"

My code works fine for the most part, but it only prints the first four characters which appears on the keypad (123A); all of these are in the first row. If I press any key that is in the same column as those four (i.e. I press 7 which is in the same column as 1), it still prints the character which is in the first row of that column. Any ideas?

I can take a look and get back to you if you can give me some time with it.
 
Thanks for the link! I've skimmed over it but I am planning on going over it in detail when I find time.

I've also been playing around with memory locations and I think I'm getting the hang of it; as to how banks really work.

At the moment, both my EEPROM read and write seem to be functioning but I'll keep testing and see how it goes.

Also, regarding the LCD problem, duffy mentioned in another thread how I might be dealing with an oscillator problem since I'm using the MM74C922N chip. After playing around with some settings, I found out that it was actually the root of the problem and now it works!
 
Banking is very simple. There are no physical "banks" per se. Banking is just a convention we use to explain how the registers are organized.

Basically what you have going on is that instructions that address the registers via direct addressing can only supply 7 of the register address bits. Well with 7 bits the highest you can count is from 0 - 127. To address any register locations beyond 127 (0x7F), the instruction has to get more bits from elsewhere.

Hence the purpose of bits RP0 and RP1 (Register Page 0 and Register Page 1). These two bits are the 8th and 9th bits of the register address, which allows us to directly address locations beyond 0x7F.

But to explain it, they chose to explain it as if the registers are organized into "banks" which contain 128 register locations each.

Now when you do indirect addressing we use the address pointer register known as FSR. The FSR pointer can access locations 0-255 (0x00 - 0xFF) without the need for a banking bit because it is an 8 bit register. However, if you want to indirectly address locations beyond 0xFF, you will need 1 more banking bit. This 9th address bit comes from bit location IRP (Indirect Register Page) in the STATUS registers. It allows you to indirectly address locations 0x100 - 0x1FF via using the FSR pointer.

Hope this helps to clarify what's really happening when bank selecting.
 
Last edited:
Alright, so essentially direct addressing and indirect addressing are simply two different ways of accessing registers 0x00 through 0x1FF?

Does this mean the directive banksel internally changes bits RP0 and RP1 depending on the name of the register we pass into it?

Also, in order to be safe, should I always banksel a certain register in case I do not know what my STATUS register is currently pointing to and I am accessing a register not in the common ram space?

Does this also mean that the following line of code specifies where VAR will be stored and I can later change its contents?

Code:
VAR		EQU 0x71

Comparing the above line of code to this one:


Code:
VAR2	EQU 0x69

Would I have to banksel to access VAR2 while not having to banksel to access VAR?

When using the directive udata in the following way:

Code:
udata
VAR3   res  1

Where is VAR3 stored exactly? Do I need to banksel it in order to use it? Will the pic ever put it in the common RAM space?

Lastly, in the link you posted, near the end, you said it is better to store the delay counters in the common ram space. Could you also store that in other general purpose (non common ram space) registers and simply select that bank when you call your delay subroutine?

Sorry for ambushing you with questions.
 
Alright, so essentially direct addressing and indirect addressing are simply two different ways of accessing registers 0x00 through 0x1FF?

Yes exactly. The register FSR (File Select Register) is the address pointer register. The register INDF (INDirect File) is the indirect addressing data buffer. Register INDF holds the data that resides at the address that is currently written to the FSR register.

For example, let's say we wanted to indirectly address register location 0x25. We would do -

Code:
		movlw		0x25			;write address to RAM address pointer
		movwf		FSR
		movfw		INDF			;place contents of location in FSR into W

This would take the contents of address location 0x25 and place the contents in W. You can do what you want with it from there.

There is some trickery to be had with using indirect addressing. When you have registers that are placed sequentially and you have to access data from these registers in sequential order, you can set up a loop to increment the FSR address and then pull the data from INDF. I use this exact technique for multiplexing LED displays. I have 4 registers set up for each digit on the display that contain the segment data for each digit and I usually place these buffers at addresses 0x20-0x23. Then everytime timer 0 overflows, it goes to the timer 0 interrupt, increments the FSR, then pulls the next segment data from INDF and drops the data into the port that is the segment driver port. When the address in FSR reaches the value of 0x24, I have the code reset the address in FSR to 0x20 and the cycle repeats.

Does this mean the directive banksel internally changes bits RP0 and RP1 depending on the name of the register we pass into it?

Banksel is not a directive. It is a built in macro. When you invoke the banksel macro, it assembles in the required bsf/bcf instructions that set/clear RP0 and RP1 for the bank you're wanting to access.

Also, in order to be safe, should I always banksel a certain register in case I do not know what my STATUS register is currently pointing to and I am accessing a register not in the common ram space?

You as the programmer should never NOT know which bank is the currently active bank. You're the one writing the code and the bank select bits don't get altered unless you directly change them.

Now to jump back to bank 0, you can always do "banksel 0". This works because register 0 resides in bank 0. The correct syntax for banksel is "banksel ADDRESS". ADDRESS can either be a physical address in the bank you're selecting or a label that has been equated to an address value in the bank that you're selecting. "Banksel 0" would select bank 0, "banksel 0x80" would select bank 1, "banksel 0x100" would select bank 2 while "banksel 0x800" would select bank 3.

You only need to banksel when you need to go to a different bank. Not everytime you access a register in a bank other than 0. Overusing banksel will just generate bloated code as it would assemble in redundant/unnecessary bsf/bcf STATUS,RP0 and bsf/bcf STATUS,RP1 instructions everytime you invoke it.

Does this also mean that the following line of code specifies where VAR will be stored and I can later change its contents?


Code:
VAR		EQU 0x71

The EQU directive just equates a label to a constant. The context you use the label in determines whether the instruction will access a memory location or whether it will load a value into W.

For example -

Code:
		movlw		VAR

This would load the value of 0x71 into the W register.

Code:
		movwf		VAR

This would load whatever is in W into memory location 0x71.

The first operand in instructions that have an "f" in the instruction is always a physical address. The first operand in instructions that have an "l" in the instruction is always a literal value.

Comparing the above line of code to this one:


Code:
VAR2	EQU 0x69

Would I have to banksel to access VAR2 while not having to banksel to access VAR?

Only if you're not in bank 0. Register address 0x69 resides in bank 0 so if you're already in bank 0 then you will not need to banksel to access it. VAR however, resides in the common RAM space so you can access that register regardless of the currently active bank.

When using the directive udata in the following way:

Code:
udata
VAR3   res  1

Where is VAR3 stored exactly? Do I need to banksel it in order to use it? Will the pic ever put it in the common RAM space?

udata would place VAR3 in the banked memory. udata_shr would place it in the common shared RAM. As far as which exact locations they end up in is up to the assembler. For relocatable code, this is a handy tool.

However, udata/res are directives that you use only when writing relocatable code. If you're writing absolute code (i.e. code that will only be intended for a specific PIC), you can just use cblock/endc to equate labels to a specific set of constants that correspond with the RAM address locations that you will use them to access. For example, my cblock for the common RAM would look like such -

Code:
		cblock		0x70
				W_TEMP			;W context save
				STATUS_TEMP		;STATUS context save
				PCLATH_TEMP		;PCLATH context save
				COUNT			;delay counter
				T1COUNT			;TMR1 interrupt counter
				MIDI_TEMP		;MIDI data buffer
				LEDSTAT			;LED status buffer
				PCSTAT			;MIDI status message buffer
				CCCONT			;MIDI controller number buffer
				PCDATA			;MIDI data message buffer
				DATA_EE_ADDR		;data EEPROM address buffer
				DATA_EE_DATA		;data EEPROM data buffer
				PCSTORE			;program change store address
				COUNT1			;delay counter 1
				COUNT2			;delay counter 2
				COUNT3			;delay counter 3
		endc


As shown above, the assembler will equate the labels in order in which they appear in the cblock to constant values 0x70-0x7F starting with the first one in the list being equated to 0x70. The address value after the "cblock" directive is the starting address of the cblock (cblock stands for "constant block").

Lastly, in the link you posted, near the end, you said it is better to store the delay counters in the common ram space. Could you also store that in other general purpose (non common ram space) registers and simply select that bank when you call your delay subroutine?

You can place them wherever you want. Placing them in common RAM just makes things easier as you will be able to call the delay routines regardless of the active bank.

Quite honestly, I tend to place the registers that will be accessed the most in common RAM as this drastically reduces the amount of bank selecting you will have to do to access them. This cuts down on bank select instructions, which keeps your code tighter.
 
Last edited:
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top