Assembler wars continue...
At this point, if you are as frustrated with all assembler gyrations as I am, you would wonder if all this pain over the best and most pious AVR assembler is really worth the trouble. avr-as and the GNU toolchian are all very well, but after wrangling with EEPROM programming for another few days, I was beginning to worry about my sanity.
While doing some more futile googling to figure out what I was doing wrong...I discovered that there were at least two more AVR assemblers for Linux beyond the GNU toolchain: 'Tom's AVR Assembler' and 'avra'. They are both a bit old; 'tavrasm' was last maintained in 2005, and the last activity with 'avra' on Sourceforge was in 2010. But the big difference: BOTH are intended specifically to assemble Atmel's avrasm-formatted files, and both include a superset of Atmel's assembler directives. The latter of which, as it turns out, I am pretty familiar.
In my last post I documented my frustrations with the 'AVR-libc' header file gibberish, and demonstrated how to adapt Atmel's include files to work with avr-as, so that using the other garbage wasn't strictly necessary. With a lot of trial-and-error, I managed to get almost everything to work...except a fully useful way to define EEPROM data and link addresses that would point to it. I could handcode a .hex file (ech). Or create a separate EEPROM file (faking in the data as if it was in program space), assemble it, then look at the list file and add constants to the main program file that refer to the right addresses (double ech).
Ugly. Non-standard. Not very portable.
Back in Atmel land...
And then I discovered tavrasm, and realized that I could return to a more sensible assembler syntax that was AVR-specific, use the standard Atmel *.inc files...errrrrr...sort of. But then the honeymoon ended, the romance was over....
Yeah. Tom Mortensen last worked on his assembler in 2005. His 'macro in macro' expansion of the assembler directives was just a BIT undocumented. I spent half a day looking vainly for code examples, experimenting with conditional assembly directives that didn't work, wondering why there weren't any command line options to generate a .list file even though there were documentation examples and directives for it.....The list of devices supported by tavrasm is/was VERY limited: it did NOT include any ATtiny chips, among other things...and there are a few instructions on the ATtiny (like MOVW, LPM <register>, Z+ and, more critically, SPM) that are too good to give up.
To add the basic support for the ATtinys (sizes of the various memory spaces), I edited the device header file and recompiled, but there wasn't any easy way, short of learning yacc and lex again, then hacking through Tom's code and Atmel's part docs, to add support for the newer instructions. Arrrrrrr....another afternoon wasted.
So I turned to avra, and all my problems went away, including the vast frustrations I was beginning to have with Tom's macros.
Here's the 'avra' version of Blink26.S:
--------------------------------------------------------
;
; blink26.S
;
; ** LOAD IO DEFS FOR THE RIGHT DEVICE
;
.nolist
.include "avrinct/avrcommon.inc"
.include "avrinct/tn26def.inc"
;
; ** GET ME SOME HANDY MACROS **
;
.include "avrinct/macros.inc"
;
; *** DEFINES HERE ***
;
.list
.equ F_CPU = 8000 ; 8Mhz clock, for msec_delay define in 'macro.inc'
.equ OUTPIN = PA7 ; for t26
.equ IO_OS = 0x20 ; for lds/sts with i/o ports, e.g 'lds r2, (IO_OS + TCNT0)'
;
; *** SRAM RESERVES HERE ***
;
.dseg
; FOR example:
;<datalabel>: .byte 1 ; to reserve 1 byte in SRAM for <datalabel>
;
; *** RESETS, IVECTORS, AND MAIN PROGRAM ***
;
.cseg
.org 0x0000
reset:
rjmp main; *** tn26 interrupt vector names, just fer handy ***
reti ; 0001 INT0_vect
reti ; 0002 PCI0_vect
reti ; 0003 TIMER1_OC1A_vect
reti ; 0004 TIMER1_OC1B_vect
reti ; 0005 TIMER1_OVF_vect
reti ; 0006 TIMER0_OVF_vect
reti ; 0007 USI_START_vect
reti ; 0008 USI_OVF_vect
reti ; 0009 EERDY_vect EEPROM Ready
reti ; 000A ACOMP_vect Analog Comparator
reti ; 000B ADC_vect ADC Conversion Complete
;
;
main:
spsetup r16, RAMEND ; use the macro to set up the stack
sbi DDRA, OUTPIN ; set pin 'OUTPIN' on port A to high for OUTPUT
ldi ZL, low(array * 2) ; to calc address right for LPM later
ldi ZH, high(array * 2) ; ditto
blink:
sbi PORTA, OUTPIN ; then drive it HIGH
lpm r20, Z
b2:
msec_delay 1, r17, r18, r19 ; 1 ms wait * r20 times
dec r20
brne b2
cbi PORTA, OUTPIN ; then LOW...
lpm r20, Z
b3:
msec_delay 1, r17, r18, r19 ; 1 ms wait * r20 times
dec r20
brne b3
adiw Z, 1
lpm r20, Z
tst r20
breq end
rjmp blink;
;
end:
rjmp end;
; *** MAIN PROGRAM END ***
;
; PROGRAM MEMORY DATA HERE
;
array: .db 10,250,20,240,30,230,40,220,50,210,60,200,70,190,80,180,90,170,100,160,110,150,120,140,130,0
;
;
;
--------------------------------------------------------
'avra' has excellent documentation, particularly the macros; there are many, many code examples and the expansion from Atmel's basic functionality allows
Things in the above code are just clear, not near as much mud. We have .dseg, .cseg, and .eseg directives; a very clear difference between register (.def) labels and symbolic constants (.equ); runtime constant expansions using .set, excellent conditional compilation directives; .list and .nolist for disassembly and automatic generation of Intel hex code, list and EEPROM hex files.
Now I can use the straight Atmel *.inc files with almost no customization, or even use the conditional directives to make them even more handy. The inclusion of 'avrcommon.inc' takes care of some of the universal constants and macros, including stack initialization.
Macros are a bit easier, and a lot more powerful; here's part of my rewritten utility file:
--------------------------------------------------------
;*
;* macros.inc file
;*
;*
;
...
...
;
;
; ***
; *** inline millisec delay loop
; ***
; *** max is about 11000
; *** !!and be sure to define F_CPU in main program for both like below!!
;
;.equ F_CPU, <CPU mhz * 1000> (e.g. for 8Mhz use 8000)
;
;
; delay = @0, ra = @1, rb = @2, rc = @3
.macro msec_delay
ldi @1, ((((@0 * F_CPU - 1) % 327680) % 1280) / 5)
ldi @2, (((@0 * F_CPU - 1) % 327680) / 1280)
ldi @3, ((@0 * F_CPU - 1) / 327680)
m_%:
subi @1, 0x01
sbci @2, 0x00
sbci @3, 0x00
brne m_%
nop
.endmacro
; ***
--------------------------------------------------------
Since avra is AVR-specific, the Makefile is REALLY streamlined. avra doesn't need the one-size-fits-all command-line options and it doesn't have to do any linking; directives in the assembled file handle almost everything:
<prompt>:avra -I <librarypath> test.S -o test.hex -l test.lst
...literally does the work of four separate functions in my previous Makefile, so you get all these files from one command:
test.hex - the uploadable file, already in Intel hex
test.eep.hex - the EEPROM file, if needed
test.lst - disassembled list file for sanity checking
test.obj - whether you need the object file or not
My current assembly Makefile, still generic but without C code support:
------------------------------------------------------------------------------------------------------
P=test
UC=t26
UPNUM=0
APRG=arduino
BUPATH=./back
VERSION=01
TS=`date +%H%M%S-%Y`
ifdef VER
TS:=$(VER)
endif
ifdef PRG
P:=$(PRG)
endif
ifdef MCU
UC:=$(MCU)
endif
ifdef UPN
UPNUM:=$(UPN)
endif
UPORT=/dev/ttyACM$(UPNUM)
AFLAGS=-I /home/zenas/mylib/
AS=avra
DUDE=avrdude
$(P).hex: $(P).S
cp $(P).S $(BUPATH)/$(P)_$(TS).S; $(AS) $(AFLAGS) $(P).S -o $(P).hex -l $(P).lst
hex: $(P).hex
$(P): $(P).hex
toucha:
touch *.S
clean:
rm *.hex; rm *.obj; rm *.cof; rm *.lst
upload: $(P).hex
$(DUDE) -P $(UPORT) -p $(UC) -c $(APRG) -b 19200 -U flash:w:$(P).hex
get:
$(DUDE) -P $(UPORT) -p $(UC) -c $(APRG) -b 19200 -U flash:r:$(UC)-$(TS)-get.hex:i
--------------------------------------------------------
With all that streamlining, I went ahead and added the 'upload' and 'get' routines using avrdude. Both of those functions assume you are using an Arduino connected to a USB port as a programmer. You could probably also use it to put assembly code directly on an Arduino, if you really wanted to do that...
Since the whole idea isn't to go blind, or broke, or murder my cat before I get a real project finished...I think I've found something tolerable to use for those functions where assembly on the bare metal is the best solution. avra meets all the qualifications; we can always use AVR-libc for C stuff, as it was intended, and look at the disassembly listings occasionally if we want to poach some of the tricks gcc might use to optimize AVR code; the 'msec_delay' macro above was specifically stolen from gcc output, for instance.
No comments:
Post a Comment