Ah yes, makefiles. Haven't written one in decades.
Do they still have the "feature" that tabs have a different meaning to spaces?
AFAIK, yes!
One approach for larger files is to put large modules in subdirectories, suitably named. It is then possible for the top level makefile to recurse through the subdirectories invoking smaller makefiles in each subdirectory. This seems like the long way around but the makefiles in the subdirectories are usually just a copy and paste.
Check out the structure of the code at
http://jcwren.com/arm/ The makefiles are worth a glance.
So, I put my UART code in a file and I need to tell the world which functions are available for use. I create a .h file to form a contract between the caller and the callee. I don't put function prototypes in the .h file unless I really want them to be callable. In the .c file, I can declare internal functions as 'static'. This also prevents name conflict with identically named functions in other .c files. If I need a prototype within a .c file just due to forward referencing, I'll put it in the .c file but not in the .h file.
I would put the initialization code, the interrupt handler and the UART get/put functions in the file but I probably wouldn't publish prototypes for the interrupt code. The user doesn't need to know. He also doesn't need to know that I use a circular queue. All he needs to know is what I tell him in the .h file and that would be the bare minimum.
As to 'tiny' CPUs and global variables, well, if they're really tiny we are probably coding in assembly language and everything will be visible.
I'm firmly in favor of hiding as much of the implementation as possible. If the user can't get at a function or variable, they can't misuse it.
Of course there are exceptions, there aren't any 'laws', just guidelines.
Global variables are required from time to time but they shouldn't be used just to avoid having to come up with a better program structure.
Then we get to the idea of dependencies. You change a .c file and then change the .h to match. How many files in the project need to be recompiled? The IDE that builds a makefile knows very well how to check dependencies and is perfectly capable of compiling just the files that include the .h file. User makefiles can also build dependencies and at least one of mine creates the dependencies and appends it to the top level makefile.
I have attached a makefile. Note that the target 'all' depends on 'depend' which creates the list of dependencies at the bottom of the file. This gets executed on every build and this is probably not the best way to do it. But it works!
This makefile is used from a Linux command line, my preferred way to build projects. I use gedit as the editor (because it is WYSIWYG) and make.
Unfortunately, makefiles can't be attached without adding a file extension.