![](templates/subSilver/images/CCSLogo.jpg) |
![CCS C Software and Maintenance Offers](templates/subSilver/images/forumAd6.jpg) |
View previous topic :: View next topic |
Author |
Message |
billy6
Joined: 12 Jan 2015 Posts: 5
|
Problem with internal memory in PIC18F27J53 |
Posted: Mon Mar 23, 2015 10:09 am |
|
|
Hello,
I´m having very serious problems trying to write inside internal flash memory in PIC18F27J53. This device does not have an EEPROM memory, therefore I have to save the data inside internal Flash memory.
The main problem I have is that when I rewrite any postion the value is not saved correctly. I´m not able to write a byte in an addres that it has been writen before even erasing it before and rewriting it later.
The write size that I´m using is 2 Bytes and the erase size is 1024 Bytes.
Please help me,
Thank you very much
Code: |
#include <18F27J53.h>
#fuses NOCPUDIV,NOWDT,NOPROTECT,PLLEN,PLL2,CLOCKOUT,INTRC_PLL_IO// , NOPROTECT
#PIN_SELECT RX2=PIN_C1 //Pines utilizados para la UART
#PIN_SELECT TX2=PIN_C0
#use delay(clock=48000000)
#use rs232(uart2,baud=920000,xmit=PIN_C0,rcv=PIN_C1,bits=8,stream=PORT1 )
void main()
{
disable_interrupts(GLOBAL);
//delay_ms(100);
write_program_eeprom(0x1FF80,0xAABB);
//delay_ms(500);
write_program_eeprom(0x1FFC0,0xCCDD);
erase_program_eeprom(0x1FF80);
write_program_eeprom(0x1FF80,0xAABB);
while(1);
}
|
![](http://i58.tinypic.com/29uq92o.jpg) |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19359
|
|
Posted: Mon Mar 23, 2015 11:24 am |
|
|
Start with - you can't use 1FF80....
The key point is that erases have to be a whole page, and the _configuration memory_ is at 1FFF8. You must not erase this.
The highest page address you can use is 1F800.
An erase at this address will erase 1024 bytes from this point. The erase address needs to be the start of a page.
I have used a crude 'modified version' of the Microchip AN for these chips. I treated the 'page' as containing 16 blocks. A block starting with 00, was deemed as 'full'. A block starting FF, was 'clear'. A block starting AA was 'in use.
I erase the page.
Then the write code reads the first byte of each block. If it is FF, it writes my record (63 bytes max), and the first byte 'AA'. Then when I want to write a new record, I wrote '00', to the first byte, and look for the next 'clear' block, and write my data to this. To retrieve a record, I step through looking for a block beginning 'AA', and retrieve the data from this. If the write search doesn't find an empty record, then I erase the page and start again. |
|
![](templates/subSilver/images/spacer.gif) |
billy6
Joined: 12 Jan 2015 Posts: 5
|
|
Posted: Mon Mar 23, 2015 4:26 pm |
|
|
Thanks for your answer TTelmah,
I have try other addresses but nothing has changed. I have the same problem, If I erase one address I can´t rewrite it.
It seems like I have missed something... but I don´t know what. |
|
![](templates/subSilver/images/spacer.gif) |
jeremiah
Joined: 20 Jul 2010 Posts: 1328
|
|
Posted: Mon Mar 23, 2015 5:50 pm |
|
|
Which other addresses did you test?
List them. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19359
|
|
Posted: Tue Mar 24, 2015 1:19 am |
|
|
Also, critical thing missing from your question. _Compiler version_. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19359
|
|
Posted: Tue Mar 24, 2015 3:39 am |
|
|
I've sat down and written a demo program to show how this can be done.
It's a little more complex than simply re-writing the whole page, being based upon the ideas from the Microchip AN, but handling a 'record', rather than a single byte.
As posted, it used a 64 byte record, containing 62 bytes of useable data, and fitting 16 of these into the single page. A page erase only has to happen, when 16 actual data 'writes' have taken place (since it is the _erase_ cycle that uses a 'life', this makes a major difference to the lives used). It comprises a simple program (I compiled it for the processor discussed here), but setup for a crystal at 20MHz (this was what I had available). Obviously change this to suit the chip you are using. There is then an include 'flash_storage.h', which contains the actual routines.
For the demo, I use a single structure called 'storage', which contains the 62byte data record (called 'data'....), and the record marker (called 'marker'). It uses a serial link, and (very crudely), allows you to change bytes, update the record to the flash, and display these. It also tests if a record is present at 'boot', and creates one if it isn't.
I have crudely tested that it does work.
main.c
Code: |
#include <18LF27J53.h>
#device ADC=16
#fuses HS,NOWDT,NOCPUDIV,NOPLLEN,NOIESO,NOFCMEN
//just running a 20MHz crystal - adjust to suit your hardware
#use delay(CLOCK=20MHz)
#USE RS232 (UART1,ERRORS) //Just setup a serial link
//crude demo of loading/saving a binary-2 byte record to the program memory
//in a relatively efficient way
#DEFINE F_RECORD_SIZE 62 //record size to use. Must be a binary-2 size
//so 6, 14, 30, 62. Smaller the record, the less often erase cycles will occur
//remember it is _erases_ that use a flash memory life.
//The -2, is because on these chips the smallest flash write is two bytes, and
//these are used in the page for the 'marker'
#DEFINE F_PAGE_SIZE getenv("FLASH_ERASE_SIZE")
#DEFINE F_MAX_ADDRESS (((getenv("PROGRAM_MEMORY"))) | (0xFF))+1
#DEFINE F_START_ADDRESS F_MAX_ADDRESS-(F_PAGE_SIZE*2)
struct
{
//Whatever you want to store
unsigned int16 marker; //Leave this untouched
unsigned int8 data[F_RECORD_SIZE]; //max size area of data - lay out how you want
} storage;
#include "flash_storage.h"
#include <stdlib.h>
void change(unsigned int8 * data)
{
//read an input string and write this converted to integer, to the array
char temp[15]; //string input buffer
int8 val;
gets(temp);
val=atoi(temp); //convert input number to integer;
*data=val; //and update in the array;
}
void update_vals(unsigned int8 * data)
{
int8 ctr;
//crude terminal I/O program to allow values to be updated - beware no size
//checking.....
printf("Type in new values in decimal on each line <ENTER>\n\r");
for(ctr=0; ctr<F_RECORD_SIZE; ctr++)
{
printf("data[%d] = %d ",ctr,data[ctr]);
change(data+ctr);
}
}
void main()
{
int8 n;
if (get_record(&storage)==FALSE)
{
//Here no record in the flash - load defaults - handle how you want
for(n=0; n<F_RECORD_SIZE; n++)
{
storage.data[n]=n;
} //just filling the array with sequential numbers as a demo
put_record(&storage); //write this record to flash
}
//Here we have either the data retrieved from flash, or the default loaded
while(TRUE)
{
if (kbhit())
{
//here I have hit a key
n=getc();
switch (n)
{
case 'R':
//display the data from the FLASH
//Just loop through, showing the bytes in hex
for(n=0; n<F_RECORD_SIZE; n++)
{
printf("%02X,",storage.data[n]);
}
printf("\n\r"); //and line feed
break;
case 'U':
//Update the values
update_vals(&storage.data);
break;
case 'W':
//write the values to FLASH
put_record(&storage);
printf("Record written\n\r");
break;
}
}
}
}
|
flash_storage.h
Code: |
//Routines to handle a flash page storing a 'F_PAGE_SIZE' byte record (plus
//marker flag) multiple times into the single page.
#define F_RECORDS_PER_PAGE (F_PAGE_SIZE/(F_RECORD_SIZE+2))
#define RECORD_FULL 0
#define RECORD_IN_USE 0x55
#define RECORD_EMPTY 0xFF //This is a hardware value - FLASH clears to FF
#ORG F_START_ADDRESS, F_START_ADDRESS+F_PAGE_SIZE-1 {}
//reserve the page
#ORG DEFAULT
//remember that the 'data' pointer here points to the actual data to transfer
//plus the space for the 'marker' in front.
int1 get_record(unsigned int8 * data)
{
//This scans through the page looking for a record, and returns 'TRUE'
//if one is found, together with the data
int8 ctr;
int32 address;
unsigned int16 temp;
for (ctr=0; ctr<F_RECORDS_PER_PAGE; ctr++)
{
address=F_START_ADDRESS+ctr*(int16)(F_RECORD_SIZE+2);
//Since the 'marker' will always be on a nice binary address, I only
//have to scan through these addresses performing single reads
//to find the marker.
read_program_memory(address,&temp,1); //read single byte
if (make8(temp,0)==RECORD_IN_USE)
{
//hurrah, we have a record!....
read_program_memory(address,data,F_RECORD_SIZE+2); //retrieve it
return TRUE; //and exit
}
}
//If we get here, a record was not found
return FALSE;
}
void put_record(unsigned int8 * data)
{
//Now we scan the page, looking to see if any space is unused
int8 ctr;
int32 address;
unsigned int16 temp;
int32 in_use_address;
int1 in_use_flag=FALSE;
//Now we have to search both for the current record, and a clear record
for (ctr=0; ctr<F_RECORDS_PER_PAGE; ctr++)
{
address=F_START_ADDRESS+ctr*(int16)(F_RECORD_SIZE+2);
read_program_memory(address,&temp,1); //read byte
if (make8(temp,0)==RECORD_IN_USE)
{
in_use_address=address;
in_use_flag=TRUE; //There is an in_use record
}
if (make8(temp,0)==RECORD_EMPTY)
{
//OK. There is an unwritten record in the page
data[0]=RECORD_IN_USE; //flag this block as IN_USE
write_program_memory(address,data,F_RECORD_SIZE+2);
//actually write the new data
//Now need to turn off the old record
if (in_use_flag)
{
temp=RECORD_FULL;
write_program_eeprom(in_use_address,temp,);
}
//and exit
return;
}
}
//now, if we get here,there is not an empty record in the page
//So need to erase the page
data[0]=RECORD_IN_USE;
write_program_memory(F_START_ADDRESS,data,F_RECORD_SIZE+2); //force an erase,
//and write new block
return;
}
|
Note particularly that the _marker_ change is written using write_program_eeprom (which does not force an erase on the page boundary), while the data uses write_program_memory (which does).
Also, realise that since records are written sequentially it'll always find 'IN_USE', before the empty record.
Three keyboard inputs (from the serial), offered:
'R' - displays the record.
'U' - update the record values in RAM
'W' - write the record.
Ideally use something a bit more useful for your own tests... ![Smile](images/smiles/icon_smile.gif) |
|
![](templates/subSilver/images/spacer.gif) |
billy6
Joined: 12 Jan 2015 Posts: 5
|
|
Posted: Tue Mar 24, 2015 9:49 am |
|
|
Thanks Ttelmah!
It is great!
Thank you very much |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19359
|
|
Posted: Tue Mar 24, 2015 11:59 am |
|
|
Good.
It's one of those problems that is common enough, that I felt it worth posting a 'working' fix. As posted it should work with most of the PIC18's, and with a little tweaking, it can also be modified to work with the PIC24's. Behaves more like a 'single sector' disk file, than the traditional EEPROM, but does provide a working solution for people needing this ability.
Have fun. |
|
![](templates/subSilver/images/spacer.gif) |
spilz
Joined: 30 Jan 2012 Posts: 218
|
|
Posted: Thu Jun 18, 2015 11:34 pm |
|
|
Hello,
I have test your code and it works well, thanks Ttelmah
I'm trying to change the chip for my project from 18F2550 to 18F27j53.
I use eeprom on the 18f2550 for only few bytes with specific address.
For example:
1 bytes for mode, I need to read often, and write often.
7 bytes for username, I need to read often and write sometime.
10 bytes for password, I need to read often and write sometime.
And that's all.
Do I need to use all your code, or is there a simple way for that?
To save erase cycle, is it not better to fix address for this 3 variables and works directly with them ?
I don't think I need to have all the part with startbloc in my case, do I ?
sorry for my bad english
Thanks for your help.
Spilz |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19359
|
|
Posted: Fri Jun 19, 2015 7:50 am |
|
|
It depends how often you are intending to change things?.
On the program memory, you _have_ to erase an entire page.
So, if you only wanted a few bytes, and they were going to change very rarely, then what you could do, is choose the page address closest to the top of memory, that can be used (avoiding the configuration data), and just write the bytes you want at the start of this page. A write to the start of the page automatically erases the whole page, so you would always have to read the current values, change the one you want, and write the whole block back. However the rest of the code (which is to reduce the number of erases used, when repeatedly writing data), would not be needed. |
|
![](templates/subSilver/images/spacer.gif) |
spilz
Joined: 30 Jan 2012 Posts: 218
|
|
Posted: Sat Jun 20, 2015 10:43 am |
|
|
what is the difference between program_memory and program_eeprom ?
which one I have to use to save change in data (as PASSWORD is always a char[10])?
can I specify an address in program where to save the data ?
the erase page "problem" is the same for program_memory and program_eeprom ?
thanks |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19359
|
|
Posted: Sun Jun 21, 2015 4:20 am |
|
|
The 'eeprom' function, is just written to more closely resemble the 'write_eeprom' function. It accepts a value, rather than a pointer to an area of memory, and just writes one word. It does not erase for you. So once any bit is set to '0', it cannot change it back to '1'. You have to manually erase the page.
The 'memory' function is more directly tied to the actual operation that has to occur. It writes a block of bytes from RAM, and will _automatically erase the page if you write to the first byte of the page_.
_If_ your password is only changing rarely, and you have got enough free ROM, then the 'simplest' solution, is to just have this stored at the bottom of the second page below the top of the program memory (second page, because the top page contains the configuration data), and write the whole thing each time.
So:
Code: |
//Include to suit processor here
#define PROG_TOP ((getenv("PROGRAM_MEMORY")) | 0xFF) + 1
//beware the 'program memory' reported, excludes the 8 bytes for the
//configuration. Since we need the actual 'page boundary', this has to
//be added in..
#define PROG_PAGE (getenv("FLASH_ERASE_SIZE"))
#define START_PAGE (PROG_TOP - (PROG_PAGE * 2))
#define END_PAGE (START_PAGE + PROG_PAGE - 1)
#ORG (START_PAGE), (END_PAGE) {}
//Force the page to be reserved by the processor
#ORG DEFAULT
#ROM int8 START_PAGE = {"Temporary "}
//This will give a 'warning', but is correct.
void write_password(char * value)
{
write_program_memory(START_PAGE,value,10);
}
void get_password(char * value)
{
read_program_memory(START_PAGE,value,10);
}
void main()
{
int32 start=START_PAGE;
int32 end=END_PAGE;
char password[10] = "New Value"; //remember 9 text characters in a 10 character string
char temp[10];
get_password(temp);
//Should be "Temporary " on the first pass
write_password(password);
get_password(temp);
//should now be "New Value"
while(TRUE)
{
delay_cycles(1);
}
}
|
Obviously as written, this is silly, and uses a write cycle on every boot, but for something that changes infrequently, then this is an acceptable way to store the data. |
|
![](templates/subSilver/images/spacer.gif) |
spilz
Joined: 30 Jan 2012 Posts: 218
|
|
Posted: Sun Jun 21, 2015 12:44 pm |
|
|
I don't understand 2 things in your solution :
what does this code ?
Code: | int32 start=START_PAGE;
int32 end=END_PAGE; |
why/how this solution can works without erase function (according to what you explain earlier) ?
an other question, can this approch work ? (code is not perfect):
Code: |
#define PROG_PAGE (getenv("FLASH_ERASE_SIZE"))
#ORG (START_PAGE), (START_PAGE+10) {}
//Force the page to be reserved by the processor
#ORG DEFAULT // I don't understand what it does
#ROM int8 START_PAGE = {"Temporary "}
//This will give a 'warning', but is correct.
void write_password(char *value){
int page[PROG_PAGE];
int i;
read_program_memory(START_PAGE,page,PROG_PAGE);
for(i=0;i<10;i++){
page[i] = value[i];
}
erase_program_eeprom(START_PAGE);
write_program_memory(START_PAGE,page,PROG_PAGE);
} |
like this I don't reserve an all page for only 10 bytes.
Is this approch wrong ? and why?
thanks |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19359
|
|
Posted: Sun Jun 21, 2015 1:35 pm |
|
|
The compiler 'knows' where the top of the program memory is. The defines and getenv calls work out where the second page _below_ the top of memory starts, and how big it is.
You have been told already that if you write (with write_program_memory), to the _start_ of a page, the function will automatically perform an erase. Because the data is aligned to the start of the page, it carries out an erase, and then a write.
The two variables were just put in there for debugging. I was checking that I had got these calculations right. I used a breakpoint, in an ICD, and checked that the values were right. You can't directly read #defined values, so I put them into variables. Should have removed them after testing. |
|
![](templates/subSilver/images/spacer.gif) |
spilz
Joined: 30 Jan 2012 Posts: 218
|
|
Posted: Sun Jun 21, 2015 2:15 pm |
|
|
what I don't understand this :
is it necessary ?
for erase, i miss that, sorry
I don't understand why the buffering page is not necessary, (in the case I use quite all the memory with software)?
sorry for my bad english |
|
![](templates/subSilver/images/spacer.gif) |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|