CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Scaled integer maths

 
This forum is locked: you cannot post, reply to, or edit topics.   This topic is locked: you cannot edit posts or make replies.    CCS Forum Index -> Best Of
View previous topic :: View next topic  
Author Message
Ttelmah



Joined: 11 Mar 2010
Posts: 19495

View user's profile Send private message

Scaled integer maths
PostPosted: Tue Apr 21, 2020 1:41 am     Reply with quote

OK.
In the forum you will quite often have the 'old hands' saying 'use integer
maths'. What is this about?. Why?.

So lets start with a very basic design. A PIC 18F4520, used as a 0 to 5v
voltmeter. Sending the value to the serial port.

Two solutions. The first the 'obvious' one. Just use fp maths:
Code:

#include <18F4520.h>
#device adc=10
#fuses NOWDT
#use delay (INTERNAL=16Mhz)

#use rs232(UART1, baud=9600, bits=8, ERRORS, STREAM=PC)
//always do the 'setup' first

void main(void)
{
    float volts;
    setup_ADC_ports(AN0, VSS_VDD);
    setup_adc(ADC_CLOCK_DIV_16);
//Read the data sheet for this. Here the Tad must be 0.7 to 25uSec
//So from 16MHz, /16 gives 1uSec. Ideal.
    set_adc_channel(0);
//Now the ADC needs time to 'acquire' the signal. This can be programmed
//as a multiplier from this clock. However With the long delays in the loop
//I'm not using this.
    fprintf(PC,"Awake\n");
    while (TRUE)
    {
        delay_ms(500);
        volts=read_adc()*5.0/1024.0;
        fprintf(PC,"Reading %5.3fv\n",volts);
    }
}

Now this merrily goes off and prints the voltage on the AN0 input
as n.nnnv every half a second. Perfect solution?.

But is it?.

The first thing to note is the size. Here 1670 bytes. Then run the code
and test how long it takes to do the maths, and print the result. 24.4mSec.

Now compare with this:
Code:

//Same setup code
void main(void)
{
   int32 volts;
   setup_ADC_ports(AN0, VSS_VDD);
   setup_adc(ADC_CLOCK_DIV_16);
//Read the data sheet for this. Here the Tad must be 0.7 to 25uSec
//So from 16MHz, /16 gives 1uSec. Ideal.
   set_adc_channel(0);
//Now the ADC needs time to 'acquire' the signal. This can be programmed
//as a multiplier from this clock. However With the long delays in the loop
//I'm not using this.
   fprintf(PC,"Awake\n");
   while (TRUE)
   {
       delay_ms(500);
       volts=(int32)read_adc()*5000/1024;
       fprintf(PC,"Reading %5.3lwv\n",volts);
       delay_cycles(1);
   } 
}


Now the first interesting thing is the code is about half the size (818 bytes).
Then it is faster. Only a little, but about 450uSec in total.

So what is going on?.

The compiler is very intelligent. On the sum:
read_adc()*5.0/1024.0;

It pre-solves the right hand half of this, and turns it into a single
multiplication by 0.0048828125. So this sum takes just 222uSec.

On the equivalent integer sum, this can't be pre-solved like this so it
stays as a multiplication then division. So actually ends up taking
slightly longer. However then there are the multiple
divisions to generate the digits in the printf. These take 412uSec
each in the floating point version, while only 298 in the integer
version. So at the end there is a total saving of nearly 0.5mSec
in speed. The total output takes just under 24mSec as given.
The saving is much more noticeable if you output the results to a
string, rather than delaying for the serial. It is the serial delays
that are giving much of the time involved here.

Now think about what it does. You have an ADC reading of 512.
For this you expect to see 2.5v. For the float version, we have:

512*5 = 2560
then
2560/1024 = 2.5

Exactly right.

Now the integer version:

512*5000 = 2560000
then
2560000/1024 = 2500

which the %w format displays as 2.500

Again exactly right.

So the key here is that if instead of using floating point values like
x.xxv, you work in smaller units and use integers, you can save a
lot of space, and significant time. Here I'm doing the sum in 'integer mV'.
Thousandths of a volt, and can use integer numbers of these.

Key thing is that to keep in 'integer' the maths used must multiply
first. If you tried instead to use /1024 earlier in the sum, data would be
lost. So a little care is needed.
There is also another factor, which is accuracy. An int32 gives over 9
usable digits. A fp value only just over 6. So values are actually more
accurate processed this way.

So, think about it, when designing your code... Very Happy

This is a link to a thread in the main forum, where using this for a
more complex sum is discussed:

<http://www.ccsinfo.com/forum/viewtopic.php?t=56091&postdays=0&postorder=asc&start=0>

This raises the next important part to this.

On an unsigned integer number, the following divisions can be done
really quickly by simply 'shifting' the value:
/2, /4, /8, /16, /32, /64, /256 and so on.
In fact /256 and /65536 can be done even faster by just taking the
required bytes out of the result.
In this thread I do the integer equivalent of *0.48828125, by just
multiplying by 32000, and then taking the upper 16bits of the result.
This is several times faster and smaller. So again. Think about it...
Display posts from previous:   
This forum is locked: you cannot post, reply to, or edit topics.   This topic is locked: you cannot edit posts or make replies.    CCS Forum Index -> Best Of All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group