|
|
View previous topic :: View next topic |
Author |
Message |
Budius
Joined: 01 Sep 2009 Posts: 8 Location: England
|
Pointing to array of constant strings |
Posted: Tue Sep 01, 2009 5:31 am |
|
|
I'm doing an LCD interface that is language selectable.
Due to the memory constrains of a PIC I'll use the language tables in the program memory area (const).
PIC 18F8722
Compiler PCWH ver. 4.073
I've already created a language table and just duplicate it for as many languages as I need.
Code: | const char English[][*] = {
"alarm 1",
"alarm 2",
"alarm 3",
"alarm 4",
"alarm 5",
"alarm 6"
}; |
I've already managed to strcpy the whole language table to a RAM variable, but this is still 'eating' almost 20% of my RAM. Max_Messages should be around 30 and Max_len 32.
Code: | char msg[Max_Messages][Max_Len] |
I wonder if there is a way to create a pointer to the table and still be able to access it's elements using Code: | msg[MessageNumber][0] |
Something like:
Code: | char *msg[][*];
void set_language(int8 Language){
Switch (Language){
case ENGLISH: msg=&English; break;
...
... etc
}
}
void main(){
...
...
...
printf(lcd_putc, msg[ALARM1][0]);
...
...
...
} |
The problem with this code I wrote is that does not compile with errors: "expect a declaration" and "subscript out of range"
And if I remove the [ ] and just declares still does not compile with errors: "Too many subscripts"
I know I can create one pointer to each Message and set all the pointers whenever selecting the language.
But I reckon that should still be a cleaner and more str8 forward way to do it.
Suggestions? Comments?
Thanks a lot !
Last edited by Budius on Wed Sep 02, 2009 9:58 am; edited 1 time in total |
|
|
Ttelmah Guest
|
|
Posted: Tue Sep 01, 2009 7:20 am |
|
|
At 'heart', there is a key bit of understanding of the PIC. Unlike the PC, the processor in the PIC, uses an architecture, with separate ROM, and RAM memory spaces. On early PICs, it wasn't actually possible to directly read the ROM at all (constants were done by creating a pgram the returned the selected value!.). Hence creating a 'pointer' to a constant, is hard.
Generally supporting pointers to constants, even on the later PICs allowing reading of the ROM space, has a significant coding overhead attached.
On this basis, CCS, have retained their original default behaviour, for 'const' types, in not allowing pointers to be constructed to them.
They have a distinct 'ROM' type, which supports pointers, but with the extra overheads.
So you could declare you array as a ROM array, rather than a constant array, and then use pointers to this. Only problem, is that you can't initialise the array of pointers at compile time, you need to do this in your code, but this is what you already show. Remember though that the name of an array, _is_ it's address, so you don't need the '&' shown, also the lines in the array, will need to be fixed lengths.
Best Wishes |
|
|
Budius
Joined: 01 Sep 2009 Posts: 8 Location: England
|
|
Posted: Tue Sep 01, 2009 7:27 am |
|
|
thanks for the reply Ttelmah,
the ROM (I really mean EEPROM) is not an option as it only have 1024 bytes available and I'm already using some of it for other parts of the system.
The only area with enough space is the Program Memory flash that have 128Kbytes
I'll make some tests around either creating several pointers (one for each message) OR a creating an array of pointers OR a function specialized with a switch language to direct access the LCD for me and just calls this whenever necessary.
thanks again for the superb explanation.
cheers!
---------
edit:
further thinking on it I reckon the option with the smaller overhead and cleaner code will be:
1. define an array of pointers (one for each message)
2. define enum t_messages
3. create a function to change the pointers upon change of language
4. printf(lcd_putc, pointer[t_message])
|
|
|
Ttelmah Guest
|
|
Posted: Tue Sep 01, 2009 7:48 am |
|
|
You are slightly misunderstanding the use of the ROM keyword.
The PIC, has multiple types of ROM. It has EEPROM, and the flash program memory, which is also ROM (basically 'ROM', is any memory, that can't be simply written, but requires programming to change).
If you declare a value as:
Code: |
ROM char English[][8] = {
"alarm 1",
"alarm 2",
"alarm 3",
"alarm 4",
"alarm 5",
"alarm 6"
};
|
This is stored in the flash memory (not the EEPROM), just like a 'const', but you can use pointers to this value.
Best Wishes |
|
|
Guest
|
|
Posted: Wed Sep 02, 2009 5:08 am |
|
|
I understood what you mean on the use of ROM.
I've been a LOT of test on this since yesterday and the ROM got me into point that I simply can't see what is happening.
Using: Code: | ROM char English[number_of_messages][max_message_lenght] = {
"Warm up\nPlease wait!",
"next message\n etc", ... |
I can't even return a
Code: | printf("%s", English[i]); |
But if I use the const, then I can see printf returning any message on the table.
Furthermore I've been printingF different values and addresses to try to see what is happening.
Code: | printf("&English[i] = %u",&English[i]);
printf("Msg: %u &Msg: %u *Msg(s, u, c, x): %s, %u, %c, %x", msg, &msg, *msg, *msg, *msg, *msg); |
returns wrote: |
&English[i] = 176
Msg: 176 &Msg: 32 *Msg(s, u, c, x): 3, 0, , 00 |
Which means it is pointed to the right address, right
My pointer is declared and pointed with (in different parts):
Code: | char* msg;
msg = &English[i]; |
ps.: I used 'i' but I'm actually using different values on the table to test. 0, 3, 6, 2, etc
I've actually manage to make the whole thing work ignoring pointers and just having a 'mirror' table of the actual language on the RAM and use
Code: | int8 i, j;
for(i=0;i<24;i++){for(j=0;j<35;j++){
msg[i][j] = English[i][j]; |
But this still used a lot of RAM and I really don't think it's the best solution. Therefore I'm been really keen to find the best solution.
ps.: If I find it I'll post it on that area of the forum for working codes |
|
|
Budius
Joined: 01 Sep 2009 Posts: 8 Location: England
|
|
Posted: Wed Sep 02, 2009 5:13 am |
|
|
this last post was mine... sorry, I didn't know guest could post here!
I thought I was logged. |
|
|
Budius
Joined: 01 Sep 2009 Posts: 8 Location: England
|
|
Posted: Wed Sep 02, 2009 7:35 am |
|
|
more more and more testings... and I guess final conclusions.
Test 1
Code: | char str[] = "abcdef";
char *ptr_str;
ptr_str = &str[0];
printf("str_prt: %s", ptr_str); |
returns:
Perfect, that is the exactly way to point to and printf a string
Test 2
Code: | char *msg;
char English[number_of_messages][max_message_lenght] = {
"\fWarm up\nPlease wait!",
"\fnext message\n goes", ...
//----------------------
msg = &English[0][0];
printf("Using str_ptr example, msg = %s", msg); |
returns:
Code: | Using str_ptr example, msg = Warm up
Please wait | Perfect, note that the language table is declared in the RAM
Test 3
Code: | char *msg;
ROM char English[number_of_messages][max_message_lenght] = {
"\fWarm up\nPlease wait!",
"\fnext message\n goes", ...
//----------------------
msg = &English[0][0];
printf("Using str_ptr example, msg = %s", msg); |
returns:
Code: | Using str_ptr example, msg = |
This time the language table was addressed to the ROM and all the rest of the code is the same.. it does not return anything. Also does not return if I try to printf straight from the language table.
the fuses and devices declaration I am doing in the main.h follows.
Code: | #device ICD=TRUE
#device adc=10
#FUSES NOWDT //No Watch Dog Timer
#FUSES WDT64 //Watch Dog Timer uses 1:64 Postscale
#FUSES HS //High speed Osc (> 4mhz)
#FUSES NOPROTECT //Code not protected from reading
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES BROWNOUT //Reset when brownout detected
#FUSES BORV25 //Brownout reset at 2.5V
#FUSES NOPUT //No Power Up Timer
#FUSES NOCPD //No EE protection
#FUSES STVREN //Stack full/underflow will cause reset
#FUSES NODEBUG //No Debug mode for ICD
#FUSES LVP //Low Voltage Programming on B3(PIC16) or B5(PIC18)
#FUSES NOWRT //Program memory not write protected
#FUSES NOCPB //No Boot Block code protection
#FUSES NOEBTRB //Boot block not protected from table reads
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES NOWRTC //configuration not registers write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES LPT1OSC //Timer1 configured for low-power operation
#FUSES MCLR //Master Clear pin enabled
#FUSES XINST //Extended set extension and Indexed Addressing mode enabled
#FUSES MCU //Microcontroller Mode
#FUSES WAIT //Wait selections for Table Reads and Table Writes
#FUSES BW16 //16-bit external bus mode
#FUSES ABW20 //20-bit Address bus
#FUSES ECCPE //Enhanced CCP PWM outpts multiplexed with RE6 thorugh RE3 |
Unless there is something on those FUSES that have to be changed, it simply does not work access straight the ROM string and the solution will be to copy from the ROM to the RAM before use it.
anyone got any comment about it? |
|
|
Ttelmah Guest
|
|
Posted: Wed Sep 02, 2009 7:49 am |
|
|
Have you actually tried looking at the manual?.....
You need to declare a ROM pointer, to access a ROM array.
Code: |
ROM char English[][8] = {
"alarm 1",
"alarm 2",
"alarm 3",
"alarm 4",
"alarm 5",
"alarm 6"
};
ROM char *ptr;
//Then
ptr = &English[3][0];
printf("%s",ptr);
|
The reason 'const' works for your printf example, is that printf itself is effectively 'overloaded'. CCS has a shortcut for const arrays, where if you hand the name of the array to a function expecting an integer, it automatically 'walks' through the array returning one byte at a time. printf, 'knows' about this, and if handed the name of a const array, automatically switches mode to handle this (as does strcpy). However you can't perform other string operations on the const array.
Using the ROM kyword for the pointer, tells the compiler to add the code to hanlde the data being in ROM, when you use this pointer.
Best Wishes |
|
|
Budius
Joined: 01 Sep 2009 Posts: 8 Location: England
|
|
Posted: Wed Sep 02, 2009 9:26 am |
|
|
Ttelmah wrote: | Using the ROM kyword for the pointer, tells the compiler to add the code to hanlde the data being in ROM, when you use this pointer. | did you know what is the compiler minimum version to do this?
Code: | ROM char *msg;
ROM char English[6][4] = {
"abc",
"def",
"ghi",
};
//************************************
char rcv_char;
int1 NewChar = FALSE;
#task(rate=300ms,max=100ms)
void serial_comm_processing() {
if(NewChar==TRUE){
printf(" -> ");
Switch(rcv_char){
case '*': printf("Hello World!"); break;
case 'a':
msg = &English[0][0];
printf("msg = %s", msg);
break;
case 'b':
msg = &English[1][0];
printf("msg = %s", msg);
break;
case 'c':
msg = &English[2][0];
printf("msg = %s", msg);
break;
}
putc(ASCII__CR); //Send CR
putc(ASCII__LF); //and send LF
NewChar = FALSE;
}
}
//************************************
#int_RDA
void RDA_isr() {
rcv_char=getc();
NewChar = TRUE;
}
|
returns:
Code: | * -> Hello World!
a -> msg =
b -> msg =
c -> msg = |
|
|
|
Ttelmah Guest
|
|
Posted: Wed Sep 02, 2009 10:04 am |
|
|
I'd have though 4.073 'ought to do it', but it is a feature that was working on some versions, then went wrong again. Since it is a documented 'meant to work' feature, you can simply moan to CCS, who should allow you to download a version that does work. It had 'oddities' initially, with going wrong, when string length totals went over certain sizes.
Worth possibly just trying with the array size matching what you initialise it to (only three lines), and without the trailing ',' on the last line, since it might well get confused by the array not really matching what you are saying it is.
Best Wishes |
|
|
Budius
Joined: 01 Sep 2009 Posts: 8 Location: England
|
|
Posted: Wed Sep 02, 2009 10:20 am |
|
|
Ttelmah wrote: | I'd have though 4.073 'ought to do it', but it is a feature that was working on some versions, then went wrong again. Since it is a documented 'meant to work' feature, you can simply moan to CCS, who should allow you to download a version that does work. It had 'oddities' initially, with going wrong, when string length totals went over certain sizes.
Worth possibly just trying with the array size matching what you initialise it to (only three lines), and without the trailing ',' on the last line, since it might well get confused by the array not really matching what you are saying it is.
Best Wishes |
I just checked CCS version list: http://www.ccsinfo.com/devices.php?page=versioninfo and there are some pointers to ROM fixes on 4.082
I just e-mailed then quoting to renew the license and keep updating for another year.
Or maybe I'll just do this:
Code: | char *display_msg(char *msg, t_Messages Message, t_Languages Language){
int16 i;
switch(Language){
case l_EN: for(i=0;i<MaxMessageLenght;i++) msg[i] = English[Message][i]; break;
}
return msg;
} |
that, despite the overhead works just perfect!
And as this display is only expected to change every few minutes, shouldn't be a problem.
Thanks a lot for all the help! |
|
|
leo66 Guest
|
rom access problem |
Posted: Mon Oct 05, 2009 4:38 am |
|
|
Code: |
#include <16f877.h>
#fuses HS,NOWDT
#use delay(clock=16000000)
#include "lcd1.c"
rom char English[][8] = {
"alarm 1",
"alarm 2",
"alarm 3",
"alarm 4",
"alarm 5",
"alarm 6"
};
rom char *ptr;
void main()
{
ptr=&English[0][0];
set_tris_c(0x18);//(0x00011000b)
set_tris_lcd(LCD_WRITE);
set_tris_lcd1(LCD_WRITE);
lcd_init(); // setup LCD-Display
delay_ms(100); // wait a little bit until Display is initialized
lcd_putc("\f");
lcd_gotoxy(1,1);
printf(lcd_putc,"%s",ptr);
delay_ms(500);
while(1);
}
|
I tried this code but it doesn't work. Is it a compiler version (4.090) related error?
If I eliminate the ROM keyword in front of the string array and the pointer declaration the program works fine.
Any suggestion will be appreciated.
Thank you very much. |
|
|
Jim Hearne
Joined: 22 Dec 2003 Posts: 109 Location: West Sussex, UK
|
|
Posted: Wed Oct 07, 2009 3:21 am |
|
|
This is how I did it on a recent project.
Everything is stored in rom except the current string being displayed.
Code: |
#define STRING_ROM_ADDRESS 0x00010000
#define NUMBER_OF_LANGUAGES 3
#define STRING_ROM_SIZE_MAX 0xffff
#ROM STRING_ROM_ADDRESS ={
// English Spanish Portuguese
" ", " ", " ", // 0 // menu and data screens
"% Post Bl'd", "% Pos Prga", "% Pós Bld", // 1
"12hr Clock", "Reloj 12hr ", "Relógio 12hr", // 2
"20mA Output", "Salida 20mA", "Saída 20mA", // 3
"24hr Clock", "Reloj 24hr", "Relógio 24hr", // 4
"4-20 Output", "Salida 4-20", "Saída 4-20", // 5
"4mA Output", "Salida 4mA", "Saída 4mA", // 6
"Accum Count", "Cntdr de Fljo", "Vazão Agua", // 7
"Accum Set", "Set flujo Ac", "Accum Set", // 8
// 200 more here normally.
"~" //end character
#define NUMBER_OF_STRINGS 9 // one more than last string above.
|
Then at startup create an index by looking for the nulls at the end of each string.
Code: |
int16 string_offsets[NUMBER_OF_STRINGS][NUMBER_OF_LANGUAGES];
void create_message_index(void)
{
int32 message_pointer=0;
int16 number_nulls=0;
int16 number_strings=0;
int16 temp_value=0;
int16 temp_start_addr=0;
for(message_pointer=0; message_pointer<STRING_ROM_SIZE_MAX && number_strings<=NUMBER_OF_STRINGS; message_pointer++)
{
temp_value=read_program_eeprom(message_pointer+STRING_ROM_ADDRESS); // read program memory returns 16 bits but it's easier to just use the bottom 8
temp_value &= 0x00ff;
if((temp_value)==0) // scan for nulls after each string
{
string_offsets[number_strings][number_nulls]=temp_start_addr;
temp_start_addr=message_pointer+1; // save current value plus one for next time.
number_nulls++;
if(number_nulls==NUMBER_OF_LANGUAGES)// got string for each language, increment number of messages.
{
number_nulls=0;
number_strings++;
}
}
else if(temp_value=='~')
break;
}
}
|
Copy the string into a local buffer for sprintf or whatever using:
Code: |
void copystring(int8 *dest,int8 msg)
{
int32 copy_address=0;
int16 copy_temp=0;
int8 language=0;
language=nvram.language; // get language from nvram
if(language>NUMBER_OF_LANGUAGES) // range check
language=0;
if(msg>NUMBER_OF_STRINGS)
msg=135; // if msg is out of range then use error string as msg
copy_address=STRING_ROM_ADDRESS+string_offsets[msg][language];
do
{
copy_temp = read_program_eeprom(copy_address);
copy_temp &= 0x00ff;
*dest=(int8)copy_temp;
dest++;
copy_address++;
}
while(copy_temp!=0);
}
|
Example
Code: |
copystring(text_buffer,2); // "12 hr clock"
printf(text_buffer); // print it
|
These are code snippets so you'll need to change the rom address etc to match your pic but it works on a 18F6722 in PCW 4.086 and all previous versions I've tried.
Jim |
|
|
bkamen
Joined: 07 Jan 2004 Posts: 1615 Location: Central Illinois, USA
|
|
Posted: Wed Oct 07, 2009 10:47 am |
|
|
Doesn't this "new" feature solve some of this:
in the help under: #DEVICE
PASS_STRINGS=IN_RAM
A new way to pass constant strings to a function by first copying the string to RAM and then passing a pointer to RAM to the function.
_________________ Dazed and confused? I don't think so. Just "plain lost" will do. :D |
|
|
Jim Hearne
Joined: 22 Dec 2003 Posts: 109 Location: West Sussex, UK
|
|
Posted: Wed Oct 07, 2009 11:48 am |
|
|
I tried that, it didn't work though it may on current versions.
Jim |
|
|
|
|
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
|