Mixing assembler and C on ATMEGA1284

Go To Last Post
5 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Apologies if this topic has been addressed before but the topic search facilities seem to be primitive.
C allows in line assembler but the resulting format is a mess.
We would just like a way to link ATMEL assembler/ macroassembler and GCC C routines avaiable in separate files.
Problem is ATMEL assembler/ macroassembler doesn't use a linker and so does not produce linkable object files. That is it doesn't allow unresolved references that have to be resolved at the linking stage. Well, we can live with that to some extent by never calling a C routine from assembler - only the other way round. Then one method would be to load the assembled assembler as a hex file into the C program as a constant data block to be stored in EPROM at an absolute location. Then we could write in C a list of defines to the absolute addresses of the APIs it implements. Haven't tried it yet!
Then it struck me that AVR libC must surely have been compiled from assembler, especially for the floating point math library for example, so I am wondering what tool was used to do that?
( I also need to think about how that code can be relocatable, i.e if an assembler routine in the library calls another assembler routine in the library it would compile to an absolute address not a relative address unless you did it in a non-standard and probably cumbersome way to be relocatable).
Microchip: We really need an assembler that compiles to .obj modules compatible with the same reference-resolving linker as C! Is there a secret one lurking around somewhere that was used to produce libC?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
Last Edited: Thu. Oct 17, 2019 - 04:36 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The gcc tools include a macro assembler - gas. GCC compiles C down to assembler as part of the process. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


PWD999 wrote:
We would just like a way to link ATMEL assembler/ macroassembler and GCC C routines avaiable in separate files.
You do know there is a user manual don't you? This is part of it:

 

https://www.nongnu.org/avr-libc/user-manual/group__asmdemo.html

 

Bottom line you just put your Asm code into .S and make sure the public API adhere to the avr-gcc ABI:

 

https://gcc.gnu.org/wiki/avr-gcc

 

(specifically understand "Calling Convention" on that page).

PWD999 wrote:
so I am wondering what tool was used to do that?
As noted above the way avr-gcc works (like most C compilers in fact) is that it simply converts the C you write into Asm source code then passes those files to the GNU avr-as assembler to generate the code. You don't normally get to see this assembler that it creates but if you use -save-temps then you do. For example:

#include <math.h>
#include <avr/io.h>

int main(void) {
	DDRB = 0xFF;
	while(1) {
		PORTB = sin(PIND);
	}
}

creates:

 

 

For each filename.c the use of -save-temps now leaves behind filename.i (the code after preprocessing) and filename.s (asm code created from the C).

 

In my example lines 66/67 are writing 0xFF (-1) to DDRB (io location 0x04) then the main while(1) loop is between line 78 and the RJMP back at line 92. You can see it does pretty much what I asked for. At line 80 is the IN that reads PIND (0x09). It's read into R22 as R25:R22 are then passed to the _floatunsisf() function. That converts the 8 bit integer into IEEE754 floating point format. Then there is the call to sin() and finally the call to __fixunssfsi() converts the floating value bak to integer and the lowest of the bytes that returns (R22) is finally output to PORTB (0x05).

 

As you'll also note this file is littered with additional information to assist the generation of DWARF debugging information. That can make it quite hard to read. For that reason I wrote a little utility:

 

https://github.com/wrightflyer/avr-source

 

If I run that on the .s I just produced then I get a .s that looks like:

 

        .text
.Ltext0:
.global __floatunsisf
.global __fixunssfsi

        .section        .text.startup.main,"ax",@progbits
.global main
        .type   main, @function
main:
//==> int main(void) {
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
//==>   DDRB = 0xFF;
        ldi r24,lo8(-1)  ;  tmp48,
        out 0x4,r24      ;  MEM[(volatile uint8_t *)36B], tmp48
.L2:
//==>           PORTB = sin(PIND);
        in r22,0x9       ;  D.1716, MEM[(volatile uint8_t *)41B]
//==>           PORTB = sin(PIND);
        ldi r23,0        ;  D.1716
        ldi r24,0        ;  D.1716
        ldi r25,0        ;  D.1716
        call __floatunsisf       ;
        call sin         ;
        call __fixunssfsi        ;
        out 0x5,r22      ;  MEM[(volatile uint8_t *)37B], tmp54
        rjmp .L2         ;
        .size   main, .-main
        .text
.Letext0:

that has filtered most of the "noise" and then added C source annotation back into the code (the same source annotation as you see in a .lss disassembly of the final ELF/DWARF)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Simple example (c, c++, makes no difference), replaced led.on()/led.off() with asm equivalents-

 

//ATTiny 416 Xplained Nano
#include <stdint.h>
#include <stdbool.h>
#include "Port.hpp"
#include "Cpuint.hpp"
#include "Delay.hpp"
#include "Usart.hpp"

 

PB5_OUTL_t led;                 //led on PB5 (on=low)
Usart< Usart0_Alt, 0 > usart0;  //usart0 alternate pins, no buffer

 

//prevent c++ from changing names

extern "C" {
    void ledon();
    void ledoff();

}

/*
 //here is the asm in this file-> led.S

.text
.globl ledon
.globl ledoff

ledon:
cbi    0x05, 5
ret
ledoff:
sbi    0x05, 5
ret

*/

 

int main(){

    //show when startup so can see any restart problems
   
ledon();//led.on();
    Delay::wait(2_sec);
   
ledoff();//led.off();

 

    usart0.setupUART( 19200, usart0.RX_NORMAL );//rx only
    usart0.irq( usart0.RXCOMPLETE_IRQ, true );  //with rxc irq

    Cpuint::set_func( Cpuint::USART0_RXC,       //set rxc function
        [](){ led.on(); usart0.getch(); }
    );
    Cpuint::on( true );                         //global irq on

    for(;;){
        if( led.isOff() ) continue;
        Delay::wait(50_ms);
        led.off();
    }
}

 

So C, C++, S, s, whatever- it all gets compiled or processed into object files and the linker goes to work on the whole set. This is nothing new, and has been around since... forever. Getting syntax correct takes time, but there are plenty of examples around, plus source code like avr libc. 

Last Edited: Thu. Oct 17, 2019 - 09:09 AM