How to combine C program with external ASM

Sometimes it is useful to program some parts in assembly language while whole program is written in C. There are two methods how to inject assembler parts in C program. One of them is to write in-line ASM and another is to link existing assembler program. For instance some parts of program may require critical timings and you are not sure if C compiler can be as optimal as you expect. Other reasons according to avr-libc

documentation may be as follows:

  • Code for AVR's that have no RAM;

  • Very timing critical applications;

  • Special tweaks that cannot be done in C.

As I mentioned all this can be also done using in-line assembler feature. For somebody in-line assembler may look too complicated because of special syntax requirements. Before starting to write assembler program first read (What Registers are used by the C compiler) to ensure you are using right registers in your assembler program and won't mess up the normal program flow by occupying registers that are storing values from other routines. In most cases push and pop may do the job.

OK lets have simple example to clear things out. Dont pay attention to fact that I am writing program which can be easily written in C. This is only for demonstration purposes how to relate C and ASM. Project consists of 3 parts:

  • mainC.c – C program;

  • extasm.S – ASM program;

  • Makefile – makefileneeded to make a project.

Note, that assembler file must have capital extension .S, otherwise program wont compile and maybe cleared during make clear command.

Lets write program:

<<<<<<<<<<<<,mainC.c>>>>>>>>>>>>>>>

#include <avr/io.h>
volatile uint8_t pinbits; //define poer pins
extern void InitPort(void);//init port for output
extern void sendpinbits(void);//send to port
int main()
{
	pinbits=1;//set pin 1 as output
	InitPort();//call subroutine from assembler file
	while(1)//repeat for ever
	{
	pinbits=1;//Pin High
	sendpinbits();//call subroutine from assembler file
	pinbits=0;//Pin Low
	sendpinbits();//call subroutine from assembler file
	}
}

Then assembler program:

<<<<<<<<<<<<extasm.S>>>>>>>>>>>

#include <avr/io.h>				;required for register definitions
.extern pinbits					;external variable
.global InitPort				;make accesible globally
InitPort:					;function name  
	push r18				;save register value
	lds r18, pinbits			;load variable to r18
	out _SFR_IO_ADDR(DDRD), r18	;set pins as output
	pop r18				;restore register
	ret					;return from subroutine
.global sendpinbits				;make global
sendpinbits:					;function name
	push r18				;save register value
	lds r18, pinbits			;load variable to r18
	out _SFR_IO_ADDR(PORTD), r18	;pins to High
	pop r18				;restore register
	ret					;return from subroutine

Makefile is generated with Mfile which comes with WinAVR. You only have to change these parts:

<<<<<<Makefile>>>>>>>>

# MCU name

MCU = atmega8

# Processor frequency.

F_CPU = 8000000

# Output format. (can be srec, ihex, binary)

FORMAT = ihex

# Target file name (without extension).

TARGET = mainC

# List Assembler source files here.

ASRC = extasm.S

Other parts of Makefile leave as they are. Then call command make all (Programmers Notepad->Tools->Make All) and program should compile without errors. I have tested this example with AVRStudio debugger and it works properly.

Good luck.

Comments

After i followed the above step by step the code didn't work and i got the following message:

-------- begin --------
avr-gcc (WinAVR 20100110) 4.3.3
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Compiling C: mainC.c
avr-gcc -c -mmcu=atmega16 -I. -gdwarf-2 -DF_CPU=8000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=./mainC.lst -std=gnu99 -Wundef -MMD -MP -MF .dep/mainC.o.d mainC.c -o mainC.o
mainC.c:34: warning: function declaration isn't a prototype

Linking: mainC.elf
avr-gcc -mmcu=atmega16 -I. -gdwarf-2 -DF_CPU=8000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,-adhlns=mainC.o -std=gnu99 -Wundef -MMD -MP -MF .dep/mainC.elf.d mainC.o --output mainC.elf -Wl,-Map=mainC.map,--cref -lm
mainC.o: In function `main':
F:\Microcontrollers\projects\MakeFile\ASSEMBLY&C/mainC.c:38: undefined reference to `InitPort'
F:\Microcontrollers\projects\MakeFile\ASSEMBLY&C/mainC.c:46: undefined reference to `sendpinbits'
F:\Microcontrollers\projects\MakeFile\ASSEMBLY&C/mainC.c:50: undefined reference to `sendpinbits'
make: *** [Clock+.elf] Error 1