0

I have my code below and I want to ask what's the best way in solving numbers (division, multiplication, logarithm, exponents) up to 4 decimals places? I'm using PIC16F1789 as my device.

float sensorValue;
float sensorAverage;

void main(){
    //Get an average data by testing 100 times
    for(int x = 0; x < 100; x++){
        // Get the total sum of all 100 data
        sensorValue = (sensorValue + ADC_GetConversion(SENSOR));
    }

    // Get the average
    sensorAverage = sensorValue/100.0;
}
too honest for this site
  • 12,050
  • 4
  • 30
  • 52
elvinguitar
  • 149
  • 3
  • 6
  • 15
  • 5
    Compilers for small embedded systems that doesn't have floating point hardware usually have special functions to handle the floating point operations, so you can still use the normal arithmetic operators. The performance may not be great though. I suggest you search for and read about *fixed point arithmetic* which is more common on smaller systems, and especially if you only need a small fixed number of decimals. It needs more code, but is usually more efficient than the floating point arithmetic. – Some programmer dude Aug 02 '15 at 23:52
  • Thanks for your help Joachim Pileborg. I'll do some readings regarding the fixed point arithmetic. – elvinguitar Aug 03 '15 at 01:18
  • 1
    Basic of fixed point is "Q format". You can do `int sensorAverageQ14 = sensorValue*16384/100;`, which is the same thing as your floating point value but 2^14 times greater. You need to keep this Q=14value in mind every time you use it. – user3528438 Aug 03 '15 at 12:50
  • Also notice that micro controllers usually do not have hardware multipliers, nor division instructions. Multiplication and division are usually in the 50+ clock cycle range, plus function call overhead as they are both runtime library functions. If you are close to real time deadline, it's better considering a DSP now. – user3528438 Aug 03 '15 at 12:55
  • In the example above, four decimal places are unnecessary since it is the sum of one hundred integers the precision is only 2 decimal places. – Clifford Aug 03 '15 at 16:51
  • Note that there is no language "Embedded C". – too honest for this site Aug 03 '15 at 17:41
  • I rolled back your last edit, as it did change/expand the question. Please do not! Ask a new question instead, but please think of the answers you got here, if the new question is connected to this one. A good idea might be to let them settle for some time (sleep it over - well meant recommendation) and do some reseach on your own on the web first. – too honest for this site Aug 04 '15 at 00:55

3 Answers3

3

In general, on MCUs, floating point types are more costly (clocks, code) to process than integer types. While this is often true for devices which have a hardware floating point unit, it becomes a vital information on devices without, like the PIC16/18 controllers. These have to emulate all floating point operations in software. This can easily cost >100 clock cycles per addition (much more for multiplication) and bloats the code.

So, best is to avoid float (not to speak of double on such systems.

For your example, the ADC returns an integer type anyway, so the summation can be done purely with integer types. You just have to make sure the summand does not overflow, so it has to hold ~100 * for your code.

Finally, to calculate the average, you can either divide the integer by the number of iterations (round to zero), or - better - apply a simple "round to nearest" by:

#define NUMBER_OF_ITERATIONS 100

sensorAverage = (sensorValue + NUMBER_OF_ITERATIONS / 2) / NUMBER_OF_ITERATIONS;

If you really want to speed up your code, set NUMBER_OF_ITERATIONS to a power of two (64 or 128 here), if your code can tolerate this.

Finally: To get not only the integer part of the division, you can treat the sum (sensoreValue) as a fractional value. For the given 100 iterations, you can treat it as decimal fraction: when converting to a string, just print a decimal point left of the lower 2 digits. As you divide by 100, there will be no more than two significal digits of decimal fraction. If you really need 4 digits, e.g. for other operations, you can multiply the sum by 100 (actually, it is 10000, but you already have multipiled it by 100 by the loop).

This is called decimal fixed point. Faster for processing (replaces multiplication by shifts) would be to use binary fixed point, as I stated above.

On PIC16, I would strongly suggest to think of using binary fraction as multiplication and division are very costly on this platform. In general, they are not well suited for signal processing. If you need to sustain some performance, an ARM Cortex-M0 or M4 would be the far better choice - at similar prices.

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
  • The "finally" comment should be the preferred solution - division is costly on a PIC16 which lacks a hardware divide operation. Simply regard the units of `sensorAverage` as hundredths; probably best indicated in the name however `sensorAveragex100`. That said a multiplier of 100 will give two fractional digits, not three. – Clifford Aug 03 '15 at 16:48
  • @Clifford: You are right about the two digits, of course - edited. By "finally" I mean that would be the final processing step not like "oh, you might think of ...". I hope the first paragraph of my answer makes that sufficiently clear. – too honest for this site Aug 03 '15 at 17:03
  • With one hundred iterations, multiplying by 100 after the summation will simply add two zero digits and not increase precision at all. – Clifford Aug 03 '15 at 18:13
  • @Clifford: Please read the text carefully. I already stated there will be no more siginificant digits. But as OP asked for more, I added this as an option. Therefore the multiplication after the iterations to get a common position of the decimal point. It is not uncommon to use more fraction digits, if the integer types used allows for. – too honest for this site Aug 03 '15 at 20:02
  • @elvinguitar: You should **not** expand your question! This is no discussion forum, but a Q&A site - reason why there are no threads. Oh, and if an answer helped, you might accept and/or upvote. – too honest for this site Aug 04 '15 at 00:51
3

In your example it is trivial to avoid non-integer representations altogether, however to answer your question more generally an ISO compliant compiler will support floating point arithmetic and the library, but for performance and code size reasons you may want to avoid that.

Fixed-point arithmetic is what you probably need. For simple calculations an ad-hoc approach to fixed point can be used whereby for example you treat the units of sensorAverage in your example as hundredths (1/100), and avoid the expensive division altogether. However if you want to perform full maths library operations, then a better approach is to use a fixed-point library. One such library is presented in Optimizing Applications with Fixed-Point Arithmetic by Anthony Williams. The code is C++ and PIC16 may lack a decent C++ compiler, but the methods can be ported somewhat less elegantly to C. It also uses a huge 64bit fixed-point 36Q28 format, which would be expensive and slow on PIC16; you might want to adapt it to use 16Q16 perhaps.

Clifford
  • 88,407
  • 13
  • 85
  • 165
0

If you are really concerned about performance, stick to integer arithmetics, try to make the number of samples to average a power of two so the division can be made by means of bit shifts, however if it is not a power of two lets say 100 (as Olaf point out for fixed point) you can also use bit shifts and additions: How can I multiply and divide using only bit shifting and adding?

If you are not concerned about performace and still want to work with floats (you already got warned this may not be very fast in a PIC16 and may use a lot of flash), math.h has the following functions: http://en.cppreference.com/w/c/numeric/math including exponeciation: pow(base,exp) and logarithms* only base 2, base 10 and base e, for arbitrary base use the change of base logarithmic property

Community
  • 1
  • 1
LuisF
  • 33
  • 5
  • Any recent compiler will convert multiplication with certain constants to inline shift/add, if reasonable. Have a look at gcc for that, you might be surprised. (sorry, no link, I read about this some time ago, but can't find the link) – too honest for this site Aug 06 '15 at 13:05
  • OK thanks, good to know that I don't have to worry about it anymore – LuisF Aug 12 '15 at 11:19