Completed: / exercises

Libraries

In this course, we use the term library broadly for reusable C code that is kept separate from the main program. Typically, libraries contain reusable functions, such as mathematical functions that can be used in several situations. The implementation may be provided as source code, for example pasta.c, or as already compiled object code, for example pasta.o, that is linked with the program. Several object files can also be packaged into a static library, typically a .a file on Unix-like systems, which the linker can use when building the final program.

Library Introduction

A C library typically provides one or more header files that describe its public interface. Header files contain the declarations, types, constants, and macros needed by programs that use the library. The actual implementation is normally provided separately in one or more source files or as already compiled code. Compiled code can also be packaged into a static library, typically a .a file in Unix-like systems, which contains one or more compiled object files (.o) that can be linked into the program. Adding a header file from the compiler's perspective is equivalent to copying the code directly where the #include command is placed in the code file.
A header file pasta.h might look like this:
#pragma once // Prevents this header from being included more than once
#include <stdio.h>
#include <stdint.h>

// Function prototypes
float add_water(void);
uint8_t add_salt(void);
uint8_t is_water_boiling(void);
void stir_pot(void);
void set_cooking_time(void);
uint8_t is_ready(void);

// New enum type for constants, exciting!!
enum pasta_types { SPAGHETTI=1,  MACARONI, FUSILLI, PENNE_RIGATE };

Library Implementation

Library source code is created in its own code module, i.e., file (here pasta.c), which contains the implementations of the shared and internal functions of the libraries.
#include "pasta.h" // Forces the library functions to follow the prototypes

// Internal library variables
float water_amount = 0;
uint8_t cooking_time = 0;
uint8_t portion_size = 0; 
enum pasta_types selected_pasta = SPAGHETTI;

// Functions
float add_water(void) {

   water_amount = portion_size * 0.2;
   return water_amount; 
}

void set_cooking_time(void) {

   switch(selected_pasta) {
      case SPAGHETTI:
         cooking_time = 10;
         break;
      case MACARONI:
         cooking_time = 8;
         break;
      ...
   }
}
When calling a function in C, we use only the function name; we do not write the name of the library that provides it. Externally visible function names therefore share the same namespace when the program is linked. If two linked modules define the same external function name, the linker will normally report a multiple-definition error.
For this reason, libraries often use a prefix in their public function names, for example pasta_add_water() or sensor_read().

Variables in the Header File

Declaring variables and/or using global variables directly through header files is somewhat questionable. Actually, it might lead to errors.
In this course we are recommending two options: using setter and getter functions or declaring the variable using the externkeyword.

Getter and setter

So far, the variables used internally by the library have been defined in pasta.c. If another source file needs to read or modify one of these variables, one option is to provide getter and setter functions as part of the library interface.
The variable itself remains defined in pasta.c and can be declared static. At file scope, static gives the variable internal linkage, meaning that other source files cannot access it directly (it is hidden to other files). Instead, they access or modify its value through the functions provided by the library.
This way, the variable is encapsulated inside the library implementation. The library can control how the value is accessed or modified, while other modules only need to know the public functions declared in the header file.
For example, the pasta.h file can declare the getter and setter functions:
#pragma once 
#include <stdint.h>

// Function prototypes
float add_water(void);
uint8_t add_salt(void);
uint8_t is_water_boiling(void);
void stir_pot(void);
void set_cooking_time(void);
uint8_t is_ready(void);

// New enum type for constants, exciting!!
enum pasta_types { SPAGHETTI=1,  MACARONI, FUSILLI, PENNE_RIGATE };

//New setters and getters functions. 
uint8_t get_portion_size(void);
void set_portion_size(uint8_t size);
The implementation of the library
// Internal library variables
static float water_amount = 0;
static uint8_t cooking_time = 0;
static uint8_t portion_size = 0; // We make sure that other files cannot modify this variable. 
static enum pasta_types selected_pasta = SPAGHETTI;

// Functions
void set_portion_size(uint8_t portions) {
   portion_size = portions;
}

uint8_t get_portion_size(void) {
   return portion_size;
}

float add_water(void) {

   water_amount = portion_size * 0.2;
   return water_amount;
}

void set_cooking_time(void) {

   switch(selected_pasta) {
      case SPAGHETTI:
         cooking_time = 10;
         break;
      case MACARONI:
         cooking_time = 8;
         break;
      ...
   }
}
Now other files can use the library:
// main.c
#include <stdio.h>
#include "pasta.h"

int main(void) {
   // Set and get the global variable
   set_portion_size(5);
   uint8_t portions = get_portion_size();
   printf("Portion size is: %d\n", portions);

   return 0;
}

Using extern keyword

Concerning global variables in header files, it is worth mentioning the extern keyword. This keyword is used to declare a variable in a header file without defining it (that is, do not allocate space in memory). The extern keyword tells the compiler that the variable or function is defined elsewhere, typically in a .c file, and allows other files to access it without redefining it.
Using extern helps to share global variables across multiple source files while ensuring that the variable is defined only once, preventing linker errors that result from multiple definitions. Usually, the extern variable is defined in the library implementation.
The pasta.h file looks like:
//pasta.h
#pragma once 
#include <stdint.h>

// Function prototypes
float add_water(void);
uint8_t add_salt(void);
uint8_t is_water_boiling(void);
void stir_pot(void);
void set_cooking_time(void);
uint8_t is_ready(void);

// New enum type for constants, exciting!!
enum pasta_types { SPAGHETTI=1,  MACARONI, FUSILLI, PENNE_RIGATE };

extern uint8_t portion_size;//We indicate that variable would be later defined. 
Then the pasta.c looks lik:
// pasta.c
#include "pasta.h"

// Shared global variable
uint8_t portion_size = 0;

// Internal library variables
static float water_amount = 0;
static uint8_t cooking_time = 0;
static enum pasta_types selected_pasta = SPAGHETTI;

float add_water(void) {

   water_amount = portion_size * 0.2;
   return water_amount; 
}

void set_cooking_time(void) {

   switch(selected_pasta) {
      case SPAGHETTI:
         cooking_time = 10;
         break;
      case MACARONI:
         cooking_time = 8;
         break;
      ...
   }
}
Now other files can use the library:
// main.c
#include <stdio.h>
#include "pasta.h"

int main(void) {
   //Now I can use the variable, since it is extern in the library, and it is defined in the pasta.c
   portion_size = 5;
   uint8_t portions = portion_size;
   printf("Portion size is: %d\n", portions);

   return 0;
}

Problems defining variables in .h

What would happen if I intuitively defined the global variable directly in the header file?
Let's see:
// pasta.h
#pragma once

#include <stdio.h>
#include <stdint.h>

// Define the global variable directly 
uint8_t portion_size = 0;

// Declare function prototypes
void set_portion_size(uint8_t portions);
uint8_t get_portion_size(void);
And then i include the library in both files
// pasta.c
#include "pasta.h"
// main.c
#include "pasta.h"
each file will define its own version of portion_size. The linker will then throw an error of multiple definition of!!!!

Constants in header files

If a constant is declared in the header file, it can be freely used across multiple code modules. As discussed in the preprocessor section, M_PI is not required by the ISO C standard, although many implementations provide it through math.h. Such implementation-specific constants can still be exposed through header files and used from multiple source files.
For example, Microsoft implementations may require _USE_MATH_DEFINES to be defined before including math.h in order to make M_PI available.
// Inside math.h
#define M_PI 3.14159265358979323846

C Standard Library

The C standard library contains, first of all, a large number of definitions that support the implementation of (ANSI) standard C across different hardware platforms and operating systems.
The implementation of parts of the standard library typically comes precompiled as binaries (especially in commercial compiler environments), often optimized by the manufacturer. Therefore, it is always a good idea to use ready-made functions whenever possible! Reinventing the wheel, especially in the workplace, won’t earn you bonus points from your boss.
You can find an overview of C's standard libraries on Wikipedia C Standard Library or in textbooks.
Some useful libraries:
In embedded systems, the available implementation of the C standard library may be reduced because of program-memory, hardware, or execution-resource limitations. Some standard-library functions may not be provided at all, especially in freestanding environments. Other functions may be available with reduced functionality.
For example, the stdio.h header may declare printf, while the corresponding library implementation provides a version without floating-point formatting support in order to reduce program size.

General Information About Libraries

As the program grows, it’s a good idea to organize functions into libraries, which will simplify the main program considerably, making it easier to manage and maintain.
Dividing into libraries also facilitates testing and debugging (debugging) in larger programs. Each library can be tested as a module before being integrated into the main program. During runtime error checking, libraries can be inspected separately using a debugger.
Programs can be divided into libraries in several different ways, and the best way often depends on who you ask. Here is one approach:

Conclusion

In this course, we will use a lot of the standard library, as well as the ready-made libraries provided by our device and our RTOS SDK for programming the device.
Hobbyists also create libraries for different functionalities and embedded components, so Google is your friend here. However, caution is advised because you don’t know what assumptions or other conditions the code in the library may have. Therefore, it’s a good idea to review external code yourself before using it in your program. Additionally, comments in the code may provide valuable information about using the library.
?