7

Below is the main function I wrote in C (for PIC18F8722 microprocessor) attempting to drive 2 multiplexing 7 segments displays at a specific frequency set by the unsigned int function get_ADC_value(). The displays also display the current multiplexing frequency. This frequency range is set by #define to be in the range LAB_Fmin and LAB_Fmax and must scale as the get_ADC_value() increases or decreases from 0 to 255.

This code however does not work as I think there is implicit conversion from int to float at freq =.

The challenge is to fix this error with floats and to find an alternative using only integer types (int, char...).

 while (1) {

   unsigned int x, y, z;
   float freq, delay;

    x = get_ADC_value(); 
    y = x & 0b00001111;
    z = (x & 0b11110000) >> 4 ;

    freq = LAB_Fmin + (((LAB_Fmax) - (LAB_Fmin))/ 255)*x ;
    delay = 1/(freq*1000); // convert hZ to ms delay accurately

    LATF = int_to_SSD(y); 
    LATH = 0b11111110; //enable 7seg U1
    for (unsigned int i = 0; i<(delay) ; i++){ 
        Delay10TCYx(250); //1ms delay
    }

    LATF = int_to_SSD(z); 
    LATH = 0b11111101; //enable 7seg U2
    for (unsigned int j = 0; j<(delay) ; j++){
        Delay10TCYx(250); //1ms delay
    }
}
smci
  • 32,567
  • 20
  • 113
  • 146
Psi
  • 268
  • 4
  • 13
  • Seems like the delay calculation should be `delay = 1000/freq`, since 1Hz is a 1000msec delay, 2Hz is 500msec, etc. – user3386109 Oct 24 '16 at 06:53
  • 11
    @MitchWheat: Please don't beg for accepts for your answers. It is a _good_ thing to wait a while to see what other, new, answers may come up. It's only been 3 hours! I'd expect a 215k-rep user to know that. – Lightness Races in Orbit Oct 24 '16 at 09:47
  • 5
    I'm not begging an accept for my answer . Poster is free to accept any answer. I'd expect a 218K-rep user to understand that! – Mitch Wheat Oct 24 '16 at 10:02
  • 2
    Are LAB_Fmin, LAB_Fmax integer? float? short int? What are their possible values? – smci Oct 24 '16 at 10:04
  • 1
    Since when are there binary literals in C? – David G Oct 24 '16 at 12:35
  • @0x499602D2 There aren't. That's a non-standard GNU extension. Tangentially, though, C++14 adopted the same syntax (yay) – underscore_d Oct 24 '16 at 13:43
  • Also, why all the `(superfluous_parentheses)`? Did you think they were achieving some particular effect? edit: Lundin's great answer pointed this out, among other code review points. – underscore_d Oct 24 '16 at 13:45
  • 2
    Without the type and typical values of `LAB_Fmin` and `LAB_Fmax`, this is not solvable in a public setting. Voting to close until type and values are posted. – chux - Reinstate Monica Oct 24 '16 at 15:23
  • 1
    The range is 0 - 255, which is 256 steps. You should be dividing by 256, which can be easily done by (delta >> 8), or ((128+delta) >> 8) with rounding. – cwallach Oct 24 '16 at 19:14

5 Answers5

17

C is defined to divide ints using integer division, and only when there is a float does it "promote" other ints to floats first. Note that this even happens if it will be assigned to a float - if the right-hand side is all ints, then the division will all be integer, and only for the final assignment will C convert the int result to float.

So, with your line:

freq = LAB_Fmin + (((LAB_Fmax) - (LAB_Fmin))/ 255)*x ;

it all depends on what LAB_Fmax and LAB_Fmin are. It doesn't matter what freq or x are, because the "damage" will already have been done due to the parentheses forcing the division to be first.

If those LAB_F variables are ints, the easiest way to use floating point division is to simply tell C that you want that by making the constant 255 a floating point number rather than an integer, by using a decimal point: 255. (or 255.0 to be less subtle).

If you want to use integer arithmetic only, then the usual suggestion is to do all of your multiplications before any divisions. Of course, this runs the risk of overflowing the intermediate result - to help that, you can use the type long. Define your LAB_F or x variables as long, and do the division last:

freq = LAB_Fmin + (((LAB_Fmax) - (LAB_Fmin)) * x / 255);
John Burger
  • 3,662
  • 1
  • 13
  • 23
  • Given 0<=x<=255 the range LAB_Fmax-LAB_Fmin would have to be quite large to cause an overflow in the last version. Given they're #defined it is in this case possible to rule overflow out/in at compile. – Persixty Oct 24 '16 at 06:50
  • 3
    Note that on many systems (most 32 bit systems, Win64-based systems), `long` has the same size as `int`. – Rudy Velthuis Oct 24 '16 at 15:52
10

Code review:

  • unsigned int x, y, z; Avoid using raw integer types on embedded systems. Exact-width types from stdint.h should always be used, so you know exactly what size you use. If you don't have access to stdint.h then typedef those types yourself.

  • float freq, delay; Floating point numbers should generally be avoided on most embedded systems. Particularly on 8 bit MCUs with no FPU! This will result in software-defined floating point numbers that are incredibly slow and memory-consuming. There seem to be no reason for you to use floats in this program, it would seem that you should be able to write this algorithm with uint16_t or smaller, unless you have extreme accuracy requirements.

  • x = get_ADC_value(); Since you only seem interested in 8 bits of the ADC read, why not use an 8 bit type?

  • Please note that binary number literals are not standard C.

  • ((LAB_Fmax) - (LAB_Fmin))/ 255 This looks fishy. First of all, are these integers or floats? What's their size? The answer to your question depends on that. By swapping the literal to 255.0f you can force a conversion to float. But are you sure the division should be by 255? And not 256?

  • i<(delay). You should always avoid using floating point expressions inside loop conditions, since it makes the loop needlessly slow and can potentially lead to floating point inaccuracy bugs. Also, the parenthesis fills no purpose.

Overall, your program suffers from "sloppy typing", meaning that the programmer has not given any thought about what types that are used in each expression. Note that literals have types too. Implicit conversions might cause a lot of these expressions to be calculated on too large types, which is very bad news for the PIC. I'd recommend reading up on "balancing", aka the usual arithmetic conversions.

This "sloppy typing" will cause your program to get very bloated and slow, for nothing gained. You must keep in mind that PIC is perhaps the least code-efficient MCU still manufactured. When writing C code for any 8-bit MCU, you should avoid types larger than 8 bit. In particular, you should avoid 32 bit integers and floating point numbers like the plague.

Your program re-scales all data to types that ease the thinking for the programmer. This is a common design mistake - instead your program should use types that are easy to use for the processor. For example, instead of milliseconds, you could use timer ticks as the unit.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • The division by 255 probably is indeed correct, assuming that `x` is an unsigned 8-bit value, and that the intent of the line is to map `x = 0` to `freq = LAB_Fmin` and `x = 255` to `freq = LAB_Fmax`. But the way the conversion is implemented, it does not work (and cannot easily be made to work) using only integer arithmetic. A better way, using only integer math, would be something like `freq = LAB_Fmin + (x * scale) >> 8`, where `const int scale = ((LAB_Fmax - LAB_Fmin) * 256) / 255`. – Ilmari Karonen Oct 24 '16 at 10:59
  • Anyway, given that inverting `freq` to compute `delay` on the following line needs floats (or at least integer division with a wide enough integer type) anyway, probably the most efficient implementation for an 8-bit MCU would be to precompute a hardcoded 256-element lookup table mapping `x` directly to `delay`. Or, if space is at a premium, use fewer entries and linearly interpolate between them. – Ilmari Karonen Oct 24 '16 at 11:03
  • 1
    @IlmariKaronen Depending on precision requirements, integer arithmetic may or may not work. Since we don't know the requirements nor the algorithm, we can't really comment on that. Btw one shouldn't manually attempt to replace `/ 256` with `>> 8`, leave that to the compiler. – Lundin Oct 24 '16 at 11:04
6

You are correct about integer division. Change to

freq = LAB_Fmin + (((LAB_Fmax) - (LAB_Fmin)) / 255.0)*x;
                                                  ^^ 
Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
4
 freq = LAB_Fmin + (((LAB_Fmax) - (LAB_Fmin))/ 255)*x ;

This is indeed an implicit conversion to integer, and you're doing integer division to do that.

That is because 255 is an Integer literal.

Change it to 255.0 to be a double literal, which should play nicely with your calculation.

If you want to be more precise, you can even use a float literal, like 255.0f or an explicit cast like (float)255.

Your code could look like this then:

freq = LAB_Fmin + (((LAB_Fmax) - (LAB_Fmin))/ 255.0)*x ;

Or this:

freq = LAB_Fmin + (((LAB_Fmax) - (LAB_Fmin))/ (float)255)*x ;
Magisch
  • 7,312
  • 9
  • 36
  • 52
3

Math operations with integers will by default result into an integer too, so you need to either express one of the literals as double/float

freq = LAB_Fmin + (((LAB_Fmax) - (LAB_Fmin))/ 255.0)*x ;

or cast (float)

as many other state the first option is the most commonly implemented.

ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97