When I write PIC programs, I tend to write them modularly. Doing so breaks the code up into logical units, making it easier to think about, and allows for code reuse. When I build my code, I link multiple modules together into one executable, rather than including all the code into one module with the include statement. There are two advantages to linking:
Number one is fairly straight forward. When you link two modules together, the only symbols that one module can access from the other module are the ones that are explicitly exported with the GLOBAL keyword. All other symbols are local to that module. In fact, as long as the symbol is not exported, both modules may have symbols with the same name. This means that you don't need to worry about naming conflicts or come up with clunky naming conventions for symbols just to keep them unique. You can call things what you want to call them, making the code easier to read and maintain.
Number two needs a little more explanation. First, a little background for those of you who are not familiar with Microchip's memory segmentation facility. The segmentation is purely a software convention; it has (practically) nothing to do with hardware. It is simply a way to allocate memory. There are two types of segments: code and data. When you create a memory segment, you may specify a specific address that the segment starts at. In my programs, I typically have three code segments:
For data segments, there are several types to choose from. The primary type is UDATA, which stands for uninitialized data. All of your variables that you want to live for the life of the program go here.
A second type of data segment is UDATA_OVR, which stands for uninitialized data overlay. Each module making up a program may create a UDATA_OVR segment with the same name. When this occurs, all of the variables in that segment from one module occupy the same memory as the variables in the segment with the same name from the another module(s). In other words, the segments overlay each other. This is useful for temporary variables -- variables that don't need to be preserved for very long. I use these type of segments in my library routines. When I implement a library routine that needs temporary variables, such as a loop counter, and that routine doesn't call any other routines, I create a segment called INNERMOST_DATA. For each such module I write and link into a program, all of their INNERMOST_DATA segments share the same memory location. The segment is as large as the largest single segment requires. So if you write 5 modules that require 5, 4, 2, 6, and 4 bytes each, the segment will only be 6 bytes. So instead of using 21 bytes of data, you've only used 6. You must be very careful that the variables you put in this segment are temporary. If not, you will run into bugs that are very difficult to track down.
Eventually, I will put some code examples here, but I haven't gotten around to it yet.
For the sake of completeness, the other two types of data segments are UDATA_SHR and UDATA_ACS. I have never used these, though, so I will not discuss them.
After you have created your separate modules, you need to put them together into one program. That is the job of the linker. In addition to supplying the various modules and libraries to the linker, you must also supply a linker script. The script defines the various regions of memory for a given device. For example, it indicates which sections of data memory contain special function registers (SFR) (e.g. PORTA, INTCON, STATUS) so that the linker won't use those for other purposes.
The script I use is mostly the same as the one for the 16F628 that is supplied with the MPLAB package. The only difference is that I add two lines to the end of it. They are:
SECTION NAME=.udata RAM=gpr0 SECTION NAME=INNERMOST_DATA RAM=gpr0
These two lines tell the linker to put the default UDATA section (i.e. when you don't provide a name for the UDATA section) as well as the INNERMOST_DATA section in the bank of general purpose registers named gpr0. Looking back in the file to the DATABANK entry named gpr0, you'll see that this means the linker will put them in memory somewhere between 0x20 and 0xEF. It doesn't really matter where. The linker will ensure that the memory locations are unique.
I have never written a program that uses large amounts of data memory. I would guess my largest program probably didn't use more than 15 bytes, almost certainly no more than 20. For programs that require more memory that what is in one bank, the linker script and the data segments will probably get more complicated, but since I've never exceeded that limit, I couldn't say.
Back to the index.
Page last modified on 03/15/2004