Electronics > Microcontrollers

Calling ASM functions in C

(1/6) > >>

Psi:
Hi,  for a AVRGCC project on an ATMega i need to do some fast integer division.

The bit shift method for divide by 2/4/8 etc wont work for this as the values are only known at runtime and can be any value.
I've been having a look at the official Atmel app notes on division which provides size/speed optimized sample code in ASM for various operations.
http://www.atmel.com/Images/doc0936.pdf
http://www.atmel.com/Images/AVR200.zip

But i'm not sure how to integrate the ASM into my C program.

Here's one of the ASM functions from the app note

--- Code: ---;***************************************************************************
;*
;* "div16u" - 16/16 Bit Unsigned Division
;*
;* This subroutine divides the two 16-bit numbers
;* "dd8uH:dd8uL" (dividend) and "dv16uH:dv16uL" (divisor).
;* The result is placed in "dres16uH:dres16uL" and the remainder in
;* "drem16uH:drem16uL".
;* 
;* Number of words :196 + return
;* Number of cycles :148/173/196 (Min/Avg/Max)
;* Low registers used :2 (drem16uL,drem16uH)
;* High registers used  :4 (dres16uL/dd16uL,dres16uH/dd16uH,dv16uL,dv16uH)
;*
;***************************************************************************

;***** Subroutine Register Variables

.def drem16uL=r14
.def drem16uH=r15
.def dres16uL=r16
.def dres16uH=r17
.def dd16uL =r16
.def dd16uH =r17
.def dv16uL =r18
.def dv16uH =r19

;***** Code

div16u: clr drem16uL ;clear remainder Low byte
sub drem16uH,drem16uH;clear remainder High byte and carry

rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_1 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_2 ;else
d16u_1: sec ;    set carry to be shifted into result

d16u_2: rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_3 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_4 ;else
d16u_3: sec ;    set carry to be shifted into result

d16u_4: rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_5 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_6 ;else
d16u_5: sec ;    set carry to be shifted into result

d16u_6: rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_7 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_8 ;else
d16u_7: sec ;    set carry to be shifted into result

d16u_8: rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_9 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_10 ;else
d16u_9: sec ;    set carry to be shifted into result

d16u_10:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_11 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_12 ;else
d16u_11:sec ;    set carry to be shifted into result

d16u_12:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_13 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_14 ;else
d16u_13:sec ;    set carry to be shifted into result

d16u_14:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_15 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_16 ;else
d16u_15:sec ;    set carry to be shifted into result

d16u_16:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_17 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_18 ;else
d16u_17: sec ;    set carry to be shifted into result

d16u_18:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_19 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_20 ;else
d16u_19:sec ;    set carry to be shifted into result

d16u_20:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_21 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_22 ;else
d16u_21:sec ;    set carry to be shifted into result

d16u_22:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_23 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_24 ;else
d16u_23:sec ;    set carry to be shifted into result

d16u_24:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_25 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_26 ;else
d16u_25:sec ;    set carry to be shifted into result

d16u_26:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_27 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_28 ;else
d16u_27:sec ;    set carry to be shifted into result

d16u_28:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_29 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_30 ;else
d16u_29:sec ;    set carry to be shifted into result

d16u_30:rol dd16uL ;shift left dividend
rol dd16uH
rol drem16uL ;shift dividend into remainder
rol drem16uH
sub drem16uL,dv16uL ;remainder = remainder - divisor
sbc drem16uH,dv16uH ;
brcc d16u_31 ;if result negative
add drem16uL,dv16uL ;    restore remainder
adc drem16uH,dv16uH
clc ;    clear carry to be shifted into result
rjmp d16u_32 ;else
d16u_31:sec ;    set carry to be shifted into result

d16u_32:rol dd16uL ;shift left dividend
rol dd16uH
ret

--- End code ---

Can someone point me in the right direction for encapsulating that into something i can call directly in C.
I've not done ASM since x86 in Uni and i seem to remember you have to be careful to restore all the cpu register values you have used to there original state when you finish your ASM code.

alm:
How much faster is this algorithm compared to the code produced by GCC? I would also expect it to produce something similar with optimizations enabled. You can look at the generated assembly or count cycles on the AVR simulator.

I'm sure you'll find tutorials in the avr-gcc docs and on avrfreaks.net.

Psi:

--- Quote from: alm on April 28, 2012, 11:23:56 am ---How much faster is this algorithm compared to the code produced by GCC? I would also expect it to produce something similar with optimizations enabled. You can look at the generated assembly or count cycles on the AVR simulator.

I'm sure you'll find tutorials in the avr-gcc docs and on avrfreaks.net.

--- End quote ---

I've read that div and ldiv in avrgcc stdlib.h aren't optimized

amspire:

--- Quote from: Psi on April 28, 2012, 11:27:04 am ---
--- Quote from: alm on April 28, 2012, 11:23:56 am ---How much faster is this algorithm compared to the code produced by GCC? I would also expect it to produce something similar with optimizations enabled. You can look at the generated assembly or count cycles on the AVR simulator.

I'm sure you'll find tutorials in the avr-gcc docs and on avrfreaks.net.

--- End quote ---

I've read that div and ldiv in avrgcc stdlib.h aren't optimized

--- End quote ---

It is worth testing the GCC performance anyway. If you have division operations in your code using constants, GCC actually can optimize the code for the particular constant.

For example if my_variable is an unsigned long, it can work out that "my_variable) / 0x01000000L" means take the top byte and ignore the rest. So the long division is optimized down to a 1 cycle "mov" instruction if my_variable and the destination were already loaded in the registers.

Richard.

Psi:
yeah, sadly this isn't a constant.

There are a few places i need a fast divide, but one is correcting an ADC value for a sensor which isn't always full adc scale (0-5V).
It has to be field adjustable so, for example, if the sensor min is 0.6V and max 4.5V this can be corrected for in code to produce a 0-255 value corresponding to min-max, eg

if (adc_data>minval)
{
    if (adc_data<maxval)
    {
        div_struct = ldiv(  (  (uint16_t)(adc_data) - minval)   *   255 )      ,   (maxval - minval));
        sensordata = div_struct.quot;                  
    }
    else
    {
        sensordata = 255;         
    }                              
}
else
{
    sensordata = 0;         
}   


Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version