Automatically Initializing Data Segment Values in MAX-IDE
OverviewVariables in MAXQ assembly applications can be stored either in working registers (such as the accumulators A through A) or in data memory (SRAM). Storing variables in data memory provides a larger working area for the application variables, but does require additional access time.
The MaxQAsm assembler and MAX-IDE environment provide a mechanism to declare separate code and data segments, with a separate hex output file generated for each segment. At run time, MAX-IDE automatically loads the code segment file into program memory (typically flash) and the data segment file into data memory (typically RAM). However, as the data memory is volatile, the data segment contents will not remain intact once the microcontroller is powered off.
This application note uses the MAXQ2000 EV (evaluation) kit to demonstrate first, how to save these preloaded data memory values in flash during the initial application run, and then how to refresh the data segment values from flash when the microcontroller is subsequently powered on again. This two-step process allows the same data segment mechanism to be used to declare and initialize variables, whether the application is in development (connected to a JTAG adapter and MAX-IDE) or running in the field.
Demonstration code for this application note is written for the MAXQ2000 microcontroller and the MAXQ2000 EV kit, but the code and principles shown are applicable to any MAXQ20-based microcontroller with rewriteable program flash memory.
The latest installation package and documentation for the MAX-IDE environment are available for free download.
Variables and Storage LocationsEmbedded applications typically require a certain amount of working space to store state information, configuration settings, intermediate working values, loop counters, and the results of calculations. Values stored in this working space are typically known as variables and share the following characteristics.
- They are temporary. They do not need to be saved if the application is interrupted by a power failure or reset.
- They are accessed and updated frequently. They must be stored in locations that can be read from or written to quickly; there must be no limit on the number of times to which the locations can be written.
- They often have defined initial values. The user’s code must set them to a particular value at the beginning of the application.
unsigned int c = 0x1234;However, when writing applications directly in MAXQ assembly language, the allocation of space for variables and setting the variables to initial values must be performed explicitly. This detailed action allows tighter control of the resources available on the MAXQ microcontroller, but adds some complexity to the system.
For small assembly-based applications or those which do not require a large amount of working space, internal registers can be used to store all application variables. This approach provides two important advantages:
- Compact, fast code. Register variables can be read from, written to, or copied to another register variable in as little as one instruction cycle, depending on the location of the register. On MAXQ20-based microcontrollers, no more than two instruction cycles will generally be required in the worst case.
- Direct operations on variables. Some internal register locations can be operated on directly. For example, any of the 16 working accumulators, A through A, can be selected (using the AP register) as the active accumulator, Acc. This means that if an operation needs to be performed on a variable stored in one of these registers, it can be performed directly on that register without having to copy the value out, perform the operation, and copy the value back in. Similarly, variables stored in the LC and LC registers can be used directly as loop counters by executing the djnz instruction.
move DP, #0010h ; Location of variable in data memory move Acc, @DP ; Read variable add #1 ; Increment variable value by 1 move @DP, Acc ; Store variable back in data memoryIf a long series of calculations must be performed on a variable, the value of that variable can be copied into a working register, as shown in the example code above. All intermediate operations can be performed using that working register, and the value can be copied back out to the variable once calculations are complete.
Segment Declarations in MAX-IDEOnce the decision is made to store application variables in SRAM-based data memory, how do you determine where to store the variables?
Typically, all of the data memory is available for application use, except for the highest 32 bytes in memory which are used by the debugger. This means that declaring a variable is simply a matter of defining a location for it in data memory. This location is then used by code whenever the variable is read or written. The #define macro can be used to associate a variable location with a symbolic name.
#define VarA #0020h #define VarB #0021h #define VarC #0022h move DP, VarA ; Point to VarA variable move Acc, @DP ; Read value of variable move DP, VarB ; Point to VarB variable move @DP, Acc ; Copy VarA to VarB move DP, VarC ; Point to VarC variable move @DP, #1234h ; Set VarC = 1234hThis approach works well enough, but there are several problems with it.
- The location of each variable must be determined in advance. This task can be time-consuming, especially if it is decided later to move all variables to a different area of data memory.
- Care must be taken not to accidentally use the same location for more than one variable. If this mistake is made, it can be difficult to track bugs.
- Any initial (starting) values for variables must be explicitly loaded by application code, as shown in the last line above. This action can consume a large amount of code space if there are many variables to initialize in this manner.
segment code move DP, #VarA ; Point to VarA move Acc, @DP ; Get current value of VarA add #1 ; Increment it move @DP, Acc ; Store value back in VarA segment data VarA: dw 0394h ; Initial value for VarAIn the above approach, addresses for variables declared in the data segment are determined automatically by the assembler as it parses the file with the same method used to assign addresses to labels in code space. Labels are used to assign symbolic names to these variable addresses, and the dw and db statements can be used to initialize word-sized and byte-sized variables with starting values. In this case, assuming that no previous segment data directive was found in the assembly file, the assembler will begin the data segment at address 0000h. This means that VarA will be stored at word address 0000h. As in code space, the org statement can force variables to be located at the beginning of a specified address.
Initializing the Data SegmentIn the previous code listing, variable VarA is defined (using the dw statement) to have an initial value of 0394h. But this value is never loaded into VarA in the code. How, then, is this value initialized? The answer is that the initialization of the data segment is performed automatically by MAX-IDE when the project is compiled and executed.
The MaxQAsm assembler responds to the segment data directive by generating a secondary hex output file. Normally, a hex file is generated for a project containing code data. For example, if project "example.prj" is compiled, a hex file will be created named "example.hex" containing the code data generated by assembling the project files. If a data segment is defined, an additional hex file will be created named "example_d.hex", which contains the data assembled in this segment.
When the project is executed, MAX-IDE checks to see if a data segment file (ending in _d.hex) was generated during project compilation. If a data segment file exists, MAX-IDE uses the standard JTAG loader to load the data from this segment into the data SRAM of the device. This is done after the standard hex file has been loaded into program memory.
This method works well during the development cycle, when the device is connected to a JTAG adapter and MAX-IDE reloads the code and segment data before each application run. However, once the device is powered off and on and allowed to run independently (with no debugger connected), MAX-IDE no longer has the ability to load the data segment with the proper values before each run. The variables will no longer be set to their expected values, and the application may execute incorrectly as a result. This type of failure can be difficult to analyze, because as soon as the device is hooked back up to the debugger, MAX-IDE will begin loading the data segment again before each run and the problem will instantly vanish.
Saving and Restoring the Data SegmentA question remains: how can you make the application operate consistently, whether it is connected to a debugger (with MAX-IDE reloading code and data before each run) or free-running (with no particular contents in RAM guaranteed following power-up). The obvious solution is a two-step process: have the application save the variable values (once they have been initialized) in flash memory, and restore the values following each reset or power-up.
As a first step, the application must save values to flash memory. This action occurs the first time that the application is executed following each master erase and code load cycle.
- The application checks a "flag" location to verify that the variables have not been previously copied to flash. This flag can be a special-purpose, nonvariable location, or it can be shared with a variable as long as that variable has a nonzero initial value (to distinguish it from a blank RAM location).
- The application copies each variable value from data RAM to flash memory. On most MAXQ microcontrollers with rewriteable flash (such as the MAXQ2000) this is done using the UROM_flashWrite function.
- The application writes a flag in flash memory to indicate that the variables have been stored.
- The application checks the flag location in flash to verify that the variable values have been stored.
- The application uses the UROM_copyBuffer routine to copy the variable values from flash memory to their proper locations in data RAM.
$include(maxQ2000.inc) ;; Code memory (flash) : 0000h-7FFFh (word addr) ;; Data memory (RAM) : 0000h-03FFh (word addr) org 0000h ljump start ; Skip over password area org 0020h start: move DPC, #1Ch ; Set all pointers to word mode move DP, #0F000h ; Check first variable value (flag) lcall UROM_moveDP0 ; 'move GR, @DP' executed by Utility ROM move Acc, GR cmp #1234h jump NE, copyToFlash ;; This is the "free-running" code, executed on subsequent power-ups, that copies ;; values from the flash back into their proper data segment locations. move DP, #0F000h ; Source: Flash location 7000h move BP, #0 ; Dest: Start of RAM move Offs, #0 move LC, #100h ; Copy 256 words lcall UROM_copyBuffer jump main ;; This is the first-pass code. A bit of a trick here; because MAX-IDE enters ;; and exits the loader separately when loading the code and data segment files, ;; the application is allowed to execute briefly before the data segment file ;; has been loaded. The first four lines under copyFlash ensure that the ;; application waits for MAX-IDE to load the data segment file before continuing. copyToFlash: move DP, #0h ; Wait for flag variable to be loaded by MAX-IDE. move Acc, @DP ; Note that this will reset the application; the cmp #1234h ; data segment is not loaded while the application jump NE, copyToFlash ; is still running. move DP, #0 ; Start of RAM variable area move A, #7000h ; Location in flash to write to move LC, #100h ; Store 256 words in flash 7000h-70FFh copyToFlash_loop: move DP, DP ; Refresh the data pointer to read values correctly, ; because calling UROM_flashWrite changes memory ; contexts and affects the cached @DP value move A, A ; Location to write move A, @DP++ ; Value to write (taken from RAM) lcall UROM_flashWrite move Acc, A add #1 move A, Acc djnz LC, copyToFlash_loop main: move PD0, #0FFh ; Set all port 0 pins to output move PO0, #000h ; Drive all port 0 pins low (LEDs off) move DPC, #1Ch ; Set pointers to word mode move DP, #varA move Acc, @DP cmp #1234h ; Verify that the variable is set correctly jump NE, fail pass: move PO0, #55h sjump $ fail: sjump $ segment data org 0000h varA: dw 1234h org 00FFh varB: dw 5678h end