Frequently Asked Questions
This page contains answers to common questions handled by our support staff, along with some tips and tricks that we have found useful and presented here as questions. This list is under development currently and will be extended with new topics soon.
Contents
- How do I use pointers to a fixed address?
- How do I access an absolute location without using pointers?
- Far and near attributes
- #pragma locate parameters
- Referring to external objects
- Referencing an array at a fixed address
- Using the BOTTOMUP control
- Locating multiple values with one statement
- Creating an array of function pointers
- Scope and how it affects locating
Interrupt programming
Is your question not listed? Contact TASKING.
How do I use pointers to a fixed address?
If you're having difficulty using pointers with memory-mapped I/O, you can use one of the following techniques. Let's assume you have a D/A converter mapped to location 0x0506. Generally, a compiler does not convert a numeric constant to a pointer if you don't explicitly cast it. This is how to accomplish the cast:
How do I access an absolute location without using pointers?
If you need to access an absolute location without using pointers, you need to declare a variable, for example dac2, then place it at the correct location, using #pragma locate:
Far and near attributes
If you need to specify a far address in another module to provide access to an absolute location, you might declare a variable far_dac, in one module, at a fixed absolute address, like this:
In another function you might try to refer to far_dac as an external, as follows:
Since the address you specified is a far address, it's important to establish the proper model control for access to it. If you're using model(nt-e), for instance, you'll get a Warning 2 and an Error 113 for this example since model(nt-e) expects data to be defined as near. When you locate this variable at address 0x1a000, you are really defining the variable as far data. If you're using a model with default near data like model(nt-e) and you wish to locate the variable as far data, you'll have to alter your extern definition to:
Another way to use the tools to access this object as a far data object, is to invoke the tools with a model which defaults to far data, like model(nt-ef). It is worthy of note that the use of a #pragma locate is to be preferred. By means of the pragma, the address will automatically be reserved for the linker/locator. The pragma construction is attended with a higher level of abstraction as well making the code easier to read/maintain.
#pragma locate parameters
The #define sequence, below, is generally expected to calculate the size of an object (an array with 20 members type widget, each occupying 5 bytes). The intent is to use "widgetarray" as a marker to locate another object behind an earlier #defined array.
The defines are handled in the compiler's pre-processor phase, and as a result, widget * 20 is passed literally, as an argument to it. The compiler is unable to replace the widget*20 by the value 100. Therefore the resulting:
doesn't work as might be expected. The #pragma locate can only handle literal integers as assignment values, and no other integral expressions.
Referring to external objects
Additional functionality is provided for referring to external functions and variables, i.e. symbols which are not linked into the application. If these symbols are public and linked into an absolute file already, you can use the linker's PUBLICSONLY control to resolve the references in your current application.The linker invocation line looks like this:
RL196 *.obj *.lib PUBLICSONLY(alreadylocated.abs)
References made in *.obj to functions and globals (defined in source files that make up alreadylocated.abs), will be filled with addresses from alreadylocated.abs.
The regular ROM and RAM locating controls can be used to assign code and data segments to their proper places in the hardware memory map. If you need to group structures and arrays, you could (for example) use a dedicated C module in which these objects are declared. The linker can be used to place this module at any desired memory location.
Referencing an array at a fixed address
File buffer.a96:
File main.c:
If you're declaring space in an assembly module and trying to reference it from a C module as a character array, you may generate "Warning 1: Symbol attribute mismatch: rx_buffer, defined in D.OBJ(D), referenced in F.OBJ(F). Although a warning is issued, the code will work fine. The warning is issued because the symbolic attribute of the assembly's rx_buffer is of type byte, and is different from the symbolic information generated for a C array.
There are two ways to work around this warning:
(I). In C, locate the array at any specific address by using #pragma locate:
To declare a far array rx_buffer in C, use a far address with #pragma locate:
(II). In assembly, make the attributes of rx_buffer compatible to all by defining it as:
Then, to reference it, refer to it as a far object, like this:
Again, adapting the c-source is to be preferred.
Using the BOTTOMUP Control
The BOTTOMUP control is sometimes useful for causing the locator to start locating in low to high address order. Use this control for 24-bit models and segments farcode, highcode, fardata and farconstant. Normally these will be filled high-to-low. With this control they will be filled low-to-high.
The control affects CODE, DATA and CONST, as long as they are to be located in far (24-bit) segments. It doesn't deal with the STACK segment.
Locating Multiple Values With One Statement
It's possible to read and write to several external devices, without having to use several #pragma locate() directives. For example, let's assume that you're trying to provide absolute locations for several different sized objects:
0x40000(WORD) 0x40002(BYTE) 0x40003(BYTE)
This can be done from within a C source file without changing your linker invocation:
Creating an Array of Function Pointers
If you need to develop a multi-dimensional array of pointers to functions in the constant area, this piece of code shows what needs to be coded. The typedef defines a "pointer to function returning int" data type. Variable addresses of this type are declared and initialized along with their external function labels. In the main function, the function, f3(), which returns an int, is called by indexing the function pointer array with values 1,0. The example shows this function being called several times, with different values passed to it each time.
Scope and How It Affects Locating
Local variables are allocated on the stack. The space for the local variables on the stack is called a FRAME. The only way to have local variables reside in external memory is to declare the variables static. Global variables can be placed in external memory by use of compiler and linker controls. The important controls are:
REGCONSERVE, a compiler control, which earmarks global variables for placement in the data segment by preventing automatic assignment to registers.
The linker control, RAM, which is used to specify memory ranges for modules residing within the data segment. For example:
If we compile our example with these controls:
c196 test.c regconserve model(nt-ef) debug symbols
and link, using these controls:
rl196 test.obj cstart.obj c96.lib model(nt-ef) ram(1ah-1fffh(stack), 060000h-07f000h(test))
The linker locates the variable local1 at offset 0002H from the base of the FRAME on the stack, while the variable local2 is located in external data memory.
An excerpt from the map file:
Using #pragma interrupt
The interrupt number as listed in the Intel 80C196 User's Manual is not the number which must be used as an argument to the #pragma interrupt in the TASKING C196 compiler. It has a different meaning for the TASKING toolset than it does in the Intel User's Manual: For the TASKING tools, the interrupt number is used to access a specific interrupt vector location. It is like an index into an array of interrupt vectors (word length).
The Intel 80C196 hardware implementation splits this 'array' in two non-contiguous parts:
This is why the #pragma interrupt(number) in the C196 tools takes a leap from 7 to 24; it's skipping special locations which do not contain interrupt vectors. So Intel's interrupt number 14 represents an interrupt which is different from the tools' #pragma interrupt (number).
All of the C196 interrupt vectors are located on the "High Page". This means that their addresses, when referenced within an extended model (24-bit addressing) will be at 0FFxxxxh: xxxx will be a value from the range which extends from 2000h to 203Fh. This range contains all of the conventional interrupt vectors, but is discontinuous (is not contiguous). Vector addresses for interrupts 0 thru 7 range from 2000h thru 200Eh, while vector addresses for interrupts 8 thru 15 range from 2030h thru 203Eh. The actual addresses for each conventional interrupt vector location and the numbers needed to specify each vector within a #pragma interrupt() are presented in the following table:
| INT# | Non-Extended Vector Addr | Extended Vector Addr | #Pragma Interrupt |
|---|---|---|---|
| 0 | 2000h | FF2000h | 0 |
| 1 | 2002h | FF2002h | 1 |
| 2 | 2004h | FF2004h | 2 |
| 3 | 2006h | FF2006h | 3 |
| 4 | 2008h | FF2008h | 4 |
| 5 | 200Ah | FF200Ah | 5 |
| 6 | 200Ch | FF200Ch | 6 |
| 7 | 200Eh | FF200Eh | 7 |
| 8 | 2030h | FF2030h | 24 |
| 9 | 2032h | FF2032h | 25 |
| 10 | 2032h | FF2034h | 26 |
| 11 | 2032h | FF2036h | 27 |
| 12 | 2032h | FF2038h | 28 |
| 13 | 2032h | FF203Ah | 29 |
| 14 | 2032h | FF203Ch | 30 |
| 15 | 2032h | FF203Eh | 31 |
The code generated for interrupt handlers is assumed to be re-entrant. This infers that interrupt routines most often use the stack for containing intermediate results. The code produced for interrupt routines by the #pragma interrupt() consists of a prologue and an epilogue along with other constructs which may be automatically generated by the complier.
- 1. The first of these constructs takes care of the prologue expected of an interrupt routine. This code preserves the Processor Status Word (PSW) on the stack (using a PUSHA instruction) and then clears the PSW. Clearing the PSW ensures that interrupts will be disabled, since the interrupt mask will be set to zero.
- 2. The second construct may or may not be present, depending on whether there are register variables declared within the (re-entrant) interrupt handler. If register variables are to be used, a pointer to a register overlay segment, called the '?OVRBASE' will be saved on the stack. This will allow for different sets of registers to be used if this interrupt handler is, in turn, interrupted by a higher priority interrupt.
- 3. The third construct provides a cleanup mechanism for the '?OVRBASE' on exit from the interrupt handler.
- 4. The fourth construct, the epilogue, ensures that the last instruction in the interrupt will pop the PSW off the stack (with a POPA instruction) to restore the prior state of the interrupt mask and re-enable this interrupt.
Examples defining two interrupt handlers, on for Intel's interrupt number 5 and one for interrupt number 14, both using the 80C196 TASKING toolset:

