Frequently it's necessary to take a value that has a range from zero to some power of two, and then scale it into a range of zero to some multiple of ten for display. For example, I have a 10 bit analog input 0..1023 that I'd like to display on a 3 1/2 digit display (0..1999). The following simple PIC assembly code does this, and can be easily adapted for inputs that have a different power of two range, and outputs that are different multiples of ten. This routine multiplies the input value by 1.953125 (2000/1024)
Note that the main routine Scale calls two subroutines ShiftX and AddXY. I've included two versions of the subroutines, one for the regular midline PICs and one for the Enhanced Midline PICs.
For anyone wondering why I'm scaling by 2000/1024 rather than 1999/1023, please refer to this discussion:
For other input ranges, simply left or right shift the input value the appropriate number of positions. For example if the input range is 0..4096 call ShiftX twice at the very beginning of the Scale routine.
For other output ranges: 0..1000, 0..500, 0..250 etc., can be had by right shifting the result.
Edit:
I should also mention that the calculation algorithm is an exact multiplication by 1.953125. If more precision is needed for wider ranges, more bytes can be used with no other changes. As presented, the calculation uses three bytes: two for the integer part and one for the fractional part which may occur during intermediate calculations. So, there's little to no loss of accuracy due to truncation of calculation results.
Code:
;Registers used
;X_H, X_L, X_F - high byte, low byte and fraction byte of input value
;Y_H, Y_L, Y_F - high byte, low byte and fraction byte of output result
;count - temporary counter register
;
Scale
;Scale 0..1024 input to 0..2000 display count 2000/1024=1.953125
;X_H, X_L, X_F should be set to input value before calling this routine
clrf Y_F
clrf Y_L
clrf Y_H
movlw 5
movwf count
;Shift and add loop
sLoop
call AddXY ;Add x to y.
call ShiftX
decfsz count,f
goto sLoop
call ShiftX
;Add final adjustment
call AddXY
;Result is in Y_H,Y_L with fractional part in Y_F
return
;
;The following subroutines use midrange instructions
ShiftX
bcf status,c
rrf X_H,f
rrf X_L,f
rrf X_F,f
return
AddXY
;Add x reg to y reg - 3 bytes
incf Y_H,f
movf X_F,w
addwf Y_F,f
btfsc status,c
incfsz Y_L,f
decf Y_H,f
movf X_L,w
addwf Y_L,f
btfsc status,c
incf Y_H,f
movf X_H,w
addwf Y_H,f
return
;
;The following subroutines use enhanced midrange instructions
ShiftX
lsrf X_H,f
rrf X_L,f
rrf X_F,f
return
AddXY
;Add x reg to y reg - 3 bytes
movf X_F,w
addwf Y_F,f
movf X_L,w
addwfc Y_L,f
movf X_H,w
addwfc Y_H,f
return
Note that the main routine Scale calls two subroutines ShiftX and AddXY. I've included two versions of the subroutines, one for the regular midline PICs and one for the Enhanced Midline PICs.
For anyone wondering why I'm scaling by 2000/1024 rather than 1999/1023, please refer to this discussion:
Why ADC/1024 is correct, and ADC/1023 is just plain wrong!
It's common to need to scale a microcontroller ADC result to give a value in some other scale. One example is to scale the ADC to read in actual volts like read from 0.00v -> 5.00v. People sometimes use ADC *500 /1023 as this gives a reading of 500 (5.00v) for the top ADC value of 1023...
www.electro-tech-online.com
For other input ranges, simply left or right shift the input value the appropriate number of positions. For example if the input range is 0..4096 call ShiftX twice at the very beginning of the Scale routine.
For other output ranges: 0..1000, 0..500, 0..250 etc., can be had by right shifting the result.
Edit:
I should also mention that the calculation algorithm is an exact multiplication by 1.953125. If more precision is needed for wider ranges, more bytes can be used with no other changes. As presented, the calculation uses three bytes: two for the integer part and one for the fractional part which may occur during intermediate calculations. So, there's little to no loss of accuracy due to truncation of calculation results.
Last edited: