Forum Menu




 


Log in Problems?
New User? Sign Up!
AVR Freaks Forum Index

Post new topic   Reply to topic
View previous topic Printable version Log in to check your private messages View next topic
Author Message
Buttercup1101
PostPosted: May 03, 2012 - 06:04 PM
Newbie


Joined: Apr 20, 2012
Posts: 7


Hello everyone.

My problem is simple. I need to get the value from my 16bit counter1 to my LCD.

I am programming in AVR Assembler!

Lets say my counter has reached a count of 5000 that means that:
TCNT1H = 0001 0011
TCNT1L = 1000 1000

The way I was trying to achieve this was by loading each nibble (after conversion) in its own byte like this:
High_Byte_High_Nibble = 5
High_Byte_Low_Nibble = 0
Low_Byte_High_Nibble = 0
Low_Byte_Low_Nibble = 0
This will make it easy to print on to the LCD.

The biggest problem I am having is that I can't wrap my mind around how to use a 16 Bit number to keep my count.

But If anyone can point me in the direction of some example code, I would really appreciate it.
 
 View user's profile Send private message  
Reply with quote Back to top
david.prentice
PostPosted: May 03, 2012 - 06:23 PM
10k+ Postman


Joined: Feb 12, 2005
Posts: 16544
Location: Wormshill, England

In the old days, there used to be books published for 6502, 6800, Z80 and other popular chips. If you can find an old book for $1, it is worth buying.

They would show you how to convert numbers to strings, multiply, divide, ... and other common tasks.

Nothing has changed. The algorithms are the same. The ASM mnemonics vary slightly.

A useful algorithm is to test and subtract 10000, 1000, 100, ... and output the relevant ascii digit. This involves no multiply or divide (and is what I used to do in 6502 ASM).

Personally, I think you would be better advised to learn C or some other HLL. Writing whole apps in ASM is not worth the effort.

David.
 
 View user's profile Send private message Send e-mail  
Reply with quote Back to top
Kidwidget
PostPosted: May 03, 2012 - 06:29 PM
Rookie


Joined: May 18, 2004
Posts: 26
Location: Stockbridge, Georgia

Try this...
Code:


; .device ATmega88
_16bitToAscii:

.def tenThousPlace = r19
.def thousPlace = r20
.def hundPlace = r21
.def tensPlace = r22
.def onesPlace = r23
.def numL = r24
.def numH = r25
; save registers

_10000:
   cpi numH, high(10000)
   brsh sub10000
   cpi numL, low(10000)
   brlo _1000
sub10000:
   subi numH, high(10000)
   sbci numL, low(10000)
   inc tenThousPlace
   rjmp _10000

_1000:
   cpi numH, high(1000)
   brsh sub1000
   cpi numL, low(1000)
   brlo _100
sub1000:
   subi numH, high(1000)
   sbci numL, low(1000)
   inc thousPlace
   rjmp _1000

_100:
   cpi numL, 100
   brlo _10
   subi numL, 100   
   inc hundPlace
   rjmp _100

_10:
   cpi numL, 10
   brlo _1
   subi numL, 10
   inc tensPlace
   rjmp _10

_1:
   add onesPlace, numL

SolveOnes:
   cpi onesPlace, 10
   brlo SolveTens
   subi onesPlace, 10
   inc tensPlace
   rjmp SolveOnes

SolveTens:
   cpi tensPlace, 10
   brlo SolveHundreds
   subi tensPlace, 10
   inc hundPlace
   rjmp SolveTens   
   
SolveHundreds:   
   cpi hundPlace, 10
   brlo SolveThousands
   subi hundPlace, 10
   inc thousPlace
   rjmp SolveHundreds

SolveThousands:
   cpi thousPlace, 10
   brlo Add48
   subi thousPlace, 10
   inc tenThousPlace
   rjmp SolveThousands

Add48:
   ori onesPlace, 0b00110000
   ori tensPlace, 0b00110000
   ori hundPlace, 0b00110000
   ori thousPlace, 0b00110000
   ori tenThousPlace, 0b00110000


Or this, it's slightly different but has lots of comments...

Code:

;Micro:   atmega16
;This program gets a value from the adc, converts it to ascii,
;then spits it out the usart
;make sure the internal 1 Mhz oscilator fuse bit isn't set or this
;program won't work, it needs to be run off the stk500's 3.69Mhz clock

;books for avr noobies and greenies
;Programing and Customizing the Avr
;Smiley's book C Programming for Microcontrollers www.smileymicros.com
;AVR An Introductory Course by John Morton
;Print out the datasheets and any appnote that applies to what you want
;to do

;define registers
.def c10000   =   r16
.def c1000   =   r17
.def c100   =   r18
.def c10   =   r19
.def c1      =   r20
.def temp   =   r21
.def upper   =   r22
.def lower   =   r23
.def treg   =   r24


;the line below tells the assembler to include the file m16def.inc
;this included a bunch of definitions so we can type stuff like
;"spl" (stack pointer low) instead of a bunch of numbers
.include "m16def.inc"

;the next two lines tell the assembler the code segment is begining
;and we're starting at address 0x00 (the first one available)
.cseg
.org 0x00

;this is your interupt vector table, if you have interupts enabled
;and one is triggered it tells the microcontroller what to do next
jmp reset         ;reset
jmp reset         ;exturnal interrupt request0
jmp reset         ;exturnal interrupt request1
jmp reset         ;timer counter2 compare match
jmp reset         ;timer counter2 overflow
jmp reset         ;timer counter1 capture event
jmp reset         ;timer counter1 compare matchA
jmp reset         ;timer counter1 compare matchB
jmp timer1_over      ;timer counter1 overflow
jmp timer1_over      ;timer counter0 overflow
jmp reset         ;spi complete
jmp reset         ;usart rx complete
jmp reset         ;usart data register empty
jmp reset         ;usart tx complete   
jmp adc_complete   ;adc converstion complete
jmp reset         ;eeprom ready
jmp reset         ;analog comparator
jmp reset         ;two wire serial interface
jmp reset         ;external interupt request2
jmp reset         ;timer counter0 compare match
jmp reset         ;store program memory ready

;these are the things we want to do after a reset
reset:
;STACK, PORTB, PORTA, TIMER1, ADC, GLOBAL INTERRUPTS

;some memory locations can not be accessed dirrectly, you have to
;use one of the general purpose registers to get to them. The ldi (load
;immediate) instructions is used for this.    
;STACK
   ldi r16, low(ramend)   ;initialize stack, sets lower and upper
   out spl, r16         ;boundries
   ldi r16, high(ramend)   
   out sph, r16         

;set port b up for out put by loading all 8 bits of ddrd (data
;direction register b) with "1"
;PORTB
   ldi r16, 0xff            ;portb all output
   out ddrb, r16            ;0xff = 0b11111111 = 255
   
;to make a pin an input make the the bit/bits 0
;PORTA
   ldi r16, 0x00
   out ddra, r16            ;porta all input

;the goal here is to get the timer to overflow about once a second, the
;values below will make the timer overflow every 1.14 seconds if the
;clock speed is 3.69MHz, like it is on the stk500
;if you don't understand the "(1<<cs11)|(1<<cs10)" read the datasheets
;for the meaning of cs11 and cs10, there in the timer/counter1 register
;discription. As for the bitwise OR (|) google "bitwise or"
 
;TIMER1
   ldi r16, (1<<cs11)|(1<<cs10)   ;prescale = 64
   out tccr1b, r16               ;sets the cs11 and cs10 bits
   
   ldi r16, (1<<toie1)            ;overflow interrupt enabled
   out timsk, r16

;ADC
   ldi r16, 0b10001101         ;adc enable, 
   out adcsr, r16            ;adc interrupt prescale = 32
   

;GLABAL INTERRUPTS
   sei                     ;enable global interupts

;USART
   ldi r16, 0x02            ;portd pin1 output
   out ddrd, r16
   
   ldi r16, (1<<txen)         ;tx enabled
   out ucsrb, r16
   
   ldi r16, 23               ;baudrate = 9600
   out ubrrl, r16            ;the value 23 will only give you    
                        ;a baudrate of 9600 if your
                        ;clock speed is 3.69MHz
                        ;there is a table and formula
                        ;to find this vaule in the datasheets
main:
   rjmp main               ;this loops is only interrupted by an
                        ;interrupt from the adc finishing a
                        ;conversion or timercounter1 overflow
                        ;(see the interupt vector table above)

;This is what we want to do when timercounte1 overflows
timer1_over:
   sbi adcsr, adsc            ;tell the adc to start a conversion
   reti

;This is what we want to do when the adc completes a conversion
adc_complete:
   in lower, adcl            ;get low byte
   in upper, adch            ;get high byte
      


;Here is the part that converts the 16bit value stored in "lower" and
;"upper" to ascii and send it out the usart

;I'm not sure how other people do it but here's I did it. I use a
;register for each place holder (one's, ten's, hundred's, etc). For the
;upperbyte I look at each bit starting with the least significant bit.
;bits if the first bit (bit zero) is set then I add 2 to the hundred's
;place, 5 to the ten's place, and 6 to the one's place (256). If the second
;bit (bit 1) is set I add a 5 to the hundreds place, 1 to the tens place,
;and 2 to the one's place (512) keep inspecting the bits until they're
;all inspected and you've added the apropriate values:
;bit 0 256
;bit 1 512
;bit 3 1024
;bit 4 2048
;bit 5 4096
;bit 6 8192
;bit 7 16384
;

SixteenBitToAsciiOutUSART:

   ldi c10000,   0                  ;set all place holders to 0
   ldi   c1000,    0
   ldi c100,   0
   ldi c10,   0   
   ldi c1,      0
   

UpperByte:            
   sbrc upper, 0                  ;check each bit, if bit is set add the
   rcall two56                     ;appropriate numbers to C1-C10000
   sbrc upper,   1                  ;sbrc = skip if bit in register is cleard
   rcall five12
   sbrc upper,   2
   rcall one024
   sbrc upper,   3
   rcall two048
   sbrc upper,   4
   rcall four096
   sbrc upper,   5
   rcall eight192
   sbrc upper,   6
   rcall one6384
   sbrc upper,   7
   rcall three2768
   rjmp LowerByte

two56:
   ldi temp, 6
   add c1,   temp
   ldi temp, 5
   add c10, temp
   ldi temp, 2
   add c100, temp
   ret

five12:
   ldi temp, 2
   add c1,   temp
   ldi temp, 1
   add c10, temp
   ldi temp, 5
   add c100, temp
   ret   

one024:
   ldi temp, 4
   add   c1, temp
   ldi temp, 2
   add c10, temp
   ldi temp, 1
   add c1000, temp
   ret

two048:
   ldi temp, 8
   add c1, temp
   ldi temp, 4
   add c10, temp
   ldi temp, 2
   add c1000, temp
   ret
                  
four096:
   ldi temp, 6
   add c1, temp
   ldi temp, 9
   add c10, temp
   ldi temp, 4
   add c1000, temp
   ret

eight192:
   ldi temp, 2
   add c1, temp
   ldi temp, 9
   add c10, temp
   ldi temp, 1
   add c100, temp
   ldi temp, 8
   add c1000, temp
   ret

one6384:
   ldi temp, 4
   add c1, temp
   ldi temp, 8
   add c10, temp
   ldi temp, 3
   add c100, temp
   ldi temp, 6
   add c1000, temp
   ldi temp, 1
   add c10000, temp
   ret

three2768:
   ldi temp, 8
   add c1, temp
   ldi temp, 6
   add c10, temp
   ldi temp, 7
   add c100, temp
   ldi temp, 2
   add c1000, temp
   ldi temp, 3
   add c10000, temp
   ret


;Here's where we work on the lower byte. The above method works on the
;lower byte but this part of the code was the first thing I programmed
;for the avr that did not involve making an led blink and I wanted
;to try out different branching instructions
;
;start with the hundreds place
;compare the byte by 100, if lower work on the ten's place, if not
;subtract 100 and repeat.
;
;repeat but use 10 and 1
;
;
;
;

LowerByte:
LowerHundredsPlace:   
   cpi lower, 100
   brlo LowerTensPlace
   subi lower, 100   
   inc c100
   rjmp LowerHundredsPlace

LowerTensPlace:
   cpi lower, 10
   brlo LowerOnesPlace
   subi lower, 10
   inc c10
   rjmp LowerTensPlace

LowerOnesPlace:
   add c1, lower


;Now the all the place holders have vaules in them but some of them 
;will have values above 10, so we have to get the place holder value
;below 10, we use a process simular to the one to convert the lowerbyte
;
;example
;123, 1 in the hundred's place, 2 in the ten's place, 3 in the one's place
;before we go to the next part of the program it may look like this:
;1 in the hundred's place, 1 in the ten's place, 13 in the one's place
SolveOnes:
   cpi c1, 10
   brlo SolveTens
   subi c1, 10
   inc c10
   rjmp SolveOnes

SolveTens:
   cpi c10, 10
   brlo SolveHundreds
   subi c10, 10
   inc c100
   rjmp SolveTens   
   
SolveHundreds:   
   cpi c100, 10
   brlo SolveThousands
   subi c100, 10
   inc c1000
   rjmp SolveHundreds

SolveThousands:
   cpi c1000, 10
   brlo Add48
   subi c1000, 10
   inc c10000
   rjmp SolveThousands

;convert the value to ascii by adding 48 or ORI 0b00110000
;I picked the ori part up at an avr tutorial website, google
;"avr tutorial" it will be one of the first few sites, it's in
;German and English
Add48:
   ori c1, 0b00110000
   ori c10, 0b00110000
   ori c100, 0b00110000
   ori c1000, 0b00110000
   ori c10000, 0b00110000

;this part sends the values out the usart and adds a ";" as a delimiter
Tx10000:
   sbis UCSRA, UDRE
   rjmp Tx10000
   out UDR, c10000
Tx1000:
   sbis UCSRA, UDRE
   rjmp Tx1000   
   out UDR, c1000
Tx100:
   sbis UCSRA, UDRE
   rjmp Tx100   
   out UDR, c100
   
Tx10:
   sbis UCSRA, UDRE
   rjmp Tx10
   out UDR, c10
   
Tx1:
   sbis UCSRA, UDRE
   rjmp TX1
   out UDR, c1

Tx59:
   sbis ucsra, udre   ;delimiter
   rjmp Tx59
   ldi temp, 59
   out udr, temp
   reti
[/code]
 
 View user's profile Send private message  
Reply with quote Back to top
MBedder
PostPosted: May 03, 2012 - 06:37 PM
Raving lunatic


Joined: Nov 02, 2009
Posts: 3239
Location: Zelenograd, Russia

Buttercup1101 wrote:

The way I was trying to achieve this was by loading each nibble (after conversion) in its own byte like this:
High_Byte_High_Nibble = 5
High_Byte_Low_Nibble = 0
Low_Byte_High_Nibble = 0
Low_Byte_Low_Nibble = 0
This will make it easy to print on to the LCD.
Wrong idea. You have to convert the integer to ASCIIZ string instead and then just send this string to LCD.
 
 View user's profile Send private message  
Reply with quote Back to top
kk6gm
PostPosted: May 03, 2012 - 06:51 PM
Raving lunatic


Joined: Sep 12, 2009
Posts: 2471
Location: Sacramento, CA

david.prentice wrote:
A useful algorithm is to test and subtract 10000, 1000, 100, ... and output the relevant ascii digit. This involves no multiply or divide (and is what I used to do in 6502 ASM).

I agree this a good approach for devices without hardware mul and div (and AVR has no hardware div)
 
 View user's profile Send private message  
Reply with quote Back to top
MBedder
PostPosted: May 03, 2012 - 07:13 PM
Raving lunatic


Joined: Nov 02, 2009
Posts: 3239
Location: Zelenograd, Russia

david.prentice wrote:
Nothing has changed. The algorithms are the same. The ASM mnemonics vary slightly.
Oh really? How about this then?
Code:

;---------------------------------------------------------------------------------------------------
; Converts an unsigned integer N in W0 to a 5-byte ASCIIZ numerical string [W4]
; using a reciprocal multiplication and fractional conversion techniques
;
; 25 clocks including RETURN (0.625 uS @40 MIPS dsPIC), 16 program words
; Registers trashed: NONE
;
; (C) 2006 MBedder
;
uitoa:
magic10 = 429497                        ; =~ 2^32/10000
        push.s                          ; Save W0..W3, SRL to shadow "stack"

        mov     #magic10 >> 16,w1
        mul.uu  w1,w0,w2                ; W3:W2 = partial product MSWs
        mov     #magic10 & 0xFFFF,w1         
        mul.uu  w1,w0,w0                ; W1:W0 = partial product LSWs
                                             
        add     w1,w2,w0                ; W0 = fract(N%10000)
        mov     #'0',w2                 ; W2 = ASCII bit mask
        addc.b  w3,w2,[w4++]            ; W3 = N/10000, store an ASCII MS character

        inc     w0,w0                   ; Correct a remainder to use 16-bit ops

        do      #3,1f                   ; Repeat the block below 4 times:
       
        mul.uu  w0,#10,w0       ; W1 = next ASCII digit (0..9 range), w0 = fractional remainder
1:      ior.b   w1,w2,[w4++]    ; Store a next ASCII character

        clr.b   [w4]                    ; Place a zero string terminator

        sub     w4,#5,w4                ; Restore W4
        pop.s                           ; Restore W0..W3, SRL

        return
;---------------------------------------------------------------------------------------------------


The (X)Mega equivalent would be ~5..8 times slower and ~3..4 times longer due to its poor architecture, lousy instruction set and primitive addressing modes, but anyway this algorithm is at least twice as fast comparing to the degrees of ten subtraction, or at least 5x faster than a popular Gorner algorithm (shift-correction).


Last edited by MBedder on May 03, 2012 - 07:22 PM; edited 1 time in total
 
 View user's profile Send private message  
Reply with quote Back to top
david.prentice
PostPosted: May 03, 2012 - 07:20 PM
10k+ Postman


Joined: Feb 12, 2005
Posts: 16544
Location: Wormshill, England

The basic algorithm is:
Code:

uint16_t decades[] = { 1, 10, 100, 1000, 10000 };

void printnum(uint16_t value, uint8_t width)
{
    uint8_t c;
    while (width--) {
        decade = decades[width];
        c = '0';
        while (value >= decade) {
            value -= decade;
            c++;
        }
        output(c);
    }
}

You should be able to code that in ASM very easily.
The output() subroutine either prints or appends a character to a string. (you need a NUL to terminate)

You can access the decades[] array with the X, Y or Z register.
The comparison can be a 'test subtraction' and inspect borrow flag.

Note that this algorithm is no more than 20 lines of ASM. Heaven knows how anyone can bloat it to pages is a mystery!

David.
 
 View user's profile Send private message Send e-mail  
Reply with quote Back to top
MBedder
PostPosted: May 03, 2012 - 07:25 PM
Raving lunatic


Joined: Nov 02, 2009
Posts: 3239
Location: Zelenograd, Russia

~15 lines, but slow enough (~300 clocks):
Code:

;-------------------------------------------------------------
; Converts unsigned integer value of r17:r16 to ASCII string x[5]
itoa_short:
   ldi   zl,low(dectab*2)
   ldi   zh,high(dectab*2)

itoa_lext:
   ldi   r18,'0'-1

   lpm   r2,z+
   lpm   r3,z+

itoa_lint:
   inc   r18

   sub   r16,r2
   sbc   r17,r3
   brsh   itoa_lint

   add   r16,r2
   adc   r17,r3

   st   x+,r18
   cpi   zl,low(dectab*2)+1
   brne   itoa_lext

   ret

dectab:   .dw   10000,1000,100,10,1
;-------------------------------------------------------------
 
 View user's profile Send private message  
Reply with quote Back to top
david.prentice
PostPosted: May 03, 2012 - 07:44 PM
10k+ Postman


Joined: Feb 12, 2005
Posts: 16544
Location: Wormshill, England

@MBedder,

I like your MIPS algorithm.

The AVR algorithm may not be as fast, but it is certainly neat and tidy.
Almost as elegant as a 6502 or 68000 solution!

Which just goes to show that AVR assembly is not too unpleasant.

David.
 
 View user's profile Send private message Send e-mail  
Reply with quote Back to top
MBedder
PostPosted: May 03, 2012 - 08:20 PM
Raving lunatic


Joined: Nov 02, 2009
Posts: 3239
Location: Zelenograd, Russia

This is dsPIC, not MIPS. I just mentioned MIPS there as Million Instructions Per Second Laughing

_________________
Warning: Grumpy Old Chuff. Reading this post may severely damage your mental health.
 
 View user's profile Send private message  
Reply with quote Back to top
bobgardner
PostPosted: May 03, 2012 - 10:57 PM
10k+ Postman


Joined: Sep 04, 2002
Posts: 21390
Location: Orlando Florida

Dave was just being diplomatic and didn't want to mention The Other Microcontroller company's cpu.

_________________
Imagecraft compiler user
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
Buttercup1101
PostPosted: May 04, 2012 - 11:42 AM
Newbie


Joined: Apr 20, 2012
Posts: 7


WoW! Thanx for all the replies... Really helped me to get my code right!!

Much appreciated!!
 
 View user's profile Send private message  
Reply with quote Back to top
bobgardner
PostPosted: May 04, 2012 - 01:02 PM
10k+ Postman


Joined: Sep 04, 2002
Posts: 21390
Location: Orlando Florida

I'd like to see the Russkie's reciprocal algorithm in c is you please.

_________________
Imagecraft compiler user
 
 View user's profile Send private message Send e-mail Visit poster's website 
Reply with quote Back to top
MBedder
PostPosted: May 04, 2012 - 03:48 PM
Raving lunatic


Joined: Nov 02, 2009
Posts: 3239
Location: Zelenograd, Russia

It's almost impossible due to many register oriented tricks and handling the partial multiplication results. You can try reconstructing the algorithm in C using unions and explicit register variables, but I doubt this would be very efficient.

_________________
Warning: Grumpy Old Chuff. Reading this post may severely damage your mental health.
 
 View user's profile Send private message  
Reply with quote Back to top
sparrow2
PostPosted: May 05, 2012 - 12:08 AM
Raving lunatic


Joined: Oct 07, 2002
Posts: 2057
Location: Denmark

If you need speed I have posted a fast (but ugly) ASM program that allways solve it in less than 70 clk.
and it does it with div by mul with 1/x , in different ways
find first digit
div with 100 (result = 2 digit reminder the other two).
div result with 10 (result = 2. digit reminder 3.)
div reminder with 10 (result=4 and reminder 5.)
 
 View user's profile Send private message  
Reply with quote Back to top
theusch
PostPosted: May 06, 2012 - 04:49 PM
10k+ Postman


Joined: Feb 19, 2001
Posts: 26102
Location: Wisconsin USA

Quote:

Nothing has changed.

Quote:

If you need speed I have posted a fast (but ugly) ASM program that allways solve it in less than 70 clk.

I thought that we explored every possible Twisted Evil AVR approach years ago.
http://www.avrfreaks.net/index.php?name ... torder=asc

Besides "fastest" and "smallest", consider also "most useful". As I opined in the linked thread and elsewhere, right-justification and leading-zero suppression and insertion of implied decimal point is used with display work.
 
 View user's profile Send private message  
Reply with quote Back to top
Display posts from previous:     
Jump to:  
All times are GMT + 1 Hour
Post new topic   Reply to topic
View previous topic Printable version Log in to check your private messages View next topic
Powered by PNphpBB2 © 2003-2006 The PNphpBB Group
Credits