Author Topic: Low on program space with pic microcontroller  (Read 20010 times)

0 Members and 1 Guest are viewing this topic.

Offline jerrykTopic starter

  • Frequent Contributor
  • **
  • Posts: 254
  • Country: us
Low on program space with pic microcontroller
« on: February 14, 2022, 05:59:31 pm »
I've just started this learning project and my program space is 87% used.  I'm using a pic16F18875 and here is the summary at build.

Memory Summary:
    Program space        used  1BD0h (  7120) of  2000h words   ( 86.9%)
    Data space             used   102h   (   258) of   400h bytes     ( 25.2%)
    EEPROM space        used     0h    (     0)   of   100h bytes     (  0.0%)
    Data stack space     used     0h    (     0)   of   2ECh bytes     (  0.0%)
    Configuration bits    used     5h    (     5)   of     5h words      (100.0%)
    ID Location space    used     0h    (     0)   of     4h bytes       (  0.0%)

The main.c program is as follows:

Code: [Select]
/**
  Included files
 */

#include "mcc_generated_files/mcc.h"
#include "lcd_jerry.h"
#include <stdio.h>
#include "app.h"


void Delay_Seconds(unsigned char z)
{
    unsigned char x,y;
    for (y = 0; y < z; y++)
    {
        for (x = 0; x < 100; x++) __delay_ms(10);
    }
       
}

//*************************Declare Global Variables******************
float voltage0;
float voltage1;
float voltage2;
float voltage3;
float voltage4;

char Buffer[20];

int ADC_Read(int channel);
float GET_Voltage(float resistorRatio, float convertedValue);
void LCD_Display(float cellVoltage, int row, int column);






/*
                         Main application
 */
void main(void)
{
    //Initialize functions
    SYSTEM_Initialize();
    Lcd4_Init();
    ADCC_Initialize();
    APP_LED_Initialize();
     
    // Enable the Global Interrupts
    INTCONbits.GIE = 1;

    // Enable the Peripheral Interrupts
    INTCONbits.PEIE = 1;
   
    //Resistor Ratios
    //Cell1
    float resistorOneCell1 = 982;
    float resistorTwoCell1 = 248.2;
    float resistorRatioCell1 = 0;
   
    //Cell2
    float resistorOneCell2 = 663;
    float resistorTwoCell2 = 248.3;
    float resistorRatioCell2 = 0;
   
    //Cell3
    float resistorOneCell3 = 364.8;
    float resistorTwoCell3 = 248.2;
    float resistorRatioCell3 = 0;
   
    //Cell4
    float resistorOneCell4 = 56.14;
    float resistorTwoCell4 = 247.8;
    float resistorRatioCell4 = 0;
 
    float cellVoltage1 = 0;
    float cellVoltage2 = 0;
    float cellVoltage3 = 0;
    float cellVoltage4 = 0;

    //Resistor Ratios
    resistorRatioCell1 = (resistorTwoCell1 / (resistorOneCell1 + resistorTwoCell1));
    resistorRatioCell2 = (resistorTwoCell2 / (resistorOneCell2 + resistorTwoCell2));
    resistorRatioCell3 = (resistorTwoCell3 / (resistorOneCell3 + resistorTwoCell3));
    resistorRatioCell4 = (resistorTwoCell4 / (resistorOneCell4 + resistorTwoCell4));
   
    // LCD Greeting
   
    Lcd4_Clear();
    Lcd4_Set_Cursor(1, 1);
    Lcd4_Write_String("Battery");
    Lcd4_Set_Cursor(2, 1);
    Lcd4_Write_String("Simulation");
    led4_Toggle();
    __delay_ms(1500);
    led4_Toggle();
    Lcd4_Clear();
       
    // Turn on the ADC module
    ADCON0bits.ADON = 1;
   
    //Main while loop
    while (1)
    {
        APP_LED_Tasks();
        //Get Voltage at balance wires
       
        voltage0 = GET_Voltage(resistorRatioCell1, ADC_Read(5));
        voltage1 = GET_Voltage(resistorRatioCell2, ADC_Read(4));
        voltage2 = GET_Voltage(resistorRatioCell3, ADC_Read(3));
        voltage3 = GET_Voltage(resistorRatioCell4, ADC_Read(2));
       
        //Converts readings at the balance wires to individual cell voltage
 
        cellVoltage1 = voltage0 - voltage1;
        cellVoltage2 = voltage1 - voltage2;
        cellVoltage3 = voltage2 - voltage3;
        cellVoltage4 = voltage3;
       
        //Display the results
       
        LCD_Display(cellVoltage1, 1, 0);
        LCD_Display(cellVoltage2, 1, 8);
        LCD_Display(cellVoltage3, 2, 0);
        LCD_Display(cellVoltage4, 2, 8);
    }
}

//Function Definitions

int ADC_Read(int channel)
{
    float convertedValue = 0;
    // select the A/D channel
    ADPCH = channel;
    __delay_ms(10);
    // Start the conversion
    ADCON0bits.ADGO = 1;
    while (ADCON0bits.ADGO);
    convertedValue = ((adc_result_t) ((ADRESH << 8) + ADRESL));
    ADCON0bits.ADGO = 0;
    return convertedValue;
}

float GET_Voltage(float resistorRatio, float convertedValue)
{
    float voltage = 0;
    voltage = ((convertedValue) * 5 / 1024) / resistorRatio;
    return voltage;

}

void LCD_Display(float cellVoltage, int row, int column)
{
    Lcd4_Set_Cursor(row, column);
    sprintf(Buffer, "%.2f", cellVoltage);
    Lcd4_Write_String(Buffer);
    Lcd4_Write_String(" V");
}

/**
 End of File
*/

It's ugly beginner code but it works so far.  I'm using MPLABX and MCC.  I am heading away from MCC because I don't like being that insulated from what's going on.  I'm trying to understand if MCC files add bulk to the program file or is it in the main.c program.  I suspect there are problems with my use (or abuse) of variables and math operators in the main program.

Please don't be gentle here.  I want to learn this stuff and either the controller I'm using does not have enough space for my end goal or I need some more schooling.

Thank in advance - Jerry



 

Offline Mechatrommer

  • Super Contributor
  • ***
  • Posts: 11727
  • Country: my
  • reassessing directives...
Re: Low on program space with pic microcontroller
« Reply #1 on: February 14, 2022, 06:12:49 pm »
float?! my goodness! how luxury people nowadays... ;) (just quick scroll on the code) otoh you are lucky you still have 13% of space left, i had to split a program into 2 because i have negative space left.
 
The following users thanked this post: jerryk

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3570
  • Country: ca
Re: Low on program space with pic microcontroller
« Reply #2 on: February 14, 2022, 06:14:52 pm »
This is because you use lots of floating point operations on an MCU which doesn't even have hardware multiplier. This produces huge overhead. That is why your code is so big.

If you wish, you can re-write with integers/fractionals and the code will be much smaller (and faster if that matters).

Or, you can always switch to PIC16F18877 which has 4 times more memory.
 
The following users thanked this post: Ian.M, jerryk

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 11331
  • Country: fi
Re: Low on program space with pic microcontroller
« Reply #3 on: February 14, 2022, 06:22:31 pm »
Yes, if you want the luxury (ease of development) of float, get a bigger device. Otherwise, go for fixed point arithmetic. It's not difficult, just a little bit of manual thinking. Chances are, you start liking the "manual ranging" of variables so much you end up using fixed point even if you have hardware floating point available.

Fixed point doesn't need to mean binary fractions, if you struggle at that idea. At simplest, you do this by choosing units like mV or even µV instead of V, so that you can just use integers.

Even a 32-bit integer on a 8-bit CPU is much more efficient than a float, and the numerical range for int32 is approx +/-2 billion.

But you need to think about the ranges, rounding is done as a "floor" function and if any intermediate result is close to zero (just a few counts), that will mean huge quantization error. Also exceeding ranges causes wrap-around.
 
The following users thanked this post: jerryk

Online mariush

  • Super Contributor
  • ***
  • Posts: 5354
  • Country: ro
  • .
Re: Low on program space with pic microcontroller
« Reply #4 on: February 14, 2022, 07:03:47 pm »
You have datasheet for that chip here: https://ww1.microchip.com/downloads/en/DeviceDoc/PIC16(L)F18855-75-Data-Sheet-40001802H.pdf

The microcontroller has a built in voltage reference, which can be set to 1.024 , 2.048v , 4.096v 
If you can afford to, set it to 4.096v and then your 10 bit ADC readings will give you 4mV per step ...

Look into staying away from floating point, the compiler has to add floating point stuff to your code because the controller doesn't have built in hardware floating point stuff.

So... don't use delay_ms  10 in your adc_read... just use a loop until it's not busy or something like that.
see 17.4 in your datasheet ... you need at least 200uS for a reading, then 200uS between consecutive readings... no need for 10ms
See 23.0 and 23.1 and 23.2 in datasheet for adc configuration  ... you could set the adgo to 1 and then do something like while .adgo==1  asm("nop")

You can be smarter and figure out how much time each instruction would use and basically do a timeout.
For example each instruction uses 1 or more cycles, where a cycle is 4 Hz ... so at 32 Mhz that would be 8,000,000 cycles in a second or 8000 cycles in a ms, or 8 cycles in a uS ... if you want to timeout in 240uS, it would be enough to spend 8 x 240 cycles doing something.
See section 36 in datasheet ... you can see there decfsz  uses 1 cycle or 2 if condition is met (it's used in while until value is 0) , goto uses 2 cycles  so you could do something like

unsigned char n = 240;
    do {
    asm("goto $+1"); asm("goto $+1"); asm("nop");
    // decrease if not zero - decfsz (1 cycle if !=0, 2 cycles otherwise)
    // goto loop start = 2 cycles

    } while(--n);

/// so that's 5 cycles for the goto commands + nop, plus 2 for the goto start , plus one for the decrement = 8 cycles and that gets repeated 240 times... you have your 240 x 8 cycles.

you could add in the do loop  something like  if (xxx.ADGO ==0) n=0; to exit loop as soon as done, or after 240 loops otherwise  .... that would be a comparison and an assignment so you can calculate the cycles (it's probably 3 or 4 cycles but you can view the assembly code and see what instructions the compiler puts there and count the cycles based on instructions chosen by compiler.


Going back to the voltage reading... like I said... keep it in mV as much as possible.

And you could probably choose those resistor values better if you can, or just not use floating point, multiply everything by 10 for example

Your values :    982, 248.2  : 0.7982    My values : 9820 , 2482  :  9820 / 12302 keep it like this, don't even bother defining a float ratio value.

So let's say the ADC gives you 852 as a measurement ... that's 3408mV if you use a voltage reference of 4.096v

You can now do 3408 x 9820 / 12302 = 2720.416 and just round it to 2720 mV and print  2.720V on screen.

If possible, you could try tweaking your ratios to be multiples of 2 and then you can keep it integer and just shift bits...
For example, you can rewrite  9820 / 12302  as 817.447 / 1024 or 81744 / 102400     because 12302 = 12.013 x 1024

So now your measurement can be 3408 x 816.447 / 1024  = 3408 x 816447 / 1024 / 1000   = (2,782,451,376 >> 10 ) / 1000  = 2,717,237 / 1000 = 2717 mV  ( 3mV error, close enough)


Same for your second cell ... 663  and 248.3  ... gives you 663 / 911.3  = rewrite it as 6640/9113  or as 745 / 1024 ( so you can shift 10 bits and be done with it)

and so on...

--


sprintf also adds up a lot of code. If you keep your voltage in mV all the way to the end, then it's just a matter of adding a dot before the last 3 digits.
« Last Edit: February 14, 2022, 07:35:55 pm by mariush »
 
The following users thanked this post: jerryk

Online hans

  • Super Contributor
  • ***
  • Posts: 1997
  • Country: 00
Re: Low on program space with pic microcontroller
« Reply #5 on: February 14, 2022, 07:25:05 pm »
I would argue against assuming 32-bit is more efficient than float, as an universal truth. It depends a lot.
(I know these MCUs don't match the PIC16, by a long shot for the 32-bit MIPS, but I just want to reference the speed of several math operations on a range of platforms)

For sure do floating point routines cost quite a lot of memory space, especially for printf, but it does not have to be a lot slower than 32-bit integers on a 8-bit MCU.
Both multiplications and divisions with floats is on the same level compared to int32 (of the PIC18 wiki link), since floats are stored in the logarithmic domain. Float add/subtract is where it primarily suffers, because it has to do conversions between linear and the logarithmic domain. However I don't see a lot of those operations in OP's program. Certainly the program calculates resistorRatioCellX, but that can be done at compile-time if the compiler is smart enough. On the PIC itself it only has to perform a couple of float multiplications/divisions to calculate the voltage.

I think there is one major advantage of using floats: you can often avoid the very costly division operation (which is expensive for both int32 and float), by taking the reciprocal of the divisor number as the constant. This doesn't incur a lot of loss of precision in float, while it can be a complete disaster for int32.

Probably the compiler is not smart enough to do this for you because it wants to store the resistorRatioCellX verbatim in memory, and then use that value for division. What you could try is help it a little bit and calculate/store the reciprocal ratio value instead, and then use:
Code: [Select]
//... start snippet of main
    resistorRatioCell1 = (resistorOneCell1 + resistorTwoCell1) / resistorTwoCell1;
// (or actually if you do the math, we now have: resistorRatioCell1 = 1+ resistorOneCell1 / resistorTwoCell1; )
//etc.
//... end snippet of main

float GET_Voltage(float resistorRatio, float convertedValue)
{
    float voltage = 0;
    voltage = convertedValue * resistorRatio * 5 / 1024;
    return voltage;

}
The compiler should be smart enough to see that *5/1024 is just another float constant, and can just multiply by 0.004883 etc. If you want to go even further, you could also put this scaling factor in your resistorRatioCellX constant, and effectively get a "ADC LSB to voltage"-conversion-factor using only 1 float multiplication. This should at least speed up the program by a bit,

but I don't think this is going to save a lot of program space.  It's very likely that printf uses float multiplications/divisions anyway to print the numerical representation of the float. Nonetheless, it can be useful to play around with how you write down your computations, especially since on the PIC XC8 compiler you can't expect it the compiler to be too smart.

I still recommended trying to worrk with fixed point 32-bit if memory space and printf is of concern :) But e.g. then you can also try to use 16-bit, if that has enough resolution.

edit: another idea/variation you could try, is to do the ADC-to-voltage conversion in floats calculating in mV, uV 0.1mV counts, or whatever resolution you need. Then go cast that float to an 16 or 32-bit integer, perform the subtraction for the cell voltages, and print those results with printf().
« Last Edit: February 14, 2022, 07:51:35 pm by hans »
 
The following users thanked this post: jerryk

Offline Ian.M

  • Super Contributor
  • ***
  • Posts: 14134
Re: Low on program space with pic microcontroller
« Reply #6 on: February 14, 2022, 07:38:33 pm »
Comment out those sprintf() calls and see how much bloat the printf() functions library is bringing in.  Its usually quite large, so rolling your own number to string routines with hard-coded formatting may be worth the pain.

I wouldn't recommend it to you *THIS* *TIME*, but its worth knowing that the XC8 compiler 'PRO' license, which enables optimizations that can significantly reduce the binary size can be purchased on what's basically a monthly rental: https://www.microchip.com/en-us/development-tool/sw006021-sub

If you ever have a project 95% or better complete, have tested all sections separately, but can't quite get it to fit or have had to disable 'nice to have' features due to lack of program memory, and you are already using the largest memory PIC in the device family and footprint, or are getting paid for the design, it may well be worth buying one month of 'PRO'!

N.B. The Microchip XC compilers licenses permit commercial use in free mode, so if you can live with the binary bloat and do without official support, you can be a money-earning cheap-skate.
« Last Edit: February 14, 2022, 11:52:38 pm by Ian.M »
 
The following users thanked this post: jerryk

Offline MarkF

  • Super Contributor
  • ***
  • Posts: 3300
  • Country: us
Re: Low on program space with pic microcontroller
« Reply #7 on: February 14, 2022, 07:49:28 pm »
I'm seeing a lot of harping on floating point math here.  And I agree it could be optimized by some smart math to avoid doing fractions (i.e. multiply and then divide instead of multiply by a float scale factor).  You could look into fixed point math functions.

However, everyone is ignoring the elephant in the room.  Graphic libraries can consume large amounts of memory.  Without being able to see what you're using, try reducing its size.  One area I seen in libraries is large storage of fonts.  Or eliminate graphic drawing primitives you don't think you will ever use as a last resort. 

Have you enabled any of the XC8 optimization?  There is some even with the 'free' license.
What version of XC8 are you using?
 
The following users thanked this post: jerryk

Online hans

  • Super Contributor
  • ***
  • Posts: 1997
  • Country: 00
Re: Low on program space with pic microcontroller
« Reply #8 on: February 14, 2022, 07:57:49 pm »
I suspect OP is using an alphanumeric LCD display considering it's printing characters at row/cols, so in that case there is not font table on the MCU.

But that is just my guess.. I don't see any X/Y pixels, but for all we know that could be hidden in the LCD library :) A quick google for the LCD function names does resolve quite a lot of code/libraries that use alphanumeric displays, though.
 
The following users thanked this post: jerryk

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Low on program space with pic microcontroller
« Reply #9 on: February 14, 2022, 09:34:19 pm »
Guarantee the sprintf is the big problem.

SoftFP routines aren't usually all that big in themselves, just for the arithmetic.
 
The following users thanked this post: Ian.M, jerryk

Offline mikerj

  • Super Contributor
  • ***
  • Posts: 3671
  • Country: gb
Re: Low on program space with pic microcontroller
« Reply #10 on: February 14, 2022, 09:51:58 pm »
I think there is one major advantage of using floats: you can often avoid the very costly division operation (which is expensive for both int32 and float), by taking the reciprocal of the divisor number as the constant. This doesn't incur a lot of loss of precision in float, while it can be a complete disaster for int32.

You can very avoid division with fixed point math as well, if you are dividing by a constant value.  The major advantage of floating point is when you really need to deal with very large and very small values, but many applications don't and beginners tend to jump to floats as the first solution to anything that has fractional values.
 
The following users thanked this post: hans, jerryk

Offline jerrykTopic starter

  • Frequent Contributor
  • **
  • Posts: 254
  • Country: us
Re: Low on program space with pic microcontroller
« Reply #11 on: February 14, 2022, 10:40:02 pm »
Just an update:  I'm moving through all the comments and redoing the code as fast as I can but it will be a moment.  Changing all floats to int dropped program space down to 71% from the original 87%.  Commenting out the sprintf() dropped it to 15% from the 71%. I'm going to try @Ian.M's roll your own character to string suggestion.  I used to use that display method but switched to sprint() after following one of Microchip's tutorials on State Machine coding.  I did not realize the memory implications. 

As for adjusting resistor sizes, I am stuck with the existing hardware design for now.  I did fine tune each divider to the actual resistor values rather that the stated values.  This was because of accuracy issues related to very high impedance inputs to the ADC.  I'm stuck with that also.  As for the display type it's a simple 16x2 LCD.

I really appreciate the amazing help.  I could have spent a while spinning my wheels on this one.

Jerry
 
The following users thanked this post: Ian.M

Offline DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6928
  • Country: es
Re: Low on program space with pic microcontroller
« Reply #12 on: February 14, 2022, 11:42:48 pm »
Are you using compiler optimization? XC8 is free up to O2 (included).
If not using floats, use iprintf, but for displaying simple integers, making your own function will produce much smaller code.
« Last Edit: February 14, 2022, 11:45:12 pm by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Low on program space with pic microcontroller
« Reply #13 on: February 14, 2022, 11:51:10 pm »
The problem with printf() is it doesn't know until runtime what kind of value you will be trying to print, so it needs to include code for ALL integer and floating point sizes, including possibly depending on which libc library you use both 32 bit and 64 bit integers, and 32 bit, 64 bit, and 128 bit floating point. Newlib is smaller than glibc, and Newlib nano (a build option for Newlib) is smaller than Newlib. There are other even more cut down libc implementations for microcontrollers.

This is one place where both the Arduino libraries and standard C++ win out, with overloaded C++ functions for Serial.print() or << respectively, that result in the linker being able to include only the code that you are actually using.
 

Offline jerrykTopic starter

  • Frequent Contributor
  • **
  • Posts: 254
  • Country: us
Re: Low on program space with pic microcontroller
« Reply #14 on: February 15, 2022, 12:07:39 am »
Are you using compiler optimization? XC8 is free up to O2 (included).
If not using floats, use iprintf, but for displaying simple integers, making your own function will produce much smaller code.

I just checked the XC8 properties and I am not using any optimization.  I did not see that part in the properties window when I set it up,  or looked at it and sat it out do to lack of knowledge when I first started with MPLABX. There are four optimization selections 1,2,3,S with the last two being supersets of O2.  I will try that when I get my code redone.

Thanks - Jerry
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 22435
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: Low on program space with pic microcontroller
« Reply #15 on: February 15, 2022, 12:21:51 am »
Float isn't necessarily a big deal, but it depends on the platform.  Definitely check the listings to see how much space is being taken up by what, how much the compiler is adding to your code versus what's stuff you're more directly responsible for.  (I mean, you're responsible for everything past startup code, ultimately; but as far as direct implementation, that is.)

I'm not familiar with the platform, so I can't comment on which of those are likely or actual problem points.  I would guess float libs aren't too terrible, for example a couple typical functions take up only a couple kwords on avr-gcc; but AVR does have hardware multiply so those can be a bit faster / more streamlined than they'll be on a more limited machine.  That shouldn't take up much more (multiplication on Z80 for example is only like a dozen instructions), and if that same operation is shared across the library calls, that's hardly a difference really -- but if it's not shared, it may bloat up much more.  Also, PIC-C / XC8 / whatever has been notorious for poor optimization options, so, maybe it's more intentional than you'd prefer... Anyway.

As for things you can control -- may or may not be better to use this, but definitely something to try.  Repeated lines like the:
Code: [Select]
voltage0 = GET_Voltage(resistorRatioCell1, ADC_Read(5));and etc.  Try putting them in a loop.  Sometimes iterating over a loop is faster than repeating all the variable/pointer loads/stores and function calls.  You could rewrite this as something like,

Code: [Select]
for (uint8_t i = 0; i < 4; i++) {
voltage[i] = GET_Voltage(resistorRatioCell[i], ADC_Read(5 - i));
}

Or I suppose possibly the whole main() loop could be written in this way, since it's fairly repetitive.

More generally this relates to the subject of vectorization, and even parallelization.  Vectors, for computing purposes, are arrays where the same operation is performed on every element of the array, or, at least some consistent behavior across elements.  Maybe some variables are off-by-one (i.e. the cellVoltages are the forward difference between [ i ] and [i-1]), or if some are in reverse order (like the ADC_Read parameter) but it's still one-to-one (or directly dependent on the index or whatever), that's still fine.  And, you can see some of these could be evaluated in parallel, assuming you had a platform that could do that.  (On PCs, such operations can be done with SIMD instructions, or even done on the GPU.  Fewer MCUs have such features though. :) )

Anyway, the aim of this is, if the function call overhead is relatively large, while the loop overhead is relatively low, this can save space.  Since doing it all unrolled, is N function calls, so, it doesn't scale well at some point.  For N=4 it's something to try.

And, above all!  Learn the instruction set, get some idea what the compiler is actually doing, see how your program is divided into functions and calls, see what code is generated by what statements -- and see what statements are prone to taking more space.

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline Mechatrommer

  • Super Contributor
  • ***
  • Posts: 11727
  • Country: my
  • reassessing directives...
Re: Low on program space with pic microcontroller
« Reply #16 on: February 15, 2022, 12:32:41 am »
However, everyone is ignoring the elephant in the room.  Graphic libraries can consume large amounts of memory.  Without being able to see what you're using, try reducing its size.  One area I seen in libraries is large storage of fonts.  Or eliminate graphic drawing primitives you don't think you will ever use as a last resort. 
usually the programmer who made the lcd library knows what he's doing. ie the best possible code for the function required. but once the "user" programmer calls the function with float as argument, then we know what we are asking. otherwise not trusting the lcd library, we can (1) not using the library at all, meaning missing lcd feature, or (2) make our own lcd library which is another elephant in the coding task. similarly to using float operations (its just a float library backstage), using it means we know what we are asking, not trusting it? we need to snap our own fixed points algorithm gymnastics. if we dont want unreadable gymnastics, we need to upgrade our mcu. ymmv.

Offline MarkF

  • Super Contributor
  • ***
  • Posts: 3300
  • Country: us
Re: Low on program space with pic microcontroller
« Reply #17 on: February 15, 2022, 01:28:16 am »
. . .
It's ugly beginner code but it works so far.  I'm using MPLABX and MCC.  I am heading away from MCC because I don't like being that insulated from what's going on.  I'm trying to understand if MCC files add bulk to the program file or is it in the main.c program.  I suspect there are problems with my use (or abuse) of variables and math operators in the main program.

I've never used the MCC to setup the configuration.
I just use the 'Configuration Bits' window of MPLAB to select my configuration and then copy&paste it into the main.c file.  Press the 'Generate Source Code to Output' button to get text to put in the main.c
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Low on program space with pic microcontroller
« Reply #18 on: February 15, 2022, 03:25:15 am »
Are you using compiler optimization? XC8 is free up to O2 (included).
If not using floats, use iprintf, but for displaying simple integers, making your own function will produce much smaller code.

I just checked the XC8 properties and I am not using any optimization.  I did not see that part in the properties window when I set it up,  or looked at it and sat it out do to lack of knowledge when I first started with MPLABX. There are four optimization selections 1,2,3,S with the last two being supersets of O2.  I will try that when I get my code redone.

I don't know what that compiler is like, but if it's anything like gcc and llvm then, basically, never eve turn optimisation off. Always use at least -O1. Otherwise the code is just huge and slow and very very hard to follow.

As it's a smallish MCU (8192 instructions, 1 KB SRAM, basically like an ATMega168, but half the EEPROM) I expect you're going to want either 1 or S, and both will make huge code savings.

I've only used the ATMega328, which has twice the program space, but I've never come close to using all the program space and I've never tried to economise and I'd think probably everything I've used it for the 168 would have been fine. (I also use the ATTiny85 a lot, but I expect to have to watch code size very carefully there)
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17826
  • Country: fr
Re: Low on program space with pic microcontroller
« Reply #19 on: February 15, 2022, 03:36:15 am »
I've used both PIC16F (purely in assembly) and PIC18F (in C, with mcc18, not xc8) MCUs, and never ever used floating point. Integer/fixed point arithmetic was always perfectly fine, never needed anything else. Most of the time (oh yeah you can find exceptions to this to your heart's content), FP on such small targets (on which you'll implement relatively simple stuff) can be avoided without a problem. Many people using FP are doing that out of laziness, or lack of arithmetic skills.

Never used printf() on those either, even without FP support. printf() is really bloated.
 

Offline Mechatrommer

  • Super Contributor
  • ***
  • Posts: 11727
  • Country: my
  • reassessing directives...
Re: Low on program space with pic microcontroller
« Reply #20 on: February 15, 2022, 04:08:54 am »
Many people using FP are doing that out of laziness, or lack of arithmetic skills.
not everybody. if the program is simple enough, to reduce development and support/debugging cost, FP is perfectly fine to use on small mcu imho. undoubtedly, a fuller bag of skills is necessary to make wiser decision on application suitability. such as in this case, the OP hits limitation, so FP is among the one that needs attention, it will "propagate"...

Offline JPortici

  • Super Contributor
  • ***
  • Posts: 3917
  • Country: it
Re: Low on program space with pic microcontroller
« Reply #21 on: February 15, 2022, 06:51:06 am »
I wouldn't recommend it to you *THIS* *TIME*, but its worth knowing that the XC8 compiler 'PRO' license, which enables optimizations that can significantly reduce the binary size can be purchased on what's basically a monthly rental: https://www.microchip.com/en-us/development-tool/sw006021-sub

but -O2 which is available in free mode does an excellent job already, true there is a diference between -O2 in free and -O2 in pro because some other aggressive optimizations are not available in free mode but...

- looking into abandoning MCC completely is a good starter, you can remove one or two layers of calls and some extra convoluted code like the UART and I2C libraries
- looking into optimizing printf is a good idea too
- looking into abandoning float.. well yes the floating point library takes a lot of space and in this example is not really necessary but i wouldn't take it as a general rule though to be fair i try to avoid 32 bit types as well.. i make an extensive use of the 24 bit types (__int24 and __uint24) since most of the time i deal with 16x8 bit operations

what i would NOT do es resort to nasty inline assembly tricks, in fact they can confuse the optimizer and put you in a vicious loop of oh i can't touch this comma.
Had done it in many places in a project in the past, i couldn't add a single line of code so i decided to rewrite it completely. in the end i got it even faster and saved space over the inline assembly version. In all new projects the only insile assembly instructions i use are TBLRD and TBLWT (only because they don't provide a builtin) otherwise it's all assembly modules included in the code so the optimizer and the linker don't get confused, they are easy to confuse in XC8
 

Online hans

  • Super Contributor
  • ***
  • Posts: 1997
  • Country: 00
Re: Low on program space with pic microcontroller
« Reply #22 on: February 15, 2022, 10:45:50 am »
I think there is one major advantage of using floats: you can often avoid the very costly division operation (which is expensive for both int32 and float), by taking the reciprocal of the divisor number as the constant. This doesn't incur a lot of loss of precision in float, while it can be a complete disaster for int32.

You can very avoid division with fixed point math as well, if you are dividing by a constant value.  The major advantage of floating point is when you really need to deal with very large and very small values, but many applications don't and beginners tend to jump to floats as the first solution to anything that has fractional values.
I agree, but excuse me for being picky, it can still depend what kind of constant divisor you have. A 2^N divisor can be executed with a bit shift. Something else, for example a calibration factor *253/1000, still requires a long-tail division function such as __udivmodsi4(nom, denom) = __udivmodsi4(x*253, 1000).

In this example it's easy to modify the calculation to *259/1024 instead, which only has 0.03% error. That implementation will for sure be a lot faster, and the precision for many applications is good enough (if you know the dynamic range of the variable, you can probably get better accuracy with a larger multiplication and 2^N divisor).

I did some benchmarks in the MPLAB XC8 simulator (O2 free) for a PIC16F1508 executing the optimized int32 arithmetic, naive int32 calculations, and a straight forward floating point calculation.

1414063-0

A naive int32 calculation isn't that much faster than float, as I alluded to before with the PIC18 benchmarks. Only if you optimize to fixed point arithmetic it will be a lot quicker... but otherwise a straight forward float implementation as @Mechatrommer says can save a lot of dev time. (Nonetheless still a good practice to do/learn) Optimizing is still worth it in terms of code size:

Fixed point (div1024): 226 bytes (of which 47 bytes is lmul)
Naive int32 (div1000): 311 bytes (of which 82 bytes is "lldiv")
Float: 1011 bytes (of which 536 bytes is "flmul", and also requires some int-float and float-int conversion functions)

Let alone the sprintf implementation for int32 vs float. But honestly, unless you're doing this professionally, spending 50ct on a larger memory part vs the extra dev time needs some (hundreds) production quantity to make it worth it :)
« Last Edit: February 15, 2022, 10:47:39 am by hans »
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 11331
  • Country: fi
Re: Low on program space with pic microcontroller
« Reply #23 on: February 15, 2022, 10:53:49 am »
Indeed, it's important to understand when you actually need a division.

For just scaling, it's never needed. You can multiply by any arbitrary constant, then bitshift. Note on bitshifting, simple architectures rarely have an instruction to shift by any arbitrary number of bits; for example, ATTiny CPUs can only shift by 1, so shift-by-3 becomes three instructions. But, shifting by multiples of 8 is always very efficient as that only means shuffling the whole bytes around, or just "ignoring" parts of the variables/registers.

In practice, this would mean something like reading a 12-bit voltage measurement into uint32, multiplying it with a constant of 65536 equaling "1.0", then shift by 16 bits. Now you can adjust this large constant to provide finely stepped gain calibration. And if the each ADC step happens to be 1.2345mV, then no problem, just instead of 65536, multiply by 53087, and you get millivolts out directly! No division anywhere.
 

Offline mikerj

  • Super Contributor
  • ***
  • Posts: 3671
  • Country: gb
Re: Low on program space with pic microcontroller
« Reply #24 on: February 15, 2022, 11:02:57 am »
I think there is one major advantage of using floats: you can often avoid the very costly division operation (which is expensive for both int32 and float), by taking the reciprocal of the divisor number as the constant. This doesn't incur a lot of loss of precision in float, while it can be a complete disaster for int32.

You can very avoid division with fixed point math as well, if you are dividing by a constant value.  The major advantage of floating point is when you really need to deal with very large and very small values, but many applications don't and beginners tend to jump to floats as the first solution to anything that has fractional values.
I agree, but excuse me for being picky, it can still depend what kind of constant divisor you have. A 2^N divisor can be executed with a bit shift. Something else, for example a calibration factor *253/1000, still requires a long-tail division function such as __udivmodsi4(nom, denom) = __udivmodsi4(x*253, 1000).

Not necessarily as it depends entirely on how much error is acceptable, for instance  253/1000 (0.253) is very close to 259/1024 (0.25293).  More accuracy can be achieved by using multiple binary factors e.g.  (2^-2 + 2^-9 + 2^-10 + 2^-14) is 0.25299

 
The following users thanked this post: hans


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

- - -
-->