7

I'm still learning C to be used in microprocessors. In the beginning I used lots of globals. Now I'm trying to avoid it as much as a can, but for me it's not always clear to see how to do this.

For example a battery monitor, in this case there are 4 functions that need to read or modify a variable. I have these functions all using the variable LowVoltage.

void Check_Voltage(){
  checks current voltage against LowVoltage
}

void Menu_Voltage(){
  a menu on the LCD screen to set the value of LowVoltage
}

void Save_LowVoltage(){
 runs after the settings menu is finished to save LowVoltage to EEPROM
}

void Load_LowVoltage(){
 reads EEPROM and sets LowVoltage at startup 
}
  • Check_Voltage() and Save_LowVoltage() need to read LowVoltage.
  • Load_LowVoltage() need to write LowVoltage.
  • Menu_Voltage() needs to read and write LowVoltage.

How can I make this work without making LowVoltage global?? Would I need to make another function to read or write LowVoltage? Something like this:

unsigned int Low_Voltage(short Get, unsigned int Value){
  static unsigned int LowVoltage;

  if(Get) return LowVoltage;
  else LowVoltage= Value;
}

Or are there better ways to do this? I guess there must be :) I've been reading about structures lately, but to be honest I don't fully understand them and I'm not even sure it would help me in cases like this?

Benno
  • 149
  • 2
  • 10
  • are these functions used simultaneously? – moffeltje Mar 30 '15 at 14:22
  • Don't confuse "global variables", which are file scope variables exposed to the whole program, with private file scope variables that are only visible to the file you put them in. The former is very bad practice and should never be used, the latter is most of the time good programming practice. – Lundin Mar 30 '15 at 14:56
  • @moffeltje, No, they are never used simultaneously – Benno Mar 30 '15 at 19:14

3 Answers3

10

There are several choices to sharing a variable among functions:

  • Allocate your variable in static memory - this is pretty much what your code does. Your two choices there are function-static, translation unit-static, and global
  • Pass a pointer to variable as function parameter - This choice requires passing the pointer around in some form
  • Use thread-local storage with clever initialization - This choice is not usually available when you work with microcontrollers; I list it here for completeness.

In your case, I think that using a translation unit-static variable would be appropriate. Put implementations of the four functions into a single C file, and declare LowVoltage at the top as a static variable:

static unsigned int LowVoltage;

This simple but efficient encapsulation mechanism gives you all benefits of having a global variable, without the drawbacks of having a global variable:

  • All functions inside the C module "see" this variable, and can freely manipulate it
  • No other functions outside the C module can access this variable. They can declare their own LowVoltage variable, giving it an entirely different meaning.
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 2
    Making it a static file scope variable and putting all those functions in a battery.h/battery.c code module seems to be exactly what the OP needs to do. – Lundin Mar 30 '15 at 15:01
  • Thanks dasblinkenlight! Well, this is indeed what I thought, but when I had the same static variable names in 2 differant C modules they were somehow in the same scope. Since it was only recently I learned about the static variable and it's scope I thought I had misunderstood it. But now I'm beginning to see why I often see complaints about MikroC... I have included the .c file with #include Maybe if I include the .c file with the project manager it works as it should... – Benno Mar 30 '15 at 19:17
0

Two solutions I can think of

  1. Make the function signatures like this

    unsigned int Load_LowVoltage(unsigned int lowVoltage);
    

    and then pass LowVoltage and assign to it the return value, like this

    LowVoltage = Load_LowVoltage(LowVoltage);
    
  2. Modify LowVoltage inside the function and pass a pointer to your original LowVoltage like this

    void LowVoltage(unsigned int *lowVoltage)
     {
        *lowVoltage = modifiedValue;
     }
    

    then you can use it like this

    Load_LowVoltage(&LowVoltage);
    

I think the second solution is cleaner and since you are in un environment where resources are limited, it's also better in that sense. But they both are easy to implement and work as good.

Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
  • 2
    This doesn't give private encapsulation though. It is pretty obvious that he is coding some sort of "battery" driver, and the caller shouldn't concern themselves with internals of that driver, which this LowVoltage variable seems to be. – Lundin Mar 30 '15 at 14:52
0

You could create a struct holding all battery parameters, for example:

typedef struct {
  int low_voltage; 
  int voltage;
  int capacity;
  int temperature;
  ...
} BatteryData

At the start of your program you allocate memory for it and initialise members to some starting values:

BatteryData *battery = malloc(sizeof(BatteryData));
battery->low_voltage = 0;
...

Then you pass pointer to the whole struct to functions that set or read individual values, for example:

void Load_LowVoltage(BatteryData *battery){
 //reads EEPROM and sets LowVoltage at startup 
 int eeprom_val = get_low_voltage_from_eeprom();
 battery->low_voltage = eeprom_val;
}

Free the structure when not needed:

free(battery);
baf
  • 4,531
  • 1
  • 21
  • 24
  • 1
    I think this is a bad idea. Using dynamic allocation is best avoided on embedded systems. If you must use it, though, then just allocate memory for it once, and do not free it. Freeing and recreating an allocation many times will lead to heap corruption very quickly depending on the circumstances. – pfabri Feb 15 '17 at 21:53