Storing and Accessing Data in Paged RAM and Flash
This chapter describes:
- The types of memory on the PDQ Board;
- How to read and write non-volatile Flash and EEPROM memory;
- How to locate non-volatile data in EEPROM;
- How to use C arrays to store data in common RAM; and,
- How to use Forth arrays to store large amounts of data acquisition results in paged RAM.
Understanding these issues will enable you to write fast, efficient instrument control programs that maintain the integrity of your data.
Memory types on the PDQ Board
The PDQ Single Board Computer (SBC) offers several types of memory, including common RAM (both on and off the processor chip), paged RAM backed by a shadow flash, and paged onchip Flash located on the Freescale HCS12 (9S12) processor chip.
Common RAM
Common RAM (whether on the HCS12 or implemented by an external RAM chip on the PDQ Board) can be read or written with standard fast data access cycles issued by the processor, and page information is irrelevant. There is a total of 30K of common RAM on the PDQ Board, located at addresses 0x0800 through 0x7FFF, with the 4K at 0x1000-1FFF reserved for the Kernel.
Paged RAM
Paged RAM can be read or written with standard fast data access cycles from the processor, but the PAGE_LATCH (PPAGE processor register) must be carefully controlled to ensure that the data access references the correct page. The advantage of RAM is fast read and write access. The main disadvantage of RAM is that it loses its contents when power is removed. Moreover, if RAM is used to store program code, it should be write protected so that processor crashes or errant writes do not corrupt the executing program image.
Pre-coded routines declared in the MEMORY.h header file facilitate reading and writing data in RAM. These include FetchChar, FetchInt, FetchLong, FetchFloat, StoreChar, StoreInt, StoreLong, and StoreFloat. To move blocks of data from one location to another in paged or common RAM, use the CmoveMany, CmoveManyCheck, or ToMemory routines.
Flash memory
Flash memory is nonvolatile: it retains its contents even when power is removed. Reading flash memory uses the same technique as reading paged RAM: standard fast processor read cycles can access the data as long as the page is properly controlled. But writing to Flash is different. Simple write cycles to the device do not modify the Flash memory contents, so program code stored in Flash is fairly safe even if the processor loses its way and tries to overwrite the flash memory (say, due to a program bug). A specially formatted sequence of write operations is required to program data into the Flash memory. This results in a slow writing process with full-speed reading. Because writing to Flash is rare (say, while you are downloading code) and reading is frequent (execution of code), this is a good tradeoff.
Fortunately, the Kernel includes pre-coded routines such as ToFlash and ToFlashMany that program the onchip Flash paged memory. Powerful “onchip-Flash-smart” routines such as ToMemory and ReceiveHex can detect when data is targeted at onchip Flash and properly store it there. You can program from 1 byte up to many pages of onchip Flash with a single function call using the pre-coded routines. These functions are declared in the MEMORY.h file, and are fully described in the C Glossary. As noted in the Glossary, interrupts are disabled for many milliseconds as each Flash sector is programmed.
A separate set of routines is used to program the “shadow” Flash that backs up the paged RAM. The lowest level function is called ToXFlash, where the “X” stands for eXternal to distinguish it from the onchip Flash inside the HCS12 processor. As described in an earlier section of this Chapter, the convenient StorePages function copies from paged RAM to the shadow Flash chip.
Locating nonvolatile data in EEPROM
The PDQ Board’s built-in EEPROM provides an ideal place to store calibration constants or other data that must be changed from time to time, but that must be retained even when power is removed. The EEPROM (Electrically Erasable Programmable Read-Only Memory) can be modified up to 10,000 times before it loses its ability to retain data (compared to only 1000 times for the Flash memory). On the HCS12, the EEPROM is implemented as a type of flash memory with a small 4-byte sector size; that is, each write to EEPROM modifies 4 bytes. The operating system provides functions that program from 1 to 4 bytes at a time, preserving the contents of any non-target bytes in the relevant 4-byte sector. The ToEEPROM function can program a larger chunk of EEPROM memory. EEPROM must be programmed using these Kernel functions, but reading the contents can be performed using standard read cycles issued by the HCS12 processor. In other words, special functions are required to write to EEPROM, but not to read from EEPROM.
An EEPROM variable should be declared as an un-initialized static variable located in the pre-defined “eeprom” section. For example, to create a 2-byte EEPROM variable named testEEVar use the following syntax:
int _eeprom testEEVar;
The int keyword causes a 2-byte integer to be allocated. The _eeprom tag instructs the linker to place the variable in the eeprom section. Note that the _eeprom tag can go anywhere on the line before the ; . You don’t have to worry about remembering where in the memory map the EEPROM area is; the compiler already knows this information. Variables in this section will be allocated in sequence; if the eeprom section overflows, the linker will generate an error message during the Make process to inform you of the problem.
You cannot create an initialized EEPROM variable with a simple assignment. For more information see: initializing-memory
To store data into the EEPROM variables, use the following functions which are declared in the MEMORY.h file:
void StoreEEChar( char value, char* addr ); void StoreEEInt( int value, int* addr ); void StoreEELong( long value, long* addr ); void StoreEEFloat( float value, float* addr ); int ToEEPROM( xaddr source, addr dest, uint numBytes );
To avoid wearing out the EEPROM by executing unneeded write cycles, these functions check whether the entire affected area of EEPROM already holds the required contents. If so, the write is not performed.
While EEPROM variables must be initialized programmatically at run-time the first time they are used, they don’t need to be re-initialized each time the processor starts up because the nonvolatile EEPROM retains the data. Even so, initializations can be performed every time the processor starts up, with no adverse effects on the life span of the EEPROM. For example, initialization code in an autostart routine could execute ATTACH functions to ensure that all needed interrupt vectors are properly initialized each time the processor restarts. If the EEPROM cells have been corrupted for some reason, the ATTACH command installs the correct contents, but if the specified interrupt vector information is already in the EEPROM, the memory cells are not needlessly rewritten.
EEPROM lifetime
At room temperature each 4-byte EEPROM cell may be written to a minimum of 105 times before it becomes unreliable. Data in the EEPROM is retained a minimum of 20 years, but typically 100 years. If individual cells are likely to be written frequently, you may want spread out the writes among multiple EEPROM cells. Consult the page on EEPROM Reliability and Wear Leveling for detailed information on EEPROM reliability and wear-leveling algorithms to maximize lifetime.
Using C arrays and Forth arrays
Storing data acquisition results in C arrays and Forth arrays
Programs written in the C language use space in common memory to store variables. You may store simple variables or arrays of variables there using standard C syntax. However, common memory available to the application program is limited to 26 Kbytes. Some programs may require access to additional RAM. Access is provided through the use of special data structures called Forth Arrays as declared in the ARRAY.h header file. Using Forth Arrays you may dynamically dimension arrays of virtually any size in the paged address space, and their memory allocation is automatically handled by the Kernel’s heap memory manager.
The code presented in the sample project Analog I/O Demo uses a C array and a FORTH_ARRAY to store the results of multiple ATD (Analog To Digital) conversions. This section uses that code as an example to discuss some interesting features of both C Arrays and FORTH_ARRAYs.
When this project opens, the main file will be called PDQ_ANALOGIO.C.
Declaring a C array
The use of C arrays is discussed in detail in all standard C texts. In this program, the one-dimensional 16-element character array named results8
is declared and allocated in RAM using the statement:
_qv uint c_results_10[DEFAULT_NUMSAMPLES];
where DEFAULT_NUMSAMPLES
is a constant equal to 16. The _qv keyword allows the array to be accessed from QED-Forth. The C arrays are easy to use. For example, the following C statement assigns the first element in the array to a static variable named my_variable:
my_variable = c_results_10[0];
To see another simple example that demonstrates how C arrays are accessed, look at the InitAnalog function in the PDQ_ANALOGIO.c file. The c_results_10 array is zeroed by executing the following statement:
for( i = 0; i < DEFAULT_NUMSAMPLES; ++i ) { c_results_10[i] = 0; // zero the C array }
Note that this array is dimensioned and allocated by the compiler and linker. In contrast, a FORTH_ARRAY is dimensioned and allocated dynamically by the heap memory manager called by the run-time program itself.
Converting a 16 bit address to a 32 bit xaddress
The ATDToCArray function in the ANALOGIO.c file provides an interesting example of type conversion. The definition of the function is:
_Q void SampleToCArray( int channel ) { ATDMultiple( COMMON_XADDR( &c_results_10 ), 0xfff, 0, DEFAULT_NUMSAMPLES, channel, 1 ); }
The purpose of COMMON_XADDR is to convert a C pointer to a 32-bit extended address to pass to ATDMultiple. ATDMultiple is optimized to use a FORTH_ARRAY buffer, and so expects a 32 bit buffer xaddress instead of a 16 bit pointer. To convert the simple 16 bit address (pointer) returned by c_results_10
into a 32 bit extended address, we take advantage of the COMMON_XADDR inline function defined in the types.h
header file in the C:\MosaicPlus\c\libraries\include\mosaic directory. COMMON_XADDR verifies that the type being passed to it is a pointer, avoiding the common error of casting a variable's value to an extended address, rather than it's address (though this is not an issue with an array).
Note that COMMON_XADDR is available in Mosaic IDE Plus revisions greater than 1500. Previously it was necessary to cast a pointer to int
and then xaddr
, i.e. (xaddr)((int)(&c_results_10))
A Review of Forth arrays
FORTH_ARRAYs have two key advantages. First, they are allocated in paged memory, so they allow your program to access the large 1 Megabyte memory space of the PDQ Board. In contrast, C arrays must reside in the available common RAM which is limited to 26 Kbytes on the PDQ Board. Second, FORTH_ARRAYs can be dynamically dimensioned, re-dimensioned and de-allocated (deleted) while your program is running, and this boosts efficiency by maximizing the use of the available memory.
To define a new Forth Array, simply use the FORTH_ARRAY typedef followed by a name of your choice. For example, in the PDQ_ANALOGIO.c file the following declaration appears:
FORTH_ARRAY forth_results_8;
Before the FORTH_ARRAY can be accessed at runtime, it must be dimensioned. This is typically accomplished by calling the DIM macro defined in the ARRAY.H header file. For example, to dimension the forth_results_8 array to have 10 rows and 1 column of integer data, we would execute:
DIM( int, 10, 1, forth_results_8 );
In the PDQ_ANALOGIO.C file, the interactively callable function named DimBuffer invokes the DIM routine, setting the number of rows equal to the contents of the numsamples+1 “variable” in EEPROM. (The extra padding byte added to the forth_results_8 buffer is a result of an implementation detail of the ATDMultiple function when storing 1-byte data in an array.)
After the FORTH_ARRAY is dimensioned, it can be accessed by a family of macros and functions that are defined in the ARRAY.H header file and are described in the Control-C Glossary. These include functions and macros that fetch from, store to, and calculate the address of individual elements, swap and copy entire arrays, fill an array with a specified character, and delete the array so that it no longer requires memory in the heap. The PrintForthArray and InitAnalog functions in PDQ_ANALOGIO.C provide examples of how to call a few of these functions.
Printing the contents of a Forth array
The PrintForthArray
function presented in the PDQ Analog Input demo is a cuztomized version of the PrintFPArray
function in the Getting Started demo as discussed in the Your First C Program Using Mosaic IDE Plus page. The function is defined as follows:
_Q void PrintForthArray( FORTH_ARRAY* array_ptr ) // works for FORTH_ARRAYS dimensioned using the standard DIM() macro. // data is printed using printf() { int r, c, value; for ( r = 0; r < NUMROWS( array_ptr ); ++r ) // for each row { for ( c = 0; c < NUMCOLUMNS( array_ptr ); ++c ) // for each column { value = ARRAYFETCH( char, r, c, array_ptr ); // fetch RAW value from array printf( "0x%03x (%f Volts)\r\n", value, Volts8( value ) ); } PauseOnKey(); // implement xon/xoff output flow control } }
First we enter the nested for
statements that print the contents of each element. We use ARRAYFETCH to allow access to the FORTH_ARRAY from GNU C. printf
outputs the raw hex value of each element at the beginning of the line. The function Volts8
returns a floating point voltage measurement, which is the next thing printed on each line. The PauseOnKey function is called once per row to suspend the PDQ Board’s printed output if the terminal program has sent the XOFF
handshake character; the printout resumes when the terminal sends the XON
character. PauseOnKey also gives the user the ability to terminate the printout by typing a carriage return character from the terminal.
This function can be tailored to meet the detailed needs of your application. You can change the printf
formatting, or insert extra carriage returns to confine the printout to one screen width.
See also → Data Acquisition Using Analog to Digital Conversion