Key Considerations for Mixed C and Assembly Programming in Keil

Click the above Key Considerations for Mixed C and Assembly Programming in Keil “Chuangxue Electronics” to follow and easily learn electronic knowledge.

Chuangxue Electronics Subscription

Daily updates of technical articles in the electronics industry and the latest news on microcontrollers, learn easily anytime, anywhere.

Here are some issues encountered in mixed programming of C language and assembly language in Keil, written down for future reference.

1. Method to add assembly language modules in C language:

Example:

void func()

{

C language code……

#pragma asm

MOV R6,#23

DELAY2: MOV R7,#191

DELAY1: DJNZ R7,DELAY1

DJNZ R6,DELAY2

RET

#pragma endasm

C language code……

}

The red part is the C language part, and the green part is the embedded assembly language part. The assembly part needs to be wrapped with #pragma asm and #pragma endasm.

2. Solution to Keil’s “asm/endasm” error prompt

If you simply add assembly code like in 1, the compilation will report an error, as follows:

compiling sendata.c…

sendata.c(81): error C272: ‘asm/endasm’ requires src-control to be active

sendata.c(87): error C272: ‘asm/endasm’ requires src-control to be active

Target not created

The solution is as follows:

Key Considerations for Mixed C and Assembly Programming in Keil

First, right-click on the C language file name that contains the assembly part, then select the option with the red box in the menu as shown above.

Key Considerations for Mixed C and Assembly Programming in Keil

In the pop-up dialog, check the two items selected in the red box in the above image (by default, the check marks in front are gray, you need to make them black), and click OK.

3. Handling warnings related to ?C_START

After handling as described in 2, compiling again will not produce error messages, but the following warning messages will appear:

linking…

*** WARNING L1: UNRESOLVED EXTERNAL SYMBOL

SYMBOL: ?C_START

MODULE: STARTUP.obj (?C_STARTUP)

*** WARNING L2: REFERENCE MADE TO UNRESOLVED EXTERNAL

SYMBOL: ?C_START

MODULE: STARTUP.obj (?C_STARTUP)

ADDRESS: 000DH

The handling method is as follows:

Key Considerations for Mixed C and Assembly Programming in Keil

Right-click on the “Source Group 1” shown in the above image, and select “Add Files to Group ‘Source Group 1′” from the menu.

Key Considerations for Mixed C and Assembly Programming in Keil

Find your KEIL installation directory, select the “LIB” directory under the “C51” directory, and choose the “C51S.LIB” file, click Add, then Close.

Note that the file selection box shown in the above image only displays .c files by default. You need to select “Library file (*.lib)” in the “File Type” to display LIB files.

After adding C51S.LIB to the project, compile again, and the warning messages will disappear.

linking…

Program Size: data=9.0 xdata=0 code=28

creating hex file from “sendata”…

“sendata” – 0 Error(s), 0 Warning(s).

4. Solution to register conflict issues

Assembly program blocks often use the general registers of the 51 microcontroller, such as R0-R7. In this case, there may be conflicts with R0-R7 that have already been used in the C language program, leading to some very subtle and bizarre errors. Although some say that KEIL can automatically allocate register groups to avoid conflicts, I still encountered conflicts during specific tests (or is there a problem with my KEIL settings?). Below is a test program:

/*************************************************************

* Test Program

*************************************************************/

#include

#include

typedef unsigned char uint8;

uint8 buf[16] =

{

0x55

};

uint8 i = 0;

/************************************************************

* Initialize microcontroller related registers

***********************************************************/

void UartInit()

{

SCON = 0x50;

TMOD |= 0x21;

PCON |= 0x80;

TH1 = 0xE8;

TL1 = 0xE8;

IE |= 0x90;

TR1 = 1;

}

/**************************************************

* Delay

***************************************************/

void delay()

{

#pragma asm

MOV R6,#19

DELAY2: MOV R7,#18

DELAY1: DJNZ R7,DELAY1

DJNZ R6,DELAY2

RET

#pragma endasm

}

/**********************************************

* Send a character to COM1

**********************************************/

void SendChar(uint8 byteToSend)

{

SBUF = byteToSend;

while(!TI);

TI = 0;

}

/************************************************************

* Read a byte

***********************************************************/

uint8 read_byte()

{

uint8 recvdata = 8;

delay();// Delay

return recvdata;

}

/**************************************************

* Main Program

***************************************************/

int main()

{

UartInit();// Serial port initialization

while(1)

{

buf[i++] = read_byte();

}

}

/**************************************************

* Serial Port Interrupt Handling

***************************************************/

void chuankou() interrupt 4

{

if(RI)

for(i=0; i < sizeof(buf); i++)

{

SendChar(buf);

}

RI = 0;

}

The program itself is very simple and straightforward. The main function continuously reads the return value of read_byte() into buf, and when there is a serial port interrupt, it outputs the content of buf to the serial port. The read_byte() function has also been simplified to return a fixed value of 8.

When this program is compiled and run in KEIL, a strange situation arises: the content of buf output to the serial port is always 0 (the output should naturally be 8).

Commenting out the delay(); line makes the program output correctly.

delay(); only serves as a delay, how could it change the function’s return value?

Let’s take a look at the assembly code generated for the read_byte() function.

USING 0

MOV R7,#08H

ACALL delay

RET

It turns out that the read_byte() function places the return value in R7, then calls the delay function, and then returns. The subsequent program calls the return value of read_byte() directly from R7.

However, during the “ACALL delay” call, R7, which already held #08H, was decremented to 0 during the delay, which is why the content of buf is all 0.

Knowing the reason makes it easier to handle. As I am a novice with limited microcontroller knowledge, I currently have thought of the following four solutions:

(1) Avoid using Rn already used in the C language part

When compiling the assembly module, check the assembly code generated by the C language part and avoid using those Rn that have already been used in the C language part and may cause conflicts in the assembly part. For example, in the delay program above, replacing R6 and R7 with R3 and R4 makes the program work correctly.

(2) Use the USINg X + ARX method to call other groups of Rn registers

The 51 microcontroller has 4 groups of R0-R7 registers, and it is said that the group generally used in main is the 0 group. Therefore, using other groups in the assembly part is fine. The content of the delay function can be modified as follows:

void delay()

{

#pragma asm

USING 2

MOV AR6,#19

DELAY2: MOV AR7,#18

DELAY1: DJNZ AR7,DELAY1

DJNZ AR6,DELAY2

RET

#pragma endasm

}

However, note that if used this way, the length of the delay may need to be recalculated. Because AR6 and R6 are different, the following two English descriptions were found online:

R0–R7 The eight 8-bit general-purpose 8051 registers in the currently active register bank.

A Maximum of four register banks are available.

AR0–AR7 Represent the absolute data addresses of R0 through R7 in the current register bank.

The absolute address for these registers changes depending on which register bank is currently selected.

These symbols are only available when the USING assembler statement is given.

Refer to the USING assembler statement for more information on selecting the register bank.

These representations are suppressed by the NOAREGS directive of the Cx51 compiler.

This means that R0–R7 are registers, while AR0–AR7 are addresses, so the clock cycle counts for “MOV AR6,#19” and “MOV R6,#19” are different. When I was debugging a data acquisition program, I initially didn’t notice this issue, which is why the data collected using AR6 and R6 was different. It was only later that I realized that they had different delays (the clock cycle counts for each instruction of the 51 microcontroller can be found in many places online, so I won’t list them here).

(3) Use stack protection for registers used in the assembly part

Simply put, before using Rn in the assembly part, push their contents onto the stack, and after the delay loop ends, pop them back from the stack and reassign them.

For example, the delay program above can be modified as follows:

void delay()

{

#pragma asm

MOV A, R6

PUSH ACC

MOV A, R7

PUSH ACC

MOV R6, #19

DELAY2: MOV R7, #18

DELAY1: DJNZ R7, DELAY1

DJNZ R6, DELAY2

POP ACC

MOV R7, A

POP ACC

MOV R6, A

RET

#pragma endasm

}

(4) Use RS0 and RS1 to switch the register groups

In the 51 microcontroller, RS0 and RS1 are used to select which group of Rn to use, so by modifying the values of these two bits in the assembly program, you can switch register groups.

The delay function can be modified as follows:

void delay()

{

#pragma asm

SETB RS1

SETB RS0

MOV R6,#19

DELAY2: MOV R7,#18

DELAY1: DJNZ R7,DELAY1

DJNZ R6,DELAY2

CLR RS1

CLR RS0

RET

#pragma endasm

}

After entering the assembly part, select to use the third group of registers, and before exiting the assembly part, switch back to the previously used group (which is the 0 group in this program).

However, for more precise delays, methods (3) and (4) may have some slight effects, as they add some push/pop or set/clear statements. Therefore, if the delay accuracy is high, and using methods (3) or (4), the time spent on push/pop or set/clear statements should also be considered when calculating the delay loop count.

In addition to the above four methods, I have also tried the following methods, but they failed:

One is to add “using 2” after the void delay() function definition, making it:

void delay() using 2

{

…………………………..

}

Or add USINg 2 in the assembly, making it:

void delay()

{

#pragma asm

USING 2

…………………………..

#pragma endasm

}

But it seems that neither works…

> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >

How to Share to Moments

Click the upper right corner Key Considerations for Mixed C and Assembly Programming in Keil and select Key Considerations for Mixed C and Assembly Programming in Keil to share to Moments.

How to Follow “Chuangxue Electronics”

1. Click the upper right corner on WeChat “+” and select “Add Friends” → Search for “Chuangxue Electronics” in “Find Official Accounts” to find and follow us. 2. You can also search for WeChat ID “Chuangxue Electronics” to find and follow us.

How to View Past Historical Messages

Click the upper right corner icon to enter

Leave a Comment