Multiplying by 2.5 is not an uncommon assembly-language operation, for example when stretching a decimal percentage (0% to 100%) input from the user into a 7.92-bit (since counts 251 to 255 get omitted) hexadecimal value. Dedicating a routine to a fixed scaling by a particular constant is often a worthwhile trade of size for speed, avoiding multiply instructions and even branching thanks to fully unrolled shift/add loops.

Suppose we have a routine that accepts an integer in the range 0 to 100 representing the stimulus to be applied to an LED. We would ideally like to map this input linearly onto the range 0x00 to 0xff, but are perfectly willing to accept a mapping onto the range 0x00 to 0xfa because it can be done with zero differential nonlinearity inside 9 bits. This will only sacrifice (255-250)/255=2% of the available intensity, so a current DAC capable of 100mA will only output 98mA at the maximum setting. Frequently, having a perfectly linear response is more important than squeezing every last potential drop of potential from the analog peripheral.

Fundamentally, multiplying by 2.5 is just a multiplication by 4 (in sequential single-bit left shifts or in a single step using a barrel shifter) followed by an addition of the original value and then a division by two.
As stated, a 9th bit indicating whole numbers or halves (bit -1, i.e. below the LSB of the result register) even comes for free and can be returned in the carry bit simply by propagating the LSB of the dividend:

 ;;; optimized routine to perform a conversion from PWM percentage to fullscale
 ;;; unsigned 8-bit value (actually only 0xfa, not 0xff) by multiplying by 5/2
 ;;; instead of 255/100 (so fullscale becomes 102%, i.e. 2% gain error)
 ;;;
 ;;; the 9th (0.5LSB) bit of the output is always equivalent to the LSB of the
 ;;; input, so it is quite trivial to obtain zero DNL at 9-bit resolution, e.g.:
 ;;;    rrf    file,w    ;
 ;;;    clrf   PWM1DCL   ;
 ;;;    rrf    PWM1DCL,f ; PWM1DCL = (file & 1) ? 0x80 : 0x00;
 ;;;    movf   file,w    ; // by looking at LSB of file, no precision lost
 ;;;    x5div2 file
 ;;;    movwf  PWM1DCH   ; PWM1DCH = (file/2) * 5; // despite dropping LSB
 ;;;
 ;;; completion is a constant 12 instruction cycles: 3us assuming Fosc is 16MHz
 x5div2 macro reg
        movlw 0x7e     ; // 0 <= reg <= 100
        andwf reg,w    ; w = reg & 0x7e; // 0 <= w <= reg (even, round down)
        bcf   STATUS,C ;
        rlf   reg,f    ;
        rlf   reg,f    ; uint16_t c = reg *= 4; // 0 <= reg <= 400
        btfsc STATUS,C ; if (c > 0xff)
        iorlw 0x01     ;  w |= 1;
        addwf reg,f    ; c = reg += w;
        btfsc STATUS,C ; if (c > 0xff)
        iorlw 0x01     ;  w |= 1;
        rrf   WREG     ; // 0 <= (w&1)*256 + reg <= 500
        rrf   reg,f    ; reg = ((w&1)*256 + reg)/2; // 0 <= reg <= 250
        endm

What we have really accomplished by returning 2.5 times the 8-bit input in one 8-bit value plus one 2^(-1) bit is to return 5 times the input inside a 9-bit word. So a full-scale value of 100 becomes 500, just represented as 250 in the top 8 bits and 0 in the leftover bit (because the input was even)!

Advertisements