Mixing C and Assembly in AVR GCC and AVR Studio 4

mixer
First, forget trying to learn the inline assembler syntax. I’ve read and re-read the AVR-GCC Inline Assembler Cookbook, and for the moment I can’t recall one useful detail.

From the Cookbook:

“Because of a lack of documentation, especially for the AVR version of the compiler, it may take some time to figure out the implementation details by studying the compiler and assembler source code.”

It’s far easier to incorporate an assembly file (.s) into your AVR Studio project. Simply include the file in your project dependencies on the left AVR GCC Window: Source Files (and any associated Header Files).

Adding Source File to AVRStudio4 Project

Notes:

Arguments are allocated left to right, starting in register R25 through R8. All arguments are aligned to start in even-numbered registers (odd-sized arguments, including char, have one free register above them), for example a single 16-bit value is passed via the R25:R24 register pair. A 32-bit value would be passed via R25:R24:R23:R22 register combination.

Notice below in my assembly function SerialPutChar() the passed byte (a char) is accessed via the R24 register (R25 is assumed empty).

If you need to pass more than what fits inside R25 to R8, the excess is passed on the stack.

Return values are expected to be passed in a similar fashion. An 8-bit value is passed via R24, a 16-bit value in R25:R24, up to 32-bits in R22:R23:R24:R25. 8-bit return values are zero/sign-extended to 16-bits by the called function.

Notice in my example below, in SerialGetChar() I pass the received byte back via the R24 register (R25 is cleared by the instruction: ldi r25, 0x00).

The passing and returning of values is demonstrated here.

It is good practice to save and restore any values inside registers before and after using them.

Study the use of the _SFR_IO_ADDR() macro in the following example.

Here is an example of accessing a byte-sized C variable in an assembly file:

  lds    r24, hi_ticks
  subi   r24, 0xff
  sts    hi_ticks, r24

The 8-bit variable hi_ticks as defined in the C file:

//global variable
volatile uint8_t hi_ticks; //overflow counter

Use the lo8() and hi8() macros to access multi-byte values.

Remember to include the defines file <avr/io.h>.

Remember to include your function prototypes.

Remember to define your functions globally:

  .global SerialPutChar
  SerialPutChar:

Example C Code Calling an Assembler Function:

  char c;

  while (1) {
    SerialPutChar('?'); //anybody out there?
    SerialPutChar(10); //NL
    SerialPutChar(13); //CR
    c = SerialGetChar(); //get user input
  }

uart.h:

//asynchronous 8n1 serial transmission per AVR AppNote #305
void SerialPutChar (char);
char SerialGetChar(void);

uart.s:

//UART Functions per AVR Application Note #305
#include <avr/io.h>
#include "uLogTimer.h"

//
//half bit delay
//
#define b 31				//38400 bps@8MHz with 0.3% error
//#define b 66				//19200 bps@8MHz with 0.6% error
UARTDelay:
		ldi  r17, b
UARTDelay1:
		dec  r17
		brne UARTDelay1
		ret

//
//asynchronous 8n1 serial transmit byte
//
#define TX_PIN 6				//TX pin is PA6

.global SerialPutChar
SerialPutChar:
		push r16
		push r17
		ldi  r16, 10		;1 start + 8 data + 1 stop bits (bit count)
		com  r24			;Invert it (r24 = xmit byte)
		sec				;Start bit
putchar0:
		brcc putchar1		;If carry set
		cbi  _SFR_IO_ADDR(PORTA), TX_PIN ;send a '0'
		rjmp putchar2		;else
putchar1:
		sbi  _SFR_IO_ADDR(PORTA), TX_PIN ;send a '1'
		nop
putchar2:
		rcall UARTDelay		;1/2 bit delay +
		rcall UARTDelay		;1/2 bit delay = 1bit delay
		lsr   r24			;Get next bit
		dec   r16			;If not all bits sent
		brne  putchar0		;send next
						;else
		pop   r17
		pop   r16
		ret				;return

//
//asynchronous 8n1 serial receive byte
//
#define RX_PIN 5				//RX pin is PA5

.global SerialGetChar
SerialGetChar:
		push  r16
		push  r17
		ldi   r16, 9		;8 data bits + 1 stop bit
getchar1:
		sbic  _SFR_IO_ADDR(PINA), RX_PIN ;Wait for start bit
		rjmp  getchar1
		rcall UARTDelay		;1.5bit delay after start bit edge
getchar2:
		rcall UARTDelay		;1 bit delay from here
		rcall UARTDelay
		clc				;clear carry
		sbic  _SFR_IO_ADDR(PINA), RX_PIN ;if RX pin high
		sec				;
		dec   r16			;If bit is stop bit
		breq  getchar3		;   return
						;else
		ror   r24			;   shift bit into r24 (rec'd byte)
		rjmp  getchar2		;   go get next
getchar3:
		ldi   r25, 0x00		;zero extend r24:r25 for return
		pop   r17
		pop   r16
		ret

References:

This is essential reading on GCC Compiler register usage.

Here is a link to an example program which mixes C and Assembly from the AVR-libc User Manual.

This document is a good comparison of AVR and GCC Assembly syntax.

The ATMEL AVR 8-bit Instruction Set Manual.

My example is an adaptation of the ATMEL Application Note AVR305: Half Duplex Compact Software UART.

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged , , , , , , , . Bookmark the permalink.

Leave a comment