; Demonstrates floating point conversion ; in Assembler, (C)2003 www.avr-asm-tutorial.net ; ; The task: You read in a 10-bit result of an ; analogue-digital-converter, number is in the ; range from hex 0000 to 03FF. ; You need to convert this into a floating point ; number in the range from 0.000 to 5.000 Volt ; ; The program scheme: ; 1. Check that the number is smaller than \$0400. ; Prevent illegal overflows during the ; following multiplication. ; 2. Multiplication by 320,313 (hex 04E338). ; That step multiplies by 5,000, 65536 and ; divides by 1023 in one step! ; 3. Round the result and cut the last two bytes ; of the result. ; This step divides by 65536 by ignoring the ; last two bytes of the result. Before doing ; that, bit 15 is used to round the result. ; 4. Convert the resulting word to ASCII and set ; the correct decimal point ; The resulting word in the range from 0 to ; 5.000 is displayed in ASCII-characters ; ; The registers used: ; The routines use the registers R10..R1 without ; saving these before. Also required is a multi- ; purpose register called rmp, located in the ; upper half of the registers. Please take care ; that this register doesn't conflict with the ; register use in the rest of your program. ; ; When entering the routine the 10-bit number is ; expected in the register pair R2:R1. ; If the number is greater than \$03FF then the ; check routine returns with the carry flag set, ; and the resulting string in R5:R6:R7:R8:R9:R10 ; is set to the null-terminated ASCII-string ; "E.EEEE". ; The multiplication uses R6:R5:R4:R3 to hold ; the multiplicator 320.313 (is shifted left ; max. ten times during multiplication) ; The result of the multiplication is calculated ; in the registers R10:R9:R8:R7. ; The result of the so called division by 65536 ; by just ignoring R8:R7 in the result, is in ; R10:R9. R10:R9 is rounded, depending on the ; highest bit of R8, and the result is copied to ; R2:R1. ; Conversion to an ASCII-string uses the input ; in R2:R1, the register pair R4:R3 as a divisor ; for conversion, and places the ASCII result ; string to R5:R6:R7:R8:R9:R10 (null-terminated). ; ; Other conventions: ; The conversion uses subroutines and the stack. ; The stack must work fine for the use of three ; levels (six bytes SRAM). ; ; Conversion times: ; The whole routine requires 326 clock cycles ; maximum (converting \$03FF), and 111 clock cycles ; minimum (converting \$0000). At 4 MHz the times ; are 81.25 microseconds resp. 27.5 microseconds. ; ; Definitions: ; Registers .DEF rmp = R16 ; used as multi-purpose register ; ; AVR type ; Tested for type AT90S8515, only required for ; stack setting, routines work fine with other ; AT90S-types also .NOLIST .INCLUDE "8515def.inc" .LIST ; ; Start of test program ; ; Just writes a number to R2:R1 and starts the ; conversion routine, for test purposes only ; .CSEG .ORG \$0000 rjmp main ; main: ldi rmp,HIGH(RAMEND) ; Set the stack out SPH,rmp ldi rmp,LOW(RAMEND) out SPL,rmp ldi rmp,\$03 ; Convert \$03FF mov R2,rmp ldi rmp,\$FF mov R1,rmp rcall fpconv10 ; call the conversion routine no_end: ; unlimited loop, when done rjmp no_end ; ; Conversion routine wrapper, calls the different conversion steps ; fpconv10: rcall fpconv10c ; Check the input value in R2:R1 brcs fpconv10e ; if carry set, set "E.EEE" rcall fpconv10m ; multiplicate by 320,313 rcall fpconv10r ; round and divide by 65536 rcall fpconv10a ; convert to ASCII string rjmp fpconv10f ; set decimal point and null-termination fpconv10e: ldi rmp,'E' ; set error condition to result string mov R5,rmp mov R7,rmp mov R8,rmp mov R9, rmp fpconv10f: ldi rmp,'.' ; set decimal point mov R6,rmp clr rmp ; null-terminate ASCII string mov R10,rmp ret ; all done ; ; Subroutine inputcheck ; fpconv10c: ldi rmp,\$03 ; compare MSB with 03 cp rmp,R2 ; if R2>\$03, set carry on return ret ; ; Subroutine multiplication by 320,313 ; ; Starting conditions: ; +---+---+ ; | R2+ R1| Input number ; +---+---+ ; +---+---+---+---+ ; | R6| R5| R4| R3| Multiplicant 320.313 = \$00 04 E3 38 ; | 00| 04| E3| 38| ; +---+---+---+---+ ; +---+---+---+---+ ; |R10| R9| R8| R7| Result ; | 00| 00| 00| 00| ; +---+---+---+---+ ; fpconv10m: clr R6 ; set the multiplicant to 320.313 ldi rmp,\$04 mov R5,rmp ldi rmp,\$E3 mov R4,rmp ldi rmp,\$38 mov R3,rmp clr R10 ; clear the result clr R9 clr R8 clr R7 fpconv10m1: mov rmp,R1 ; check if the number is clear or rmp,R2 ; any bit of the word a one? brne fpconv10m2 ; still one's, go on convert ret ; ready, return back fpconv10m2: lsr R2 ; shift MSB to the right (div by 2) ror R1 ; rotate LSB to the right and set bit 7 brcc fpconv10m3 ; if the lowest bit was 0, then skip adding add R7,R3 ; add the number in R6:R5:R4:R3 to the result adc R8,R4 adc R9,R5 adc R10,R6 fpconv10m3: lsl R3 ; multiply R6:R5:R4:R3 by 2 rol R4 rol R5 rol R6 rjmp fpconv10m1 ; repeat for next bit ; ; Round the value in R10:R9 with the value in bit 7 of R8 ; fpconv10r: clr rmp ; put zero to rmp lsl R8 ; rotate bit 7 to carry adc R9,rmp ; add LSB with carry adc R10,rmp ; add MSB with carry mov R2,R10 ; copy the value to R2:R1 (divide by 65536) mov R1,R9 ret ; ; Convert the word in R2:R1 to an ASCII string in R5:R6:R7:R8:R9:R10 ; ; +---+---+ ; + R2| R1| Input value 0..5,000 ; +---+---+ ; +---+---+ ; | R4| R3| Decimal divider value ; +---+---+ ; +---+---+---+---+---+---+ ; | R5| R6| R7| R8| R9|R10| Resulting ASCII string (for input value 5,000) ; |'5'|'.'|'0'|'0'|'0'|\$00| null-terminated ; +---+---+---+---+---+---+ ; fpconv10a: ldi rmp,HIGH(1000) ; Set the decimal divider value to 1,000 mov R4,rmp ldi rmp,LOW(1000) mov R3,rmp rcall fpconv10d ; get ASCII digit by repeated subtraction mov R5,rmp ; set thousands string char clr R4 ; Set the decimal divider value to 100 ldi rmp,100 mov R3,rmp rcall fpconv10d ; get the next ASCII digit mov R7,rmp ; set hundreds string char ldi rmp,10 ; Set the decimal divider value to 10 mov R3,rmp rcall fpconv10d ; get the next ASCII digit mov R8,rmp ; set tens string char ldi rmp,'0' ; convert the rest to an ASCII char add rmp,R1 mov R9,rmp ; set ones string char ret ; ; Convert binary word in R2:R1 to a decimal digit by substracting ; the decimal divider value in R4:R3 (1000, 100, 10) ; fpconv10d: ldi rmp,'0' ; start with decimal value 0 fpconv10d1: cp R1,R3 ; Compare word with decimal divider value cpc R2,R4 brcc fpconv10d2 ; Carry clear, subtract divider value ret ; done subtraction fpconv10d2: sub R1,R3 ; subtract divider value sbc R2,R4 inc rmp ; up one digit rjmp fpconv10d1 ; once again ; ; End of floating point conversion routines ; ; ; End of conversion test routine ;