|
|
View previous topic :: View next topic |
Author |
Message |
avatarengineer
Joined: 13 May 2013 Posts: 51 Location: Arizona
|
PIC24 : using Flash memory to store calibration data |
Posted: Wed Jul 23, 2014 12:30 pm |
|
|
My task is to store about 80 bytes of calibration information
into the Flash memory since there isn't any EErom to play with.
I've search through many many discussion threads and
haven't found a clear, "complete", and concise answer
on implementing the CCS write_program_memory( ) command.
-- I'm using PIC24EP512GP806.
The data sheet confuses me more than helps.
-- I suspect the page size is 2K (2048 bytes), is that true?
-- I wish to begin a write at a page boundary (to incur an erase) and
I can only assume there is one every 0x0800.
If program memory starts at 0x200, can I assume
that a page starts every 0x800 from 0x200 (ie 0xA00)
or that it starts at 0x800, then 0x1000, etc...?
-- Reserving an area for data, using the #org statement seems unclear
if that should use the entire page start and end address.
Shouldn't it? If a page erase occurs, that space mustn't have any program code, yes?
Funny that it's not mentioned in the Help for the write command.
-- Some sample code I've seen adds a delay statement after the write.
How long should a delay be if the write statement is invoked?
--- write_program_memory( address, dataptr, count ), the "CCS Help" defines the count as a qty of bytes.
IF I am writing 32bit data, does the command know about the 4th byte is a dummy due to 24bit addressing
and spreads the bytes while skipping the dummy byte?
or do I chop my 32bit data into 16bit Longs and make 2 writes?
As you can tell, this has been a frustrating journey. ---
It would be really nice to see a complete functional code example
instead of the dancing around the details as seen in many threads.
=== |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1353
|
Re: PIC24 : using Flash memory to store calibration data |
Posted: Wed Jul 23, 2014 1:33 pm |
|
|
avatarengineer wrote: |
-- I'm using PIC24EP512GP806.
The data sheet confuses me more than helps.
-- I suspect the page size is 2K (2048 bytes), is that true?
|
I didn't check your chip's data sheet, but all PIC24 chips that I have used have a 2kbyte page. EDIT: Your data sheet says 1024 instructions, which is a 4kbyte page.
avatarengineer wrote: |
-- I wish to begin a write at a page boundary (to incur an erase) and
I can only assume there is one every 0x0800.
|
Program Memory is WORD alligned, which is 2bytes per, so each address goes with 2 bytes or 1 WORD of data. This means, each page is every 0x800 addresses (or WORDs). EDIT: size 0x400 => 0x800
avatarengineer wrote: |
If program memory starts at 0x200, can I assume
that a page starts every 0x800 from 0x200 (ie 0xA00)
or that it starts at 0x800, then 0x1000, etc...?
|
Program Memory starts at 0x0000. You have a page every 0x800:
0x0000
0x0800
0x1000
0x1800
... so on
EDIT: used 0x800 sizes
avatarengineer wrote: |
-- Reserving an area for data, using the #org statement seems unclear
if that should use the entire page start and end address.
Shouldn't it? If a page erase occurs, that space mustn't have any program code, yes?
|
#org tells the compile to put something in a specific location. You can even use #org to tell the compiler to put nothing in a space, which is what must happen if you want to use it for "eeprom" like space. You don't want the compiler putting code in the same page as your "eeprom". To answer the last part: Correct. Do not put code in the same page as the data you are saving.
avatarengineer wrote: |
Funny that it's not mentioned in the Help for the write command.
|
Not all chips have the same exact requirements, so the write command must be general enough to cover all chips. Some chips let you erase on the byte/word level (no pages), so the write_program_memory cannot go into specific details if they differ from chip to chip.
avatarengineer wrote: |
-- Some sample code I've seen adds a delay statement after the write.
How long should a delay be if the write statement is invoked?
|
It actually varies by chip. Most I have seen take 5-6 ms to finish a write. Your data sheet will mention the time in the Electrical Specifications section. I think write_program_memory() handles this by polling a bit to see when done/ready. EDIT: your times are faster on that chip. see page 510 of your data sheet.
avatarengineer wrote: |
--- write_program_memory( address, dataptr, count ), the "CCS Help" defines the count as a qty of bytes.
IF I am writing 32bit data, does the command know about the 4th byte is a dummy due to 24bit addressing
and spreads the bytes while skipping the dummy byte?
or do I chop my 32bit data into 16bit Longs and make 2 writes?
|
Since each chip is different, the command does not know. You need to place a 0 in every 4th byte manually and you MUST always read/write 4 bytes at a time (or multiples of 4 bytes is fine as well). Also, some chips have maximum number of bytes that can be written at a time. My guess is your chip can only write 512 (EDITED) bytes at a time. However, I think write_program_memory accounts for this. Also note that if the address you give to write_program_memory is the address to the start of the page, it is supposed to erase the page for you. Be careful:
Code: |
write_program_memory(0x804, data1, 4);
write_program_memory(0x800, data2, 4); //oops...just erased data1
|
avatarengineer wrote: |
As you can tell, this has been a frustrating journey. ---
It would be really nice to see a complete functional code example
instead of the dancing around the details as seen in many threads.
=== |
I have some code that I will post that does work for me on the PIC24FJ series. I haven't tested against other chips and I haven't spent the time carefully testing out the edge cases (512 byte write boundaries that I mentioned above for example). Feel free to use it, but you should probably spend some time testing it first.
Last edited by jeremiah on Wed Jul 23, 2014 2:02 pm; edited 2 times in total |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1353
|
|
Posted: Wed Jul 23, 2014 1:41 pm |
|
|
NOTES:
1. If write_program_memory() normally erases a page when the address is the page boundary, then pgm_eeprom_write() will as well. It just provides a wrapper for write_program_memory()
2. This wrapper takes away the need to specify a specific ROM address. All addresses supplied to these functions should be 0-(page_size-2).
3. This is a single page psuedo eeprom. It uses the page prior to the page that stores the FUSES statements.
4. While odd length data is allowed, odd addresses are currently not. All supplied addresses must be even.
5. The wrapper handles reading/writing zeros to the correct spaces. Your data arrays need only to have the data you want to save.
6. Again, this isn't fully edge case tested. It works on my small project.
7. It uses a RAM buffer to handle writing. There are probably better ways to do it, but this is how my current one works.
Code: |
/******************************************************************************
* NAME: pgm_eeprom.c
* DESC: Provides pseudo EEPROM functionality for PCD chips by allocating one
* page of program memory as "EEPROM". This is tricky for PCD chips
* because they use the 24 bit architecture. The eeprom is implemented
* as follows (assuming program memory page size of 0x400):
*
* ----------------------------
* PGM_EEPROM_PAGE + 0x0000 | 0x00 | NOP | D001 | D000 |
* PGM_EEPROM_PAGE + 0x0002 | 0x00 | NOP | D003 | D002 |
* PGM_EEPROM_PAGE + 0x0004 | 0x00 | NOP | D005 | D004 |
* PGM_EEPROM_PAGE + 0x0006 | 0x00 | NOP | D007 | D006 |
* PGM_EEPROM_PAGE + 0x0008 | 0x00 | NOP | D009 | D008 |
* PGM_EEPROM_PAGE + 0x000A | 0x00 | NOP | D00B | D00A |
* *** | *** | *** | *** | *** |
* PGM_EEPROM_PAGE + 0x03FA | 0x00 | NOP | D3FB | D3FA |
* PGM_EEPROM_PAGE + 0x03FC | 0x00 | NOP | D3FD | D3FC |
* PGM_EEPROM_PAGE + 0x03FE | 0x00 | NOP | D3FF | D3FE |
* ----------------------------
*
* The first column is a "Don't Care" that must be set to 0x00 for PCD
* chips. The second column is typically the "instruction" byte. This
* is set to NOP to avoid random code from being run in this region.
* The third colomn is for odd addressed bytes, and the fourth column
* is for even column bytes. Note that the driver does not currently
* support odd addressable bytes directly, so one must always read and
* write starting at even number addresses.
*
* Note that programmemory must be erased prior to writing to it. The
* pgm_eeprom_write() method follows the same erase rules as the
* write_program_memory() method supplied by the compiler. An erase
* method, pgm_eeprom_erase() is provided when manual erasing is desired.
*
* All reads and writes happen in chunks of memory, called blocks. By
* default, the block size is the same as the PGM_EEP_WRITE_SIZE value,
* but the user can opt to change the size as desired (to reduce RAM
* usage). This value is defined as PGM_EEPROM_BLOCK_SIZE.
*
*
******************************************************************************/
#ifndef __PCD__
#error Only for PCD micros
#endif
//Defines needed to calculate location
#define PGM_EEP_PM_END (getenv("PROGRAM_MEMORY")-1) //in words
#define PGM_EEP_PM_PAGE_SIZE (getenv("FLASH_ERASE_SIZE")/2) //in words
#define PGM_EEP_PM_NUM_PAGES (PGM_EEP_PM_END/PGM_EEP_PM_PAGE_SIZE)
#define PGM_EEP_PM_FUSE_PAGE (PGM_EEP_PM_NUM_PAGES*PGM_EEP_PM_PAGE_SIZE)
#define PGM_EEP_WRITE_SIZE (getenv("FLASH_WRITE_SIZE"))
//use page just prior to fuses
#define PGM_EEPROM_PAGE (PGM_EEP_PM_FUSE_PAGE-PGM_EEP_PM_PAGE_SIZE)
//this line ensures application code won't be in this range, which is eeprom
#ORG PGM_EEPROM_PAGE,PGM_EEP_PM_FUSE_PAGE-1{}
//Adjust as memory/speed allow. Default is PGM_EEP_WRITE_SIZE
#define PGM_EEPROM_BLOCK_SIZE PGM_EEP_WRITE_SIZE
//all PCD chips must read and write in multiples of 4 bytes at at time
#if PGM_EEPROM_BLOCK_SIZE % 4
#error PGM_EEPROM.C PGM_EEPROM_BLOCK_SIZE must be multiple of 4
#endif
///////////////////////////////////////////////////////////////////////////////
// NAME: pgm_eeprom_erase
// DESC: Erases program memory EEPROM Block
// IN: NONE
// OUT: NONE
// NOTE: Must be performed prior to writing to any location in EEPROM for
// a second time.
///////////////////////////////////////////////////////////////////////////////
void pgm_eeprom_erase(){
erase_program_memory(PGM_EEPROM_PAGE);
}
///////////////////////////////////////////////////////////////////////////////
// NAME: pgm_eeprom_read
// DESC: Reads N bytes of data from program memory implemented eeprom.
// IN: address - "EEPROM" location to read from. Must be even
// data - Location to store read data
// length - Length of stored data. Must be less than a program
// memory page size.
// OUT: TRUE if successful. FALSE if parameters are invalid
// NOTE: In addition to the length requirement. Address + length must be less
// than a program memory page size.
///////////////////////////////////////////////////////////////////////////////
BOOLEAN pgm_eeprom_read(unsigned int16 address, unsigned int8 *data, unsigned int16 length){
unsigned int8 eepromBlock[PGM_EEPROM_BLOCK_SIZE];
unsigned int32 pgmAddress = PGM_EEPROM_PAGE;
unsigned int16 blockSize;
unsigned int16 i = 0; //accessing data
unsigned int16 j = 0; //accessing eepromBlock
//parameter verification
if(bit_test(address,0)){return FALSE;} //if odd address, return false;
if(address >= PGM_EEP_PM_PAGE_SIZE){return FALSE;} //eeprom size
if(length > (PGM_EEP_PM_PAGE_SIZE-address)){return FALSE;} //eeprom size
//set the initial block size, 2 bytes per address
//If address is odd (NYI), then block size will be off
blockSize = 2*((sizeof(eepromBlock)/2) - (address % (sizeof(eepromBlock)/2)));
//Initialize loop variables start loop
pgmAddress += address;
while(i < length){
//read a block of data
read_program_memory(pgmAddress,eepromBlock,blockSize);
//copy over actual data
while(i < length && j < blockSize){
if(bit_test(j,0)){ //odd index
data[i++] = eepromBlock[j];
j+=3; //skip upper bytes
}else{ //even index
data[i++] = eepromBlock[j++];
}
}
//update loop variables
pgmAddress += (j/2); //increment for next write, 2 bytes per word addr
blockSize = sizeof(eepromBlock); //always the same after the first time
j = 0; //reset for next copy.
}
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
// NAME: pgm_eeprom_write
// DESC: Writes N bytes of data to program memory implemented eeprom.
// IN: address - "EEPROM" location to write to. Must be even
// data - Location of data to write.
// length - Length of data to write. Must be less than a program
// memory page size.
// OUT: TRUE if successful. FALSE if parameters are invalid
// NOTE: In addition to the length requirement. Address + length must be less
// than a program memory page size.
///////////////////////////////////////////////////////////////////////////////
BOOLEAN pgm_eeprom_write(unsigned int16 address, unsigned int8 *data, unsigned int16 length){
unsigned int8 eepromBlock[PGM_EEPROM_BLOCK_SIZE];
unsigned int32 pgmAddress = PGM_EEPROM_PAGE;
unsigned int16 blockSize;
unsigned int16 i = 0; //accessing data
unsigned int16 j = 0; //accessing eepromBlock
//parameter verification
if(bit_test(address,0)){return FALSE;} //if odd address, return false;
if(address >= PGM_EEP_PM_PAGE_SIZE){return FALSE;} //eeprom size
if(length > (PGM_EEP_PM_PAGE_SIZE-address)){return FALSE;} //eeprom size
//set the initial block size, 2 bytes per address
//If address is odd (NYI), then block size will be off
blockSize = 2*((sizeof(eepromBlock)/2) - (address % (sizeof(eepromBlock)/2)));
//Initialize loop variables start loop
pgmAddress += address;
while(i < length){
//copy over data for write
while(i < length && j < blockSize){
if(bit_test(j,0)){ //odd index
eepromBlock[j++] = data[i++];
eepromBlock[j++] = 0; //sets instruction as a NOP
eepromBlock[j++] = 0; //required for PIC24 type chips
}else{ //even index
eepromBlock[j++] = data[i++];
}
}
//pad end if length is odd (last loop execution was on even index)
if( i>= length && bit_test(length,0)){
eepromBlock[j++] = 0xFF; //0xFF is "not writing"
eepromBlock[j++] = 0; //sets instruction as a NOP
eepromBlock[j++] = 0; //required for PIC24 type chips
}
//Do write here
write_program_memory(pgmAddress,eepromBlock,j);
//update loop variables
pgmAddress += (j/2); //increment for next write, 2 bytes per word addr
blockSize = sizeof(eepromBlock); //always the same after the first time
j = 0; //reset for next copy.
}
return TRUE;
}
|
EDIT: some usage examples:
Both crc_struct_t and cfg_struct_t are structures as the name implies
Code: |
crc_struct_t crc = {1,2}; //high,low - two bytes long
cfg_struct_t cfg = {1,2,3,4,5}; //various params
pgm_eeprom_write(0,&crc,sizeof(crc_struct_t)); //should erase the page since address is 0
pgm_eeprom_write(2,&cfg,sizeof(cfg_struct_t)); //remember always even addresses
|
The gist of how it works is the write function
1. take your data array, length, and address, then it figures out where in program memory to put the data.
2. copy a portion of your data to a temporary RAM buffer so it can format the fields for copying.
3. write the "block" of your data to program memory
4. load the next "block" of your data into the temporary RAM buffer and repeat until done.
The read function does something similar. |
|
|
avatarengineer
Joined: 13 May 2013 Posts: 51 Location: Arizona
|
PIC24 : using Flash memory to store ... |
Posted: Wed Jul 23, 2014 2:17 pm |
|
|
Bless you, Jeremiah.
This looks great! |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1353
|
|
Posted: Wed Jul 23, 2014 2:21 pm |
|
|
no problem.
Just remember to thoroughly test it. While it works for my PIC24 chip, yours is very different from mine, so you'll need to make sure it works correctly.
Also remember, that program memory tends to have a shorter number of write attempts to it compared to EEPROM, so don't try to write to it often. |
|
|
avatarengineer
Joined: 13 May 2013 Posts: 51 Location: Arizona
|
PIC24 : using Flash memory to store calibration data |
Posted: Wed Jul 23, 2014 2:47 pm |
|
|
Thanks again;
Since I only intend to store calibration information that is entered only a few times in the lifetime of the product, I doubt there will be much wear-out.
Question:
I have 20 Float values which I will "translate" into 80 bytes for storage.
The struct { int8 CAL[80] } CALPOINTS
Would my intended code be to call
pgm_eeprom_write(0,&CALPOINTS,sizeof(CALPOINTS));
to complete the storage? |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1353
|
|
Posted: Wed Jul 23, 2014 4:48 pm |
|
|
Yes that is the intended usage.
You just need to verify that writing to address 0 of the "eeprom" in program memory is correctly erasing first:
Read data from eeprom and print to screen
Increment one of the values
Write back to eeprom
Reset power
See if value read is the one stored last time after the increment. I assume it works on your chip too, but have only tested on my own.
To read back you would use:
pgm_eeprom_read(0,&CALPOINTS,sizeof(CALPOINTS));
EDIT: if you just have an array, you don't need the struct:
int8 data[80];
pgm_eeprom_write(0,data,sizeof(data));
should also work.
If you have trouble, try a simpler structure or array first and use read_program_memory() to verify the data is being written to the right spot. |
|
|
guy
Joined: 21 Oct 2005 Posts: 297
|
Another option |
Posted: Mon Sep 01, 2014 10:29 am |
|
|
Thanks for the code Jeremiah.
Here is a more simple approach to configuration / calibration data with obvious pros and cons. I used a fixed-size RAM buffer as a copy of a Flash Memory page. It can store 64 16-bit numbers.
On startup the flash memory page is read to RAM (readFlashPage) then during runtime Reads and Writes are done to RAM (readFlash, writeFlash), and at any time it can also be written back to flash (before shutdown or optionally after every change) using flashWritePage.
The code is for PIC24FJ64GA308, write page size is 64 words and erase page is 512 words (or 2kbytes as address ranges).
* Pay attention to the addresses and data sizes when migrating to a different device.
Code: |
#define FLASH_DATA_AREA 0xA000
#define FLASH_DATA_SIZE 64 // in instructions. x3 or x4 = in bytes
#ORG FLASH_DATA_AREA, FLASH_DATA_AREA+2047 {} // page reserved for data
byte flashBuf[FLASH_DATA_SIZE*4]; // flash page buffer
// example how to declare your parameters. Each one can be 16-bit int.
#define FLASH_CALIB1 0 // location in flash parameter page
#define FLASH_CALIB2 1
#define FLASH_CRC 2
////////////////////////////
void readFlashPage() { // read flash data page
read_program_memory(FLASH_DATA_AREA,flashBuf,FLASH_DATA_SIZE*4);
}
//////////////////////////////
void writeFlashPage() {
// make sure to call readFlashPage() on startup, to avoid losing data
// automatic page erase
write_program_memory(FLASH_DATA_AREA, flashBuf, FLASH_DATA_SIZE*4);
}
////////////////////////////
int16 readFlash(byte a) { // read from RAM copy, not real flash
int16 r;
a<<=2; // *4
r=flashBuf[a++]+flashBuf[a]*256;
return(r); // get first 2 bytes of instruction
}
////////////////////////////
void writeFlash(byte a, int16 d) { // write to RAM copy, not real flash
a<<=2; // *4
flashBuf[a++]=d&0xFF; // lo byte
flashBuf[a]=(d>>8); // hi byte
}
|
|
|
|
Orcino
Joined: 07 Sep 2003 Posts: 56
|
|
Posted: Mon Dec 12, 2016 4:08 pm |
|
|
jeremiah, can you give a simple example of how to write int8 and int16 variables ?
I'm not getting.
Thanks |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19537
|
|
Posted: Tue Dec 13, 2016 1:47 am |
|
|
One thing that has not been mentioned.
CCS do now supply a 'virtual EEPROM' driver for these chips. This uses the same system the MicroChip showed in an application note for these. It uses a minimum of two pages of the ROM memory, and emulates EEPROM, by working on the basis of holding a 'flag/counter' to say what each entry holds. This is a better driver, if only a few bytes in the configuration data need to change, since it does not erase a whole page, until it has to. As locations are 'used up', it flags them as used, and uses other locations in the same page (remember you can change any bit from '1' to '0' without having to erase - it's only when you have to change to '1' that an erase is needed). Each locations stores the 'address' being used, and the value stored at this address.
For what is being described this will be more efficient, since you can set the virtual EEPROM size to just 128bytes (for your 80 bytes), and this will then allow several writes before an erase it needed. The downside is it using two pages of ROM. So if ROM is short, don't use it.
The driver is called 'virtual_eeprom.c'.
If you #define MAX_EEPROM_MEMORY (128), then this will behave just like an external EEPROM, with 128 byte addresses.
The driver tells you how to modify the standard example program 2416.c, for testing. |
|
|
Orcino
Joined: 07 Sep 2003 Posts: 56
|
|
Posted: Tue Dec 13, 2016 5:03 am |
|
|
Thank you very much Ttelmah.
Solved my problem.
This month is already the second time you help me.
Thanks again. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19537
|
|
Posted: Tue Dec 13, 2016 5:12 am |
|
|
Glad to help.
Like Jeremiah, I had to write my own version some time ago, then 'spotted' more recently that CCS now did a driver, and made a point of 'remembering this' in case a version was needed in the future... |
|
|
|
|
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
|