![](templates/subSilver/images/CCSLogo.jpg) |
![CCS C Software and Maintenance Offers](templates/subSilver/images/forumAd6.jpg) |
View previous topic :: View next topic |
Author |
Message |
FFT
Joined: 07 Jul 2010 Posts: 92
|
A struct in program memory [Solved] |
Posted: Sat Jul 31, 2021 12:28 pm |
|
|
Hello,
I need to have a large (1200 bytes) struct variable with 18LF14K50, but it's RAM is not large enough to hold that struct.
The only way seems to be to store the struct in the program memory.
Is there an easy way to read/write the members of the struct in parts when it is stored in the program memory?
I have read about the addressmod feature and write_program_memory function but I couldn't imagine the use case. There are some limitations like every forth byte is 0x00 and some erasing rules.
I just want to access (write/read) the members of the struct one by one as it was in RAM memory.
The struct's usage is like : Code: | Filter.Programs[Filter.Options.SelectedProgram].Channels[1].PwmOut = 1; |
Thanks in advance.
Last edited by FFT on Sun Jan 02, 2022 3:56 pm; edited 1 time in total |
|
![](templates/subSilver/images/spacer.gif) |
temtronic
Joined: 01 Jul 2010 Posts: 9168 Location: Greensville,Ontario
|
|
Posted: Sat Jul 31, 2021 4:36 pm |
|
|
possible options...
1) choose another PIC, similar but with more RAM
2) add a 2KB RAM peripheral(say SPI<>RAM ??)
#1 should be easy, #2 costs a bit more and some code.
Using any onboard EEPROM will mean significant delays during writes and you may NOT be able to just write a single byte. Most will require a 'page' to be written, say 64 bytes ? Read in the current page into RAM, make the change, then write the page. You'll have to read very carefully the PIC datasheet on that. Not all PICs use the same EEPROM structure !!
I'm sure others will think of more options..
You'll also have to consider ... is this a one off project, or making 1000 units ? If 1000, then you have to consider R&D costs as well as additional parts, PCB design, etc. |
|
![](templates/subSilver/images/spacer.gif) |
Jerson
Joined: 31 Jul 2009 Posts: 122 Location: Bombay, India
|
|
Posted: Sat Jul 31, 2021 9:05 pm |
|
|
If it is feasible, try to keep in RAM one channel worth of settings(if it fits). In your code, you could then retrieve from EEPROM space, modify and write back one channel at a time. So, the entire struct with a multiple number of channels will not reside in RAM at all times. You will pull out of EEPROM the data for the channel you wish to use. Lengthy? yes!
The struct in code could keep the entire default state of the EEPROM struct which you can use to write a known state. However, all read/write will be one channel at a time via the route above.
This is how I think I would tackle it. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19358
|
|
Posted: Sun Aug 01, 2021 2:18 am |
|
|
There seems to be some confusion here.
You refer to:
"There are some limitations like every forth byte is 0x00"
No. This is wrong.
This applies on the DSPIC's, but not on the PIC18.
On the PIC16, the program memory is only 14 bits wide. So if you want
to store bytes here, you effectively can only use the bottom 8 bits of each
location.
On the PIC18, the program memory is 16bits wide, and there is no special
organisation needed (hurrah!).
On the PIC24/30/33, the program memory is 24bits wide, with every fourth
byte therefore 'non existent'.
It looks as if you have been reading something about one of these later
chips....
So the first good news, is that you don't have any need to deal with every
fourth byte being non existent.
But there are big problems. These are:
First limited life. If you are changing this at all often, think again. Use
an external EEPROM or similar memory instead. The 'cell endurance', is just
10K cycles.
Then voltage. You talk about the LF chip. What is your supply voltage?.
There is a _minimum_ 2.2v requirement for erase on this chip. During the
erase, 5mA can be drawn, so you have to be very confident that the
voltage will not droop below this voltage when this happens.
Then as you have already realised, erase. The block size on the chip is
64 bytes. To change (say) byte 12 in a block, you have to read the entire
block, erase the block, change the byte in the RAM copy, and then write
the block back. No writing individual bytes,
Now the supplied CCS 'write_program_memory' function, will automatically
erase if you write to the first byte of a block. So you can simply have an
internal RAM 64byte buffer, and when you read a byte, instead read the
whole page and return the byte from this. When you write a byte, make
sure the buffer contains the page, change the byte, and write the page.
Then understand that during a write to the program memory, the chip
_stops_ completely. During the write instruction execution cannot happen.
A total of about 8mSec is needed to erase/write a 64byte block. During
this nothing at all can happen.
Understand, that the compiler already provides functions to allow you
to read such a value in program memory. const, and rom. You can store
a 'variable' declared with these in ROM, and read it just as if it was in RAM.
With the rom form, you can even use pointers to these variables.
Writing though is not directly supported. However if you use the rom
form, and #locate to a page boundary, you relatively easily could write
a function allowing your to write a specific byte in this. However to write
to it as a 'variable', would require addressmod to be used. |
|
![](templates/subSilver/images/spacer.gif) |
FFT
Joined: 07 Jul 2010 Posts: 92
|
|
Posted: Sun Aug 01, 2021 2:50 am |
|
|
Thank you all for the valued answers.
I will try to answer all of your questions;
- The PIC has
---- 16k flash
---- 8192 single-word instruction
---- 768 bytes RAM
---- 256 bytes eeprom
- yes, I'm making 1000+ units
- I cannot use external component neither change the PIC
- PCB and all other stuff are ready
- One channel does not fit into RAM, but I can use 64 bytes buffer
- PIC18 - hurrah!! really good news.
- Supply voltage is 3.3V / 100mA
- 64 bytes buffer and 8ms write time with 5mA is OK for me
- I don't change those settings very often, so no problem about endurance
- Most of the time I will read the data, writes are rare.
- The PIC has 256 bytes EEPROM also, I can move the often-write part there if I have any.
I need a code example for how to use those features (addressmod, #locate, #rom etc..)
I have a big struct but I can divide it in parts as well (well, I don't want so much )
I need a RAM emulation in the ROM supporting the page-write rules.
I will be happy if you help with some example code just to redirect me to the right scenario.
What about this virtual eeprom driver?
Sincerely |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19358
|
|
Posted: Mon Aug 02, 2021 4:44 am |
|
|
There is a virtual EEPROM driver supplied with the compiler.
However understand that you use the value as virtual EEPROM, will
require you to do the work to handle it as a variable. The point about
addressmod, is it does the work to 'virtualise' the variable.
Try it. Write your own code.
If you have problem, then post this code, and we will help you. |
|
![](templates/subSilver/images/spacer.gif) |
FFT
Joined: 07 Jul 2010 Posts: 92
|
|
Posted: Tue Dec 28, 2021 2:46 am |
|
|
Hello,
I have 12 identical structs which are 55 bytes long in a upper struct. I removed the upper struct and planned to place 12 structs on 64 bytes blocks one by one
So the rest of the block will stay empty. Every struct should start from the beginning of a block whom I know the exact address for erase then write, also read as a const.
1 - How can I locate the "rom" labeled struct at given address?
2 - Where are the block addresses given in the datasheet, I couldn't find them.
I tested the read from "rom" labeled struct works properly. So I can use it as a normal const variable in the code.
Thanks. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19358
|
|
Posted: Tue Dec 28, 2021 4:42 am |
|
|
The 'addresses', are just every multiple of 64bytes. In the data sheet,
table at the start of the section 'Flash Program Memory'. 0, 0x40, 0x80
etc.etc..
Now, being able to use it as 'const', has a big downside, in that it means the
compiler will add the 'access' routine in front of the actual data. So trying
to put the individual sections on page boundaries becomes more complex.
So, something like:
Code: |
#org 0x37F2, 0x3FFF //note start address offset to allow for routine
const struct data_block {
int16 demo[30]; //60 bytes
char filler[4];
} block[12] = \
//Note this is made 64 bytes long. Do the same with your structure. Justify it
//out so that the structure is actually 64 bytes long.
{
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29}
}; //set of 12 sets of default valuea
#org DEFAULT
//Then your main code.
|
If you look at 'symbols', you will find that 'block.data', is now at 0x3800,
and since each element is 64 bytes, each element of the array is nicely
sitting on the page boundaries. |
|
![](templates/subSilver/images/spacer.gif) |
FFT
Joined: 07 Jul 2010 Posts: 92
|
|
Posted: Tue Dec 28, 2021 1:03 pm |
|
|
So I can hold an instance in RAM and read the ROM side one time. That will improve the performance and size as far as I understood.
My questions;
- Why 0x37F2 but not 0x3800 on #ORG line?
- How to read/erase/write_program_memory on that struct's block indexes correctly? Any example calls will help.
If you open the CCS help, that part looks not completed. It's not clear enough for me. Help can be improved with more explanation and examples like my case to decrease similar questions/confusions.
Thanks |
|
![](templates/subSilver/images/spacer.gif) |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Dec 28, 2021 5:05 pm |
|
|
FFT wrote: |
Why 0x37F2 but not 0x3800 on #ORG line?
|
Look at the .LST file:
Code: |
37F2: ADDLW 00
37F4: MOVWF FF6
37F6: MOVLW 38
37F8: ADDWFC FF7,F
37FA: TBLRD*+
37FC: MOVF FF5,W
37FE: RETURN 0
3800: DATA 00,00
3802: DATA 01,00
3804: DATA 02,00
3806: DATA 03,00
3808: DATA 04,00 |
Then consider Ttelmah's comment:
Quote: | Being able to use it as 'const', has a big downside, in that it means
the compiler will add the 'access' routine in front of the actual data.
|
|
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19358
|
|
Posted: Wed Dec 29, 2021 2:41 am |
|
|
As PCM says, I was offsetting to allow for the access routine. The actual
address where the data 'is' can be found using label_address.
Seriously, if you are going to access with program_memory instructions,
then don't declare this as 'const'. Just use #rom to place the data where
you want, and copy the data into ram with a read_program_memory
instruction. The point is that declaring this as 'const', allows it to be read
using an instruction like: ram_copy=block[count]; and the compiler
will automatically copy block[count] into a version in RAM. Also you can
directly read the versions in ROM, so block[0].demo[2] is a directly
readable value, and the compiler will handle accessing this. The extra code
to allow this access is what the compiler adds 'in front' of the data.
Remember you can also declare using rom, instead of const. So:
Code: |
rom struct data_block {
int16 demo[30]; //60 bytes
char filler[4];
} block[12] = \
//Note this is made 64 bytes long. Do the same with your structure. Justify it
//out so that the structure is actually 64 bytes long.
{
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29}
}; //set of 12 sets of default valuea
void main()
{
struct data_block ram_copy;
int count;
while(TRUE)
{
for (count=0; count<fred; count++)
{
ram_copy=block[count];
}
//TODO: User Code
}
}
|
Again 'label_address' allows you to find where the data actually is,
and by default rom with an element that is 64bytes long will align it
to the pages.
You can explicitly put data 'at' a rom address, instead using #rom.
But this then can't be read as a variable. You would have to transfer
using read_program_memory. |
|
![](templates/subSilver/images/spacer.gif) |
FFT
Joined: 07 Jul 2010 Posts: 92
|
|
Posted: Wed Dec 29, 2021 4:06 am |
|
|
Thank you very much. I understood the difference and I choose using const to be able to read it easily in code.
- Is there a calculation of the offsetting for the access routine?
- Is it always 14 addresses long?
The last help I would need is how to write a RAM instance into ROM place properly.
- writing whole data_block instance in the block[4] into ROM
- writing data_block instance's parameter block[3].demo[2] into ROM
I need to store the instances when there is a change.
I would be very happy if you give an example code. BTW I would need to erase the flash block first as far as I understand correctly for chips where getenv("FLASH_ERASE_SIZE") > getenv("FLASH_WRITE_SIZE")
Thanks for the clarifications. |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19358
|
|
Posted: Wed Dec 29, 2021 5:42 am |
|
|
You would never write a single parameter into ROM. If you have a RAM
version that needs to go to a specific block in ROM, then you just write
the entire entity. The compiler automatically erases if you write to an
address that is the start of a block. so:
Code: |
int32 address=label_address(block); //This is now the start of all the data
//in ROM
void write_block(int16 block_number, byte * ram_version)
{
int32 write_to = address+block_number*64;
write_program_memory(write_to, ram_version, sizeof(data_block));
}
|
You work with a RAM copy. Pass the address of this to the function, and
the number of the array entry it is to go to.
If you are changing a single byte in an entity, then you read a version from
ROM, change the byte to want to alter, and then write the whole version
back.
It does seem to be making things unnecessarily complex to use const as
well.
Just have a RAM version that you work with. If you want this to contain
block[4], just use read_program_memory to read block 4 (exactly the
same address calculation as above). When you make changes write the
block back.
You can then just declare the data with #ROM, and know exactly where
everything is. |
|
![](templates/subSilver/images/spacer.gif) |
FFT
Joined: 07 Jul 2010 Posts: 92
|
|
Posted: Wed Dec 29, 2021 3:14 pm |
|
|
Hello and thanks alot.
I implemented the idea and I see the ROM address of my struct in symbols file:
Quote: | User Memory space: rom
003D00-003FFF Channels |
I didn't use #ORG, compiler has placed the variable there on its own.
The problem is that the label_address line gives the following error:
Quote: | Error 72 .... : Incorrectly constructed label |
My ROM variable is defined like this:
Code: | Channel_t rom Channels[12]; |
Definition of the struct:
Code: | typedef struct
{
char data[55]; // this part is more complex with inner structs and variables
char __filler[9]; // complete to 64
} Channel_t; |
Call of label_address:
Code: | int32 addr = label_address(Channels); |
Where can be the mistake? I read the help but no chance.
Thanks |
|
![](templates/subSilver/images/spacer.gif) |
Ttelmah
Joined: 11 Mar 2010 Posts: 19358
|
|
Posted: Thu Dec 30, 2021 2:17 am |
|
|
You don't use label address with rom data. You use this with const data to
find where the actual data 'is'. rom data supports using & to just find it,
and like RAM arrays, the name of the array _is_ it's address.
Since this is already the address, and not a label, you are asking the
compiler to try to give the address of the address, which is meaningless... |
|
![](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
|