An example:
lcd_driver.h:
#pragma once
extern int variable_that_needs_to_be_so_quick_you_cant_use_a_getter_function;
void lcd_init();
void lcd_print_text(char* text);
lcd_driver.c:
#include "lcd.h"
int variable_that_needs_to_be_so_quick_you_cant_use_a_getter_function; // Actually defined only here
static int internal_variable;
static void internal_detail()
{
...
}
void lcd_init()
{
internal_detail();
}
void lcd_print_text(char* text)
{
....
internal_variable = 123;
}
main.c or whatever:
#include "lcd_driver.h"
void main()
{
lcd_init();
lcd_print_text("hello world");
variable_that_needs_to_be_so_quick_you_cant_use_a_getter_function = 123; // Computer science people won't like this way, but you don't need to care.
}
Compilation, for example:
gcc -c lcd_driver.c -o lcd_driver.o
gcc -c main.c -o main.o
ld -o out.elf lcd_driver.o main.o
(-c means: do not link, only compile this module)
It took me about a decade to learn this. This isn't too well taught anywhere. This should be the first freaking C lesson ever, the 101. This is the way C implements private and public member variables and functions. Works very well and is understandable, you need to know two reserved words (static and extern) and how to use them.
Yes, sometimes you could use a single .h and multiple .c, if the interface (.h) is simple, but the implementation (.c) is complex but somehow logical to divide into several .c files. If you only do this to "reduce number of files", you are doing it wrong. If you have too many files and it confuses you, the chances are, your modules are too small, or the overall design is too complex to do what you want it to do.
Yes, sometimes you include a .c file from another .c file - one typical example would be an autogenerated dataset like a large lookup table, which is only needed by that specific implementation. By having it in a separate file, it's easier to autogenerate again, overwriting the old file; or choose between files by changing the include line only; or just simply to keep it out of sight. It could be named .whatever, but .c is a logical extension if it's valid C code, such as:
sin_lut.c:
// Autogenerated, do not modify
static const int8_t sin_lut[256] =
{0, 3, 6, 9, 12, ... };
Note that while this could be .h (it's just a file), it would be misleading since this actually defines variables. And, while you could compile it as a separate module and link together at the end, just including the .c to another .c may look simpler; the dependency is so clear and limited. If you did it through the linker, then you'd need to to declare the variable somewhere, preferably in another .h file, which would look stupid for such a trivially small task of including one constant table which would obviously be in the single .c file directly if it was smaller and human-generated.