Esc - 1991 - Vol2 - Page710 - Ward - Manipulating Hardware With C
Esc - 1991 - Vol2 - Page710 - Ward - Manipulating Hardware With C
Esc - 1991 - Vol2 - Page710 - Ward - Manipulating Hardware With C
Robert Ward
Publisher, TIu! C Users Journal and TECH Specialist
C is the language of choice for any type of systems programming (including
embedded systems), because it allows the programmer to gain easy access to the
hardware. Unfortunately, the built-in support for that access isn't as great as what
C's reputation might imply.
Depending on the environment, programmers may need to directly manipu-
late memory, registers, processor status flags, I/O ports, or interrupt vectors and
handlers. Pointers do allow direct access to memory, but all other hardware resour-
ces must be accessed with assembly language, either through a separately written
and linked function, in-line assembly (if your compiler allows it), or very nasty tricks
like executable strings.
Fortunately, because C is so often used for systems programming, most com-
piler vendors include in their standard library package routmes for manipulating
ports and restricted aspects of interrupts. Mamstream compilers, though, seldom in-
clude a means of accessing processor l ~ s and register contents, or completely
general support for interrupt use. Thus, If you need to manipulate only memory and
ports, you may be able to avoid writing any assembly in your project by becoming
familiar with the support built into your compiler's library. OtheIWise you will need
to resort to the techniques developed here.
ACCESSING MEMORY
On a machine with a strictly linear address space (like the 68000 series),
manipulating memory is very straightfoIWard. C's pointers are special variables
which are assumed to hold a memory address. The asterisk (*) operator fetches or
writes a value to the memory address in the pointer. Thus to prepare to write a byte
to location Ox05 (perhaps to set the I/O byte on an old CP/M system):
char *loc;
lac = (char *) Ox05;
(The cast is more than just good form. On word-oriented and some segmented ar-
chitectures, byte addresses are not always represented as simple integers. If you
don't cast the integer, you may not get a pointer that works lIke you expect.)
Page 711
To write a seven into this location:
*loc = 7;
To copy the contents of location five into a variable that may be manipulated by a C
program:
char workspace;
workspace = *10c;
It is important that the type of the pointer agree with the type of the data element
you intend to manipulate, otherwise you may effect more or less data than you in-
tended.
For example:
char *loc;
loc = (char *) 7;
*loc = 276;
actually puts 20 in location 7, because a character pointer only transfers one byte.
Similarly:
char *wideloc;
wideloc :: (int *) 7;
*wideloc :: 276;
may compile and run (perhaps with a warning), but will still only put 20 in location 7.
Big end ian versus little endian conventions may also complicate memory ac-
cesses. For example, if you create a data table in assembly that looks like this:
ORG 100H
DB 07H, 09H, 13H, 42H. 47H
and then execute this code:
int entry;
int *secondint;
secondint = (int *) Oxl02;
entry = *secondint;
printf("%xll.entry} ;
You may be surprised with this result:
4213
Page 712
Some machines assume integers are stored smallest byte first, others assume they
are stored largest byte first.
Segmented architectures have their own quirky requirements. To exploit the
efficiency benefits of the segmented architectures, programs for these machines are
usually compiled in a mode that uses 16 bit pointers - i.e., pointers that are relative
to some default base or segment register. If you want to access some data item
that can be reached by a relative offset from the default register, the process is as
simple as in a linear address (i.e. if you are staying within the present 64K data seg-
ment). But, if you need to access an address outside of this segment (say the video
RAM page), things are a little more complicated. You must use a special pointer
that references off of a different base regIster or changes and then restores the
default register. Modern 8086 compilers offer built-in support for this latter opera-
tion in far pointers. However, the use of these special'pointers is not always ob-
vious. For example, one could reasonably expect to wnte to the second line of a PC
clone's video RAM (physical location OxB0140) with:
char far *video;
video = (char far *) OxB0140L;
*vi deo = I X I;
Unfortunately, this crashes the system. The problem is that far pointers aren't
simple integer representations of memory addresses. Instead they are segments (all
but the least four bits of the address) and offsets (only the least 16 bits of the ad-
dress). The code to correctly initialize the far pointer is:
char far *video;
/* concatenate OxBOOO and 0140 */
video = (char far *) OxB0000140L;
Pointers, together with bit operators, are all you need to conveniently handle all low-
level hardware operations on a machine with memory-mapped I/O. If, for example,
bit two of location FFFOO was the keyboard ready status bit on a machine with a
simple linear memory structure, you could wait on a character directly in C with:
char nextchar, *kbdst;
kbdst = OxFFFOO;
while (!nextchar=kbdst) & Ox2}};
That's how C earned it's reputation for being a hardware language.
INCORPORATING ASSEMBLY LANGUAGE
C has no built-in understanding of non built-in operators for any address
space other than memory. Thus if your machine uses port-mapped I/O, you must
use the machine's native operators to manipulate the ports. The same applies to spe-
cial resources like special registers (e.g. a built-in UART) and CPU status flags.
Page 713
Even if your compiler vendor's library includes routines to manipulate port
space, special registers, and to disable and enable interrupts, you may still find a
need to incorporate your own assembly langauge. I'll explain three dIfferent
methods: modifying compiler output, using in-line sequences, and executable strings.
MODUYING COMPILER OUTPUT
The most complicated part of writing a C library routine in assembly is get-
ting the calling interface right. Why not let the compiler do it for you? If you com-
pile to assembly (instead of to object code), a function with the same number and
type of parameters as you need in your assembly language function, you can edit the
compiler's output to produce the finished function. For example, if I needed to
write a routine to get a byte from an arbitrary port, I would compile the function:
extern int mark;
char inp(address)
int address;
{
mark = address + 3.
return (mark + 7);
}
I then edit the assembly listing to remove the external declaration and replace the
dummy calculations with a port operation:
/* compiler generated */
Line 6
; address = 4
; Li ne 7
mov
add
mov
; Li ne 8
add
/* replacement
push
mov
in
pop
ax,WORO PTR [bp+4]
ax,3
_mark.ax
ax,7
*/
;address
dx ;save dx in case
dx,WORD PTR [bp+4] ;get port # In dx
ax,dx ;get byte in ax
dx ; restore
Referencing an external in the dummy code and performing some simple calcula-
tion on the parameter and return values "mark" the lines that access the parameter
and "mark" the registers that are available for your use.
One advantage of using the compiler generated code: stack probes and other
built-in protection mechanisms can be easily incorporated into your module.
Page 114
IN-liNE ASSEMBLY
In the "good old days", Assembly language could be passed directly to a com-
piler with the pre-processor escape
#asm
#endasm
Because this capability doesn't belong in the pre-processor, ANSI conforming com-
pilers won't support it. Many will, however, support the same functionality through
pragmas.
In either form, this capability is best reserved for single operations that don't
change memory. For example:
To output a constant to port 7:
#asm
OUT 7,OCH
#endasm
To enable interupts
#asm
EI
#endasm
To capture status flags in an external variable
#asm
PUSH AX
LAHF
jsave in case the compiler is using it
MOV stbyte.AH
POP AX ;restore
#endasm
If you change a register, unless you are very about how your compiler
generates code, it's probably best to save registers as m this last example. In
general, though, if you are going to use registers and modify memory, it's probably
best to write the entire module in assembly, because it's very hard to predIct how
your use of memory and registers will interact with the compiler's.
EXECUTABLE STRiNGS
In very simple environments, it is sometimes practical to hand-assembly a
few instructions and insert them in a string. It is usually not too hard to convince a
simple-minded compiler that a pointer to the string represents a function.
Page 715
For example, to disable interrupts on a Z-80 we could code:
void (*cheapf)();
char * asmcode;
/* D1 ; OxF3 = 363 ; RET = OxC9 = 311 */
asmcode = "\363\311";
cheapf = asmcode;
(*cheapf)(); /* call to the code via the pointer */
Your compiler should at least complain about this abuse, but may produce ex-
ecutable code anyway. This technique will NOT work if relocatable objects are
manipulated by the string code. I can't in good conscience recommend that you ever
use this technique, but here it is just in case.
INTERRUPTS
Of course you can and service interrupts by assembly
language using any of the techmques discussed above. Some compilers (mcluding
Turbo C) include special keywords and pragmas that reduce the likelyhood you will
have to resort to assembly. Of particular use are the ability to label a function as
needing a "return from interrupt" exit, and library functions to enable, disable, and
re-vector interrupts.
SUMMARY
C gives excellent direct control over memory and memory-mapped I/O.
Other hardware resources are less directly accessible, but C compiler vendors tend
to supply library functions that make it easy to manipulate all but the lowest-level
hardware resources. In compilers targeted for the embedded system programmer,
you should also find in-line assembly pragmas, special keywords, and machine
specific library support. Even if these tools are missing, as long as you can make
your compiler generate assembly, it's fairly easy to add your functions to manipulate
special resources. 0
Page 716