Difference between revisions of "Storing constant data in program memory"

From Mech
Jump to navigationJump to search
 
(19 intermediate revisions by 4 users not shown)
Line 1: Line 1:
== Original Assignment ==

Your PIC has a relatively large amount of program memory (32 Kbytes) but relatively little data memory (1536 bytes). For some applications, we want to have lookup tables or calibration data stored on our PIC, but we don't want it hogging data memory. This project is to demonstrate how to write a program that stores constant data in program memory. Test your ability to do this by creating a look-up table for the sin function. This is a 1-d array with integer index corresponding to an angle (e.g., numbers 0 to 89 if the index is in degrees; angles in other quadrants can be determined by simple transformations). Depending on your choice of int8, int16, or float to represent the number, what angle resolution can you use for the index before you reach the limits of program memory?

See p. 43 of the PIC MCU C Compiler book.

Finally, give sample code for storing data in the EEPROM, too.

== Overview ==
== Overview ==
The PIC has a relatively large amount of program memory (32 Kbytes) but relatively little data memory (1536 bytes). For some applications, we want to have lookup tables or calibration data stored on our PIC, but we don't want it hogging data memory. The CCS C Compiler provides a few different ways to use program memory for data, discussed below. We have also provided an example of how to create a look-up table for the sin function to illustrate one of these methods.
The PIC has a relatively large amount of flash program memory (32 Kbytes) but relatively little data memory (1536 bytes of SRAM), as well as a very small amount of EEPROM (256 bytes). For some applications, we want to have lookup tables or calibration data stored on our PIC, but we don't want it hogging data memory. The CCS C Compiler provides a few different ways to use program memory for data, discussed below. We have also provided an example of how to create a look-up table for the sine function to illustrate one of these methods.


=== Constant Data ===
=== Constant Data ===
The CONST qualifier will place variables into program memory. If the keyword CONST is used before the identifier, the identifier is treated as a constant. These constants need to be initialized and cannot be changed at run-time.
The CONST qualifier will place variables into flash program memory. If the keyword CONST is used before the identifier, the identifier is treated as a constant. These constants need to be initialized and cannot be changed at run-time.


The ROM qualifier puts data in program memory with 3 bytes per instruction space. The address used for ROM data is a true byte address not a physical address. The & operator can be used on ROM variables even though the address is logical not physical.
The ROM qualifier puts data into flash program memory with 3 bytes per instruction space. The address used for ROM data is a true byte address not a physical address. The & operator can be used on ROM variables even though the address is logical not physical. Physical memory space is how the data is laid out on the memory in hardware, and may be spaced apart. This is in contrast to logical, or virtual memory, which is how the software views the memory.


The syntax is: const type id[cexpr] = {value}
The syntax is: const ''type'' ''name''[''size''] = {''values''}
Here's an example storing a list of numbers from 1 to 10 in program memory:
const int iCanCount[10] = {1,2,3,4,5,6,7,8,9,10};


You must initialize the constant variable when you declare it or the compiler will spit out an error at you. Which means, unfortunately, you can't do something like:
You must initialize the constant variable when you declare it or the compiler will spit out an error at you. This means you cannot use a loop to directly initialize the constant variable (in the case of large arrays). It is also not possible to use a temporary variable to run calculations in a loop before storing in a constant variable because the compiler will not let you declare new variables after running the calculations in the loop.

const int listOfNumbers[100];
for (i = 0; i < 100; i++)
listOfNumbers[i] = i;
end

What you may be able to do, though, is this:
int temp[100];
for (i = 0; i < 100; i++)
temp[i] = i;
end
const int listOfNumbers[100] = temp;


=== #ROM Directive ===
=== #ROM Directive ===
Line 38: Line 20:
The following example places the numbers 1, 3, 5, 7, 9 into ROM addresses starting at 0x2000:
The following example places the numbers 1, 3, 5, 7, 9 into ROM addresses starting at 0x2000:


#rom 0x2000 = {1, 3, 5, 7, 9}
#rom 0x2000 = {1,3,5,7,9}


You can also put strings into program memory using the #ROM directive:
You can also put strings into program memory using the #ROM directive:
Line 53: Line 35:
* Writes <b>count</b> bytes of data from <b>dataptr</b> to <b>address</b> in program memory.
* Writes <b>count</b> bytes of data from <b>dataptr</b> to <b>address</b> in program memory.
* Every fourth byte of data will not be written, this needs to be filled with 0x00.
* Every fourth byte of data will not be written, this needs to be filled with 0x00.




== Code ==
== Code ==
Line 71: Line 51:


===Creating a look-up table for the sine function using CONST===
===Creating a look-up table for the sine function using CONST===

*int8 = 8 bit number (1 byte); range 0 to 255 (2-3 digits)
*int16 = 16 bit number (2 bytes); range 0 to 65535 (4-5 digits)
*float = 32 bit number (4 bytes); range -1.5x10^45 to 3.4x10^38 (7-8 digits)


With values of sine from 0-89°, we can calculate sine values for all 360°. Below is code storing a table of sine values from 0-89° with a angle resolution of 1° and algorithm for calculating sine values for 90-360°.
With values of sine from 0-89°, we can calculate sine values for all 360°. Below is code storing a table of sine values from 0-89° with a angle resolution of 1° and algorithm for calculating sine values for 90-360°.
Line 90: Line 66:
//To look up sine values from the table, you could use a function like the following:
//To look up sine values from the table, you could use a function like the following:
float sineLookup(index){
//prior to calling the function, make sure that the input, i, is 0<=i<=359. If you don't,
i = index % 360; //Is there a better way to implement this?
//you will index outside of the sineTable[90] array and create the infamous, "stray pointer"
if (i >= 0 && i < 90)
return sineTable[i];
float sineLookup(unsigned int16 i){
else if (i >= 90 && i < 180)
if (i < 90)
return sineTable[179-i];
else if (i >= 180 && i < 270)
{return sineTable[i];}
return -sineTable[180-i];
else if (i < 180)
else if (i >= 270 && i < 360)
{return sineTable[179-i];}
else if (i < 270)
return -sineTable[359-i];
{return (-1*sineTable[i-180]);}
end
else if (i < 360)
{return (-1*sineTable[359-i]);}
}
}

===Using a Loop to Write Constant Data===
Although we must initialize a constant variable when we declare it, it is possible to use loops to calculate an array, which is then stored into program memory via other means.

The code below creates a list of 100 integers using a loop and writes them to program memory:

#include <stdlibm.h>
int * listptr;
int i;
listptr = calloc(1000, sizeof(int)); //allocates memory space for temporary variable
for(i=0x1000;i<=0x13e8;i++){
listptr[i]=i;
write_program_memory(i, listptr[i], 1);}
free(listptr); //deallocates memory used by temporary variable for future use

The data can be retrieved by read_program_memory(address, pointer, count) :ex. read_program_memory(0x1000,listptr,1);


===Memory Management===
===Memory Management===


This table stores values as floats and is 360 bytes in size. Since the PIC has 32 KB of program memory, we could theoretically hold a sine lookup table precise to 0.01125° (32000/360 = 88.8x larger size; 1°/88.8 = 0.01125°).
The above sine table stores values as floats and is 360 bytes in size. Since the PIC has 32 KB of program memory, we could theoretically hold a sine lookup table precise to 0.01125° (32000/360 = 88.8x larger size; 1°/88.8 = 0.01125°).

*int8 = 8 bit number (1 byte); range 0 to 255 (2-3 digits)
*int16 = 16 bit number (2 bytes); range 0 to 65535 (4-5 digits)
*float = 32 bit number (4 bytes); range -1.5x10^45 to 3.4x10^38 (7-8 digits)


const int8 sineTable[90] = ...
const int8 sineTable[90] = ...
Line 133: Line 131:


read_program_eeprom(address)
read_program_eeprom(address)

==References==
*PIC MCU C Compiler
*PICmicro MCU C
*http://en.wikipedia.org/wiki/Memory_address

Latest revision as of 16:31, 6 July 2008

Overview

The PIC has a relatively large amount of flash program memory (32 Kbytes) but relatively little data memory (1536 bytes of SRAM), as well as a very small amount of EEPROM (256 bytes). For some applications, we want to have lookup tables or calibration data stored on our PIC, but we don't want it hogging data memory. The CCS C Compiler provides a few different ways to use program memory for data, discussed below. We have also provided an example of how to create a look-up table for the sine function to illustrate one of these methods.

Constant Data

The CONST qualifier will place variables into flash program memory. If the keyword CONST is used before the identifier, the identifier is treated as a constant. These constants need to be initialized and cannot be changed at run-time.

The ROM qualifier puts data into flash program memory with 3 bytes per instruction space. The address used for ROM data is a true byte address not a physical address. The & operator can be used on ROM variables even though the address is logical not physical. Physical memory space is how the data is laid out on the memory in hardware, and may be spaced apart. This is in contrast to logical, or virtual memory, which is how the software views the memory.

The syntax is: const type name[size] = {values} Here's an example storing a list of numbers from 1 to 10 in program memory:

const int iCanCount[10] = {1,2,3,4,5,6,7,8,9,10};

You must initialize the constant variable when you declare it or the compiler will spit out an error at you. This means you cannot use a loop to directly initialize the constant variable (in the case of large arrays). It is also not possible to use a temporary variable to run calculations in a loop before storing in a constant variable because the compiler will not let you declare new variables after running the calculations in the loop.

#ROM Directive

Another method that can be used to assign data to program memory is the #ROM directive.

The syntax is: #rom address = {data, data, ... , data}

The following example places the numbers 1, 3, 5, 7, 9 into ROM addresses starting at 0x2000:

#rom 0x2000 = {1,3,5,7,9}

You can also put strings into program memory using the #ROM directive:

#rom 0x3000 = {"mechatronicsrocks"}

Built-in Functions

The compiler provides built in functions to place data in program memory.

write_program_eeprom(address,data);

  • Writes data to program memory. data is stored in 16-bit chunks.

write_program_memory(address, dataptr, count);

  • Writes count bytes of data from dataptr to address in program memory.
  • Every fourth byte of data will not be written, this needs to be filled with 0x00.

Code

#ROM Directive Examples

Writes sine values to ROM addresses starting at 0x1000

#rom 0x1000 = {0,0.017452,0.034899,0.052336,0.069756,0.087156,0.104528,0.121869,0.139173,0.156434,
   0.173648,0.190809,0.207912,0.224951,0.241922,0.258819,0.275637,0.292372,0.309017,0.325568,
   0.34202,0.358368,0.374607,0.390731,0.406737,0.422618,0.438371,0.45399,0.469472,0.48481,0.5,
   0.515038,0.529919,0.544639,0.559193,0.573576,0.587785,0.601815,0.615661,0.62932,0.642788,
   0.656059,0.669131,0.681998,0.694658,0.707107,0.71934,0.731354,0.743145,0.75471,0.766044,
   0.777146,0.788011,0.798636,0.809017,0.819152,0.829038,0.838671,0.848048,0.857167,0.866025,
   0.87462,0.882948,0.891007,0.898794,0.906308,0.913545,0.920505,0.927184,0.93358,0.939693,
   0.945519,0.951057,0.956305,0.961262,0.965926,0.970296,0.97437,0.978148,0.981627,0.984808,
   0.987688,0.990268,0.992546,0.994522,0.996195,0.997564,0.99863,0.999391,0.999848};

Creating a look-up table for the sine function using CONST

With values of sine from 0-89°, we can calculate sine values for all 360°. Below is code storing a table of sine values from 0-89° with a angle resolution of 1° and algorithm for calculating sine values for 90-360°.

const float sineTable[90] =
    {0,0.017452,0.034899,0.052336,0.069756,0.087156,0.104528,0.121869,0.139173,0.156434,
    0.173648,0.190809,0.207912,0.224951,0.241922,0.258819,0.275637,0.292372,0.309017,0.325568,
    0.34202,0.358368,0.374607,0.390731,0.406737,0.422618,0.438371,0.45399,0.469472,0.48481,0.5,
    0.515038,0.529919,0.544639,0.559193,0.573576,0.587785,0.601815,0.615661,0.62932,0.642788,
    0.656059,0.669131,0.681998,0.694658,0.707107,0.71934,0.731354,0.743145,0.75471,0.766044,
    0.777146,0.788011,0.798636,0.809017,0.819152,0.829038,0.838671,0.848048,0.857167,0.866025,
    0.87462,0.882948,0.891007,0.898794,0.906308,0.913545,0.920505,0.927184,0.93358,0.939693,
    0.945519,0.951057,0.956305,0.961262,0.965926,0.970296,0.97437,0.978148,0.981627,0.984808,
    0.987688,0.990268,0.992546,0.994522,0.996195,0.997564,0.99863,0.999391,0.999848};

//To look up sine values from the table, you could use a function like the following:

//prior to calling the function, make sure that the input, i, is 0<=i<=359.  If you don't,
//you will index outside of the sineTable[90] array and create the infamous, "stray pointer"

float sineLookup(unsigned int16 i){
   if (i < 90)
       {return sineTable[i];}
   else if (i < 180)
       {return sineTable[179-i];}
   else if (i < 270)
       {return (-1*sineTable[i-180]);}
   else if (i < 360)
       {return (-1*sineTable[359-i]);}
}

Using a Loop to Write Constant Data

Although we must initialize a constant variable when we declare it, it is possible to use loops to calculate an array, which is then stored into program memory via other means.

The code below creates a list of 100 integers using a loop and writes them to program memory:

#include <stdlibm.h>
int * listptr;
int i;
listptr = calloc(1000, sizeof(int)); //allocates memory space for temporary variable
for(i=0x1000;i<=0x13e8;i++){
    listptr[i]=i;
    write_program_memory(i, listptr[i], 1);}
free(listptr); //deallocates memory used by temporary variable for future use

The data can be retrieved by read_program_memory(address, pointer, count) :ex. read_program_memory(0x1000,listptr,1);

Memory Management

The above sine table stores values as floats and is 360 bytes in size. Since the PIC has 32 KB of program memory, we could theoretically hold a sine lookup table precise to 0.01125° (32000/360 = 88.8x larger size; 1°/88.8 = 0.01125°).

  • int8 = 8 bit number (1 byte); range 0 to 255 (2-3 digits)
  • int16 = 16 bit number (2 bytes); range 0 to 65535 (4-5 digits)
  • float = 32 bit number (4 bytes); range -1.5x10^45 to 3.4x10^38 (7-8 digits)
const int8 sineTable[90] = ...

Because int8 cannot hold decimal values, and can only have up to 256 different values, a table made with int8 values might be difficult. A more realistic version using int16 would be more useful, and could start off like this:

{0,175,349,523,697,872,1045,1219,1392,1564,1736,
1908,2079,2250,2419,2588,2756,2924,3090,3256...}

In your main program, you could insert a function that divides all these values by 10,000 to obtain the decimal value for the sine table. In the previous example, a sine table would take up 180 bytes. We could theoretically hold a sine lookup table precise to 0.00562° (32000/180 = 177.8x larger size; 1°/177.8 = 0.00562°).

Creating a sine look-up table using EEPROM

Writing a sine table to EEPROM:

float data[4];
for (i = 0,i <= 90>>2, i++)
    for (j = 0,j<4,j++)
        data[j]=sineTable[i<<2 + j];
    end
    write_program_eeprom(i,data);
end

To read, use:

read_program_eeprom(address)

References