📄 c516.txt
字号:
发信人: reflection (似水流年), 信区: EEtechnology
标 题: C51 Primer (5) C Language Extensions for 8051
发信站: 南京大学小百合站 (Wed Nov 24 11:58:24 1999), 转信
5 C Language Extensions For 8051
Programming
8051 programming is mainly concerned with accessing real devices at specific
locations, plus coping with interrupt servicing. C51 has made many extensio
ns to the C language to allow near-assembler code efficiency. The main point
s are now covered.
5.1 Accessing 8051 On-Chip Peripherals
In the typical embedded control application, reading and writing port data,
setting timer registers and reading input captures etc. are commonplace. To
cope with this without recourse to assembler, C51 has the special data types
sfr and sbit.
Typical declarations are:
sfr P0 0x80
sfr P1 0x81
sfr ADCON; 0xDE
sbit EA 0x9F
and so on.
These declarations reside in header files such as reg51.h for the basic 8051
or reg552.h for the 80C552 and so on. It is the definition of sfrs in these
header files that customises the compiler to the target processor. Accessin
g the sfr data is then a simple matter:
{
ADCON = 0x08 ; /* Write data to register */
P1 = 0xFF ; /* Write data to Port */
io_status = P0 ; /* Read data from Port */
EA = 1 ; /* Set a bit (enable all interrupts) */
}
It is worth noting that control bits in registers which are not part of Inte
l's original 8051 design generally cannot be bit-addressed.
The rule is usually that addresses that are divisible by 8 are bit addressab
le. Thus for example, the serial Port 1 control bits in an 80C537 must be ad
dressed via byte instructions and masking.
Always check the processor's user manual to verify which sfr register bits c
an be bit addressed.
5.2 Interrupts
Interrupts play an important part in most 8051 applications. There are sever
al factors to be taken into account when servicing an interrupt:
The correct vector must be generated so that the routine may be called. C51
does this automatically.
The local variables in the service routine must not be shared with locals in
the background loop code: the L51 linker will try to re-use locations so th
at the same byte of RAM will have different significance depending on which
function is currently being executed. This is essential to make best use of
the limited internal memory. Obviously this relies on functions being execut
ed only sequentially. Unexpected interrupts cannot therefore use the same RA
M.
5.2.1 The Interrupt Function Type
To allow C coding of interrupts a special function type is used thus;
timer0_int() interrupt 1 using 2
{
unsigned char temp1 ;
unsigned char temp2 ;
executable C statements ;
}
Firstly, the argument of the "interrupt" statement, "1" causes a vector to b
e generated at (8*n+3), where n is the argument of the "interrupt" declarati
on. Here a "LJMP timer0_int" will be placed at location 0BH in the code memo
ry. Any local variables declared in the routine are not overlaid by the link
er to prevent the overwriting of background variables.
Logically, with an interrupt routine, parameters cannot be passed to it or r
eturned. When the interrupt occurs, compiler-inserted code is run which push
es the accumulator, B,DPTR and the PSW (program status word) onto the stack.
Finally, on exiting the interrupt routine, the items previously stored on t
he stack are restored and the closing "}" causes a RETI to be used rather th
an a normal RET.
5.2.2 Using C51 With Target Monitor Debuggers
Many simple 8032 target debuggers place the monitor's EPROM code at 0, with
a RAM mapped into both CODE and XDATA spaces at 0x8000. The user's program i
s then loaded into the RAM at 0x8000 and, as the PSEN is ANDed with the RD p
in, the program is executed. This poses something of a problem as regards in
terrupt vectors. C51/L51 assume that the vectors can be placed at 0. Most mo
nitors for the 8032 foresee this problem by redirecting all the interrupt ve
ctors up to 0x8000 and above, i.e. they add a fixed offset of 0x8000. Thus t
he timer 0 overflow interrupt is redirected by a vector at C:0x000B to C:0x8
00B.
Before C51 v3.40 the interrupt vector generation had to be disabled and asse
mbler jumps had to be inserted. However now the INTVECTOR control has been i
ntroduced to allow the interrupt vector area to be based at any address.
In most cases the vector area will start at 0x8000 so that the familar "8 *
n + 3" formula outlined in section 5.2.1 effectively becomes:
8 * n + 3 + INTVECTOR
To use this:
#pragma INTVECTOR(0x8000) /* Set vector area start to 0x8000 */
void timer0_int(void) interrupt 1 {
/* CODE...*/
}
This produces an LJMP timer0_int at address C:0x800B. The redirection by the
monitor from C:0x000B will now work correctly.
5.2.3 Coping Interrupt Spacings Other Than 8
Some 8051's do not follow the normal interrupt spacing of 8 bytes - the '8'
in the 8 * n + 3 formula. Fortunately the "INTERVAL #pragma" copes with this
.
The interrupt formula is, in reality:
INTERVAL * n + INTVECTOR and so:
#pragma INTERVAL(6) /* Change spacing */
will allow a 6 byte spacing.
Please note that for convenience INTERVAL defaults to 8 and INTVECTOR to 0x8
0000!
5.2.4 The Using Control
The "using" control tells the compiler to switch register banks. This is an
area where the 8051 architecture works for the compiler rather than against
it; the registers R0 to R7 are used extensively for the temporary storage of
library routines and for locals. Ordinarily Bank 1 is used. However, to be
able to use this standard code in an interrupt the register bank must be swi
tched to 2 in the above example. Thus the variables of the interrupted routi
nes are preserved.
As a rule interrupts of the same priority can share a register bank, since t
here is no risk that they will interrupt each other.
If interrupt runtime is not important the USING can be omitted, in which cas
e C51 examines the registers which are actually used within the routine and
pushes only these onto the stack. This obviously increases the effective int
errupt latency.
5.3 Interrupts, USING, Registerbanks, NOAREGS In C51
Everything You Need To Know
Interrupts play an important part in most 8051 applications and fortunately,
C51 allows interrupt service routines to be written entirely in C. Whilst y
ou can write perfectly workable (and safe) programs by using just straight A
NSI C, you can significantly improve the efficiency of your code by gaining
an understanding of the following special C51 controls:
INTERRUPT
USING
NOAREGS
RE-ENTRANT
REGISTERBANK
5.3.1 The Basic Interrupt Service Function Attribute
The correct vector must be generated so that the routine may be called. C51
does this based on the argument to the interrupt keyword. The linker thereaf
ter does not allow local data from interrupt routines to be overlaid with th
at from the background by creating special sections in RAM. C51 special "int
errupt" function attribute example:
/*Timer 0 Overflow Interrupt Service Routine */
timer0_int() interrupt1
{
unsigned char temp1 ;
unsigned char temp2 ;
/* executable C statements ; */
}
The "interrupt 1" causes a vector to be generated at (8*n+3), where n is the
argument of the "interrupt" declaration. An "LJMP timer0_int" will be place
d at location 0BH in the code memory.
Local variables declared in the routine are not overlaid by the linker to pr
event the overwriting of background variables.
When the interrupt occurs, compiler-inserted code is run which pushes the ac
cumulator, B,DPTR and the PSW (program status word) onto the stack if used i
n function, along with any registers R0-R7 used in the function.
A RETI is inserted at the end of the function rather than RET. Taking an emp
ty interrupt service function for the timer 0 overflow interrupt, this is ho
w C51 starts off an interrupt routine that uses no registers at all:
timer0_int Entry Code
void timer0_int(void) interrupt1
{
RSEG ?PR?timer0_int?TIMER0
USING 0
timer0_int:
; SOURCE LINE # 2
If a function, here called "sys_interp" is now called from the timer0 servic
e function, this is how the entry code to the interrupt changes.
timer0_int Entry Code Now With Called Function
; void timer0_int(void) interrupt 1
{
RSEG ?PR?timer0_int?TIMER0
USING 0
timer0_int:
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
PUSH AR0
PUSH AR1
PUSH AR2
PUSH AR3
PUSH AR4
PUSH AR5
PUSH AR6
PUSH AR7
Note that the entire current registerbank is pushed onto the stack when ente
ring timer0_int() as C51 assumes that all will be used by sys_interp. Sys_in
terp receives parameters in registers; if the entry to sys_interp is examine
d, an important compiler trick is revealed:
sys_interp() Entry Code
; unsigned char sys_interp(unsigned char x_value,
RSEG ?PR?_sys_interp?INTERP
USING 0
_sys_interp:
MOV y_value?10,R5
MOV map_base?10,R2
MOV map_base?10+01H,R3
;--Variable 'x_value?10' assigned to Register 'R1' --
MOV R1,AR7
The efficient MOV of R7 to R1 by using AR7 allows a MOV direct, direct on en
try to sys_interp(). This is absolute register addressing and is a useful do
dge for speeding up code.
5.3.2 The absolute register addressing trick in detail
The situation often arises that the contents of one Ri register needs to be
moved directly into another general purpose register. This usually occurs du
ring a function's entry code when a pointer is passed. Unfortunately, Intel
did not provide a MOV Reg,Reg instruction and so Keil use the trick of treat
ing a register as an absolute D: segment address:
Simulating A MOV Reg,Reg Instruction:
In registerbank 0 - MOV R0,AR7, is identical to - MOV R0,07H.
Implementing a "MOV Reg,Reg" instruction the long way:
XCH A,R1
MOV A,R1
The use of this trick means however, that you must make sure that the compil
er knows which is the current registerbank in use so that it can get the abs
olute addresses right. If you use the USING control, problems can arise! See
the next few sections...
5.3.3 The USING Control
"using" tells the compiler to switch register banks on entry to an interrupt
routine. This "context" switch is the fastest way of providing a fresh regi
sterbank for an interrupt routine's local data and is to be preferred to sta
cking registers for very time-critical routines. Note that interrupts of the
same priority can share a register bank, since there is no risk that they w
ill interrupt each other.
8051 Register Bank Base Addresses
R0 AR0 Absolute Addr.0x00 REGISTERBANK 0
R1 AR1
R2 AR2
R3 AR3
R4 AR4
R5 AR5
R6 AR6
R7 AR7
R0 Absolute Addr. 0x08 REGISTERBANK 1, "USING 1"
R1
R2
R3
R4
R5
R6
R7
R0 Absolute Addr. 0x10 REGISTERBANK 2, "USING 2"
R1
R2
R3
R4
R5
R6
R7
R0 Absolute Addr. 0x18 REGISTERBANK 3, "USING 3"
R1
R2
R3
R4
R5
R6
R7
If a USING 1 is added to the timer1 interrupt function prototype, the pushin
g of registers is replaced by a simple MOV to PSW to switch registerbanks. U
nfortunately, while the interrupt entry is speeded up, the direct register a
ddressing used on entry to sys_interp fails. This is because C51 has not yet
been told that the registerbank has been changed. If no working registers a
re used and no other function is called, the optimizer eliminiates teh code
to switch register banks.
timer0_int Entry Code With USING
With USING 1
; void timer0_int(void) interrupt 1 using 1 {
RSEG ?PR?timer0_int?TIMER0
USING 1 <--- New register bank now
timer0_int:
PUSH ACC
PUSH B
PUSH DPH
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -